[
  {
    "path": ".github/CODEOWNERS",
    "content": "# Default: everything\n* @bckohan\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n  # GitHub Actions\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n\n    groups:\n      gha-updates:\n        patterns:\n          - \"*\"\n"
  },
  {
    "path": ".github/workflows/bandit.yml",
    "content": "name: Bandit\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    paths:\n      - \"src/**\"\n      - \"pyproject.toml\"\n      - \"justfile\"\n      - \".github/workflows/bandit.yml\"\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\njobs:\n  bandit-analysis:\n    name: Run Bandit\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      security-events: write\n\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          persist-credentials: false\n      - name: Set up Python\n        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405\n        id: sp\n        with:\n          python-version: '3.x'\n          allow-prereleases: true\n      - name: Install uv\n        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b\n        with:\n          enable-cache: false\n      - name: Install Just\n        uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3\n\n      - name: Run Bandit\n        run: |\n          just bandit\n\n      - name: Upload analysis results\n        if: ${{ always() }}\n        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a\n        with:\n          name: bandit-results\n          path: bandit.sarif\n          retention-days: 7\n\n      - name: Upload to code-scanning\n        if: ${{ always() }}\n        uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7\n        with:\n          sarif_file: bandit.sarif\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\n\npermissions:\n  contents: read\n\non:\n  push:\n    tags-ignore:\n      - '*'\n    branches:\n      - '*'\n  pull_request:\n  workflow_call:\n  workflow_dispatch:\n    inputs:\n      debug:\n        description: 'Open ssh debug session.'\n        required: true\n        default: false\n        type: boolean\n\njobs:\n\n  lint:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n    strategy:\n      fail-fast: false\n      matrix:\n        # run static analysis on bleeding and trailing edges\n        python-version: ['3.10', '3.12', '3.14']\n        django-version:\n          - 'dj42'    # LTS April 2026\n          - 'dj52'    # LTS April 2028\n          - 'dj60'    #\n        exclude:\n          - python-version: '3.12'\n            django-version: 'dj42'\n          - python-version: '3.14'\n            django-version: 'dj42'\n\n          - python-version: '3.10'\n            django-version: 'dj52'\n          - python-version: '3.14'\n            django-version: 'dj52'\n\n          - python-version: '3.10'\n            django-version: 'dj60'\n          - python-version: '3.12'\n            django-version: 'dj60'\n\n    env:\n      TEST_PYTHON_VERSION: ${{ matrix.python-version }}\n      TEST_DJANGO_VERSION: ${{ matrix.django-version }}\n\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          persist-credentials: false\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405\n        id: sp\n        with:\n          python-version: ${{ matrix.python-version }}\n          allow-prereleases: true\n      - name: Install uv\n        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b\n        with:\n          enable-cache: false\n      - name: Install Just\n        uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3\n      - name: Install Emacs\n        if: ${{ github.event.inputs.debug == 'true' }}\n        run: |\n          sudo apt install emacs\n      - name: Setup tmate session\n        if: ${{ github.event.inputs.debug == 'true' }}\n        uses: mxschmitt/action-tmate@c0afd6f790e3a5564914980036ebf83216678101\n        timeout-minutes: 60\n      - name: Run Static Analysis\n        env:\n          PYTHON_PATH: ${{ steps.sp.outputs.python-path }}\n        run: |\n          just check -p \"$PYTHON_PATH\" --group \"$TEST_DJANGO_VERSION\"\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Publish Release\n\npermissions: read-all\n\nconcurrency:\n  # stop previous release runs if tag is recreated\n  group: release-${{ github.ref }}\n  cancel-in-progress: true\n\non:\n  push:\n    tags:\n      - \"v[0-9]*.[0-9]*.[0-9]*\" # only publish on version tags (e.g. v1.0.0)\n\njobs:\n  lint:\n    permissions:\n      contents: read\n      actions: write\n    uses: ./.github/workflows/lint.yml\n\n  test:\n    permissions:\n      contents: read\n      actions: write\n      id-token: write\n    uses: ./.github/workflows/test.yml\n\n  build:\n    name: Build Package\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      actions: write\n    outputs:\n      PACKAGE_NAME: ${{ steps.set-package.outputs.package_name }}\n      RELEASE_VERSION: ${{ steps.set-package.outputs.release_version }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          persist-credentials: false\n      - name: Set up Python\n        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405\n        with:\n          python-version: \"3.14\" # for tomlib\n      - name: Install uv\n        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b\n        with:\n          enable-cache: false\n          restore-cache: false\n          save-cache: false\n      - name: Setup Just\n        uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3\n      - name: Verify Tag\n        run: |\n          TAG_NAME=${GITHUB_REF#refs/tags/}\n          echo \"Verifying tag $TAG_NAME...\"\n          # if a tag was deleted and recreated we may have the old one cached\n          # be sure that we're publishing the current tag!\n          git fetch --force origin refs/tags/$TAG_NAME:refs/tags/$TAG_NAME\n\n          # verify signature\n          curl -sL \"https://github.com/${GITHUB_ACTOR}.gpg\" | gpg --import\n          git tag -v \"$TAG_NAME\"\n\n          # verify version\n          RELEASE_VERSION=$(just validate_version $TAG_NAME)\n\n          # export the release version\n          echo \"RELEASE_VERSION=${RELEASE_VERSION}\" >> $GITHUB_ENV\n      - name: Install gettext\n        run: sudo apt-get install -y gettext\n      - name: Build the binary wheel and a source tarball\n        run: just build\n      - name: Store the distribution packages\n        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a\n        with:\n          name: python-package-distributions\n          path: dist/\n      - name: Set Package Name\n        id: set-package\n        run:\n          PACKAGE_NAME=$(python -c \"import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['name'])\")\n          echo \"PACKAGE_NAME=${PACKAGE_NAME}\" >> $GITHUB_ENV\n\n  publish-to-pypi:\n    name: Publish to PyPI\n    needs:\n      - lint\n      - test\n      - build\n    runs-on: ubuntu-latest\n    environment:\n      name: pypi\n      url: https://pypi.org/p/${{ needs.build.outputs.PACKAGE_NAME }}\n    permissions:\n      id-token: write # IMPORTANT: mandatory for trusted publishing\n    steps:\n      - name: Download all the dists\n        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c\n        with:\n          name: python-package-distributions\n          path: dist/\n      - name: Publish distribution 📦 to PyPI\n        uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b\n\n  github-release:\n    name: Publish GitHub Release\n    runs-on: ubuntu-latest\n    needs:\n      - lint\n      - test\n      - build\n    permissions:\n      contents: write # IMPORTANT: mandatory for making GitHub Releases\n      id-token: write # IMPORTANT: mandatory for sigstore\n\n    steps:\n      - name: Download all the dists\n        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c\n        with:\n          name: python-package-distributions\n          path: dist/\n      - name: Sign the dists with Sigstore\n        uses: sigstore/gh-action-sigstore-python@04cffa1d795717b140764e8b640de88853c92acc\n        with:\n          inputs: >-\n            ./dist/*.tar.gz\n            ./dist/*.whl\n      - name: Create GitHub Release\n        env:\n          GITHUB_TOKEN: ${{ github.token }}\n          GITHUB_REF_NAME: ${{ github.ref_name }}\n          GITHUB_REPOSITORY: ${{ github.repository }}\n        run: >-\n          gh release create\n          \"$GITHUB_REF_NAME\"\n          --repo \"$GITHUB_REPOSITORY\"\n          --generate-notes\n      - name: Upload artifact signatures to GitHub Release\n        env:\n          GITHUB_TOKEN: ${{ github.token }}\n          GITHUB_REF_NAME: ${{ github.ref_name }}\n          GITHUB_REPOSITORY: ${{ github.repository }}\n        # Upload to GitHub Release using the `gh` CLI.\n        # `dist/` contains the built packages, and the\n        # sigstore-produced signatures and certificates.\n        run: >-\n          gh release upload\n          \"$GITHUB_REF_NAME\" dist/**\n          --repo \"$GITHUB_REPOSITORY\"\n\n  # chrisglass owns this: https://test.pypi.org/project/django-polymorphic/\n  # publish-to-testpypi:  \n  #   name: Publish to TestPyPI\n  #   needs:\n  #     - build\n  #   runs-on: ubuntu-latest\n\n  #   environment:\n  #     name: testpypi\n  #     url: https://test.pypi.org/project/${{ needs.build.outputs.PACKAGE_NAME }}\n\n  #   permissions:\n  #     id-token: write # IMPORTANT: mandatory for trusted publishing\n\n  #   steps:\n  #     - name: Download all the dists\n  #       uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c\n  #       with:\n  #         name: python-package-distributions\n  #         path: dist/\n  #     - name: Publish distribution 📦 to TestPyPI\n  #       uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b\n  #       with:\n  #         repository-url: https://test.pypi.org/legacy/\n  #         skip-existing: true\n"
  },
  {
    "path": ".github/workflows/scorecard.yml",
    "content": "name: OpenSSF Scorecard\non:\n  # For Branch-Protection check. Only the default branch is supported. See\n  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection\n  branch_protection_rule:\n  # To guarantee Maintained check is occasionally updated. See\n  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained\n  push:\n    branches: [ main ]\n  workflow_dispatch:\n\npermissions: read-all\n\njobs:\n  analysis:\n    name: Scorecard analysis\n    runs-on: ubuntu-latest\n    environment:\n      name: scorecard\n      deployment: false  # Prevents creating a GitHub deployment object\n    permissions:\n      security-events: write\n      id-token: write\n\n    steps:\n      - name: \"Checkout code\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          persist-credentials: false\n\n      - name: \"Run analysis\"\n        uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a\n        with:\n          results_file: results.sarif\n          results_format: sarif\n          # (Optional) \"write\" PAT token. Uncomment the `repo_token` line below if:\n          # - you want to enable the Branch-Protection check on a *public* repository, or\n          # - you are installing Scorecard on a *private* repository\n          # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.\n          repo_token: ${{ secrets.SCORECARD_TOKEN }}\n\n          # Public repositories:\n          #   - Publish results to OpenSSF REST API for easy access by consumers\n          #   - Allows the repository to include the Scorecard badge.\n          #   - See https://github.com/ossf/scorecard-action#publishing-results.\n          # For private repositories:\n          #   - `publish_results` will always be set to `false`, regardless\n          #     of the value entered here.\n          publish_results: true\n\n      # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF\n      # format to the repository Actions tab.\n      - name: \"Upload artifact\"\n        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a\n        with:\n          name: SARIF file\n          path: results.sarif\n          retention-days: 5\n\n      # Upload the results to GitHub's code scanning dashboard (optional).\n      # Commenting out will disable upload of results to your repo's Code Scanning dashboard\n      - name: \"Upload to code-scanning\"\n        uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7\n        with:\n          sarif_file: results.sarif\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\n\npermissions:\n  contents: read\n\non:\n  push:\n    tags-ignore:\n      - '*'\n    branches: ['main']\n  pull_request:\n    branches: ['main']\n    paths:\n      - \"src/**\"\n      - \".github/workflows/test.yml\"\n      - \"pyproject.toml\"\n      - \"justfile\"\n  merge_group:\n  workflow_call:\n  workflow_dispatch:\n    inputs:\n      debug:\n        description: 'Open ssh debug session.'\n        required: true\n        default: false\n        type: boolean\n      clear_cache:\n        description: 'Clear GitHub Tool cache.'\n        required: true\n        default: false\n        type: boolean\n  schedule:\n    # Every Sunday at 9:00 AM Los Angeles time.\n    # GitHub cron is UTC; 09:00 PT == 17:00 UTC (PST).\n    - cron: '0 17 * * 0'\n\n\njobs:\n\n  postgres:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      actions: write\n    # Service containers to run with `container-job`\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']\n        postgres-version: ['12', '14', 'latest']\n        psycopg-version: ['psycopg2', 'psycopg3']\n        django-version:\n          - 'dj42' # LTS April 2026\n          - 'dj52' # LTS April 2028\n          - 'dj60' #\n        exclude:\n\n          - python-version: '3.10'\n            django-version: 'dj60'\n          - python-version: '3.11'\n            django-version: 'dj60'\n\n          - python-version: '3.13'\n            django-version: 'dj42'\n\n          - python-version: '3.14'\n            django-version: 'dj42'\n          - python-version: '3.14'\n            django-version: 'dj52'\n\n          - postgres-version: '12'\n            django-version: 'dj52'\n          - postgres-version: '12'\n            django-version: 'dj60'\n\n          - postgres-version: '14'\n            django-version: 'dj42'\n          - postgres-version: '14'\n            django-version: 'dj52'\n          - postgres-version: '14'\n            django-version: 'dj60'\n\n          - postgres-version: 'latest'\n            django-version: 'dj42'\n\n          - postgres-version: '12'\n            psycopg-version: 'psycopg3'\n          - postgres-version: 'latest'\n            psycopg-version: 'psycopg2'\n\n          # https://github.com/psycopg/psycopg2/pull/1695\n          - python-version: '3.13'\n            psycopg-version: 'psycopg2'\n          - python-version: '3.14'\n            psycopg-version: 'psycopg2'\n\n    env:\n      RDBMS: postgres\n      POSTGRES_PASSWORD: postgres\n      PGPASSWORD: postgres\n      POSTGRES_USER: postgres\n      POSTGRES_HOST: localhost\n      POSTGRES_PORT: 5432\n      COVERAGE_FILE: linux-py${{ matrix.python-version }}-${{ matrix.django-version }}-${{ matrix.psycopg-version }}-pg${{ matrix.postgres-version }}.coverage\n      TEST_PYTHON_VERSION: ${{ matrix.python-version }}\n      TEST_DJANGO_VERSION: ${{ matrix.django-version }}\n      TEST_DATABASE_CLIENT_VERSION: ${{ matrix.psycopg-version }}\n      TEST_DATABASE_VERSION: ${{ matrix.postgres-version }}\n\n    # Service containers to run with `runner-job`\n    services:\n      # Label used to access the service container\n      postgres:\n        # Docker Hub image\n        image: postgres:${{ matrix.postgres-version }}\n        # Provide the password for postgres\n        env:\n          POSTGRES_PASSWORD: postgres\n        # Set health checks to wait until postgres has started\n        options: >-\n          --health-cmd pg_isready\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n        ports:\n          # Maps tcp port 5432 on service container to the host\n          - 5432:5432\n\n    steps:\n    - name: Clear Tool cache\n      if: ${{ github.event.inputs.clear_cache == 'true' }}\n      run: sudo rm -rf /opt/hostedtoolcache\n    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      with:\n        persist-credentials: false\n    - name: Set up Python ${{ matrix.python-version }}\n      id: sp\n      uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405\n      with:\n        python-version: ${{ matrix.python-version }}\n        allow-prereleases: true\n    - name: Create test databases\n      run: |\n        psql -h localhost -p 5432 -U postgres -d postgres -c \"CREATE DATABASE test1;\"\n        psql -h localhost -p 5432 -U postgres -d postgres -c \"CREATE DATABASE test2;\"\n    - name: Install uv\n      uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b\n      with:\n        enable-cache: false\n    - name: Setup Just\n      uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3\n    - name: Install Emacs\n      if: ${{ github.event.inputs.debug == 'true' }}\n      run: |\n        sudo apt install emacs\n    - name: Setup tmate session\n      if: ${{ github.event.inputs.debug == 'true' }}\n      uses: mxschmitt/action-tmate@c0afd6f790e3a5564914980036ebf83216678101\n      timeout-minutes: 60\n    - name: Run Unit Tests\n      env:\n        TEST_PYTHON: ${{ steps.sp.outputs.python-path }}\n      run: |\n        just test-all --group \"${{ matrix.psycopg-version }}\" -p \"$TEST_PYTHON\" --group \"$TEST_DJANGO_VERSION\"\n    - name: Store coverage files\n      uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a\n      with:\n        name: ${{ env.COVERAGE_FILE }}\n        path: ${{ env.COVERAGE_FILE }}\n\n\n  sqlite:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      actions: write\n    env:\n      RDBMS: sqlite\n      COVERAGE_FILE: linux-py${{ matrix.python-version }}-${{ matrix.django-version }}-sqlite.coverage\n      TEST_PYTHON_VERSION: ${{ matrix.python-version }}\n      TEST_DJANGO_VERSION: ${{ matrix.django-version }}\n      TEST_DATABASE_VERSION: \"sqlite\"\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: ['3.10', '3.12', '3.14']\n        django-version:\n          - 'dj42' # LTS April 2026\n          - 'dj52' # LTS April 2028\n          - 'dj60' #\n        exclude:\n          - python-version: '3.10'\n            django-version: 'dj52'\n          - python-version: '3.10'\n            django-version: 'dj60'\n\n          - python-version: '3.12'\n            django-version: 'dj42'\n          - python-version: '3.12'\n            django-version: 'dj60'\n\n          - python-version: '3.14'\n            django-version: 'dj42'\n          - python-version: '3.14'\n            django-version: 'dj52'\n\n    steps:\n      - name: Clear Tool cache\n        if: ${{ github.event.inputs.clear_cache == 'true' }}\n        run: sudo rm -rf /opt/hostedtoolcache\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          persist-credentials: false\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405\n        id: sp\n        with:\n          python-version: ${{ matrix.python-version }}\n          allow-prereleases: true\n      - name: Install uv\n        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b\n        with:\n          enable-cache: false\n      - name: Setup Just\n        uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3\n      - name: Install Emacs\n        if: ${{ github.event.inputs.debug == 'true' }}\n        run: |\n          sudo apt install emacs\n      - name: Setup tmate session\n        if: ${{ github.event.inputs.debug == 'true' }}\n        uses: mxschmitt/action-tmate@c0afd6f790e3a5564914980036ebf83216678101\n        timeout-minutes: 60\n      - name: Run Unit Tests\n        env:\n          TEST_PYTHON: ${{ steps.sp.outputs.python-path }}\n        run: |\n          just test-all -p \"$TEST_PYTHON\" --group \"$TEST_DJANGO_VERSION\"\n      - name: Store coverage files\n        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a\n        with:\n          name: ${{ env.COVERAGE_FILE }}\n          path: ${{ env.COVERAGE_FILE }}\n\n\n  mysql:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      actions: write\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: ['3.10', '3.12', '3.14']\n        mysql-version: ['8.0', 'latest']\n        mysqlclient-version: ['mysqlclient14', 'mysqlclient2x']\n        django-version:\n          - 'dj42'    # LTS April 2024\n          - 'dj52'    # LTS April 2028\n          - 'dj60'    #\n        exclude:\n          - django-version: 'dj42'\n            mysql-version: 'latest'\n\n          - django-version: 'dj60'\n            mysql-version: '8.0'\n\n          - mysql-version: 'latest'\n            mysqlclient-version: 'mysqlclient14'\n\n          - python-version: '3.12'\n            django-version: 'dj42'\n          - python-version: '3.14'\n            django-version: 'dj42'\n\n          - python-version: '3.10'\n            django-version: 'dj52'\n          - python-version: '3.14'\n            django-version: 'dj52'\n          - django-version: 'dj52'\n            mysqlclient-version: 'mysqlclient14'\n\n          - python-version: '3.10'\n            django-version: 'dj60'\n          - python-version: '3.12'\n            django-version: 'dj60'\n\n    env:\n      RDBMS: mysql\n      MYSQL_VERSION: ${{ matrix.mysql-version }}\n      COVERAGE_FILE: linux-py${{ matrix.python-version }}-${{ matrix.django-version }}-${{ matrix.mysqlclient-version }}-mysql${{ matrix.mysql-version }}.coverage\n      TEST_PYTHON_VERSION: ${{ matrix.python-version }}\n      TEST_DJANGO_VERSION: ${{ matrix.django-version }}\n      TEST_DATABASE_CLIENT_VERSION: ${{ matrix.mysqlclient-version }}\n      TEST_DATABASE_VERSION: ${{ matrix.mysql-version }}\n\n    services:\n      mysql:\n        # Docker Hub image\n        image: mysql:${{ matrix.mysql-version }}\n        # Provide the password for mysql\n        env:\n          MYSQL_ROOT_PASSWORD: root\n          MYSQL_MULTIPLE_DATABASES: test1,test2\n        # Set health checks to wait until mysql has started\n        options: >-\n          --health-cmd \"mysqladmin ping\"\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n        ports:\n          # Maps tcp port 3306 on service container to the host\n          - 3306:3306\n\n    steps:\n      # make text comparisons case sensitive (some tests)\n      - name: Set default collation for MySQL\n        run: |\n          mysql -h 127.0.0.1 -u root -proot <<EOF\n          SET GLOBAL character_set_server='utf8mb4';\n          SET GLOBAL collation_server='utf8mb4_0900_as_cs';\n          EOF\n      - name: Clear Tool cache\n        if: ${{ github.event.inputs.clear_cache == 'true' }}\n        run: sudo rm -rf /opt/hostedtoolcache\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          persist-credentials: false\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405\n        id: sp\n        with:\n          python-version: ${{ matrix.python-version }}\n          allow-prereleases: true\n      - name: Install Emacs\n        if: ${{ github.event.inputs.debug == 'true' }}\n        run: |\n          sudo apt install emacs\n      - name: Setup tmate session\n        if: ${{ github.event.inputs.debug == 'true' }}\n        uses: mxschmitt/action-tmate@c0afd6f790e3a5564914980036ebf83216678101\n        timeout-minutes: 60\n      - name: Install uv\n        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b\n        with:\n          enable-cache: false\n      - name: Setup Just\n        uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3\n      - name: Run Unit Tests\n        env:\n          TEST_PYTHON: ${{ steps.sp.outputs.python-path }}\n        run: |\n          just test-all --group \"${{ matrix.mysqlclient-version }}\" -p \"$TEST_PYTHON\" --group \"$TEST_DJANGO_VERSION\"\n      - name: Store coverage files\n        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a\n        with:\n          name: ${{ env.COVERAGE_FILE }}\n          path: ${{ env.COVERAGE_FILE }}\n\n  mariadb:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      actions: write\n    env:\n      RDBMS: mariadb\n      COVERAGE_FILE: linux-py${{ matrix.python-version }}-${{ matrix.django-version }}-${{ matrix.mysqlclient-version }}-mariadb${{ matrix.mariadb-version }}.coverage\n      TEST_PYTHON_VERSION: ${{ matrix.python-version }}\n      TEST_DJANGO_VERSION: ${{ matrix.django-version }}\n      TEST_DATABASE_CLIENT_VERSION: ${{ matrix.mysqlclient-version }}\n      TEST_DATABASE_VERSION: ${{ matrix.mariadb-version }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: ['3.10', '3.12', '3.14']\n        mysqlclient-version: ['mysqlclient14', 'mysqlclient2x']\n        mariadb-version: ['10.4', 'latest']\n        mariadb-healthcheck: [\"mysqladmin ping\", \"healthcheck.sh --connect --innodb_initialized\"]\n        django-version:\n          - 'dj42'    # LTS April 2024\n          - 'dj52'    # LTS April 2028\n          - 'dj60'    #\n        exclude:\n          - django-version: 'dj42'\n            mariadb-version: 'latest'\n\n          - django-version: 'dj52'\n            mariadb-version: '10.4'\n          - django-version: 'dj60'\n            mariadb-version: '10.4'\n\n          - mariadb-version: 'latest'\n            mysqlclient-version: 'mysqlclient14'\n\n          - python-version: '3.12'\n            django-version: 'dj42'\n          - python-version: '3.14'\n            django-version: 'dj42'\n\n          - python-version: '3.10'\n            django-version: 'dj52'\n          - python-version: '3.14'\n            django-version: 'dj52'\n          - django-version: 'dj52'\n            mysqlclient-version: 'mysqlclient14'\n\n          - python-version: '3.10'\n            django-version: 'dj60'\n          - python-version: '3.12'\n            django-version: 'dj60'\n\n          - mariadb-version: '10.4'\n            mysqlclient-version: 'mysqlclient2x'\n\n          - mariadb-version: 'latest'\n            mariadb-healthcheck: \"mysqladmin ping\"\n          - mariadb-version: '10.4'\n            mariadb-healthcheck: \"healthcheck.sh --connect --innodb_initialized\"\n\n    services:\n      mariadb:\n        # Docker Hub image\n        image: mariadb:${{ matrix.mariadb-version }}\n        # Provide the password for mysql\n        env:\n          MYSQL_ROOT_PASSWORD: root\n          MYSQL_MULTIPLE_DATABASES: test1,test2\n        # Set health checks to wait until mysql has started\n        options: >-\n          --health-cmd=\"${{ matrix.mariadb-healthcheck }}\"\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n        ports:\n          # Maps tcp port 3306 on service container to the host\n          - 3306:3306\n\n    steps:\n      # make text comparisons case sensitive (some tests)\n      - name: Set default collation for MariaDB\n        run: |\n          mysql -h 127.0.0.1 -u root -proot <<EOF\n          SET GLOBAL character_set_server='utf8mb4';\n          SET GLOBAL collation_server='utf8mb4_bin';\n          EOF\n      - name: Clear Tool cache\n        if: ${{ github.event.inputs.clear_cache == 'true' }}\n        run: sudo rm -rf /opt/hostedtoolcache\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          persist-credentials: false\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405\n        id: sp\n        with:\n          python-version: ${{ matrix.python-version }}\n          allow-prereleases: true\n      - name: Install Emacs\n        if: ${{ github.event.inputs.debug == 'true' }}\n        run: |\n          sudo apt install emacs\n      - name: Setup tmate session\n        if: ${{ github.event.inputs.debug == 'true' }}\n        uses: mxschmitt/action-tmate@c0afd6f790e3a5564914980036ebf83216678101\n        timeout-minutes: 60\n      - name: Install uv\n        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b\n        with:\n          enable-cache: false\n      - name: Setup Just\n        uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3\n      - name: Run Unit Tests\n        env:\n          TEST_PYTHON: ${{ steps.sp.outputs.python-path }}\n        run: |\n          just test-all --group \"${{ matrix.mysqlclient-version }}\" -p \"$TEST_PYTHON\" --group \"$TEST_DJANGO_VERSION\"\n      - name: Store coverage files\n        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a\n        with:\n          name: ${{ env.COVERAGE_FILE }}\n          path: ${{ env.COVERAGE_FILE }}\n\n  oracle:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      actions: write\n    env:\n      RDBMS: oracle\n      TEST_PYTHON_VERSION: ${{ matrix.python-version }}\n      TEST_DJANGO_VERSION: ${{ matrix.django-version }}\n      TEST_DATABASE_VERSION: ${{ matrix.oracle-version }}\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: ['3.10', '3.14']\n        django-version:\n          - 'dj42' # LTS April 2026\n          - 'dj52' # LTS April 2028\n          - 'dj60' #\n        oracle-version:\n          - 'oracle-xe:21'\n          - 'oracle-free:latest'\n        exclude:\n\n          - django-version: 'dj42'\n            oracle-version: 'oracle-free:latest'\n\n          - django-version: 'dj52'\n            oracle-version: 'oracle-free:latest'\n\n          - django-version: 'dj60'\n            oracle-version: 'oracle-xe:21'\n\n          - python-version: '3.10'\n            django-version: 'dj60'\n\n          - python-version: '3.14'\n            django-version: 'dj42'\n          - python-version: '3.14'\n            django-version: 'dj52'\n\n    services:\n      oracle1:\n        image: gvenzl/${{ matrix.oracle-version }}  # zizmor: ignore[unpinned-images]\n        env:\n          ORACLE_PASSWORD: password\n          ORACLE_DATABASE: test1\n        # Forward Oracle port\n        ports:\n          - 1521:1521\n        # Provide healthcheck script options for startup\n        options: >-\n          --health-cmd healthcheck.sh\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 10\n\n      oracle2:\n        image: gvenzl/${{ matrix.oracle-version }}  # zizmor: ignore[unpinned-images]\n        env:\n          ORACLE_PASSWORD: password\n          ORACLE_DATABASE: test2\n        # Forward Oracle port\n        ports:\n          - 1522:1521\n        # Provide healthcheck script options for startup\n        options: >-\n          --health-cmd healthcheck.sh\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 10\n\n    steps:\n      - name: Set coverage file\n        run: |\n          ORACLE_TAG=$(echo \"${{ matrix.oracle-version }}\" | cut -d':' -f2)\n          echo \"COVERAGE_FILE=linux-py${{ matrix.python-version }}-${{ matrix.django-version }}-oracle${ORACLE_TAG}.coverage\" >> $GITHUB_ENV\n      - name: Clear Tool cache\n        if: ${{ github.event.inputs.clear_cache == 'true' }}\n        run: sudo rm -rf /opt/hostedtoolcache\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          persist-credentials: false\n      - name: Set up Python ${{ matrix.python-version }}\n        id: sp\n        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405\n        with:\n          python-version: ${{ matrix.python-version }}\n          allow-prereleases: true\n      - name: Install Emacs\n        if: ${{ github.event.inputs.debug == 'true' }}\n        run: |\n          sudo apt install emacs\n      - name: Setup tmate session\n        if: ${{ github.event.inputs.debug == 'true' }}\n        uses: mxschmitt/action-tmate@c0afd6f790e3a5564914980036ebf83216678101\n        timeout-minutes: 60\n      - name: Install uv\n        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b\n        with:\n          enable-cache: false\n      - name: Setup Just\n        uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3\n      - name: Install Oracle Client\n        # https://askubuntu.com/questions/1512196/libaio1-on-noble\n        run: |\n          sudo apt install alien libaio1t64\n          sudo ln -s /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/x86_64-linux-gnu/libaio.so.1\n          if [[ \"${{ matrix.oracle-version }}\" == oracle-xe* ]]; then\n            curl --output oracle-client.rpm https://download.oracle.com/otn_software/linux/instantclient/2116000/oracle-instantclient-basiclite-21.16.0.0.0-1.el8.x86_64.rpm\n            sudo alien -i oracle-client.rpm\n            sudo sh -c \"echo /usr/lib/oracle/21/client64/lib/ > /etc/ld.so.conf.d/oracle.conf\"\n          else\n            curl --output oracle-client.rpm https://download.oracle.com/otn_software/linux/instantclient/2326000/oracle-instantclient-basiclite-23.26.0.0.0-1.el9.x86_64.rpm\n            sudo alien -i oracle-client.rpm\n            sudo sh -c \"echo /usr/lib/oracle/23/client64/lib/ > /etc/ld.so.conf.d/oracle.conf\"\n          fi\n          sudo ldconfig\n      # we don't run integration tests against Oracle in CI, these are slow enough\n      - name: Run Full Unit Tests\n        env:\n          TEST_PYTHON: ${{ steps.sp.outputs.python-path }}\n        run: |\n          if [[ \"${{ matrix.oracle-version }}\" == oracle-xe* ]]; then\n            just test-all --group cx_oracle -p \"$TEST_PYTHON\" --group \"$TEST_DJANGO_VERSION\"\n          else\n            just test-all --group oracledb -p \"$TEST_PYTHON\" --group \"$TEST_DJANGO_VERSION\"\n          fi\n\n      - name: Store coverage files\n        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a\n        with:\n          name: ${{ env.COVERAGE_FILE }}\n          path: ${{ env.COVERAGE_FILE }}\n\n\n  windows:\n    runs-on: windows-latest\n    permissions:\n      contents: read\n      actions: write\n    env:\n      RDBMS: sqlite\n      COVERAGE_FILE: windows-py${{ matrix.python-version }}-${{ matrix.django-version }}-sqlite.coverage\n      TEST_PYTHON_VERSION: ${{ matrix.python-version }}\n      TEST_DJANGO_VERSION: ${{ matrix.django-version }}\n      TEST_DATABASE_VERSION: \"sqlite\"\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: ['3.10', '3.12', '3.14']\n        django-version:\n          - 'dj42' # LTS April 2026\n          - 'dj52' # LTS April 2028\n          - 'dj60' #\n        exclude:\n          - python-version: '3.10'\n            django-version: 'dj52'\n          - python-version: '3.10'\n            django-version: 'dj60'\n\n          - python-version: '3.12'\n            django-version: 'dj42'\n          - python-version: '3.12'\n            django-version: 'dj60'\n\n          - python-version: '3.14'\n            django-version: 'dj42'\n          - python-version: '3.14'\n            django-version: 'dj52'\n\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          persist-credentials: false\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405\n        id: sp\n        with:\n          python-version: ${{ matrix.python-version }}\n          allow-prereleases: true\n      - name: Install uv\n        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b\n        with:\n          enable-cache: false\n      - name: Setup Just\n        uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3\n      - name: install-vim-windows\n        if: ${{ github.event.inputs.debug == 'true' }}\n        uses: rhysd/action-setup-vim@febef33995d6649302e9d88dda81e071b68f16a7\n      - name: Setup tmate session\n        if: ${{ github.event.inputs.debug == 'true' }}\n        uses: mxschmitt/action-tmate@c0afd6f790e3a5564914980036ebf83216678101\n        timeout-minutes: 60\n      - name: Run Unit Tests\n        env:\n          TEST_PYTHON: ${{ steps.sp.outputs.python-path }}\n        run: |\n          just test-all -p \"$env:TEST_PYTHON\" --group \"$env:TEST_DJANGO_VERSION\"\n        shell: pwsh\n      - name: Store coverage files\n        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a\n        with:\n          name: ${{ env.COVERAGE_FILE }}\n          path: ${{ env.COVERAGE_FILE }}\n\n\n  macos:\n    runs-on: macos-latest\n    permissions:\n      contents: read\n      actions: write\n    env:\n      RDBMS: sqlite\n      COVERAGE_FILE: macos-py${{ matrix.python-version }}-${{ matrix.django-version }}-sqlite.coverage\n      TEST_PYTHON_VERSION: ${{ matrix.python-version }}\n      TEST_DJANGO_VERSION: ${{ matrix.django-version }}\n      TEST_DATABASE_VERSION: \"sqlite\"\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: ['3.10', '3.12', '3.14']\n        django-version:\n          - 'dj42' # LTS April 2024\n          - 'dj52' # LTS April 2028\n          - 'dj60' #\n        exclude:\n          - python-version: '3.10'\n            django-version: 'dj52'\n          - python-version: '3.10'\n            django-version: 'dj60'\n\n          - python-version: '3.12'\n            django-version: 'dj42'\n          - python-version: '3.12'\n            django-version: 'dj60'\n\n          - python-version: '3.14'\n            django-version: 'dj42'\n          - python-version: '3.14'\n            django-version: 'dj52'\n\n    steps:\n      - name: Clear Tool cache\n        if: ${{ github.event.inputs.clear_cache == 'true' }}\n        run: sudo rm -rf /opt/hostedtoolcache\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          persist-credentials: false\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405\n        id: sp\n        with:\n          python-version: ${{ matrix.python-version }}\n          allow-prereleases: true\n      - name: Install uv\n        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b\n        with:\n          enable-cache: false\n      - name: Setup Just\n        uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3\n      - name: install-emacs-macos\n        if: ${{ github.event.inputs.debug == 'true' }}\n        run: |\n          brew install emacs\n      - name: Setup tmate session\n        if: ${{ github.event.inputs.debug == 'true' }}\n        uses: mxschmitt/action-tmate@c0afd6f790e3a5564914980036ebf83216678101\n        timeout-minutes: 60\n      - name: Run Unit Tests\n        env:\n          TEST_PYTHON: ${{ steps.sp.outputs.python-path }}\n        run: |\n          just test-all -p \"$TEST_PYTHON\" --group \"$TEST_DJANGO_VERSION\"\n      - name: Store coverage files\n        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a\n        with:\n          name: ${{ env.COVERAGE_FILE }}\n          path: ${{ env.COVERAGE_FILE }}\n\n\n  coverage-combine:\n    needs: [postgres, sqlite, mysql, mariadb, oracle, windows, macos]\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          persist-credentials: false\n      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405\n        id: sp\n        with:\n          python-version: '3.12'\n      - name: Install uv\n        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b\n        with:\n          enable-cache: false\n      - name: Setup Just\n        uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3\n      - name: Get coverage files\n        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c\n        with:\n          pattern: \"*.coverage\"\n          merge-multiple: true\n      - run: ls -la *.coverage\n      - run: just coverage\n\n      - name: Upload coverage to Codecov\n        uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2\n        with:\n          use_oidc: true\n          files:\n            ./coverage.xml\n"
  },
  {
    "path": ".github/workflows/update_coc.yml",
    "content": "name: Update Code of Conduct\n\npermissions: read-all\n\non:\n  workflow_dispatch:\n  # Run every Sunday at midnight UTC (00:00) - triggered by webhook?\n  schedule:\n    - cron: '0 0 * * SUN'\n\n\njobs:\n  update_code_of_conduct:\n    permissions:\n     contents: write\n     issues: write\n     pull-requests: write\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          persist-credentials: false\n\n      - name: Fetch CODE_OF_CONDUCT.md from django-commons\n        run: |\n          curl -o fetched_code_of_conduct.md https://raw.githubusercontent.com/django-commons/membership/main/CODE_OF_CONDUCT.md\n\n      - name: Check if CODE_OF_CONDUCT.md has changed\n        id: check_changes\n        run: |\n          if cmp -s fetched_code_of_conduct.md CODE_OF_CONDUCT.md; then\n            echo \"No changes in CODE_OF_CONDUCT.md\"\n            echo \"changed=false\" >> $GITHUB_OUTPUT\n          else\n            echo \"CODE_OF_CONDUCT.md has changed\"\n            echo \"changed=true\" >> $GITHUB_OUTPUT\n            cp fetched_code_of_conduct.md CODE_OF_CONDUCT.md\n          fi\n\n      # Create a pull request to merge the changes into the main branch\n      - name: Create Pull Request\n        if: steps.check_changes.outputs.changed == 'true'\n        uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1\n        with:\n            token: ${{ secrets.GITHUB_TOKEN }}\n            branch: bot-update-coc\n            add-paths: |\n                CODE_OF_CONDUCT.md\n            title: \"🤖 Update Code of Conduct 🤖\"\n            body: \"Update the Code of Conduct with the latest version from the django-commons repository.\"\n            commit-message: \"Update CODE_OF_CONDUCT.md from django-commons\"\n"
  },
  {
    "path": ".github/workflows/zizmor.yml",
    "content": "name: Zizmor\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    paths:\n      - \".github/workflows/**/*.yml\"\n  schedule:\n    # Run weekly\n    - cron: '0 0 * * 0'\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\njobs:\n  zizmor-analysis:\n    name: Run Zizmor\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      security-events: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          persist-credentials: false\n\n      - name: Set up Rust\n        uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7\n      - name: Install jq\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y jq\n      - name: Install Zizmor\n        run: |\n          cargo install --locked zizmor\n\n      - name: Run Zizmor analysis\n        run: |\n          zizmor --format sarif .github/workflows/ > zizmor.sarif\n\n      - name: Upload analysis results\n        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a\n        with:\n          name: zizmor-results\n          path: zizmor.sarif\n          retention-days: 7\n\n      - name: Upload to code-scanning\n        uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7\n        with:\n          sarif_file: zizmor.sarif\n\n      - name: Fail on Findings\n        run: |\n          count=\"$(\n            jq '([.runs[]? | (.results // [])[] | select(.level != \"note\")] | length) // 0' \\\n              zizmor.sarif\n          )\"\n          echo \"Zizmor findings: $count\"\n          test \"$count\" -eq 0\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[codz]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py.cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Test migrations (generated dynamically by tests)\nsrc/polymorphic/tests/test_migrations/migrations/0*.py\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# UV\n#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#uv.lock\n\n# poetry\n#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n#poetry.toml\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#   pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.\n#   https://pdm-project.org/en/latest/usage/project/#working-with-version-control\n#pdm.lock\n#pdm.toml\n.pdm-python\n.pdm-build/\n\n# pixi\n#   Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.\n#pixi.lock\n#   Pixi creates a virtual environment in the .pixi directory, just like venv module creates one\n#   in the .venv directory. It is recommended not to include this directory in version control.\n.pixi\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# Redis \n*.rdb\n*.aof\n*.pid\n\n# RabbitMQ\nmnesia/\nrabbitmq/\nrabbitmq-data/\n\n# ActiveMQ\nactivemq-data/\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.envrc\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n\n# Abstra\n# Abstra is an AI-powered process automation framework.\n# Ignore directories containing user credentials, local state, and settings.\n# Learn more at https://abstra.io/docs\n.abstra/\n\n# Visual Studio Code\n#  Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore \n#  that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore\n#  and can be added to the global gitignore or merged into this file. However, if you prefer, \n#  you could uncomment the following to ignore the entire vscode folder\n# .vscode/\n\n# Ruff stuff:\n.ruff_cache/\n\n# PyPI configuration file\n.pypirc\n\n# Marimo\nmarimo/_static/\nmarimo/_lsp/\n__marimo__/\n\n# Streamlit\n.streamlit/secrets.toml\n\n.DS_Store\n\n.python-version\n\ntest1.db\ntest2.db\nexample/example.db\nCLAUDE.md\nzizmor.sarif\nbandit.sarif\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "ci:\n  # Don't run these in pre-commit.ci at all\n  skip: [lint, format, docs, pip]\n\nrepos:\n  - repo: local\n    hooks:\n      - id: lint\n        name: Lint\n        entry: just lint\n        language: system\n        pass_filenames: false\n\n      - id: format\n        name: Format\n        entry: just format\n        language: system\n        pass_filenames: false\n\n      - id: docs\n        name: Docs\n        entry: just check-docs\n        language: system\n        pass_filenames: false\n\n      - id: pip\n        name: Package\n        entry: just check-package\n        language: system\n        pass_filenames: false\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# .readthedocs.yaml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\n# Set the version of Python and other tools you might need\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.12\"\n  apt_packages:\n    - gettext\n  jobs:\n    post_install:\n      - pip install uv\n      - UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --all-extras --group docs --link-mode=copy\n\n# Build documentation in the docs/ directory with Sphinx\nsphinx:\n   configuration: docs/conf.py\n   \n# Optionally build your docs in additional formats such as PDF and ePub\nformats:\n  - pdf\n"
  },
  {
    "path": "AUTHORS.md",
    "content": "# Current Maintainer(s)\n\n* Brian Kohan\n\n## Contributors\n\n* Abel Daniel\n* Adam Chainz\n* Adam Wentz\n* Adam Donaghy\n* Andrew Ingram (contributed setup.py)\n* Al Johri\n* Alex Alvarez\n* Andrew Dodd\n* Angel Velasquez\n* Austin Matsick\n* Bastien Vallet\n* Ben Konrath\n* Bert Constantin\n* Bertrand Bordage\n* Chad Shryock\n* Charles Leifer (python 2.4 compatibility)\n* Chris Barna\n* Chris Brantley\n* Christopher Glass\n* David Sanders\n* Emad Rad\n* Éric Araujo\n* Evan Borgstrom\n* Frankie Dintino\n* Gavin Wahl\n* Germán M. Bravo\n* Gonzalo Bustos\n* Gregory Avery-Weir\n* Hugo Osvaldo Barrera\n* Jacob Rief\n* James Murty\n* Jedediah Smith (proxy models support)\n* Jesús Leganés-Combarro (Auto-discover child models and inlines, #582)\n* John Furr\n* Jonas Haag\n* Jonas Obrist\n* Julian Wachholz\n* Kamil Bar\n* Kelsey Gilmore-Innis\n* Kevin Armenat\n* Krzysztof Gromadzki\n* Krzysztof Nazarewski\n* Luis Zárate\n* Marius Lueck\n* Martin Brochhaus\n* Martin Maillard\n* Michael Fladischer\n* Nick Ward\n* Oleg Myltsyn\n* Omer Strumpf\n* Paweł Adamczak\n* Petr Dlouhý\n* Sander van Leeuwen\n* Sobolev Nikita\n* Tadas Dailyda\n* Tai Lee\n* Tomas Peterka\n* Tony Narlock\n* Vail Gold\n\n\n## Former authors / maintainers\n\n* Bert Constantin 2009/2010 (Original author, disappeared from the internet :( )\n* Chris Glass\n* Diederik van der Boor\n* Charlie Denton\n* Jerome Leclanche\n\n\n## django-rest-framework\n\n### Development Lead\n\n* Denis Orehovsky <denis.orehovsky@gmail.com>\n\n### Contributors\n\n* Jeff Hackshaw <jeffrey.hackshaw@gmail.com>\n* TFranzel\n* Ignacio Losiggio <iglosiggio@gmail.com>\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "\n# Django Commons Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, caste, color, religion, or sexual\nidentity and orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the overall\n  community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or advances of\n  any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email address,\n  without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official email address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\n[django-commons-coc@googlegroups.com](mailto:django-commons-coc@googlegroups.com).\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Warning\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or permanent\nban.\n\n### 2. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 3. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior, harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within the\ncommunity.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.1, available at\n[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].\n\nCommunity Impact Guidelines were inspired by\n[Mozilla's code of conduct enforcement ladder][Mozilla CoC].\n\nFor answers to common questions about this code of conduct, see the FAQ at\n[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at\n[https://www.contributor-covenant.org/translations][translations].\n\n[homepage]: https://www.contributor-covenant.org\n[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html\n[Mozilla CoC]: https://github.com/mozilla/diversity\n[FAQ]: https://www.contributor-covenant.org/faq\n[translations]: https://www.contributor-covenant.org/translations\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nContributions are encouraged! Please use the issue page to submit feature requests or bug reports. Issues with attached PRs will be given priority and have a much higher likelihood of acceptance. Please also open an issue and associate it with any submitted PRs.\n\nThis is a [django-commons](https://django-commons.org) project. If you wish to contribute, [please join the organization!](https://django-commons.org/#how-to-join-as-a-contributor)\n\nWe are actively seeking additional maintainers. If you're interested, please open an issue or [contact me](https://github.com/bckohan).\n\n## Installation\n\n### Install Just\n\nWe provide a platform independent justfile with recipes for all the development tasks. You should [install just](https://just.systems/man/en/) if it is not on your system already.\n\n[django-polymorphic](https://pypi.python.org/pypi/django-polymorphic) uses [uv](https://docs.astral.sh/uv) for environment, package, and dependency management. ``just setup`` will install the necessary build tooling if you do not already have it:\n\n ```bash\njust setup\n```\n\nSetup also may take a python version:\n\n```bash\njust setup 3.12\n```\n\nIf you already have uv and python installed running install will just install the development dependencies:\n\n ```bash\njust install\n```\n\n**To run pre-commit checks you will have to install just.**\n\n## Documentation\n\n`django-polymorphic` documentation is generated using [Sphinx](https://www.sphinx-doc.org) with the [furo](https://github.com/pradyunsg/furo) theme. Any new feature PRs must provide updated documentation for the features added. To build the docs run doc8 to check for formatting issues then run Sphinx:\n\n```bash\njust docs  # builds docs\njust check-docs  # lint the docs\njust check-docs-links  # check for broken links in the docs\n```\n\nRun the docs with auto rebuild using:\n\n```bash\njust docs-live\n```\n\n## Static Analysis\n\n`django-polymorphic` uses [ruff](https://docs.astral.sh/ruff/) for Python linting, header import standardization and code formatting. Before any PR is accepted the following must be run, and static analysis tools should not produce any errors or warnings. Disabling certain errors or warnings where justified is acceptable:\n\nTo fix formatting and linting problems that are fixable run:\n\n```bash\njust fix\n```\n\nTo run all static analysis without automated fixing you can run:\n\n```bash\njust check\n```\n\nTo format source files you can run:\n\n```bash\njust format\n```\n\n### Type Checking\n\nBoth [mypy](https://www.mypy-lang.org) and [pyright](https://microsoft.github.io/pyright) are used to verify static typing. You can run them together or individually:\n\n```bash\njust check-types\njust check-types-mypy\njust check-types-pyright\n```\n\n## Running Tests\n\n`django-polymorphic` is set up to use [pytest](https://docs.pytest.org) to run unit tests. All the tests are housed in `src/polymorphic/tests`. Before a PR is accepted, all tests must be passing and the code coverage must be at 100%. A small number of exempted error handling branches are acceptable.\n\nTo run the full suite:\n\n ```bash\njust test\n```\n\nTo run a single test, or group of tests in a class:\n\n ```bash\njust test <path_to_tests_file>::ClassName::FunctionName\n```\n\nFor instance, to run all admin tests, and then just the test_admin_registration test you would do:\n\n ```bash\njust test src/polymorphic/tests/test_admin.py\njust test src/polymorphic/tests/test_admin.py::PolymorphicAdminTests::test_admin_registration\n```\n\n### Running UI Tests\n\nMake sure you have playwright installed:\n\n```bash\njust install-playwright\n```\n\nIf you want to see the test step through the UI actions you can run the test like so:\n\n```bash\njust debug-test -k <test_name>\n```\n\nThis will open a browser and the debugger at the start of the test, you can then ``next`` through and see the UI actions happen.\n\n## Versioning\n\n[django-polymorphic](https://pypi.python.org/pypi/django-polymorphic) strictly adheres to [semantic versioning](https://semver.org).\n\n## Issuing Releases\n\nThe release workflow is triggered by tag creation. You must have [git tag signing enabled](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits). Our justfile has a release shortcut:\n\n```bash\njust release x.x.x\n```\n\n## Just Recipes\n\n```bash\nbuild                             # build docs and package\nbuild-docs                        # build the docs\nbuild-docs-html                   # build html documentation\nbuild-docs-pdf                    # build pdf documentation\ncheck                             # run all static checks\ncheck-all                         # run all checks including doc link staleness (slow)\ncheck-docs                        # lint the documentation\ncheck-docs-links                  # check the documentation links for broken links\ncheck-format                      # check if the code needs formatting\ncheck-lint                        # lint the code\ncheck-package                     # run package checks\ncheck-readme                      # check that the readme renders\ncheck-types                       # run all static type checking\ncheck-types-isolated              # run all static type checking in an isolated environment\ncheck-types-mypy *RUN_ARGS        # run static type checking with mypy\ncheck-types-pyright *RUN_ARGS     # run static type checking with pyright\nclean                             # remove all non repository artifacts\nclean-docs                        # remove doc build artifacts-\nclean-env                         # remove the virtual environment\nclean-git-ignored                 # remove all git ignored files\ncoverage                          # generate the test coverage report\ndebug-test *TESTS                 # debug an test\ndocs                              # build and open the documentation\ndocs-live                         # serve the documentation, with auto-reload\nfetch-refs LIB                    # get the intersphinx references for the given library\nfix                               # fix formatting, linting issues and import sorting\nformat                            # format the code and sort imports\ninstall *OPTS                     # update and install development dependencies\ninstall-playwright                # install playwright dependencies\ninstall-precommit                 # install git pre-commit hooks\ninstall_uv                        # install the uv package manager\nlint                              # sort the imports and fix linting issues\nmanage *COMMAND                   # run the django admin\nopen-docs                         # open the html documentation\nprecommit                         # run the pre-commit checks\nrelease VERSION                   # issue a release for the given semver string (e.g. 2.1.0)\nremake-test-migrations            # regenerate test migrations using the lowest version of Django\nrun +ARGS                         # run the command in the virtual environment\nsetup python=\"python\"             # setup the venv, pre-commit hooks and playwright dependencies\nsort-imports                      # sort the python imports\ntest *TESTS                       # run tests\ntest-db DB_CLIENT=\"dev\" *TESTS    # test against the specified database backend\ntest-drf *TESTS                   # test django rest framework integration\ntest-extra-views *TESTS           # test django extra views integration\ntest-guardian *TESTS              # test guardian integration\ntest-integrations DB_CLIENT=\"dev\" # run all third party integration tests\ntest-lock +PACKAGES               # lock to specific python and versions of given dependencies\ntest-reversion *TESTS             # test django-revision integration\nvalidate_version VERSION          # validate the given version string against the lib version\n```\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2009 or later by the individual contributors.\nPlease see the AUTHORS file.\n\nAll rights reserved.\n\r\nRedistribution and use in source and binary forms, with or without\r\nmodification, are permitted provided that the following conditions are\r\nmet:\r\n\r\n    * Redistributions of source code must retain the above copyright\r\n      notice, this list of conditions and the following disclaimer.\r\n\r\n    * Redistributions in binary form must reproduce the above\r\n      copyright notice, this list of conditions and the following\r\n      disclaimer in the documentation and/or other materials provided\r\n      with the distribution.\r\n\r\n    * The names of the contributors may not be used to endorse or\r\n      promote products derived from this software without specific\r\n      prior written permission.\r\n\r\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\r\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\r\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\r\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\r\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\r\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\r\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r\n"
  },
  {
    "path": "README.md",
    "content": "# django-polymorphic\n\n[![License: BSD](https://img.shields.io/badge/License-BSD-blue.svg)](https://opensource.org/license/bsd-3-clause)\n[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)\n[![PyPI version](https://badge.fury.io/py/django-polymorphic.svg)](https://pypi.python.org/pypi/django-polymorphic/)\n[![PyPI pyversions](https://img.shields.io/pypi/pyversions/django-polymorphic.svg)](https://pypi.python.org/pypi/django-polymorphic/)\n[![PyPI djversions](https://img.shields.io/pypi/djversions/django-polymorphic.svg)](https://pypi.org/project/django-polymorphic/)\n[![PyPI status](https://img.shields.io/pypi/status/django-polymorphic.svg)](https://pypi.python.org/pypi/django-polymorphic)\n[![PyPI - Types](https://img.shields.io/pypi/types/django-polymorphic.svg)](https://pypi.python.org/pypi/django-polymorphic)\n[![Documentation Status](https://readthedocs.org/projects/django-polymorphic/badge/?version=latest)](http://django-polymorphic.readthedocs.io/?badge=latest/)\n[![Code Cov](https://img.shields.io/codecov/c/github/django-commons/django-polymorphic/main.svg)](https://codecov.io/github/django-commons/django-polymorphic?branch=main)\n[![Test Status](https://github.com/django-commons/django-polymorphic/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/django-commons/django-polymorphic/actions/workflows/test.yml?query=branch:main)\n[![Lint Status](https://github.com/django-commons/django-polymorphic/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/django-commons/django-polymorphic/actions/workflows/lint.yml?query=branch:main)\n[![Published on Django Packages](https://img.shields.io/badge/Published%20on-Django%20Packages-0c3c26)](https://djangopackages.org/packages/p/django-polymorphic/)\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/django-commons/django-polymorphic/badge)](https://securityscorecards.dev/viewer/?uri=github.com/django-commons/django-polymorphic)\n\n---------------------------------------------------------------------------------------------------\n\n[![Postgres](https://img.shields.io/badge/Postgres-12%2B-blue)](https://www.postgresql.org/)\n[![MySQL](https://img.shields.io/badge/MySQL-8.0%2B-blue)](https://www.mysql.com/)\n[![MariaDB](https://img.shields.io/badge/MariaDB-10.4%2B-blue)](https://mariadb.org/)\n[![SQLite](https://img.shields.io/badge/SQLite-3.8%2B-blue)](https://www.sqlite.org/)\n[![Oracle](https://img.shields.io/badge/Oracle-21c%2B-blue)](https://www.oracle.com/database/)\n\n---------------------------------------------------------------------------------------------------\n\n## Polymorphic Models for Django\n\n[django-polymorphic](https://pypi.python.org/pypi/django-polymorphic) simplifies using inherited models in [Django](https://djangoproject.com) projects. When a query is made at the base model, the inherited model classes are returned.\n\nWhen we store models that inherit from a ``Project`` model...\n\n```python\n\n    >>> Project.objects.create(topic=\"Department Party\")\n    >>> ArtProject.objects.create(topic=\"Painting with Tim\", artist=\"T. Turner\")\n    >>> ResearchProject.objects.create(topic=\"Swallow Aerodynamics\", supervisor=\"Dr. Winter\")\n```\n\n...and want to retrieve all our projects, the subclassed models are returned!\n\n```python\n\n    >>> Project.objects.all()\n    [ <Project:         id 1, topic \"Department Party\">,\n      <ArtProject:      id 2, topic \"Painting with Tim\", artist \"T. Turner\">,\n      <ResearchProject: id 3, topic \"Swallow Aerodynamics\", supervisor \"Dr. Winter\"> ]\n```\n\nUsing vanilla Django, we get the base class objects, which is rarely what we wanted:\n\n```python\n\n    >>> Project.objects.all()\n    [ <Project: id 1, topic \"Department Party\">,\n      <Project: id 2, topic \"Painting with Tim\">,\n      <Project: id 3, topic \"Swallow Aerodynamics\"> ]\n```\n\nThis also works when the polymorphic model is accessed via\nForeignKeys, ManyToManyFields or OneToOneFields.\n\n### Features\n\n* Full admin integration.\n* ORM integration:\n\n  * support for ForeignKey, ManyToManyField, OneToOneField descriptors.\n  * Filtering/ordering of inherited models (``ArtProject___artist``).\n  * Filtering model types: ``instance_of(...)`` and ``not_instance_of(...)``\n  * Combining querysets of different models (``qs3 = qs1 | qs2``)\n  * Support for custom user-defined managers.\n* Uses the minimum amount of queries needed to fetch the inherited models.\n* Disabling polymorphic behavior when needed.\n\n\n**Note:** While [django-polymorphic](https://pypi.python.org/pypi/django-polymorphic) makes subclassed models easy to use in Django, we still encourage to use them with caution. Each subclassed model will require Django to perform an ``INNER JOIN`` to fetch the model fields from the database. While taking this in mind, there are valid reasons for using subclassed models. That's what this library is designed for!\n\nFor more information, see the [documentation at Read the Docs](https://django-polymorphic.readthedocs.io).\n\n### Installation\n\n```bash\n    $ pip install django-polymorphic\n```\n\n```python\nINSTALLED_APPS = [\n  ...\n  \"django.contrib.contenttypes\",  # we rely on the contenttypes framework\n  \"polymorphic\"\n]\n```\n\n## License\n\n[django-polymorphic](https://pypi.python.org/pypi/django-polymorphic) uses the same license as Django (BSD-like).\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n[![CodeQL](https://github.com/django-commons/django-polymorphic/actions/workflows/github-code-scanning/codeql/badge.svg?branch=main)](https://github.com/django-commons/django-polymorphic/actions/workflows/github-code-scanning/codeql?query=branch:main)\n[![Zizmor](https://github.com/django-commons/django-polymorphic/actions/workflows/zizmor.yml/badge.svg?branch=main)](https://docs.zizmor.sh)\n[![Bandit](https://github.com/django-commons/django-polymorphic/actions/workflows/bandit.yml/badge.svg?branch=main)](https://bandit.readthedocs.io)\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/django-commons/django-polymorphic/badge)](https://securityscorecards.dev/viewer/?uri=github.com/django-commons/django-polymorphic)\n\n## Supported Versions\n\nOnly the latest version [![PyPI version](https://badge.fury.io/py/django-polymorphic.svg)](https://pypi.python.org/pypi/django-polymorphic) is supported.\n\n## Reporting a Vulnerability\n\nIf you think you have found a vulnerability, and even if you are not sure, please [report it to us in private](https://github.com/django-commons/django-polymorphic/security/advisories/new). We will review it and get back to you. Please refrain from public discussions of the issue.\n"
  },
  {
    "path": "conftest.py",
    "content": "import inspect\nimport subprocess\nimport sys\n\nimport pytest\n\n\ndef pytest_configure(config):\n    # stash it somewhere global-ish\n    from polymorphic import tests\n\n    tests.HEADLESS = not config.getoption(\"--headed\")\n\n\ndef first_breakable_line(obj) -> tuple[str, int]:\n    \"\"\"\n    Return the absolute line number of the first executable statement\n    in a function or bound method.\n    \"\"\"\n    import ast\n    import textwrap\n\n    func = obj.__func__ if inspect.ismethod(obj) else obj\n\n    source = inspect.getsource(func)\n    source = textwrap.dedent(source)\n    filename = inspect.getsourcefile(func)\n    assert filename\n    _, start_lineno = inspect.getsourcelines(func)\n\n    tree = ast.parse(source)\n\n    for node in tree.body[0].body:\n        if (\n            isinstance(node, ast.Expr)\n            and isinstance(node.value, ast.Constant)\n            and isinstance(node.value.value, str)\n        ):\n            continue\n\n        return filename, start_lineno + node.lineno - 1\n\n    # fallback: just return the line after the def\n    return filename, start_lineno + 1\n\n\ndef pytest_runtest_call(item):\n    # --trace cli option does not work for unittest style tests so we implement it here\n    test = getattr(item, \"obj\", None)\n    if item.config.option.trace and inspect.ismethod(test):\n        from IPython.terminal.debugger import TerminalPdb\n\n        try:\n            file = inspect.getsourcefile(test)\n            assert file\n            dbg = TerminalPdb()\n            dbg.set_break(*first_breakable_line(test))\n            dbg.cmdqueue.append(\"continue\")\n            dbg.set_trace()\n        except (OSError, AssertionError):\n            pass\n\n\ndef _install_playwright_browsers() -> None:\n    cmd = [sys.executable, \"-m\", \"playwright\", \"install\", \"chromium\"]\n    subprocess.run(cmd, check=True)\n\n\ndef pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None:\n    any_ui = any(item.get_closest_marker(\"ui\") is not None for item in items)\n\n    if any_ui and not getattr(config, \"_did_install_playwright\", False):\n        setattr(config, \"_did_install_playwright\", True)\n        _install_playwright_browsers()\n"
  },
  {
    "path": "docs/_ext/djangodummy/__init__.py",
    "content": ""
  },
  {
    "path": "docs/_ext/djangodummy/requirements.txt",
    "content": "# for readthedocs\n# Remaining requirements are picked up from setup.py\nDjango>=4.2.20\ndjango-extra-views>=0.14.0\nsphinxcontrib-django>=2.5\nsphinx_rtd_theme>=2.0.0\n"
  },
  {
    "path": "docs/_ext/djangodummy/settings.py",
    "content": "# Settings file to allow parsing API documentation of Django modules,\n# and provide defaults to use in the documentation.\n#\n# This file is placed in a subdirectory,\n# so the docs root won't be detected by find_packages()\n\n# Display sane URLs in the docs:\nSTATIC_URL = \"/static/\"\n\n# Avoid error for missing the secret key\nSECRET_KEY = \"docs\"\n\nINSTALLED_APPS = [\"django.contrib.contenttypes\"]\n"
  },
  {
    "path": "docs/_static/.gitkeep",
    "content": ""
  },
  {
    "path": "docs/_static/style.css",
    "content": "section#api-documentation div.highlight pre {\n    color: #b30000;\n    display: block;           /* ensures it's treated as a block */\n    margin-left: auto;        /* auto margins center block elements */\n    margin-right: auto;\n    width: fit-content;\n}\nbody[data-theme=\"light\"] section#api-documentation div.highlight,\nbody[data-theme=\"light\"] section#api-documentation div.highlight pre {\n  background-color: #f8f8f8;\n}\n\nbody[data-theme=\"dark\"] section#api-documentation div.highlight,\nbody[data-theme=\"dark\"] section#api-documentation div.highlight pre {\n  background-color: #202020;\n}\n\n/* AUTO → system prefers DARK (acts like dark unless user forced light) */\n@media (prefers-color-scheme: dark) {\n  body:not([data-theme=\"light\"]) #api-documentation .highlight,\n  body:not([data-theme=\"light\"]) #api-documentation .highlight pre {\n    background-color: #202020;\n  }\n}\n\n/* AUTO → system prefers LIGHT (acts like light unless user forced dark) */\n@media (prefers-color-scheme: light) {\n  body:not([data-theme=\"dark\"]) #api-documentation .highlight,\n  body:not([data-theme=\"dark\"]) #api-documentation .highlight pre {\n    background-color: #f8f8f8;\n  }\n}\n"
  },
  {
    "path": "docs/admin.rst",
    "content": "Admin Integration\n=================\n\nOf course, it's possible to register individual polymorphic models in the\n:doc:`Django admin interface <django:ref/contrib/admin/index>`. However, to use these models in a\nsingle cohesive interface, some extra base classes are available.\n\nSetup\n-----\n\nBoth the parent model and child model need to have a :class:`~django.contrib.admin.ModelAdmin`\nclass.\n\nThe shared base model should use the :class:`~polymorphic.admin.PolymorphicParentModelAdmin` as base\nclass.\n\n* :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.base_model` should be set\n* :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.child_models` or\n  :meth:`~polymorphic.admin.PolymorphicParentModelAdmin.get_child_models` should return an iterable\n  of Model classes.\n\nThe admin class for every child model should inherit from\n:class:`~polymorphic.admin.PolymorphicChildModelAdmin`\n\n* :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_model` should be set.\n\nAlthough the child models are registered too, they won't be shown in the admin index page.\nThis only happens when :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show_in_index` is set to\n``True``.\n\nFieldset configuration\n~~~~~~~~~~~~~~~~~~~~~~\n\nThe parent admin is only used for the list display of models, and for the edit/delete view of\nnon-subclassed models.\n\nAll other model types are redirected to the edit/delete/history view of the child model admin.\nHence, the fieldset configuration should be placed on the child admin.\n\n.. tip::\n\n    When the child admin is used as base class for various derived classes, avoid using\n    the standard ``ModelAdmin`` attributes ``form`` and ``fieldsets``.\n    Instead, use the ``base_form`` and ``base_fieldsets`` attributes.\n    This allows the :class:`~polymorphic.admin.PolymorphicChildModelAdmin` class\n    to detect any additional fields in case the child model is overwritten.\n\n.. versionchanged:: 1.0\n\n    It's now needed to register the child model classes too.\n\n    In :pypi:`django-polymorphic` 0.9 and below, the\n    :meth:`~polymorphic.admin.PolymorphicParentModelAdmin.child_models` was a tuple of a\n    (:class:`~django.db.models.Model`, :class:`~polymorphic.admin.PolymorphicChildModelAdmin`). The\n    admin classes were registered in an internal class, and kept away from the main admin site. This\n    caused various subtle problems with the :class:`~django.db.models.ManyToManyField` and related\n    field wrappers, which are fixed by registering the child admin classes too. Note that they are\n    hidden from the main view, unless\n    :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show_in_index` is set.\n\n.. _admin-example:\n\nExample\n-------\n\nThe models are taken from :ref:`advanced-features`.\n\n.. code-block:: python\n\n    from django.contrib import admin\n    from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter\n    from .models import ModelA, ModelB, ModelC, StandardModel\n\n\n    class ModelAChildAdmin(PolymorphicChildModelAdmin):\n        \"\"\" Base admin class for all child models \"\"\"\n        base_model = ModelA  # Optional, explicitly set here.\n\n        # By using these `base_...` attributes instead of the regular ModelAdmin `form` and `fieldsets`,\n        # the additional fields of the child models are automatically added to the admin form.\n        base_form = ...\n        base_fieldsets = (\n            ...\n        )\n\n\n    @admin.register(ModelB)\n    class ModelBAdmin(ModelAChildAdmin):\n        base_model = ModelB  # Explicitly set here!\n        # define custom features here\n\n\n    @admin.register(ModelC)\n    class ModelCAdmin(ModelBAdmin):\n        base_model = ModelC  # Explicitly set here!\n        show_in_index = True  # makes child model admin visible in main admin site\n        # define custom features here\n\n\n    @admin.register(ModelA)\n    class ModelAParentAdmin(PolymorphicParentModelAdmin):\n        \"\"\" The parent model admin \"\"\"\n        base_model = ModelA  # Optional, explicitly set here.\n        child_models = (ModelB, ModelC)\n        list_filter = (PolymorphicChildModelFilter,)  # This is optional.\n\n\n\nFiltering child types\n---------------------\n\nChild model types can be filtered by adding a\n:class:`~polymorphic.admin.PolymorphicChildModelFilter` to the\n:attr:`~django.contrib.admin.ModelAdmin.list_filter` attribute. See the example above.\n\n\nInline models\n-------------\n\n.. versionadded:: 1.0\n\nInline models are handled via a special :class:`~polymorphic.admin.StackedPolymorphicInline` class.\n\nFor models with a generic foreign key, there is a\n:class:`~polymorphic.admin.GenericStackedPolymorphicInline` class available.\n\nWhen the inline is included to a normal :class:`~django.contrib.admin.ModelAdmin`, make sure the\n:class:`~polymorphic.admin.PolymorphicInlineSupportMixin` is included. This is not needed when the\nadmin inherits from the :class:`~polymorphic.admin.PolymorphicParentModelAdmin` or\n:class:`~polymorphic.admin.PolymorphicChildModelAdmin` classes.\n\nIn the following example, the ``PaymentInline`` supports several types. These are defined as\nseparate inline classes. The child classes can be nested for clarity, but this is not a requirement.\n\n.. code-block:: python\n\n    from django.contrib import admin\n\n    from polymorphic.admin import PolymorphicInlineSupportMixin, StackedPolymorphicInline\n    from .models import Order, Payment, CreditCardPayment, BankPayment, SepaPayment\n\n\n    class PaymentInline(StackedPolymorphicInline):\n        \"\"\"\n        An inline for a polymorphic model.\n        The actual form appearance of each row is determined by\n        the child inline that corresponds with the actual model type.\n        \"\"\"\n        class CreditCardPaymentInline(StackedPolymorphicInline.Child):\n            model = CreditCardPayment\n\n        class BankPaymentInline(StackedPolymorphicInline.Child):\n            model = BankPayment\n\n        class SepaPaymentInline(StackedPolymorphicInline.Child):\n            model = SepaPayment\n\n        model = Payment\n        child_inlines = (\n            CreditCardPaymentInline,\n            BankPaymentInline,\n            SepaPaymentInline,\n        )\n\n\n    @admin.register(Order)\n    class OrderAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):\n        \"\"\"\n        Admin for orders.\n        The inline is polymorphic.\n        To make sure the inlines are properly handled,\n        the ``PolymorphicInlineSupportMixin`` is needed to\n        \"\"\"\n        inlines = (PaymentInline,)\n\n\nUsing polymorphic models in standard inlines\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nTo add a polymorphic child model as an Inline for another model, add a field to the inline's\n:attr:`~django.contrib.admin.ModelAdmin.readonly_fields` list formed by the lowercased name of the\npolymorphic parent model with the string ``_ptr`` appended to it. Otherwise, trying to save that\nmodel in the admin will raise an :exc:`AttributeError` with the message \"can't set attribute\".\n\n.. code-block:: python\n\n    from django.contrib import admin\n    from .models import StandardModel\n\n\n    class ModelBInline(admin.StackedInline):\n        model = ModelB\n        fk_name = 'modelb'\n        readonly_fields = ['modela_ptr']\n\n\n    @admin.register(StandardModel)\n    class StandardModelAdmin(admin.ModelAdmin):\n        inlines = [ModelBInline]\n\n\n\nInternal details\n----------------\n\nThe polymorphic admin interface works in a simple way:\n\n* The add screen gains an additional step where the desired child model is selected.\n* The edit screen displays the admin interface of the child model.\n* The list screen still displays all objects of the base class.\n\nThe polymorphic admin is implemented via a parent admin that redirects the ``edit`` and ``delete``\nviews to the :class:`~django.contrib.admin.ModelAdmin` of the derived child model. The ``list`` page\nis still implemented by the parent model admin.\n\nThe parent model\n~~~~~~~~~~~~~~~~\n\nThe parent model needs to inherit :class:`~polymorphic.admin.PolymorphicParentModelAdmin`, and\nimplement the following:\n\n* :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.base_model` should be set\n* :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.child_models` or\n  :meth:`~polymorphic.admin.PolymorphicParentModelAdmin.get_child_models` should return an iterable\n  of Model classes.\n\nThe exact implementation can depend on the way your module is structured. For simple inheritance\nsituations, :meth:`~polymorphic.admin.PolymorphicParentModelAdmin.child_models` is the best\nsolution. For large applications,\n:meth:`~polymorphic.admin.PolymorphicParentModelAdmin.get_child_models` can be used to query a\nplugin registration system.\n\nBy default, the :meth:`~polymorphic.managers.PolymorphicQuerySet.non_polymorphic` method will be\ncalled on the queryset, so only the Parent model will be provided to the list template. This is to\navoid the performance hit of retrieving child models.\n\nThis can be controlled by setting the\n:attr:`~polymorphic.admin.PolymorphicParentModelAdmin.polymorphic_list` property on the parent\nadmin. Setting it to True will provide child models to the list template.\n\nIf you use other applications such as django-reversion_ or django-mptt_, please check\n:ref:`integrations`.\n\nNote: If you are using non-integer primary keys in your model, you have to edit\n:attr:`~polymorphic.admin.PolymorphicParentModelAdmin.pk_regex`, for example\n``pk_regex = '([\\w-]+)'`` if you use :class:`~uuid.UUID` primary keys. Otherwise you cannot change\nmodel entries.\n\nThe child models\n~~~~~~~~~~~~~~~~\n\nThe admin interface of the derived models should inherit from\n:class:`~polymorphic.admin.PolymorphicChildModelAdmin`. Again,\n:attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_model` should be set in this class as\nwell. This class implements the following features:\n\n* It corrects the breadcrumbs in the admin pages.\n* It extends the template lookup paths, to look for both the parent model and child model in the\n  ``admin/app/model/change_form.html`` path.\n* It allows to set :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_form` so the derived\n  class will automatically include other fields in the form.\n* It allows to set :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_fieldsets` so the\n  derived class will automatically display any extra fields.\n* Although it must be registered with admin site, by default it's hidden from admin site index page.\n  This can be overridden by adding\n  :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show_in_index` = ``True`` in admin class.\n\n\n.. _django-reversion: https://github.com/etianen/django-reversion\n.. _django-mptt: https://github.com/django-mptt/django-mptt\n"
  },
  {
    "path": "docs/advanced.rst",
    "content": ".. _advanced-features:\n\nAdvanced Features\n=================\n\nIn the examples below, these models are being used:\n\n.. code-block:: python\n\n    from django.db import models\n    from polymorphic.models import PolymorphicModel\n\n    class ModelA(PolymorphicModel):\n        field1 = models.CharField(max_length=10)\n\n    class ModelB(ModelA):\n        field2 = models.CharField(max_length=10)\n\n    class ModelC(ModelB):\n        field3 = models.CharField(max_length=10)\n\n\nFiltering for classes (equivalent to python's :func:`isinstance`):\n------------------------------------------------------------------\n\n.. code-block:: python\n\n    >>> ModelA.objects.instance_of(ModelB)\n    [ <ModelB: id 2, field1 (CharField), field2 (CharField)>,\n      <ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]\n\nIn general, including or excluding parts of the inheritance tree:\n\n.. code-block:: python\n\n    ModelA.objects.instance_of(ModelB [, ModelC ...])\n    ModelA.objects.not_instance_of(ModelB [, ModelC ...])\n\nYou can also use this feature in Q-objects (with the same result as above):\n\n.. code-block:: python\n\n    >>> ModelA.objects.filter( Q(instance_of=ModelB) )\n\n\nPolymorphic filtering (for fields in inherited classes)\n-------------------------------------------------------\n\nFor example, cherry-picking objects from multiple derived classes anywhere in the inheritance tree,\nusing Q objects (with the syntax: ``exact model name + three _ + field name``):\n\n.. code-block:: python\n\n    >>> ModelA.objects.filter(  Q(ModelB___field2 = 'B2') | Q(ModelC___field3 = 'C3')  )\n    [ <ModelB: id 2, field1 (CharField), field2 (CharField)>,\n      <ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]\n\n\nCombining Querysets\n-------------------\n\nQuerysets could now be regarded as object containers that allow the\naggregation of different object types, very similar to python\nlists - as long as the objects are accessed through the manager of\na common base class:\n\n.. code-block:: python\n\n    >>> Base.objects.instance_of(ModelX) | Base.objects.instance_of(ModelY)\n\n    [ <ModelX: id 1, field_x (CharField)>,\n      <ModelY: id 2, field_y (CharField)> ]\n\n\nManyToManyField, ForeignKey, OneToOneField\n------------------------------------------\n\nRelationship fields referring to polymorphic models work as\nexpected: like polymorphic querysets they now always return the\nreferred objects with the same type/class these were created and\nsaved as.\n\nE.g., if in your model you define:\n\n.. code-block:: python\n\n    field1 = OneToOneField(ModelA)\n\nthen field1 may now also refer to objects of type ``ModelB`` or ``ModelC``.\n\nA :class:`~django.db.models.ManyToManyField` example:\n\n.. code-block:: python\n\n    # The model holding the relation may be any kind of model, polymorphic or not\n    class RelatingModel(models.Model):\n\n        # ManyToMany relation to a polymorphic model\n        many2many = models.ManyToManyField('ModelA')\n\n    >>> o=RelatingModel.objects.create()\n    >>> o.many2many.add(ModelA.objects.get(id=1))\n    >>> o.many2many.add(ModelB.objects.get(id=2))\n    >>> o.many2many.add(ModelC.objects.get(id=3))\n\n    >>> o.many2many.all()\n    [ <ModelA: id 1, field1 (CharField)>,\n      <ModelB: id 2, field1 (CharField), field2 (CharField)>,\n      <ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]\n\nCopying Polymorphic objects\n---------------------------\n\n**Copying polymorphic models is no different than copying regular multi-table models.** You have\ntwo options:\n\n1. Use :meth:`~django.db.models.query.QuerySet.create` and provide all field values from the\n   original instance except the primary key(s).\n2. Set the primary key attribute, and parent table pointers at all levels of inheritance to ``None``\n   and call :meth:`~django.db.models.Model.save`.\n\nThe Django documentation :ref:`offers some discussion on copying\n<topics/db/queries:copying model instances>`, including the complexity around related fields and\nmulti-table inheritance. :pypi:`django-polymorphic` offers a utility function\n:func:`~polymorphic.utils.prepare_for_copy` that resets all necessary fields on a model instance to\nprepare it for copying:\n\n.. code-block:: python\n\n    from polymorphic.utils import prepare_for_copy\n\n    obj = ModelB.objects.first()\n    prepare_for_copy(obj)\n    obj.save()\n    # obj is now a copy of the original ModelB instance\n\n\nWorking with Fixtures\n---------------------\n\nPolymorphic models work with Django's :django-admin:`dumpdata` and :django-admin:`loaddata`\ncommands just as regular models do. There are two important considerations:\n\n1. Polymorphic models are multi-table models and :django-admin:`dumpdata` serializes each table\n   separately. :pypi:`django-polymorphic` `does it's best\n   <https://github.com/django-commons/django-polymorphic/pull/814>`_ to ensure non-polymorphic managers\n   are used when creating fixtures but there may be edge cases where this fails. If you override\n   :django-admin:`dumpdata` you must make sure any polymorphic managers encountered\n   :meth:`toggle polymorphism off <polymorphic.managers.PolymorphicQuerySet.non_polymorphic>`. Other\n   usual multi-table model caveats apply. If you serialize a subset of tables in the model\n   inheritance you may generate corrupt data or \"upcast\" your models if child tables were omitted.\n2. Polymorphic models rely on the :class:`~django.contrib.contenttypes.models.ContentType`\n   framework. When serializing and deserializing polymorphic models, the\n   ``polymorphic_ctype`` field must be handled correctly. If there is any question about if the\n   content type primary keys are or will be different between the source and target database you\n   should use the :option:`--natural-foreign <dumpdata.--natural-foreign>` flag to serialize those\n   relations by-value. Polymorphism introduces no special consideration here - any model using\n   contenttypes, polymorphic or not, must handle this correctly.\n\n.. note::\n\n    Prior documentation urged users to use both :option:`--natural-primary <dumpdata.--natural-primary>`\n    and :option:`--natural-foreign <dumpdata.--natural-foreign>` flags when dumping polymorphic\n    models. This is not necessary and only needs to be done when the primary keys are not guaranteed\n    to match or be available at the target database.\n\nLoading Fixtures (loaddata)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nFixtures should be loadable as normal with :django-admin:`loaddata`. However, if there are problems\nwith the ``polymorphic_ctype`` references, you may fix them using\n:func:`~polymorphic.utils.reset_polymorphic_ctype`:\n\n.. code-block:: python\n\n    from polymorphic.utils import reset_polymorphic_ctype\n    from myapp.models import Animal, Dog, Cat\n\n    # Reset polymorphic_ctype for all models in the inheritance tree\n    reset_polymorphic_ctype(Animal, Dog, Cat)\n\nUsing Third Party Models (without modifying them)\n-------------------------------------------------\n\nThird party models can be used as polymorphic models without\nrestrictions by subclassing them. E.g. using a third party\nmodel as the root of a polymorphic inheritance tree:\n\n.. code-block:: python\n\n    from thirdparty import ThirdPartyModel\n\n    class MyThirdPartyBaseModel(PolymorphicModel, ThirdPartyModel):\n        pass    # or add fields\n\nOr instead integrating the third party model anywhere into an\nexisting polymorphic inheritance tree:\n\n.. code-block:: python\n\n    class MyBaseModel(SomePolymorphicModel):\n        my_field = models.CharField(max_length=10)\n\n    class MyModelWithThirdParty(MyBaseModel, ThirdPartyModel):\n        pass    # or add fields\n\n\nNon-Polymorphic Queries\n-----------------------\n\nIf you insert :meth:`~polymorphic.managers.PolymorphicQuerySet.non_polymorphic` anywhere into the\nquery chain, then :pypi:`django-polymorphic` will simply leave out the final step of retrieving the\nreal objects, and the manager/queryset will return objects of the type of the base class you used\nfor the query, like vanilla Django would (``ModelA`` in this example).\n\n.. code-block:: python\n\n    >>> qs=ModelA.objects.non_polymorphic().all()\n    >>> qs\n    [ <ModelA: id 1, field1 (CharField)>,\n      <ModelA: id 2, field1 (CharField)>,\n      <ModelA: id 3, field1 (CharField)> ]\n\nThere are no other changes in the behaviour of the queryset. For example,\nenhancements for ``filter()`` or ``instance_of()`` etc. still work as expected.\nIf you do the final step yourself, you get the usual polymorphic result:\n\n.. code-block:: python\n\n    >>> ModelA.objects.get_real_instances(qs)\n    [ <ModelA: id 1, field1 (CharField)>,\n      <ModelB: id 2, field1 (CharField), field2 (CharField)>,\n      <ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]\n\n\nAbout Queryset Methods\n----------------------\n\n*   :meth:`~django.db.models.query.QuerySet.annotate` and\n    :meth:`~django.db.models.query.QuerySet.aggregate` work just as usual, with the addition that\n    the ``ModelX___field`` syntax can be used for the keyword arguments (but not for the non-keyword\n    arguments).\n\n*   :meth:`~django.db.models.query.QuerySet.order_by` similarly supports the ``ModelX___field``\n    syntax for specifying ordering through a field in a submodel.\n\n*   :meth:`~django.db.models.query.QuerySet.distinct` works as expected. It only regards the fields\n    of the base class, but this should never make a difference.\n\n*   :meth:`~django.db.models.query.QuerySet.select_related` works just as usual, but it can not\n    (yet) be used to select relations in inherited models (like\n    ``ModelA.objects.select_related('ModelC___fieldxy')`` )\n\n*   :meth:`~django.db.models.query.QuerySet.extra` works as expected (it returns polymorphic\n    results) but currently has one restriction: The resulting objects are required to have a unique\n    primary key within the result set - otherwise an error is thrown (this case could be made to\n    work, however it may be mostly unneeded).. The keyword-argument \"polymorphic\" is no longer\n    supported. You can get back the old non-polymorphic behaviour by using\n    ``ModelA.objects.non_polymorphic().extra(...)``.\n\n*   :meth:`~polymorphic.managers.PolymorphicQuerySet.get_real_instances` allows you to turn a\n    queryset or list  of base model objects efficiently into the real objects.\n    For example, you could do ``base_objects_queryset=ModelA.extra(...).non_polymorphic()``\n    and then call ``real_objects=base_objects_queryset.get_real_instances()``. Or alternatively\n    ``real_objects=ModelA.objects.get_real_instances(base_objects_queryset_or_object_list)``\n\n*   :meth:`~django.db.models.query.QuerySet.values` &\n    :meth:`~django.db.models.query.QuerySet.values_list` currently do not return polymorphic\n    results. This may change in the future however. If you want to use these methods now, it's best\n    if you use ``Model.base_objects.values...`` as this is guaranteed to not change.\n\n*   :meth:`~django.db.models.query.QuerySet.defer` and :meth:`~django.db.models.query.QuerySet.only`\n    work as expected. On Django 1.5+ they support the ``ModelX___field`` syntax, but on Django 1.4\n    it is only possible to pass fields on the base model into these methods.\n\n\nUsing enhanced Q-objects in any Places\n--------------------------------------\n\nThe queryset enhancements (e.g. :meth:`~polymorphic.managers.PolymorphicQuerySet.instance_of`)\nonly work as arguments to the member functions of a polymorphic queryset.  Occasionally it may\nbe useful to be able to use Q objects with these enhancements in other places. As Django doesn't\nunderstand these enhanced Q objects, you need to transform them manually into normal Q objects\nbefore you can feed them to a Django queryset or function:\n\n.. code-block:: python\n\n    normal_q_object = ModelA.translate_polymorphic_Q_object( Q(instance_of=Model2B) )\n\nThis function cannot be used at model creation time however (in models.py), as it may need to access\nthe ContentTypes database table.\n\n\nNicely Displaying Polymorphic Querysets\n---------------------------------------\n\nIn order to get the output as seen in all examples here, you need to use the\n:class:`~polymorphic.showfields.ShowFieldType` class mixin:\n\n.. code-block:: python\n\n    from polymorphic.models import PolymorphicModel\n    from polymorphic.showfields import ShowFieldType\n\n    class ModelA(ShowFieldType, PolymorphicModel):\n        field1 = models.CharField(max_length=10)\n\nYou may also use :class:`~polymorphic.showfields.ShowFieldContent` or\n:class:`~polymorphic.showfields.ShowFieldTypeAndContent` to display additional information when\nprinting querysets (or converting them to text).\n\nWhen showing field contents, they will be truncated to 20 characters. You can modify this behavior\nby setting a class variable in your model like this:\n\n.. code-block:: python\n\n    class ModelA(ShowFieldType, PolymorphicModel):\n        polymorphic_showfield_max_field_width = 20\n        ...\n\nSimilarly, pre-V1.0 output formatting can be re-estated by using\n``polymorphic_showfield_old_format = True``.\n\n\nCreate Children from Parents (Downcasting)\n------------------------------------------\n\nYou can create an instance of a subclass from an existing instance of a superclass using the\n:meth:`~polymorphic.managers.PolymorphicManager.create_from_super` method\nof the subclass's manager. For example:\n\n.. code-block:: python\n\n    super_instance = ModelA.objects.get(id=1)\n    sub_instance = ModelB.objects.create_from_super(super_instance, field2='value2')\n\nThe restriction is that ``super_instance`` must be an instance of the direct superclass of\n``ModelB``, and any required fields of ``ModelB`` must be provided as keyword arguments. If multiple\nlevels of subclassing are involved, you must call this method multiple times to \"promote\" each\nlevel.\n\nDelete Children, Leaving Parents (Upcasting)\n--------------------------------------------\n\nThe reverse operation of :meth:`~polymorphic.managers.PolymorphicManager.create_from_super` is to\ndelete the subclass instance while keeping the superclass instance. This can be done using the\n``keep_parents=True`` argument to :meth:`~django.db.models.Model.delete`. :pypi:`django-polymorphic`\nensures that the ``polymorphic_ctype`` fields of the superclass instances are updated accordingly\nwhen doing this.\n\n.. _restrictions:\n\nRestrictions & Caveats\n----------------------\n\n*   Database Performance regarding concrete Model inheritance in general. Please see\n    :ref:`performance`.\n\n*   Queryset methods :meth:`~django.db.models.query.QuerySet.values`,\n    :meth:`~django.db.models.query.QuerySet.values_list`, and\n    :meth:`~django.db.models.query.QuerySet.select_related` are not yet fully supported (see above).\n    :meth:`~django.db.models.query.QuerySet.extra` has one restriction: the resulting objects are\n    required to have a unique primary key within the result set.\n\n*   Diamond shaped inheritance: There seems to be a general problem with diamond shaped multiple\n    model inheritance with Django models (tested with V1.1 - V1.3). An example\n    `is here <http://code.djangoproject.com/ticket/10808>`_. This problem is aggravated when trying\n    to enhance :class:`~django.db.models.Model` by subclassing it instead of modifying Django core\n    (as we do here with :class:`~polymorphic.models.PolymorphicModel`).\n\n*   The enhanced filter-definitions/Q-objects only work as arguments for the methods of the\n    polymorphic querysets. Please see above for ``translate_polymorphic_Q_object``.\n\n*   When using the :django-admin:`dumpdata` management command on polymorphic tables\n    (or any table that has a reference to :class:`~django.contrib.contenttypes.models.ContentType`),\n    include the :option:`--natural-primary <dumpdata.--natural-primary>` and\n    :option:`--natural-foreign <dumpdata.--natural-foreign>` flags in the arguments.\n\n*   If the ``polymorphic_ctype_id`` on the base table points to the wrong\n    :class:`~django.contrib.contenttypes.models.ContentType` (this can happen if you delete child\n    rows manually with raw SQL, ``DELETE FROM table``), then polymorphic queries will elide the\n    corresponding model objects:\n\n    *   ``BaseClass.objects.all()`` will **exclude** these rows (it filters for existing child\n        types).\n    *   ``BaseClass.objects.non_polymorphic().all()`` will behave as normal - but polymorphic\n        behavior for the affected rows will be undefined - for instance,\n        :meth:`~polymorphic.managers.PolymorphicQuerySet.get_real_instances` will raise an\n        exception.\n\n    Always use ``instance.delete()`` or ``QuerySet.delete()`` to ensure cascading deletion of the\n    base row. If you must delete manually, ensure you also delete the corresponding row from the\n    base table.\n\n*   There will be problems if the :class:`~django.contrib.contenttypes.models.ContentType` cache\n    becomes out of sync with the database. This can especially happen in tests. You should ensure\n    that the cache is cleared (\n    :meth:`~django.contrib.contenttypes.models.ContentTypeManager.clear_cache`) whenever this\n    happens. In tests this happens when if :django-admin:`flush` is called by the teardown sequence\n    in :class:`~django.test.TransactionTestCase`.\n\n.. old links:\n    - http://code.djangoproject.com/wiki/ModelInheritance\n    - http://lazypython.blogspot.com/2009/02/second-look-at-inheritance-and.html\n    - http://www.djangosnippets.org/snippets/1031/\n    - http://www.djangosnippets.org/snippets/1034/\n    - http://groups.google.com/group/django-developers/browse_frm/thread/7d40ad373ebfa912/a20fabc661b7035d?lnk=gst&q=model+inheritance+CORBA#a20fabc661b7035d\n    - http://groups.google.com/group/django-developers/browse_thread/thread/9bc2aaec0796f4e0/0b92971ffc0aa6f8?lnk=gst&q=inheritance#0b92971ffc0aa6f8\n    - http://groups.google.com/group/django-developers/browse_thread/thread/3947c594100c4adb/d8c0af3dacad412d?lnk=gst&q=inheritance#d8c0af3dacad412d\n    - http://groups.google.com/group/django-users/browse_thread/thread/52f72cffebb705e/b76c9d8c89a5574f\n    - http://peterbraden.co.uk/article/django-inheritance\n    - http://www.hopelessgeek.com/2009/11/25/a-hack-for-multi-table-inheritance-in-django\n    - http://stackoverflow.com/questions/929029/how-do-i-access-the-child-classes-of-an-object-in-django-without-knowing-the-name/929982#929982\n    - http://stackoverflow.com/questions/1581024/django-inheritance-how-to-have-one-method-for-all-subclasses\n    - http://groups.google.com/group/django-users/browse_thread/thread/cbdaf2273781ccab/e676a537d735d9ef?lnk=gst&q=polymorphic#e676a537d735d9ef\n    - http://groups.google.com/group/django-users/browse_thread/thread/52f72cffebb705e/bc18c18b2e83881e?lnk=gst&q=model+inheritance#bc18c18b2e83881e\n    - http://code.djangoproject.com/ticket/10808\n    - http://code.djangoproject.com/ticket/7270\n"
  },
  {
    "path": "docs/api/index.rst",
    "content": "API Documentation\n=================\n\n.. _base:\n\n.. automodule:: polymorphic\n   :members:\n   :show-inheritance:\n   :inherited-members:\n\n.. toctree::\n\n   polymorphic.admin\n   polymorphic.contrib/index\n   polymorphic.formsets\n   polymorphic.managers\n   polymorphic.models\n   polymorphic.deletion\n   polymorphic.showfields\n   polymorphic.templatetags/index\n   polymorphic.utils\n"
  },
  {
    "path": "docs/api/polymorphic.admin.rst",
    "content": "polymorphic.admin\n=================\n\nModelAdmin classes\n------------------\n\nThe ``PolymorphicParentModelAdmin`` class\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. autoclass:: polymorphic.admin.PolymorphicParentModelAdmin\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n\nThe ``PolymorphicChildModelAdmin`` class\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. autoclass:: polymorphic.admin.PolymorphicChildModelAdmin\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n\nList filtering\n--------------\n\nThe ``PolymorphicChildModelFilter`` class\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. autoclass:: polymorphic.admin.PolymorphicChildModelFilter\n    :show-inheritance:\n\n\nInlines support\n---------------\n\nThe ``StackedPolymorphicInline`` class\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. autoclass:: polymorphic.admin.StackedPolymorphicInline\n    :show-inheritance:\n\n\nThe ``GenericStackedPolymorphicInline`` class\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. autoclass:: polymorphic.admin.GenericStackedPolymorphicInline\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n\nThe ``PolymorphicInlineSupportMixin`` class\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. autoclass:: polymorphic.admin.PolymorphicInlineSupportMixin\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n\nLow-level classes\n-----------------\n\nThese classes are useful when existing parts of the admin classes.\n\n\n.. autoclass:: polymorphic.admin.PolymorphicModelChoiceForm\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n\n.. autoclass:: polymorphic.admin.PolymorphicInlineModelAdmin\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n\n.. autoclass:: polymorphic.admin.GenericPolymorphicInlineModelAdmin\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n\n.. autoclass:: polymorphic.admin.PolymorphicInlineAdminForm\n    :show-inheritance:\n\n\n.. autoclass:: polymorphic.admin.PolymorphicInlineAdminFormSet\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/polymorphic.contrib/drf.rst",
    "content": "drf.serializers\n===============\n\n.. automodule:: polymorphic.contrib.drf.serializers\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/polymorphic.contrib/extra_views.rst",
    "content": "extra_views\n===========\n\n.. automodule:: polymorphic.contrib.extra_views\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/polymorphic.contrib/guardian.rst",
    "content": "guardian\n========\n\n.. automodule:: polymorphic.contrib.guardian\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/polymorphic.contrib/index.rst",
    "content": "polymorphic.contrib\n===================\n\n.. _contrib:\n\n.. automodule:: polymorphic.contrib\n   :members:\n   :show-inheritance:\n   :inherited-members:\n\n.. toctree::\n\n   extra_views\n   guardian\n   drf\n"
  },
  {
    "path": "docs/api/polymorphic.deletion.rst",
    "content": "polymorphic.deletion\n====================\n\n.. automodule:: polymorphic.deletion\n\n.. autoclass:: polymorphic.deletion.PolymorphicGuard\n    :members: __call__\n    :show-inheritance:\n\n.. autoclass:: polymorphic.deletion.PolymorphicGuardSerializer\n    :members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/polymorphic.formsets.rst",
    "content": "polymorphic.formsets\n====================\n\n.. automodule:: polymorphic.formsets\n\n\nModel formsets\n--------------\n\n.. autofunction:: polymorphic.formsets.polymorphic_modelformset_factory\n\n.. autoclass:: polymorphic.formsets.PolymorphicFormSetChild\n\n\nInline formsets\n---------------\n\n.. autofunction:: polymorphic.formsets.polymorphic_inlineformset_factory\n\n\nGeneric formsets\n----------------\n\n.. autofunction:: polymorphic.formsets.generic_polymorphic_inlineformset_factory\n\n\nLow-level features\n------------------\n\nThe internal machinery can be used to extend the formset classes. This includes:\n\n.. autofunction:: polymorphic.formsets.polymorphic_child_forms_factory\n\n.. autoclass:: polymorphic.formsets.BasePolymorphicModelFormSet\n    :show-inheritance:\n\n.. autoclass:: polymorphic.formsets.BasePolymorphicInlineFormSet\n    :show-inheritance:\n\n.. autoclass:: polymorphic.formsets.BaseGenericPolymorphicInlineFormSet\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/polymorphic.managers.rst",
    "content": "polymorphic.managers\n====================\n\n.. automodule:: polymorphic.managers\n\n\nThe ``PolymorphicManager`` class\n--------------------------------\n\n\n.. autoclass:: polymorphic.managers.PolymorphicManager\n    :members:\n    :show-inheritance:\n\n\nThe ``PolymorphicQuerySet`` class\n---------------------------------\n\n.. autoclass:: polymorphic.managers.PolymorphicQuerySet\n    :members:\n    :show-inheritance:\n\n\n.. _type_hint_descriptors:\n\nType Hint Descriptors\n---------------------\n\n.. autodata:: polymorphic.managers._All\n.. autodata:: polymorphic.managers._Base\n.. autodata:: polymorphic.managers._Through\n.. autodata:: polymorphic.managers._Nullable\n\n.. autodata:: polymorphic.managers.Nullable\n\n.. autoclass:: polymorphic.managers.PolymorphicForwardManyToOneDescriptor\n    :members:\n    :show-inheritance:\n\n.. autoclass:: polymorphic.managers.PolymorphicReverseManyToOneDescriptor\n    :members:\n    :show-inheritance:\n\n.. autoclass:: polymorphic.managers.PolymorphicForwardOneToOneDescriptor\n    :members:\n    :show-inheritance:\n\n.. autoclass:: polymorphic.managers.PolymorphicReverseOneToOneDescriptor\n    :members:\n    :show-inheritance:\n\n.. autoclass:: polymorphic.managers.PolymorphicManyToManyDescriptor\n    :members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/polymorphic.models.rst",
    "content": "polymorphic.models\n==================\n\n.. automodule:: polymorphic.models\n    :members:\n    :show-inheritance:\n    :private-members:\n"
  },
  {
    "path": "docs/api/polymorphic.showfields.rst",
    "content": "polymorphic.showfields\n======================\n\n.. automodule:: polymorphic.showfields\n   :members:\n   :show-inheritance:\n   :inherited-members:\n"
  },
  {
    "path": "docs/api/polymorphic.templatetags/index.rst",
    "content": "polymorphic.templatetags\n========================\n\n.. _templatetags:\n\n.. automodule:: polymorphic.templatetags\n\n.. toctree::\n\n   polymorphic_admin_tags\n   polymorphic_formset_tags\n"
  },
  {
    "path": "docs/api/polymorphic.templatetags/polymorphic_admin_tags.rst",
    "content": "polymorphic_admin_tags\n======================\n\n.. automodule:: polymorphic.templatetags.polymorphic_admin_tags\n    :members:\n"
  },
  {
    "path": "docs/api/polymorphic.templatetags/polymorphic_formset_tags.rst",
    "content": "polymorphic_formset_tags\n========================\n\n.. automodule:: polymorphic.templatetags.polymorphic_formset_tags\n    :members:\n"
  },
  {
    "path": "docs/api/polymorphic.utils.rst",
    "content": "polymorphic.utils\n=================\n\n.. automodule:: polymorphic.utils\n    :members:\n"
  },
  {
    "path": "docs/changelog/drf.rst",
    "content": ".. :drf_changelog:\n\ndrf\n---\n\nBelow is the changelog for the :pypi:`django-rest-polymorphic` before it was included into\n:pypi:`django-polymorphic`.\n\n\n0.1.10 (2022-07-17)\n+++++++++++++++++++\n\n* Allow partial updates without resourcetype.\n\n0.1.9 (2020-04-02)\n++++++++++++++++++\n\n* Fix validation error and update versions.\n\n0.1.8 (2018-10-11)\n++++++++++++++++++\n\n* Add Python 3.7 and Django 2.1 support.\n\n0.1.7 (2018-06-06)\n++++++++++++++++++\n\n0.1.6 (2018-06-03)\n++++++++++++++++++\n\n* Add Django REST Framework 3.8 support.\n\n0.1.5 (2018-03-29)\n++++++++++++++++++\n\n* Fix validation for nested serializers.\n\n0.1.4 (2018-02-10)\n++++++++++++++++++\n\n* Fix serializer instantiation.\n\n0.1.3 (2018-02-09)\n++++++++++++++++++\n\n* Add Python 2.7 support.\n\n0.1.2 (2018-01-30)\n++++++++++++++++++\n\n* Add Django 2.0 support.\n* Drop Django 1.10 support.\n\n0.1.1 (2017-12-08)\n++++++++++++++++++\n\n* Add example project.\n\n0.1.0 (2017-12-07)\n++++++++++++++++++\n\n* First release on PyPI.\n"
  },
  {
    "path": "docs/changelog/index.rst",
    "content": "Changelog\n=========\n\nv4.11.3 (2026-04-30)\n--------------------\n\n* `django-polymorphic <https://github.com/django-commons/django-polymorphic>`_ has a new home at the\n  `django-commons <https://django-commons.org>`_ organization!\n\n  * This was necessary because `Jazzband is sunsetting\n    <https://github.com/jazzband/help/issues/425>`_. Jazzband was a great home that facilitated\n    maintainership transfer of this package. We are extremely thankful for all of `jezdez's\n    <https://github.com/jezdez>`_ work over the years running the Jazzband community.\n  * We limited publishing access to restrict our supply chain vulnerability surface. If you were a\n    former contributor/maintainer of this project and wish to contribute again please `join\n    django-commons <https://github.com/django-commons/membership>`_ and let us know.\n\n\nv4.11.2 (2026-03-05)\n--------------------\n\n* Add typed classifier.\n* Remove unnecessary :func:`~django.utils.safestring.mark_safe` from `admin context variables\n  <https://github.com/bckohan/django-polymorphic/commit/6c6114d4c72bb6db73eee54ce58beb93409ec137>`_.\n* Significant security tightening of CI pipelines including security focused static\n  analysis tools: zizmor, CodeQL and bandit.\n\nv4.11.1 (2026-02-20)\n--------------------\n\n* Fixed `Release 4.11 causes UPDATE query to be routed to reader DB <https://github.com/django-commons/django-polymorphic/issues/865>`_\n\nv4.11.0 (2026-02-05)\n--------------------\n\n* Implemented `Add type hints for django-polymorphic <https://github.com/django-commons/django-polymorphic/issues/647>`_\n\nv4.9.2, v4.10.5 (2026-01-26)\n----------------------------\n\n* Fixed `Bad warning messages polymorphic.W001 and polymorphic.W002 when deriving from PolymorphicModel <https://github.com/django-commons/django-polymorphic/issues/857>`_\n* Fixed `Unexpected return type of polymorphic data since 4.9.1 <https://github.com/django-commons/django-polymorphic/issues/855>`_\n* Fixed `related item missing <https://github.com/django-commons/django-polymorphic/issues/851>`_\n\nv4.10.4 (2026-01-19)\n--------------------\n\n* Documented `model-bakery integration and test considerations <https://github.com/django-commons/django-polymorphic/issues/849>`_\n\nv4.9.1, v4.10.3 (2026-01-15)\n----------------------------\n\n* Fixed `Meta.base_manager_name is not respected <https://github.com/django-commons/django-polymorphic/issues/845>`_\n\n\nv4.10.2 (2026-01-14)\n--------------------\n\n* Fixed `changelog url on pypi is broken <https://github.com/django-commons/django-polymorphic/issues/842>`_\n\nv4.10.1 (2026-01-13)\n--------------------\n\n* Fixed `Django-polymorphic does not update the PolymorphicSerializer's validated_data after running the child's validation method <https://github.com/django-commons/django-polymorphic/pull/378>`_\n\nv4.10.0 (2026-01-13)\n--------------------\n\nThis release is primarily an integrations release. Many tests were added for the documented\nintergrations and a few bugs were fixed. Some warnings were converted to system checks. The two most\nnotable changes are:\n\n1. The :pypi:`django-rest-polymorphic` package is now part of :pypi:`django-polymorphic`.\n2. In queries that need content types that are not cached those fetches are `now embedded as\n   subqueries <https://github.com/django-commons/django-polymorphic/pull/828>`_. This reduces the total\n   number of queries needed but most importantly it eliminates `a class of errors\n   <https://github.com/django-commons/django-polymorphic/issues/383>`_ that could happen if\n   Manager/QuerySet methods that should not but do generate database queries are called before\n   models are loaded (e.g. get_queryset()).\n\nIssues\n~~~~~~\n\n* Implemented `Bring django-rest-polymorphic into this package. <https://github.com/django-commons/django-polymorphic/issues/655>`_\n\n  .. tip::\n\n     :pypi:`django-rest-polymorphic` is now part of :pypi:`django-polymorphic`. You must update\n     your import paths from ``rest_polymorphic.serializers`` to\n     :mod:`polymorphic.contrib.drf.serializers`.\n\n* Fixed `Guardian contenttype integraton hook is extremely brittle <https://github.com/django-commons/django-polymorphic/issues/836>`_\n* Fixed `Models defined after load break inheritance utility caches <https://github.com/django-commons/django-polymorphic/issues/827>`_\n* Fixed `get_queryset() results in db queries for proxy models <https://github.com/django-commons/django-polymorphic/issues/824>`_\n* Implemented `System check error for PolymorphicManager marked as use_in_migrations <https://github.com/django-commons/django-polymorphic/issues/820>`_\n* Implemented `Manager warnings should be system checks instead. <https://github.com/django-commons/django-polymorphic/issues/819>`_\n\nv4.9.0 (2026-01-09)\n-------------------\n\n.. note::\n\n  This update may generate new migrations for your polymorphic models, similar to the following.\n  This is ok and an expected side effect of fixing `#815\n  <https://github.com/django-commons/django-polymorphic/issues/815>`_\n\n  .. code-block:: python\n\n      migrations.AlterModelOptions(\n          name='modelname',\n          options={},\n      )\n\n* Fixed `PolymorphicModel.base_manager is the same as default_manager when custom default manager is supplied. <https://github.com/django-commons/django-polymorphic/issues/815>`_\n* Fixed `Use non-polymorphic managers for all invocations of dumpdata <https://github.com/django-commons/django-polymorphic/pull/814>`_\n* Documented `Fixture usage <https://github.com/django-commons/django-polymorphic/pull/791>`_\n\nv4.8.0 (2026-01-08)\n-------------------\n\n* Fixed `PolymorphicFormSetChild overrides form exclude <https://github.com/django-commons/django-polymorphic/issues/578>`_\n* Fixed `Issue with polymorphic_ctype when populating polymorphic inline formsets. <https://github.com/django-commons/django-polymorphic/issues/549>`_\n* Fixed `Nested polymorphic_inline_formsets gives AttributeError: 'NoneType' object has no attribute 'get_real_instance_class' <https://github.com/django-commons/django-polymorphic/issues/363>`_\n\nv4.7.0 (2026-01-07)\n-------------------\n\nFixed a few outstanding admin bugs, updated documentation and added more admin tests for things like\nm2m relationships.\n\n* Documented `How to handle non-admin polymorphic forms? <https://github.com/django-commons/django-polymorphic/issues/346>`_\n* Fixed `Admin: add view popup breaks if initial submit has validation error <https://github.com/django-commons/django-polymorphic/issues/612>`_\n* Fixed `Filters are not preserved in polymorphic parent admin <https://github.com/django-commons/django-polymorphic/issues/356>`_\n* Fixed `Admin change form doesn't preserve changelist filter <https://github.com/django-commons/django-polymorphic/issues/125>`_\n\nv4.6.0 (2026-01-05)\n-------------------\n\nThe release fixes longstanding bugs with respect to expected ORM behavior. For a complete list of\nchanges see the `v4.6.0 release\n<https://github.com/django-commons/django-polymorphic/releases/tag/v4.6.0>`_.\n\n* Fixed `get_real_instance() should also gracefully retry parents on failure (best effort) <https://github.com/django-commons/django-polymorphic/issues/784>`_\n* Fixed `polymorphic_primary_key_name no longer points to base classes polymorphic field <https://github.com/django-commons/django-polymorphic/issues/758>`_\n* Fixed `create_from_super needs to run atomically. <https://github.com/django-commons/django-polymorphic/issues/744>`_\n* Fixed `Support polymorphic models that have different pk fields/values at different levels of the hierarchy. <https://github.com/django-commons/django-polymorphic/issues/686>`_\n* Fixed `Issue with .delete(keep_parents=True) <https://github.com/django-commons/django-polymorphic/issues/645>`_\n* Fixed `PolymorphicChildModelAdmin with show_in_index=False in Django Admin with nav_sidebar <https://github.com/django-commons/django-polymorphic/issues/497>`_\n* Fixed `ForeignKeyViolation when trying to save an entity when using a non-default db <https://github.com/django-commons/django-polymorphic/issues/486>`_\n* Fixed `Content types pulled from wrong database when using database routers <https://github.com/django-commons/django-polymorphic/issues/446>`_\n* Fixed `Copying Polymorphic objects for below the first level not working <https://github.com/django-commons/django-polymorphic/issues/414>`_\n* Fixed `Getting a specific element from a queryset in an post_delete returns None <https://github.com/django-commons/django-polymorphic/issues/347>`_\n* Fixed `Reverse related object descriptor is overwritten on class <https://github.com/django-commons/django-polymorphic/issues/71>`_\n\nv4.3.1, v4.4.2, v4.5.2 (2026-01-01)\n-----------------------------------\n\n* Fixed `Significant performance regression on polymorphic queryset iteration <https://github.com/django-commons/django-polymorphic/pull/781>`_\n\nv4.5.1 (2025-12-24)\n-------------------\n\n* Fixed `4.5.0 generates a lot of migrations on my project <https://github.com/django-commons/django-polymorphic/pull/759>`_\n* Fixed `Annotations with F <https://github.com/django-commons/django-polymorphic/pull/755>`_\n* Fixed `show_in_index=False visibility in admin sites and sidebar <https://github.com/django-commons/django-polymorphic/pull/760>`_\n\nv4.5.0 (2025-12-22)\n-------------------\n\nThe release fixes longstanding bugs with respect to deletion of polymorphic models.\n\n.. warning::\n\n  This version has a bug that generates unnecessary migrations - use 4.5.1 instead!\n\n* Implemented `Deletion fixes <https://github.com/django-commons/django-polymorphic/pull/746>`_\n  **This release fixes the longstanding polymorphic deletion bug.** The fix should be transparent\n  and not generate new migrations files. If you experience any issues, please report them.\n* Fixed `AttributeError Using .alias() On Polymorphic Querysets <https://github.com/django-commons/django-polymorphic/pull/745>`_\n\nv4.4.1 (2025-12-15)\n-------------------\n\n* `Fix infinite recursion bug when using only() <https://github.com/django-commons/django-polymorphic/pull/739>`_\n\nv4.4.0 (2025-12-14)\n-------------------\n\n* Implemented `Add create_from_super method and test <https://github.com/django-commons/django-polymorphic/pull/684>`_\n* Implemented `Move model definition errors to system checks <https://github.com/django-commons/django-polymorphic/pull/693>`_\n* Fixed `Fix ordering of stacked inline admin forms on add. <https://github.com/django-commons/django-polymorphic/pull/719>`_\n* Fixed `Change model definition errors to be system checks <https://github.com/django-commons/django-polymorphic/pull/693>`_\n* Fixed `Replace deepcopy of the Q object <https://github.com/django-commons/django-polymorphic/pull/543>`_\n\nv4.3.0 (2025-12-09)\n-------------------\n\n* Fixed `Resolve primary key name correctly. <https://github.com/django-commons/django-polymorphic/pull/620>`_\n* Implemented `Include get_child_inlines() hook in stacked inline admin forms. <https://github.com/django-commons/django-polymorphic/pull/681>`_\n* Fixed `multi-database support in inheritance accessors. <https://github.com/django-commons/django-polymorphic/pull/550>`_\n* Fixed `Caching in inheritance accessor functions <https://github.com/django-commons/django-polymorphic/pull/510>`_\n* Fixed `Foreign key resolves to parent class when using abstract models <https://github.com/django-commons/django-polymorphic/issues/437>`_\n* Fixed `Support Q expressions that contain subquery expressions <https://github.com/django-commons/django-polymorphic/pull/572>`_\n\nv4.2.0 (2025-12-04)\n-------------------\n\n* Fixed `The objects which were transmogrified aren't initialized correctly if they implement __init__ method. <https://github.com/django-commons/django-polymorphic/issues/615>`_\n* Implemented `Defer to chunk_size parameter on .iterators for fetching get_real_instances() <https://github.com/django-commons/django-polymorphic/pull/672>`_\n* Fixed `Show full admin context (breadcrumb and logout nav) in model type selection admin form <https://github.com/django-commons/django-polymorphic/pull/580>`_\n* Fixed `Issue with Autocomplete Fields in StackedPolymorphicInline.Child Inline <https://github.com/django-commons/django-polymorphic/issues/546>`_\n* Support Python 3.14 and Django 6.0, drop support for EOL python 3.9, Django 3.2, 4.0, 4.1 and 5.0.\n* `Modernized package management with new build, test, docs tooling and improved CI\n  <https://github.com/django-commons/django-polymorphic/issues/651>`_.\n\nv4.1.0 (2025-05-20)\n-------------------\n\n* `Fixed a bug on Django 5 <https://github.com/django-commons/django-polymorphic/pull/621>`_ where\n  `aggregation queries could result in None-type errors <https://github.com/django-commons/django-polymorphic/issues/616>`_\n* `Use css variables in the admin css <https://github.com/django-commons/django-polymorphic/pull/622>`_\n\nv4.0.0 (2025-05-20)\n-------------------\n\n**There were no breaking changes in this major release**\n\n*This was the first release under* `Jazzband <https://jazzband.co/>`_.\n\nThere were many updates modernizing the package and incorporating Jazzband standards:\n\n* Updates to documentation\n* Formatting and linting with ruff\n* Moving to GHA from Travis CI\n* Switch to pytest\n\nChanges that touched the core package code were:\n\n* Remove `legacy Django/python version checks <https://github.com/django-commons/django-polymorphic/pull/567>`_\n* Replace `string formats with f-strings <https://github.com/django-commons/django-polymorphic/pull/566>`_\n* Removed `deprecated usage of package_resources <https://github.com/django-commons/django-polymorphic/pull/541>`_\n    - as of Python 3.12 package_resources was removed. To get prior releases to work on >3.12 you\n      would also need to install `setuptools <https://pypi.org/project/setuptools/>`_.\n* Fixed `multi field lines do not render in the admin <https://github.com/django-commons/django-polymorphic/pull/539>`_\n* Fixed `dark mode rendering in the polymorphic admin <https://github.com/django-commons/django-polymorphic/pull/508>`_\n\nv3.1.0 (2021-11-18)\n-------------------\n\n* Added support for Django 4.0.\n* Fixed crash when the admin \"add type\" view has no choices; will show a permission denied.\n* Fixed missing ``locale`` folder in sdist.\n* Fixed missing ``QuerySet.bulk_create(.., ignore_conflicts=True)`` parameter support.\n* Fixed ``FilteredRelation`` support.\n* Fixed supporting class keyword arguments in model definitions for ``__init_subclass__()``.\n* Fixed including ``polymorphic.tests.migrations`` in the sdist.\n* Fixed non-polymorphic parent handling, which has no ``_base_objects``.\n* Fixed missing ``widgets`` support for ``modelform_factory()``.\n* Fixed ``has_changed`` handling for ``polymorphic_ctype_id`` due to implicit str to int\n  conversions.\n* Fixed ``Q`` object handling when lists are used (e.g. in django-advanced-filters_).\n* Fixed Django Admin support when using a script-prefix.\n\nMany thanks to everyone providing clear pull requests!\n\n\nv3.0.0 (2020-08-21)\n-------------------\n\n* Support for Django 3.X\n* Dropped support for python 2.X\n* A lot of various fixes and improvements by various authors. Thanks a lot!\n\n\nv2.1.2 (2019-07-15)\n-------------------\n\n* Fix ``PolymorphicInlineModelAdmin`` media jQuery include for Django 2.0+\n\n\nv2.1.1 (2019-07-15)\n-------------------\n\n* Fixed admin import error due to ``isort`` changes.\n\n\nv2.1 (2019-07-15)\n-----------------\n\n* Added Django 2.2 support.\n* Changed ``.non_polymorphic()``, to use a different iterable class that completely circumvent\n  polymorphic.\n* Changed SQL for ``instance_of`` filter: use ``IN`` statement instead of ``OR`` clauses.\n* Changed queryset iteration to implement ``prefetch_related()`` support.\n* Fixed Django 3.0 alpha compatibility.\n* Fixed compatibility with current django-extra-views_ in ``polymorphic.contrib.extra_views``.\n* Fixed ``prefetch_related()`` support on polymorphic M2M relations.\n* Fixed model subclass ``___`` selector for abstract/proxy models.\n* Fixed model subclass ``___`` selector for models with a custom\n  ``OneToOneField(parent_link=True)``.\n* Fixed unwanted results on calling ``queryset.get_real_instances([])``.\n* Fixed unwanted ``TypeError`` exception when ``PolymorphicTypeInvalid`` should have raised.\n* Fixed hiding the add-button of polymorphic lines in the Django admin.\n* Reformatted all files with black\n\n\nv2.0.3 (2018-08-24)\n-------------------\n\n* Fixed admin crash for Django 2.1 with missing ``use_required_attribute``.\n\n\nv2.0.2 (2018-02-05)\n-------------------\n\n* Fixed manager inheritance behavior for Django 1.11, by automatically enabling\n  ``Meta.manager_inheritance_from_future`` if it's not defined. This restores the manager\n  inheritance behavior that *django-polymorphic 1.3* provided for Django 1.x projects.\n* Fixed internal ``base_objects`` usage.\n\n\nv2.0.1 (2018-02-05)\n-------------------\n\n* Fixed manager inheritance detection for Django 1.11.\n\n  It's recommended to use ``Meta.manager_inheritance_from_future`` so Django 1.x code also inherit\n  the ``PolymorphicManager`` in all subclasses. Django 2.0 already does this by default.\n\n* Deprecated the ``base_objects`` manager. Use ``objects.non_polymorphic()`` instead.\n* Optimized detection for dumpdata behavior, avoiding the performance hit of ``__getattribute__()``.\n* Fixed test management commands\n\n\nv2.0.0 (2018-01-22)\n-------------------\n\n* **BACKWARDS INCOMPATIBILITY:** Dropped Django 1.8 and 1.10 support.\n* **BACKWARDS INCOMPATIBILITY:** Removed old deprecated code from 1.0, thus:\n\n * Import managers from ``polymorphic.managers`` (plural), not ``polymorphic.manager``.\n * Register child models to the admin as well using ``@admin.register()`` or\n   ``admin.site.register()``, as this is no longer done automatically.\n\n* Added Django 2.0 support.\n\nAlso backported into 1.3.1:\n\n* Added ``PolymorphicTypeUndefined`` exception for incomplete imported models.\n  When a data migration or import creates an polymorphic model,\n  the ``polymorphic_ctype_id`` field should be filled in manually too.\n  The ``polymorphic.utils.reset_polymorphic_ctype`` function can be used for that.\n* Added ``PolymorphicTypeInvalid`` exception when database was incorrectly imported.\n* Added ``polymorphic.utils.get_base_polymorphic_model()`` to find the base model for types.\n* Using ``base_model`` on the polymorphic admins is no longer required, as this can be autodetected.\n* Fixed manager errors for swappable models.\n* Fixed ``deleteText`` of ``|as_script_options`` template filter.\n* Fixed ``.filter(applabel__ModelName___field=...)`` lookups.\n* Fixed proxy model support in formsets.\n* Fixed error with .defer and child models that use the same parent.\n* Fixed error message when ``polymorphic_ctype_id`` is null.\n* Fixed fieldsets recursion in the admin.\n* Improved ``polymorphic.utils.reset_polymorphic_ctype()`` to accept models in random ordering.\n* Fix fieldsets handling in the admin (``declared_fieldsets`` is removed since Django 1.9)\n\n\nv1.3.1 (2018-04-16)\n-------------------\n\nBackported various fixes from 2.x to support older Django versions:\n\n* Added ``PolymorphicTypeUndefined`` exception for incomplete imported models.\n  When a data migration or import creates an polymorphic model,\n  the ``polymorphic_ctype_id`` field should be filled in manually too.\n  The ``polymorphic.utils.reset_polymorphic_ctype`` function can be used for that.\n* Added ``PolymorphicTypeInvalid`` exception when database was incorrectly imported.\n* Added ``polymorphic.utils.get_base_polymorphic_model()`` to find the base model for types.\n* Using ``base_model`` on the polymorphic admins is no longer required, as this can be autodetected.\n* Fixed manager errors for swappable models.\n* Fixed ``deleteText`` of ``|as_script_options`` template filter.\n* Fixed ``.filter(applabel__ModelName___field=...)`` lookups.\n* Fixed proxy model support in formsets.\n* Fixed error with .defer and child models that use the same parent.\n* Fixed error message when ``polymorphic_ctype_id`` is null.\n* Fixed fieldsets recursion in the admin.\n* Improved ``polymorphic.utils.reset_polymorphic_ctype()`` to accept models in random ordering.\n* Fix fieldsets handling in the admin (``declared_fieldsets`` is removed since Django 1.9)\n\n\nv1.3.0 (2017-08-01)\n-------------------\n\n* **BACKWARDS INCOMPATIBILITY:** Dropped Django 1.4, 1.5, 1.6, 1.7, 1.9 and Python 2.6 support.\n  Only official Django releases (1.8, 1.10, 1.11) are supported now.\n* Allow expressions to pass unchanged in ``.order_by()``\n* Fixed Django 1.11 accessor checks (to support subclasses of ``ForwardManyToOneDescriptor``, like\n  ``ForwardOneToOneDescriptor``)\n* Fixed polib syntax error messages in translations.\n\n\nv1.2.0 (2017-05-01)\n-------------------\n\n* Django 1.11 support.\n* Fixed ``PolymorphicInlineModelAdmin`` to explictly exclude ``polymorphic_ctype``.\n* Fixed Python 3 TypeError in the admin when preserving the query string.\n* Fixed Python 3 issue due to ``force_unicode()`` usage instead of ``force_text()``.\n* Fixed ``z-index`` attribute for admin menu appearance.\n\n\nv1.1.0 (2017-02-03)\n-------------------\n\n* Added class based formset views in ``polymorphic/contrib/extra_views``.\n* Added helper function ``polymorphic.utils.reset_polymorphic_ctype()``.\n  This eases the migration old existing models to polymorphic.\n* Fixed Python 2.6 issue.\n* Fixed Django 1.6 support.\n\n\nv1.0.2 (2016-10-14)\n-------------------\n\n* Added helper function for django-guardian_; add\n  ``GUARDIAN_GET_CONTENT_TYPE = 'polymorphic.contrib.guardian.get_polymorphic_base_content_type'``\n  to the project settings to let guardian handles inherited models properly.\n* Fixed ``polymorphic_modelformset_factory()`` usage.\n* Fixed Python 3 bug for inline formsets.\n* Fixed CSS for Grappelli, so model choice menu properly overlaps.\n* Fixed ``ParentAdminNotRegistered`` exception for models that are registered via a proxy model\n  instead of the real base model.\n\n\nv1.0.1 (2016-09-11)\n-------------------\n\n* Fixed compatibility with manager changes in Django 1.10.1\n\n\nv1.0.0 (2016-09-02)\n-------------------\n\n* Added Django 1.10 support.\n* Added **admin inline** support for polymorphic models.\n* Added **formset** support for polymorphic models.\n* Added support for polymorphic queryset limiting effects on *proxy models*.\n* Added support for multiple databases with the ``.using()`` method and ``using=..`` keyword\n  argument.\n* Fixed modifying passed ``Q()`` objects in place.\n\n.. note::\n   This version provides a new method for registering the admin models.\n   While the old method is still supported, we recommend to upgrade your code.\n   The new registration style improves the compatibility in the Django admin.\n\n   * Register each ``PolymorphicChildModelAdmin`` with the admin site too.\n   * The ``child_models`` attribute of the ``PolymorphicParentModelAdmin`` should be a flat list of\n     all child models. The ``(model, admin)`` tuple is obsolete.\n\n   Also note that proxy models will now limit the queryset too.\n\n\nFixed since 1.0b1 (2016-08-10)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Fix formset empty-form display when there are form errors.\n* Fix formset empty-form hiding for Grappelli_.\n* Fixed packing ``admin/polymorphic/edit_inline/stacked.html`` in the wheel format.\n\n\nv0.9.2 (2016-05-04)\n-------------------\n\n* Fix error when using ``date_hierarchy`` field in the admin\n* Fixed Django 1.10 warning in admin add-type view.\n\n\nv0.9.1 (2016-02-18)\n-------------------\n\n* Fixed support for ``PolymorphicManager.from_queryset()`` for custom query sets.\n* Fixed Django 1.7 ``changeform_view()`` redirection to the child admin site. This fixes custom\n  admin code that uses these views, such as django-reversion_'s ``revision_view()`` /\n  ``recover_view()``.\n* Fixed ``.only('pk')`` field support.\n* Fixed ``object_history_template`` breadcrumb.\n  **NOTE:** when using django-reversion_ / django-reversion-compare_, make sure to implement\n  a ``admin/polymorphic/object_history.html`` template in your project that extends\n  from ``reversion/object_history.html`` or ``reversion-compare/object_history.html`` respectively.\n\n\nv0.9.0 (2016-02-17)\n-------------------\n\n* Added ``.only()`` and ``.defer()`` support.\n* Added support for Django 1.8 complex expressions in ``.annotate()`` / ``.aggregate()``.\n* Fix Django 1.9 handling of custom URLs.\n  The new change-URL redirect overlapped any custom URLs defined in the child admin.\n* Fix Django 1.9 support in the admin.\n* Fix setting an extra custom manager without overriding the ``_default_manager``.\n* Fix missing ``history_view()`` redirection to the child admin, which is important for\n  django-reversion_ support. See the documentation for hints for\n  `django-reversion-compare support <https://github.com/django-commons/django-polymorphic/discussions/831>_`.\n\n\nv0.8.1 (2015-12-29)\n-------------------\n\n* Fixed support for reverse relations for ``relname___field`` when the field starts with an ``_``\n  character. Otherwise, the query will be interpreted as subclass lookup (``ClassName___field``).\n\n\nv0.8.0 (2015-12-28)\n-------------------\n\n* Added Django 1.9 compatibility.\n* Renamed ``polymorphic.manager`` => ``polymorphic.managers`` for consistentcy.\n* **BACKWARDS INCOMPATIBILITY:** The import paths have changed to support Django 1.9.\n  Instead of ``from polymorphic import X``,\n  you'll have to import from the proper package. For example:\n\n.. code-block:: python\n\n    from polymorphic.models import PolymorphicModel\n    from polymorphic.managers import PolymorphicManager, PolymorphicQuerySet\n    from polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent\n\n* **BACKWARDS INCOMPATIBILITY:** Removed ``__version__.py`` in favor of a standard ``__version__``\n  in ``polymorphic/__init__.py``.\n* **BACKWARDS INCOMPATIBILITY:** Removed automatic proxying of method calls to the queryset class.\n  Use the standard Django methods instead:\n\n.. code-block:: python\n\n    # In model code:\n    objects = PolymorphicQuerySet.as_manager()\n\n    # For manager code:\n    MyCustomManager = PolymorphicManager.from_queryset(MyCustomQuerySet)\n\n\n\nv0.7.2 (2015-10-01)\n-------------------\n\n* Added ``queryset.as_manager()`` support for Django 1.7/1.8\n* Optimize model access for non-dumpdata usage; avoid ``__getattribute__()`` call each time to\n  access the manager.\n* Fixed 500 error when using invalid PK's in the admin URL, return 404 instead.\n* Fixed possible issues when using an custom ``AdminSite`` class for the parent object.\n* Fixed Pickle exception when polymorphic model is cached.\n\n\nv0.7.1 (2015-04-30)\n-------------------\n\n* Fixed Django 1.8 support for related field widgets.\n\n\nv0.7.0 (2015-04-08)\n-------------------\n\n* Added Django 1.8 support\n* Added support for custom primary key defined using\n  ``mybase_ptr = models.OneToOneField(BaseClass, parent_link=True, related_name=\"...\")``.\n* Fixed Python 3 issue in the admin\n* Fixed ``_default_manager`` to be consistent with Django, it's now assigned directly instead of\n  using ``add_to_class()``\n* Fixed 500 error for admin URLs without a '/', e.g. ``admin/app/parentmodel/id``.\n* Fixed preserved filter for Django admin in delete views\n* Removed test noise for diamond inheritance problem (which Django 1.7 detects)\n\n\nv0.6.1 (2014-12-30)\n-------------------\n\n* Remove Django 1.7 warnings\n* Fix Django 1.4/1.5 queryset calls on related objects for unknown methods. The ``RelatedManager``\n  code overrides ``get_query_set()`` while ``__getattr__()`` used the new-style ``get_queryset()``.\n* Fix validate_model_fields(), caused errors when metaclass raises errors\n\n\nv0.6.0 (2014-10-14)\n-------------------\n\n* Added Django 1.7 support.\n* Added permission check for all child types.\n* **BACKWARDS INCOMPATIBILITY:** the ``get_child_type_choices()`` method receives 2 arguments now\n  (request, action). If you have overwritten this method in your code, make sure the method\n  signature is updated accordingly.\n\n\nv0.5.6 (2014-07-21)\n-------------------\n\n* Added ``pk_regex`` to the ``PolymorphicParentModelAdmin`` to support non-integer primary keys.\n* Fixed passing ``?ct_id=`` to the add view for Django 1.6 (fixes compatibility with\n  django-parler_).\n\n\nv0.5.5 (2014-04-29)\n-------------------\n\n* Fixed ``get_real_instance_class()`` for proxy models (broke in 0.5.4).\n\n\nv0.5.4 (2014-04-09)\n-------------------\n\n* Fix ``.non_polymorphic()`` to returns a clone of the queryset, instead of effecting the existing\n  queryset.\n* Fix missing ``alters_data = True`` annotations on the overwritten ``save()`` methods.\n* Fix infinite recursion bug in the admin with Django 1.6+\n* Added detection of bad ``ContentType`` table data.\n\n\nv0.5.3 (2013-09-17)\n-------------------\n\n* Fix TypeError when ``base_form`` was not defined.\n* Fix passing ``/admin/app/model/id/XYZ`` urls to the correct admin backend.\n  There is no need to include a ``?ct_id=..`` field, as the ID already provides enough information.\n\n\nv0.5.2 (2013-09-05)\n-------------------\n\n* Fix Grappelli_ breadcrumb support in the views.\n* Fix unwanted ``___`` handling in the ORM when a field name starts with an underscore;\n  this detects you meant ``relatedfield__ _underscorefield`` instead of ``ClassName___field``.\n* Fix missing permission check in the \"add type\" view. This was caught however in the next step.\n* Fix admin validation errors related to additional non-model form fields.\n\n\nv0.5.1 (2013-07-05)\n-------------------\n\n* Add Django 1.6 support.\n* Fix Grappelli_ theme support in the \"Add type\" view.\n\n\nv0.5.0 (2013-04-20)\n-------------------\n\n* Add Python 3.2 and 3.3 support\n* Fix errors with ContentType objects that don't refer to an existing model.\n\n\nv0.4.2 (2013-04-10)\n-------------------\n\n* Used proper ``__version__`` marker.\n\n\nv0.4.1 (2013-04-10)\n-------------------\n\n* Add Django 1.5 and 1.6 support\n* Add proxy model support\n* Add default admin ``list_filter`` for polymorphic model type.\n* Fix queryset support of related objects.\n* Performed an overall cleanup of the project\n* **Deprecated** the ``queryset_class`` argument of the ``PolymorphicManager`` constructor, use the\n  class attribute instead.\n* **Dropped** Django 1.1, 1.2 and 1.3 support\n\n\nv0.4.0 (2013-03-25)\n-------------------\n\n* Update example project for Django 1.4\n* Added tox and Travis configuration\n\n\nv0.3.1 (2013-02-28)\n-------------------\n\n* SQL optimization, avoid query in pre_save_polymorphic()\n\n\nv0.3.0 (2013-02-28)\n-------------------\n\nMany changes to the codebase happened, but no new version was released to pypi for years.\n0.3 contains fixes submitted by many contributors, huge thanks to everyone!\n\n* Added a polymorphic admin interface.\n* PEP8 and code cleanups by various authors\n\n\nv0.2.0 (2011-04-27)\n-------------------\n\nThe 0.2 release serves as legacy release.\nIt supports Django 1.1 up till 1.4 and Python 2.4 up till 2.7.\n\n.. _Grappelli: http://grappelliproject.com/\n.. _django-advanced-filters: https://github.com/modlinltd/django-advanced-filters\n.. _django-extra-views: https://github.com/AndrewIngram/django-extra-views\n.. _django-guardian: https://github.com/django-guardian/django-guardian\n.. _django-parler: https://github.com/django-parler/django-parler\n.. _django-reversion: https://github.com/etianen/django-reversion\n.. _django-reversion-compare: https://github.com/jedie/django-reversion-compare\n\n\nV1.0 Release Candidate 1 (2011-01-24)\n-------------------------------------\n\n* Fixed GitHub issue 15 (query result incomplete with inheritance).\n  Thanks to John Debs for reporting and the test case.\n\n\nRenaming, refactoring, new maintainer (2011-12-20)\n--------------------------------------------------\n\nSince the original author disappeared from the internet, we undertook to\nmaintain and upgrade this piece of software.\n\nThe latest \"legacy\" tag should be V1.0-RC-1. Anything above that should be\nconsidered experimental and unstable until further notice (there be dragons).\n\nNew features, bug fixes and other improvements will be added to trunk from now on.\n\n\nV1.0 Beta 2 (2010-11-11)\n------------------------\n\nBeta 2 accumulated somewhat more changes than intended, and also\nhas been delayed by DBMS benchmark testing I wanted to do on model\ninheritance. These benchmarks show that there are considerable\nproblems with concrete model inheritance and contemporary DBM systems.\nThe results will be forthcoming on the google discussion forum.\n\nPlease also see: http://www.jacobian.org/writing/concrete-inheritance/\n\nThe API should be stable now with Beta 2, so it's just about potential\nbugfixes from now on regarding V1.0.\n\nBeta 2 is still intended for testing and development environments and not\nfor production. No complaints have been heard regarding Beta 1 however,\nand Beta 1 is used on a few production sites by some enterprising users.\n\nThere will be a release candidate for V1.0 in the very near future.\n\nNew Features and changes\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n*   API CHANGE: ``.extra()`` has been re-implemented. Now it's polymorphic by\n    default and works (nearly) without restrictions (please see docs). This is a (very)\n    incompatible API change regarding previous versions of django_polymorphic.\n    Support for the ``polymorphic`` keyword parameter has been removed.\n    You can get back the non-polymorphic behaviour by using\n    ``ModelA.objects.non_polymorphic().extra(...)``.\n\n*   API CHANGE: ``ShowFieldContent`` and ``ShowFieldTypeAndContent`` now\n    use a slightly different output format. If this causes too much trouble for\n    your test cases, you can get the old behaviour back (mostly) by adding\n    ``polymorphic_showfield_old_format = True`` to your model definitions.\n    ``ShowField...`` now also produces more informative output for custom\n    primary keys.\n\n*   ``.non_polymorphic()`` queryset member function added. This is preferable to\n    using ``.base_objects...``, as it just makes the resulting queryset non-polymorphic\n    and does not change anything else in the behaviour of the manager used (while\n    ``.base_objects`` is just a different manager).\n\n*   ``.get_real_instances()``: implementation modified to allow the following\n    more simple and intuitive use::\n\n    >>> qs = ModelA.objects.all().non_polymorphic()\n    >>> qs.get_real_instances()\n\n    which is equivalent to::\n\n    >>> ModelA.objects.all()\n\n*   added member function:\n    ``normal_q_object = ModelA.translate_polymorphic_Q_object(enhanced_q_object)``\n\n*   misc changes/improvements\n\nBugfixes\n~~~~~~~~\n\n*   Custom fields could cause problems when used as the primary key.\n    In inherited models, Django's automatic \".pk\" field does not always work\n    correctly for such custom fields: \"some_object.pk\" and \"some_object.id\"\n    return different results (which they shouldn't, as pk should always be just\n    an alias for the primary key field). It's unclear yet if the problem lies in\n    Django or the affected custom fields. Regardless, the problem resulting\n    from this has been fixed with a small workaround.\n    \"python manage.py test polymorphic\" also tests and reports on this problem now.\n    Thanks to Mathieu Steele for reporting and the test case.\n\nV1.0 Beta 1 (2010-10-18)\n------------------------\n\nThis release is mostly a cleanup and maintenance release that also\nimproves a number of minor things and fixes one (non-critical) bug.\n\nSome pending API changes and corrections have been folded into this release\nin order to make the upcoming V1.0 API as stable as possible.\n\nThis release is also about getting feedback from you in case you don't\napprove of any of these changes or would like to get additional\nAPI fixes into V1.0.\n\nThe release contains a considerable amount of changes in some of the more\ncritical parts of the software. It's intended for testing and development\nenvironments and not for production environments. For these, it's best to\nwait a few weeks for the proper V1.0 release, to allow some time for any\npotential problems to show up (if they exist).\n\nIf you encounter any such problems, please post them in the discussion group\nor open an issue on GitHub or BitBucket (or send me an email).\n\nThere also have been a number of minor API changes.\nPlease see the README for more information.\n\nNew Features\n~~~~~~~~~~~~\n\n*   official Django 1.3 alpha compatibility\n\n*   ``PolymorphicModel.__getattribute__`` hack removed.\n    This improves performance considerably as python's __getattribute__\n    generally causes a pretty large processing overhead. It's gone now.\n\n*   the ``polymorphic_dumpdata`` management command is not needed anymore\n    and has been disabled, as the regular Django dumpdata command now automatically\n    works correctly with polymorphic models (for all supported versions of Django).\n\n*   ``.get_real_instances()`` has been elevated to an official part of the API::\n\n        real_objects = ModelA.objects.get_real_instances(base_objects_list_or_queryset)\n\n    allows you to turn a queryset or list of base objects into a list of the real instances.\n    This is useful if e.g. you use ``ModelA.base_objects.extra(...)`` and then want to\n    transform the result to its polymorphic equivalent.\n\n*   ``translate_polymorphic_Q_object``  (see DOCS)\n\n*   improved testing\n\n*   Changelog added: CHANGES.rst/html\n\nBugfixes\n~~~~~~~~\n\n*   Removed requirement for primary key to be an IntegerField.\n    Thanks to Mathieu Steele and Malthe Borch.\n\nAPI Changes\n~~~~~~~~~~~\n\n**polymorphic_dumpdata**\n\nThe management command ``polymorphic_dumpdata`` is not needed anymore\nand has been disabled, as the regular Django dumpdata command now automatically\nworks correctly with polymorphic models (for all supported versions of Django).\n\n**Output of Queryset or Object Printing**\n\nIn order to improve compatibility with vanilla Django, printing quersets\n(__repr__ and __unicode__) does not use django_polymorphic's pretty printing\nby default anymore. To get the old behaviour when printing querysets,\nyou need to replace your model definition:\n\n>>> class Project(PolymorphicModel):\n\nby:\n\n>>> class Project(PolymorphicModel, ShowFieldType):\n\nThe mixin classes for pretty output have been renamed:\n\n    ``ShowFieldTypes, ShowFields, ShowFieldsAndTypes``\n\nare now:\n\n    ``ShowFieldType, ShowFieldContent and ShowFieldTypeAndContent``\n\n(the old ones still exist for compatibility)\n\n**Running the Test suite with Django 1.3**\n\nDjango 1.3 requires ``python manage.py test polymorphic`` instead of\njust ``python manage.py test``.\n\n\nBeta Release (2010-2-22)\n------------------------\n\nIMPORTANT: API Changed (import path changed), and Installation Note\n\nThe django_polymorphic source code has been restructured\nand as a result needs to be installed like a normal Django App\n- either via copying the \"polymorphic\" directory into your\nDjango project or by running setup.py. Adding 'polymorphic'\nto INSTALLED_APPS in settings.py is still optional, however.\n\nThe file `polymorphic.py` cannot be used as a standalone\nextension module anymore, as is has been split into a number\nof smaller files.\n\nImporting works slightly different now: All relevant symbols are\nimported directly from 'polymorphic' instead from\n'polymorphic.models'::\n\n    # new way\n    from polymorphic import PolymorphicModel, ...\n\n    # old way, doesn't work anymore\n    from polymorphic.models import PolymorphicModel, ...\n\n+ minor API addition: 'from polymorphic import VERSION, get_version'\n\nNew Features\n~~~~~~~~~~~~\n\nPython 2.4 compatibility, contributed by Charles Leifer. Thanks!\n\nBugfixes\n~~~~~~~~\n\nFix: The exception \"...has no attribute 'sub_and_superclass_dict'\"\ncould be raised. (This occurred if a subclass defined __init__\nand accessed class members before calling the superclass __init__).\nThanks to Mattias Brändström.\n\nFix: There could be name conflicts if\nfield_name == model_name.lower() or similar.\nNow it is possible to give a field the same name as the class\n(like with normal Django models).\n(Found through the example provided by Mattias Brändström)\n\n\nBeta Release (2010-2-4)\n-----------------------\n\nNew features (and documentation)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nqueryset order_by method added\n\nqueryset aggregate() and extra() methods implemented\n\nqueryset annotate() method implemented\n\nqueryset values(), values_list(), distinct() documented; defer(),\nonly() allowed (but not yet supported)\n\nsetup.py added. Thanks to Andrew Ingram.\n\nMore about these additions in the docs:\n``http://bserve.webhop.org/wiki/django_polymorphic/doc``\n\nBugfixes\n~~~~~~~~\n\n*   fix remaining potential accessor name clashes (but this only works\n    with Django 1.2+, for 1.1 no changes). Thanks to Andrew Ingram.\n\n*   fix use of 'id' model field, replaced with 'pk'.\n\n*   fix select_related bug for objects from derived classes (till now\n    sel.-r. was just ignored)\n\n\"Restrictions & Caveats\" updated\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n*   Django 1.1 only - the names of polymorphic models must be unique\n    in the whole project, even if they are in two different apps.\n    This results from a restriction in the Django 1.1 \"related_name\"\n    option (fixed in Django 1.2).\n\n*   Django 1.1 only - when ContentType is used in models, Django's\n    seralisation or fixtures cannot be used. This issue seems to be\n    resolved for Django 1.2 (changeset 11863: Fixed #7052, Added\n    support for natural keys in serialization).\n\n\nBeta Release (2010-1-30)\n------------------------\n\nFixed ContentType related field accessor clash (an error emitted\nby model validation) by adding related_name to the ContentType\nForeignKey. This happened if your polymorphc model used a ContentType\nForeignKey. Thanks to Andrew Ingram.\n\n\nBeta Release (2010-1-29)\n------------------------\n\nRestructured django_polymorphic into a regular Django add-on\napplication. This is needed for the management commands, and\nalso seems to be a generally good idea for future enhancements\nas well (and it makes sure the tests are always included).\n\nThe ``poly`` app - until now being used for test purposes only\n- has been renamed to ``polymorphic``. See DOCS.rst\n(\"installation/testing\") for more info.\n\n\nBeta Release (2010-1-28)\n------------------------\n\nAdded the polymorphic_dumpdata management command (github issue 4),\nfor creating fixtures, this should be used instead of\nthe normal Django dumpdata command.\nThanks to Charles Leifer.\n\nImportant: Using ContentType together with dumpdata generally\nneeds Django 1.2 (important as any polymorphic model uses\nContentType).\n\nBeta Release (2010-1-26)\n------------------------\n\nIMPORTANT - database schema change (more info in change log).\nI hope I got this change in early enough before anyone started\nto use polymorphic.py in earnest. Sorry for any inconvenience.\nThis should be the final DB schema now.\n\nDjango's ContentType is now used instead of app-label and model-name\nThis is a cleaner and more efficient solution\nThanks to Ilya Semenov for the suggestion.\n\n\n.. toctree::\n\n   drf\n\n"
  },
  {
    "path": "docs/conf.py",
    "content": "#\n# django-polymorphic documentation build configuration file, created by\n# sphinx-quickstart on Sun May 19 12:20:47 2013.\n#\n# This file is execfile()d with the current directory set to its containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport os\nimport shutil\nimport sys\nfrom pathlib import Path\n\nimport django\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\nsys.path.insert(0, os.path.abspath(\"_ext\"))\nsys.path.insert(0, os.path.abspath(\"..\"))\nos.environ[\"DJANGO_SETTINGS_MODULE\"] = \"polymorphic.tests.settings\"\n\ndjango.setup()\n\nimport polymorphic  # noqa: E402\n\n# -- General configuration -----------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be extensions\n# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.graphviz\",\n    \"sphinx.ext.intersphinx\",\n    \"sphinx.ext.todo\",\n    \"sphinxcontrib_django\",\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# The suffix of source filenames.\nsource_suffix = \".rst\"\n\n# The encoding of source files.\n# source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# -- Project information -----------------------------------------------------\nproject = polymorphic.__title__\ncopyright = polymorphic.__copyright__\nauthor = polymorphic.__author__\nrelease = polymorphic.__version__\nversion = \".\".join(polymorphic.__version__.split(\".\")[:2])\n\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n# language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n# today = ''\n# Else, today_fmt is used as the format for a strftime call.\n# today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = [\"_build\"]\n\n# The reST default role (used for this markup: `text`) to use for all documents.\n# default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n# add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n# add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n# show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\n# pygments_style = \"sphinx\"\n\n# A list of ignored prefixes for module index sorting.\n# modindex_common_prefix = []\n\n\n# -- Options for HTML output ---------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = \"furo\"\nhtml_theme_options = {\n    \"source_repository\": \"https://github.com/jazzband/django-polymorphic/\",\n    \"source_branch\": \"main\",\n    \"source_directory\": \"docs\",\n}\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n# html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\n# html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n# html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n# html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n# html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n# html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = [\"_static\"]\nhtml_css_files = [\n    \"style.css\",\n]\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n# html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n# html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n# html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n# html_additional_pages = {}\n\n# If false, no module index is generated.\n# html_domain_indices = True\n\n# If false, no index is generated.\n# html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n# html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n# html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n# html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n# html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n# html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n# html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"django-polymorphicdoc\"\n\n\n# -- Options for LaTeX output --------------------------------------------------\n\nlatex_engine = \"xelatex\"\nlatex_elements = {\n    # The paper size ('letterpaper' or 'a4paper').\n    #'papersize': 'letterpaper',\n    # The font size ('10pt', '11pt' or '12pt').\n    #'pointsize': '10pt',\n    # Additional stuff for the LaTeX preamble.\n    #'preamble': '',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass [howto/manual]).\nlatex_documents = [\n    (\n        \"index\",\n        \"django-polymorphic.tex\",\n        \"django-polymorphic Documentation\",\n        \"Bert Constantin, Chris Glass, Diederik van der Boor, Brian Kohan\",\n        \"manual\",\n    )\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n# latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n# latex_use_parts = False\n\n# If true, show page references after internal links.\n# latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n# latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n# latex_appendices = []\n\n# If false, no module index is generated.\n# latex_domain_indices = True\n\n\n# -- Options for manual page output --------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    (\n        \"index\",\n        \"django-polymorphic\",\n        \"django-polymorphic Documentation\",\n        [\"Bert Constantin, Chris Glass, Diederik van der Boor\", \"Brian Kohan\"],\n        1,\n    )\n]\n\n# If true, show URL addresses after external links.\n# man_show_urls = False\n\n\n# -- Options for Texinfo output ------------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    (\n        \"index\",\n        \"django-polymorphic\",\n        \"django-polymorphic Documentation\",\n        \"Bert Constantin, Chris Glass, Diederik van der Boor, Brian Kohan\",\n        \"django-polymorphic\",\n        \"One line description of project.\",\n        \"Miscellaneous\",\n    )\n]\n\n# Documents to append as an appendix to all manuals.\n# texinfo_appendices = []\n\n# If false, no module index is generated.\n# texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n# texinfo_show_urls = 'footnote'\n\n\nintersphinx_mapping = {\n    \"django\": (\n        \"https://docs.djangoproject.com/en/stable\",\n        \"https://docs.djangoproject.com/en/stable/_objects/\",\n    ),\n    \"python\": (\"https://docs.python.org/3\", None),\n    \"typing-extensions\": (\"https://typing-extensions.readthedocs.io/en/latest/\", None),\n    \"django-guardian\": (\"https://django-guardian.readthedocs.io/en/stable\", None),\n    \"django-reversion\": (\"https://django-reversion.readthedocs.io/en/stable\", None),\n    \"django-extra-views\": (\"https://django-extra-views.readthedocs.io/en/stable\", None),\n    \"model-bakery\": (\"https://model-bakery.readthedocs.io/en/stable\", None),\n}\n\n# autodoc settings\nautodoc_default_options = {\n    \"show-inheritance\": True,\n    # Add other autodoc options here if desired, e.g.:\n    # 'members': True,\n    # 'inherited-members': True,\n}\n# In your Sphinx conf.py\nautodoc_typehints = \"description\"\nautodoc_typehints_format = \"short\"\nautodoc_class_signature = \"separated\"\nautodoc_member_order = \"groupwise\"  # \"bysource\"\n\n# sphinxcontrib-django / Sphinx 9+ autodoc compat\nautodoc_use_legacy_class_based = True\n\n\ndef pypi_role(name, rawtext, text, lineno, inliner, options={}, content=[]):\n    from docutils import nodes\n\n    url = f\"https://pypi.org/project/{text}/\"\n    node = nodes.reference(rawtext, text, refuri=url, **options)\n    return [node], []\n\n\ndef setup(app):\n    from docutils.parsers.rst import roles\n\n    # https://sphinxcontrib-typer.readthedocs.io/en/latest/howto.html#build-to-multiple-formats\n    if Path(app.doctreedir).exists():\n        shutil.rmtree(app.doctreedir)\n    app.add_crossref_type(directivename=\"django-admin\", rolename=\"django-admin\")\n    roles.register_local_role(\"pypi\", pypi_role)\n    return app\n"
  },
  {
    "path": "docs/deletion.rst",
    "content": "Deletion\n========\n\n.. versionadded:: 4.5.0\n\nThere is nothing special about deleting polymorphic models. The same rules apply as to\n:ref:`the deletion of normal Django models <topics-db-queries-delete>` that have parent/child\nrelationships up and down a model inheritance hierarchy. Django must walk the model inheritance and\nrelationship graph and collect all of the affected objects so that it can correctly order deletion\nSQL statements to respect database constraints and issue signals.\n\nThe polymorphic deletion logic is the same as the normal Django deletion logic because Django\nalready walks the model inheritance hierarchy. :class:`~polymorphic.query.PolymorphicQuerySet` and\n:class:`~polymorphic.managers.PolymorphicManager` disrupt this process by confusing Django's graph\nwalker by returning concrete subclass instances instead of base class instances when it attempts to\nwalk reverse relationships to polymorphic models. To prevent this confusion,\n:pypi:`django-polymorphic` wraps the :attr:`~django.db.models.ForeignKey.on_delete` handlers of\nreverse relations to polymorphic models with :class:`~polymorphic.deletion.PolymorphicGuard`\nwhich disables polymorphic behavior on the related querysets during collection.\n\n**You may define your polymorphic models as you normally would using the standard Django**\n:attr:`~django.db.models.ForeignKey.on_delete` **actions**.\n:class:`~polymorphic.models.PolymorphicModel` will automatically wrap the actions for you. actions\nwrapped with :class:`~polymorphic.deletion.PolymorphicGuard` serialize in migrations as the\nunderlying wrapped action. This ensures migrations generated by versions of\n:pypi:`django-polymorphic` after 4.5.0 should be the same as with prior versions. The guard is also\nunnecessary during migrations because Django generates basic managers instead of using the default\npolymorphic managers.\n\nIt is a design goal of :pypi:`django-polymorphic` that deletion should just work without any special\ntreatment. However if you encounter attribute errors or database integrity errors during deletion\nyou may manually wrap the :attr:`~django.db.models.ForeignKey.on_delete` action of reverse relations\nto polymorphic models with :class:`~polymorphic.deletion.PolymorphicGuard` to disable polymorphic\nbehavior during deletion collection. If you encounter an issue like this\n`please report it to us <https://github.com/django-commons/django-polymorphic/issues>`_. For example:\n\n.. code-block:: python\n\n    from polymorphic.models import PolymorphicModel\n    from polymorphic.deletion import PolymorphicGuard\n    from django.db import models\n\n    class MyModel(models.Model):\n        # ...\n\n    class RelatedModel(PolymorphicModel):\n        my_model = models.ForeignKey(\n            MyModel,\n            on_delete=PolymorphicGuard(models.CASCADE),\n        )\n\nDeleting Children (upcasting)\n-----------------------------\n\nWhen deleting a polymorphic model instance, you can choose to keep the parent model instances by\npassing the ``keep_parents=True`` argument to the\n:meth:`~polymorphic.models.PolymorphicModel.delete` method. This will delete only the subclass\ninstance, and leave the parent instances intact. :pypi:`django-polymorphic` will ensure that the\n``polymorphic_ctype`` fields of the parent instances are updated accordingly to reflect their new\nconcrete model type.\n\n.. tip::\n\n    You can delete multiple levels of child rows by deleting the model from the desired parent\n    level. For example, if you have a model inheritance hierarchy of ``Base -> ChildA -> ChildB``,\n    and you delete a ``ChildB`` row from its parent model ``ChildA`` instance both the ``ChildA``\n    and ``ChildB`` rows will be deleted leaving a concrete row type of ``Base``.\n"
  },
  {
    "path": "docs/formsets.rst",
    "content": "Formsets\n========\n\n.. versionadded:: 1.0\n\nPolymorphic models can be used in formsets.\n\nThe implementation is almost identical to the regular Django :doc:`django:topics/forms/formsets`.\nAs extra parameter, the factory needs to know how to display the child models.\nProvide a list of :class:`~polymorphic.formsets.PolymorphicFormSetChild` objects for this.\n\n.. code-block:: python\n\n    from polymorphic.formsets import polymorphic_modelformset_factory, PolymorphicFormSetChild\n\n    ModelAFormSet = polymorphic_modelformset_factory(ModelA, formset_children=(\n        PolymorphicFormSetChild(ModelB),\n        PolymorphicFormSetChild(ModelC),\n    ))\n\nThe formset can be used just like all other formsets:\n\n.. code-block:: python\n\n    if request.method == \"POST\":\n        formset = ModelAFormSet(request.POST, request.FILES, queryset=ModelA.objects.all())\n        if formset.is_valid():\n            formset.save()\n    else:\n        formset = ModelAFormSet(queryset=ModelA.objects.all())\n\nLike standard Django :doc:`django:topics/forms/formsets`, there are 3 factory methods available:\n\n* :func:`~polymorphic.formsets.polymorphic_modelformset_factory` - create a regular model formset.\n* :func:`~polymorphic.formsets.polymorphic_inlineformset_factory` - create a inline model formset.\n* :func:`~polymorphic.formsets.generic_polymorphic_inlineformset_factory` - create an inline formset\n  for a generic foreign key.\n\nEach one uses a different base class:\n\n* :class:`~polymorphic.formsets.BasePolymorphicModelFormSet`\n* :class:`~polymorphic.formsets.BasePolymorphicInlineFormSet`\n* :class:`~polymorphic.formsets.BaseGenericPolymorphicInlineFormSet`\n\nWhen needed, the base class can be overwritten and provided to the factory via the ``formset``\nparameter.\n"
  },
  {
    "path": "docs/index.rst",
    "content": "django-polymorphic\n==================\n\n.. only:: html\n\n   .. image:: https://img.shields.io/badge/License-BSD-blue.svg\n      :target: https://opensource.org/license/bsd-3-clause\n      :alt: License: BSD\n\n   .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json\n      :target: https://github.com/astral-sh/ruff\n      :alt: Ruff\n\n   .. image:: https://badge.fury.io/py/django-polymorphic.svg\n      :target: https://pypi.python.org/pypi/django-polymorphic/\n      :alt: PyPI version\n\n   .. image:: https://img.shields.io/pypi/pyversions/django-polymorphic.svg\n      :target: https://pypi.python.org/pypi/django-polymorphic/\n      :alt: PyPI pyversions\n\n   .. image:: https://img.shields.io/pypi/djversions/django-polymorphic.svg\n      :target: https://pypi.org/project/django-polymorphic/\n      :alt: PyPI Django versions\n\n   .. image:: https://img.shields.io/pypi/status/django-polymorphic.svg\n      :target: https://pypi.python.org/pypi/django-polymorphic\n      :alt: PyPI status\n\n   .. image:: https://img.shields.io/pypi/types/django-polymorphic.svg\n      :target: https://pypi.python.org/pypi/django-polymorphic\n      :alt: PyPi Typed\n\n   .. image:: https://readthedocs.org/projects/django-polymorphic/badge/?version=latest\n      :target: http://django-polymorphic.readthedocs.io/?badge=latest/\n      :alt: Documentation Status\n\n   .. image:: https://img.shields.io/codecov/c/github/django-commons/django-polymorphic/main.svg\n      :target: https://codecov.io/github/django-commons/django-polymorphic?branch=main\n      :alt: Code Coverage\n\n   .. image:: https://github.com/django-commons/django-polymorphic/actions/workflows/test.yml/badge.svg?branch=main\n      :target: https://github.com/django-commons/django-polymorphic/actions/workflows/test.yml?query=branch:main\n      :alt: Test Status\n\n   .. image:: https://github.com/django-commons/django-polymorphic/actions/workflows/lint.yml/badge.svg?branch=main\n      :target: https://github.com/django-commons/django-polymorphic/actions/workflows/lint.yml?query=branch:main\n      :alt: Lint Status\n\n   .. image:: https://img.shields.io/badge/Published%20on-Django%20Packages-0c3c26\n      :target: https://djangopackages.org/packages/p/django-polymorphic/\n      :alt: Published on Django Packages\n\n\n:pypi:`django-polymorphic` builds on top of the standard Django model inheritance.\nIt makes using inherited models easier. When a query is made at the base model,\nthe inherited model classes are returned.\n\nWhen we store models that inherit from a ``Project`` model...\n\n.. code-block:: python\n\n    >>> Project.objects.create(topic=\"Department Party\")\n    >>> ArtProject.objects.create(topic=\"Painting with Tim\", artist=\"T. Turner\")\n    >>> ResearchProject.objects.create(topic=\"Swallow Aerodynamics\", supervisor=\"Dr. Winter\")\n\n...and want to retrieve all our projects, the subclassed models are returned!\n\n.. code-block:: python\n\n    >>> Project.objects.all()\n    [ <Project:         id 1, topic \"Department Party\">,\n      <ArtProject:      id 2, topic \"Painting with Tim\", artist \"T. Turner\">,\n      <ResearchProject: id 3, topic \"Swallow Aerodynamics\", supervisor \"Dr. Winter\"> ]\n\nUsing vanilla Django, we get the base class objects, which is rarely what we wanted:\n\n.. code-block:: python\n\n    >>> Project.objects.all()\n    [ <Project: id 1, topic \"Department Party\">,\n      <Project: id 2, topic \"Painting with Tim\">,\n      <Project: id 3, topic \"Swallow Aerodynamics\"> ]\n\nFeatures\n--------\n\n* Full admin integration.\n* ORM integration:\n\n  - Support for ForeignKey, ManyToManyField, OneToOneField descriptors.\n  - Support for proxy models.\n  - Filtering/ordering of inherited models (``ArtProject___artist``).\n  - Filtering model types: :meth:`~polymorphic.managers.PolymorphicQuerySet.instance_of` and\n    :meth:`~polymorphic.managers.PolymorphicQuerySet.not_instance_of`\n  - Combining querysets of different models (``qs3 = qs1 | qs2``)\n  - Support for custom user-defined managers.\n\n* Formset support.\n* Uses the minimum amount of queries needed to fetch the inherited models.\n* Disabling polymorphic behavior when needed.\n\n\nGetting started\n---------------\n\n.. toctree::\n   :maxdepth: 2\n\n   quickstart\n   admin\n   performance\n   integrations/index\n\nAdvanced topics\n---------------\n\n.. toctree::\n   :maxdepth: 2\n\n   formsets\n   views\n   migrating\n   managers\n   deletion\n   typing\n   advanced\n   changelog/index\n   api/index\n\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n"
  },
  {
    "path": "docs/integrations/drf.rst",
    "content": ".. _django-rest-framework-support:\n\n===================\ndjangorestframework\n===================\n\nPolymorphic serializers for `Django REST Framework <https://www.django-rest-framework.org/>`_.\n\nThe :pypi:`django-rest-polymorphic` package has been incorporated into :pypi:`django-polymorphic`.\nThis contrib package allows you to easily define serializers for your inherited models that you have\ncreated using ``django-polymorphic`` library. To migrate from :pypi:`django-rest-polymorphic`, you\nneed to change your import paths from ``rest_polymorphic.serializers`` to\n``polymorphic.contrib.drf.serializers``.\n\nUsage\n-----\n\nDefine your polymorphic models:\n\n.. literalinclude:: ../../src/polymorphic/tests/examples/integrations/drf/models/example_models.py\n    :language: python\n    :linenos:\n\n\nDefine serializers for each polymorphic model the way you did it when you used\n:pypi:`djangorestframework`:\n\n.. literalinclude:: ../../src/polymorphic/tests/examples/integrations/drf/example_serializers.py\n    :language: python\n    :linenos:\n    :lines: 1-29\n\n\nNote that if you extend `HyperlinkedModelSerializer\n<https://www.django-rest-framework.org/api-guide/serializers/#hyperlinkedmodelserializer>`_ instead\nof `ModelSerializer <https://www.django-rest-framework.org/api-guide/serializers/#modelserializer>`_\nyou need to define `extra_kwargs\n<https://www.django-rest-framework.org/community/3.0-announcement/#the-extra_kwargs-option>`_ to\ndirect the URL to the appropriate view for your polymorphic serializer.\n\nThen you have to create a polymorphic serializer that serves as a mapper between models and\nserializers which you have defined above:\n\n.. literalinclude:: ../../src/polymorphic/tests/examples/integrations/drf/example_serializers.py\n    :language: python\n    :lines: 32-\n\nCreate viewset with serializer_class equals to your polymorphic serializer:\n\n.. literalinclude:: ../../src/polymorphic/tests/examples/integrations/drf/views.py\n    :language: python\n    :linenos:\n\nTest it:\n\n.. code-block:: bash\n\n    $ http GET \"http://localhost:8000/projects/\"\n\n.. code-block:: http\n\n    HTTP/1.0 200 OK\n    Content-Length: 227\n    Content-Type: application/json\n\n    [\n        {\n            \"resourcetype\": \"Project\",\n            \"topic\": \"John's gathering\"\n        },\n        {\n            \"artist\": \"T. Turner\",\n            \"resourcetype\": \"ArtProject\",\n            \"topic\": \"Sculpting with Tim\",\n            \"url\": \"http://localhost:8000/projects/2/\"\n        },\n        {\n            \"resourcetype\": \"ResearchProject\",\n            \"supervisor\": \"Dr. Winter\",\n            \"topic\": \"Swallow Aerodynamics\"\n        }\n    ]\n\n.. code-block:: bash\n\n    $ http POST \"http://localhost:8000/projects/\" resourcetype=\"ArtProject\" topic=\"Guernica\" artist=\"Picasso\"\n\n.. code-block:: http\n\n    HTTP/1.0 201 Created\n    Content-Length: 67\n    Content-Type: application/json\n\n    {\n        \"artist\": \"Picasso\",\n        \"resourcetype\": \"ArtProject\",\n        \"topic\": \"Guernica\",\n        \"url\": \"http://localhost:8000/projects/4/\"\n    }\n\n\nCustomize resource type\n-----------------------\n\nAs you can see from the example above, in order to specify the type of your polymorphic model, you\nneed to send a request with resource type field. The value of resource type should be the name of\nthe model.\n\nIf you want to change the resource type field name from ``resourcetype`` to something else, you\nshould override ``resource_type_field_name`` attribute:\n\n.. code-block:: python\n\n    class ProjectPolymorphicSerializer(PolymorphicSerializer):\n        resource_type_field_name = 'projecttype'\n        ...\n\nIf you want to change the behavior of resource type, you should override ``to_resource_type``\nmethod:\n\n.. code-block:: python\n\n    class ProjectPolymorphicSerializer(PolymorphicSerializer):\n        ...\n\n        def to_resource_type(self, model_or_instance):\n            return model_or_instance._meta.object_name.lower()\n\nNow, the request for creating new object will look like this:\n\n.. code-block:: bash\n\n    $ http POST \"http://localhost:8000/projects/\" projecttype=\"artproject\" topic=\"Guernica\" artist=\"Picasso\"\n"
  },
  {
    "path": "docs/integrations/index.rst",
    "content": ".. _integrations:\n\nIntegrations\n============\n\nWhen integrating polymorphic models into third party apps you have three primary options:\n\n0. Hope it just works (it might!).\n1. Ensure the querysets the third party apps see are\n   :meth:`not polymorphic <polymorphic.query.PolymorphicQuerySet.non_polymorphic>`.\n2. Override or extend relevant third party app code to work with polymorphic querysets.\n\nIf it does not just work, option 1 is usually the easiest. We provide some integrations in\n:mod:`polymorphic.contrib` for popular third party apps and provide guidance for others below.\n\nThis page does not exhaustively cover all integrations. If you feel your integration need is\nvery common you may consider opening a PR to either provide support in code or documentation here.\n\nThis page covers supported and tested integration advice. For all other integration advice please\nrefer to `our integrations discussion page\n<https://github.com/jazzband/django-polymorphic/discussions/categories/integrations>`_.\n\nFor the integration examples on this page, we use the following polymorphic model hierarchy:\n\n.. literalinclude:: ../../src/polymorphic/tests/examples/integrations/models.py\n    :language: python\n    :linenos:\n\n\n.. _django-django-guardian-support:\n\n:pypi:`django-guardian`\n-----------------------\n\n.. versionadded:: 1.0.2\n\nNo special modifications are required to integrate with :pypi:`django-guardian`. However, if you\nwould like all object level permissions to be managed at the base model level, rather than have\nunique permissions for each polymorphic subclass, then you can use the helper function\n:func:`polymorphic.contrib.guardian.get_polymorphic_base_content_type` to unify the permissions\nfor your entire polymorphic model tree into a single namespace a the base level:\n\n.. code-block:: python\n\n    GUARDIAN_GET_CONTENT_TYPE = \\\n        \"polymorphic.contrib.guardian.get_polymorphic_base_content_type\"\n\nThis option requires :pypi:`django-guardian` >= 1.4.6. Details about how this option works are\navailable in the `django-guardian documentation\n<https://django-guardian.readthedocs.io/en/stable/configuration>`_.\n\n.. _django-extra-views-support:\n\n:pypi:`django-extra-views`\n--------------------------\n\n.. versionadded:: 1.1\n\nThe :mod:`polymorphic.contrib.extra_views` package provides classes to display polymorphic formsets\nusing the classes from :pypi:`django-extra-views`. See the documentation of:\n\n* :class:`~polymorphic.contrib.extra_views.PolymorphicFormSetView`\n* :class:`~polymorphic.contrib.extra_views.PolymorphicInlineFormSetView`\n* :class:`~polymorphic.contrib.extra_views.PolymorphicInlineFormSet`\n\n.. tip::\n\n    The complete working code for this example can be found `in the extra_views integration test\n    <https://github.com/jazzband/django-polymorphic/tree/HEAD/src/polymorphic/tests/examples/integrations/extra_views>`_.\n\n\nExample View\n~~~~~~~~~~~~\n\nHere's how to create a view using :class:`~polymorphic.contrib.extra_views.PolymorphicFormSetView`\nto handle polymorphic formsets:\n\n.. literalinclude:: ../../src/polymorphic/tests/examples/integrations/extra_views/views.py\n    :language: python\n    :linenos:\n\n\nURL Configuration\n~~~~~~~~~~~~~~~~~\n\nConfigure the URL patterns to route to your formset view:\n\n.. literalinclude:: ../../src/polymorphic/tests/examples/integrations/extra_views/urls.py\n    :language: python\n    :linenos:\n\n\nTemplate\n~~~~~~~~\n\nThe template for rendering the formset:\n\n.. literalinclude:: ../../src/polymorphic/tests/examples/integrations/extra_views/templates/extra_views/article_formset.html\n    :language: html+django\n\n``model_name`` is a template tag implemented like so:\n\n.. literalinclude:: ../../src/polymorphic/tests/examples/integrations/extra_views/templatetags/extra_views_tags.py\n    :language: python\n    :lines: 6-\n\n.. _django-reversion-support:\n\n:pypi:`django-reversion`\n------------------------\n\nSupport for :pypi:`django-reversion` works as expected with polymorphic models. We just need to\ndo two things:\n\n1. Inherit our admin classes from both :class:`~polymorphic.admin.PolymorphicParentModelAdmin` /\n   :class:`~polymorphic.admin.PolymorphicChildModelAdmin` and\n   :ref:`VersionAdmin <django-reversion:versionadmin>`.\n2. Override the ``admin/polymorphic/object_history.html`` template.\n\n.. tip::\n\n    The complete working code for this example can be found `in the reversion integration test\n    <https://github.com/jazzband/django-polymorphic/tree/HEAD/src/polymorphic/tests/examples/integrations/reversion>`_.\n\n\nAdmin Configuration\n~~~~~~~~~~~~~~~~~~~\n\nThe admin configuration combines :class:`~polymorphic.admin.PolymorphicParentModelAdmin` and\n:class:`~polymorphic.admin.PolymorphicChildModelAdmin` with\n:ref:`VersionAdmin <django-reversion:versionadmin>`:\n\n.. literalinclude:: ../../src/polymorphic/tests/examples/integrations/reversion/admin.py\n    :language: python\n    :linenos:\n\n\nCustom Template\n~~~~~~~~~~~~~~~\n\nSince both :class:`~polymorphic.admin.PolymorphicParentModelAdmin` and\n:ref:`VersionAdmin <django-reversion:versionadmin>`. define ``object_history.html`` template, you\nneed to create a custom template that combines both:\n\n.. literalinclude:: ../../src/polymorphic/tests/examples/integrations/reversion/templates/admin/polymorphic/object_history.html\n    :language: html+django\n\nThis makes sure both the reversion template is used, and the breadcrumb is corrected for the\npolymorphic model using the :templatetag:`breadcrumb_scope`\ntag.\n\n.. _model-bakery:\n\n:pypi:`model-bakery`\n--------------------\n\n:pypi:`model-bakery` does not work without without special configuration for polymorphic models\nbecause it overrides the :attr:`~polymorphic.models.PolymorphicModel.polymorphic_ctype` field.\nThe best option to make it work in all cases is to `supply a custom Baker\n<https://model-bakery.readthedocs.io/en/latest/how_bakery_behaves.html#customizing-baker>`_ class\nthat fills in all fields except :attr:`~polymorphic.models.PolymorphicModel.polymorphic_ctype`:\n\n.. code-block:: python\n    :linenos:\n    :caption: yoursite/tests/baker.py\n\n    from polymorphic.models import PolymorphicModel\n    from model_bakery import baker\n\n\n    class PolymorphicAwareBaker(baker.Baker):\n        \"\"\"\n        Our custom model baker ignores the polymorphic_ctype field on all polymorphic\n        models - this allows the base class to set it correctly.\n        See https://github.com/python/pythondotorg/issues/2567\n        \"\"\"\n\n        def get_fields(self):\n            fields = super().get_fields()\n            if issubclass(self.model, PolymorphicModel):\n                fields = {\n                    field\n                    for field in fields\n                    if field.name != \"polymorphic_ctype\"\n                }\n            return fields\n\n\nThen in your test settings file:\n\n.. code-block:: python\n\n    BAKER_CUSTOM_CLASS = \"yoursite.tests.baker.PolymorphicAwareBaker\"\n\nYou may also simply pass the correct :class:`~django.contrib.contenttypes.models.ContentType`\ninstance to the :attr:`~polymorphic.models.PolymorphicModel.polymorphic_ctype` field when creating\npolymorphic model instances with ``make()``\n\n\nOther Integrations\n------------------\n\n.. toctree::\n   :maxdepth: 1\n   :titlesonly:\n\n   drf\n"
  },
  {
    "path": "docs/managers.rst",
    "content": "Managers & Querysets\n====================\n\nUsing a Custom Manager\n----------------------\n\nA nice feature of Django is the possibility to define one's own custom object managers.\nThis is fully supported with :pypi:`django-polymorphic`. For creating a custom polymorphic\nmanager class, just derive your manager from :class:`~polymorphic.managers.PolymorphicManager`\ninstead of :class:`~django.db.models.Manager`. As with vanilla Django, in your model class, you\nshould explicitly add the default manager first, and then your custom manager:\n\n.. code-block:: python\n\n    from polymorphic.models import PolymorphicModel\n    from polymorphic.managers import PolymorphicManager\n\n    class TimeOrderedManager(PolymorphicManager):\n        def get_queryset(self):\n            qs = super(TimeOrderedManager,self).get_queryset()\n            return qs.order_by('-start_date')\n\n        def most_recent(self):\n            qs = self.get_queryset()  # get my ordered queryset\n            return qs[:10]            # limit => get ten most recent entries\n\n    class Project(PolymorphicModel):\n        objects = PolymorphicManager()          # add the default polymorphic manager first\n        objects_ordered = TimeOrderedManager()  # then add your own manager\n        start_date = DateTimeField()            # project start is this date/time\n\nThe first manager defined (:attr:`~django.db.models.Model.objects` in the example) is used by Django\nas automatic manager for several purposes, including accessing related objects. It must not filter\nobjects and it's safest to use the plain :class:`~polymorphic.managers.PolymorphicManager` here.\n\nManager Inheritance\n-------------------\n\nPolymorphic models inherit/propagate all managers from their base models, as long as these are\npolymorphic. This means that all managers defined in polymorphic base models continue to work as\nexpected in models inheriting from this base model:\n\n.. code-block:: python\n\n    from polymorphic.models import PolymorphicModel\n    from polymorphic.managers import PolymorphicManager\n\n    class TimeOrderedManager(PolymorphicManager):\n        def get_queryset(self):\n            qs = super(TimeOrderedManager,self).get_queryset()\n            return qs.order_by('-start_date')\n\n        def most_recent(self):\n            qs = self.get_queryset()  # get my ordered queryset\n            return qs[:10]            # limit => get ten most recent entries\n\n    class Project(PolymorphicModel):\n        objects = PolymorphicManager()          # add the default polymorphic manager first\n        objects_ordered = TimeOrderedManager()  # then add your own manager\n        start_date = DateTimeField()            # project start is this date/time\n\n    class ArtProject(Project):  # inherit from Project, inheriting its fields and managers\n        artist = models.CharField(max_length=30)\n\nArtProject inherited the managers ``objects`` and ``objects_ordered`` from Project.\n\n``ArtProject.objects_ordered.all()`` will return all art projects ordered regarding their start time\nand ``ArtProject.objects_ordered.most_recent()`` will return the ten most recent art projects.\n\nUsing a Custom Queryset Class\n-----------------------------\n\nThe :class:`~polymorphic.managers.PolymorphicManager` class accepts one initialization argument,\nwhich is the queryset class the manager should use. Just as with vanilla Django, you may define your\nown custom queryset classes. Just use :class:`~polymorphic.managers.PolymorphicQuerySet` instead of\nDjango's :class:`~django.db.models.query.QuerySet` as the base class:\n\n.. code-block:: python\n\n        from polymorphic.models import PolymorphicModel\n        from polymorphic.managers import PolymorphicManager\n        from polymorphic.query import PolymorphicQuerySet\n\n        class MyQuerySet(PolymorphicQuerySet):\n            def my_queryset_method(self):\n                ...\n\n        class MyModel(PolymorphicModel):\n            my_objects = PolymorphicManager.from_queryset(MyQuerySet)()\n            ...\n\nIf you do not wish to extend from a custom :class:`~polymorphic.managers.PolymorphicManager` you\nmay also prefer the :meth:`~polymorphic.managers.PolymorphicQuerySet.as_manager`\nshortcut:\n\n.. code-block:: python\n\n    from polymorphic.models import PolymorphicModel\n    from polymorphic.query import PolymorphicQuerySet\n\n    class MyQuerySet(PolymorphicQuerySet):\n        def my_queryset_method(self):\n            ...\n\n    class MyModel(PolymorphicModel):\n        my_objects = MyQuerySet.as_manager()\n        ...\n\nFor further discussion see `this topic on the Q&A page\n<https://github.com/django-commons/django-polymorphic/discussions/696#discussioncomment-15223661>`_.\n"
  },
  {
    "path": "docs/migrating.rst",
    "content": "Migrating Existing Models\n=========================\n\nExisting models can be migrated to become polymorphic models. During migration, the\n:attr:`~polymorphic.models.PolymorphicModel.polymorphic_ctype` field needs to be populated.\n\nThis can be done in the following steps:\n\n#. Inherit your model from :class:`~polymorphic.models.PolymorphicModel`.\n#. Create a Django migration file to create the ``polymorphic_ctype_id`` database column.\n#. Make sure the proper :class:`~django.contrib.contenttypes.models.ContentType` value is filled in.\n\nFilling the content type value\n------------------------------\n\nThe following code can be used to fill the value of a model:\n\n.. code-block:: python\n\n    from django.contrib.contenttypes.models import ContentType\n    from myapp.models import MyModel\n\n    new_ct = ContentType.objects.get_for_model(MyModel)\n    MyModel.objects.filter(polymorphic_ctype__isnull=True).update(polymorphic_ctype=new_ct)\n\nThe creation and update of the ``polymorphic_ctype_id`` column can be included in a single Django\nmigration. For example:\n\n.. code-block:: python\n\n    # -*- coding: utf-8 -*-\n    from django.db import migrations, models\n\n\n    def forwards_func(apps, schema_editor):\n        MyModel = apps.get_model('myapp', 'MyModel')\n        ContentType = apps.get_model('contenttypes', 'ContentType')\n\n        new_ct = ContentType.objects.get_for_model(MyModel)\n        MyModel.objects.filter(polymorphic_ctype__isnull=True).update(\n            polymorphic_ctype=new_ct\n        )\n\n\n    class Migration(migrations.Migration):\n\n        dependencies = [\n            ('contenttypes', '0001_initial'),\n            ('myapp', '0001_initial'),\n        ]\n\n        operations = [\n            migrations.AddField(\n                model_name='mymodel',\n                name='polymorphic_ctype',\n                field=models.ForeignKey(\n                    related_name='polymorphic_myapp.mymodel_set+',\n                    editable=False,\n                    to='contenttypes.ContentType',\n                    null=True\n                ),\n            ),\n            migrations.RunPython(forwards_func, migrations.RunPython.noop),\n        ]\n\nIt's recommended to let :django-admin:`makemigrations` create the migration file, and include the\n:class:`~django.db.migrations.operations.RunPython` manually before running the migration.\n\n.. versionadded:: 1.1\n\nWhen the model is created elsewhere, you can also use the\n:func:`~polymorphic.utils.reset_polymorphic_ctype` function:\n\n.. code-block:: python\n\n    from polymorphic.utils import reset_polymorphic_ctype\n    from myapp.models import Base, Sub1, Sub2\n\n    reset_polymorphic_ctype(Base, Sub1, Sub2)\n\n    reset_polymorphic_ctype(Base, Sub1, Sub2, ignore_existing=True)\n"
  },
  {
    "path": "docs/performance.rst",
    "content": ".. _performance:\n\nPerformance Considerations\n==========================\n\nUsually, when Django users create their own polymorphic ad-hoc solution without a tool like\n:pypi:`django-polymorphic`, this usually results in a variation of:\n\n.. code-block:: python\n\n    result_objects = [ o.get_real_instance() for o in BaseModel.objects.filter(...) ]\n\nwhich has very bad performance, as it introduces one additional SQL query for every object in the\nresult which is not of class ``BaseModel``. Compared to these solutions, :pypi:`django-polymorphic`\nhas the advantage that it only needs 1 SQL query *per object type*, and not *per object*.\n\nThe current implementation does not use any custom SQL or Django DB layer internals - it is purely\nbased on the standard Django ORM. Specifically, the query:\n\n.. code-block:: python\n\n    result_objects = list( ModelA.objects.filter(...) )\n\nperforms one SQL query to retrieve ``ModelA`` objects and one additional query for each unique\nderived class occurring in result_objects. The best case for retrieving 100 objects is 1 SQL query\nif all are class ``ModelA``. If 50 objects are ``ModelA`` and 50 are ``ModelB``, then two queries\nare executed. The pathological worst case is 101 db queries if result_objects contains 100 different\nobject types (with all of them subclasses of ``ModelA``).\n\nIteration: Memory vs DB Round Trips\n-----------------------------------\n\nWhen iterating over large QuerySets, there is a trade-off between memory consumption and number\nof round trips to the database. One additional query is needed per model subclass present in the\nQuerySet and these queries take the form of ``SELECT ... WHERE pk IN (....)`` with a potentially\nlarge number of IDs in the IN clause. All models in the IN clause will be loaded into memory during\niteration.\n\nTo balance this trade-off, by default a maximum of 2000 objects are requested at once. This means\nthat if your QuerySet contains 10,000 objects of 3 different subclasses, then 16 queries will be\nexecuted: 1 to fetch the base objects, and 5 (10/2 == 5) * 3 more to fetch the subclasses.\n\nThe `chunk_size` parameter on :meth:`~django.db.models.query.QuerySet.iterator` can be used to\nchange the number of objects loaded into memory at once during iteration. For example, to load 5000\nobjects at once:\n\n.. code-block:: python\n\n    for obj in ModelA.objects.all().iterator(chunk_size=5000):\n        process(obj)\n\n.. note::\n\n    ``chunk_size`` on non-polymorphic QuerySets controls the number of rows fetched from the\n    database at once, but for polymorphic QuerySets the behavior is more analogous to its behavior\n    when :meth:`~django.db.models.query.QuerySet.prefetch_related` is used.\n\nSome database backends limit the number of parameters in a query. For those backends the\n``chunk_size`` will be restricted to be no greater than that limit. This limit can be checked in:\n\n.. code-block:: python\n\n    from django.db import connection\n\n    print(connection.features.max_query_params)\n\n\nYou may change the global default fallback ``chunk_size`` by modifying the\n:attr:`polymorphic.query.Polymorphic_QuerySet_objects_per_request` attribute. Place code like\nthis somewhere that will be executed during startup:\n\n.. code-block:: python\n\n    from polymorphic import query\n\n    query.Polymorphic_QuerySet_objects_per_request = 5000\n\n\n:class:`~django.contrib.contenttypes.models.ContentType` retrieval\n------------------------------------------------------------------\n\nWhen fetching the :class:`~django.contrib.contenttypes.models.ContentType` class, it's tempting to\nread the :attr:`~polymorphic.models.PolymorphicModel.polymorphic_ctype` field directly. However,\nthis performs an additional query via the :class:`~django.db.models.ForeignKey` object to fetch the\n:class:`~django.contrib.contenttypes.models.ContentType`. Instead, use:\n\n.. code-block:: python\n\n    from django.contrib.contenttypes.models import ContentType\n\n    ctype = ContentType.objects.get_for_id(object.polymorphic_ctype_id)\n\nThis uses the :meth:`~django.contrib.contenttypes.models.ContentTypeManager.get_for_id` function\nwhich caches the results internally.\n"
  },
  {
    "path": "docs/quickstart.rst",
    "content": "Quickstart\n===========\n\nInstall the project using::\n\n    pip install django-polymorphic\n\nUpdate the settings file:\n\n.. code-block:: python\n\n    INSTALLED_APPS += (\n        'polymorphic',\n        'django.contrib.contenttypes',\n    )\n\n.. only:: html\n\n    The current release of :pypi:`django-polymorphic` supports:\n\n    .. image:: https://badge.fury.io/py/django-polymorphic.svg\n        :target: https://pypi.python.org/pypi/django-polymorphic/\n        :alt: PyPI version\n\n    .. image:: https://img.shields.io/pypi/pyversions/django-polymorphic.svg\n        :target: https://pypi.python.org/pypi/django-polymorphic/\n        :alt: Supported Pythons\n\n    .. image:: https://img.shields.io/pypi/djversions/django-polymorphic.svg\n        :target: https://pypi.org/project/django-polymorphic/\n        :alt: Supported Django\n\n\nMaking Your Models Polymorphic\n------------------------------\n\nUse :class:`~polymorphic.models.PolymorphicModel` instead of Django's\n:class:`~django.db.models.Model`, like so:\n\n.. code-block:: python\n\n    from polymorphic.models import PolymorphicModel\n\n    class Project(PolymorphicModel):\n        topic = models.CharField(max_length=30)\n\n    class ArtProject(Project):\n        artist = models.CharField(max_length=30)\n\n    class ResearchProject(Project):\n        supervisor = models.CharField(max_length=30)\n\nAll models inheriting from your polymorphic models will be polymorphic as well.\n\nUsing Polymorphic Models\n------------------------\n\nCreate some objects:\n\n.. code-block:: python\n\n    >>> Project.objects.create(topic=\"Department Party\")\n    >>> ArtProject.objects.create(topic=\"Painting with Tim\", artist=\"T. Turner\")\n    >>> ResearchProject.objects.create(topic=\"Swallow Aerodynamics\", supervisor=\"Dr. Winter\")\n\nGet polymorphic query results:\n\n.. code-block:: python\n\n    >>> Project.objects.all()\n    [ <Project:         id 1, topic \"Department Party\">,\n      <ArtProject:      id 2, topic \"Painting with Tim\", artist \"T. Turner\">,\n      <ResearchProject: id 3, topic \"Swallow Aerodynamics\", supervisor \"Dr. Winter\"> ]\n\nUse :meth:`~polymorphic.managers.PolymorphicQuerySet.instance_of` and\n:meth:`~polymorphic.managers.PolymorphicQuerySet.not_instance_of` for narrowing the result to\nspecific subtypes:\n\n.. code-block:: python\n\n    >>> Project.objects.instance_of(ArtProject)\n    [ <ArtProject:      id 2, topic \"Painting with Tim\", artist \"T. Turner\"> ]\n\n.. code-block:: python\n\n    >>> Project.objects.instance_of(ArtProject) | Project.objects.instance_of(ResearchProject)\n    [ <ArtProject:      id 2, topic \"Painting with Tim\", artist \"T. Turner\">,\n      <ResearchProject: id 3, topic \"Swallow Aerodynamics\", supervisor \"Dr. Winter\"> ]\n\nPolymorphic filtering: Get all projects where Mr. Turner is involved as an artist\nor supervisor (note the three underscores):\n\n.. code-block:: python\n\n    >>> Project.objects.filter(Q(ArtProject___artist='T. Turner') | Q(ResearchProject___supervisor='T. Turner'))\n    [ <ArtProject:      id 2, topic \"Painting with Tim\", artist \"T. Turner\">,\n      <ResearchProject: id 4, topic \"Color Use in Late Cubism\", supervisor \"T. Turner\"> ]\n\nThis is basically all you need to know, as *django-polymorphic* mostly\nworks fully automatic and just delivers the expected results.\n\n.. note::\n    While :pypi:`django-polymorphic` makes subclassed models easy to use in Django,\n    we still encourage to use them with caution. Each subclassed model will require\n    Django to perform an ``INNER JOIN`` to fetch the model fields from the database.\n    While taking this in mind, there are valid reasons for using subclassed models.\n    That's what this library is designed for!\n"
  },
  {
    "path": "docs/typing.rst",
    "content": "Type Hints\n==========\n\n.. versionadded:: 4.11\n\n:pypi:`django-polymorphic` is now fully typed, and ships with type hints for all public\nAPIs. Typing is checked with :pypi:`mypy` and :pypi:`pyright` in the CI pipeline.\n\nThe utility and power of the Django ORM derives from its dynamism but this makes static typing\nmore difficult. There are *no additional runtime dependencies* but **to use the packaged type hint\nclasses and descriptors, you must install the following in your type checking context**:\n\n* :pypi:`django-stubs` (required)\n* :pypi:`django-stubs-ext` (optional)\n\n.. tip::\n\n    The most useful type hints to add to your own code will make it so your type checking system\n    knows which model types your :class:`~polymorphic.managers.PolymorphicManager` and\n    :class:`~polymorphic.managers.PolymorphicQuerySet` objects might return.\n\nCorrect type hints for polymorphic managers and querysets cannot be automatically inferred - you\nwill have to add them explicitly if you want them:\n\n.. _typing_managers:\n\nManagers\n--------\n\nYou can type your managers like this. It might not always be the case that you can add hints for all\nchild model types, especially if they are included in dependent apps. You can alleviate some of this\ncomplexity with forward type references but strict typing may not always be appropriate.\n\n.. literalinclude:: ../src/polymorphic/tests/examples/type_hints/managers/models.py\n    :language: python\n    :linenos:\n\n\n.. code-block:: python\n\n    ParentModel.objects.all()  # type: PolymorphicQuerySet[ParentModel | Child1 | Child2]\n    ParentModel.objects.instance_of(Child1)  # type: PolymorphicQuerySet[Child1]\n    Child1.objects.non_polymorphic()  # type: QuerySet[Child1]\n\n.. _typing_foreign_key:\n\nForeign Key\n-----------\n\n:pypi:`django-polymorphic` includes several :ref:`type hint descriptors <type_hint_descriptors>`.\nYou can use them to type your forward and reverse relationship fields. For foreign key relationships\nwe provide :class:`~polymorphic.managers.PolymorphicForwardManyToOneDescriptor` and\n:class:`~polymorphic.managers.PolymorphicReverseManyToOneDescriptor`:\n\n.. literalinclude:: ../src/polymorphic/tests/examples/type_hints/fk/models.py\n    :language: python\n    :linenos:\n\n.. code-block:: python\n\n    RelatedModel().parent: Optional[ParentModel | Child1 | Child2]\n    RelatedModel().children.all(): PolymorphicQuerySet[ParentModel | Child1 | Child2]\n\n.. _typing_one_to_one:\n\nOne to One\n----------\nFor foreign key relationships\nwe provide :class:`~polymorphic.managers.PolymorphicForwardOneToOneDescriptor` and\n:class:`~polymorphic.managers.PolymorphicReverseOneToOneDescriptor`:\n\n.. literalinclude:: ../src/polymorphic/tests/examples/type_hints/one2one/models.py\n    :language: python\n    :linenos:\n\n.. code-block:: python\n\n    RelatedModel().parent_forward: ParentModel | Child1 | Child2 | None\n    RelatedModel().parent_reverse: ParentModel | Child1 | Child2\n\n.. _typing_many_to_many:\n\nMany to Many\n------------\n\nYou can use the same :class:`~polymorphic.managers.PolymorphicManyToManyDescriptor` for\nboth forward and reverse :class:`~django.db.models.ManyToManyField` relationships.\n\nThe following example shows two :class:`~django.db.models.ManyToManyField` relationships:\n\n1. :class:`~polymorphic.models.PolymorphicModel` -> :class:`~django.db.models.Model`\n   (with a custom through model)\n2. :class:`~django.db.models.Model` -> :class:`~polymorphic.models.PolymorphicModel`\n   (with the default through model)\n\nFor the custom through model you will need to annotate using the :ref:`foreign key descriptors\n<typing_foreign_key>` as well.\n\n.. literalinclude:: ../src/polymorphic/tests/examples/type_hints/m2m/models.py\n    :language: python\n    :linenos:\n"
  },
  {
    "path": "docs/views.rst",
    "content": ".. _views:\n\nClass Based Views\n=================\n\nWhile :pypi:`django-polymorphic` provides full admin integration, you might want to build front-end\nviews that allow users to create polymorphic objects. Since a single URL cannot easily handle\ndifferent form fields for different models, the best approach is a two-step process:\n\n1.  **Step 1:** Let the user choose the desired type.\n2.  **Step 2:** Display the form for that specific type.\n\n.. tip::\n\n    The code for this example can be found `here\n    <https://github.com/jazzband/django-polymorphic/tree/HEAD/src/polymorphic/tests/examples/views>`_.\n\nThis example uses model labels (e.g., ``app.ModelName``) to identify the selected type. Assume we\nhave the following models:\n\n.. literalinclude:: ../src/polymorphic/tests/examples/views/models.py\n    :language: python\n    :linenos:\n\nStep 1: Selecting the Type\n--------------------------\n\nCreate a form that allows users select the desired model type. You can use a simple choice field\nfor this.\n\n.. literalinclude:: ../src/polymorphic/tests/examples/views/views.py\n    :language: python\n    :lines: 1-45\n    :linenos:\n\nYour template ``project_type_select.html``, might look like this:\n\n.. literalinclude:: ../src/polymorphic/tests/examples/views/templates/project_type_select.html\n    :language: html\n\nStep 2: Displaying the Form\n---------------------------\n\nThe creation view needs to dynamically select the correct form class based on the chosen model\nlabel.\n\n.. literalinclude:: ../src/polymorphic/tests/examples/views/views.py\n    :language: python\n    :lines: 47-\n    :linenos:\n\n\nIn your template ``project_form.html``, make sure to preserve the ``model`` parameter:\n\n.. literalinclude:: ../src/polymorphic/tests/examples/views/templates/project_form.html\n    :language: html\n\n\nAnd our urls might look like this:\n\n.. literalinclude:: ../src/polymorphic/tests/examples/views/urls.py\n    :linenos:\n\nUsing ``extra_views``\n---------------------\n\nIf you are using :pypi:`django-extra-views`, :pypi:`django-polymorphic` provides mixins to help with\nformsets. See :mod:`polymorphic.contrib.extra_views` for more details.\n"
  },
  {
    "path": "example/example/__init__.py",
    "content": ""
  },
  {
    "path": "example/example/settings.py",
    "content": "import os\n\nDEBUG = True\n\nADMINS = (\n    # ('Your Name', 'your_email@example.com'),\n)\n\nMANAGERS = ADMINS\nPROJECT_ROOT = os.path.dirname(os.path.dirname(__file__))\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.sqlite3\",\n        \"NAME\": os.path.join(PROJECT_ROOT, \"example.db\"),\n    }\n}\n\nSITE_ID = 1\n\n# Make this unique, and don't share it with anybody.\nSECRET_KEY = \"5$f%)&amp;a4tc*bg(79+ku!7o$kri-duw99@hq_)va^_kaw9*l)!7\"\n\n\n# Language\n# TIME_ZONE = 'America/Chicago'\nLANGUAGE_CODE = \"en-us\"\nUSE_I18N = True\nUSE_L10N = True\nUSE_TZ = True\n\n# Paths\nMEDIA_ROOT = \"\"\nMEDIA_URL = \"/media/\"\nSTATIC_ROOT = \"\"\nSTATIC_URL = \"/static/\"\n\n# Apps\nSTATICFILES_FINDERS = (\n    \"django.contrib.staticfiles.finders.FileSystemFinder\",\n    \"django.contrib.staticfiles.finders.AppDirectoriesFinder\",\n)\n\nMIDDLEWARE = (\n    \"django.middleware.common.CommonMiddleware\",\n    \"django.contrib.sessions.middleware.SessionMiddleware\",\n    \"django.middleware.csrf.CsrfViewMiddleware\",\n    \"django.contrib.auth.middleware.AuthenticationMiddleware\",\n    \"django.contrib.messages.middleware.MessageMiddleware\",\n)\n\nTEMPLATES = [\n    {\n        \"BACKEND\": \"django.template.backends.django.DjangoTemplates\",\n        \"DIRS\": (),\n        \"OPTIONS\": {\n            \"loaders\": (\n                \"django.template.loaders.filesystem.Loader\",\n                \"django.template.loaders.app_directories.Loader\",\n            ),\n            \"context_processors\": (\n                \"django.template.context_processors.debug\",\n                \"django.template.context_processors.i18n\",\n                \"django.template.context_processors.media\",\n                \"django.template.context_processors.request\",\n                \"django.template.context_processors.static\",\n                \"django.contrib.messages.context_processors.messages\",\n                \"django.contrib.auth.context_processors.auth\",\n            ),\n        },\n    }\n]\n\nROOT_URLCONF = \"example.urls\"\n\nWSGI_APPLICATION = \"example.wsgi.application\"\n\nINSTALLED_APPS = (\n    \"django.contrib.auth\",\n    \"django.contrib.admin\",\n    \"django.contrib.contenttypes\",\n    \"django.contrib.sessions\",\n    \"django.contrib.messages\",\n    \"django.contrib.staticfiles\",\n    \"polymorphic\",  # needed if you want to use the polymorphic admin\n    \"pexp\",  # this Django app is for testing and experimentation; not needed otherwise\n    \"orders\",\n)\n\nTEST_RUNNER = \"django.test.runner.DiscoverRunner\"  # silence system checks\n\n# Logging configuration\nLOGGING = {\n    \"version\": 1,\n    \"disable_existing_loggers\": False,\n    \"filters\": {\"require_debug_false\": {\"()\": \"django.utils.log.RequireDebugFalse\"}},\n    \"handlers\": {\n        \"mail_admins\": {\n            \"level\": \"ERROR\",\n            \"filters\": [\"require_debug_false\"],\n            \"class\": \"django.utils.log.AdminEmailHandler\",\n        }\n    },\n    \"loggers\": {\n        \"django.request\": {\n            \"handlers\": [\"mail_admins\"],\n            \"level\": \"ERROR\",\n            \"propagate\": True,\n        }\n    },\n}\n"
  },
  {
    "path": "example/example/urls.py",
    "content": "from django.contrib import admin\nfrom django.urls import path, reverse_lazy\nfrom django.views.generic import RedirectView\n\nadmin.autodiscover()\n\nurlpatterns = [\n    path(\"admin/\", admin.site.urls),\n    path(\"\", RedirectView.as_view(url=reverse_lazy(\"admin:index\"), permanent=False)),\n]\n"
  },
  {
    "path": "example/example/wsgi.py",
    "content": "\"\"\"\nWSGI config for example project.\n\nThis module contains the WSGI application used by Django's development server\nand any production WSGI deployments. It should expose a module-level variable\nnamed ``application``. Django's ``runserver`` and ``runfcgi`` commands discover\nthis application via the ``WSGI_APPLICATION`` setting.\n\nUsually you will have the standard Django WSGI application here, but it also\nmight make sense to replace the whole Django WSGI application with a custom one\nthat later delegates to the Django one. For example, you could introduce WSGI\nmiddleware here, or combine a Django application with an application of another\nframework.\n\n\"\"\"\n\nimport os\n\n# This application object is used by any WSGI server configured to use this\n# file. This includes Django's development server, if the WSGI_APPLICATION\n# setting points here.\nfrom django.core.wsgi import get_wsgi_application\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"example.settings\")\n\n\napplication = get_wsgi_application()\n\n# Apply WSGI middleware here.\n# from helloworld.wsgi import HelloWorldApplication\n# application = HelloWorldApplication(application)\n"
  },
  {
    "path": "example/manage.py",
    "content": "#!/usr/bin/env python\nimport os\nimport sys\n\nif __name__ == \"__main__\":\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"example.settings\")\n\n    # Import polymorphic from this folder.\n    SRC_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))\n    sys.path.insert(0, SRC_ROOT)\n\n    from django.core.management import execute_from_command_line\n\n    execute_from_command_line(sys.argv)\n"
  },
  {
    "path": "example/orders/__init__.py",
    "content": ""
  },
  {
    "path": "example/orders/admin.py",
    "content": "from django.contrib import admin\n\nfrom polymorphic.admin import PolymorphicInlineSupportMixin, StackedPolymorphicInline\n\nfrom .models import BankPayment, CreditCardPayment, Order, Payment, SepaPayment\n\n\nclass CreditCardPaymentInline(StackedPolymorphicInline.Child):\n    model = CreditCardPayment\n\n\nclass BankPaymentInline(StackedPolymorphicInline.Child):\n    model = BankPayment\n\n\nclass SepaPaymentInline(StackedPolymorphicInline.Child):\n    model = SepaPayment\n\n\nclass PaymentInline(StackedPolymorphicInline):\n    \"\"\"\n    An inline for a polymorphic model.\n    The actual form appearance of each row is determined by\n    the child inline that corresponds with the actual model type.\n    \"\"\"\n\n    model = Payment\n    child_inlines = (CreditCardPaymentInline, BankPaymentInline, SepaPaymentInline)\n\n\n@admin.register(Order)\nclass OrderAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):\n    \"\"\"\n    Admin for orders.\n    The inline is polymorphic.\n    To make sure the inlines are properly handled,\n    the ``PolymorphicInlineSupportMixin`` is needed to\n    \"\"\"\n\n    inlines = (PaymentInline,)\n"
  },
  {
    "path": "example/orders/migrations/0001_initial.py",
    "content": "from django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [(\"contenttypes\", \"0002_remove_content_type_name\")]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"Order\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        verbose_name=\"ID\",\n                        serialize=False,\n                        auto_created=True,\n                        primary_key=True,\n                    ),\n                ),\n                (\"title\", models.CharField(max_length=200, verbose_name=\"Title\")),\n            ],\n            options={\n                \"ordering\": (\"title\",),\n                \"verbose_name\": \"Organisation\",\n                \"verbose_name_plural\": \"Organisations\",\n            },\n        ),\n        migrations.CreateModel(\n            name=\"Payment\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        verbose_name=\"ID\",\n                        serialize=False,\n                        auto_created=True,\n                        primary_key=True,\n                    ),\n                ),\n                (\"currency\", models.CharField(default=b\"USD\", max_length=3)),\n                (\"amount\", models.DecimalField(max_digits=10, decimal_places=2)),\n            ],\n            options={\"verbose_name\": \"Payment\", \"verbose_name_plural\": \"Payments\"},\n        ),\n        migrations.CreateModel(\n            name=\"BankPayment\",\n            fields=[\n                (\n                    \"payment_ptr\",\n                    models.OneToOneField(\n                        parent_link=True,\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        on_delete=models.CASCADE,\n                        to=\"orders.Payment\",\n                    ),\n                ),\n                (\"bank_name\", models.CharField(max_length=100)),\n                (\"swift\", models.CharField(max_length=20)),\n            ],\n            options={\n                \"verbose_name\": \"Bank Payment\",\n                \"verbose_name_plural\": \"Bank Payments\",\n            },\n            bases=(\"orders.payment\",),\n        ),\n        migrations.CreateModel(\n            name=\"CreditCardPayment\",\n            fields=[\n                (\n                    \"payment_ptr\",\n                    models.OneToOneField(\n                        parent_link=True,\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        on_delete=models.CASCADE,\n                        to=\"orders.Payment\",\n                    ),\n                ),\n                (\"card_type\", models.CharField(max_length=10)),\n                (\n                    \"expiry_month\",\n                    models.PositiveSmallIntegerField(\n                        choices=[\n                            (1, \"jan\"),\n                            (2, \"feb\"),\n                            (3, \"mar\"),\n                            (4, \"apr\"),\n                            (5, \"may\"),\n                            (6, \"jun\"),\n                            (7, \"jul\"),\n                            (8, \"aug\"),\n                            (9, \"sep\"),\n                            (10, \"oct\"),\n                            (11, \"nov\"),\n                            (12, \"dec\"),\n                        ]\n                    ),\n                ),\n                (\"expiry_year\", models.PositiveIntegerField()),\n            ],\n            options={\n                \"verbose_name\": \"Credit Card Payment\",\n                \"verbose_name_plural\": \"Credit Card Payments\",\n            },\n            bases=(\"orders.payment\",),\n        ),\n        migrations.CreateModel(\n            name=\"SepaPayment\",\n            fields=[\n                (\n                    \"payment_ptr\",\n                    models.OneToOneField(\n                        parent_link=True,\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        on_delete=models.CASCADE,\n                        to=\"orders.Payment\",\n                    ),\n                ),\n                (\"iban\", models.CharField(max_length=34)),\n                (\"bic\", models.CharField(max_length=11)),\n            ],\n            options={\n                \"verbose_name\": \"Bank Payment\",\n                \"verbose_name_plural\": \"Bank Payments\",\n            },\n            bases=(\"orders.payment\",),\n        ),\n        migrations.AddField(\n            model_name=\"payment\",\n            name=\"order\",\n            field=models.ForeignKey(to=\"orders.Order\", on_delete=models.CASCADE),\n        ),\n        migrations.AddField(\n            model_name=\"payment\",\n            name=\"polymorphic_ctype\",\n            field=models.ForeignKey(\n                related_name=\"polymorphic_orders.payment_set+\",\n                editable=False,\n                on_delete=models.CASCADE,\n                to=\"contenttypes.ContentType\",\n                null=True,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "example/orders/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "example/orders/models.py",
    "content": "from django.db import models\nfrom django.utils.dates import MONTHS_3\nfrom django.utils.translation import gettext_lazy as _\n\nfrom polymorphic.models import PolymorphicModel\n\n\nclass Order(models.Model):\n    \"\"\"\n    An example order that has polymorphic relations\n    \"\"\"\n\n    title = models.CharField(_(\"Title\"), max_length=200)\n\n    class Meta:\n        verbose_name = _(\"Organisation\")\n        verbose_name_plural = _(\"Organisations\")\n        ordering = (\"title\",)\n\n    def __str__(self):\n        return self.title\n\n\nclass Payment(PolymorphicModel):\n    \"\"\"\n    A generic payment model.\n    \"\"\"\n\n    order = models.ForeignKey(Order, on_delete=models.CASCADE)\n    currency = models.CharField(default=\"USD\", max_length=3)\n    amount = models.DecimalField(max_digits=10, decimal_places=2)\n\n    class Meta:\n        verbose_name = _(\"Payment\")\n        verbose_name_plural = _(\"Payments\")\n\n    def __str__(self):\n        return f\"{self.currency} {self.amount}\"\n\n\nclass CreditCardPayment(Payment):\n    \"\"\"\n    Credit card\n    \"\"\"\n\n    MONTH_CHOICES = [(i, n) for i, n in sorted(MONTHS_3.items())]\n\n    card_type = models.CharField(max_length=10)\n    expiry_month = models.PositiveSmallIntegerField(choices=MONTH_CHOICES)\n    expiry_year = models.PositiveIntegerField()\n\n    class Meta:\n        verbose_name = _(\"Credit Card Payment\")\n        verbose_name_plural = _(\"Credit Card Payments\")\n\n\nclass BankPayment(Payment):\n    \"\"\"\n    Payment by bank\n    \"\"\"\n\n    bank_name = models.CharField(max_length=100)\n    swift = models.CharField(max_length=20)\n\n    class Meta:\n        verbose_name = _(\"Bank Payment\")\n        verbose_name_plural = _(\"Bank Payments\")\n\n\nclass SepaPayment(Payment):\n    \"\"\"\n    Payment by SEPA (EU)\n    \"\"\"\n\n    iban = models.CharField(max_length=34)\n    bic = models.CharField(max_length=11)\n\n    class Meta:\n        verbose_name = _(\"SEPA Payment\")\n        verbose_name_plural = _(\"SEPA Payments\")\n"
  },
  {
    "path": "example/pexp/__init__.py",
    "content": ""
  },
  {
    "path": "example/pexp/admin.py",
    "content": "from django.contrib import admin\nfrom pexp.models import *\n\nfrom polymorphic.admin import (\n    PolymorphicChildModelAdmin,\n    PolymorphicChildModelFilter,\n    PolymorphicParentModelAdmin,\n)\n\n\nclass ProjectAdmin(PolymorphicParentModelAdmin):\n    base_model = Project  # Can be set explicitly.\n    list_filter = (PolymorphicChildModelFilter,)\n    child_models = (Project, ArtProject, ResearchProject)\n\n\nclass ProjectChildAdmin(PolymorphicChildModelAdmin):\n    base_model = Project  # Can be set explicitly.\n\n    # On purpose, only have the shared fields here.\n    # The fields of the derived model should still be displayed.\n    base_fieldsets = ((\"Base fields\", {\"fields\": (\"topic\",)}),)\n\n\nadmin.site.register(Project, ProjectAdmin)\nadmin.site.register(ArtProject, ProjectChildAdmin)\nadmin.site.register(ResearchProject, ProjectChildAdmin)\n\n\nclass UUIDModelAAdmin(PolymorphicParentModelAdmin):\n    list_filter = (PolymorphicChildModelFilter,)\n    child_models = (UUIDModelA, UUIDModelB)\n\n\nclass UUIDModelAChildAdmin(PolymorphicChildModelAdmin):\n    pass\n\n\nadmin.site.register(UUIDModelA, UUIDModelAAdmin)\nadmin.site.register(UUIDModelB, UUIDModelAChildAdmin)\nadmin.site.register(UUIDModelC, UUIDModelAChildAdmin)\n\n\nclass ProxyAdmin(PolymorphicParentModelAdmin):\n    list_filter = (PolymorphicChildModelFilter,)\n    child_models = (ProxyA, ProxyB)\n\n\nclass ProxyChildAdmin(PolymorphicChildModelAdmin):\n    pass\n\n\nadmin.site.register(ProxyBase, ProxyAdmin)\nadmin.site.register(ProxyA, ProxyChildAdmin)\nadmin.site.register(ProxyB, ProxyChildAdmin)\n"
  },
  {
    "path": "example/pexp/dumpdata_test_correct_output.txt",
    "content": "[\n    {\n        \"pk\": 1,\n        \"model\": \"pexp.project\",\n        \"fields\": {\n            \"topic\": \"John's gathering\",\n            \"polymorphic_ctype\": 2\n        }\n    },\n    {\n        \"pk\": 2,\n        \"model\": \"pexp.project\",\n        \"fields\": {\n            \"topic\": \"Sculpting with Tim\",\n            \"polymorphic_ctype\": 3\n        }\n    },\n    {\n        \"pk\": 3,\n        \"model\": \"pexp.project\",\n        \"fields\": {\n            \"topic\": \"Swallow Aerodynamics\",\n            \"polymorphic_ctype\": 4\n        }\n    },\n    {\n        \"pk\": 2,\n        \"model\": \"pexp.artproject\",\n        \"fields\": {\n            \"artist\": \"T. Turner\"\n        }\n    },\n    {\n        \"pk\": 3,\n        \"model\": \"pexp.researchproject\",\n        \"fields\": {\n            \"supervisor\": \"Dr. Winter\"\n        }\n    }\n]\n"
  },
  {
    "path": "example/pexp/management/__init__.py",
    "content": ""
  },
  {
    "path": "example/pexp/management/commands/__init__.py",
    "content": ""
  },
  {
    "path": "example/pexp/management/commands/p2cmd.py",
    "content": "\"\"\"\nThis module is a scratchpad for general development, testing & debugging\nWell, even more so than pcmd.py. You best ignore p2cmd.py.\n\"\"\"\n\nimport sys\nimport time\nfrom pprint import pprint\nfrom random import Random\n\nfrom django.core.management import BaseCommand\nfrom django.db import connection\nfrom pexp.models import *\n\nrnd = Random()\n\n\ndef show_queries():\n    print()\n    print(\"QUERIES:\", len(connection.queries))\n    pprint(connection.queries)\n    print()\n    connection.queries = []\n\n\ndef print_timing(func, message=\"\", iterations=1):\n    def wrapper(*arg):\n        results = []\n        connection.queries_log.clear()\n        for i in range(iterations):\n            t1 = time.time()\n            x = func(*arg)\n            t2 = time.time()\n            results.append((t2 - t1) * 1000.0)\n        res_sum = 0\n        for r in results:\n            res_sum += r\n        print(\n            f\"{message}{func.func_name:<19}: {res_sum:.4f} ms, \"\n            f\"{len(connection.queries)} queries ({iterations} times)\"\n        )\n        sys.stdout.flush()\n\n    return wrapper\n\n\nclass Command(BaseCommand):\n    help = \"\"\n\n    def handle_noargs(self, **options):\n        if False:\n            TestModelA.objects.all().delete()\n            a = TestModelA.objects.create(field1=\"A1\")\n            b = TestModelB.objects.create(field1=\"B1\", field2=\"B2\")\n            c = TestModelC.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n            connection.queries_log.clear()\n            print(TestModelC.base_objects.all())\n            show_queries()\n\n        if False:\n            TestModelA.objects.all().delete()\n            for i in range(1000):\n                a = TestModelA.objects.create(field1=str(i % 100))\n                b = TestModelB.objects.create(field1=str(i % 100), field2=str(i % 200))\n                c = TestModelC.objects.create(\n                    field1=str(i % 100), field2=str(i % 200), field3=str(i % 300)\n                )\n                if i % 100 == 0:\n                    print(i)\n\n        f = print_timing(poly_sql_query, iterations=1000)\n        f()\n\n        f = print_timing(poly_sql_query2, iterations=1000)\n        f()\n\n        return\n\n        NormalModelA.objects.all().delete()\n        a = NormalModelA.objects.create(field1=\"A1\")\n        b = NormalModelB.objects.create(field1=\"B1\", field2=\"B2\")\n        c = NormalModelC.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n        qs = TestModelA.objects.raw(\"SELECT * from pexp_testmodela\")\n        for o in list(qs):\n            print(o)\n\n\ndef poly_sql_query():\n    cursor = connection.cursor()\n    cursor.execute(\n        \"\"\"\n        SELECT id, pexp_testmodela.field1, pexp_testmodelb.field2, pexp_testmodelc.field3\n        FROM pexp_testmodela\n        LEFT OUTER JOIN pexp_testmodelb\n        ON pexp_testmodela.id = pexp_testmodelb.testmodela_ptr_id\n        LEFT OUTER JOIN pexp_testmodelc\n        ON pexp_testmodelb.testmodela_ptr_id = pexp_testmodelc.testmodelb_ptr_id\n        WHERE pexp_testmodela.field1=%i\n        ORDER BY pexp_testmodela.id\n        \"\"\"\n        % rnd.randint(0, 100)\n    )\n    # row=cursor.fetchone()\n    return\n\n\ndef poly_sql_query2():\n    cursor = connection.cursor()\n    cursor.execute(\n        f\"\"\"\n        SELECT id, pexp_testmodela.field1\n        FROM pexp_testmodela\n        WHERE pexp_testmodela.field1={rnd.randint(0, 100)}\n        ORDER BY pexp_testmodela.id\n        \"\"\"\n    )\n    # row=cursor.fetchone()\n    return\n"
  },
  {
    "path": "example/pexp/management/commands/pcmd.py",
    "content": "\"\"\"\nThis module is a scratchpad for general development, testing & debugging.\n\"\"\"\n\nfrom django.core.management.base import NoArgsCommand\nfrom django.db import connection\nfrom pexp.models import *\n\n\ndef reset_queries():\n    connection.queries = []\n\n\nclass Command(NoArgsCommand):\n    help = \"\"\n\n    def handle_noargs(self, **options):\n        Project.objects.all().delete()\n        a = Project.objects.create(topic=\"John's gathering\")\n        b = ArtProject.objects.create(topic=\"Sculpting with Tim\", artist=\"T. Turner\")\n        c = ResearchProject.objects.create(topic=\"Swallow Aerodynamics\", supervisor=\"Dr. Winter\")\n        print(Project.objects.all())\n        print(\"\")\n\n        TestModelA.objects.all().delete()\n        a = TestModelA.objects.create(field1=\"A1\")\n        b = TestModelB.objects.create(field1=\"B1\", field2=\"B2\")\n        c = TestModelC.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n        print(TestModelA.objects.all())\n        print(\"\")\n"
  },
  {
    "path": "example/pexp/management/commands/polybench.py",
    "content": "\"\"\"\nThis module is a scratchpad for general development, testing & debugging\n\"\"\"\n\nimport sys\nimport time\nfrom pprint import pprint\n\nfrom django.core.management import BaseCommand\nfrom django.db import connection\nfrom pexp.models import *\n\nnum_objects = 1000\n\n\ndef show_queries():\n    print()\n    print(\"QUERIES:\", len(connection.queries))\n    pprint(connection.queries)\n    print()\n    connection.queries_log.clear()\n\n\n###################################################################################\n# benchmark wrappers\n\n\ndef print_timing(func, message=\"\", iterations=1):\n    def wrapper(*arg):\n        results = []\n        connection.queries_log.clear()\n        for i in range(iterations):\n            t1 = time.time()\n            x = func(*arg)\n            t2 = time.time()\n            results.append((t2 - t1) * 1000.0)\n        res_sum = 0\n        for r in results:\n            res_sum += r\n        median = res_sum / len(results)\n        print(\n            f\"{message}{func.func_name:<19}: {median:.0f} ms, \"\n            f\"{len(connection.queries) / len(results):d} queries\"\n        )\n        sys.stdout.flush()\n\n    return wrapper\n\n\ndef run_vanilla_any_poly(func, iterations=1):\n    f = print_timing(func, \"     \", iterations)\n    f(NormalModelC)\n    f = print_timing(func, \"poly \", iterations)\n    f(TestModelC)\n\n\n###################################################################################\n# benchmarks\n\n\ndef bench_create(model):\n    for i in range(num_objects):\n        model.objects.create(\n            field1=f\"abc{i}\",\n            field2=f\"abcd{i}\",\n            field3=f\"abcde{i}\",\n        )\n    # print 'count:',model.objects.count()\n\n\ndef bench_load1(model):\n    for o in model.objects.all():\n        pass\n\n\ndef bench_load1_short(model):\n    for i in range(num_objects / 100):\n        for o in model.objects.all()[:100]:\n            pass\n\n\ndef bench_load2(model):\n    for o in model.objects.all():\n        f1 = o.field1\n        f2 = o.field2\n        f3 = o.field3\n\n\ndef bench_load2_short(model):\n    for i in range(num_objects / 100):\n        for o in model.objects.all()[:100]:\n            f1 = o.field1\n            f2 = o.field2\n            f3 = o.field3\n\n\ndef bench_delete(model):\n    model.objects.all().delete()\n\n\n###################################################################################\n# Command\n\n\nclass Command(BaseCommand):\n    help = \"\"\n\n    def handle_noargs(self, **options):\n        func_list = [\n            (bench_delete, 1),\n            (bench_create, 1),\n            (bench_load1, 5),\n            (bench_load1_short, 5),\n            (bench_load2, 5),\n            (bench_load2_short, 5),\n        ]\n        for f, iterations in func_list:\n            run_vanilla_any_poly(f, iterations=iterations)\n"
  },
  {
    "path": "example/pexp/management/commands/polymorphic_create_test_data.py",
    "content": "\"\"\"\nThis module is a scratchpad for general development, testing & debugging\n\"\"\"\n\nfrom django.core.management import BaseCommand\nfrom pexp.models import *\n\n\nclass Command(BaseCommand):\n    help = \"\"\n\n    def handle_noargs(self, **options):\n        Project.objects.all().delete()\n        o = Project.objects.create(topic=\"John's gathering\")\n        o = ArtProject.objects.create(topic=\"Sculpting with Tim\", artist=\"T. Turner\")\n        o = ResearchProject.objects.create(topic=\"Swallow Aerodynamics\", supervisor=\"Dr. Winter\")\n        print(Project.objects.all())\n"
  },
  {
    "path": "example/pexp/migrations/0001_initial.py",
    "content": "from django.db import migrations, models\n\nimport polymorphic.showfields\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [(\"contenttypes\", \"0002_remove_content_type_name\")]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"NormalModelA\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        verbose_name=\"ID\",\n                        serialize=False,\n                        auto_created=True,\n                        primary_key=True,\n                    ),\n                ),\n                (\"field1\", models.CharField(max_length=10)),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"Project\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        verbose_name=\"ID\",\n                        serialize=False,\n                        auto_created=True,\n                        primary_key=True,\n                    ),\n                ),\n                (\"topic\", models.CharField(max_length=30)),\n            ],\n            options={\"abstract\": False},\n            bases=(polymorphic.showfields.ShowFieldContent, models.Model),\n        ),\n        migrations.CreateModel(\n            name=\"ProxyBase\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        verbose_name=\"ID\",\n                        serialize=False,\n                        auto_created=True,\n                        primary_key=True,\n                    ),\n                ),\n                (\"title\", models.CharField(max_length=200)),\n                (\n                    \"polymorphic_ctype\",\n                    models.ForeignKey(\n                        related_name=\"polymorphic_pexp.proxybase_set+\",\n                        editable=False,\n                        on_delete=models.CASCADE,\n                        to=\"contenttypes.ContentType\",\n                        null=True,\n                    ),\n                ),\n            ],\n            options={\"ordering\": (\"title\",)},\n        ),\n        migrations.CreateModel(\n            name=\"TestModelA\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        verbose_name=\"ID\",\n                        serialize=False,\n                        auto_created=True,\n                        primary_key=True,\n                    ),\n                ),\n                (\"field1\", models.CharField(max_length=10)),\n            ],\n            options={\"abstract\": False},\n            bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),\n        ),\n        migrations.CreateModel(\n            name=\"UUIDModelA\",\n            fields=[\n                (\n                    \"uuid_primary_key\",\n                    models.UUIDField(serialize=False, primary_key=True),\n                ),\n                (\"field1\", models.CharField(max_length=10)),\n            ],\n            options={\"abstract\": False},\n            bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),\n        ),\n        migrations.CreateModel(\n            name=\"ArtProject\",\n            fields=[\n                (\n                    \"project_ptr\",\n                    models.OneToOneField(\n                        parent_link=True,\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        on_delete=models.CASCADE,\n                        to=\"pexp.Project\",\n                    ),\n                ),\n                (\"artist\", models.CharField(max_length=30)),\n            ],\n            options={\"abstract\": False},\n            bases=(\"pexp.project\",),\n        ),\n        migrations.CreateModel(\n            name=\"NormalModelB\",\n            fields=[\n                (\n                    \"normalmodela_ptr\",\n                    models.OneToOneField(\n                        parent_link=True,\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        on_delete=models.CASCADE,\n                        to=\"pexp.NormalModelA\",\n                    ),\n                ),\n                (\"field2\", models.CharField(max_length=10)),\n            ],\n            bases=(\"pexp.normalmodela\",),\n        ),\n        migrations.CreateModel(\n            name=\"ResearchProject\",\n            fields=[\n                (\n                    \"project_ptr\",\n                    models.OneToOneField(\n                        parent_link=True,\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        on_delete=models.CASCADE,\n                        to=\"pexp.Project\",\n                    ),\n                ),\n                (\"supervisor\", models.CharField(max_length=30)),\n            ],\n            options={\"abstract\": False},\n            bases=(\"pexp.project\",),\n        ),\n        migrations.CreateModel(\n            name=\"TestModelB\",\n            fields=[\n                (\n                    \"testmodela_ptr\",\n                    models.OneToOneField(\n                        parent_link=True,\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        on_delete=models.CASCADE,\n                        to=\"pexp.TestModelA\",\n                    ),\n                ),\n                (\"field2\", models.CharField(max_length=10)),\n            ],\n            options={\"abstract\": False},\n            bases=(\"pexp.testmodela\",),\n        ),\n        migrations.CreateModel(\n            name=\"UUIDModelB\",\n            fields=[\n                (\n                    \"uuidmodela_ptr\",\n                    models.OneToOneField(\n                        parent_link=True,\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        on_delete=models.CASCADE,\n                        to=\"pexp.UUIDModelA\",\n                    ),\n                ),\n                (\"field2\", models.CharField(max_length=10)),\n            ],\n            options={\"abstract\": False},\n            bases=(\"pexp.uuidmodela\",),\n        ),\n        migrations.AddField(\n            model_name=\"uuidmodela\",\n            name=\"polymorphic_ctype\",\n            field=models.ForeignKey(\n                related_name=\"polymorphic_pexp.uuidmodela_set+\",\n                editable=False,\n                on_delete=models.CASCADE,\n                to=\"contenttypes.ContentType\",\n                null=True,\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"testmodela\",\n            name=\"polymorphic_ctype\",\n            field=models.ForeignKey(\n                related_name=\"polymorphic_pexp.testmodela_set+\",\n                editable=False,\n                on_delete=models.CASCADE,\n                to=\"contenttypes.ContentType\",\n                null=True,\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"project\",\n            name=\"polymorphic_ctype\",\n            field=models.ForeignKey(\n                related_name=\"polymorphic_pexp.project_set+\",\n                editable=False,\n                on_delete=models.CASCADE,\n                to=\"contenttypes.ContentType\",\n                null=True,\n            ),\n        ),\n        migrations.CreateModel(\n            name=\"ProxyA\", fields=[], options={\"proxy\": True}, bases=(\"pexp.proxybase\",)\n        ),\n        migrations.CreateModel(\n            name=\"ProxyB\", fields=[], options={\"proxy\": True}, bases=(\"pexp.proxybase\",)\n        ),\n        migrations.CreateModel(\n            name=\"NormalModelC\",\n            fields=[\n                (\n                    \"normalmodelb_ptr\",\n                    models.OneToOneField(\n                        parent_link=True,\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        on_delete=models.CASCADE,\n                        to=\"pexp.NormalModelB\",\n                    ),\n                ),\n                (\"field3\", models.CharField(max_length=10)),\n            ],\n            bases=(\"pexp.normalmodelb\",),\n        ),\n        migrations.CreateModel(\n            name=\"TestModelC\",\n            fields=[\n                (\n                    \"testmodelb_ptr\",\n                    models.OneToOneField(\n                        parent_link=True,\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        on_delete=models.CASCADE,\n                        to=\"pexp.TestModelB\",\n                    ),\n                ),\n                (\"field3\", models.CharField(max_length=10)),\n                (\n                    \"field4\",\n                    models.ManyToManyField(related_name=\"related_c\", to=\"pexp.TestModelB\"),\n                ),\n            ],\n            options={\"abstract\": False},\n            bases=(\"pexp.testmodelb\",),\n        ),\n        migrations.CreateModel(\n            name=\"UUIDModelC\",\n            fields=[\n                (\n                    \"uuidmodelb_ptr\",\n                    models.OneToOneField(\n                        parent_link=True,\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        on_delete=models.CASCADE,\n                        to=\"pexp.UUIDModelB\",\n                    ),\n                ),\n                (\"field3\", models.CharField(max_length=10)),\n            ],\n            options={\"abstract\": False},\n            bases=(\"pexp.uuidmodelb\",),\n        ),\n    ]\n"
  },
  {
    "path": "example/pexp/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "example/pexp/models.py",
    "content": "from django.db import models\n\nfrom polymorphic.models import PolymorphicModel\nfrom polymorphic.showfields import ShowFieldContent, ShowFieldTypeAndContent\n\n\nclass Project(ShowFieldContent, PolymorphicModel):\n    \"\"\"Polymorphic model\"\"\"\n\n    topic = models.CharField(max_length=30)\n\n\nclass ArtProject(Project):\n    artist = models.CharField(max_length=30)\n\n\nclass ResearchProject(Project):\n    supervisor = models.CharField(max_length=30)\n\n\nclass UUIDModelA(ShowFieldTypeAndContent, PolymorphicModel):\n    \"\"\"UUID as primary key example\"\"\"\n\n    uuid_primary_key = models.UUIDField(primary_key=True)\n    field1 = models.CharField(max_length=10)\n\n\nclass UUIDModelB(UUIDModelA):\n    field2 = models.CharField(max_length=10)\n\n\nclass UUIDModelC(UUIDModelB):\n    field3 = models.CharField(max_length=10)\n\n\nclass ProxyBase(PolymorphicModel):\n    \"\"\"Proxy model example - a single table with multiple types.\"\"\"\n\n    title = models.CharField(max_length=200)\n\n    def __unicode__(self):\n        return f\"<ProxyBase[type={self.polymorphic_ctype}]: {self.title}>\"\n\n    class Meta:\n        ordering = (\"title\",)\n\n\nclass ProxyA(ProxyBase):\n    class Meta:\n        proxy = True\n\n    def __unicode__(self):\n        return f\"<ProxyA: {self.title}>\"\n\n\nclass ProxyB(ProxyBase):\n    class Meta:\n        proxy = True\n\n    def __unicode__(self):\n        return f\"<ProxyB: {self.title}>\"\n\n\n# Internals for management command tests\n\n\nclass TestModelA(ShowFieldTypeAndContent, PolymorphicModel):\n    field1 = models.CharField(max_length=10)\n\n\nclass TestModelB(TestModelA):\n    field2 = models.CharField(max_length=10)\n\n\nclass TestModelC(TestModelB):\n    field3 = models.CharField(max_length=10)\n    field4 = models.ManyToManyField(TestModelB, related_name=\"related_c\")\n\n\nclass NormalModelA(models.Model):\n    \"\"\"Normal Django inheritance, no polymorphic behavior\"\"\"\n\n    field1 = models.CharField(max_length=10)\n\n\nclass NormalModelB(NormalModelA):\n    field2 = models.CharField(max_length=10)\n\n\nclass NormalModelC(NormalModelB):\n    field3 = models.CharField(max_length=10)\n"
  },
  {
    "path": "justfile",
    "content": "set windows-shell := [\"powershell.exe\", \"-NoLogo\", \"-Command\"]\nset unstable := true\nset script-interpreter := ['uv', 'run', '--script']\n\nexport PYTHONPATH := source_directory()\n\n[private]\ndefault:\n    @just --list --list-submodules\n\n# run the django admin\n[script]\nmanage *COMMAND:\n    import os\n    import sys\n    from django.core import management\n    sys.path.append(os.getcwd())\n    os.environ[\"DJANGO_SETTINGS_MODULE\"] = \"polymorphic.tests.debug\"\n    os.environ[\"SQLITE_DATABASES\"] = \"test1.db,test2.db\"\n    management.execute_from_command_line(sys.argv + \"{{ COMMAND }}\".split(\" \"))\n\n# install the uv package manager\n[linux]\n[macos]\ninstall_uv:\n    curl -LsSf https://astral.sh/uv/install.sh | sh\n\n# install the uv package manager\n[windows]\ninstall_uv:\n    powershell -ExecutionPolicy ByPass -c \"irm https://astral.sh/uv/install.ps1 | iex\"\n\n# setup the venv, pre-commit hooks\nsetup python=\"python\":\n    uv venv -p {{ python }}\n    @just install-precommit\n\n# install git pre-commit hooks\ninstall-precommit:\n    @just run --no-default-groups --group precommit --exact --isolated pre-commit install\n\n# update and install development dependencies\ninstall *OPTS:\n    @just install-precommit\n    uv sync {{ OPTS }}\n\n# install playwright dependencies\ninstall-playwright:\n    @just run --no-default-groups --group test playwright install chromium\n\n# run static type checking with mypy\ncheck-types-mypy *ENV:\n    @just run {{ ENV }} --no-default-groups --group integrations --group typing mypy src/polymorphic\n\n# run static type checking with pyright\ncheck-types-pyright *ENV:\n    @just run {{ ENV }} --no-default-groups --group integrations --group typing pyright src/polymorphic\n    @just run {{ ENV }} --no-default-groups --group integrations --group typing pyright --project src/polymorphic/tests/examples/type_hints/pyright.json\n\n# run all static type checking\ncheck-types *ENV:\n    @just check-types-mypy {{ ENV }}\n    @just check-types-pyright {{ ENV }}\n\n# run all static type checking in an isolated environment\ncheck-types-isolated *ENV:\n    @just check-types-mypy {{ ENV }} --exact --isolated\n    @just check-types-pyright {{ ENV }} --exact --isolated\n\n# run package checks\ncheck-package:\n    uv pip check\n\n# remove doc build artifacts-\n[script]\nclean-docs:\n    import shutil\n    shutil.rmtree('./docs/_build', ignore_errors=True)\n\n# remove the virtual environment\n[script]\nclean-env:\n    import shutil\n    import sys\n    shutil.rmtree(\".venv\", ignore_errors=True)\n\n# remove all git ignored files\nclean-git-ignored:\n    git clean -fdX\n\n# remove all non repository artifacts\nclean: clean-docs clean-env clean-git-ignored\n\n# build html documentation\nbuild-docs-html:\n    @just run --group docs --group integrations sphinx-build --fresh-env --builder html --doctree-dir ./docs/_build/doctrees ./docs/ ./docs/_build/html\n\n[script]\n_open-pdf-docs:\n    import webbrowser\n    from pathlib import Path\n    webbrowser.open(f\"file://{Path('./docs/_build/pdf/django-polymorphic.pdf').absolute()}\")\n\n# build pdf documentation\nbuild-docs-pdf:\n    @just run --group docs --group integrations sphinx-build --fresh-env --builder latex --doctree-dir ./docs/_build/doctrees ./docs/ ./docs/_build/pdf\n    make -C ./docs/_build/pdf\n    @just _open-pdf-docs\n\n# build the docs\nbuild-docs: build-docs-html\n\n# build docs and package\nbuild: build-docs-html\n    @just manage compilemessages --ignore \".venv/*\"\n    uv build\n\n# regenerate test migrations using the lowest version of Django\nremake-test-migrations:\n    - rm src/polymorphic/tests/migrations/00*.py\n    - rm src/polymorphic/tests/deletion/migrations/00*.py\n    - rm src/polymorphic/tests/other/migrations/00*.py\n    - rm src/polymorphic/tests/examples/**/migrations/00*.py\n    - rm src/polymorphic/tests/examples/integrations/**/migrations/00*.py\n    - rm src/polymorphic/tests/examples/type_hints/**/migrations/00*.py\n    uv run --no-default-groups  --exact --isolated --resolution lowest-direct --group integrations --script ./manage.py makemigrations\n\n# open the html documentation\n[script]\nopen-docs:\n    import os\n    import webbrowser\n    webbrowser.open(f'file://{os.getcwd()}/docs/_build/html/index.html')\n\n# build and open the documentation\ndocs: build-docs-html open-docs\n\n# serve the documentation, with auto-reload\ndocs-live:\n    @just run --no-default-groups --group docs --all-extras --group integrations --exact --isolated sphinx-autobuild docs docs/_build --open-browser --watch src --port 0 --delay 1\n\n_link_check:\n    -uv run --no-default-groups --group docs sphinx-build -b linkcheck -Q -D linkcheck_timeout=10 ./docs/ ./docs/_build\n\n# check the documentation links for broken links\n[script]\ncheck-docs-links: _link_check\n    import os\n    import sys\n    import json\n    from pathlib import Path\n    # The json output isn't valid, so we have to fix it before we can process.\n    data = json.loads(f\"[{','.join((Path(os.getcwd()) / 'docs/_build/output.json').read_text().splitlines())}]\")\n    broken_links = [link for link in data if link[\"status\"] not in {\"working\", \"redirected\", \"unchecked\", \"ignored\"}]\n    if broken_links:\n        for link in broken_links:\n            print(f\"[{link['status']}] {link['filename']}:{link['lineno']} -> {link['uri']}\", file=sys.stderr)\n        sys.exit(1)\n\n# lint the documentation\ncheck-docs *ENV:\n    @just run {{ ENV }} --no-default-groups --group lint doc8 --ignore-path ./docs/_build --max-line-length 100 -q ./docs\n\n# lint the code\ncheck-lint *ENV:\n    @just run {{ ENV }} --no-default-groups --group lint ruff check --select I\n    @just run {{ ENV }} --no-default-groups --group lint ruff check\n\n# check if the code needs formatting\ncheck-format *ENV:\n    @just run {{ ENV }} --no-default-groups --group lint ruff format --check\n    @just run {{ ENV }} --no-default-groups --group lint ruff format --line-length 80 --check src/polymorphic/tests/examples\n\n# check that the readme renders\ncheck-readme *ENV:\n    @just run {{ ENV }} --no-default-groups --group lint -m readme_renderer ./README.md -o /tmp/README.html\n\n_check-readme-quiet *ENV:\n    @just --quiet check-readme {{ ENV }}\n\n# sort the python imports\nsort-imports:\n    @just run --no-default-groups --group lint ruff check --fix --select I\n\n# format the code and sort imports\nformat: sort-imports\n    just --fmt --unstable\n    @just run --no-default-groups --group lint ruff format\n    @just run --no-default-groups --group lint ruff format --line-length 80 src/polymorphic/tests/examples\n\n# sort the imports and fix linting issues\nlint: sort-imports\n    @just run --no-default-groups --group lint ruff check --fix\n\n# fix formatting, linting issues and import sorting\nfix: lint format\n\n# run bandit static security analysis\nbandit:\n    @just run --no-default-groups --group lint bandit -c pyproject.toml -r ./src -f sarif -o bandit.sarif\n\n# run all static checks\ncheck *ENV:\n    @just check-lint {{ ENV }}\n    @just check-format {{ ENV }}\n    @just check-types {{ ENV }}\n    @just check-docs {{ ENV }}\n    @just check-readme {{ ENV }}\n    @just check-package\n\n# run all checks including documentation link checking (slow)\ncheck-all *ENV:\n    @just check {{ ENV }}\n    @just check-docs-links\n\n# run zizmor security analysis of CI\nzizmor:\n    cargo install --locked zizmor\n    zizmor --format sarif .github/workflows/ > zizmor.sarif\n\n# run tests\ntest *TESTS:\n    @just run --group test pytest {{ TESTS }} --cov\n\n# run all tests including integrations (accepts uv run flags e.g. -p /path/to/python --group dj52)\ntest-all *ENV:\n    @just run {{ ENV }} --no-default-groups --exact --group test --isolated pytest --cov\n    @just run {{ ENV }} --no-default-groups --group integrations --group guardian --group reversion --group test --exact --isolated pytest -m integration --cov --cov-append\n\n# test django-revision integration\ntest-reversion *TESTS:\n    @just run --no-default-groups --group reversion --group test --exact --isolated pytest -m integration src/polymorphic/tests/examples/integrations/reversion {{ TESTS }}\n\n# test django extra views integration\ntest-extra-views *TESTS:\n    @just run --no-default-groups --group extra-views --group test --exact --isolated pytest -m integration src/polymorphic/tests/examples/integrations/extra_views {{ TESTS }}\n\n# test django rest framework integration\ntest-drf *TESTS:\n    @just run --no-default-groups --group drf --group test --exact --isolated pytest -m integration src/polymorphic/tests/examples/integrations/drf {{ TESTS }}\n\n# test guardian integration\ntest-guardian *TESTS:\n    @just run --no-default-groups --group guardian --group test --exact --isolated pytest -m integration src/polymorphic/tests/examples/integrations/guardian {{ TESTS }}\n\n# run all third party integration tests\ntest-integrations:\n    @just run --no-default-groups --group integrations --group guardian --group reversion --group test --exact --isolated pytest -m integration --cov --cov-append\n\n# debug an test\ndebug-test *TESTS:\n    @just run pytest \\\n      -o addopts='-ra -q' \\\n      -s --trace --pdbcls=IPython.terminal.debugger:Pdb \\\n      --headed {{ TESTS }}\n\n# run the pre-commit checks\nprecommit:\n    @just run pre-commit\n\n# generate the test coverage report\ncoverage:\n    @just run --no-default-groups --group coverage coverage combine --keep *.coverage\n    @just run --no-default-groups --group coverage coverage report\n    @just run --no-default-groups --group coverage coverage xml\n\n_install-docs:\n    uv sync --no-default-groups --group docs --all-extras\n\n# get the intersphinx references for the given library\n[script]\nfetch-refs LIB: _install-docs\n    import os\n    from pathlib import Path\n    import logging as _logging\n    import sys\n    import runpy\n    from sphinx.ext.intersphinx import inspect_main\n    _logging.basicConfig()\n\n    libs = runpy.run_path(Path(os.getcwd()) / \"docs/conf.py\").get(\"intersphinx_mapping\")\n    url = libs.get(\"{{ LIB }}\", None)\n    if not url:\n        sys.exit(f\"Unrecognized {{ LIB }}, must be one of: {', '.join(libs.keys())}\")\n    if url[1] is None:\n        url = f\"{url[0].rstrip('/')}/objects.inv\"\n    else:\n        url = url[1]\n\n    raise SystemExit(inspect_main([url]))\n\n# run the command in the virtual environment\nrun +ARGS:\n    uv run {{ ARGS }}\n\n# validate the given version string against the lib version\n[script]\nvalidate_version VERSION:\n    import re\n    import tomllib\n    import polymorphic\n    from packaging.version import Version\n    raw_version = \"{{ VERSION }}\".lstrip(\"v\")\n    version_obj = Version(raw_version)\n    # the version should be normalized\n    assert str(version_obj) == raw_version\n    # make sure all places the version appears agree\n    assert raw_version == tomllib.load(open('pyproject.toml', 'rb'))['project']['version']\n    assert raw_version == polymorphic.__version__\n    print(raw_version)\n\n# issue a release for the given semver string (e.g. 2.1.0)\nrelease VERSION: install check-all\n    @just validate_version v{{ VERSION }}\n    git tag -s v{{ VERSION }} -m \"{{ VERSION }} Release\"\n    git push upstream v{{ VERSION }}\n"
  },
  {
    "path": "manage.py",
    "content": "# This helps pytest-django locate the project.\nimport os\nimport sys\n\nif __name__ == \"__main__\":\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"polymorphic.tests.settings\")\n    from django.core.management import execute_from_command_line\n\n    execute_from_command_line(sys.argv)\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"django-polymorphic\"\nversion = \"4.11.3\"\ndescription = \"Seamless polymorphic inheritance for Django models.\"\nreadme = \"README.md\"\nlicense = \"BSD-3-Clause\"\nlicense-files = [\n    \"LICENSE\",\n    \"src/polymorphic/contrib/drf/LICENSE\"\n]\nrequires-python = \">=3.10,<4.0\"\nrepository = \"https://github.com/django-commons/django-polymorphic\"\nhomepage = \"https://django-polymorphic.rtfd.io\"\nauthors = [\n  {name = \"Bert Constantin\", email = \"bert.constantin@gmx.de\"},\n  {name = \"Diederik van der Boor\", email = \"vdboor@edoburu.nl\"},\n  {name = \"Christopher Glass\", email = \"tribaal@ubuntu.com\"},\n]\nmaintainers = [\n  {name = \"Brian Kohan\", email = \"bckohan@gmail.com\"}\n]\nkeywords = [\n    \"django\", \"polymorphic\", \"polymorphism\", \"django-admin\", \"django-orm\", \"django-formsets\", \"model\"\n]\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Environment :: Web Environment\",\n    \"Framework :: Django\",\n    \"Framework :: Django :: 4.2\",\n    \"Framework :: Django :: 5.2\",\n    \"Framework :: Django :: 6.0\",\n    \"Intended Audience :: Developers\",\n    \"License :: OSI Approved :: BSD License\",\n    \"Natural Language :: English\",\n    \"Operating System :: OS Independent\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3 :: Only\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Programming Language :: Python :: 3.14\",\n    \"Topic :: Database\",\n    \"Topic :: Internet :: WWW/HTTP\",\n    \"Topic :: Internet :: WWW/HTTP :: Site Management\",\n    \"Topic :: Software Development :: Libraries\",\n    \"Topic :: Software Development :: Libraries :: Python Modules\",\n    \"Typing :: Typed\"\n]\ndependencies = [\n    \"Django >= 4.2\",\n    \"typing_extensions >= 4.12.0\",\n]\n\n[project.urls]\n\"Download\" = \"https://github.com/django-commons/django-polymorphic/tarball/main\"\n\"Documentation\" = \"https://django-polymorphic.readthedocs.io\"\n\"Homepage\" = \"https://github.com/django-commons/django-polymorphic\"\n\"Repository\" = \"https://github.com/django-commons/django-polymorphic\"\n\"Issues\" = \"https://github.com/django-commons/django-polymorphic/issues\"\n\"Changelog\" = \"https://django-polymorphic.readthedocs.io/en/stable/changelog/index.html\"\n\"Code_of_Conduct\" = \"https://github.com/django-commons/membership/blob/main/CODE_OF_CONDUCT.md\"\n\n[tool.uv]\npackage = true\nconflicts = [\n    [{ group = \"dj42\" }, { group = \"dj52\" }, { group = \"dj60\" }],\n    [{ group = \"mysqlclient14\" }, { group = \"mysqlclient2x\" }],\n    [{ group = \"psycopg2\" }, { group = \"psycopg3\" }],\n    [{ group = \"cx_oracle\" }, { group = \"oracledb\" }],\n]\n\n[tool.hatch.version]\npath = \"src/polymorphic/__init__.py\"\n\n[tool.hatch.build.targets.sdist]\ninclude = [\"src/polymorphic\"]\nexclude = [\"src/polymorphic/tests\"]\n\n[tool.hatch.build.targets.wheel]\npackages = [\"src/polymorphic\"]\nartifacts = [\"*.mo\", \"py.typed\"]\n\n[tool.doc8]\nmax-line-length = 100\nsphinx = true\n\n[tool.ruff]\nline-length = 99\ntarget-version = \"py310\"\n\nexclude = [\n    \"**/migrations/*.py\",\n    \"**/migrations/**\",\n    \"src/polymorphic/tests/examples\",\n]\n\n[tool.ruff.lint]\nextend-ignore = [\n    \"E501\",\n]\nselect = [\n    \"E\",\n    \"F\",\n    \"I\",\n    \"W\",\n]\n\n[tool.ruff.lint.per-file-ignores]\n\"example/**\" = [\n    \"F401\",\n    \"F403\",\n    \"F405\",\n    \"F841\",\n    \"I\",\n]\n\"src/polymorphic/tests/**\" = [\n    \"F401\",\n    \"F403\",\n    \"F405\",\n    \"F841\",\n    \"I\",\n]\n\n[tool.pytest.ini_options]\nDJANGO_SETTINGS_MODULE = \"polymorphic.tests.settings\"\npythonpath = [\"src\"]\ndjango_find_project = false\ntestpaths = [\"src/polymorphic/tests\"]\npython_files = \"test*.py\"\npython_classes = \"Test*\"\npython_functions = \"test_*\"\nnorecursedirs = \"*.egg .eggs dist build docs .tox .git __pycache__\"\nmarkers = [\n  \"integration: tests under examples/integrations (opt-in)\",\n  \"ui: browser-based tests\",\n]\naddopts = [\n    \"--strict-markers\",\n   \"-m\", \"not integration\",\n]\n\n[tool.coverage.run]\nsource = [\n    \"src\"\n]\nomit = [\"*/migrations/*\", \"*/tests/*\", \"src/polymorphic/tests/*\"]\nbranch = true\nrelative_files = true\n\n[tool.coverage.report]\nshow_missing = true\n\n[tool.mypy]\nstrict = false\nwarn_return_any = true\nwarn_unused_configs = true\ndisallow_untyped_defs = false\ndisallow_incomplete_defs = false\ncheck_untyped_defs = true\ndisallow_untyped_decorators = false\nno_implicit_optional = true\nwarn_redundant_casts = true\nwarn_unused_ignores = true\nwarn_no_return = true\nwarn_unreachable = true\nstrict_equality = true\nplugins = [\"mypy_django_plugin.main\"]\n\n[[tool.mypy.overrides]]\nmodule = \"polymorphic.tests.*\"\nignore_errors = true\n\n[[tool.mypy.overrides]]\nmodule = \"polymorphic.tests.examples.type_hints.*\"\nignore_errors = false\n\n[tool.django-stubs]\ndjango_settings_module = \"polymorphic.tests.settings\"\n\n[tool.pyright]\npythonVersion = \"3.10\"\ntypeCheckingMode = \"basic\"\ninclude = [\"src/polymorphic\"]\nexclude = [\n  \"**/migrations\",\n  \"example\",\n  \"src/polymorphic/**/tests/**\",\n]\nreportMissingImports = true\nreportMissingTypeStubs = false\nreportUnusedImport = true\nreportUnusedClass = false\nreportUnusedFunction = false\nreportUnusedVariable = true\nreportDuplicateImport = true\nreportOptionalMemberAccess = false\nreportGeneralTypeIssues = false\nreportOptionalSubscript = false\nreportPrivateImportUsage = false\n\n\n[tool.bandit]\ntargets = [\"src\"]\nexclude_dirs = [\n    \"./src/polymorphic/tests\",\n    \"./src/polymorphic/migrations\",\n    \"./example\",\n    \"./.venv\",\n    \"./docs\",\n]\n\nskips = [\n    \"B101\",  # assert_used - assertions are fine in tests and Django code\n]\n\n# Severity level threshold\n# Options: LOW, MEDIUM, HIGH\nseverity = \"MEDIUM\"\n\n# Confidence level threshold\n# Options: LOW, MEDIUM, HIGH\nconfidence = \"MEDIUM\"\n\n\n[dependency-groups]\ntyping = [\n    \"django-stubs>=5.1.1\",\n    \"django-stubs-ext>=5.1.1\",\n    \"djangorestframework-stubs>=3.16.1\",\n    \"pyright>=1.1.390\",\n    \"mypy>=1.19.1\",\n]\nlint = [\n    \"ruff>=0.15.0\",\n    \"readme-renderer[md]>=43.0\",\n    \"doc8>=1.1.2\",\n    \"bandit[toml,sarif]>=1.7.10\"\n]\ncoverage = [\"coverage>=7.6.1\"]\ntest = [\n    { include-group = \"coverage\" },\n    \"django-test-migrations>=1.5.0\",\n    \"pytest>=8.3.4\",\n    \"pytest-cov>=5.0.0\",\n    \"pytest-django>=4.10.0\",\n    \"pytest-mock>=3.15.1\",\n    \"pytest-playwright>=0.7.2\"\n]\ndocs = [\n    \"django-extra-views>=0.16.0\",\n    \"furo>=2025.7.19\",\n    \"sphinx>=7.1.2\",\n    \"sphinx-autobuild>=2024.10.3\",\n    \"sphinxcontrib-django>=2.5\",\n]\npsycopg2 = [\n    \"psycopg2>=2.9.10\",\n]\npsycopg3 = [\n    \"psycopg>=3.3.2\",\n]\nmysql = [\n    \"mysqlclient>=1.4.0\",\n]\nmysqlclient14 = [\n    \"mysqlclient==1.4.3\",\n]\nmysqlclient2x = [\n    \"mysqlclient>=2.2.8\",\n]\ncx_oracle = [\n    \"cx-oracle>=8.3.0\"\n]\n\noracledb = [\n    \"oracledb>=2.3.0\",\n]\nreversion = [\n    \"django-reversion>=6.1.0\",\n]\nextra-views = [\n    \"django-extra-views>=0.16.0\",\n]\ndrf = [\n    \"django-filter>=24.0\",\n    \"djangorestframework>=3.16.1\",\n]\nguardian = [\n    \"django-guardian<3.3.0\",\n]\nintegrations = [\n    { include-group = \"extra-views\" },\n    { include-group = \"drf\" },\n]\nprecommit = [\n    \"pre-commit>=3.5.0\",\n]\ndj42 = [\n    \"Django~=4.2; python_version < '3.13'\",\n]\ndj52 = [\n    \"Django~=5.2\",\n]\ndj60 = [\n    \"Django~=6.0; python_version >= '3.12'\",\n]\ndev = [\n    { include-group = \"typing\" },\n    { include-group = \"test\" },\n    { include-group = \"docs\" },\n    { include-group = \"drf\" },\n    { include-group = \"lint\" },\n    { include-group = \"precommit\" },\n    \"packaging>=24.2\",\n    \"tomlkit>=0.13.3\",\n    \"ipdb>=0.13.13\",\n    \"ipython>=8.18.1\",\n]\n"
  },
  {
    "path": "src/polymorphic/__init__.py",
    "content": "r\"\"\"\n::\n\n                    ██████╗      ██╗ █████╗ ███╗   ██╗ ██████╗  ██████╗\n                    ██╔══██╗     ██║██╔══██╗████╗  ██║██╔════╝ ██╔═══██╗\n                    ██║  ██║     ██║███████║██╔██╗ ██║██║  ███╗██║   ██║\n                    ██║  ██║██   ██║██╔══██║██║╚██╗██║██║   ██║██║   ██║\n                    ██████╔╝╚█████╔╝██║  ██║██║ ╚████║╚██████╔╝╚██████╔╝\n                    ╚═════╝  ╚════╝ ╚═╝  ╚═╝╚═╝  ╚═══╝ ╚═════╝  ╚═════╝\n\n    ██████╗  ██████╗ ██╗  ██╗   ██╗███╗   ███╗ ██████╗ ██████╗ ██████╗ ██╗  ██╗██╗ ██████╗\n    ██╔══██╗██╔═══██╗██║  ╚██╗ ██╔╝████╗ ████║██╔═══██╗██╔══██╗██╔══██╗██║  ██║██║██╔════╝\n    ██████╔╝██║   ██║██║   ╚████╔╝ ██╔████╔██║██║   ██║██████╔╝██████╔╝███████║██║██║\n    ██╔═══╝ ██║   ██║██║    ╚██╔╝  ██║╚██╔╝██║██║   ██║██╔══██╗██╔═══╝ ██╔══██║██║██║\n    ██║     ╚██████╔╝███████╗██║   ██║ ╚═╝ ██║╚██████╔╝██║  ██║██║     ██║  ██║██║╚██████╗\n    ╚═╝      ╚═════╝ ╚══════╝╚═╝   ╚═╝     ╚═╝ ╚═════╝ ╚═╝  ╚═╝╚═╝     ╚═╝  ╚═╝╚═╝ ╚═════╝\n\n\nSeamless Polymorphic Inheritance for Django Models\n\"\"\"\n\nfrom typing import Final\n\nVERSION: Final[str] = \"4.11.3\"\n\n__title__: Final = \"Django Polymorphic\"\n__version__ = VERSION  # version synonym for backwards compatibility\n__author__: Final[str] = \"Brian Kohan\"\n__license__: Final = \"BSD-3-Clause\"\n__copyright__: Final[str] = (\n    \"Copyright 2010-2026, Bert Constantin, Chris Glass, Diederik van der Boor, Brian Kohan\"\n)\n"
  },
  {
    "path": "src/polymorphic/admin/__init__.py",
    "content": "\"\"\"\nModelAdmin code to display polymorphic models.\n\nThe admin consists of a parent admin (which shows in the admin with a list),\nand a child admin (which is used internally to show the edit/delete dialog).\n\"\"\"\n\n# Admins for the regular models\nfrom .parentadmin import PolymorphicParentModelAdmin  # noqa\nfrom .childadmin import PolymorphicChildModelAdmin\nfrom .filters import PolymorphicChildModelFilter\n\n# Utils\nfrom .forms import PolymorphicModelChoiceForm\n\n# Expose generic admin features too. There is no need to split those\n# as the admin already relies on contenttypes.\nfrom .generic import GenericPolymorphicInlineModelAdmin  # base class\nfrom .generic import GenericStackedPolymorphicInline  # stacked inline\n\n# Helpers for the inlines\nfrom .helpers import PolymorphicInlineSupportMixin  # mixin for the regular model admin!\nfrom .helpers import PolymorphicInlineAdminForm, PolymorphicInlineAdminFormSet\n\n# Inlines\nfrom .inlines import PolymorphicInlineModelAdmin  # base class\nfrom .inlines import StackedPolymorphicInline  # stacked inline\n\n__all__ = (\n    \"PolymorphicParentModelAdmin\",\n    \"PolymorphicChildModelAdmin\",\n    \"PolymorphicModelChoiceForm\",\n    \"PolymorphicChildModelFilter\",\n    \"PolymorphicInlineAdminForm\",\n    \"PolymorphicInlineAdminFormSet\",\n    \"PolymorphicInlineSupportMixin\",\n    \"PolymorphicInlineModelAdmin\",\n    \"StackedPolymorphicInline\",\n    \"GenericPolymorphicInlineModelAdmin\",\n    \"GenericStackedPolymorphicInline\",\n)\n"
  },
  {
    "path": "src/polymorphic/admin/childadmin.py",
    "content": "\"\"\"\nThe child admin displays the change/delete view of the subclass model.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport inspect\nfrom typing import TYPE_CHECKING, Any, Generic\n\nfrom django.contrib import admin\nfrom django.db import models\nfrom django.forms import ModelForm\nfrom django.http import HttpRequest\nfrom django.urls import resolve\nfrom django.utils.translation import gettext_lazy as _\nfrom typing_extensions import TypeVar\n\nfrom polymorphic.models import PolymorphicModel\nfrom polymorphic.utils import get_base_polymorphic_model\n\nfrom ..admin import PolymorphicParentModelAdmin\n\n_ModelT = TypeVar(\"_ModelT\", bound=PolymorphicModel, default=PolymorphicModel)\n\nif TYPE_CHECKING:\n    _ModelAdminBase = admin.ModelAdmin[_ModelT]\nelse:\n    _ModelAdminBase = admin.ModelAdmin\n\n\nclass ParentAdminNotRegistered(RuntimeError):\n    \"The admin site for the model is not registered.\"\n\n\nclass PolymorphicChildModelAdmin(_ModelAdminBase, Generic[_ModelT]):\n    \"\"\"\n    The *optional* base class for the admin interface of derived models.\n\n    This base class defines some convenience behavior for the admin interface:\n\n    * It corrects the breadcrumbs in the admin pages.\n    * It adds the base model to the template lookup paths.\n    * It allows to set ``base_form`` so the derived class will automatically include other fields in the form.\n    * It allows to set ``base_fieldsets`` so the derived class will automatically display any extra fields.\n    \"\"\"\n\n    #: The base model that the class uses (auto-detected if not set explicitly)\n    base_model: type[models.Model] | None = None\n\n    #: By setting ``base_form`` instead of ``form``, any subclass fields are automatically added to the form.\n    #: This is useful when your model admin class is inherited by others.\n    base_form: type[ModelForm[Any]] | None = None\n\n    #: By setting ``base_fieldsets`` instead of ``fieldsets``,\n    #: any subclass fields can be automatically added.\n    #: This is useful when your model admin class is inherited by others.\n    base_fieldsets: Any = None\n\n    #: Default title for extra fieldset\n    extra_fieldset_title = _(\"Contents\")\n\n    #: Whether the child admin model should be visible in the admin index page.\n    show_in_index = False\n\n    def __init__(self, model: type[_ModelT], admin_site: Any, *args: Any, **kwargs: Any) -> None:\n        super().__init__(model, admin_site, *args, **kwargs)\n\n        if self.base_model is None:\n            self.base_model = get_base_polymorphic_model(model)\n\n    def get_form(\n        self, request: HttpRequest, obj: Any | None = None, change: bool = False, **kwargs: Any\n    ) -> type[ModelForm[Any]]:\n        # The django admin validation requires the form to have a 'class Meta: model = ..'\n        # attribute, or it will complain that the fields are missing.\n        # However, this enforces all derived ModelAdmin classes to redefine the model as well,\n        # because they need to explicitly set the model again - it will stick with the base model.\n        #\n        # Instead, pass the form unchecked here, because the standard ModelForm will just work.\n        # If the derived class sets the model explicitly, respect that setting.\n        kwargs.setdefault(\"form\", self.base_form or self.form)\n\n        # prevent infinite recursion when this is called from get_subclass_fields\n        if not self.fieldsets and not self.fields:\n            kwargs.setdefault(\"fields\", \"__all__\")\n\n        return super().get_form(request, obj, **kwargs)\n\n    def get_model_perms(self, request):\n        match = resolve(request.path_info)\n\n        if not self.show_in_index and match.namespace == self.admin_site.name:\n            return {\"add\": False, \"change\": False, \"delete\": False}\n        return super().get_model_perms(request)\n\n    @property\n    def change_form_template(self) -> list[str]:  # type: ignore[override]\n        opts = self.model._meta\n        app_label = opts.app_label\n\n        # Pass the base options\n        assert self.base_model is not None, \"base_model must be set\"\n        base_opts = self.base_model._meta\n        base_app_label = base_opts.app_label\n\n        return [\n            f\"admin/{app_label}/{opts.object_name.lower()}/change_form.html\",  # type: ignore[union-attr]\n            f\"admin/{app_label}/change_form.html\",\n            # Added:\n            f\"admin/{base_app_label}/{base_opts.object_name.lower()}/change_form.html\",  # type: ignore[union-attr]\n            f\"admin/{base_app_label}/change_form.html\",\n            \"admin/polymorphic/change_form.html\",\n            \"admin/change_form.html\",\n        ]\n\n    @property\n    def delete_confirmation_template(self) -> list[str]:  # type: ignore[override]\n        opts = self.model._meta\n        app_label = opts.app_label\n\n        # Pass the base options\n        assert self.base_model is not None, \"base_model must be set\"\n        base_opts = self.base_model._meta\n        base_app_label = base_opts.app_label\n\n        return [\n            f\"admin/{app_label}/{opts.object_name.lower()}/delete_confirmation.html\",  # type: ignore[union-attr]\n            f\"admin/{app_label}/delete_confirmation.html\",\n            # Added:\n            f\"admin/{base_app_label}/{base_opts.object_name.lower()}/delete_confirmation.html\",  # type: ignore[union-attr]\n            f\"admin/{base_app_label}/delete_confirmation.html\",\n            \"admin/polymorphic/delete_confirmation.html\",\n            \"admin/delete_confirmation.html\",\n        ]\n\n    @property\n    def object_history_template(self) -> list[str]:  # type: ignore[override]\n        opts = self.model._meta\n        app_label = opts.app_label\n\n        # Pass the base options\n        assert self.base_model is not None, \"base_model must be set\"\n        base_opts = self.base_model._meta\n        base_app_label = base_opts.app_label\n\n        return [\n            f\"admin/{app_label}/{opts.object_name.lower()}/object_history.html\",  # type: ignore[union-attr]\n            f\"admin/{app_label}/object_history.html\",\n            # Added:\n            f\"admin/{base_app_label}/{base_opts.object_name.lower()}/object_history.html\",  # type: ignore[union-attr]\n            f\"admin/{base_app_label}/object_history.html\",\n            \"admin/polymorphic/object_history.html\",\n            \"admin/object_history.html\",\n        ]\n\n    def _get_parent_admin(self):\n        # this returns parent admin instance on which to call response_post_save methods\n        parent_model = self.model._meta.get_field(\"polymorphic_ctype\").model\n        if parent_model == self.model:\n            # when parent_model is in among child_models, just return super instance\n            return super()\n\n        try:\n            return self.admin_site._registry[parent_model]\n        except KeyError:\n            # Admin is not registered for polymorphic_ctype model, but perhaps it's registered\n            # for a intermediate proxy model, between the parent_model and this model.\n            for klass in inspect.getmro(self.model):\n                if not issubclass(klass, parent_model):\n                    continue  # e.g. found a mixin.\n\n                # Fetch admin instance for model class, see if it's a possible candidate.\n                model_admin = self.admin_site._registry.get(klass)\n                if model_admin is not None and isinstance(\n                    model_admin, PolymorphicParentModelAdmin\n                ):\n                    return model_admin  # Success!\n\n            # If we get this far without returning there is no admin available\n            raise ParentAdminNotRegistered(\n                f\"No parent admin was registered for a '{parent_model}' model.\"\n            )\n\n    def response_post_save_add(self, request, obj):\n        return self._get_parent_admin().response_post_save_add(request, obj)\n\n    def response_post_save_change(self, request, obj):\n        return self._get_parent_admin().response_post_save_change(request, obj)\n\n    def render_change_form(self, request, context, add=False, change=False, form_url=\"\", obj=None):\n        assert self.base_model is not None, \"base_model must be set\"\n        context.update({\"base_opts\": self.base_model._meta})\n        return super().render_change_form(\n            request, context, add=add, change=change, form_url=form_url, obj=obj\n        )\n\n    def delete_view(self, request, object_id, context=None):\n        assert self.base_model is not None, \"base_model must be set\"\n        extra_context = {\"base_opts\": self.base_model._meta}\n        return super().delete_view(request, object_id, extra_context)\n\n    def history_view(self, request, object_id, extra_context=None):\n        # Make sure the history view can also display polymorphic breadcrumbs\n        assert self.base_model is not None, \"base_model must be set\"\n        context = {\"base_opts\": self.base_model._meta}\n        if extra_context:\n            context.update(extra_context)\n        return super().history_view(request, object_id, extra_context=context)\n\n    # ---- Extra: improving the form/fieldset default display ----\n\n    def get_base_fieldsets(self, request, obj=None):\n        return self.base_fieldsets\n\n    def get_fieldsets(self, request, obj=None):\n        base_fieldsets = self.get_base_fieldsets(request, obj)\n\n        # If subclass declares fieldsets or fields, this is respected\n        if self.fieldsets or self.fields or not self.base_fieldsets:\n            return super().get_fieldsets(request, obj)\n\n        # Have a reasonable default fieldsets,\n        # where the subclass fields are automatically included.\n        other_fields = self.get_subclass_fields(request, obj)\n\n        if other_fields:\n            return (\n                base_fieldsets[0],\n                (self.extra_fieldset_title, {\"fields\": other_fields}),\n            ) + base_fieldsets[1:]\n        else:\n            return base_fieldsets\n\n    def get_subclass_fields(self, request, obj=None):\n        # Find out how many fields would really be on the form,\n        # if it weren't restricted by declared fields.\n        exclude = list(self.exclude or [])\n        exclude.extend(self.get_readonly_fields(request, obj))\n\n        # By not declaring the fields/form in the base class,\n        # get_form() will populate the form with all available fields.\n        form = self.get_form(request, obj, exclude=exclude)\n        subclass_fields = list(form.base_fields.keys()) + list(\n            self.get_readonly_fields(request, obj)\n        )\n\n        # Find which fields are not part of the common fields.\n        for fieldset in self.get_base_fieldsets(request, obj):\n            for field in fieldset[1][\"fields\"]:\n                # multiple elements in single line\n                if isinstance(field, tuple):\n                    for line_field in field:\n                        try:\n                            subclass_fields.remove(line_field)\n                        except ValueError:\n                            pass  # field not found in form, Django will raise exception later.\n                else:\n                    # regular one-element-per-line\n                    try:\n                        subclass_fields.remove(field)\n                    except ValueError:\n                        pass  # field not found in form, Django will raise exception later.\n        return subclass_fields\n"
  },
  {
    "path": "src/polymorphic/admin/filters.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Iterable\nfrom typing import Any, cast\n\nfrom django.contrib import admin\nfrom django.core.exceptions import PermissionDenied\nfrom django.db.models import QuerySet\nfrom django.http import HttpRequest\nfrom django.utils.translation import gettext_lazy as _\n\nfrom .parentadmin import PolymorphicParentModelAdmin\n\n\nclass PolymorphicChildModelFilter(admin.SimpleListFilter):\n    \"\"\"\n    An admin list filter for the PolymorphicParentModelAdmin which enables\n    filtering by its child models.\n\n    This can be used in the parent admin:\n\n    .. code-block:: python\n\n        list_filter = (PolymorphicChildModelFilter,)\n    \"\"\"\n\n    title: str = _(\"Type\")  # type: ignore[assignment]\n    parameter_name: str = \"polymorphic_ctype\"\n\n    def lookups(  # type: ignore[override]\n        self, request: HttpRequest, model_admin: PolymorphicParentModelAdmin\n    ) -> Iterable[tuple[str, str]]:\n        return cast(\n            Iterable[tuple[str, str]], model_admin.get_child_type_choices(request, \"change\")\n        )\n\n    def queryset(self, request: HttpRequest, queryset: QuerySet[Any]) -> QuerySet[Any]:\n        raw_value = self.value()\n        if not raw_value:\n            return queryset\n        try:\n            value = int(raw_value)\n        except TypeError:\n            value = None\n        if value:\n            # ensure the content type is allowed\n            for choice_value, _ in self.lookup_choices:  # noqa: F402\n                if int(choice_value) == value:\n                    return queryset.filter(polymorphic_ctype_id=choice_value)\n            raise PermissionDenied(\n                f'Invalid ContentType \"{value}\". It must be registered as child model.'\n            )\n        return queryset\n"
  },
  {
    "path": "src/polymorphic/admin/forms.py",
    "content": "from typing import Any\n\nfrom django import forms\nfrom django.contrib.admin.widgets import AdminRadioSelect\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass PolymorphicModelChoiceForm(forms.Form):\n    \"\"\"\n    The default form for the ``add_type_form``. Can be overwritten and replaced.\n    \"\"\"\n\n    #: Define the label for the radiofield\n    type_label = _(\"Type\")\n\n    ct_id = forms.ChoiceField(\n        label=type_label, widget=AdminRadioSelect(attrs={\"class\": \"radiolist\"})\n    )\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        # Allow to easily redefine the label (a commonly expected usecase)\n        super().__init__(*args, **kwargs)\n        self.fields[\"ct_id\"].label = self.type_label\n"
  },
  {
    "path": "src/polymorphic/admin/generic.py",
    "content": "from __future__ import annotations\n\nfrom typing import Any, cast\n\nfrom django.contrib.contenttypes.admin import GenericInlineModelAdmin\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.http import HttpRequest\nfrom django.utils.functional import cached_property\n\nfrom polymorphic.formsets import (\n    BaseGenericPolymorphicInlineFormSet,\n    GenericPolymorphicFormSetChild,\n    PolymorphicFormSetChild,\n    polymorphic_child_forms_factory,\n)\n\nfrom .inlines import PolymorphicInlineModelAdmin\n\n\nclass GenericPolymorphicInlineModelAdmin(PolymorphicInlineModelAdmin, GenericInlineModelAdmin):\n    \"\"\"\n    Base class for variation of inlines based on generic foreign keys.\n    \"\"\"\n\n    #: The formset class\n    formset: type[BaseGenericPolymorphicInlineFormSet] = BaseGenericPolymorphicInlineFormSet  # type: ignore[assignment]\n\n    def get_formset(  # type: ignore[override]\n        self, request: HttpRequest, obj: Any = None, **kwargs: Any\n    ) -> type[BaseGenericPolymorphicInlineFormSet]:\n        \"\"\"\n        Construct the generic inline formset class.\n        \"\"\"\n        # Construct the FormSet class. This is almost the same as parent version,\n        # except that a different super is called so generic_inlineformset_factory() is used.\n        # NOTE that generic_inlineformset_factory() also makes sure the GFK fields are excluded in the form.\n        FormSet = GenericInlineModelAdmin.get_formset(self, request, obj=obj, **kwargs)\n\n        setattr(\n            FormSet,\n            \"child_forms\",\n            polymorphic_child_forms_factory(\n                formset_children=self.get_formset_children(request, obj=obj)\n            ),\n        )\n        return cast(type[BaseGenericPolymorphicInlineFormSet], FormSet)\n\n    class Child(PolymorphicInlineModelAdmin.Child):\n        \"\"\"\n        Variation for generic inlines.\n        \"\"\"\n\n        # Make sure that the GFK fields are excluded from the child forms\n        formset_child: type[GenericPolymorphicFormSetChild] = GenericPolymorphicFormSetChild\n        ct_field: str = \"content_type\"\n        ct_fk_field: str = \"object_id\"\n\n        @cached_property\n        def content_type(self) -> ContentType:\n            \"\"\"\n            Expose the ContentType that the child relates to.\n            This can be used for the ``polymorphic_ctype`` field.\n            \"\"\"\n            return ContentType.objects.get_for_model(self.model, for_concrete_model=False)\n\n        def get_formset_child(\n            self, request: HttpRequest, obj: Any = None, **kwargs: Any\n        ) -> PolymorphicFormSetChild:\n            # Similar to GenericInlineModelAdmin.get_formset(),\n            # make sure the GFK is automatically excluded from the form\n            defaults = {\"ct_field\": self.ct_field, \"fk_field\": self.ct_fk_field}\n            defaults.update(kwargs)\n            return super(GenericPolymorphicInlineModelAdmin.Child, self).get_formset_child(\n                request, obj=obj, **defaults\n            )\n\n\nclass GenericStackedPolymorphicInline(GenericPolymorphicInlineModelAdmin):\n    \"\"\"\n    The stacked layout for generic inlines.\n    \"\"\"\n\n    #: The default template to use.\n    template: str = \"admin/polymorphic/edit_inline/stacked.html\"\n"
  },
  {
    "path": "src/polymorphic/admin/helpers.py",
    "content": "\"\"\"\nRendering utils for admin forms;\n\nThis makes sure that admin fieldsets/layout settings are exported to the template.\n\"\"\"\n\nimport json\nfrom collections.abc import Iterator\nfrom typing import Any, cast\n\nfrom django.contrib.admin.helpers import AdminField, InlineAdminForm, InlineAdminFormSet\nfrom django.http import HttpRequest\nfrom django.utils.encoding import force_str\nfrom django.utils.text import capfirst\nfrom django.utils.translation import gettext\n\nfrom polymorphic.formsets import BasePolymorphicModelFormSet\n\n\nclass PolymorphicInlineAdminForm(InlineAdminForm):\n    \"\"\"\n    Expose the admin configuration for a form\n    \"\"\"\n\n    def polymorphic_ctype_field(self) -> AdminField:\n        return AdminField(self.form, \"polymorphic_ctype\", False)\n\n    @property\n    def is_empty(self) -> bool:\n        if not self.form.prefix:\n            return False\n        return \"__prefix__\" in self.form.prefix\n\n\nclass PolymorphicInlineAdminFormSet(InlineAdminFormSet):\n    \"\"\"\n    Internally used class to expose the formset in the template.\n    \"\"\"\n\n    request: HttpRequest | None\n    obj: Any | None\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        # Assigned later via PolymorphicInlineSupportMixin later.\n        self.request = kwargs.pop(\"request\", None)\n        self.obj = kwargs.pop(\"obj\", None)\n        super().__init__(*args, **kwargs)\n\n    def __iter__(self) -> Iterator[PolymorphicInlineAdminForm]:\n        \"\"\"\n        Output all forms using the proper subtype settings.\n        \"\"\"\n        for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()):\n            # Output the form\n            model = original.get_real_instance_class()\n            child_inline = self.opts.get_child_inline_instance(model)\n            view_on_site_url = self.opts.get_view_on_site_url(original)\n\n            yield PolymorphicInlineAdminForm(\n                formset=self.formset,\n                form=form,\n                fieldsets=self.get_child_fieldsets(child_inline),\n                prepopulated_fields=self.get_child_prepopulated_fields(child_inline),\n                original=original,\n                readonly_fields=self.get_child_readonly_fields(child_inline),\n                model_admin=child_inline,\n                view_on_site_url=view_on_site_url,\n            )\n\n        # Extra rows, and empty prefixed forms.\n        for form in self.formset.extra_forms + self.formset.empty_forms:\n            model = form._meta.model\n            child_inline = self.opts.get_child_inline_instance(model)\n            yield PolymorphicInlineAdminForm(\n                formset=self.formset,\n                form=form,\n                fieldsets=self.get_child_fieldsets(child_inline),\n                prepopulated_fields=self.get_child_prepopulated_fields(child_inline),\n                original=None,\n                readonly_fields=self.get_child_readonly_fields(child_inline),\n                model_admin=child_inline,\n            )\n\n    def get_child_fieldsets(self, child_inline: Any) -> list[tuple[str | None, dict[str, Any]]]:\n        return list(child_inline.get_fieldsets(self.request, self.obj) or ())\n\n    def get_child_readonly_fields(self, child_inline: Any) -> list[str]:\n        return list(child_inline.get_readonly_fields(self.request, self.obj))\n\n    def get_child_prepopulated_fields(self, child_inline: Any) -> dict[str, Any]:\n        fields = self.prepopulated_fields.copy()\n        fields.update(child_inline.get_prepopulated_fields(self.request, self.obj))\n        return fields\n\n    def inline_formset_data(self) -> str:\n        \"\"\"\n        A JavaScript data structure for the JavaScript code\n        This overrides the default Django version to add the ``childTypes`` data.\n        \"\"\"\n        verbose_name = self.opts.verbose_name\n        return json.dumps(\n            {\n                \"name\": f\"#{self.formset.prefix}\",\n                \"options\": {\n                    \"prefix\": self.formset.prefix,\n                    \"addText\": gettext(\"Add another %(verbose_name)s\")\n                    % {\"verbose_name\": capfirst(verbose_name)},\n                    \"childTypes\": [\n                        {\n                            \"type\": model._meta.model_name,\n                            \"name\": force_str(model._meta.verbose_name),\n                        }\n                        for model in self.formset.child_forms.keys()\n                    ],\n                    \"deleteText\": gettext(\"Remove\"),\n                },\n            }\n        )\n\n\nclass PolymorphicInlineSupportMixin:\n    \"\"\"\n    A Mixin to add to the regular admin, so it can work with our polymorphic inlines.\n\n    This mixin needs to be included in the admin that hosts the ``inlines``.\n    It makes sure the generated admin forms have different fieldsets/fields\n    depending on the polymorphic type of the form instance.\n\n    This is achieved by overwriting :func:`get_inline_formsets` to return\n    an :class:`PolymorphicInlineAdminFormSet` instead of a standard Django\n    :class:`~django.contrib.admin.helpers.InlineAdminFormSet` for the polymorphic formsets.\n    \"\"\"\n\n    def get_inline_formsets(\n        self,\n        request: HttpRequest,\n        formsets: list[Any],\n        inline_instances: list[Any],\n        obj: Any = None,\n        *args: Any,\n        **kwargs: Any,\n    ) -> list[InlineAdminFormSet]:\n        \"\"\"\n        Overwritten version to produce the proper admin wrapping for the\n        polymorphic inline formset. This fixes the media and form appearance\n        of the inline polymorphic models.\n        \"\"\"\n        inline_admin_formsets = super().get_inline_formsets(  # type: ignore[misc]\n            request, formsets, inline_instances, obj=obj\n        )\n\n        for admin_formset in inline_admin_formsets:\n            if isinstance(admin_formset.formset, BasePolymorphicModelFormSet):\n                # This is a polymorphic formset, which belongs to our inline.\n                # Downcast the admin wrapper that generates the form fields.\n                admin_formset.__class__ = PolymorphicInlineAdminFormSet\n                admin_formset.request = request\n                admin_formset.obj = obj\n        return cast(list[InlineAdminFormSet], inline_admin_formsets)\n"
  },
  {
    "path": "src/polymorphic/admin/inlines.py",
    "content": "\"\"\"\nDjango Admin support for polymorphic inlines.\n\nEach row in the inline can correspond with a different subclass.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom functools import partial\nfrom typing import TYPE_CHECKING, Any, cast\n\nfrom django.conf import settings\nfrom django.contrib.admin.options import InlineModelAdmin\nfrom django.contrib.admin.sites import AdminSite\nfrom django.contrib.admin.utils import flatten_fieldsets\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.db import models\nfrom django.forms import Media\nfrom django.http import HttpRequest\n\nfrom polymorphic.formsets import (\n    BasePolymorphicInlineFormSet,\n    PolymorphicFormSetChild,\n    UnsupportedChildType,\n    polymorphic_child_forms_factory,\n)\nfrom polymorphic.formsets.utils import add_media\n\nfrom .helpers import PolymorphicInlineSupportMixin\n\nif TYPE_CHECKING:\n    from django.contrib.admin.options import _FieldGroups, _FieldsetSpec\n\n\nclass PolymorphicInlineModelAdmin(InlineModelAdmin):\n    \"\"\"\n    A polymorphic inline, where each formset row can be a different form.\n\n    Note that:\n\n    * Permissions are only checked on the base model.\n    * The child inlines can't override the base model fields, only this parent inline can do that.\n    \"\"\"\n\n    formset: type[BasePolymorphicInlineFormSet] = BasePolymorphicInlineFormSet\n\n    #: The extra media to add for the polymorphic inlines effect.\n    #: This can be redefined for subclasses.\n    polymorphic_media: Media = Media(\n        js=(\n            f\"admin/js/vendor/jquery/{'jquery' if settings.DEBUG else 'jquery.min'}.js\",\n            \"admin/js/jquery.init.js\",\n            \"polymorphic/js/polymorphic_inlines.js\",\n        ),\n        css={\"all\": (\"polymorphic/css/polymorphic_inlines.css\",)},\n    )\n\n    #: The extra forms to show\n    #: By default there are no 'extra' forms as the desired type is unknown.\n    #: Instead, add each new item using JavaScript that first offers a type-selection.\n    extra: int = 0\n\n    #: Inlines for all model sub types that can be displayed in this inline.\n    #: Each row is a :class:`PolymorphicInlineModelAdmin.Child`\n    child_inlines: list[type[\"PolymorphicInlineModelAdmin.Child\"]] = []\n\n    child_inline_instances: list[\"PolymorphicInlineModelAdmin.Child\"]\n\n    def __init__(self, parent_model: type[models.Model], admin_site: AdminSite) -> None:\n        super().__init__(parent_model, admin_site)\n\n        # Extra check to avoid confusion\n        # While we could monkeypatch the admin here, better stay explicit.\n        parent_admin = admin_site._registry.get(parent_model, None)\n        if parent_admin is not None:  # Can be None during check\n            if not isinstance(parent_admin, PolymorphicInlineSupportMixin):\n                raise ImproperlyConfigured(\n                    \"To use polymorphic inlines, add the `PolymorphicInlineSupportMixin` mixin \"\n                    \"to the ModelAdmin that hosts the inline.\"\n                )\n\n        # While the inline is created per request, the 'request' object is not known here.\n        # Hence, creating all child inlines unconditionally, without checking permissions.\n        self.child_inline_instances = self.get_child_inline_instances()\n\n        # Create a lookup table\n        self._child_inlines_lookup = {}\n        for child_inline in self.child_inline_instances:\n            self._child_inlines_lookup[child_inline.model] = child_inline\n\n    def get_child_inlines(self) -> list[type[\"PolymorphicInlineModelAdmin.Child\"]]:\n        \"\"\"\n        Return the derived inline classes which this admin should handle.\n\n        This should return an iterable of\n        :class:`~polymorphic.admin.inlines.PolymorphicInlineModelAdmin.Child` classes,\n        to override :attr:`~polymorphic.admin.inlines.PolymorphicInlineModelAdmin.child_inlines`.\n        \"\"\"\n        return self.child_inlines or []\n\n    def get_child_inline_instances(self) -> list[\"PolymorphicInlineModelAdmin.Child\"]:\n        \"\"\"\n        :rtype List[PolymorphicInlineModelAdmin.Child]\n        \"\"\"\n        instances = []\n        for ChildInlineType in self.get_child_inlines():\n            instances.append(ChildInlineType(parent_inline=self))\n        return instances\n\n    def get_child_inline_instance(\n        self, model: type[models.Model]\n    ) -> \"PolymorphicInlineModelAdmin.Child\":\n        \"\"\"\n        Find the child inline for a given model.\n\n        :rtype: PolymorphicInlineModelAdmin.Child\n        \"\"\"\n        try:\n            return self._child_inlines_lookup[model]\n        except KeyError:\n            raise UnsupportedChildType(f\"Model '{model.__name__}' not found in child_inlines\")\n\n    def get_formset(\n        self, request: HttpRequest, obj: Any = None, **kwargs: Any\n    ) -> type[BasePolymorphicInlineFormSet]:\n        \"\"\"\n        Construct the inline formset class.\n\n        This passes all class attributes to the formset.\n\n        :rtype: type\n        \"\"\"\n        # Construct the FormSet class\n        FormSet = super().get_formset(request, obj=obj, **kwargs)\n\n        # Instead of completely redefining super().get_formset(), we use\n        # the regular inlineformset_factory(), and amend that with our extra bits.\n        # This code line is the essence of what polymorphic_inlineformset_factory() does.\n        FormSet.child_forms = polymorphic_child_forms_factory(  # type: ignore[attr-defined]\n            formset_children=self.get_formset_children(request, obj=obj)\n        )\n        return cast(type[BasePolymorphicInlineFormSet], FormSet)\n\n    def get_formset_children(\n        self, request: HttpRequest, obj: Any = None\n    ) -> list[PolymorphicFormSetChild]:\n        \"\"\"\n        The formset 'children' provide the details for all child models that are part of this formset.\n        It provides a stripped version of the modelform/formset factory methods.\n        \"\"\"\n        formset_children = []\n        for child_inline in self.child_inline_instances:\n            # TODO: the children can be limited here per request based on permissions.\n            formset_children.append(child_inline.get_formset_child(request, obj=obj))\n        return formset_children\n\n    def get_fieldsets(self, request: HttpRequest, obj: Any = None) -> \"_FieldsetSpec\":\n        \"\"\"\n        Hook for specifying fieldsets.\n        \"\"\"\n        if self.fieldsets:\n            return self.fieldsets\n        else:\n            return []  # Avoid exposing fields to the child\n\n    def get_fields(self, request: HttpRequest, obj: Any = None) -> \"_FieldGroups\":\n        if self.fields:\n            # Django's stubs type fields as Sequence[str | Sequence[str]]\n            return self.fields\n        else:\n            return []  # Avoid exposing fields to the child\n\n    @property\n    def media(self):\n        # The media of the inline focuses on the admin settings,\n        # whether to expose the scripts for filter_horizontal etc..\n        # The admin helper exposes the inline + formset media.\n        base_media = super().media\n        all_media = Media()\n        add_media(all_media, base_media)\n\n        # Add all media of the child inline instances\n        for child_instance in self.child_inline_instances:\n            child_media = child_instance.media\n\n            # Avoid adding the same media object again and again\n            if child_media._css != base_media._css and child_media._js != base_media._js:  # type: ignore[attr-defined]\n                add_media(all_media, child_media)\n\n        add_media(all_media, self.polymorphic_media)\n\n        return all_media\n\n    class Child(InlineModelAdmin):\n        \"\"\"\n        The child inline; which allows configuring the admin options\n        for the child appearance.\n\n        Note that not all options will be honored by the parent, notably the formset options:\n        * :attr:`extra`\n        * :attr:`min_num`\n        * :attr:`max_num`\n\n        The model form options however, will all be read.\n        \"\"\"\n\n        formset_child: type[PolymorphicFormSetChild] = PolymorphicFormSetChild\n        extra: int = 0  # TODO: currently unused for the children.\n        parent_inline: \"PolymorphicInlineModelAdmin\"\n\n        def __init__(self, parent_inline: \"PolymorphicInlineModelAdmin\") -> None:\n            self.parent_inline = parent_inline\n            super(PolymorphicInlineModelAdmin.Child, self).__init__(\n                parent_inline.parent_model, parent_inline.admin_site\n            )\n\n        def get_formset(self, request: HttpRequest, obj: Any = None, **kwargs: Any) -> None:  # type: ignore[override]\n            # The child inline is only used to construct the form,\n            # and allow to override the form field attributes.\n            # The formset is created by the parent inline.\n            raise RuntimeError(\"The child get_formset() is not used.\")\n\n        def get_fields(self, request: HttpRequest, obj: Any = None) -> \"_FieldGroups\":\n            if self.fields:\n                return self.fields\n\n            # Standard Django logic, use the form to determine the fields.\n            # The form needs to pass through all factory logic so all 'excludes' are set as well.\n            # Default Django does: form = self.get_formset(request, obj, fields=None).form\n            # Use 'fields=None' avoids recursion in the field autodetection.\n            form = self.get_formset_child(request, obj, fields=None).get_form()\n            # Cast list[str] to _FieldGroups (compatible at runtime)\n            return cast(\n                \"_FieldGroups\",\n                list(form.base_fields) + list(self.get_readonly_fields(request, obj)),\n            )\n\n        def get_formset_child(\n            self, request: HttpRequest, obj: Any = None, **kwargs: Any\n        ) -> PolymorphicFormSetChild:\n            \"\"\"\n            Return the formset child that the parent inline can use to represent us.\n\n            :rtype: PolymorphicFormSetChild\n            \"\"\"\n            # Similar to the normal get_formset(), the caller may pass fields to override the defaults settings\n            # in the inline. In Django's GenericInlineModelAdmin.get_formset() this is also used in the same way,\n            # to make sure the 'exclude' also contains the GFK fields.\n            #\n            # Hence this code is almost identical to InlineModelAdmin.get_formset()\n            # and GenericInlineModelAdmin.get_formset()\n            #\n            # Transfer the local inline attributes to the formset child,\n            # this allows overriding settings.\n            if \"fields\" in kwargs:\n                fields = kwargs.pop(\"fields\")\n            else:\n                fields = flatten_fieldsets(self.get_fieldsets(request, obj))\n\n            if self.exclude is None:\n                exclude = []\n            else:\n                exclude = list(self.exclude)\n\n            exclude.extend(self.get_readonly_fields(request, obj))\n            # Add forcefully, as Django 1.10 doesn't include readonly fields.\n            exclude.append(\"polymorphic_ctype\")\n\n            if self.exclude is None and hasattr(self.form, \"_meta\") and self.form._meta.exclude:\n                # Take the custom ModelForm's Meta.exclude into account only if the\n                # InlineModelAdmin doesn't define its own.\n                exclude.extend(self.form._meta.exclude)\n\n            # can_delete = self.can_delete and self.has_delete_permission(request, obj)\n            defaults = {\n                \"form\": self.form,\n                \"fields\": fields,\n                \"exclude\": exclude or None,\n                \"formfield_callback\": partial(self.formfield_for_dbfield, request=request),\n            }\n            defaults.update(kwargs)\n\n            # This goes through the same logic that get_formset() calls\n            # by passing the inline class attributes to modelform_factory()\n            FormSetChildClass = self.formset_child\n            return FormSetChildClass(self.model, **defaults)\n\n\nclass StackedPolymorphicInline(PolymorphicInlineModelAdmin):\n    \"\"\"\n    Stacked inline for django-polymorphic models.\n    Since tabular doesn't make much sense with changed fields, just offer this one.\n    \"\"\"\n\n    #: The default template to use.\n    template: str = \"admin/polymorphic/edit_inline/stacked.html\"\n"
  },
  {
    "path": "src/polymorphic/admin/parentadmin.py",
    "content": "\"\"\"\nThe parent admin displays the list view of the base model.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any, Generic, cast\n\nfrom django.contrib import admin\nfrom django.contrib.admin.helpers import AdminErrorList, AdminForm\nfrom django.contrib.admin.templatetags.admin_urls import add_preserved_filters\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.core.exceptions import ImproperlyConfigured, PermissionDenied\nfrom django.db import models\nfrom django.http import Http404, HttpResponseRedirect\nfrom django.template.response import TemplateResponse\nfrom django.utils.encoding import force_str\nfrom django.utils.translation import gettext_lazy as _\nfrom typing_extensions import TypeVar\n\nfrom polymorphic.models import PolymorphicModel\nfrom polymorphic.query import PolymorphicQuerySet\nfrom polymorphic.utils import get_base_polymorphic_model\n\nfrom .forms import PolymorphicModelChoiceForm\n\n_ModelT = TypeVar(\"_ModelT\", bound=PolymorphicModel, default=PolymorphicModel)\n\nif TYPE_CHECKING:\n    _ModelAdminBase = admin.ModelAdmin[_ModelT]\nelse:\n    _ModelAdminBase = admin.ModelAdmin\n\n\nclass RegistrationClosed(RuntimeError):\n    \"The admin model can't be registered anymore at this point.\"\n\n\nclass ChildAdminNotRegistered(RuntimeError):\n    \"The admin site for the model is not registered.\"\n\n\nclass PolymorphicParentModelAdmin(_ModelAdminBase, Generic[_ModelT]):\n    \"\"\"\n    A admin interface that can displays different change/delete pages, depending on the polymorphic model.\n    To use this class, one attribute need to be defined:\n\n    * :attr:`child_models` should be a list models.\n\n    Alternatively, the following methods can be implemented:\n\n    * :func:`get_child_models` should return a list of models.\n    * optionally, :func:`get_child_type_choices` can be overwritten to refine the choices for the add dialog.\n\n    This class needs to be inherited by the model admin base class that is registered in the site.\n    The derived models should *not* register the ModelAdmin, but instead it should be returned by :func:`get_child_models`.\n    \"\"\"\n\n    #: The base model that the class uses (auto-detected if not set explicitly)\n    base_model: type[models.Model] | None = None\n\n    #: The child models that should be displayed\n    child_models: list[type[models.Model]] | None = None\n\n    #: Whether the list should be polymorphic too, leave to ``False`` to optimize\n    polymorphic_list = False\n\n    add_type_template = None\n    add_type_form = PolymorphicModelChoiceForm\n\n    #: The regular expression to filter the primary key in the URL.\n    #: This accepts only numbers as defensive measure against catch-all URLs.\n    #: If your primary key consists of string values, update this regular expression.\n    pk_regex = r\"(\\d+|__fk__)\"\n\n    def __init__(self, model: type[_ModelT], admin_site: Any, *args: Any, **kwargs: Any) -> None:\n        super().__init__(model, admin_site, *args, **kwargs)\n        self._is_setup = False\n\n        if self.base_model is None:\n            self.base_model = get_base_polymorphic_model(model)\n\n    def _lazy_setup(self):\n        if self._is_setup:\n            return\n\n        self._child_models = self.get_child_models()\n\n        # Make absolutely sure that the child models don't use the old 0.9 format,\n        # as of polymorphic 1.4 this deprecated configuration is no longer supported.\n        # Instead, register the child models in the admin too.\n        if self._child_models and not issubclass(self._child_models[0], models.Model):\n            raise ImproperlyConfigured(\n                \"Since django-polymorphic 1.4, the `child_models` attribute \"\n                \"and `get_child_models()` method should be a list of models only.\\n\"\n                \"The model-admin class should be registered in the regular Django admin.\"\n            )\n\n        self._child_admin_site = self.admin_site\n        self._is_setup = True\n\n    def get_child_models(self):\n        \"\"\"\n        Return the derived model classes which this admin should handle.\n        This should return a list of tuples, exactly like :attr:`child_models` is.\n\n        The model classes can be retrieved as ``base_model.__subclasses__()``,\n        a setting in a config file, or a query of a plugin registration system at your option\n        \"\"\"\n        if self.child_models is None:\n            raise NotImplementedError(\"Implement get_child_models() or child_models\")\n\n        return self.child_models\n\n    def get_child_type_choices(self, request, action):\n        \"\"\"\n        Return a list of polymorphic types for which the user has the permission to perform the given action.\n        \"\"\"\n        self._lazy_setup()\n        choices = []\n        content_types = ContentType.objects.get_for_models(\n            *self.get_child_models(), for_concrete_models=False\n        )\n\n        for model, ct in content_types.items():\n            perm_function_name = f\"has_{action}_permission\"\n            model_admin = self._get_real_admin_by_model(model)\n            perm_function = getattr(model_admin, perm_function_name)\n            if not perm_function(request):\n                continue\n            choices.append((ct.id, model._meta.verbose_name))\n        return choices\n\n    def _get_real_admin(self, object_id, super_if_self=True):\n        try:\n            obj = (\n                self.model.objects.non_polymorphic().values(\"polymorphic_ctype\").get(pk=object_id)\n            )\n        except self.model.DoesNotExist:\n            raise Http404\n        return self._get_real_admin_by_ct(obj[\"polymorphic_ctype\"], super_if_self=super_if_self)\n\n    def _get_real_admin_by_ct(self, ct_id, super_if_self=True):\n        try:\n            ct = ContentType.objects.get_for_id(ct_id)\n        except ContentType.DoesNotExist as e:\n            raise Http404(e)  # Handle invalid GET parameters\n\n        model_class = ct.model_class()\n        if not model_class:\n            # Handle model deletion\n            app_label, model = ct.natural_key()\n            raise Http404(f\"No model found for '{app_label}.{model}'.\")\n\n        return self._get_real_admin_by_model(model_class, super_if_self=super_if_self)\n\n    def _get_real_admin_by_model(self, model_class, super_if_self=True):\n        # In case of a ?ct_id=### parameter, the view is already checked for permissions.\n        # Hence, make sure this is a derived object, or risk exposing other admin interfaces.\n        if model_class not in self._child_models:\n            raise PermissionDenied(\n                f\"Invalid model '{model_class}', it must be registered as child model.\"\n            )\n\n        try:\n            # HACK: the only way to get the instance of an model admin,\n            # is to read the registry of the AdminSite.\n            real_admin = self._child_admin_site._registry[model_class]\n        except KeyError:\n            raise ChildAdminNotRegistered(\n                f\"No child admin site was registered for a '{model_class}' model.\"\n            )\n\n        if super_if_self and real_admin is self:\n            return super()\n        else:\n            return real_admin\n\n    def get_queryset(self, request):\n        # optimize the list display.\n        qs = cast(PolymorphicQuerySet, super().get_queryset(request))\n        if not self.polymorphic_list:\n            qs = qs.non_polymorphic()\n        return qs\n\n    def add_view(self, request, form_url=\"\", extra_context=None):\n        \"\"\"Redirect the add view to the real admin.\"\"\"\n        ct_id = int(request.GET.get(\"ct_id\", 0))\n        if not ct_id:\n            # Display choices\n            return self.add_type_view(request)\n        else:\n            real_admin = self._get_real_admin_by_ct(ct_id)\n            # rebuild form_url, otherwise libraries below will override it.\n            # Preserve popup-related parameters to ensure popup functionality works\n            # correctly even after validation errors (issue #612)\n            form_url = add_preserved_filters(\n                {\n                    \"preserved_filters\": request.GET.urlencode(),\n                    \"opts\": self.model._meta,\n                },\n                form_url,\n            )\n            return real_admin.add_view(request, form_url, extra_context)\n\n    def change_view(self, request, object_id, *args, **kwargs):\n        \"\"\"Redirect the change view to the real admin.\"\"\"\n        real_admin = self._get_real_admin(object_id)\n        return real_admin.change_view(request, object_id, *args, **kwargs)\n\n    def changeform_view(self, request, object_id=None, *args, **kwargs):\n        # The `changeform_view` is available as of Django 1.7, combining the add_view and change_view.\n        # As it's directly called by django-reversion, this method is also overwritten to make sure it\n        # also redirects to the child admin.\n        if object_id:\n            real_admin = self._get_real_admin(object_id)\n            return real_admin.changeform_view(request, object_id, *args, **kwargs)\n        else:\n            # Add view. As it should already be handled via `add_view`, this means something custom is done here!\n            return super().changeform_view(request, object_id, *args, **kwargs)\n\n    def history_view(self, request, object_id, extra_context=None):\n        \"\"\"Redirect the history view to the real admin.\"\"\"\n        real_admin = self._get_real_admin(object_id)\n        return real_admin.history_view(request, object_id, extra_context=extra_context)\n\n    def delete_view(self, request, object_id, extra_context=None):\n        \"\"\"Redirect the delete view to the real admin.\"\"\"\n        real_admin = self._get_real_admin(object_id)\n        return real_admin.delete_view(request, object_id, extra_context)\n\n    def get_urls(self):\n        \"\"\"\n        Expose the custom URLs for the subclasses and the URL resolver.\n        \"\"\"\n        urls = super().get_urls()\n\n        # At this point. all admin code needs to be known.\n        self._lazy_setup()\n\n        return urls\n\n    def add_type_view(self, request, form_url=\"\"):\n        \"\"\"\n        Display a choice form to select which page type to add.\n        \"\"\"\n        if not self.has_add_permission(request):\n            raise PermissionDenied\n\n        extra_qs = \"\"\n        if request.META[\"QUERY_STRING\"]:\n            # QUERY_STRING is bytes in Python 3, using force_str() to decode it as string.\n            # See QueryDict how Django deals with that.\n            # TODO: should this use a Django method instead of manipulating the string directly?\n            extra_qs = f\"&{force_str(request.META['QUERY_STRING'])}\"\n\n        choices = self.get_child_type_choices(request, \"add\")\n        if len(choices) == 0:\n            raise PermissionDenied\n        if len(choices) == 1:\n            return HttpResponseRedirect(f\"?ct_id={choices[0][0]}{extra_qs}\")\n\n        # Create form\n        form = self.add_type_form(\n            data=request.POST if request.method == \"POST\" else None,\n            initial={\"ct_id\": choices[0][0]},\n        )\n        setattr(form.fields[\"ct_id\"], \"choices\", choices)\n\n        if form.is_valid():\n            return HttpResponseRedirect(f\"?ct_id={form.cleaned_data['ct_id']}{extra_qs}\")\n\n        # Wrap in all admin layout\n        fieldsets = ((None, {\"fields\": (\"ct_id\",)}),)\n        adminForm = AdminForm(form, fieldsets, {}, model_admin=self)  # type: ignore[arg-type]\n        media = self.media + adminForm.media\n        opts = self.model._meta\n\n        context = {\n            \"title\": _(\"Add %s\") % force_str(opts.verbose_name),\n            \"adminform\": adminForm,\n            \"is_popup\": (\"_popup\" in request.POST or \"_popup\" in request.GET),\n            \"media\": media,\n            \"errors\": AdminErrorList(form, ()),  # type: ignore[arg-type]\n            \"app_label\": opts.app_label,\n        }\n        return self.render_add_type_form(request, context, form_url)\n\n    def render_add_type_form(self, request, context, form_url=\"\"):\n        \"\"\"\n        Render the page type choice form.\n        \"\"\"\n        opts = self.model._meta\n        app_label = opts.app_label\n        context.update(\n            {\n                \"has_change_permission\": self.has_change_permission(request),\n                \"form_url\": form_url,\n                \"opts\": opts,\n                \"add\": True,\n                \"save_on_top\": self.save_on_top,\n                **self.admin_site.each_context(request),\n            }\n        )\n\n        templates = self.add_type_template or [\n            f\"admin/{app_label}/{opts.object_name.lower()}/add_type_form.html\",  # type: ignore[union-attr]\n            f\"admin/{app_label}/add_type_form.html\",\n            \"admin/polymorphic/add_type_form.html\",  # added default here\n            \"admin/add_type_form.html\",\n        ]\n\n        request.current_app = self.admin_site.name\n        return self.admin_site.admin_view(TemplateResponse)(request, templates, context)\n\n    @property\n    def change_list_template(self) -> list[str]:  # type: ignore[override]\n        opts = self.model._meta\n        app_label = opts.app_label\n\n        # Pass the base options\n        assert self.base_model is not None, \"base_model must be set\"\n        base_opts = self.base_model._meta\n        base_app_label = base_opts.app_label\n\n        return [\n            f\"admin/{app_label}/{opts.object_name.lower()}/change_list.html\",  # type: ignore[union-attr]\n            f\"admin/{app_label}/change_list.html\",\n            # Added base class:\n            f\"admin/{base_app_label}/{base_opts.object_name.lower()}/change_list.html\",  # type: ignore[union-attr]\n            f\"admin/{base_app_label}/change_list.html\",\n            \"admin/change_list.html\",\n        ]\n"
  },
  {
    "path": "src/polymorphic/apps.py",
    "content": "from typing import Any, Iterable, Sequence\n\nfrom django.apps import AppConfig, apps\nfrom django.core.checks import CheckMessage, Error, Tags, Warning, register\nfrom django.db import models\n\n\n@register(Tags.models)\ndef check_reserved_field_names(\n    app_configs: Sequence[AppConfig] | None, **kwargs: Any\n) -> Iterable[CheckMessage]:\n    \"\"\"\n    System check that ensures models don't use reserved field names.\n    \"\"\"\n    from .models import PolymorphicModel\n\n    findings: list[CheckMessage] = []\n\n    for app_config in app_configs or apps.get_app_configs():\n        for model in app_config.get_models():\n            if issubclass(model, PolymorphicModel):\n                findings.extend(_check_model_reserved_field_names(model))\n                findings.extend(_check_polymorphic_managers(model))\n\n    return findings\n\n\ndef _check_polymorphic_managers(model: type[models.Model]) -> list[CheckMessage]:\n    from polymorphic.managers import PolymorphicManager\n    from polymorphic.query import PolymorphicQuerySet\n\n    findings: list[CheckMessage] = []\n\n    # First manager declared with use_in_migrations=True wins.\n    for mgr in model._meta.managers:\n        if getattr(mgr, \"use_in_migrations\", True):\n            if isinstance(mgr, PolymorphicManager):\n                findings.append(\n                    Error(\n                        f\"The migration manager '{model._meta.label}.{mgr.name}' is polymorphic.\",\n                        obj=mgr,\n                        hint=\"Set use_in_migrations = False on the manager.\",\n                        id=\"polymorphic.E002\",\n                    )\n                )\n            break\n\n    for manager in [\"base\", \"default\"]:\n        mgr = getattr(model._meta, f\"{manager}_manager\")\n        if not isinstance(mgr, PolymorphicManager):\n            findings.append(\n                Warning(\n                    f\"The {manager} manager {model._meta.label}.{mgr.name}' is not polymorphic.\",\n                    obj=mgr,\n                    id=\"polymorphic.W001\",\n                )\n            )\n        if not isinstance(mgr.get_queryset(), PolymorphicQuerySet):\n            findings.append(\n                Warning(\n                    f\"The {manager} manager {model._meta.label}.{mgr.name}' is not \"\n                    \"using a PolymorphicQuerySet.\",\n                    obj=mgr,\n                    id=\"polymorphic.W002\",\n                )\n            )\n\n    return findings\n\n\ndef _check_model_reserved_field_names(model: type[models.Model]) -> list[CheckMessage]:\n    from polymorphic.base import POLYMORPHIC_SPECIAL_Q_KWORDS\n\n    errors: list[CheckMessage] = []\n\n    for field in model._meta.get_fields():\n        if field.name in POLYMORPHIC_SPECIAL_Q_KWORDS:\n            errors.append(\n                Error(\n                    f\"Field '{field.name}' on model '{model.__name__}' is a reserved name.\",\n                    obj=field,\n                    id=\"polymorphic.E001\",\n                )\n            )\n\n    return errors\n\n\nclass PolymorphicConfig(AppConfig):\n    name: str = \"polymorphic\"\n    verbose_name: str = \"Django Polymorphic\"\n\n    def ready(self) -> None:\n        pass\n"
  },
  {
    "path": "src/polymorphic/base.py",
    "content": "\"\"\"\nPolymorphicModel Meta Class\n\"\"\"\n\nimport sys\nimport warnings\nfrom typing import Any, cast\n\nfrom django.db import models\nfrom django.db.models.base import ModelBase\nfrom django.db.models.options import Options\n\nfrom .deletion import PolymorphicGuard\nfrom .managers import PolymorphicManager\nfrom .related_descriptors import (\n    NonPolymorphicForwardOneToOneDescriptor,\n    NonPolymorphicReverseOneToOneDescriptor,\n)\nfrom .utils import _clear_utility_caches\n\n# PolymorphicQuerySet Q objects (and filter()) support these additional key words.\n# These are forbidden as field names (a descriptive exception is raised)\nPOLYMORPHIC_SPECIAL_Q_KWORDS: set[str] = {\"instance_of\", \"not_instance_of\"}\n\n\nclass ManagerInheritanceWarning(RuntimeWarning):\n    pass\n\n\n# check that we're on cpython to enable dumpdata frame inspection guard\ncheck_dump: bool = hasattr(sys, \"_getframe\")\n\n\n# We wrap the base_manager property to return a PolymorphicManager\n# for polymorphic models when the base manager would otherwise\n# be the default auto-created manager. This ensures that\n# reverse relations to polymorphic models also use polymorphic\n# querysets by default.\n# https://github.com/jazzband/django-polymorphic/pull/858\ndj_base_manager = Options.base_manager.func  # type: ignore[attr-defined]\n\n\ndef polymorphic_base_manager(self):\n    \"\"\"\n    Return a polymorphic base manager for polymorphic models.\n    \"\"\"\n    from polymorphic.models import PolymorphicModel\n\n    mgr = dj_base_manager(self)\n    if (\n        issubclass(self.model, PolymorphicModel)\n        and mgr.__class__ is models.Manager\n        and mgr.auto_created\n    ):\n        manager: PolymorphicManager = PolymorphicManager()\n        manager.name = \"_base_manager\"\n        manager.model = self.model\n        manager.auto_created = True\n        return manager\n    return mgr\n\n\nsetattr(Options.base_manager, \"func\", polymorphic_base_manager)\n\n\nclass PolymorphicModelBase(ModelBase):\n    \"\"\"\n    Manager inheritance is a pretty complex topic which may need\n    more thought regarding how this should be handled for polymorphic\n    models.\n\n    In any case, we probably should propagate 'objects' and 'base_objects'\n    from PolymorphicModel to every subclass. We also want to somehow\n    inherit/propagate _default_manager as well, as it needs to be polymorphic.\n\n    The current implementation below is an experiment to solve this\n    problem with a very simplistic approach: We unconditionally\n    inherit/propagate any and all managers (using _copy_to_model),\n    as long as they are defined on polymorphic models\n    (the others are left alone).\n\n    Like Django ModelBase, we special-case _default_manager:\n    if there are any user-defined managers, it is set to the first of these.\n\n    We also require that _default_manager as well as any user defined\n    polymorphic managers produce querysets that are derived from\n    PolymorphicQuerySet.\n\n    We also replace the parent/child relation field descriptors with versions that will\n    use non-polymorphic querysets.\n\n    If we have inheritance of the form ModelA -> ModelB ->ModelC then\n    Django creates accessors like this:\n    - ModelA: modelb\n    - ModelB: modela_ptr, modelb, modelc\n    - ModelC: modela_ptr, modelb, modelb_ptr, modelc\n\n    These accessors allow Django (and everyone else) to travel up and down\n    the inheritance tree for the db object at hand. This is important for deletion among\n    other things.\n    \"\"\"\n\n    def __new__(\n        cls, model_name: str, bases: tuple[type, ...], attrs: dict[str, Any], **kwargs: Any\n    ) -> type:\n        # skip special setup for PolymorphicModel itself\n        if attrs.pop(\"_meta_skip\", False):\n            return super().__new__(cls, model_name, bases, attrs, **kwargs)\n\n        from .models import PolymorphicModel\n\n        new_class = cast(\n            type[PolymorphicModel], super().__new__(cls, model_name, bases, attrs, **kwargs)\n        )\n\n        # wrap on_delete handlers of reverse relations back to this model with the\n        # polymorphic deletion guard\n        for fk in new_class._meta.fields:\n            if isinstance(fk, (models.ForeignKey, models.OneToOneField)) and not isinstance(\n                fk.remote_field.on_delete, PolymorphicGuard\n            ):\n                fk.remote_field.on_delete = PolymorphicGuard(fk.remote_field.on_delete)  # type: ignore[arg-type]\n\n        # replace the parent/child descriptors\n        if new_class._meta.parents and not (new_class._meta.abstract or new_class._meta.proxy):\n\n            def replace_inheritance_descriptors(model):\n                for super_cls, field_to_super in model._meta.parents.items():\n                    if issubclass(super_cls, PolymorphicModel):\n                        if field_to_super is not None:\n                            setattr(\n                                new_class,\n                                field_to_super.name,\n                                NonPolymorphicForwardOneToOneDescriptor(field_to_super),\n                            )\n                            setattr(\n                                super_cls,\n                                field_to_super.remote_field.related_name\n                                or field_to_super.remote_field.name,\n                                NonPolymorphicReverseOneToOneDescriptor(\n                                    field_to_super.remote_field\n                                ),\n                            )\n                        else:  # pragma: no cover\n                            # proxy models have no field_to_super because the relations\n                            # are to the parent model - the else here should never\n                            # happen b/c we filter out proxy models above\n                            pass\n                        replace_inheritance_descriptors(super_cls)\n\n            replace_inheritance_descriptors(new_class)\n        _clear_utility_caches()\n        return new_class\n\n    @property\n    def base_objects(self) -> models.Manager[Any]:\n        warnings.warn(\n            \"Using PolymorphicModel.base_objects is deprecated.\\n\"\n            f\"Use {self.__class__.__name__}.objects.non_polymorphic() instead.\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n        return self._base_objects\n\n    @property\n    def _base_objects(self) -> models.Manager[Any]:\n        # Create a manager so the API works as expected. Just don't register it\n        # anymore in the Model Meta, so it doesn't substitute our polymorphic\n        # manager as default manager for the third level of inheritance when\n        # that third level doesn't define a manager at all.\n        manager: models.Manager[Any] = models.Manager()\n        manager.name = \"base_objects\"\n        manager.model = self\n        return manager\n\n    @property\n    def _default_manager(cls) -> PolymorphicManager[Any]:\n        mgr: Any = super()._default_manager  # type: ignore[misc]\n        if (\n            check_dump\n            and sys._getframe(1).f_globals.get(\"__name__\")\n            == \"django.core.management.commands.dumpdata\"\n        ):\n            # The downcasting of polymorphic querysets breaks dumpdata because it\n            # expects to serialize multi-table models at each inheritance level.\n            # dumpdata uses Model._default_manager to retrieve the objects by default\n            # and uses Model._base_manager to retrieve objects if the --all flag is\n            # specified. We need to make both of these managers polymorphic to satisfy\n            # our contract that both Model.objects (_default_manager) is polymorphic and\n            # reverse relations Other.related (_base_manager) to our polymorphic models\n            # are also polymorphic.\n            #\n            # It would be best if load/dump data constructed its own managers like\n            # migrations do, but it doesn't. The only way to get around this is to\n            # detect when dumpdata is running and return the non-polymorphic manager in\n            # that case. We do this here by inspecting the call stack and checking if\n            # it came from the dumpdata command module. We use a CPython specific API\n            # sys._getframe to inspect the call stack because it is very fast\n            # (10s of nanoseconds) and disable the check if not on CPython\n            # conceding that dumpdata will just not work in that case. It is important\n            # that this check be fast because _default_manager is accessed very often.\n            # inspect.stack() builds the entire stack frame and a bunch of complicated\n            # datastructures - its use here should be avoided.\n            #\n            # Note that if you are stepping through this code in the debugger it will\n            # be looking at the wrong frame because a bunch of debugging frames will be\n            # on the top of the stack.\n            return cast(\n                PolymorphicManager[Any],\n                mgr.non_polymorphic() if isinstance(mgr, PolymorphicManager) else mgr,\n            )\n        return cast(PolymorphicManager[Any], mgr)\n\n    @property\n    def _base_manager(cls) -> PolymorphicManager[Any]:\n        mgr: Any = super()._base_manager  # type: ignore[misc]\n        if (\n            check_dump\n            and sys._getframe(1).f_globals.get(\"__name__\")\n            == \"django.core.management.commands.dumpdata\"\n        ):\n            # base manager is used when the --all flag is passed - see analogous comment\n            # for _default_manager\n            return cast(\n                PolymorphicManager[Any],\n                mgr.non_polymorphic() if isinstance(mgr, PolymorphicManager) else mgr,\n            )\n        return cast(PolymorphicManager[Any], mgr)\n"
  },
  {
    "path": "src/polymorphic/contrib/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/contrib/drf/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017, Denis Orehovsky\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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.\n"
  },
  {
    "path": "src/polymorphic/contrib/drf/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/contrib/drf/serializers.py",
    "content": "from collections.abc import Mapping\n\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.db import models\nfrom rest_framework import serializers\nfrom rest_framework.fields import empty\n\n\nclass PolymorphicSerializer(serializers.Serializer):\n    model_serializer_mapping: dict[models.Model, serializers.Serializer]\n    resource_type_field_name = \"resourcetype\"\n\n    def __new__(cls, *args, **kwargs):\n        if getattr(cls, \"model_serializer_mapping\", None) is None:\n            raise ImproperlyConfigured(\n                \"`{cls}` is missing a `{cls}.model_serializer_mapping` attribute\".format(\n                    cls=cls.__name__\n                )\n            )\n        if not isinstance(cls.resource_type_field_name, str):\n            raise ImproperlyConfigured(\n                \"`{cls}.resource_type_field_name` must be a string\".format(cls=cls.__name__)\n            )\n        return super(PolymorphicSerializer, cls).__new__(cls, *args, **kwargs)\n\n    def __init__(self, *args, **kwargs):\n        super(PolymorphicSerializer, self).__init__(*args, **kwargs)\n\n        model_serializer_mapping = self.model_serializer_mapping\n        self.model_serializer_mapping = {}\n        self.resource_type_model_mapping = {}\n\n        for model, serializer in model_serializer_mapping.items():\n            resource_type = self.to_resource_type(model)\n            if callable(serializer):\n                serializer = serializer(*args, **kwargs)\n                serializer.parent = self\n\n            self.resource_type_model_mapping[resource_type] = model\n            self.model_serializer_mapping[model] = serializer\n\n    # ----------\n    # Public API\n\n    def to_resource_type(self, model_or_instance):\n        return model_or_instance._meta.object_name\n\n    def to_representation(self, instance):\n        if isinstance(instance, Mapping):\n            resource_type = self._get_resource_type_from_mapping(instance)\n            serializer = self._get_serializer_from_resource_type(resource_type)\n        else:\n            resource_type = self.to_resource_type(instance)\n            serializer = self._get_serializer_from_model_or_instance(instance)\n\n        ret = serializer.to_representation(instance)\n        ret[self.resource_type_field_name] = resource_type\n        return ret\n\n    def to_internal_value(self, data):\n        if self.partial and self.instance:\n            resource_type = self.to_resource_type(self.instance)\n            serializer = self._get_serializer_from_model_or_instance(self.instance)\n        else:\n            resource_type = self._get_resource_type_from_mapping(data)\n            serializer = self._get_serializer_from_resource_type(resource_type)\n\n        ret = serializer.to_internal_value(data)\n        ret[self.resource_type_field_name] = resource_type\n        return ret\n\n    def create(self, validated_data):\n        resource_type = validated_data.pop(self.resource_type_field_name)\n        serializer = self._get_serializer_from_resource_type(resource_type)\n        return serializer.create(validated_data)\n\n    def update(self, instance, validated_data):\n        resource_type = validated_data.pop(self.resource_type_field_name)\n        serializer = self._get_serializer_from_resource_type(resource_type)\n        return serializer.update(instance, validated_data)\n\n    def is_valid(self, *args, **kwargs):\n        valid = super(PolymorphicSerializer, self).is_valid(*args, **kwargs)\n        try:\n            if self.partial and self.instance:\n                resource_type = self.to_resource_type(self.instance)\n                serializer = self._get_serializer_from_model_or_instance(self.instance)\n            else:\n                resource_type = self._get_resource_type_from_mapping(self.initial_data)\n                serializer = self._get_serializer_from_resource_type(resource_type)\n\n        except serializers.ValidationError:\n            child_valid = False\n        else:\n            child_valid = serializer.is_valid(*args, **kwargs)\n            # Update parent's validated_data with child's validated_data\n            # to preserve any modifications made in child's validate() method\n            if child_valid and hasattr(self, \"_validated_data\"):\n                self._validated_data.update(serializer._validated_data)  # pyright: ignore[reportAttributeAccessIssue]\n\n            self._errors.update(serializer.errors)  # type:ignore[attr-defined]  # pyright: ignore[reportAttributeAccessIssue]\n        return valid and child_valid\n\n    def run_validation(self, data=empty):\n        if self.partial and self.instance:\n            resource_type = self.to_resource_type(self.instance)\n            serializer = self._get_serializer_from_model_or_instance(self.instance)\n        else:\n            resource_type = self._get_resource_type_from_mapping(data)\n            serializer = self._get_serializer_from_resource_type(resource_type)\n\n        validated_data = serializer.run_validation(data)\n        validated_data[self.resource_type_field_name] = resource_type\n        return validated_data\n\n    # --------------\n    # Implementation\n\n    def _to_model(self, model_or_instance):\n        return (\n            model_or_instance.__class__\n            if isinstance(model_or_instance, models.Model)\n            else model_or_instance\n        )\n\n    def _get_resource_type_from_mapping(self, mapping):\n        try:\n            return mapping[self.resource_type_field_name]\n        except KeyError:\n            raise serializers.ValidationError(\n                {\n                    self.resource_type_field_name: \"This field is required\",\n                }\n            )\n\n    def _get_serializer_from_model_or_instance(self, model_or_instance):\n        model = self._to_model(model_or_instance)\n\n        for klass in model.mro():\n            if klass in self.model_serializer_mapping:\n                return self.model_serializer_mapping[klass]\n\n        raise KeyError(\n            \"`{cls}.model_serializer_mapping` is missing \"\n            \"a corresponding serializer for `{model}` model\".format(\n                cls=self.__class__.__name__, model=model.__name__\n            )\n        )\n\n    def _get_serializer_from_resource_type(self, resource_type):\n        try:\n            model = self.resource_type_model_mapping[resource_type]\n        except KeyError:\n            raise serializers.ValidationError(\n                {\n                    self.resource_type_field_name: \"Invalid {0}\".format(\n                        self.resource_type_field_name\n                    )\n                }\n            )\n\n        return self._get_serializer_from_model_or_instance(model)\n"
  },
  {
    "path": "src/polymorphic/contrib/extra_views.py",
    "content": "\"\"\"\nThe ``extra_views.formsets`` provides a simple way to handle formsets.\nThe ``extra_views.advanced`` provides a method to combine that with a create/update form.\n\nThis package provides classes that support both options for polymorphic formsets.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom typing import Any, cast\n\nimport extra_views  # type: ignore[import-untyped]\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.forms import BaseFormSet\n\nfrom polymorphic.formsets import (\n    BasePolymorphicInlineFormSet,\n    BasePolymorphicModelFormSet,\n    PolymorphicFormSetChild,\n    polymorphic_child_forms_factory,\n)\n\n__all__ = (\n    \"PolymorphicFormSetView\",\n    \"PolymorphicInlineFormSetView\",\n    \"PolymorphicInlineFormSet\",\n)\n\n\nclass PolymorphicFormSetMixin:\n    \"\"\"\n    Internal Mixin, that provides polymorphic integration with the ``extra_views`` package.\n    \"\"\"\n\n    formset_class: type[BaseFormSet] = BasePolymorphicModelFormSet\n\n    #: Default 0 extra forms\n    factory_kwargs: dict[str, Any] = {\"extra\": 0}\n\n    #: Define the children\n    # :type: list[PolymorphicFormSetChild]\n    formset_children: list[PolymorphicFormSetChild] | None = None\n\n    def get_formset_children(self) -> list[PolymorphicFormSetChild]:\n        \"\"\"\n        :rtype: list[PolymorphicFormSetChild]\n        \"\"\"\n        if not self.formset_children:\n            raise ImproperlyConfigured(\n                \"Define 'formset_children' as list of `PolymorphicFormSetChild`\"\n            )\n        return self.formset_children\n\n    def get_formset_child_kwargs(self) -> dict[str, Any]:\n        return {}\n\n    def get_formset(self) -> type[BaseFormSet]:\n        \"\"\"\n        Returns the formset class from the inline formset factory\n        \"\"\"\n        # Implementation detail:\n        # Since `polymorphic_modelformset_factory` and `polymorphic_inlineformset_factory` mainly\n        # reuse the standard factories, and then add `child_forms`, the same can be done here.\n        # This makes sure the base class construction is completely honored.\n        FormSet = super().get_formset()  # type: ignore[misc]\n        FormSet.child_forms = polymorphic_child_forms_factory(\n            self.get_formset_children(), **self.get_formset_child_kwargs()\n        )\n        return cast(type[BaseFormSet], FormSet)\n\n\nclass PolymorphicFormSetView(PolymorphicFormSetMixin, extra_views.ModelFormSetView):\n    \"\"\"\n    A view that displays a single polymorphic formset.\n\n    .. code-block:: python\n\n        from polymorphic.formsets import PolymorphicFormSetChild\n\n\n        class ItemsView(PolymorphicFormSetView):\n            model = Item\n            formset_children = [\n                PolymorphicFormSetChild(ItemSubclass1),\n                PolymorphicFormSetChild(ItemSubclass2),\n            ]\n\n    \"\"\"\n\n    formset_class: type[BasePolymorphicModelFormSet] = BasePolymorphicModelFormSet\n\n\nclass PolymorphicInlineFormSetView(PolymorphicFormSetMixin, extra_views.InlineFormSetView):\n    \"\"\"\n    A view that displays a single polymorphic formset - with one parent object.\n    This is a variation of the :mod:`extra_views` package classes for django-polymorphic.\n\n    .. code-block:: python\n\n        from polymorphic.formsets import PolymorphicFormSetChild\n\n\n        class OrderItemsView(PolymorphicInlineFormSetView):\n            model = Order\n            inline_model = Item\n            formset_children = [\n                PolymorphicFormSetChild(ItemSubclass1),\n                PolymorphicFormSetChild(ItemSubclass2),\n            ]\n    \"\"\"\n\n    formset_class: type[BasePolymorphicInlineFormSet] = BasePolymorphicInlineFormSet\n\n\nclass PolymorphicInlineFormSet(PolymorphicFormSetMixin, extra_views.InlineFormSetFactory):\n    \"\"\"\n    An inline to add to the ``inlines`` of\n    the :class:`~extra_views.advanced.CreateWithInlinesView`\n    and :class:`~extra_views.advanced.UpdateWithInlinesView` class.\n\n    .. code-block:: python\n\n        from polymorphic.formsets import PolymorphicFormSetChild\n\n\n        class ItemsInline(PolymorphicInlineFormSet):\n            model = Item\n            formset_children = [\n                PolymorphicFormSetChild(ItemSubclass1),\n                PolymorphicFormSetChild(ItemSubclass2),\n            ]\n\n\n        class OrderCreateView(CreateWithInlinesView):\n            model = Order\n            inlines = [ItemsInline]\n\n            def get_success_url(self):\n                return self.object.get_absolute_url()\n\n    \"\"\"\n\n    formset_class: type[BasePolymorphicInlineFormSet] = BasePolymorphicInlineFormSet\n"
  },
  {
    "path": "src/polymorphic/contrib/guardian.py",
    "content": "from typing import Any\n\nfrom django.contrib.contenttypes.models import ContentType\n\nfrom ..models import PolymorphicModel\nfrom ..utils import get_base_polymorphic_model\n\n\ndef get_polymorphic_base_content_type(obj: Any) -> ContentType:\n    \"\"\"\n    Helper function to return the base polymorphic content type id. This should used\n    with django-guardian and the ``GUARDIAN_GET_CONTENT_TYPE`` option.\n\n    See the django-guardian documentation for more information:\n\n    https://django-guardian.readthedocs.io/en/latest/configuration\n    \"\"\"\n    model_type = obj if isinstance(obj, type) else type(obj)\n    if issubclass(model_type, PolymorphicModel) and (\n        base := get_base_polymorphic_model(model_type)\n    ):\n        return ContentType.objects.get_for_model(base)\n    return ContentType.objects.get_for_model(model_type)\n"
  },
  {
    "path": "src/polymorphic/deletion.py",
    "content": "\"\"\"\nClasses and utilities for handling deletions in polymorphic models.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom collections.abc import Callable\nfrom functools import cached_property\nfrom typing import Any, cast\n\nfrom django.db import models\nfrom django.db.migrations.serializer import BaseSerializer, serializer_factory\nfrom django.db.migrations.writer import MigrationWriter\nfrom django.db.models.deletion import Collector\n\nfrom .query import PolymorphicQuerySet\n\n\ndef migration_fingerprint(value: Any) -> Any:\n    \"\"\"\n    Produce a stable, hashable fingerprint for a value as Django would represent\n    it in migrations, but in a structured form when possible.\n    \"\"\"\n    # Canonical deconstruction path for SET(...), @deconstructible, etc.\n    deconstruct = getattr(value, \"deconstruct\", None)\n    if callable(deconstruct):\n        path, args, kwargs = value.deconstruct()\n        return (\n            path,\n            tuple(migration_fingerprint(a) for a in args),\n            tuple(sorted((k, migration_fingerprint(v)) for k, v in kwargs.items())),\n        )\n\n    # Fallback: canonical \"code string\" Django would emit in a migration.\n    # (Works for CASCADE/PROTECT/SET_NULL, primitives, etc.)\n    code, _imports = serializer_factory(value).serialize()\n    return code\n\n\nclass PolymorphicGuard:\n    \"\"\"\n    Wrap an :attr:`django.db.models.ForeignKey.on_delete` callable\n    (CASCADE/PROTECT/SET_NULL/SET(...)/custom), but serialize as the underlying\n    callable.\n\n    :param action: The :attr:`django.db.models.ForeignKey.on_delete` callable to wrap.\n    \"\"\"\n\n    action: Callable[..., Any]\n\n    def __init__(self, action: Callable[..., Any]) -> None:\n        if not callable(action):\n            raise TypeError(\"action must be callable\")\n        self.action = action\n\n    def __call__(\n        self,\n        collector: Collector,\n        field: models.Field[Any, Any],\n        sub_objs: PolymorphicQuerySet[Any, Any],\n        using: str,\n    ) -> None:\n        \"\"\"\n        This guard wraps an on_delete action to ensure that any polymorphic queryset\n        passed to it is converted to a non-polymorphic queryset before proceeding.\n        This prevents issues with cascading deletes on polymorphic models.\n\n        This guard should be automatically applied to reverse relations such that\n\n        .. code-block:: python\n\n            class MyModel(PolymorphicModel):\n                related = models.ForeignKey(\n                    OtherModel,\n                    on_delete=models.CASCADE # <- equal to PolymorphicGuard(models.CASCADE)\n                )\n\n        \"\"\"\n        if isinstance(sub_objs, PolymorphicQuerySet) and not sub_objs.polymorphic_disabled:\n            sub_objs = sub_objs.non_polymorphic()\n        self.action(collector, field, sub_objs, using)\n\n    @cached_property\n    def migration_key(self) -> Any:\n        return migration_fingerprint(self.action)\n\n    def __eq__(self, other: object) -> bool:\n        if (\n            isinstance(other, tuple)\n            and len(other) == 3\n            and callable(getattr(self.action, \"deconstruct\", None))\n        ):\n            # In some cases the autodetector compares us to a reconstructed,\n            # deconstruct() tuple. This has been seen for SET(...) callables.\n            # The arguments element may be a list instead of a tuple though, this\n            # handles that special case\n            result = self.action.deconstruct() == (  # type: ignore[attr-defined]\n                other[0],\n                tuple(other[1]) if isinstance(other[1], list) else other[1],\n                other[2],\n            )\n            # cast(bool) needed because deconstruct() returns Any\n            return cast(bool, result)\n        if isinstance(other, PolymorphicGuard):\n            result = self.migration_key == other.migration_key\n        else:\n            try:\n                result = self.migration_key == migration_fingerprint(other)\n            except Exception:\n                return False\n        # cast(bool) needed because migration_key and migration_fingerprint return Any\n        return cast(bool, result)\n\n    def __hash__(self) -> int:\n        return hash(self.migration_key)\n\n\nclass PolymorphicGuardSerializer(BaseSerializer):\n    \"\"\"\n    A serializer for PolymorphicGuard that serializes the underlying action.\n\n    There is no need to serialize the PolymorphicGuard itself, as it is just a wrapper\n    that ensures that polymorphic querysets are converted to non-polymorphic but no\n    polymorphic managers are present in migrations. This also ensures that new\n    migrations will not be generated.\n    \"\"\"\n\n    def serialize(self) -> tuple[str, set[str]]:\n        \"\"\"\n        Serialize the underlying action of the PolymorphicGuard.\n        \"\"\"\n        return serializer_factory(self.value.action).serialize()\n\n\nMigrationWriter.register_serializer(PolymorphicGuard, PolymorphicGuardSerializer)\n"
  },
  {
    "path": "src/polymorphic/formsets/__init__.py",
    "content": "\"\"\"\nThis allows creating formsets where each row can be a different form type.\nThe logic of the formsets work similar to the standard Django formsets;\nthere are factory methods to construct the classes with the proper form settings.\n\nThe \"parent\" formset hosts the entire model and their child model.\nFor every child type, there is an :class:`PolymorphicFormSetChild` instance\nthat describes how to display and construct the child.\nIt's parameters are very similar to the parent's factory method.\n\"\"\"\n\nfrom .generic import (  # Can import generic here, as polymorphic already depends on the 'contenttypes' app.\n    BaseGenericPolymorphicInlineFormSet,\n    GenericPolymorphicFormSetChild,\n    generic_polymorphic_inlineformset_factory,\n)\nfrom .models import (\n    BasePolymorphicInlineFormSet,\n    BasePolymorphicModelFormSet,\n    PolymorphicFormSetChild,\n    UnsupportedChildType,\n    polymorphic_child_forms_factory,\n    polymorphic_inlineformset_factory,\n    polymorphic_modelformset_factory,\n)\n\n__all__ = (\n    \"BasePolymorphicModelFormSet\",\n    \"BasePolymorphicInlineFormSet\",\n    \"PolymorphicFormSetChild\",\n    \"UnsupportedChildType\",\n    \"polymorphic_modelformset_factory\",\n    \"polymorphic_inlineformset_factory\",\n    \"polymorphic_child_forms_factory\",\n    \"BaseGenericPolymorphicInlineFormSet\",\n    \"GenericPolymorphicFormSetChild\",\n    \"generic_polymorphic_inlineformset_factory\",\n)\n"
  },
  {
    "path": "src/polymorphic/formsets/generic.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Callable, Iterable\nfrom typing import Any, cast\n\nfrom django.contrib.contenttypes.forms import (\n    BaseGenericInlineFormSet,\n    generic_inlineformset_factory,\n)\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.db import models\nfrom django.forms.models import ModelForm\n\nfrom .models import (\n    BasePolymorphicModelFormSet,\n    PolymorphicFormSetChild,\n    polymorphic_child_forms_factory,\n)\n\n\nclass GenericPolymorphicFormSetChild(PolymorphicFormSetChild):\n    \"\"\"\n    Formset child for generic inlines\n    \"\"\"\n\n    ct_field: str\n    fk_field: str\n\n    def __init__(\n        self,\n        *args: Any,\n        ct_field: str = \"content_type\",\n        fk_field: str = \"object_id\",\n        **kwargs: Any,\n    ) -> None:\n        self.ct_field = ct_field\n        self.fk_field = fk_field\n        super().__init__(*args, **kwargs)\n\n    def get_form(\n        self, ct_field: str = \"content_type\", fk_field: str = \"object_id\", **kwargs: Any\n    ) -> type[ModelForm[Any]]:\n        \"\"\"\n        Construct the form class for the formset child.\n        \"\"\"\n        exclude = list(self.exclude)\n        extra_exclude = kwargs.pop(\"extra_exclude\", None)\n        if extra_exclude:\n            exclude += list(extra_exclude)\n\n        # Make sure the GFK fields are excluded by default\n        # This is similar to what generic_inlineformset_factory() does\n        # if there is no field called `ct_field` let the exception propagate\n        opts = self.model._meta\n        ct_field_obj = opts.get_field(self.ct_field)\n\n        if (\n            not isinstance(ct_field_obj, models.ForeignKey)\n            or ct_field_obj.remote_field.model != ContentType\n        ):\n            raise Exception(f\"fk_name '{ct_field_obj}' is not a ForeignKey to ContentType\")\n\n        fk_field_obj = opts.get_field(self.fk_field)  # let the exception propagate\n        exclude.extend([ct_field_obj.name, fk_field_obj.name])\n        kwargs[\"exclude\"] = exclude\n\n        return super().get_form(**kwargs)\n\n\nclass BaseGenericPolymorphicInlineFormSet(BaseGenericInlineFormSet, BasePolymorphicModelFormSet):\n    \"\"\"\n    Polymorphic formset variation for inline generic formsets\n    \"\"\"\n\n\ndef generic_polymorphic_inlineformset_factory(\n    model: type[models.Model],\n    formset_children: Iterable[PolymorphicFormSetChild],\n    form: type[ModelForm[Any]] = ModelForm,\n    formset: type[BaseGenericPolymorphicInlineFormSet] = BaseGenericPolymorphicInlineFormSet,\n    ct_field: str = \"content_type\",\n    fk_field: str = \"object_id\",\n    # Base form\n    # TODO: should these fields be removed in favor of creating\n    # the base form as a formset child too?\n    fields: list[str] | None = None,\n    exclude: list[str] | None = None,\n    extra: int = 1,\n    can_order: bool = False,\n    can_delete: bool = True,\n    max_num: int | None = None,\n    formfield_callback: Callable[..., Any] | None = None,\n    validate_max: bool = False,\n    for_concrete_model: bool = True,\n    min_num: int | None = None,\n    validate_min: bool = False,\n    child_form_kwargs: dict[str, Any] | None = None,\n) -> type[BaseGenericPolymorphicInlineFormSet]:\n    \"\"\"\n    Construct the class for a generic inline polymorphic formset.\n\n    All arguments are identical to :func:`~django.contrib.contenttypes.forms.generic_inlineformset_factory`,\n    with the exception of the ``formset_children`` argument.\n\n    :param formset_children: A list of all child :class:`PolymorphicFormSetChild` objects\n                             that tell the inline how to render the child model types.\n    :type formset_children: Iterable[PolymorphicFormSetChild]\n    :rtype: type\n    \"\"\"\n    kwargs = {\n        \"model\": model,\n        \"form\": form,\n        \"formfield_callback\": formfield_callback,\n        \"formset\": formset,\n        \"ct_field\": ct_field,\n        \"fk_field\": fk_field,\n        \"extra\": extra,\n        \"can_delete\": can_delete,\n        \"can_order\": can_order,\n        \"fields\": fields,\n        \"exclude\": exclude,\n        \"min_num\": min_num,\n        \"max_num\": max_num,\n        \"validate_min\": validate_min,\n        \"validate_max\": validate_max,\n        \"for_concrete_model\": for_concrete_model,\n        # 'localized_fields': localized_fields,\n        # 'labels': labels,\n        # 'help_texts': help_texts,\n        # 'error_messages': error_messages,\n        # 'field_classes': field_classes,\n    }\n    if child_form_kwargs is None:\n        child_form_kwargs = {}\n\n    child_kwargs = {\n        # 'exclude': exclude,\n        \"ct_field\": ct_field,\n        \"fk_field\": fk_field,\n    }\n    if child_form_kwargs:\n        child_kwargs.update(child_form_kwargs)\n\n    FormSet = generic_inlineformset_factory(**kwargs)  # type: ignore[arg-type]\n    FormSet.child_forms = polymorphic_child_forms_factory(formset_children, **child_kwargs)  # type: ignore[attr-defined]\n    return cast(type[BaseGenericPolymorphicInlineFormSet], FormSet)\n"
  },
  {
    "path": "src/polymorphic/formsets/models.py",
    "content": "from __future__ import annotations\n\nfrom collections import OrderedDict\nfrom collections.abc import Callable, Iterable\nfrom typing import Any, cast\n\nfrom django import forms\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.core.exceptions import ImproperlyConfigured, ValidationError\nfrom django.db import models\nfrom django.forms import BaseForm, Media\nfrom django.forms.models import (\n    BaseInlineFormSet,\n    BaseModelFormSet,\n    ModelForm,\n    inlineformset_factory,\n    modelform_factory,\n    modelformset_factory,\n)\nfrom django.utils.functional import cached_property\n\nfrom polymorphic.models import PolymorphicModel\n\nfrom .utils import add_media\n\n\nclass UnsupportedChildType(LookupError):\n    pass\n\n\nclass PolymorphicFormSetChild:\n    \"\"\"\n    Metadata to define the inline of a polymorphic child.\n    Provide this information in the :func:'polymorphic_inlineformset_factory' construction.\n    \"\"\"\n\n    model: type[models.Model]\n    fields: list[str] | None\n    exclude: tuple[str, ...] | list[str]\n    formfield_callback: Callable[..., Any] | None\n    widgets: dict[str, Any] | None\n    localized_fields: list[str] | None\n    labels: dict[str, str] | None\n    help_texts: dict[str, str] | None\n    error_messages: dict[str, dict[str, str]] | None\n\n    def __init__(\n        self,\n        model: type[models.Model],\n        form: type[ModelForm[Any]] = ModelForm,\n        fields: list[str] | None = None,\n        exclude: tuple[str, ...] | list[str] | None = None,\n        formfield_callback: Callable[..., Any] | None = None,\n        widgets: dict[str, Any] | None = None,\n        localized_fields: list[str] | None = None,\n        labels: dict[str, str] | None = None,\n        help_texts: dict[str, str] | None = None,\n        error_messages: dict[str, dict[str, str]] | None = None,\n    ) -> None:\n        self.model = model\n\n        # Instead of initializing the form here right away,\n        # the settings are saved so get_form() can receive additional exclude kwargs.\n        # This is mostly needed for the generic inline formsets\n        self._form_base = form\n        self.fields = fields\n        # Normalize exclude=None to () to match Django's formset behavior\n        self.exclude = () if exclude is None else exclude\n        self.formfield_callback = formfield_callback\n        self.widgets = widgets\n        self.localized_fields = localized_fields\n        self.labels = labels\n        self.help_texts = help_texts\n        self.error_messages = error_messages\n\n    @cached_property\n    def content_type(self) -> ContentType:\n        \"\"\"\n        Expose the ContentType that the child relates to.\n        This can be used for the ''polymorphic_ctype'' field.\n        \"\"\"\n        return ContentType.objects.get_for_model(self.model, for_concrete_model=False)\n\n    def get_form(self, **kwargs: Any) -> type[ModelForm[Any]]:\n        \"\"\"\n        Construct the form class for the formset child.\n        \"\"\"\n        # Do what modelformset_factory() / inlineformset_factory() does to the 'form' argument;\n        # Construct the form with the given ModelFormOptions values\n\n        # Fields can be overwritten. To support the global 'polymorphic_child_forms_factory' kwargs,\n        # that doesn't completely replace all 'exclude' settings defined per child type,\n        # we allow to define things like 'extra_...' fields that are amended to the current child settings.\n\n        # Handle exclude parameter carefully:\n        # - If exclude was explicitly provided (not empty), use it\n        # - If extra_exclude is provided, merge it with self.exclude\n        # - If neither was provided, don't pass exclude to modelform_factory at all,\n        #   allowing the form's Meta.exclude to take effect\n        extra_exclude = kwargs.pop(\"extra_exclude\", None)\n\n        # Determine if we should pass exclude to modelform_factory\n        # Treat empty tuples/lists the same as None to allow form's Meta.exclude to take effect\n        should_pass_exclude = bool(self.exclude) or extra_exclude is not None\n\n        if should_pass_exclude:\n            if self.exclude:\n                exclude = list(self.exclude)\n            else:\n                exclude = []\n\n            if extra_exclude:\n                exclude += list(extra_exclude)\n\n        defaults = {\n            \"form\": self._form_base,\n            \"formfield_callback\": self.formfield_callback,\n            \"fields\": self.fields,\n            # 'for_concrete_model': for_concrete_model,\n            \"localized_fields\": self.localized_fields,\n            \"labels\": self.labels,\n            \"help_texts\": self.help_texts,\n            \"error_messages\": self.error_messages,\n            \"widgets\": self.widgets,\n            # 'field_classes': field_classes,\n        }\n\n        # Only add exclude to defaults if we determined it should be passed\n        if should_pass_exclude:\n            defaults[\"exclude\"] = exclude\n\n        defaults.update(kwargs)\n\n        return modelform_factory(self.model, **defaults)  # type: ignore[arg-type]\n\n\ndef polymorphic_child_forms_factory(\n    formset_children: Iterable[PolymorphicFormSetChild], **kwargs: Any\n) -> dict[type[models.Model], type[ModelForm[Any]]]:\n    \"\"\"\n    Construct the forms for the formset children.\n    This is mostly used internally, and rarely needs to be used by external projects.\n    When using the factory methods (:func:'polymorphic_inlineformset_factory'),\n    this feature is called already for you.\n    \"\"\"\n    child_forms = OrderedDict()\n\n    for formset_child in formset_children:\n        child_forms[formset_child.model] = formset_child.get_form(**kwargs)\n\n    return child_forms\n\n\nclass BasePolymorphicModelFormSet(BaseModelFormSet):\n    \"\"\"\n    A formset that can produce different forms depending on the object type.\n\n    Note that the 'add' feature is therefore more complex,\n    as all variations need ot be exposed somewhere.\n\n    When switching existing formsets to the polymorphic formset,\n    note that the ID field will no longer be named ''model_ptr'',\n    but just appear as ''id''.\n    \"\"\"\n\n    # Assigned by the factory\n    child_forms: dict[type[models.Model], type[ModelForm[Any]]] = OrderedDict()\n    queryset_data: Any\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        super().__init__(*args, **kwargs)\n        self.queryset_data = self.get_queryset()\n\n    def _construct_form(self, i: int, **kwargs: Any) -> BaseForm:\n        \"\"\"\n        Create the form, depending on the model that's behind it.\n        \"\"\"\n        # BaseModelFormSet logic\n        if self.is_bound and i < self.initial_form_count():\n            pk_key = f\"{self.add_prefix(i)}-{self.model._meta.pk.name}\"  # type: ignore[union-attr]\n            pk = self.data[pk_key]\n            pk_field = self.model._meta.pk  # type: ignore[union-attr]\n            to_python = self._get_to_python(pk_field)  # type: ignore[attr-defined]\n            pk = to_python(pk)\n            kwargs[\"instance\"] = self._existing_object(pk)  # type: ignore[attr-defined]\n        if i < self.initial_form_count() and \"instance\" not in kwargs:\n            kwargs[\"instance\"] = self.get_queryset()[i]\n        if i >= self.initial_form_count() and self.initial_extra:\n            # Set initial values for extra forms\n            try:\n                kwargs[\"initial\"] = self.initial_extra[i - self.initial_form_count()]\n            except IndexError:\n                pass\n\n        # BaseFormSet logic, with custom formset_class\n        defaults: dict[str, Any] = {\n            \"auto_id\": self.auto_id,\n            \"prefix\": self.add_prefix(i),\n            \"error_class\": self.error_class,\n        }\n        if self.is_bound:\n            defaults[\"data\"] = self.data\n            defaults[\"files\"] = self.files\n        if self.initial and \"initial\" not in kwargs:\n            try:\n                defaults[\"initial\"] = self.initial[i]\n            except IndexError:\n                pass\n        # Allow extra forms to be empty, unless they're part of\n        # the minimum forms.\n        if i >= self.initial_form_count() and i >= self.min_num:\n            defaults[\"empty_permitted\"] = True\n            defaults[\"use_required_attribute\"] = False\n        defaults.update(kwargs)\n\n        # Need to find the model that will be displayed in this form.\n        # Hence, peeking in the self.queryset_data beforehand.\n        model: type[models.Model] | None\n        if self.is_bound:\n            if \"instance\" in defaults and defaults[\"instance\"] is not None:\n                # Object is already bound to a model, won't change the content type\n                model = cast(\n                    PolymorphicModel, defaults[\"instance\"]\n                ).get_real_instance_class()  # allow proxy models\n            else:\n                # Extra or empty form, use the provided type.\n                # Note this completely tru\n                prefix = defaults[\"prefix\"]\n                try:\n                    ct_id = int(self.data[f\"{prefix}-polymorphic_ctype\"])\n                except (KeyError, ValueError):\n                    raise ValidationError(\n                        f\"Formset row {prefix} has no 'polymorphic_ctype' defined!\"\n                    )\n\n                model = ContentType.objects.get_for_id(ct_id).model_class()\n                if model not in self.child_forms:\n                    # Perform basic validation, as we skip the ChoiceField here.\n                    raise UnsupportedChildType(\n                        f\"Child model type {model} is not part of the formset\"\n                    )\n        else:\n            if \"instance\" in defaults and defaults[\"instance\"] is not None:\n                model = cast(\n                    PolymorphicModel, defaults[\"instance\"]\n                ).get_real_instance_class()  # allow proxy models\n            elif \"polymorphic_ctype\" in cast(dict[str, Any], defaults.get(\"initial\", {})):\n                ct_value = cast(dict[str, Any], defaults[\"initial\"])[\"polymorphic_ctype\"]\n                # Handle both ContentType instances and IDs\n                if isinstance(ct_value, ContentType):\n                    model = ct_value.model_class()\n                else:\n                    model = ContentType.objects.get_for_id(ct_value).model_class()\n            elif i < len(self.queryset_data):\n                model = self.queryset_data[i].__class__\n            else:\n                # Extra forms, cycle between all types\n                # TODO: take the 'extra' value of each child formset into account.\n                total_known = len(self.queryset_data)\n                child_models = list(self.child_forms.keys())\n                model = child_models[(i - total_known) % len(child_models)]\n\n        # Normalize polymorphic_ctype in initial data if it's a ContentType instance\n        # This allows users to set initial[i]['polymorphic_ctype'] = ct (ContentType instance)\n        # while the form field expects an integer ID\n        # We do this AFTER determining the model so the model determination can use the ContentType\n        if \"initial\" in defaults and \"polymorphic_ctype\" in cast(\n            dict[str, Any], defaults[\"initial\"]\n        ):\n            ct_value = cast(dict[str, Any], defaults[\"initial\"])[\"polymorphic_ctype\"]\n            if isinstance(ct_value, ContentType):\n                # Create a copy to avoid modifying the original formset.initial\n                defaults[\"initial\"] = cast(dict[str, Any], defaults[\"initial\"]).copy()\n                # Convert ContentType instance to its ID\n                cast(dict[str, Any], defaults[\"initial\"])[\"polymorphic_ctype\"] = ct_value.pk\n\n        assert model is not None, \"Model must be determined before creating form\"\n        form_class = self.get_form_class(model)\n        form = form_class(**defaults)\n        self.add_fields(form, i)\n        return form\n\n    def add_fields(self, form: BaseForm, index: int | None) -> None:\n        \"\"\"Add a hidden field for the content type.\"\"\"\n        ct = ContentType.objects.get_for_model(form._meta.model, for_concrete_model=False)  # type: ignore[attr-defined]\n        choices = [(ct.pk, ct)]  # Single choice, existing forms can't change the value.\n        form.fields[\"polymorphic_ctype\"] = forms.TypedChoiceField(\n            choices=choices,\n            initial=ct.pk,\n            required=False,\n            widget=forms.HiddenInput,\n            coerce=int,\n        )\n        super().add_fields(form, index)\n\n    def get_form_class(self, model: type[models.Model]) -> type[ModelForm[Any]]:\n        \"\"\"\n        Return the proper form class for the given model.\n        \"\"\"\n        if not self.child_forms:\n            raise ImproperlyConfigured(f\"No 'child_forms' defined in {self.__class__.__name__}\")\n        if not issubclass(model, PolymorphicModel):\n            raise TypeError(f\"Expect polymorphic model type, not {model}\")\n\n        try:\n            return self.child_forms[model]\n        except KeyError:\n            # This may happen when the query returns objects of a type that was not handled by the formset.\n            raise UnsupportedChildType(\n                f\"The '{self.__class__.__name__}' found a '{model.__name__}' model in the queryset, \"\n                f\"but no form class is registered to display it.\"\n            )\n\n    def is_multipart(self) -> bool:\n        \"\"\"\n        Returns True if the formset needs to be multipart, i.e. it\n        has FileInput. Otherwise, False.\n        \"\"\"\n        return any(f.is_multipart() for f in self.empty_forms)\n\n    @property\n    def media(self) -> Media:\n        # Include the media of all form types.\n        # The form media includes all form widget media\n        media = forms.Media()\n        for form in self.empty_forms:\n            add_media(media, form.media)\n        return media\n\n    @cached_property\n    def empty_forms(self) -> list[BaseForm]:\n        \"\"\"\n        Return all possible empty forms\n        \"\"\"\n        forms: list[BaseForm] = []\n        for _model, form_class in self.child_forms.items():\n            kwargs = self.get_form_kwargs(None)\n\n            form = form_class(\n                auto_id=self.auto_id,\n                prefix=self.add_prefix(\"__prefix__\"),\n                empty_permitted=True,\n                use_required_attribute=False,\n                **kwargs,\n            )\n            self.add_fields(form, None)\n            forms.append(form)\n        return forms\n\n    @property\n    def empty_form(self) -> BaseForm:\n        # TODO: make an exception when can_add_base is defined?\n        raise RuntimeError(\n            \"'empty_form' is not used in polymorphic formsets, use 'empty_forms' instead.\"\n        )\n\n\ndef polymorphic_modelformset_factory(\n    model: type[models.Model],\n    formset_children: Iterable[PolymorphicFormSetChild],\n    formset: type[BasePolymorphicModelFormSet] = BasePolymorphicModelFormSet,\n    # Base field\n    # TODO: should these fields be removed in favor of creating\n    # the base form as a formset child too?\n    form: type[ModelForm[Any]] = ModelForm,\n    fields: list[str] | None = None,\n    exclude: list[str] | None = None,\n    extra: int = 1,\n    can_order: bool = False,\n    can_delete: bool = True,\n    max_num: int | None = None,\n    formfield_callback: Callable[..., Any] | None = None,\n    widgets: dict[str, Any] | None = None,\n    validate_max: bool = False,\n    localized_fields: list[str] | None = None,\n    labels: dict[str, str] | None = None,\n    help_texts: dict[str, str] | None = None,\n    error_messages: dict[str, dict[str, str]] | None = None,\n    min_num: int | None = None,\n    validate_min: bool = False,\n    field_classes: dict[str, type[Any]] | None = None,\n    child_form_kwargs: dict[str, Any] | None = None,\n) -> type[BasePolymorphicModelFormSet]:\n    \"\"\"\n    Construct the class for an polymorphic model formset.\n\n    All arguments are identical to :func:'~django.forms.models.modelformset_factory',\n    with the exception of the ''formset_children'' argument.\n\n    :param formset_children: A list of all child :class:'PolymorphicFormSetChild' objects\n                             that tell the inline how to render the child model types.\n    :type formset_children: Iterable[PolymorphicFormSetChild]\n    :rtype: type\n    \"\"\"\n    kwargs = {\n        \"model\": model,\n        \"form\": form,\n        \"formfield_callback\": formfield_callback,\n        \"formset\": formset,\n        \"extra\": extra,\n        \"can_delete\": can_delete,\n        \"can_order\": can_order,\n        \"fields\": fields,\n        \"exclude\": exclude,\n        \"min_num\": min_num,\n        \"max_num\": max_num,\n        \"widgets\": widgets,\n        \"validate_min\": validate_min,\n        \"validate_max\": validate_max,\n        \"localized_fields\": localized_fields,\n        \"labels\": labels,\n        \"help_texts\": help_texts,\n        \"error_messages\": error_messages,\n        \"field_classes\": field_classes,\n    }\n    FormSet = modelformset_factory(**kwargs)  # type: ignore[arg-type]\n\n    child_kwargs = {\n        \"fields\": fields,\n        # 'exclude': exclude,\n    }\n    if child_form_kwargs:\n        child_kwargs.update(child_form_kwargs)\n\n    FormSet.child_forms = polymorphic_child_forms_factory(formset_children, **child_kwargs)  # type: ignore[attr-defined]\n    return cast(type[BasePolymorphicModelFormSet], FormSet)\n\n\nclass BasePolymorphicInlineFormSet(BaseInlineFormSet, BasePolymorphicModelFormSet):\n    \"\"\"\n    Polymorphic formset variation for inline formsets\n    \"\"\"\n\n    def _construct_form(self, i: int, **kwargs: Any) -> BaseForm:\n        return super()._construct_form(i, **kwargs)\n\n\ndef polymorphic_inlineformset_factory(\n    parent_model: type[models.Model],\n    model: type[models.Model],\n    formset_children: Iterable[PolymorphicFormSetChild],\n    formset: type[BasePolymorphicInlineFormSet] = BasePolymorphicInlineFormSet,\n    fk_name: str | None = None,\n    # Base field\n    # TODO: should these fields be removed in favor of creating\n    # the base form as a formset child too?\n    form: type[ModelForm[Any]] = ModelForm,\n    fields: list[str] | None = None,\n    exclude: list[str] | None = None,\n    extra: int = 1,\n    can_order: bool = False,\n    can_delete: bool = True,\n    max_num: int | None = None,\n    formfield_callback: Callable[..., Any] | None = None,\n    widgets: dict[str, Any] | None = None,\n    validate_max: bool = False,\n    localized_fields: list[str] | None = None,\n    labels: dict[str, str] | None = None,\n    help_texts: dict[str, str] | None = None,\n    error_messages: dict[str, dict[str, str]] | None = None,\n    min_num: int | None = None,\n    validate_min: bool = False,\n    field_classes: dict[str, type[Any]] | None = None,\n    child_form_kwargs: dict[str, Any] | None = None,\n) -> type[BasePolymorphicInlineFormSet]:\n    \"\"\"\n    Construct the class for an inline polymorphic formset.\n\n    All arguments are identical to :func:'~django.forms.models.inlineformset_factory',\n    with the exception of the ''formset_children'' argument.\n\n    :param formset_children: A list of all child :class:'PolymorphicFormSetChild' objects\n                             that tell the inline how to render the child model types.\n    :type formset_children: Iterable[PolymorphicFormSetChild]\n    :rtype: type\n    \"\"\"\n    kwargs = {\n        \"parent_model\": parent_model,\n        \"model\": model,\n        \"form\": form,\n        \"formfield_callback\": formfield_callback,\n        \"formset\": formset,\n        \"fk_name\": fk_name,\n        \"extra\": extra,\n        \"can_delete\": can_delete,\n        \"can_order\": can_order,\n        \"fields\": fields,\n        \"exclude\": exclude,\n        \"min_num\": min_num,\n        \"max_num\": max_num,\n        \"widgets\": widgets,\n        \"validate_min\": validate_min,\n        \"validate_max\": validate_max,\n        \"localized_fields\": localized_fields,\n        \"labels\": labels,\n        \"help_texts\": help_texts,\n        \"error_messages\": error_messages,\n        \"field_classes\": field_classes,\n    }\n    FormSet = inlineformset_factory(**kwargs)  # type: ignore[arg-type]\n\n    child_kwargs = {\n        \"fields\": fields,\n        # 'exclude': exclude,\n    }\n    if child_form_kwargs:\n        child_kwargs.update(child_form_kwargs)\n\n    FormSet.child_forms = polymorphic_child_forms_factory(formset_children, **child_kwargs)  # type: ignore[attr-defined]\n    return cast(type[BasePolymorphicInlineFormSet], FormSet)\n"
  },
  {
    "path": "src/polymorphic/formsets/utils.py",
    "content": "\"\"\"\nInternal utils\n\"\"\"\n\nfrom django.forms import Media\n\n\ndef add_media(dest: Media, media: Media) -> None:\n    \"\"\"\n    Optimized version of django.forms.Media.__add__() that doesn't create new objects.\n    \"\"\"\n    dest._css_lists.extend(media._css_lists)  # type: ignore[attr-defined]\n    dest._js_lists.extend(media._js_lists)  # type: ignore[attr-defined]\n"
  },
  {
    "path": "src/polymorphic/locale/en/LC_MESSAGES/django.po",
    "content": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same license as the PACKAGE package.\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2013-11-29 18:12+0100\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"Language: \\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\n#: admin.py:41\nmsgid \"Type\"\nmsgstr \"\"\n\n#: admin.py:56\nmsgid \"Content type\"\nmsgstr \"\"\n\n\n#: admin.py:403\nmsgid \"Contents\"\nmsgstr \"\"\n"
  },
  {
    "path": "src/polymorphic/locale/es/LC_MESSAGES/django.po",
    "content": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same license as the PACKAGE package.\n# Gonzalo Bustos, 2015.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: \\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2013-11-29 18:12+0100\\n\"\n\"PO-Revision-Date: 2015-10-12 11:42-0300\\n\"\n\"Last-Translator: Gonzalo Bustos\\n\"\n\"Language-Team: Spanish <LL@li.org>\\n\"\n\"Language: es\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"X-Generator: Poedit 1.6.10\\n\"\n\n#: admin.py:41\nmsgid \"Type\"\nmsgstr \"Tipo\"\n\n#: admin.py:56\nmsgid \"Content type\"\nmsgstr \"Tipo de contenido\"\n\n#: admin.py:333 admin.py:403\n#, python-format\nmsgid \"Contents\"\nmsgstr \"Contenidos\"\n"
  },
  {
    "path": "src/polymorphic/locale/fr/LC_MESSAGES/django.po",
    "content": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same license as the PACKAGE package.\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2013-11-29 18:12+0100\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"Language: \\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n > 1);\\n\"\n\n#: admin.py:41\nmsgid \"Type\"\nmsgstr \"Type\"\n\n#: admin.py:56\nmsgid \"Content type\"\nmsgstr \"Type de contenu\"\n\n# This is already translated in Django\n# #: admin.py:333\n# #, python-format\n# msgid \"Add %s\"\n# msgstr \"\"\n\n#: admin.py:403\nmsgid \"Contents\"\nmsgstr \"Contenus\"\n"
  },
  {
    "path": "src/polymorphic/managers.py",
    "content": "\"\"\"\nThe manager class for use in the models.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom collections.abc import Iterable\nfrom typing import TYPE_CHECKING, Any, Generic, Literal, TypeAlias, cast, overload\n\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.db import DEFAULT_DB_ALIAS, models, transaction\nfrom django.db.models.fields.related_descriptors import (\n    ForwardManyToOneDescriptor,\n    ForwardOneToOneDescriptor,\n    ManyToManyDescriptor,\n    ReverseManyToOneDescriptor,\n    ReverseOneToOneDescriptor,\n)\nfrom typing_extensions import Self, TypeVar\n\nfrom polymorphic.query import PolymorphicQuerySet\n\nif TYPE_CHECKING:\n    from .models import PolymorphicModel  # noqa: F401\n\n\n__all__ = [\n    \"PolymorphicManager\",\n    \"PolymorphicQuerySet\",\n    \"PolymorphicManyToManyDescriptor\",\n    \"PolymorphicReverseManyToOneDescriptor\",\n    \"PolymorphicForwardManyToOneDescriptor\",\n    \"PolymorphicForwardOneToOneDescriptor\",\n    \"PolymorphicReverseOneToOneDescriptor\",\n    \"Nullable\",\n]\n\n_All = TypeVar(\"_All\", bound=\"PolymorphicModel\", covariant=True)\n\"\"\"\nThis :class:`~typing.TypeVar` represents the union of all possible polymorphic types\nthat a manager may return. All models must derive from\n:class:`~polymorphic.models.PolymorphicModel`\n\"\"\"\n\n_Base = TypeVar(\"_Base\", bound=\"PolymorphicModel\", default=\"PolymorphicModel\", covariant=True)\n\"\"\"\nThis :class:`~typing.TypeVar` represents the base model type from which polymorphic\nmodels derive. For managers on a :class:`~polymorphic.models.PolymorphicModel` subclass,\nyou will likely want to use :data:`~typing.Self`.\n\"\"\"\n\n_Through = TypeVar(\"_Through\", bound=models.Model, default=models.Model, covariant=True)\n\"\"\"\nThis :class:`~typing.TypeVar` represents the \"through\" model type for many-to-many\nrelations. By default it is just a regular :class:`~django.db.models.Model`, which\nwill lack the foreign key relations to the linked models.\n\"\"\"\n\n_Nullable = TypeVar(\"_Nullable\", Literal[True], Literal[False], default=Literal[False])\n\"\"\"\nProvided for nullable relations - should be set to ``Literal[True]`` if the relation is\nnullable, otherwise can be left as the default ``Literal[False]``.\n\"\"\"\n\n_A = TypeVar(\"_A\", bound=\"PolymorphicModel\")\n_B = TypeVar(\"_B\", bound=\"PolymorphicModel\")\n_C = TypeVar(\"_C\", bound=\"PolymorphicModel\")\n_D = TypeVar(\"_D\", bound=\"PolymorphicModel\")\n\n\nNullable: TypeAlias = Literal[True]\n\"\"\"A more readable type hint alias to indicate that a relation is nullable.\"\"\"\n\n\nclass PolymorphicManager(models.Manager[_All], Generic[_All, _Base]):\n    \"\"\"\n    Manager for PolymorphicModel\n\n    Usually not explicitly needed, except if a custom manager or\n    a custom queryset class is to be used.\n    \"\"\"\n\n    queryset_class: type[PolymorphicQuerySet[_All, _Base]] = PolymorphicQuerySet\n\n    if TYPE_CHECKING:\n\n        def all(self) -> PolymorphicQuerySet[_All, _Base]: ...\n        def filter(self, *args: Any, **kwargs: Any) -> PolymorphicQuerySet[_All, _Base]: ...\n\n    @classmethod\n    def from_queryset(\n        cls, queryset_class: type[models.query.QuerySet[_All]], class_name: str | None = None\n    ) -> type[Self]:\n        manager = super().from_queryset(queryset_class, class_name=class_name)\n        # also set our version, Django uses _queryset_class\n        manager.queryset_class = queryset_class  # type: ignore[assignment]\n        return manager\n\n    def get_queryset(self) -> PolymorphicQuerySet[_All, _Base]:\n        qs = self.queryset_class(self.model, using=self._db, hints=getattr(self, \"_hints\", None))\n        if self.model._meta.proxy:\n            qs = qs.instance_of(self.model)\n        return qs\n\n    def __str__(self) -> str:\n        return (\n            f\"{self.__class__.__name__} (PolymorphicManager) using {self.queryset_class.__name__}\"\n        )\n\n    # Proxied methods\n    def non_polymorphic(self) -> PolymorphicQuerySet[_Base, _Base]:\n        return self.all().non_polymorphic()\n\n    # fixme: remove overloads when/if typing ever supports variadic generic unions\n    @overload\n    def instance_of(self, __a: type[_A], /) -> PolymorphicQuerySet[_A, _Base]: ...\n\n    @overload\n    def instance_of(\n        self, __a: type[_A], __b: type[_B], /\n    ) -> PolymorphicQuerySet[_A | _B, _Base]: ...\n\n    @overload\n    def instance_of(\n        self, __a: type[_A], __b: type[_B], __c: type[_C], /\n    ) -> PolymorphicQuerySet[_A | _B | _C, _Base]: ...\n\n    @overload\n    def instance_of(\n        self, __a: type[_A], __b: type[_B], __c: type[_C], __d: type[_D], /\n    ) -> PolymorphicQuerySet[_A | _B | _C | _D, _Base]: ...\n\n    @overload\n    def instance_of(self, *args: type[PolymorphicModel]) -> PolymorphicQuerySet[_All, _Base]: ...\n\n    def instance_of(\n        self: PolymorphicManager[_All], *args: type[PolymorphicModel]\n    ) -> PolymorphicQuerySet[PolymorphicModel, _Base]:\n        return cast(PolymorphicQuerySet[\"PolymorphicModel\", _Base], self.all().instance_of(*args))\n\n    def not_instance_of(self, *args: type[PolymorphicModel]) -> PolymorphicQuerySet[_All, _Base]:\n        return self.all().not_instance_of(*args)\n\n    def get_real_instances(self, base_result_objects: Iterable[_All] | None = None) -> list[_All]:\n        return self.all().get_real_instances(base_result_objects=base_result_objects)\n\n    def create_from_super(self, obj: models.Model, **kwargs: Any) -> _Base:\n        \"\"\"\n        Create an instance of this manager's model class from the given instance of a\n        parent class.\n\n        This is useful when \"promoting\" an instance down the inheritance chain.\n\n        :param obj: An instance of a parent class of the manager's model class.\n        :param kwargs: Additional fields to set on the new instance.\n        :return: The newly created instance.\n        \"\"\"\n        from .models import PolymorphicModel\n\n        with transaction.atomic(using=obj._state.db or DEFAULT_DB_ALIAS):\n            # ensure we have the most derived real instance\n            if isinstance(obj, PolymorphicModel):\n                obj = obj.get_real_instance()\n\n            parent_ptr = self.model._meta.parents.get(type(obj), None)\n\n            if not parent_ptr:\n                raise TypeError(\n                    f\"{obj.__class__.__name__} is not a direct parent of {self.model.__name__}\"\n                )\n            kwargs[parent_ptr.get_attname()] = obj.pk\n\n            # create the new base class with only fields that apply to  it.\n            ctype = ContentType.objects.db_manager(\n                using=(obj._state.db or DEFAULT_DB_ALIAS)\n            ).get_for_model(self.model)\n            nobj: _Base = self.model(**kwargs, polymorphic_ctype=ctype)  # type: ignore[assignment]\n            nobj.save_base(raw=True, using=obj._state.db or DEFAULT_DB_ALIAS, force_insert=True)\n            # force update the content type, but first we need to\n            # retrieve a clean copy from the db to fill in the null\n            # fields otherwise they would be overwritten.\n            if isinstance(obj, PolymorphicModel):\n                parent = obj.__class__.objects.using(obj._state.db or DEFAULT_DB_ALIAS).get(\n                    pk=obj.pk\n                )\n                parent.polymorphic_ctype = ctype\n                parent.save()\n\n            nobj.refresh_from_db()  # cast to cls\n            return nobj\n\n\nif TYPE_CHECKING:\n    from django.db.models.fields.related_descriptors import (\n        ManyRelatedManager,\n        RelatedManager,\n    )\n\n    class PolymorphicManyRelatedManager(  # type: ignore[type-var]\n        PolymorphicManager[_All, _Base],\n        ManyRelatedManager[_All, _Through],  # pyright: ignore[reportInvalidTypeArguments]\n        Generic[_All, _Base, _Through],\n    ): ...\n\n    class PolymorphicRelatedManager(  # type: ignore[type-var]\n        PolymorphicManager[_All, _Base],\n        RelatedManager[_All],  # pyright: ignore[reportInvalidTypeArguments]\n        Generic[_All, _Base],\n    ): ...\n\nelse:\n\n    class PolymorphicRelatedManager(PolymorphicManager[_All, _Base], Generic[_All, _Base]): ...\n\n    class PolymorphicManyRelatedManager(\n        PolymorphicManager[_All, _Base],\n        Generic[_All, _Base, _Through],\n    ): ...\n\n\nclass PolymorphicManyToManyDescriptor(ManyToManyDescriptor, Generic[_All, _Base, _Through]):\n    \"\"\"\n    Use this descriptor class as a type hint for your forward and reverse\n    :class:`~django.db.models.ManyToManyField` relations to/from polymorphic models.\n    For example:\n\n    .. code-block:: python\n\n        to_parents: PolymorphicManyToManyDescriptor[\n            ParentModel | Child1 | Child2,  # all possible polymorphic types\n            ParentModel,                    # the base type (for non_polymorphic)\n            ThroughModel                    # if custom through model\n        ] = models.ManyToManyField(         # type: ignore[assignment]\n            \"ParentModel\",\n            related_name=\"to_parents_reverse\"\n        )\n\n    \"\"\"\n\n    @overload  # type: ignore[override]\n    def __get__(self, instance: None, cls: Any | None = None, /) -> Self: ...\n\n    @overload\n    def __get__(\n        self, instance: models.Model, cls: Any | None = None, /\n    ) -> PolymorphicManyRelatedManager[_All, _Base, _Through]: ...\n\n    def __get__(\n        self, instance: models.Model | None, cls: Any | None = None, /\n    ) -> Self | PolymorphicManyRelatedManager[_All, _Base, _Through]:\n        return cast(  # pragma: no cover\n            Self | PolymorphicManyRelatedManager[_All, _Base, _Through],\n            super().__get__(instance, cls),\n        )\n\n\nclass PolymorphicReverseManyToOneDescriptor(\n    ReverseManyToOneDescriptor,\n    Generic[_All, _Base],\n):\n    \"\"\"\n    Use this descriptor class as a type hint for your reverse\n    :class:`~django.db.models.ForeignKey` relations to polymorphic models. For example:\n\n\n    .. code-block:: python\n\n        class ParentModel(PolymorphicModel):\n            models.ForeignKey(\n                RelatedModel,\n                on_delete=models.CASCADE,\n                null=True,\n                related_name=\"reverse\"\n            )\n\n        class RelatedModel(models.Model):\n\n            reverse: PolymorphicReverseManyToOneDescriptor[\n                ParentModel | Child1 | Child2,\n                ParentModel\n            ]\n    \"\"\"\n\n    @overload\n    def __get__(self, instance: None, cls: Any | None = None, /) -> Self: ...\n\n    @overload\n    def __get__(\n        self, instance: models.Model, cls: Any | None = None, /\n    ) -> PolymorphicRelatedManager[_All, _Base]: ...\n\n    def __get__(\n        self, instance: models.Model | None, cls: Any | None = None, /\n    ) -> Self | PolymorphicRelatedManager[_All, _Base]:\n        return cast(  # pragma: no cover\n            Self | PolymorphicRelatedManager[_All, _Base], super().__get__(instance, cls)\n        )\n\n\nclass PolymorphicForwardManyToOneDescriptor(\n    ForwardManyToOneDescriptor,\n    Generic[_All, _Base, _Nullable],\n):\n    \"\"\"\n    Use this descriptor class as a type hint for your\n    :class:`~django.db.models.ForeignKey` relations to polymorphic models. For example:\n\n    .. note::\n\n        Your typing system will likely flag an assignment error on the class attribute\n        - this is unfortunate but unavoidable - we suggest you add a\n        `# type: ignore[assignment]`.\n\n    .. code-block:: python\n\n        parent: PolymorphicForwardManyToOneDescriptor[\n            ParentModel | Child1 | Child2,\n            ParentModel,\n            Nullable,\n        ] = models.ForeignKey(\n            ParentModel,\n            on_delete=models.CASCADE,\n            null=True,\n        )\n    \"\"\"\n\n    @overload  # type: ignore[override]\n    def __get__(self, instance: None, cls: Any | None = None, /) -> Self: ...\n\n    @overload\n    def __get__(\n        self: PolymorphicForwardManyToOneDescriptor[_All, _Base, Literal[False]],\n        instance: models.Model,\n        cls: Any | None = None,\n        /,\n    ) -> _All: ...\n\n    @overload\n    def __get__(\n        self: PolymorphicForwardManyToOneDescriptor[_All, _Base, Literal[True]],\n        instance: models.Model,\n        cls: Any | None = None,\n        /,\n    ) -> _All | None: ...\n\n    def __get__(\n        self, instance: models.Model | None, cls: Any | None = None, /\n    ) -> Self | _All | None:\n        return cast(  # pragma: no cover\n            Self | _All | None, super().__get__(instance, cls)\n        )\n\n    def get_queryset(self, **hints: Any) -> PolymorphicQuerySet[_All, _Base]:\n        return cast(  # pragma: no cover\n            PolymorphicQuerySet[_All, _Base],\n            super().get_queryset(**hints),\n        )\n\n\nclass PolymorphicForwardOneToOneDescriptor(\n    ForwardOneToOneDescriptor,\n    PolymorphicForwardManyToOneDescriptor[_All, _Base, _Nullable],\n    Generic[_All, _Base, _Nullable],\n):\n    \"\"\"\n    Use this descriptor class as a type hint for your\n    :class:`~django.db.models.OneToOneField` relations to polymorphic models. For\n    example:\n\n    .. note::\n\n        Your typing system will likely flag an assignment error on the class attribute\n        - this is unfortunate but unavoidable - we suggest you add a\n        `# type: ignore[assignment]`.\n\n    .. code-block:: python\n\n        parent: PolymorphicForwardOneToOneDescriptor[\n            ParentModel | Child1 | Child2,\n            ParentModel,\n            Nullable,\n        ] = models.OneToOneField(\n            ParentModel,\n            on_delete=models.CASCADE,\n            null=True,\n        )\n    \"\"\"\n\n\nclass PolymorphicReverseOneToOneDescriptor(\n    ReverseOneToOneDescriptor,\n    Generic[_All, _Base, _Nullable],\n):\n    \"\"\"\n    Use this descriptor class as a type hint for your reverse\n    :class:`~django.db.models.OneToOneField` relations to polymorphic models. For\n    example:\n\n    .. code-block:: python\n\n        class ParentModel(PolymorphicModel):\n            models.OneToOneField(\n                RelatedModel,\n                on_delete=models.CASCADE,\n                null=True,\n                related_name=\"reverse\"\n            )\n\n        class RelatedModel(models.Model):\n\n            reverse: PolymorphicReverseOneToOneDescriptor[\n                ParentModel | Child1 | Child2,\n                ParentModel,\n                Nullable,\n            ]\n    \"\"\"\n\n    @overload\n    def __get__(self, instance: None, cls: Any | None = None, /) -> Self: ...\n\n    @overload\n    def __get__(\n        self: PolymorphicReverseOneToOneDescriptor[_All, _Base, Literal[False]],\n        instance: models.Model,\n        cls: Any | None = None,\n        /,\n    ) -> _All: ...\n\n    @overload\n    def __get__(\n        self: PolymorphicReverseOneToOneDescriptor[_All, _Base, Literal[True]],\n        instance: models.Model,\n        cls: Any | None = None,\n        /,\n    ) -> _All | None: ...\n\n    def __get__(\n        self, instance: models.Model | None, cls: Any | None = None, /\n    ) -> Self | _All | None:\n        return cast(  # pragma: no cover\n            Self | _All | None, super().__get__(instance, cls)\n        )\n\n    def get_queryset(self, **hints: Any) -> PolymorphicQuerySet[_All, _Base]:\n        return cast(  # pragma: no cover\n            PolymorphicQuerySet[_All, _Base],\n            super().get_queryset(**hints),\n        )\n"
  },
  {
    "path": "src/polymorphic/models.py",
    "content": "\"\"\"\nSeamless Polymorphic Inheritance for Django Models\n\"\"\"\n\nfrom __future__ import annotations\n\nimport warnings\nfrom collections.abc import Iterable\nfrom typing import ClassVar, cast\n\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.db import models, router, transaction\nfrom django.db.models import Q\nfrom django.db.models.base import ModelBase\nfrom django.db.utils import DEFAULT_DB_ALIAS\nfrom django.utils.functional import classproperty\nfrom typing_extensions import Self\n\nfrom .base import PolymorphicModelBase\nfrom .managers import PolymorphicManager\nfrom .query_translate import translate_polymorphic_Q_object\nfrom .utils import get_base_polymorphic_model, lazy_ctype\n\n###################################################################################\n# PolymorphicModel\n\n\nclass PolymorphicTypeUndefined(LookupError): ...\n\n\nclass PolymorphicTypeInvalid(RuntimeError): ...\n\n\nclass PolymorphicModel(models.Model, metaclass=PolymorphicModelBase):\n    \"\"\"\n    Abstract base class that provides polymorphic behaviour\n    for any model directly or indirectly derived from it.\n\n    PolymorphicModel declares one field for internal use (:attr:`polymorphic_ctype`)\n    and provides a polymorphic manager as the default manager (and as 'objects').\n    \"\"\"\n\n    _meta_skip: ClassVar[bool] = True\n\n    # for PolymorphicModelBase, so it can tell which models are polymorphic and which are not (duck typing)\n    polymorphic_model_marker: ClassVar[bool] = True\n\n    # for PolymorphicQuery, True => an overloaded __repr__ with nicer multi-line output is used by PolymorphicQuery\n    polymorphic_query_multiline_output: ClassVar[bool] = False\n\n    # avoid ContentType related field accessor clash (an error emitted by model validation)\n    #: The model field that stores the :class:`~django.contrib.contenttypes.models.ContentType` reference to the actual class.\n    polymorphic_ctype: models.ForeignKey[ContentType | None, ContentType | None] = (\n        models.ForeignKey(\n            ContentType,\n            null=True,\n            editable=False,\n            on_delete=models.CASCADE,\n            related_name=\"polymorphic_%(app_label)s.%(class)s_set+\",\n        )\n    )\n\n    # some applications want to know the name of the fields that are added to its models\n    polymorphic_internal_model_fields: ClassVar[list[str]] = [\"polymorphic_ctype\"]\n\n    objects: ClassVar[PolymorphicManager[Self]] = PolymorphicManager()\n\n    class Meta:\n        abstract: ClassVar[bool] = True\n\n    @classproperty\n    def polymorphic_primary_key_name(cls) -> str:\n        \"\"\"\n        The name of the root primary key field of this polymorphic inheritance chain.\n        \"\"\"\n        warnings.warn(\n            \"polymorphic_primary_key_name is deprecated and will be removed in \"\n            \"version 5.0, use get_base_polymorphic_model(Model)._meta.pk.attname \"\n            \"instead.\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n        base_model = get_base_polymorphic_model(cls, allow_abstract=True)\n        assert base_model is not None, \"Polymorphic model must have a base\"\n        return base_model._meta.pk.attname\n\n    @classmethod\n    def translate_polymorphic_Q_object(cls, q: Q) -> Q:\n        return translate_polymorphic_Q_object(cls, q)\n\n    def pre_save_polymorphic(self, using: str = DEFAULT_DB_ALIAS) -> None:\n        \"\"\"\n        Make sure the ``polymorphic_ctype`` value is correctly set on this model.\n\n        This method automatically updates the polymorphic_ctype when:\n        - The object is being saved for the first time\n        - The object is being saved to a different database than it was loaded from\n\n        This ensures cross-database saves work correctly without ForeignKeyViolation.\n        \"\"\"\n        # This function may be called manually in special use-cases. When the object\n        # is saved for the first time, we store its real class in polymorphic_ctype.\n        # When the object later is retrieved by PolymorphicQuerySet, it uses this\n        # field to figure out the real class of this object\n        # (used by PolymorphicQuerySet._get_real_instances)\n\n        # Update polymorphic_ctype if:\n        # 1. It's not set yet (new object), OR\n        # 2. The database has changed (cross-database save)\n        needs_update = not self.polymorphic_ctype_id or (\n            self._state.db and self._state.db != using\n        )\n\n        if needs_update:\n            # Set polymorphic_ctype_id directly to avoid database router issues\n            # when saving across databases\n            ctype = ContentType.objects.db_manager(using).get_for_model(\n                self, for_concrete_model=False\n            )\n            self.polymorphic_ctype_id = ctype.pk\n\n    def save(\n        self,\n        force_insert: bool | tuple[ModelBase, ...] = False,\n        force_update: bool = False,\n        using: str | None = None,\n        update_fields: Iterable[str] | None = None,\n    ) -> None:\n        \"\"\"Calls :meth:`pre_save_polymorphic` and saves the model.\"\"\"\n        # Determine the database to use via Django's routing infrastructure:\n        # 1. Explicit 'using' parameter takes precedence\n        # 2. Otherwise consult DATABASE_ROUTERS via router.db_for_write()\n        # 3. The router falls back to _state.db, then DEFAULT_DB_ALIAS\n        self.pre_save_polymorphic(\n            using=(\n                using\n                or router.db_for_write(self.__class__, instance=self)\n                or self._state.db\n                or DEFAULT_DB_ALIAS\n            )\n        )\n        return super().save(\n            force_insert=force_insert,\n            force_update=force_update,\n            using=using,\n            update_fields=update_fields,\n        )\n\n    save.alters_data = True  # type: ignore[attr-defined]\n\n    def get_real_instance_class(self) -> type[Self] | None:\n        \"\"\"\n        Return the actual model type of the object.\n\n        If a non-polymorphic manager (like base_objects) has been used to\n        retrieve objects, then the real class/type of these objects may be\n        determined using this method.\n        \"\"\"\n        if self.polymorphic_ctype_id is None:\n            raise PolymorphicTypeUndefined(\n                f\"The model {self.__class__.__name__}#{self.pk} does not have a `polymorphic_ctype_id` value defined.\\n\"\n                f\"If you created models outside polymorphic, e.g. through an import or migration, \"\n                f\"make sure the `polymorphic_ctype_id` field points to the ContentType ID of the model subclass.\"\n            )\n\n        # the following line would be the easiest way to do this, but it produces sql queries\n        # return self.polymorphic_ctype.model_class()\n        # so we use the following version, which uses the ContentType manager cache.\n        # Note that model_class() can return None for stale content types;\n        # when the content type record still exists but no longer refers to an existing model.\n        model = (\n            ContentType.objects.db_manager(self._state.db)\n            .get_for_id(self.polymorphic_ctype_id)\n            .model_class()\n        )\n\n        # Protect against bad imports (dumpdata without --natural) or other\n        # issues missing with the ContentType models.\n        if (\n            model is not None\n            and not issubclass(model, self.__class__)\n            and (\n                self.__class__._meta.proxy_for_model is None\n                or not issubclass(model, self.__class__._meta.proxy_for_model)\n            )\n        ):\n            raise PolymorphicTypeInvalid(\n                f\"ContentType {self.polymorphic_ctype_id} for {model} #{self.pk} does \"\n                \"not point to a subclass!\"\n            )\n\n        return cast(type[Self] | None, model)\n\n    def get_real_concrete_instance_class_id(self) -> int | None:\n        model_class = self.get_real_instance_class()\n        if model_class is None:\n            return None\n        return (\n            ContentType.objects.db_manager(self._state.db)\n            .get_for_model(model_class, for_concrete_model=True)\n            .pk\n        )\n\n    def get_real_concrete_instance_class(self) -> type[Self] | None:\n        model_class = self.get_real_instance_class()\n        if model_class is None:\n            return None\n        return cast(\n            type[Self] | None,\n            ContentType.objects.db_manager(self._state.db)\n            .get_for_model(model_class, for_concrete_model=True)\n            .model_class(),\n        )\n\n    def get_real_instance(self) -> Self:\n        \"\"\"\n        Upcast an object to it's actual type.\n\n        If a non-polymorphic manager (like base_objects) has been used to\n        retrieve objects, then the complete object with it's real class/type\n        and all fields may be retrieved with this method.\n\n        If the model of the object's actual type does not exist (i.e. its\n        ContentType is stale), this method raises a\n        :class:`~polymorphic.models.PolymorphicTypeInvalid` exception.\n\n        .. note::\n            Each method call executes one db query (if necessary).\n            Use the :meth:`~polymorphic.managers.PolymorphicQuerySet.get_real_instances`\n            to upcast a complete list in a single efficient query.\n        \"\"\"\n        real_model = self.get_real_instance_class()\n        if real_model is self.__class__:\n            return self\n        if real_model is None:\n            raise PolymorphicTypeInvalid(\n                f\"ContentType {self.polymorphic_ctype_id} for {self.__class__} \"\n                f\"#{self.pk} does not have a corresponding model!\"\n            )\n        return self.__class__.objects.db_manager(self._state.db).get(pk=self.pk)\n\n    def delete(\n        self, using: str | None = None, keep_parents: bool = False\n    ) -> tuple[int, dict[str, int]]:\n        \"\"\"\n        Behaves the same as Django's default :meth:`~django.db.models.Model.delete()`,\n        but with support for upcasting when ``keep_parents`` is True. When keeping\n        parents (upcasting the row) the ``polymorphic_ctype`` fields of the parent rows\n        are updated accordingly in a transaction with the child row deletion.\n        \"\"\"\n        # if we are keeping parents, we must first determine which polymorphic_ctypes we\n        # need to update\n        parent_updates = (\n            [\n                (parent_model, getattr(self, parent_field.get_attname()))  # type: ignore[union-attr]\n                for parent_model, parent_field in self._meta.parents.items()\n                if issubclass(parent_model, PolymorphicModel)\n            ]\n            if keep_parents\n            else []\n        )\n        if parent_updates:\n            with transaction.atomic(using=using):\n                # If keeping the parents (upcasting) we need to update the relevant\n                # content types for all parent inheritance paths.\n                ret = super().delete(using=using, keep_parents=keep_parents)\n                for parent_model, pk in parent_updates:\n                    parent_model.objects.db_manager(using=using).non_polymorphic().filter(\n                        pk=pk\n                    ).update(\n                        polymorphic_ctype=lazy_ctype(parent_model, using=using or DEFAULT_DB_ALIAS)\n                    )\n                return ret\n        return super().delete(using=using, keep_parents=keep_parents)\n\n    delete.alters_data = True  # type: ignore[attr-defined]\n"
  },
  {
    "path": "src/polymorphic/py.typed",
    "content": ""
  },
  {
    "path": "src/polymorphic/query.py",
    "content": "\"\"\"\nQuerySet for PolymorphicModel\n\"\"\"\n\nfrom __future__ import annotations\n\nimport copy\nimport heapq\nfrom collections import defaultdict\nfrom collections.abc import Collection, Iterable, Iterator, Sequence\nfrom typing import TYPE_CHECKING, Any, Generic, cast, overload\n\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.core.exceptions import FieldDoesNotExist\nfrom django.db import connections, models\nfrom django.db.models import FilteredRelation, Q\nfrom django.db.models.expressions import Combinable\nfrom django.db.models.query import ModelIterable, QuerySet\nfrom typing_extensions import Self, TypeVar\n\nfrom .query_translate import (\n    translate_polymorphic_field_path,\n    translate_polymorphic_filter_definitions_in_args,\n    translate_polymorphic_filter_definitions_in_kwargs,\n    translate_polymorphic_Q_object,\n)\nfrom .utils import concrete_descendants, route_to_ancestor\n\nif TYPE_CHECKING:\n    from .models import PolymorphicModel  # noqa: F401\n\n_All = TypeVar(\"_All\", bound=\"PolymorphicModel\", covariant=True)\n_Base = TypeVar(\"_Base\", bound=\"PolymorphicModel\", default=\"PolymorphicModel\", covariant=True)\n\n_A = TypeVar(\"_A\", bound=\"PolymorphicModel\")\n_B = TypeVar(\"_B\", bound=\"PolymorphicModel\")\n_C = TypeVar(\"_C\", bound=\"PolymorphicModel\")\n_D = TypeVar(\"_D\", bound=\"PolymorphicModel\")\n\nPolymorphic_QuerySet_objects_per_request: int = 2000\n\"\"\"\nThe maximum number of objects requested per db-request by the polymorphic\nqueryset.iterator() implementation\n\"\"\"\n\nif TYPE_CHECKING:\n\n    class BasePolymorphicModelIterable(ModelIterable[_All]):\n        pass\nelse:\n\n    class BasePolymorphicModelIterable(ModelIterable):\n        pass\n\n\nclass _Inconsistent:\n    \"\"\"\n    A marker class indicating that there is a mismatch between the content type\n    and the actual class of an object retrieved from the database.\n    \"\"\"\n\n    ...\n\n\nclass PolymorphicModelIterable(BasePolymorphicModelIterable, Generic[_All, _Base]):\n    \"\"\"\n    ModelIterable for PolymorphicModel\n\n    Yields real instances if qs.polymorphic_disabled is False,\n    otherwise acts like a regular ModelIterable.\n    \"\"\"\n\n    queryset: \"PolymorphicQuerySet[_All, _Base]\"\n\n    def __iter__(self) -> Iterator[_All]:\n        base_iter = super().__iter__()\n        if self.queryset.polymorphic_disabled:\n            return base_iter\n        return self._polymorphic_iterator(base_iter)\n\n    def _polymorphic_iterator(self, base_iter: Iterator[_All]) -> Iterator[_All]:\n        \"\"\"\n        Here we do the same as::\n\n            real_results = queryset._get_real_instances(list(base_iter))\n            for o in real_results: yield o\n\n        but it requests the objects in chunks from the database,\n        with QuerySet.iterator(chunk_size) per chunk\n        \"\"\"\n\n        # some databases have a limit on the number of query parameters, we must\n        # respect this for generating get_real_instances queries because those\n        # queries do a large WHERE IN clause with primary keys\n        max_chunk = connections[self.queryset.db].features.max_query_params\n        sql_chunk = self.chunk_size if self.chunked_fetch else None\n        if max_chunk:\n            sql_chunk = (\n                max_chunk\n                if not self.chunked_fetch  # chunk_size was not provided\n                else min(max_chunk, self.chunk_size or max_chunk)\n            )\n\n        sql_chunk = sql_chunk or Polymorphic_QuerySet_objects_per_request\n\n        while True:\n            base_result_objects = []\n            reached_end = False\n\n            # Fetch in chunks\n            for _ in range(sql_chunk):\n                try:\n                    o = next(base_iter)\n                    base_result_objects.append(o)\n                except StopIteration:\n                    reached_end = True\n                    break\n\n            yield from self.queryset._get_real_instances(base_result_objects)\n\n            if reached_end:\n                return\n\n\ndef transmogrify(cls: type[_All], obj: models.Model) -> _All:\n    \"\"\"\n    Upcast a class to a different type without asking questions.\n    \"\"\"\n    if \"__init__\" not in obj.__class__.__dict__:\n        # Just assign __class__ to a different value.\n        new = obj\n        new.__class__ = cls\n        return cast(_All, new)\n    else:\n        # Run constructor, reassign values\n        new = cls()\n        for k, v in obj.__dict__.items():\n            new.__dict__[k] = v  # pyright: ignore[reportIndexIssue]\n        return new\n\n\n###################################################################################\n# PolymorphicQuerySet\n\n\nclass PolymorphicQuerySet(QuerySet[_All], Generic[_All, _Base]):\n    \"\"\"\n    QuerySet for PolymorphicModel\n\n    Contains the core functionality for PolymorphicModel\n\n    Usually not explicitly needed, except if a custom queryset class\n    is to be used.\n    \"\"\"\n\n    polymorphic_disabled: bool\n    polymorphic_deferred_loading: tuple[set[str], bool]\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        super().__init__(*args, **kwargs)\n        self._iterable_class = PolymorphicModelIterable\n\n        self.polymorphic_disabled = False\n        # A parallel structure to django.db.models.query.Query.deferred_loading,\n        # which we maintain with the untranslated field names passed to\n        # .defer() and .only() in order to be able to retranslate them when\n        # retrieving the real instance (so that the deferred fields apply\n        # to that queryset as well).\n        self.polymorphic_deferred_loading = (set(), True)\n\n    def _clone(self, *args: Any, **kwargs: Any) -> Self:\n        # Django's _clone only copies its own variables, so we need to copy ours here\n        new = cast(Self, super()._clone(*args, **kwargs))  # type: ignore[misc]\n        new.polymorphic_disabled = self.polymorphic_disabled\n        new.polymorphic_deferred_loading = (\n            copy.copy(self.polymorphic_deferred_loading[0]),\n            self.polymorphic_deferred_loading[1],\n        )\n        return new\n\n    @classmethod\n    def as_manager(cls) -> models.Manager[_All]:\n        \"\"\"\n        Override base :meth:`~django.db.models.query.QuerySet.as_manager` to return\n        a manager extended from :class:`polymorphic.managers.PolymorphicManager`.\n        \"\"\"\n\n        from .managers import PolymorphicManager\n\n        manager = PolymorphicManager[_All, _Base].from_queryset(cls)()\n        setattr(manager, \"_built_with_as_manager\", True)\n        return manager\n\n    as_manager.queryset_only = True  # type: ignore[attr-defined]\n\n    def bulk_create(\n        self,\n        objs: Iterable[_All],\n        batch_size: int | None = None,\n        ignore_conflicts: bool = False,\n        update_conflicts: bool = False,\n        update_fields: Collection[str] | None = None,\n        unique_fields: Collection[str] | None = None,\n    ) -> list[_All]:\n        objs = list(objs)\n        for obj in objs:\n            obj.pre_save_polymorphic()\n        return super().bulk_create(objs, batch_size, ignore_conflicts=ignore_conflicts)\n\n    def non_polymorphic(self) -> PolymorphicQuerySet[_Base, _Base]:\n        \"\"\"switch off polymorphic behaviour for this query.\n        When the queryset is evaluated, only objects of the type of the\n        base class used for this query are returned.\"\"\"\n        qs = self._clone()\n        qs.polymorphic_disabled = True\n        if issubclass(qs._iterable_class, PolymorphicModelIterable):\n            qs._iterable_class = ModelIterable\n        return cast(PolymorphicQuerySet[_Base, _Base], qs)\n\n    @overload\n    def instance_of(self, __a: type[_A], /) -> PolymorphicQuerySet[_A, _Base]: ...\n\n    @overload\n    def instance_of(\n        self, __a: type[_A], __b: type[_B], /\n    ) -> PolymorphicQuerySet[_A | _B, _Base]: ...\n\n    @overload\n    def instance_of(\n        self, __a: type[_A], __b: type[_B], __c: type[_C], /\n    ) -> PolymorphicQuerySet[_A | _B | _C, _Base]: ...\n\n    @overload\n    def instance_of(\n        self, __a: type[_A], __b: type[_B], __c: type[_C], __d: type[_D], /\n    ) -> PolymorphicQuerySet[_A | _B | _C | _D, _Base]: ...\n\n    @overload\n    def instance_of(self, *args: type[\"PolymorphicModel\"]) -> PolymorphicQuerySet[_All, _Base]: ...\n\n    def instance_of(\n        self, *args: type[\"PolymorphicModel\"]\n    ) -> PolymorphicQuerySet[\"PolymorphicModel\", _Base]:\n        \"\"\"Filter the queryset to only include the classes in args (and their subclasses).\"\"\"\n        # Implementation in _translate_polymorphic_filter_defnition.\n        return self.filter(instance_of=args)\n\n    def not_instance_of(self, *args: type[\"PolymorphicModel\"]) -> Self:\n        \"\"\"Filter the queryset to exclude the classes in args (and their subclasses).\"\"\"\n        # Implementation in _translate_polymorphic_filter_defnition.\"\"\"\n        return self.filter(not_instance_of=args)\n\n    def _filter_or_exclude(self, negate: bool, args: Any, kwargs: Any) -> Self:\n        # We override this internal Django function as it is used for all filter member functions.\n        q_objects = translate_polymorphic_filter_definitions_in_args(\n            queryset_model=self.model, args=args, using=self.db\n        )\n        # filter_field='data'\n        additional_args = translate_polymorphic_filter_definitions_in_kwargs(\n            queryset_model=self.model, kwargs=kwargs, using=self.db\n        )\n        args = list(q_objects) + additional_args\n        return cast(Self, super()._filter_or_exclude(negate=negate, args=args, kwargs=kwargs))  # type: ignore[misc]\n\n    def order_by(self, *field_names: str | Combinable) -> Self:\n        \"\"\"translate the field paths in the args, then call vanilla order_by.\"\"\"\n        translated_fields = [\n            translate_polymorphic_field_path(self.model, a)\n            if isinstance(a, str)\n            else a  # allow expressions to pass unchanged\n            for a in field_names\n        ]\n        return super().order_by(*translated_fields)\n\n    @overload\n    def defer(self, field: None, /) -> Self: ...\n    @overload\n    def defer(self, *fields: str) -> Self: ...\n    def defer(self, *fields: str | None) -> Self:\n        \"\"\"\n        Translate the field paths in the args, then call vanilla defer.\n\n        Also retain a copy of the original fields passed, which we'll need\n        when we're retrieving the real instance (since we'll need to translate\n        them again, as the model will have changed).\n        \"\"\"\n        # Filter out None and translate fields\n        str_fields = tuple(f for f in fields if f is not None)\n        if str_fields:\n            new_fields = tuple(translate_polymorphic_field_path(self.model, a) for a in str_fields)\n            clone = super().defer(*new_fields)\n            clone._polymorphic_add_deferred_loading(str_fields)\n        else:\n            # Handle defer(None) case\n            clone = super().defer(None)\n        return clone\n\n    def only(self, *fields: str) -> Self:\n        \"\"\"\n        Translate the field paths in the args, then call vanilla only.\n\n        Also retain a copy of the original fields passed, which we'll need\n        when we're retrieving the real instance (since we'll need to translate\n        them again, as the model will have changed).\n        \"\"\"\n        new_fields = {translate_polymorphic_field_path(self.model, a) for a in fields}\n        new_fields.add(\"polymorphic_ctype_id\")\n        clone = super().only(*new_fields)\n        clone._polymorphic_add_immediate_loading(fields)\n        return clone\n\n    def _polymorphic_add_deferred_loading(self, field_names: Iterable[str]) -> None:\n        \"\"\"\n        Follows the logic of django.db.models.query.Query.add_deferred_loading(),\n        but for the non-translated field names that were passed to self.defer().\n        \"\"\"\n        existing, defer = self.polymorphic_deferred_loading\n        if defer:\n            # Add to existing deferred names.\n            self.polymorphic_deferred_loading = existing.union(field_names), True\n        else:\n            # Remove names from the set of any existing \"immediate load\" names.\n            self.polymorphic_deferred_loading = existing.difference(field_names), False\n\n    def _polymorphic_add_immediate_loading(self, field_names: Iterable[str]) -> None:\n        \"\"\"\n        Follows the logic of django.db.models.query.Query.add_immediate_loading(),\n        but for the non-translated field names that were passed to self.only()\n        \"\"\"\n        existing, defer = self.polymorphic_deferred_loading\n        field_names = set(field_names)\n        if \"pk\" in field_names:\n            field_names.remove(\"pk\")\n            field_names.add(self.model._meta.pk.name)\n\n        if defer:\n            # Remove any existing deferred names from the current set before\n            # setting the new names.\n            self.polymorphic_deferred_loading = field_names.difference(existing), False\n        else:\n            # Replace any existing \"immediate load\" field names.\n            self.polymorphic_deferred_loading = field_names, False\n\n    def _process_aggregate_args(self, args: Sequence[Any], kwargs: dict[str, Any]) -> None:\n        \"\"\"for aggregate and annotate kwargs: allow ModelX___field syntax for kwargs, forbid it for args.\n        Modifies kwargs if needed (these are Aggregate objects, we translate the lookup member variable)\n        \"\"\"\n        ___lookup_assert_msg = \"PolymorphicModel: annotate()/aggregate(): ___ model lookup supported for keyword arguments only\"\n\n        def patch_lookup(a):\n            # The field on which the aggregate operates is\n            # stored inside a complex query expression.\n            if isinstance(a, Q):\n                # modify in place - this should be fixed if we want immutable\n                # aggregate/annotation expressions\n                a.children = translate_polymorphic_Q_object(self.model, a).children\n            elif isinstance(a, FilteredRelation):\n                patch_lookup(a.condition)\n            elif isinstance(a, models.F):\n                a.name = translate_polymorphic_field_path(self.model, a.name)  # type: ignore[attr-defined]\n            elif hasattr(a, \"get_source_expressions\"):\n                for source_expression in a.get_source_expressions():\n                    if source_expression is not None:\n                        patch_lookup(source_expression)\n            else:\n                a.name = translate_polymorphic_field_path(self.model, a.name)\n\n        def test___lookup(a):\n            \"\"\"*args might be complex expressions too in django 1.8 so\n            the testing for a '___' is rather complex on this one\"\"\"\n            if isinstance(a, Q):\n\n                def tree_node_test___lookup(my_model, node):\n                    \"process all children of this Q node\"\n                    for i in range(len(node.children)):\n                        child = node.children[i]\n\n                        if type(child) is tuple:\n                            # this Q object child is a tuple => a kwarg like Q( instance_of=ModelB )\n                            assert \"___\" not in child[0], ___lookup_assert_msg\n                        else:\n                            # this Q object child is another Q object, recursively process this as well\n                            tree_node_test___lookup(my_model, child)\n\n                tree_node_test___lookup(self.model, a)\n            elif hasattr(a, \"get_source_expressions\"):\n                for source_expression in a.get_source_expressions():\n                    if source_expression is not None:\n                        test___lookup(source_expression)\n            else:\n                assert \"___\" not in a.name, ___lookup_assert_msg\n\n        for a in args:\n            test___lookup(a)\n        for a in kwargs.values():\n            patch_lookup(a)\n\n    def annotate(self, *args: Any, **kwargs: Any) -> Self:\n        \"\"\"translate the polymorphic field paths in the kwargs, then call vanilla annotate.\n        _get_real_instances will do the rest of the job after executing the query.\"\"\"\n        self._process_aggregate_args(args, kwargs)\n        return super().annotate(*args, **kwargs)\n\n    def aggregate(self, *args: Any, **kwargs: Any) -> dict[str, Any]:\n        \"\"\"translate the polymorphic field paths in the kwargs, then call vanilla aggregate.\n        We need no polymorphic object retrieval for aggregate => switch it off.\"\"\"\n        self._process_aggregate_args(args, kwargs)\n        qs = self.non_polymorphic()\n        return super(PolymorphicQuerySet, qs).aggregate(*args, **kwargs)\n\n    # Starting with Django 1.9, the copy returned by 'qs.values(...)' has the\n    # same class as 'qs', so our polymorphic modifications would apply.\n    # We want to leave values queries untouched, so we set 'polymorphic_disabled'.\n    def _values(self, *args: Any, **kwargs: Any) -> Self:\n        clone = cast(Self, super()._values(*args, **kwargs))  # type: ignore[misc]\n        clone.polymorphic_disabled = True\n        return clone\n\n    # Since django_polymorphic 'V1.0 beta2', extra() always returns polymorphic results.\n    # The resulting objects are required to have a unique primary key within the result set\n    # (otherwise an error is thrown).\n    # The \"polymorphic\" keyword argument is not supported anymore.\n    # def extra(self, *args, **kwargs):\n\n    def _get_real_instances(self, base_result_objects: Sequence[_All]) -> list[_All]:\n        \"\"\"\n        Polymorphic object loader\n\n        Does the same as:\n\n            return [ o.get_real_instance() for o in base_result_objects ]\n\n        but more efficiently.\n\n        The list base_result_objects contains the objects from the executed\n        base class query. The class of all of them is self.model (our base model).\n\n        Some, many or all of these objects were not created and stored as\n        class self.model, but as a class derived from self.model. We want to re-fetch\n        these objects from the db as their original class so we can return them\n        just as they were created/saved.\n\n        We identify these objects by looking at o.polymorphic_ctype, which specifies\n        the real class of these objects (the class at the time they were saved).\n\n        First, we sort the result objects in base_result_objects for their\n        subclass (from o.polymorphic_ctype), and then we execute one db query per\n        subclass of objects. Here, we handle any annotations from annotate().\n\n        Finally we re-sort the resulting objects into the correct order and\n        return them as a list.\n        \"\"\"\n        resultlist: list[Any] = []  # polymorphic list of result-objects\n\n        # dict contains one entry per unique model type occurring in result,\n        # in the format idlist_per_model[modelclass]=[list-of-object-ids]\n        idlist_per_model: defaultdict[Any, list[Any]] = defaultdict(list)\n        indexlist_per_model: defaultdict[Any, list[tuple[int, int]]] = defaultdict(list)\n\n        # priority queue holding the query order of concrete classes\n        # we need this so we can retry fetching objects further up the hierarchy with\n        # stale content types - this can happen in some legitimate cases (deletion)\n        # we build up and pop classes off this queue to ensure we process in tree\n        # traversal order (leaves first) - this allows us to retry fetching as parent\n        # classes if child class retrieval fails\n        classes_to_query: list[tuple[int, Any]] = []\n\n        # use the pk attribute for the base model type used in the query to identify\n        # objects\n        pk_name = self.model._meta.pk.attname\n\n        # - sort base_result_object ids into idlist_per_model lists, depending on their real class;\n        # - store objects that already have the correct class into \"results\"\n        content_type_manager = ContentType.objects.db_manager(self.db)\n        self_model_class_id = content_type_manager.get_for_model(\n            self.model, for_concrete_model=False\n        ).pk\n        self_concrete_model_class_id = content_type_manager.get_for_model(\n            self.model, for_concrete_model=True\n        ).pk\n\n        class_priorities = {\n            mdl: idx + 1\n            for idx, mdl in enumerate((*reversed(concrete_descendants(self.model)), self.model))\n        }\n\n        for i, base_object in enumerate(base_result_objects):\n            if base_object.polymorphic_ctype_id == self_model_class_id:\n                # Real class is exactly the same as base class, go straight to results\n                resultlist.append(base_object)\n            else:\n                real_concrete_class = base_object.get_real_instance_class()\n                real_concrete_class_id = base_object.get_real_concrete_instance_class_id()\n\n                if real_concrete_class_id is None:\n                    # Dealing with a stale content type\n                    continue\n                elif real_concrete_class_id == self_concrete_model_class_id:\n                    # Real and base classes share the same concrete ancestor,\n                    # upcast it and put it in the results\n                    # real_concrete_class is guaranteed to be a PolymorphicModel subclass\n                    resultlist.append(\n                        transmogrify(\n                            cast(\"type[PolymorphicModel]\", real_concrete_class), base_object\n                        )\n                    )\n                else:\n                    # This model has a concrete derived class, track it for bulk retrieval.\n                    real_concrete_class = cast(\n                        \"type[_All] | None\",\n                        content_type_manager.get_for_id(real_concrete_class_id).model_class(),\n                    )\n                    if real_concrete_class is not None:\n                        if real_concrete_class not in idlist_per_model:\n                            # Maintain a priority queue to process the model classes\n                            # in order of their occurrence in the inheritance tree - leafs\n                            # first\n                            heapq.heappush(\n                                classes_to_query,\n                                (\n                                    class_priorities.get(real_concrete_class, 0),\n                                    real_concrete_class,\n                                ),\n                            )\n                        idlist_per_model[real_concrete_class].append(getattr(base_object, pk_name))\n                        indexlist_per_model[real_concrete_class].append((i, len(resultlist)))\n                    resultlist.append(None)\n\n        # For each model in \"idlist_per_model\" request its objects (the real model)\n        # from the db and store them in results[].\n        # Then we copy the annotate fields from the base objects to the real objects.\n        # Then we copy the extra() select fields from the base objects to the real objects.\n        # TODO: defer(), only(): support for these would be around here\n\n        while classes_to_query:\n            _, real_concrete_class = heapq.heappop(classes_to_query)\n            assert real_concrete_class is not None  # Ensured by guard at line 467\n            idlist = idlist_per_model.pop(real_concrete_class)\n            indices = indexlist_per_model.pop(real_concrete_class)\n            real_objects = real_concrete_class._base_objects.db_manager(self.db).filter(\n                **{(f\"{pk_name}__in\"): idlist}\n            )\n            # copy select related configuration to new qs\n            real_objects.query.select_related = self.query.select_related\n\n            # Copy deferred fields configuration to the new queryset\n            deferred_loading_fields = []\n            existing_fields = self.polymorphic_deferred_loading[0]\n            for field in existing_fields:\n                try:\n                    translated_field_name = translate_polymorphic_field_path(\n                        real_concrete_class, field\n                    )\n                except AssertionError:\n                    if \"___\" in field:\n                        # The originally passed argument to .defer() or .only()\n                        # was in the form Model2B___field2, where Model2B is\n                        # now a superclass of real_concrete_class. Thus it's\n                        # sufficient to just use the field name.\n                        translated_field_name = field.rpartition(\"___\")[-1]\n\n                        # Check if the field does exist.\n                        # Ignore deferred fields that don't exist in this subclass type.\n                        try:\n                            real_concrete_class._meta.get_field(translated_field_name)\n                        except FieldDoesNotExist:\n                            continue\n                    else:\n                        raise\n\n                deferred_loading_fields.append(translated_field_name)\n            real_objects.query.deferred_loading = (\n                set(deferred_loading_fields),\n                self.query.deferred_loading[1],\n            )\n\n            real_objects_dict = {\n                getattr(real_object, pk_name): real_object for real_object in real_objects\n            }\n\n            for base_idx, result_idx in indices:\n                base_object = base_result_objects[base_idx]\n                o_pk = getattr(base_object, pk_name)\n                real_object = real_objects_dict.get(o_pk)\n                if real_object is None:\n                    # Our content type is pointing to a row that does not exist anymore\n                    # We try to find the next best available parent row\n                    inheritance_path = route_to_ancestor(real_concrete_class, self.model)\n                    if not inheritance_path or inheritance_path[0].model is self.model:\n                        resultlist[result_idx] = base_object\n                    else:\n                        next_best_class = inheritance_path[0].model\n                        if next_best_class not in idlist_per_model:\n                            # add this class to the priority try queue\n                            heapq.heappush(\n                                classes_to_query,\n                                (class_priorities.get(next_best_class, 0), next_best_class),\n                            )\n                        idlist_per_model[next_best_class].append(o_pk)\n                        indexlist_per_model[next_best_class].append((base_idx, result_idx))\n                        resultlist[result_idx] = _Inconsistent\n                    continue\n\n                # need shallow copy to avoid duplication in caches (see PR #353)\n                real_object = copy.copy(real_object)\n                real_class = (\n                    real_concrete_class\n                    if resultlist[result_idx] is _Inconsistent\n                    else real_object.get_real_instance_class()\n                )\n\n                # If the real class is a proxy, upcast it\n                if real_class != real_concrete_class:\n                    real_object = transmogrify(\n                        cast(\"type[PolymorphicModel]\", real_class), real_object\n                    )\n\n                if self.query.annotations:\n                    # New in Django 3.2+: annotation_select contains only the selected annotations\n                    # (excluding aliases). Fallback for older Django versions if needed.\n                    annotation_select = getattr(\n                        self.query, \"annotation_select\", self.query.annotations\n                    )\n                    for anno_field_name in annotation_select.keys():\n                        if hasattr(base_object, anno_field_name):\n                            attr = getattr(base_object, anno_field_name)\n                            setattr(real_object, anno_field_name, attr)\n\n                if self.query.extra_select:\n                    for select_field_name in self.query.extra_select.keys():\n                        attr = getattr(base_object, select_field_name)\n                        setattr(real_object, select_field_name, attr)\n\n                resultlist[result_idx] = real_object\n\n        resultlist = [i for i in resultlist if i and i is not _Inconsistent]\n\n        # set polymorphic_annotate_names in all objects (currently just used for debugging/printing)\n        if self.query.annotations:\n            # get annotate field list\n            annotation_select = getattr(self.query, \"annotation_select\", self.query.annotations)\n            annotate_names = list(annotation_select.keys())\n            for real_object in resultlist:\n                real_object.polymorphic_annotate_names = annotate_names\n\n        # set polymorphic_extra_select_names in all objects (currently just used for debugging/printing)\n        if self.query.extra_select:\n            # get extra select field list\n            extra_select_names = list(self.query.extra_select.keys())\n            for real_object in resultlist:\n                real_object.polymorphic_extra_select_names = extra_select_names\n\n        return resultlist\n\n    def __repr__(self, *args, **kwargs):\n        if self.model.polymorphic_query_multiline_output:\n            result = \",\\n  \".join(repr(o) for o in self.all())\n            return f\"[ {result} ]\"\n        else:\n            return super().__repr__(*args, **kwargs)\n\n    class _p_list_class(list[Any]):\n        def __repr__(self, *args: Any, **kwargs: Any) -> str:\n            result = \",\\n  \".join(repr(o) for o in self)\n            return f\"[ {result} ]\"\n\n    def get_real_instances(self, base_result_objects: Iterable[_All] | None = None) -> list[_All]:\n        \"\"\"\n        Cast a list of objects to their actual classes.\n\n        This does roughly the same as::\n\n            return [ o.get_real_instance() for o in base_result_objects ]\n\n        but more efficiently.\n\n        :rtype: PolymorphicQuerySet\n        \"\"\"\n        \"same as _get_real_instances, but make sure that __repr__ for ShowField... creates correct output\"\n        if base_result_objects is None:\n            base_result_objects = cast(Iterable[_All], self)\n        # Convert to list if needed for indexing\n        base_result_list: Sequence[_All]\n        if not isinstance(base_result_objects, list):\n            base_result_list = list(base_result_objects)\n        else:\n            base_result_list = base_result_objects\n        olist = self._get_real_instances(base_result_list)\n        if not self.model.polymorphic_query_multiline_output:\n            return olist\n        clist = PolymorphicQuerySet._p_list_class(olist)\n        return clist\n\n    def delete(self) -> tuple[int, dict[str, int]]:\n        \"\"\"\n        Deletion will be done non-polymorphically because Django's multi-table deletion\n        mechanism is already walking the class hierarchy and producing a correct\n        deletion graph. Introducing polymorphic querysets into the deletion process\n        disrupts the model hierarchy/relationship traversal.\n        \"\"\"\n        return QuerySet.delete(self.non_polymorphic())\n"
  },
  {
    "path": "src/polymorphic/query_translate.py",
    "content": "\"\"\"\nPolymorphicQuerySet support functions\n\"\"\"\n\nimport copy\nfrom functools import reduce\nfrom operator import or_\nfrom typing import Any\n\nfrom django.apps import apps\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.core.exceptions import FieldDoesNotExist, FieldError\nfrom django.db import models\nfrom django.db.models import Q, Subquery\nfrom django.db.models.fields.related import ForeignObjectRel, RelatedField\nfrom django.db.utils import DEFAULT_DB_ALIAS\n\nfrom .utils import _lazy_ctype, _map_queryname_to_class, concrete_descendants\n\n# These functions implement the additional filter- and Q-object functionality.\n# They form a kind of small framework for easily adding more\n# functionality to filters and Q objects.\n# Probably a more general queryset enhancement class could be made out of them.\n\n###################################################################################\n# PolymorphicQuerySet support functions\n\n\ndef translate_polymorphic_filter_definitions_in_kwargs(\n    queryset_model: type[models.Model], kwargs: dict[str, Any], using: str = DEFAULT_DB_ALIAS\n) -> list[Q]:\n    \"\"\"\n    Translate the keyword argument list for PolymorphicQuerySet.filter()\n\n    Any kwargs with special polymorphic functionality are replaced in the kwargs\n    dict with their vanilla django equivalents.\n\n    For some kwargs a direct replacement is not possible, as a Q object is needed\n    instead to implement the required functionality. In these cases the kwarg is\n    deleted from the kwargs dict and a Q object is added to the return list.\n\n    Modifies: kwargs dict\n    Returns: a list of non-keyword-arguments (Q objects) to be added to the filter() query.\n    \"\"\"\n    additional_args = []\n    for field_path, val in kwargs.copy().items():  # `copy` so we're not mutating the dict\n        new_expr = _translate_polymorphic_filter_definition(\n            queryset_model, field_path, val, using=using\n        )\n\n        if isinstance(new_expr, tuple):\n            # replace kwargs element\n            del kwargs[field_path]\n            kwargs[new_expr[0]] = new_expr[1]\n\n        elif isinstance(new_expr, models.Q):\n            del kwargs[field_path]\n            additional_args.append(new_expr)\n\n    return additional_args\n\n\ndef translate_polymorphic_Q_object(\n    queryset_model: type[models.Model], potential_q_object: Q, using: str = DEFAULT_DB_ALIAS\n) -> Q:\n    def tree_node_correct_field_specs(my_model: type[models.Model], node: Q) -> Q:\n        \"process all children of this Q node\"\n        cpy = copy.copy(node)\n        cpy.children = []\n        for child in node.children:\n            if isinstance(child, (tuple, list)):\n                # this Q object child is a tuple => a kwarg like Q( instance_of=ModelB )\n                key, val = child\n                new_expr = _translate_polymorphic_filter_definition(\n                    my_model, key, val, using=using\n                )\n                cpy.children.append(new_expr or child)\n            elif isinstance(child, models.Q):\n                # this Q object child is another Q object, recursively process\n                cpy.children.append(tree_node_correct_field_specs(my_model, child))\n            else:\n                cpy.children.append(child)\n        return cpy\n\n    if isinstance(potential_q_object, models.Q):\n        return tree_node_correct_field_specs(queryset_model, potential_q_object)\n\n    return potential_q_object  # type: ignore[unreachable]\n\n\ndef translate_polymorphic_filter_definitions_in_args(\n    queryset_model: type[models.Model], args: tuple[Q, ...], using: str = DEFAULT_DB_ALIAS\n) -> list[Q]:\n    \"\"\"\n    Translate the non-keyword argument list for PolymorphicQuerySet.filter()\n\n    In the args list, we return all kwargs to Q-objects that contain special\n    polymorphic functionality with their vanilla django equivalents.\n    We traverse the Q object tree for this (which is simple).\n\n\n    Returns: modified Q objects\n    \"\"\"\n\n    return [translate_polymorphic_Q_object(queryset_model, q, using=using) for q in args]\n\n\ndef _translate_polymorphic_filter_definition(\n    queryset_model: type[models.Model],\n    field_path: str,\n    field_val: Any,\n    using: str = DEFAULT_DB_ALIAS,\n) -> tuple[str, Any] | Q | None:\n    \"\"\"\n    Translate a keyword argument (field_path=field_val), as used for\n    PolymorphicQuerySet.filter()-like functions (and Q objects).\n\n    A kwarg with special polymorphic functionality is translated into\n    its vanilla django equivalent, which is returned, either as tuple\n    (field_path, field_val) or as Q object.\n\n    Returns: kwarg tuple or Q object or None (if no change is required)\n    \"\"\"\n\n    # handle instance_of expressions or alternatively,\n    # if this is a normal Django filter expression, return None\n    if field_path == \"instance_of\":\n        return create_instanceof_q(field_val, using=using)\n    elif field_path == \"not_instance_of\":\n        return create_instanceof_q(field_val, not_instance_of=True, using=using)\n    elif \"___\" not in field_path:\n        return None  # no change\n\n    # filter expression contains '___' (i.e. filter for polymorphic field)\n    # => get the model class specified in the filter expression\n    newpath = translate_polymorphic_field_path(queryset_model, field_path)\n    return (newpath, field_val)\n\n\ndef translate_polymorphic_field_path(queryset_model: type[models.Model], field_path: str) -> str:\n    \"\"\"\n    Translate a field path from a keyword argument, as used for\n    PolymorphicQuerySet.filter()-like functions (and Q objects).\n    Supports leading '-' (for order_by args).\n\n    E.g.: if queryset_model is ModelA, then \"ModelC___field3\" is translated\n    into modela__modelb__modelc__field3.\n    Returns: translated path (unchanged, if no translation needed)\n    \"\"\"\n    classname, sep, pure_field_path = field_path.partition(\"___\")\n    if not sep or not classname:\n        return field_path\n\n    negated = False\n    if classname[0] == \"-\":\n        negated = True\n        classname = classname.lstrip(\"-\")\n\n    if \"__\" in classname:\n        # the user has app label prepended to class name via __ => use Django's get_model function\n        appname, sep, classname = classname.partition(\"__\")\n        try:\n            model = apps.get_model(appname, classname)\n        except LookupError as le:\n            raise FieldError(f\"Model {appname}.{classname} does not exist\") from le\n        if not issubclass(model, queryset_model):\n            raise FieldError(\n                f\"{model._meta.label} is not derived from {queryset_model._meta.label}\"\n            )\n\n    else:\n        # the user has only given us the class name via ___\n        # => select the model from the sub models of the queryset base model\n\n        # Test whether it's actually a regular relation__ _fieldname (the field starting with an _)\n        # so no tripple ClassName___field was intended.\n        try:\n            # This also retreives M2M relations now (including reverse foreign key relations)\n            field = queryset_model._meta.get_field(classname)\n\n            if isinstance(field, (RelatedField, ForeignObjectRel)):\n                # Can also test whether the field exists in the related object to avoid ambiguity between\n                # class names and field names, but that never happens when your class names are in CamelCase.\n                return field_path  # No exception raised, field does exist.\n        except FieldDoesNotExist:\n            pass\n\n        model = _map_queryname_to_class(queryset_model, classname)\n\n    basepath = _create_base_path(queryset_model, model)\n\n    if negated:\n        newpath = \"-\"\n    else:\n        newpath = \"\"\n\n    newpath += basepath\n    if basepath:\n        newpath += \"__\"\n\n    newpath += pure_field_path\n    return newpath\n\n\ndef _create_base_path(baseclass: type[models.Model], myclass: type[models.Model]) -> str:\n    # create new field path for expressions, e.g. for baseclass=ModelA, myclass=ModelC\n    # 'modelb__modelc\" is returned\n    for b in myclass.__bases__:\n        if b == baseclass:\n            return _get_query_related_name(myclass)\n\n        path = _create_base_path(baseclass, b)\n        if path:\n            if b._meta.abstract or b._meta.proxy:  # type: ignore[attr-defined]\n                return _get_query_related_name(myclass)\n            else:\n                return f\"{path}__{_get_query_related_name(myclass)}\"\n    return \"\"\n\n\ndef _get_query_related_name(myclass: type[models.Model]) -> str:\n    for f in myclass._meta.local_fields:\n        if isinstance(f, models.OneToOneField) and f.remote_field.parent_link:\n            return f.related_query_name()\n\n    # Fallback to undetected name,\n    # this happens on proxy models (e.g. SubclassSelectorProxyModel)\n    return myclass.__name__.lower()\n\n\ndef create_instanceof_q(\n    modellist: type[models.Model] | list[type[models.Model]] | tuple[type[models.Model], ...],\n    not_instance_of: bool = False,\n    using: str = DEFAULT_DB_ALIAS,\n) -> Q | None:\n    \"\"\"\n    Helper function for instance_of / not_instance_of\n    Creates and returns a Q object that filters for the models in modellist,\n    including all subclasses of these models (as we want to do the same\n    as pythons isinstance() ).\n    .\n    We recursively collect all __subclasses__(), create a Q filter for each,\n    and or-combine these Q objects. This could be done much more\n    efficiently however (regarding the resulting sql), should an optimization\n    be needed.\n    \"\"\"\n    if not modellist:\n        return None\n\n    if not isinstance(modellist, (list, tuple)):\n        from .models import PolymorphicModel\n\n        if issubclass(modellist, PolymorphicModel):\n            modellist = [modellist]\n        else:\n            raise TypeError(\n                \"PolymorphicModel: instance_of expects a list of (polymorphic) \"\n                \"models or a single (polymorphic) model\"\n            )\n\n    lazy_cts, ct_ids = _get_mro_content_type_ids(modellist, using)\n    q = Q()\n    if lazy_cts:\n        q |= Q(\n            polymorphic_ctype__in=Subquery(\n                # no need to pass using here\n                ContentType.objects.filter(reduce(or_, lazy_cts)).values(\"pk\")\n            )\n        )\n    if ct_ids:\n        q |= Q(polymorphic_ctype__in=ct_ids)\n    if not_instance_of:\n        q = ~q\n    return q\n\n\ndef _get_mro_content_type_ids(\n    models: list[type[models.Model]] | tuple[type[models.Model], ...], using: str\n) -> tuple[list[Q], list[int]]:\n    lazy: list[Q] = []\n    ids: list[int] = []\n    for model in models:\n        cid = _lazy_ctype(model, using=using)\n        ids.append(cid.pk) if isinstance(cid, ContentType) else lazy.append(cid)\n        for descendent in concrete_descendants(model, include_proxy=True):\n            cid = _lazy_ctype(descendent, using=using)\n            ids.append(cid.pk) if isinstance(cid, ContentType) else lazy.append(cid)\n    return lazy, ids\n"
  },
  {
    "path": "src/polymorphic/related_descriptors.py",
    "content": "from typing import Any, cast\n\nfrom django.db.models import QuerySet\nfrom django.db.models.fields.related_descriptors import (\n    ForwardOneToOneDescriptor,\n    ReverseOneToOneDescriptor,\n)\n\n\nclass NonPolymorphicForwardOneToOneDescriptor(ForwardOneToOneDescriptor):\n    \"\"\"\n    A custom descriptor for forward OneToOne relations to polymorphic models that\n    returns non-polymorphic instances. This is used for the parent to child links\n    in multi-table polymorphic models.\n    \"\"\"\n\n    def get_queryset(self, **hints: Any) -> QuerySet[Any]:\n        return cast(\n            QuerySet[Any],\n            (\n                getattr(\n                    self.field.remote_field.model,\n                    \"_base_objects\",\n                    # don't fail if we've been used on a non-poly model\n                    self.field.remote_field.model._base_manager,\n                )\n            )\n            .db_manager(hints=hints)\n            .all(),\n        )\n\n\nclass NonPolymorphicReverseOneToOneDescriptor(ReverseOneToOneDescriptor):\n    \"\"\"\n    A custom descriptor for reverse OneToOne relations to polymorphic models that\n    returns non-polymorphic instances. This is used for the child to parent links\n    in multi-table polymorphic models.\n    \"\"\"\n\n    def get_queryset(self, **hints: Any) -> QuerySet[Any]:\n        return cast(\n            QuerySet[Any],\n            (\n                getattr(\n                    self.related.related_model,\n                    \"_base_objects\",\n                    # don't fail if we've been used on a non-poly model\n                    self.related.related_model._base_manager,  # type: ignore[union-attr]\n                )\n            )\n            .db_manager(hints=hints)\n            .all(),\n        )\n"
  },
  {
    "path": "src/polymorphic/showfields.py",
    "content": "import re\nfrom typing import TYPE_CHECKING, ClassVar\n\nfrom django.db import models\n\nif TYPE_CHECKING:\n    from polymorphic.models import PolymorphicModel\n\n    _Base = PolymorphicModel\nelse:\n    _Base = object\n\nRE_DEFERRED: re.Pattern[str] = re.compile(\"_Deferred_.*\")\n\n\nclass ShowFieldBase(_Base):\n    \"\"\"base class for the ShowField... model mixins, does the work\"\"\"\n\n    # cause nicer multiline PolymorphicQuery output\n    polymorphic_query_multiline_output: ClassVar[bool] = True\n\n    polymorphic_showfield_type: ClassVar[bool] = False\n    polymorphic_showfield_content: ClassVar[bool] = False\n    polymorphic_showfield_deferred: ClassVar[bool] = False\n\n    # these may be overridden by the user\n    polymorphic_showfield_max_line_width: ClassVar[int | None] = None\n    polymorphic_showfield_max_field_width: ClassVar[int] = 20\n    polymorphic_showfield_old_format: ClassVar[bool] = False\n\n    def __repr__(self) -> str:\n        return self.__str__()\n\n    def _showfields_get_content(self, field_name: str, field_type: type = type(None)) -> str:\n        \"helper for __unicode__\"\n        content = getattr(self, field_name)\n        if self.polymorphic_showfield_old_format:\n            out = \": \"\n        else:\n            out = \" \"\n        if issubclass(field_type, models.ForeignKey):\n            if content is None:\n                out += \"None\"\n            else:\n                out += content.__class__.__name__\n        elif issubclass(field_type, models.ManyToManyField):\n            out += f\"{content.count()}\"\n        elif isinstance(content, int):\n            out += str(content)\n        elif content is None:\n            out += \"None\"\n        else:\n            txt = str(content)\n            max_len = self.polymorphic_showfield_max_field_width\n            if len(txt) > max_len:\n                txt = f\"{txt[: max_len - 2]}..\"\n            out += f'\"{txt}\"'\n        return out\n\n    def _showfields_add_regular_fields(self, parts: list[tuple[bool, str, str]]) -> None:\n        \"helper for __unicode__\"\n        done_fields = set()\n        for field in self._meta.fields + self._meta.many_to_many:\n            if field.name in self.polymorphic_internal_model_fields or \"_ptr\" in field.name:\n                continue\n            if field.name in done_fields:\n                continue  # work around django diamond inheritance problem\n            done_fields.add(field.name)\n\n            out = field.name\n\n            # if this is the standard primary key named \"id\", print it as we did with older versions of django_polymorphic\n            if field.primary_key and field.name == \"id\" and type(field) is models.AutoField:\n                out += f\" {getattr(self, field.name)}\"\n\n            # otherwise, display it just like all other fields (with correct type, shortened content etc.)\n            else:\n                if self.polymorphic_showfield_type:\n                    out += f\" ({type(field).__name__}\"\n                    if field.primary_key:\n                        out += \"/pk\"\n                    out += \")\"\n\n                if self.polymorphic_showfield_content:\n                    out += self._showfields_get_content(field.name, type(field))\n\n            parts.append((False, out, \",\"))\n\n    def _showfields_add_dynamic_fields(\n        self, field_list: list[str], title: str, parts: list[tuple[bool, str, str]]\n    ) -> None:\n        \"helper for __unicode__\"\n        parts.append((True, f\"- {title}\", \":\"))\n        for field_name in field_list:\n            out = field_name\n            content = getattr(self, field_name)\n            if self.polymorphic_showfield_type:\n                out += f\" ({type(content).__name__})\"\n            if self.polymorphic_showfield_content:\n                out += self._showfields_get_content(field_name)\n\n            parts.append((False, out, \",\"))\n\n    def __str__(self) -> str:\n        # create list (\"parts\") containing one tuple for each title/field:\n        # ( bool: new section , item-text , separator to use after item )\n\n        # start with model name\n        parts = [(True, RE_DEFERRED.sub(\"\", self.__class__.__name__), \":\")]\n\n        # add all regular fields\n        self._showfields_add_regular_fields(parts)\n\n        # add annotate fields\n        annotate_names = getattr(self, \"polymorphic_annotate_names\", None)\n        if annotate_names is not None:\n            self._showfields_add_dynamic_fields(annotate_names, \"Ann\", parts)\n\n        # add extra() select fields\n        extra_select_names = getattr(self, \"polymorphic_extra_select_names\", None)\n        if extra_select_names is not None:\n            self._showfields_add_dynamic_fields(extra_select_names, \"Extra\", parts)\n\n        if self.polymorphic_showfield_deferred:\n            fields = self.get_deferred_fields()\n            if fields:\n                fields_str = \",\".join(sorted(fields))\n                parts.append((False, f\"deferred[{fields_str}]\", \"\"))\n\n        # format result\n\n        indent = len(self.__class__.__name__) + 5\n        indentstr = \"\".rjust(indent)\n        out = \"\"\n        xpos = 0\n        possible_line_break_pos = None\n\n        for i in range(len(parts)):\n            new_section, p, separator = parts[i]\n            final = i == len(parts) - 1\n            if not final:\n                next_new_section, _, _ = parts[i + 1]\n\n            if (\n                self.polymorphic_showfield_max_line_width\n                and xpos + len(p) > self.polymorphic_showfield_max_line_width\n                and possible_line_break_pos is not None\n            ):\n                rest = out[possible_line_break_pos:]\n                out = out[:possible_line_break_pos]\n                out += f\"\\n{indentstr}{rest}\"\n                xpos = indent + len(rest)\n\n            out += p\n            xpos += len(p)\n\n            if not final:\n                if not next_new_section:\n                    out += separator\n                    xpos += len(separator)\n                out += \" \"\n                xpos += 1\n\n            if not new_section:\n                possible_line_break_pos = len(out)\n\n        return f\"<{out}>\"\n\n\nclass ShowFieldType(ShowFieldBase):\n    \"\"\"model mixin that shows the object's class and it's field types\"\"\"\n\n    polymorphic_showfield_type: ClassVar[bool] = True\n\n\nclass ShowFieldContent(ShowFieldBase):\n    \"\"\"model mixin that shows the object's class, it's fields and field contents\"\"\"\n\n    polymorphic_showfield_content: ClassVar[bool] = True\n\n\nclass ShowFieldTypeAndContent(ShowFieldBase):\n    \"\"\"model mixin, like ShowFieldContent, but also show field types\"\"\"\n\n    polymorphic_showfield_type: ClassVar[bool] = True\n    polymorphic_showfield_content: ClassVar[bool] = True\n"
  },
  {
    "path": "src/polymorphic/static/polymorphic/css/polymorphic_inlines.css",
    "content": ".polymorphic-add-choice {\n  position: relative;\n  clear: left;\n}\n\n.polymorphic-add-choice a:focus {\n  text-decoration: none;\n}\n\n.polymorphic-type-menu {\n  position: absolute;\n  top: 2.2em;\n  left: 0.5em;\n  border: 1px solid var(--border-color, #ccc);\n  border-radius: 4px;\n  padding: 2px;\n  background-color: var(--body-bg, #fff);\n  z-index: 1000;\n}\n\n.polymorphic-type-menu ul {\n  padding: 2px;\n  margin: 0;\n}\n\n.polymorphic-type-menu li {\n  list-style: none inside none;\n  padding: 4px 8px;\n}\n\n.inline-related.empty-form {\n  /* needed for grapelli, which uses grp-empty-form */\n  display: none;\n}\n\n@media (prefers-color-scheme: dark) {\n  .polymorphic-type-menu {\n    border: 1px solid var(--border-color, #121212);\n    background-color: var(--body-bg, #212121);\n  }\n}\n"
  },
  {
    "path": "src/polymorphic/static/polymorphic/js/polymorphic_inlines.js",
    "content": "/*global DateTimeShortcuts, SelectFilter*/\n\n// This is a slightly adapted version of Django's inlines.js\n// Forked for polymorphic by Diederik van der Boor\n\n/**\n * Django admin inlines\n *\n * Based on jQuery Formset 1.1\n * @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com)\n * @requires jQuery 1.2.6 or later\n *\n * Copyright (c) 2009, Stanislaus Madueke\n * All rights reserved.\n *\n * Spiced up with Code from Zain Memon's GSoC project 2009\n * and modified for Django by Jannis Leidel, Travis Swicegood and Julien Phalip.\n *\n * Licensed under the New BSD License\n * See: http://www.opensource.org/licenses/bsd-license.php\n */\n(function($) {\n    'use strict';\n    $.fn.polymorphicFormset = function(opts) {\n        var options = $.extend({}, $.fn.polymorphicFormset.defaults, opts);\n        var $this = $(this);\n        var $parent = $this.parent();\n        var updateElementIndex = function(el, prefix, ndx) {\n            var id_regex = new RegExp(\"(\" + prefix + \"-(\\\\d+|__prefix__))\");\n            var replacement = prefix + \"-\" + ndx;\n            if ($(el).prop(\"for\")) {\n                $(el).prop(\"for\", $(el).prop(\"for\").replace(id_regex, replacement));\n            }\n            if (el.id) {\n                el.id = el.id.replace(id_regex, replacement);\n            }\n            if (el.name) {\n                el.name = el.name.replace(id_regex, replacement);\n            }\n        };\n        var totalForms = $(\"#id_\" + options.prefix + \"-TOTAL_FORMS\").prop(\"autocomplete\", \"off\");\n        var nextIndex = parseInt(totalForms.val(), 10);\n        var maxForms = $(\"#id_\" + options.prefix + \"-MAX_NUM_FORMS\").prop(\"autocomplete\", \"off\");\n        // only show the add button if we are allowed to add more items,\n        // note that max_num = None translates to a blank string.\n        var showAddButton = maxForms.val() === '' || (maxForms.val() - totalForms.val()) > 0;\n        $this.each(function(i) {\n            $(this).not(\".\" + options.emptyCssClass).addClass(options.formCssClass);\n        });\n        if ($this.length && showAddButton) {\n            var addContainer;\n            var menuButton;\n            var addButtons;\n\n            if(options.childTypes == null) {\n              throw Error(\"The polymorphic fieldset options.childTypes is not defined!\");\n            }\n\n            // For Polymorphic inlines, the add button opens a menu.\n            var menu = '<div class=\"polymorphic-type-menu\" style=\"display: none;\"><ul>';\n            for (var i = 0; i < options.childTypes.length; i++) {\n                var obj = options.childTypes[i];\n                menu += '<li><a href=\"#\" data-type=\"' + obj.type + '\">' + obj.name + '</a></li>';\n            }\n            menu += '</ul></div>';\n\n            if ($this.prop(\"tagName\") === \"TR\") {\n                // If forms are laid out as table rows, insert the\n                // \"add\" button in a new table row:\n                var numCols = this.eq(-1).children().length;\n                $parent.append('<tr class=\"' + options.addCssClass + ' polymorphic-add-choice\"><td colspan=\"' + numCols + '\"><a href=\"#\">' + options.addText + \"</a>\" + menu + \"</tr>\");\n                addContainer = $parent.find(\"tr:last > td\");\n                menuButton = addContainer.children('a');\n                addButtons = addContainer.find(\"li a\");\n            } else {\n                // Otherwise, insert it immediately after the last form:\n                $this.filter(\":last\").after('<div class=\"' + options.addCssClass + ' polymorphic-add-choice\"><a href=\"#\">' + options.addText + \"</a>\" + menu + \"</div>\");\n                addContainer = $this.filter(\":last\").next();\n                menuButton = addContainer.children('a');\n                addButtons = addContainer.find(\"li a\");\n            }\n\n            menuButton.click(function(event) {\n                event.preventDefault();\n                event.stopPropagation();  // for menu hide\n                var $menu = $(event.target).next('.polymorphic-type-menu');\n\n                if(! $menu.is(':visible')) {\n                    var hideMenu = function() {\n                        $menu.slideUp(50);\n                        $(document).unbind('click', hideMenu);\n                    };\n\n                    $(document).click(hideMenu);\n                }\n\n                $menu.slideToggle(50);\n            });\n\n            addButtons.click(function(event) {\n                event.preventDefault();\n                var polymorphicType = $(event.target).attr('data-type');  // Select polymorphic type.\n                var template = $(\"#\" + polymorphicType + \"-empty\");\n                var row = template.clone(true);\n                row.removeClass(options.emptyCssClass)\n                .addClass(options.formCssClass)\n                .attr(\"id\", options.prefix + \"-\" + nextIndex);\n                if (row.is(\"tr\")) {\n                    // If the forms are laid out in table rows, insert\n                    // the remove button into the last table cell:\n                    row.children(\":last\").append('<div><a class=\"' + options.deleteCssClass + '\" href=\"#\">' + options.deleteText + \"</a></div>\");\n                } else if (row.is(\"ul\") || row.is(\"ol\")) {\n                    // If they're laid out as an ordered/unordered list,\n                    // insert an <li> after the last list item:\n                    row.append('<li><a class=\"' + options.deleteCssClass + '\" href=\"#\">' + options.deleteText + \"</a></li>\");\n                } else {\n                    // Otherwise, just insert the remove button as the\n                    // last child element of the form's container:\n                    row.children(\":first\").append('<span><a class=\"' + options.deleteCssClass + '\" href=\"#\">' + options.deleteText + \"</a></span>\");\n                }\n                let totForms = parseInt(totalForms.val(), 10);\n                row.find(\"*\").each(function() {\n                    updateElementIndex(this, options.prefix, totForms);\n                });\n                row.find(\"h3 span.inline_label\").each(function () {\n                    $(this).text($(this).text().replace(/##/g, `#${totForms+1}`));\n                });\n                // Insert the new form when it has been fully edited\n                const firstTemplate = $(\"#\" + options.childTypes[0].type + \"-empty\");\n                row.insertBefore($(firstTemplate));\n                // Update number of total forms\n                $(totalForms).val(totForms+1);\n                nextIndex += 1;\n                // Hide add button in case we've hit the max, except we want to add infinitely\n                if ((maxForms.val() !== '') && (maxForms.val() - totalForms.val()) <= 0) {\n                    addContainer.hide();\n                }\n                // The delete button of each row triggers a bunch of other things\n                row.find(\"a.\" + options.deleteCssClass).click(function(e1) {\n                    e1.preventDefault();\n                    // Remove the parent form containing this button:\n                    row.remove();\n                    nextIndex -= 1;\n                    // If a post-delete callback was provided, call it with the deleted form:\n                    if (options.removed) {\n                        options.removed(row);\n                    }\n                    document.dispatchEvent(new CustomEvent(\"formset:removed\", {\n                        detail: {\n                            formsetName: options.prefix\n                        }\n                    }));\n                    // Update the TOTAL_FORMS form count.\n                    var forms = $(\".\" + options.formCssClass);\n                    $(\"#id_\" + options.prefix + \"-TOTAL_FORMS\").val(forms.length);\n                    // Show add button again once we drop below max\n                    if ((maxForms.val() === '') || (maxForms.val() - forms.length) > 0) {\n                        addContainer.show();\n                    }\n                    // Also, update names and ids for all remaining form controls\n                    // so they remain in sequence:\n                    var i, formCount;\n                    var updateElementCallback = function() {\n                        updateElementIndex(this, options.prefix, i);\n                    };\n                    for (i = 0, formCount = forms.length; i < formCount; i++) {\n                        updateElementIndex($(forms).get(i), options.prefix, i);\n                        $(forms.get(i)).find(\"*\").each(updateElementCallback);\n                        $(forms.get(i)).find(\"h3 span.inline_label\").each(function () {\n                            $(this).text($(this).text().replace(/#\\d+/g, `#${i+1}`));\n                        });\n                    }\n                });\n                // If a post-add callback was supplied, call it with the added form:\n                if (options.added) {\n                    options.added(row);\n                }\n                row.get(0).dispatchEvent(new CustomEvent(\"formset:added\", {\n                    bubbles: true,\n                    detail: {\n                        formsetName: options.prefix\n                    }\n                }));\n            });\n        }\n        return this;\n    };\n\n    /* Setup plugin defaults */\n    $.fn.polymorphicFormset.defaults = {\n        prefix: \"form\",          // The form prefix for your django formset\n        addText: \"add another\",      // Text for the add link\n        childTypes: null,           // defined by the client.\n        deleteText: \"remove\",      // Text for the delete link\n        addCssClass: \"add-row\",      // CSS class applied to the add link\n        deleteCssClass: \"delete-row\",  // CSS class applied to the delete link\n        emptyCssClass: \"empty-row\",    // CSS class applied to the empty row\n        formCssClass: \"dynamic-form\",  // CSS class applied to each form in a formset\n        added: null,          // Function called each time a new form is added\n        removed: null,          // Function called each time a form is deleted\n        addButton: null       // Existing add button to use\n    };\n\n\n    // Tabular inlines ---------------------------------------------------------\n    $.fn.tabularPolymorphicFormset = function(options) {\n        var $rows = $(this);\n        var alternatingRows = function(row) {\n            $($rows.selector).not(\".add-row\").removeClass(\"row1 row2\")\n            .filter(\":even\").addClass(\"row1\").end()\n            .filter(\":odd\").addClass(\"row2\");\n        };\n\n        var reinitDateTimeShortCuts = function() {\n            // Reinitialize the calendar and clock widgets by force\n            if (typeof DateTimeShortcuts !== \"undefined\") {\n                $(\".datetimeshortcuts\").remove();\n                DateTimeShortcuts.init();\n            }\n        };\n\n        var updateSelectFilter = function() {\n            // If any SelectFilter widgets are a part of the new form,\n            // instantiate a new SelectFilter instance for it.\n            if (typeof SelectFilter !== 'undefined') {\n                $('.selectfilter').each(function(index, value) {\n                    var namearr = value.name.split('-');\n                    SelectFilter.init(value.id, namearr[namearr.length - 1], false);\n                });\n                $('.selectfilterstacked').each(function(index, value) {\n                    var namearr = value.name.split('-');\n                    SelectFilter.init(value.id, namearr[namearr.length - 1], true);\n                });\n            }\n        };\n\n        var initPrepopulatedFields = function(row) {\n            row.find('.prepopulated_field').each(function() {\n                var field = $(this),\n                    input = field.find('input, select, textarea'),\n                    dependency_list = input.data('dependency_list') || [],\n                    dependencies = [];\n                $.each(dependency_list, function(i, field_name) {\n                    dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id'));\n                });\n                if (dependencies.length) {\n                    input.prepopulate(dependencies, input.attr('maxlength'));\n                }\n            });\n        };\n\n        $rows.polymorphicFormset({\n            prefix: options.prefix,\n            addText: options.addText,\n            childTypes: options.childTypes,\n            formCssClass: \"dynamic-\" + options.prefix,\n            deleteCssClass: \"inline-deletelink\",\n            deleteText: options.deleteText,\n            emptyCssClass: \"empty-form\",\n            removed: alternatingRows,\n            added: function(row) {\n                initPrepopulatedFields(row);\n                reinitDateTimeShortCuts();\n                updateSelectFilter();\n                alternatingRows(row);\n            },\n            addButton: options.addButton\n        });\n\n        return $rows;\n    };\n\n    // Stacked inlines ---------------------------------------------------------\n    $.fn.stackedPolymorphicFormset = function(options) {\n        var $rows = $(this);\n        var updateInlineLabel = function(row) {\n            $($rows.selector).find(\".inline_label\").each(function(i) {\n                var count = i + 1;\n                $(this).html($(this).html().replace(/(#\\d+)/g, \"#\" + count));\n            });\n        };\n\n        var reinitDateTimeShortCuts = function() {\n            // Reinitialize the calendar and clock widgets by force, yuck.\n            if (typeof DateTimeShortcuts !== \"undefined\") {\n                $(\".datetimeshortcuts\").remove();\n                DateTimeShortcuts.init();\n            }\n        };\n\n        var updateSelectFilter = function() {\n            // If any SelectFilter widgets were added, instantiate a new instance.\n            if (typeof SelectFilter !== \"undefined\") {\n                $(\".selectfilter\").each(function(index, value) {\n                    var namearr = value.name.split('-');\n                    SelectFilter.init(value.id, namearr[namearr.length - 1], false);\n                });\n                $(\".selectfilterstacked\").each(function(index, value) {\n                    var namearr = value.name.split('-');\n                    SelectFilter.init(value.id, namearr[namearr.length - 1], true);\n                });\n            }\n        };\n\n        var initPrepopulatedFields = function(row) {\n            row.find('.prepopulated_field').each(function() {\n                var field = $(this),\n                    input = field.find('input, select, textarea'),\n                    dependency_list = input.data('dependency_list') || [],\n                    dependencies = [];\n                $.each(dependency_list, function(i, field_name) {\n                    dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id'));\n                });\n                if (dependencies.length) {\n                    input.prepopulate(dependencies, input.attr('maxlength'));\n                }\n            });\n        };\n\n        $rows.polymorphicFormset({\n            prefix: options.prefix,\n            addText: options.addText,\n            childTypes: options.childTypes,\n            formCssClass: \"dynamic-\" + options.prefix,\n            deleteCssClass: \"inline-deletelink\",\n            deleteText: options.deleteText,\n            emptyCssClass: \"empty-form\",\n            removed: updateInlineLabel,\n            added: function(row) {\n                initPrepopulatedFields(row);\n                reinitDateTimeShortCuts();\n                updateSelectFilter();\n                updateInlineLabel(row);\n            },\n            addButton: options.addButton\n        });\n\n        return $rows;\n    };\n\n    $(document).ready(function() {\n        $(\".js-inline-polymorphic-admin-formset\").each(function() {\n            var data = $(this).data(),\n                inlineOptions = data.inlineFormset;\n            switch(data.inlineType) {\n            case \"stacked\":\n                $(inlineOptions.name + \"-group .inline-related\").stackedPolymorphicFormset(inlineOptions.options);\n                break;\n            case \"tabular\":\n                $(inlineOptions.name + \"-group .tabular.inline-related tbody tr\").tabularPolymorphicFormset(inlineOptions.options);\n                break;\n            }\n        });\n    });\n})(django.jQuery);\n"
  },
  {
    "path": "src/polymorphic/templates/admin/polymorphic/add_type_form.html",
    "content": "{% extends \"admin/change_form.html\" %}\n\n{% if save_on_top %}\n  {% block submit_buttons_top %}\n    {% include 'admin/submit_line.html' with show_save=1 %}\n  {% endblock %}\n{% endif %}\n\n{% block submit_buttons_bottom %}\n  {% include 'admin/submit_line.html' with show_save=1 %}\n{% endblock %}\n"
  },
  {
    "path": "src/polymorphic/templates/admin/polymorphic/change_form.html",
    "content": "{% extends \"admin/change_form.html\" %}\n{% load polymorphic_admin_tags %}\n\n{% block breadcrumbs %}\n  {% breadcrumb_scope base_opts %}{{ block.super }}{% endbreadcrumb_scope %}\n{% endblock %}\n"
  },
  {
    "path": "src/polymorphic/templates/admin/polymorphic/delete_confirmation.html",
    "content": "{% extends \"admin/delete_confirmation.html\" %}\n{% load polymorphic_admin_tags %}\n\n{% block breadcrumbs %}\n  {% breadcrumb_scope base_opts %}{{ block.super }}{% endbreadcrumb_scope %}\n{% endblock %}\n"
  },
  {
    "path": "src/polymorphic/templates/admin/polymorphic/edit_inline/stacked.html",
    "content": "{% load i18n admin_urls static %}\n\n<div class=\"js-inline-polymorphic-admin-formset inline-group\"\n     id=\"{{ inline_admin_formset.formset.prefix }}-group\"\n     data-inline-type=\"stacked\"\n     data-inline-formset=\"{{ inline_admin_formset.inline_formset_data }}\">\n\n<fieldset class=\"module {{ inline_admin_formset.classes }}\">\n  <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>\n{{ inline_admin_formset.formset.management_form }}\n{{ inline_admin_formset.formset.non_form_errors }}\n\n{% for inline_admin_form in inline_admin_formset %}\n  <div class=\"inline-related inline-{{ inline_admin_form.model_admin.opts.model_name }}{% if inline_admin_form.original or inline_admin_form.show_url %} has_original{% endif %}{% if inline_admin_form.is_empty %} empty-form {% endif %}{% if forloop.last %} last-related{% endif %}\"\n       id=\"{% if inline_admin_form.original.pk %}{{ inline_admin_formset.formset.prefix }}-{{ forloop.counter0 }}{% else %}{{ inline_admin_form.model_admin.opts.model_name }}-empty{% endif %}\">\n\n    <h3><b>{{ inline_admin_form.model_admin.opts.verbose_name|capfirst }}:</b>&nbsp;<span class=\"inline_label\">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %} <a href=\"{% url inline_admin_form.model_admin.opts|admin_urlname:'change' inline_admin_form.original.pk|admin_urlquote %}\" class=\"inlinechangelink\">{% trans \"Change\" %}</a>{% endif %}\n{% else %}##{% endif %}</span>\n        {% if inline_admin_form.show_url %}<a href=\"{{ inline_admin_form.absolute_url }}\">{% trans \"View on site\" %}</a>{% endif %}\n      {% if inline_admin_form.formset.can_delete and inline_admin_form.original %}<span class=\"delete\">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}\n    </h3>\n\n    {% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %}\n\n    {% for fieldset in inline_admin_form %}\n      {% include \"admin/includes/fieldset.html\" %}\n    {% endfor %}\n\n    {% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %}\n\n    {{ inline_admin_form.fk_field.field }}\n    {{ inline_admin_form.polymorphic_ctype_field.field }}\n  </div>\n{% endfor %}\n</fieldset>\n</div>\n"
  },
  {
    "path": "src/polymorphic/templates/admin/polymorphic/object_history.html",
    "content": "{% extends \"admin/object_history.html\" %}\n{% load polymorphic_admin_tags %}\n\n{% block breadcrumbs %}\n  {% breadcrumb_scope base_opts %}{{ block.super }}{% endbreadcrumb_scope %}\n{% endblock %}\n"
  },
  {
    "path": "src/polymorphic/templatetags/__init__.py",
    "content": "\"\"\"\nWe provide collections of tags that override or extend template tags for formsets and\nthe admin interface.\n\"\"\"\n"
  },
  {
    "path": "src/polymorphic/templatetags/polymorphic_admin_tags.py",
    "content": "from django.template import Context, Library, Node, NodeList, TemplateSyntaxError\nfrom django.template.base import FilterExpression, Parser, Token\nfrom typing_extensions import Self\n\nregister: Library = Library()\n\n\nclass BreadcrumbScope(Node):\n    base_opts: FilterExpression\n    nodelist: NodeList\n\n    def __init__(self, base_opts: FilterExpression, nodelist: NodeList) -> None:\n        self.base_opts = base_opts\n        self.nodelist = nodelist  # Note, takes advantage of Node.child_nodelists\n\n    @classmethod\n    def parse(cls, parser: Parser, token: Token) -> Self:\n        bits = token.split_contents()\n        if len(bits) == 2:\n            (_tagname, base_opts_str) = bits\n            base_opts = parser.compile_filter(base_opts_str)\n            nodelist = parser.parse((\"endbreadcrumb_scope\",))\n            parser.delete_first_token()\n\n            return cls(base_opts=base_opts, nodelist=nodelist)\n        else:\n            raise TemplateSyntaxError(f\"{token.contents[0]} tag expects 1 argument\")\n\n    def render(self, context: Context) -> str:\n        # app_label is really hard to overwrite in the standard Django ModelAdmin.\n        # To insert it in the template, the entire render_change_form() and delete_view() have to copied and adjusted.\n        # Instead, have an assignment tag that inserts that in the template.\n        base_opts = self.base_opts.resolve(context)\n        new_vars = {}\n        if base_opts and not isinstance(base_opts, str):\n            new_vars = {\n                \"app_label\": base_opts.app_label,  # What this is all about\n                \"opts\": base_opts,\n            }\n\n        new_scope = context.push()\n        new_scope.update(new_vars)\n        html = self.nodelist.render(context)\n        context.pop()\n        return html\n\n\n@register.tag\ndef breadcrumb_scope(parser: Parser, token: Token) -> BreadcrumbScope:\n    \"\"\"\n    .. templatetag:: breadcrumb_scope\n\n    Easily allow the breadcrumb to be generated in the admin change templates.\n\n    The ``{% breadcrumb_scope ... %}`` tag makes sure the ``{{ opts }}`` and\n    ``{{ app_label }}`` values are temporary based on the provided\n    ``{{ base_opts }}``.\n\n    This allows fixing the breadcrumb in admin templates:\n\n    .. code-block:: html+django\n\n        {% extends \"admin/change_form.html\" %}\n        {% load polymorphic_admin_tags %}\n\n        {% block breadcrumbs %}\n        {% breadcrumb_scope base_opts %}{{ block.super }}{% endbreadcrumb_scope %}\n        {% endblock %}\n    \"\"\"\n    return BreadcrumbScope.parse(parser, token)\n"
  },
  {
    "path": "src/polymorphic/templatetags/polymorphic_formset_tags.py",
    "content": "\"\"\"\n.. versionadded:: 1.1\n\nTo render formsets in the frontend, the ``polymorphic_tags`` provides extra\nfilters to implement HTML rendering of polymorphic formsets.\n\nThe following filters are provided;\n\n* ``{{ formset|as_script_options }}`` render the ``data-options`` for a JavaScript formset library.\n* ``{{ formset|include_empty_form }}`` provide the placeholder form for an add button.\n* ``{{ form|as_form_type }}`` return the model name that the form instance uses.\n* ``{{ model|as_model_name }}`` performs the same, for a model class or instance.\n\n.. code-block:: html+django\n\n    {% load i18n polymorphic_formset_tags %}\n\n    <div class=\"inline-group\" id=\"{{ formset.prefix }}-group\" data-options=\"{{ formset|as_script_options }}\">\n        {% block add_button %}\n            {% if formset.show_add_button|default_if_none:'1' %}\n                {% if formset.empty_forms %}\n                    {# django-polymorphic formset (e.g. PolymorphicInlineFormSetView) #}\n                    <div class=\"btn-group\" role=\"group\">\n                      {% for model in formset.child_forms %}\n                          <a type=\"button\" data-type=\"{{ model|as_model_name }}\" class=\"js-add-form btn btn-default\">{% glyphicon 'plus' %} {{ model|as_verbose_name }}</a>\n                      {% endfor %}\n                    </div>\n                {% else %}\n                    <a class=\"btn btn-default js-add-form\">{% trans \"Add\" %}</a>\n                {% endif %}\n            {% endif %}\n        {% endblock %}\n\n        {{ formset.management_form }}\n\n        {% for form in formset|include_empty_form %}\n          {% block formset_form_wrapper %}\n            <div id=\"{{ form.prefix }}\" data-inline-type=\"{{ form|as_form_type|lower }}\" class=\"inline-related{% if '__prefix__' in form.prefix %} empty-form{% endif %}\">\n                {{ form.non_field_errors }}\n\n                {# Add the 'pk' field that is not mentioned in crispy #}\n                {% for field in form.hidden_fields %}\n                  {{ field }}\n                {% endfor %}\n\n                {% block formset_form %}\n                    {% crispy form %}\n                {% endblock %}\n            </div>\n          {% endblock %}\n        {% endfor %}\n    </div>\n\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nfrom collections.abc import Generator\nfrom typing import TYPE_CHECKING, Any, cast\n\nfrom django.db import models\nfrom django.forms import BaseForm, BaseFormSet\nfrom django.template import Library\nfrom django.utils.encoding import force_str\nfrom django.utils.text import capfirst\nfrom django.utils.translation import gettext\n\nfrom polymorphic.formsets import BasePolymorphicModelFormSet\n\nif TYPE_CHECKING:\n    from django.forms import BaseModelFormSet, ModelForm\n\nregister: Library = Library()\n\n\n@register.filter()\ndef include_empty_form(formset: BaseFormSet) -> Generator[BaseForm, None, None]:\n    \"\"\"\n    .. templatetag:: include_empty_form\n\n    Make sure the \"empty form\" is included when displaying a formset (typically table with input rows)\n    \"\"\"\n    yield from formset\n\n    empty_forms = getattr(formset, \"empty_forms\", None)\n    if empty_forms is not None:\n        # BasePolymorphicModelFormSet\n        yield from empty_forms\n    else:\n        # Standard Django formset\n        yield formset.empty_form\n\n\n@register.filter\ndef as_script_options(formset: \"BaseModelFormSet[Any, Any]\") -> str:\n    \"\"\"\n    .. templatetag:: as_script_options\n\n    A JavaScript data structure for the JavaScript code\n\n    This generates the ``data-options`` attribute for ``jquery.django-inlines.js``\n    The formset may define the following extra attributes:\n\n    - ``verbose_name``\n    - ``add_text``\n    - ``show_add_button``\n    \"\"\"\n    verbose_name = getattr(formset, \"verbose_name\", formset.model._meta.verbose_name)  # type: ignore[union-attr]\n    options = {\n        \"prefix\": formset.prefix,\n        \"pkFieldName\": formset.model._meta.pk.name,  # type: ignore[union-attr]\n        \"addText\": getattr(formset, \"add_text\", None)\n        or gettext(\"Add another %(verbose_name)s\") % {\"verbose_name\": capfirst(verbose_name)},\n        \"showAddButton\": getattr(formset, \"show_add_button\", True),\n        \"deleteText\": gettext(\"Delete\"),\n    }\n\n    if isinstance(formset, BasePolymorphicModelFormSet):\n        # Allow to add different types\n        options[\"childTypes\"] = [\n            {\n                \"name\": force_str(model._meta.verbose_name),\n                \"type\": model._meta.model_name,\n            }\n            for model in formset.child_forms.keys()\n        ]\n\n    return json.dumps(options)\n\n\n@register.filter\ndef as_form_type(form: \"ModelForm[Any]\") -> str:\n    \"\"\"\n    .. templatetag:: as_form_type\n\n    Usage: ``{{ form|as_form_type }}``\n    \"\"\"\n    # model_name is never None for a valid model, cast is safe\n    return cast(str, form._meta.model._meta.model_name)\n\n\n@register.filter\ndef as_model_name(model: type[models.Model]) -> str:\n    \"\"\"\n    .. templatetag:: as_model_name\n\n    Usage: ``{{ model|as_model_name }}``\n    \"\"\"\n    # model_name is never None for a valid model, cast is safe\n    return cast(str, model._meta.model_name)\n"
  },
  {
    "path": "src/polymorphic/tests/__init__.py",
    "content": "HEADLESS = True\n"
  },
  {
    "path": "src/polymorphic/tests/admin.py",
    "content": "from inspect import isclass\nfrom django.contrib.admin import register, ModelAdmin, TabularInline, site as admin_site\nfrom django.db.models.query import QuerySet\nfrom django.http import HttpRequest\nfrom polymorphic.admin import (\n    StackedPolymorphicInline,\n    PolymorphicInlineSupportMixin,\n    PolymorphicChildModelAdmin,\n    PolymorphicChildModelFilter,\n    PolymorphicParentModelAdmin,\n)\n\nfrom polymorphic.tests.models import (\n    PlainA,\n    Model2A,\n    Model2B,\n    Model2C,\n    Model2D,\n    InlineModelA,\n    InlineModelB,\n    InlineParent,\n    NoChildren,\n    ModelWithPolyFK,\n    M2MAdminTest,\n    M2MAdminTestChildA,\n    M2MAdminTestChildB,\n    M2MAdminTestChildC,\n    M2MThroughBase,\n    M2MThroughProject,\n    M2MThroughPerson,\n    M2MThroughMembership,\n    M2MThroughMembershipWithPerson,\n    M2MThroughMembershipWithSpecialPerson,\n    M2MThroughProjectWithTeam,\n    M2MThroughSpecialPerson,\n    DirectM2MContainer,\n)\n\n\n@register(Model2A)\nclass Model2Admin(PolymorphicParentModelAdmin):\n    list_filter = (PolymorphicChildModelFilter,)\n    child_models = (Model2A, Model2B, Model2C, Model2D)\n\n\nadmin_site.register(Model2B, PolymorphicChildModelAdmin)\nadmin_site.register(Model2C, PolymorphicChildModelAdmin)\n\n\n@register(Model2D)\nclass Model2DAdmin(PolymorphicChildModelAdmin):\n    exclude = (\"field3\",)\n\n\n@register(PlainA)\nclass PlainAAdmin(ModelAdmin):\n    search_fields = [\"field1\"]\n\n    def get_queryset(self, request: HttpRequest) -> QuerySet:\n        return super().get_queryset(request).order_by(\"pk\")\n\n\nclass Inline(StackedPolymorphicInline):\n    model = InlineModelA\n\n    def get_child_inlines(self):\n        return [\n            child\n            for child in self.__class__.__dict__.values()\n            if isclass(child) and issubclass(child, StackedPolymorphicInline.Child)\n        ]\n\n    class InlineModelAChild(StackedPolymorphicInline.Child):\n        model = InlineModelA\n\n    class InlineModelBChild(StackedPolymorphicInline.Child):\n        model = InlineModelB\n        autocomplete_fields = [\"plain_a\"]\n\n\n@register(InlineParent)\nclass InlineParentAdmin(PolymorphicInlineSupportMixin, ModelAdmin):\n    inlines = (Inline,)\n    extra = 1\n\n\n@register(NoChildren)\nclass NoChildrenAdmin(PolymorphicParentModelAdmin):\n    child_models = (NoChildren,)\n\n\n@register(ModelWithPolyFK)\nclass ModelWithPolyFKAdmin(ModelAdmin):\n    fields = [\"name\", \"poly_fk\"]\n\n\n@register(M2MAdminTest)\nclass M2MAdminTestAdmin(PolymorphicParentModelAdmin):\n    list_filter = (PolymorphicChildModelFilter,)\n    child_models = (M2MAdminTestChildA, M2MAdminTestChildB, M2MAdminTestChildC)\n\n\n@register(M2MAdminTestChildA)\nclass M2MAdminTestChildA(PolymorphicChildModelAdmin):\n    raw_id_fields = (\"child_bs\",)\n\n\n@register(M2MAdminTestChildB)\nclass M2MAdminTestChildB(PolymorphicChildModelAdmin):\n    raw_id_fields = (\"child_as\",)\n\n\n@register(M2MAdminTestChildC)\nclass M2MAdminTestChildC(PolymorphicChildModelAdmin):\n    raw_id_fields = (\"child_as\",)\n\n\n# Issue #182: M2M field in model admin\n# Register models to test M2M field to polymorphic model\n@register(M2MThroughBase)\nclass M2MThroughBaseAdmin(PolymorphicParentModelAdmin):\n    \"\"\"Base admin for polymorphic M2M test models.\"\"\"\n\n    child_models = (\n        M2MThroughProject,\n        M2MThroughPerson,\n        M2MThroughProjectWithTeam,\n        M2MThroughSpecialPerson,\n    )\n\n\n@register(M2MThroughProject)\nclass M2MThroughProjectAdmin(PolymorphicChildModelAdmin):\n    \"\"\"Admin for M2MThroughProject polymorphic child.\"\"\"\n\n    pass\n\n\n@register(M2MThroughPerson)\nclass M2MThroughPersonAdmin(PolymorphicChildModelAdmin):\n    \"\"\"Admin for M2MThroughPerson polymorphic child.\"\"\"\n\n    pass\n\n\n@register(M2MThroughSpecialPerson)\nclass M2MThroughSpecialPersonAdmin(PolymorphicChildModelAdmin):\n    \"\"\"Admin for M2MThroughSpecialPerson polymorphic child.\"\"\"\n\n    pass\n\n\n@register(DirectM2MContainer)\nclass DirectM2MContainerAdmin(ModelAdmin):\n    \"\"\"\n    Test case for Issue #182: M2M field in model admin.\n    DirectM2MContainer has a direct M2M field to polymorphic M2MThroughBase model.\n    This should work without AttributeError: 'int' object has no attribute 'pk'.\n    \"\"\"\n\n    filter_horizontal = (\"items\",)\n\n\n# Issue #375: Admin with M2M through table on polymorphic model\nclass M2MThroughMembershipInline(StackedPolymorphicInline):\n    \"\"\"\n    Polymorphic inline for Issue #375: M2M through table with polymorphic membership types.\n    This tests creating different membership types inline based on person type.\n    \"\"\"\n\n    model = M2MThroughMembership\n    extra = 1\n\n    class MembershipWithPersonChild(StackedPolymorphicInline.Child):\n        \"\"\"Inline for regular Person membership.\"\"\"\n\n        model = M2MThroughMembershipWithPerson\n\n    class MembershipWithSpecialPersonChild(StackedPolymorphicInline.Child):\n        \"\"\"Inline for SpecialPerson membership with special notes.\"\"\"\n\n        model = M2MThroughMembershipWithSpecialPerson\n\n    child_inlines = (\n        MembershipWithPersonChild,\n        MembershipWithSpecialPersonChild,\n    )\n\n\n@register(M2MThroughProjectWithTeam)\nclass M2MThroughProjectWithTeamAdmin(PolymorphicInlineSupportMixin, PolymorphicChildModelAdmin):\n    \"\"\"\n    Test case for Issue #375: Admin with M2M through table on polymorphic model.\n    M2MThroughProjectWithTeam (polymorphic) has M2M to M2MThroughPerson (polymorphic)\n    with custom through model M2MThroughMembership (now polymorphic).\n    Uses polymorphic inlines to support different membership types.\n    \"\"\"\n\n    inlines = (M2MThroughMembershipInline,)\n"
  },
  {
    "path": "src/polymorphic/tests/admintestcase.py",
    "content": "from django.conf import settings\nfrom django.contrib.admin import AdminSite\nfrom django.contrib.admin.templatetags.admin_urls import admin_urlname\nfrom django.contrib.auth.models import User\nfrom django.contrib.messages.middleware import MessageMiddleware\nfrom django.http.response import HttpResponse\nfrom django.test import RequestFactory, TestCase\nfrom django.urls import clear_url_caches, include, path, reverse, set_urlconf\n\n\nclass AdminTestCase(TestCase):\n    \"\"\"\n    Testing the admin site\n    \"\"\"\n\n    #: The model to test\n    model = None\n    #: The admin class to test\n    admin_class = None\n\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        cls.admin_user = User.objects.create_superuser(\n            \"admin\", \"admin@example.org\", password=\"admin\"\n        )\n\n    def setUp(self):\n        super().setUp()\n\n        # Have a separate site, to avoid dependency on polymorphic wrapping or standard admin configuration\n        self.admin_site = AdminSite()\n\n        if self.model is not None:\n            self.admin_register(self.model, self.admin_class)\n\n    def tearDown(self):\n        clear_url_caches()\n        set_urlconf(None)\n\n    def register(self, model):\n        \"\"\"Decorator, like admin.register()\"\"\"\n\n        def _dec(admin_class):\n            self.admin_register(model, admin_class)\n            return admin_class\n\n        return _dec\n\n    def admin_register(self, model, admin_site):\n        \"\"\"Register an model with admin to the test case, test client and URL reversing code.\"\"\"\n        self.admin_site.register(model, admin_site)\n\n        # Make sure the URLs are reachable by reverse()\n        clear_url_caches()\n        set_urlconf(tuple([path(\"tmp-admin/\", self.admin_site.urls)]))\n\n    def get_admin_instance(self, model):\n        try:\n            return self.admin_site._registry[model]\n        except KeyError:\n            raise ValueError(f\"Model not registered with admin: {model}\")\n\n    @classmethod\n    def tearDownClass(cls):\n        super().tearDownClass()\n        clear_url_caches()\n        set_urlconf(None)\n\n    def get_add_url(self, model):\n        admin_instance = self.get_admin_instance(model)\n        return reverse(admin_urlname(admin_instance.opts, \"add\"))\n\n    def get_changelist_url(self, model):\n        admin_instance = self.get_admin_instance(model)\n        return reverse(admin_urlname(admin_instance.opts, \"changelist\"))\n\n    def get_change_url(self, model, object_id):\n        admin_instance = self.get_admin_instance(model)\n        return reverse(admin_urlname(admin_instance.opts, \"change\"), args=(object_id,))\n\n    def get_history_url(self, model, object_id):\n        admin_instance = self.get_admin_instance(model)\n        return reverse(admin_urlname(admin_instance.opts, \"history\"), args=(object_id,))\n\n    def get_delete_url(self, model, object_id):\n        admin_instance = self.get_admin_instance(model)\n        return reverse(admin_urlname(admin_instance.opts, \"delete\"), args=(object_id,))\n\n    def admin_get_add(self, model, qs=\"\"):\n        \"\"\"\n        Make a direct \"add\" call to the admin page, circumvening login checks.\n        \"\"\"\n        admin_instance = self.get_admin_instance(model)\n        request = self.create_admin_request(\"get\", self.get_add_url(model) + qs)\n        response = admin_instance.add_view(request)\n        assert response.status_code == 200\n        return response\n\n    def admin_post_add(self, model, formdata, qs=\"\"):\n        \"\"\"\n        Make a direct \"add\" call to the admin page, circumvening login checks.\n        \"\"\"\n        admin_instance = self.get_admin_instance(model)\n        request = self.create_admin_request(\"post\", self.get_add_url(model) + qs, data=formdata)\n        response = admin_instance.add_view(request)\n        self.assertFormSuccess(request.path, response)\n        return response\n\n    def admin_get_changelist(self, model):\n        \"\"\"\n        Make a direct \"add\" call to the admin page, circumvening login checks.\n        \"\"\"\n        admin_instance = self.get_admin_instance(model)\n        request = self.create_admin_request(\"get\", self.get_changelist_url(model))\n        response = admin_instance.changelist_view(request)\n        assert response.status_code == 200\n        return response\n\n    def admin_get_change(self, model, object_id, query=None, **extra):\n        \"\"\"\n        Perform a GET request on the admin page\n        \"\"\"\n        admin_instance = self.get_admin_instance(model)\n        request = self.create_admin_request(\n            \"get\", self.get_change_url(model, object_id), data=query, **extra\n        )\n        response = admin_instance.change_view(request, str(object_id))\n        assert response.status_code == 200\n        return response\n\n    def admin_post_change(self, model, object_id, formdata, **extra):\n        \"\"\"\n        Make a direct \"add\" call to the admin page, circumvening login checks.\n        \"\"\"\n        admin_instance = self.get_admin_instance(model)\n        request = self.create_admin_request(\n            \"post\", self.get_change_url(model, object_id), data=formdata, **extra\n        )\n        response = admin_instance.change_view(request, str(object_id))\n        self.assertFormSuccess(request.path, response)\n        return response\n\n    def admin_get_history(self, model, object_id, query=None, **extra):\n        \"\"\"\n        Perform a GET request on the admin page\n        \"\"\"\n        admin_instance = self.get_admin_instance(model)\n        request = self.create_admin_request(\n            \"get\", self.get_history_url(model, object_id), data=query, **extra\n        )\n        response = admin_instance.history_view(request, str(object_id))\n        assert response.status_code == 200\n        return response\n\n    def admin_get_delete(self, model, object_id, query=None, **extra):\n        \"\"\"\n        Perform a GET request on the admin delete page\n        \"\"\"\n        admin_instance = self.get_admin_instance(model)\n        request = self.create_admin_request(\n            \"get\", self.get_delete_url(model, object_id), data=query, **extra\n        )\n        response = admin_instance.delete_view(request, str(object_id))\n        assert response.status_code == 200\n        return response\n\n    def admin_post_delete(self, model, object_id, **extra):\n        \"\"\"\n        Make a direct \"add\" call to the admin page, circumvening login checks.\n        \"\"\"\n        if not extra:\n            extra = {\"data\": {\"post\": \"yes\"}}\n\n        admin_instance = self.get_admin_instance(model)\n        request = self.create_admin_request(\"post\", self.get_delete_url(model, object_id), **extra)\n        response = admin_instance.delete_view(request, str(object_id))\n        assert response.status_code == 302, f\"Form errors in calling {request.path}\"\n        return response\n\n    def create_admin_request(self, method, url, data=None, **extra):\n        \"\"\"\n        Construct an Request instance for the admin view.\n        \"\"\"\n        factory_method = getattr(RequestFactory(), method)\n\n        if data is not None:\n            if method != \"get\":\n                data[\"csrfmiddlewaretoken\"] = \"foo\"\n            dummy_request = factory_method(url, data=data)\n            dummy_request.user = self.admin_user\n\n            # Add the management form fields if needed.\n            # base_data = self._get_management_form_data(dummy_request)\n            # base_data.update(data)\n            # data = base_data\n\n        request = factory_method(url, data=data, **extra)\n        request.COOKIES[settings.CSRF_COOKIE_NAME] = \"foo\"\n        request.csrf_processing_done = True\n\n        # Add properties which middleware would typically do\n        request.session = {}\n        request.user = self.admin_user\n        MessageMiddleware(lambda r: HttpResponse(\"OK?\")).process_request(request)\n        return request\n\n    def assertFormSuccess(self, request_url, response):\n        \"\"\"\n        Assert that the response was a redirect, not a form error.\n        \"\"\"\n        assert response.status_code in [200, 302]\n        if response.status_code != 302:\n            context_data = response.context_data\n            if \"errors\" in context_data:\n                errors = response.context_data[\"errors\"]\n            elif \"form\" in context_data:\n                errors = context_data[\"form\"].errors\n            else:\n                raise KeyError(\"Unknown field for errors in the TemplateResponse!\")\n\n            assert response.status_code == 302, (\n                f\"Form errors in calling {request_url}:\\n{errors.as_text()}\"\n            )\n        assert \"/login/?next=\" not in response[\"Location\"], (\n            f\"Received login response for {request_url}\"\n        )\n"
  },
  {
    "path": "src/polymorphic/tests/conftest.py",
    "content": "from __future__ import annotations\n\nimport pathlib\nimport pytest\n\nINTEGRATION_DIR = pathlib.Path(__file__).resolve().parent / \"examples\" / \"integrations\"\n\n\ndef pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None:\n    for item in items:\n        # item.path is pathlib.Path on modern pytest; fall back for older\n        p = pathlib.Path(str(getattr(item, \"path\", item.fspath))).resolve()\n        if INTEGRATION_DIR in p.parents:\n            item.add_marker(pytest.mark.integration)\n"
  },
  {
    "path": "src/polymorphic/tests/debug.py",
    "content": "from .settings import *\n\nDEBUG = True\n"
  },
  {
    "path": "src/polymorphic/tests/deletion/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/deletion/migrations/0001_initial.py",
    "content": "# Generated by Django 4.2 on 2026-02-05 14:47\n\nfrom decimal import Decimal\nfrom django.conf import settings\nfrom django.db import migrations, models\nimport django.db.models.deletion\nimport polymorphic.tests.deletion.models\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('contenttypes', '0002_remove_content_type_name'),\n        migrations.swappable_dependency(settings.AUTH_USER_MODEL),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='A_160',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='A_160Plain',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='A_274',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='A_540',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n                ('self_referential', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='deletion.a_540')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Animal',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(max_length=100)),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Answer',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='B_160',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('a', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='deletion.a_160')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='B_160Plain',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('a', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='deletion.a_160plain')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Base',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Beneficiary',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('firstname', models.CharField(max_length=100)),\n                ('lastname', models.CharField(max_length=100)),\n            ],\n        ),\n        migrations.CreateModel(\n            name='CustomModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Farm',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Normal2',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Normal3',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Normal4',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Normal5',\n            fields=[\n                ('n_pk', models.AutoField(primary_key=True, serialize=False)),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Order',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('title', models.CharField(max_length=200, verbose_name='Title')),\n            ],\n            options={\n                'ordering': ('title',),\n            },\n        ),\n        migrations.CreateModel(\n            name='Payment',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('amount', models.DecimalField(blank=True, decimal_places=2, default=Decimal('0'), max_digits=10)),\n                ('index', models.PositiveIntegerField(default=0)),\n                ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='deletion.order')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'ordering': ('index',),\n            },\n        ),\n        migrations.CreateModel(\n            name='PlainA',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Poll',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Poly1',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Poly2',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('normal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='polies', to='deletion.normal2')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Poly4',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('normals', models.ManyToManyField(blank=True, related_name='polies', to='deletion.normal4')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Poly4_1',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='PolyDevice',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(max_length=64)),\n            ],\n        ),\n        migrations.CreateModel(\n            name='PolyInterface',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(max_length=64)),\n                ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='deletion.polydevice')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Standalone',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='A1',\n            fields=[\n                ('poly1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.poly1')),\n                ('some_data', models.CharField(blank=True, default='', max_length=100)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.poly1',),\n        ),\n        migrations.CreateModel(\n            name='A2',\n            fields=[\n                ('poly2_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.poly2')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.poly2',),\n        ),\n        migrations.CreateModel(\n            name='A4',\n            fields=[\n                ('poly4_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.poly4')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.poly4',),\n        ),\n        migrations.CreateModel(\n            name='A4_1',\n            fields=[\n                ('poly4_1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.poly4_1')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.poly4_1',),\n        ),\n        migrations.CreateModel(\n            name='B1',\n            fields=[\n                ('poly1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.poly1')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.poly1',),\n        ),\n        migrations.CreateModel(\n            name='B1_160',\n            fields=[\n                ('b_160_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.b_160')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.b_160',),\n        ),\n        migrations.CreateModel(\n            name='B1_160Plain',\n            fields=[\n                ('b_160plain_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.b_160plain')),\n            ],\n            bases=('deletion.b_160plain',),\n        ),\n        migrations.CreateModel(\n            name='B2',\n            fields=[\n                ('poly2_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.poly2')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.poly2',),\n        ),\n        migrations.CreateModel(\n            name='B2_160',\n            fields=[\n                ('b_160_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.b_160')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.b_160',),\n        ),\n        migrations.CreateModel(\n            name='B2_160Plain',\n            fields=[\n                ('b_160plain_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.b_160plain')),\n            ],\n            bases=('deletion.b_160plain',),\n        ),\n        migrations.CreateModel(\n            name='B4',\n            fields=[\n                ('poly4_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.poly4')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.poly4',),\n        ),\n        migrations.CreateModel(\n            name='B4_1',\n            fields=[\n                ('poly4_1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.poly4_1')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.poly4_1',),\n        ),\n        migrations.CreateModel(\n            name='B_274',\n            fields=[\n                ('a_274_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.a_274')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.a_274',),\n        ),\n        migrations.CreateModel(\n            name='B_540',\n            fields=[\n                ('a_540_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.a_540')),\n                ('name', models.CharField(max_length=256)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.a_540',),\n        ),\n        migrations.CreateModel(\n            name='C1',\n            fields=[\n                ('poly1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.poly1')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.poly1',),\n        ),\n        migrations.CreateModel(\n            name='C2',\n            fields=[\n                ('poly2_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.poly2')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.poly2',),\n        ),\n        migrations.CreateModel(\n            name='C4',\n            fields=[\n                ('poly4_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.poly4')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.poly4',),\n        ),\n        migrations.CreateModel(\n            name='C4_1',\n            fields=[\n                ('poly4_1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.poly4_1')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.poly4_1',),\n        ),\n        migrations.CreateModel(\n            name='Cat',\n            fields=[\n                ('animal_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.animal')),\n                ('cat_param', models.CharField(max_length=100)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.animal',),\n        ),\n        migrations.CreateModel(\n            name='Child',\n            fields=[\n                ('base_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.base')),\n            ],\n            bases=('deletion.base',),\n        ),\n        migrations.CreateModel(\n            name='CreditCardPayment',\n            fields=[\n                ('payment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.payment')),\n                ('card_type', models.CharField(max_length=32)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.payment',),\n        ),\n        migrations.CreateModel(\n            name='D_274',\n            fields=[\n                ('a_274_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.a_274')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.a_274',),\n        ),\n        migrations.CreateModel(\n            name='DatasetFolder',\n            fields=[\n                ('custommodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.custommodel')),\n            ],\n            bases=('deletion.custommodel',),\n        ),\n        migrations.CreateModel(\n            name='Dog',\n            fields=[\n                ('animal_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.animal')),\n                ('dog_param', models.CharField(max_length=100)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.animal',),\n        ),\n        migrations.CreateModel(\n            name='OriginalFile',\n            fields=[\n                ('custommodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.custommodel')),\n                ('created_at', models.DateTimeField(auto_now_add=True)),\n                ('updated_at', models.DateTimeField(auto_now=True)),\n                ('content_type', models.CharField(max_length=100)),\n                ('size', models.PositiveIntegerField()),\n                ('dataset_folder', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='deletion.datasetfolder')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.custommodel', models.Model),\n        ),\n        migrations.CreateModel(\n            name='PlainB1',\n            fields=[\n                ('plaina_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.plaina')),\n                ('standalones', models.ManyToManyField(to='deletion.standalone')),\n            ],\n            bases=('deletion.plaina',),\n        ),\n        migrations.CreateModel(\n            name='Poly3',\n            fields=[\n                ('normal3_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.normal3')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.normal3', models.Model),\n        ),\n        migrations.CreateModel(\n            name='Poly5',\n            fields=[\n                ('normal5_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='deletion.normal5')),\n                ('p_pk', models.AutoField(primary_key=True, serialize=False)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.normal5', models.Model),\n        ),\n        migrations.CreateModel(\n            name='PolyEthernetInterface',\n            fields=[\n                ('polyinterface_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.polyinterface')),\n                ('ethernety_stuff', models.CharField(max_length=64)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.polyinterface',),\n        ),\n        migrations.CreateModel(\n            name='PolyFCInterface',\n            fields=[\n                ('polyinterface_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.polyinterface')),\n                ('fc_stuff', models.CharField(max_length=64)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.polyinterface',),\n        ),\n        migrations.CreateModel(\n            name='PolyWirelessInterface',\n            fields=[\n                ('polyinterface_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.polyinterface')),\n                ('wirelessy_stuff', models.CharField(max_length=64)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.polyinterface',),\n        ),\n        migrations.CreateModel(\n            name='TextAnswer',\n            fields=[\n                ('answer_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.answer')),\n                ('answer', models.CharField(blank=True, default='', max_length=500)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.answer',),\n        ),\n        migrations.CreateModel(\n            name='YesNoAnswer',\n            fields=[\n                ('answer_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.answer')),\n                ('answer', models.BooleanField(default=False, verbose_name='answer')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.answer',),\n        ),\n        migrations.CreateModel(\n            name='Question',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('poll', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='deletion.poll')),\n            ],\n        ),\n        migrations.AddField(\n            model_name='plaina',\n            name='standalone_parent',\n            field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='plainas', to='deletion.standalone'),\n        ),\n        migrations.CreateModel(\n            name='Normal4_1',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polies', models.ManyToManyField(blank=True, related_name='normals', to='deletion.poly4_1')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Normal1',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('poly', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='deletion.poly1')),\n            ],\n        ),\n        migrations.AddField(\n            model_name='answer',\n            name='question',\n            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='deletion.question'),\n        ),\n        migrations.AddField(\n            model_name='animal',\n            name='farm',\n            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='animals', to='deletion.farm'),\n        ),\n        migrations.AddField(\n            model_name='animal',\n            name='polymorphic_ctype',\n            field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'),\n        ),\n        migrations.CreateModel(\n            name='A3',\n            fields=[\n                ('poly3_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.poly3')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.poly3',),\n        ),\n        migrations.CreateModel(\n            name='A5',\n            fields=[\n                ('poly5_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='deletion.poly5')),\n                ('a_pk', models.AutoField(primary_key=True, serialize=False)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.poly5',),\n        ),\n        migrations.CreateModel(\n            name='B3',\n            fields=[\n                ('poly3_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.poly3')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.poly3',),\n        ),\n        migrations.CreateModel(\n            name='B5',\n            fields=[\n                ('poly5_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='deletion.poly5')),\n                ('b_pk', models.AutoField(primary_key=True, serialize=False)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.poly5',),\n        ),\n        migrations.CreateModel(\n            name='C_274',\n            fields=[\n                ('b_274_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.b_274')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.b_274',),\n        ),\n        migrations.CreateModel(\n            name='DatasetRelation',\n            fields=[\n                ('originalfile_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.originalfile')),\n                ('file', models.FileField(max_length=500, upload_to=polymorphic.tests.deletion.models.project_directory_path)),\n                ('original_file_name', models.CharField(max_length=100)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.originalfile',),\n        ),\n        migrations.CreateModel(\n            name='E_274',\n            fields=[\n                ('d_274_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.d_274')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.d_274',),\n        ),\n        migrations.CreateModel(\n            name='GrandChild',\n            fields=[\n                ('child_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.child')),\n            ],\n            bases=('deletion.child',),\n        ),\n        migrations.CreateModel(\n            name='OriginalImage',\n            fields=[\n                ('originalfile_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.originalfile')),\n                ('original_file_name', models.CharField(max_length=100)),\n                ('file', models.FileField(max_length=500, upload_to=polymorphic.tests.deletion.models.project_directory_path)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.originalfile',),\n        ),\n        migrations.CreateModel(\n            name='PolyFixedInterface',\n            fields=[\n                ('polyethernetinterface_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.polyethernetinterface')),\n                ('fixed_stuff', models.CharField(max_length=64)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.polyethernetinterface',),\n        ),\n        migrations.CreateModel(\n            name='PolyModularInterface',\n            fields=[\n                ('polyethernetinterface_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.polyethernetinterface')),\n                ('modular_stuff', models.CharField(max_length=64)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.polyethernetinterface',),\n        ),\n        migrations.CreateModel(\n            name='SepaPayment',\n            fields=[\n                ('payment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.payment')),\n                ('iban', models.CharField(max_length=34)),\n                ('bic', models.CharField(max_length=11)),\n                ('beneficiaries', models.ManyToManyField(blank=True, related_name='sepa', to='deletion.beneficiary')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.payment',),\n        ),\n        migrations.CreateModel(\n            name='RelatedToChild',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('child', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='relatives', to='deletion.child')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Project',\n            fields=[\n                ('custommodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.custommodel')),\n                ('name', models.CharField(max_length=30)),\n                ('created_at', models.DateTimeField(auto_now_add=True)),\n                ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),\n            ],\n            bases=('deletion.custommodel',),\n        ),\n        migrations.CreateModel(\n            name='PlainB2',\n            fields=[\n                ('plaina_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.plaina')),\n                ('standalone', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='plainb2s', to='deletion.standalone')),\n            ],\n            bases=('deletion.plaina',),\n        ),\n        migrations.AddField(\n            model_name='datasetfolder',\n            name='prjct',\n            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='deletion.project'),\n        ),\n        migrations.CreateModel(\n            name='C_160Plain',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('b', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='deletion.b1_160plain')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='C_160',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('b', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='deletion.b1_160')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='RelatedToGrandChild',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('grand_child', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='grand_relatives', to='deletion.grandchild')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='PlainC1',\n            fields=[\n                ('plainb1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.plainb1')),\n                ('standalone', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='plainc1s', to='deletion.standalone')),\n            ],\n            bases=('deletion.plainb1',),\n        ),\n        migrations.CreateModel(\n            name='OriginalDataset',\n            fields=[\n                ('originalfile_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='deletion.originalfile')),\n                ('file', models.FileField(max_length=500, upload_to=polymorphic.tests.deletion.models.project_directory_path)),\n                ('original_file_name', models.CharField(blank=True, max_length=100, null=True)),\n                ('table_name', models.CharField(blank=True, max_length=100, null=True)),\n                ('rows_number', models.PositiveIntegerField()),\n                ('dataset_metadata', models.JSONField()),\n                ('dataset_relation', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='deletion.datasetrelation')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('deletion.originalfile',),\n        ),\n    ]\n"
  },
  {
    "path": "src/polymorphic/tests/deletion/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/deletion/models.py",
    "content": "from django.db import models\nfrom django.conf import settings\nfrom polymorphic.models import PolymorphicModel\nfrom polymorphic.deletion import PolymorphicGuard\nfrom decimal import Decimal\n\n\ndef project_directory_path(instance, filename):\n    # just to satisfy upload_to; keep it deterministic for tests\n    return f\"p{instance.dataset_folder.prjct_id}/{filename}\"\n\n\nCASCADE = models.CASCADE\n\n\nclass Standalone(models.Model):\n    pass\n\n\nclass PlainA(models.Model):\n    standalone_parent = models.ForeignKey(\n        Standalone, on_delete=CASCADE, null=True, default=None, related_name=\"plainas\"\n    )\n\n\nclass PlainB1(PlainA):\n    standalones = models.ManyToManyField(Standalone)\n\n\nclass PlainB2(PlainA):\n    standalone = models.ForeignKey(\n        Standalone, on_delete=CASCADE, null=True, default=None, related_name=\"plainb2s\"\n    )\n\n\nclass PlainC1(PlainB1):\n    standalone = models.ForeignKey(\n        Standalone, on_delete=CASCADE, null=True, default=None, related_name=\"plainc1s\"\n    )\n\n\nclass RelatedToChild(models.Model):\n    child = models.ForeignKey(\n        \"Child\", on_delete=CASCADE, null=True, default=None, related_name=\"relatives\"\n    )\n\n\nclass Base(models.Model):\n    pass\n\n\nclass Child(Base):\n    pass\n\n\nclass GrandChild(Child):\n    pass\n\n\nclass RelatedToGrandChild(models.Model):\n    grand_child = models.ForeignKey(\n        GrandChild, on_delete=CASCADE, null=True, default=None, related_name=\"grand_relatives\"\n    )\n\n\n###########################################################\n\n\"\"\"\nScenario 1\n\n        <-- cascade --\nNormal1 ----- FK ---->  Poly1\n                       /  |  \\\n                     A1   B1   C1\n\"\"\"\n\n\nclass Normal1(models.Model):\n    poly = models.ForeignKey(\"Poly1\", on_delete=models.CASCADE)  # <-- this is fine\n\n\nclass Poly1(PolymorphicModel):\n    pass\n\n\nclass A1(Poly1):\n    some_data = models.CharField(max_length=100, blank=True, default=\"\")\n\n\nclass B1(Poly1):\n    pass\n\n\nclass C1(Poly1):\n    pass\n\n\n\"\"\"\nScenario 2\n\n        -- cascade -->\nNormal2 <----- FK ----  Poly2\n                       /  |  \\\n                     A2   B2   C2\n\"\"\"\n\n\nclass Normal2(models.Model):\n    pass\n\n\nclass Poly2(PolymorphicModel):\n    normal = models.ForeignKey(Normal2, on_delete=CASCADE, related_name=\"polies\")\n\n\nclass A2(Poly2):\n    pass\n\n\nclass B2(Poly2):\n    pass\n\n\nclass C2(Poly2):\n    pass\n\n\n\"\"\"\nScenario 3\n\nNormal3\n  |\n Poly3\n  | \\\n  A3 B3\n\"\"\"\n\n\nclass Normal3(models.Model):\n    pass\n\n\nclass Poly3(PolymorphicModel, Normal3):\n    pass\n\n\nclass A3(Poly3):\n    pass\n\n\nclass B3(Poly3):\n    pass\n\n\n\"\"\"\nScenario 4\n\n        <--- cascade --->\nNormal4 <----- M2M ----->  Poly4\n                          /  |  \\\n                        A4   B4   C4\n\"\"\"\n\n\nclass Normal4(models.Model):\n    pass\n\n\nclass Poly4(PolymorphicModel):\n    normals = models.ManyToManyField(Normal4, related_name=\"polies\", blank=True)\n\n\nclass A4(Poly4):\n    pass\n\n\nclass B4(Poly4):\n    pass\n\n\nclass C4(Poly4):\n    pass\n\n\nclass Normal4_1(models.Model):\n    polies = models.ManyToManyField(\"Poly4_1\", related_name=\"normals\", blank=True)\n\n\nclass Poly4_1(PolymorphicModel):\n    pass\n\n\nclass A4_1(Poly4_1):\n    pass\n\n\nclass B4_1(Poly4_1):\n    pass\n\n\nclass C4_1(Poly4_1):\n    pass\n\n\n\"\"\"\nScenario 5 - scenario3 with custom/different PKs\n\nNormal3\n  |\n Poly3\n  | \\\n  A3 B3\n\"\"\"\n\n\nclass Normal5(models.Model):\n    n_pk = models.AutoField(primary_key=True)\n\n\nclass Poly5(PolymorphicModel, Normal5):\n    p_pk = models.AutoField(primary_key=True)\n\n\nclass A5(Poly5):\n    a_pk = models.AutoField(primary_key=True)\n\n\nclass B5(Poly5):\n    b_pk = models.AutoField(primary_key=True)\n\n\n########################################################################################\n# There were 15 years of deletion bug reports - many were duplicates but many also\n# included example models. We copy all of these provided tests in here not being too\n# concerned about redundancy - we just want to make sure we don't regress on any of\n# them. Each block is tagged with the root issue. In some cases we mirror the setup\n# with a plain django model parallel - mostly for debug/comparison purposes\n\n###########################################################\n# https://github.com/jazzband/django-polymorphic/issues/160\n\n\nclass A_160(models.Model):\n    pass\n\n\nclass B_160(PolymorphicModel):\n    a = models.ForeignKey(A_160, on_delete=CASCADE)\n\n\nclass B1_160(B_160):\n    pass\n\n\nclass B2_160(B_160):\n    pass\n\n\nclass C_160(models.Model):\n    b = models.ForeignKey(B1_160, on_delete=CASCADE)\n\n\n# Plain\nclass A_160Plain(models.Model):\n    pass\n\n\nclass B_160Plain(models.Model):\n    a = models.ForeignKey(A_160Plain, on_delete=CASCADE)\n\n\nclass B1_160Plain(B_160Plain):\n    pass\n\n\nclass B2_160Plain(B_160Plain):\n    pass\n\n\nclass C_160Plain(models.Model):\n    # test that guard misapplication is fine\n    b = models.ForeignKey(B1_160Plain, on_delete=PolymorphicGuard(CASCADE))\n\n\n###########################################################\n###########################################################\n# https://github.com/jazzband/django-polymorphic/issues/229\n\n\nclass Farm(models.Model):\n    pass\n\n\nclass Animal(PolymorphicModel):\n    farm = models.ForeignKey(\n        \"Farm\", on_delete=PolymorphicGuard(models.CASCADE), related_name=\"animals\"\n    )\n    name = models.CharField(max_length=100)\n\n\nclass Dog(Animal):\n    dog_param = models.CharField(max_length=100)\n\n\nclass Cat(Animal):\n    cat_param = models.CharField(max_length=100)\n\n\n###########################################################\n# https://github.com/jazzband/django-polymorphic/issues/274\n\n\nclass A_274(PolymorphicModel):\n    pass\n\n\nclass B_274(A_274):\n    pass\n\n\nclass D_274(A_274):\n    pass\n\n\nclass E_274(D_274):\n    pass\n\n\nclass C_274(B_274):\n    pass\n\n\n###########################################################\n###########################################################\n# https://github.com/jazzband/django-polymorphic/issues/357\n\n\nclass Order(models.Model):\n    title = models.CharField(\"Title\", max_length=200)\n\n    class Meta:\n        ordering = (\"title\",)\n\n    def __str__(self):\n        return self.title\n\n\nclass Payment(PolymorphicModel):\n    order = models.ForeignKey(Order, on_delete=CASCADE)\n    amount = models.DecimalField(default=Decimal(0.0), blank=True, max_digits=10, decimal_places=2)\n    index = models.PositiveIntegerField(default=0, blank=False)\n\n    class Meta:\n        ordering = (\"index\",)\n\n\nclass CreditCardPayment(Payment):\n    card_type = models.CharField(max_length=32)\n\n\nclass Beneficiary(models.Model):\n    firstname = models.CharField(max_length=100)\n    lastname = models.CharField(max_length=100)\n\n\nclass SepaPayment(Payment):\n    iban = models.CharField(max_length=34)\n    bic = models.CharField(max_length=11)\n    beneficiaries = models.ManyToManyField(Beneficiary, \"sepa\", blank=True)\n\n\n###########################################################\n###########################################################\n# https://github.com/jazzband/django-polymorphic/issues/481\n\n# no example given - how to?\n\n\n###########################################################\n###########################################################\n# https://github.com/jazzband/django-polymorphic/issues/540\n\n\nclass A_540(PolymorphicModel):\n    self_referential = models.ForeignKey(\"self\", null=True, blank=True, on_delete=CASCADE)\n\n\nclass B_540(A_540):\n    name = models.CharField(max_length=256)\n\n\n###########################################################\n###########################################################\n# https://github.com/jazzband/django-polymorphic/issues/547\n\n\nclass CustomModel(models.Model):\n    pass\n\n\nclass Project(CustomModel):\n    name = models.CharField(max_length=30)\n    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=CASCADE)\n    created_at = models.DateTimeField(auto_now_add=True)\n\n\nclass DatasetFolder(CustomModel):\n    prjct = models.ForeignKey(Project, on_delete=CASCADE)\n\n\nclass OriginalFile(PolymorphicModel, CustomModel):\n    dataset_folder = models.ForeignKey(DatasetFolder, on_delete=CASCADE)\n    created_at = models.DateTimeField(auto_now_add=True)\n    updated_at = models.DateTimeField(auto_now=True)\n    content_type = models.CharField(max_length=100)\n    size = models.PositiveIntegerField()\n\n\nclass DatasetRelation(OriginalFile):\n    file = models.FileField(max_length=500, upload_to=project_directory_path)\n    original_file_name = models.CharField(max_length=100)\n\n\nclass OriginalDataset(OriginalFile):\n    dataset_relation = models.ForeignKey(\n        DatasetRelation, on_delete=models.SET_NULL, blank=True, null=True\n    )\n    file = models.FileField(max_length=500, upload_to=project_directory_path)\n    original_file_name = models.CharField(max_length=100, null=True, blank=True)\n    table_name = models.CharField(max_length=100, null=True, blank=True)\n    rows_number = models.PositiveIntegerField()\n    dataset_metadata = models.JSONField()\n\n\nclass OriginalImage(OriginalFile):\n    original_file_name = models.CharField(max_length=100)\n    file = models.FileField(max_length=500, upload_to=project_directory_path)\n\n\n###########################################################\n###########################################################\n# https://github.com/jazzband/django-polymorphic/issues/608\n\n\nclass PolyDevice(models.Model):\n    name = models.CharField(max_length=64)\n\n\nclass PolyInterface(PolymorphicModel):\n    name = models.CharField(max_length=64)\n    device = models.ForeignKey(to=PolyDevice, on_delete=CASCADE)\n\n\nclass PolyEthernetInterface(PolyInterface):\n    ethernety_stuff = models.CharField(max_length=64)\n\n\nclass PolyModularInterface(PolyEthernetInterface):\n    modular_stuff = models.CharField(max_length=64)\n\n\nclass PolyFixedInterface(PolyEthernetInterface):\n    fixed_stuff = models.CharField(max_length=64)\n\n\nclass PolyWirelessInterface(PolyInterface):\n    wirelessy_stuff = models.CharField(max_length=64)\n\n\nclass PolyFCInterface(PolyInterface):\n    fc_stuff = models.CharField(max_length=64)\n\n\nclass Poll(models.Model):\n    pass\n\n\nclass Question(models.Model):\n    poll = models.ForeignKey(to=Poll, on_delete=CASCADE)\n\n\nclass Answer(PolymorphicModel):\n    question = models.ForeignKey(to=Question, on_delete=CASCADE)\n\n\nclass TextAnswer(Answer):\n    answer = models.CharField(default=\"\", blank=True, max_length=500)\n\n\nclass YesNoAnswer(Answer):\n    answer = models.BooleanField(\"answer\", default=False)\n"
  },
  {
    "path": "src/polymorphic/tests/deletion/test_deletion.py",
    "content": "from django.test import TestCase\nimport shutil\nimport tempfile\nfrom django.contrib.auth import get_user_model\nfrom django.core.files.base import ContentFile\nfrom django.test.utils import CaptureQueriesContext\nfrom django.test import override_settings\nfrom django.db import connection\n\n\n@override_settings()\nclass TestDeletion(TestCase):\n    def setUp(self):\n        super().setUp()\n        self._media_root = tempfile.mkdtemp(prefix=\"test-media-\")\n        self._media_override = override_settings(MEDIA_ROOT=self._media_root)\n        self._media_override.enable()\n\n    def tearDown(self):\n        self._media_override.disable()\n        shutil.rmtree(self._media_root, ignore_errors=True)\n        super().tearDown()\n\n    def test_deletion_bug_160(self):\n        \"\"\"https://github.com/jazzband/django-polymorphic/issues/160\"\"\"\n        from .models import A_160, B_160, B1_160, B2_160, C_160\n        from .models import A_160Plain, B_160Plain, C_160Plain, B1_160Plain, B2_160Plain\n\n        a = A_160Plain.objects.create()\n        a2 = A_160Plain.objects.create()\n        b1 = B1_160Plain.objects.create(a=a)\n        b2 = B2_160Plain.objects.create(a=a)\n        b2_2 = B2_160Plain.objects.create(a=a2)\n        c = C_160Plain.objects.create(b=b1)\n        a.delete()\n        assert [a2] == list(A_160Plain.objects.all())\n        assert B_160Plain.objects.count() == 1\n        assert B1_160Plain.objects.count() == 0\n        assert [b2_2] == list(B2_160Plain.objects.all())\n        assert C_160Plain.objects.count() == 0\n\n        a = A_160.objects.create()\n        a2 = A_160.objects.create()\n        b1 = B1_160.objects.create(a=a)\n        b2 = B2_160.objects.create(a=a)\n        b2_2 = B2_160.objects.create(a=a2)\n        c = C_160.objects.create(b=b1)\n        a.delete()\n        assert [a2] == list(A_160.objects.all())\n        assert B_160.objects.count() == 1\n        assert B1_160.objects.count() == 0\n        assert [b2_2] == list(B2_160.objects.all())\n        assert C_160.objects.count() == 0\n\n    def test_deletion_bug_229(self):\n        \"\"\"\n        https://github.com/jazzband/django-polymorphic/issues/229\n        \"\"\"\n        from .models import Farm, Animal, Dog, Cat\n\n        farm = Farm.objects.create()\n        Dog.objects.create(farm=farm, name=\"Rex\", dog_param=\"kibble\")\n        Cat.objects.create(farm=farm, name=\"Misty\", cat_param=\"whiskers\")\n        farm.delete()\n        assert Animal.objects.count() == 0\n        assert Dog.objects.count() == 0\n        assert Cat.objects.count() == 0\n        assert Farm.objects.count() == 0\n\n        farm = Farm.objects.create()\n        Dog.objects.create(farm=farm, name=\"Rex\", dog_param=\"kibble\")\n        Cat.objects.create(farm=farm, name=\"Misty\", cat_param=\"whiskers\")\n        farm2 = Farm.objects.create()\n        hugo = Cat.objects.create(farm=farm2, name=\"Hugo\", cat_param=\"10\")\n        marlo = Cat.objects.create(farm=farm2, name=\"Marlo\", cat_param=\"14\")\n        assert Animal.objects.count() == 4\n        farm.delete()\n        assert Animal.objects.count() == 2\n        assert hugo in Cat.objects.all()\n        assert marlo in Cat.objects.all()\n        assert hugo in Animal.objects.all()\n        assert marlo in Animal.objects.all()\n        assert hugo in farm2.animals.all()\n        assert marlo in farm2.animals.all()\n\n        Animal.objects.all().delete()\n        assert Animal.objects.count() == 0\n        assert Dog.objects.count() == 0\n        assert Cat.objects.count() == 0\n        assert Farm.objects.count() == 1\n        assert farm2 in Farm.objects.all()\n        assert farm2.animals.count() == 0\n\n    def test_deletion_bug_274(self):\n        \"\"\"\n        https://github.com/jazzband/django-polymorphic/issues/274\n        \"\"\"\n        from .models import A_274, B_274, C_274, D_274, E_274\n\n        B_274.objects.create()\n        D_274.objects.create()\n        E_274.objects.create()\n        A_274.objects.all().delete()\n\n        assert A_274.objects.count() == 0\n        assert B_274.objects.count() == 0\n        assert D_274.objects.count() == 0\n        assert E_274.objects.count() == 0\n        assert C_274.objects.count() == 0\n\n    def test_deletion_bug_357(self):\n        \"\"\"\n        https://github.com/jazzband/django-polymorphic/issues/357\n        \"\"\"\n        from .models import Order, Payment, CreditCardPayment, SepaPayment, Beneficiary\n\n        order1 = Order.objects.create(title=\"Order 1\")\n        payment1 = SepaPayment.objects.create(\n            order=order1,\n            amount=100.00,\n            iban=\"DE89370400440532013000\",\n            bic=\"COBADEFFXXX\",\n        )\n        CreditCardPayment.objects.create(\n            order=order1,\n            amount=100.00,\n            card_type=\"VISA\",\n        )\n        bk = Beneficiary.objects.create(firstname=\"Brian\", lastname=\"Kohan\")\n        ea = Beneficiary.objects.create(firstname=\"Edward\", lastname=\"Abbey\")\n        payment1.beneficiaries.add(bk, ea)\n\n        Order.objects.all().delete()\n\n        assert Order.objects.count() == 0\n        assert Payment.objects.count() == 0\n        assert set(Beneficiary.objects.all()) == {bk, ea}\n\n    def test_deletion_bug_540(self):\n        \"\"\"\n        https://github.com/jazzband/django-polymorphic/issues/540\n        \"\"\"\n        from .models import A_540, B_540\n\n        b = B_540.objects.create(self_referential=None, name=\"b\")\n        a = A_540.objects.create(self_referential=b)\n\n        A_540.objects.all().delete()\n\n        assert A_540.objects.count() == 0\n        assert B_540.objects.count() == 0\n\n    def test_deletion_bug_547(self):\n        \"\"\"\n        https://github.com/jazzband/django-polymorphic/issues/547\n        \"\"\"\n        from .models import Project, DatasetFolder, OriginalFile, DatasetRelation, OriginalDataset\n\n        User = get_user_model()\n        user = User.objects.create_user(username=\"u1\", password=\"x\")\n\n        project = Project.objects.create(name=\"p1\", created_by=user)\n        folder = DatasetFolder.objects.create(prjct=project)\n\n        rel_bytes = b\"id,parent_id\\n1,\\n2,1\\n\"\n        rel_file = ContentFile(rel_bytes, name=\"relations.csv\")\n        relation = DatasetRelation.objects.create(\n            dataset_folder=folder,\n            content_type=\"text/csv\",\n            size=len(rel_bytes),\n            file=rel_file,\n            original_file_name=\"relations.csv\",\n        )\n\n        ds_bytes = b\"id,value\\n1,foo\\n2,bar\\n\"\n        ds_file = ContentFile(ds_bytes, name=\"data.csv\")\n        OriginalDataset.objects.create(\n            dataset_folder=folder,\n            content_type=\"text/csv\",\n            size=len(ds_bytes),\n            dataset_relation=relation,\n            file=ds_file,\n            original_file_name=\"data.csv\",\n            table_name=\"data\",\n            rows_number=2,\n            dataset_metadata={\"columns\": [\"id\", \"value\"]},\n        )\n\n        # This is the operation that (per report) can crash with:\n        # AttributeError: 'NoneType' object has no attribute 'attname'\n        #\n        # If the bug is present, this test will error here.\n        project.delete()\n\n        # If deletion succeeded, everything should be gone.\n        assert Project.objects.count() == 0\n        assert DatasetFolder.objects.count() == 0\n        assert OriginalFile.objects.count() == 0\n        assert DatasetRelation.objects.count() == 0\n        assert OriginalDataset.objects.count() == 0\n\n    def test_deletion_bug_608(self):\n        \"\"\"\n        https://github.com/jazzband/django-polymorphic/issues/608\n        \"\"\"\n        from .models import (\n            PolyDevice,\n            PolyEthernetInterface,\n            PolyFCInterface,\n            PolyFixedInterface,\n            PolyInterface,\n            PolyModularInterface,\n            PolyWirelessInterface,\n            # NotPolyInterface\n        )\n\n        device = PolyDevice.objects.create(name=\"Device 1\")\n        PolyEthernetInterface.objects.create(name=\"Eth0\", device=device, ethernety_stuff=\"stuff\")\n        PolyFCInterface.objects.create(name=\"FC0\", device=device, fc_stuff=\"stuff\")\n        PolyFixedInterface.objects.create(name=\"Fixed0\", device=device, fixed_stuff=\"stuff\")\n        PolyModularInterface.objects.create(name=\"Modular0\", device=device, modular_stuff=\"stuff\")\n        PolyWirelessInterface.objects.create(\n            name=\"Wireless0\", device=device, wirelessy_stuff=\"stuff\"\n        )\n        PolyDevice.objects.all().delete()\n        assert PolyDevice.objects.count() == 0\n\n    def test_deletion_bug_608_2(self):\n        \"\"\"\n        https://github.com/jazzband/django-polymorphic/issues/608\n        \"\"\"\n        from .models import Poll, Question, Answer, TextAnswer, YesNoAnswer\n\n        poll = Poll.objects.create()\n        question = Question.objects.create(poll=poll)\n        answer1 = TextAnswer.objects.create(question=question, answer=\"test\")\n        answer2 = YesNoAnswer.objects.create(question=question, answer=True)\n\n        poll.delete()\n\n        assert Poll.objects.count() == 0\n        assert Question.objects.count() == 0\n        assert Answer.objects.count() == 0\n        assert TextAnswer.objects.count() == 0\n        assert YesNoAnswer.objects.count() == 0\n\n    def test_vanilla_deletion(self):\n        \"\"\"\n        Test Django's vanilla multi table inheritance deletion and signaling.\n\n                                    PlainA *-----> Standalone\n                                 /          \\\n        Standalone *------* PlainB1       PlainB2 *------> Standalone\n                               |\n                            PlainC1 *------> Standalone\n        \"\"\"\n        from .models import (\n            PlainA,\n            PlainB1,\n            PlainC1,\n            PlainB2,\n            Standalone,\n            RelatedToChild,\n            Base,\n            Child,\n            GrandChild,\n            RelatedToGrandChild,\n        )\n\n        print(\"---------------------------\")\n\n        PlainA.objects.create()\n        with CaptureQueriesContext(connection) as ctx:\n            \"\"\"\n            SELECT \"plaina\".\"id\", \"plaina\".\"standalone_parent_id\" FROM \"plaina\"\n            SELECT \"plainb1\".\"plaina_ptr_id\" FROM \"plainb1\" WHERE \"plainb1\".\"plaina_ptr_id\" IN (1)\n            DELETE FROM \"plainb2\" WHERE \"plainb2\".\"plaina_ptr_id\" IN (1)\n            DELETE FROM \"plaina\" WHERE \"plaina\".\"id\" IN (1)\n            \"\"\"\n            PlainA.objects.all().delete()\n\n        # for q in ctx.captured_queries:\n        #     print(q[\"sql\"])\n\n        print(\"---------------------------\")\n        PlainB1.objects.create()\n        with CaptureQueriesContext(connection) as ctx:\n            \"\"\"\n            SELECT \"plaina\".\"id\", \"plaina\".\"standalone_parent_id\", \"plainb1\".\"plaina_ptr_id\" FROM \"plainb1\" INNER JOIN \"plaina\" ON (\"plainb1\".\"plaina_ptr_id\" = \"plaina\".\"id\")\n            SELECT \"plainb1\".\"plaina_ptr_id\", \"plainc1\".\"plainb1_ptr_id\" FROM \"plainc1\" INNER JOIN \"plainb1\" ON (\"plainc1\".\"plainb1_ptr_id\" = \"plainb1\".\"plaina_ptr_id\") WHERE \"plainc1\".\"plainb1_ptr_id\" IN (2)\n            DELETE FROM \"plainb1_standalones\" WHERE \"plainb1_standalones\".\"plainb1_id\" IN (2)\n            DELETE FROM \"plainb1\" WHERE \"plainb1\".\"plaina_ptr_id\" IN (2)\n            DELETE FROM \"plaina\" WHERE \"plaina\".\"id\" IN (2)\n            \"\"\"\n            PlainB1.objects.all().delete()\n\n        # for q in ctx.captured_queries:\n        #     print(q[\"sql\"])\n\n        print(\"---------------------------\")\n        PlainC1.objects.create()\n        with CaptureQueriesContext(connection) as ctx:\n            \"\"\"\n            SELECT \"plaina\".\"id\", \"plaina\".\"standalone_parent_id\", \"plainb1\".\"plaina_ptr_id\", \"plainc1\".\"plainb1_ptr_id\", \"plainc1\".\"standalone_id\" FROM \"plainc1\" INNER JOIN \"plainb1\" ON (\"plainc1\".\"plainb1_ptr_id\" = \"plainb1\".\"plaina_ptr_id\") INNER JOIN \"plaina\" ON (\"plainb1\".\"plaina_ptr_id\" = \"plaina\".\"id\")\n            DELETE FROM \"plainb1_standalones\" WHERE \"plainb1_standalones\".\"plainb1_id\" IN (3)\n            DELETE FROM \"plainc1\" WHERE \"plainc1\".\"plainb1_ptr_id\" IN (3)\n            DELETE FROM \"plainb1\" WHERE \"plainb1\".\"plaina_ptr_id\" IN (3)\n            DELETE FROM \"plaina\" WHERE \"plaina\".\"id\" IN (3)\n            \"\"\"\n            PlainC1.objects.all().delete()\n\n        # for q in ctx.captured_queries:\n        #     print(q[\"sql\"])\n\n        print(\"---------------------------\")\n        PlainC1.objects.create()\n        with CaptureQueriesContext(connection) as ctx:\n            \"\"\"\n            SELECT \"plaina\".\"id\", \"plaina\".\"standalone_parent_id\" FROM \"plaina\"\n            SELECT \"plainb1\".\"plaina_ptr_id\" FROM \"plainb1\" WHERE \"plainb1\".\"plaina_ptr_id\" IN (4)\n            SELECT \"plaina\".\"id\", \"plaina\".\"standalone_parent_id\" FROM \"plaina\" WHERE \"plaina\".\"id\" = 4 LIMIT 21\n            SELECT \"plainb1\".\"plaina_ptr_id\", \"plainc1\".\"plainb1_ptr_id\" FROM \"plainc1\" INNER JOIN \"plainb1\" ON (\"plainc1\".\"plainb1_ptr_id\" = \"plainb1\".\"plaina_ptr_id\") WHERE \"plainc1\".\"plainb1_ptr_id\" IN (4)\n            SELECT \"plaina\".\"id\", \"plaina\".\"standalone_parent_id\", \"plainb1\".\"plaina_ptr_id\" FROM \"plainb1\" INNER JOIN \"plaina\" ON (\"plainb1\".\"plaina_ptr_id\" = \"plaina\".\"id\") WHERE \"plainb1\".\"plaina_ptr_id\" = 4 LIMIT 21\n            DELETE FROM \"plainb1_standalones\" WHERE \"plainb1_standalones\".\"plainb1_id\" IN (4)\n            DELETE FROM \"plainb1_standalones\" WHERE \"plainb1_standalones\".\"plainb1_id\" IN (4)\n            DELETE FROM \"plainb2\" WHERE \"plainb2\".\"plaina_ptr_id\" IN (4)\n            DELETE FROM \"plainc1\" WHERE \"plainc1\".\"plainb1_ptr_id\" IN (4)\n            DELETE FROM \"plainb1\" WHERE \"plainb1\".\"plaina_ptr_id\" IN (4)\n            DELETE FROM \"plaina\" WHERE \"plaina\".\"id\" IN (4)\n            \"\"\"\n            PlainA.objects.all().delete()\n\n        assert PlainC1.objects.count() == 0\n        # for q in ctx.captured_queries:\n        #     print(q[\"sql\"])\n\n        print(\"---------------------------\")\n        s0 = Standalone.objects.create()\n        PlainC1.objects.create(standalone=s0)\n        PlainB2.objects.create(standalone=s0)\n        with CaptureQueriesContext(connection) as ctx:\n            \"\"\"\n            SELECT \"standalone\".\"id\" FROM \"standalone\"\n            SELECT \"plaina\".\"id\" FROM \"plaina\" WHERE \"plaina\".\"standalone_parent_id\" IN (1)\n            SELECT \"plaina\".\"id\", \"plaina\".\"standalone_parent_id\", \"plainb2\".\"plaina_ptr_id\", \"plainb2\".\"standalone_id\" FROM \"plainb2\" INNER JOIN \"plaina\" ON (\"plainb2\".\"plaina_ptr_id\" = \"plaina\".\"id\") WHERE \"plainb2\".\"standalone_id\" IN (1)\n            SELECT \"plainb1\".\"plaina_ptr_id\", \"plainc1\".\"plainb1_ptr_id\" FROM \"plainc1\" INNER JOIN \"plainb1\" ON (\"plainc1\".\"plainb1_ptr_id\" = \"plainb1\".\"plaina_ptr_id\") WHERE \"plainc1\".\"standalone_id\" IN (1)\n            SELECT \"plaina\".\"id\", \"plaina\".\"standalone_parent_id\", \"plainb1\".\"plaina_ptr_id\" FROM \"plainb1\" INNER JOIN \"plaina\" ON (\"plainb1\".\"plaina_ptr_id\" = \"plaina\".\"id\") WHERE \"plainb1\".\"plaina_ptr_id\" = 5 LIMIT 21\n            DELETE FROM \"plainb1_standalones\" WHERE \"plainb1_standalones\".\"plainb1_id\" IN (5)\n            DELETE FROM \"plainb1_standalones\" WHERE \"plainb1_standalones\".\"standalone_id\" IN (1)\n            DELETE FROM \"standalone\" WHERE \"standalone\".\"id\" IN (1)\n            DELETE FROM \"plainb2\" WHERE \"plainb2\".\"plaina_ptr_id\" IN (6)\n            DELETE FROM \"plainc1\" WHERE \"plainc1\".\"plainb1_ptr_id\" IN (5)\n            DELETE FROM \"plainb1\" WHERE \"plainb1\".\"plaina_ptr_id\" IN (5)\n            DELETE FROM \"plaina\" WHERE \"plaina\".\"id\" IN (6, 5)\n            \"\"\"\n            Standalone.objects.all().delete()\n\n        assert PlainC1.objects.count() == 0\n        assert PlainB2.objects.count() == 0\n        # for q in ctx.captured_queries:\n        #     print(q[\"sql\"])\n\n        print(\"---------------------------\")\n        s0 = Standalone.objects.create()\n        s1 = Standalone.objects.create()\n        PlainB2.objects.create(standalone_parent=s0, standalone=s1)\n        with CaptureQueriesContext(connection) as ctx:\n            \"\"\"\n            SELECT \"plaina\".\"id\" FROM \"plaina\" WHERE \"plaina\".\"standalone_parent_id\" IN (2)\n            SELECT \"plainb1\".\"plaina_ptr_id\" FROM \"plainb1\" WHERE \"plainb1\".\"plaina_ptr_id\" IN (7)\n            SELECT \"plaina\".\"id\", \"plaina\".\"standalone_parent_id\", \"plainb2\".\"plaina_ptr_id\", \"plainb2\".\"standalone_id\" FROM \"plainb2\" INNER JOIN \"plaina\" ON (\"plainb2\".\"plaina_ptr_id\" = \"plaina\".\"id\") WHERE \"plainb2\".\"standalone_id\" IN (2)\n            SELECT \"plainb1\".\"plaina_ptr_id\", \"plainc1\".\"plainb1_ptr_id\" FROM \"plainc1\" INNER JOIN \"plainb1\" ON (\"plainc1\".\"plainb1_ptr_id\" = \"plainb1\".\"plaina_ptr_id\") WHERE \"plainc1\".\"standalone_id\" IN (2)\n            DELETE FROM \"plainb2\" WHERE \"plainb2\".\"plaina_ptr_id\" IN (7)\n            DELETE FROM \"plainb1_standalones\" WHERE \"plainb1_standalones\".\"standalone_id\" IN (2)\n            DELETE FROM \"standalone\" WHERE \"standalone\".\"id\" IN (2)\n            DELETE FROM \"plaina\" WHERE \"plaina\".\"id\" IN (7)\n            \"\"\"\n            s0.delete()\n\n        assert PlainB2.objects.count() == 0\n        assert list(Standalone.objects.all()) == [s1]\n        # for q in ctx.captured_queries:\n        #     print(q[\"sql\"])\n\n        print(\"---------------------------\")\n        grand_child = GrandChild.objects.create()\n        RelatedToChild.objects.create(child=Child.objects.get(pk=grand_child.pk))\n        RelatedToGrandChild.objects.create(grand_child=grand_child)\n\n        with CaptureQueriesContext(connection) as ctx:\n            \"\"\"\n            SELECT \"base\".\"id\" FROM \"base\" WHERE \"base\".\"id\" = 1\n            SELECT \"child\".\"base_ptr_id\" FROM \"child\" WHERE \"child\".\"base_ptr_id\" IN (1)\n            SELECT \"base\".\"id\" FROM \"base\" WHERE \"base\".\"id\" = 1 LIMIT 21\n            SELECT \"child\".\"base_ptr_id\", \"grandchild\".\"child_ptr_id\" FROM \"grandchild\" INNER JOIN \"child\" ON (\"grandchild\".\"child_ptr_id\" = \"child\".\"base_ptr_id\") WHERE \"grandchild\".\"child_ptr_id\" IN (1)\n            SELECT \"base\".\"id\", \"child\".\"base_ptr_id\" FROM \"child\" INNER JOIN \"base\" ON (\"child\".\"base_ptr_id\" = \"base\".\"id\") WHERE \"child\".\"base_ptr_id\" = 1 LIMIT 21\n            DELETE FROM \"relatedtochild\" WHERE \"relatedtochild\".\"child_id\" IN (1)\n            DELETE FROM \"relatedtograndchild\" WHERE \"relatedtograndchild\".\"grand_child_id\" IN (1)\n            DELETE FROM \"relatedtochild\" WHERE \"relatedtochild\".\"child_id\" IN (1)\n            DELETE FROM \"grandchild\" WHERE \"grandchild\".\"child_ptr_id\" IN (1)\n            DELETE FROM \"child\" WHERE \"child\".\"base_ptr_id\" IN (1)\n            DELETE FROM \"base\" WHERE \"base\".\"id\" IN (1)\n            \"\"\"\n            Base.objects.filter(pk=grand_child.pk).delete()  # cascade should reach relatives!\n\n        assert Base.objects.count() == 0\n        assert Child.objects.count() == 0\n        assert GrandChild.objects.count() == 0\n        assert RelatedToChild.objects.count() == 0\n        assert RelatedToGrandChild.objects.count() == 0\n        # for q in ctx.captured_queries:\n        #     print(q[\"sql\"])\n\n    def test_polymorphic_deletion_scenario1(self):\n        \"\"\"\n        Test the first polymorphic deletion scenario:\n\n        A normal model holds a foreign key to a polymorphic base model with several\n        children.\n\n                 <-- cascade --\n         Normal1 ----- FK ---->  Poly1\n                               /   |   \\\n                              A1   B1   C1\n\n        Tests that when you delete from a poly instance at any level of the\n        poly hierarchy, cascading deletion propagates correctly to Normal1.\n\n        And deleting Normal1 also works with no effects on the poly instances.\n        \"\"\"\n        from .models import (\n            Normal1,\n            Poly1,\n            A1,\n            B1,\n            C1,\n        )\n\n        p1 = Poly1.objects.create()\n        a1 = A1.objects.create()\n        b1 = B1.objects.create()\n        c1 = C1.objects.create()\n\n        n1 = Normal1.objects.create(poly=p1)\n        n2 = Normal1.objects.create(poly=c1)\n\n        n3 = Normal1.objects.create(poly=a1)\n        n4 = Normal1.objects.create(poly=b1)\n\n        n5 = Normal1.objects.create(poly=p1)\n        n6 = Normal1.objects.create(poly=b1)\n\n        n7 = Normal1.objects.create(poly=b1)\n        n8 = Normal1.objects.create(poly=c1)\n\n        # test delete from parent\n        Poly1.objects.filter(pk=a1.pk).delete()\n        assert Normal1.objects.count() == 7\n        assert n3 not in Normal1.objects.all()\n        assert A1.objects.count() == 0\n\n        Poly1.objects.filter(pk=p1.pk).delete()\n        assert Normal1.objects.count() == 5\n        assert n1 not in Normal1.objects.all()\n        assert n5 not in Normal1.objects.all()\n\n        n6.delete()\n        assert Normal1.objects.count() == 4\n        assert B1.objects.count() == 1\n        assert C1.objects.count() == 1\n        assert b1 in Poly1.objects.all()\n        assert c1 in Poly1.objects.all()\n\n        Poly1.objects.all().delete()\n        assert Normal1.objects.count() == 0\n        assert Poly1.objects.count() == 0\n\n    def test_polymorphic_deletion_scenario2(self):\n        \"\"\"\n        Test the second polymorphic deletion scenario:\n\n        A polymorphic model holds a foreign key to a base model with several\n        children.\n\n                -- cascade -->\n        Normal2 <----- FK ----  Poly2\n                               /  |  \\\n                             A2   B2   C2\n\n        Tests that when you delete a normal instance all related poly instances cascade\n        correctly regardless of their concrete type in the hierarchy.\n\n        This is the major collector failure mode.\n        \"\"\"\n\n        from .models import (\n            Normal2,\n            Poly2,\n            A2,\n            B2,\n            C2,\n        )\n\n        n1, n2, n3, n4 = (\n            Normal2.objects.create(),\n            Normal2.objects.create(),\n            Normal2.objects.create(),\n            Normal2.objects.create(),\n        )\n\n        p1, p2 = Poly2.objects.create(normal=n1), Poly2.objects.create(normal=n4)\n        a1, a2 = A2.objects.create(normal=n2), A2.objects.create(normal=n3)\n        b1, b2 = B2.objects.create(normal=n4), B2.objects.create(normal=n2)\n        c1, c2 = C2.objects.create(normal=n3), C2.objects.create(normal=n1)\n\n        assert set(n1.polies.all()) == {p1, c2}\n        assert set(n2.polies.all()) == {a1, b2}\n        assert set(n3.polies.all()) == {a2, c1}\n        assert set(n4.polies.all()) == {p2, b1}\n\n        n2.delete()\n        assert Poly2.objects.count() == 6\n        assert a1 not in Poly2.objects.all()\n        assert b2 not in Poly2.objects.all()\n\n        Normal2.objects.filter(pk__in=[n1.pk, n4.pk]).delete()\n        assert Poly2.objects.count() == 2\n        assert p1 not in Poly2.objects.all()\n        assert c2 not in Poly2.objects.all()\n        assert p2 not in Poly2.objects.all()\n        assert b1 not in Poly2.objects.all()\n        n3.delete()\n        assert Poly2.objects.count() == 0\n\n    def test_polymorphic_deletion_scenario3(self):\n        \"\"\"\n        Scenario 3\n\n        Normal3\n        |\n        Poly3\n        | \\\n        A3 B3\n\n        Deleting Poly3 should cascade delete Normal3 and deleting from Normal3 should\n        cascade down to children.\n        \"\"\"\n\n        from .models import (\n            Normal3,\n            Poly3,\n            A3,\n            B3,\n        )\n\n        b1 = B3.objects.create()\n        assert b1 in Poly3.objects.all()\n        Normal3.objects.filter(pk=b1.pk).delete()\n        assert b1 not in Poly3.objects.all()\n        assert Poly3.objects.count() == 0\n\n        b2 = B3.objects.create()\n        assert Normal3.objects.filter(pk=b2.pk).exists()\n        assert b2 in Poly3.objects.all()\n        b2.delete()\n        assert not Normal3.objects.filter(pk=b2.pk).exists()\n        assert Poly3.objects.count() == 0\n\n    def test_polymorphic_deletion_scenario4(self):\n        \"\"\"\n        Scenario 4 - M2Ms between normal/poly models\n\n                <--- cascade --->\n        Normal4 <----- M2M ----->  Poly4\n                                  /  |  \\\n                               A4   B4   C4\n\n        Ensure relations are appropriately cascaded on deletions from either side.\n        \"\"\"\n\n        from .models import (\n            Normal4,\n            Poly4,\n            A4,\n            B4,\n            C4,\n        )\n\n        n1, n2, n3, n4 = (\n            Normal4.objects.create(),\n            Normal4.objects.create(),\n            Normal4.objects.create(),\n            Normal4.objects.create(),\n        )\n\n        p1, p2 = Poly4.objects.create(), Poly4.objects.create()\n        a1, a2 = A4.objects.create(), A4.objects.create()\n        b1, b2 = B4.objects.create(), B4.objects.create()\n        c1, c2 = C4.objects.create(), C4.objects.create()\n\n        n1.polies.add(p1, a1, b1, c1)\n        n2.polies.add(p2, a2, b2, c2)\n        n3.polies.add(b1, c1)\n        n4.polies.add(p2, c2)\n\n        assert set(n1.polies.all()) == {p1, a1, b1, c1}\n        assert set(n2.polies.all()) == {p2, a2, b2, c2}\n        assert set(n3.polies.all()) == {b1, c1}\n        assert set(n4.polies.all()) == {p2, c2}\n\n        a1.delete()\n        assert set(n1.polies.all()) == {p1, b1, c1}\n        assert set(n2.polies.all()) == {p2, a2, b2, c2}\n        assert set(n3.polies.all()) == {b1, c1}\n        assert set(n4.polies.all()) == {p2, c2}\n\n        n4.delete()\n        assert set(n1.polies.all()) == {p1, b1, c1}\n        assert set(n2.polies.all()) == {p2, a2, b2, c2}\n        assert set(n3.polies.all()) == {b1, c1}\n        assert set(p2.normals.all()) == {n2}\n        assert set(c2.normals.all()) == {n2}\n\n        Poly4.objects.all().delete()\n        assert n1.polies.count() == 0\n        assert n2.polies.count() == 0\n        assert n3.polies.count() == 0\n\n    def test_polymorphic_deletion_scenario4_1(self):\n        \"\"\"\n        Scenario 4 - M2Ms between normal/poly models\n\n                  <--- cascade --->\n        Normal4_1 <----- M2M ----->  Poly4\n                                    /  |  \\\n                                  A4   B4   C4\n\n        Ensure relations are appropriately cascaded on deletions from either side.\n        \"\"\"\n\n        from .models import (\n            Normal4_1,\n            Poly4_1,\n            A4_1,\n            B4_1,\n            C4_1,\n        )\n\n        n1, n2, n3, n4 = (\n            Normal4_1.objects.create(),\n            Normal4_1.objects.create(),\n            Normal4_1.objects.create(),\n            Normal4_1.objects.create(),\n        )\n\n        p1, p2 = Poly4_1.objects.create(), Poly4_1.objects.create()\n        a1, a2 = A4_1.objects.create(), A4_1.objects.create()\n        b1, b2 = B4_1.objects.create(), B4_1.objects.create()\n        c1, c2 = C4_1.objects.create(), C4_1.objects.create()\n\n        n1.polies.add(p1, a1, b1, c1)\n        n2.polies.add(p2, a2, b2, c2)\n        n3.polies.add(b1, c1)\n        n4.polies.add(p2, c2)\n\n        assert set(n1.polies.all()) == {p1, a1, b1, c1}\n        assert set(n2.polies.all()) == {p2, a2, b2, c2}\n        assert set(n3.polies.all()) == {b1, c1}\n        assert set(n4.polies.all()) == {p2, c2}\n\n        a1.delete()\n        assert set(n1.polies.all()) == {p1, b1, c1}\n        assert set(n2.polies.all()) == {p2, a2, b2, c2}\n        assert set(n3.polies.all()) == {b1, c1}\n        assert set(n4.polies.all()) == {p2, c2}\n\n        n4.delete()\n        assert set(n1.polies.all()) == {p1, b1, c1}\n        assert set(n2.polies.all()) == {p2, a2, b2, c2}\n        assert set(n3.polies.all()) == {b1, c1}\n        assert set(p2.normals.all()) == {n2}\n        assert set(c2.normals.all()) == {n2}\n\n        Poly4_1.objects.all().delete()\n        assert n1.polies.count() == 0\n        assert n2.polies.count() == 0\n        assert n3.polies.count() == 0\n\n    def test_polymorphic_deletion_scenario5(self):\n        \"\"\"\n        Scenario 5 - scenario3 with custom/different PKs\n\n        Normal5\n        |\n        Poly5\n        | \\\n        A5 B5\n\n        Deleting Poly5 should cascade delete Normal5 and deleting from Normal5 should\n        cascade down to children.\n        \"\"\"\n\n        from .models import (\n            Normal5,\n            Poly5,\n            A5,\n            B5,\n        )\n\n        b1 = B5.objects.create()\n        assert b1 in Poly5.objects.all()\n        Normal5.objects.filter(pk=b1.pk).delete()\n        assert b1 not in Poly5.objects.all()\n        assert Poly5.objects.count() == 0\n\n        b2 = B5.objects.create()\n        assert Normal5.objects.filter(pk=b2.pk).exists()\n        assert b2 in Poly5.objects.all()\n        b2.delete()\n        assert not Normal5.objects.filter(pk=b2.pk).exists()\n        assert Poly5.objects.count() == 0\n\n        # FIXME: django-polymorphic assumes all rows share the same PK value\n        # n = Normal5.objects.create(n_pk=100)\n        # p = Poly5.objects.create_from_super(n, p_pk=200)\n        # A5.objects.create_from_super(p, a_pk=300)\n        # b = B5.objects.create_from_super(p, b_pk=400)\n        # assert Poly5.objects.count() == 1\n        # assert b in Poly5.objects.all()\n        # Normal5.objects.filter(pk=n.pk).delete()\n        # assert Poly5.objects.count() == 0\n\n        # n1 = Normal5.objects.create(n_pk=101)\n        # p1 = Poly5.objects.create_from_super(n1, p_pk=201)\n        # A5.objects.create_from_super(p1, a_pk=301)\n        # b1 = B5.objects.create_from_super(p1, b_pk=401)\n        # assert Poly5.objects.count() == 1\n        # assert b1 in Poly5.objects.all()\n        # b1.delete()\n        # assert Poly5.objects.count() == 0\n        # assert Normal5.objects.count() == 0\n\n    def test_raw_delete_results(self):\n        \"\"\"\n        Test what happens when you delete a child row with raw SQL then try to access\n        polymorphic objects.\n\n        With best effort approach, when a polymorphic_ctype_id points to a non-existing\n        derived row, the parent object is returned instead of being filtered out.\n        \"\"\"\n        from .models import Poly1, A1\n\n        a1 = A1.objects.create(some_data=\"test\")\n        p1 = Poly1.objects.non_polymorphic().get(pk=a1.pk)\n        p2 = Poly1.objects.create()\n\n        with connection.cursor() as cursor:\n            cursor.execute(\n                f\"DELETE FROM {A1._meta.db_table} WHERE {A1._meta.pk.column} = %s\", [a1.pk]\n            )\n\n        # Best effort: parent object is returned when child is deleted via raw SQL\n        result = list(Poly1.objects.all())\n        assert len(result) == 2\n        assert p2 in result\n        # p1 is returned as Poly1 (parent) since A1 (child) was deleted\n        assert any(\n            obj.pk == p1.pk and isinstance(obj, Poly1) and not isinstance(obj, A1)\n            for obj in result\n        )\n\n        assert set(Poly1.objects.non_polymorphic().all()) == {p1, p2}\n\n        p1_fetched = Poly1.objects.non_polymorphic().get(pk=a1.pk)\n        assert p1_fetched.get_real_instance().__class__ is Poly1\n\n    def test_delete_keep_parents(self):\n        \"\"\"\n        Test that delete(keep_parents=True) works as expected in polymorphic models\n        by updating the relevant parent row ctypes.\n        \"\"\"\n        from .models import Poly3, A3, B3, Normal3\n\n        a1 = A3.objects.create()\n        b1 = B3.objects.create()\n        p1 = Poly3.objects.create()\n        Normal3.objects.create()\n        a1_pk = a1.pk\n        b1_pk = b1.pk\n        p1_pk = p1.pk\n\n        a1.delete(keep_parents=True)\n        assert A3.objects.count() == 0\n        assert B3.objects.count() == 1\n        assert Poly3.objects.count() == 3\n        assert Normal3.objects.count() == 4\n        assert Poly3.objects.get(pk=a1_pk).__class__ is Poly3\n\n        p1.delete(keep_parents=True)\n        assert A3.objects.count() == 0\n        assert B3.objects.count() == 1\n        assert Poly3.objects.count() == 2\n        assert Normal3.objects.count() == 4\n        assert Normal3.objects.get(pk=p1_pk).__class__ is Normal3\n\n        # deleting an instance with more derived tables from a class higher up in its\n        # hierarchy will delete all child rows below that level.\n        b1_base = Poly3.objects.non_polymorphic().get(pk=b1_pk)\n        b1_base.delete(keep_parents=True)\n        assert A3.objects.count() == 0\n        assert B3.objects.count() == 0\n        assert Poly3.objects.count() == 1\n        assert Normal3.objects.count() == 4\n        assert Normal3.objects.get(pk=b1_pk).__class__ is Normal3\n        assert not Poly3.objects.filter(pk=b1_pk).exists()\n"
  },
  {
    "path": "src/polymorphic/tests/errata/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/errata/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/errata/models.py",
    "content": "from polymorphic.models import PolymorphicModel\nfrom polymorphic.managers import PolymorphicManager\nfrom django.db import models\nfrom django.db.models import Manager\nfrom django.db.models.query import QuerySet\n\n\nclass BadModel(PolymorphicModel):\n    instance_of = models.CharField(max_length=100)\n    not_instance_of = models.IntegerField()\n\n\nclass PolymorphicMigrationManager(PolymorphicManager):\n    use_in_migrations = True\n\n\nclass OkMigrationManager(Manager):\n    use_in_migrations = True\n\n\nclass GoodMigrationManager(PolymorphicModel):\n    objects = PolymorphicManager()\n    migration_manager = OkMigrationManager()\n\n\nclass BadMigrationManager(PolymorphicModel):\n    objects = PolymorphicMigrationManager()\n\n\nclass BadManager(PolymorphicModel):\n    objects = models.Manager()  # not polymorphic\n\n\nclass BadQuerySet(PolymorphicModel):\n    default_objects = PolymorphicManager().from_queryset(QuerySet)\n"
  },
  {
    "path": "src/polymorphic/tests/errata/settings.py",
    "content": "from ..settings import *\n\nINSTALLED_APPS = [\n    *INSTALLED_APPS,\n    \"polymorphic.tests.errata\",\n]\n"
  },
  {
    "path": "src/polymorphic/tests/errata/test_errata.py",
    "content": "from django.core.checks import Error, run_checks\nfrom django.test.utils import override_settings\nfrom django.test import SimpleTestCase, TestCase\nfrom django.core.exceptions import FieldError\n\n\n@override_settings(\n    INSTALLED_APPS=[\n        \"polymorphic.tests.errata\",\n        \"django.contrib.contenttypes\",\n        \"django.contrib.auth\",\n    ]\n)\nclass TestErrata(SimpleTestCase):\n    def test_system_checks(self):\n        \"\"\"Test that using reserved field names triggers polymorphic.E001 system check.\"\"\"\n\n        # Run the check function directly on the model\n        errors = run_checks()\n\n        assert len(errors) == 5, f\"Expected 12 system check errors but got {len(errors)}: {errors}\"\n\n        assert errors[0].id == \"polymorphic.E001\"\n        assert errors[0].msg == \"Field 'instance_of' on model 'BadModel' is a reserved name.\"\n        assert errors[1].id == \"polymorphic.E001\"\n        assert errors[1].msg == \"Field 'not_instance_of' on model 'BadModel' is a reserved name.\"\n\n        assert errors[2].id == \"polymorphic.E002\"\n        assert (\n            errors[2].msg\n            == \"The migration manager 'errata.BadMigrationManager.objects' is polymorphic.\"\n        )\n\n        assert errors[3].id == \"polymorphic.W001\"\n        assert (\n            errors[3].msg == \"The default manager errata.BadManager.objects' is not polymorphic.\"\n        )\n\n        assert errors[4].id == \"polymorphic.W002\"\n        assert (\n            errors[4].msg\n            == \"The default manager errata.BadManager.objects' is not using a PolymorphicQuerySet.\"\n        )\n\n    def test_polymorphic_guard_requires_callable(self):\n        \"\"\"Test that PolymorphicGuard raises TypeError if initialized with non-callable.\"\"\"\n\n        from polymorphic.deletion import PolymorphicGuard\n\n        non_callable_values = [42, \"not a function\", None, 3.14, [], {}]\n\n        for value in non_callable_values:\n            try:\n                PolymorphicGuard(value)\n            except TypeError as e:\n                assert str(e) == \"action must be callable\", (\n                    f\"Expected TypeError with message 'action must be callable' but got: {e}\"\n                )\n            else:\n                assert False, f\"Expected TypeError when initializing PolymorphicGuard with {value}\"\n\n\nclass TestFilterErrata(TestCase):\n    def test_invalid_field_lookup_raises_field_error(self):\n        from polymorphic.tests.models import Participant\n\n        with self.assertRaises(FieldError):\n            Participant.objects.get(tests__Model2C___field3=\"userprofile1\")\n\n        with self.assertRaises(FieldError):\n            Participant.objects.get(notreal__Model2C___field3=\"userprofile1\")\n\n        with self.assertRaises(FieldError):\n            Participant.objects.get(tests__NotReal___field3=\"userprofile1\")\n"
  },
  {
    "path": "src/polymorphic/tests/examples/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass IntegrationsExampleConfig(AppConfig):\n    name = \"polymorphic.tests.examples.integrations\"\n    label = \"integrations\"\n    verbose_name = \"Integration Examples\"\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/drf/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/drf/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass DRFExampleConfig(AppConfig):\n    name = \"polymorphic.tests.examples.integrations.drf\"\n    label = \"drf_example\"\n    verbose_name = \"DRF Examples\"\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/drf/example_serializers.py",
    "content": "from rest_framework import serializers\n\nfrom polymorphic.contrib.drf.serializers import PolymorphicSerializer\n\nfrom .models import Project, ArtProject, ResearchProject\n\n\nclass ProjectSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = Project\n        fields = (\"topic\",)\n\n\nclass ArtProjectSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = ArtProject\n        fields = (\"topic\", \"artist\", \"url\")\n        extra_kwargs = {\n            \"url\": {\n                \"view_name\": \"drf:project-detail\",\n                \"lookup_field\": \"pk\",\n            },\n        }\n\n\nclass ResearchProjectSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = ResearchProject\n        fields = (\"topic\", \"supervisor\")\n\n\nclass ProjectPolymorphicSerializer(PolymorphicSerializer):\n    model_serializer_mapping = {\n        Project: ProjectSerializer,\n        ArtProject: ArtProjectSerializer,\n        ResearchProject: ResearchProjectSerializer,\n    }\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/drf/filter_serializers.py",
    "content": "from .models import AiModelAnnotator, UserAnnotator, Annotator, Data\nfrom rest_framework import serializers\nfrom polymorphic.contrib.drf.serializers import PolymorphicSerializer\n\n\nclass AiModelAnnotatorSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = AiModelAnnotator\n        fields = \"__all__\"\n\n\nclass UserAnnotatorSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = UserAnnotator\n        fields = \"__all__\"\n\n\nclass AnnotatorSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = Annotator\n        fields = \"__all__\"\n\n\nclass AnnotatorPolymorphicSerializer(PolymorphicSerializer):\n    model_serializer_mapping = {\n        Annotator: AnnotatorSerializer,\n        AiModelAnnotator: AiModelAnnotatorSerializer,\n        UserAnnotator: UserAnnotatorSerializer,\n    }\n\n\nclass AnnotationSerializer(serializers.ModelSerializer):\n    annotator = serializers.PrimaryKeyRelatedField(\n        queryset=Annotator.objects.all()\n    )\n\n    class Meta:\n        model = Data\n        fields = \"__all__\"\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/drf/filter_views.py",
    "content": "from .filter_serializers import AnnotationSerializer\nfrom .models import Data, AiModelAnnotator\nfrom rest_framework import viewsets, mixins\nfrom django_filters.rest_framework import DjangoFilterBackend\nimport django_filters\n\n\nclass DataFilterSet(django_filters.FilterSet):\n    \"\"\"FilterSet for Data model with polymorphic annotator filtering.\"\"\"\n\n    annotator__ai_model = django_filters.CharFilter(method=\"filter_by_ai_model\")\n\n    class Meta:\n        model = Data\n        fields = [\"annotator\"]\n\n    def filter_by_ai_model(self, queryset, name, value):\n        return queryset.filter(\n            annotator__in=AiModelAnnotator.objects.filter(ai_model=value)\n        )\n\n\nclass AnnotationTrainingViewSet(\n    mixins.CreateModelMixin,\n    mixins.ListModelMixin,\n    mixins.RetrieveModelMixin,\n    viewsets.GenericViewSet,\n):\n    queryset = Data.objects.all()\n    serializer_class = AnnotationSerializer\n    filter_backends = [DjangoFilterBackend]\n    filterset_class = DataFilterSet\n\n    # this does not work\n    # filterset_fields = [\"annotator\", \"annotator___AiModelAnnotator__ai_model\"]\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/drf/migrations/0001_initial.py",
    "content": "# Generated by Django 4.2 on 2026-02-05 14:47\n\nfrom django.conf import settings\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('contenttypes', '0002_remove_content_type_name'),\n        migrations.swappable_dependency(settings.AUTH_USER_MODEL),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='Annotator',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='BlogBase',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(max_length=10)),\n                ('slug', models.SlugField(max_length=255, unique=True)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Project',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('topic', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='AiModelAnnotator',\n            fields=[\n                ('annotator_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='drf_example.annotator')),\n                ('ai_model', models.CharField(max_length=255)),\n                ('version', models.CharField(default=None, max_length=16, null=True)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('drf_example.annotator',),\n        ),\n        migrations.CreateModel(\n            name='ArtProject',\n            fields=[\n                ('project_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='drf_example.project')),\n                ('artist', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('drf_example.project',),\n        ),\n        migrations.CreateModel(\n            name='BlogOne',\n            fields=[\n                ('blogbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='drf_example.blogbase')),\n                ('info', models.CharField(max_length=10)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('drf_example.blogbase',),\n        ),\n        migrations.CreateModel(\n            name='BlogTwo',\n            fields=[\n                ('blogbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='drf_example.blogbase')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('drf_example.blogbase',),\n        ),\n        migrations.CreateModel(\n            name='ResearchProject',\n            fields=[\n                ('project_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='drf_example.project')),\n                ('supervisor', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('drf_example.project',),\n        ),\n        migrations.CreateModel(\n            name='Data',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('annotator', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='drf_example.annotator')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='UserAnnotator',\n            fields=[\n                ('annotator_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='drf_example.annotator')),\n                ('user', models.ForeignKey(default=None, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('drf_example.annotator',),\n        ),\n        migrations.CreateModel(\n            name='BlogThree',\n            fields=[\n                ('blogbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='drf_example.blogbase')),\n                ('info', models.CharField(max_length=255)),\n                ('about', models.CharField(max_length=255)),\n            ],\n            options={\n                'unique_together': {('info', 'about')},\n            },\n            bases=('drf_example.blogbase',),\n        ),\n    ]\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/drf/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/drf/models/__init__.py",
    "content": "from .models_test import BlogBase, BlogOne, BlogTwo, BlogThree\nfrom .example_models import (\n    Project,\n    ArtProject,\n    ResearchProject,\n)\nfrom .filters import (\n    Annotator,\n    UserAnnotator,\n    AiModelAnnotator,\n    Data,\n)\n\n__all__ = [\n    \"BlogBase\",\n    \"BlogOne\",\n    \"BlogTwo\",\n    \"BlogThree\",\n    \"Project\",\n    \"ArtProject\",\n    \"ResearchProject\",\n    \"Annotator\",\n    \"UserAnnotator\",\n    \"AiModelAnnotator\",\n    \"Data\",\n]\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/drf/models/example_models.py",
    "content": "from django.db import models\n\nfrom polymorphic.models import PolymorphicModel\n\n\nclass Project(PolymorphicModel):\n    topic = models.CharField(max_length=30)\n\n\nclass ArtProject(Project):\n    artist = models.CharField(max_length=30)\n\n\nclass ResearchProject(Project):\n    supervisor = models.CharField(max_length=30)\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/drf/models/filters.py",
    "content": "\"\"\"\nhttps://github.com/jazzband/django-polymorphic/issues/520\n\"\"\"\n\nfrom polymorphic.models import PolymorphicModel\nfrom django.db import models\nfrom django.contrib.auth import get_user_model\n\n\nclass Annotator(PolymorphicModel):\n    pass\n\n\nclass UserAnnotator(Annotator):\n    user = models.ForeignKey(\n        get_user_model(), on_delete=models.PROTECT, default=None\n    )\n\n\nclass AiModelAnnotator(Annotator):\n    ai_model = models.CharField(max_length=255)\n    version = models.CharField(max_length=16, default=None, null=True)\n\n\nclass Data(models.Model):\n    annotator = models.ForeignKey(Annotator, on_delete=models.PROTECT)\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/drf/models/models_test.py",
    "content": "from django.db import models\n\nfrom polymorphic.models import PolymorphicModel\n\n\nclass BlogBase(PolymorphicModel):\n    name = models.CharField(max_length=10)\n    slug = models.SlugField(max_length=255, unique=True)\n\n\nclass BlogOne(BlogBase):\n    info = models.CharField(max_length=10)\n\n\nclass BlogTwo(BlogBase):\n    pass\n\n\nclass BlogThree(BlogBase):\n    info = models.CharField(max_length=255)\n    about = models.CharField(max_length=255)\n\n    class Meta:\n        unique_together = ((\"info\", \"about\"),)\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/drf/serializers.py",
    "content": "from rest_framework import serializers\n\nfrom polymorphic.contrib.drf.serializers import PolymorphicSerializer\n\nfrom .models import BlogBase, BlogOne, BlogTwo, BlogThree\n\n\nclass BlogBaseSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = BlogBase\n        fields = (\"name\", \"slug\")\n\n\nclass BlogOneSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = BlogOne\n        fields = (\"name\", \"slug\", \"info\")\n\n\nclass BlogTwoSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = BlogTwo\n        fields = (\"name\", \"slug\")\n\n\nclass BlogThreeSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = BlogThree\n        fields = (\"name\", \"slug\", \"info\", \"about\")\n\n\nclass BlogPolymorphicSerializer(PolymorphicSerializer):\n    model_serializer_mapping = {\n        BlogBase: BlogBaseSerializer,\n        BlogOne: BlogOneSerializer,\n        BlogTwo: BlogTwoSerializer,\n        BlogThree: BlogThreeSerializer,\n    }\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/drf/test.py",
    "content": "from django.core.exceptions import ImproperlyConfigured\n\nimport pytest\n\ntry:\n    from rest_framework import serializers\n    from rest_framework.test import APIClient\n\n    from polymorphic.contrib.drf.serializers import PolymorphicSerializer\n    from .serializers import (\n        BlogBaseSerializer,\n        BlogOneSerializer,\n        BlogPolymorphicSerializer,\n    )\n\n    from .models import (\n        BlogBase,\n        BlogOne,\n        BlogTwo,\n        Project,\n        ArtProject,\n        ResearchProject,\n    )\nexcept ImportError:\n    pytest.skip(\"djangorestframework is not installed\", allow_module_level=True)\n\n\npytestmark = pytest.mark.django_db\n\n\nclass TestPolymorphicSerializer:\n    def test_model_serializer_mapping_is_none(self):\n        class EmptyPolymorphicSerializer(PolymorphicSerializer):\n            pass\n\n        with pytest.raises(ImproperlyConfigured) as excinfo:\n            EmptyPolymorphicSerializer()\n\n        assert str(excinfo.value) == (\n            \"`EmptyPolymorphicSerializer` is missing a \"\n            \"`EmptyPolymorphicSerializer.model_serializer_mapping` attribute\"\n        )\n\n    def test_resource_type_field_name_is_not_string(self, mocker):\n        class NotStringPolymorphicSerializer(PolymorphicSerializer):\n            model_serializer_mapping = mocker.MagicMock\n            resource_type_field_name = 1\n\n        with pytest.raises(ImproperlyConfigured) as excinfo:\n            NotStringPolymorphicSerializer()\n\n        assert str(excinfo.value) == (\n            \"`NotStringPolymorphicSerializer.resource_type_field_name` must be a string\"\n        )\n\n    def test_each_serializer_has_context(self, mocker):\n        context = mocker.MagicMock()\n        serializer = BlogPolymorphicSerializer(context=context)\n        for inner_serializer in serializer.model_serializer_mapping.values():\n            assert inner_serializer.context == context\n\n    def test_non_callable_serializer_in_mapping(self):\n        # Test the case where serializer is already instantiated (not callable)\n        # This tests the else branch of the callable(serializer) check\n\n        # Create an already-instantiated serializer\n        blog_base_serializer_instance = BlogBaseSerializer()\n\n        class TestPolymorphicSerializer(PolymorphicSerializer):\n            model_serializer_mapping = {\n                BlogBase: blog_base_serializer_instance,  # Already an instance\n                BlogOne: BlogOneSerializer,  # Still a class (callable)\n            }\n\n        serializer = TestPolymorphicSerializer()\n\n        # The instance should be used directly without re-instantiation\n        assert (\n            serializer.model_serializer_mapping[BlogBase]\n            is blog_base_serializer_instance\n        )\n\n        # The callable should be instantiated\n        assert isinstance(\n            serializer.model_serializer_mapping[BlogOne], BlogOneSerializer\n        )\n        assert (\n            serializer.model_serializer_mapping[BlogOne]\n            is not BlogOneSerializer\n        )\n\n        # Both should be in resource_type_model_mapping\n        assert serializer.resource_type_model_mapping[\"BlogBase\"] == BlogBase\n        assert serializer.resource_type_model_mapping[\"BlogOne\"] == BlogOne\n\n        # Now test that serialization actually works with the non-callable serializer\n        base_instance = BlogBase.objects.create(name=\"base\", slug=\"base-slug\")\n        one_instance = BlogOne.objects.create(\n            name=\"one\", slug=\"one-slug\", info=\"info\"\n        )\n\n        # Serialize BlogBase (using the pre-instantiated serializer)\n        base_serializer = TestPolymorphicSerializer(base_instance)\n        base_data = base_serializer.data\n        assert base_data == {\n            \"name\": \"base\",\n            \"slug\": \"base-slug\",\n            \"resourcetype\": \"BlogBase\",\n        }\n\n        # Serialize BlogOne (using the callable serializer that was instantiated)\n        one_serializer = TestPolymorphicSerializer(one_instance)\n        one_data = one_serializer.data\n        assert one_data == {\n            \"name\": \"one\",\n            \"slug\": \"one-slug\",\n            \"info\": \"info\",\n            \"resourcetype\": \"BlogOne\",\n        }\n\n        # Test serialization of multiple instances (many=True)\n        instances = [base_instance, one_instance]\n        many_serializer = TestPolymorphicSerializer(instances, many=True)\n        many_data = many_serializer.data\n        assert len(many_data) == 2\n        assert many_data[0][\"resourcetype\"] == \"BlogBase\"\n        assert many_data[1][\"resourcetype\"] == \"BlogOne\"\n        assert many_data[0][\"name\"] == \"base\"\n        assert many_data[1][\"name\"] == \"one\"\n        assert many_data[1][\"info\"] == \"info\"\n\n    def test_serialize(self):\n        instance = BlogBase.objects.create(name=\"blog\", slug=\"blog\")\n        serializer = BlogPolymorphicSerializer(instance)\n        assert serializer.data == {\n            \"name\": \"blog\",\n            \"slug\": \"blog\",\n            \"resourcetype\": \"BlogBase\",\n        }\n\n    def test_deserialize(self):\n        data = {\n            \"name\": \"blog\",\n            \"slug\": \"blog\",\n            \"resourcetype\": \"BlogBase\",\n        }\n        serializers = BlogPolymorphicSerializer(data=data)\n        assert serializers.is_valid()\n        assert serializers.data == data\n\n    def test_deserialize_with_invalid_resourcetype(self):\n        data = {\n            \"name\": \"blog\",\n            \"resourcetype\": \"Invalid\",\n        }\n        serializers = BlogPolymorphicSerializer(data=data)\n        assert not serializers.is_valid()\n\n    def test_create(self):\n        data = [\n            {\"name\": \"a\", \"slug\": \"a\", \"resourcetype\": \"BlogBase\"},\n            {\n                \"name\": \"b\",\n                \"slug\": \"b\",\n                \"info\": \"info\",\n                \"resourcetype\": \"BlogOne\",\n            },\n            {\"name\": \"c\", \"slug\": \"c\", \"resourcetype\": \"BlogTwo\"},\n        ]\n        serializer = BlogPolymorphicSerializer(data=data, many=True)\n        assert serializer.is_valid()\n\n        instances = serializer.save()\n        assert len(instances) == 3\n        assert [item.name for item in instances] == [\"a\", \"b\", \"c\"]\n\n        assert BlogBase.objects.count() == 3\n        assert BlogBase.objects.instance_of(BlogOne).count() == 1\n        assert BlogBase.objects.instance_of(BlogTwo).count() == 1\n\n        assert serializer.data == data\n\n    def test_update(self):\n        instance = BlogBase.objects.create(name=\"blog\", slug=\"blog\")\n        data = {\n            \"name\": \"new-blog\",\n            \"slug\": \"blog\",\n            \"resourcetype\": \"BlogBase\",\n        }\n\n        serializer = BlogPolymorphicSerializer(instance, data=data)\n        assert serializer.is_valid()\n\n        serializer.save()\n        assert instance.name == \"new-blog\"\n        assert instance.slug == \"blog\"\n\n    def test_partial_update(self):\n        instance = BlogBase.objects.create(name=\"blog\", slug=\"blog\")\n        data = {\"name\": \"new-blog\", \"resourcetype\": \"BlogBase\"}\n\n        serializer = BlogPolymorphicSerializer(\n            instance, data=data, partial=True\n        )\n        assert serializer.is_valid()\n\n        serializer.save()\n        assert instance.name == \"new-blog\"\n        assert instance.slug == \"blog\"\n\n    def test_partial_update_without_resourcetype(self):\n        instance = BlogBase.objects.create(name=\"blog\", slug=\"blog\")\n        data = {\"name\": \"new-blog\"}\n\n        serializer = BlogPolymorphicSerializer(\n            instance, data=data, partial=True\n        )\n        assert serializer.is_valid()\n\n        serializer.save()\n        assert instance.name == \"new-blog\"\n        assert instance.slug == \"blog\"\n\n    def test_object_validators_are_applied(self):\n        data = {\n            \"name\": \"test-blog\",\n            \"slug\": \"test-blog-slug\",\n            \"info\": \"test-blog-info\",\n            \"about\": \"test-blog-about\",\n            \"resourcetype\": \"BlogThree\",\n        }\n        serializer = BlogPolymorphicSerializer(data=data)\n        assert serializer.is_valid()\n        serializer.save()\n\n        data[\"slug\"] = \"test-blog-slug-new\"\n        duplicate = BlogPolymorphicSerializer(data=data)\n\n        assert not duplicate.is_valid()\n        assert \"non_field_errors\" in duplicate.errors\n        err = duplicate.errors[\"non_field_errors\"]\n\n        assert err == [\"The fields info, about must make a unique set.\"]\n\n    def test_to_internal_value_with_valid_data(self):\n        data = {\n            \"name\": \"blog\",\n            \"slug\": \"blog\",\n            \"resourcetype\": \"BlogBase\",\n        }\n        serializer = BlogPolymorphicSerializer(data=data)\n        internal_value = serializer.to_internal_value(data)\n\n        assert internal_value[\"name\"] == \"blog\"\n        assert internal_value[\"slug\"] == \"blog\"\n        assert internal_value[\"resourcetype\"] == \"BlogBase\"\n\n    def test_to_internal_value_with_missing_resourcetype(self):\n        from rest_framework.exceptions import ValidationError\n\n        data = {\n            \"name\": \"blog\",\n            \"slug\": \"blog\",\n        }\n        serializer = BlogPolymorphicSerializer(data=data)\n\n        with pytest.raises(ValidationError) as excinfo:\n            serializer.to_internal_value(data)\n\n        assert \"resourcetype\" in excinfo.value.detail\n        assert excinfo.value.detail[\"resourcetype\"] == \"This field is required\"\n\n    def test_to_internal_value_with_partial_update(self):\n        instance = BlogBase.objects.create(name=\"blog\", slug=\"blog\")\n        data = {\"name\": \"new-blog\"}\n\n        serializer = BlogPolymorphicSerializer(\n            instance, data=data, partial=True\n        )\n        internal_value = serializer.to_internal_value(data)\n\n        assert internal_value[\"name\"] == \"new-blog\"\n        assert internal_value[\"resourcetype\"] == \"BlogBase\"\n\n    def test_get_serializer_from_model_or_instance_raises_keyerror(self):\n        from polymorphic.models import PolymorphicModel\n\n        # Create a model that is not in the mapping\n        class UnmappedModel(PolymorphicModel):\n            class Meta:\n                app_label = \"drf\"\n\n        serializer = BlogPolymorphicSerializer()\n\n        with pytest.raises(KeyError) as excinfo:\n            serializer._get_serializer_from_model_or_instance(UnmappedModel)\n\n        assert \"model_serializer_mapping\" in str(excinfo.value)\n        assert \"UnmappedModel\" in str(excinfo.value)\n\n    def test_get_serializer_from_resource_type_keyerror_propagation(self):\n        # This tests the case where _get_serializer_from_resource_type\n        # successfully finds a resource_type in the mapping, but then\n        # _get_serializer_from_model_or_instance raises a KeyError\n        # when trying to find the serializer for that model.\n        #\n        # However, looking at the code, this scenario is actually not possible\n        # in normal operation because resource_type_model_mapping and\n        # model_serializer_mapping are populated together in __init__.\n        #\n        # The KeyError in _get_serializer_from_resource_type would only\n        # occur if the resource_type is not in resource_type_model_mapping,\n        # which is already caught and converted to ValidationError at line 149.\n        #\n        # So we'll test that the ValidationError is raised properly instead.\n        from rest_framework.exceptions import ValidationError\n\n        data = {\n            \"name\": \"blog\",\n            \"slug\": \"blog\",\n            \"resourcetype\": \"InvalidResourceType\",\n        }\n        serializer = BlogPolymorphicSerializer(data=data)\n\n        with pytest.raises(ValidationError) as excinfo:\n            serializer._get_serializer_from_resource_type(\"InvalidResourceType\")\n\n        assert \"resourcetype\" in excinfo.value.detail\n        assert \"Invalid resourcetype\" in str(\n            excinfo.value.detail[\"resourcetype\"]\n        )\n\n    def test_validate_method_modifications_are_preserved(self):\n        \"\"\"Test that modifications made in child serializer's validate() method are preserved.\"\"\"\n        # Track whether the extra_field was present during create\n        created_with_extra_field = []\n\n        # Create a custom serializer that adds a field in validate()\n        class CustomBlogOneSerializer(BlogOneSerializer):\n            extra_field = serializers.CharField(required=False, allow_null=True)\n\n            class Meta(BlogOneSerializer.Meta):\n                fields = BlogOneSerializer.Meta.fields + (\"extra_field\",)\n\n            def validate(self, attrs):\n                attrs = super().validate(attrs)\n                # Simulate adding data in validate(), like adding the current user\n                attrs[\"extra_field\"] = \"added_in_validate\"\n                return attrs\n\n            def create(self, validated_data):\n                # Record whether extra_field was in validated_data\n                created_with_extra_field.append(\"extra_field\" in validated_data)\n                # Remove extra_field before creating the model instance\n                validated_data.pop(\"extra_field\", None)\n                return super().create(validated_data)\n\n        class CustomBlogPolymorphicSerializer(PolymorphicSerializer):\n            model_serializer_mapping = {\n                BlogBase: BlogBaseSerializer,\n                BlogOne: CustomBlogOneSerializer,\n            }\n\n        # Create data without the extra_field\n        data = {\n            \"name\": \"test\",\n            \"slug\": \"test-slug\",\n            \"info\": \"test-info\",\n            \"resourcetype\": \"BlogOne\",\n        }\n\n        serializer = CustomBlogPolymorphicSerializer(data=data)\n        assert serializer.is_valid(), f\"Validation errors: {serializer.errors}\"\n\n        # Verify that the extra_field added in validate() is in validated_data\n        assert \"extra_field\" in serializer.validated_data\n        assert serializer.validated_data[\"extra_field\"] == \"added_in_validate\"\n\n        # Verify that resource_type field is still preserved in parent's validated_data\n        assert \"resourcetype\" in serializer.validated_data\n        assert serializer.validated_data[\"resourcetype\"] == \"BlogOne\"\n\n        # Save and verify that the field was present during create\n        # Note: This would fail before the fix because the parent's _validated_data\n        # wasn't updated with the child's _validated_data after calling child.is_valid()\n        instance = serializer.save()\n\n        # Verify that extra_field was indeed present when create() was called\n        assert created_with_extra_field == [True], (\n            \"extra_field should have been in validated_data when create() was called\"\n        )\n\n        # Verify the instance was created successfully\n        assert instance.name == \"test\"\n        assert instance.slug == \"test-slug\"\n        assert instance.info == \"test-info\"\n\n\nclass TestProjectViewSet:\n    \"\"\"Test the example Project ViewSet with polymorphic serializers.\"\"\"\n\n    @pytest.fixture\n    def client(self):\n        return APIClient()\n\n    @pytest.fixture\n    def base_project(self):\n        return Project.objects.create(topic=\"General Project\")\n\n    @pytest.fixture\n    def art_project(self):\n        return ArtProject.objects.create(topic=\"Art\", artist=\"Picasso\")\n\n    @pytest.fixture\n    def research_project(self):\n        return ResearchProject.objects.create(\n            topic=\"Research\", supervisor=\"Dr. Smith\"\n        )\n\n    def test_list_projects(\n        self, client, base_project, art_project, research_project\n    ):\n        response = client.get(\"/examples/integrations/drf/projects/\")\n        assert response.status_code == 200\n        assert len(response.data) == 3\n\n        topics = {item[\"topic\"] for item in response.data}\n        assert topics == {\"General Project\", \"Art\", \"Research\"}\n\n    def test_retrieve_base_project(self, client, base_project):\n        response = client.get(\n            f\"/examples/integrations/drf/projects/{base_project.pk}/\"\n        )\n        assert response.status_code == 200\n        assert response.data[\"topic\"] == \"General Project\"\n        assert response.data[\"resourcetype\"] == \"Project\"\n\n    def test_retrieve_art_project(self, client, art_project):\n        response = client.get(\n            f\"/examples/integrations/drf/projects/{art_project.pk}/\"\n        )\n        assert response.status_code == 200\n        assert response.data[\"topic\"] == \"Art\"\n        assert response.data[\"artist\"] == \"Picasso\"\n        assert response.data[\"resourcetype\"] == \"ArtProject\"\n        assert \"url\" in response.data\n\n    def test_retrieve_research_project(self, client, research_project):\n        response = client.get(\n            f\"/examples/integrations/drf/projects/{research_project.pk}/\"\n        )\n        assert response.status_code == 200\n        assert response.data[\"topic\"] == \"Research\"\n        assert response.data[\"supervisor\"] == \"Dr. Smith\"\n        assert response.data[\"resourcetype\"] == \"ResearchProject\"\n\n    def test_create_base_project(self, client):\n        data = {\"topic\": \"New Project\", \"resourcetype\": \"Project\"}\n        response = client.post(\n            \"/examples/integrations/drf/projects/\", data, format=\"json\"\n        )\n        assert response.status_code == 201\n        assert response.data[\"topic\"] == \"New Project\"\n        assert response.data[\"resourcetype\"] == \"Project\"\n\n        assert Project.objects.count() == 1\n        project = Project.objects.first()\n        assert project.topic == \"New Project\"\n        assert type(project) is Project\n\n    def test_create_art_project(self, client):\n        data = {\n            \"topic\": \"Sculpture\",\n            \"artist\": \"Michelangelo\",\n            \"resourcetype\": \"ArtProject\",\n        }\n        response = client.post(\n            \"/examples/integrations/drf/projects/\", data, format=\"json\"\n        )\n        assert response.status_code == 201\n        assert response.data[\"topic\"] == \"Sculpture\"\n        assert response.data[\"artist\"] == \"Michelangelo\"\n        assert response.data[\"resourcetype\"] == \"ArtProject\"\n        assert \"url\" in response.data\n\n        assert Project.objects.count() == 1\n        project = Project.objects.first()\n        assert isinstance(project, ArtProject)\n        assert project.artist == \"Michelangelo\"\n\n    def test_create_research_project(self, client):\n        data = {\n            \"topic\": \"AI Research\",\n            \"supervisor\": \"Dr. Johnson\",\n            \"resourcetype\": \"ResearchProject\",\n        }\n        response = client.post(\n            \"/examples/integrations/drf/projects/\", data, format=\"json\"\n        )\n        assert response.status_code == 201\n        assert response.data[\"topic\"] == \"AI Research\"\n        assert response.data[\"supervisor\"] == \"Dr. Johnson\"\n        assert response.data[\"resourcetype\"] == \"ResearchProject\"\n\n        assert Project.objects.count() == 1\n        project = Project.objects.first()\n        assert isinstance(project, ResearchProject)\n        assert project.supervisor == \"Dr. Johnson\"\n\n    def test_update_project(self, client, base_project):\n        data = {\"topic\": \"Updated Project\", \"resourcetype\": \"Project\"}\n        response = client.put(\n            f\"/examples/integrations/drf/projects/{base_project.pk}/\",\n            data,\n            format=\"json\",\n        )\n        assert response.status_code == 200\n        assert response.data[\"topic\"] == \"Updated Project\"\n\n        base_project.refresh_from_db()\n        assert base_project.topic == \"Updated Project\"\n\n    def test_partial_update_art_project(self, client, art_project):\n        data = {\"artist\": \"Van Gogh\"}\n        response = client.patch(\n            f\"/examples/integrations/drf/projects/{art_project.pk}/\",\n            data,\n            format=\"json\",\n        )\n        assert response.status_code == 200\n        assert response.data[\"artist\"] == \"Van Gogh\"\n        assert response.data[\"topic\"] == \"Art\"  # unchanged\n\n        art_project.refresh_from_db()\n        assert art_project.artist == \"Van Gogh\"\n        assert art_project.topic == \"Art\"\n\n    def test_partial_update_research_project(self, client, research_project):\n        data = {\"supervisor\": \"Dr. Williams\"}\n        response = client.patch(\n            f\"/examples/integrations/drf/projects/{research_project.pk}/\",\n            data,\n            format=\"json\",\n        )\n        assert response.status_code == 200\n        assert response.data[\"supervisor\"] == \"Dr. Williams\"\n        assert response.data[\"topic\"] == \"Research\"  # unchanged\n\n        research_project.refresh_from_db()\n        assert research_project.supervisor == \"Dr. Williams\"\n        assert research_project.topic == \"Research\"\n\n    def test_delete_project(self, client, base_project):\n        project_id = base_project.pk\n        response = client.delete(\n            f\"/examples/integrations/drf/projects/{project_id}/\"\n        )\n        assert response.status_code == 204\n\n        assert not Project.objects.filter(pk=project_id).exists()\n\n    def test_create_with_invalid_resourcetype(self, client):\n        data = {\"topic\": \"Test\", \"resourcetype\": \"InvalidType\"}\n        response = client.post(\n            \"/examples/integrations/drf/projects/\", data, format=\"json\"\n        )\n        assert response.status_code == 400\n\n\nclass TestDjangoFiltersViewSet:\n    \"\"\"Test django-filter integration with polymorphic models (issue #520).\"\"\"\n\n    @pytest.fixture\n    def client(self):\n        return APIClient()\n\n    @pytest.fixture\n    def user(self, django_user_model):\n        return django_user_model.objects.create_user(\n            username=\"testuser\", password=\"testpass\"\n        )\n\n    @pytest.fixture\n    def user_annotator(self, user):\n        from .models import UserAnnotator\n\n        return UserAnnotator.objects.create(user=user)\n\n    @pytest.fixture\n    def ai_annotator_gpt4(self):\n        from .models import AiModelAnnotator\n\n        return AiModelAnnotator.objects.create(ai_model=\"gpt-4\", version=\"1.0\")\n\n    @pytest.fixture\n    def ai_annotator_claude(self):\n        from .models import AiModelAnnotator\n\n        return AiModelAnnotator.objects.create(\n            ai_model=\"claude-3\", version=\"2.0\"\n        )\n\n    @pytest.fixture\n    def data_by_user(self, user_annotator):\n        from .models import Data\n\n        return Data.objects.create(annotator=user_annotator)\n\n    @pytest.fixture\n    def data_by_gpt4(self, ai_annotator_gpt4):\n        from .models import Data\n\n        return Data.objects.create(annotator=ai_annotator_gpt4)\n\n    @pytest.fixture\n    def data_by_claude(self, ai_annotator_claude):\n        from .models import Data\n\n        return Data.objects.create(annotator=ai_annotator_claude)\n\n    def test_list_all_annotations(\n        self, client, data_by_user, data_by_gpt4, data_by_claude\n    ):\n        \"\"\"Test listing all annotation data without filters.\"\"\"\n        response = client.get(\"/examples/integrations/drf/annotations/\")\n        assert response.status_code == 200\n        assert len(response.data) == 3\n\n    def test_filter_by_annotator(\n        self, client, data_by_user, data_by_gpt4, ai_annotator_gpt4\n    ):\n        \"\"\"Test filtering by annotator ID.\"\"\"\n        response = client.get(\n            f\"/examples/integrations/drf/annotations/?annotator={ai_annotator_gpt4.pk}\"\n        )\n        assert response.status_code == 200\n        assert len(response.data) == 1\n        assert response.data[0][\"id\"] == data_by_gpt4.pk\n\n    def test_filter_by_ai_model(\n        self, client, data_by_user, data_by_gpt4, data_by_claude\n    ):\n        \"\"\"Test filtering by annotator__ai_model field (issue #520).\"\"\"\n        # This is the key test - filtering by a field on the polymorphic child model\n        response = client.get(\n            \"/examples/integrations/drf/annotations/?annotator__ai_model=gpt-4\"\n        )\n        assert response.status_code == 200\n        assert len(response.data) == 1\n        assert response.data[0][\"id\"] == data_by_gpt4.pk\n\n    def test_filter_by_different_ai_model(\n        self, client, data_by_user, data_by_gpt4, data_by_claude\n    ):\n        \"\"\"Test filtering by a different AI model.\"\"\"\n        response = client.get(\n            \"/examples/integrations/drf/annotations/?annotator__ai_model=claude-3\"\n        )\n        assert response.status_code == 200\n        assert len(response.data) == 1\n        assert response.data[0][\"id\"] == data_by_claude.pk\n\n    def test_filter_by_nonexistent_ai_model(\n        self, client, data_by_user, data_by_gpt4, data_by_claude\n    ):\n        \"\"\"Test filtering by an AI model that doesn't exist.\"\"\"\n        response = client.get(\n            \"/examples/integrations/drf/annotations/?annotator__ai_model=nonexistent\"\n        )\n        assert response.status_code == 200\n        assert len(response.data) == 0\n\n    def test_filter_excludes_non_ai_annotators(\n        self, client, data_by_user, data_by_gpt4, data_by_claude\n    ):\n        \"\"\"Test that filtering by ai_model excludes UserAnnotator instances.\"\"\"\n        # When filtering by annotator__ai_model, only AiModelAnnotator results should be returned\n        # UserAnnotator doesn't have ai_model field, so data_by_user should not appear\n        response = client.get(\n            \"/examples/integrations/drf/annotations/?annotator__ai_model=gpt-4\"\n        )\n        assert response.status_code == 200\n        assert len(response.data) == 1\n        # Verify the user-annotated data is not in results\n        assert all(item[\"id\"] != data_by_user.pk for item in response.data)\n\n    def test_retrieve_annotation(self, client, data_by_gpt4):\n        \"\"\"Test retrieving a single annotation.\"\"\"\n        response = client.get(\n            f\"/examples/integrations/drf/annotations/{data_by_gpt4.pk}/\"\n        )\n        assert response.status_code == 200\n        assert response.data[\"id\"] == data_by_gpt4.pk\n\n    def test_create_annotation_with_user_annotator(\n        self, client, user_annotator\n    ):\n        \"\"\"Test creating annotation data with a UserAnnotator.\"\"\"\n        data = {\"annotator\": user_annotator.pk}\n        response = client.post(\n            \"/examples/integrations/drf/annotations/\", data, format=\"json\"\n        )\n        assert response.status_code == 201\n        assert response.data[\"annotator\"] == user_annotator.pk\n\n        from .models import Data\n\n        assert Data.objects.count() == 1\n        created = Data.objects.first()\n        assert created.annotator.pk == user_annotator.pk\n\n    def test_create_annotation_with_ai_annotator(\n        self, client, ai_annotator_gpt4\n    ):\n        \"\"\"Test creating annotation data with an AiModelAnnotator.\"\"\"\n        data = {\"annotator\": ai_annotator_gpt4.pk}\n        response = client.post(\n            \"/examples/integrations/drf/annotations/\", data, format=\"json\"\n        )\n        assert response.status_code == 201\n        assert response.data[\"annotator\"] == ai_annotator_gpt4.pk\n\n        from .models import Data\n\n        assert Data.objects.count() == 1\n        created = Data.objects.first()\n        assert created.annotator.pk == ai_annotator_gpt4.pk\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/drf/urls.py",
    "content": "from rest_framework.routers import DefaultRouter\n\nfrom .views import ProjectViewSet\nfrom .filter_views import AnnotationTrainingViewSet\n\napp_name = \"drf\"\n\nrouter = DefaultRouter()\nrouter.register(r\"projects\", ProjectViewSet)\nrouter.register(r\"annotations\", AnnotationTrainingViewSet)\n\nurlpatterns = router.urls\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/drf/views.py",
    "content": "from rest_framework import viewsets\n\nfrom .models import Project\nfrom .example_serializers import ProjectPolymorphicSerializer\n\n\nclass ProjectViewSet(viewsets.ModelViewSet):\n    queryset = Project.objects.all()\n    serializer_class = ProjectPolymorphicSerializer\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/extra_views/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/extra_views/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass ExtraViewsExampleConfig(AppConfig):\n    name = \"polymorphic.tests.examples.integrations.extra_views\"\n    label = \"extra_views\"\n    verbose_name = \"django-extra-views Integration Example\"\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/extra_views/templates/extra_views/article_formset.html",
    "content": "{% load extra_views_tags %}\n<!DOCTYPE html>\n<html>\n<head>\n    <title>Article Formset</title>\n</head>\n<body>\n    <h1>Article Formset</h1>\n    <form method=\"post\">\n        {% csrf_token %}\n        {{ formset.management_form }}\n        {% for form in formset %}\n            <div class=\"formset-form\">\n                <h3>{{ form.instance|model_name }}</h3>\n                {{ form.as_p }}\n            </div>\n        {% endfor %}\n        <button type=\"submit\">Save</button>\n    </form>\n</body>\n</html>\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/extra_views/templatetags/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/extra_views/templatetags/extra_views_tags.py",
    "content": "from django import template\n\nregister = template.Library()\n\n\n@register.filter\ndef model_name(instance):\n    \"\"\"Get the model class name of an instance.\"\"\"\n    return instance._meta.verbose_name.title()\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/extra_views/test.py",
    "content": "from unittest import skipUnless\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.test import TestCase, override_settings\nfrom django.urls import reverse\nfrom playwright.sync_api import expect\n\ntry:\n    import extra_views\n\n    EXTRA_VIEWS_INSTALLED = True\nexcept ImportError:\n    EXTRA_VIEWS_INSTALLED = False\n\nfrom ..models import Article, BlogPost, NewsArticle\nfrom polymorphic.tests.utils import _GenericUITest\n\n\n@skipUnless(EXTRA_VIEWS_INSTALLED, \"django-extra-views is not installed\")\nclass ExtraViewsIntegrationTests(TestCase):\n    \"\"\"\n    Tests for django-extra-views integration with polymorphic models.\n\n    These tests verify that:\n    1. PolymorphicFormSetView works with polymorphic models\n    2. Formsets can create and update polymorphic child instances\n    3. Child forms are correctly rendered for different model types\n    \"\"\"\n\n    def test_formset_children_configuration(self):\n        \"\"\"Test that formset children are properly configured.\"\"\"\n        from .views import ArticleFormSetView\n\n        view = ArticleFormSetView()\n        children = view.get_formset_children()\n\n        self.assertEqual(len(children), 2)\n        self.assertEqual(children[0].model, BlogPost)\n        self.assertEqual(children[1].model, NewsArticle)\n\n    def test_formset_generation(self):\n        \"\"\"Test that the formset is properly generated with child forms.\"\"\"\n        from .views import ArticleFormSetView\n\n        view = ArticleFormSetView()\n        formset_class = view.get_formset()\n\n        # Verify child_forms are attached\n        self.assertTrue(hasattr(formset_class, \"child_forms\"))\n        self.assertEqual(len(formset_class.child_forms), 2)\n        self.assertIn(BlogPost, formset_class.child_forms)\n        self.assertIn(NewsArticle, formset_class.child_forms)\n\n    def test_formset_with_existing_objects(self):\n        \"\"\"Test formset with existing polymorphic objects.\"\"\"\n        # Create test objects\n        blog_post = BlogPost.objects.create(\n            title=\"Test Blog\", content=\"Blog content\", author=\"Blog Author\"\n        )\n        news_article = NewsArticle.objects.create(\n            title=\"Test News\", content=\"News content\", source=\"News Source\"\n        )\n\n        from .views import ArticleFormSetView\n\n        view = ArticleFormSetView()\n        formset_class = view.get_formset()\n\n        # Create formset instance with queryset\n        formset = formset_class(queryset=Article.objects.all())\n\n        # Verify formset contains both objects\n        self.assertEqual(len(formset.forms), 4)\n\n        # Verify polymorphic types are preserved\n        form_models = [form.instance.__class__ for form in formset.forms]\n        self.assertIn(BlogPost, form_models)\n        self.assertIn(NewsArticle, form_models)\n\n    def test_formset_extra_forms_configuration(self):\n        \"\"\"Test that formset generates correct extra forms when empty.\"\"\"\n        from .views import ArticleFormSetView\n\n        # Ensure no objects exist\n        Article.objects.all().delete()\n\n        view = ArticleFormSetView()\n        formset_class = view.get_formset()\n\n        # Create empty formset\n        formset = formset_class(queryset=Article.objects.none())\n\n        # Verify we have 2 extra forms (as configured in factory_kwargs)\n        self.assertEqual(len(formset.forms), 2)\n\n        # Verify the forms cycle through child types\n        # Form 0 should be BlogPost (first child)\n        self.assertEqual(formset.forms[0]._meta.model, BlogPost)\n\n        # Form 1 should be NewsArticle (second child)\n        self.assertEqual(formset.forms[1]._meta.model, NewsArticle)\n\n    def test_formset_saving_new_objects(self):\n        \"\"\"Test creating new polymorphic objects through formset.\"\"\"\n        from .views import ArticleFormSetView\n\n        view = ArticleFormSetView()\n        formset_class = view.get_formset()\n\n        # Create formset with POST data for new objects\n        data = {\n            \"form-TOTAL_FORMS\": \"2\",\n            \"form-INITIAL_FORMS\": \"0\",\n            \"form-MIN_NUM_FORMS\": \"0\",\n            \"form-MAX_NUM_FORMS\": \"1000\",\n            # BlogPost\n            \"form-0-polymorphic_ctype\": str(\n                ContentType.objects.get_for_model(\n                    BlogPost, for_concrete_model=False\n                ).pk\n            ),\n            \"form-0-title\": \"New Blog Post\",\n            \"form-0-content\": \"Blog post content\",\n            \"form-0-author\": \"Blog Author\",\n            # NewsArticle\n            \"form-1-polymorphic_ctype\": str(\n                ContentType.objects.get_for_model(\n                    NewsArticle, for_concrete_model=False\n                ).pk\n            ),\n            \"form-1-title\": \"New News Article\",\n            \"form-1-content\": \"News article content\",\n            \"form-1-source\": \"News Source\",\n        }\n\n        formset = formset_class(data)\n\n        # Verify formset is valid\n        self.assertTrue(formset.is_valid(), formset.errors)\n\n        # Save the formset\n        instances = formset.save()\n\n        # Verify objects were created\n        self.assertEqual(len(instances), 2)\n        self.assertEqual(Article.objects.count(), 2)\n\n        # Verify polymorphic types\n        blog_post = BlogPost.objects.get(title=\"New Blog Post\")\n        self.assertEqual(blog_post.author, \"Blog Author\")\n\n        news_article = NewsArticle.objects.get(title=\"New News Article\")\n        self.assertEqual(news_article.source, \"News Source\")\n\n    def test_formset_updating_objects(self):\n        \"\"\"Test updating existing polymorphic objects through formset.\"\"\"\n        # Create existing objects\n        blog_post = BlogPost.objects.create(\n            title=\"Original Blog\",\n            content=\"Original content\",\n            author=\"Original Author\",\n        )\n\n        from .views import ArticleFormSetView\n\n        view = ArticleFormSetView()\n        formset_class = view.get_formset()\n\n        # Create formset with update data\n        data = {\n            \"form-TOTAL_FORMS\": \"1\",\n            \"form-INITIAL_FORMS\": \"1\",\n            \"form-MIN_NUM_FORMS\": \"0\",\n            \"form-MAX_NUM_FORMS\": \"1000\",\n            \"form-0-id\": str(blog_post.pk),\n            \"form-0-polymorphic_ctype\": str(\n                ContentType.objects.get_for_model(\n                    BlogPost, for_concrete_model=False\n                ).pk\n            ),\n            \"form-0-title\": \"Updated Blog\",\n            \"form-0-content\": \"Updated content\",\n            \"form-0-author\": \"Updated Author\",\n        }\n\n        formset = formset_class(data, queryset=Article.objects.all())\n\n        # Verify formset is valid\n        self.assertTrue(formset.is_valid(), formset.errors)\n\n        # Save the formset\n        formset.save()\n\n        # Verify object was updated\n        blog_post.refresh_from_db()\n        self.assertEqual(blog_post.title, \"Updated Blog\")\n        self.assertEqual(blog_post.author, \"Updated Author\")\n\n    def test_formset_deleting_objects(self):\n        \"\"\"Test deleting polymorphic objects through formset.\"\"\"\n        # Create object to delete\n        blog_post = BlogPost.objects.create(\n            title=\"To Delete\", content=\"Content\", author=\"Author\"\n        )\n\n        from .views import ArticleFormSetView\n\n        view = ArticleFormSetView()\n        formset_class = view.get_formset()\n\n        # Create formset with delete data\n        data = {\n            \"form-TOTAL_FORMS\": \"1\",\n            \"form-INITIAL_FORMS\": \"1\",\n            \"form-MIN_NUM_FORMS\": \"0\",\n            \"form-MAX_NUM_FORMS\": \"1000\",\n            \"form-0-id\": str(blog_post.pk),\n            \"form-0-polymorphic_ctype\": str(\n                ContentType.objects.get_for_model(\n                    BlogPost, for_concrete_model=False\n                ).pk\n            ),\n            \"form-0-title\": blog_post.title,\n            \"form-0-content\": blog_post.content,\n            \"form-0-author\": blog_post.author,\n            \"form-0-DELETE\": \"on\",\n        }\n\n        formset = formset_class(data, queryset=Article.objects.all())\n\n        # Verify formset is valid\n        self.assertTrue(formset.is_valid(), formset.errors)\n\n        # Save the formset\n        formset.save()\n\n        # Verify object was deleted\n        self.assertFalse(BlogPost.objects.filter(pk=blog_post.pk).exists())\n\n    def test_formset_mixed_operations(self):\n        \"\"\"Test creating, updating, and deleting in single formset submission.\"\"\"\n        # Create existing object to update\n        blog_post = BlogPost.objects.create(\n            title=\"Existing Blog\",\n            content=\"Existing content\",\n            author=\"Existing Author\",\n        )\n\n        # Create object to delete\n        news_to_delete = NewsArticle.objects.create(\n            title=\"To Delete\",\n            content=\"Delete content\",\n            source=\"Delete Source\",\n        )\n\n        from .views import ArticleFormSetView\n\n        view = ArticleFormSetView()\n        formset_class = view.get_formset()\n\n        data = {\n            \"form-TOTAL_FORMS\": \"3\",\n            \"form-INITIAL_FORMS\": \"2\",\n            \"form-MIN_NUM_FORMS\": \"0\",\n            \"form-MAX_NUM_FORMS\": \"1000\",\n            # Update existing blog post\n            \"form-0-id\": str(blog_post.pk),\n            \"form-0-polymorphic_ctype\": str(\n                ContentType.objects.get_for_model(\n                    BlogPost, for_concrete_model=False\n                ).pk\n            ),\n            \"form-0-title\": \"Updated Existing Blog\",\n            \"form-0-content\": \"Updated content\",\n            \"form-0-author\": \"Updated Author\",\n            # Delete news article\n            \"form-1-id\": str(news_to_delete.pk),\n            \"form-1-polymorphic_ctype\": str(\n                ContentType.objects.get_for_model(\n                    NewsArticle, for_concrete_model=False\n                ).pk\n            ),\n            \"form-1-title\": news_to_delete.title,\n            \"form-1-content\": news_to_delete.content,\n            \"form-1-source\": news_to_delete.source,\n            \"form-1-DELETE\": \"on\",\n            # Create new blog post\n            \"form-2-polymorphic_ctype\": str(\n                ContentType.objects.get_for_model(\n                    BlogPost, for_concrete_model=False\n                ).pk\n            ),\n            \"form-2-title\": \"New Blog Post\",\n            \"form-2-content\": \"New content\",\n            \"form-2-author\": \"New Author\",\n        }\n\n        formset = formset_class(data, queryset=Article.objects.all())\n        self.assertTrue(formset.is_valid(), formset.errors)\n        formset.save()\n\n        # Verify update\n        blog_post.refresh_from_db()\n        self.assertEqual(blog_post.title, \"Updated Existing Blog\")\n\n        # Verify deletion\n        self.assertFalse(\n            NewsArticle.objects.filter(pk=news_to_delete.pk).exists()\n        )\n\n        # Verify creation\n        new_blog = BlogPost.objects.get(title=\"New Blog Post\")\n        self.assertEqual(new_blog.author, \"New Author\")\n\n        # Verify total count\n        self.assertEqual(Article.objects.count(), 2)\n\n\n@skipUnless(EXTRA_VIEWS_INSTALLED, \"django-extra-views is not installed\")\nclass ExtraViewsUITests(TestCase):\n    \"\"\"Test django-extra-views functionality through the Client API.\"\"\"\n\n    def test_formset_view_get_request(self):\n        \"\"\"Test that the formset view handles GET requests correctly.\"\"\"\n        from .views import ArticleFormSetView\n\n        # Create some existing objects\n        BlogPost.objects.create(\n            title=\"Existing Blog\", content=\"Content\", author=\"Author\"\n        )\n        NewsArticle.objects.create(\n            title=\"Existing News\", content=\"Content\", source=\"Source\"\n        )\n\n        view = ArticleFormSetView.as_view()\n        from django.test import RequestFactory\n\n        factory = RequestFactory()\n        request = factory.get(\"/articles/\")\n        response = view(request)\n        response.render()\n\n        # Verify response\n        self.assertEqual(response.status_code, 200)\n        self.assertIn(b\"Article Formset\", response.content)\n\n    def test_formset_view_post_request(self):\n        \"\"\"Test creating new polymorphic objects through POST request.\"\"\"\n        from .views import ArticleFormSetView\n\n        view = ArticleFormSetView.as_view()\n        from django.test import RequestFactory\n\n        factory = RequestFactory()\n\n        # Create POST data\n        data = {\n            \"form-TOTAL_FORMS\": \"1\",\n            \"form-INITIAL_FORMS\": \"0\",\n            \"form-MIN_NUM_FORMS\": \"0\",\n            \"form-MAX_NUM_FORMS\": \"1000\",\n            \"form-0-polymorphic_ctype\": str(\n                ContentType.objects.get_for_model(\n                    BlogPost, for_concrete_model=False\n                ).pk\n            ),\n            \"form-0-title\": \"New Blog Post\",\n            \"form-0-content\": \"Blog content\",\n            \"form-0-author\": \"Author Name\",\n        }\n\n        request = factory.post(\"/articles/\", data)\n        response = view(request)\n\n        # Verify redirect on success\n        self.assertEqual(response.status_code, 302)\n\n        # Verify object was created\n        blog_post = BlogPost.objects.get(title=\"New Blog Post\")\n        self.assertEqual(blog_post.author, \"Author Name\")\n\n\n@skipUnless(EXTRA_VIEWS_INSTALLED, \"django-extra-views is not installed\")\nclass ExtraViewsLiveServerTests(_GenericUITest):\n    \"\"\"Test django-extra-views functionality through the live test server.\"\"\"\n\n    def setUp(self):\n        \"\"\"Create a page without admin login (not needed for formset view).\"\"\"\n        self.page = self.browser.new_page()\n\n    def tearDown(self):\n        \"\"\"Close the page after each test.\"\"\"\n        if self.page:\n            self.page.close()\n\n    def test_formset_view_renders_existing_objects(self):\n        \"\"\"Test that existing polymorphic objects are rendered in the formset.\"\"\"\n        # Create existing objects\n        blog_post = BlogPost.objects.create(\n            title=\"Existing Blog\",\n            content=\"Blog content\",\n            author=\"Blog Author\",\n        )\n        news_article = NewsArticle.objects.create(\n            title=\"Existing News\",\n            content=\"News content\",\n            source=\"News Source\",\n        )\n\n        # Navigate to formset view\n        url = f\"{self.live_server_url}{reverse('extra_views:articles')}\"\n        self.page.goto(url)\n\n        # Verify page loaded\n        expect(self.page.locator(\"h1\")).to_contain_text(\"Article Formset\")\n\n        # Verify form is rendered\n        form = self.page.locator(\"form\")\n        expect(form).to_be_visible()\n\n        # Verify existing objects are shown in the formset\n        # The formset should have inputs for the existing objects\n        expect(\n            self.page.locator(f\"input[value='{blog_post.title}']\")\n        ).to_be_visible()\n        expect(\n            self.page.locator(f\"input[value='{news_article.title}']\")\n        ).to_be_visible()\n\n    def test_formset_update_existing_object(self):\n        \"\"\"Test updating an existing object through the formset.\"\"\"\n        # Create an object to update\n        blog_post = BlogPost.objects.create(\n            title=\"Original Title\",\n            content=\"Original content\",\n            author=\"Original Author\",\n        )\n\n        # Navigate to formset view\n        url = f\"{self.live_server_url}{reverse('extra_views:articles')}\"\n        self.page.goto(url)\n\n        # Find the title input for this blog post\n        # The form should have a hidden id field and visible title field\n        title_input = self.page.locator(\n            f\"input[value='{blog_post.title}']\"\n        ).first\n        title_input.fill(\"Updated Title\")\n\n        # Find and update the author field\n        # The author field should be in the same form container\n        author_input = self.page.locator(\n            f\"input[value='{blog_post.author}']\"\n        ).first\n        author_input.fill(\"Updated Author\")\n\n        # Submit the form\n        with self.page.expect_navigation(timeout=30000):\n            self.page.locator(\"button[type='submit']\").click()\n\n        # Verify the object was updated\n        blog_post.refresh_from_db()\n        self.assertEqual(blog_post.title, \"Updated Title\")\n        self.assertEqual(blog_post.author, \"Updated Author\")\n\n    def test_formset_delete_existing_object(self):\n        \"\"\"Test deleting an existing object through the formset.\"\"\"\n        # Create an object to delete\n        blog_post = BlogPost.objects.create(\n            title=\"To Delete\",\n            content=\"Delete content\",\n            author=\"Delete Author\",\n        )\n        blog_post_id = blog_post.id\n\n        # Navigate to formset view\n        url = f\"{self.live_server_url}{reverse('extra_views:articles')}\"\n        self.page.goto(url)\n\n        # Find the DELETE checkbox for this object\n        # Django formsets add a DELETE checkbox for each form when can_delete=True\n        delete_checkbox = self.page.locator(\n            \"input[type='checkbox'][name*='DELETE']\"\n        ).first\n\n        # Check if the checkbox is visible, if not we need to handle it differently\n        if delete_checkbox.is_visible():\n            delete_checkbox.check()\n\n            # Submit the form\n            with self.page.expect_navigation(timeout=30000):\n                self.page.locator(\"button[type='submit']\").click()\n\n            # Verify the object was deleted\n            self.assertFalse(BlogPost.objects.filter(id=blog_post_id).exists())\n        else:\n            # If DELETE checkboxes aren't visible, skip this test\n            self.skipTest(\"DELETE checkboxes not visible in UI\")\n\n    def test_formset_displays_multiple_polymorphic_types(self):\n        \"\"\"Test that the formset correctly displays forms for different polymorphic types.\"\"\"\n        # Create instances of both child models\n        blog_post = BlogPost.objects.create(\n            title=\"Blog Post\", content=\"Blog content\", author=\"Blog Author\"\n        )\n        news_article = NewsArticle.objects.create(\n            title=\"News Article\",\n            content=\"News content\",\n            source=\"News Source\",\n        )\n\n        # Navigate to formset view\n        url = f\"{self.live_server_url}{reverse('extra_views:articles')}\"\n        self.page.goto(url)\n\n        # Verify both types are displayed\n        # Blog post should have author field\n        expect(\n            self.page.locator(f\"input[value='{blog_post.author}']\")\n        ).to_be_visible()\n\n        # News article should have source field\n        expect(\n            self.page.locator(f\"input[value='{news_article.source}']\")\n        ).to_be_visible()\n\n        # Verify the formset contains both forms\n        # Each form should have an id field\n        id_inputs = self.page.locator(\"input[type='hidden'][name$='-id']\").all()\n        self.assertGreaterEqual(len(id_inputs), 2)\n\n    def test_formset_mixed_operations_through_ui(self):\n        \"\"\"Test creating, updating, and deleting in a single formset submission.\"\"\"\n        # Create objects\n        blog_to_update = BlogPost.objects.create(\n            title=\"Update Me\",\n            content=\"Update content\",\n            author=\"Update Author\",\n        )\n        news_to_delete = NewsArticle.objects.create(\n            title=\"Delete Me\",\n            content=\"Delete content\",\n            source=\"Delete Source\",\n        )\n        news_to_delete_id = news_to_delete.id\n\n        # Navigate to formset view\n        url = f\"{self.live_server_url}{reverse('extra_views:articles')}\"\n        self.page.goto(url)\n\n        # Update the blog post\n        title_input = self.page.locator(\n            f\"input[value='{blog_to_update.title}']\"\n        ).first\n        title_input.fill(\"Updated Blog Title\")\n\n        # Try to delete the news article\n        delete_checkboxes = self.page.locator(\n            \"input[type='checkbox'][name*='DELETE']\"\n        ).all()\n        if delete_checkboxes:\n            # Check the second DELETE checkbox (for the news article)\n            if len(delete_checkboxes) > 1:\n                delete_checkboxes[1].check()\n\n        # Submit the form\n        with self.page.expect_navigation(timeout=30000):\n            self.page.locator(\"button[type='submit']\").click()\n\n        # Verify the blog post was updated\n        blog_to_update.refresh_from_db()\n        self.assertEqual(blog_to_update.title, \"Updated Blog Title\")\n\n        # Verify the news article was deleted (if DELETE checkbox was available)\n        if delete_checkboxes and len(delete_checkboxes) > 1:\n            self.assertFalse(\n                NewsArticle.objects.filter(id=news_to_delete_id).exists()\n            )\n\n    def test_formset_empty_state_shows_extra_forms(self):\n        \"\"\"Test that the formset shows extra forms for adding new objects.\"\"\"\n        # Ensure no objects exist\n        Article.objects.all().delete()\n\n        # Navigate to formset view\n        url = f\"{self.live_server_url}{reverse('extra_views:articles')}\"\n        self.page.goto(url)\n\n        # Verify page loaded\n        expect(self.page.locator(\"h1\")).to_contain_text(\"Article Formset\")\n\n        # Verify form is rendered\n        form = self.page.locator(\"form\")\n        expect(form).to_be_visible()\n\n        # The formset should have management form fields (they're hidden)\n        self.assertIsNotNone(\n            self.page.locator(\"input[name='form-TOTAL_FORMS']\").first\n        )\n        self.assertIsNotNone(\n            self.page.locator(\"input[name='form-INITIAL_FORMS']\").first\n        )\n\n        # Verify we have 2 extra forms (one for each child type)\n        formset_forms = self.page.locator(\".formset-form\").all()\n        self.assertEqual(len(formset_forms), 2, \"Should have 2 extra forms\")\n\n        # Verify we have headers indicating the form types\n        expect(self.page.locator(\"h3:has-text('Blog Post')\")).to_be_visible()\n        expect(self.page.locator(\"h3:has-text('News Article')\")).to_be_visible()\n\n        # Verify BlogPost form has author field\n        expect(self.page.locator(\"input[name='form-0-author']\")).to_be_visible()\n\n        # Verify NewsArticle form has source field\n        expect(self.page.locator(\"input[name='form-1-source']\")).to_be_visible()\n\n    def test_formset_create_new_objects_via_extra_forms(self):\n        \"\"\"Test creating new objects using the extra forms in the UI.\"\"\"\n        # Ensure no objects exist\n        Article.objects.all().delete()\n\n        # Navigate to formset view\n        url = f\"{self.live_server_url}{reverse('extra_views:articles')}\"\n        self.page.goto(url)\n\n        # Fill in the BlogPost form (form-0)\n        self.page.locator(\"input[name='form-0-title']\").fill(\"New Blog from UI\")\n        self.page.locator(\"textarea[name='form-0-content']\").fill(\n            \"Blog content from UI\"\n        )\n        self.page.locator(\"input[name='form-0-author']\").fill(\"UI Author\")\n\n        # Fill in the NewsArticle form (form-1)\n        self.page.locator(\"input[name='form-1-title']\").fill(\"New News from UI\")\n        self.page.locator(\"textarea[name='form-1-content']\").fill(\n            \"News content from UI\"\n        )\n        self.page.locator(\"input[name='form-1-source']\").fill(\"UI Source\")\n\n        # Submit the form\n        with self.page.expect_navigation(timeout=30000):\n            self.page.locator(\"button[type='submit']\").click()\n\n        # Verify objects were created\n        self.assertEqual(Article.objects.count(), 2)\n\n        # Verify BlogPost was created\n        blog_post = BlogPost.objects.get(title=\"New Blog from UI\")\n        self.assertEqual(blog_post.author, \"UI Author\")\n\n        # Verify NewsArticle was created\n        news_article = NewsArticle.objects.get(title=\"New News from UI\")\n        self.assertEqual(news_article.source, \"UI Source\")\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/extra_views/urls.py",
    "content": "from django.urls import path\nfrom .views import ArticleFormSetView\n\napp_name = \"extra_views\"\n\nurlpatterns = [\n    path(\"articles/\", ArticleFormSetView.as_view(), name=\"articles\"),\n]\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/extra_views/views.py",
    "content": "from polymorphic.contrib.extra_views import PolymorphicFormSetView\nfrom polymorphic.formsets import PolymorphicFormSetChild\nfrom django.urls import reverse_lazy\nfrom ..models import Article, BlogPost, NewsArticle\n\n\nclass ArticleFormSetView(PolymorphicFormSetView):\n    model = Article\n    template_name = \"extra_views/article_formset.html\"\n    success_url = reverse_lazy(\"extra_views:articles\")\n    fields = \"__all__\"\n\n    # extra will add two empty forms for models in the order of their appearance\n    # in formset_children\n    factory_kwargs = {\"extra\": 2, \"can_delete\": True}\n\n    formset_children = [\n        PolymorphicFormSetChild(BlogPost, fields=\"__all__\"),\n        PolymorphicFormSetChild(NewsArticle, fields=\"__all__\"),\n    ]\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/guardian/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/guardian/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass GuardianExampleConfig(AppConfig):\n    name = \"polymorphic.tests.examples.integrations.guardian\"\n    label = \"guardian_integration\"\n    verbose_name = \"django-guardian Integration Example\"\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/guardian/test.py",
    "content": "from unittest import skipUnless\nfrom django.contrib.auth import get_user_model\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.test import TestCase\n\ntry:\n    import guardian  # noqa: F401\n\n    GUARDIAN_INSTALLED = True\nexcept ImportError:\n    GUARDIAN_INSTALLED = False\n\nfrom ..models import Article, BlogPost, NewsArticle\nfrom ....models import PlainD\nfrom polymorphic.contrib.guardian import get_polymorphic_base_content_type\n\n\nUser = get_user_model()\n\n\n@skipUnless(GUARDIAN_INSTALLED, \"django-guardian is not installed\")\nclass GuardianIntegrationTests(TestCase):\n    \"\"\"\n    Tests for django-guardian integration with polymorphic models.\n\n    These tests verify that get_polymorphic_base_content_type returns the correct\n    base content type for polymorphic models, which is essential for django-guardian\n    to work correctly with polymorphic inheritance.\n\n    When configured with GUARDIAN_GET_CONTENT_TYPE pointing to this function,\n    django-guardian will assign permissions to the base polymorphic model rather\n    than the specific child model, ensuring consistent permission handling across\n    the polymorphic hierarchy.\n    \"\"\"\n\n    def setUp(self):\n        \"\"\"Create test objects.\"\"\"\n        self.user = User.objects.create_user(\n            username=\"testuser\", password=\"testpass\"\n        )\n        self.blog_post = BlogPost.objects.create(\n            title=\"Test Blog\", content=\"Blog content\", author=\"Blog Author\"\n        )\n        self.news_article = NewsArticle.objects.create(\n            title=\"Test News\", content=\"News content\", source=\"News Source\"\n        )\n\n    def test_non_polymorphic_model(self):\n        \"\"\"Test that non-polymorphic models return their own content type.\"\"\"\n        plain_d = PlainD.objects.create(field1=\"Plain D\", field2=\"Plain D\")\n        ctype = get_polymorphic_base_content_type(plain_d)\n\n        expected_ctype = ContentType.objects.get_for_model(PlainD)\n        self.assertEqual(ctype, expected_ctype)\n        self.assertEqual(ctype.model, \"plaind\")\n\n    def test_get_polymorphic_base_content_type_for_child_model(self):\n        \"\"\"Test that get_polymorphic_base_content_type returns base type for child models.\"\"\"\n        # Get content type for BlogPost instance\n        blog_ctype = get_polymorphic_base_content_type(self.blog_post)\n\n        # Should return Article content type, not BlogPost\n        article_ctype = ContentType.objects.get_for_model(\n            Article, for_concrete_model=False\n        )\n        self.assertEqual(blog_ctype, article_ctype)\n        self.assertNotEqual(blog_ctype.model, \"blogpost\")\n        self.assertEqual(blog_ctype.model, \"article\")\n\n    def test_get_polymorphic_base_content_type_for_different_child_models(\n        self,\n    ):\n        \"\"\"Test that different child models return the same base content type.\"\"\"\n        blog_ctype = get_polymorphic_base_content_type(self.blog_post)\n        news_ctype = get_polymorphic_base_content_type(self.news_article)\n\n        # Both should return the same Article content type\n        self.assertEqual(blog_ctype, news_ctype)\n        self.assertEqual(blog_ctype.model, \"article\")\n\n    def test_get_polymorphic_base_content_type_for_base_model(self):\n        \"\"\"Test that base polymorphic models return their own content type.\"\"\"\n        article = Article.objects.create(\n            title=\"Plain Article\", content=\"Plain content\"\n        )\n        article_ctype = get_polymorphic_base_content_type(article)\n\n        expected_ctype = ContentType.objects.get_for_model(\n            Article, for_concrete_model=False\n        )\n        self.assertEqual(article_ctype, expected_ctype)\n        self.assertEqual(article_ctype.model, \"article\")\n\n    def test_get_polymorphic_base_content_type_for_non_polymorphic_model(\n        self,\n    ):\n        \"\"\"Test that non-polymorphic models return their own content type.\"\"\"\n        user_ctype = get_polymorphic_base_content_type(self.user)\n\n        expected_ctype = ContentType.objects.get_for_model(User)\n        self.assertEqual(user_ctype, expected_ctype)\n\n    def test_get_polymorphic_base_content_type_with_model_class(self):\n        \"\"\"Test that the function works with model classes, not just instances.\"\"\"\n        # Test with a model class instead of instance\n        blog_class_ctype = get_polymorphic_base_content_type(BlogPost)\n\n        article_ctype = ContentType.objects.get_for_model(\n            Article, for_concrete_model=False\n        )\n        self.assertEqual(blog_class_ctype, article_ctype)\n\n    def test_content_type_consistency_across_inheritance_chain(self):\n        \"\"\"Test that content type is consistent across the inheritance chain.\"\"\"\n        # Create multiple levels if they exist\n        blog_ctype = get_polymorphic_base_content_type(self.blog_post)\n        news_ctype = get_polymorphic_base_content_type(self.news_article)\n\n        # All should point to the same base Article type\n        article_ctype = ContentType.objects.get_for_model(\n            Article, for_concrete_model=False\n        )\n        self.assertEqual(blog_ctype, article_ctype)\n        self.assertEqual(news_ctype, article_ctype)\n\n    def test_get_polymorphic_base_content_type_with_instance_and_class(\n        self,\n    ):\n        \"\"\"Test that the function works consistently with instances and classes.\"\"\"\n        # Get content type from instance\n        instance_ctype = get_polymorphic_base_content_type(self.blog_post)\n\n        # Get content type from class\n        class_ctype = get_polymorphic_base_content_type(BlogPost)\n\n        # Both should return the same Article content type\n        self.assertEqual(instance_ctype, class_ctype)\n        self.assertEqual(instance_ctype.model, \"article\")\n\n    def test_get_polymorphic_base_content_type_returns_content_type_object(\n        self,\n    ):\n        \"\"\"Test that the function returns a ContentType instance.\"\"\"\n        self.assertIsInstance(\n            get_polymorphic_base_content_type(self.blog_post), ContentType\n        )\n\n    def test_guardian_permissions_use_base_model_namespace(self):\n        \"\"\"\n        Test that guardian permissions are assigned to base model when configured.\n\n        This test verifies that when GUARDIAN_GET_CONTENT_TYPE is set to use\n        get_polymorphic_base_content_type, permissions assigned to child model\n        instances (BlogPost, NewsArticle) are stored against the base Article\n        content type, creating a single permission namespace for the entire\n        polymorphic model hierarchy.\n        \"\"\"\n        from guardian.shortcuts import assign_perm, remove_perm\n        from guardian.models import UserObjectPermission\n\n        # The setting is configured in settings.py when guardian is installed\n        # GUARDIAN_GET_CONTENT_TYPE = \"polymorphic.contrib.guardian...\"\n\n        # Assign permission to a BlogPost instance\n        assign_perm(\"add_article\", self.user, self.blog_post)\n\n        # Get the stored permission object\n        perm = UserObjectPermission.objects.filter(\n            user=self.user, object_pk=str(self.blog_post.pk)\n        ).first()\n\n        # Verify permission was created\n        self.assertIsNotNone(perm, \"Permission should be created\")\n\n        # The critical assertion: permission should use Article content type,\n        # NOT BlogPost content type\n        article_ctype = ContentType.objects.get_for_model(\n            Article, for_concrete_model=False\n        )\n        self.assertEqual(\n            perm.content_type,\n            article_ctype,\n            \"Permission should be stored against Article, not BlogPost\",\n        )\n\n        # Verify the permission codename is correct\n        self.assertEqual(perm.permission.codename, \"add_article\")\n\n        # Now assign permission to a NewsArticle instance\n        assign_perm(\"change_article\", self.user, self.news_article)\n\n        news_perm = UserObjectPermission.objects.filter(\n            user=self.user, object_pk=str(self.news_article.pk)\n        ).first()\n\n        # NewsArticle permission should ALSO use Article content type\n        self.assertEqual(\n            news_perm.content_type,\n            article_ctype,\n            \"NewsArticle permission should also use Article content type\",\n        )\n\n        # All permissions for this polymorphic tree should share the same\n        # content type, creating a unified permission namespace\n        all_perms = UserObjectPermission.objects.filter(user=self.user)\n        content_types = set(p.content_type for p in all_perms)\n\n        self.assertEqual(\n            len(content_types),\n            1,\n            \"All permissions should use the same Article content type\",\n        )\n        self.assertEqual(\n            content_types.pop(),\n            article_ctype,\n            \"The shared content type should be Article\",\n        )\n\n        # Clean up\n        remove_perm(\"add_article\", self.user, self.blog_post)\n        remove_perm(\"change_article\", self.user, self.news_article)\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/migrations/0001_initial.py",
    "content": "# Generated by Django 4.2 on 2026-02-05 14:47\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('contenttypes', '0002_remove_content_type_name'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='Article',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('title', models.CharField(max_length=100)),\n                ('content', models.TextField()),\n                ('created', models.DateTimeField(auto_now_add=True)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='BlogPost',\n            fields=[\n                ('article_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='integrations.article')),\n                ('author', models.CharField(max_length=100)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('integrations.article',),\n        ),\n        migrations.CreateModel(\n            name='NewsArticle',\n            fields=[\n                ('article_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='integrations.article')),\n                ('source', models.CharField(max_length=100)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('integrations.article',),\n        ),\n    ]\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/models.py",
    "content": "from django.db import models\nfrom polymorphic.models import PolymorphicModel\n\n\nclass Article(PolymorphicModel):\n    title = models.CharField(max_length=100)\n    content = models.TextField()\n    created = models.DateTimeField(auto_now_add=True)\n\n    def __str__(self):\n        return self.title\n\n\nclass BlogPost(Article):\n    author = models.CharField(max_length=100)\n\n\nclass NewsArticle(Article):\n    source = models.CharField(max_length=100)\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/reversion/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/reversion/admin.py",
    "content": "from django.contrib import admin\nfrom polymorphic.admin import (\n    PolymorphicParentModelAdmin,\n    PolymorphicChildModelAdmin,\n)\nfrom reversion.admin import VersionAdmin\nfrom ..models import Article, BlogPost, NewsArticle\n\n\nclass ArticleChildAdmin(PolymorphicChildModelAdmin, VersionAdmin):\n    base_model = Article\n\n\n@admin.register(BlogPost)\nclass BlogPostAdmin(ArticleChildAdmin):\n    pass\n\n\n@admin.register(NewsArticle)\nclass NewsArticleAdmin(ArticleChildAdmin):\n    pass\n\n\nclass ArticleParentAdmin(VersionAdmin, PolymorphicParentModelAdmin):\n    \"\"\"\n    Parent admin for Article model with reversion support.\n\n    Note: VersionAdmin must come before PolymorphicParentModelAdmin\n    in the inheritance order.\n    \"\"\"\n\n    base_model = Article\n    child_models = (BlogPost, NewsArticle)\n    list_display = (\"title\", \"created\")\n\n\nadmin.site.register(Article, ArticleParentAdmin)\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/reversion/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass ReversionExampleConfig(AppConfig):\n    name = \"polymorphic.tests.examples.integrations.reversion\"\n    label = \"reversion_example\"\n    verbose_name = \"Reversion Integration Example\"\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/reversion/templates/admin/polymorphic/object_history.html",
    "content": "{% extends 'reversion/object_history.html' %}\n{% load polymorphic_admin_tags %}\n\n{% block breadcrumbs %}\n    {% breadcrumb_scope base_opts %}{{ block.super }}{% endbreadcrumb_scope %}\n{% endblock %}\n"
  },
  {
    "path": "src/polymorphic/tests/examples/integrations/reversion/test.py",
    "content": "from unittest import skipUnless\nfrom django.test import TestCase\nfrom django.urls import reverse\nfrom playwright.sync_api import expect\n\ntry:\n    from reversion import revisions\n    from reversion.models import Version\n\n    REVERSION_INSTALLED = True\nexcept ImportError:\n    REVERSION_INSTALLED = False\n    revisions = None\n    Version = None\n\nfrom ..models import Article, BlogPost, NewsArticle\nfrom polymorphic.tests.utils import _GenericUITest\n\n\n@skipUnless(REVERSION_INSTALLED, \"django-reversion is not installed\")\nclass ReversionIntegrationTests(TestCase):\n    \"\"\"\n    Tests for django-reversion integration with polymorphic models.\n\n    These tests verify that:\n    1. Polymorphic models can be versioned\n    2. The follow parameter correctly tracks parent model changes\n    3. Revisions are created and can be retrieved\n    \"\"\"\n\n    def test_blogpost_versioning(self):\n        \"\"\"Test that BlogPost instances are properly versioned.\"\"\"\n        # Create a blog post with reversion\n        with revisions.create_revision():\n            blog_post = BlogPost.objects.create(\n                title=\"First Post\",\n                content=\"This is my first blog post.\",\n                author=\"John Doe\",\n            )\n            revisions.set_comment(\"Initial version\")\n\n        # Verify a version was created\n        versions = Version.objects.get_for_object(blog_post)\n        self.assertEqual(versions.count(), 1)\n        self.assertEqual(versions[0].revision.comment, \"Initial version\")\n\n        # Update the blog post\n        with revisions.create_revision():\n            blog_post.title = \"Updated Post\"\n            blog_post.content = \"This is my updated blog post.\"\n            blog_post.save()\n            revisions.set_comment(\"Updated title and content\")\n\n        # Verify we now have two versions\n        versions = Version.objects.get_for_object(blog_post)\n        self.assertEqual(versions.count(), 2)\n\n        # Verify we can retrieve the old version\n        old_version = versions[1]\n        old_data = old_version.field_dict\n        self.assertEqual(old_data[\"title\"], \"First Post\")\n        self.assertEqual(old_data[\"content\"], \"This is my first blog post.\")\n\n    def test_newsarticle_versioning(self):\n        \"\"\"Test that NewsArticle instances are properly versioned.\"\"\"\n        # Create a news article with reversion\n        with revisions.create_revision():\n            news_article = NewsArticle.objects.create(\n                title=\"Breaking News\",\n                content=\"Important news story.\",\n                source=\"Daily News\",\n            )\n            revisions.set_comment(\"Published article\")\n\n        # Verify a version was created\n        versions = Version.objects.get_for_object(news_article)\n        self.assertEqual(versions.count(), 1)\n\n        # Update the news article\n        with revisions.create_revision():\n            news_article.content = \"Updated news story with more details.\"\n            news_article.save()\n            revisions.set_comment(\"Added more details\")\n\n        # Verify we now have two versions\n        versions = Version.objects.get_for_object(news_article)\n        self.assertEqual(versions.count(), 2)\n\n    def test_polymorphic_queryset_with_versioned_objects(self):\n        \"\"\"Test that polymorphic queries work correctly with versioned objects.\"\"\"\n        # Create instances of both child models\n        with revisions.create_revision():\n            BlogPost.objects.create(\n                title=\"Blog Post\",\n                content=\"Blog content\",\n                author=\"Jane Smith\",\n            )\n            NewsArticle.objects.create(\n                title=\"News Article\",\n                content=\"News content\",\n                source=\"News Corp\",\n            )\n\n        # Query using the polymorphic base model\n        articles = Article.objects.all()\n        self.assertEqual(articles.count(), 2)\n\n        # Verify polymorphic behavior\n        self.assertIsInstance(articles[0], (BlogPost, NewsArticle))\n        self.assertIsInstance(articles[1], (BlogPost, NewsArticle))\n\n        # Verify both have versions\n        for article in articles:\n            versions = Version.objects.get_for_object(article)\n            self.assertGreaterEqual(versions.count(), 1)\n\n    def test_revert_to_previous_version(self):\n        \"\"\"Test that we can revert an object to a previous version.\"\"\"\n        # Create initial version\n        with revisions.create_revision():\n            blog_post = BlogPost.objects.create(\n                title=\"Original Title\",\n                content=\"Original content\",\n                author=\"Author One\",\n            )\n\n        # Make several updates\n        with revisions.create_revision():\n            blog_post.title = \"Second Title\"\n            blog_post.save()\n\n        with revisions.create_revision():\n            blog_post.title = \"Third Title\"\n            blog_post.author = \"Author Two\"\n            blog_post.save()\n\n        # Verify current state\n        blog_post.refresh_from_db()\n        self.assertEqual(blog_post.title, \"Third Title\")\n        self.assertEqual(blog_post.author, \"Author Two\")\n\n        # Revert to first version\n        versions = Version.objects.get_for_object(blog_post)\n        first_version = versions[\n            2\n        ]  # Versions are in reverse chronological order\n        first_version.revision.revert()\n\n        # Verify reverted state\n        blog_post.refresh_from_db()\n        self.assertEqual(blog_post.title, \"Original Title\")\n        self.assertEqual(blog_post.author, \"Author One\")\n\n    def test_manual_reversion_workflow(self):\n        \"\"\"Test complete manual reversion workflow without admin interface.\"\"\"\n        # Create a blog post with initial version\n        with revisions.create_revision():\n            blog_post = BlogPost.objects.create(\n                title=\"Manual Test Post\",\n                content=\"Initial content for manual testing.\",\n                author=\"Test Author\",\n            )\n            revisions.set_user(None)\n            revisions.set_comment(\"Manual creation\")\n\n        original_id = blog_post.id\n\n        # Make first update\n        with revisions.create_revision():\n            blog_post.title = \"Updated Manual Test Post\"\n            blog_post.content = \"First update to content.\"\n            blog_post.save()\n            revisions.set_comment(\"First manual update\")\n\n        # Make second update\n        with revisions.create_revision():\n            blog_post.author = \"Updated Author\"\n            blog_post.content = \"Second update to content.\"\n            blog_post.save()\n            revisions.set_comment(\"Second manual update\")\n\n        # Verify we have 3 versions\n        versions = Version.objects.get_for_object(blog_post)\n        self.assertEqual(versions.count(), 3)\n\n        # Test getting version data\n        latest_version = versions[0]\n        self.assertEqual(latest_version.field_dict[\"author\"], \"Updated Author\")\n        self.assertEqual(\n            latest_version.field_dict[\"content\"],\n            \"Second update to content.\",\n        )\n\n        middle_version = versions[1]\n        self.assertEqual(\n            middle_version.field_dict[\"title\"], \"Updated Manual Test Post\"\n        )\n        self.assertEqual(\n            middle_version.field_dict[\"content\"],\n            \"First update to content.\",\n        )\n\n        original_version = versions[2]\n        self.assertEqual(\n            original_version.field_dict[\"title\"], \"Manual Test Post\"\n        )\n        self.assertEqual(original_version.field_dict[\"author\"], \"Test Author\")\n\n        # Test reverting to middle version manually\n        middle_version.revision.revert()\n        blog_post.refresh_from_db()\n        self.assertEqual(blog_post.title, \"Updated Manual Test Post\")\n        self.assertEqual(blog_post.content, \"First update to content.\")\n        self.assertEqual(\n            blog_post.author, \"Test Author\"\n        )  # Should be from original\n\n        # Test accessing revision metadata\n        revision = middle_version.revision\n        self.assertEqual(revision.comment, \"First manual update\")\n\n        # Test that polymorphic type is preserved across reversion\n        self.assertIsInstance(blog_post, BlogPost)\n        self.assertEqual(blog_post.id, original_id)\n\n    def test_manual_newsarticle_reversion_with_deletion(self):\n        \"\"\"Test manual reversion including object deletion and recovery.\"\"\"\n        # Create a news article\n        with revisions.create_revision():\n            news = NewsArticle.objects.create(\n                title=\"Breaking News\",\n                content=\"Important breaking news.\",\n                source=\"Test News Network\",\n            )\n            revisions.set_comment(\"Initial news article\")\n\n        news_id = news.id\n\n        # Update the article\n        with revisions.create_revision():\n            news.content = \"Updated breaking news with more details.\"\n            news.source = \"Updated News Network\"\n            news.save()\n            revisions.set_comment(\"Updated news details\")\n\n        # Get versions before deletion\n        versions = Version.objects.get_for_object(news)\n        self.assertEqual(versions.count(), 2)\n        original_version = versions[1]\n\n        # Delete the object\n        with revisions.create_revision():\n            news.delete()\n            revisions.set_comment(\"Deleted news article\")\n\n        # Verify object is deleted\n        self.assertFalse(NewsArticle.objects.filter(id=news_id).exists())\n\n        # Manually recover from deletion by reverting to a previous version\n        original_version.revision.revert()\n\n        # Verify object is restored\n        recovered_news = NewsArticle.objects.get(id=news_id)\n        self.assertEqual(recovered_news.title, \"Breaking News\")\n        self.assertEqual(recovered_news.content, \"Important breaking news.\")\n        self.assertEqual(recovered_news.source, \"Test News Network\")\n        self.assertIsInstance(recovered_news, NewsArticle)\n\n    def test_manual_batch_reversion(self):\n        \"\"\"Test reverting multiple polymorphic objects in a single revision.\"\"\"\n        # Create multiple objects in one revision\n        with revisions.create_revision():\n            blog1 = BlogPost.objects.create(\n                title=\"Blog 1\", content=\"Content 1\", author=\"Author 1\"\n            )\n            blog2 = BlogPost.objects.create(\n                title=\"Blog 2\", content=\"Content 2\", author=\"Author 2\"\n            )\n            news = NewsArticle.objects.create(\n                title=\"News 1\", content=\"News content\", source=\"Source 1\"\n            )\n            revisions.set_comment(\"Batch creation\")\n\n        # Update all objects in another revision\n        with revisions.create_revision():\n            blog1.title = \"Updated Blog 1\"\n            blog1.save()\n            blog2.title = \"Updated Blog 2\"\n            blog2.save()\n            news.title = \"Updated News 1\"\n            news.save()\n            revisions.set_comment(\"Batch update\")\n\n        # Verify updated state\n        blog1.refresh_from_db()\n        blog2.refresh_from_db()\n        news.refresh_from_db()\n        self.assertEqual(blog1.title, \"Updated Blog 1\")\n        self.assertEqual(blog2.title, \"Updated Blog 2\")\n        self.assertEqual(news.title, \"Updated News 1\")\n\n        # Get the original revision (should contain all three objects)\n        from reversion.models import Revision\n\n        original_revision = Revision.objects.order_by(\"date_created\")[0]\n        self.assertEqual(original_revision.comment, \"Batch creation\")\n\n        # Revert the entire revision\n        original_revision.revert()\n\n        # Verify all objects reverted\n        blog1.refresh_from_db()\n        blog2.refresh_from_db()\n        news.refresh_from_db()\n        self.assertEqual(blog1.title, \"Blog 1\")\n        self.assertEqual(blog2.title, \"Blog 2\")\n        self.assertEqual(news.title, \"News 1\")\n\n\n@skipUnless(REVERSION_INSTALLED, \"django-reversion is not installed\")\nclass ReversionAdminUITests(_GenericUITest):\n    \"\"\"Test reversion functionality through the admin interface.\"\"\"\n\n    def test_blogpost_admin_reversion(self):\n        \"\"\"Test BlogPost admin integration: creating, updating, versioning, and reverting through UI.\"\"\"\n        # Navigate to BlogPost add page\n        add_url = self.add_url(BlogPost)\n        self.page.goto(add_url)\n\n        # Create initial BlogPost\n        self.page.fill(\"input[name='title']\", \"Admin Test Post\")\n        self.page.fill(\"textarea[name='content']\", \"Initial admin content\")\n        self.page.fill(\"input[name='author']\", \"Admin Author\")\n\n        # Save the object\n        with self.page.expect_navigation(timeout=30000):\n            self.page.click(\"input[name='_save']\")\n\n        # Verify BlogPost was created\n        blog_post = BlogPost.objects.get(title=\"Admin Test Post\")\n        self.assertEqual(blog_post.author, \"Admin Author\")\n        self.assertEqual(blog_post.content, \"Initial admin content\")\n\n        # Verify we have 1 version (created by admin)\n        versions = Version.objects.get_for_object(blog_post)\n        self.assertEqual(versions.count(), 1)\n        first_version = versions[0]\n        self.assertEqual(first_version.field_dict[\"title\"], \"Admin Test Post\")\n        self.assertEqual(first_version.field_dict[\"author\"], \"Admin Author\")\n\n        # Navigate to change page and update the BlogPost\n        change_url = self.change_url(BlogPost, blog_post.pk)\n        self.page.goto(change_url)\n\n        self.page.fill(\"input[name='title']\", \"Updated Admin Test Post\")\n        self.page.fill(\"textarea[name='content']\", \"Updated admin content\")\n        self.page.fill(\"input[name='author']\", \"Updated Admin Author\")\n\n        with self.page.expect_navigation(timeout=30000):\n            self.page.click(\"input[name='_save']\")\n\n        # Verify update\n        blog_post.refresh_from_db()\n        self.assertEqual(blog_post.title, \"Updated Admin Test Post\")\n        self.assertEqual(blog_post.author, \"Updated Admin Author\")\n\n        # Verify we now have 2 versions (admin creates version on each save)\n        versions = Version.objects.get_for_object(blog_post)\n        self.assertEqual(versions.count(), 2)\n        latest_version = versions[0]\n        self.assertEqual(\n            latest_version.field_dict[\"title\"], \"Updated Admin Test Post\"\n        )\n        self.assertEqual(\n            latest_version.field_dict[\"author\"], \"Updated Admin Author\"\n        )\n\n        # Navigate to history page and verify it's accessible\n        history_url = f\"{self.live_server_url}{reverse('admin:integrations_blogpost_history', args=[blog_post.pk])}\"\n        self.page.goto(history_url)\n\n        # Verify we can see the history page\n        expect(self.page.locator(\"#content h1\")).to_contain_text(\n            \"Change history\"\n        )\n\n        # Verify history table shows version information\n        history_table = self.page.locator(\n            \"table#change-history, div#change-history\"\n        )\n        expect(history_table).to_be_visible()\n\n        # Use the UI to revert: Click on the oldest version's date/time link\n        # The history table typically has links in each row - we want the last row (oldest)\n        history_links = self.page.locator(\"table#change-history a\").all()\n        self.assertGreater(len(history_links), 1, \"Should have history links\")\n\n        # Click on the last link (oldest version) to view that revision\n        with self.page.expect_navigation(timeout=30000):\n            history_links[0].click()\n\n        # We're now on the specific version's history page (history/<version_id>/)\n        current_url = self.page.url\n        self.assertIn(\n            \"/history/\",\n            current_url,\n            f\"Should be on history detail page, got: {current_url}\",\n        )\n\n        # Wait for page to fully load\n        self.page.wait_for_load_state(\"domcontentloaded\", timeout=10000)\n\n        # The page should show the old version's data\n        page_content = self.page.content()\n        self.assertIn(\n            \"Admin Test Post\",\n            page_content,\n            \"Should see original title on the version page\",\n        )\n\n        # Find and click the submit button to revert\n        submit_button = self.page.locator(\"input[type='submit']\").first\n\n        with self.page.expect_navigation(timeout=30000):\n            submit_button.click()\n\n        # Verify the object was reverted\n        blog_post.refresh_from_db()\n        self.assertEqual(blog_post.title, \"Admin Test Post\")\n        self.assertEqual(blog_post.author, \"Admin Author\")\n        self.assertIsInstance(blog_post, BlogPost)\n\n    def test_article_admin_reversion(self):\n        \"\"\"Test Article (polymorphic parent) admin versioning and reversion through UI.\"\"\"\n        # Create an article first via API, then test admin updates\n        with revisions.create_revision():\n            article = BlogPost.objects.create(\n                title=\"Parent Article Test\",\n                content=\"Parent article content\",\n                author=\"Parent Author\",\n            )\n\n        # Verify version created\n        versions = Version.objects.get_for_object(article)\n        self.assertEqual(versions.count(), 1)\n\n        # Update through parent Article admin interface\n        change_url = self.change_url(Article, article.pk)\n        self.page.goto(change_url)\n\n        # Verify we're on the change page for the parent admin\n        expect(self.page.locator(\"#content h1\")).to_contain_text(\"Change\")\n\n        self.page.fill(\"input[name='title']\", \"Updated Parent Article\")\n        self.page.fill(\"textarea[name='content']\", \"Updated parent content\")\n\n        with self.page.expect_navigation(timeout=30000):\n            self.page.click(\"input[name='_save']\")\n\n        # Verify update\n        article.refresh_from_db()\n        self.assertEqual(article.title, \"Updated Parent Article\")\n\n        # Verify we now have 2 versions (1 from API, 1 from admin)\n        versions = Version.objects.get_for_object(article)\n        self.assertEqual(versions.count(), 2)\n        self.assertEqual(\n            versions[0].field_dict[\"title\"], \"Updated Parent Article\"\n        )\n        self.assertEqual(versions[1].field_dict[\"title\"], \"Parent Article Test\")\n\n        # Navigate to history page through parent admin\n        history_url = f\"{self.live_server_url}{reverse('admin:integrations_article_history', args=[article.pk])}\"\n        self.page.goto(history_url)\n        expect(self.page.locator(\"#content h1\")).to_contain_text(\n            \"Change history\"\n        )\n\n        # Use the UI to revert: Click on the oldest version\n        history_links = self.page.locator(\"table#change-history a\").all()\n        self.assertGreater(len(history_links), 1)\n\n        with self.page.expect_navigation(timeout=30000):\n            history_links[0].click()\n\n        # Wait for page to load\n        self.page.wait_for_load_state(\"domcontentloaded\", timeout=10000)\n\n        # Verify we're on the revert page\n        page_content = self.page.content()\n        self.assertIn(\"Revert\", page_content, \"Should be on a revert page\")\n\n        # Click the submit button to revert\n        submit_button = self.page.locator(\"input[type='submit']\").first\n\n        with self.page.expect_navigation(timeout=30000):\n            submit_button.click()\n\n        # Verify the object was reverted\n        article.refresh_from_db()\n        self.assertEqual(article.title, \"Parent Article Test\")\n        self.assertEqual(article.content, \"Parent article content\")\n        self.assertIsInstance(article, BlogPost)\n\n    def test_newsarticle_admin_reversion(self):\n        \"\"\"Test NewsArticle admin versioning and reversion through UI.\"\"\"\n        # Navigate to NewsArticle add page\n        add_url = self.add_url(NewsArticle)\n        self.page.goto(add_url)\n\n        # Create NewsArticle\n        self.page.fill(\"input[name='title']\", \"Breaking Admin News\")\n        self.page.fill(\"textarea[name='content']\", \"Admin news content\")\n        self.page.fill(\"input[name='source']\", \"Admin News Network\")\n\n        with self.page.expect_navigation(timeout=30000):\n            self.page.click(\"input[name='_save']\")\n\n        # Verify creation\n        news = NewsArticle.objects.get(title=\"Breaking Admin News\")\n        self.assertEqual(news.source, \"Admin News Network\")\n\n        # Verify version created\n        versions = Version.objects.get_for_object(news)\n        self.assertEqual(versions.count(), 1)\n\n        # Update\n        change_url = self.change_url(NewsArticle, news.pk)\n        self.page.goto(change_url)\n\n        self.page.fill(\"input[name='title']\", \"Updated Breaking News\")\n        self.page.fill(\"input[name='source']\", \"Updated Network\")\n\n        with self.page.expect_navigation(timeout=30000):\n            self.page.click(\"input[name='_save']\")\n\n        news.refresh_from_db()\n        self.assertEqual(news.title, \"Updated Breaking News\")\n\n        # Verify we have 2 versions from admin operations\n        versions = Version.objects.get_for_object(news)\n        self.assertEqual(versions.count(), 2)\n        self.assertEqual(\n            versions[0].field_dict[\"title\"], \"Updated Breaking News\"\n        )\n        self.assertEqual(versions[1].field_dict[\"title\"], \"Breaking Admin News\")\n\n        # Verify history page is accessible\n        history_url = f\"{self.live_server_url}{reverse('admin:integrations_newsarticle_history', args=[news.pk])}\"\n        self.page.goto(history_url)\n        expect(self.page.locator(\"#content h1\")).to_contain_text(\n            \"Change history\"\n        )\n\n        # Use the UI to revert: Click on the oldest version\n        history_links = self.page.locator(\"table#change-history a\").all()\n        self.assertGreater(len(history_links), 1)\n\n        with self.page.expect_navigation(timeout=30000):\n            history_links[0].click()\n\n        # Wait for page to load\n        self.page.wait_for_load_state(\"domcontentloaded\", timeout=10000)\n\n        # Verify we're on the revert page\n        page_content = self.page.content()\n        self.assertIn(\"Revert\", page_content, \"Should be on a revert page\")\n\n        # Try to revert through UI - click the save button\n        submit_buttons = self.page.locator(\"input[type='submit']\").all()\n\n        with self.page.expect_navigation(timeout=30000):\n            submit_buttons[0].click()\n\n        # Check if reversion worked\n        news.refresh_from_db()\n        # Reversion worked! Verify values\n        self.assertEqual(news.source, \"Admin News Network\")\n        self.assertIsInstance(news, NewsArticle)\n\n        # Verify a new version was created for the revert operation\n        versions_after_revert = Version.objects.get_for_object(news)\n        self.assertEqual(versions_after_revert.count(), 3)\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/fk/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/fk/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass TypeHintsFKConfig(AppConfig):\n    name = \"polymorphic.tests.examples.type_hints.fk\"\n    label = \"type_hints_fk\"\n    verbose_name = \"Type Hints Foreign Key Example\"\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/fk/migrations/0001_initial.py",
    "content": "# Generated by Django 4.2 on 2026-02-05 14:47\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('contenttypes', '0002_remove_content_type_name'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='ParentModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Child1',\n            fields=[\n                ('parentmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_fk.parentmodel')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('type_hints_fk.parentmodel',),\n        ),\n        migrations.CreateModel(\n            name='RelatedModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='type_hints_fk.parentmodel')),\n            ],\n        ),\n        migrations.AddField(\n            model_name='parentmodel',\n            name='related_forward',\n            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='parents_reverse', to='type_hints_fk.relatedmodel'),\n        ),\n        migrations.CreateModel(\n            name='Child2',\n            fields=[\n                ('child1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_fk.child1')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('type_hints_fk.child1',),\n        ),\n    ]\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/fk/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/fk/models.py",
    "content": "from django.db import models\nfrom polymorphic.models import PolymorphicModel\nfrom polymorphic.managers import (\n    PolymorphicForwardManyToOneDescriptor,\n    PolymorphicReverseManyToOneDescriptor,\n    Nullable,  # Alias for typing.Literal[True]\n)\n\n\nclass ParentModel(PolymorphicModel):\n    related_forward = models.ForeignKey(\n        \"RelatedModel\", on_delete=models.CASCADE, related_name=\"parents_reverse\"\n    )\n\n\nclass Child1(ParentModel):\n    pass\n\n\nclass Child2(Child1):\n    pass\n\n\nclass RelatedModel(models.Model):\n    # fmt: off\n    # Foreign Key Descriptor Type Hint:\n    #   1. Class Attribute: ForwardManyToOneDescriptor\n    #   2. Instance attribute: Union of all listed model types or None\n    parent: PolymorphicForwardManyToOneDescriptor[\n        ParentModel | Child1 | Child2,  # all possible polymorphic types\n        ParentModel,                    # the base type (for non_polymorphic)\n        Nullable,                       # when null=True\n    ] = models.ForeignKey(              # type: ignore[assignment]\n        ParentModel,\n        on_delete=models.CASCADE,\n        null=True,\n    )\n\n    # Reverse FK Relation Descriptor Type Hint:\n    #   1. Class Attribute: ReverseManyToOneDescriptor\n    #   2. Instance attribute: Union of all listed model types\n    parents_reverse: PolymorphicReverseManyToOneDescriptor[\n        ParentModel | Child1 | Child2,  # all possible polymorphic types\n        ParentModel,                    # the base type (for non_polymorphic)\n                                        # nullable defaults to False\n    ]\n    # fmt: on\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/fk/test.py",
    "content": "import typing as t\nfrom typing_extensions import assert_type\nfrom django.test import TestCase\nfrom .models import ParentModel, Child1, Child2, RelatedModel\n\n\nclass TypeHintsFKTest(TestCase):\n    def test_type_hints(self):\n        related = RelatedModel.objects.create()\n        parent = ParentModel.objects.create(related_forward=related)\n        child1 = Child1.objects.create(related_forward=related)\n        child2 = Child2.objects.create(related_forward=related)\n\n        assert_type(related.parent, t.Optional[ParentModel | Child1 | Child2])\n\n        related.parent = child1\n        related.save()\n\n        if t.TYPE_CHECKING:\n            from django.db.models.fields.related import ForeignKey\n            from django.db.models.fields.reverse_related import ManyToOneRel\n\n            assert_type(\n                RelatedModel.parents_reverse.field, ForeignKey[t.Any, t.Any]\n            )\n            assert_type(RelatedModel.parents_reverse.rel, ManyToOneRel)\n\n        _1: t.Optional[ParentModel | Child1 | Child2] = (\n            related.parents_reverse.first()\n        )\n        assert _1 == parent\n\n        _2: t.Optional[ParentModel | Child1 | Child2] = (\n            related.parents_reverse.filter().first()\n        )\n        assert _2 == parent\n\n        _3: t.Optional[ParentModel] = (\n            related.parents_reverse.non_polymorphic().last()\n        )\n        assert _3 == ParentModel.objects.non_polymorphic().get(pk=child2.pk)\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/m2m/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/m2m/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass TypeHintsM2MConfig(AppConfig):\n    name = \"polymorphic.tests.examples.type_hints.m2m\"\n    label = \"type_hints_m2m\"\n    verbose_name = \"Type Hints M2M Example\"\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/m2m/migrations/0001_initial.py",
    "content": "# Generated by Django 4.2 on 2026-02-05 14:47\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('contenttypes', '0002_remove_content_type_name'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='ParentModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='PolyThrough',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='parents', to='type_hints_m2m.parentmodel')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Child1',\n            fields=[\n                ('parentmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_m2m.parentmodel')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('type_hints_m2m.parentmodel',),\n        ),\n        migrations.CreateModel(\n            name='ThroughChild',\n            fields=[\n                ('polythrough_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_m2m.polythrough')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('type_hints_m2m.polythrough',),\n        ),\n        migrations.CreateModel(\n            name='RelatedModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('to_parents', models.ManyToManyField(related_name='to_parents_reverse', to='type_hints_m2m.parentmodel')),\n            ],\n        ),\n        migrations.AddField(\n            model_name='polythrough',\n            name='related',\n            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='type_hints_m2m.relatedmodel'),\n        ),\n        migrations.AddField(\n            model_name='parentmodel',\n            name='to_related',\n            field=models.ManyToManyField(related_name='to_related_reverse', through='type_hints_m2m.PolyThrough', to='type_hints_m2m.relatedmodel'),\n        ),\n        migrations.CreateModel(\n            name='Child2',\n            fields=[\n                ('child1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_m2m.child1')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('type_hints_m2m.child1',),\n        ),\n    ]\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/m2m/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/m2m/models.py",
    "content": "from __future__ import annotations\nfrom typing import ClassVar\nfrom django.db import models\nfrom polymorphic.models import PolymorphicModel\nfrom polymorphic.managers import (\n    PolymorphicManager,\n    PolymorphicManyToManyDescriptor,\n    PolymorphicForwardManyToOneDescriptor,\n    PolymorphicReverseManyToOneDescriptor,\n)\n\n\nclass ParentModel(PolymorphicModel):\n    to_related = models.ManyToManyField(  # type: ignore[var-annotated]\n        \"RelatedModel\", related_name=\"to_related_reverse\", through=\"PolyThrough\"\n    )\n\n    parents: PolymorphicReverseManyToOneDescriptor[\n        ParentModel | Child1 | Child2, ParentModel\n    ]\n\n    objects: ClassVar[\n        PolymorphicManager[ParentModel | Child1 | Child2, ParentModel]\n    ]\n\n\nclass Child1(ParentModel):\n    pass\n\n\nclass Child2(Child1):\n    pass\n\n\nclass PolyThrough(PolymorphicModel):\n    parent: PolymorphicForwardManyToOneDescriptor[\n        ParentModel | Child1 | Child2, ParentModel\n    ] = models.ForeignKey(  # type: ignore[assignment]\n        ParentModel, on_delete=models.CASCADE, related_name=\"parents\"\n    )\n\n    related = models.ForeignKey(\"RelatedModel\", on_delete=models.CASCADE)\n\n    objects: ClassVar[\n        PolymorphicManager[PolyThrough | ThroughChild, PolyThrough]\n    ]\n\n\nclass ThroughChild(PolyThrough):\n    pass\n\n\nclass RelatedModel(models.Model):\n    # fmt: off\n    # ManyToMany Descriptor Type Hint:\n    #   1. Class Attribute: ManyToManyDescriptor\n    #   2. Instance attribute: PolymorphicManager for related type(s)\n    to_parents: PolymorphicManyToManyDescriptor[\n        ParentModel | Child1 | Child2,  # all possible polymorphic types\n        ParentModel,                    # the base type (for non_polymorphic)\n                                        # no custom through model\n    ] = models.ManyToManyField(         # type: ignore[assignment]\n        \"ParentModel\",\n        related_name=\"to_parents_reverse\"\n    )\n\n    # ManyToMany Descriptor for the reverse relation\n    #   1. Class Attribute: ManyToManyDescriptor\n    #   2. Instance attribute: PolymorphicManager for related type(s)\n    to_related_reverse: PolymorphicManyToManyDescriptor[\n        ParentModel | Child1 | Child2,\n        ParentModel,\n        PolyThrough  # custom through model (may be polymorphic!)\n    ]\n    # fmt: on\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/m2m/test.py",
    "content": "import typing as t\nfrom django.test import TestCase\nfrom django.db import models\n\n# from django.db import models\nfrom .models import (\n    ParentModel,\n    Child1,\n    Child2,\n    RelatedModel,\n    PolyThrough,\n    ThroughChild,\n)\n\n\nclass TypeHintsM2MTest(TestCase):\n    def test_type_hints(self):\n        parent = ParentModel.objects.create()\n        child1 = Child1.objects.create()\n        child2 = Child2.objects.create()\n        related1 = RelatedModel.objects.create()\n        related2 = RelatedModel.objects.create()\n\n        _t1 = PolyThrough.objects.create(parent=child1, related=related1)\n\n        _t2 = ThroughChild.objects.create(parent=child2, related=related2)\n\n        related1.to_parents.add(parent)\n        related1.to_related_reverse.add(child2)\n        related2.to_parents.add(child2)\n\n        related1.refresh_from_db()\n        assert set(related1.to_parents.all()) == {parent}\n        assert set(related2.to_parents.all()) == {child2}\n        assert set(related1.to_related_reverse.all()) == {child1, child2}\n        assert set(related2.to_related_reverse.all()) == {child2}\n\n        assert parent.to_related.count() == 0\n        assert set(child1.to_related.all()) == {related1}\n        assert set(child2.to_related.all()) == {related1, related2}\n\n        through1: type[PolyThrough] = parent.to_related.through\n        assert through1.objects.count() == 3\n\n        tlist1: t.List[PolyThrough | ThroughChild] = list(\n            parent.to_related.through.objects.all()\n        )\n        assert len(tlist1) == 3\n\n        _1: t.List[PolyThrough] = list(\n            parent.to_related.through.objects.non_polymorphic()\n        )\n        assert len(_1) == 3\n\n        _2: t.List[ParentModel | Child1 | Child2] = list(\n            related1.to_related_reverse.all()\n        )\n        assert set(_2) == {child1, child2}\n\n        _3: t.List[ParentModel | Child1 | Child2] = list(\n            related1.to_parents.all()\n        )\n\n        assert set(_3) == {parent}\n\n        _through2: type[models.Model] = related1.to_parents.through\n        _through3: type[PolyThrough] = related1.to_related_reverse.through\n\n        assert _through2.objects.count() == 2  # type: ignore[attr-defined]\n\n        assert set(_through3.objects.all()) == {\n            _t1,\n            _t2,\n            PolyThrough.objects.last(),\n        }\n\n        _4: t.List[ParentModel] = list(\n            related1.to_related_reverse.non_polymorphic()\n        )\n        assert set(_4) == set(\n            ParentModel.objects.non_polymorphic().filter(\n                pk__in=[child1.pk, child2.pk]\n            )\n        )\n\n        _5: t.List[ParentModel] = list(\n            related1.to_parents.all().non_polymorphic()\n        )\n        assert set(_5) == {parent}\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/managers/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/managers/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass TypeHintsManagersConfig(AppConfig):\n    name = \"polymorphic.tests.examples.type_hints.managers\"\n    label = \"type_hints_managers\"\n    verbose_name = \"Type Hints Managers Example\"\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/managers/migrations/0001_initial.py",
    "content": "# Generated by Django 4.2 on 2026-02-05 14:47\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('contenttypes', '0002_remove_content_type_name'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='ParentModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Child1',\n            fields=[\n                ('parentmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_managers.parentmodel')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('type_hints_managers.parentmodel',),\n        ),\n        migrations.CreateModel(\n            name='Child2',\n            fields=[\n                ('child1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_managers.child1')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('type_hints_managers.child1',),\n        ),\n    ]\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/managers/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/managers/models.py",
    "content": "from __future__ import annotations\nimport typing as t\nfrom typing_extensions import Self\nfrom polymorphic.models import PolymorphicModel\nfrom polymorphic.managers import PolymorphicManager\n\n\nclass ParentModel(PolymorphicModel):\n    # fmt: off\n    # If you want a polymorphic manager with type hint support you can\n    # override the default one like this:\n    objects: t.ClassVar[\n        PolymorphicManager[\n            Self | Child1 | Child2,  # union of all polymorphic types\n            Self,                    # the base type (for non_polymorphic)\n        ]\n    ] = PolymorphicManager()\n    # fmt: on\n\n\nclass Child1(ParentModel):\n    # you may also override the type hints for the child default\n    # managers to narrow the filter returns at this level\n    objects: t.ClassVar[PolymorphicManager[Self | Child2, Self]]\n\n\nclass Child2(Child1):\n    objects: t.ClassVar[PolymorphicManager[Self, Self]]\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/managers/test.py",
    "content": "import typing as t\nfrom django.test import TestCase\nfrom .models import ParentModel, Child1, Child2\n\n\nclass TypeHintsManagersTest(TestCase):\n    def test_type_hints(self):\n\n        parent = ParentModel.objects.create()\n        child1 = Child1.objects.create()\n        child2 = Child2.objects.create()\n\n        _1: t.Optional[ParentModel | Child1 | Child2] = (\n            ParentModel.objects.first()\n        )\n        assert _1 == parent or _1 == child1 or _1 == child2\n        assert _1 == parent\n        _2: t.Optional[ParentModel | Child1 | Child2] = (\n            ParentModel.objects.order_by(\"pk\").first()\n        )\n        assert _2 == parent\n        _3: t.Optional[Child1 | Child2] = Child1.objects.first()\n        assert _3 == child1 or _3 == child2\n        _4: Child1 | Child2 = Child1.objects.filter().all().get(pk=child1.pk)\n        assert _4 == child1\n        _5: t.Optional[Child2] = Child2.objects.first()\n        assert _5 == child2\n        _6: Child2 = Child2.objects.filter().get(pk=child2.pk)\n        assert _6 == child2\n\n        assert ParentModel.objects.count() == 3\n        assert Child1.objects.count() == 2\n        assert Child2.objects.count() == 1\n\n        # mypy has trouble with these - they work on pyright/pylance. I consider this\n        # a failing of mypy type inference not a deficiency in our typing - for now\n        # we can ignore the errors\n        _7: t.Optional[ParentModel] = (\n            ParentModel.objects.non_polymorphic().first()  # type: ignore[assignment]\n        )\n        assert _7 == parent\n        _8: t.Optional[ParentModel] = (\n            ParentModel.objects.all().non_polymorphic().first()  # type: ignore[assignment]\n        )\n        assert _8 == parent\n        _9: t.Optional[Child1] = Child1.objects.non_polymorphic().first()  # type: ignore[assignment]\n        assert _9 == child1\n        _10: t.Optional[Child1] = (\n            Child1.objects.filter().non_polymorphic().all().first()  # type: ignore[assignment]\n        )\n        assert _10 == child1\n        _11: t.Optional[Child2] = Child2.objects.non_polymorphic().first()\n        assert _11 == child2\n        _12: Child2 = (\n            Child2.objects.filter().non_polymorphic().get(pk=child2.pk)\n        )\n        assert _12 == child2\n\n        _13: t.Optional[ParentModel] = ParentModel.objects.instance_of(\n            ParentModel\n        ).first()\n        assert _13 == parent\n        _14: t.Optional[ParentModel] = (\n            ParentModel.objects.all().instance_of(ParentModel).first()\n        )\n        assert _14 == parent\n\n        _15: t.Optional[Child1] = ParentModel.objects.instance_of(\n            Child1\n        ).first()\n        assert _15 == child1\n        _16: t.Optional[Child1] = (\n            ParentModel.objects.all().instance_of(Child1).first()\n        )\n        assert _16 == child1\n        _17: t.Optional[Child2] = ParentModel.objects.instance_of(\n            Child2\n        ).first()\n        assert _17 == child2\n        _18: t.Optional[Child2] = (\n            ParentModel.objects.all().instance_of(Child2).first()\n        )\n        assert _18 == child2\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/models.py",
    "content": "# from __future__ import annotations\n# import typing as t\n# from typing_extensions import Self\n# from django.db import models\n# from polymorphic.models import PolymorphicModel\n# from polymorphic.managers import PolymorphicManager\n\n# if t.TYPE_CHECKING:\n#     # The polymorphic manager and related descriptors type hints are only available\n#     # during type checking\n#     from polymorphic.managers import (\n#         PolymorphicManyToManyDescriptor,\n#         PolymorphicRelatedManager,\n#     )\n\n\n# class Article(PolymorphicModel):\n#     title = models.CharField(max_length=100)\n\n#     outlet: \"MediaOutlet\" | \"Newspaper\" | \"OnlineBlog\" = models.ForeignKey(  # type: ignore[assignment]\n#         \"MediaOutlet\", on_delete=models.CASCADE, related_name=\"articles\"\n#     )\n\n#     objects: t.ClassVar[\n#         PolymorphicManager[Self | \"BlogPost\" | \"NewsArticle\", Self]\n#     ] = PolymorphicManager()\n\n#     topics: PolymorphicManyToManyDescriptor[\n#         \"Topic\" | \"LocationTopic\" | \"EditorialTopic\", \"Topic\"\n#     ]\n\n\n# class BlogPost(Article):\n#     author = models.CharField(max_length=100)\n\n\n# class NewsArticle(Article):\n#     source = models.CharField(max_length=100)\n\n\n# class Topic(PolymorphicModel):\n#     name = models.CharField(max_length=50)\n\n#     articles: PolymorphicManyToManyDescriptor[\n#         Article | BlogPost | NewsArticle, Article\n#     ] = models.ManyToManyField(Article, related_name=\"topics\")  # type: ignore[assignment]\n\n#     articles2 = models.ManyToManyField(Article, related_name=\"topics2\")\n#     if t.TYPE_CHECKING:\n#         objects: t.ClassVar[\n#             PolymorphicManager[Self | \"LocationTopic\" | \"EditorialTopic\", Self]\n#         ]\n\n\n# class LocationTopic(Topic):\n#     location = models.CharField(max_length=100)\n\n\n# class EditorialTopic(Topic):\n#     editor = models.CharField(max_length=100)\n\n\n# class MediaOutlet(PolymorphicModel):\n#     name = models.CharField(max_length=100)\n\n#     articles: PolymorphicRelatedManager[\n#         Article | NewsArticle | BlogPost, \"Article\"\n#     ]\n\n\n# class Newspaper(MediaOutlet):\n#     service_area = models.CharField(max_length=100)\n\n\n# class OnlineBlog(MediaOutlet):\n#     url = models.URLField()\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/one2one/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/one2one/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass TypeHintsOne2OneConfig(AppConfig):\n    name = \"polymorphic.tests.examples.type_hints.one2one\"\n    label = \"type_hints_one2one\"\n    verbose_name = \"Type Hints One2One Example\"\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/one2one/migrations/0001_initial.py",
    "content": "# Generated by Django 4.2 on 2026-02-05 14:47\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('contenttypes', '0002_remove_content_type_name'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='ParentModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Child1',\n            fields=[\n                ('parentmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_one2one.parentmodel')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('type_hints_one2one.parentmodel',),\n        ),\n        migrations.CreateModel(\n            name='RelatedModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('parent_forward', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='type_hints_one2one.parentmodel')),\n            ],\n        ),\n        migrations.AddField(\n            model_name='parentmodel',\n            name='related_forward',\n            field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='parent_reverse', to='type_hints_one2one.relatedmodel'),\n        ),\n        migrations.CreateModel(\n            name='Child2',\n            fields=[\n                ('child1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_one2one.child1')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('type_hints_one2one.child1',),\n        ),\n    ]\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/one2one/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/one2one/models.py",
    "content": "from django.db import models\nfrom polymorphic.models import PolymorphicModel\nfrom polymorphic.managers import (\n    PolymorphicForwardOneToOneDescriptor,\n    PolymorphicReverseOneToOneDescriptor,\n    Nullable,  # Alias for typing.Literal[True]\n)\n\n\nclass ParentModel(PolymorphicModel):\n    related_forward = models.OneToOneField(\n        \"RelatedModel\", on_delete=models.CASCADE, related_name=\"parent_reverse\"\n    )\n\n\nclass Child1(ParentModel):\n    pass\n\n\nclass Child2(Child1):\n    pass\n\n\nclass RelatedModel(models.Model):\n    # fmt: off\n    # Forward Relation Descriptor Type Hint:\n    #   1. Class Attribute: ForwardOneToOneDescriptor\n    #   2. Instance attribute: Union of all listed model types or None\n    parent_forward: PolymorphicForwardOneToOneDescriptor[\n        ParentModel | Child1 | Child2,  # all possible polymorphic types\n        ParentModel,                    # the base type (for non_polymorphic)\n        Nullable,                       # when null=True\n    ] = models.OneToOneField(           # type: ignore[assignment]\n        \"ParentModel\",\n        on_delete=models.CASCADE,\n        null=True\n    )\n\n    # Reverse Relation Descriptor Type Hint:\n    #   1. Class Attribute: ReverseOneToOneDescriptor\n    #   2. Instance attribute: Union of all listed model types\n    parent_reverse: PolymorphicReverseOneToOneDescriptor[\n        ParentModel | Child1 | Child2,  # possible polymorphic types\n        ParentModel,                    # the base type (for non_polymorphic)\n                                        # nullable defaults to False\n    ]\n    # fmt: on\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/one2one/test.py",
    "content": "import typing as t\nfrom django.db.models.fields.reverse_related import OneToOneRel\nfrom typing_extensions import assert_type\nfrom django.test import TestCase\nfrom polymorphic.managers import PolymorphicQuerySet\n\n# from django.db import models\nfrom .models import ParentModel, Child1, Child2, RelatedModel\n\n\nclass TypeHintsOne2OneTest(TestCase):\n    def test_type_hints(self):\n        related1 = RelatedModel.objects.create()\n        related2 = RelatedModel.objects.create()\n        related3 = RelatedModel.objects.create()\n        parent = ParentModel.objects.create(related_forward=related1)\n        child1 = Child1.objects.create(related_forward=related2)\n        Child2.objects.create(related_forward=related3)\n        assert_type(\n            related1.parent_forward, t.Optional[ParentModel | Child1 | Child2]\n        )\n        assert_type(related1.parent_reverse, ParentModel | Child1 | Child2)\n\n        related1.parent_forward = child1\n        related1.save()\n\n        assert_type(RelatedModel.parent_reverse.related, OneToOneRel)\n        _1: PolymorphicQuerySet[ParentModel | Child1 | Child2, ParentModel] = (\n            RelatedModel.parent_reverse.get_queryset()\n        )\n        assert _1.all().count() == 3\n\n        # assert_type(RelatedModel.parent_forward.related, OneToOneRel)\n        _2: PolymorphicQuerySet[ParentModel | Child1 | Child2, ParentModel] = (\n            RelatedModel.parent_forward.get_queryset()\n        )\n        assert _2.all().count() == 3\n\n        _3: ParentModel | Child1 | Child2 = related1.parent_reverse\n        assert _3 == parent\n\n        _4: t.Optional[ParentModel | Child1 | Child2] = related1.parent_forward\n        assert _4 == child1\n"
  },
  {
    "path": "src/polymorphic/tests/examples/type_hints/pyright.json",
    "content": "{\n  \"extends\": \"../../../../../pyproject.toml\",\n  \"exclude\": [],\n  \"include\": [\"**/*.py\"]\n}\n"
  },
  {
    "path": "src/polymorphic/tests/examples/views/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/views/migrations/0001_initial.py",
    "content": "# Generated by Django 4.2 on 2026-02-05 14:47\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('contenttypes', '0002_remove_content_type_name'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='Project',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('topic', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='ArtProject',\n            fields=[\n                ('project_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='views.project')),\n                ('artist', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('views.project',),\n        ),\n        migrations.CreateModel(\n            name='ResearchProject',\n            fields=[\n                ('project_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='views.project')),\n                ('supervisor', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('views.project',),\n        ),\n    ]\n"
  },
  {
    "path": "src/polymorphic/tests/examples/views/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/examples/views/models.py",
    "content": "from django.db import models\nfrom polymorphic.models import PolymorphicModel\n\n\nclass Project(PolymorphicModel):\n    topic = models.CharField(max_length=30)\n\n\nclass ArtProject(Project):\n    artist = models.CharField(max_length=30)\n\n\nclass ResearchProject(Project):\n    supervisor = models.CharField(max_length=30)\n"
  },
  {
    "path": "src/polymorphic/tests/examples/views/templates/project_form.html",
    "content": "<form method=\"post\" action=\".?model={{ model_label }}\">\n    {% csrf_token %}\n    {{ form.as_p }}\n    <button type=\"submit\">Save</button>\n</form>\n"
  },
  {
    "path": "src/polymorphic/tests/examples/views/templates/project_type_select.html",
    "content": "<form method=\"post\">\n    {% csrf_token %}\n    {{ form.as_p }}\n    <button type=\"submit\">Next</button>\n</form>\n"
  },
  {
    "path": "src/polymorphic/tests/examples/views/test.py",
    "content": "from django.urls import reverse\nfrom playwright.sync_api import expect\n\nfrom polymorphic.tests.utils import _GenericUITest\nfrom .models import Project, ArtProject, ResearchProject\n\n\nclass ViewExampleTests(_GenericUITest):\n    def test_view_example(self):\n        \"\"\"Test that the example view code works correctly.\"\"\"\n        # Step 1: Navigate to the type selection page\n        select_url = f\"{self.live_server_url}{reverse('project-select')}\"\n        self.page.goto(select_url)\n\n        # Verify the page loaded\n        expect(self.page).to_have_url(select_url)\n\n        # Get model labels for the child models\n        art_project_label = ArtProject._meta.label\n        research_project_label = ResearchProject._meta.label\n\n        # Verify radio buttons for both types exist\n        art_radio = self.page.locator(\n            f\"input[type='radio'][value='{art_project_label}']\"\n        )\n        research_radio = self.page.locator(\n            f\"input[type='radio'][value='{research_project_label}']\"\n        )\n\n        expect(art_radio).to_be_visible()\n        expect(research_radio).to_be_visible()\n\n        # Step 2: Select ArtProject and submit\n        art_radio.click()\n        self.page.click(\"button[type='submit']\")\n\n        # Should redirect to the create view with model parameter\n        create_url_pattern = f\"{self.live_server_url}{reverse('project-create')}?model={art_project_label}\"\n        expect(self.page).to_have_url(create_url_pattern)\n\n        # Step 3: Fill in the ArtProject form\n        # The form should have fields: topic (from Project) and artist (from ArtProject)\n        self.page.fill(\"input[name='topic']\", \"Modern Art\")\n        self.page.fill(\"input[name='artist']\", \"Picasso\")\n\n        # Submit the form\n        with self.page.expect_navigation(timeout=10000):\n            self.page.click(\"button[type='submit']\")\n\n        # Verify the object was created\n        art_project = ArtProject.objects.filter(\n            topic=\"Modern Art\", artist=\"Picasso\"\n        ).first()\n        assert art_project is not None, \"ArtProject was not created\"\n        assert art_project.topic == \"Modern Art\"\n        assert art_project.artist == \"Picasso\"\n\n        # Step 4: Test creating a ResearchProject\n        self.page.goto(select_url)\n        research_radio = self.page.locator(\n            f\"input[type='radio'][value='{research_project_label}']\"\n        )\n        research_radio.click()\n        self.page.click(\"button[type='submit']\")\n\n        # Verify redirect to create view\n        create_url_pattern = f\"{self.live_server_url}{reverse('project-create')}?model={research_project_label}\"\n        expect(self.page).to_have_url(create_url_pattern)\n\n        # Fill in the ResearchProject form\n        # Should have fields: topic and supervisor\n        self.page.fill(\"input[name='topic']\", \"Quantum Computing\")\n        self.page.fill(\"input[name='supervisor']\", \"Dr. Smith\")\n\n        # Submit the form\n        with self.page.expect_navigation(timeout=10000):\n            self.page.click(\"button[type='submit']\")\n\n        # Verify the object was created\n        research_project = ResearchProject.objects.filter(\n            topic=\"Quantum Computing\", supervisor=\"Dr. Smith\"\n        ).first()\n        assert research_project is not None, \"ResearchProject was not created\"\n        assert research_project.topic == \"Quantum Computing\"\n        assert research_project.supervisor == \"Dr. Smith\"\n\n        # Verify polymorphic querying works\n        all_projects = Project.objects.all()\n        assert all_projects.count() == 2\n        assert isinstance(all_projects[0], (ArtProject, ResearchProject))\n        assert isinstance(all_projects[1], (ArtProject, ResearchProject))\n"
  },
  {
    "path": "src/polymorphic/tests/examples/views/urls.py",
    "content": "from django.urls import path\nfrom .views import ProjectTypeSelectView, ProjectCreateView\n\nurlpatterns = [\n    path(\"select/\", ProjectTypeSelectView.as_view(), name=\"project-select\"),\n    path(\"create/\", ProjectCreateView.as_view(), name=\"project-create\"),\n]\n"
  },
  {
    "path": "src/polymorphic/tests/examples/views/views.py",
    "content": "from django.apps import apps\nfrom django.shortcuts import redirect\nfrom django.urls import reverse\nfrom django.views.generic import FormView, CreateView\nfrom .models import Project, ArtProject, ResearchProject\n\nfrom django import forms\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass ProjectTypeChoiceForm(forms.Form):\n    model_type = forms.ChoiceField(\n        label=_(\"Project Type\"),\n        widget=forms.RadioSelect(attrs={\"class\": \"radiolist\"}),\n    )\n\n\nclass ProjectTypeSelectView(FormView):\n    form_class = ProjectTypeChoiceForm\n    template_name = \"project_type_select.html\"\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        # Build choices using model labels: [(model_label, verbose_name), ...]\n        choices = [\n            (model._meta.label, model._meta.verbose_name)\n            for model in [ArtProject, ResearchProject]\n        ]\n        kwargs[\"initial\"] = {\"model_type\": choices[0][0] if choices else None}\n        return kwargs\n\n    def get_form(self, form_class=None):\n        form = super().get_form(form_class)\n        # Populate the choices for the form using model labels\n        choices = [\n            (model._meta.label, model._meta.verbose_name)\n            for model in [ArtProject, ResearchProject]\n        ]\n        form.fields[\"model_type\"].choices = choices\n        return form\n\n    def form_valid(self, form):\n        model_label = form.cleaned_data[\"model_type\"]\n        return redirect(f\"{reverse('project-create')}?model={model_label}\")\n\n\nclass ProjectCreateView(CreateView):\n    model = Project\n    template_name = \"project_form.html\"\n\n    def get_success_url(self):\n        return reverse(\"project-select\")\n\n    def get_form_class(self):\n        # Get the requested model label from query parameter\n        model_label = self.request.GET.get(\"model\")\n        if not model_label:\n            # Fallback or redirect to selection view\n            return super().get_form_class()\n\n        # Get the model class using the app registry\n        model_class = apps.get_model(model_label)\n\n        # Create a form for this model\n        # You can also use a factory or a dict mapping if you have custom forms\n        class SpecificForm(forms.ModelForm):\n            class Meta:\n                model = model_class\n                fields = \"__all__\"  # Or specify fields\n\n        return SpecificForm\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        # Pass the model label to the template so it can be preserved\n        # in the form action\n        context[\"model_label\"] = self.request.GET.get(\"model\")\n        return context\n"
  },
  {
    "path": "src/polymorphic/tests/migrations/0001_initial.py",
    "content": "# Generated by Django 4.2 on 2026-02-05 14:47\n\nfrom django.conf import settings\nfrom django.db import migrations, models\nimport django.db.models.deletion\nimport django.db.models.manager\nimport polymorphic.showfields\nimport polymorphic.tests.models\nimport uuid\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('contenttypes', '0002_remove_content_type_name'),\n        ('auth', '0012_alter_user_first_name_max_length'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='Account',\n            fields=[\n                ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='account', serialize=False, to=settings.AUTH_USER_MODEL)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Author',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Base',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field_b', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldType, models.Model),\n        ),\n        migrations.CreateModel(\n            name='BetMultiple',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='BlogBase',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),\n        ),\n        migrations.CreateModel(\n            name='Book',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.author')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Bookmark',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('url', models.URLField()),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='ChoiceBlank',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='CustomPkBase',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('b', models.CharField(max_length=1)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),\n        ),\n        migrations.CreateModel(\n            name='DeepCopyTester',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('binary_field', models.BinaryField()),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='DerivedManagerTest',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('abstract_field', models.CharField(max_length=32)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='DisparateKeysParent',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('text', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Duck',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='DucksLake',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('time', models.CharField(max_length=10)),\n                ('duck', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.duck')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Enhance_Base',\n            fields=[\n                ('base_id', models.AutoField(primary_key=True, serialize=False)),\n                ('field_b', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),\n        ),\n        migrations.CreateModel(\n            name='Enhance_Plain',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field_p', models.CharField(max_length=30)),\n            ],\n        ),\n        migrations.CreateModel(\n            name='GenericFKParent',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(max_length=100)),\n            ],\n        ),\n        migrations.CreateModel(\n            name='InitTestModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('bar', models.CharField(max_length=300)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldType, models.Model),\n        ),\n        migrations.CreateModel(\n            name='InlineParent',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('title', models.CharField(max_length=30)),\n            ],\n        ),\n        migrations.CreateModel(\n            name='M2MAdminTest',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='M2MThroughBase',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(max_length=50)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='M2MThroughMembership',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('role', models.CharField(max_length=50)),\n                ('joined_date', models.DateField(auto_now_add=True)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='ManagerTest',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'base_manager_name': 'objects',\n            },\n        ),\n        migrations.CreateModel(\n            name='ManagerTestPlain',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n            ],\n            options={\n                'base_manager_name': 'objects',\n            },\n        ),\n        migrations.CreateModel(\n            name='Model2A',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field1', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldType, models.Model),\n        ),\n        migrations.CreateModel(\n            name='ModelArticle',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('sales_points', models.IntegerField()),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='ModelExtraA',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field1', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),\n        ),\n        migrations.CreateModel(\n            name='ModelExtraExternal',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('topic', models.CharField(max_length=30)),\n            ],\n        ),\n        migrations.CreateModel(\n            name='ModelShow1_plain',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field1', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='MROBase1',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field1', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldType, models.Model),\n        ),\n        migrations.CreateModel(\n            name='MROBase3',\n            fields=[\n                ('base_3_id', models.AutoField(primary_key=True, serialize=False)),\n            ],\n        ),\n        migrations.CreateModel(\n            name='MultiTableBase',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field1', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='MyBaseModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='NatKeyParent',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('slug', models.SlugField(unique=True)),\n                ('content', models.CharField(blank=True, max_length=100)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='NormalBase',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('nb_field', models.IntegerField()),\n            ],\n        ),\n        migrations.CreateModel(\n            name='One2OneRelatingModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field1', models.CharField(max_length=30)),\n                ('one2one', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='tests.model2a')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Participant',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='PlainA',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field1', models.CharField(max_length=30)),\n            ],\n        ),\n        migrations.CreateModel(\n            name='PlainParentModelWithManager',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='PolymorphicTagBase',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('tag', models.SlugField()),\n                ('object_id', models.PositiveIntegerField()),\n                ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='ProxyBase',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('some_data', models.CharField(max_length=128)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Regression295Related',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('_real_field', models.CharField(max_length=10)),\n            ],\n        ),\n        migrations.CreateModel(\n            name='RelatedKeyModel',\n            fields=[\n                ('custom_id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),\n            ],\n        ),\n        migrations.CreateModel(\n            name='RelatedManagerTest',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='RelationBase',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field_base', models.CharField(max_length=30)),\n                ('fk', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='relationbase_set', to='tests.relationbase')),\n                ('m2m', models.ManyToManyField(to='tests.relationbase')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),\n        ),\n        migrations.CreateModel(\n            name='SubclassSelectorAbstractBaseModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('base_field', models.CharField(default='test_bf', max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Top',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(max_length=50)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='UUIDPlainA',\n            fields=[\n                ('uuid_primary_key', models.UUIDField(default=uuid.uuid1, primary_key=True, serialize=False)),\n                ('field1', models.CharField(max_length=30)),\n            ],\n        ),\n        migrations.CreateModel(\n            name='UUIDProject',\n            fields=[\n                ('uuid_primary_key', models.UUIDField(default=uuid.uuid1, primary_key=True, serialize=False)),\n                ('topic', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),\n        ),\n        migrations.CreateModel(\n            name='Assignment',\n            fields=[\n                ('bookmark_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.bookmark')),\n                ('assigned_to', models.CharField(max_length=100)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.bookmark',),\n        ),\n        migrations.CreateModel(\n            name='BlogA',\n            fields=[\n                ('blogbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.blogbase')),\n                ('info', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.blogbase',),\n        ),\n        migrations.CreateModel(\n            name='BlogB',\n            fields=[\n                ('blogbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.blogbase')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.blogbase',),\n        ),\n        migrations.CreateModel(\n            name='BlueHeadDuck',\n            fields=[\n                ('duck_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.duck')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.duck',),\n        ),\n        migrations.CreateModel(\n            name='ChoiceAthlete',\n            fields=[\n                ('choiceblank_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.choiceblank')),\n                ('choice', models.CharField(max_length=100)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.choiceblank',),\n        ),\n        migrations.CreateModel(\n            name='CustomPkInherit',\n            fields=[\n                ('custompkbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='tests.custompkbase')),\n                ('custom_id', models.AutoField(primary_key=True, serialize=False)),\n                ('i', models.CharField(max_length=1)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.custompkbase',),\n        ),\n        migrations.CreateModel(\n            name='DeepCopyTester2',\n            fields=[\n                ('deepcopytester_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.deepcopytester')),\n                ('binary_field2', models.BinaryField()),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.deepcopytester',),\n        ),\n        migrations.CreateModel(\n            name='DerivedManagerTest2',\n            fields=[\n                ('derivedmanagertest_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.derivedmanagertest')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.derivedmanagertest',),\n        ),\n        migrations.CreateModel(\n            name='DisparateKeysChild1',\n            fields=[\n                ('disparatekeysparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='tests.disparatekeysparent')),\n                ('key', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='tests.relatedkeymodel')),\n                ('text_child1', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.disparatekeysparent',),\n        ),\n        migrations.CreateModel(\n            name='DisparateKeysChild2',\n            fields=[\n                ('disparatekeysparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='tests.disparatekeysparent')),\n                ('text_child2', models.CharField(max_length=30)),\n                ('key', models.PositiveIntegerField(primary_key=True, serialize=False)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.disparatekeysparent',),\n        ),\n        migrations.CreateModel(\n            name='Enhance_Inherit',\n            fields=[\n                ('enhance_plain_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='tests.enhance_plain')),\n                ('enhance_base_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.enhance_base')),\n                ('field_i', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.enhance_base', 'tests.enhance_plain'),\n        ),\n        migrations.CreateModel(\n            name='FKTestChild',\n            fields=[\n                ('base_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.base')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.base',),\n        ),\n        migrations.CreateModel(\n            name='InitTestModelSubclass',\n            fields=[\n                ('inittestmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.inittestmodel')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.inittestmodel',),\n        ),\n        migrations.CreateModel(\n            name='M2MAdminTestChildA',\n            fields=[\n                ('m2madmintest_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.m2madmintest')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.m2madmintest',),\n        ),\n        migrations.CreateModel(\n            name='M2MAdminTestChildB',\n            fields=[\n                ('m2madmintest_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.m2madmintest')),\n                ('child_as', models.ManyToManyField(blank=True, related_name='related_bs', to='tests.m2madmintestchilda')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.m2madmintest',),\n        ),\n        migrations.CreateModel(\n            name='M2MThroughMembershipWithPerson',\n            fields=[\n                ('m2mthroughmembership_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.m2mthroughmembership')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.m2mthroughmembership',),\n        ),\n        migrations.CreateModel(\n            name='M2MThroughMembershipWithSpecialPerson',\n            fields=[\n                ('m2mthroughmembership_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.m2mthroughmembership')),\n                ('special_notes', models.TextField(blank=True, default='')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.m2mthroughmembership',),\n        ),\n        migrations.CreateModel(\n            name='M2MThroughPerson',\n            fields=[\n                ('m2mthroughbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.m2mthroughbase')),\n                ('email', models.EmailField(blank=True, max_length=254)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.m2mthroughbase',),\n        ),\n        migrations.CreateModel(\n            name='M2MThroughProject',\n            fields=[\n                ('m2mthroughbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.m2mthroughbase')),\n                ('description', models.TextField(blank=True)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.m2mthroughbase',),\n        ),\n        migrations.CreateModel(\n            name='ManagerTestChild',\n            fields=[\n                ('managertest_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.managertest')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.managertest',),\n        ),\n        migrations.CreateModel(\n            name='ManagerTestChildPlain',\n            fields=[\n                ('managertestplain_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.managertestplain')),\n            ],\n            bases=('tests.managertestplain',),\n        ),\n        migrations.CreateModel(\n            name='Middle',\n            fields=[\n                ('top_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.top')),\n                ('description', models.TextField()),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.top',),\n        ),\n        migrations.CreateModel(\n            name='Model2B',\n            fields=[\n                ('model2a_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.model2a')),\n                ('field2', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.tests.models.RandomMixinB, 'tests.model2a'),\n        ),\n        migrations.CreateModel(\n            name='ModelComponent',\n            fields=[\n                ('modelarticle_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.modelarticle')),\n                ('name', models.CharField(max_length=300)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.modelarticle',),\n        ),\n        migrations.CreateModel(\n            name='ModelExtraB',\n            fields=[\n                ('modelextraa_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.modelextraa')),\n                ('field2', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.modelextraa',),\n        ),\n        migrations.CreateModel(\n            name='ModelPackage',\n            fields=[\n                ('modelarticle_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.modelarticle')),\n                ('name', models.CharField(max_length=300)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.modelarticle',),\n        ),\n        migrations.CreateModel(\n            name='ModelShow2_plain',\n            fields=[\n                ('modelshow1_plain_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.modelshow1_plain')),\n                ('field2', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.modelshow1_plain',),\n        ),\n        migrations.CreateModel(\n            name='ModelWithMyManager',\n            fields=[\n                ('model2a_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.model2a')),\n                ('field4', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldTypeAndContent, 'tests.model2a'),\n        ),\n        migrations.CreateModel(\n            name='ModelWithMyManager2',\n            fields=[\n                ('model2a_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.model2a')),\n                ('field4', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldTypeAndContent, 'tests.model2a'),\n        ),\n        migrations.CreateModel(\n            name='ModelWithMyManagerDefault',\n            fields=[\n                ('model2a_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.model2a')),\n                ('field4', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldTypeAndContent, 'tests.model2a'),\n            managers=[\n                ('my_objects', django.db.models.manager.Manager()),\n            ],\n        ),\n        migrations.CreateModel(\n            name='ModelWithMyManagerNoDefault',\n            fields=[\n                ('model2a_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.model2a')),\n                ('field4', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldTypeAndContent, 'tests.model2a'),\n        ),\n        migrations.CreateModel(\n            name='ModelX',\n            fields=[\n                ('base_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.base')),\n                ('field_x', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.base',),\n        ),\n        migrations.CreateModel(\n            name='ModelY',\n            fields=[\n                ('base_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.base')),\n                ('field_y', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.base',),\n        ),\n        migrations.CreateModel(\n            name='MROBase2',\n            fields=[\n                ('mrobase1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.mrobase1')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.mrobase1',),\n        ),\n        migrations.CreateModel(\n            name='MultiTableDerived',\n            fields=[\n                ('multitablebase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.multitablebase')),\n                ('field2', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.multitablebase',),\n        ),\n        migrations.CreateModel(\n            name='MyChild1Model',\n            fields=[\n                ('mybasemodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.mybasemodel')),\n                ('fieldA', models.IntegerField()),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.mybasemodel',),\n        ),\n        migrations.CreateModel(\n            name='MyChild2Model',\n            fields=[\n                ('mybasemodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.mybasemodel')),\n                ('fieldB', models.IntegerField()),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.mybasemodel',),\n        ),\n        migrations.CreateModel(\n            name='NatKeyChild',\n            fields=[\n                ('foo', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.natkeyparent')),\n                ('val', models.IntegerField(default=0)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.natkeyparent',),\n        ),\n        migrations.CreateModel(\n            name='NonProxyChild',\n            fields=[\n                ('proxybase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.proxybase')),\n                ('name', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.proxybase',),\n        ),\n        migrations.CreateModel(\n            name='NormalExtension',\n            fields=[\n                ('normalbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.normalbase')),\n                ('ne_field', models.CharField(max_length=50)),\n            ],\n            bases=('tests.normalbase',),\n        ),\n        migrations.CreateModel(\n            name='One2OneRelatingModelDerived',\n            fields=[\n                ('one2onerelatingmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.one2onerelatingmodel')),\n                ('field2', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.one2onerelatingmodel',),\n        ),\n        migrations.CreateModel(\n            name='ParentLinkAndRelatedName',\n            fields=[\n                ('superclass', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name='related_name_subclass', serialize=False, to='tests.modelshow1_plain')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.modelshow1_plain',),\n        ),\n        migrations.CreateModel(\n            name='PlainB',\n            fields=[\n                ('plaina_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.plaina')),\n                ('field2', models.CharField(max_length=30)),\n            ],\n            bases=('tests.plaina',),\n        ),\n        migrations.CreateModel(\n            name='PlainD',\n            fields=[\n                ('plaina_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.plaina')),\n                ('field2', models.CharField(max_length=30)),\n            ],\n            bases=('tests.plaina',),\n        ),\n        migrations.CreateModel(\n            name='PolymorphicTagA',\n            fields=[\n                ('polymorphictagbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.polymorphictagbase')),\n                ('priority', models.IntegerField(default=0)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.polymorphictagbase',),\n        ),\n        migrations.CreateModel(\n            name='PolymorphicTagB',\n            fields=[\n                ('polymorphictagbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.polymorphictagbase')),\n                ('color', models.CharField(default='', max_length=20)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.polymorphictagbase',),\n        ),\n        migrations.CreateModel(\n            name='RelationA',\n            fields=[\n                ('relationbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.relationbase')),\n                ('field_a', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.relationbase',),\n        ),\n        migrations.CreateModel(\n            name='RelationB',\n            fields=[\n                ('relationbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.relationbase')),\n                ('field_b', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.relationbase',),\n        ),\n        migrations.CreateModel(\n            name='SpecialAccount1',\n            fields=[\n                ('account_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.account')),\n                ('extra1', models.IntegerField(blank=True, default=None, null=True)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.account',),\n        ),\n        migrations.CreateModel(\n            name='SpecialAccount2',\n            fields=[\n                ('account_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.account')),\n                ('extra1', models.CharField(blank=True, default='', max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.account',),\n        ),\n        migrations.CreateModel(\n            name='SpecialBook',\n            fields=[\n                ('book_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.book')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.book',),\n        ),\n        migrations.CreateModel(\n            name='SubclassSelectorAbstractConcreteModel',\n            fields=[\n                ('subclassselectorabstractbasemodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.subclassselectorabstractbasemodel')),\n                ('abstract_field', models.CharField(default='test_af', max_length=30)),\n                ('concrete_field', models.CharField(default='test_cf', max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.subclassselectorabstractbasemodel',),\n        ),\n        migrations.CreateModel(\n            name='UserProfile',\n            fields=[\n                ('participant_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.participant')),\n                ('name', models.CharField(max_length=100)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.participant',),\n        ),\n        migrations.CreateModel(\n            name='UUIDArtProject',\n            fields=[\n                ('uuidproject_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.uuidproject')),\n                ('artist', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.uuidproject',),\n        ),\n        migrations.CreateModel(\n            name='UUIDPlainB',\n            fields=[\n                ('uuidplaina_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.uuidplaina')),\n                ('field2', models.CharField(max_length=30)),\n            ],\n            bases=('tests.uuidplaina',),\n        ),\n        migrations.CreateModel(\n            name='UUIDResearchProject',\n            fields=[\n                ('uuidproject_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.uuidproject')),\n                ('supervisor', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.uuidproject',),\n        ),\n        migrations.CreateModel(\n            name='TaggedItem',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('tag', models.SlugField()),\n                ('object_id', models.PositiveIntegerField()),\n                ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='SwappedModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='SubclassSelectorProxyBaseModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('base_field', models.CharField(default='test_bf', max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='RelatingModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('many2many', models.ManyToManyField(to='tests.model2a')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='RelatedNameClash',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldType, models.Model),\n        ),\n        migrations.CreateModel(\n            name='Regression295Parent',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n                ('related_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.regression295related')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='RecursionBug',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n                ('status', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='recursions', to='tests.plaina')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='RankedAthlete',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('rank', models.IntegerField()),\n                ('bet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.betmultiple')),\n                ('choiceAthlete', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.choiceblank')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='ProxiedBase',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),\n        ),\n        migrations.CreateModel(\n            name='PlainChildModelWithManager',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('fk', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='childmodel_set', to='tests.plainparentmodelwithmanager')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='ParentModelWithManager',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='NonPolymorphicParent',\n            fields=[\n                ('group_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='auth.group')),\n                ('test', models.CharField(default='test_non_polymorphic_parent', max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('auth.group', models.Model),\n        ),\n        migrations.CreateModel(\n            name='NoChildren',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field1', models.CharField(max_length=12)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='ModelWithPolyFK',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(max_length=100)),\n                ('poly_fk', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='tests.model2a')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='ModelUnderRelParent',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field1', models.CharField(max_length=30)),\n                ('_private', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='ModelUnderRelChild',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('_private2', models.CharField(max_length=30)),\n                ('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='children', to='tests.modelunderrelparent')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='ModelShow3',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field1', models.CharField(max_length=30)),\n                ('m2m', models.ManyToManyField(to='tests.modelshow3')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),\n        ),\n        migrations.CreateModel(\n            name='ModelShow2',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field1', models.CharField(max_length=30)),\n                ('m2m', models.ManyToManyField(to='tests.modelshow2')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldContent, models.Model),\n        ),\n        migrations.CreateModel(\n            name='ModelShow1',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field1', models.CharField(max_length=30)),\n                ('m2m', models.ManyToManyField(to='tests.modelshow1')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldType, models.Model),\n        ),\n        migrations.CreateModel(\n            name='ModelOrderLine',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('articles', models.ManyToManyField(related_name='orderline', to='tests.modelarticle')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='ModelFieldNameTest',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('modelfieldnametest', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldType, models.Model),\n        ),\n        migrations.CreateModel(\n            name='LakeWithThrough',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('ducks', models.ManyToManyField(through='tests.DucksLake', to='tests.duck')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Lake',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('ducks', models.ManyToManyField(to='tests.duck')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='InlineModelA',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field1', models.CharField(max_length=30)),\n                ('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inline_children', to='tests.inlineparent')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Foo',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='FKTestBase',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='FKTest',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('fk', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='tests.base')),\n            ],\n        ),\n        migrations.AddField(\n            model_name='duckslake',\n            name='lake',\n            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.lakewiththrough'),\n        ),\n        migrations.CreateModel(\n            name='DirectM2MContainer',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(max_length=50)),\n                ('items', models.ManyToManyField(blank=True, related_name='containers', to='tests.m2mthroughbase')),\n            ],\n        ),\n        migrations.AddField(\n            model_name='derivedmanagertest',\n            name='related_test',\n            field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='derived', to='tests.relatedmanagertest'),\n        ),\n        migrations.CreateModel(\n            name='DateModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('date', models.DateTimeField()),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='ChildModelWithManager',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field1', models.CharField(max_length=30)),\n                ('fk', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='childmodel_set', to='tests.parentmodelwithmanager')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='BlogEntry_limit_choices_to',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('text', models.CharField(max_length=30)),\n                ('blog', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.blogbase')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),\n        ),\n        migrations.AddField(\n            model_name='betmultiple',\n            name='answer',\n            field=models.ManyToManyField(blank=True, through='tests.RankedAthlete', to='tests.choiceblank'),\n        ),\n        migrations.CreateModel(\n            name='Baz',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created_at', models.DateTimeField(auto_now_add=True)),\n                ('modified_at', models.DateTimeField(auto_now=True)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='Bar',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created_at', models.DateTimeField(auto_now_add=True)),\n                ('modified_at', models.DateTimeField(auto_now=True)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='ArtProject',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('topic', models.CharField(max_length=30)),\n                ('artist', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n        ),\n        migrations.CreateModel(\n            name='SwappableModel',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'swappable': 'POLYMORPHIC_TEST_SWAPPABLE',\n            },\n        ),\n        migrations.CreateModel(\n            name='ProxyChild',\n            fields=[\n            ],\n            options={\n                'proxy': True,\n                'indexes': [],\n                'constraints': [],\n            },\n            bases=('tests.proxybase',),\n        ),\n        migrations.CreateModel(\n            name='ProxyModelBase',\n            fields=[\n            ],\n            options={\n                'proxy': True,\n                'indexes': [],\n                'constraints': [],\n            },\n            bases=('tests.proxiedbase',),\n        ),\n        migrations.CreateModel(\n            name='RedheadDuck',\n            fields=[\n            ],\n            options={\n                'proxy': True,\n                'indexes': [],\n                'constraints': [],\n            },\n            bases=('tests.duck',),\n        ),\n        migrations.CreateModel(\n            name='RubberDuck',\n            fields=[\n            ],\n            options={\n                'proxy': True,\n                'indexes': [],\n                'constraints': [],\n            },\n            bases=('tests.duck',),\n        ),\n        migrations.CreateModel(\n            name='SubclassSelectorProxyModel',\n            fields=[\n            ],\n            options={\n                'proxy': True,\n                'indexes': [],\n                'constraints': [],\n            },\n            bases=('tests.subclassselectorproxybasemodel',),\n        ),\n        migrations.CreateModel(\n            name='Bottom',\n            fields=[\n                ('middle_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.middle')),\n                ('author', models.CharField(max_length=50)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.middle',),\n        ),\n        migrations.CreateModel(\n            name='DisparateKeysGrandChild',\n            fields=[\n                ('disparatekeyschild1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.disparatekeyschild1')),\n                ('text_grand_child', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.disparatekeyschild1',),\n        ),\n        migrations.CreateModel(\n            name='DisparateKeysGrandChild2',\n            fields=[\n                ('disparatekeyschild2_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.disparatekeyschild2')),\n                ('text_grand_child', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.disparatekeyschild2',),\n        ),\n        migrations.CreateModel(\n            name='M2MAdminTestChildC',\n            fields=[\n                ('m2madmintestchildb_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.m2madmintestchildb')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.m2madmintestchildb',),\n        ),\n        migrations.CreateModel(\n            name='M2MThroughSpecialPerson',\n            fields=[\n                ('m2mthroughperson_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.m2mthroughperson')),\n                ('special_code', models.CharField(blank=True, max_length=20)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.m2mthroughperson',),\n        ),\n        migrations.CreateModel(\n            name='Model2BFiltered',\n            fields=[\n                ('model2b_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.model2b')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.model2b',),\n        ),\n        migrations.CreateModel(\n            name='Model2C',\n            fields=[\n                ('model2b_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.model2b')),\n                ('field3', models.CharField(blank=True, default='', max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.tests.models.RandomMixinC, 'tests.model2b'),\n        ),\n        migrations.CreateModel(\n            name='ModelExtraC',\n            fields=[\n                ('modelextrab_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.modelextrab')),\n                ('field3', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.modelextrab',),\n        ),\n        migrations.CreateModel(\n            name='MRODerived',\n            fields=[\n                ('mrobase3_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='tests.mrobase3')),\n                ('mrobase2_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.mrobase2')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.mrobase2', 'tests.mrobase3'),\n        ),\n        migrations.CreateModel(\n            name='PlainC',\n            fields=[\n                ('plainb_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.plainb')),\n                ('field3', models.CharField(max_length=30)),\n            ],\n            bases=('tests.plainb',),\n        ),\n        migrations.CreateModel(\n            name='PolyExtension',\n            fields=[\n                ('normalextension_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.normalextension')),\n                ('poly_ext_field', models.IntegerField()),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.normalextension', models.Model),\n        ),\n        migrations.CreateModel(\n            name='ProxyModelA',\n            fields=[\n                ('proxiedbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.proxiedbase')),\n                ('field1', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.proxymodelbase',),\n        ),\n        migrations.CreateModel(\n            name='ProxyModelB',\n            fields=[\n                ('proxiedbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.proxiedbase')),\n                ('field2', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.proxymodelbase',),\n        ),\n        migrations.CreateModel(\n            name='RelationBC',\n            fields=[\n                ('relationb_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.relationb')),\n                ('field_c', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.relationb',),\n        ),\n        migrations.CreateModel(\n            name='SpecialAccount1_1',\n            fields=[\n                ('specialaccount1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.specialaccount1')),\n                ('extra2', models.IntegerField(blank=True, default=None, null=True)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.specialaccount1',),\n        ),\n        migrations.CreateModel(\n            name='SubclassSelectorProxyConcreteModel',\n            fields=[\n                ('subclassselectorproxybasemodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.subclassselectorproxybasemodel')),\n                ('concrete_field', models.CharField(default='test_cf', max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.subclassselectorproxymodel',),\n        ),\n        migrations.CreateModel(\n            name='UUIDArtProjectA',\n            fields=[\n                ('uuidartproject_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.uuidartproject')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.uuidartproject',),\n        ),\n        migrations.CreateModel(\n            name='UUIDPlainC',\n            fields=[\n                ('uuidplainb_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.uuidplainb')),\n                ('field3', models.CharField(max_length=30)),\n            ],\n            bases=('tests.uuidplainb',),\n        ),\n        migrations.CreateModel(\n            name='Team',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('team_name', models.CharField(max_length=100)),\n                ('user_profiles', models.ManyToManyField(related_name='user_teams', to='tests.userprofile')),\n            ],\n        ),\n        migrations.AddField(\n            model_name='m2mthroughmembership',\n            name='person',\n            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.m2mthroughperson'),\n        ),\n        migrations.AddField(\n            model_name='m2madmintestchilda',\n            name='child_bs',\n            field=models.ManyToManyField(blank=True, related_name='related_as', to='tests.m2madmintestchildb'),\n        ),\n        migrations.CreateModel(\n            name='InlineModelB',\n            fields=[\n                ('inlinemodela_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.inlinemodela')),\n                ('field2', models.CharField(max_length=30)),\n                ('file_upload', models.FileField(blank=True, default=None, null=True, upload_to='test_uploads/')),\n                ('plain_a', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='inline_bs', to='tests.plaina')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.inlinemodela',),\n        ),\n        migrations.CreateModel(\n            name='BlogEntry',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('text', models.CharField(max_length=30)),\n                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),\n                ('blog', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.bloga')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),\n        ),\n        migrations.CreateModel(\n            name='PurpleHeadDuck',\n            fields=[\n            ],\n            options={\n                'proxy': True,\n                'indexes': [],\n                'constraints': [],\n            },\n            bases=('tests.blueheadduck', models.Model),\n        ),\n        migrations.CreateModel(\n            name='Model2CFiltered',\n            fields=[\n                ('model2bfiltered_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.model2bfiltered')),\n                ('field3', models.CharField(blank=True, default='', max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.model2bfiltered',),\n        ),\n        migrations.CreateModel(\n            name='Model2D',\n            fields=[\n                ('model2c_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.model2c')),\n                ('field4', models.CharField(max_length=30)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.model2c',),\n        ),\n        migrations.CreateModel(\n            name='PolyExtChild',\n            fields=[\n                ('polyextension_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.polyextension')),\n                ('poly_child_field', models.CharField(max_length=50)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.polyextension',),\n        ),\n        migrations.CreateModel(\n            name='UUIDArtProjectB',\n            fields=[\n                ('uuidartprojecta_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.uuidartprojecta')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.uuidartprojecta',),\n        ),\n        migrations.CreateModel(\n            name='M2MThroughProjectWithTeam',\n            fields=[\n                ('m2mthroughproject_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.m2mthroughproject')),\n                ('team', models.ManyToManyField(blank=True, related_name='projects', through='tests.M2MThroughMembership', to='tests.m2mthroughperson')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.m2mthroughproject',),\n        ),\n        migrations.AddField(\n            model_name='m2mthroughmembership',\n            name='project',\n            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.m2mthroughprojectwithteam'),\n        ),\n        migrations.CreateModel(\n            name='Model2CNamedDefault',\n            fields=[\n                ('model2cfiltered_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.model2cfiltered')),\n            ],\n            options={\n                'default_manager_name': 'custom_objects',\n            },\n            bases=('tests.model2cfiltered',),\n            managers=[\n                ('custom_objects', django.db.models.manager.Manager()),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Model2CNamedManagers',\n            fields=[\n                ('model2cfiltered_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.model2cfiltered')),\n            ],\n            options={\n                'base_manager_name': 'all_objects',\n                'default_manager_name': 'filtered_objects',\n            },\n            bases=('tests.model2cfiltered',),\n            managers=[\n                ('all_objects', django.db.models.manager.Manager()),\n                ('filtered_objects', django.db.models.manager.Manager()),\n            ],\n        ),\n        migrations.CreateModel(\n            name='UUIDArtProjectC',\n            fields=[\n                ('uuidartprojectb_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.uuidartprojectb')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.uuidartprojectb',),\n        ),\n        migrations.CreateModel(\n            name='UUIDArtProjectD',\n            fields=[\n                ('uuidartprojectc_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.uuidartprojectc')),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.uuidartprojectc',),\n        ),\n    ]\n"
  },
  {
    "path": "src/polymorphic/tests/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/models.py",
    "content": "import uuid\n\nfrom django.contrib.auth.models import Group\nfrom django.contrib.auth import get_user_model\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.db.models import Manager\nfrom django.db import models\nfrom django.db.models.query import QuerySet\nfrom django.db.models import F\nfrom django.db.models.functions import Upper\nfrom django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation\n\nfrom polymorphic.managers import PolymorphicManager\nfrom polymorphic.models import PolymorphicModel\nfrom polymorphic.query import PolymorphicQuerySet\nfrom polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent\n\n\nclass PlainA(models.Model):\n    field1 = models.CharField(max_length=30)\n\n    def __str__(self):\n        return self.field1\n\n\nclass PlainB(PlainA):\n    field2 = models.CharField(max_length=30)\n\n\nclass PlainC(PlainB):\n    field3 = models.CharField(max_length=30)\n\n\nclass PlainD(PlainA):\n    field2 = models.CharField(max_length=30)\n\n\nclass Model2A(ShowFieldType, PolymorphicModel):\n    field1 = models.CharField(max_length=30)\n    polymorphic_showfield_deferred = True\n\n\nclass RandomMixinB:\n    def random_method(self):\n        return \"random b\"\n\n\nclass Model2B(RandomMixinB, Model2A):\n    field2 = models.CharField(max_length=30)\n\n\nclass RandomMixinC:\n    def random_method(self):\n        return \"random c\"\n\n\nclass Model2C(RandomMixinC, Model2B):\n    field3 = models.CharField(max_length=30, blank=True, default=\"\")\n\n\nclass Model2D(Model2C):\n    field4 = models.CharField(max_length=30)\n\n\nclass ModelExtraA(ShowFieldTypeAndContent, PolymorphicModel):\n    field1 = models.CharField(max_length=30)\n\n\nclass ModelExtraB(ModelExtraA):\n    field2 = models.CharField(max_length=30)\n\n\nclass ModelExtraC(ModelExtraB):\n    field3 = models.CharField(max_length=30)\n\n\nclass ModelExtraExternal(models.Model):\n    topic = models.CharField(max_length=30)\n\n\nclass ModelShow1(ShowFieldType, PolymorphicModel):\n    field1 = models.CharField(max_length=30)\n    m2m = models.ManyToManyField(\"self\")\n\n\nclass ModelShow2(ShowFieldContent, PolymorphicModel):\n    field1 = models.CharField(max_length=30)\n    m2m = models.ManyToManyField(\"self\")\n\n\nclass ModelShow3(ShowFieldTypeAndContent, PolymorphicModel):\n    field1 = models.CharField(max_length=30)\n    m2m = models.ManyToManyField(\"self\")\n\n\nclass ModelShow1_plain(PolymorphicModel):\n    field1 = models.CharField(max_length=30)\n\n\nclass ModelShow2_plain(ModelShow1_plain):\n    field2 = models.CharField(max_length=30)\n\n\nclass Base(ShowFieldType, PolymorphicModel):\n    polymorphic_showfield_deferred = True\n    field_b = models.CharField(max_length=30)\n\n\nclass ModelX(Base):\n    field_x = models.CharField(max_length=30)\n\n\nclass ModelY(Base):\n    field_y = models.CharField(max_length=30)\n\n\nclass Enhance_Plain(models.Model):\n    field_p = models.CharField(max_length=30)\n\n\nclass Enhance_Base(ShowFieldTypeAndContent, PolymorphicModel):\n    base_id = models.AutoField(primary_key=True)\n    field_b = models.CharField(max_length=30)\n\n\nclass Enhance_Inherit(Enhance_Base, Enhance_Plain):\n    field_i = models.CharField(max_length=30)\n\n\nclass RelationAbstractModel(models.Model):\n    class Meta:\n        abstract = True\n\n\nclass RelationBase(RelationAbstractModel, ShowFieldTypeAndContent, PolymorphicModel):\n    field_base = models.CharField(max_length=30)\n    fk = models.ForeignKey(\n        \"self\", on_delete=models.CASCADE, null=True, related_name=\"relationbase_set\"\n    )\n    m2m = models.ManyToManyField(\"self\")\n\n\nclass RelationA(RelationBase):\n    field_a = models.CharField(max_length=30)\n\n\nclass RelationB(RelationBase):\n    field_b = models.CharField(max_length=30)\n\n\nclass RelationBC(RelationB):\n    field_c = models.CharField(max_length=30)\n\n\nclass RelatingModel(models.Model):\n    many2many = models.ManyToManyField(Model2A)\n\n\nclass One2OneRelatingModel(PolymorphicModel):\n    one2one = models.OneToOneField(Model2A, on_delete=models.CASCADE)\n    field1 = models.CharField(max_length=30)\n\n\nclass One2OneRelatingModelDerived(One2OneRelatingModel):\n    field2 = models.CharField(max_length=30)\n\n\nclass ModelUnderRelParent(PolymorphicModel):\n    field1 = models.CharField(max_length=30)\n    _private = models.CharField(max_length=30)\n\n\nclass ModelUnderRelChild(PolymorphicModel):\n    parent = models.ForeignKey(\n        ModelUnderRelParent, on_delete=models.CASCADE, related_name=\"children\"\n    )\n    _private2 = models.CharField(max_length=30)\n\n\nclass MyManagerQuerySet(PolymorphicQuerySet):\n    def my_queryset_foo(self):\n        # Just a method to prove the existence of the custom queryset.\n        return self.all()\n\n\nclass MyManager(PolymorphicManager):\n    queryset_class = MyManagerQuerySet\n\n    def get_queryset(self):\n        return super().get_queryset().order_by(\"-field1\")\n\n    def my_queryset_foo(self):\n        return self.all().my_queryset_foo()\n\n\nclass ModelWithMyManager(ShowFieldTypeAndContent, Model2A):\n    objects = MyManager()\n    field4 = models.CharField(max_length=30)\n\n\nclass ModelWithMyManagerNoDefault(ShowFieldTypeAndContent, Model2A):\n    objects = PolymorphicManager()\n    my_objects = MyManager()\n    field4 = models.CharField(max_length=30)\n\n\nclass ModelWithMyManagerDefault(ShowFieldTypeAndContent, Model2A):\n    my_objects = MyManager()\n    objects = PolymorphicManager()\n    field4 = models.CharField(max_length=30)\n\n\nclass ModelWithMyManager2(ShowFieldTypeAndContent, Model2A):\n    objects = MyManagerQuerySet.as_manager()\n    field4 = models.CharField(max_length=30)\n\n\nclass ModelArticle(PolymorphicModel):\n    sales_points = models.IntegerField()\n\n\nclass ModelPackage(ModelArticle):\n    name = models.CharField(max_length=300)\n\n\nclass ModelComponent(ModelArticle):\n    name = models.CharField(max_length=300)\n\n\nclass ModelOrderLine(models.Model):\n    articles = models.ManyToManyField(ModelArticle, related_name=\"orderline\")\n\n\nclass MROBase1(ShowFieldType, PolymorphicModel):\n    objects = MyManager()\n    field1 = models.CharField(max_length=30)  # needed as MyManager uses it\n\n\nclass MROBase2(MROBase1):\n    pass\n    # No manager_inheritance_from_future or Meta set. test that polymorphic restores that.\n\n\nclass MROBase3(models.Model):\n    # make sure 'id' field doesn't clash, detected by Django 1.11\n    base_3_id = models.AutoField(primary_key=True)\n    objects = models.Manager()\n\n\nclass MRODerived(MROBase2, MROBase3):\n    pass\n\n\nclass ParentModelWithManager(PolymorphicModel):\n    pass\n\n\nclass ChildModelWithManager(PolymorphicModel):\n    # Also test whether foreign keys receive the manager:\n    field1 = models.CharField(max_length=30)  # needed as MyManager uses it\n    fk = models.ForeignKey(\n        ParentModelWithManager, on_delete=models.CASCADE, related_name=\"childmodel_set\"\n    )\n    objects = MyManager()\n\n\nclass PlainMyManagerQuerySet(QuerySet):\n    def my_queryset_foo(self):\n        # Just a method to prove the existence of the custom queryset.\n        return self.all()\n\n\nclass PlainMyManager(models.Manager):\n    def my_queryset_foo(self):\n        return self.get_queryset().my_queryset_foo()\n\n    def get_queryset(self):\n        return PlainMyManagerQuerySet(self.model, using=self._db)\n\n\nclass PlainParentModelWithManager(models.Model):\n    pass\n\n\nclass PlainChildModelWithManager(models.Model):\n    fk = models.ForeignKey(\n        PlainParentModelWithManager,\n        on_delete=models.CASCADE,\n        related_name=\"childmodel_set\",\n    )\n    objects = PlainMyManager()\n\n\nclass BlogBase(ShowFieldTypeAndContent, PolymorphicModel):\n    name = models.CharField(max_length=30)\n\n\nclass BlogA(BlogBase):\n    info = models.CharField(max_length=30)\n\n\nclass BlogB(BlogBase):\n    pass\n\n\nclass BlogEntry(ShowFieldTypeAndContent, PolymorphicModel):\n    blog = models.ForeignKey(BlogA, on_delete=models.CASCADE)\n    text = models.CharField(max_length=30)\n\n\nclass BlogEntry_limit_choices_to(ShowFieldTypeAndContent, PolymorphicModel):\n    blog = models.ForeignKey(BlogBase, on_delete=models.CASCADE)\n    text = models.CharField(max_length=30)\n\n\nclass ModelFieldNameTest(ShowFieldType, PolymorphicModel):\n    modelfieldnametest = models.CharField(max_length=30)\n\n\nclass InitTestModel(ShowFieldType, PolymorphicModel):\n    bar = models.CharField(max_length=300)\n\n    def __init__(self, *args, **kwargs):\n        kwargs[\"bar\"] = self.x()\n        super().__init__(*args, **kwargs)\n\n\nclass InitTestModelSubclass(InitTestModel):\n    def x(self):\n        return \"XYZ\"\n\n\n# models from github issue\n\n\nclass Top(PolymorphicModel):\n    name = models.CharField(max_length=50)\n\n\nclass Middle(Top):\n    description = models.TextField()\n\n\nclass Bottom(Middle):\n    author = models.CharField(max_length=50)\n\n\nclass UUIDProject(ShowFieldTypeAndContent, PolymorphicModel):\n    uuid_primary_key = models.UUIDField(primary_key=True, default=uuid.uuid1)\n    topic = models.CharField(max_length=30)\n\n\nclass UUIDArtProject(UUIDProject):\n    artist = models.CharField(max_length=30)\n\n\nclass UUIDResearchProject(UUIDProject):\n    supervisor = models.CharField(max_length=30)\n\n\nclass UUIDArtProjectA(UUIDArtProject): ...\n\n\nclass UUIDArtProjectB(UUIDArtProjectA): ...\n\n\nclass UUIDArtProjectC(UUIDArtProjectB): ...\n\n\nclass UUIDArtProjectD(UUIDArtProjectC): ...\n\n\nclass UUIDPlainA(models.Model):\n    uuid_primary_key = models.UUIDField(primary_key=True, default=uuid.uuid1)\n    field1 = models.CharField(max_length=30)\n\n\nclass UUIDPlainB(UUIDPlainA):\n    field2 = models.CharField(max_length=30)\n\n\nclass UUIDPlainC(UUIDPlainB):\n    field3 = models.CharField(max_length=30)\n\n\n# base -> proxy\n\n\nclass ProxyBase(PolymorphicModel):\n    some_data = models.CharField(max_length=128)\n\n\nclass ProxyChild(ProxyBase):\n    class Meta:\n        proxy = True\n\n\nclass NonProxyChild(ProxyBase):\n    name = models.CharField(max_length=30)\n\n\n# base -> proxy -> real models\n\n\nclass ProxiedBase(ShowFieldTypeAndContent, PolymorphicModel):\n    name = models.CharField(max_length=30)\n\n\nclass ProxyModelBase(ProxiedBase):\n    class Meta:\n        proxy = True\n\n\nclass ProxyModelA(ProxyModelBase):\n    field1 = models.CharField(max_length=30)\n\n\nclass ProxyModelB(ProxyModelBase):\n    field2 = models.CharField(max_length=30)\n\n\n# test bad field name\n# class TestBadFieldModel(ShowFieldType, PolymorphicModel):\n#    instance_of = models.CharField(max_length=30)\n\n\n# validation error: \"polymorphic.relatednameclash: Accessor for field 'polymorphic_ctype' clashes\n# with related field 'ContentType.relatednameclash_set'.\" (reported by Andrew Ingram)\n# fixed with related_name\nclass RelatedNameClash(ShowFieldType, PolymorphicModel):\n    ctype = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, editable=False)\n\n\n# class with a parent_link to superclass, and a related_name back to subclass\n\n\nclass ParentLinkAndRelatedName(ModelShow1_plain):\n    superclass = models.OneToOneField(\n        ModelShow1_plain,\n        on_delete=models.CASCADE,\n        parent_link=True,\n        related_name=\"related_name_subclass\",\n    )\n\n\nclass CustomPkBase(ShowFieldTypeAndContent, PolymorphicModel):\n    b = models.CharField(max_length=1)\n\n\nclass CustomPkInherit(CustomPkBase):\n    custom_id = models.AutoField(primary_key=True)\n    i = models.CharField(max_length=1)\n\n\nclass DateModel(PolymorphicModel):\n    date = models.DateTimeField()\n\n\n# Define abstract and swappable (being swapped for SwappedModel) models\n# To test manager validation (should be skipped for such models)\nclass AbstractModel(PolymorphicModel):\n    class Meta:\n        abstract = True\n\n\nclass SwappableModel(AbstractModel):\n    class Meta:\n        swappable = \"POLYMORPHIC_TEST_SWAPPABLE\"\n\n\nclass SwappedModel(AbstractModel):\n    pass\n\n\nclass InlineParent(models.Model):\n    title = models.CharField(max_length=30)\n\n\nclass InlineModelA(PolymorphicModel):\n    parent = models.ForeignKey(\n        InlineParent, related_name=\"inline_children\", on_delete=models.CASCADE\n    )\n    field1 = models.CharField(max_length=30)\n\n\nclass InlineModelB(InlineModelA):\n    field2 = models.CharField(max_length=30)\n\n    plain_a = models.ForeignKey(\n        PlainA,\n        null=True,\n        blank=True,\n        default=None,\n        on_delete=models.SET_NULL,\n        related_name=\"inline_bs\",\n    )\n\n    # File field for testing multipart encoding in polymorphic inlines (issue #380)\n    file_upload = models.FileField(upload_to=\"test_uploads/\", null=True, blank=True, default=None)\n\n\nclass AbstractProject(PolymorphicModel):\n    topic = models.CharField(max_length=30)\n\n    class Meta:\n        abstract = True\n\n\nclass ArtProject(AbstractProject):\n    artist = models.CharField(max_length=30)\n\n\nclass Duck(PolymorphicModel):\n    name = models.CharField(max_length=30)\n\n\nclass RedheadDuck(Duck):\n    class Meta:\n        proxy = True\n\n\nclass RubberDuck(Duck):\n    class Meta:\n        proxy = True\n\n\nclass MultiTableBase(PolymorphicModel):\n    field1 = models.CharField(max_length=30)\n\n\nclass MultiTableDerived(MultiTableBase):\n    field2 = models.CharField(max_length=30)\n\n\nclass SubclassSelectorAbstractBaseModel(PolymorphicModel):\n    base_field = models.CharField(max_length=30, default=\"test_bf\")\n\n\nclass SubclassSelectorAbstractModel(SubclassSelectorAbstractBaseModel):\n    abstract_field = models.CharField(max_length=30, default=\"test_af\")\n\n    class Meta:\n        abstract = True\n\n\nclass SubclassSelectorAbstractConcreteModel(SubclassSelectorAbstractModel):\n    concrete_field = models.CharField(max_length=30, default=\"test_cf\")\n\n\nclass SubclassSelectorProxyBaseModel(PolymorphicModel):\n    base_field = models.CharField(max_length=30, default=\"test_bf\")\n\n\nclass SubclassSelectorProxyModel(SubclassSelectorProxyBaseModel):\n    class Meta:\n        proxy = True\n\n\nclass SubclassSelectorProxyConcreteModel(SubclassSelectorProxyModel):\n    concrete_field = models.CharField(max_length=30, default=\"test_cf\")\n\n\nclass NonPolymorphicParent(PolymorphicModel, Group):\n    test = models.CharField(max_length=30, default=\"test_non_polymorphic_parent\")\n\n\nclass Participant(PolymorphicModel):\n    pass\n\n\nclass UserProfile(Participant):\n    name = models.CharField(max_length=100)\n\n    def __str__(self):\n        return self.name\n\n\nclass Team(models.Model):\n    team_name = models.CharField(max_length=100)\n    user_profiles = models.ManyToManyField(UserProfile, related_name=\"user_teams\")\n\n\nclass BlueHeadDuck(Duck):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.color = \"blue\"\n\n\nclass HomeDuck(models.Model):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.home = \"Duckburg\"\n\n    class Meta:\n        abstract = True\n\n\nclass PurpleHeadDuck(HomeDuck, BlueHeadDuck):\n    class Meta:\n        proxy = True\n\n\nclass Account(PolymorphicModel):\n    user = models.OneToOneField(\n        get_user_model(), primary_key=True, on_delete=models.CASCADE, related_name=\"account\"\n    )\n\n\nclass SpecialAccount1(Account):\n    extra1 = models.IntegerField(null=True, default=None, blank=True)\n\n\nclass SpecialAccount1_1(SpecialAccount1):\n    extra2 = models.IntegerField(null=True, default=None, blank=True)\n\n\nclass SpecialAccount2(Account):\n    extra1 = models.CharField(default=\"\", blank=True, max_length=30)\n\n\nclass ModelMixin(models.Model):\n    class Meta:\n        abstract = True\n\n    created_at = models.DateTimeField(auto_now_add=True)\n    modified_at = models.DateTimeField(auto_now=True)\n\n\nclass PolymorphicMixin(PolymorphicModel):\n    class Meta:\n        abstract = True\n\n    created_at = models.DateTimeField(auto_now_add=True)\n    modified_at = models.DateTimeField(auto_now=True)\n\n\nclass Foo(PolymorphicModel):\n    pass\n\n\nclass Bar(PolymorphicMixin, PolymorphicModel):\n    pass\n\n\nclass Baz(ModelMixin, PolymorphicModel):\n    pass\n\n\nclass MyBaseQuerySet(PolymorphicQuerySet):\n    def filter_by_user(self, _):\n        return self.all()\n\n\nclass MyBaseModel(PolymorphicModel):\n    ...\n    objects = MyBaseQuerySet.as_manager()\n\n\nclass MyChild1QuerySet(MyBaseQuerySet):\n    def filter_by_user(self, num):\n        return self.filter(fieldA__lt=num)\n\n\nclass MyChild1Model(MyBaseModel):\n    fieldA = models.IntegerField()\n    ...\n    objects = MyChild1QuerySet.as_manager()\n\n\nclass MyChild2QuerySet(MyBaseQuerySet):\n    def filter_by_user(self, num):\n        return self.filter(fieldB__gt=num)\n\n\nclass MyChild2Model(MyBaseModel):\n    fieldB = models.IntegerField()\n    ...\n    objects = PolymorphicManager.from_queryset(MyChild2QuerySet)()\n    base_manager = MyBaseQuerySet.as_manager()\n\n\nclass SpecialQuerySet(PolymorphicQuerySet):\n    def has_text(self, text):\n        return self.filter(abstract_field__icontains=text)\n\n\nclass SpecialPolymorphicManager(PolymorphicManager.from_queryset(SpecialQuerySet)):\n    def custom_queryset(self):\n        return self.get_queryset()\n\n\nclass AbstractManagerTest(PolymorphicModel):\n    \"\"\"\n    Tests that custom manager patterns work on abstract base models\n    \"\"\"\n\n    objects = SpecialPolymorphicManager()\n    basic_manager = Manager()\n    default_manager = PolymorphicManager()\n\n    abstract_field = models.CharField(max_length=32)\n\n    class Meta:\n        abstract = True\n\n\nclass RelatedManagerTest(models.Model): ...\n\n\nclass DerivedManagerTest(AbstractManagerTest):\n    related_test = models.ForeignKey(\n        RelatedManagerTest,\n        on_delete=models.CASCADE,\n        default=None,\n        null=True,\n        related_name=\"derived\",\n    )\n\n\nclass DerivedManagerTest2(DerivedManagerTest):\n    objects = PolymorphicManager()\n\n\nclass FKTestBase(PolymorphicModel): ...\n\n\nclass FKTestChild(Base): ...\n\n\nclass FKTest(models.Model):\n    fk = models.ForeignKey(Base, null=True, on_delete=models.SET_NULL)\n\n\nclass NoChildren(PolymorphicModel):\n    field1 = models.CharField(max_length=12)\n\n\nclass ModelWithPolyFK(models.Model):\n    \"\"\"Model with FK to polymorphic model for popup testing.\"\"\"\n\n    name = models.CharField(max_length=100)\n    poly_fk = models.ForeignKey(Model2A, on_delete=models.CASCADE, null=True, blank=True)\n\n\nclass NormalBase(models.Model):\n    nb_field = models.IntegerField()\n\n    def add_to_nb(self, value):\n        self.nb_field += value\n        self.save(update_fields=[\"nb_field\"])\n\n\nclass NormalExtension(NormalBase):\n    ne_field = models.CharField(max_length=50)\n\n    def add_to_ne(self, value):\n        self.ne_field += value\n        self.save(update_fields=[\"ne_field\"])\n\n\nclass PolyExtension(PolymorphicModel, NormalExtension):\n    poly_ext_field = models.IntegerField()\n\n    def add_to_ext(self, value):\n        self.poly_ext_field += value\n        self.save(update_fields=[\"poly_ext_field\"])\n\n\nclass PolyExtChild(PolyExtension):\n    poly_child_field = models.CharField(max_length=50)\n\n    def add_to_child(self, value):\n        self.poly_child_field += value\n        self.save(update_fields=[\"poly_child_field\"])\n\n    def override_add_to_ne(self, value):\n        # test that we can still access NormalExtension methods\n        self.ne_field += value.upper()\n        self.save(update_fields=[\"ne_field\"])\n\n    def override_add_to_ext(self, value):\n        # test that we can still access PolyExtension methods\n        self.poly_ext_field += value * 2\n        self.save(update_fields=[\"poly_ext_field\"])\n\n\nclass DeepCopyTester(PolymorphicModel):\n    binary_field = models.BinaryField()\n\n\nclass DeepCopyTester2(DeepCopyTester):\n    binary_field2 = models.BinaryField()\n\n\nclass DucksLake(models.Model):\n    lake = models.ForeignKey(\"LakeWithThrough\", on_delete=models.CASCADE)\n    duck = models.ForeignKey(Duck, on_delete=models.CASCADE)\n    time = models.CharField(max_length=10)\n\n\nclass Lake(models.Model):\n    ducks = models.ManyToManyField(Duck)\n\n\nclass LakeWithThrough(models.Model):\n    ducks = models.ManyToManyField(Duck, through=DucksLake)\n\n\nclass ChoiceBlank(PolymorphicModel):\n    pass\n\n\nclass ChoiceAthlete(ChoiceBlank):\n    choice = models.CharField(max_length=100)\n\n\nclass BetMultiple(models.Model):\n    answer = models.ManyToManyField(\"ChoiceBlank\", blank=True, through=\"RankedAthlete\")\n\n\nclass RankedAthlete(models.Model):\n    choiceAthlete = models.ForeignKey(\"ChoiceBlank\", on_delete=models.CASCADE)\n    bet = models.ForeignKey(\"BetMultiple\", on_delete=models.CASCADE)\n    rank = models.IntegerField()\n\n\nclass RecursionBug(PolymorphicModel):\n    status = models.ForeignKey(PlainA, on_delete=models.CASCADE, related_name=\"recursions\")\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"\n        https://github.com/jazzband/django-polymorphic/issues/334\n        \"\"\"\n        super().__init__(*args, **kwargs)\n        self.old_status_id = self.status_id\n\n\nclass TaggedItem(models.Model):\n    tag = models.SlugField()\n    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)\n    object_id = models.PositiveIntegerField()\n    content_object = GenericForeignKey(\"content_type\", \"object_id\")\n\n\nclass BookmarkManager(PolymorphicManager):\n    def get_queryset(self) -> PolymorphicQuerySet:\n        return super().get_queryset().annotate(cpy=models.F(\"url\"))\n\n\nclass Bookmark(PolymorphicModel):\n    url = models.URLField()\n    tags = GenericRelation(TaggedItem)\n    objects = BookmarkManager()\n\n\nclass Assignment(Bookmark):\n    assigned_to = models.CharField(max_length=100)\n\n\nclass Regression295Related(models.Model):\n    _real_field = models.CharField(max_length=10)\n\n\nclass Regression295Parent(PolymorphicModel):\n    related_object = models.ForeignKey(Regression295Related, on_delete=models.CASCADE)\n\n\nclass RelatedKeyModel(models.Model):\n    custom_id = models.UUIDField(primary_key=True, default=uuid.uuid4)\n\n\nclass DisparateKeysParent(PolymorphicModel):\n    text = models.CharField(max_length=30)\n\n\nclass DisparateKeysChild1(DisparateKeysParent):\n    key = models.OneToOneField(RelatedKeyModel, primary_key=True, on_delete=models.CASCADE)\n\n    text_child1 = models.CharField(max_length=30)\n\n\nclass DisparateKeysChild2(DisparateKeysParent):\n    text_child2 = models.CharField(max_length=30)\n    key = models.PositiveIntegerField(primary_key=True)\n\n\nclass DisparateKeysGrandChild2(DisparateKeysChild2):\n    text_grand_child = models.CharField(max_length=30)\n\n\nclass DisparateKeysGrandChild(DisparateKeysChild1):\n    text_grand_child = models.CharField(max_length=30)\n\n\nclass M2MAdminTest(PolymorphicModel):\n    name = models.CharField(max_length=30)\n\n    def __str__(self):\n        return self.name\n\n\nclass M2MAdminTestChildA(M2MAdminTest):\n    child_bs = models.ManyToManyField(\"M2MAdminTestChildB\", related_name=\"related_as\", blank=True)\n\n\nclass M2MAdminTestChildB(M2MAdminTest):\n    child_as = models.ManyToManyField(\"M2MAdminTestChildA\", related_name=\"related_bs\", blank=True)\n\n\nclass M2MAdminTestChildC(M2MAdminTestChildB):\n    pass\n\n\n# Models for testing Issue #182 and #375: M2M with through tables to/from polymorphic models\nclass M2MThroughBase(PolymorphicModel):\n    \"\"\"Base polymorphic model for M2M through table tests.\"\"\"\n\n    name = models.CharField(max_length=50)\n\n    def __str__(self):\n        return self.name\n\n\nclass M2MThroughPerson(M2MThroughBase):\n    \"\"\"Polymorphic child representing a person who can be on teams.\"\"\"\n\n    email = models.EmailField(blank=True)\n\n\nclass M2MThroughSpecialPerson(M2MThroughPerson):\n    \"\"\"Polymorphic child representing a special person.\"\"\"\n\n    special_code = models.CharField(max_length=20, blank=True)\n\n\nclass M2MThroughProject(M2MThroughBase):\n    \"\"\"Polymorphic child representing a project.\"\"\"\n\n    description = models.TextField(blank=True)\n\n\nclass M2MThroughProjectWithTeam(M2MThroughProject):\n    \"\"\"\n    Polymorphic child with M2M to Person through Membership.\n    Tests Issue #375: M2M with through table on polymorphic model.\n    \"\"\"\n\n    pass\n\n\nclass M2MThroughMembership(PolymorphicModel):\n    \"\"\"Polymorphic through model for M2M relationship between ProjectWithTeam and Person.\"\"\"\n\n    project = models.ForeignKey(\"M2MThroughProjectWithTeam\", on_delete=models.CASCADE)\n    person = models.ForeignKey(M2MThroughPerson, on_delete=models.CASCADE)\n    role = models.CharField(max_length=50)\n    joined_date = models.DateField(auto_now_add=True)\n\n    def __str__(self):\n        return f\"{self.person.name} - {self.role} on {self.project.name}\"\n\n\nclass M2MThroughMembershipWithPerson(M2MThroughMembership):\n    \"\"\"Membership for regular Person instances.\"\"\"\n\n    pass\n\n\nclass M2MThroughMembershipWithSpecialPerson(M2MThroughMembership):\n    \"\"\"Membership for SpecialPerson instances with additional tracking.\"\"\"\n\n    special_notes = models.TextField(blank=True, default=\"\")\n\n\n# Add the M2M field after the through model is defined\nM2MThroughProjectWithTeam.add_to_class(\n    \"team\",\n    models.ManyToManyField(\n        M2MThroughPerson, through=M2MThroughMembership, related_name=\"projects\", blank=True\n    ),\n)\n\n\n# Additional models for Issue #182: Direct M2M to polymorphic model\nclass DirectM2MContainer(models.Model):\n    \"\"\"Non-polymorphic model with direct M2M to polymorphic model.\"\"\"\n\n    name = models.CharField(max_length=50)\n    items = models.ManyToManyField(M2MThroughBase, related_name=\"containers\", blank=True)\n\n    def __str__(self):\n        return self.name\n\n\nclass Author(models.Model):\n    pass\n\n\nclass Book(PolymorphicModel):\n    author = models.ForeignKey(Author, on_delete=models.CASCADE)\n\n\nclass SpecialBook(Book):\n    pass\n\n\nclass FilteredManager(PolymorphicManager):\n    def get_queryset(self):\n        return super().get_queryset().exclude(field2=Upper(F(\"field2\")))\n\n\nclass Model2BFiltered(Model2B):\n    objects = FilteredManager()\n\n\nclass Model2CFiltered(Model2BFiltered):\n    field3 = models.CharField(max_length=30, blank=True, default=\"\")\n\n\nclass CustomBaseManager(PolymorphicManager):\n    pass\n\n\nclass FilteredManager2(FilteredManager):\n    pass\n\n\nclass Model2CNamedManagers(Model2CFiltered):\n    all_objects = CustomBaseManager()\n    filtered_objects = FilteredManager2()\n\n    class Meta:\n        base_manager_name = \"all_objects\"\n        default_manager_name = \"filtered_objects\"\n\n\nclass Model2CNamedDefault(Model2CFiltered):\n    custom_objects = FilteredManager2()\n\n    class Meta:\n        default_manager_name = \"custom_objects\"\n\n\n# serialization natural key tests #517\nclass NatKeyManager(PolymorphicManager):\n    def get_by_natural_key(self, slug):\n        return self.get(slug=slug)\n\n\nclass NatKeyParent(PolymorphicModel):\n    slug = models.SlugField(unique=True)\n    content = models.CharField(blank=True, max_length=100)\n\n    objects = NatKeyManager()\n\n    def natural_key(self):\n        return (self.slug,)\n\n\nclass NatKeyChild(NatKeyParent):\n    foo = models.OneToOneField(NatKeyParent, models.CASCADE, parent_link=True, primary_key=True)\n    val = models.IntegerField(default=0)\n\n    def natural_key(self):\n        return self.foo.natural_key()\n\n    natural_key.dependencies = [\"tests.natkeyparent\"]\n\n\nclass ManagerTest(PolymorphicModel):\n    name = models.CharField(max_length=30)\n\n    objects = CustomBaseManager()\n\n    class Meta:\n        base_manager_name = \"objects\"\n\n\nclass ManagerTestChild(ManagerTest):\n    pass\n\n\nclass PlainManager(models.Manager): ...\n\n\nclass ManagerTestPlain(models.Model):\n    objects = PlainManager()\n\n    class Meta:\n        base_manager_name = \"objects\"\n\n\nclass ManagerTestChildPlain(ManagerTestPlain):\n    pass\n\n\n# Models for testing generic polymorphic formsets\nclass GenericFKParent(models.Model):\n    \"\"\"A parent model that generic polymorphic items can point to.\"\"\"\n\n    name = models.CharField(max_length=100)\n\n    def __str__(self):\n        return self.name\n\n\nclass PolymorphicTagBase(PolymorphicModel):\n    \"\"\"Base polymorphic model with a GenericForeignKey for testing generic formsets.\"\"\"\n\n    tag = models.SlugField()\n    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)\n    object_id = models.PositiveIntegerField()\n    content_object = GenericForeignKey(\"content_type\", \"object_id\")\n\n    def __str__(self):\n        return self.tag\n\n\nclass PolymorphicTagA(PolymorphicTagBase):\n    \"\"\"First child of PolymorphicTagBase with an extra field.\"\"\"\n\n    priority = models.IntegerField(default=0)\n\n\nclass PolymorphicTagB(PolymorphicTagBase):\n    \"\"\"Second child of PolymorphicTagBase with a different extra field.\"\"\"\n\n    color = models.CharField(max_length=20, default=\"\")\n"
  },
  {
    "path": "src/polymorphic/tests/other/migrations/0001_initial.py",
    "content": "# Generated by Django 4.2 on 2026-02-05 14:47\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('tests', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='UserProfile',\n            fields=[\n                ('participant', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name='other_userprofile', serialize=False, to='tests.participant')),\n                ('name', models.CharField(max_length=100)),\n            ],\n            options={\n                'abstract': False,\n            },\n            bases=('tests.participant',),\n        ),\n    ]\n"
  },
  {
    "path": "src/polymorphic/tests/other/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/other/models.py",
    "content": "from polymorphic.tests.models import Participant\nfrom django.db import models\n\n\nclass UserProfile(Participant):\n    participant = models.OneToOneField(\n        Participant, parent_link=True, on_delete=models.CASCADE, related_name=\"other_userprofile\"\n    )\n    name = models.CharField(max_length=100)\n\n    def __str__(self):\n        return self.name\n"
  },
  {
    "path": "src/polymorphic/tests/other/test_cross_apps.py",
    "content": "from django.test import TestCase\nfrom django.core.exceptions import FieldError\n\n\nclass TestCrossAppSubclasses(TestCase):\n    def test_samename_different_app_subclasses(self):\n        from polymorphic.tests.other.models import UserProfile as OtherUserProfile\n        from polymorphic.tests.models import Participant, UserProfile\n\n        p1 = Participant.objects.create()\n        p2 = UserProfile.objects.create(name=\"userprofile1\")\n        p3 = OtherUserProfile.objects.create(name=\"otheruserprofile1\")\n\n        assert set(Participant.objects.all()) == {p1, p2, p3}\n\n        with self.assertRaises(FieldError):\n            Participant.objects.filter(UserProfile___name=\"otheruserprofile1\")\n\n        assert set(Participant.objects.filter(other__UserProfile___name=\"otheruserprofile1\")) == {\n            p3\n        }\n"
  },
  {
    "path": "src/polymorphic/tests/settings.py",
    "content": "import os\n\ntry:\n    import django_stubs_ext\n\n    django_stubs_ext.monkeypatch()\nexcept ImportError:\n    pass\n\n\nDEBUG = False\n\nrdbms = os.environ.get(\"RDBMS\", \"sqlite\")\n\nPYTEST_DB_NAME = os.environ.get(\"PYTEST_DB_NAME\", None)\n\nDEFAULT_DBS = f\"{PYTEST_DB_NAME or 'test1'},test2\"\n\nif rdbms == \"sqlite\":  # pragma: no cover\n    sqlite_dbs = os.environ.get(\n        \"SQLITE_DATABASES\", f\"{PYTEST_DB_NAME or ':memory:'},:memory:\"\n    ).split(\",\")\n    DATABASES = {\n        \"default\": {\"ENGINE\": \"django.db.backends.sqlite3\", \"NAME\": sqlite_dbs[0]},\n        \"secondary\": {\"ENGINE\": \"django.db.backends.sqlite3\", \"NAME\": sqlite_dbs[1]},\n    }\nelif rdbms == \"postgres\":  # pragma: no cover\n    creds = {\n        \"USER\": os.environ.get(\"POSTGRES_USER\", \"postgres\"),\n        \"PASSWORD\": os.environ.get(\"POSTGRES_PASSWORD\", \"\"),\n        \"HOST\": os.environ.get(\"POSTGRES_HOST\", \"\"),\n        \"PORT\": os.environ.get(\"POSTGRES_PORT\", \"\"),\n    }\n    DATABASES = {\n        \"default\": {\n            \"ENGINE\": \"django.db.backends.postgresql\",\n            \"NAME\": PYTEST_DB_NAME or \"test1\",\n            **creds,\n        },\n        \"secondary\": {\n            \"ENGINE\": \"django.db.backends.postgresql\",\n            \"NAME\": \"test2\",\n            **creds,\n        },\n    }\nelif rdbms == \"mysql\":  # pragma: no cover\n    dbs = os.environ.get(\"MYSQL_MULTIPLE_DATABASES\", DEFAULT_DBS).split(\",\")\n    creds = {\n        \"USER\": os.environ.get(\"MYSQL_USER\", \"root\"),\n        \"PASSWORD\": os.environ.get(\"MYSQL_PASSWORD\", \"root\"),\n        \"HOST\": os.environ.get(\"MYSQL_HOST\", \"127.0.0.1\"),\n        \"PORT\": os.environ.get(\"MYSQL_PORT\", \"3306\"),\n    }\n    DATABASES = {\n        \"default\": {\n            \"ENGINE\": \"django.db.backends.mysql\",\n            \"NAME\": dbs[0],\n            **creds,\n        },\n        \"secondary\": {\n            \"ENGINE\": \"django.db.backends.mysql\",\n            \"NAME\": dbs[1],\n            **creds,\n        },\n    }\nelif rdbms == \"mariadb\":  # pragma: no cover\n    dbs = os.environ.get(\"MYSQL_MULTIPLE_DATABASES\", DEFAULT_DBS).split(\",\")\n    creds = {\n        \"USER\": os.environ.get(\"MYSQL_USER\", \"root\"),\n        \"PASSWORD\": os.environ.get(\"MYSQL_ROOT_PASSWORD\", \"root\"),\n        \"HOST\": os.environ.get(\"MYSQL_HOST\", \"127.0.0.1\"),\n        \"PORT\": os.environ.get(\"MYSQL_PORT\", \"3306\"),\n    }\n    DATABASES = {\n        \"default\": {\n            \"ENGINE\": \"django.db.backends.mysql\",\n            \"NAME\": dbs[0],\n            **creds,\n        },\n        \"secondary\": {\n            \"ENGINE\": \"django.db.backends.mysql\",\n            \"NAME\": dbs[1],\n            **creds,\n        },\n    }\nelif rdbms == \"oracle\":  # pragma: no cover\n    dbs = os.environ.get(\"ORACLE_DATABASES\", DEFAULT_DBS).split(\",\")\n    ports = os.environ.get(\"ORACLE_PORTS\", \"1521,1522\").split(\",\")\n    creds = {\n        \"USER\": os.environ.get(\"ORACLE_USER\", \"system\"),\n        \"PASSWORD\": os.environ.get(\"ORACLE_PASSWORD\", \"password\"),\n    }\n    DATABASES = {\n        \"default\": {\n            \"ENGINE\": \"django.db.backends.oracle\",\n            \"NAME\": f\"{os.environ.get('ORACLE_HOST', 'localhost')}:{ports[0]}/{dbs[0]}\",\n            **creds,\n        }\n    }\n    if len(dbs) > 1:\n        DATABASES[\"secondary\"] = {\n            \"ENGINE\": \"django.db.backends.oracle\",\n            \"NAME\": f\"{os.environ.get('ORACLE_HOST', 'localhost')}:{ports[1]}/{dbs[1]}\",\n            **creds,\n        }\nDEFAULT_AUTO_FIELD = \"django.db.models.AutoField\"\nINSTALLED_APPS = [\n    \"polymorphic.tests.examples.type_hints.managers\",\n    \"polymorphic.tests.examples.type_hints.one2one\",\n    \"polymorphic.tests.examples.type_hints.m2m\",\n    \"polymorphic.tests.examples.type_hints.fk\",\n    \"polymorphic.tests.examples.integrations\",\n    \"polymorphic.tests\",\n    \"polymorphic.tests.deletion\",\n    \"polymorphic.tests.other\",\n    \"polymorphic.tests.test_migrations\",\n    \"polymorphic.tests.examples.views\",\n    \"polymorphic\",\n    \"django.contrib.staticfiles\",\n    \"django.contrib.auth\",\n    \"django.contrib.contenttypes\",\n    \"django.contrib.messages\",\n    \"django.contrib.sessions\",\n    \"django.contrib.sites\",\n    \"django.contrib.admin\",\n]\n\n# Add reversion if installed\ntry:\n    import reversion  # noqa: F401\n\n    INSTALLED_APPS.insert(0, \"reversion\")\n    INSTALLED_APPS.insert(0, \"polymorphic.tests.examples.integrations.reversion\")\nexcept ImportError:\n    pass\n\n# Add extra_views if installed\ntry:\n    import extra_views  # noqa: F401\n\n    INSTALLED_APPS.insert(0, \"polymorphic.tests.examples.integrations.extra_views\")\nexcept ImportError:\n    pass\n\ntry:\n    import django_filters\n\n    INSTALLED_APPS.insert(0, \"django_filters\")\nexcept ImportError:\n    pass\n\ntry:\n    import rest_framework  # noqa: F401\n\n    INSTALLED_APPS.insert(0, \"rest_framework\")\n    INSTALLED_APPS.insert(0, \"polymorphic.tests.examples.integrations.drf\")\nexcept ImportError:\n    pass\n\ntry:\n    import guardian  # noqa: F401\n\n    INSTALLED_APPS.insert(0, \"guardian\")\n    INSTALLED_APPS.insert(0, \"polymorphic.tests.examples.integrations.guardian\")\n    GUARDIAN_GET_CONTENT_TYPE = \"polymorphic.contrib.guardian.get_polymorphic_base_content_type\"\nexcept ImportError:\n    pass\n\n\nMIDDLEWARE = (\n    \"django.middleware.common.CommonMiddleware\",\n    \"django.contrib.sessions.middleware.SessionMiddleware\",\n    \"django.middleware.csrf.CsrfViewMiddleware\",\n    \"django.contrib.auth.middleware.AuthenticationMiddleware\",\n    \"django.contrib.messages.middleware.MessageMiddleware\",\n)\nSITE_ID = 3\nTEMPLATES = [\n    {\n        \"BACKEND\": \"django.template.backends.django.DjangoTemplates\",\n        \"DIRS\": (),\n        \"OPTIONS\": {\n            \"loaders\": (\n                \"django.template.loaders.filesystem.Loader\",\n                \"django.template.loaders.app_directories.Loader\",\n            ),\n            \"context_processors\": (\n                \"django.template.context_processors.debug\",\n                \"django.template.context_processors.i18n\",\n                \"django.template.context_processors.media\",\n                \"django.template.context_processors.request\",\n                \"django.template.context_processors.static\",\n                \"django.contrib.messages.context_processors.messages\",\n                \"django.contrib.auth.context_processors.auth\",\n            ),\n        },\n    }\n]\nPOLYMORPHIC_TEST_SWAPPABLE = \"polymorphic.swappedmodel\"\nSECRET_KEY = \"supersecret\"\nSTATIC_URL = \"/static/\"\n\nALLOWED_HOSTS = [\"*\"]\n\nROOT_URLCONF = \"polymorphic.tests.urls\"\n\nUSE_TZ = False\n"
  },
  {
    "path": "src/polymorphic/tests/test_admin.py",
    "content": "import pytest\nfrom django.urls import reverse\nfrom django.contrib import admin\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.utils.html import escape\nfrom django.test import RequestFactory\nfrom django.urls import resolve\nfrom playwright._impl._errors import TargetClosedError\nfrom polymorphic.admin import (\n    GenericStackedPolymorphicInline,\n    PolymorphicChildModelAdmin,\n    PolymorphicChildModelFilter,\n    PolymorphicInlineAdminForm,\n    PolymorphicInlineAdminFormSet,\n    PolymorphicInlineSupportMixin,\n    PolymorphicParentModelAdmin,\n    StackedPolymorphicInline,\n)\nfrom polymorphic.tests.admintestcase import AdminTestCase\nfrom polymorphic.tests.models import (\n    PlainA,\n    InlineModelA,\n    InlineModelB,\n    InlineParent,\n    Model2A,\n    Model2B,\n    Model2C,\n    Model2D,\n    NoChildren,\n    ModelWithPolyFK,\n    PolymorphicTagBase,\n    PolymorphicTagA,\n    PolymorphicTagB,\n)\n\nfrom playwright.sync_api import expect\nfrom urllib.parse import urljoin\n\nfrom .utils import _GenericUITest\n\n\nclass FileFieldInlineA(StackedPolymorphicInline.Child):\n    model = InlineModelA\n\n\nclass FileFieldInlineB(StackedPolymorphicInline.Child):\n    model = InlineModelB\n\n\nclass FileFieldInline(StackedPolymorphicInline):\n    model = InlineModelA\n    child_inlines = (FileFieldInlineA, FileFieldInlineB)\n\n\nclass FileFieldParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):\n    inlines = (FileFieldInline,)\n\n\nclass PolymorphicAdminTests(AdminTestCase):\n    def test_admin_registration(self):\n        \"\"\"\n        Test how the registration works\n        \"\"\"\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            list_filter = (PolymorphicChildModelFilter,)\n            child_models = (Model2B, Model2C, Model2D)\n\n        @self.register(Model2B)\n        @self.register(Model2C)\n        @self.register(Model2D)\n        class Model2ChildAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n            base_fieldsets = ((\"Base fields\", {\"fields\": (\"field1\",)}),)\n\n        # -- add page\n        ct_id = ContentType.objects.get_for_model(Model2D).pk\n        self.admin_get_add(Model2A)  # shows type page\n        self.admin_get_add(Model2A, qs=f\"?ct_id={ct_id}\")  # shows type page\n\n        self.admin_get_add(Model2A)  # shows type page\n        self.admin_post_add(\n            Model2A,\n            {\"field1\": \"A\", \"field2\": \"B\", \"field3\": \"C\", \"field4\": \"D\"},\n            qs=f\"?ct_id={ct_id}\",\n        )\n\n        d_obj = Model2A.objects.all()[0]\n        assert d_obj.__class__ == Model2D\n        assert d_obj.field1 == \"A\"\n        assert d_obj.field2 == \"B\"\n\n        # -- list page\n        self.admin_get_changelist(Model2A)  # asserts 200\n\n        # -- edit\n        response = self.admin_get_change(Model2A, d_obj.pk)\n        self.assertContains(response, \"field4\")\n        self.admin_post_change(\n            Model2A,\n            d_obj.pk,\n            {\"field1\": \"A2\", \"field2\": \"B2\", \"field3\": \"C2\", \"field4\": \"D2\"},\n        )\n\n        d_obj.refresh_from_db()\n        assert d_obj.field1 == \"A2\"\n        assert d_obj.field2 == \"B2\"\n        assert d_obj.field3 == \"C2\"\n        assert d_obj.field4 == \"D2\"\n\n        # -- history\n        self.admin_get_history(Model2A, d_obj.pk)\n\n        # -- delete\n        self.admin_get_delete(Model2A, d_obj.pk)\n        self.admin_post_delete(Model2A, d_obj.pk)\n        pytest.raises(Model2A.DoesNotExist, (lambda: d_obj.refresh_from_db()))\n\n    def test_get_child_inlines(self):\n        from .admin import Inline\n\n        inline = Inline(parent_model=InlineParent, admin_site=admin.site)\n        child_inlines = inline.get_child_inlines()\n        self.assertEqual(len(child_inlines), 2)\n        self.assertEqual(child_inlines[0], Inline.InlineModelAChild)\n        self.assertEqual(child_inlines[1], Inline.InlineModelBChild)\n\n    def test_show_in_index(self):\n        \"\"\"\n        Test that show_in_index=False hides the model from the index and sidebar.\n        \"\"\"\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2B,)\n\n        @self.register(Model2B)\n        class Model2BChildAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n            show_in_index = False\n\n        @self.register(Model2C)\n        class Model2CChildAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n            show_in_index = True\n\n        # Case 1: Index Page (url_name=\"index\")\n        request = self.create_admin_request(\"get\", \"/tmp-admin/\")\n        app_list = self.admin_site.get_app_list(request)\n\n        # Check that Model2B is NOT present\n        found_model2b = any(\n            model[\"object_name\"] == \"Model2B\" for app in app_list for model in app[\"models\"]\n        )\n        self.assertFalse(found_model2b, \"Child model should be hidden in index (Issue #532)\")\n\n        found_model2c = any(\n            model[\"object_name\"] == \"Model2C\" for app in app_list for model in app[\"models\"]\n        )\n        self.assertTrue(found_model2c, \"Child model should be visible in sidebar on change page\")\n\n        # Case 2: Change Page (url_name=\"change\") - Simulating Sidebar (Issue #497)\n        # We need a URL that resolves to a change view to test the sidebar context.\n        change_url = \"/tmp-admin/polymorphic/model2a/1/change/\"\n        request = self.create_admin_request(\"get\", change_url)\n        app_list = self.admin_site.get_app_list(request)\n\n        found_model2b = any(\n            model[\"object_name\"] == \"Model2B\" for app in app_list for model in app[\"models\"]\n        )\n        found_model2c = any(\n            model[\"object_name\"] == \"Model2C\" for app in app_list for model in app[\"models\"]\n        )\n        self.assertFalse(\n            found_model2b, \"Child model should be hidden in sidebar on change page (Issue #497)\"\n        )\n        self.assertTrue(found_model2c, \"Child model should be visible in sidebar on change page\")\n\n    def test_show_in_index_custom_site(self):\n        \"\"\"\n        Test that show_in_index=False works correctly with a custom AdminSite.\n        \"\"\"\n        original_name = self.admin_site.name\n        try:\n            # Change the site name to simulate a custom site\n            self.admin_site.name = \"custom_admin\"\n\n            # Register the model\n            @self.register(Model2B)\n            class Model2ChildAdmin(PolymorphicChildModelAdmin):\n                base_model = Model2A\n                show_in_index = False\n\n            # Re-set URLConf to update patterns with new name\n            from django.urls import clear_url_caches, set_urlconf, path, resolve\n\n            clear_url_caches()\n            set_urlconf(tuple([path(\"tmp-admin/\", self.admin_site.urls)]))\n\n            request = self.create_admin_request(\"get\", \"/tmp-admin/\")\n\n            # Verify resolving matches namespace 'custom_admin'\n            match = resolve(\"/tmp-admin/\")\n            assert match.namespace == \"custom_admin\"\n\n            # Now check app list\n            app_list = self.admin_site.get_app_list(request)\n\n            found_model2b = any(\n                model[\"object_name\"] == \"Model2B\" for app in app_list for model in app[\"models\"]\n            )\n            self.assertFalse(found_model2b, \"Child model should be hidden in Custom Admin Site\")\n\n        finally:\n            self.admin_site.name = original_name\n\n    def test_get_model_perms_hidden(self):\n        # Register a child admin with show_in_index=False\n        @self.register(Model2B)\n        class Model2ChildAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n            show_in_index = False\n\n        # Simulate a request to the admin index\n        factory = RequestFactory()\n        request = factory.get(\"/tmp-admin/\")\n        match = resolve(\"/tmp-admin/\")\n\n        # Ensure namespace matches admin site\n        match.namespace = self.admin_site.name\n        request._resolver_match = match\n\n        # Call get_model_perms directly\n        perms = Model2ChildAdmin(Model2B, self.admin_site).get_model_perms(request)\n\n        # Assert that all perms are False\n        assert perms == {\"add\": False, \"change\": False, \"delete\": False}\n\n    def test_admin_inlines(self):\n        \"\"\"\n        Test the registration of inline models.\n        \"\"\"\n\n        class InlineModelAChild(StackedPolymorphicInline.Child):\n            model = InlineModelA\n\n        class InlineModelBChild(StackedPolymorphicInline.Child):\n            model = InlineModelB\n\n        class Inline(StackedPolymorphicInline):\n            model = InlineModelA\n            child_inlines = (InlineModelAChild, InlineModelBChild)\n\n        @self.register(InlineParent)\n        class InlineParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):\n            inlines = (Inline,)\n\n        parent = InlineParent.objects.create(title=\"FOO\")\n        assert parent.inline_children.count() == 0\n\n        # -- get edit page\n        response = self.admin_get_change(InlineParent, parent.pk)\n\n        # Make sure the fieldset has the right data exposed in data-inline-formset\n        self.assertContains(response, \"childTypes\")\n        self.assertContains(response, escape('\"type\": \"inlinemodela\"'))\n        self.assertContains(response, escape('\"type\": \"inlinemodelb\"'))\n\n        # -- post edit page\n        self.admin_post_change(\n            InlineParent,\n            parent.pk,\n            {\n                \"title\": \"FOO2\",\n                \"inline_children-INITIAL_FORMS\": 0,\n                \"inline_children-TOTAL_FORMS\": 1,\n                \"inline_children-MIN_NUM_FORMS\": 0,\n                \"inline_children-MAX_NUM_FORMS\": 1000,\n                \"inline_children-0-parent\": parent.pk,\n                \"inline_children-0-polymorphic_ctype\": ContentType.objects.get_for_model(\n                    InlineModelB\n                ).pk,\n                \"inline_children-0-field1\": \"A2\",\n                \"inline_children-0-field2\": \"B2\",\n            },\n        )\n\n        parent.refresh_from_db()\n        assert parent.title == \"FOO2\"\n        assert parent.inline_children.count() == 1\n        child = parent.inline_children.all()[0]\n        assert child.__class__ == InlineModelB\n        assert child.field1 == \"A2\"\n        assert child.field2 == \"B2\"\n\n    def test_render_change_form_sets_has_file_field(self):\n        \"\"\"\n        Test that render_change_form correctly sets has_file_field\n        when a polymorphic inline contains a FileField.\n\n        This tests the fix for issue #380 where file uploads don't work\n        in polymorphic inlines because the form lacks multipart encoding.\n\n        The issue occurs because Django's default admin checks formset.is_multipart()\n        but polymorphic formsets may not have all child forms instantiated at that point,\n        so the check can miss file fields in child inlines.\n        \"\"\"\n        # Register the admin for testing\n        self.register(InlineParent)(FileFieldParentAdmin)\n\n        parent = InlineParent.objects.create(title=\"Parent with file inline\")\n\n        # Go to the change page\n        response = self.admin_get_change(InlineParent, parent.pk)\n        response.render()  # Force TemplateResponse to render\n\n        # Verify has_file_field is set in context\n        self.assertIn(\"has_file_field\", response.context_data)\n        self.assertTrue(\n            response.context_data[\"has_file_field\"],\n            \"has_file_field should be True when polymorphic inline has FileField\",\n        )\n\n        # Verify the rendered HTML contains multipart encoding\n        content = response.content.decode(\"utf-8\")\n        self.assertIn(\n            'enctype=\"multipart/form-data\"',\n            content,\n            \"Form should have multipart/form-data encoding when file fields present\",\n        )\n\n\nclass _GenericAdminFormTest(_GenericUITest):\n    \"\"\"Generic admin form test using Playwright.\"\"\"\n\n    def admin_url(self):\n        return f\"{self.live_server_url}{reverse('admin:index')}\"\n\n    def add_url(self, model):\n        path = reverse(f\"admin:{model._meta.label_lower.replace('.', '_')}_add\")\n        return f\"{self.live_server_url}{path}\"\n\n    def change_url(self, model, id):\n        path = reverse(\n            f\"admin:{model._meta.label_lower.replace('.', '_')}_change\",\n            args=[id],\n        )\n        return f\"{self.live_server_url}{path}\"\n\n    def list_url(self, model):\n        path = reverse(f\"admin:{model._meta.label_lower.replace('.', '_')}_changelist\")\n        return f\"{self.live_server_url}{path}\"\n\n    def get_object_ids(self, model):\n        self.page.goto(self.list_url(model))\n        return self.page.eval_on_selector_all(\n            \"input[name='_selected_action']\", \"elements => elements.map(e => e.value)\"\n        )\n\n\nclass StackedInlineTests(_GenericAdminFormTest):\n    def test_admin_inline_add_autocomplete(self):\n        # https://github.com/jazzband/django-polymorphic/issues/546\n        for name in [\"Brian\", \"Alice\", \"Emma\", \"Anna\"]:\n            PlainA.objects.create(field1=name)\n        self.page.goto(self.add_url(InlineParent))\n        self.page.fill(\"input[name='title']\", \"Parent 1\")\n        with self.page.expect_navigation(timeout=30000) as nav_info:\n            self.page.click(\"input[name='_save']\")\n\n        response = nav_info.value\n        assert response.status < 400\n\n        # verify the add\n        added = InlineParent.objects.get(title=\"Parent 1\")\n        self.page.goto(self.change_url(InlineParent, added.pk))\n        polymorphic_menu = self.page.locator(\n            \"div.polymorphic-add-choice div.polymorphic-type-menu\"\n        )\n        expect(polymorphic_menu).to_be_hidden()\n\n        self.page.click(\"div.polymorphic-add-choice a\")\n\n        expect(polymorphic_menu).to_be_visible()\n\n        self.page.click(\"div.polymorphic-type-menu a[data-type='inlinemodelb']\")\n\n        selector_menu = self.page.locator(\"span.select2-dropdown.select2-dropdown--below\")\n        expect(selector_menu).to_be_hidden()\n        with self.page.expect_response(\"**autocomplete**\", timeout=30000):\n            self.page.click(\"span.select2-selection__arrow b[role='presentation']\")\n\n        expect(selector_menu).to_be_visible()\n\n        suggestions = self.page.locator(\"ul.select2-results__options > li\").all_inner_texts()\n        assert \"Alice\" in suggestions\n        assert \"Anna\" in suggestions\n        assert \"Brian\" in suggestions\n        assert \"Emma\" in suggestions\n\n        with self.page.expect_response(\"**autocomplete**\", timeout=30000):\n            self.page.locator(\"input.select2-search__field[type='search']\").type(\"B\")\n\n        suggestions = self.page.locator(\"ul.select2-results__options > li\").all_inner_texts()\n        assert suggestions == [\"Brian\"]\n\n    def test_inline_form_ordering_and_removal(self):\n        \"\"\"\n        Test that the javascript places the inline forms in the correct order on\n        repeated adds without a save.\n\n        https://github.com/jazzband/django-polymorphic/issues/426\n        \"\"\"\n        self.page.goto(self.add_url(InlineParent))\n\n        polymorphic_menu = self.page.locator(\n            \"div.polymorphic-add-choice div.polymorphic-type-menu\"\n        )\n\n        self.page.click(\"div.polymorphic-add-choice a\")\n        polymorphic_menu.wait_for(state=\"visible\")\n        self.page.click(\"div.polymorphic-type-menu a[data-type='inlinemodelb']\")\n        polymorphic_menu.wait_for(state=\"hidden\")\n        self.page.click(\"div.polymorphic-add-choice a\")\n        polymorphic_menu.wait_for(state=\"visible\")\n        self.page.click(\"div.polymorphic-type-menu a[data-type='inlinemodela']\")\n        polymorphic_menu.wait_for(state=\"hidden\")\n        self.page.click(\"div.polymorphic-add-choice a\")\n        polymorphic_menu.wait_for(state=\"visible\")\n        self.page.click(\"div.polymorphic-type-menu a[data-type='inlinemodela']\")\n        polymorphic_menu.wait_for(state=\"hidden\")\n        self.page.click(\"div.polymorphic-add-choice a\")\n        polymorphic_menu.wait_for(state=\"visible\")\n        self.page.click(\"div.polymorphic-type-menu a[data-type='inlinemodelb']\")\n        polymorphic_menu.wait_for(state=\"hidden\")\n\n        inline0 = self.page.locator(\"div#inline_children-0\")\n        inline1 = self.page.locator(\"div#inline_children-1\")\n        inline2 = self.page.locator(\"div#inline_children-2\")\n        inline3 = self.page.locator(\"div#inline_children-3\")\n\n        inline0.wait_for(state=\"visible\")\n        inline1.wait_for(state=\"visible\")\n        inline2.wait_for(state=\"visible\")\n        inline3.wait_for(state=\"visible\")\n\n        assert \"model b\" in inline0.inner_text() and \"#1\" in inline0.inner_text()\n        assert \"model a\" in inline1.inner_text() and \"#2\" in inline1.inner_text()\n        assert \"model a\" in inline2.inner_text() and \"#3\" in inline2.inner_text()\n        assert \"model b\" in inline3.inner_text() and \"#4\" in inline3.inner_text()\n\n        # Now remove inline 2 and check the numbering is correct\n        inline1.locator(\"a.inline-deletelink\").click()\n        # the ids are updated - so we expect the last div id to be removed\n        inline3.wait_for(state=\"detached\")\n        assert \"model b\" in inline0.inner_text() and \"#1\" in inline0.inner_text()\n        assert \"model a\" in inline1.inner_text() and \"#2\" in inline1.inner_text()\n        assert \"model b\" in inline2.inner_text() and \"#3\" in inline2.inner_text()\n\n        inline0.locator(\"a.inline-deletelink\").click()\n        inline2.wait_for(state=\"detached\")\n        assert \"model a\" in inline0.inner_text() and \"#1\" in inline0.inner_text()\n        assert \"model b\" in inline1.inner_text() and \"#2\" in inline1.inner_text()\n\n        inline1.locator(\"a.inline-deletelink\").click()\n        inline1.wait_for(state=\"detached\")\n        assert \"model a\" in inline0.inner_text() and \"#1\" in inline0.inner_text()\n\n        inline0.locator(\"a.inline-deletelink\").click()\n        inline0.wait_for(state=\"detached\")\n\n    def test_polymorphic_inline_file_upload(self):\n        \"\"\"\n        Test that file uploads work correctly in polymorphic inlines.\n\n        This is a comprehensive end-to-end test for issue #380 where\n        file uploads don't work in polymorphic inlines because the form\n        lacks multipart encoding.\n\n        Scenario:\n        1. Navigate to InlineParent change page\n        2. Add a polymorphic InlineModelB inline\n        3. Upload a file to the file_upload field\n        4. Save the form\n        5. Verify file was uploaded and saved correctly\n        \"\"\"\n        import tempfile\n        import os\n\n        # Create a parent object\n        parent = InlineParent.objects.create(title=\"Parent for file upload test\")\n\n        # Navigate to change page\n        self.page.goto(self.change_url(InlineParent, parent.pk))\n\n        # Verify form has multipart encoding\n        form_element = self.page.locator(\"form#inlineparent_form\")\n        expect(form_element).to_have_attribute(\"enctype\", \"multipart/form-data\")\n\n        # Click add button to show polymorphic menu\n        polymorphic_menu = self.page.locator(\n            \"div.polymorphic-add-choice div.polymorphic-type-menu\"\n        )\n        expect(polymorphic_menu).to_be_hidden()\n\n        self.page.click(\"div.polymorphic-add-choice a\")\n        expect(polymorphic_menu).to_be_visible()\n\n        # Select InlineModelB from polymorphic menu\n        self.page.click(\"div.polymorphic-type-menu a[data-type='inlinemodelb']\")\n        polymorphic_menu.wait_for(state=\"hidden\")\n\n        # Wait for the inline form to appear\n        inline_form = self.page.locator(\"div#inline_children-0\")\n        inline_form.wait_for(state=\"visible\")\n\n        # Fill in required fields\n        self.page.fill(\"input[name='inline_children-0-field1']\", \"FileTest1\")\n        self.page.fill(\"input[name='inline_children-0-field2']\", \"FileTest2\")\n\n        # Create a temporary test file to upload\n        with tempfile.NamedTemporaryFile(mode=\"w\", delete=False, suffix=\".txt\") as temp_file:\n            temp_file.write(\"This is a test file for polymorphic inline upload\")\n            temp_file_path = temp_file.name\n\n        try:\n            # Upload the file\n            file_input = self.page.locator(\"input[name='inline_children-0-file_upload']\")\n            file_input.set_input_files(temp_file_path)\n\n            # Save the form\n            with self.page.expect_navigation(timeout=30000) as nav_info:\n                self.page.click(\"input[name='_save']\")\n\n            response = nav_info.value\n            assert response.status < 400, f\"Form submission failed with status {response.status}\"\n\n            # Verify the inline was created\n            parent.refresh_from_db()\n            inlines = list(parent.inline_children.all())\n            assert len(inlines) == 1, \"Should have created one inline\"\n\n            inline = inlines[0]\n            assert inline.__class__ == InlineModelB, \"Inline should be InlineModelB instance\"\n            assert inline.field1 == \"FileTest1\"\n            assert inline.field2 == \"FileTest2\"\n\n            # Verify the file was uploaded\n            assert inline.file_upload, \"file_upload field should not be empty\"\n            assert inline.file_upload.name, \"Uploaded file should have a name\"\n            assert \"test_uploads/\" in inline.file_upload.name, (\n                \"File should be in test_uploads directory\"\n            )\n\n            # Verify file exists and has correct content\n            file_path = inline.file_upload.path\n            assert os.path.exists(file_path), f\"Uploaded file should exist at {file_path}\"\n\n            with open(file_path, \"r\") as uploaded_file:\n                content = uploaded_file.read()\n                assert content == \"This is a test file for polymorphic inline upload\", (\n                    \"Uploaded file should have correct content\"\n                )\n\n            # Clean up uploaded file\n            os.remove(file_path)\n\n        finally:\n            # Clean up temp file\n            if os.path.exists(temp_file_path):\n                os.remove(temp_file_path)\n\n\nclass PolymorphicFormTests(_GenericAdminFormTest):\n    def test_admin_polymorphic_add(self):\n        model2b_ct = ContentType.objects.get_for_model(Model2B)\n        model2c_ct = ContentType.objects.get_for_model(Model2C)\n        model2d_ct = ContentType.objects.get_for_model(Model2D)\n\n        for model_type, fields in [\n            (\n                model2b_ct,\n                {\n                    \"field1\": \"2B1\",\n                    \"field2\": \"2B2\",\n                },\n            ),\n            (\n                model2c_ct,\n                {\n                    \"field1\": \"2C1\",\n                    \"field2\": \"2C2\",\n                    \"field3\": \"2C3\",\n                },\n            ),\n            (\n                model2d_ct,\n                {\n                    \"field1\": \"2D1\",\n                    \"field2\": \"2D2\",\n                    # \"field3\": \"2D3\", excluded!\n                    \"field4\": \"2D4\",\n                },\n            ),\n        ]:\n            self.page.goto(self.add_url(Model2A))\n\n            # https://github.com/jazzband/django-polymorphic/pull/580\n            expect(self.page.locator(\"div.breadcrumbs\")).to_have_count(1)\n            expect(self.page.locator(\"form#logout-form\")).to_have_count(1)\n\n            self.page.locator(f\"input[type=radio][value='{model_type.pk}']\").check()\n            with self.page.expect_navigation(timeout=30000) as nav_info:\n                self.page.click(\"input[name='_save']\")\n\n            response = nav_info.value\n            assert response.status < 400\n\n            for field, value in fields.items():\n                self.page.fill(f\"input[name='{field}']\", value)\n\n            with self.page.expect_navigation(timeout=30000) as nav_info:\n                self.page.click(\"input[name='_save']\")\n\n            response = nav_info.value\n            assert response.status < 400\n\n        assert Model2A.objects.count() == 3\n\n        object_ids = [int(oid) for oid in self.get_object_ids(Model2A)]\n\n        assert len(object_ids) == 3\n\n        assert Model2B.objects.first().pk in object_ids\n        assert Model2C.objects.first().pk in object_ids\n        assert Model2D.objects.first().pk in object_ids\n\n        assert Model2B.objects.first().field1 == \"2B1\"\n        assert Model2B.objects.first().field2 == \"2B2\"\n\n        assert Model2C.objects.first().field1 == \"2C1\"\n        assert Model2C.objects.first().field2 == \"2C2\"\n        assert Model2C.objects.first().field3 == \"2C3\"\n\n        assert Model2D.objects.first().field1 == \"2D1\"\n        assert Model2D.objects.first().field2 == \"2D2\"\n        assert Model2D.objects.first().field3 == \"\"\n        assert Model2D.objects.first().field4 == \"2D4\"\n\n    def test_admin_popup_validation_error(self):\n        \"\"\"\n        Test that popup functionality works correctly after validation errors.\n\n        Scenario:\n        1. Open admin page with FK field to polymorphic model\n        2. Click green \"+\" button to add new object in popup\n        3. Select polymorphic type\n        4. Submit form with validation error (missing required fields)\n        5. Fix the error and submit again\n\n        Expected: Object is added, popup closes, FK field is populated\n        Actual (bug #612): Popup parameters lost during validation\n\n        Regression test for issue #612.\n        \"\"\"\n        model2d_ct = ContentType.objects.get_for_model(Model2D)\n\n        # Navigate to the add page for ModelWithPolyFK\n        self.page.goto(self.add_url(ModelWithPolyFK))\n\n        # Fill in the name field\n        self.page.fill(\"input[name='name']\", \"Test Related Object\")\n\n        # Click the \"+\" button next to the FK field to open popup\n        with self.page.expect_popup(timeout=30000) as popup_info:\n            self.page.click(\"a#add_id_poly_fk\")\n\n        popup = popup_info.value\n        popup.wait_for_load_state(\"networkidle\")\n\n        # In the popup, select Model2D type\n        popup.locator(f\"input[type=radio][value='{model2d_ct.pk}']\").check()\n        with popup.expect_navigation(timeout=30000) as nav_info:\n            popup.click(\"input[name='_save']\")\n\n        response = nav_info.value\n        assert response.status < 400\n\n        # Verify popup parameters are preserved after type selection\n        current_url = popup.url\n        assert \"_popup=1\" in current_url, (\n            f\"_popup parameter lost after type selection. URL: {current_url}\"\n        )\n\n        # Submit form with validation error (missing required fields)\n        # Only fill field1, leave field2 and field4 empty\n        popup.fill(\"input[name='field1']\", \"PopupTest1\")\n\n        with popup.expect_navigation(timeout=30000) as nav_info:\n            popup.click(\"input[name='_save']\")\n\n        response = nav_info.value\n        assert response.status < 400\n\n        # CRITICAL: Verify popup parameters preserved after validation error\n        current_url = popup.url\n        assert \"_popup=1\" in current_url, (\n            f\"_popup parameter lost after validation error. URL: {current_url}\"\n        )\n        assert \"ct_id=\" in current_url, (\n            f\"ct_id parameter lost after validation error. URL: {current_url}\"\n        )\n\n        # Verify error messages are displayed\n        error_list = popup.locator(\".errorlist\").first\n        expect(error_list).to_be_visible()\n\n        # Fix validation errors by filling all required fields\n        popup.fill(\"input[name='field1']\", \"PopupTest1\")\n        popup.fill(\"input[name='field2']\", \"PopupTest2\")\n        popup.fill(\"input[name='field4']\", \"PopupTest4\")\n\n        # Submit the form - this should close the popup\n        with popup.expect_event(\"close\", timeout=30000):\n            try:\n                popup.click(\"input[name='_save']\", no_wait_after=True)\n            except TargetClosedError:\n                # Popup closed as expected\n                pass\n\n        # Verify the popup closed\n        assert popup.is_closed(), \"Popup should have closed after successful submit\"\n\n        # Verify the object was created\n        created_obj = Model2D.objects.filter(\n            field1=\"PopupTest1\", field2=\"PopupTest2\", field4=\"PopupTest4\"\n        ).first()\n        assert created_obj is not None, \"Model2D object should have been created\"\n\n        # Verify the FK field was populated on the main page\n        # The popup should have called window.opener and set the value\n        selected_value = self.page.locator(\"select#id_poly_fk\").input_value()\n        assert selected_value == str(created_obj.pk), (\n            f\"FK field should be populated with {created_obj.pk}, got {selected_value}\"\n        )\n\n\nclass PolymorphicNoChildrenTests(_GenericAdminFormTest):\n    def test_admin_no_polymorphic_children(self):\n        self.page.goto(self.add_url(NoChildren))\n        self.page.fill(\"input[name='field1']\", \"NoChildren1\")\n        with self.page.expect_navigation(timeout=30000) as nav_info:\n            self.page.click(\"input[name='_save']\")\n\n        response = nav_info.value\n        assert response.status < 400\n\n        # verify the add\n        added = NoChildren.objects.get(field1=\"NoChildren1\")\n        self.page.goto(self.change_url(NoChildren, added.pk))\n        assert self.page.locator(\"input[name='field1']\").input_value() == \"NoChildren1\"\n\n\nclass AdminRecentActionsTests(_GenericAdminFormTest):\n    def test_admin_recent_actions(self):\n        \"\"\"\n        Test that recent actions links respect polymorphism\n        \"\"\"\n        model2a_ct = ContentType.objects.get_for_model(Model2A)\n        model2d_ct = ContentType.objects.get_for_model(Model2D)\n\n        for model_type, fields in [\n            (\n                model2a_ct,\n                {\n                    \"field1\": \"2A1\",\n                },\n            ),\n            (\n                model2d_ct,\n                {\n                    \"field1\": \"2D1\",\n                    \"field2\": \"2D2\",\n                    # \"field3\": \"2D3\",  excluded!\n                    \"field4\": \"2D4\",\n                },\n            ),\n        ]:\n            self.page.goto(self.add_url(Model2A))\n            self.page.locator(f\"input[type=radio][value='{model_type.pk}']\").check()\n            with self.page.expect_navigation(timeout=30000) as nav_info:\n                self.page.click(\"input[name='_save']\")\n\n            response = nav_info.value\n            assert response.status < 400\n\n            for field, value in fields.items():\n                self.page.fill(f\"input[name='{field}']\", value)\n\n            with self.page.expect_navigation(timeout=30000) as nav_info:\n                self.page.click(\"input[name='_save']\")\n\n            response = nav_info.value\n            assert response.status < 400\n\n        self.page.goto(self.admin_url())\n        links = self.page.locator(\"ul.actionlist a\")\n        count = links.count()\n\n        # Collect hrefs\n        hrefs = []\n        for i in range(count):\n            href = links.nth(i).get_attribute(\"href\")\n            if href:  # ignore missing hrefs just in case\n                hrefs.append(href)\n\n        assert hrefs, \"No links found in .actionlist\"\n\n        # Visit each link and ensure the HTTP status is OK\n        for href in hrefs:\n            action_url = urljoin(self.live_server_url, href)\n            response = self.page.goto(action_url)\n            assert response is not None, f\"No response for {action_url}\"\n            assert response.ok, f\"{action_url} returned bad status {response.status}\"\n            if \"model2a\" in action_url:\n                inputs = self.page.locator(\"#model2a_form input[type='text']\")\n                count = inputs.count()\n                assert count == 1\n\n                values = []\n                for i in range(count):\n                    values.append(inputs.nth(i).input_value())\n\n                assert values == [\"2A1\"]\n            elif \"model2d\" in action_url:\n                # this also tests that exclusion of field3 works\n                inputs = self.page.locator(\"#model2d_form input[type='text']\")\n                count = inputs.count()\n                assert count == 3\n\n                values = []\n                for i in range(count):\n                    values.append(inputs.nth(i).input_value())\n\n                assert values == [\"2D1\", \"2D2\", \"2D4\"]\n            else:\n                assert False, f\"Unexpected change url: {action_url}\"\n\n\nclass AdminPreservedFiltersTests(_GenericAdminFormTest):\n    def test_changelist_filter_persists_after_edit(self):\n        \"\"\"\n        Test that changelist filters are preserved after editing an object.\n\n        Regression test for:\n        - #356: Filters are not preserved in polymorphic parent admin\n        - #125: Admin change form doesn't preserve changelist filter\n        \"\"\"\n        # Arrange: create 1 instance for each concrete polymorphic child model\n        # so the changelist has something to filter and something to click into.\n        obj_b = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        obj_c = Model2C.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n\n        # Get the ContentType for Model2B to verify the filter later.\n        ct_b = ContentType.objects.get_for_model(Model2B)\n\n        # Act: Navigate to the changelist and apply the polymorphic content type filter\n        # by clicking the filter link in the admin UI.\n        self.page.goto(self.list_url(Model2A))\n\n        # Click the filter link for Model2B in the polymorphic child model filter sidebar.\n        # Clicking a filter link navigates to a new URL, so we wait for navigation.\n        with self.page.expect_navigation(timeout=30000):\n            self.page.click(\"text=model2b\")\n\n        # Click the first row's object link in the results table to go to its change form.\n        with self.page.expect_navigation(timeout=30000):\n            self.page.click(\"table#result_list tbody tr th a\")\n\n        # Edit a field on the change form.\n        self.page.fill(\"input[name='field1']\", \"B1-edited\")\n\n        # Click Save and explicitly wait for navigation caused by form submission.\n        # Capturing the navigation response lets us assert the HTTP status.\n        with self.page.expect_navigation(timeout=30000) as nav_info:\n            self.page.click(\"input[name='_save']\")\n        response = nav_info.value\n\n        # Assert: request succeeded (admin returned a normal page load).\n        expected_status_code = 200\n        assert response.status == expected_status_code\n\n        # Assert: after saving, the redirected URL still contains the original filter,\n        # meaning the changelist preserved the querystring across edit/save.\n        assert f\"polymorphic_ctype={ct_b.pk}\" in self.page.url\n\n        # Assert: the changelist is actually filtered - only Model2B objects should be shown.\n        # Model2C (which is a subclass of Model2B) should also appear since the filter\n        # matches the polymorphic content type of Model2B and its subclasses.\n        displayed_ids = [\n            int(id_str)\n            for id_str in self.page.eval_on_selector_all(\n                \"input[name='_selected_action']\", \"elements => elements.map(e => e.value)\"\n            )\n        ]\n        # Only obj_b should be displayed (obj_c has a different content type)\n        assert displayed_ids == [obj_b.pk]\n\n\nclass M2MAdminTests(_GenericAdminFormTest):\n    def test_m2m_admin_raw_id_fields(self):\n        \"\"\"\n        Test M2M relationships in polymorphic admin using raw_id_fields.\n\n        This test verifies that:\n        1. M2M relationships can be created between polymorphic child models\n        2. Raw ID field lookups display the correct polymorphic instances\n        3. M2M relationships are properly saved and displayed\n        \"\"\"\n        from polymorphic.tests.models import (\n            M2MAdminTestChildA,\n            M2MAdminTestChildB,\n            M2MAdminTestChildC,\n        )\n\n        # Create test instances\n        a1 = M2MAdminTestChildA.objects.create(name=\"A1\")\n        b1 = M2MAdminTestChildB.objects.create(name=\"B1\")\n        c1 = M2MAdminTestChildC.objects.create(name=\"C1\")\n\n        # Navigate to A1's change page\n        self.page.goto(self.change_url(M2MAdminTestChildA, a1.pk))\n\n        # Verify the page loaded correctly\n        assert self.page.locator(\"input[name='name']\").input_value() == \"A1\"\n\n        # Test adding B1 to A1's child_bs field using the raw ID lookup\n        # Click the lookup button (magnifying glass icon) for child_bs\n        with self.page.expect_popup(timeout=30000) as popup_info:\n            self.page.click(\"a#lookup_id_child_bs\")\n\n        popup = popup_info.value\n        popup.wait_for_load_state(\"networkidle\")\n\n        # In the popup, we should see both B1 and C1 (since C1 is a subclass of B)\n        # Verify B1 is present in the list\n        b1_link = popup.locator(\"table#result_list a:has-text('B1')\")\n        expect(b1_link).to_be_visible()\n\n        # Verify C1 is present in the list\n        c1_link = popup.locator(\"table#result_list a:has-text('C1')\")\n        expect(c1_link).to_be_visible()\n\n        # Verify that A1 is not present\n        expect(popup.locator(\"table#result_list a:has-text('A1')\")).to_have_count(0)\n\n        # Click B1 to select it\n        with popup.expect_event(\"close\", timeout=30000):\n            try:\n                b1_link.click(no_wait_after=True)\n            except TargetClosedError:\n                pass\n\n        # Wait a moment for the popup to close and value to be set\n        self.page.wait_for_timeout(500)\n\n        # Verify B1's ID was added to the raw ID field\n        child_bs_value = self.page.locator(\"input[name='child_bs']\").input_value()\n        assert str(b1.pk) in child_bs_value\n\n        # Now add C1 as well by clicking the lookup again\n        with self.page.expect_popup(timeout=30000) as popup_info:\n            self.page.click(\"a#lookup_id_child_bs\")\n\n        popup = popup_info.value\n        popup.wait_for_load_state(\"networkidle\")\n\n        # Click C1 to add it\n        c1_link = popup.locator(\"table#result_list a:has-text('C1')\")\n        with popup.expect_event(\"close\", timeout=30000):\n            try:\n                c1_link.click(no_wait_after=True)\n            except TargetClosedError:\n                pass\n\n        self.page.wait_for_timeout(500)\n\n        # Verify both B1 and C1 are in the raw ID field (comma-separated)\n        child_bs_value = self.page.locator(\"input[name='child_bs']\").input_value()\n        assert str(b1.pk) in child_bs_value\n        assert str(c1.pk) in child_bs_value\n\n        # Save the changes to A1\n        with self.page.expect_navigation(timeout=30000) as nav_info:\n            self.page.click(\"input[name='_save']\")\n\n        response = nav_info.value\n        assert response.status < 400\n\n        # Verify the relationships were saved\n        a1.refresh_from_db()\n        child_bs_ids = set(a1.child_bs.values_list(\"pk\", flat=True))\n        assert b1.pk in child_bs_ids\n        assert c1.pk in child_bs_ids\n        assert len(child_bs_ids) == 2\n\n        # Now test the reverse relationship: add A1 to B1's child_as\n        self.page.goto(self.change_url(M2MAdminTestChildB, b1.pk))\n\n        # Verify the page loaded correctly\n        assert self.page.locator(\"input[name='name']\").input_value() == \"B1\"\n\n        # Click the lookup button for child_as\n        with self.page.expect_popup(timeout=30000) as popup_info:\n            self.page.click(\"a#lookup_id_child_as\")\n\n        popup = popup_info.value\n        popup.wait_for_load_state(\"networkidle\")\n\n        # In the popup, we should see A1\n        a1_link = popup.locator(\"table#result_list a:has-text('A1')\")\n        expect(a1_link).to_be_visible()\n\n        # Verify that AB is not present\n        expect(popup.locator(\"table#result_list a:has-text('B1')\")).to_have_count(0)\n        expect(popup.locator(\"table#result_list a:has-text('C1')\")).to_have_count(0)\n\n        # Click A1 to select it\n        with popup.expect_event(\"close\", timeout=30000):\n            try:\n                a1_link.click(no_wait_after=True)\n            except TargetClosedError:\n                pass\n\n        self.page.wait_for_timeout(500)\n\n        # Verify A1's ID was added to the raw ID field\n        child_as_value = self.page.locator(\"input[name='child_as']\").input_value()\n        assert str(a1.pk) in child_as_value\n\n        # Save the changes to B1\n        with self.page.expect_navigation(timeout=30000) as nav_info:\n            self.page.click(\"input[name='_save']\")\n\n        response = nav_info.value\n        assert response.status < 400\n\n        # Verify the relationship was saved\n        b1.refresh_from_db()\n        child_as_ids = set(b1.child_as.values_list(\"pk\", flat=True))\n        assert a1.pk in child_as_ids\n        assert len(child_as_ids) == 1\n\n        # Verify the relationships display correctly when we go back to the change page\n        self.page.goto(self.change_url(M2MAdminTestChildA, a1.pk))\n\n        # The raw ID field should show both B1 and C1\n        child_bs_value = self.page.locator(\"input[name='child_bs']\").input_value()\n        assert str(b1.pk) in child_bs_value\n        assert str(c1.pk) in child_bs_value\n\n    def test_issue_182_m2m_field_to_polymorphic_model(self):\n        \"\"\"\n        Test for Issue #182: M2M field in model admin.\n\n        When a model has a direct ManyToManyField to a polymorphic model,\n        the admin should work without raising AttributeError: 'int' object has no attribute 'pk'.\n\n        Scenario:\n        1. Create polymorphic M2MThroughBase instances (Project and Person)\n        2. Create a DirectM2MContainer with M2M to polymorphic models\n        3. Navigate to DirectM2MContainer's admin change page\n        4. Add polymorphic items using filter_horizontal widget\n        5. Save and verify no errors occur\n        6. Verify the relationships are correctly displayed\n\n        References:\n        - https://github.com/django-polymorphic/django-polymorphic/issues/182\n        \"\"\"\n        from polymorphic.tests.models import (\n            M2MThroughProject,\n            M2MThroughPerson,\n            M2MThroughSpecialPerson,\n            DirectM2MContainer,\n        )\n\n        # Create polymorphic instances\n        project1 = M2MThroughProject.objects.create(\n            name=\"Django Project\", description=\"Web framework\"\n        )\n        project2 = M2MThroughProject.objects.create(\n            name=\"React Project\", description=\"Frontend library\"\n        )\n        person1 = M2MThroughPerson.objects.create(\n            name=\"Alice Developer\", email=\"alice@example.com\"\n        )\n        person2 = M2MThroughSpecialPerson.objects.create(\n            name=\"Bob Special\", email=\"bob@example.com\", special_code=\"SP123\"\n        )\n\n        # Create a DirectM2MContainer instance\n        container = DirectM2MContainer.objects.create(name=\"Active Items\")\n\n        # Navigate to DirectM2MContainer's change page\n        self.page.goto(self.change_url(DirectM2MContainer, container.pk))\n\n        # Verify the page loads without errors\n        expect(self.page.locator(\"form#directm2mcontainer_form\")).to_be_visible()\n\n        # The filter_horizontal widget should display available polymorphic items\n        # All items should be in the \"available\" select box\n        available_box = self.page.locator(\"select#id_items_from\")\n        expect(available_box).to_be_visible()\n\n        # Verify all four polymorphic items appear in the available list\n        available_options = available_box.locator(\"option\").all_inner_texts()\n        assert \"Django Project\" in str(available_options)\n        assert \"React Project\" in str(available_options)\n        assert \"Alice Developer\" in str(available_options)\n        assert \"Bob Special\" in str(available_options)\n\n        # Select and move items to the \"chosen\" box using the filter_horizontal widget\n        # Double-click on items to move them (Django's filter_horizontal behavior)\n\n        # Double-click Django Project to move it\n        available_box.locator(f\"option[value='{project1.pk}']\").dblclick()\n        self.page.wait_for_timeout(300)\n\n        # Double-click Alice Developer to move it\n        available_box.locator(f\"option[value='{person1.pk}']\").dblclick()\n        self.page.wait_for_timeout(300)\n\n        # Double-click Bob Special to move it\n        available_box.locator(f\"option[value='{person2.pk}']\").dblclick()\n        self.page.wait_for_timeout(300)\n\n        # Verify they moved to the chosen box\n        chosen_box = self.page.locator(\"select#id_items_to\")\n        chosen_options = chosen_box.locator(\"option\").all_inner_texts()\n        assert \"Django Project\" in str(chosen_options)\n        assert \"Alice Developer\" in str(chosen_options)\n        assert \"Bob Special\" in str(chosen_options)\n\n        # Save the form - this should NOT raise AttributeError\n        with self.page.expect_navigation(timeout=30000) as nav_info:\n            self.page.click(\"input[name='_save']\")\n\n        response = nav_info.value\n        assert response.status < 400, (\n            f\"Form submission failed with status {response.status}. \"\n            \"This may indicate Issue #182 is not fixed.\"\n        )\n\n        # Verify the relationships were saved correctly\n        container.refresh_from_db()\n        item_ids = set(container.items.values_list(\"pk\", flat=True))\n        assert project1.pk in item_ids\n        assert person1.pk in item_ids\n        assert person2.pk in item_ids\n        assert project2.pk not in item_ids\n        assert len(item_ids) == 3\n\n        # Navigate back to the change page and verify the display\n        self.page.goto(self.change_url(DirectM2MContainer, container.pk))\n\n        # The chosen box should show the selected polymorphic items\n        chosen_box = self.page.locator(\"select#id_items_to\")\n        chosen_options = chosen_box.locator(\"option\").all_inner_texts()\n        assert \"Django Project\" in str(chosen_options)\n        assert \"Alice Developer\" in str(chosen_options)\n        assert \"Bob Special\" in str(chosen_options)\n\n        # Available box should only show React Project\n        available_box = self.page.locator(\"select#id_items_from\")\n        available_options = available_box.locator(\"option\").all_inner_texts()\n        assert \"React Project\" in str(available_options)\n        assert \"Django Project\" not in str(available_options)\n        assert \"Alice Developer\" not in str(available_options)\n        assert \"Bob Special\" not in str(available_options)\n\n    def test_issue_375_m2m_polymorphic_with_through_model(self):\n        \"\"\"\n        Test for Issue #375: Admin with M2M through table between polymorphic models.\n\n        When a polymorphic model has a ManyToManyField with a custom through model\n        to another polymorphic model, the admin should work using polymorphic inlines\n        for the through model.\n\n        This tests M2M between TWO polymorphic models with a POLYMORPHIC through table.\n\n        Scenario:\n        1. Create M2MThroughPerson instances (polymorphic model)\n        2. Create a M2MThroughProjectWithTeam instance (polymorphic model)\n        3. Navigate to M2MThroughProjectWithTeam's admin change page\n        4. Add team members using the POLYMORPHIC M2MThroughMembership inline\n        5. Test creating both MembershipWithPerson and MembershipWithSpecialPerson types\n        6. Save and verify the correct polymorphic types were created\n\n        References:\n        - https://github.com/django-polymorphic/django-polymorphic/issues/375\n        \"\"\"\n        from polymorphic.tests.models import (\n            M2MThroughPerson,\n            M2MThroughSpecialPerson,\n            M2MThroughProjectWithTeam,\n            M2MThroughMembership,\n            M2MThroughMembershipWithPerson,\n            M2MThroughMembershipWithSpecialPerson,\n        )\n        from django.contrib.contenttypes.models import ContentType\n\n        # Create polymorphic Person instances\n        person1 = M2MThroughPerson.objects.create(name=\"Charlie Lead\", email=\"charlie@example.com\")\n        person2 = M2MThroughSpecialPerson.objects.create(\n            name=\"Diana Special\", email=\"diana@example.com\", special_code=\"SP456\"\n        )\n        person3 = M2MThroughPerson.objects.create(name=\"Eve Tester\", email=\"eve@example.com\")\n\n        # Create a polymorphic ProjectWithTeam instance\n        project = M2MThroughProjectWithTeam.objects.create(\n            name=\"AI Platform\", description=\"Machine learning platform\"\n        )\n\n        # Navigate to M2MThroughProjectWithTeam's change page\n        self.page.goto(self.change_url(M2MThroughProjectWithTeam, project.pk))\n\n        # Verify the page loads without errors\n        expect(self.page.locator(\"form#m2mthroughprojectwithteam_form\")).to_be_visible()\n\n        # Verify the polymorphic inline formset is present\n        polymorphic_menu = self.page.locator(\n            \"div.polymorphic-add-choice div.polymorphic-type-menu\"\n        )\n        expect(polymorphic_menu).to_be_hidden()\n\n        # Click to show the polymorphic type menu\n        self.page.click(\"div.polymorphic-add-choice a\")\n        expect(polymorphic_menu).to_be_visible()\n\n        # Get ContentType for MembershipWithPerson\n        membership_person_ct = ContentType.objects.get_for_model(M2MThroughMembershipWithPerson)\n\n        # Select \"Membership with person\" type\n        self.page.click(\"div.polymorphic-type-menu a[data-type='m2mthroughmembershipwithperson']\")\n        polymorphic_menu.wait_for(state=\"hidden\")\n        self.page.wait_for_timeout(500)\n\n        # Fill in the first membership (regular Person)\n        self.page.select_option(\n            \"select[name='m2mthroughmembership_set-0-person']\", str(person1.pk)\n        )\n        self.page.fill(\"input[name='m2mthroughmembership_set-0-role']\", \"Tech Lead\")\n\n        # Add another membership - click the polymorphic add button again\n        self.page.click(\"div.polymorphic-add-choice a\")\n        self.page.wait_for_timeout(300)\n        polymorphic_menu.wait_for(state=\"visible\")\n\n        # This time select \"Membership with special person\" type\n        self.page.click(\n            \"div.polymorphic-type-menu a[data-type='m2mthroughmembershipwithspecialperson']\"\n        )\n        polymorphic_menu.wait_for(state=\"hidden\")\n        self.page.wait_for_timeout(500)\n\n        # Verify the polymorphic inline form was added\n        # Check for the polymorphic_ctype hidden field\n        ctype_field = self.page.locator(\n            \"input[name='m2mthroughmembership_set-1-polymorphic_ctype']\"\n        )\n        expect(ctype_field).to_be_attached()\n\n        # NOTE: There appears to be a limitation in the polymorphic inline JavaScript\n        # where selecting different types for multiple inline forms doesn't always work correctly.\n        # For now, we'll just verify that polymorphic inlines can be used even if both\n        # end up being the same type. The important thing is that the polymorphic inline\n        # infrastructure works.\n\n        # Fill in the second membership (SpecialPerson)\n        self.page.select_option(\n            \"select[name='m2mthroughmembership_set-1-person']\", str(person2.pk)\n        )\n        self.page.fill(\"input[name='m2mthroughmembership_set-1-role']\", \"Lead Developer\")\n        # Check if special_notes field is rendered\n        special_notes_field = self.page.locator(\n            \"textarea[name='m2mthroughmembershipwithspecialperson_set-1-special_notes'], textarea[name='m2mthroughmembership_set-1-special_notes']\"\n        )\n        if special_notes_field.count() > 0:\n            special_notes_field.first.fill(\"VIP team member\")\n\n        # Save the form\n        with self.page.expect_navigation(timeout=30000) as nav_info:\n            self.page.click(\"input[name='_save']\")\n\n        response = nav_info.value\n        assert response.status < 400, (\n            f\"Form submission failed with status {response.status}. \"\n            \"This may indicate Issue #375 polymorphic inline is not working.\"\n        )\n\n        # Verify the relationships were saved correctly via the polymorphic through model\n        project.refresh_from_db()\n        memberships = M2MThroughMembership.objects.filter(project=project)\n        assert memberships.count() == 2\n\n        # Check first membership\n        membership1 = memberships.filter(person=person1).first()\n        assert membership1 is not None\n        # Verify it's a polymorphic instance (has polymorphic_ctype)\n        assert hasattr(membership1, \"polymorphic_ctype\")\n        assert membership1.role == \"Tech Lead\"\n        assert membership1.person.pk == person1.pk\n\n        # Check second membership\n        membership2 = memberships.filter(person=person2).first()\n        assert membership2 is not None\n        # Verify it's a polymorphic instance\n        assert hasattr(membership2, \"polymorphic_ctype\")\n        assert membership2.role == \"Lead Developer\"\n        assert membership2.person.pk == person2.pk\n\n        # NOTE: Due to limitations in polymorphic inline JavaScript, both memberships\n        # might be the same polymorphic type. The key success is that:\n        # 1. The polymorphic inline formset works\n        # 2. Multiple memberships can be created\n        # 3. They are saved as polymorphic instances\n\n        # Verify via the M2M relationship\n        team_member_ids = set(project.team.values_list(\"pk\", flat=True))\n        assert person1.pk in team_member_ids\n        assert person2.pk in team_member_ids\n        assert person3.pk not in team_member_ids\n        assert len(team_member_ids) == 2\n\n\nclass PolymorphicAdminCoverageTests(AdminTestCase):\n    \"\"\"Tests targeting uncovered lines in polymorphic/admin/*.\"\"\"\n\n    # ── parentadmin.py ────────────────────────────────────────────────────────\n\n    def test_get_child_models_not_implemented(self):\n        \"\"\"get_child_models() raises NotImplementedError when child_models not set.\"\"\"\n\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n\n        admin_instance = Model2Admin(Model2A, self.admin_site)\n        with pytest.raises(NotImplementedError):\n            admin_instance.get_child_models()\n\n    def test_lazy_setup_old_format(self):\n        \"\"\"_lazy_setup() raises ImproperlyConfigured for old (model, admin) tuple format.\"\"\"\n        from django.core.exceptions import ImproperlyConfigured\n\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = [str]  # str is a class but not a models.Model subclass\n\n        admin_instance = Model2Admin(Model2A, self.admin_site)\n        with pytest.raises(ImproperlyConfigured):\n            admin_instance._lazy_setup()\n\n    def test_get_real_admin_nonexistent_pk(self):\n        \"\"\"_get_real_admin() raises Http404 when the object does not exist.\"\"\"\n        from django.http import Http404\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2B,)\n\n        @self.register(Model2B)\n        class Model2BAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n\n        admin_instance = self.get_admin_instance(Model2A)\n        admin_instance._lazy_setup()\n        with pytest.raises(Http404):\n            admin_instance._get_real_admin(999999)\n\n    def test_get_real_admin_by_ct_nonexistent(self):\n        \"\"\"_get_real_admin_by_ct() raises Http404 for a nonexistent CT id.\"\"\"\n        from django.http import Http404\n        from unittest.mock import patch\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2B,)\n\n        @self.register(Model2B)\n        class Model2BAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n\n        admin_instance = self.get_admin_instance(Model2A)\n        admin_instance._lazy_setup()\n        with patch.object(ContentType.objects, \"get_for_id\", side_effect=ContentType.DoesNotExist):\n            with pytest.raises(Http404):\n                admin_instance._get_real_admin_by_ct(999999)\n\n    def test_get_real_admin_by_ct_deleted_model(self):\n        \"\"\"_get_real_admin_by_ct() raises Http404 when the model class is gone (stale CT).\"\"\"\n        from django.http import Http404\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2B,)\n\n        @self.register(Model2B)\n        class Model2BAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n\n        admin_instance = self.get_admin_instance(Model2A)\n        admin_instance._lazy_setup()\n\n        from unittest.mock import MagicMock, patch\n\n        stale_ct = MagicMock()\n        stale_ct.model_class.return_value = None\n        stale_ct.natural_key.return_value = (\"fake_app\", \"deletedmodel\")\n        with patch.object(ContentType.objects, \"get_for_id\", return_value=stale_ct):\n            with pytest.raises(Http404):\n                admin_instance._get_real_admin_by_ct(stale_ct.pk)\n\n    def test_get_real_admin_by_model_permission_denied(self):\n        \"\"\"_get_real_admin_by_model() raises PermissionDenied for non-child model.\"\"\"\n        from django.core.exceptions import PermissionDenied\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2B,)\n\n        @self.register(Model2B)\n        class Model2BAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n\n        admin_instance = self.get_admin_instance(Model2A)\n        admin_instance._lazy_setup()\n        with pytest.raises(PermissionDenied):\n            admin_instance._get_real_admin_by_model(Model2C)\n\n    def test_child_admin_not_registered(self):\n        \"\"\"_get_real_admin_by_model() raises ChildAdminNotRegistered when model not in site.\"\"\"\n        from polymorphic.admin.parentadmin import ChildAdminNotRegistered\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2B, Model2C)\n\n        @self.register(Model2B)\n        class Model2BAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n\n        # Model2C is in child_models but NOT registered in the admin site\n        admin_instance = self.get_admin_instance(Model2A)\n        admin_instance._lazy_setup()\n        with pytest.raises(ChildAdminNotRegistered):\n            admin_instance._get_real_admin_by_model(Model2C)\n\n    def test_real_admin_is_self_returns_super(self):\n        \"\"\"_get_real_admin_by_model() returns super() proxy when real_admin is self.\"\"\"\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2A, Model2B)\n\n        @self.register(Model2B)\n        class Model2BAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n\n        admin_instance = self.get_admin_instance(Model2A)\n        admin_instance._lazy_setup()\n        result = admin_instance._get_real_admin_by_model(Model2A, super_if_self=True)\n        # Returns a super() proxy, not the instance itself\n        assert result is not admin_instance\n\n    def test_get_queryset_polymorphic_list_true(self):\n        \"\"\"get_queryset() with polymorphic_list=True returns polymorphic instances.\"\"\"\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2B, Model2C, Model2D)\n            polymorphic_list = True\n\n        @self.register(Model2B)\n        @self.register(Model2C)\n        @self.register(Model2D)\n        class Model2ChildAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n\n        Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        admin_instance = self.get_admin_instance(Model2A)\n        request = self.create_admin_request(\"get\", self.get_changelist_url(Model2A))\n        qs = admin_instance.get_queryset(request)\n        obj = qs.first()\n        assert isinstance(obj, Model2B)\n\n    def test_get_child_type_choices_skips_no_permission(self):\n        \"\"\"get_child_type_choices() skips child models the user cannot add.\"\"\"\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2B, Model2C)\n\n        @self.register(Model2B)\n        class Model2BAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n\n            def has_add_permission(self, request):\n                return True\n\n        @self.register(Model2C)\n        class Model2CAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n\n            def has_add_permission(self, request):\n                return False\n\n        admin_instance = self.get_admin_instance(Model2A)\n        admin_instance._lazy_setup()\n        request = self.create_admin_request(\"get\", self.get_add_url(Model2A))\n        choices = admin_instance.get_child_type_choices(request, \"add\")\n        ct_ids = [ct_id for ct_id, _ in choices]\n        assert ContentType.objects.get_for_model(Model2B).pk in ct_ids\n        assert ContentType.objects.get_for_model(Model2C).pk not in ct_ids\n\n    def test_add_type_view_no_add_permission(self):\n        \"\"\"add_type_view() raises PermissionDenied when the user has no add permission.\"\"\"\n        from django.core.exceptions import PermissionDenied\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2B,)\n\n            def has_add_permission(self, request):\n                return False\n\n        @self.register(Model2B)\n        class Model2BAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n\n        admin_instance = self.get_admin_instance(Model2A)\n        request = self.create_admin_request(\"get\", self.get_add_url(Model2A))\n        with pytest.raises(PermissionDenied):\n            admin_instance.add_type_view(request)\n\n    def test_add_type_view_no_choices_permission_denied(self):\n        \"\"\"add_type_view() raises PermissionDenied when no child types are available.\"\"\"\n        from django.core.exceptions import PermissionDenied\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2B,)\n\n            def get_child_type_choices(self, request, action):\n                return []\n\n        @self.register(Model2B)\n        class Model2BAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n\n        admin_instance = self.get_admin_instance(Model2A)\n        request = self.create_admin_request(\"get\", self.get_add_url(Model2A))\n        with pytest.raises(PermissionDenied):\n            admin_instance.add_type_view(request)\n\n    # ── childadmin.py ─────────────────────────────────────────────────────────\n\n    def test_child_admin_get_form_with_fieldsets(self):\n        \"\"\"get_form() skips setting fields='__all__' when fieldsets is already set.\"\"\"\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2B,)\n\n        @self.register(Model2B)\n        class Model2BAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n            fieldsets = ((\"Fields\", {\"fields\": (\"field1\", \"field2\")}),)\n\n        obj = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        b_admin = self.get_admin_instance(Model2B)\n        request = self.create_admin_request(\"get\", self.get_change_url(Model2B, obj.pk))\n        form_class = b_admin.get_form(request, obj)\n        assert form_class is not None\n\n    def test_child_admin_get_parent_admin_self_is_parent(self):\n        \"\"\"_get_parent_admin() returns super() when parent_model == self.model (base model).\"\"\"\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2A, Model2B)\n\n        # A PolymorphicChildModelAdmin for Model2A itself:\n        # polymorphic_ctype is defined on Model2A, so parent_model == Model2A == self.model\n        child_admin = PolymorphicChildModelAdmin(Model2A, self.admin_site)\n        parent = child_admin._get_parent_admin()\n        assert parent is not child_admin\n\n    def test_child_admin_get_parent_admin_mro_scan(self):\n        \"\"\"_get_parent_admin() finds parent via MRO when direct parent not registered.\"\"\"\n\n        # Register Model2B as a parent admin (but NOT Model2A)\n        @self.register(Model2B)\n        class Model2BParentAdmin(PolymorphicParentModelAdmin):\n            base_model = Model2B\n            child_models = (Model2C,)\n\n        @self.register(Model2C)\n        class Model2CAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2B\n\n        # Model2C._meta.get_field(\"polymorphic_ctype\").model == Model2A (not in registry)\n        # MRO scan finds Model2B which IS registered as PolymorphicParentModelAdmin\n        c_admin = self.get_admin_instance(Model2C)\n        parent = c_admin._get_parent_admin()\n        assert isinstance(parent, PolymorphicParentModelAdmin)\n\n    def test_child_admin_get_parent_admin_not_registered(self):\n        \"\"\"_get_parent_admin() raises ParentAdminNotRegistered when MRO has no parent admin.\"\"\"\n        from polymorphic.admin.childadmin import ParentAdminNotRegistered\n\n        @self.register(Model2C)\n        class Model2CAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n\n        c_admin = self.get_admin_instance(Model2C)\n        with pytest.raises(ParentAdminNotRegistered):\n            c_admin._get_parent_admin()\n\n    def test_child_admin_history_view_extra_context(self):\n        \"\"\"history_view() correctly merges extra_context into the template context.\"\"\"\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2B,)\n\n        @self.register(Model2B)\n        class Model2BAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n\n        obj = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        b_admin = self.get_admin_instance(Model2B)\n        request = self.create_admin_request(\"get\", self.get_history_url(Model2B, obj.pk))\n        response = b_admin.history_view(request, str(obj.pk), extra_context={\"custom_key\": \"v\"})\n        assert response.status_code == 200\n\n    def test_child_admin_get_fieldsets_no_subclass_fields(self):\n        \"\"\"get_fieldsets() returns base_fieldsets when no extra subclass fields exist.\"\"\"\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2B,)\n\n        @self.register(Model2B)\n        class Model2BAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n            # Covers all fields of Model2B (field1 from Model2A + field2)\n            base_fieldsets = ((\"All Fields\", {\"fields\": (\"field1\", \"field2\")}),)\n\n        obj = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        b_admin = self.get_admin_instance(Model2B)\n        request = self.create_admin_request(\"get\", self.get_change_url(Model2B, obj.pk))\n        fieldsets = b_admin.get_fieldsets(request, obj)\n        # No extra fieldset added; base_fieldsets returned directly\n        assert len(fieldsets) == 1\n        assert fieldsets[0][0] == \"All Fields\"\n\n    def test_child_admin_get_subclass_fields_with_tuple_field(self):\n        \"\"\"get_subclass_fields() handles tuple entries (multiple fields on one line).\"\"\"\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2B,)\n\n        @self.register(Model2B)\n        class Model2BAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n            # field1 is in a tuple (two fields per row); \"nonexistent\" triggers ValueError→pass\n            base_fieldsets = ((\"Base\", {\"fields\": ((\"field1\", \"nonexistent\"),)}),)\n\n        obj = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        b_admin = self.get_admin_instance(Model2B)\n        request = self.create_admin_request(\"get\", self.get_change_url(Model2B, obj.pk))\n        fieldsets = b_admin.get_fieldsets(request, obj)\n        assert fieldsets == (\n            (\"Base\", {\"fields\": ((\"field1\", \"nonexistent\"),)}),\n            (\"Contents\", {\"fields\": [\"field2\"]}),\n        )\n\n    def test_child_admin_get_subclass_fields_missing_field(self):\n        \"\"\"get_subclass_fields() silently skips fields not present in the form.\"\"\"\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            base_model = Model2A\n            child_models = (Model2B,)\n\n        @self.register(Model2B)\n        class Model2BAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n            # \"nonexistent_field\" doesn't exist on Model2B\n            base_fieldsets = ((\"Base\", {\"fields\": (\"field1\", \"nonexistent_field\")}),)\n\n        obj = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        b_admin = self.get_admin_instance(Model2B)\n        request = self.create_admin_request(\"get\", self.get_change_url(Model2B, obj.pk))\n        subclass_fields = b_admin.get_subclass_fields(request, obj)\n        assert isinstance(subclass_fields, list)\n\n    # ── filters.py ────────────────────────────────────────────────────────────\n\n    def test_filter_queryset_type_error(self):\n        \"\"\"filter queryset() handles TypeError from a non-string truthy value.\"\"\"\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            list_filter = (PolymorphicChildModelFilter,)\n            child_models = (Model2B, Model2C)\n\n        @self.register(Model2B)\n        @self.register(Model2C)\n        class Model2ChildAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n\n        admin_instance = self.get_admin_instance(Model2A)\n        admin_instance._lazy_setup()\n        request = self.create_admin_request(\"get\", \"/tmp-admin/\")\n        filter_instance = PolymorphicChildModelFilter(request, {}, Model2A, admin_instance)\n        # A list is truthy but int([1]) raises TypeError\n        filter_instance.used_parameters = {\"polymorphic_ctype\": [1]}\n        qs = Model2A.objects.all()\n        result = filter_instance.queryset(request, qs)\n        # TypeError → value=None → queryset returned unchanged\n        assert list(result) == list(qs)\n\n    def test_filter_queryset_permission_denied(self):\n        \"\"\"filter queryset() raises PermissionDenied for a CT not in allowed choices.\"\"\"\n        from django.core.exceptions import PermissionDenied\n\n        @self.register(Model2A)\n        class Model2Admin(PolymorphicParentModelAdmin):\n            list_filter = (PolymorphicChildModelFilter,)\n            child_models = (Model2B,)\n\n        @self.register(Model2B)\n        class Model2BAdmin(PolymorphicChildModelAdmin):\n            base_model = Model2A\n\n        admin_instance = self.get_admin_instance(Model2A)\n        admin_instance._lazy_setup()\n        request = self.create_admin_request(\"get\", \"/tmp-admin/\")\n        filter_instance = PolymorphicChildModelFilter(request, {}, Model2A, admin_instance)\n        # Use the CT id of Model2C which is not in the allowed choices (only Model2B is)\n        model2c_ct_id = ContentType.objects.get_for_model(Model2C).pk\n        filter_instance.used_parameters = {\"polymorphic_ctype\": str(model2c_ct_id)}\n        qs = Model2A.objects.all()\n        with pytest.raises(PermissionDenied):\n            filter_instance.queryset(request, qs)\n\n    # ── inlines.py ────────────────────────────────────────────────────────────\n\n    def test_inline_parent_not_in_registry_skips_check(self):\n        \"\"\"Inline instantiation skips the mixin check when parent model is not registered.\"\"\"\n\n        class InlineAChild(StackedPolymorphicInline.Child):\n            model = InlineModelA\n\n        class TestInline(StackedPolymorphicInline):\n            model = InlineModelA\n            child_inlines = (InlineAChild,)\n\n        # InlineParent is NOT registered in self.admin_site → parent_admin is None → skip check\n        inline = TestInline(parent_model=InlineParent, admin_site=self.admin_site)\n        assert inline is not None\n\n    def test_inline_improperly_configured_missing_mixin(self):\n        \"\"\"Raise ImproperlyConfigured when parent admin lacks PolymorphicInlineSupportMixin.\"\"\"\n        from django.core.exceptions import ImproperlyConfigured\n\n        class InlineAChild(StackedPolymorphicInline.Child):\n            model = InlineModelA\n\n        class TestInline(StackedPolymorphicInline):\n            model = InlineModelA\n            child_inlines = (InlineAChild,)\n\n        @self.register(InlineParent)\n        class BadParentAdmin(admin.ModelAdmin):  # missing PolymorphicInlineSupportMixin\n            inlines = (TestInline,)\n\n        with pytest.raises(ImproperlyConfigured):\n            TestInline(parent_model=InlineParent, admin_site=self.admin_site)\n\n    def test_inline_unsupported_child_type(self):\n        \"\"\"get_child_inline_instance() raises UnsupportedChildType for an unknown model.\"\"\"\n        from polymorphic.formsets import UnsupportedChildType\n\n        class InlineAChild(StackedPolymorphicInline.Child):\n            model = InlineModelA\n\n        class TestInline(StackedPolymorphicInline):\n            model = InlineModelA\n            child_inlines = (InlineAChild,)\n\n        @self.register(InlineParent)\n        class GoodParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):\n            inlines = (TestInline,)\n\n        inline = TestInline(parent_model=InlineParent, admin_site=self.admin_site)\n        with pytest.raises(UnsupportedChildType):\n            inline.get_child_inline_instance(InlineModelB)\n\n    def test_inline_get_fieldsets_with_fieldsets_set(self):\n        \"\"\"get_fieldsets() returns self.fieldsets when fieldsets is set on the parent inline.\"\"\"\n\n        class TestInline(StackedPolymorphicInline):\n            model = InlineModelA\n            child_inlines = []\n            fieldsets = ((\"Test Fields\", {\"fields\": [\"field1\"]}),)\n\n        @self.register(InlineParent)\n        class ParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):\n            inlines = (TestInline,)\n\n        inline = TestInline(parent_model=InlineParent, admin_site=self.admin_site)\n        request = self.create_admin_request(\"get\", \"/tmp-admin/\")\n        result = inline.get_fieldsets(request)\n        assert result == ((\"Test Fields\", {\"fields\": [\"field1\"]}),)\n\n    def test_inline_get_fieldsets_empty(self):\n        \"\"\"get_fieldsets() returns [] when no fieldsets are set on the parent inline.\"\"\"\n\n        class TestInline(StackedPolymorphicInline):\n            model = InlineModelA\n            child_inlines = []\n\n        @self.register(InlineParent)\n        class ParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):\n            inlines = (TestInline,)\n\n        inline = TestInline(parent_model=InlineParent, admin_site=self.admin_site)\n        request = self.create_admin_request(\"get\", \"/tmp-admin/\")\n        assert inline.get_fieldsets(request) == []\n\n    def test_inline_get_fields_with_fields_set(self):\n        \"\"\"get_fields() returns self.fields when explicitly set on the parent inline.\"\"\"\n\n        class TestInline(StackedPolymorphicInline):\n            model = InlineModelA\n            child_inlines = []\n            fields = [\"field1\"]\n\n        @self.register(InlineParent)\n        class ParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):\n            inlines = (TestInline,)\n\n        inline = TestInline(parent_model=InlineParent, admin_site=self.admin_site)\n        request = self.create_admin_request(\"get\", \"/tmp-admin/\")\n        assert \"field1\" in inline.get_fields(request)\n\n    def test_inline_get_fields_no_fields_returns_empty(self):\n        \"\"\"get_fields() returns [] when self.fields is not set on the parent inline.\"\"\"\n\n        class TestInline(StackedPolymorphicInline):\n            model = InlineModelA\n            child_inlines = []\n            # fields is None (default)\n\n        @self.register(InlineParent)\n        class ParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):\n            inlines = (TestInline,)\n\n        inline = TestInline(parent_model=InlineParent, admin_site=self.admin_site)\n        request = self.create_admin_request(\"get\", \"/tmp-admin/\")\n        assert inline.get_fields(request) == []\n\n    def test_inline_child_get_formset_raises_runtime_error(self):\n        \"\"\"Child.get_formset() raises RuntimeError because it is not used.\"\"\"\n\n        class InlineAChild(StackedPolymorphicInline.Child):\n            model = InlineModelA\n\n        class TestInline(StackedPolymorphicInline):\n            model = InlineModelA\n            child_inlines = (InlineAChild,)\n\n        @self.register(InlineParent)\n        class ParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):\n            inlines = (TestInline,)\n\n        inline = TestInline(parent_model=InlineParent, admin_site=self.admin_site)\n        child = inline.child_inline_instances[0]\n        request = self.create_admin_request(\"get\", \"/tmp-admin/\")\n        with pytest.raises(RuntimeError):\n            child.get_formset(request)\n\n    def test_inline_child_get_fields_with_fields_set(self):\n        \"\"\"Child.get_fields() returns self.fields when explicitly set.\"\"\"\n\n        class InlineAChild(StackedPolymorphicInline.Child):\n            model = InlineModelA\n            fields = [\"field1\"]\n\n        class TestInline(StackedPolymorphicInline):\n            model = InlineModelA\n            child_inlines = (InlineAChild,)\n\n        @self.register(InlineParent)\n        class ParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):\n            inlines = (TestInline,)\n\n        inline = TestInline(parent_model=InlineParent, admin_site=self.admin_site)\n        child = inline.child_inline_instances[0]\n        request = self.create_admin_request(\"get\", \"/tmp-admin/\")\n        assert \"field1\" in child.get_fields(request)\n\n    def test_inline_child_exclude_in_formset(self):\n        \"\"\"get_formset_child() respects the Child's exclude attribute.\"\"\"\n\n        class InlineAChild(StackedPolymorphicInline.Child):\n            model = InlineModelA\n            exclude = [\"field1\"]\n\n        class TestInline(StackedPolymorphicInline):\n            model = InlineModelA\n            child_inlines = (InlineAChild,)\n\n        @self.register(InlineParent)\n        class ParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):\n            inlines = (TestInline,)\n\n        inline = TestInline(parent_model=InlineParent, admin_site=self.admin_site)\n        child = inline.child_inline_instances[0]\n        request = self.create_admin_request(\"get\", \"/tmp-admin/\")\n        formset_child = child.get_formset_child(request)\n        form = formset_child.get_form()\n        assert \"field1\" not in form.base_fields\n\n    def test_inline_child_form_meta_exclude(self):\n        \"\"\"get_formset_child() incorporates form's Meta.exclude when Child.exclude is None.\"\"\"\n        from django import forms\n\n        class InlineModelAForm(forms.ModelForm):\n            class Meta:\n                model = InlineModelA\n                exclude = [\"field1\"]\n\n        class InlineAChild(StackedPolymorphicInline.Child):\n            model = InlineModelA\n            form = InlineModelAForm\n            # exclude remains None (default)\n\n        class TestInline(StackedPolymorphicInline):\n            model = InlineModelA\n            child_inlines = (InlineAChild,)\n\n        @self.register(InlineParent)\n        class ParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):\n            inlines = (TestInline,)\n\n        inline = TestInline(parent_model=InlineParent, admin_site=self.admin_site)\n        child = inline.child_inline_instances[0]\n        request = self.create_admin_request(\"get\", \"/tmp-admin/\")\n        formset_child = child.get_formset_child(request)\n        assert formset_child is not None\n\n    def test_inline_media_different_child_media(self):\n        \"\"\"inline.media adds child media when CSS and JS both differ from base media.\"\"\"\n\n        class InlineBChild(StackedPolymorphicInline.Child):\n            model = InlineModelB\n\n            class Media:\n                css = {\"all\": [\"admin/css/unique_child_coverage.css\"]}\n                js = [\"admin/js/unique_child_coverage.js\"]\n\n        class InlineAChild(StackedPolymorphicInline.Child):\n            model = InlineModelA\n\n        class TestInline(StackedPolymorphicInline):\n            model = InlineModelA\n            child_inlines = (InlineAChild, InlineBChild)\n\n        @self.register(InlineParent)\n        class ParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):\n            inlines = (TestInline,)\n\n        inline = TestInline(parent_model=InlineParent, admin_site=self.admin_site)\n        media = inline.media\n        # The unique CSS + JS from InlineBChild should have been merged in\n        all_css = str(media._css)\n        all_js = str(media._js)\n        assert \"unique_child_coverage.css\" in all_css\n        assert \"unique_child_coverage.js\" in all_js\n\n    def test_inline_get_inline_formsets_non_polymorphic(self):\n        \"\"\"PolymorphicInlineSupportMixin.get_inline_formsets handles non-polymorphic formsets.\"\"\"\n        from django.contrib.admin import TabularInline\n\n        class RegularInline(TabularInline):\n            model = InlineModelA\n            extra = 0\n\n        class InlineAChild(StackedPolymorphicInline.Child):\n            model = InlineModelA\n\n        class PolyInline(StackedPolymorphicInline):\n            model = InlineModelA\n            child_inlines = (InlineAChild,)\n\n        @self.register(InlineParent)\n        class ParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):\n            inlines = (RegularInline, PolyInline)\n\n        parent = InlineParent.objects.create(title=\"Test\")\n        response = self.admin_get_change(InlineParent, parent.pk)\n        assert response.status_code == 200\n\n    # ── helpers.py ────────────────────────────────────────────────────────────\n\n    def test_polymorphic_inline_admin_form_is_empty_false_no_prefix(self):\n        \"\"\"is_empty returns False when the form has no prefix.\"\"\"\n\n        class FormWithNoPrefix:\n            prefix = None\n\n        admin_form = PolymorphicInlineAdminForm.__new__(PolymorphicInlineAdminForm)\n        admin_form.form = FormWithNoPrefix()\n        assert admin_form.is_empty is False\n\n    def test_polymorphic_inline_admin_formset_init(self):\n        \"\"\"PolymorphicInlineAdminFormSet.__init__ stores request and obj from kwargs.\"\"\"\n        from unittest.mock import MagicMock, patch\n\n        request = MagicMock()\n        obj = MagicMock()\n        with patch(\"django.contrib.admin.helpers.InlineAdminFormSet.__init__\", return_value=None):\n            formset = PolymorphicInlineAdminFormSet(request=request, obj=obj)\n        assert formset.request is request\n        assert formset.obj is obj\n\n    # ── generic.py ────────────────────────────────────────────────────────────\n\n    def test_generic_polymorphic_inline_get_formset(self):\n        \"\"\"GenericPolymorphicInlineModelAdmin.get_formset() and Child methods work.\"\"\"\n\n        class TagInlineA(GenericStackedPolymorphicInline.Child):\n            model = PolymorphicTagA\n\n        class TagInlineB(GenericStackedPolymorphicInline.Child):\n            model = PolymorphicTagB\n\n        class TagInline(GenericStackedPolymorphicInline):\n            model = PolymorphicTagBase\n            child_inlines = (TagInlineA, TagInlineB)\n\n        @self.register(InlineParent)\n        class ParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):\n            inlines = (TagInline,)\n\n        parent = InlineParent.objects.create(title=\"Tag Test\")\n        request = self.create_admin_request(\"get\", self.get_change_url(InlineParent, parent.pk))\n        inline = TagInline(parent_model=InlineParent, admin_site=self.admin_site)\n\n        # Exercises GenericPolymorphicInlineModelAdmin.get_formset()\n        FormSet = inline.get_formset(request, parent)\n        assert FormSet is not None\n\n        # Exercises Child.content_type cached_property and Child.get_formset_child()\n        child_inline = inline.child_inline_instances[0]\n        assert child_inline.content_type is not None\n        formset_child = child_inline.get_formset_child(request, parent)\n        assert formset_child is not None\n\n\nclass ExistingInlineTests(_GenericAdminFormTest):\n    \"\"\"Playwright tests that render change forms with pre-existing polymorphic inline rows.\n\n    This exercises PolymorphicInlineAdminFormSet.__iter__ over initial_forms (helpers.py).\n    \"\"\"\n\n    def test_inline_change_form_with_existing_children(self):\n        \"\"\"Navigate to a change form for a parent that already has inline children.\"\"\"\n        parent = InlineParent.objects.create(title=\"Parent With Inlines\")\n        child_a = InlineModelA.objects.create(parent=parent, field1=\"ChildA1\")\n        InlineModelB.objects.create(parent=parent, field1=\"ChildB1\", field2=\"ChildB2\")\n\n        self.page.goto(self.change_url(InlineParent, parent.pk))\n        expect(self.page.locator(\"input[name='title']\")).to_have_value(\"Parent With Inlines\")\n\n        # Both existing inline forms must be rendered (initial_forms iteration)\n        inline0 = self.page.locator(\"div#inline_children-0\")\n        inline1 = self.page.locator(\"div#inline_children-1\")\n        inline0.wait_for(state=\"visible\")\n        inline1.wait_for(state=\"visible\")\n\n        assert (\n            self.page.locator(\"input[name='inline_children-0-field1']\").input_value() == \"ChildA1\"\n        )\n        assert (\n            self.page.locator(\"input[name='inline_children-1-field1']\").input_value() == \"ChildB1\"\n        )\n\n        # Edit and save to confirm the round-trip works\n        self.page.fill(\"input[name='inline_children-0-field1']\", \"ChildA1-updated\")\n        with self.page.expect_navigation(timeout=30000) as nav_info:\n            self.page.click(\"input[name='_save']\")\n\n        assert nav_info.value.status < 400\n        child_a.refresh_from_db()\n        assert child_a.field1 == \"ChildA1-updated\"\n"
  },
  {
    "path": "src/polymorphic/tests/test_base.py",
    "content": "\"\"\"\nTests for base.py metaclass edge cases to achieve high-value coverage.\n\"\"\"\n\nimport os\nimport sys\nimport warnings\nfrom unittest.mock import patch\n\nfrom django.db import models\nfrom django.test import TestCase\n\nfrom polymorphic.base import ManagerInheritanceWarning, PolymorphicModelBase\nfrom polymorphic.managers import PolymorphicManager\nfrom polymorphic.models import PolymorphicModel\nfrom polymorphic.query import PolymorphicQuerySet\n\n\nclass PrimaryKeyNameTest(TestCase):\n    def test_polymorphic_primary_key_name_correctness(self):\n        \"\"\"\n        Verify that polymorphic_primary_key_name points to the root pk in the\n        inheritance chain.\n\n        Regression test for #758. Will go away in version 5.0\n        \"\"\"\n        from polymorphic.tests.models import (\n            CustomPkInherit,\n            CustomPkBase,\n            Model2A,\n            Model2B,\n            Model2C,\n        )\n\n        with warnings.catch_warnings(record=True) as w:\n            warnings.simplefilter(\"always\")\n            self.assertEqual(\n                CustomPkInherit.polymorphic_primary_key_name, CustomPkBase._meta.pk.attname\n            )\n            self.assertEqual(CustomPkInherit.polymorphic_primary_key_name, \"id\")\n\n            self.assertEqual(Model2A.polymorphic_primary_key_name, Model2A._meta.pk.attname)\n            self.assertEqual(Model2A.polymorphic_primary_key_name, \"id\")\n\n            self.assertEqual(Model2B.polymorphic_primary_key_name, Model2A._meta.pk.attname)\n            self.assertEqual(Model2B.polymorphic_primary_key_name, \"id\")\n\n            self.assertEqual(Model2C.polymorphic_primary_key_name, Model2A._meta.pk.attname)\n            self.assertEqual(Model2C.polymorphic_primary_key_name, \"id\")\n\n            assert w[0].category is DeprecationWarning\n            assert \"polymorphic_primary_key_name\" in str(w[0].message)\n\n    def test_multiple_inheritance_pk_name(self):\n        \"\"\"\n        Verify multiple inheritance scenarios.\n        \"\"\"\n        from polymorphic.tests.models import Enhance_Inherit, Enhance_Base\n\n        with warnings.catch_warnings(record=True) as w:\n            warnings.simplefilter(\"always\")\n            self.assertEqual(\n                Enhance_Inherit.polymorphic_primary_key_name, Enhance_Base._meta.pk.attname\n            )\n            self.assertEqual(Enhance_Inherit.polymorphic_primary_key_name, \"base_id\")\n            assert w[0].category is DeprecationWarning\n            assert \"polymorphic_primary_key_name\" in str(w[0].message)\n"
  },
  {
    "path": "src/polymorphic/tests/test_contrib.py",
    "content": "from django.test import TestCase\n\nfrom polymorphic.contrib.guardian import get_polymorphic_base_content_type\nfrom polymorphic.tests.models import Model2D, PlainC\n\n\nclass ContribTests(TestCase):\n    \"\"\"\n    The test suite\n    \"\"\"\n\n    def test_contrib_guardian(self):\n        # Regular Django inheritance should return the child model content type.\n        obj = PlainC()\n        ctype = get_polymorphic_base_content_type(obj)\n        assert ctype.name == \"plain c\"\n\n        ctype = get_polymorphic_base_content_type(PlainC)\n        assert ctype.name == \"plain c\"\n\n        # Polymorphic inheritance should return the parent model content type.\n        obj = Model2D()\n        ctype = get_polymorphic_base_content_type(obj)\n        assert ctype.name == \"model2a\"\n\n        ctype = get_polymorphic_base_content_type(Model2D)\n        assert ctype.name == \"model2a\"\n"
  },
  {
    "path": "src/polymorphic/tests/test_formsets.py",
    "content": "\"\"\"\nTests for polymorphic formsets.\n\"\"\"\n\nimport pytest\nfrom django import forms\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.core.exceptions import ImproperlyConfigured, ValidationError\nfrom django.test import TestCase\n\nfrom polymorphic.formsets.generic import (\n    GenericPolymorphicFormSetChild,\n    generic_polymorphic_inlineformset_factory,\n)\nfrom polymorphic.formsets.models import (\n    PolymorphicFormSetChild,\n    UnsupportedChildType,\n    polymorphic_inlineformset_factory,\n    polymorphic_modelformset_factory,\n)\nfrom polymorphic.tests.models import (\n    GenericFKParent,\n    Model2A,\n    Model2B,\n    Model2C,\n    PolymorphicTagA,\n    PolymorphicTagB,\n    PolymorphicTagBase,\n)\n\n\nclass PolymorphicFormSetChildTest(TestCase):\n    \"\"\"Test PolymorphicFormSetChild configuration\"\"\"\n\n    def test_content_type_property(self):\n        \"\"\"ContentType is cached for child model\"\"\"\n        child = PolymorphicFormSetChild(model=Model2A)\n        ct = child.content_type\n\n        assert ct.model_class() == Model2A\n        assert child.content_type is ct  # Verify caching\n\n    def test_extra_exclude_parameter(self):\n        \"\"\"extra_exclude adds to existing exclude list\"\"\"\n        child = PolymorphicFormSetChild(model=Model2A, exclude=[\"field1\"])\n        form_class = child.get_form(extra_exclude=[\"field2\"])\n        form = form_class()\n\n        assert \"field1\" not in form.fields\n        assert \"field2\" not in form.fields\n\n\nclass PolymorphicModelFormSetTest(TestCase):\n    \"\"\"Test polymorphic model formset functionality\"\"\"\n\n    def setUp(self):\n        self.obj_a = Model2A.objects.create(field1=\"A1\")\n        self.obj_b = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n\n    def test_empty_form_property_raises_error(self):\n        \"\"\"Accessing empty_form raises RuntimeError (use empty_forms instead)\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[PolymorphicFormSetChild(model=Model2A)],\n        )\n        formset = FormSet(queryset=Model2A.objects.none())\n\n        with pytest.raises(RuntimeError, match=\"use 'empty_forms'\"):\n            _ = formset.empty_form\n\n    def test_error_no_child_forms(self):\n        \"\"\"get_form_class raises ImproperlyConfigured when child_forms empty\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[PolymorphicFormSetChild(model=Model2A)],\n        )\n        formset = FormSet(queryset=Model2A.objects.none())\n        formset.child_forms = {}\n\n        with pytest.raises(ImproperlyConfigured, match=\"No 'child_forms' defined\"):\n            formset.get_form_class(Model2A)\n\n    def test_error_non_polymorphic_model(self):\n        \"\"\"get_form_class raises TypeError for non-polymorphic models\"\"\"\n        from django.contrib.auth.models import User\n\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[PolymorphicFormSetChild(model=Model2A)],\n        )\n        formset = FormSet(queryset=Model2A.objects.none())\n\n        with pytest.raises(TypeError, match=\"Expect polymorphic model\"):\n            formset.get_form_class(User)\n\n    def test_error_unsupported_child_type(self):\n        \"\"\"get_form_class raises UnsupportedChildType for unregistered models\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[PolymorphicFormSetChild(model=Model2A)],\n        )\n        formset = FormSet(queryset=Model2A.objects.none())\n\n        with pytest.raises(UnsupportedChildType, match=\"no form class is registered\"):\n            formset.get_form_class(Model2B)\n\n    def test_bound_formset_with_data(self):\n        \"\"\"Bound formset processes existing objects correctly\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n                PolymorphicFormSetChild(model=Model2B),\n            ],\n        )\n\n        ct_a = ContentType.objects.get_for_model(Model2A, for_concrete_model=False)\n        ct_b = ContentType.objects.get_for_model(Model2B, for_concrete_model=False)\n\n        data = {\n            \"form-TOTAL_FORMS\": \"2\",\n            \"form-INITIAL_FORMS\": \"2\",\n            \"form-MIN_NUM_FORMS\": \"0\",\n            \"form-MAX_NUM_FORMS\": \"1000\",\n            \"form-0-id\": str(self.obj_a.pk),\n            \"form-0-field1\": \"Modified A\",\n            \"form-0-polymorphic_ctype\": str(ct_a.pk),\n            \"form-1-id\": str(self.obj_b.pk),\n            \"form-1-field1\": \"Modified B\",\n            \"form-1-field2\": \"Modified B2\",\n            \"form-1-polymorphic_ctype\": str(ct_b.pk),\n        }\n\n        queryset = Model2A.objects.filter(pk__in=[self.obj_a.pk, self.obj_b.pk])\n        formset = FormSet(data=data, queryset=queryset)\n\n        assert formset.is_bound\n        assert formset.is_valid()\n        assert formset.forms[0].instance.pk == self.obj_a.pk\n        assert formset.forms[1].instance.pk == self.obj_b.pk\n\n    def test_extra_forms_cycle_child_types(self):\n        \"\"\"Extra forms cycle through registered child types\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            extra=3,\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n                PolymorphicFormSetChild(model=Model2B),\n            ],\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n\n        # Forms cycle: A, B, A\n        assert \"field2\" not in formset.forms[0].fields  # Model2A\n        assert \"field2\" in formset.forms[1].fields  # Model2B\n        assert \"field2\" not in formset.forms[2].fields  # Model2A\n\n    def test_validation_error_missing_ctype(self):\n        \"\"\"ValidationError raised when polymorphic_ctype missing in bound data\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[PolymorphicFormSetChild(model=Model2A)],\n        )\n\n        data = {\n            \"form-TOTAL_FORMS\": \"1\",\n            \"form-INITIAL_FORMS\": \"0\",\n            \"form-MIN_NUM_FORMS\": \"0\",\n            \"form-MAX_NUM_FORMS\": \"1000\",\n            \"form-0-field1\": \"Test\",\n        }\n\n        formset = FormSet(data=data, queryset=Model2A.objects.none())\n\n        with pytest.raises(ValidationError, match=\"has no 'polymorphic_ctype'\"):\n            _ = formset.forms\n\n    def test_unsupported_child_in_bound_data(self):\n        \"\"\"UnsupportedChildType when bound data has unregistered child type\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[PolymorphicFormSetChild(model=Model2A)],\n        )\n\n        ct_b = ContentType.objects.get_for_model(Model2B, for_concrete_model=False)\n        data = {\n            \"form-TOTAL_FORMS\": \"1\",\n            \"form-INITIAL_FORMS\": \"0\",\n            \"form-MIN_NUM_FORMS\": \"0\",\n            \"form-MAX_NUM_FORMS\": \"1000\",\n            \"form-0-field1\": \"Test\",\n            \"form-0-polymorphic_ctype\": str(ct_b.pk),\n        }\n\n        formset = FormSet(data=data, queryset=Model2A.objects.none())\n\n        with pytest.raises(UnsupportedChildType, match=\"is not part of the formset\"):\n            _ = formset.forms\n\n    def test_unbound_with_ctype_in_initial(self):\n        \"\"\"Unbound formset with polymorphic_ctype in initial creates correct form\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            extra=1,\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n                PolymorphicFormSetChild(model=Model2B),\n            ],\n        )\n\n        ct_b = ContentType.objects.get_for_model(Model2B, for_concrete_model=False)\n        initial = [{\"polymorphic_ctype\": ct_b}]\n        formset = FormSet(queryset=Model2A.objects.none(), initial=initial)\n\n        # Form should be for Model2B\n        assert \"field2\" in formset.forms[0].fields\n\n    def test_child_form_kwargs(self):\n        \"\"\"child_form_kwargs passed to child form factory\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[PolymorphicFormSetChild(model=Model2A)],\n            child_form_kwargs={\"extra_exclude\": [\"field1\"]},\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n        assert \"field1\" not in formset.forms[0].fields\n\n    def test_is_multipart_with_file_field(self):\n        \"\"\"is_multipart returns True when form has FileField\"\"\"\n\n        class FileForm(forms.ModelForm):\n            file = forms.FileField()\n\n            class Meta:\n                model = Model2A\n                fields = [\"field1\"]\n\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=[\"field1\"],\n            formset_children=[PolymorphicFormSetChild(model=Model2A, form=FileForm)],\n            extra=1,\n        )\n        formset = FormSet(queryset=Model2A.objects.none())\n\n        assert formset.is_multipart()\n\n    def test_media_aggregation(self):\n        \"\"\"media property aggregates all child form media\"\"\"\n\n        class MediaForm(forms.ModelForm):\n            class Media:\n                js = (\"test.js\",)\n\n            class Meta:\n                model = Model2A\n                fields = [\"field1\"]\n\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=[\"field1\"],\n            formset_children=[PolymorphicFormSetChild(model=Model2A, form=MediaForm)],\n            extra=1,\n        )\n        formset = FormSet(queryset=Model2A.objects.none())\n\n        assert \"test.js\" in str(formset.media)\n\n\nclass PolymorphicInlineFormSetTest(TestCase):\n    \"\"\"Test polymorphic inline formsets\"\"\"\n\n    def test_inline_formset_factory(self):\n        \"\"\"Inline formset factory creates functional formsets\"\"\"\n        InlineFormSet = polymorphic_inlineformset_factory(\n            parent_model=Model2A,\n            model=Model2B,\n            fields=\"__all__\",\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2B),\n                PolymorphicFormSetChild(model=Model2C),\n            ],\n        )\n\n        parent = Model2A.objects.create(field1=\"Parent\")\n        formset = InlineFormSet(instance=parent)\n\n        assert formset.instance == parent\n        assert len(formset.forms) > 0\n\n    def test_inline_with_child_form_kwargs(self):\n        \"\"\"Inline formset passes child_form_kwargs to children\"\"\"\n        InlineFormSet = polymorphic_inlineformset_factory(\n            parent_model=Model2A,\n            model=Model2B,\n            fields=\"__all__\",\n            formset_children=[PolymorphicFormSetChild(model=Model2B)],\n            child_form_kwargs={\"extra_exclude\": [\"field1\"]},\n        )\n\n        parent = Model2A.objects.create(field1=\"Parent\")\n        formset = InlineFormSet(instance=parent)\n\n        assert \"field1\" not in formset.forms[0].fields\n\n\nclass GenericPolymorphicFormSetChildTest(TestCase):\n    \"\"\"Test GenericPolymorphicFormSetChild configuration\"\"\"\n\n    def test_content_type_property(self):\n        \"\"\"ContentType is cached for generic child model\"\"\"\n        child = GenericPolymorphicFormSetChild(model=PolymorphicTagA)\n        ct = child.content_type\n\n        assert ct.model_class() == PolymorphicTagA\n        assert child.content_type is ct  # Verify caching\n\n    def test_ct_field_fk_field_defaults(self):\n        \"\"\"Default ct_field and fk_field are 'content_type' and 'object_id'\"\"\"\n        child = GenericPolymorphicFormSetChild(model=PolymorphicTagA)\n        assert child.ct_field == \"content_type\"\n        assert child.fk_field == \"object_id\"\n\n    def test_custom_ct_field_fk_field(self):\n        \"\"\"Custom ct_field and fk_field can be specified\"\"\"\n        child = GenericPolymorphicFormSetChild(\n            model=PolymorphicTagA, ct_field=\"content_type\", fk_field=\"object_id\"\n        )\n        assert child.ct_field == \"content_type\"\n        assert child.fk_field == \"object_id\"\n\n    def test_get_form_excludes_gfk_fields(self):\n        \"\"\"get_form automatically excludes the GFK ct_field and fk_field\"\"\"\n        child = GenericPolymorphicFormSetChild(model=PolymorphicTagA)\n        form_class = child.get_form()\n        form = form_class()\n\n        # The GFK fields should be excluded\n        assert \"content_type\" not in form.fields\n        assert \"object_id\" not in form.fields\n        # But model-specific fields should be present\n        assert \"tag\" in form.fields\n        assert \"priority\" in form.fields\n\n    def test_get_form_with_extra_exclude(self):\n        \"\"\"get_form with extra_exclude adds to existing excludes\"\"\"\n        child = GenericPolymorphicFormSetChild(model=PolymorphicTagA, exclude=[\"tag\"])\n        form_class = child.get_form(extra_exclude=[\"priority\"])\n        form = form_class()\n\n        assert \"tag\" not in form.fields\n        assert \"priority\" not in form.fields\n        # GFK fields also excluded\n        assert \"content_type\" not in form.fields\n        assert \"object_id\" not in form.fields\n\n    def test_get_form_invalid_ct_field_raises(self):\n        \"\"\"get_form raises Exception if ct_field is not a ForeignKey to ContentType\"\"\"\n        child = GenericPolymorphicFormSetChild(\n            model=PolymorphicTagA, ct_field=\"tag\", fk_field=\"object_id\"\n        )\n\n        with pytest.raises(Exception, match=\"is not a ForeignKey to ContentType\"):\n            child.get_form()\n\n\nclass GenericPolymorphicInlineFormSetTest(TestCase):\n    \"\"\"Test generic polymorphic inline formsets\"\"\"\n\n    def setUp(self):\n        self.parent = GenericFKParent.objects.create(name=\"Test Parent\")\n        self.parent_ct = ContentType.objects.get_for_model(GenericFKParent)\n\n    def test_factory_creates_functional_formset(self):\n        \"\"\"generic_polymorphic_inlineformset_factory creates functional formsets\"\"\"\n        FormSet = generic_polymorphic_inlineformset_factory(\n            model=PolymorphicTagBase,\n            formset_children=[\n                GenericPolymorphicFormSetChild(model=PolymorphicTagA),\n                GenericPolymorphicFormSetChild(model=PolymorphicTagB),\n            ],\n        )\n\n        formset = FormSet(instance=self.parent)\n\n        assert formset.instance == self.parent\n        assert len(formset.forms) > 0\n\n    def test_formset_gfk_fields_excluded(self):\n        \"\"\"GFK fields are excluded from forms in the formset\"\"\"\n        FormSet = generic_polymorphic_inlineformset_factory(\n            model=PolymorphicTagBase,\n            formset_children=[\n                GenericPolymorphicFormSetChild(model=PolymorphicTagA),\n            ],\n        )\n\n        formset = FormSet(instance=self.parent)\n\n        # Check all forms have GFK fields excluded\n        for form in formset.forms:\n            assert \"content_type\" not in form.fields\n            assert \"object_id\" not in form.fields\n\n    def test_extra_forms_cycle_child_types(self):\n        \"\"\"Extra forms cycle through registered child types\"\"\"\n        FormSet = generic_polymorphic_inlineformset_factory(\n            model=PolymorphicTagBase,\n            extra=3,\n            formset_children=[\n                GenericPolymorphicFormSetChild(model=PolymorphicTagA),\n                GenericPolymorphicFormSetChild(model=PolymorphicTagB),\n            ],\n        )\n\n        formset = FormSet(instance=self.parent)\n\n        # Forms cycle: A, B, A\n        assert \"priority\" in formset.forms[0].fields  # PolymorphicTagA\n        assert \"color\" in formset.forms[1].fields  # PolymorphicTagB\n        assert \"priority\" in formset.forms[2].fields  # PolymorphicTagA\n\n    def test_bound_formset_with_existing_objects(self):\n        \"\"\"Bound formset processes existing polymorphic objects correctly\"\"\"\n        tag_a = PolymorphicTagA.objects.create(\n            tag=\"tag-a\",\n            content_type=self.parent_ct,\n            object_id=self.parent.pk,\n            priority=5,\n        )\n        tag_b = PolymorphicTagB.objects.create(\n            tag=\"tag-b\",\n            content_type=self.parent_ct,\n            object_id=self.parent.pk,\n            color=\"red\",\n        )\n\n        FormSet = generic_polymorphic_inlineformset_factory(\n            model=PolymorphicTagBase,\n            formset_children=[\n                GenericPolymorphicFormSetChild(model=PolymorphicTagA),\n                GenericPolymorphicFormSetChild(model=PolymorphicTagB),\n            ],\n        )\n\n        ct_a = ContentType.objects.get_for_model(PolymorphicTagA, for_concrete_model=False)\n        ct_b = ContentType.objects.get_for_model(PolymorphicTagB, for_concrete_model=False)\n\n        data = {\n            \"tests-polymorphictagbase-content_type-object_id-TOTAL_FORMS\": \"2\",\n            \"tests-polymorphictagbase-content_type-object_id-INITIAL_FORMS\": \"2\",\n            \"tests-polymorphictagbase-content_type-object_id-MIN_NUM_FORMS\": \"0\",\n            \"tests-polymorphictagbase-content_type-object_id-MAX_NUM_FORMS\": \"1000\",\n            \"tests-polymorphictagbase-content_type-object_id-0-id\": str(tag_a.pk),\n            \"tests-polymorphictagbase-content_type-object_id-0-tag\": \"tag-a-modified\",\n            \"tests-polymorphictagbase-content_type-object_id-0-priority\": \"10\",\n            \"tests-polymorphictagbase-content_type-object_id-0-polymorphic_ctype\": str(ct_a.pk),\n            \"tests-polymorphictagbase-content_type-object_id-1-id\": str(tag_b.pk),\n            \"tests-polymorphictagbase-content_type-object_id-1-tag\": \"tag-b-modified\",\n            \"tests-polymorphictagbase-content_type-object_id-1-color\": \"blue\",\n            \"tests-polymorphictagbase-content_type-object_id-1-polymorphic_ctype\": str(ct_b.pk),\n        }\n\n        formset = FormSet(data=data, instance=self.parent)\n\n        assert formset.is_bound\n        assert formset.is_valid(), formset.errors\n        assert formset.forms[0].instance.pk == tag_a.pk\n        assert formset.forms[1].instance.pk == tag_b.pk\n\n    def test_formset_with_child_form_kwargs(self):\n        \"\"\"child_form_kwargs passed to child form factory\"\"\"\n        FormSet = generic_polymorphic_inlineformset_factory(\n            model=PolymorphicTagBase,\n            formset_children=[\n                GenericPolymorphicFormSetChild(model=PolymorphicTagA),\n            ],\n            child_form_kwargs={\"extra_exclude\": [\"tag\"]},\n        )\n\n        formset = FormSet(instance=self.parent)\n        assert \"tag\" not in formset.forms[0].fields\n\n    def test_empty_forms_property(self):\n        \"\"\"empty_forms returns all possible empty form types\"\"\"\n        FormSet = generic_polymorphic_inlineformset_factory(\n            model=PolymorphicTagBase,\n            formset_children=[\n                GenericPolymorphicFormSetChild(model=PolymorphicTagA),\n                GenericPolymorphicFormSetChild(model=PolymorphicTagB),\n            ],\n        )\n\n        formset = FormSet(instance=self.parent)\n        empty_forms = formset.empty_forms\n\n        assert len(empty_forms) == 2\n        # Check that different form types are represented\n        form_models = {form._meta.model for form in empty_forms}\n        assert PolymorphicTagA in form_models\n        assert PolymorphicTagB in form_models\n\n    def test_empty_form_raises_runtime_error(self):\n        \"\"\"Accessing empty_form raises RuntimeError (use empty_forms instead)\"\"\"\n        FormSet = generic_polymorphic_inlineformset_factory(\n            model=PolymorphicTagBase,\n            formset_children=[\n                GenericPolymorphicFormSetChild(model=PolymorphicTagA),\n            ],\n        )\n\n        formset = FormSet(instance=self.parent)\n\n        with pytest.raises(RuntimeError, match=\"use 'empty_forms'\"):\n            _ = formset.empty_form\n\n    def test_unsupported_child_type_in_bound_data(self):\n        \"\"\"UnsupportedChildType when bound data has unregistered child type\"\"\"\n        FormSet = generic_polymorphic_inlineformset_factory(\n            model=PolymorphicTagBase,\n            formset_children=[\n                GenericPolymorphicFormSetChild(model=PolymorphicTagA),\n            ],\n        )\n\n        ct_b = ContentType.objects.get_for_model(PolymorphicTagB, for_concrete_model=False)\n        data = {\n            \"tests-polymorphictagbase-content_type-object_id-TOTAL_FORMS\": \"1\",\n            \"tests-polymorphictagbase-content_type-object_id-INITIAL_FORMS\": \"0\",\n            \"tests-polymorphictagbase-content_type-object_id-MIN_NUM_FORMS\": \"0\",\n            \"tests-polymorphictagbase-content_type-object_id-MAX_NUM_FORMS\": \"1000\",\n            \"tests-polymorphictagbase-content_type-object_id-0-tag\": \"test\",\n            \"tests-polymorphictagbase-content_type-object_id-0-polymorphic_ctype\": str(ct_b.pk),\n        }\n\n        formset = FormSet(data=data, instance=self.parent)\n\n        with pytest.raises(UnsupportedChildType, match=\"is not part of the formset\"):\n            _ = formset.forms\n\n    def test_validation_error_missing_ctype(self):\n        \"\"\"ValidationError raised when polymorphic_ctype missing in bound data\"\"\"\n        FormSet = generic_polymorphic_inlineformset_factory(\n            model=PolymorphicTagBase,\n            formset_children=[\n                GenericPolymorphicFormSetChild(model=PolymorphicTagA),\n            ],\n        )\n\n        data = {\n            \"tests-polymorphictagbase-content_type-object_id-TOTAL_FORMS\": \"1\",\n            \"tests-polymorphictagbase-content_type-object_id-INITIAL_FORMS\": \"0\",\n            \"tests-polymorphictagbase-content_type-object_id-MIN_NUM_FORMS\": \"0\",\n            \"tests-polymorphictagbase-content_type-object_id-MAX_NUM_FORMS\": \"1000\",\n            \"tests-polymorphictagbase-content_type-object_id-0-tag\": \"test\",\n        }\n\n        formset = FormSet(data=data, instance=self.parent)\n\n        with pytest.raises(ValidationError, match=\"has no 'polymorphic_ctype'\"):\n            _ = formset.forms\n\n    def test_is_multipart_with_file_field(self):\n        \"\"\"is_multipart returns True when form has FileField\"\"\"\n\n        class FileForm(forms.ModelForm):\n            file = forms.FileField()\n\n            class Meta:\n                model = PolymorphicTagA\n                fields = [\"tag\", \"priority\"]\n\n        FormSet = generic_polymorphic_inlineformset_factory(\n            model=PolymorphicTagBase,\n            formset_children=[\n                GenericPolymorphicFormSetChild(model=PolymorphicTagA, form=FileForm),\n            ],\n            extra=1,\n        )\n\n        formset = FormSet(instance=self.parent)\n        assert formset.is_multipart()\n\n    def test_media_aggregation(self):\n        \"\"\"media property aggregates all child form media\"\"\"\n\n        class MediaForm(forms.ModelForm):\n            class Media:\n                js = (\"generic_test.js\",)\n\n            class Meta:\n                model = PolymorphicTagA\n                fields = [\"tag\", \"priority\"]\n\n        FormSet = generic_polymorphic_inlineformset_factory(\n            model=PolymorphicTagBase,\n            formset_children=[\n                GenericPolymorphicFormSetChild(model=PolymorphicTagA, form=MediaForm),\n            ],\n            extra=1,\n        )\n\n        formset = FormSet(instance=self.parent)\n        assert \"generic_test.js\" in str(formset.media)\n\n    def test_save_new_objects(self):\n        \"\"\"Formset can save new polymorphic objects via generic relation\"\"\"\n        FormSet = generic_polymorphic_inlineformset_factory(\n            model=PolymorphicTagBase,\n            formset_children=[\n                GenericPolymorphicFormSetChild(model=PolymorphicTagA),\n                GenericPolymorphicFormSetChild(model=PolymorphicTagB),\n            ],\n        )\n\n        ct_a = ContentType.objects.get_for_model(PolymorphicTagA, for_concrete_model=False)\n\n        data = {\n            \"tests-polymorphictagbase-content_type-object_id-TOTAL_FORMS\": \"1\",\n            \"tests-polymorphictagbase-content_type-object_id-INITIAL_FORMS\": \"0\",\n            \"tests-polymorphictagbase-content_type-object_id-MIN_NUM_FORMS\": \"0\",\n            \"tests-polymorphictagbase-content_type-object_id-MAX_NUM_FORMS\": \"1000\",\n            \"tests-polymorphictagbase-content_type-object_id-0-tag\": \"new-tag\",\n            \"tests-polymorphictagbase-content_type-object_id-0-priority\": \"99\",\n            \"tests-polymorphictagbase-content_type-object_id-0-polymorphic_ctype\": str(ct_a.pk),\n        }\n\n        formset = FormSet(data=data, instance=self.parent)\n        assert formset.is_valid(), formset.errors\n\n        saved_objects = formset.save()\n        assert len(saved_objects) == 1\n        assert isinstance(saved_objects[0], PolymorphicTagA)\n        assert saved_objects[0].tag == \"new-tag\"\n        assert saved_objects[0].priority == 99\n        assert saved_objects[0].content_object == self.parent\n"
  },
  {
    "path": "src/polymorphic/tests/test_inheritance.py",
    "content": "from polymorphic.tests.models import Foo, Bar, Baz\nfrom polymorphic.managers import PolymorphicManager\nfrom django.test import TestCase\n\n\nclass InheritanceTests(TestCase):\n    def test_mixin_inherited_managers(self):\n        self.assertIsInstance(Foo._base_manager, PolymorphicManager)\n        self.assertIsInstance(Bar._base_manager, PolymorphicManager)\n        self.assertIsInstance(Baz._base_manager, PolymorphicManager)\n"
  },
  {
    "path": "src/polymorphic/tests/test_migration_managers.py",
    "content": "from django.test import TestCase\n\nfrom django_test_migrations.contrib.unittest_case import MigratorTestCase\nfrom django_test_migrations.migrator import Migrator\n\nfrom polymorphic.managers import PolymorphicManager\n\n\nclass TestRelatedManagersInMigrationState(MigratorTestCase):\n    \"\"\"\n    Test that only non-polymorphic managers are used in migrations.\n    \"\"\"\n\n    app = \"tests\"\n    migrate_from = (app, None)\n    migrate_to = (app, \"0001_initial\")\n\n    def test_migration_managers_are_nonpoly(self):\n        migrator = Migrator(database=\"default\")\n\n        # Apply migrations up to the state you care about and get the historical apps registry.\n        state = migrator.apply_initial_migration(self.migrate_to)\n        apps = state.apps\n\n        ChildModelWithManager = apps.get_model(\"tests\", \"ChildModelWithManager\")\n        ParentModelWithManager = apps.get_model(\"tests\", \"ParentModelWithManager\")\n        pm = ParentModelWithManager.objects.create()\n        ChildModelWithManager.objects.create(field1=\"test\", fk=pm)\n\n        assert not isinstance(ParentModelWithManager.objects, PolymorphicManager)\n        assert not isinstance(ChildModelWithManager.objects, PolymorphicManager)\n        assert not isinstance(ChildModelWithManager._meta.base_manager, PolymorphicManager)\n        assert not isinstance(ParentModelWithManager._meta.base_manager, PolymorphicManager)\n        assert not isinstance(pm.childmodel_set, PolymorphicManager)\n\n        One2OneRelatingModel = apps.get_model(\"tests\", \"One2OneRelatingModel\")\n        One2OneRelatingModelDerived = apps.get_model(\"tests\", \"One2OneRelatingModelDerived\")\n        Model2A = apps.get_model(\"tests\", \"Model2A\")\n        Model2BFiltered = apps.get_model(\"tests\", \"Model2BFiltered\")\n        Model2CFiltered = apps.get_model(\"tests\", \"Model2CFiltered\")\n        Model2CNamedManagers = apps.get_model(\"tests\", \"Model2CNamedManagers\")\n        Model2CNamedDefault = apps.get_model(\"tests\", \"Model2CNamedDefault\")\n        ManagerTest = apps.get_model(\"tests\", \"ManagerTest\")\n        ManagerTestChild = apps.get_model(\"tests\", \"ManagerTestChild\")\n        RelatingModel = apps.get_model(\"tests\", \"RelatingModel\")\n\n        assert not isinstance(Model2BFiltered._meta.base_manager, PolymorphicManager)\n        assert not isinstance(Model2CFiltered._meta.base_manager, PolymorphicManager)\n        assert not isinstance(Model2CNamedManagers._meta.base_manager, PolymorphicManager)\n        assert not isinstance(Model2CNamedDefault._meta.base_manager, PolymorphicManager)\n        assert not isinstance(Model2BFiltered._meta.default_manager, PolymorphicManager)\n        assert not isinstance(Model2CFiltered._meta.default_manager, PolymorphicManager)\n        assert not isinstance(Model2CNamedManagers._meta.default_manager, PolymorphicManager)\n        assert not isinstance(Model2CNamedDefault._meta.default_manager, PolymorphicManager)\n        assert not isinstance(Model2BFiltered.objects, PolymorphicManager)\n        assert not isinstance(Model2CFiltered.objects, PolymorphicManager)\n        assert not isinstance(Model2CNamedManagers.objects, PolymorphicManager)\n        assert not isinstance(Model2CNamedDefault.objects, PolymorphicManager)\n\n        assert not isinstance(ManagerTest._meta.base_manager, PolymorphicManager)\n        assert not isinstance(ManagerTest._meta.default_manager, PolymorphicManager)\n        assert not isinstance(ManagerTestChild._meta.base_manager, PolymorphicManager)\n        assert not isinstance(ManagerTestChild._meta.default_manager, PolymorphicManager)\n\n        b2 = Model2BFiltered.objects.create(field1=\"testB1\", field2=\"testB2\")\n        b2_2 = Model2BFiltered.objects.create(field1=\"testB12\", field2=\"testB22\")\n        o2o1 = One2OneRelatingModel.objects.create(field1=\"relating1\", one2one=b2)\n        o2o2 = One2OneRelatingModelDerived.objects.create(\n            field1=\"relating2\", field2=\"relating2\", one2one=b2_2\n        )\n\n        o2o1.refresh_from_db()\n        o2o2.refresh_from_db()\n\n        assert o2o1.one2one.__class__ is Model2A\n        assert o2o2.one2one.__class__ is Model2A\n\n        a2 = Model2A.objects.create(field1=\"testA1\")\n\n        rel = RelatingModel.objects.create()\n        assert not isinstance(rel.many2many, PolymorphicManager)\n        rel.many2many.add(b2)\n        rel.many2many.add(b2_2)\n        rel.many2many.add(a2)\n\n        rel.refresh_from_db()\n        assert set(rel.many2many.all()) == {\n            Model2A.objects.get(pk=b2.pk),\n            Model2A.objects.get(pk=b2_2.pk),\n            a2,\n        }\n"
  },
  {
    "path": "src/polymorphic/tests/test_migrations/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/test_migrations/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "src/polymorphic/tests/test_migrations/models.py",
    "content": "from django.db import models\nfrom polymorphic.models import PolymorphicModel\n\n\ndef get_default_related():\n    \"\"\"Default function for SET() callable\"\"\"\n    return None\n\n\nclass RelatedModel(models.Model):\n    \"\"\"A regular non-polymorphic model that will be referenced\"\"\"\n\n    name = models.CharField(max_length=100)\n\n\nclass BasePolyModel(PolymorphicModel):\n    \"\"\"\n    Base polymorphic model to test that PolymorphicGuard wraps\n    on_delete handlers properly and serializes them correctly.\n    \"\"\"\n\n    name = models.CharField(max_length=100)\n\n\nclass ChildPolyModel(BasePolyModel):\n    \"\"\"Child polymorphic model\"\"\"\n\n    description = models.CharField(max_length=200, blank=True)\n\n\nclass GrandChildPolyModel(ChildPolyModel):\n    \"\"\"Grandchild polymorphic model\"\"\"\n\n    extra_info = models.CharField(max_length=200, blank=True)\n\n\n# Models with ForeignKey using different on_delete behaviors\n# These should all be wrapped with PolymorphicGuard automatically\n\n\nclass ModelWithCascade(PolymorphicModel):\n    \"\"\"Test CASCADE on_delete\"\"\"\n\n    related = models.ForeignKey(RelatedModel, on_delete=models.CASCADE)\n\n\nclass ModelWithProtect(PolymorphicModel):\n    \"\"\"Test PROTECT on_delete\"\"\"\n\n    related = models.ForeignKey(RelatedModel, on_delete=models.PROTECT)\n\n\nclass ModelWithSetNull(PolymorphicModel):\n    \"\"\"Test SET_NULL on_delete\"\"\"\n\n    related = models.ForeignKey(RelatedModel, on_delete=models.SET_NULL, null=True)\n\n\nclass ModelWithSetDefault(PolymorphicModel):\n    \"\"\"Test SET_DEFAULT on_delete\"\"\"\n\n    related = models.ForeignKey(\n        RelatedModel, on_delete=models.SET_DEFAULT, null=True, default=None\n    )\n\n\nclass ModelWithSet(PolymorphicModel):\n    \"\"\"Test SET(...) on_delete\"\"\"\n\n    related = models.ForeignKey(RelatedModel, on_delete=models.SET(get_default_related), null=True)\n\n\nclass ModelWithDoNothing(PolymorphicModel):\n    \"\"\"Test DO_NOTHING on_delete\"\"\"\n\n    related = models.ForeignKey(RelatedModel, on_delete=models.DO_NOTHING)\n\n\nclass ModelWithRestrict(PolymorphicModel):\n    \"\"\"Test RESTRICT on_delete\"\"\"\n\n    related = models.ForeignKey(RelatedModel, on_delete=models.RESTRICT)\n\n\n# OneToOneField tests\n\n\nclass ModelWithOneToOneCascade(PolymorphicModel):\n    \"\"\"Test CASCADE on_delete with OneToOneField\"\"\"\n\n    related = models.OneToOneField(RelatedModel, on_delete=models.CASCADE)\n\n\nclass ModelWithOneToOneProtect(PolymorphicModel):\n    \"\"\"Test PROTECT on_delete with OneToOneField\"\"\"\n\n    related = models.OneToOneField(\n        RelatedModel, on_delete=models.PROTECT, related_name=\"one_to_one_protect\"\n    )\n\n\nclass ModelWithOneToOneSetNull(PolymorphicModel):\n    \"\"\"Test SET_NULL on_delete with OneToOneField\"\"\"\n\n    related = models.OneToOneField(\n        RelatedModel, on_delete=models.SET_NULL, null=True, related_name=\"one_to_one_set_null\"\n    )\n"
  },
  {
    "path": "src/polymorphic/tests/test_migrations/test_on_delete.py",
    "content": "\"\"\"\nTests for PolymorphicGuard serialization of on_delete functions.\n\nThis test module ensures that all Django on_delete handlers (CASCADE, PROTECT,\nSET_NULL, SET_DEFAULT, SET(...), DO_NOTHING, and RESTRICT) are properly wrapped\nwith PolymorphicGuard and serialize correctly in migrations.\n\"\"\"\n\nfrom django.core.management import call_command\nfrom pathlib import Path\nfrom django.test import TestCase, TransactionTestCase\nfrom django.db import models\nfrom django.db.migrations.serializer import serializer_factory\nfrom django.db.models import ProtectedError, RestrictedError\nfrom ..utils import GeneratedMigrationsPerClassMixin\nfrom polymorphic.deletion import PolymorphicGuard\nfrom polymorphic.managers import PolymorphicManager\nfrom polymorphic.query import PolymorphicQuerySet\n\n\nclass OnDeleteSerializationTest(GeneratedMigrationsPerClassMixin, TransactionTestCase):\n    \"\"\"\n    Test that PolymorphicGuard wraps on_delete handlers and serializes them correctly.\n    \"\"\"\n\n    apps_to_migrate: list[str] = [\"test_migrations\"]\n\n    @property\n    def state(self):\n        return self._applied_states[\"test_migrations\"]\n\n    @classmethod\n    def setUpClass(cls):\n        \"\"\"Set up by generating and applying migrations for test_migrations app\"\"\"\n        super().setUpClass()\n        cls.migrations_dir = Path(__file__).parent / \"migrations\"\n\n    def test_migration_managers_non_polymorphic(self):\n        for mdl in [\n            \"BasePolyModel\",\n            \"ChildPolyModel\",\n            \"GrandChildPolyModel\",\n            \"ModelWithCascade\",\n            \"ModelWithProtect\",\n            \"ModelWithSetNull\",\n            \"ModelWithSetDefault\",\n            \"ModelWithSet\",\n            \"ModelWithDoNothing\",\n            \"ModelWithRestrict\",\n            \"ModelWithOneToOneCascade\",\n            \"ModelWithOneToOneProtect\",\n            \"ModelWithOneToOneSetNull\",\n        ]:\n            Model = self.state.apps.get_model(\"test_migrations\", mdl)\n\n        managers = Model._meta.managers\n        assert not isinstance(Model.objects, (PolymorphicManager, PolymorphicQuerySet))\n        assert all(not isinstance(m, (PolymorphicManager, PolymorphicQuerySet)) for m in managers)\n\n        RelatedModel = self.state.apps.get_model(\"test_migrations\", \"RelatedModel\")\n        related = RelatedModel.objects.create(name=\"tester\")\n        ModelWithOneToOneCascade = self.state.apps.get_model(\n            \"test_migrations\", \"ModelWithOneToOneCascade\"\n        )\n        ModelWithOneToOneProtect = self.state.apps.get_model(\n            \"test_migrations\", \"ModelWithOneToOneProtect\"\n        )\n        ModelWithOneToOneSetNull = self.state.apps.get_model(\n            \"test_migrations\", \"ModelWithOneToOneSetNull\"\n        )\n\n        ModelWithOneToOneCascade.objects.create(related=related)\n        ModelWithOneToOneProtect.objects.create(related=related)\n        ModelWithOneToOneSetNull.objects.create(related=related)\n\n        for relation in [\n            \"modelwithcascade_set\",\n            \"modelwithprotect_set\",\n            \"modelwithsetnull_set\",\n            \"modelwithsetdefault_set\",\n            \"modelwithset_set\",\n            \"modelwithdonothing_set\",\n            \"modelwithrestrict_set\",\n            \"modelwithonetoonecascade\",\n            \"one_to_one_protect\",\n            \"one_to_one_set_null\",\n        ]:\n            assert not isinstance(\n                getattr(related, relation), (PolymorphicManager, PolymorphicQuerySet)\n            )\n\n    def test_foreign_keys_wrapped_with_PolymorphicGuard(self):\n        \"\"\"Verify that ForeignKey on_delete handlers are wrapped with PolymorphicGuard\"\"\"\n        from .models import (\n            ModelWithCascade,\n            ModelWithProtect,\n            ModelWithSetNull,\n            ModelWithSetDefault,\n            ModelWithSet,\n            ModelWithDoNothing,\n            ModelWithRestrict,\n        )\n\n        # Get the 'related' field from each model\n        models_to_test = [\n            ModelWithCascade,\n            ModelWithProtect,\n            ModelWithSetNull,\n            ModelWithSetDefault,\n            ModelWithSet,\n            ModelWithDoNothing,\n            ModelWithRestrict,\n        ]\n\n        for model_class in models_to_test:\n            with self.subTest(model=model_class.__name__):\n                field = model_class._meta.get_field(\"related\")\n                on_delete = field.remote_field.on_delete\n\n                # Assert that the on_delete handler is wrapped with PolymorphicGuard\n                self.assertIsInstance(\n                    on_delete,\n                    PolymorphicGuard,\n                    f\"{model_class.__name__}.related field should have PolymorphicGuard wrapper\",\n                )\n\n    def test_one_to_one_wrapped_with_PolymorphicGuard(self):\n        \"\"\"Verify that OneToOneField on_delete handlers are wrapped with PolymorphicGuard\"\"\"\n        from .models import (\n            ModelWithOneToOneCascade,\n            ModelWithOneToOneProtect,\n            ModelWithOneToOneSetNull,\n        )\n\n        models_to_test = [\n            ModelWithOneToOneCascade,\n            ModelWithOneToOneProtect,\n            ModelWithOneToOneSetNull,\n        ]\n\n        for model_class in models_to_test:\n            with self.subTest(model=model_class.__name__):\n                field = model_class._meta.get_field(\"related\")\n                on_delete = field.remote_field.on_delete\n\n                # Assert that the on_delete handler is wrapped with PolymorphicGuard\n                self.assertIsInstance(\n                    on_delete,\n                    PolymorphicGuard,\n                    f\"{model_class.__name__}.related field should have PolymorphicGuard wrapper\",\n                )\n\n    def test_cascade_serialization(self):\n        \"\"\"Test that CASCADE serializes correctly through PolymorphicGuard\"\"\"\n        from .models import ModelWithCascade\n\n        field = ModelWithCascade._meta.get_field(\"related\")\n        on_delete = field.remote_field.on_delete\n\n        # Serialize the PolymorphicGuard wrapped CASCADE\n        serialized, imports = serializer_factory(on_delete).serialize()\n\n        # Should serialize as CASCADE from django.db.models.deletion, not as PolymorphicGuard\n        self.assertIn(\"CASCADE\", serialized)\n        self.assertNotIn(\"PolymorphicGuard\", serialized)\n\n    def test_protect_serialization(self):\n        \"\"\"Test that PROTECT serializes correctly through PolymorphicGuard\"\"\"\n        from .models import ModelWithProtect\n\n        field = ModelWithProtect._meta.get_field(\"related\")\n        on_delete = field.remote_field.on_delete\n\n        serialized, imports = serializer_factory(on_delete).serialize()\n\n        self.assertIn(\"PROTECT\", serialized)\n        self.assertNotIn(\"PolymorphicGuard\", serialized)\n\n    def test_set_null_serialization(self):\n        \"\"\"Test that SET_NULL serializes correctly through PolymorphicGuard\"\"\"\n        from .models import ModelWithSetNull\n\n        field = ModelWithSetNull._meta.get_field(\"related\")\n        on_delete = field.remote_field.on_delete\n\n        serialized, imports = serializer_factory(on_delete).serialize()\n\n        self.assertIn(\"SET_NULL\", serialized)\n        self.assertNotIn(\"PolymorphicGuard\", serialized)\n\n    def test_set_default_serialization(self):\n        \"\"\"Test that SET_DEFAULT serializes correctly through PolymorphicGuard\"\"\"\n        from .models import ModelWithSetDefault\n\n        field = ModelWithSetDefault._meta.get_field(\"related\")\n        on_delete = field.remote_field.on_delete\n\n        serialized, imports = serializer_factory(on_delete).serialize()\n\n        self.assertIn(\"SET_DEFAULT\", serialized)\n        self.assertNotIn(\"PolymorphicGuard\", serialized)\n\n    def test_set_callable_serialization(self):\n        \"\"\"Test that SET(...) with a callable serializes correctly through PolymorphicGuard\"\"\"\n        from .models import ModelWithSet\n\n        field = ModelWithSet._meta.get_field(\"related\")\n        on_delete = field.remote_field.on_delete\n\n        serialized, imports = serializer_factory(on_delete).serialize()\n\n        # Should serialize the SET() function with the callable reference\n        self.assertIn(\"SET\", serialized)\n        self.assertIn(\"get_default_related\", serialized)\n        self.assertNotIn(\"PolymorphicGuard\", serialized)\n\n    def test_do_nothing_serialization(self):\n        \"\"\"Test that DO_NOTHING serializes correctly through PolymorphicGuard\"\"\"\n        from .models import ModelWithDoNothing\n\n        field = ModelWithDoNothing._meta.get_field(\"related\")\n        on_delete = field.remote_field.on_delete\n\n        serialized, imports = serializer_factory(on_delete).serialize()\n\n        self.assertIn(\"DO_NOTHING\", serialized)\n        self.assertNotIn(\"PolymorphicGuard\", serialized)\n\n    def test_restrict_serialization(self):\n        \"\"\"Test that RESTRICT serializes correctly through PolymorphicGuard\"\"\"\n        from .models import ModelWithRestrict\n\n        field = ModelWithRestrict._meta.get_field(\"related\")\n        on_delete = field.remote_field.on_delete\n\n        serialized, imports = serializer_factory(on_delete).serialize()\n\n        self.assertIn(\"RESTRICT\", serialized)\n        self.assertNotIn(\"PolymorphicGuard\", serialized)\n\n    def test_migration_file_generated(self):\n        \"\"\"Test that a migration file was generated\"\"\"\n        # Check that at least one migration file was created\n        migration_files = list(self.migrations_dir.glob(\"0001_*.py\"))\n        self.assertTrue(len(migration_files) > 0, \"No migration file was generated\")\n\n    def test_migration_file_content(self):\n        \"\"\"Test that the generated migration file contains correct serialization\"\"\"\n        # Find the initial migration file\n        migration_files = list(self.migrations_dir.glob(\"0001_*.py\"))\n        self.assertTrue(len(migration_files) > 0, \"No migration file found\")\n\n        migration_file = migration_files[0]\n        content = migration_file.read_text()\n\n        # Check that PolymorphicGuard is NOT in the migration file\n        self.assertNotIn(\n            \"PolymorphicGuard\", content, \"Migration file should not contain PolymorphicGuard\"\n        )\n\n        # Check that on_delete handlers are properly serialized\n        self.assertIn(\"django.db.models.deletion.CASCADE\", content)\n        self.assertIn(\"django.db.models.deletion.PROTECT\", content)\n        self.assertIn(\"django.db.models.deletion.SET_NULL\", content)\n        self.assertIn(\"django.db.models.deletion.SET_DEFAULT\", content)\n        self.assertIn(\"django.db.models.deletion.DO_NOTHING\", content)\n        self.assertIn(\"django.db.models.deletion.RESTRICT\", content)\n\n        # Check that SET() with callable is properly serialized\n        self.assertIn(\"models.SET\", content)\n        self.assertIn(\"get_default_related\", content)\n\n    def test_migration_serialization_stability(self):\n        \"\"\"\n        Test that the migration file contains stable serialization.\n        This ensures that PolymorphicGuard doesn't cause migration churn\n        by verifying the migration was generated successfully in setUpClass.\n        \"\"\"\n        # The fact that we have a migration file and it contains the right\n        # serialization (tested in test_migration_file_content) proves\n        # that the serialization is stable. If it wasn't stable, the\n        # migration file would either fail to generate or contain\n        # PolymorphicGuard references.\n        migration_files = list(self.migrations_dir.glob(\"0001_*.py\"))\n        self.assertEqual(len(migration_files), 1, \"Should have exactly one initial migration file\")\n\n    def test_PolymorphicGuard_unwraps_correctly(self):\n        \"\"\"Test that PolymorphicGuard properly unwraps to the underlying action\"\"\"\n        from .models import ModelWithCascade\n\n        field = ModelWithCascade._meta.get_field(\"related\")\n        on_delete = field.remote_field.on_delete\n\n        # Verify it's wrapped\n        self.assertIsInstance(on_delete, PolymorphicGuard)\n\n        # Verify the underlying action is CASCADE\n        self.assertEqual(on_delete.action, models.CASCADE)\n\n    def test_all_on_delete_types_covered(self):\n        \"\"\"\n        Meta-test to ensure we've covered all Django on_delete types.\n        This test documents which on_delete types we're testing.\n        \"\"\"\n        tested_types = {\n            \"CASCADE\": models.CASCADE,\n            \"PROTECT\": models.PROTECT,\n            \"SET_NULL\": models.SET_NULL,\n            \"SET_DEFAULT\": models.SET_DEFAULT,\n            \"SET\": models.SET,  # This is a callable that returns the actual handler\n            \"DO_NOTHING\": models.DO_NOTHING,\n            \"RESTRICT\": models.RESTRICT,\n        }\n\n        # Document that we have test models for each type\n        from .models import (\n            ModelWithCascade,\n            ModelWithProtect,\n            ModelWithSetNull,\n            ModelWithSetDefault,\n            ModelWithSet,\n            ModelWithDoNothing,\n            ModelWithRestrict,\n        )\n\n        model_mapping = {\n            \"CASCADE\": ModelWithCascade,\n            \"PROTECT\": ModelWithProtect,\n            \"SET_NULL\": ModelWithSetNull,\n            \"SET_DEFAULT\": ModelWithSetDefault,\n            \"SET\": ModelWithSet,\n            \"DO_NOTHING\": ModelWithDoNothing,\n            \"RESTRICT\": ModelWithRestrict,\n        }\n\n        # Verify we have a model for each on_delete type\n        for type_name, on_delete_handler in tested_types.items():\n            with self.subTest(type=type_name):\n                self.assertIn(type_name, model_mapping, f\"Missing test model for {type_name}\")\n                model_class = model_mapping[type_name]\n                field = model_class._meta.get_field(\"related\")\n\n                # Verify the field is properly configured\n                self.assertIsNotNone(field)\n                self.assertIsInstance(field.remote_field.on_delete, PolymorphicGuard)\n\n    def test_guard_equality_with_same_guard(self):\n        \"\"\"Test that PolymorphicGuard equals another PolymorphicGuard with the same action\"\"\"\n        guard1 = PolymorphicGuard(models.CASCADE)\n        guard2 = PolymorphicGuard(models.CASCADE)\n\n        self.assertEqual(guard1, guard2)\n        self.assertEqual(guard1.migration_key, guard2.migration_key)\n\n    def test_guard_equality_with_different_guard(self):\n        \"\"\"Test that PolymorphicGuard doesn't equal another with a different action\"\"\"\n        guard1 = PolymorphicGuard(models.CASCADE)\n        guard2 = PolymorphicGuard(models.PROTECT)\n\n        self.assertNotEqual(guard1, guard2)\n        self.assertNotEqual(guard1.migration_key, guard2.migration_key)\n\n    def test_guard_equality_with_non_serializable_object(self):\n        \"\"\"Test PolymorphicGuard equality when comparing to an object that cannot be serialized\"\"\"\n\n        class UnserializableCallable:\n            \"\"\"A callable that cannot be properly serialized by Django's migration system\"\"\"\n\n            def __call__(self, collector, field, sub_objs, using):\n                pass\n\n        guard = PolymorphicGuard(models.CASCADE)\n        non_serializable = UnserializableCallable()\n\n        result = guard == non_serializable\n        self.assertFalse(result)\n\n    def test_guard_equality_with_serialization_exception(self):\n        \"\"\"Test PolymorphicGuard equality with an object that causes an exception during fingerprinting\"\"\"\n\n        class ProblematicObject:\n            \"\"\"An object that breaks during serialization\"\"\"\n\n            def __repr__(self):\n                raise RuntimeError(\"Cannot serialize this object\")\n\n        guard = PolymorphicGuard(models.CASCADE)\n        problematic = ProblematicObject()\n\n        result = guard == problematic\n        self.assertFalse(result)\n\n\nclass PolymorphicInheritanceSerializationTest(TestCase):\n    \"\"\"\n    Test that PolymorphicGuard works correctly with polymorphic model inheritance.\n    \"\"\"\n\n    def test_polymorphic_inheritance_chain(self):\n        \"\"\"Test that polymorphic model inheritance works with all on_delete types\"\"\"\n        from .models import BasePolyModel, ChildPolyModel, GrandChildPolyModel\n\n        # Verify the inheritance chain is set up correctly\n        self.assertTrue(issubclass(ChildPolyModel, BasePolyModel))\n        self.assertTrue(issubclass(GrandChildPolyModel, ChildPolyModel))\n        self.assertTrue(issubclass(GrandChildPolyModel, BasePolyModel))\n\n        # Verify each model has the polymorphic_ctype field\n        for model_class in [BasePolyModel, ChildPolyModel, GrandChildPolyModel]:\n            with self.subTest(model=model_class.__name__):\n                ctype_field = model_class._meta.get_field(\"polymorphic_ctype\")\n                self.assertIsNotNone(ctype_field)\n                # The polymorphic_ctype field uses CASCADE which should also be wrapped\n                self.assertIsInstance(ctype_field.remote_field.on_delete, PolymorphicGuard)\n\n\nclass OnDeleteBehaviorTest(GeneratedMigrationsPerClassMixin, TransactionTestCase):\n    \"\"\"\n    Test that PolymorphicGuard correctly executes on_delete actions.\n\n    These tests verify the runtime behavior of each on_delete type when\n    wrapped with PolymorphicGuard by creating and deleting model instances.\n    \"\"\"\n\n    apps_to_migrate: list[str] = [\"test_migrations\"]\n\n    def test_cascade_deletes_related_objects(self):\n        \"\"\"Test that CASCADE deletes related polymorphic objects\"\"\"\n        from .models import RelatedModel, ModelWithCascade\n\n        # Create a related model and a polymorphic model that references it\n        related = RelatedModel.objects.create(name=\"test\")\n        cascade_obj = ModelWithCascade.objects.create(related=related)\n        cascade_obj_id = cascade_obj.id\n\n        # Verify the object exists\n        self.assertTrue(ModelWithCascade.objects.filter(id=cascade_obj_id).exists())\n\n        # Delete the related model\n        related.delete()\n\n        # Verify the cascade object was deleted\n        self.assertFalse(ModelWithCascade.objects.filter(id=cascade_obj_id).exists())\n\n    def test_protect_prevents_deletion(self):\n        \"\"\"Test that PROTECT prevents deletion of related objects\"\"\"\n        from .models import RelatedModel, ModelWithProtect\n\n        # Create a related model and a polymorphic model that references it\n        related = RelatedModel.objects.create(name=\"test\")\n        ModelWithProtect.objects.create(related=related)\n\n        # Attempting to delete the related model should raise ProtectedError\n        with self.assertRaises(ProtectedError):\n            related.delete()\n\n        # Verify both objects still exist\n        self.assertTrue(RelatedModel.objects.filter(id=related.id).exists())\n        self.assertTrue(ModelWithProtect.objects.filter(related=related).exists())\n\n    def test_set_null_sets_field_to_null(self):\n        \"\"\"Test that SET_NULL sets the foreign key to null\"\"\"\n        from .models import RelatedModel, ModelWithSetNull\n\n        # Create a related model and a polymorphic model that references it\n        related = RelatedModel.objects.create(name=\"test\")\n        set_null_obj = ModelWithSetNull.objects.create(related=related)\n        set_null_obj_id = set_null_obj.id\n\n        # Verify the relationship exists\n        self.assertEqual(set_null_obj.related, related)\n\n        # Delete the related model\n        related.delete()\n\n        # Verify the object still exists but the field is now null\n        set_null_obj = ModelWithSetNull.objects.get(id=set_null_obj_id)\n        self.assertIsNone(set_null_obj.related)\n\n    def test_set_default_sets_field_to_default(self):\n        \"\"\"Test that SET_DEFAULT sets the foreign key to its default value\"\"\"\n        from .models import RelatedModel, ModelWithSetDefault\n\n        # Create a related model and a polymorphic model that references it\n        related = RelatedModel.objects.create(name=\"test\")\n        set_default_obj = ModelWithSetDefault.objects.create(related=related)\n        set_default_obj_id = set_default_obj.id\n\n        # Verify the relationship exists\n        self.assertEqual(set_default_obj.related, related)\n\n        # Delete the related model\n        related.delete()\n\n        # Verify the object still exists but the field is now set to default (None)\n        set_default_obj = ModelWithSetDefault.objects.get(id=set_default_obj_id)\n        self.assertIsNone(set_default_obj.related)\n\n    def test_set_callable_uses_function(self):\n        \"\"\"Test that SET(...) calls the provided function\"\"\"\n        from .models import RelatedModel, ModelWithSet\n\n        # Create a related model and a polymorphic model that references it\n        related = RelatedModel.objects.create(name=\"test\")\n        set_obj = ModelWithSet.objects.create(related=related)\n        set_obj_id = set_obj.id\n\n        # Verify the relationship exists\n        self.assertEqual(set_obj.related, related)\n\n        # Delete the related model\n        related.delete()\n\n        # Verify the object s\n        set_obj = ModelWithSet.objects.get(id=set_obj_id)\n        self.assertIsNone(set_obj.related)\n\n    def test_do_nothing_behavior(self):\n        \"\"\"Test that DO_NOTHING doesn't prevent deletion or update related objects\"\"\"\n        from .models import RelatedModel, ModelWithDoNothing\n\n        # Create a related model and a polymorphic model that references it\n        related = RelatedModel.objects.create(name=\"test\")\n        do_nothing_obj = ModelWithDoNothing.objects.create(related=related)\n\n        # Verify the object is wrapped with PolymorphicGuard\n        field = ModelWithDoNothing._meta.get_field(\"related\")\n        self.assertIsInstance(field.remote_field.on_delete, PolymorphicGuard)\n\n        # Verify the underlying action is DO_NOTHING\n        self.assertEqual(field.remote_field.on_delete.action, models.DO_NOTHING)\n\n        # DO_NOTHING doesn't cascade delete or set null - it simply does nothing\n        # In practice, this means the deletion succeeds but leaves an orphaned reference\n        # However, database constraints may prevent this in production\n        # Here we just verify that the wrapper is correct and the object exists\n        self.assertTrue(ModelWithDoNothing.objects.filter(id=do_nothing_obj.id).exists())\n        self.assertEqual(do_nothing_obj.related, related)\n\n    def test_restrict_prevents_deletion_when_objects_exist(self):\n        \"\"\"Test that RESTRICT prevents deletion when related objects exist\"\"\"\n        from .models import RelatedModel, ModelWithRestrict\n\n        # Create a related model and a polymorphic model that references it\n        related = RelatedModel.objects.create(name=\"test\")\n        restrict_obj = ModelWithRestrict.objects.create(related=related)\n\n        # Attempting to delete the related model should raise RestrictedError\n        with self.assertRaises(RestrictedError):\n            related.delete()\n\n        # Verify both objects still exist\n        self.assertTrue(RelatedModel.objects.filter(id=related.id).exists())\n        self.assertTrue(ModelWithRestrict.objects.filter(id=restrict_obj.id).exists())\n\n    def test_cascade_with_polymorphic_inheritance(self):\n        \"\"\"Test CASCADE works correctly with polymorphic child models\"\"\"\n        from .models import RelatedModel, ModelWithCascade\n\n        # Create a related model\n        related = RelatedModel.objects.create(name=\"test\")\n\n        # Create multiple instances of the polymorphic model\n        obj1 = ModelWithCascade.objects.create(related=related)\n        obj2 = ModelWithCascade.objects.create(related=related)\n        obj1_id, obj2_id = obj1.id, obj2.id\n\n        # Verify they exist\n        self.assertEqual(ModelWithCascade.objects.filter(related=related).count(), 2)\n\n        # Delete the related model\n        related.delete()\n\n        # Verify all cascade objects were deleted\n        self.assertFalse(ModelWithCascade.objects.filter(id=obj1_id).exists())\n        self.assertFalse(ModelWithCascade.objects.filter(id=obj2_id).exists())\n        self.assertEqual(ModelWithCascade.objects.count(), 0)\n\n    def test_one_to_one_cascade_deletes_related_object(self):\n        \"\"\"Test CASCADE with OneToOneField deletes related polymorphic object\"\"\"\n        from .models import RelatedModel, ModelWithOneToOneCascade\n\n        # Create a related model and a polymorphic model with OneToOne\n        related = RelatedModel.objects.create(name=\"test\")\n        one_to_one_obj = ModelWithOneToOneCascade.objects.create(related=related)\n        one_to_one_obj_id = one_to_one_obj.id\n\n        # Verify the object exists\n        self.assertTrue(ModelWithOneToOneCascade.objects.filter(id=one_to_one_obj_id).exists())\n\n        # Delete the related model\n        related.delete()\n\n        # Verify the one-to-one object was deleted\n        self.assertFalse(ModelWithOneToOneCascade.objects.filter(id=one_to_one_obj_id).exists())\n\n    def test_one_to_one_protect_prevents_deletion(self):\n        \"\"\"Test PROTECT with OneToOneField prevents deletion\"\"\"\n        from .models import RelatedModel, ModelWithOneToOneProtect\n\n        # Create a related model and a polymorphic model with OneToOne\n        related = RelatedModel.objects.create(name=\"test\")\n        ModelWithOneToOneProtect.objects.create(related=related)\n\n        # Attempting to delete should raise ProtectedError\n        with self.assertRaises(ProtectedError):\n            related.delete()\n\n        # Verify both objects still exist\n        self.assertTrue(RelatedModel.objects.filter(id=related.id).exists())\n        self.assertTrue(ModelWithOneToOneProtect.objects.filter(related=related).exists())\n\n    def test_one_to_one_set_null_sets_to_null(self):\n        \"\"\"Test SET_NULL with OneToOneField sets field to null\"\"\"\n        from .models import RelatedModel, ModelWithOneToOneSetNull\n\n        # Create a related model and a polymorphic model with OneToOne\n        related = RelatedModel.objects.create(name=\"test\")\n        one_to_one_obj = ModelWithOneToOneSetNull.objects.create(related=related)\n        one_to_one_obj_id = one_to_one_obj.id\n\n        # Verify the relationship exists\n        self.assertEqual(one_to_one_obj.related, related)\n\n        # Delete the related model\n        related.delete()\n\n        # Verify the object still exists but the field is now null\n        one_to_one_obj = ModelWithOneToOneSetNull.objects.get(id=one_to_one_obj_id)\n        self.assertIsNone(one_to_one_obj.related)\n\n\n# class TestMigrationStateStability(TestCase):\n#     \"\"\"\n#     Test that unchanged models do not generate new migrations.\n#     \"\"\"\n\n#     def test_migration_state_stability(self):\n#         call_command(\"makemigrations\")\n\n#         migrations_dirs = [\n#             Path(__file__).parent.parent / \"deletion\" / \"migrations\",\n#             Path(__file__).parent.parent / \"test_migrations\" / \"migrations\",\n#             Path(__file__).parent.parent / \"migrations\",\n#         ]\n\n#         migrations = set()\n\n#         for migrations_dir in migrations_dirs:\n#             migrations.update(migrations_dir.glob(\"00*.py\"))\n\n#         call_command(\"makemigrations\")\n#         call_command(\"makemigrations\")\n\n#         migrations_post = set()\n\n#         for migrations_dir in migrations_dirs:\n#             migrations_post.update(migrations_dir.glob(\"00*.py\"))\n\n#         self.assertEqual(migrations, migrations_post)\n"
  },
  {
    "path": "src/polymorphic/tests/test_multidb.py",
    "content": "from django.contrib.contenttypes.models import ContentType\nfrom django.db.models import Q\nfrom django.test import TestCase\n\nfrom polymorphic.tests.models import (\n    Base,\n    BlogA,\n    BlogEntry,\n    Model2A,\n    Model2B,\n    Model2C,\n    Model2D,\n    ModelX,\n    ModelY,\n    One2OneRelatingModel,\n    RelatingModel,\n    RelationA,\n    RelationB,\n    RelationBase,\n)\n\n\nclass MultipleDatabasesTests(TestCase):\n    databases = [\"default\", \"secondary\"]\n\n    def test_save_to_non_default_database(self):\n        Model2A.objects.db_manager(\"secondary\").create(field1=\"A1\")\n        Model2C(field1=\"C1\", field2=\"C2\", field3=\"C3\").save(using=\"secondary\")\n        Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        Model2D(field1=\"D1\", field2=\"D2\", field3=\"D3\", field4=\"D4\").save()\n\n        self.assertQuerySetEqual(\n            Model2A.objects.order_by(\"id\"),\n            [Model2B, Model2D],\n            transform=lambda o: o.__class__,\n        )\n\n        self.assertQuerySetEqual(\n            Model2A.objects.db_manager(\"secondary\").order_by(\"id\"),\n            [Model2A, Model2C],\n            transform=lambda o: o.__class__,\n        )\n\n    def test_instance_of_filter_on_non_default_database(self):\n        Base.objects.db_manager(\"secondary\").create(field_b=\"B1\")\n        ModelX.objects.db_manager(\"secondary\").create(field_b=\"B\", field_x=\"X\")\n        ModelY.objects.db_manager(\"secondary\").create(field_b=\"Y\", field_y=\"Y\")\n\n        objects = Base.objects.db_manager(\"secondary\").filter(instance_of=Base)\n        self.assertQuerySetEqual(\n            objects,\n            [Base, ModelX, ModelY],\n            transform=lambda o: o.__class__,\n            ordered=False,\n        )\n\n        self.assertQuerySetEqual(\n            Base.objects.db_manager(\"secondary\").filter(instance_of=ModelX),\n            [ModelX],\n            transform=lambda o: o.__class__,\n        )\n\n        self.assertQuerySetEqual(\n            Base.objects.db_manager(\"secondary\").filter(instance_of=ModelY),\n            [ModelY],\n            transform=lambda o: o.__class__,\n        )\n\n        self.assertQuerySetEqual(\n            Base.objects.db_manager(\"secondary\").filter(\n                Q(instance_of=ModelX) | Q(instance_of=ModelY)\n            ),\n            [ModelX, ModelY],\n            transform=lambda o: o.__class__,\n            ordered=False,\n        )\n\n    def test_forward_many_to_one_descriptor_on_non_default_database(self):\n        def func():\n            blog = BlogA.objects.db_manager(\"secondary\").create(name=\"Blog\", info=\"Info\")\n            entry = BlogEntry.objects.db_manager(\"secondary\").create(blog=blog, text=\"Text\")\n            ContentType.objects.clear_cache()\n            entry = BlogEntry.objects.db_manager(\"secondary\").get(pk=entry.id)\n            assert blog == entry.blog\n\n        # Ensure no queries are made using the default database.\n        self.assertNumQueries(0, func)\n\n    def test_reverse_many_to_one_descriptor_on_non_default_database(self):\n        def func():\n            blog = BlogA.objects.db_manager(\"secondary\").create(name=\"Blog\", info=\"Info\")\n            entry = BlogEntry.objects.db_manager(\"secondary\").create(blog=blog, text=\"Text\")\n            ContentType.objects.clear_cache()\n            blog = BlogA.objects.db_manager(\"secondary\").get(pk=blog.id)\n            assert entry == blog.blogentry_set.using(\"secondary\").get()\n\n        # Ensure no queries are made using the default database.\n        self.assertNumQueries(0, func)\n\n    def test_reverse_one_to_one_descriptor_on_non_default_database(self):\n        def func():\n            m2a = Model2A.objects.db_manager(\"secondary\").create(field1=\"A1\")\n            one2one = One2OneRelatingModel.objects.db_manager(\"secondary\").create(\n                one2one=m2a, field1=\"121\"\n            )\n            ContentType.objects.clear_cache()\n            m2a = Model2A.objects.db_manager(\"secondary\").get(pk=m2a.id)\n            assert one2one == m2a.one2onerelatingmodel\n\n        # Ensure no queries are made using the default database.\n        self.assertNumQueries(0, func)\n\n    def test_many_to_many_descriptor_on_non_default_database(self):\n        def func():\n            m2a = Model2A.objects.db_manager(\"secondary\").create(field1=\"A1\")\n            rm = RelatingModel.objects.db_manager(\"secondary\").create()\n            rm.many2many.add(m2a)\n            ContentType.objects.clear_cache()\n            m2a = Model2A.objects.db_manager(\"secondary\").get(pk=m2a.id)\n            assert rm == m2a.relatingmodel_set.using(\"secondary\").get()\n\n        # Ensure no queries are made using the default database.\n        self.assertNumQueries(0, func)\n\n    def test_deletion_cascade_on_non_default_db(self):\n        def run():\n            base_db1 = RelationA.objects.db_manager(\"secondary\").create(field_a=\"Base DB1\")\n            base_db2 = RelationB.objects.db_manager(\"secondary\").create(\n                field_b=\"Base DB2\", fk=base_db1\n            )\n\n            ContentType.objects.clear_cache()\n\n            RelationBase.objects.db_manager(\"secondary\").filter(pk=base_db2.pk).delete()\n\n            self.assertEqual(RelationB.objects.db_manager(\"secondary\").count(), 0)\n\n        # Ensure no queries are made using the default database.\n        self.assertNumQueries(0, run)\n\n    def test_create_from_super(self):\n        # run create test 3 times because initial implementation\n        # would fail after first success.\n        from polymorphic.tests.models import (\n            NormalBase,\n            NormalExtension,\n            PolyExtension,\n            PolyExtChild,\n        )\n\n        nb = NormalBase.objects.db_manager(\"secondary\").create(nb_field=1)\n        ne = NormalExtension.objects.db_manager(\"secondary\").create(nb_field=2, ne_field=\"ne2\")\n\n        with self.assertRaises(TypeError):\n            PolyExtension.objects.db_manager(\"secondary\").create_from_super(nb, poly_ext_field=3)\n\n        pe = PolyExtension.objects.db_manager(\"secondary\").create_from_super(ne, poly_ext_field=3)\n\n        ne.refresh_from_db()\n        self.assertEqual(type(ne), NormalExtension)\n        self.assertEqual(type(pe), PolyExtension)\n        self.assertEqual(pe.pk, ne.pk)\n\n        self.assertEqual(pe.nb_field, 2)\n        self.assertEqual(pe.ne_field, \"ne2\")\n        self.assertEqual(pe.poly_ext_field, 3)\n        pe.refresh_from_db()\n        self.assertEqual(pe.nb_field, 2)\n        self.assertEqual(pe.ne_field, \"ne2\")\n        self.assertEqual(pe.poly_ext_field, 3)\n\n        pc = PolyExtChild.objects.db_manager(\"secondary\").create_from_super(\n            pe, poly_child_field=\"pcf6\"\n        )\n\n        pe.refresh_from_db()\n        ne.refresh_from_db()\n        self.assertEqual(type(ne), NormalExtension)\n        self.assertEqual(type(pe), PolyExtension)\n        self.assertEqual(pe.pk, ne.pk)\n        self.assertEqual(pe.pk, pc.pk)\n\n        self.assertEqual(pc.nb_field, 2)\n        self.assertEqual(pc.ne_field, \"ne2\")\n        self.assertEqual(pc.poly_ext_field, 3)\n        pc.refresh_from_db()\n        self.assertEqual(pc.nb_field, 2)\n        self.assertEqual(pc.ne_field, \"ne2\")\n        self.assertEqual(pc.poly_ext_field, 3)\n        self.assertEqual(pc.poly_child_field, \"pcf6\")\n\n        self.assertEqual(\n            pe.polymorphic_ctype,\n            ContentType.objects.db_manager(\"secondary\").get_for_model(PolyExtChild),\n        )\n        self.assertEqual(\n            pc.polymorphic_ctype,\n            ContentType.objects.db_manager(\"secondary\").get_for_model(PolyExtChild),\n        )\n\n        self.assertEqual(set(PolyExtension.objects.db_manager(\"secondary\").all()), {pc})\n\n        a1 = Model2A.objects.db_manager(\"secondary\").create(field1=\"A1a\")\n        a2 = Model2A.objects.db_manager(\"secondary\").create(field1=\"A1b\")\n\n        b1 = Model2B.objects.db_manager(\"secondary\").create(field1=\"B1a\", field2=\"B2a\")\n        b2 = Model2B.objects.db_manager(\"secondary\").create(field1=\"B1b\", field2=\"B2b\")\n\n        c1 = Model2C.objects.db_manager(\"secondary\").create(\n            field1=\"C1a\", field2=\"C2a\", field3=\"C3a\"\n        )\n        c2 = Model2C.objects.db_manager(\"secondary\").create(\n            field1=\"C1b\", field2=\"C2b\", field3=\"C3b\"\n        )\n\n        d1 = Model2D.objects.db_manager(\"secondary\").create(\n            field1=\"D1a\", field2=\"D2a\", field3=\"D3a\", field4=\"D4a\"\n        )\n        d2 = Model2D.objects.db_manager(\"secondary\").create(\n            field1=\"D1b\", field2=\"D2b\", field3=\"D3b\", field4=\"D4b\"\n        )\n\n        with self.assertRaises(TypeError):\n            Model2D.objects.db_manager(\"secondary\").create_from_super(\n                b1, field3=\"D3x\", field4=\"D4x\"\n            )\n\n        b1_of_c = Model2B.objects.db_manager(\"secondary\").non_polymorphic().get(pk=c1.pk)\n        with self.assertRaises(TypeError):\n            Model2C.objects.db_manager(\"secondary\").create_from_super(b1_of_c, field3=\"C3x\")\n\n        self.assertEqual(\n            c1.polymorphic_ctype,\n            ContentType.objects.db_manager(\"secondary\").get_for_model(Model2C),\n        )\n        dfs1 = Model2D.objects.db_manager(\"secondary\").create_from_super(b1_of_c, field4=\"D4x\")\n        self.assertEqual(type(dfs1), Model2D)\n        self.assertEqual(dfs1.pk, c1.pk)\n        self.assertEqual(dfs1.field1, \"C1a\")\n        self.assertEqual(dfs1.field2, \"C2a\")\n        self.assertEqual(dfs1.field3, \"C3a\")\n        self.assertEqual(dfs1.field4, \"D4x\")\n        self.assertEqual(\n            dfs1.polymorphic_ctype,\n            ContentType.objects.db_manager(\"secondary\").get_for_model(Model2D),\n        )\n        c1.refresh_from_db()\n        self.assertEqual(\n            c1.polymorphic_ctype,\n            ContentType.objects.db_manager(\"secondary\").get_for_model(Model2D),\n        )\n\n        self.assertEqual(\n            b2.polymorphic_ctype,\n            ContentType.objects.db_manager(\"secondary\").get_for_model(Model2B),\n        )\n        cfs1 = Model2C.objects.db_manager(\"secondary\").create_from_super(b2, field3=\"C3y\")\n        self.assertEqual(type(cfs1), Model2C)\n        self.assertEqual(cfs1.pk, b2.pk)\n        self.assertEqual(cfs1.field1, \"B1b\")\n        self.assertEqual(cfs1.field2, \"B2b\")\n        self.assertEqual(cfs1.field3, \"C3y\")\n        b2.refresh_from_db()\n        self.assertEqual(\n            b2.polymorphic_ctype,\n            ContentType.objects.db_manager(\"secondary\").get_for_model(Model2C),\n        )\n        self.assertEqual(\n            cfs1.polymorphic_ctype,\n            ContentType.objects.db_manager(\"secondary\").get_for_model(Model2C),\n        )\n\n        self.assertEqual(\n            set(Model2A.objects.db_manager(\"secondary\").all()),\n            {a1, a2, b1, dfs1, cfs1, c2, d1, d2},\n        )\n\n        self.assertEqual(Model2A.objects.count(), 0)\n\n    def test_cross_database_save(self):\n        \"\"\"Test saving an object from one database to another (issue #486)\"\"\"\n        # Create object in default database\n        obj = Model2B.objects.create(field1=\"test\", field2=\"value\")\n        original_pk = obj.pk\n        original_ctype_id = obj.polymorphic_ctype_id\n\n        # Get the ContentType ID for Model2B in default database\n        default_ctype = ContentType.objects.get_for_model(Model2B, for_concrete_model=False)\n        self.assertEqual(obj.polymorphic_ctype_id, default_ctype.pk)\n\n        # Save to secondary database (simulating cross-database copy)\n        obj.pk = None  # Make it a new object\n        obj.save(using=\"secondary\")\n\n        # polymorphic_ctype_id should be updated for secondary database\n        secondary_ctype = ContentType.objects.db_manager(\"secondary\").get_for_model(\n            Model2B, for_concrete_model=False\n        )\n        self.assertEqual(obj.polymorphic_ctype_id, secondary_ctype.pk)\n\n        # Verify it can be retrieved correctly from secondary database\n        retrieved = Model2B.objects.using(\"secondary\").get(pk=obj.pk)\n        self.assertIsInstance(retrieved, Model2B)\n        self.assertEqual(retrieved.field1, \"test\")\n        self.assertEqual(retrieved.field2, \"value\")\n\n        # Verify original object still exists in default database\n        original_obj = Model2B.objects.get(pk=original_pk)\n        self.assertEqual(original_obj.polymorphic_ctype_id, default_ctype.pk)\n\n    def test_database_router_respected(self):\n        \"\"\"Test that _state.db is respected when no explicit using is provided (issue #446)\"\"\"\n        # Create object in secondary database\n        obj = Model2B.objects.using(\"secondary\").create(field1=\"test\", field2=\"value\")\n\n        # Verify it was created in secondary database\n        secondary_ctype = ContentType.objects.db_manager(\"secondary\").get_for_model(\n            Model2B, for_concrete_model=False\n        )\n        self.assertEqual(obj.polymorphic_ctype_id, secondary_ctype.pk)\n        self.assertEqual(obj._state.db, \"secondary\")\n\n        # Modify and save without explicit using parameter\n        # Should use _state.db (secondary) not default\n        obj.field1 = \"modified\"\n        obj.save()  # No using parameter\n\n        # Verify it's still using secondary database's ContentType\n        obj.refresh_from_db(using=\"secondary\")\n        self.assertEqual(obj.polymorphic_ctype_id, secondary_ctype.pk)\n        self.assertEqual(obj.field1, \"modified\")\n\n        # Verify it wasn't saved to default database\n        self.assertFalse(Model2B.objects.filter(pk=obj.pk).exists())\n\n    def test_save_respects_db_for_write_router(self):\n        \"\"\"\n        When a DATABASE_ROUTER routes writes to a different database than where\n        the object was read from, save() should respect db_for_write() rather\n        than blindly using _state.db. Regression test for issue #865.\n        \"\"\"\n\n        class ReadWriteSplitRouter:\n            \"\"\"Router that sends writes to 'default' regardless of _state.db.\"\"\"\n\n            def db_for_read(self, model, **hints):\n                return None\n\n            def db_for_write(self, model, **hints):\n                return \"default\"\n\n            def allow_relation(self, obj1, obj2, **hints):\n                return True\n\n            def allow_migrate(self, db, app_label, **hints):\n                return True\n\n        # Create an object on \"secondary\", simulating an object loaded from a\n        # read-replica whose _state.db points at the replica.\n        obj = Model2B.objects.using(\"secondary\").create(field1=\"test\", field2=\"value\")\n        self.assertEqual(obj._state.db, \"secondary\")\n\n        with self.settings(DATABASE_ROUTERS=[ReadWriteSplitRouter()]):\n            # save() without an explicit `using` should consult the router's\n            # db_for_write() and route to \"default\", not to _state.db (\"secondary\").\n            obj.field1 = \"modified\"\n            obj.save()\n\n        # The write should have landed in \"default\" (the write database).\n        self.assertTrue(Model2B.objects.using(\"default\").filter(field1=\"modified\").exists())\n        self.assertFalse(Model2B.objects.using(\"secondary\").filter(field1=\"modified\").exists())\n"
  },
  {
    "path": "src/polymorphic/tests/test_orm.py",
    "content": "import warnings\nimport pytest\nimport uuid\n\nimport django\nfrom packaging.version import Version\nfrom django.contrib.auth import get_user_model\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.db import models, connection\nfrom django.db.models import (\n    Case,\n    Count,\n    FilteredRelation,\n    Q,\n    Sum,\n    When,\n    Exists,\n    OuterRef,\n    Subquery,\n)\nfrom django.db.utils import IntegrityError, NotSupportedError\nfrom django.test import TransactionTestCase\nfrom django.test.utils import CaptureQueriesContext\n\nfrom polymorphic import query_translate\nfrom polymorphic.managers import PolymorphicManager\nfrom polymorphic.models import PolymorphicTypeInvalid, PolymorphicTypeUndefined\nfrom polymorphic.tests.models import (\n    ArtProject,\n    Base,\n    BlogA,\n    BlogB,\n    BlogBase,\n    BlogEntry,\n    BlogEntry_limit_choices_to,\n    ChildModelWithManager,\n    CustomPkBase,\n    CustomPkInherit,\n    Enhance_Base,\n    Enhance_Plain,\n    Enhance_Inherit,\n    InlineParent,\n    InlineModelA,\n    InlineModelB,\n    InitTestModelSubclass,\n    Model2A,\n    Model2B,\n    Model2C,\n    Model2D,\n    ModelExtraA,\n    ModelExtraB,\n    ModelExtraC,\n    ModelExtraExternal,\n    ModelFieldNameTest,\n    ModelOrderLine,\n    ModelShow1,\n    ModelShow1_plain,\n    ModelShow2,\n    ModelShow2_plain,\n    ModelShow3,\n    ModelUnderRelChild,\n    ModelUnderRelParent,\n    ModelWithMyManager,\n    ModelWithMyManager2,\n    ModelWithMyManagerDefault,\n    ModelWithMyManagerNoDefault,\n    ModelX,\n    ModelY,\n    MRODerived,\n    MultiTableDerived,\n    MyManager,\n    MyManagerQuerySet,\n    NonPolymorphicParent,\n    NonProxyChild,\n    One2OneRelatingModel,\n    One2OneRelatingModelDerived,\n    ParentModelWithManager,\n    PlainA,\n    PlainB,\n    PlainC,\n    PlainChildModelWithManager,\n    PlainMyManager,\n    PlainMyManagerQuerySet,\n    PlainParentModelWithManager,\n    ProxiedBase,\n    ProxyBase,\n    ProxyChild,\n    ProxyModelA,\n    ProxyModelB,\n    ProxyModelBase,\n    RedheadDuck,\n    RelatingModel,\n    RelationA,\n    RelationB,\n    RelationBase,\n    RelationBC,\n    RubberDuck,\n    SubclassSelectorAbstractBaseModel,\n    SubclassSelectorAbstractConcreteModel,\n    SubclassSelectorProxyBaseModel,\n    SubclassSelectorProxyConcreteModel,\n    ParentLinkAndRelatedName,\n    UUIDArtProject,\n    UUIDArtProjectA,\n    UUIDArtProjectB,\n    UUIDArtProjectC,\n    UUIDArtProjectD,\n    UUIDPlainA,\n    UUIDPlainB,\n    UUIDPlainC,\n    UUIDProject,\n    UUIDResearchProject,\n    Duck,\n    PurpleHeadDuck,\n    Account,\n    SpecialAccount1,\n    SpecialAccount1_1,\n    SpecialAccount2,\n    Model2BFiltered,\n    Model2CFiltered,\n    Model2CNamedManagers,\n    Model2CNamedDefault,\n)\n\n\nclass PolymorphicTests(TransactionTestCase):\n    \"\"\"\n    The test suite\n    \"\"\"\n\n    def test_annotate_aggregate_order(self):\n        # create a blog of type BlogA\n        # create two blog entries in BlogA\n        # create some blogs of type BlogB to make the BlogBase table data really polymorphic\n        blog = BlogA.objects.create(name=\"B1\", info=\"i1\")\n        blog.blogentry_set.create(text=\"bla\")\n        BlogEntry.objects.create(blog=blog, text=\"bla2\")\n        BlogB.objects.create(name=\"Bb1\")\n        BlogB.objects.create(name=\"Bb2\")\n        BlogB.objects.create(name=\"Bb3\")\n\n        qs = BlogBase.objects.annotate(entrycount=Count(\"BlogA___blogentry\"))\n        assert len(qs) == 4\n\n        for o in qs:\n            if o.name == \"B1\":\n                assert o.entrycount == 2\n            else:\n                assert o.entrycount == 0\n\n        x = BlogBase.objects.aggregate(entrycount=Count(\"BlogA___blogentry\"))\n        assert x[\"entrycount\"] == 2\n\n        # create some more blogs for next test\n        BlogA.objects.create(name=\"B2\", info=\"i2\")\n        BlogA.objects.create(name=\"B3\", info=\"i3\")\n        BlogA.objects.create(name=\"B4\", info=\"i4\")\n        BlogA.objects.create(name=\"B5\", info=\"i5\")\n\n        # test ordering for field in all entries\n        expected = \"\"\"\n[ <BlogB: id 4, name (CharField) \"Bb3\">,\n  <BlogB: id 3, name (CharField) \"Bb2\">,\n  <BlogB: id 2, name (CharField) \"Bb1\">,\n  <BlogA: id 8, name (CharField) \"B5\", info (CharField) \"i5\">,\n  <BlogA: id 7, name (CharField) \"B4\", info (CharField) \"i4\">,\n  <BlogA: id 6, name (CharField) \"B3\", info (CharField) \"i3\">,\n  <BlogA: id 5, name (CharField) \"B2\", info (CharField) \"i2\">,\n  <BlogA: id 1, name (CharField) \"B1\", info (CharField) \"i1\"> ]\"\"\"\n        assert repr(BlogBase.objects.order_by(\"-name\")).strip() == expected.strip()\n\n        # different RDBMS return different orders for the nulls, and we can't use F\n        # and nulls_first or nulls_last here to standardize it, so our test is\n        # conditional\n        blog_names = [blg.name for blg in BlogBase.objects.order_by(\"-BlogA___info\")]\n        ordered = blog_names[:3]\n        if all([name.startswith(\"Bb\") for name in ordered]):\n            ordered = blog_names[3:]\n        else:\n            assert all([name.startswith(\"Bb\") for name in blog_names[-3:]])\n            ordered = blog_names[:-3]\n        assert ordered == [\"B5\", \"B4\", \"B3\", \"B2\", \"B1\"]\n\n    def test_limit_choices_to(self):\n        \"\"\"\n        this is not really a testcase, as limit_choices_to only affects the Django admin\n        \"\"\"\n        # create a blog of type BlogA\n        blog_a = BlogA.objects.create(name=\"aa\", info=\"aa\")\n        blog_b = BlogB.objects.create(name=\"bb\")\n        # create two blog entries\n        entry1 = BlogEntry_limit_choices_to.objects.create(blog=blog_b, text=\"bla2\")\n        entry2 = BlogEntry_limit_choices_to.objects.create(blog=blog_b, text=\"bla2\")\n\n    def test_primary_key_custom_field_problem(self):\n        \"\"\"\n        object retrieval problem occuring with some custom primary key fields (UUIDField as test case)\n        \"\"\"\n        up1 = UUIDProject.objects.create(topic=\"John's gathering\")\n        up2 = UUIDArtProject.objects.create(topic=\"Sculpting with Tim\", artist=\"T. Turner\")\n        up3 = UUIDResearchProject.objects.create(\n            topic=\"Swallow Aerodynamics\", supervisor=\"Dr. Winter\"\n        )\n\n        up4 = UUIDArtProjectA.objects.create(topic=\"ProjectA\", artist=\"Artist A\")\n        up5 = UUIDArtProjectB.objects.create(topic=\"ProjectB\", artist=\"Artist B\")\n        up6 = UUIDArtProjectC.objects.create(topic=\"ProjectC\", artist=\"Artist C\")\n        up7 = UUIDArtProjectD.objects.create(topic=\"ProjectD\", artist=\"Artist D\")\n\n        qs = UUIDProject.objects.all()\n        ol = list(qs)\n        a = qs[0]\n        b = qs[1]\n        c = qs[2]\n        assert len(qs) == 7\n        assert isinstance(a.uuid_primary_key, uuid.UUID)\n        assert isinstance(a.pk, uuid.UUID)\n\n        # https://github.com/jazzband/django-polymorphic/issues/306\n        assert {up1, up2, up3, up4, up5, up6, up7} == set(qs)\n        assert {up2, up4, up5, up6, up7} == set(UUIDArtProject.objects.all())\n        assert {up3} == set(UUIDResearchProject.objects.all())\n        assert {up4, up5, up6, up7} == set(UUIDArtProjectA.objects.all())\n        assert {up5, up6, up7} == set(UUIDArtProjectB.objects.all())\n        assert {up6, up7} == set(UUIDArtProjectC.objects.all())\n        assert {up7} == set(UUIDArtProjectD.objects.all())\n\n        a = UUIDPlainA.objects.create(field1=\"A1\")\n        b = UUIDPlainB.objects.create(field1=\"B1\", field2=\"B2\")\n        c = UUIDPlainC.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n        qs = UUIDPlainA.objects.all()\n        # Test that primary key values are valid UUIDs\n        assert uuid.UUID(f\"urn:uuid:{a.pk}\", version=1) == a.pk\n        assert uuid.UUID(f\"urn:uuid:{c.pk}\", version=1) == c.pk\n\n    def create_model2abcd(self):\n        \"\"\"\n        Create the chain of objects of Model2,\n        this is reused in various tests.\n        \"\"\"\n        a = Model2A.objects.create(field1=\"A1\")\n        b = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        c = Model2C.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n        d = Model2D.objects.create(field1=\"D1\", field2=\"D2\", field3=\"D3\", field4=\"D4\")\n\n        return a, b, c, d\n\n    def test_simple_inheritance(self):\n        self.create_model2abcd()\n\n        objects = Model2A.objects.all()\n        self.assertQuerySetEqual(\n            objects,\n            [Model2A, Model2B, Model2C, Model2D],\n            transform=lambda o: o.__class__,\n            ordered=False,\n        )\n\n    def test_defer_fields(self):\n        self.create_model2abcd()\n\n        objects_deferred = Model2A.objects.defer(\"field1\").order_by(\"id\")\n\n        assert \"field1\" not in objects_deferred[0].__dict__, (\n            \"field1 was not deferred (using defer())\"\n        )\n\n        # Check that we have exactly one deferred field ('field1') per resulting object.\n        for obj in objects_deferred:\n            deferred_fields = obj.get_deferred_fields()\n            assert len(deferred_fields) == 1\n            assert \"field1\" in deferred_fields\n\n        objects_only = Model2A.objects.only(\"pk\", \"polymorphic_ctype\", \"field1\")\n\n        assert \"field1\" in objects_only[0].__dict__, (\n            'qs.only(\"field1\") was used, but field1 was incorrectly deferred'\n        )\n        assert \"field1\" in objects_only[3].__dict__, (\n            'qs.only(\"field1\") was used, but field1 was incorrectly deferred on a child model'\n        )\n        assert \"field4\" not in objects_only[3].__dict__, \"field4 was not deferred (using only())\"\n        assert \"field1\" not in objects_only[0].get_deferred_fields()\n\n        assert \"field2\" in objects_only[1].get_deferred_fields()\n\n        # objects_only[2] has several deferred fields, ensure they are all set as such.\n        model2c_deferred = objects_only[2].get_deferred_fields()\n        assert \"field2\" in model2c_deferred\n        assert \"field3\" in model2c_deferred\n        assert \"model2a_ptr_id\" in model2c_deferred\n\n        # objects_only[3] has a few more fields that should be set as deferred.\n        model2d_deferred = objects_only[3].get_deferred_fields()\n        assert \"field2\" in model2d_deferred\n        assert \"field3\" in model2d_deferred\n        assert \"field4\" in model2d_deferred\n        assert \"model2a_ptr_id\" in model2d_deferred\n        assert \"model2b_ptr_id\" in model2d_deferred\n\n        ModelX.objects.create(field_b=\"A1\", field_x=\"A2\")\n        ModelY.objects.create(field_b=\"B1\", field_y=\"B2\")\n\n        # If we defer a field on a descendent, the parent's field is not deferred.\n        objects_deferred = Base.objects.defer(\"ModelY___field_y\")\n        assert \"field_y\" not in objects_deferred[0].get_deferred_fields()\n        assert \"field_y\" in objects_deferred[1].get_deferred_fields()\n\n        objects_only = Base.objects.only(\n            \"polymorphic_ctype\", \"ModelY___field_y\", \"ModelX___field_x\"\n        )\n        assert \"field_b\" in objects_only[0].get_deferred_fields()\n        assert \"field_b\" in objects_only[1].get_deferred_fields()\n\n    def test_defer_related_fields(self):\n        self.create_model2abcd()\n\n        objects_deferred_field4 = Model2A.objects.defer(\"Model2D___field4\")\n        assert \"field4\" not in objects_deferred_field4[3].__dict__, (\n            \"field4 was not deferred (using defer(), traversing inheritance)\"\n        )\n        assert objects_deferred_field4[0].__class__ == Model2A\n        assert objects_deferred_field4[1].__class__ == Model2B\n        assert objects_deferred_field4[2].__class__ == Model2C\n        assert objects_deferred_field4[3].__class__ == Model2D\n\n        objects_only_field4 = Model2A.objects.only(\n            \"polymorphic_ctype\",\n            \"field1\",\n            \"Model2B___id\",\n            \"Model2B___field2\",\n            \"Model2B___model2a_ptr\",\n            \"Model2C___id\",\n            \"Model2C___field3\",\n            \"Model2C___model2b_ptr\",\n            \"Model2D___id\",\n            \"Model2D___model2c_ptr\",\n        )\n        assert objects_only_field4[0].__class__ == Model2A\n        assert objects_only_field4[1].__class__ == Model2B\n        assert objects_only_field4[2].__class__ == Model2C\n        assert objects_only_field4[3].__class__ == Model2D\n\n    def test_manual_get_real_instance(self):\n        self.create_model2abcd()\n\n        o = Model2A.objects.non_polymorphic().get(field1=\"C1\")\n        assert o.get_real_instance().__class__ == Model2C\n\n    def test_get_real_instance_with_stale_content_type(self):\n        ctype = ContentType.objects.create(app_label=\"tests\", model=\"stale\")\n        o = Model2A.objects.create(field1=\"A1\", polymorphic_ctype=ctype)\n\n        assert o.get_real_instance_class() is None\n        match = \"does not have a corresponding model\"\n        with pytest.raises(PolymorphicTypeInvalid, match=match):\n            o.get_real_instance()\n\n    def test_get_real_concrete_instance_class_id_with_stale_content_type(self):\n        \"\"\"Test get_real_concrete_instance_class_id returns None for stale ContentType\"\"\"\n        ctype = ContentType.objects.create(app_label=\"tests\", model=\"stale_model\")\n        o = Model2A.objects.create(field1=\"A1\", polymorphic_ctype=ctype)\n\n        # When ContentType is stale, get_real_instance_class returns None\n        # which should cause get_real_concrete_instance_class_id to return None\n        assert o.get_real_concrete_instance_class_id() is None\n\n    def test_get_real_concrete_instance_class_with_stale_content_type(self):\n        \"\"\"Test get_real_concrete_instance_class returns None for stale ContentType\"\"\"\n        ctype = ContentType.objects.create(app_label=\"tests\", model=\"another_stale\")\n        o = Model2A.objects.create(field1=\"A1\", polymorphic_ctype=ctype)\n\n        # When ContentType is stale, get_real_instance_class returns None\n        # which should cause get_real_concrete_instance_class to return None\n        assert o.get_real_concrete_instance_class() is None\n\n    def test_get_real_concrete_instance_class_with_proxy_model(self):\n        \"\"\"Test get_real_concrete_instance_class with a proxy model\"\"\"\n        # Create a regular polymorphic object\n        a = Model2A.objects.create(field1=\"A1\")\n\n        # get_real_concrete_instance_class should return the concrete model class\n        concrete_class = a.get_real_concrete_instance_class()\n        assert concrete_class == Model2A\n\n    def test_non_polymorphic(self):\n        self.create_model2abcd()\n\n        objects = list(Model2A.objects.all().non_polymorphic())\n        self.assertQuerySetEqual(\n            objects,\n            [Model2A, Model2A, Model2A, Model2A],\n            transform=lambda o: o.__class__,\n        )\n\n    def test_get_real_instances(self):\n        self.create_model2abcd()\n        qs = Model2A.objects.all().non_polymorphic()\n\n        # from queryset\n        objects = qs.get_real_instances()\n        self.assertQuerySetEqual(\n            objects,\n            [Model2A, Model2B, Model2C, Model2D],\n            transform=lambda o: o.__class__,\n        )\n\n        # from a manual list\n        objects = Model2A.objects.get_real_instances(list(qs))\n        self.assertQuerySetEqual(\n            objects,\n            [Model2A, Model2B, Model2C, Model2D],\n            transform=lambda o: o.__class__,\n        )\n\n        # from empty list\n        objects = Model2A.objects.get_real_instances([])\n        self.assertQuerySetEqual(objects, [], transform=lambda o: o.__class__)\n\n    def test_queryset_missing_derived(self):\n        a = Model2A.objects.create(field1=\"A1\")\n        b = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        c = Model2C.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n        b_base = Model2A.objects.non_polymorphic().get(pk=b.pk)\n        c_base = Model2A.objects.non_polymorphic().get(pk=c.pk)\n\n        b_pk = b.pk  # Save pk before deletion\n        b.delete(keep_parents=True)  # e.g. table was truncated\n\n        qs_base = Model2A.objects.order_by(\"field1\").non_polymorphic()\n        qs_polymorphic = Model2A.objects.order_by(\"field1\").all()\n\n        assert list(qs_base) == [a, b_base, c_base]\n        assert list(qs_polymorphic) == [a, b_base, c]\n        result = list(qs_polymorphic)\n        assert len(result) == 3\n        assert result[0] == a\n        assert result[1].pk == b_pk  # b returned as Model2A (parent)\n        assert isinstance(result[1], Model2A)\n        assert not isinstance(result[1], Model2B)\n        assert result[2] == c\n\n    def test_queryset_missing_contenttype(self):\n        stale_ct = ContentType.objects.create(app_label=\"tests\", model=\"nonexisting\")\n        a1 = Model2A.objects.create(field1=\"A1\")\n        a2 = Model2A.objects.create(field1=\"A2\")\n        c = Model2C.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n        c_base = Model2A.objects.non_polymorphic().get(pk=c.pk)\n\n        Model2B.objects.filter(pk=a2.pk).update(polymorphic_ctype=stale_ct)\n\n        qs_base = Model2A.objects.order_by(\"field1\").non_polymorphic()\n        qs_polymorphic = Model2A.objects.order_by(\"field1\").all()\n\n        assert list(qs_base) == [a1, a2, c_base]\n        assert list(qs_polymorphic) == [a1, a2, c]\n\n    def test_translate_polymorphic_q_object(self):\n        self.create_model2abcd()\n\n        q = Model2A.translate_polymorphic_Q_object(Q(instance_of=Model2C))\n        objects = Model2A.objects.filter(q)\n        self.assertQuerySetEqual(\n            objects, [Model2C, Model2D], transform=lambda o: o.__class__, ordered=False\n        )\n\n    def test_create_instanceof_q(self):\n        # Test with a list of models\n        cached = [\n            Model2B,\n            Model2C,\n            Model2D,\n        ]\n        uncached = [\n            Model2BFiltered,\n            Model2CFiltered,\n            Model2CNamedManagers,\n            Model2CNamedDefault,\n        ]\n        ContentType.objects._cache.clear()\n\n        expected_cached = [ContentType.objects.get_for_model(m).pk for m in cached]\n        q = query_translate.create_instanceof_q([Model2B])\n\n        assert q.children[0][0] == \"polymorphic_ctype__in\"\n        assert isinstance(q.children[0][1], Subquery)\n        for model in uncached:\n            assert model._meta.app_label in str(q.children[0][1].query)\n            assert model._meta.model_name in str(q.children[0][1].query)\n        assert q.children[1][0] == \"polymorphic_ctype__in\"\n        assert set(q.children[1][1]) == set(expected_cached)\n\n    def test_instance_of_single_lazy_query(self):\n        a = Model2A.objects.create(field1=\"A1\")\n        b = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        c1 = Model2C.objects.create(field1=\"C1\", field2=\"C2\")\n        c2 = Model2C.objects.create(field1=\"C1\", field2=\"C2\")\n\n        ContentType.objects._cache.clear()\n\n        with CaptureQueriesContext(connection) as captured_queries:\n            assert Model2A.objects.filter(instance_of=Model2C).count() == 2\n\n        assert len(captured_queries) == 1\n        assert set(Model2A.objects.filter(instance_of=Model2C)) == {c1, c2}\n\n        # warm up the cache\n        ContentType.objects.get_for_models(Model2A, Model2B, Model2C)\n\n        with CaptureQueriesContext(connection) as captured_queries:\n            assert Model2A.objects.filter(instance_of=Model2C).count() == 2\n\n        assert set(Model2A.objects.filter(instance_of=Model2C)) == {c1, c2}\n\n    def test_base_manager(self):\n        from .models import ManagerTest, CustomBaseManager, ManagerTestChild\n\n        def base_manager(model):\n            return (type(model._base_manager), model._base_manager.model)\n\n        assert base_manager(PlainA) == (models.Manager, PlainA)\n        assert base_manager(PlainB) == (models.Manager, PlainB)\n        assert base_manager(PlainC) == (models.Manager, PlainC)\n\n        assert base_manager(Model2A) == (PolymorphicManager, Model2A)\n        assert base_manager(Model2B) == (PolymorphicManager, Model2B)\n        assert base_manager(Model2C) == (PolymorphicManager, Model2C)\n\n        assert base_manager(One2OneRelatingModel) == (PolymorphicManager, One2OneRelatingModel)\n        assert base_manager(One2OneRelatingModelDerived) == (\n            PolymorphicManager,\n            One2OneRelatingModelDerived,\n        )\n\n        assert type(Model2BFiltered._base_manager) is PolymorphicManager\n        assert type(Model2CFiltered._base_manager) is PolymorphicManager\n        assert type(Model2CNamedManagers._base_manager) is CustomBaseManager\n        assert type(Model2CNamedDefault._base_manager) is PolymorphicManager\n\n        assert type(ManagerTest._base_manager) is CustomBaseManager\n        assert type(ManagerTest._default_manager) is CustomBaseManager\n        assert ManagerTest._base_manager is ManagerTest._default_manager\n        assert type(ManagerTestChild._base_manager) is CustomBaseManager\n        assert type(ManagerTestChild._default_manager) is CustomBaseManager\n        assert ManagerTestChild._base_manager is ManagerTestChild._default_manager\n\n    def test_default_manager(self):\n        from .models import FilteredManager, FilteredManager2\n\n        def default_manager(instance):\n            return (\n                type(instance.__class__._default_manager),\n                instance.__class__._default_manager.model,\n            )\n\n        plain_a = PlainA(field1=\"C1\")\n        plain_b = PlainB(field2=\"C1\")\n        plain_c = PlainC(field3=\"C1\")\n\n        model_2a = Model2A(field1=\"C1\")\n        model_2b = Model2B(field2=\"C1\")\n        model_2c = Model2C(field3=\"C1\")\n\n        assert default_manager(plain_a) == (models.Manager, PlainA)\n        assert default_manager(plain_b) == (models.Manager, PlainB)\n        assert default_manager(plain_c) == (models.Manager, PlainC)\n\n        assert default_manager(model_2a) == (PolymorphicManager, Model2A)\n        assert default_manager(model_2b) == (PolymorphicManager, Model2B)\n        assert default_manager(model_2c) == (PolymorphicManager, Model2C)\n\n        assert type(Model2BFiltered._default_manager) is FilteredManager\n        assert type(Model2CFiltered._default_manager) is FilteredManager\n        assert type(Model2CNamedManagers._default_manager) is FilteredManager2\n        assert type(Model2CNamedDefault._default_manager) is FilteredManager2\n        assert Model2BFiltered._default_manager is not Model2BFiltered._base_manager\n        assert Model2CFiltered._default_manager is not Model2CFiltered._base_manager\n        assert Model2CNamedDefault._default_manager is not Model2CNamedDefault._base_manager\n        assert Model2CNamedManagers._default_manager is not Model2CNamedManagers._base_manager\n\n    def test_foreignkey_field(self):\n        self.create_model2abcd()\n\n        object2a = Model2A.objects.get(field1=\"C1\")\n        assert object2a.model2b.__class__ == Model2B\n\n        object2b = Model2B.objects.get(field1=\"C1\")\n        assert object2b.model2c.__class__ == Model2C\n\n    def test_parentage_links_are_non_polymorphic(self):\n        \"\"\"\n        OneToOne parent links should return non-polymorphic instances\n        \"\"\"\n        d = Model2D.objects.create(field1=\"D1\", field2=\"D2\", field3=\"D3\", field4=\"D4\")\n        c = Model2C.objects.non_polymorphic().get(pk=d.pk)\n        b = Model2B.objects.non_polymorphic().get(pk=d.pk)\n        a = Model2A.objects.non_polymorphic().get(pk=d.pk)\n        assert d.model2a_ptr.__class__ == Model2A\n        assert d.model2b_ptr.__class__ == Model2B\n        assert d.model2c_ptr.__class__ == Model2C\n        assert d.model2c_ptr == c\n        assert d.model2b_ptr == b\n        assert d.model2a_ptr == a\n        assert c.model2d == d\n        assert c.model2d.__class__ == Model2D\n        assert c.model2b_ptr.__class__ == Model2B\n        assert c.model2a_ptr.__class__ == Model2A\n        assert c.model2b_ptr == b\n        assert c.model2a_ptr == a\n        assert b.model2c == c\n        assert b.model2c.__class__ == Model2C\n        assert b.model2a_ptr.__class__ == Model2A\n        assert b.model2a_ptr == a\n        assert a.model2b.__class__ == Model2B\n        assert a.model2b == b\n\n    def test_onetoone_field(self):\n        self.create_model2abcd()\n\n        a = Model2A.objects.non_polymorphic().get(field1=\"C1\")\n        b = One2OneRelatingModelDerived.objects.create(one2one=a, field1=\"f1\", field2=\"f2\")\n\n        # FIXME: this result is basically wrong, probably due to Django cacheing\n        # (we used base_objects), but should not be a problem\n        assert b.one2one.__class__ == Model2A\n        assert b.one2one_id == b.one2one.id\n\n        c = One2OneRelatingModelDerived.objects.get(field1=\"f1\")\n        assert c.one2one.__class__ == Model2C\n        assert a.one2onerelatingmodel.__class__ == One2OneRelatingModelDerived\n\n    def test_manytomany_field(self):\n        # Model 1\n        o = ModelShow1.objects.create(field1=\"abc\")\n        o.m2m.add(o)\n        o.save()\n        assert (\n            repr(ModelShow1.objects.all())\n            == \"[ <ModelShow1: id 1, field1 (CharField), m2m (ManyToManyField)> ]\"\n        )\n\n        # Model 2\n        o = ModelShow2.objects.create(field1=\"abc\")\n        o.m2m.add(o)\n        o.save()\n        assert repr(ModelShow2.objects.all()) == '[ <ModelShow2: id 1, field1 \"abc\", m2m 1> ]'\n\n        # Model 3\n        o = ModelShow3.objects.create(field1=\"abc\")\n        o.m2m.add(o)\n        o.save()\n        assert (\n            repr(ModelShow3.objects.all())\n            == '[ <ModelShow3: id 1, field1 (CharField) \"abc\", m2m (ManyToManyField) 1> ]'\n        )\n        assert (\n            repr(ModelShow1.objects.all().annotate(Count(\"m2m\")))\n            == \"[ <ModelShow1: id 1, field1 (CharField), m2m (ManyToManyField) - Ann: m2m__count (int)> ]\"\n        )\n        assert (\n            repr(ModelShow2.objects.all().annotate(Count(\"m2m\")))\n            == '[ <ModelShow2: id 1, field1 \"abc\", m2m 1 - Ann: m2m__count 1> ]'\n        )\n        assert (\n            repr(ModelShow3.objects.all().annotate(Count(\"m2m\")))\n            == '[ <ModelShow3: id 1, field1 (CharField) \"abc\", m2m (ManyToManyField) 1 - Ann: m2m__count (int) 1> ]'\n        )\n\n        # no pretty printing\n        ModelShow1_plain.objects.create(field1=\"abc\")\n        ModelShow2_plain.objects.create(field1=\"abc\", field2=\"def\")\n        self.assertQuerySetEqual(\n            ModelShow1_plain.objects.all(),\n            [ModelShow1_plain, ModelShow2_plain],\n            transform=lambda o: o.__class__,\n            ordered=False,\n        )\n\n    def test_extra_method(self):\n        from django.db import connection\n\n        a, b, c, d = self.create_model2abcd()\n\n        objects = Model2A.objects.extra(where=[f\"id IN ({b.id}, {c.id})\"])\n        self.assertQuerySetEqual(\n            objects, [Model2B, Model2C], transform=lambda o: o.__class__, ordered=False\n        )\n\n        if connection.vendor == \"oracle\":\n            objects = Model2A.objects.extra(\n                select={\"select_test\": \"CASE WHEN field1 = 'A1' THEN 1 ELSE 0 END\"},\n                where=[\"field1 = 'A1' OR field1 = 'B1'\"],\n                order_by=[\"-id\"],\n            )\n        else:\n            objects = Model2A.objects.extra(\n                select={\"select_test\": \"field1 = 'A1'\"},\n                where=[\"field1 = 'A1' OR field1 = 'B1'\"],\n                order_by=[\"-id\"],\n            )\n        self.assertQuerySetEqual(objects, [Model2B, Model2A], transform=lambda o: o.__class__)\n\n        ModelExtraA.objects.create(field1=\"A1\")\n        ModelExtraB.objects.create(field1=\"B1\", field2=\"B2\")\n        ModelExtraC.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n        ModelExtraExternal.objects.create(topic=\"extra1\")\n        ModelExtraExternal.objects.create(topic=\"extra2\")\n        ModelExtraExternal.objects.create(topic=\"extra3\")\n        objects = ModelExtraA.objects.extra(\n            tables=[\"tests_modelextraexternal\"],\n            select={\"topic\": \"tests_modelextraexternal.topic\"},\n            where=[\"tests_modelextraa.id = tests_modelextraexternal.id\"],\n        )\n        assert (\n            repr(objects[0])\n            == '<ModelExtraA: id 1, field1 (CharField) \"A1\" - Extra: topic (str) \"extra1\">'\n        )\n        assert (\n            repr(objects[1])\n            == '<ModelExtraB: id 2, field1 (CharField) \"B1\", field2 (CharField) \"B2\" - Extra: topic (str) \"extra2\">'\n        )\n        assert (\n            repr(objects[2])\n            == '<ModelExtraC: id 3, field1 (CharField) \"C1\", field2 (CharField) \"C2\", field3 (CharField) \"C3\" - Extra: topic (str) \"extra3\">'\n        )\n        assert len(objects) == 3\n\n    def test_instance_of_filter(self):\n        self.create_model2abcd()\n\n        objects = Model2A.objects.instance_of(Model2B)\n        self.assertQuerySetEqual(\n            objects,\n            [Model2B, Model2C, Model2D],\n            transform=lambda o: o.__class__,\n            ordered=False,\n        )\n\n        objects = Model2A.objects.filter(instance_of=Model2B)\n        self.assertQuerySetEqual(\n            objects,\n            [Model2B, Model2C, Model2D],\n            transform=lambda o: o.__class__,\n            ordered=False,\n        )\n\n        objects = Model2A.objects.filter(Q(instance_of=Model2B))\n        self.assertQuerySetEqual(\n            objects,\n            [Model2B, Model2C, Model2D],\n            transform=lambda o: o.__class__,\n            ordered=False,\n        )\n\n        objects = Model2A.objects.not_instance_of(Model2B)\n        self.assertQuerySetEqual(\n            objects, [Model2A], transform=lambda o: o.__class__, ordered=False\n        )\n\n    def test_polymorphic___filter(self):\n        self.create_model2abcd()\n\n        objects = Model2A.objects.filter(Q(Model2B___field2=\"B2\") | Q(Model2C___field3=\"C3\"))\n        self.assertQuerySetEqual(\n            objects, [Model2B, Model2C], transform=lambda o: o.__class__, ordered=False\n        )\n\n    def test_polymorphic_applabel___filter(self):\n        self.create_model2abcd()\n\n        assert Model2B._meta.app_label == \"tests\"\n        objects = Model2A.objects.filter(\n            Q(tests__Model2B___field2=\"B2\") | Q(tests__Model2C___field3=\"C3\")\n        )\n        self.assertQuerySetEqual(\n            objects, [Model2B, Model2C], transform=lambda o: o.__class__, ordered=False\n        )\n\n    def test_query_filter_exclude_is_immutable(self):\n        # given\n        q_to_reuse = Q(Model2B___field2=\"something\")\n        untouched_q_object = Q(Model2B___field2=\"something\")\n        # when\n        Model2A.objects.filter(q_to_reuse).all()\n        # then\n        assert q_to_reuse.children == untouched_q_object.children\n\n        # given\n        q_to_reuse = Q(Model2B___field2=\"something\")\n        untouched_q_object = Q(Model2B___field2=\"something\")\n        # when\n        Model2B.objects.filter(q_to_reuse).all()\n        # then\n        assert q_to_reuse.children == untouched_q_object.children\n\n    def test_polymorphic___filter_field(self):\n        p = ModelUnderRelParent.objects.create(_private=True, field1=\"AA\")\n        ModelUnderRelChild.objects.create(parent=p, _private2=True)\n\n        # The \"___\" filter should also parse to \"parent\" -> \"_private\" as fallback.\n        objects = ModelUnderRelChild.objects.filter(parent___private=True)\n        assert len(objects) == 1\n\n    def test_polymorphic___filter_reverse_field(self):\n        p = ModelUnderRelParent.objects.create(_private=True, field1=\"BB\")\n        ModelUnderRelChild.objects.create(parent=p, _private2=True)\n\n        # Also test for reverse relations\n        objects = ModelUnderRelParent.objects.filter(children___private2=True)\n        assert len(objects) == 1\n\n    def test_delete(self):\n        a, b, c, d = self.create_model2abcd()\n\n        oa = Model2A.objects.get(id=b.id)\n        assert oa.__class__ == Model2B\n        assert Model2A.objects.count() == 4\n\n        oa.delete()\n        objects = Model2A.objects.all()\n        self.assertQuerySetEqual(\n            objects,\n            [Model2A, Model2C, Model2D],\n            transform=lambda o: o.__class__,\n            ordered=False,\n        )\n\n    def test_combine_querysets(self):\n        ModelX.objects.create(field_x=\"x\", field_b=\"1\")\n        ModelY.objects.create(field_y=\"y\", field_b=\"2\")\n\n        qs = Base.objects.instance_of(ModelX) | Base.objects.instance_of(ModelY)\n        qs = qs.order_by(\"field_b\")\n        assert repr(qs[0]) == \"<ModelX: id 1, field_b (CharField), field_x (CharField)>\"\n        assert repr(qs[1]) == \"<ModelY: id 2, field_b (CharField), field_y (CharField)>\"\n        assert len(qs) == 2\n\n    def test_multiple_inheritance(self):\n        # multiple inheritance, subclassing third party models (mix PolymorphicModel with models.Model)\n\n        Enhance_Base.objects.create(field_b=\"b-base\")\n        Enhance_Inherit.objects.create(field_b=\"b-inherit\", field_p=\"p\", field_i=\"i\")\n\n        qs = Enhance_Base.objects.all()\n        assert len(qs) == 2\n        assert (\n            repr(qs[0]) == '<Enhance_Base: base_id (AutoField/pk) 1, field_b (CharField) \"b-base\">'\n        )\n        assert (\n            repr(qs[1])\n            == '<Enhance_Inherit: base_id (AutoField/pk) 2, field_b (CharField) \"b-inherit\", id 1, field_p (CharField) \"p\", field_i (CharField) \"i\">'\n        )\n\n    def test_relation_base(self):\n        # ForeignKey, ManyToManyField\n        obase = RelationBase.objects.create(field_base=\"base\")\n        oa = RelationA.objects.create(field_base=\"A1\", field_a=\"A2\", fk=obase)\n        ob = RelationB.objects.create(field_base=\"B1\", field_b=\"B2\", fk=oa)\n        oc = RelationBC.objects.create(field_base=\"C1\", field_b=\"C2\", field_c=\"C3\", fk=oa)\n        oa.m2m.add(oa)\n        oa.m2m.add(ob)\n\n        objects = RelationBase.objects.order_by(\"pk\").all()\n        assert (\n            repr(objects[0])\n            == '<RelationBase: id 1, field_base (CharField) \"base\", fk (ForeignKey) None, m2m (ManyToManyField) 0>'\n        )\n        assert (\n            repr(objects[1])\n            == '<RelationA: id 2, field_base (CharField) \"A1\", fk (ForeignKey) RelationBase, field_a (CharField) \"A2\", m2m (ManyToManyField) 2>'\n        )\n        assert (\n            repr(objects[2])\n            == '<RelationB: id 3, field_base (CharField) \"B1\", fk (ForeignKey) RelationA, field_b (CharField) \"B2\", m2m (ManyToManyField) 1>'\n        )\n        assert (\n            repr(objects[3])\n            == '<RelationBC: id 4, field_base (CharField) \"C1\", fk (ForeignKey) RelationA, field_b (CharField) \"C2\", field_c (CharField) \"C3\", m2m (ManyToManyField) 0>'\n        )\n        assert len(objects) == 4\n\n        oa = RelationBase.objects.get(id=2)\n        assert (\n            repr(oa.fk)\n            == '<RelationBase: id 1, field_base (CharField) \"base\", fk (ForeignKey) None, m2m (ManyToManyField) 0>'\n        )\n\n        objects = oa.relationbase_set.order_by(\"pk\").all()\n        assert (\n            repr(objects[0])\n            == '<RelationB: id 3, field_base (CharField) \"B1\", fk (ForeignKey) RelationA, field_b (CharField) \"B2\", m2m (ManyToManyField) 1>'\n        )\n        assert (\n            repr(objects[1])\n            == '<RelationBC: id 4, field_base (CharField) \"C1\", fk (ForeignKey) RelationA, field_b (CharField) \"C2\", field_c (CharField) \"C3\", m2m (ManyToManyField) 0>'\n        )\n        assert len(objects) == 2\n\n        ob = RelationBase.objects.get(id=3)\n        assert (\n            repr(ob.fk)\n            == '<RelationA: id 2, field_base (CharField) \"A1\", fk (ForeignKey) RelationBase, field_a (CharField) \"A2\", m2m (ManyToManyField) 2>'\n        )\n\n        oa = RelationA.objects.get()\n        objects = oa.m2m.order_by(\"pk\").all()\n        assert (\n            repr(objects[0])\n            == '<RelationA: id 2, field_base (CharField) \"A1\", fk (ForeignKey) RelationBase, field_a (CharField) \"A2\", m2m (ManyToManyField) 2>'\n        )\n        assert (\n            repr(objects[1])\n            == '<RelationB: id 3, field_base (CharField) \"B1\", fk (ForeignKey) RelationA, field_b (CharField) \"B2\", m2m (ManyToManyField) 1>'\n        )\n        assert len(objects) == 2\n\n    def test_user_defined_manager(self):\n        self.create_model2abcd()\n        ModelWithMyManager.objects.create(field1=\"D1a\", field4=\"D4a\")\n        ModelWithMyManager.objects.create(field1=\"D1b\", field4=\"D4b\")\n\n        # MyManager should reverse the sorting of field1\n        objects = ModelWithMyManager.objects.all()\n        self.assertQuerySetEqual(\n            objects,\n            [(ModelWithMyManager, \"D1b\", \"D4b\"), (ModelWithMyManager, \"D1a\", \"D4a\")],\n            transform=lambda o: (o.__class__, o.field1, o.field4),\n        )\n\n        assert type(ModelWithMyManager.objects) is MyManager\n        assert type(ModelWithMyManager._default_manager) is MyManager\n\n    def test_user_defined_manager_as_secondary(self):\n        self.create_model2abcd()\n        ModelWithMyManagerNoDefault.objects.create(field1=\"D1a\", field4=\"D4a\")\n        ModelWithMyManagerNoDefault.objects.create(field1=\"D1b\", field4=\"D4b\")\n\n        # MyManager should reverse the sorting of field1\n        objects = ModelWithMyManagerNoDefault.my_objects.all()\n        self.assertQuerySetEqual(\n            objects,\n            [\n                (ModelWithMyManagerNoDefault, \"D1b\", \"D4b\"),\n                (ModelWithMyManagerNoDefault, \"D1a\", \"D4a\"),\n            ],\n            transform=lambda o: (o.__class__, o.field1, o.field4),\n        )\n\n        assert type(ModelWithMyManagerNoDefault.my_objects) is MyManager\n        assert type(ModelWithMyManagerNoDefault.objects) is PolymorphicManager\n        assert type(ModelWithMyManagerNoDefault._default_manager) is PolymorphicManager\n\n    def test_user_objects_manager_as_secondary(self):\n        self.create_model2abcd()\n        ModelWithMyManagerDefault.objects.create(field1=\"D1a\", field4=\"D4a\")\n        ModelWithMyManagerDefault.objects.create(field1=\"D1b\", field4=\"D4b\")\n\n        assert type(ModelWithMyManagerDefault.my_objects) is MyManager\n        assert type(ModelWithMyManagerDefault.objects) is PolymorphicManager\n        assert type(ModelWithMyManagerDefault._default_manager) is MyManager\n\n    def test_user_defined_queryset_as_manager(self):\n        self.create_model2abcd()\n        ModelWithMyManager2.objects.create(field1=\"D1a\", field4=\"D4a\")\n        ModelWithMyManager2.objects.create(field1=\"D1b\", field4=\"D4b\")\n\n        objects = ModelWithMyManager2.objects.all()\n        self.assertQuerySetEqual(\n            objects,\n            [(ModelWithMyManager2, \"D1a\", \"D4a\"), (ModelWithMyManager2, \"D1b\", \"D4b\")],\n            transform=lambda o: (o.__class__, o.field1, o.field4),\n            ordered=False,\n        )\n\n        assert (\n            type(ModelWithMyManager2.objects).__name__ == \"PolymorphicManagerFromMyManagerQuerySet\"\n        )\n        assert (\n            type(ModelWithMyManager2._default_manager).__name__\n            == \"PolymorphicManagerFromMyManagerQuerySet\"\n        )\n\n    def test_manager_inheritance(self):\n        # by choice of MRO, should be MyManager from MROBase1.\n        assert type(MRODerived.objects) is MyManager\n\n    def test_queryset_assignment(self):\n        # This is just a consistency check for now, testing standard Django behavior.\n        parent = PlainParentModelWithManager.objects.create()\n        child = PlainChildModelWithManager.objects.create(fk=parent)\n        assert type(PlainParentModelWithManager._default_manager) is models.Manager\n        assert type(PlainChildModelWithManager._default_manager) is PlainMyManager\n        assert type(PlainChildModelWithManager.objects) is PlainMyManager\n        assert type(PlainChildModelWithManager.objects.all()) is PlainMyManagerQuerySet\n\n        # A related set is created using the model's _default_manager, so does gain extra methods.\n        assert type(parent.childmodel_set.my_queryset_foo()) is PlainMyManagerQuerySet\n\n        # For polymorphic models, the same should happen.\n        parent = ParentModelWithManager.objects.create()\n        child = ChildModelWithManager.objects.create(fk=parent)\n        assert type(ParentModelWithManager._default_manager) is PolymorphicManager\n        assert type(ChildModelWithManager._default_manager) is MyManager\n        assert type(ChildModelWithManager.objects) is MyManager\n        assert type(ChildModelWithManager.objects.my_queryset_foo()) is MyManagerQuerySet\n\n        # A related set is created using the model's _default_manager, so does gain extra methods.\n        assert type(parent.childmodel_set.my_queryset_foo()) is MyManagerQuerySet\n\n    def test_proxy_models(self):\n        # prepare some data\n        for data in (\"bleep bloop\", \"I am a\", \"computer\"):\n            ProxyChild.objects.create(some_data=data)\n\n        # this caches ContentType queries so they don't interfere with our query counts later\n        list(ProxyBase.objects.all())\n\n        # one query per concrete class\n        with self.assertNumQueries(1):\n            items = list(ProxyBase.objects.all())\n\n        assert isinstance(items[0], ProxyChild)\n\n    def test_queryset_on_proxy_model_does_not_return_superclasses(self):\n        ProxyBase.objects.create(some_data=\"Base1\")\n        ProxyBase.objects.create(some_data=\"Base2\")\n        ProxyChild.objects.create(some_data=\"Child1\")\n        ProxyChild.objects.create(some_data=\"Child2\")\n        ProxyChild.objects.create(some_data=\"Child3\")\n\n        assert ProxyBase.objects.count() == 5\n        assert ProxyChild.objects.count() == 3\n\n    def test_proxy_get_real_instance_class(self):\n        \"\"\"\n        The call to ``get_real_instance()`` also checks whether the returned model is of the correct type.\n        This unit test guards that this check is working properly. For instance,\n        proxy child models need to be handled separately.\n        \"\"\"\n        name = \"Item1\"\n        nonproxychild = NonProxyChild.objects.create(name=name)\n\n        pb = ProxyBase.objects.get(id=1)\n        assert pb.get_real_instance_class() == NonProxyChild\n        assert pb.get_real_instance() == nonproxychild\n        assert pb.name == name\n\n        pbm = NonProxyChild.objects.get(id=1)\n        assert pbm.get_real_instance_class() == NonProxyChild\n        assert pbm.get_real_instance() == nonproxychild\n        assert pbm.name == name\n\n    def test_content_types_for_proxy_models(self):\n        \"\"\"Checks if ContentType is capable of returning proxy models.\"\"\"\n        from django.contrib.contenttypes.models import ContentType\n\n        ct = ContentType.objects.get_for_model(ProxyChild, for_concrete_model=False)\n        assert ProxyChild == ct.model_class()\n\n    def test_proxy_model_inheritance(self):\n        \"\"\"\n        Polymorphic abilities should also work when the base model is a proxy object.\n        \"\"\"\n        # The managers should point to the proper objects.\n        # otherwise, the whole excersise is pointless.\n        assert ProxiedBase.objects.model == ProxiedBase\n        assert ProxyModelBase.objects.model == ProxyModelBase\n        assert ProxyModelA.objects.model == ProxyModelA\n        assert ProxyModelB.objects.model == ProxyModelB\n\n        # Create objects\n        object1_pk = ProxyModelA.objects.create(name=\"object1\").pk\n        object2_pk = ProxyModelB.objects.create(name=\"object2\", field2=\"bb\").pk\n\n        # Getting single objects\n        object1 = ProxyModelBase.objects.get(name=\"object1\")\n        object2 = ProxyModelBase.objects.get(name=\"object2\")\n        assert repr(object1) == (\n            f'<ProxyModelA: id {object1_pk}, name (CharField) \"object1\", field1 (CharField) \"\">'\n        )\n        assert repr(object2) == (\n            '<ProxyModelB: id %i, name (CharField) \"object2\", field2 (CharField) \"bb\">'\n            % object2_pk\n        )\n        assert isinstance(object1, ProxyModelA)\n        assert isinstance(object2, ProxyModelB)\n\n        # Same for lists\n        objects = list(ProxyModelBase.objects.all().order_by(\"name\"))\n        assert repr(objects[0]) == (\n            f'<ProxyModelA: id {object1_pk}, name (CharField) \"object1\", field1 (CharField) \"\">'\n        )\n        assert repr(objects[1]) == (\n            '<ProxyModelB: id %i, name (CharField) \"object2\", field2 (CharField) \"bb\">'\n            % object2_pk\n        )\n        assert isinstance(objects[0], ProxyModelA)\n        assert isinstance(objects[1], ProxyModelB)\n\n    def test_custom_pk(self):\n        pk_base = CustomPkBase.objects.create(b=\"b\")\n        pk_inherit = CustomPkInherit.objects.create(b=\"b\", i=\"i\")\n        qs = CustomPkBase.objects.all()\n        assert len(qs) == 2\n        assert repr(qs[0]) == f'<CustomPkBase: id {pk_base.id}, b (CharField) \"b\">'\n        assert (\n            repr(qs[1])\n            == f'<CustomPkInherit: id {pk_inherit.id}, b (CharField) \"b\", custom_id (AutoField/pk) {pk_inherit.custom_id}, i (CharField) \"i\">'\n        )\n\n    def test_fix_getattribute(self):\n        # fixed issue in PolymorphicModel.__getattribute__: field name same as model name\n        o = ModelFieldNameTest.objects.create(modelfieldnametest=\"1\")\n        assert repr(o) == \"<ModelFieldNameTest: id 1, modelfieldnametest (CharField)>\"\n\n        # if subclass defined __init__ and accessed class members,\n        # __getattribute__ had a problem: \"...has no attribute 'sub_and_superclass_dict'\"\n        o = InitTestModelSubclass.objects.create()\n        assert o.bar == \"XYZ\"\n\n    def test_parent_link_and_related_name(self):\n        t = ParentLinkAndRelatedName(field1=\"ParentLinkAndRelatedName\")\n        t.save()\n        p = ModelShow1_plain.objects.get(field1=\"ParentLinkAndRelatedName\")\n\n        # check that p is equal to the\n        assert isinstance(p, ParentLinkAndRelatedName)\n        assert p == t\n\n        # check that the accessors to parent and sublass work correctly and return the right object\n        p = ModelShow1_plain.objects.non_polymorphic().get(field1=\"ParentLinkAndRelatedName\")\n        # p should be Plain1 and t ParentLinkAndRelatedName, so not equal\n        assert p != t\n        assert p == t.superclass\n        assert p.related_name_subclass == t\n\n        # test that we can delete the object\n        t.delete()\n\n    def test_polymorphic__accessor_caching(self):\n        blog_a = BlogA.objects.create(name=\"blog\")\n\n        blog_base = BlogBase.objects.non_polymorphic().get(id=blog_a.id)\n        blog_a = BlogA.objects.get(id=blog_a.id)\n\n        # test reverse accessor & check that we get back cached object on repeated access\n        self.assertEqual(blog_base.bloga, blog_a)\n        self.assertIs(blog_base.bloga, blog_base.bloga)\n        cached_blog_a = blog_base.bloga\n\n        # test forward accessor & check that we get back cached object on repeated access\n        self.assertEqual(blog_a.blogbase_ptr, blog_base)\n        self.assertIs(blog_a.blogbase_ptr, blog_a.blogbase_ptr)\n        cached_blog_base = blog_a.blogbase_ptr\n\n        # check that refresh_from_db correctly clears cached related objects\n        blog_base.refresh_from_db()\n        blog_a.refresh_from_db()\n\n        self.assertIsNot(cached_blog_a, blog_base.bloga)\n        self.assertIsNot(cached_blog_base, blog_a.blogbase_ptr)\n\n    def test_polymorphic__aggregate(self):\n        \"\"\"test ModelX___field syntax on aggregate (should work for annotate either)\"\"\"\n\n        Model2A.objects.create(field1=\"A1\")\n        Model2B.objects.create(field1=\"A1\", field2=\"B2\")\n        Model2B.objects.create(field1=\"A1\", field2=\"B2\")\n\n        # aggregate using **kwargs\n        result = Model2A.objects.aggregate(cnt=Count(\"Model2B___field2\"))\n        assert result == {\"cnt\": 2}\n\n        # aggregate using **args\n        with pytest.raises(\n            AssertionError,\n            match=\"model lookup supported for keyword arguments only\",\n        ):\n            Model2A.objects.aggregate(Count(\"Model2B___field2\"))\n\n    def test_polymorphic__aggregate_empty_queryset(self):\n        \"\"\"test the fix for test___lookup in Django 5.1+\"\"\"\n        line = ModelOrderLine.objects.create()\n        result = line.articles.aggregate(Sum(\"sales_points\"))\n        assert result == {\"sales_points__sum\": None}\n\n    def test_polymorphic__complex_aggregate(self):\n        \"\"\"test (complex expression on) aggregate (should work for annotate either)\"\"\"\n\n        Model2A.objects.create(field1=\"A1\")\n        Model2B.objects.create(field1=\"A1\", field2=\"B2\")\n        Model2B.objects.create(field1=\"A1\", field2=\"B2\")\n\n        # aggregate using **kwargs\n        cnt = Count(Case(When(Model2B___field2=\"B2\", then=1)))\n        result = Model2A.objects.aggregate(\n            cnt_a1=Count(Case(When(field1=\"A1\", then=1))),\n            cnt_b2=cnt,\n        )\n        assert result == {\"cnt_b2\": 2, \"cnt_a1\": 3}\n\n        # test that our expression was immutable\n        # FIXME - expression passed into aggregate are not immutable!\n        # assert (\n        #     cnt.get_source_expressions()[0]\n        #     .get_source_expressions()[0]\n        #     .get_source_expressions()[0]\n        #     .children[0][0]\n        #     == \"Model2B___field2\"\n        # )\n\n        # aggregate using **args\n        # we have to set the defaul alias or django won't except a complex expression\n        # on aggregate/annotate\n        def ComplexAgg(expression):\n            complexagg = Count(expression) * 10\n            complexagg.default_alias = \"complexagg\"\n            return complexagg\n\n        with pytest.raises(\n            AssertionError,\n            match=\"model lookup supported for keyword arguments only\",\n        ):\n            Model2A.objects.aggregate(ComplexAgg(\"Model2B___field2\"))\n\n    def test_annotate_f_expression(self):\n        \"\"\"\n        Verify that F() expressions with '___' syntax correctly translate in annotate() calls.\n        \"\"\"\n        Model2A.objects.create(field1=\"A_only\")\n        Model2B.objects.create(field1=\"A_from_B1\", field2=\"B2_val1\")\n        Model2B.objects.create(field1=\"A_from_B2\", field2=\"B2_val2\")\n\n        # Use annotate with an F-expression targeting a child model field\n        # We'll count occurrences of field2 from Model2B\n        # This implicitly tests that 'Model2B___field2' is correctly translated\n        annotated_queryset = Model2A.objects.annotate(\n            field2_count=Count(models.F(\"Model2B___field2\"))\n        ).order_by(\"pk\")\n\n        results = list(annotated_queryset)\n        assert len(results) == 3\n\n        # For Model2A that is not a Model2B, the count should be 0\n        assert results[0].field1 == \"A_only\"\n        assert results[0].field2_count == 0\n\n        # For Model2B instances, the field2_count should be 1\n        assert results[1].field1 == \"A_from_B1\"\n        assert results[1].field2_count == 1\n\n        assert results[2].field1 == \"A_from_B2\"\n        assert results[2].field2_count == 1\n\n    def test_polymorphic__filtered_relation(self):\n        \"\"\"test annotation using FilteredRelation\"\"\"\n\n        blog = BlogA.objects.create(name=\"Ba1\", info=\"i1 joined\")\n        blog.blogentry_set.create(text=\"bla1 joined\")\n        blog.blogentry_set.create(text=\"bla2 joined\")\n        blog.blogentry_set.create(text=\"bla3 joined\")\n        blog.blogentry_set.create(text=\"bla4\")\n        blog.blogentry_set.create(text=\"bla5\")\n        BlogA.objects.create(name=\"Ba2\", info=\"i2 joined\")\n        BlogA.objects.create(name=\"Ba3\", info=\"i3\")\n        BlogB.objects.create(name=\"Bb3\")\n\n        result = BlogA.objects.annotate(\n            text_joined=FilteredRelation(\n                \"blogentry\", condition=Q(blogentry__text__contains=\"joined\")\n            ),\n        ).aggregate(Count(\"text_joined\"))\n        assert result == {\"text_joined__count\": 3}\n\n        result = BlogA.objects.annotate(\n            text_joined=FilteredRelation(\n                \"blogentry\", condition=Q(blogentry__text__contains=\"joined\")\n            ),\n        ).aggregate(count=Count(\"text_joined\"))\n        assert result == {\"count\": 3}\n\n        result = BlogBase.objects.annotate(\n            info_joined=FilteredRelation(\"bloga\", condition=Q(BlogA___info__contains=\"joined\")),\n        ).aggregate(Count(\"info_joined\"))\n        assert result == {\"info_joined__count\": 2}\n\n        result = BlogBase.objects.annotate(\n            info_joined=FilteredRelation(\"bloga\", condition=Q(BlogA___info__contains=\"joined\")),\n        ).aggregate(count=Count(\"info_joined\"))\n        assert result == {\"count\": 2}\n\n        # We should get a BlogA and a BlogB\n        result = BlogBase.objects.annotate(\n            info_joined=FilteredRelation(\"bloga\", condition=Q(BlogA___info__contains=\"joined\")),\n        ).filter(info_joined__isnull=True)\n        assert result.count() == 2\n        assert isinstance(result.first(), BlogA)\n        assert isinstance(result.last(), BlogB)\n\n    def test_polymorphic__expressions(self):\n        from django.db.models.functions import Concat\n\n        # no exception raised\n        result = Model2B.objects.annotate(val=Concat(\"field1\", \"field2\"))\n        assert list(result) == []\n\n    def test_null_polymorphic_id(self):\n        \"\"\"Test that a proper error message is displayed when the database lacks the ``polymorphic_ctype_id``\"\"\"\n        Model2A.objects.create(field1=\"A1\")\n        Model2B.objects.create(field1=\"A1\", field2=\"B2\")\n        Model2B.objects.create(field1=\"A1\", field2=\"B2\")\n        Model2A.objects.all().update(polymorphic_ctype_id=None)\n\n        with pytest.raises(PolymorphicTypeUndefined):\n            list(Model2A.objects.all())\n\n    def test_invalid_polymorphic_id(self):\n        \"\"\"Test that a proper error message is displayed when the database ``polymorphic_ctype_id`` is invalid\"\"\"\n        Model2A.objects.create(field1=\"A1\")\n        Model2B.objects.create(field1=\"A1\", field2=\"B2\")\n        Model2B.objects.create(field1=\"A1\", field2=\"B2\")\n        invalid = ContentType.objects.get_for_model(PlainA).pk\n        Model2A.objects.all().update(polymorphic_ctype_id=invalid)\n\n        with pytest.raises(PolymorphicTypeInvalid):\n            list(Model2A.objects.all())\n\n    def test_bulk_create_abstract_inheritance(self):\n        ArtProject.objects.bulk_create(\n            [\n                ArtProject(topic=\"Painting with Tim\", artist=\"T. Turner\"),\n                ArtProject(topic=\"Sculpture with Tim\", artist=\"T. Turner\"),\n            ]\n        )\n        assert sorted(ArtProject.objects.values_list(\"topic\", \"artist\")) == [\n            (\"Painting with Tim\", \"T. Turner\"),\n            (\"Sculpture with Tim\", \"T. Turner\"),\n        ]\n\n    def test_bulk_create_proxy_inheritance(self):\n        RedheadDuck.objects.bulk_create(\n            [\n                RedheadDuck(name=\"redheadduck1\"),\n                Duck(name=\"duck1\"),\n                RubberDuck(name=\"rubberduck1\"),\n            ]\n        )\n        RubberDuck.objects.bulk_create(\n            [\n                RedheadDuck(name=\"redheadduck2\"),\n                RubberDuck(name=\"rubberduck2\"),\n                Duck(name=\"duck2\"),\n            ]\n        )\n        assert sorted(RedheadDuck.objects.values_list(\"name\", flat=True)) == [\n            \"redheadduck1\",\n            \"redheadduck2\",\n        ]\n        assert sorted(RubberDuck.objects.values_list(\"name\", flat=True)) == [\n            \"rubberduck1\",\n            \"rubberduck2\",\n        ]\n        assert sorted(Duck.objects.values_list(\"name\", flat=True)) == [\n            \"duck1\",\n            \"duck2\",\n            \"redheadduck1\",\n            \"redheadduck2\",\n            \"rubberduck1\",\n            \"rubberduck2\",\n        ]\n\n    def test_bulk_create_unsupported_multi_table_inheritance(self):\n        with pytest.raises(ValueError):\n            MultiTableDerived.objects.bulk_create(\n                [MultiTableDerived(field1=\"field1\", field2=\"field2\")]\n            )\n\n    def test_bulk_create_ignore_conflicts(self):\n        try:\n            ArtProject.objects.bulk_create(\n                [\n                    ArtProject(topic=\"Painting with Tim\", artist=\"T. Turner\"),\n                    ArtProject.objects.create(topic=\"Sculpture with Tim\", artist=\"T. Turner\"),\n                ],\n                ignore_conflicts=True,\n            )\n            assert ArtProject.objects.count() == 2\n        except NotSupportedError:\n            from django.db import connection\n\n            assert connection.vendor in (\"oracle\"), (\n                f\"{connection.vendor} should support ignore_conflicts\"\n            )\n\n    def test_bulk_create_no_ignore_conflicts(self):\n        with pytest.raises(IntegrityError):\n            ArtProject.objects.bulk_create(\n                [\n                    ArtProject(topic=\"Painting with Tim\", artist=\"T. Turner\"),\n                    ArtProject.objects.create(topic=\"Sculpture with Tim\", artist=\"T. Turner\"),\n                ],\n                ignore_conflicts=False,\n            )\n        assert ArtProject.objects.count() == 1\n\n    def test_can_query_using_subclass_selector_on_abstract_model(self):\n        obj = SubclassSelectorAbstractConcreteModel.objects.create(concrete_field=\"abc\")\n\n        queried_obj = SubclassSelectorAbstractBaseModel.objects.filter(\n            SubclassSelectorAbstractConcreteModel___concrete_field=\"abc\"\n        ).get()\n\n        assert obj.pk == queried_obj.pk\n\n    def test_intermediate_abstract_descriptors(self):\n        mdl = SubclassSelectorAbstractConcreteModel.objects.create()\n        base = SubclassSelectorAbstractBaseModel.objects.non_polymorphic().get(pk=mdl.pk)\n\n        assert mdl.subclassselectorabstractbasemodel_ptr == base\n        assert base.subclassselectorabstractconcretemodel == mdl\n\n    def test_can_query_using_subclass_selector_on_proxy_model(self):\n        obj = SubclassSelectorProxyConcreteModel.objects.create(concrete_field=\"abc\")\n\n        queried_obj = SubclassSelectorProxyBaseModel.objects.filter(\n            SubclassSelectorProxyConcreteModel___concrete_field=\"abc\"\n        ).get()\n\n        assert obj.pk == queried_obj.pk\n\n    def test_intermediate_proxy_descriptors(self):\n        mdl = SubclassSelectorProxyConcreteModel.objects.create()\n        base = SubclassSelectorProxyBaseModel.objects.non_polymorphic().get(pk=mdl.pk)\n\n        assert mdl.subclassselectorproxybasemodel_ptr == base\n        assert mdl.subclassselectorproxybasemodel_ptr.__class__ is SubclassSelectorProxyBaseModel\n        assert (\n            base.subclassselectorproxyconcretemodel.__class__ is SubclassSelectorProxyConcreteModel\n        )\n\n    def test_prefetch_related_behaves_normally_with_polymorphic_model(self):\n        b1 = RelatingModel.objects.create()\n        b2 = RelatingModel.objects.create()\n        a = b1.many2many.create()  # create Model2A\n        b2.many2many.add(a)  # add same to second relating model\n        qs = RelatingModel.objects.prefetch_related(\"many2many\")\n        for obj in qs:\n            assert len(obj.many2many.all()) == 1\n\n    def test_prefetch_related_with_missing(self):\n        b1 = RelatingModel.objects.create()\n        b2 = RelatingModel.objects.create()\n\n        rel1 = Model2A.objects.create(field1=\"A1\")\n        rel2 = Model2B.objects.create(field1=\"A2\", field2=\"B2\")\n\n        b1.many2many.add(rel1)\n        b2.many2many.add(rel2)\n\n        rel2_pk = rel2.pk  # Save pk before deletion\n        rel2.delete(keep_parents=True)\n\n        qs = RelatingModel.objects.order_by(\"pk\").prefetch_related(\"many2many\")\n        objects = list(qs)\n        assert len(objects[0].many2many.all()) == 1\n\n        # derived object was upcast by deletion that keeps parents\n        assert len(objects[1].many2many.all()) == 1\n        assert objects[1].many2many.first() == Model2A.objects.get(field1=\"A2\")\n        assert len(objects[1].many2many.all()) == 1\n        parent_obj = objects[1].many2many.all()[0]\n        assert parent_obj.pk == rel2_pk\n        assert isinstance(parent_obj, Model2A)\n        assert not isinstance(parent_obj, Model2B)\n\n        # base object does exist\n        assert len(objects[1].many2many.non_polymorphic()) == 1\n\n    def test_refresh_from_db_fields(self):\n        \"\"\"Test whether refresh_from_db(fields=..) works as it performs .only() queries\"\"\"\n        obj = Model2B.objects.create(field1=\"aa\", field2=\"bb\")\n        Model2B.objects.filter(pk=obj.pk).update(field1=\"aa1\", field2=\"bb2\")\n        obj.refresh_from_db(fields=[\"field2\"])\n        assert obj.field1 == \"aa\"\n        assert obj.field2 == \"bb2\"\n\n        obj.refresh_from_db(fields=[\"field1\"])\n        assert obj.field1 == \"aa1\"\n\n    def test_non_polymorphic_parent(self):\n        obj = NonPolymorphicParent.objects.create()\n        assert obj.delete()\n\n    def test_iteration(self):\n        for i in range(250):\n            Model2B.objects.create(field1=f\"B1-{i}\", field2=f\"B2-{i}\")\n        for i in range(1000):\n            Model2C.objects.create(\n                field1=f\"C1-{i + 250}\", field2=f\"C2-{i + 250}\", field3=f\"C3-{i + 250}\"\n            )\n        for i in range(2000):\n            Model2D.objects.create(\n                field1=f\"D1-{i + 1250}\",\n                field2=f\"D2-{i + 1250}\",\n                field3=f\"D3-{i + 1250}\",\n                field4=f\"D4-{i + 1250}\",\n            )\n\n        with CaptureQueriesContext(connection) as base_all:\n            for _ in Model2A.objects.non_polymorphic().all():\n                pass  # Evaluating the queryset\n\n        len_base_all = len(base_all)\n        assert len_base_all == 1, (\n            f\"Expected 1 queries for chunked iteration over 3250 base objects. {len_base_all}\"\n        )\n\n        with CaptureQueriesContext(connection) as base_iterator:\n            for _ in Model2A.objects.non_polymorphic().iterator():\n                pass  # Evaluating the queryset\n\n        len_base_iterator = len(base_iterator)\n        assert len_base_iterator == 1, (\n            f\"Expected 1 queries for chunked iteration over 3250 base objects. {len_base_iterator}\"\n        )\n\n        with CaptureQueriesContext(connection) as base_chunked:\n            for _ in Model2A.objects.non_polymorphic().iterator(chunk_size=1000):\n                pass  # Evaluating the queryset\n\n        len_base_chunked = len(base_chunked)\n        assert len_base_chunked == 1, (\n            f\"Expected 1 queries for chunked iteration over 3250 base objects. {len_base_chunked}\"\n        )\n\n        with CaptureQueriesContext(connection) as poly_all:\n            b, c, d = 0, 0, 0\n            for idx, obj in enumerate(reversed(list(Model2A.objects.order_by(\"-pk\").all()))):\n                if isinstance(obj, Model2D):\n                    d += 1\n                    assert obj.field1 == f\"D1-{idx}\"\n                    assert obj.field2 == f\"D2-{idx}\"\n                    assert obj.field3 == f\"D3-{idx}\"\n                    assert obj.field4 == f\"D4-{idx}\"\n                elif isinstance(obj, Model2C):\n                    c += 1\n                    assert obj.field1 == f\"C1-{idx}\"\n                    assert obj.field2 == f\"C2-{idx}\"\n                    assert obj.field3 == f\"C3-{idx}\"\n                elif isinstance(obj, Model2B):\n                    b += 1\n                    assert obj.field1 == f\"B1-{idx}\"\n                    assert obj.field2 == f\"B2-{idx}\"\n                else:\n                    assert False, \"Unexpected model type\"\n            assert (b, c, d) == (250, 1000, 2000)\n\n        assert len(poly_all) <= 8, (\n            f\"Expected < 7 queries for chunked iteration over 3250 \"\n            f\"objects with 3 child models and the default chunk size of 2000, encountered \"\n            f\"{len(poly_all)}\"\n        )\n\n        with CaptureQueriesContext(connection) as poly_all:\n            b, c, d = 0, 0, 0\n            for idx, obj in enumerate(Model2A.objects.order_by(\"pk\").iterator(chunk_size=None)):\n                if isinstance(obj, Model2D):\n                    d += 1\n                    assert obj.field1 == f\"D1-{idx}\"\n                    assert obj.field2 == f\"D2-{idx}\"\n                    assert obj.field3 == f\"D3-{idx}\"\n                    assert obj.field4 == f\"D4-{idx}\"\n                elif isinstance(obj, Model2C):\n                    c += 1\n                    assert obj.field1 == f\"C1-{idx}\"\n                    assert obj.field2 == f\"C2-{idx}\"\n                    assert obj.field3 == f\"C3-{idx}\"\n                elif isinstance(obj, Model2B):\n                    b += 1\n                    assert obj.field1 == f\"B1-{idx}\"\n                    assert obj.field2 == f\"B2-{idx}\"\n                else:\n                    assert False, \"Unexpected model type\"\n            assert (b, c, d) == (250, 1000, 2000)\n\n        assert len(poly_all) <= 7, (\n            f\"Expected < 7 queries for chunked iteration over 3250 \"\n            f\"objects with 3 child models and a chunk size of 2000, encountered \"\n            f\"{len(poly_all)}\"\n        )\n\n        with CaptureQueriesContext(connection) as poly_iterator:\n            b, c, d = 0, 0, 0\n            for idx, obj in enumerate(Model2A.objects.order_by(\"pk\").iterator()):\n                if isinstance(obj, Model2D):\n                    d += 1\n                    assert obj.field1 == f\"D1-{idx}\"\n                    assert obj.field2 == f\"D2-{idx}\"\n                    assert obj.field3 == f\"D3-{idx}\"\n                    assert obj.field4 == f\"D4-{idx}\"\n                elif isinstance(obj, Model2C):\n                    c += 1\n                    assert obj.field1 == f\"C1-{idx}\"\n                    assert obj.field2 == f\"C2-{idx}\"\n                    assert obj.field3 == f\"C3-{idx}\"\n                elif isinstance(obj, Model2B):\n                    b += 1\n                    assert obj.field1 == f\"B1-{idx}\"\n                    assert obj.field2 == f\"B2-{idx}\"\n                else:\n                    assert False, \"Unexpected model type\"\n            assert (b, c, d) == (250, 1000, 2000)\n\n        assert len(poly_iterator) <= 7, (\n            f\"Expected <= 7 queries for chunked iteration over 3250 \"\n            f\"objects with 3 child models and a default chunk size of 2000, encountered \"\n            f\"{len(poly_iterator)}\"\n        )\n\n        with CaptureQueriesContext(connection) as poly_chunked:\n            b, c, d = 0, 0, 0\n            for idx, obj in enumerate(Model2A.objects.order_by(\"pk\").iterator(chunk_size=4000)):\n                if isinstance(obj, Model2D):\n                    d += 1\n                    assert obj.field1 == f\"D1-{idx}\"\n                    assert obj.field2 == f\"D2-{idx}\"\n                    assert obj.field3 == f\"D3-{idx}\"\n                    assert obj.field4 == f\"D4-{idx}\"\n                elif isinstance(obj, Model2C):\n                    c += 1\n                    assert obj.field1 == f\"C1-{idx}\"\n                    assert obj.field2 == f\"C2-{idx}\"\n                    assert obj.field3 == f\"C3-{idx}\"\n                elif isinstance(obj, Model2B):\n                    b += 1\n                    assert obj.field1 == f\"B1-{idx}\"\n                    assert obj.field2 == f\"B2-{idx}\"\n                else:\n                    assert False, \"Unexpected model type\"\n            assert (b, c, d) == (250, 1000, 2000)\n\n        assert len(poly_chunked) <= 7, (\n            f\"Expected <= 7 queries for chunked iteration over 3250 objects with 3 child \"\n            f\"models and a chunk size of 4000, encountered {len(poly_chunked)}\"\n        )\n\n        if connection.vendor == \"postgresql\":\n            assert len(poly_chunked) == 4, \"On postgres with a 4000 chunk size, expected 4 queries\"\n\n        result = Model2A.objects.all().delete()\n        assert result == (\n            11500,\n            {\n                \"tests.Model2D\": 2000,\n                \"tests.Model2C\": 3000,\n                \"tests.Model2A\": 3250,\n                \"tests.Model2B\": 3250,\n            },\n        )\n\n    def test_transmogrify_with_init(self):\n        pur = PurpleHeadDuck.objects.create()\n        assert pur.color == \"blue\"\n        assert pur.home == \"Duckburg\"\n\n        pur = Duck.objects.get(id=pur.id)\n        assert pur.color == \"blue\"\n        # issues/615 fixes following line:\n        assert pur.home == \"Duckburg\"\n\n    def test_subqueries(self):\n        pa1 = PlainA.objects.create(field1=\"plain1\")\n        PlainA.objects.create(field1=\"plain2\")\n\n        ip1 = InlineParent.objects.create(title=\"parent1\")\n        ip2 = InlineParent.objects.create(title=\"parent2\")\n\n        ima1 = InlineModelA.objects.create(parent=ip1, field1=\"ima1\")\n        ima2 = InlineModelA.objects.create(parent=ip2, field1=\"ima2\")\n        imb1 = InlineModelB.objects.create(parent=ip1, field1=\"imab1\", field2=\"imb1\", plain_a=pa1)\n        imb2 = InlineModelB.objects.create(parent=ip2, field1=\"imab2\", field2=\"imb2\")\n\n        results = InlineModelA.objects.filter(\n            Exists(PlainA.objects.filter(inline_bs=OuterRef(\"pk\")))\n            | Exists(InlineParent.objects.filter(inline_children=OuterRef(\"pk\")))\n        )\n\n        assert ima1 in results\n        assert ima2 in results\n        assert imb1 in results\n        assert imb2 in results\n\n        results = InlineModelA.objects.filter(\n            Exists(PlainA.objects.filter(inline_bs=OuterRef(\"pk\"), field1=\"plain1\"))\n            | Exists(InlineParent.objects.filter(inline_children=OuterRef(\"pk\"), title=\"parent2\"))\n        )\n\n        assert ima1 not in results\n        assert ima2 in results\n        assert imb1 in results\n        assert imb2 in results\n\n        results = InlineModelA.objects.filter(\n            Exists(PlainA.objects.filter(inline_bs=OuterRef(\"pk\")))\n        )\n\n        assert ima1 not in results\n        assert ima2 not in results\n        assert imb1 in results\n        assert imb2 not in results\n\n        results = InlineModelA.objects.filter(\n            Exists(PlainA.objects.filter(inline_bs=OuterRef(\"pk\"))), field1=\"imab1\"\n        )\n\n        assert ima1 not in results\n        assert ima2 not in results\n        assert imb1 in results\n        assert imb2 not in results\n\n        results = InlineModelA.objects.filter(\n            Exists(PlainA.objects.filter(inline_bs=OuterRef(\"pk\"))), InlineModelB___field2=\"imb2\"\n        )\n\n        assert not results\n\n        results = InlineModelA.objects.filter(\n            ~Exists(PlainA.objects.filter(inline_bs=OuterRef(\"pk\"))), InlineModelB___field2=\"imb2\"\n        )\n\n        assert len(results) == 1\n        assert imb2 in results\n\n        PlainA.objects.all().delete()\n        InlineParent.objects.all().delete()\n        InlineModelA.objects.all().delete()\n        InlineModelB.objects.all().delete()\n\n    def test_one_to_one_primary_key(self):\n        # check pk name resolution\n\n        with warnings.catch_warnings(record=True) as w:\n            warnings.simplefilter(\"always\")\n\n            for mdl in [Account, SpecialAccount1, SpecialAccount1_1, SpecialAccount2]:\n                assert mdl.polymorphic_primary_key_name == Account._meta.pk.attname\n\n            assert w[0].category is DeprecationWarning\n            assert \"polymorphic_primary_key_name\" in str(w[0].message)\n\n        user1 = get_user_model().objects.create(\n            username=\"user1\", email=\"user1@example.com\", password=\"password\"\n        )\n        user2 = get_user_model().objects.create(\n            username=\"user2\", email=\"user2@example.com\", password=\"password\"\n        )\n        user3 = get_user_model().objects.create(\n            username=\"user3\", email=\"user3@example.com\", password=\"password\"\n        )\n        user4 = get_user_model().objects.create(\n            username=\"user4\", email=\"user4@example.com\", password=\"password\"\n        )\n\n        user1_profile = SpecialAccount1_1.objects.create(user=user1, extra1=5, extra2=6)\n\n        user2_profile = SpecialAccount1.objects.create(user=user2, extra1=5)\n\n        user3_profile = SpecialAccount2.objects.create(user=user3, extra1=\"test\")\n\n        user4_profile = SpecialAccount1_1.objects.create(user=user4, extra1=7, extra2=8)\n\n        user1.refresh_from_db()\n        assert user1.account.__class__ is SpecialAccount1_1\n        assert user1.account.extra1 == 5\n        assert user1.account.extra2 == 6\n        assert user1_profile.pk == user1.account.pk\n\n        user2.refresh_from_db()\n        assert user2.account.__class__ is SpecialAccount1\n        assert user2.account.extra1 == 5\n        assert user2_profile.pk == user2.account.pk\n        assert not hasattr(user2.account, \"extra2\")\n\n        user3.refresh_from_db()\n        assert user3.account.__class__ is SpecialAccount2\n        assert user3.account.extra1 == \"test\"\n        assert user3_profile.pk == user3.account.pk\n        assert not hasattr(user3.account, \"extra2\")\n\n        user4.refresh_from_db()\n        assert user4.account.__class__ is SpecialAccount1_1\n        assert user4.account.extra1 == 7\n        assert user4.account.extra2 == 8\n        assert user4_profile.pk == user4.account.pk\n\n        assert get_user_model().objects.filter(pk=user2.pk).delete() == (\n            3,\n            {\"tests.SpecialAccount1\": 1, \"tests.Account\": 1, \"auth.User\": 1},\n        )\n\n        assert SpecialAccount1.objects.count() == 2\n        assert Account.objects.count() == 3\n\n        remaining = get_user_model().objects.filter(\n            pk__in=[user1.pk, user2.pk, user3.pk, user4.pk]\n        )\n        assert remaining.count() == 3\n        for usr, expected in zip(\n            remaining.order_by(\"pk\"), (user1_profile, user3_profile, user4_profile)\n        ):\n            assert usr.account == expected\n\n        assert get_user_model().objects.filter(pk__in=[user3.pk]).delete() == (\n            3,\n            {\"tests.SpecialAccount2\": 1, \"tests.Account\": 1, \"auth.User\": 1},\n        )\n\n        assert Account.objects.count() == 2\n\n        assert SpecialAccount1_1.objects.all().delete() == (\n            6,\n            {\"tests.SpecialAccount1_1\": 2, \"tests.SpecialAccount1\": 2, \"tests.Account\": 2},\n        )\n\n        assert Account.objects.count() == 0\n\n        remaining = get_user_model().objects.filter(pk__gte=user1.pk)\n        assert remaining.count() == 2\n        for usr in remaining:\n            assert not hasattr(usr, \"account\")\n\n        assert get_user_model().objects.filter(pk__in=[user1.pk, user4.pk]).delete() == (\n            2,\n            {\"auth.User\": 2},\n        )\n\n    def test_manager_override(self):\n        from polymorphic.tests.models import MyBaseModel, MyChild1Model, MyChild2Model\n\n        child1 = MyChild1Model.objects.create(fieldA=4)\n        child2 = MyChild2Model.objects.create(fieldB=6)\n\n        assert MyBaseModel.objects.filter_by_user(5).count() == 2\n        assert child1 in MyBaseModel.objects.all()\n        assert child2 in MyBaseModel.objects.all()\n        assert MyChild1Model.objects.filter_by_user(5).count() == 1\n        assert MyChild2Model.objects.filter_by_user(5).count() == 1\n        assert MyChild1Model.objects.filter_by_user(5).count() == 1\n        assert MyChild1Model.objects.filter_by_user(5).first() == child1\n        assert MyChild2Model.objects.filter_by_user(5).first() == child2\n\n        assert MyChild2Model._default_manager is MyChild2Model.objects\n        MyChild2Model.objects.filter_by_user(6).count() == 0\n        MyChild2Model.base_manager.filter_by_user(6).count() == 1\n\n    def test_abstract_managers(self):\n        from django.db.models import Manager\n        from polymorphic.tests.models import (\n            AbstractManagerTest,\n            DerivedManagerTest,\n            DerivedManagerTest2,\n            SpecialPolymorphicManager,\n            SpecialQuerySet,\n            RelatedManagerTest,\n        )\n\n        with self.assertRaises(AttributeError):\n            AbstractManagerTest.objects\n        with self.assertRaises(AttributeError):\n            AbstractManagerTest.basic_manager\n        with self.assertRaises(AttributeError):\n            AbstractManagerTest.default_manager\n\n        assert type(DerivedManagerTest.objects) is SpecialPolymorphicManager\n        assert type(DerivedManagerTest.basic_manager) is Manager\n        assert type(DerivedManagerTest.default_manager) is PolymorphicManager\n        assert type(DerivedManagerTest._default_manager) is SpecialPolymorphicManager\n\n        assert type(DerivedManagerTest2.objects) is PolymorphicManager\n        assert type(DerivedManagerTest2.basic_manager) is Manager\n        assert type(DerivedManagerTest2.default_manager) is PolymorphicManager\n        assert type(DerivedManagerTest2._default_manager) is PolymorphicManager\n\n        dmt1 = DerivedManagerTest.objects.create(abstract_field=\"dmt1\")\n        dmt2 = DerivedManagerTest2.objects.create(abstract_field=\"dmt2\")\n\n        assert DerivedManagerTest.objects.has_text(\"dmt\").count() == 2\n        assert dmt1 in DerivedManagerTest.objects.has_text(\"dmt\")\n        assert dmt2 in DerivedManagerTest.objects.has_text(\"dmt\")\n        assert DerivedManagerTest.objects.custom_queryset().has_text(\"dmt\").count() == 2\n\n        assert isinstance(DerivedManagerTest.objects.has_text(\"dmt\"), SpecialQuerySet)\n\n        with self.assertRaises(AttributeError):\n            DerivedManagerTest2.objects.has_text(\"dmt\")\n\n        related = RelatedManagerTest.objects.create()\n        assert isinstance(related.derived, SpecialPolymorphicManager)\n\n    def test_fk_polymorphism(self):\n        from polymorphic.tests.models import FKTest, FKTestChild\n\n        child = FKTestChild.objects.create()\n\n        fk_test = FKTest.objects.create(fk=child)\n\n        assert fk_test.fk is child\n\n        fk_test = FKTest.objects.get(pk=fk_test.id)\n\n        assert fk_test.fk == child\n        assert isinstance(fk_test.fk, FKTestChild)\n\n    def test_polymorphic_extension(self):\n        from polymorphic.tests.models import (\n            NormalBase,\n            NormalExtension,\n            PolyExtension,\n            PolyExtChild,\n        )\n\n        nb = NormalBase.objects.create(nb_field=5)\n        ne = NormalExtension.objects.create(nb_field=6, ne_field=\"normal ext\")\n        poly_ext = PolyExtension.objects.create(nb_field=6, ne_field=\"poly ext\", poly_ext_field=7)\n        child_ext = PolyExtChild.objects.create(\n            nb_field=7, ne_field=\"child ext\", poly_ext_field=8, poly_child_field=\"poly child\"\n        )\n        assert set(NormalBase.objects.all()) == {\n            nb,\n            NormalBase.objects.get(pk=ne.pk),\n            NormalBase.objects.get(pk=poly_ext.pk),\n            NormalBase.objects.get(pk=child_ext.pk),\n        }\n        assert set(NormalExtension.objects.all()) == {\n            NormalExtension.objects.get(pk=ne.pk),\n            NormalExtension.objects.get(pk=poly_ext.pk),\n            NormalExtension.objects.get(pk=child_ext.pk),\n        }\n        assert set(PolyExtension.objects.all()) == {poly_ext, child_ext}\n        assert set(PolyExtChild.objects.all()) == {child_ext}\n\n    def test_manytomany_without_through_field(self):\n        from polymorphic.tests.models import Lake, RedheadDuck, RubberDuck\n\n        lake = Lake.objects.create()\n        rubber = RubberDuck.objects.create(name=\"Rubber\")\n        redhead = RedheadDuck.objects.create(name=\"Redheat\")\n        lake.ducks.add(rubber)\n        lake.ducks.add(redhead)\n        self.assertEqual(lake.ducks.count(), 2)\n        self.assertIsInstance(lake.ducks.all()[0], RubberDuck)\n        self.assertIsInstance(lake.ducks.all()[1], RedheadDuck)\n\n    def test_manytomany_with_through_field(self):\n        from polymorphic.tests.models import LakeWithThrough, DucksLake, RedheadDuck, RubberDuck\n\n        lake = LakeWithThrough.objects.create()\n        rubber = RubberDuck.objects.create(name=\"Rubber\")\n        redhead = RedheadDuck.objects.create(name=\"Redheat\")\n        DucksLake.objects.create(lake=lake, duck=rubber, time=\"morning\")\n        DucksLake.objects.create(lake=lake, duck=redhead, time=\"afternoon\")\n        self.assertEqual(lake.ducks.count(), 2)\n        self.assertIsInstance(lake.ducks.all()[0], RubberDuck)\n        self.assertIsInstance(lake.ducks.all()[1], RedheadDuck)\n\n    def test_create_from_super(self):\n        # run create test 3 times because initial implementation\n        # would fail after first success.\n        from polymorphic.tests.models import (\n            NormalBase,\n            NormalExtension,\n            PolyExtension,\n            PolyExtChild,\n            CustomPkBase,\n            CustomPkInherit,\n        )\n\n        nb = NormalBase.objects.create(nb_field=1)\n        ne = NormalExtension.objects.create(nb_field=2, ne_field=\"ne2\")\n\n        with self.assertRaises(TypeError):\n            PolyExtension.objects.create_from_super(nb, poly_ext_field=3)\n\n        with CaptureQueriesContext(connection) as ctx:\n            pe = PolyExtension.objects.create_from_super(ne, poly_ext_field=3)\n\n        # for q in ctx.captured_queries:\n        #     print(q[\"sql\"])\n\n        ne.refresh_from_db()\n        self.assertEqual(type(ne), NormalExtension)\n        self.assertEqual(type(pe), PolyExtension)\n        self.assertEqual(pe.pk, ne.pk)\n\n        self.assertEqual(pe.nb_field, 2)\n        self.assertEqual(pe.ne_field, \"ne2\")\n        self.assertEqual(pe.poly_ext_field, 3)\n        pe.refresh_from_db()\n        self.assertEqual(pe.nb_field, 2)\n        self.assertEqual(pe.ne_field, \"ne2\")\n        self.assertEqual(pe.poly_ext_field, 3)\n\n        print(\"===================================\")\n\n        with CaptureQueriesContext(connection) as ctx:\n            \"\"\"\n            BEGIN\n            SELECT \"django_content_type\".\"id\", \"django_content_type\".\"app_label\", \"django_content_type\".\"model\" FROM \"django_content_type\" WHERE (\"django_content_type\".\"app_label\" = 'tests' AND \"django_content_type\".\"model\" = 'polyextchild') LIMIT 21\n            INSERT INTO \"tests_polyextchild\" (\"polyextension_ptr_id\", \"poly_child_field\") VALUES (2, 'pcf6')\n            SELECT \"tests_normalbase\".\"id\", \"tests_normalbase\".\"nb_field\", \"tests_normalextension\".\"normalbase_ptr_id\", \"tests_normalextension\".\"ne_field\", \"tests_polyextension\".\"normalextension_ptr_id\", \"tests_polyextension\".\"polymorphic_ctype_id\", \"tests_polyextension\".\"poly_ext_field\" FROM \"tests_polyextension\" INNER JOIN \"tests_normalextension\" ON (\"tests_polyextension\".\"normalextension_ptr_id\" = \"tests_normalextension\".\"normalbase_ptr_id\") INNER JOIN \"tests_normalbase\" ON (\"tests_normalextension\".\"normalbase_ptr_id\" = \"tests_normalbase\".\"id\") WHERE \"tests_polyextension\".\"normalextension_ptr_id\" = 2 LIMIT 21\n            UPDATE \"tests_normalbase\" SET \"nb_field\" = 2 WHERE \"tests_normalbase\".\"id\" = 2\n            UPDATE \"tests_normalextension\" SET \"ne_field\" = 'ne2' WHERE \"tests_normalextension\".\"normalbase_ptr_id\" = 2\n            UPDATE \"tests_polyextension\" SET \"polymorphic_ctype_id\" = 100, \"poly_ext_field\" = 3 WHERE \"tests_polyextension\".\"normalextension_ptr_id\" = 2\n            SELECT \"tests_normalbase\".\"id\", \"tests_normalbase\".\"nb_field\", \"tests_normalextension\".\"normalbase_ptr_id\", \"tests_normalextension\".\"ne_field\", \"tests_polyextension\".\"normalextension_ptr_id\", \"tests_polyextension\".\"polymorphic_ctype_id\", \"tests_polyextension\".\"poly_ext_field\", \"tests_polyextchild\".\"polyextension_ptr_id\", \"tests_polyextchild\".\"poly_child_field\" FROM \"tests_polyextchild\" INNER JOIN \"tests_polyextension\" ON (\"tests_polyextchild\".\"polyextension_ptr_id\" = \"tests_polyextension\".\"normalextension_ptr_id\") INNER JOIN \"tests_normalextension\" ON (\"tests_polyextension\".\"normalextension_ptr_id\" = \"tests_normalextension\".\"normalbase_ptr_id\") INNER JOIN \"tests_normalbase\" ON (\"tests_normalextension\".\"normalbase_ptr_id\" = \"tests_normalbase\".\"id\") WHERE \"tests_polyextchild\".\"polyextension_ptr_id\" = 2 LIMIT 21\n            COMMIT\n            \"\"\"\n            pc = PolyExtChild.objects.create_from_super(pe, poly_child_field=\"pcf6\")\n\n        # for q in ctx.captured_queries:\n        #     print(q[\"sql\"])\n\n        pe.refresh_from_db()\n        ne.refresh_from_db()\n        self.assertEqual(type(ne), NormalExtension)\n        self.assertEqual(type(pe), PolyExtension)\n        self.assertEqual(pe.pk, ne.pk)\n        self.assertEqual(pe.pk, pc.pk)\n\n        self.assertEqual(pc.nb_field, 2)\n        self.assertEqual(pc.ne_field, \"ne2\")\n        self.assertEqual(pc.poly_ext_field, 3)\n        pc.refresh_from_db()\n        self.assertEqual(pc.nb_field, 2)\n        self.assertEqual(pc.ne_field, \"ne2\")\n        self.assertEqual(pc.poly_ext_field, 3)\n        self.assertEqual(pc.poly_child_field, \"pcf6\")\n\n        self.assertEqual(pe.polymorphic_ctype, ContentType.objects.get_for_model(PolyExtChild))\n        self.assertEqual(pc.polymorphic_ctype, ContentType.objects.get_for_model(PolyExtChild))\n\n        self.assertEqual(set(PolyExtension.objects.all()), {pc})\n\n        a1 = Model2A.objects.create(field1=\"A1a\")\n        a2 = Model2A.objects.create(field1=\"A1b\")\n\n        b1 = Model2B.objects.create(field1=\"B1a\", field2=\"B2a\")\n        b2 = Model2B.objects.create(field1=\"B1b\", field2=\"B2b\")\n\n        c1 = Model2C.objects.create(field1=\"C1a\", field2=\"C2a\", field3=\"C3a\")\n        c2 = Model2C.objects.create(field1=\"C1b\", field2=\"C2b\", field3=\"C3b\")\n\n        d1 = Model2D.objects.create(field1=\"D1a\", field2=\"D2a\", field3=\"D3a\", field4=\"D4a\")\n        d2 = Model2D.objects.create(field1=\"D1b\", field2=\"D2b\", field3=\"D3b\", field4=\"D4b\")\n\n        with self.assertRaises(TypeError):\n            Model2D.objects.create_from_super(b1, field3=\"D3x\", field4=\"D4x\")\n\n        b1_of_c = Model2B.objects.non_polymorphic().get(pk=c1.pk)\n        with self.assertRaises(TypeError):\n            Model2C.objects.create_from_super(b1_of_c, field3=\"C3x\")\n\n        self.assertEqual(c1.polymorphic_ctype, ContentType.objects.get_for_model(Model2C))\n        dfs1 = Model2D.objects.create_from_super(b1_of_c, field4=\"D4x\")\n        self.assertEqual(type(dfs1), Model2D)\n        self.assertEqual(dfs1.pk, c1.pk)\n        self.assertEqual(dfs1.field1, \"C1a\")\n        self.assertEqual(dfs1.field2, \"C2a\")\n        self.assertEqual(dfs1.field3, \"C3a\")\n        self.assertEqual(dfs1.field4, \"D4x\")\n        self.assertEqual(dfs1.polymorphic_ctype, ContentType.objects.get_for_model(Model2D))\n        c1.refresh_from_db()\n        self.assertEqual(c1.polymorphic_ctype, ContentType.objects.get_for_model(Model2D))\n\n        self.assertEqual(b2.polymorphic_ctype, ContentType.objects.get_for_model(Model2B))\n        cfs1 = Model2C.objects.create_from_super(b2, field3=\"C3y\")\n        self.assertEqual(type(cfs1), Model2C)\n        self.assertEqual(cfs1.pk, b2.pk)\n        self.assertEqual(cfs1.field1, \"B1b\")\n        self.assertEqual(cfs1.field2, \"B2b\")\n        self.assertEqual(cfs1.field3, \"C3y\")\n        b2.refresh_from_db()\n        self.assertEqual(b2.polymorphic_ctype, ContentType.objects.get_for_model(Model2C))\n        self.assertEqual(cfs1.polymorphic_ctype, ContentType.objects.get_for_model(Model2C))\n\n        self.assertEqual(set(Model2A.objects.all()), {a1, a2, b1, dfs1, cfs1, c2, d1, d2})\n\n        custom_pk = CustomPkBase.objects.create(b=\"0\")\n        custom_pk_ext = CustomPkInherit.objects.create_from_super(custom_pk, i=\"4\")\n        self.assertEqual(type(custom_pk_ext), CustomPkInherit)\n        custom_pk_ext.refresh_from_db()\n        self.assertEqual(custom_pk_ext.id, custom_pk.id)\n        self.assertEqual(CustomPkBase.objects.get(pk=custom_pk.id), custom_pk_ext)\n        self.assertEqual(CustomPkBase.objects.count(), 1)\n\n        custom_pk2 = CustomPkBase.objects.create(b=\"2\")\n        custom_pk_ext2 = CustomPkInherit.objects.create_from_super(\n            custom_pk2, custom_id=100, i=\"4\"\n        )\n        self.assertEqual(type(custom_pk_ext2), CustomPkInherit)\n        custom_pk_ext2.refresh_from_db()\n        self.assertEqual(custom_pk_ext2.id, custom_pk2.id)\n        self.assertEqual(custom_pk_ext2.custom_id, 100)\n        self.assertEqual(CustomPkBase.objects.get(pk=custom_pk2.id), custom_pk_ext2)\n        self.assertEqual(CustomPkBase.objects.count(), 2)\n\n    def test_create_from_super_child_exists(self):\n        \"\"\"\n        Test several scenarios creating a child row where a parent already exists.\n\n        Should get integrity errors!\n        \"\"\"\n        from polymorphic.tests.models import (\n            NormalExtension,\n            PolyExtension,\n            CustomPkBase,\n            CustomPkInherit,\n        )\n\n        pe1 = PolyExtension.objects.create(nb_field=10, ne_field=\"ne10\", poly_ext_field=20)\n        ne1 = NormalExtension.objects.get(pk=pe1.pk)\n\n        with self.assertRaises(IntegrityError):\n            PolyExtension.objects.create_from_super(ne1, poly_ext_field=30)\n\n        # FIXME: uncomment when #686 is fixed\n        # CustomPkInherit.objects.create(b=\"base1\", i=\"1\")\n        # with self.assertRaises(IntegrityError):\n        #     CustomPkInherit.objects.create_from_super(\n        #         CustomPkBase.objects.non_polymorphic().first(), i=\"2\"\n        #     )\n\n    def test_through_models_creates_and_reads(self):\n        from polymorphic.tests.models import (\n            BetMultiple,\n            ChoiceAthlete,\n            ChoiceBlank,\n            RankedAthlete,\n        )\n\n        bet = BetMultiple.objects.create()\n\n        a1 = ChoiceAthlete.objects.create(choice=\"Alice\")\n        a2 = ChoiceAthlete.objects.create(choice=\"Bob\")\n        a3 = ChoiceBlank.objects.create()\n\n        # Exercise the \"through\" model via the M2M manager using through_defaults.\n        bet.answer.add(a1, through_defaults={\"rank\": 2})\n        bet.answer.add(a2, through_defaults={\"rank\": 1})\n        bet.answer.add(a3, through_defaults={\"rank\": 3})  # ChoiceBlank also works\n\n        # Through rows were created with rank preserved\n        rows = list(\n            RankedAthlete.objects.filter(bet=bet)\n            .order_by(\"rank\")\n            .values_list(\"choiceAthlete_id\", \"rank\")\n        )\n        assert rows == [(a2.pk, 1), (a1.pk, 2), (a3.pk, 3)]\n\n        # Reading back via the M2M returns polymorphic instances (ChoiceAthlete, not ChoiceBlank)\n        answers = list(bet.answer.order_by(\"rankedathlete__rank\"))\n        assert answers == [a2, a1, a3]\n        assert isinstance(answers[0], ChoiceAthlete)\n        assert isinstance(answers[1], ChoiceAthlete)\n        assert isinstance(answers[2], ChoiceBlank)\n\n        # Sanity: the through model is the one we expect\n        assert bet.answer.through is RankedAthlete\n        assert isinstance(bet.answer, PolymorphicManager)\n\n    def test_through_model_updates(self):\n        from polymorphic.tests.models import BetMultiple, ChoiceAthlete, RankedAthlete, ChoiceBlank\n\n        bet = BetMultiple.objects.create()\n        a1 = ChoiceAthlete.objects.create(choice=\"Alice\")\n        a2 = ChoiceBlank.objects.create()\n\n        bet.answer.add(a2, through_defaults={\"rank\": 0})\n        bet.answer.add(a1, through_defaults={\"rank\": 1})\n        ra = RankedAthlete.objects.get(bet=bet, choiceAthlete=a1)\n        assert ra.rank == 1\n\n        ra.rank = 99\n        ra.save(update_fields=[\"rank\"])\n\n        ra2 = RankedAthlete.objects.get(bet=bet, choiceAthlete=a2)\n        assert ra2.rank == 0\n\n        # ordering uses the through-table rank\n        assert list(bet.answer.order_by(\"rankedathlete__rank\")) == [a2, a1]\n        assert RankedAthlete.objects.get(pk=ra.pk).rank == 99\n        assert RankedAthlete.objects.get(pk=ra2.pk).rank == 0\n\n    def test_infinite_recursion_with_only(self):\n        \"\"\"\n        https://github.com/jazzband/django-polymorphic/issues/334\n        \"\"\"\n        from polymorphic.tests.models import RecursionBug\n\n        draft = PlainA.objects.create(field1=\"draft\")\n        closed = PlainA.objects.create(field1=\"closed\")\n\n        assert isinstance(closed.recursions, PolymorphicManager)\n\n        item = RecursionBug.objects.create(status=draft)\n        RecursionBug.objects.filter(id=item.id).update(status=closed)\n        item.refresh_from_db(fields=(\"status\",))\n        assert item.status == closed\n\n    @pytest.mark.skipif(\n        Version(django.get_version()) < Version(\"5.0\"),\n        reason=\"Requires Django 5.0+\",\n    )\n    def test_generic_relation_prefetch(self):\n        \"\"\"\n        https://github.com/jazzband/django-polymorphic/issues/613\n        \"\"\"\n        from polymorphic.tests.models import Bookmark, TaggedItem, Assignment\n        from django.contrib.contenttypes.prefetch import GenericPrefetch\n\n        bm1 = Bookmark.objects.create(url=\"http://example.com/1\")\n        ass = Assignment.objects.create(url=\"http://example.com/2\", assigned_to=\"Alice\")\n\n        TaggedItem.objects.create(tag=\"tag1\", content_object=bm1)\n        TaggedItem.objects.create(tag=\"tag2\", content_object=ass)\n\n        bookmarks = list(Bookmark.objects.prefetch_related(\"tags\").order_by(\"pk\"))\n        assert len(bookmarks) == 2\n        assert list(bookmarks[0].tags.all()) == [TaggedItem.objects.get(tag=\"tag1\")]\n        assert list(bookmarks[1].tags.all()) == [TaggedItem.objects.get(tag=\"tag2\")]\n        assert bookmarks[0].__class__ is Bookmark\n        assert bookmarks[1].__class__ is Assignment\n\n        tags = TaggedItem.objects.prefetch_related(\n            GenericPrefetch(\n                lookup=\"content_object\",\n                querysets=[\n                    Bookmark.objects.all(),\n                ],\n            ),\n        ).order_by(\"pk\")\n\n        assert tags[0].content_object == bookmarks[0]\n        assert tags[1].content_object == bookmarks[1]\n\n        for tag in tags.all():\n            assert tag.content_object\n\n    def test_besteffort_iteration(self):\n        \"\"\"\n        Test that our best effort iteration avoids n+1 queries when n objects have stale\n        content type pointers.\n        \"\"\"\n        for i in range(100):\n            Model2A.objects.create(field1=f\"Model2C_{i}\")\n\n        # force stale ctype condition\n        Model2A.objects.all().update(polymorphic_ctype=ContentType.objects.get_for_model(Model2C))\n\n        assert Model2C.objects.count() == 0\n        assert Model2B.objects.count() == 0\n        assert Model2A.objects.count() == 100\n\n        with CaptureQueriesContext(connection) as initial_all_2a:\n            for obj in Model2A.objects.all():\n                assert obj.__class__ is Model2A\n\n        assert len(initial_all_2a.captured_queries) <= 4\n\n    def test_besteffort_get_real_instance(self):\n        obj = Model2B.objects.create(field1=\"TestB\", field2=\"TestB2\")\n        obj.polymorphic_ctype = ContentType.objects.get_for_model(Model2C)\n        obj.save()\n        as_a = Model2A.objects.non_polymorphic().get(pk=obj.pk)\n        assert as_a.__class__ is Model2A\n        should_be_b = as_a.get_real_instance()\n        assert should_be_b.__class__ is Model2B\n        # ctype should still be wrong\n        assert should_be_b.polymorphic_ctype == ContentType.objects.get_for_model(Model2C)\n\n    def test_queryset_first_returns_none_on_empty_queryset(self):\n        self.assertIsNone(Model2A.objects.first())\n\n    def test_queryset_getitem_raises_indexerror_on_empty_queryset(self):\n        with self.assertRaises(IndexError):\n            _ = Model2A.objects.all()[0]\n\n    def test_queryset_getitem_negative_index_raises_valueerror(self):\n        Model2A.objects.create(field1=\"OnlyOne\")\n        with self.assertRaises(ValueError):\n            _ = Model2A.objects.all()[-1]\n\n    def test_queryset_getitem_slice_returns_objects(self):\n        Model2A.objects.create(field1=\"First\")\n        Model2A.objects.create(field1=\"Second\")\n        objs = Model2A.objects.all()[0:2]\n        self.assertEqual(len(objs), 2)\n        self.assertEqual([o.field1 for o in objs], [\"First\", \"Second\"])\n\n    def test_aggregate_with_filtered_relation(self):\n        \"\"\"Test _process_aggregate_args with FilteredRelation (lines 273-280)\"\"\"\n        # Create test data\n        a1 = Model2A.objects.create(field1=\"A1\")\n        b1 = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        c1 = Model2C.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n\n        # Create related objects\n        rel1 = RelatingModel.objects.create()\n        rel2 = RelatingModel.objects.create()\n        rel1.many2many.add(a1, b1)\n        rel2.many2many.add(c1)\n\n        # Test FilteredRelation with annotate\n        # This exercises the patch_lookup function with FilteredRelation\n        qs = RelatingModel.objects.annotate(\n            filtered_m2m=FilteredRelation(\n                \"many2many\", condition=Q(many2many__field1__startswith=\"B\")\n            )\n        ).filter(filtered_m2m__isnull=False)\n\n        assert rel1 in qs\n        assert rel2 not in qs\n\n    def test_aggregate_with_nested_q_objects(self):\n        \"\"\"Test _process_aggregate_args with nested Q objects (lines 285-298)\"\"\"\n        a1 = Model2A.objects.create(field1=\"A1\")\n        b1 = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        c1 = Model2C.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n\n        # Test with nested Q objects in annotate\n        # This exercises the tree_node_test___lookup function\n        result = Model2A.objects.annotate(\n            has_b_field=Case(\n                When(Q(field1__startswith=\"B\") | Q(field1__startswith=\"C\"), then=1),\n                default=0,\n                output_field=models.IntegerField(),\n            )\n        ).filter(has_b_field=1)\n\n        assert b1 in result\n        assert c1 in result\n        assert a1 not in result\n\n    def test_aggregate_with_subclass_field_in_expression(self):\n        \"\"\"Test _process_aggregate_args with source expressions (lines 275-278, 300-303)\"\"\"\n        b1 = Model2B.objects.create(field1=\"B1\", field2=\"100\")\n        b2 = Model2B.objects.create(field1=\"B2\", field2=\"200\")\n        c1 = Model2C.objects.create(field1=\"C1\", field2=\"150\", field3=\"C3\")\n\n        # Test with complex expression containing field references\n        # This exercises the get_source_expressions path\n        from django.db.models import F, Value\n        from django.db.models.functions import Concat\n\n        result = Model2A.objects.annotate(\n            combined=Concat(F(\"field1\"), Value(\" - \"), F(\"Model2B___field2\"))\n        ).filter(Model2B___field2__isnull=False)\n\n        assert b1 in result\n        assert b2 in result\n        assert c1 in result  # C inherits from B\n\n    def test_get_best_effort_instance_with_missing_derived(self):\n        \"\"\"Test _get_best_effort_instance when derived class is missing (lines 339-387)\"\"\"\n        # Create a Model2C object (which inherits from Model2B -> Model2A)\n        c1 = Model2C.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n        c1_pk = c1.pk\n\n        # Delete the Model2C part but keep Model2B and Model2A parts\n        # This simulates a partially deleted object\n        c1.delete(keep_parents=True)\n\n        # Now try to fetch it - should fall back to Model2B\n        result = list(Model2A.objects.filter(pk=c1_pk))\n        assert len(result) == 1\n        assert result[0].pk == c1_pk\n        assert isinstance(result[0], Model2B)\n        assert not isinstance(result[0], Model2C)\n\n    def test_get_best_effort_instance_with_annotations(self):\n        \"\"\"Test _get_best_effort_instance preserves annotations (lines 367-374)\"\"\"\n        # Create a Model2C object\n        c1 = Model2C.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n        c1_pk = c1.pk\n\n        # Add annotation\n        annotated = Model2A.objects.annotate(field_count=Count(\"field1\")).filter(pk=c1_pk)\n\n        # Delete the Model2C part\n        c1.delete(keep_parents=True)\n\n        # Fetch with annotation - should preserve annotation on fallback object\n        result = list(annotated)\n        assert len(result) == 1\n        assert hasattr(result[0], \"field_count\")\n        assert result[0].field_count == 1\n\n    def test_get_best_effort_instance_with_extra_select(self):\n        \"\"\"Test _get_best_effort_instance preserves extra select (lines 376-379)\"\"\"\n        # Create a Model2C object\n        c1 = Model2C.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n        c1_pk = c1.pk\n\n        # Add extra select\n        qs = Model2A.objects.extra(select={\"upper_field1\": \"UPPER(field1)\"}).filter(pk=c1_pk)\n\n        # Delete the Model2C part\n        c1.delete(keep_parents=True)\n\n        # Fetch with extra - should preserve extra on fallback object\n        result = list(qs)\n        assert len(result) == 1\n        assert hasattr(result[0], \"upper_field1\")\n        assert result[0].upper_field1 == \"C1\"\n\n    def test_get_best_effort_instance_multiple_inheritance_levels(self):\n        \"\"\"Test _get_best_effort_instance walks up multiple levels (lines 351-384)\"\"\"\n        # Create a Model2D object (D -> C -> B -> A)\n        d1 = Model2D.objects.create(field1=\"D1\", field2=\"D2\", field3=\"D3\", field4=\"D4\")\n        d1_pk = d1.pk\n\n        # Delete Model2D part, keep Model2C\n        d1.delete(keep_parents=True)\n\n        # Should fall back to Model2C\n        result = list(Model2A.objects.filter(pk=d1_pk))\n        assert len(result) == 1\n        assert isinstance(result[0], Model2C)\n        assert not isinstance(result[0], Model2D)\n\n        # Now delete Model2C part too\n        c1 = Model2C.objects.get(pk=d1_pk)\n        c1.delete(keep_parents=True)\n\n        # Should fall back to Model2B\n        result = list(Model2A.objects.filter(pk=d1_pk))\n        assert len(result) == 1\n        assert isinstance(result[0], Model2B)\n        assert not isinstance(result[0], Model2C)\n\n    def test_deferred_loading_with_subclass_syntax(self):\n        \"\"\"Test deferred loading with Model___field syntax (lines 481-505)\"\"\"\n        b1 = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        c1 = Model2C.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n\n        # Test defer with subclass field syntax\n        qs = Model2A.objects.defer(\"Model2B___field2\")\n        result = list(qs)\n\n        # field2 should be deferred for Model2B instances\n        b_obj = [r for r in result if r.pk == b1.pk][0]\n        assert isinstance(b_obj, Model2B)\n        # Accessing deferred field should trigger a query\n        assert b_obj.field2 == \"B2\"\n\n    def test_deferred_loading_with_nonexistent_field(self):\n        \"\"\"Test deferred loading handles non-existent fields gracefully (lines 496-501)\"\"\"\n        b1 = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n\n        # Try to defer a field that doesn't exist in Model2B using subclass syntax\n        # This should be handled gracefully (field doesn't exist in this subclass)\n        qs = Model2A.objects.defer(\"Model2C___field3\")\n        result = list(qs)\n\n        # Should still work, just ignoring the non-existent field for Model2B\n        assert len(result) == 1\n        assert result[0].field1 == \"B1\"\n\n    def test_only_with_subclass_syntax(self):\n        \"\"\"Test only() with Model___field syntax (lines 214-226)\"\"\"\n        b1 = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        c1 = Model2C.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n\n        # Test only with subclass field syntax\n        qs = Model2A.objects.only(\"field1\", \"Model2B___field2\")\n        result = list(qs)\n\n        # Only field1 and field2 should be loaded for Model2B instances\n        b_obj = [r for r in result if r.pk == b1.pk][0]\n        assert isinstance(b_obj, Model2B)\n        assert b_obj.field1 == \"B1\"\n        assert b_obj.field2 == \"B2\"\n\n    def test_real_instances_with_stale_content_type(self):\n        \"\"\"Test _get_real_instances handles stale content types (lines 451-453)\"\"\"\n        # This test verifies the stale content type handling by checking\n        # that objects with invalid content type IDs are skipped gracefully\n        # We'll use the existing prefetch_related_with_missing test pattern\n        # which already covers this scenario\n        pass  # Covered by test_prefetch_related_with_missing\n\n    def test_real_instances_with_proxy_model(self):\n        \"\"\"Test _get_real_instances handles proxy models (lines 527-529)\"\"\"\n        # Create a proxy model instance\n        proxy = ProxyModelA.objects.create(field1=\"Proxy1\")\n\n        # Fetch through base class\n        result = list(ProxyModelBase.objects.filter(pk=proxy.pk))\n        assert len(result) == 1\n        assert isinstance(result[0], ProxyModelA)\n        assert result[0].field1 == \"Proxy1\"\n\n    def test_annotate_with_polymorphic_field_path(self):\n        \"\"\"Test annotate with polymorphic field paths (lines 312-316)\"\"\"\n        b1 = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        b2 = Model2B.objects.create(field1=\"B2\", field2=\"B3\")\n        c1 = Model2C.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n\n        # Test annotate with subclass field\n        result = Model2A.objects.annotate(b_field_count=Count(\"Model2B___field2\"))\n\n        # All objects should be returned with annotation\n        assert result.count() == 3\n\n    def test_aggregate_with_polymorphic_field_path(self):\n        \"\"\"Test aggregate with polymorphic field paths (lines 318-323)\"\"\"\n        b1 = Model2B.objects.create(field1=\"B1\", field2=\"10\")\n        b2 = Model2B.objects.create(field1=\"B2\", field2=\"20\")\n        c1 = Model2C.objects.create(field1=\"C1\", field2=\"30\", field3=\"C3\")\n\n        # Test aggregate with subclass field\n        # This should use non_polymorphic internally\n        result = Model2A.objects.aggregate(total=Count(\"Model2B___field2\"))\n\n        assert \"total\" in result\n        assert result[\"total\"] >= 0\n\n    def test_disparate_pk_values_in_hierarchy(self):\n        \"\"\"\n        Test that polymorphic models with different primary key field types and values\n        at different levels of the inheritance hierarchy can be created, queried, and\n        deleted without issues.\n        \"\"\"\n        from polymorphic.tests.models import (\n            DisparateKeysParent,\n            RelatedKeyModel,\n            DisparateKeysChild1,\n            DisparateKeysChild2,\n            DisparateKeysGrandChild,\n            DisparateKeysGrandChild2,\n        )\n\n        extern_key1 = RelatedKeyModel.objects.create()\n        extern_key2 = RelatedKeyModel.objects.create()\n        extern_key3 = RelatedKeyModel.objects.create()\n\n        parent1 = DisparateKeysParent.objects.create(text=\"parent1\")\n        parent2 = DisparateKeysParent.objects.create(text=\"parent2\")\n        child1 = DisparateKeysChild1.objects.create(\n            text=\"child1\", text_child1=\"child1 extra\", key=extern_key1\n        )\n        child2 = DisparateKeysChild1.objects.create(\n            text=\"child2\", text_child1=\"child2 extra\", key=extern_key2\n        )\n\n        grandchild1 = DisparateKeysGrandChild.objects.create(\n            text=\"grandchild1\",\n            text_child1=\"grandchild1 extra\",\n            text_grand_child=\"grandchild1 extra extra\",\n            key=extern_key3,\n        )\n        grandchild2 = DisparateKeysGrandChild2.objects.create(\n            text=\"grandchild2\",\n            text_child2=\"grandchild2 extra\",\n            text_grand_child=\"grandchild2 extra extra\",\n            id=50,\n            key=100,\n        )\n\n        child2_1 = DisparateKeysChild2.objects.create(\n            text=\"child2_1\", text_child2=\"child2_1 extra\", key=101\n        )\n        child2_2 = DisparateKeysChild2.objects.create(\n            text=\"child2_2\", text_child2=\"child2_2 extra\", key=102\n        )\n\n        assert set(DisparateKeysParent.objects.all()) == {\n            parent1,\n            parent2,\n            child1,\n            child2,\n            grandchild1,\n            child2_1,\n            child2_2,\n            grandchild2,\n        }\n\n        assert set(DisparateKeysChild1.objects.all()) == {child1, child2, grandchild1}\n        assert set(DisparateKeysChild2.objects.all()) == {child2_1, child2_2, grandchild2}\n        assert set(DisparateKeysGrandChild.objects.all()) == {grandchild1}\n        assert set(DisparateKeysGrandChild2.objects.all()) == {grandchild2}\n\n        # test get_real_instance\n        real_instances = set()\n        for obj in DisparateKeysParent.objects.non_polymorphic().all():\n            real_instances.add(obj.get_real_instance())\n\n        assert real_instances == {\n            parent1,\n            parent2,\n            child1,\n            child2,\n            grandchild1,\n            child2_1,\n            child2_2,\n            grandchild2,\n        }\n\n        # test parentage links\n        assert grandchild2.disparatekeyschild2_ptr.__class__ == DisparateKeysChild2\n        assert (\n            grandchild2.disparatekeyschild2_ptr\n            == DisparateKeysChild2.objects.non_polymorphic().get(key=grandchild2.key)\n        )\n        assert grandchild2.disparatekeysparent_ptr.__class__ == DisparateKeysParent\n\n        assert (\n            grandchild2.disparatekeysparent_ptr\n            == DisparateKeysParent.objects.non_polymorphic().get(pk=grandchild2.id)\n        )\n\n        DisparateKeysGrandChild2.objects.all().delete()\n\n    def test_manager_cache_clear_persistence(self):\n        \"\"\"\n        Test that clearing the model registry cache does not remove overriden\n        base managers.\n\n        https://github.com/jazzband/django-polymorphic/issues/857\n        \"\"\"\n        from django.apps import apps\n\n        apps.clear_cache()\n\n        self.test_base_manager()\n        self.test_default_manager()\n"
  },
  {
    "path": "src/polymorphic/tests/test_performance.py",
    "content": "from django.test import TransactionTestCase\nfrom polymorphic.tests.models import (\n    Model2A,\n    Model2B,\n    Model2C,\n    Model2D,\n)\n\n\nclass PerformanceTests(TransactionTestCase):\n    def test_baseline_number_of_queries(self):\n        \"\"\"\n        Test that the number of queries for loading polymorphic models is within\n        expected limits.\n        \"\"\"\n        for idx in range(100):\n            Model2A.objects.create(field1=f\"A{idx}\")\n            Model2B.objects.create(field1=f\"A{idx}\", field2=f\"B{idx}\")\n            Model2C.objects.create(field1=f\"A{idx}\", field2=f\"B{idx}\", field3=f\"C{idx}\")\n            Model2D.objects.create(\n                field1=f\"A{idx}\", field2=f\"B{idx}\", field3=f\"C{idx}\", field4=f\"D{idx}\"\n            )\n\n        with self.assertNumQueries(4):\n            list(Model2A.objects.all().order_by(\"pk\"))\n\n        with self.assertNumQueries(3):\n            list(Model2B.objects.all().order_by(\"pk\"))\n\n        with self.assertNumQueries(2):\n            list(Model2C.objects.all().order_by(\"pk\"))\n\n        with self.assertNumQueries(1):\n            list(Model2D.objects.all().order_by(\"pk\"))\n"
  },
  {
    "path": "src/polymorphic/tests/test_query_translate.py",
    "content": "import copy\nimport tempfile\nimport pickle\nimport threading\n\nfrom django.db.models import Q\nfrom django.test import TestCase\n\nfrom polymorphic.tests.models import Bottom, Middle, Top\nfrom polymorphic.query_translate import translate_polymorphic_filter_definitions_in_args\n\n\nclass QueryTranslateTests(TestCase):\n    def test_translate_with_not_pickleable_query(self):\n        \"\"\"\n        In some cases, Django may attacha _thread object to the query and we\n        will get the following when we try to deepcopy inside of\n        translate_polymorphic_filter_definitions_in_args:\n\n            TypeError: cannot pickle '_thread.lock' object\n\n\n        For this to trigger, we need to somehoe go down this path:\n\n                File \"/perfdash/.venv/lib64/python3.12/site-packages/polymorphic/query_translate.py\", line 95, in translate_polymorphic_filter_definitions_in_args\n            translate_polymorphic_Q_object(queryset_model, copy.deepcopy(q), using=using) for q in args\n                                                        ^^^^^^^^^^^^^^^^\n        File \"/usr/lib64/python3.12/copy.py\", line 143, in deepcopy\n            y = copier(memo)\n                ^^^^^^^^^^^^\n        File \"/perfdash/.venv/lib64/python3.12/site-packages/django/utils/tree.py\", line 53, in __deepcopy__\n            obj.children = copy.deepcopy(self.children, memodict)\n                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n        File \"/usr/lib64/python3.12/copy.py\", line 136, in deepcopy\n            y = copier(x, memo)\n                ^^^^^^^^^^^^^^^\n\n        Internals in Django, somehow we must trigger this tree.py code in django via\n        the deepcopy in order to trigger this.\n\n        \"\"\"\n\n        with tempfile.TemporaryFile() as fd:\n            # verify this is definitely not pickleable\n            with self.assertRaises(TypeError):\n                pickle.dumps(threading.Lock())\n\n            # I know this doesn't make sense to pass as a Q(), but\n            # I haven't found another way to trigger the copy.deepcopy failing.\n            q = Q(blog__info=\"blog info\") | Q(blog__info=threading.Lock())\n\n            translate_polymorphic_filter_definitions_in_args(Bottom, args=[q])\n\n    def test_deep_copy_of_q_objects(self):\n        import os\n        from polymorphic.tests.models import DeepCopyTester, DeepCopyTester2\n        # binary fields can have an unpickleable memoryview object in them\n        # see https://github.com/jazzband/django-polymorphic/issues/524\n\n        d1_bf = os.urandom(32)\n        d2_bf1 = os.urandom(32)\n        d2_bf2 = os.urandom(32)\n\n        dct1 = DeepCopyTester.objects.create(binary_field=d1_bf)\n        dct2 = DeepCopyTester2.objects.create(binary_field=d2_bf1, binary_field2=d2_bf2)\n\n        self.assertEqual(list(DeepCopyTester.objects.filter(binary_field=d1_bf).all()), [dct1])\n\n        q1 = Q(DeepCopyTester2___binary_field2=d2_bf1)\n        self.assertEqual(list(DeepCopyTester.objects.filter(q1).all()), [])\n        assert q1.children[0][0] == \"DeepCopyTester2___binary_field2\"\n        q2 = Q(DeepCopyTester2___binary_field2=d2_bf2)\n        self.assertEqual(list(DeepCopyTester.objects.filter(q2).all()), [dct2])\n        assert q2.children[0][0] == \"DeepCopyTester2___binary_field2\"\n\n        assert len(DeepCopyTester.objects.filter(Q(binary_field=memoryview(d1_bf)))) == 1\n\n        self.assertEqual(DeepCopyTester.objects.all().delete()[0], 3)\n        self.assertEqual(DeepCopyTester.objects.count(), 0)\n\n    def test_proxy_model_query_related_name(self):\n        \"\"\"Test _get_query_related_name fallback for proxy models\"\"\"\n        from polymorphic.query_translate import _get_query_related_name\n        from polymorphic.tests.models import ProxyChild, SubclassSelectorProxyModel\n\n        # Test that proxy models use the fallback (lowercase class name)\n        # since they don't have a OneToOneField parent link\n        result = _get_query_related_name(ProxyChild)\n        assert result == \"proxychild\"\n\n        result = _get_query_related_name(SubclassSelectorProxyModel)\n        assert result == \"subclassselectorproxymodel\"\n"
  },
  {
    "path": "src/polymorphic/tests/test_regression.py",
    "content": "from django import forms\nfrom django.db import models\nfrom django.db.models import functions\nfrom polymorphic.models import PolymorphicModel, PolymorphicTypeInvalid\nfrom polymorphic.tests.models import (\n    Bottom,\n    Middle,\n    Top,\n    Team,\n    UserProfile,\n    Model2A,\n    Model2B,\n    Regression295Parent,\n    Regression295Related,\n    RelationBase,\n    RelationA,\n    RelationB,\n    SpecialBook,\n    Book,\n)\nfrom django.test import TestCase\nfrom django.contrib.contenttypes.models import ContentType\nfrom polymorphic.formsets import polymorphic_modelformset_factory, PolymorphicFormSetChild\n\n\nclass RegressionTests(TestCase):\n    def test_for_query_result_incomplete_with_inheritance(self):\n        \"\"\"https://github.com/bconstantin/django_polymorphic/issues/15\"\"\"\n\n        top = Top()\n        top.save()\n        middle = Middle()\n        middle.save()\n        bottom = Bottom()\n        bottom.save()\n\n        expected_queryset = [top, middle, bottom]\n        self.assertQuerySetEqual(\n            Top.objects.order_by(\"pk\"),\n            [repr(r) for r in expected_queryset],\n            transform=repr,\n        )\n\n        expected_queryset = [middle, bottom]\n        self.assertQuerySetEqual(\n            Middle.objects.order_by(\"pk\"),\n            [repr(r) for r in expected_queryset],\n            transform=repr,\n        )\n\n        expected_queryset = [bottom]\n        self.assertQuerySetEqual(\n            Bottom.objects.order_by(\"pk\"),\n            [repr(r) for r in expected_queryset],\n            transform=repr,\n        )\n\n    def test_pr_254(self):\n        user_a = UserProfile.objects.create(name=\"a\")\n        user_b = UserProfile.objects.create(name=\"b\")\n        user_c = UserProfile.objects.create(name=\"c\")\n\n        team1 = Team.objects.create(team_name=\"team1\")\n        team1.user_profiles.add(user_a, user_b, user_c)\n        team1.save()\n\n        team2 = Team.objects.create(team_name=\"team2\")\n        team2.user_profiles.add(user_c)\n        team2.save()\n\n        # without prefetch_related, the test passes\n        my_teams = (\n            Team.objects.filter(user_profiles=user_c)\n            .order_by(\"team_name\")\n            .prefetch_related(\"user_profiles\")\n            .distinct()\n        )\n\n        self.assertEqual(len(my_teams[0].user_profiles.all()), 3)\n\n        self.assertEqual(len(my_teams[1].user_profiles.all()), 1)\n\n        self.assertEqual(len(my_teams[0].user_profiles.all()), 3)\n        self.assertEqual(len(my_teams[1].user_profiles.all()), 1)\n\n        # without this \"for\" loop, the test passes\n        for _ in my_teams:\n            pass\n\n        # This time, test fails.  PR 254 claim\n        # with sqlite:      4 != 3\n        # with postgresql:  2 != 3\n        self.assertEqual(len(my_teams[0].user_profiles.all()), 3)\n        self.assertEqual(len(my_teams[1].user_profiles.all()), 1)\n\n    def test_alias_queryset(self):\n        \"\"\"\n        Test that .alias() works works correctly with polymorphic querysets.\n        It should not raise AttributeError, and the aliased field should NOT be present on the instance.\n        \"\"\"\n        Model2B.objects.create(field1=\"val1\", field2=\"val2\")\n\n        # Scenario 1: .alias() only\n        # Should not crash, and 'lower_field1' should NOT be an attribute\n        qs = Model2A.objects.alias(lower_field1=functions.Lower(\"field1\"))\n        results = list(qs)\n        self.assertEqual(len(results), 1)\n        self.assertIsInstance(results[0], Model2B)\n        self.assertFalse(hasattr(results[0], \"lower_field1\"))\n\n        # Scenario 2: .annotate()\n        # Should work, and 'upper_field1' SHOULD be an attribute\n        qs = Model2A.objects.annotate(upper_field1=functions.Upper(\"field1\"))\n        results = list(qs)\n        self.assertEqual(len(results), 1)\n        self.assertTrue(hasattr(results[0], \"upper_field1\"))\n        self.assertEqual(results[0].upper_field1, \"VAL1\")\n\n        # Scenario 3: Mixed alias() and annotate()\n        qs = Model2A.objects.alias(alias_val=functions.Lower(\"field1\")).annotate(\n            anno_val=functions.Upper(\"field1\")\n        )\n        results = list(qs)\n        self.assertEqual(len(results), 1)\n        self.assertFalse(hasattr(results[0], \"alias_val\"))\n        self.assertTrue(hasattr(results[0], \"anno_val\"))\n        self.assertEqual(results[0].anno_val, \"VAL1\")\n\n    def test_alias_advanced(self):\n        \"\"\"\n        Test .alias() interactions with filter, order_by, only, and defer.\n        \"\"\"\n        obj1 = Model2B.objects.create(field1=\"Alpha\", field2=\"One\")\n        obj2 = Model2B.objects.create(field1=\"Beta\", field2=\"Two\")\n        obj3 = Model2B.objects.create(field1=\"Gamma\", field2=\"Three\")\n\n        # 1. Filter by alias\n        qs = Model2A.objects.alias(lower_f1=functions.Lower(\"field1\")).filter(lower_f1=\"beta\")\n        self.assertEqual(qs.count(), 1)\n        self.assertEqual(qs[0], obj2)\n        self.assertFalse(hasattr(qs[0], \"lower_f1\"))\n\n        # 2. Order by alias\n        qs = Model2A.objects.alias(len_f2=functions.Length(\"model2b__field2\")).order_by(\"len_f2\")\n        # Lengths: One=3, Two=3, Three=5. (Ordering of equal values is DB dep, but logic holds)\n        results = list(qs)\n        self.assertEqual(len(results), 3)\n        self.assertFalse(hasattr(results[0], \"len_f2\"))\n\n        # 3. Alias + Only\n        qs = Model2A.objects.alias(lower_f1=functions.Lower(\"field1\")).only(\"field1\")\n        # Should not crash\n        results = list(qs)\n        self.assertEqual(len(results), 3)\n        # Verify deferral logic didn't break\n        # accessing field1 should not trigger refresh (hard to test without internals, but basic access works)\n        self.assertEqual(results[0].field1, \"Alpha\")\n\n        # 4. Alias + Defer\n        qs = Model2A.objects.alias(lower_f1=functions.Lower(\"field1\")).defer(\"field1\")\n        results = list(qs)\n        self.assertEqual(len(results), 3)\n        # accessing field1 should trigger refresh\n        self.assertEqual(results[0].field1, \"Alpha\")\n\n    def test_upcasting_to_sibling_class(self):\n        \"\"\"\n        Test that querying a model that has been upcasted to a sibling\n        polymorphic class does not raise a TypeError.\n        Reproduces issue #280.\n        \"\"\"\n        # Create a Model2A instance with a specific pk\n        Model2A.objects.create(pk=1, field1=\"original\")\n\n        # \"Upcast\" it to a Model2B by creating an object with the same pk.\n        # The polymorphic_ctype will now point to Model2B.\n        Model2B.objects.create(pk=1, field1=\"updated\", field2=\"new\")\n\n        # The original bug raised TypeError. We expect that accessing the\n        # queryset should not raise TypeError. It should either be empty,\n        # or raise a clean PolymorphicTypeInvalid error.\n        try:\n            list(Model2A.objects.all())\n        except PolymorphicTypeInvalid:\n            pass  # This is an acceptable outcome.\n        except TypeError as e:\n            self.fail(f\"Querying for upcasted sibling raised TypeError: {e}\")\n\n    def test_mixed_inheritance_save_issue_495(self):\n        \"\"\"\n        Test that saving models with mixed polymorphic and non-polymorphic\n        inheritance works correctly. This addresses issue #495.\n        \"\"\"\n        from polymorphic.tests.models import NormalExtension, PolyExtension, PolyExtChild\n\n        # Create and save NormalExtension\n        normal_ext = NormalExtension.objects.create(nb_field=1, ne_field=\"normal\")\n        normal_ext.add_to_ne(\" extended\")\n        normal_ext.refresh_from_db()\n        self.assertEqual(normal_ext.ne_field, \"normal extended\")\n        normal_ext.add_to_nb(5)\n        normal_ext.refresh_from_db()\n        self.assertEqual(normal_ext.nb_field, 6)\n\n        # Create and save PolyExtension\n        poly_ext = PolyExtension.objects.create(nb_field=1, ne_field=\"normal\", poly_ext_field=10)\n        poly_ext.add_to_ne(\" extended\")\n        poly_ext.refresh_from_db()\n        self.assertEqual(poly_ext.ne_field, \"normal extended\")\n        poly_ext.add_to_ext(5)\n        poly_ext.refresh_from_db()\n        self.assertEqual(poly_ext.poly_ext_field, 15)\n        poly_ext.add_to_nb(5)\n        poly_ext.refresh_from_db()\n        self.assertEqual(poly_ext.nb_field, 6)\n\n        # Create and save PolyExtChild\n        poly_child = PolyExtChild.objects.create(\n            nb_field=1, ne_field=\"normal\", poly_ext_field=20, poly_child_field=\"child\"\n        )\n        poly_child.add_to_ne(\" extended\")\n        poly_child.add_to_nb(5)\n        poly_child.add_to_ext(10)\n        poly_child.add_to_child(\" added\")\n        poly_child.refresh_from_db()\n        self.assertEqual(poly_child.nb_field, 6)\n        self.assertEqual(poly_child.ne_field, \"normal extended\")\n        self.assertEqual(poly_child.poly_ext_field, 30)\n        self.assertEqual(poly_child.poly_child_field, \"child added\")\n\n        poly_child.override_add_to_ne(\" overridden\")\n        poly_child.override_add_to_ext(5)\n        poly_child.refresh_from_db()\n        self.assertEqual(poly_child.ne_field, \"normal extended OVERRIDDEN\")\n        self.assertEqual(poly_child.poly_ext_field, 40)\n\n    def test_create_or_update(self):\n        \"\"\"\n        https://github.com/jazzband/django-polymorphic/issues/494\n        \"\"\"\n        from polymorphic.tests.models import Model2B, Model2C\n\n        obj, created = Model2B.objects.update_or_create(\n            field1=\"value1\", defaults={\"field2\": \"value2\"}\n        )\n        self.assertTrue(created)\n        self.assertEqual(obj.field1, \"value1\")\n        self.assertEqual(obj.field2, \"value2\")\n\n        obj2, created = Model2B.objects.update_or_create(\n            field1=\"value1\", defaults={\"field2\": \"new_value2\"}\n        )\n        self.assertFalse(created)\n        self.assertEqual(obj2.pk, obj.pk)\n        self.assertEqual(obj2.field1, \"value1\")\n        self.assertEqual(obj2.field2, \"new_value2\")\n\n        self.assertEqual(Model2B.objects.count(), 1)\n\n        obj3, created = Model2C.objects.update_or_create(\n            field1=\"value1\", defaults={\"field2\": \"new_value3\", \"field3\": \"value3\"}\n        )\n\n        self.assertTrue(created)\n        self.assertEqual(Model2B.objects.count(), 2)\n        self.assertEqual(Model2C.objects.count(), 1)\n        self.assertEqual(obj3, Model2B.objects.order_by(\"pk\").last())\n\n    def test_double_underscore_in_related_name(self):\n        \"\"\"\n        Test filtering on a related field when the relation name itself contains '__'.\n        This reproduces the issue in #295, where 'my__relation___real_field' was\n        being incorrectly parsed as a polymorphic lookup.\n        \"\"\"\n\n        related = Regression295Related.objects.create(_real_field=\"test_value\")\n        Regression295Parent.objects.create(related_object=related)\n\n        # The following filter would be translated to 'related_object___real_field'\n        # by Django's query machinery.\n        qs = Regression295Parent.objects.filter(related_object___real_field=\"test_value\")\n        self.assertEqual(qs.count(), 1)\n\n    def test_issue_252_abstract_base_class(self):\n        \"\"\"\n        Test that polymorphic models inheriting from both an abstract model\n        and PolymorphicModel can be created and saved without IndexError.\n\n        This reproduces issue #252:\n        https://github.com/jazzband/django-polymorphic/issues/252\n\n        The issue reported an IndexError when trying to create an Event object\n        that inherited from both an abstract model (AbstractDateInfo) and\n        PolymorphicModel. The error occurred in Django's query_utils.py when\n        accessing polymorphic_ctype_id.\n\n        We use RelationBase which inherits from RelationAbstractModel (abstract)\n        and PolymorphicModel, demonstrating the same pattern.\n        \"\"\"\n        # Test creating base polymorphic model with abstract parent\n        # RelationBase inherits from RelationAbstractModel (abstract) and PolymorphicModel\n        base = RelationBase(field_base=\"test_base\")\n        # This should not raise IndexError\n        base.save()\n\n        # Verify the object was saved correctly\n        self.assertIsNotNone(base.pk)\n        self.assertEqual(base.field_base, \"test_base\")\n        self.assertIsNotNone(base.polymorphic_ctype_id)\n\n        # Test creating child models\n        relation_a = RelationA.objects.create(field_base=\"base_a\", field_a=\"field_a_value\")\n        self.assertIsNotNone(relation_a.pk)\n        self.assertEqual(relation_a.field_a, \"field_a_value\")\n\n        relation_b = RelationB.objects.create(field_base=\"base_b\", field_b=\"field_b_value\")\n        self.assertIsNotNone(relation_b.pk)\n        self.assertEqual(relation_b.field_b, \"field_b_value\")\n\n        # Test querying polymorphic objects\n        relations = RelationBase.objects.all().order_by(\"pk\")\n        self.assertEqual(relations.count(), 3)\n\n        # Verify polymorphic behavior - objects should be returned as their actual types\n        self.assertIsInstance(relations[0], RelationBase)\n        self.assertIsInstance(relations[1], RelationA)\n        self.assertIsInstance(relations[2], RelationB)\n\n\nclass SpecialBookForm(forms.ModelForm):\n    class Meta:\n        model = SpecialBook\n        exclude = (\"author\",)\n\n\nclass TestFormsetExclude(TestCase):\n    def test_formset_child_respects_exclude(self):\n        SpecialBookFormSet = polymorphic_modelformset_factory(\n            Book,\n            fields=[],\n            formset_children=(PolymorphicFormSetChild(SpecialBook, form=SpecialBookForm),),\n        )\n        formset = SpecialBookFormSet(queryset=SpecialBook.objects.none())\n        self.assertNotIn(\"author\", formset.forms[0].fields)\n\n    def test_formset_initial_with_contenttype_instance(self):\n        \"\"\"Test that polymorphic_ctype can be set as ContentType instance in initial data (issue #549)\"\"\"\n        from django.contrib.contenttypes.models import ContentType\n\n        ct = ContentType.objects.get_for_model(SpecialBook, for_concrete_model=False)\n\n        SpecialBookFormSet = polymorphic_modelformset_factory(\n            Book,\n            fields=\"__all__\",\n            formset_children=(PolymorphicFormSetChild(SpecialBook, form=SpecialBookForm),),\n        )\n\n        # Set initial data with ContentType instance (as users do in issue #549)\n        formset = SpecialBookFormSet(\n            queryset=SpecialBook.objects.none(),\n            initial=[{\"polymorphic_ctype\": ct}],\n        )\n\n        # Should not raise an error when creating the formset\n        form = formset.forms[0]\n\n        # Verify the polymorphic_ctype field is properly set up with the ID\n        self.assertIn(\"polymorphic_ctype\", form.fields)\n\n        # The critical assertion: the field's initial value should be the ID (int),\n        # not the ContentType instance. This proves the normalization worked.\n        self.assertEqual(form.fields[\"polymorphic_ctype\"].initial, ct.pk)\n        self.assertIsInstance(form.fields[\"polymorphic_ctype\"].initial, int)\n\n    def test_formset_with_none_instance(self):\n        \"\"\"Test that formset handles None instance without AttributeError (issue #363).\n\n        This occurs when a bound formset has a pk that doesn't exist in the queryset,\n        causing Django's _existing_object to return None. The polymorphic formset\n        must handle this gracefully instead of calling get_real_instance_class() on None.\n        \"\"\"\n        from django.contrib.contenttypes.models import ContentType\n\n        ct = ContentType.objects.get_for_model(SpecialBook, for_concrete_model=False)\n\n        SpecialBookFormSet = polymorphic_modelformset_factory(\n            Book,\n            fields=\"__all__\",\n            formset_children=(PolymorphicFormSetChild(SpecialBook, form=SpecialBookForm),),\n        )\n\n        # Simulate the scenario where _existing_object returns None:\n        # - Bound formset with data\n        # - Claims to have an initial form (INITIAL_FORMS > 0)\n        # - But the pk doesn't exist in queryset, so _existing_object returns None\n        data = {\n            \"form-TOTAL_FORMS\": \"1\",\n            \"form-INITIAL_FORMS\": \"1\",\n            \"form-MIN_NUM_FORMS\": \"0\",\n            \"form-MAX_NUM_FORMS\": \"1000\",\n            \"form-0-id\": \"99999\",  # Non-existent pk - _existing_object will return None\n            \"form-0-polymorphic_ctype\": str(ct.pk),\n        }\n\n        formset = SpecialBookFormSet(data=data, queryset=SpecialBook.objects.none())\n\n        # This should not raise AttributeError when instance is None\n        forms = formset.forms\n        self.assertEqual(len(forms), 1)\n        self.assertIn(\"polymorphic_ctype\", forms[0].fields)\n\n    def test_combined_formset_behaviors(self):\n        # 1. __init__ exclude handling\n        child_none = PolymorphicFormSetChild(Book, form=SpecialBookForm, exclude=None)\n        self.assertEqual(child_none.exclude, ())\n\n        child_list = PolymorphicFormSetChild(Book, form=SpecialBookForm, exclude=[\"author\"])\n        self.assertIn(\"author\", child_list.exclude)\n\n        # 2. get_form exclude merging\n        form = child_list.get_form(extra_exclude=[\"field1\"])\n        self.assertIn(\"author\", form._meta.exclude)\n        self.assertIn(\"field1\", form._meta.exclude)\n\n        form_meta_default = child_none.get_form()\n        self.assertIn(\"author\", form_meta_default._meta.exclude)\n\n        # 3. polymorphic_ctype normalization\n        ct = ContentType.objects.get_for_model(SpecialBook, for_concrete_model=False)\n        SpecialBookFormSet = polymorphic_modelformset_factory(\n            Book,\n            fields=\"__all__\",\n            formset_children=(PolymorphicFormSetChild(SpecialBook, form=SpecialBookForm),),\n        )\n        formset = SpecialBookFormSet(\n            queryset=SpecialBook.objects.none(),\n            initial=[{\"polymorphic_ctype\": ct}],\n        )\n        # The formset should normalize the ContentType instance to its ID\n        form_ct = formset.forms[0]\n        self.assertIsInstance(form_ct.initial[\"polymorphic_ctype\"], int)\n        self.assertEqual(form_ct.initial[\"polymorphic_ctype\"], ct.pk)\n"
  },
  {
    "path": "src/polymorphic/tests/test_serialization.py",
    "content": "\"\"\"\nTests for serialization and dumpdata functionality.\n\nThis module tests that polymorphic models are correctly serialized using Django's\ndumpdata command, both via call_command and subprocess invocation.\n\nRegression test for issue #146 - ensuring dumpdata works correctly with polymorphic\nmodels.\n\"\"\"\n\nimport json\nimport os\nimport pytest\nimport tempfile\nimport subprocess\nimport sys\nfrom io import StringIO\nfrom pathlib import Path\n\nfrom django.conf import settings\nfrom django.core.management import call_command\nfrom django.db import connections\n\nfrom polymorphic.tests.models import (\n    Model2A,\n    Model2B,\n    Model2C,\n    Model2BFiltered,\n    Model2CFiltered,\n    RelatingModel,\n    NatKeyParent,\n    NatKeyChild,\n)\nfrom .utils import is_sqlite_in_memory, is_oracle, get_subprocess_test_db_env\n\nmanage_py = Path(__file__).parent.parent.parent.parent / \"manage.py\"\nassert manage_py.exists()\n\n\ndef call_dumpdata(*models, natural_foreign=True, natural_primary=True, all=False):\n    out = StringIO()\n    call_command(\n        \"dumpdata\",\n        *models,\n        format=\"json\",\n        stdout=out,\n        natural_foreign=natural_foreign,\n        natural_primary=natural_primary,\n        all=all,\n    )\n    return json.loads(out.getvalue())\n\n\ndef run_dumpdata(*models, natural_foreign=True, natural_primary=True, all=False):\n    cmd = [sys.executable, manage_py, \"dumpdata\", *models, \"--format=json\"]\n    if natural_foreign:\n        cmd.append(\"--natural-foreign\")\n    if all:\n        cmd.append(\"--all\")\n    if natural_primary:\n        cmd.append(\"--natural-primary\")\n    result = subprocess.run(cmd, capture_output=True, env=get_subprocess_test_db_env())\n    assert result.returncode == 0, result.stderr or result.stdout\n    return json.loads(result.stdout)\n\n\n@pytest.fixture\ndef dump_objects(db):\n    return (\n        Model2A.objects.create(field1=\"A1\"),\n        Model2B.objects.create(field1=\"B1\", field2=\"B2\"),\n        Model2C.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\"),\n        Model2BFiltered.objects.create(field1=\"BF1\", field2=\"BF2\"),\n        Model2CFiltered.objects.create(field1=\"cf1\", field2=\"cf2\", field3=\"cf3\"),\n        Model2CFiltered.objects.create(field1=\"CF1\", field2=\"CF2\", field3=\"CF3\"),\n    )\n\n\n@pytest.fixture\ndef natkey_dump_objects(db):\n    \"\"\"\n    Create a small graph of NatKeyParent / NatKeyChild instances.\n\n    Returns:\n        tuple[list[NatKeyParent], list[NatKeyChild]]\n    \"\"\"\n    return [\n        NatKeyChild.objects.create(slug=f\"slug-{i}\", content=f\"content {i}\", val=i)\n        for i in range(5)\n    ]\n\n\n@pytest.mark.django_db(transaction=True)\n@pytest.mark.parametrize(\n    \"dumpdata\",\n    [\n        pytest.param(call_dumpdata, id=\"call_command\"),\n        pytest.param(\n            run_dumpdata,\n            id=\"manage\",\n            marks=pytest.mark.skipif(\n                is_sqlite_in_memory(),\n                reason=\"Subprocess test disabled for in-memory sqlite test runs\",\n            ),\n        ),\n    ],\n)\n@pytest.mark.parametrize(\"all\", [False, True])\ndef test_dumpdata_returns_base_objects_not_downcasted(dumpdata, dump_objects, all):\n    \"\"\"\n    Test that dumpdata serializes base table rows without polymorphic downcasting.\n\n    When querying Model2A table directly, we should get 3 rows\n    (A, B, C base objects).\n    \"\"\"\n    # Should have only Model2As\n    assert dumpdata(\"tests.Model2A\", all=all) == [\n        {\n            \"fields\": {\"field1\": \"A1\", \"polymorphic_ctype\": [\"tests\", \"model2a\"]},\n            \"model\": \"tests.model2a\",\n            \"pk\": dump_objects[0].pk,\n        },\n        {\n            \"fields\": {\"field1\": \"B1\", \"polymorphic_ctype\": [\"tests\", \"model2b\"]},\n            \"model\": \"tests.model2a\",\n            \"pk\": dump_objects[1].pk,\n        },\n        {\n            \"fields\": {\"field1\": \"C1\", \"polymorphic_ctype\": [\"tests\", \"model2c\"]},\n            \"model\": \"tests.model2a\",\n            \"pk\": dump_objects[2].pk,\n        },\n        {\n            \"fields\": {\"field1\": \"BF1\", \"polymorphic_ctype\": [\"tests\", \"model2bfiltered\"]},\n            \"model\": \"tests.model2a\",\n            \"pk\": dump_objects[3].pk,\n        },\n        {\n            \"fields\": {\"field1\": \"cf1\", \"polymorphic_ctype\": [\"tests\", \"model2cfiltered\"]},\n            \"model\": \"tests.model2a\",\n            \"pk\": dump_objects[4].pk,\n        },\n        {\n            \"fields\": {\"field1\": \"CF1\", \"polymorphic_ctype\": [\"tests\", \"model2cfiltered\"]},\n            \"model\": \"tests.model2a\",\n            \"pk\": dump_objects[5].pk,\n        },\n    ]\n\n\n@pytest.mark.django_db(transaction=True)\n@pytest.mark.parametrize(\n    \"dumpdata\",\n    [\n        pytest.param(call_dumpdata, id=\"call_command\"),\n        pytest.param(\n            run_dumpdata,\n            id=\"manage\",\n            marks=pytest.mark.skipif(\n                is_sqlite_in_memory() or is_oracle(),\n                reason=\"Subprocess test disabled for in-memory sqlite test runs\",\n            ),\n        ),\n    ],\n)\n@pytest.mark.parametrize(\"all\", [False, True], ids=[\"default\", \"all\"])\ndef test_dumpdata_all_flag(dumpdata, dump_objects, all):\n    \"\"\"Test dumping only a child model works correctly.\"\"\"\n\n    expected = [\n        *(\n            [\n                {\n                    \"fields\": {\"model2b_ptr\": dump_objects[3].pk},\n                    \"model\": \"tests.model2bfiltered\",\n                    \"pk\": dump_objects[3].pk,\n                }\n            ]\n            if all\n            else []\n        ),\n        {\n            \"fields\": {\"model2b_ptr\": dump_objects[4].pk},\n            \"model\": \"tests.model2bfiltered\",\n            \"pk\": dump_objects[4].pk,\n        },\n        *(\n            [\n                {\n                    \"fields\": {\"model2b_ptr\": dump_objects[5].pk},\n                    \"model\": \"tests.model2bfiltered\",\n                    \"pk\": dump_objects[5].pk,\n                }\n            ]\n            if all\n            else []\n        ),\n        {\n            \"fields\": {\"field3\": \"cf3\", \"model2bfiltered_ptr\": dump_objects[4].pk},\n            \"model\": \"tests.model2cfiltered\",\n            \"pk\": dump_objects[4].pk,\n        },\n        *(\n            [\n                {\n                    \"fields\": {\"field3\": \"CF3\", \"model2bfiltered_ptr\": dump_objects[5].pk},\n                    \"model\": \"tests.model2cfiltered\",\n                    \"pk\": dump_objects[5].pk,\n                }\n            ]\n            if all\n            else []\n        ),\n    ]\n    assert dumpdata(\"tests.Model2BFiltered\", \"tests.Model2CFiltered\", all=all) == expected\n\n\n@pytest.mark.django_db(transaction=True)\n@pytest.mark.parametrize(\n    \"dumpdata\",\n    [\n        pytest.param(call_dumpdata, id=\"call_command\"),\n        pytest.param(\n            run_dumpdata,\n            id=\"manage\",\n            marks=pytest.mark.skipif(\n                is_sqlite_in_memory() or is_oracle(),\n                reason=\"Subprocess test disabled for in-memory sqlite test runs\",\n            ),\n        ),\n    ],\n)\ndef test_dumpdata_child_model_only(dumpdata, dump_objects):\n    \"\"\"Test dumping only a child model works correctly.\"\"\"\n    assert dumpdata(\"tests.Model2C\") == [\n        {\n            \"fields\": {\n                \"field3\": \"C3\",\n                \"model2b_ptr\": dump_objects[2].pk,\n            },\n            \"model\": \"tests.model2c\",\n            \"pk\": dump_objects[2].pk,\n        }\n    ]\n\n\n@pytest.mark.django_db(transaction=True)\n@pytest.mark.parametrize(\n    \"dumpdata\",\n    [\n        pytest.param(call_dumpdata, id=\"call_command\"),\n        pytest.param(\n            run_dumpdata,\n            id=\"manage\",\n            marks=pytest.mark.skipif(\n                is_sqlite_in_memory() or is_oracle(),\n                reason=\"Subprocess test disabled for in-memory sqlite test runs\",\n            ),\n        ),\n    ],\n)\n@pytest.mark.parametrize(\"all\", [False, True], ids=[\"default\", \"all\"])\ndef test_dumpdata_multi_table_roundtrip(dumpdata, dump_objects, all):\n    data = dumpdata(\n        \"tests.Model2A\",\n        \"tests.Model2B\",\n        \"tests.Model2C\",\n        \"tests.Model2BFiltered\",\n        \"tests.Model2CFiltered\",\n        all=all,\n    )\n\n    # When dumping Model2A (the parent model), ALL instances are returned regardless of\n    # the `all` parameter, because Model2A doesn't have a filtered manager.\n    # The `all` parameter only affects behavior when dumping child models WITHOUT their parent.\n    expected = [\n        {\n            \"fields\": {\"field1\": \"A1\", \"polymorphic_ctype\": [\"tests\", \"model2a\"]},\n            \"model\": \"tests.model2a\",\n            \"pk\": dump_objects[0].pk,\n        },\n        {\n            \"fields\": {\"field1\": \"B1\", \"polymorphic_ctype\": [\"tests\", \"model2b\"]},\n            \"model\": \"tests.model2a\",\n            \"pk\": dump_objects[1].pk,\n        },\n        {\n            \"fields\": {\"field1\": \"C1\", \"polymorphic_ctype\": [\"tests\", \"model2c\"]},\n            \"model\": \"tests.model2a\",\n            \"pk\": dump_objects[2].pk,\n        },\n        {\n            \"fields\": {\"field1\": \"BF1\", \"polymorphic_ctype\": [\"tests\", \"model2bfiltered\"]},\n            \"model\": \"tests.model2a\",\n            \"pk\": dump_objects[3].pk,\n        },\n        {\n            \"fields\": {\"field1\": \"cf1\", \"polymorphic_ctype\": [\"tests\", \"model2cfiltered\"]},\n            \"model\": \"tests.model2a\",\n            \"pk\": dump_objects[4].pk,\n        },\n        {\n            \"fields\": {\"field1\": \"CF1\", \"polymorphic_ctype\": [\"tests\", \"model2cfiltered\"]},\n            \"model\": \"tests.model2a\",\n            \"pk\": dump_objects[5].pk,\n        },\n        {\n            \"fields\": {\"field2\": \"B2\", \"model2a_ptr\": dump_objects[1].pk},\n            \"model\": \"tests.model2b\",\n            \"pk\": dump_objects[1].pk,\n        },\n        {\n            \"fields\": {\"field2\": \"C2\", \"model2a_ptr\": dump_objects[2].pk},\n            \"model\": \"tests.model2b\",\n            \"pk\": dump_objects[2].pk,\n        },\n        {\n            \"fields\": {\"field2\": \"BF2\", \"model2a_ptr\": dump_objects[3].pk},\n            \"model\": \"tests.model2b\",\n            \"pk\": dump_objects[3].pk,\n        },\n        {\n            \"fields\": {\"field2\": \"cf2\", \"model2a_ptr\": dump_objects[4].pk},\n            \"model\": \"tests.model2b\",\n            \"pk\": dump_objects[4].pk,\n        },\n        {\n            \"fields\": {\"field2\": \"CF2\", \"model2a_ptr\": dump_objects[5].pk},\n            \"model\": \"tests.model2b\",\n            \"pk\": dump_objects[5].pk,\n        },\n        {\n            \"fields\": {\"field3\": \"C3\", \"model2b_ptr\": dump_objects[2].pk},\n            \"model\": \"tests.model2c\",\n            \"pk\": dump_objects[2].pk,\n        },\n        *(\n            [\n                {\n                    \"fields\": {\"model2b_ptr\": dump_objects[3].pk},\n                    \"model\": \"tests.model2bfiltered\",\n                    \"pk\": dump_objects[3].pk,\n                }\n            ]\n            if all\n            else []\n        ),\n        {\n            \"fields\": {\"model2b_ptr\": dump_objects[4].pk},\n            \"model\": \"tests.model2bfiltered\",\n            \"pk\": dump_objects[4].pk,\n        },\n        *(\n            [\n                {\n                    \"fields\": {\"model2b_ptr\": dump_objects[5].pk},\n                    \"model\": \"tests.model2bfiltered\",\n                    \"pk\": dump_objects[5].pk,\n                }\n            ]\n            if all\n            else []\n        ),\n        {\n            \"fields\": {\"field3\": \"cf3\", \"model2bfiltered_ptr\": dump_objects[4].pk},\n            \"model\": \"tests.model2cfiltered\",\n            \"pk\": dump_objects[4].pk,\n        },\n        *(\n            [\n                {\n                    \"fields\": {\"field3\": \"CF3\", \"model2bfiltered_ptr\": dump_objects[5].pk},\n                    \"model\": \"tests.model2cfiltered\",\n                    \"pk\": dump_objects[5].pk,\n                }\n            ]\n            if all\n            else []\n        ),\n    ]\n\n    assert data == expected\n\n    Model2A.objects.all().delete()\n\n    with tempfile.NamedTemporaryFile(mode=\"w\", suffix=\".json\", delete=False) as f:\n        f.write(json.dumps(data))\n        fixture_file = f.name\n\n    try:\n        call_command(\"loaddata\", fixture_file, verbosity=0)\n\n        # After loaddata, all 6 objects are loaded\n        assert Model2A.objects.count() == 6\n        assert Model2B.objects.count() == 5\n        assert Model2C.objects.count() == 1\n        # Filtered managers still apply, so only cf1 passes the filter\n        assert Model2BFiltered.objects.count() == 1\n        assert Model2CFiltered.objects.count() == 1\n\n        model2a_objects = list(Model2A.objects.order_by(\"pk\"))\n        a, b, c, bf, cf_lower, cf_upper = model2a_objects\n\n        assert a.__class__ == Model2A\n        assert b.__class__ == Model2B\n        assert c.__class__ == Model2C\n        assert cf_lower.__class__ == Model2CFiltered\n\n        if all:\n            assert bf.__class__ == Model2BFiltered\n            assert cf_upper.__class__ == Model2CFiltered\n        else:\n            # the parent class wasnt filtered so these should have been upcasted\n            assert bf.__class__ == Model2B\n            assert cf_upper.__class__ == Model2B\n\n        assert a.field1 == \"A1\"\n        assert b.field1 == \"B1\"\n        assert b.field2 == \"B2\"\n        assert c.field1 == \"C1\"\n        assert c.field2 == \"C2\"\n        assert c.field3 == \"C3\"\n        assert cf_lower.field1 == \"cf1\"\n        assert cf_lower.field2 == \"cf2\"\n        assert cf_lower.field3 == \"cf3\"\n\n        if all:\n            assert bf.field1 == \"BF1\"\n            assert bf.field2 == \"BF2\"\n            assert cf_upper.field1 == \"CF1\"\n            assert cf_upper.field2 == \"CF2\"\n            assert cf_upper.field3 == \"CF3\"\n        else:\n            assert bf.field1 == \"BF1\"\n            assert bf.field2 == \"BF2\"\n            # cf_upper is now a Model2B, so field3 does not exist\n            assert cf_upper.field1 == \"CF1\"\n            assert cf_upper.field2 == \"CF2\"\n            assert not hasattr(cf_upper, \"field3\")\n\n    finally:\n        # Clean up temporary file\n        os.unlink(fixture_file)\n\n\n@pytest.mark.django_db(transaction=True)\n@pytest.mark.parametrize(\n    \"dumpdata\",\n    [\n        pytest.param(call_dumpdata, id=\"call_command\"),\n        pytest.param(\n            run_dumpdata,\n            id=\"manage\",\n            marks=pytest.mark.skipif(\n                is_sqlite_in_memory() or is_oracle(),\n                reason=\"Subprocess test disabled for in-memory sqlite test runs\",\n            ),\n        ),\n    ],\n)\n@pytest.mark.parametrize(\"all\", [False, True], ids=[\"default\", \"all\"])\ndef test_dumpdata_related_polymorphic_roundtrip(dumpdata, dump_objects, all):\n    rm1 = RelatingModel.objects.create()\n    rm2 = RelatingModel.objects.create()\n    rm3 = RelatingModel.objects.create()\n    rm1.many2many.add(dump_objects[0])\n    rm2.many2many.add(dump_objects[1], dump_objects[2])\n    # Add all filtered models to rm3\n    rm3.many2many.add(dump_objects[3], dump_objects[4], dump_objects[5])\n\n    data = dumpdata(\n        \"tests.Model2A\",\n        \"tests.Model2B\",\n        \"tests.Model2C\",\n        \"tests.Model2BFiltered\",\n        \"tests.Model2CFiltered\",\n        \"tests.RelatingModel\",\n        natural_foreign=True,\n        natural_primary=False,\n        all=all,\n    )\n\n    Model2A.objects.all().delete()\n    RelatingModel.objects.all().delete()\n\n    with tempfile.NamedTemporaryFile(mode=\"w\", suffix=\".json\", delete=False) as f:\n        f.write(json.dumps(data))\n        fixture_file = f.name\n\n    try:\n        call_command(\"loaddata\", fixture_file, verbosity=0)\n\n        # After loaddata, all 6 objects are loaded\n        assert Model2A.objects.count() == 6\n        assert Model2B.objects.count() == 5\n        assert Model2C.objects.count() == 1\n        # Filtered managers still apply, so only cf1 passes the filter\n        assert Model2BFiltered.objects.count() == 1\n        assert Model2BFiltered._base_manager.count() == (3 if all else 1)\n        assert Model2CFiltered.objects.count() == 1\n        assert Model2CFiltered._base_manager.count() == (2 if all else 1)\n\n        model2a_objects = Model2A.objects.order_by(\"pk\")\n        a, b, c, bf, cf_lower, cf_upper = model2a_objects.all()\n\n        assert a.__class__ == Model2A\n        assert b.__class__ == Model2B\n        assert c.__class__ == Model2C\n        assert cf_lower.__class__ == Model2CFiltered\n\n        if all:\n            assert bf.__class__ == Model2BFiltered\n            assert cf_upper.__class__ == Model2CFiltered\n        else:\n            # the parent class wasnt filtered so these should have been upcasted\n            assert bf.__class__ == Model2B\n            assert cf_upper.__class__ == Model2B\n\n        assert a.field1 == \"A1\"\n        assert b.field1 == \"B1\"\n        assert b.field2 == \"B2\"\n        assert c.field1 == \"C1\"\n        assert c.field2 == \"C2\"\n        assert c.field3 == \"C3\"\n        assert cf_lower.field1 == \"cf1\"\n        assert cf_lower.field2 == \"cf2\"\n        assert cf_lower.field3 == \"cf3\"\n\n        if all:\n            assert bf.field1 == \"BF1\"\n            assert bf.field2 == \"BF2\"\n            assert cf_upper.field1 == \"CF1\"\n            assert cf_upper.field2 == \"CF2\"\n            assert cf_upper.field3 == \"CF3\"\n        else:\n            assert bf.field1 == \"BF1\"\n            assert bf.field2 == \"BF2\"\n            # cf_upper is now a Model2B, so field3 does not exist\n            assert cf_upper.field1 == \"CF1\"\n            assert cf_upper.field2 == \"CF2\"\n            assert not hasattr(cf_upper, \"field3\")\n\n        # Verify relationships\n        assert RelatingModel.objects.count() == 3\n        rm_objects = list(RelatingModel.objects.order_by(\"pk\"))\n\n        # rm1 has A\n        assert rm_objects[0].many2many.count() == 1\n        assert rm_objects[0].many2many.first() == a\n\n        # rm2 has B and C\n        assert rm_objects[1].many2many.count() == 2\n        assert set(rm_objects[1].many2many.all()) == {b, c}\n\n        # rm3 has all three filtered models\n        assert rm_objects[2].many2many.count() == 3\n        rm3_related = set(rm_objects[2].many2many.all())\n        assert len(rm3_related) == 3\n        assert bf in rm3_related\n        assert cf_lower in rm3_related\n        assert cf_upper in rm3_related\n\n    finally:\n        # Clean up temporary file\n        os.unlink(fixture_file)\n\n\n@pytest.mark.django_db(transaction=True)\n@pytest.mark.parametrize(\n    \"dumpdata\",\n    [\n        pytest.param(call_dumpdata, id=\"call_command\"),\n        pytest.param(\n            run_dumpdata,\n            id=\"manage\",\n            marks=pytest.mark.skipif(\n                is_sqlite_in_memory() or is_oracle(),\n                reason=\"Subprocess test disabled for in-memory sqlite test runs\",\n            ),\n        ),\n    ],\n)\ndef test_dumpdata_natural_keys(dumpdata, natkey_dump_objects):\n    data = dumpdata(\n        \"tests\",\n        natural_foreign=True,\n        natural_primary=True,\n        all=all,\n    )\n\n    assert data == [\n        {\n            \"fields\": {\n                \"content\": \"content 0\",\n                \"polymorphic_ctype\": [\"tests\", \"natkeychild\"],\n                \"slug\": \"slug-0\",\n            },\n            \"model\": \"tests.natkeyparent\",\n        },\n        {\n            \"fields\": {\n                \"content\": \"content 1\",\n                \"polymorphic_ctype\": [\"tests\", \"natkeychild\"],\n                \"slug\": \"slug-1\",\n            },\n            \"model\": \"tests.natkeyparent\",\n        },\n        {\n            \"fields\": {\n                \"content\": \"content 2\",\n                \"polymorphic_ctype\": [\"tests\", \"natkeychild\"],\n                \"slug\": \"slug-2\",\n            },\n            \"model\": \"tests.natkeyparent\",\n        },\n        {\n            \"fields\": {\n                \"content\": \"content 3\",\n                \"polymorphic_ctype\": [\"tests\", \"natkeychild\"],\n                \"slug\": \"slug-3\",\n            },\n            \"model\": \"tests.natkeyparent\",\n        },\n        {\n            \"fields\": {\n                \"content\": \"content 4\",\n                \"polymorphic_ctype\": [\"tests\", \"natkeychild\"],\n                \"slug\": \"slug-4\",\n            },\n            \"model\": \"tests.natkeyparent\",\n        },\n        {\"fields\": {\"foo\": [\"slug-0\"], \"val\": 0}, \"model\": \"tests.natkeychild\"},\n        {\"fields\": {\"foo\": [\"slug-1\"], \"val\": 1}, \"model\": \"tests.natkeychild\"},\n        {\"fields\": {\"foo\": [\"slug-2\"], \"val\": 2}, \"model\": \"tests.natkeychild\"},\n        {\"fields\": {\"foo\": [\"slug-3\"], \"val\": 3}, \"model\": \"tests.natkeychild\"},\n        {\"fields\": {\"foo\": [\"slug-4\"], \"val\": 4}, \"model\": \"tests.natkeychild\"},\n    ]\n\n    NatKeyChild.objects.all().delete()\n    NatKeyParent.objects.all().delete()\n\n    with tempfile.NamedTemporaryFile(mode=\"w\", suffix=\".json\", delete=False) as f:\n        f.write(json.dumps(data))\n        fixture_file = f.name\n\n    try:\n        call_command(\"loaddata\", fixture_file, verbosity=0)\n\n        for old, new in zip(\n            natkey_dump_objects,\n            NatKeyParent.objects.order_by(\"pk\").all(),\n        ):\n            assert new.__class__ == old.__class__ is NatKeyChild\n            assert new.slug == old.slug\n            assert new.content == old.content\n            assert new.val == old.val\n\n    finally:\n        # Clean up temporary file\n        os.unlink(fixture_file)\n"
  },
  {
    "path": "src/polymorphic/tests/test_signals.py",
    "content": "from django.test import TestCase\nfrom django.db.models.signals import post_delete\nfrom django.db import connection\nfrom .models import Model2A, Model2B, Model2C, PlainA, PlainB, PlainC\n\nfrom django.test.utils import CaptureQueriesContext\n\n\nclass TestSignals(TestCase):\n    def test_first_behavior_during_post_delete_signal_1(self):\n        \"\"\"\n        Regression test for issue #347: first() returning None in post_delete signals.\n\n        The bug occurs when:\n        1. An object with a post_delete signal is created first\n        2. Another object is created second\n        3. The first object is deleted\n        4. In the post_delete signal, first() should return the second object but\n           returned None\n        \"\"\"\n        obj1 = Model2B.objects.create(field1=\"First\", field2=\"B\")\n        obj2 = Model2A.objects.create(field1=\"Second\")\n\n        def log_first_with_first_method(sender, instance, **kwargs):\n            self.assertEqual(Model2A.objects.order_by(\"pk\").first().pk, obj1.pk)\n            self.assertEqual(Model2A.objects.order_by(\"pk\").last().pk, obj2.pk)\n            self.assertEqual(Model2B.objects.count(), 0)\n            self.assertEqual(Model2A.objects.count(), 2)\n\n        post_delete.connect(log_first_with_first_method, sender=Model2B)\n        obj1.delete()\n        post_delete.disconnect(log_first_with_first_method, sender=Model2C)\n\n    def test_first_behavior_during_post_delete_signal_2(self):\n        \"\"\"\n        Test that the fix works when the object with signal is created second.\n        The bug only occurred when the signaled object was created first.\n        \"\"\"\n        obj1 = Model2A.objects.create(field1=\"Second\")\n        obj2 = Model2B.objects.create(field1=\"First\", field2=\"B\")\n\n        def log_first_with_first_method(sender, instance, **kwargs):\n            self.assertEqual(Model2A.objects.order_by(\"pk\").first(), obj1)\n            self.assertEqual(Model2B.objects.count(), 0)\n\n        post_delete.connect(log_first_with_first_method, sender=Model2B)\n        obj2.delete()\n        post_delete.disconnect(log_first_with_first_method, sender=Model2C)\n\n    def test_getitem_behavior_during_post_delete_signal(self):\n        \"\"\"\n        Regression test for issue #347: [0] returning None in post_delete signals.\n        \"\"\"\n        obj1 = Model2C.objects.create(field1=\"First\", field2=\"C\", field3=\"C3\")\n        obj2 = Model2A.objects.create(field1=\"Second\")\n\n        def log_first_with_brackets(sender, instance, **kwargs):\n            try:\n                self.assertEqual(Model2A.objects.order_by(\"pk\")[0].pk, obj1.pk)\n                self.assertEqual(Model2A.objects.order_by(\"pk\")[1].pk, obj2.pk)\n                self.assertEqual(Model2C.objects.count(), 0)\n                self.assertEqual(Model2A.objects.count(), 2)\n            except IndexError:\n                self.fail(\"Queryset __getitem__[0] returned IndexError unexpectedly\")\n\n        post_delete.connect(log_first_with_brackets, sender=Model2C)\n        obj1.delete()  # trigger signal handling\n        post_delete.disconnect(log_first_with_brackets, sender=Model2C)\n\n    def test_normal_getitem_behavior_during_post_delete_signal(self):\n        \"\"\"\n        Illustrate standard Django multi-table inheritance during this test.\n        \"\"\"\n        obj1 = PlainC.objects.create(field1=\"First\", field2=\"C\", field3=\"C3\")\n        obj2 = PlainA.objects.create(field1=\"Second\")\n\n        def log_first_with_brackets(sender, instance, **kwargs):\n            try:\n                self.assertEqual(PlainA.objects.order_by(\"pk\")[0].pk, obj1.pk)\n                self.assertEqual(PlainA.objects.order_by(\"pk\")[1], obj2)\n                self.assertEqual(PlainC.objects.count(), 0)\n            except IndexError:\n                self.fail(\"Queryset __getitem__[0] returned IndexError unexpectedly\")\n\n        post_delete.connect(log_first_with_brackets, sender=PlainC)\n        obj1.delete()  # trigger signal handling\n        post_delete.disconnect(log_first_with_brackets, sender=PlainC)\n\n    def test_queryset_first_returns_remaining_object_in_post_delete_signal(self):\n        \"\"\"\n        Regression test for issue #347: first() returning None in post_delete signals.\n\n        The bug occurs when:\n        1. An object with a post_delete signal is created first\n        2. Another object is created second\n        3. The first object is deleted\n        4. In the post_delete signal, first() should return the second object but returned None\n        \"\"\"\n        obj1 = Model2B.objects.create(field1=\"First\", field2=\"B\")\n        obj1_base = Model2A.objects.non_polymorphic().get(pk=obj1.pk)\n        obj2 = Model2A.objects.create(field1=\"Second\")\n\n        def check_2b_delete(sender, instance, **kwargs):\n            assert Model2B.objects.count() == 0\n            assert Model2A.objects.order_by(\"pk\").first() == obj1_base\n            assert Model2A.objects.count() == 2\n\n        def check_2a_delete(sender, instance, **kwargs):\n            assert Model2A.objects.order_by(\"pk\").first() == obj2\n            assert Model2A.objects.count() == 1\n\n        try:\n            post_delete.connect(check_2b_delete, sender=Model2B)\n            post_delete.connect(check_2a_delete, sender=Model2A)\n\n            # This will trigger the post_delete signal, first for the deletion of the\n            # 2b row - then for the deletion of the 2a row - at each signal the database\n            # should be consistent with the staged deletion of rows most derived first\n            # order.\n            obj1.delete()\n\n        finally:\n            post_delete.disconnect(check_2b_delete, sender=Model2B)\n            post_delete.disconnect(check_2a_delete, sender=Model2A)\n\n    def test_queryset_getitem_returns_remaining_object_in_post_delete_signal(self):\n        \"\"\"\n        Regression test for issue #347: [0] returning None in post_delete signals.\n        \"\"\"\n        obj1 = Model2C.objects.create(field1=\"First\", field2=\"C\", field3=\"C3\")\n        obj1_baseb = Model2B.objects.non_polymorphic().get(pk=obj1.pk)\n        obj1_basea = Model2A.objects.non_polymorphic().get(pk=obj1.pk)\n        obj2 = Model2A.objects.create(field1=\"Second\")\n\n        def check_2c_delete(sender, instance, **kwargs):\n            assert Model2C.objects.count() == 0\n            assert Model2B.objects.count() == 1\n            assert Model2A.objects.count() == 2\n            assert Model2B.objects.order_by(\"pk\")[0] == obj1_baseb\n            assert Model2A.objects.order_by(\"pk\")[0] == obj1_baseb\n\n        def check_2b_delete(sender, instance, **kwargs):\n            assert Model2C.objects.count() == 0\n            assert Model2B.objects.count() == 0\n            assert Model2A.objects.count() == 2\n            assert Model2A.objects.order_by(\"pk\")[0] == obj1_basea\n\n        def check_2a_delete(sender, instance, **kwargs):\n            assert Model2C.objects.count() == 0\n            assert Model2B.objects.count() == 0\n            assert Model2A.objects.count() == 1\n            assert Model2A.objects.order_by(\"pk\")[0] == obj2\n\n        # Connect signal\n        try:\n            post_delete.connect(check_2c_delete, sender=Model2C)\n            post_delete.connect(check_2b_delete, sender=Model2B)\n            post_delete.connect(check_2a_delete, sender=Model2A)\n            obj1.delete()\n        finally:\n            post_delete.disconnect(check_2c_delete, sender=Model2C)\n            post_delete.disconnect(check_2b_delete, sender=Model2B)\n            post_delete.disconnect(check_2a_delete, sender=Model2A)\n\n    def test_queryset_first_works_when_deleted_object_created_second(self):\n        \"\"\"\n        Test that the fix works when the object with signal is created second.\n        The bug only occurred when the signaled object was created first.\n        \"\"\"\n        obj1 = Model2A.objects.create(field1=\"Second\")\n        obj2 = Model2B.objects.create(field1=\"First\", field2=\"B\")\n        obj2_base = Model2A.objects.non_polymorphic().get(pk=obj2.pk)\n\n        def check_2b_delete(sender, instance, **kwargs):\n            assert Model2B.objects.count() == 0\n            assert Model2A.objects.order_by(\"pk\").first() == obj1\n            assert Model2A.objects.last() == obj2_base\n            assert Model2A.objects.count() == 2\n\n        def check_2a_delete(sender, instance, **kwargs):\n            assert Model2A.objects.order_by(\"pk\").first() == obj1\n            assert Model2A.objects.count() == 1\n\n        try:\n            post_delete.connect(check_2a_delete, sender=Model2A)\n            post_delete.connect(check_2b_delete, sender=Model2B)\n\n            # This will trigger the post_delete signal, first for the deletion of the\n            # 2b row - then for the deletion of the 2a row - at each signal the database\n            # should be consistent with the staged deletion of rows most derived first\n            # order.\n            obj2.delete()\n\n        finally:\n            post_delete.disconnect(check_2b_delete, sender=Model2B)\n            post_delete.disconnect(check_2a_delete, sender=Model2A)\n\n    def test_besteffort_iteration_avoids_nplusone(self):\n        \"\"\"\n        Test that our best effort iteration avoids n+1 queries when n objects have stale\n        content type pointers.\n        \"\"\"\n        for i in range(100):\n            Model2C.objects.create(\n                field1=f\"Model2C_{i}\", field2=\"Model2C_{i}\", field3=\"Model2C_{i}\"\n            )\n\n        def check_2c_delete(sender, instance, **kwargs):\n            assert Model2C.objects.count() == 0\n            assert Model2B.objects.count() == 100\n            assert Model2A.objects.count() == 100\n\n            from django.db.backends.utils import CursorWrapper\n\n            for _ in Model2B.objects.all():\n                pass  # Evaluating the queryset\n\n            with CaptureQueriesContext(connection) as all_2b:\n                for _ in Model2B.objects.all():\n                    pass  # Evaluating the queryset\n\n            assert len(all_2b.captured_queries) <= 3\n\n            with CaptureQueriesContext(connection) as all_2a:\n                for _ in Model2A.objects.all():\n                    pass  # Evaluating the queryset\n\n            assert len(all_2a.captured_queries) <= 4\n\n        def check_2b_delete(sender, instance, **kwargs):\n            assert Model2C.objects.count() == 0\n            assert Model2B.objects.count() == 0\n            assert Model2A.objects.count() == 100\n\n            with CaptureQueriesContext(connection) as all_2a:\n                for _ in Model2A.objects.all():\n                    pass  # Evaluating the queryset\n\n            assert len(all_2a.captured_queries) <= 3\n\n        def check_2a_delete(sender, instance, **kwargs):\n            assert Model2C.objects.count() == 0\n            assert Model2B.objects.count() == 0\n            assert Model2A.objects.count() == 0\n\n            with CaptureQueriesContext(connection) as all_2a:\n                for _ in Model2A.objects.all():\n                    pass  # Evaluating the queryset\n\n            assert len(all_2a.captured_queries) <= 3\n\n        try:\n            post_delete.connect(check_2b_delete, sender=Model2B)\n            post_delete.connect(check_2a_delete, sender=Model2A)\n            post_delete.connect(check_2c_delete, sender=Model2C)\n\n            Model2C.objects.all().delete()\n\n        finally:\n            post_delete.disconnect(check_2b_delete, sender=Model2B)\n            post_delete.disconnect(check_2a_delete, sender=Model2A)\n            post_delete.disconnect(check_2c_delete, sender=Model2C)\n"
  },
  {
    "path": "src/polymorphic/tests/test_templatetags.py",
    "content": "\"\"\"\nTests for polymorphic templatetags.\n\"\"\"\n\nimport html\nimport json\n\nimport pytest\nfrom django.template import Context, Template, TemplateSyntaxError\nfrom django.test import TestCase\n\nfrom polymorphic.formsets import (\n    PolymorphicFormSetChild,\n    polymorphic_modelformset_factory,\n)\nfrom polymorphic.tests.models import Model2A, Model2B\n\n\ndef parse_json_from_template(result):\n    \"\"\"Parse JSON from template output, handling HTML escaping.\"\"\"\n    return json.loads(html.unescape(result))\n\n\nclass BreadcrumbScopeTagTest(TestCase):\n    \"\"\"Tests for the breadcrumb_scope template tag\"\"\"\n\n    def test_breadcrumb_scope_sets_app_label(self):\n        \"\"\"breadcrumb_scope sets app_label from base_opts\"\"\"\n        template = Template(\n            \"{% load polymorphic_admin_tags %}\"\n            \"{% breadcrumb_scope base_opts %}{{ app_label }}{% endbreadcrumb_scope %}\"\n        )\n        context = Context({\"base_opts\": Model2A._meta})\n        result = template.render(context)\n\n        assert result == \"tests\"\n\n    def test_breadcrumb_scope_sets_opts(self):\n        \"\"\"breadcrumb_scope sets opts from base_opts\"\"\"\n        template = Template(\n            \"{% load polymorphic_admin_tags %}\"\n            \"{% breadcrumb_scope base_opts %}{{ opts.model_name }}{% endbreadcrumb_scope %}\"\n        )\n        context = Context({\"base_opts\": Model2A._meta})\n        result = template.render(context)\n\n        assert result == \"model2a\"\n\n    def test_breadcrumb_scope_restores_context(self):\n        \"\"\"breadcrumb_scope restores original context after block\"\"\"\n        template = Template(\n            \"{% load polymorphic_admin_tags %}\"\n            \"before:{{ app_label }}|\"\n            \"{% breadcrumb_scope base_opts %}inside:{{ app_label }}{% endbreadcrumb_scope %}|\"\n            \"after:{{ app_label }}\"\n        )\n        context = Context({\"base_opts\": Model2A._meta, \"app_label\": \"original\"})\n        result = template.render(context)\n\n        assert result == \"before:original|inside:tests|after:original\"\n\n    def test_breadcrumb_scope_with_none_base_opts(self):\n        \"\"\"breadcrumb_scope handles None base_opts gracefully\"\"\"\n        template = Template(\n            \"{% load polymorphic_admin_tags %}\"\n            \"{% breadcrumb_scope base_opts %}{{ app_label|default:'empty' }}{% endbreadcrumb_scope %}\"\n        )\n        context = Context({\"base_opts\": None, \"app_label\": \"fallback\"})\n        result = template.render(context)\n\n        # When base_opts is None, original context values are preserved\n        assert result == \"fallback\"\n\n    def test_breadcrumb_scope_with_string_base_opts(self):\n        \"\"\"breadcrumb_scope handles string base_opts (doesn't set new values)\"\"\"\n        template = Template(\n            \"{% load polymorphic_admin_tags %}\"\n            \"{% breadcrumb_scope base_opts %}{{ app_label|default:'empty' }}{% endbreadcrumb_scope %}\"\n        )\n        context = Context({\"base_opts\": \"some_string\", \"app_label\": \"original\"})\n        result = template.render(context)\n\n        # When base_opts is a string, original context values are preserved\n        assert result == \"original\"\n\n    def test_breadcrumb_scope_missing_argument_raises_error(self):\n        \"\"\"breadcrumb_scope raises TemplateSyntaxError without argument\"\"\"\n        with pytest.raises(TemplateSyntaxError, match=\"expects 1 argument\"):\n            Template(\n                \"{% load polymorphic_admin_tags %}{% breadcrumb_scope %}{% endbreadcrumb_scope %}\"\n            )\n\n    def test_breadcrumb_scope_too_many_arguments_raises_error(self):\n        \"\"\"breadcrumb_scope raises TemplateSyntaxError with too many arguments\"\"\"\n        with pytest.raises(TemplateSyntaxError, match=\"expects 1 argument\"):\n            Template(\n                \"{% load polymorphic_admin_tags %}\"\n                \"{% breadcrumb_scope arg1 arg2 %}{% endbreadcrumb_scope %}\"\n            )\n\n    def test_breadcrumb_scope_with_variable_lookup(self):\n        \"\"\"breadcrumb_scope works with variable lookup\"\"\"\n        template = Template(\n            \"{% load polymorphic_admin_tags %}\"\n            \"{% breadcrumb_scope model_meta %}{{ opts.verbose_name }}{% endbreadcrumb_scope %}\"\n        )\n        context = Context({\"model_meta\": Model2B._meta})\n        result = template.render(context)\n\n        assert \"model2b\" in result.lower()\n\n\nclass IncludeEmptyFormFilterTest(TestCase):\n    \"\"\"Tests for the include_empty_form filter\"\"\"\n\n    def test_include_empty_form_with_polymorphic_formset(self):\n        \"\"\"include_empty_form yields forms and empty_forms for polymorphic formsets\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            extra=0,\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n                PolymorphicFormSetChild(model=Model2B),\n            ],\n        )\n\n        obj = Model2A.objects.create(field1=\"test\")\n        formset = FormSet(queryset=Model2A.objects.filter(pk=obj.pk))\n\n        template = Template(\n            \"{% load polymorphic_formset_tags %}\"\n            \"{% for form in formset|include_empty_form %}{{ forloop.counter }},{% endfor %}\"\n        )\n        context = Context({\"formset\": formset})\n        result = template.render(context)\n\n        # 1 existing form + 2 empty forms (one for each child type)\n        assert result == \"1,2,3,\"\n\n    def test_include_empty_form_with_standard_formset(self):\n        \"\"\"include_empty_form yields forms and single empty_form for standard formsets\"\"\"\n        from django.forms import modelformset_factory\n\n        FormSet = modelformset_factory(Model2A, fields=\"__all__\", extra=0)\n        obj = Model2A.objects.create(field1=\"test\")\n        formset = FormSet(queryset=Model2A.objects.filter(pk=obj.pk))\n\n        template = Template(\n            \"{% load polymorphic_formset_tags %}\"\n            \"{% for form in formset|include_empty_form %}{{ forloop.counter }},{% endfor %}\"\n        )\n        context = Context({\"formset\": formset})\n        result = template.render(context)\n\n        # 1 existing form + 1 empty form\n        assert result == \"1,2,\"\n\n    def test_include_empty_form_with_empty_polymorphic_formset(self):\n        \"\"\"include_empty_form with no existing objects yields only empty forms\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            extra=0,\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n                PolymorphicFormSetChild(model=Model2B),\n            ],\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n\n        template = Template(\n            \"{% load polymorphic_formset_tags %}\"\n            \"{% for form in formset|include_empty_form %}{{ forloop.counter }},{% endfor %}\"\n        )\n        context = Context({\"formset\": formset})\n        result = template.render(context)\n\n        # 0 existing forms + 2 empty forms\n        assert result == \"1,2,\"\n\n\nclass AsScriptOptionsFilterTest(TestCase):\n    \"\"\"Tests for the as_script_options filter\"\"\"\n\n    def test_as_script_options_returns_valid_json(self):\n        \"\"\"as_script_options returns valid JSON\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n            ],\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ formset|as_script_options }}\")\n        context = Context({\"formset\": formset})\n        result = template.render(context)\n\n        # Should be valid JSON\n        options = parse_json_from_template(result)\n        assert isinstance(options, dict)\n\n    def test_as_script_options_contains_prefix(self):\n        \"\"\"as_script_options includes formset prefix\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n            ],\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ formset|as_script_options }}\")\n        context = Context({\"formset\": formset})\n        result = template.render(context)\n\n        options = parse_json_from_template(result)\n        assert \"prefix\" in options\n        assert options[\"prefix\"] == formset.prefix\n\n    def test_as_script_options_contains_pk_field_name(self):\n        \"\"\"as_script_options includes pkFieldName\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n            ],\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ formset|as_script_options }}\")\n        context = Context({\"formset\": formset})\n        result = template.render(context)\n\n        options = parse_json_from_template(result)\n        assert \"pkFieldName\" in options\n        assert options[\"pkFieldName\"] == \"id\"\n\n    def test_as_script_options_contains_add_text(self):\n        \"\"\"as_script_options includes addText\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n            ],\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ formset|as_script_options }}\")\n        context = Context({\"formset\": formset})\n        result = template.render(context)\n\n        options = parse_json_from_template(result)\n        assert \"addText\" in options\n        assert \"Add another\" in options[\"addText\"]\n\n    def test_as_script_options_contains_show_add_button(self):\n        \"\"\"as_script_options includes showAddButton\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n            ],\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ formset|as_script_options }}\")\n        context = Context({\"formset\": formset})\n        result = template.render(context)\n\n        options = parse_json_from_template(result)\n        assert \"showAddButton\" in options\n        assert options[\"showAddButton\"] is True\n\n    def test_as_script_options_contains_delete_text(self):\n        \"\"\"as_script_options includes deleteText\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n            ],\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ formset|as_script_options }}\")\n        context = Context({\"formset\": formset})\n        result = template.render(context)\n\n        options = parse_json_from_template(result)\n        assert \"deleteText\" in options\n        assert options[\"deleteText\"] == \"Delete\"\n\n    def test_as_script_options_polymorphic_contains_child_types(self):\n        \"\"\"as_script_options includes childTypes for polymorphic formsets\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n                PolymorphicFormSetChild(model=Model2B),\n            ],\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ formset|as_script_options }}\")\n        context = Context({\"formset\": formset})\n        result = template.render(context)\n\n        options = parse_json_from_template(result)\n        assert \"childTypes\" in options\n        assert len(options[\"childTypes\"]) == 2\n\n        child_type_names = [ct[\"type\"] for ct in options[\"childTypes\"]]\n        assert \"model2a\" in child_type_names\n        assert \"model2b\" in child_type_names\n\n    def test_as_script_options_child_types_have_name_and_type(self):\n        \"\"\"as_script_options childTypes have name and type keys\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n            ],\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ formset|as_script_options }}\")\n        context = Context({\"formset\": formset})\n        result = template.render(context)\n\n        options = parse_json_from_template(result)\n        child_type = options[\"childTypes\"][0]\n        assert \"name\" in child_type\n        assert \"type\" in child_type\n\n    def test_as_script_options_standard_formset_no_child_types(self):\n        \"\"\"as_script_options for standard formsets doesn't include childTypes\"\"\"\n        from django.forms import modelformset_factory\n\n        FormSet = modelformset_factory(Model2A, fields=\"__all__\")\n        formset = FormSet(queryset=Model2A.objects.none())\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ formset|as_script_options }}\")\n        context = Context({\"formset\": formset})\n        result = template.render(context)\n\n        options = parse_json_from_template(result)\n        assert \"childTypes\" not in options\n\n    def test_as_script_options_custom_verbose_name(self):\n        \"\"\"as_script_options uses custom verbose_name from formset\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n            ],\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n        formset.verbose_name = \"Custom Name\"\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ formset|as_script_options }}\")\n        context = Context({\"formset\": formset})\n        result = template.render(context)\n\n        options = parse_json_from_template(result)\n        assert \"Custom Name\" in options[\"addText\"]\n\n    def test_as_script_options_custom_add_text(self):\n        \"\"\"as_script_options uses custom add_text from formset\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n            ],\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n        formset.add_text = \"Custom Add Button\"\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ formset|as_script_options }}\")\n        context = Context({\"formset\": formset})\n        result = template.render(context)\n\n        options = parse_json_from_template(result)\n        assert options[\"addText\"] == \"Custom Add Button\"\n\n    def test_as_script_options_custom_show_add_button(self):\n        \"\"\"as_script_options uses custom show_add_button from formset\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n            ],\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n        formset.show_add_button = False\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ formset|as_script_options }}\")\n        context = Context({\"formset\": formset})\n        result = template.render(context)\n\n        options = parse_json_from_template(result)\n        assert options[\"showAddButton\"] is False\n\n\nclass AsFormTypeFilterTest(TestCase):\n    \"\"\"Tests for the as_form_type filter\"\"\"\n\n    def test_as_form_type_returns_model_name(self):\n        \"\"\"as_form_type returns the model name from a form\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n                PolymorphicFormSetChild(model=Model2B),\n            ],\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n        # Get one of the empty forms\n        form = formset.empty_forms[0]\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ form|as_form_type }}\")\n        context = Context({\"form\": form})\n        result = template.render(context)\n\n        assert result in [\"model2a\", \"model2b\"]\n\n    def test_as_form_type_with_model2a_form(self):\n        \"\"\"as_form_type returns 'model2a' for Model2A form\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2A),\n            ],\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n        form = formset.empty_forms[0]\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ form|as_form_type }}\")\n        context = Context({\"form\": form})\n        result = template.render(context)\n\n        assert result == \"model2a\"\n\n    def test_as_form_type_with_model2b_form(self):\n        \"\"\"as_form_type returns 'model2b' for Model2B form\"\"\"\n        FormSet = polymorphic_modelformset_factory(\n            model=Model2A,\n            fields=\"__all__\",\n            formset_children=[\n                PolymorphicFormSetChild(model=Model2B),\n            ],\n        )\n\n        formset = FormSet(queryset=Model2A.objects.none())\n        form = formset.empty_forms[0]\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ form|as_form_type }}\")\n        context = Context({\"form\": form})\n        result = template.render(context)\n\n        assert result == \"model2b\"\n\n\nclass AsModelNameFilterTest(TestCase):\n    \"\"\"Tests for the as_model_name filter\"\"\"\n\n    def test_as_model_name_with_model_class(self):\n        \"\"\"as_model_name returns model name from a model class\"\"\"\n        template = Template(\"{% load polymorphic_formset_tags %}{{ model|as_model_name }}\")\n        context = Context({\"model\": Model2A})\n        result = template.render(context)\n\n        assert result == \"model2a\"\n\n    def test_as_model_name_with_model_instance(self):\n        \"\"\"as_model_name returns model name from a model instance\"\"\"\n        obj = Model2A(field1=\"test\")\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ model|as_model_name }}\")\n        context = Context({\"model\": obj})\n        result = template.render(context)\n\n        assert result == \"model2a\"\n\n    def test_as_model_name_with_child_model(self):\n        \"\"\"as_model_name returns correct name for child model\"\"\"\n        template = Template(\"{% load polymorphic_formset_tags %}{{ model|as_model_name }}\")\n        context = Context({\"model\": Model2B})\n        result = template.render(context)\n\n        assert result == \"model2b\"\n\n    def test_as_model_name_with_child_model_instance(self):\n        \"\"\"as_model_name returns correct name for child model instance\"\"\"\n        obj = Model2B(field1=\"test\", field2=\"test2\")\n\n        template = Template(\"{% load polymorphic_formset_tags %}{{ model|as_model_name }}\")\n        context = Context({\"model\": obj})\n        result = template.render(context)\n\n        assert result == \"model2b\"\n\n    def test_as_model_name_in_loop(self):\n        \"\"\"as_model_name works correctly in a loop over models\"\"\"\n        template = Template(\n            \"{% load polymorphic_formset_tags %}\"\n            \"{% for model in models %}{{ model|as_model_name }},{% endfor %}\"\n        )\n        context = Context({\"models\": [Model2A, Model2B]})\n        result = template.render(context)\n\n        assert result == \"model2a,model2b,\"\n"
  },
  {
    "path": "src/polymorphic/tests/test_utils.py",
    "content": "import pytest\nfrom django.test import TransactionTestCase\nfrom django.contrib.contenttypes.models import ContentType\n\nfrom polymorphic.models import PolymorphicModel, PolymorphicTypeUndefined\nfrom polymorphic.tests.models import (\n    Enhance_Base,\n    Enhance_Inherit,\n    Model2A,\n    Model2B,\n    Model2C,\n    Model2D,\n)\nfrom polymorphic.utils import (\n    get_base_polymorphic_model,\n    reset_polymorphic_ctype,\n    sort_by_subclass,\n    route_to_ancestor,\n    concrete_descendants,\n)\n\n\nclass UtilsTests(TransactionTestCase):\n    def test_sort_by_subclass(self):\n        assert sort_by_subclass(Model2D, Model2B, Model2D, Model2A, Model2C) == [\n            Model2A,\n            Model2B,\n            Model2C,\n            Model2D,\n            Model2D,\n        ]\n\n    def test_reset_polymorphic_ctype(self):\n        \"\"\"\n        Test the the polymorphic_ctype_id can be restored.\n        \"\"\"\n        Model2A.objects.create(field1=\"A1\")\n        Model2D.objects.create(field1=\"A1\", field2=\"B2\", field3=\"C3\", field4=\"D4\")\n        Model2B.objects.create(field1=\"A1\", field2=\"B2\")\n        Model2B.objects.create(field1=\"A1\", field2=\"B2\")\n        Model2A.objects.all().update(polymorphic_ctype_id=None)\n\n        with pytest.raises(PolymorphicTypeUndefined):\n            list(Model2A.objects.all())\n\n        reset_polymorphic_ctype(Model2D, Model2B, Model2D, Model2A, Model2C)\n\n        self.assertQuerySetEqual(\n            Model2A.objects.order_by(\"pk\"),\n            [Model2A, Model2D, Model2B, Model2B],\n            transform=lambda o: o.__class__,\n        )\n\n    def test_get_base_polymorphic_model(self):\n        \"\"\"\n        Test that finding the base polymorphic model works.\n        \"\"\"\n        # Finds the base from every level (including lowest)\n        assert get_base_polymorphic_model(Model2D) is Model2A\n        assert get_base_polymorphic_model(Model2C) is Model2A\n        assert get_base_polymorphic_model(Model2B) is Model2A\n        assert get_base_polymorphic_model(Model2A) is Model2A\n\n        # Properly handles multiple inheritance\n        assert get_base_polymorphic_model(Enhance_Inherit) is Enhance_Base\n\n        # Ignores PolymorphicModel itself.\n        assert get_base_polymorphic_model(PolymorphicModel) is None\n\n    def test_get_base_polymorphic_model_skip_abstract(self):\n        \"\"\"\n        Skipping abstract models that can't be used for querying.\n        \"\"\"\n\n        class A(PolymorphicModel):\n            class Meta:\n                abstract = True\n\n        class B(A):\n            pass\n\n        class C(B):\n            pass\n\n        assert get_base_polymorphic_model(A) is None\n        assert get_base_polymorphic_model(B) is B\n        assert get_base_polymorphic_model(C) is B\n\n        assert get_base_polymorphic_model(C, allow_abstract=True) is A\n\n    def test_concrete_descendants(self):\n        \"\"\"\n        Test that finding concrete descendants works.\n        \"\"\"\n        from .models import (\n            Model2A,\n            Model2B,\n            Model2C,\n            Model2D,\n            ModelWithMyManager,\n            ModelWithMyManagerNoDefault,\n            ModelWithMyManagerDefault,\n            ModelWithMyManager2,\n            Base,\n            ModelX,\n            ModelY,\n            BlogBase,\n            BlogA,\n            BlogB,\n            RelationBase,\n            RelationA,\n            RelationB,\n            RelationBC,\n            ProxyBase,\n            NonProxyChild,\n            ProxiedBase,\n            ProxyModelA,\n            ProxyModelB,\n            ProxyModelBase,\n            CustomPkBase,\n            CustomPkInherit,\n            MultiTableBase,\n            MultiTableDerived,\n            FKTestChild,\n            Model2BFiltered,\n            Model2CFiltered,\n            Model2CNamedDefault,\n            Model2CNamedManagers,\n        )\n\n        # Model2A hierarchy (with manager variants)\n        assert concrete_descendants(Model2A) == [\n            Model2B,\n            Model2C,\n            Model2D,\n            Model2BFiltered,\n            Model2CFiltered,\n            Model2CNamedManagers,\n            Model2CNamedDefault,\n            ModelWithMyManager,\n            ModelWithMyManagerNoDefault,\n            ModelWithMyManagerDefault,\n            ModelWithMyManager2,\n        ]\n        assert concrete_descendants(Model2B) == [\n            Model2C,\n            Model2D,\n            Model2BFiltered,\n            Model2CFiltered,\n            Model2CNamedManagers,\n            Model2CNamedDefault,\n        ]\n        assert concrete_descendants(Model2C) == [Model2D]\n        assert len(concrete_descendants(Model2D)) == 0\n\n        # ModelWithMyManager variants (no further descendants)\n        assert len(concrete_descendants(ModelWithMyManager)) == 0\n        assert len(concrete_descendants(ModelWithMyManagerNoDefault)) == 0\n        assert len(concrete_descendants(ModelWithMyManagerDefault)) == 0\n        assert len(concrete_descendants(ModelWithMyManager2)) == 0\n\n        # Base hierarchy (tree order: ModelX defined before ModelY)\n        assert concrete_descendants(Base) == [ModelX, ModelY, FKTestChild]\n        assert len(concrete_descendants(ModelX)) == 0\n        assert len(concrete_descendants(ModelY)) == 0\n\n        # BlogBase hierarchy (tree order: BlogA defined before BlogB)\n        assert concrete_descendants(BlogBase) == [BlogA, BlogB]\n        assert len(concrete_descendants(BlogA)) == 0\n        assert len(concrete_descendants(BlogB)) == 0\n\n        # RelationBase hierarchy (tree order: RelationA before RelationB, RelationBC is child of RelationB)\n        assert concrete_descendants(RelationBase) == [\n            RelationA,\n            RelationB,\n            RelationBC,\n        ]\n        assert len(concrete_descendants(RelationA)) == 0\n        assert concrete_descendants(RelationB) == [RelationBC]\n        assert len(concrete_descendants(RelationBC)) == 0\n\n        # ProxyBase hierarchy (ProxyChild is proxy, so excluded)\n        assert concrete_descendants(ProxyBase) == [NonProxyChild]\n        assert len(concrete_descendants(NonProxyChild)) == 0\n\n        # ProxiedBase hierarchy (tree order: ProxyModelA defined before ProxyModelB)\n        # ProxyModelBase is proxy, but has concrete children\n        assert concrete_descendants(ProxiedBase) == [ProxyModelA, ProxyModelB]\n        # ProxyModelBase is proxy but should still return its concrete descendants\n        assert concrete_descendants(ProxyModelBase) == [ProxyModelA, ProxyModelB]\n        assert len(concrete_descendants(ProxyModelA)) == 0\n        assert len(concrete_descendants(ProxyModelB)) == 0\n\n        # CustomPkBase hierarchy\n        assert concrete_descendants(CustomPkBase) == [CustomPkInherit]\n        assert len(concrete_descendants(CustomPkInherit)) == 0\n\n        # MultiTableBase hierarchy\n        assert concrete_descendants(MultiTableBase) == [MultiTableDerived]\n        assert len(concrete_descendants(MultiTableDerived)) == 0\n\n        # Enhance_Base hierarchy\n        assert concrete_descendants(Enhance_Base) == [Enhance_Inherit]\n        assert len(concrete_descendants(Enhance_Inherit)) == 0\n\n    def test_route_to_ancestor(self):\n        \"\"\"\n        Test that finding routes to ancestors works correctly.\n        \"\"\"\n        from .models import (\n            Model2A,\n            Model2B,\n            Model2C,\n            Model2D,\n            Base,\n            ModelX,\n            ModelY,\n            BlogBase,\n            BlogA,\n            RelationBase,\n            RelationA,\n            RelationB,\n            RelationBC,\n            Enhance_Base,\n            Enhance_Inherit,\n            PurpleHeadDuck,\n            Duck,\n        )\n\n        # Test direct parent (one hop)\n        route = route_to_ancestor(Model2B, Model2A)\n        assert len(route) == 1\n        assert route[0].model == Model2A\n        assert route[0].link.name == \"model2a_ptr\"\n\n        # Test grandparent (two hops)\n        route = route_to_ancestor(Model2C, Model2A)\n        assert len(route) == 2\n        assert route[0].model == Model2B\n        assert route[0].link.name == \"model2b_ptr\"\n        assert route[1].model == Model2A\n        assert route[1].link.name == \"model2a_ptr\"\n\n        # Test great-grandparent (three hops)\n        route = route_to_ancestor(Model2D, Model2A)\n        assert len(route) == 3\n        assert route[0].model == Model2C\n        assert route[0].link.name == \"model2c_ptr\"\n        assert route[1].model == Model2B\n        assert route[1].link.name == \"model2b_ptr\"\n        assert route[2].model == Model2A\n        assert route[2].link.name == \"model2a_ptr\"\n\n        # Test intermediate ancestor (skip one level)\n        route = route_to_ancestor(Model2D, Model2B)\n        assert len(route) == 2\n        assert route[0].model == Model2C\n        assert route[0].link.name == \"model2c_ptr\"\n        assert route[1].model == Model2B\n        assert route[1].link.name == \"model2b_ptr\"\n\n        route = route_to_ancestor(Model2D, Model2C)\n        assert len(route) == 1\n        assert route[0].model == Model2C\n        assert route[0].link.name == \"model2c_ptr\"\n\n        # Test self (should return empty)\n        assert route_to_ancestor(Model2A, Model2A) == []\n        assert route_to_ancestor(Model2B, Model2B) == []\n        assert route_to_ancestor(Model2D, Model2D) == []\n\n        # Test non-ancestor (should return empty)\n        assert route_to_ancestor(Model2A, Model2B) == []\n        assert route_to_ancestor(Model2B, Model2C) == []\n        assert route_to_ancestor(Model2C, Model2D) == []\n\n        # Test unrelated models (should return empty)\n        assert route_to_ancestor(ModelX, Model2A) == []\n        assert route_to_ancestor(Model2A, ModelX) == []\n        assert route_to_ancestor(BlogA, RelationA) == []\n\n        # Test different hierarchy - Base -> ModelX\n        route = route_to_ancestor(ModelX, Base)\n        assert len(route) == 1\n        assert route[0].model == Base\n        assert route[0].link.name == \"base_ptr\"\n\n        # Test different hierarchy - Base -> ModelY\n        route = route_to_ancestor(ModelY, Base)\n        assert len(route) == 1\n        assert route[0].model == Base\n        assert route[0].link.name == \"base_ptr\"\n\n        # Test BlogBase hierarchy\n        route = route_to_ancestor(BlogA, BlogBase)\n        assert len(route) == 1\n        assert route[0].model == BlogBase\n        assert route[0].link.name == \"blogbase_ptr\"\n\n        # Test multi-level RelationBase hierarchy\n        route = route_to_ancestor(RelationBC, RelationBase)\n        assert len(route) == 2\n        assert route[0].model == RelationB\n        assert route[0].link.name == \"relationb_ptr\"\n        assert route[1].model == RelationBase\n        assert route[1].link.name == \"relationbase_ptr\"\n\n        route = route_to_ancestor(RelationBC, RelationB)\n        assert len(route) == 1\n        assert route[0].model == RelationB\n        assert route[0].link.name == \"relationb_ptr\"\n\n        route = route_to_ancestor(RelationB, RelationBase)\n        assert len(route) == 1\n        assert route[0].model == RelationBase\n        assert route[0].link.name == \"relationbase_ptr\"\n\n        # Test multiple inheritance - Enhance_Inherit\n        route = route_to_ancestor(Enhance_Inherit, Enhance_Base)\n        assert len(route) == 1\n        assert route[0].model == Enhance_Base\n        assert route[0].link.name == \"enhance_base_ptr\"\n\n        route = route_to_ancestor(PurpleHeadDuck, Duck)\n        assert len(route) == 1\n        assert route[0].model == Duck\n        assert route[0].link.name == \"duck_ptr\"\n\n\nclass PrepareForCopyTests(TransactionTestCase):\n    def test_copy_polymorphic_objects(self):\n        \"\"\"\n        Test copying polymorphic objects with multi-level inheritance.\n        https://github.com/jazzband/django-polymorphic/issues/414\n\n        This test verifies that the prepare_for_copy() method\n        correctly handles copying objects with 2, 3, and 4 levels of inheritance.\n        \"\"\"\n        from polymorphic.utils import prepare_for_copy\n        from polymorphic.tests.models import Model2B, Model2C, Model2D\n\n        # Create original objects\n        obj_b = Model2B.objects.create(field1=\"B1\", field2=\"B2\")\n        obj_c = Model2C.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n        obj_d = Model2D.objects.create(field1=\"D1\", field2=\"D2\", field3=\"D3\", field4=\"D4\")\n\n        original_b_pk = obj_b.pk\n        original_c_pk = obj_c.pk\n        original_d_pk = obj_d.pk\n\n        # Note: Model2C and Model2D inherit from Model2B, so they're also counted in Model2B.objects\n        # Initial counts: Model2B=3 (obj_b, obj_c, obj_d), Model2C=2 (obj_c, obj_d), Model2D=1 (obj_d)\n        assert Model2B.objects.count() == 3  # obj_b + obj_c + obj_d\n        assert Model2C.objects.count() == 2  # obj_c + obj_d\n        assert Model2D.objects.count() == 1  # obj_d\n\n        # Test 1: Copy Model2B (2-level inheritance) using new method\n        copy_b = Model2B.objects.get(pk=obj_b.pk)\n        copy_b.field1 = \"B1_copy\"\n        prepare_for_copy(copy_b)\n        copy_b.save()\n\n        # Verify the copy\n        assert copy_b.pk != original_b_pk\n        assert copy_b.field1 == \"B1_copy\"\n        assert copy_b.field2 == \"B2\"\n        assert Model2B.objects.filter(pk=original_b_pk).exists()\n        assert Model2B.objects.filter(pk=copy_b.pk).exists()\n        # Now we have: obj_b, copy_b, obj_c, obj_d\n        assert Model2B.objects.count() == 4\n\n        # Test 2: Copy Model2C (3-level inheritance) using new method\n        # This is the main issue from #414 - previously failed\n        copy_c = Model2C.objects.get(pk=obj_c.pk)\n        copy_c.field1 = \"C1_copy\"\n        prepare_for_copy(copy_c)\n        copy_c.save()\n\n        # Verify the copy\n        assert copy_c.pk != original_c_pk\n        assert copy_c.field1 == \"C1_copy\"\n        assert copy_c.field2 == \"C2\"\n        assert copy_c.field3 == \"C3\"\n        assert Model2C.objects.filter(pk=original_c_pk).exists()\n        assert Model2C.objects.filter(pk=copy_c.pk).exists()\n        # Now we have Model2C: obj_c, copy_c, obj_d\n        assert Model2C.objects.count() == 3\n        # And Model2B: obj_b, copy_b, obj_c, copy_c, obj_d\n        assert Model2B.objects.count() == 5\n\n        # Test 3: Copy Model2D (4-level inheritance) using new method\n        copy_d = Model2D.objects.get(pk=obj_d.pk)\n        copy_d.field1 = \"D1_copy\"\n        prepare_for_copy(copy_d)\n        copy_d.save()\n\n        # Verify the copy\n        assert copy_d.pk != original_d_pk\n        assert copy_d.field1 == \"D1_copy\"\n        assert copy_d.field2 == \"D2\"\n        assert copy_d.field3 == \"D3\"\n        assert copy_d.field4 == \"D4\"\n        assert Model2D.objects.filter(pk=original_d_pk).exists()\n        assert Model2D.objects.filter(pk=copy_d.pk).exists()\n        # Now we have Model2D: obj_d, copy_d\n        assert Model2D.objects.count() == 2\n        # Model2C: obj_c, copy_c, obj_d, copy_d\n        assert Model2C.objects.count() == 4\n        # Model2B: obj_b, copy_b, obj_c, copy_c, obj_d, copy_d\n        assert Model2B.objects.count() == 6\n\n        # Test 4: Verify old manual method still works for 2-level inheritance\n        manual_copy_b = Model2B.objects.get(pk=obj_b.pk)\n        manual_copy_b.field1 = \"B1_manual\"\n        manual_copy_b.pk = None\n        manual_copy_b.id = None\n        manual_copy_b.save()\n\n        assert manual_copy_b.pk not in [original_b_pk, copy_b.pk]\n        assert manual_copy_b.field1 == \"B1_manual\"\n        assert manual_copy_b.field2 == \"B2\"\n        # Now we have Model2B: obj_b, copy_b, manual_copy_b, obj_c, copy_c, obj_d, copy_d\n        assert Model2B.objects.count() == 7\n\n        # Test 5: Verify that polymorphic queries work correctly on copied objects\n        all_b = list(Model2B.objects.all().order_by(\"pk\"))\n        assert len(all_b) == 7  # obj_b, copy_b, manual_copy_b, obj_c, copy_c, obj_d, copy_d\n        # Check that each is the correct type\n        b_only = [obj for obj in all_b if type(obj).__name__ == \"Model2B\"]\n        assert len(b_only) == 3  # obj_b, copy_b, manual_copy_b\n\n        all_c = list(Model2C.objects.all().order_by(\"pk\"))\n        assert len(all_c) == 4  # obj_c, copy_c, obj_d, copy_d\n        c_only = [obj for obj in all_c if type(obj).__name__ == \"Model2C\"]\n        assert len(c_only) == 2  # obj_c, copy_c\n\n        all_d = list(Model2D.objects.all().order_by(\"pk\"))\n        assert len(all_d) == 2  # obj_d, copy_d\n        assert all(type(obj).__name__ == \"Model2D\" for obj in all_d)\n\n        # Test 6: Verify polymorphic_ctype is set correctly on copied objects\n        assert copy_b.polymorphic_ctype == ContentType.objects.get_for_model(Model2B)\n        assert copy_c.polymorphic_ctype == ContentType.objects.get_for_model(Model2C)\n        assert copy_d.polymorphic_ctype == ContentType.objects.get_for_model(Model2D)\n\n    def test_prepare_for_copy_edge_cases(self):\n        \"\"\"\n        Test edge cases in prepare_for_copy() method.\n\n\n        The method should only reset parent link fields within the inheritance chain,\n        not regular OneToOneFields to external models.\n        \"\"\"\n        from polymorphic.utils import prepare_for_copy\n        from polymorphic.tests.models import One2OneRelatingModel, Model2A, Model2C\n\n        # Clean up\n        One2OneRelatingModel.objects.all().delete()\n        Model2A.objects.all().delete()\n\n        # Create a Model2A instance to link to\n        related_obj = Model2A.objects.create(field1=\"Related\")\n\n        # Create a One2OneRelatingModel with a regular OneToOneField\n        obj_with_o2o = One2OneRelatingModel.objects.create(field1=\"Test1\", one2one=related_obj)\n\n        original_pk = obj_with_o2o.pk\n        original_one2one_id = obj_with_o2o.one2one_id\n\n        # Now copy the object\n        copy_obj = One2OneRelatingModel.objects.get(pk=obj_with_o2o.pk)\n        copy_obj.field1 = \"Test1_copy\"\n        prepare_for_copy(copy_obj)\n\n        # Verify that pk and polymorphic_ctype_id are reset\n        assert copy_obj.pk is None\n        assert copy_obj.id is None\n        assert copy_obj.polymorphic_ctype_id is None\n\n        assert copy_obj.one2one_id == original_one2one_id, (\n            \"Regular OneToOneField should NOT be reset by prepare_for_copy()\"\n        )\n        assert copy_obj.one2one == related_obj\n\n        # To save the copy, we need to create a new related object or clear the field\n        # because the OneToOneField constraint prevents duplicate links\n        new_related = Model2A.objects.create(field1=\"Related2\")\n        copy_obj.one2one = new_related\n        copy_obj.save()\n\n        # Verify the copy was created successfully\n        assert copy_obj.pk != original_pk\n        assert copy_obj.field1 == \"Test1_copy\"\n        assert One2OneRelatingModel.objects.count() == 2\n\n        # Create a 3-level inheritance object\n        obj_c = Model2C.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n        original_c_pk = obj_c.pk\n\n        # Model2C -> Model2B -> Model2A\n        # Model2C should have model2b_ptr (parent link)\n\n        copy_c = Model2C.objects.get(pk=obj_c.pk)\n        copy_c.field1 = \"C1_copy\"\n\n        # Before reset, the parent link fields should have values\n        assert copy_c.model2b_ptr_id is not None\n\n        prepare_for_copy(copy_c)\n\n        # After reset, parent link fields should be None\n        assert copy_c.pk is None\n        assert copy_c.id is None\n        assert copy_c.polymorphic_ctype_id is None\n        assert copy_c.model2b_ptr_id is None  # Parent link should be reset\n\n        copy_c.save()\n\n        # Verify the copy was created\n        assert copy_c.pk != original_c_pk\n        assert copy_c.field1 == \"C1_copy\"\n        assert copy_c.field2 == \"C2\"\n        assert copy_c.field3 == \"C3\"\n\n        # Create another object with OneToOneField to Model2A\n        related_obj2 = Model2A.objects.create(field1=\"Related3\")\n        obj_with_o2o2 = One2OneRelatingModel.objects.create(field1=\"Test2\", one2one=related_obj2)\n\n        copy_obj2 = One2OneRelatingModel.objects.get(pk=obj_with_o2o2.pk)\n        copy_obj2.field1 = \"Test2_copy\"\n\n        # Store the one2one_id before reset\n        one2one_id_before = copy_obj2.one2one_id\n\n        prepare_for_copy(copy_obj2)\n\n        # The one2one field should NOT be reset because Model2A is not in the inheritance tree\n        # of One2OneRelatingModel\n        assert copy_obj2.one2one_id == one2one_id_before, (\n            \"OneToOneField to model outside inheritance tree should NOT be reset\"\n        )\n\n        # Create a new related object for the copy\n        new_related2 = Model2A.objects.create(field1=\"Related4\")\n        copy_obj2.one2one = new_related2\n        copy_obj2.save()\n\n        assert One2OneRelatingModel.objects.count() == 4  # original, copy, obj2, copy2\n\n    def test_prepare_for_copy_upcast(self):\n        from polymorphic.utils import prepare_for_copy\n        from polymorphic.tests.models import Model2B, Model2C\n\n        c = Model2C.objects.create(field1=\"C1\", field2=\"C2\", field3=\"C3\")\n        c_as_b = Model2B.objects.non_polymorphic().get(pk=c.pk)\n\n        # copy c as a b instance\n        prepare_for_copy(c_as_b)\n        c_as_b.save()\n\n        assert Model2B.objects.count() == 2\n        assert Model2C.objects.count() == 1\n\n        c_as_b.refresh_from_db()\n        assert c_as_b.field1 == \"C1\"\n        assert c_as_b.field2 == \"C2\"\n        assert not hasattr(c_as_b, \"field3\")\n        assert c_as_b.polymorphic_ctype == ContentType.objects.get_for_model(Model2B)\n        assert c_as_b.pk != c.pk\n\n    def test_prepare_for_copy_plain(self):\n        \"\"\"\n        Test that prepare_for_copy works on non-polymorphic (plain) multi-table models.\n        \"\"\"\n        from polymorphic.utils import prepare_for_copy\n        from polymorphic.tests.models import PlainC\n\n        plain_c = PlainC.objects.create(field1=\"PC1\", field2=\"PC2\", field3=\"PC3\")\n        prepare_for_copy(plain_c)\n        plain_c.save()\n        plain_c.refresh_from_db()\n\n        assert PlainC.objects.count() == 2\n        assert PlainC.objects.filter(field1=\"PC1\").count() == 2\n        assert PlainC.objects.order_by(\"pk\").last() == plain_c\n\n    def test_copy_with_abstract_base(self):\n        \"\"\"\n        Test copying polymorphic objects with an abstract base class.\n        \"\"\"\n        from polymorphic.utils import prepare_for_copy\n        from polymorphic.tests.models import RelationBase, RelationA, RelationB, RelationBC\n\n        obase = RelationBase.objects.create(field_base=\"base\")\n        oa = RelationA.objects.create(field_base=\"A1\", field_a=\"A2\", fk=obase)\n        ob = RelationB.objects.create(field_base=\"B1\", field_b=\"B2\", fk=oa)\n        oc = RelationBC.objects.create(field_base=\"C1\", field_b=\"C2\", field_c=\"C3\", fk=oa)\n        oc.m2m.add(oa)\n        oc.m2m.add(ob)\n\n        assert set(oc.m2m.all()) == {oa, ob}\n        prepare_for_copy(oc)\n        oc.save()\n\n        assert oc.m2m.count() == 0  # M2M should not be copied\n        assert RelationBC.objects.count() == 2\n        assert oc.field_base == \"C1\"\n        assert oc.field_b == \"C2\"\n        assert oc.field_c == \"C3\"\n        assert oc.fk == oa  # FK to parent should remain unchanged\n\n    def test_copy_with_proxies(self):\n        \"\"\"\n        Test that copying walks up proxy model chains correctly.\n        \"\"\"\n        from polymorphic.utils import prepare_for_copy\n        from polymorphic.tests.models import Duck, PurpleHeadDuck\n\n        daffy1 = PurpleHeadDuck.objects.create(name=\"daffy\")\n        assert Duck.objects.count() == 1\n        daffy2 = PurpleHeadDuck.objects.get(pk=daffy1.pk)\n        prepare_for_copy(daffy2)\n        daffy2.save()\n        daffy2.refresh_from_db()\n        assert daffy2.pk != daffy1.pk\n        assert Duck.objects.count() == 2\n        assert PurpleHeadDuck.objects.count() == 2\n        assert Duck.objects.filter(name=\"daffy\").count() == 2\n\n        daffy3 = Duck.objects.non_polymorphic().last()\n        prepare_for_copy(daffy3)\n        daffy3.save()\n        assert Duck.objects.count() == 3\n        assert PurpleHeadDuck.objects.count() == 2\n        assert Duck.objects.filter(name=\"daffy\").count() == 3\n        assert set(Duck.objects.all()) == {daffy1, daffy2, daffy3}\n\n    def test_model_registration_and_utils_caches(self):\n        \"\"\"\n        Test that a polymorphic model that is not registered with Django does not\n        appear in our subclass trees.\n        \"\"\"\n        from django.apps import apps\n        from ..utils import concrete_descendants\n\n        # warm up the cache\n        assert concrete_descendants(Model2C)\n\n        class UnregisteredModel(Model2C):\n            pass\n\n        # dynamic registration works (model meta class invalidates the\\\n        # concrete_descendants cache)\n        assert UnregisteredModel in concrete_descendants(Model2C)\n\n        # this is a weird thing to do - we just do it for coverage\n        apps.get_app_config(UnregisteredModel._meta.app_label).models.pop(\n            UnregisteredModel._meta.model_name\n        )\n\n        concrete_descendants.cache_clear()\n        assert UnregisteredModel not in concrete_descendants(Model2C)\n"
  },
  {
    "path": "src/polymorphic/tests/urls.py",
    "content": "from django.contrib import admin\nfrom django.urls import path, include\nfrom .models import Model2C\n\nurlpatterns = [\n    path(\"admin/\", admin.site.urls),\n    path(\"examples/views\", include(\"polymorphic.tests.examples.views.urls\")),\n]\n\ntry:\n    import extra_views  # noqa: F401\n\n    urlpatterns.append(\n        path(\n            \"examples/integrations/extra_views/\",\n            include(\n                \"polymorphic.tests.examples.integrations.extra_views.urls\", namespace=\"extra_views\"\n            ),\n        )\n    )\nexcept ImportError:\n    pass\n\ntry:\n    import rest_framework  # noqa: F401\n\n    urlpatterns.append(\n        path(\n            \"examples/integrations/drf/\",\n            include(\"polymorphic.tests.examples.integrations.drf.urls\", namespace=\"drf\"),\n        )\n    )\nexcept ImportError:\n    pass\n"
  },
  {
    "path": "src/polymorphic/tests/utils.py",
    "content": "import os\nimport shutil\nimport re\nfrom pathlib import Path\nfrom functools import lru_cache\nimport pytest\n\nfrom django.core.management import call_command\nfrom django_test_migrations.migrator import Migrator\nfrom django.contrib.auth import get_user_model\nfrom django.contrib.staticfiles.testing import StaticLiveServerTestCase\nfrom django.db import connections\nfrom django.urls import reverse\nfrom django.apps import apps\n\nfrom playwright.sync_api import sync_playwright, expect\nfrom polymorphic import tests\n\n\nDSN_RE = re.compile(r\"^(?P<host>[^:/]+)(:(?P<port>\\d+))?/(?P<service>.+)$\")\n\n\n@lru_cache()\ndef is_sqlite_in_memory(db_name: str = \"default\") -> bool:\n    from django.conf import settings\n\n    return (\n        settings.DATABASES[db_name][\"ENGINE\"] == \"django.db.backends.sqlite3\"\n        and settings.DATABASES[db_name][\"NAME\"] == \":memory:\"\n    )\n\n\n@lru_cache()\ndef is_oracle(db_name: str = \"default\") -> bool:\n    from django.conf import settings\n\n    return settings.DATABASES[db_name][\"ENGINE\"] == \"django.db.backends.oracle\"\n\n\ndef get_subprocess_test_db_env(db_name: str = \"default\") -> dict[str, str]:\n    \"\"\"\n    If you need to run a test in a subprocess that accesses the active test database\n    (e.g. to call management commands), you need to set up the environment variables\n    so that the subprocess can connect to the correct test database.\n    This function returns a copy of os.environ with the necessary variables set.\n    \"\"\"\n    env = os.environ.copy()\n    db = connections[db_name].settings_dict\n    # this is where django's renaming of test databases gets very annoying - we need\n    # to make sure our subprocess invocation uses the test database - which it wont\n    # do by default because it thinks we aren't in test mode.\n    if is_oracle(db_name):\n        dsn = db[\"NAME\"]\n        m = DSN_RE.match(dsn)\n\n        if not m:\n            raise AssertionError(\n                f\"Can't parse Oracle DSN from NAME={dsn!r}. \"\n                \"Expected format like 'host:1521/service' or 'host/service'.\"\n            )\n\n        host = m.group(\"host\")\n        port = m.group(\"port\") or \"1521\"\n        service = m.group(\"service\")\n\n        env[\"ORACLE_DATABASES\"] = service\n        env[\"ORACLE_USER\"] = db[\"USER\"]\n        env[\"ORACLE_PASSWORD\"] = db[\"PASSWORD\"]\n\n        # Only set non-empty values\n        env[\"ORACLE_HOST\"] = host\n        env[\"ORACLE_PORTS\"] = port\n    else:\n        env[\"PYTEST_DB_NAME\"] = db[\"NAME\"]\n    return env\n\n\nclass GeneratedMigrationsPerClassMixin:\n    \"\"\"\n    Generates migrations at class setup, applies them, and rolls them back at teardown.\n\n    Configure:\n      - apps_to_migrate = [\"my_app\", ...]\n      - database = \"default\" (optional)\n    \"\"\"\n\n    apps_to_migrate: list[str] = []\n    database: str = \"default\"\n    settings: str = os.environ.get(\"DJANGO_SETTINGS_MODULE\", \"polymorphic.tests.settings\")\n\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n\n        if not cls.apps_to_migrate:\n            raise RuntimeError(\"Set apps_to_migrate = ['your_app', ...]\")\n\n        for app_label in cls.apps_to_migrate:\n            call_command(\n                \"makemigrations\",\n                app_label,\n                interactive=False,\n                verbosity=0,\n            )\n\n        # 2) Apply all migrations (up to latest) using django-test-migrations\n        cls.migrator = Migrator(database=cls.database)\n\n        cls._applied_states = {}\n        for app_label in cls.apps_to_migrate:\n            latest = cls._find_latest_migration_name(app_label)\n            # apply_initial_migration applies all migrations up to and including `latest`\n            cls._applied_states[app_label] = cls.migrator.apply_initial_migration(\n                (app_label, latest)\n            )\n\n    @classmethod\n    def tearDownClass(cls):\n        try:\n            # Roll everything back / cleanup:\n            if hasattr(cls, \"migrator\"):\n                cls.migrator.reset()\n        finally:\n            # remove files\n            for app_label in cls.apps_to_migrate:\n                app_config = apps.get_app_config(app_label)  # app *label*\n                mig_dir = Path(app_config.path) / \"migrations\"\n\n                for mig_file in mig_dir.glob(\"*.py\"):\n                    if mig_file.name != \"__init__.py\" and mig_file.name[0:4].isdigit():\n                        os.remove(mig_file)\n\n                # also remove __pycache__ if exists\n                pycache_dir = mig_dir / \"__pycache__\"\n                if pycache_dir.exists() and pycache_dir.is_dir():\n                    shutil.rmtree(pycache_dir)\n\n            super().tearDownClass()\n\n    @classmethod\n    def _find_latest_migration_name(cls, app_label: str) -> str:\n        \"\"\"\n        Returns \"000X_...\" latest migration filename (without .py).\n        \"\"\"\n        app_config = apps.get_app_config(app_label)  # app *label*\n        mig_dir = Path(app_config.path) / \"migrations\"\n\n        candidates = sorted(\n            p for p in mig_dir.glob(\"*.py\") if p.name != \"__init__.py\" and p.name[0:4].isdigit()\n        )\n        if not candidates:\n            raise RuntimeError(f\"No migrations generated for {app_label}\")\n        return candidates[-1].stem\n\n\nclass _GenericUITest(StaticLiveServerTestCase):\n    \"\"\"Generic admin form test using Playwright.\"\"\"\n\n    HEADLESS = tests.HEADLESS\n\n    admin_username = \"admin\"\n    admin_password = \"password\"\n    admin = None\n\n    pytestmark = pytest.mark.ui\n\n    def admin_url(self):\n        return f\"{self.live_server_url}{reverse('admin:index')}\"\n\n    def add_url(self, model):\n        path = reverse(f\"admin:{model._meta.label_lower.replace('.', '_')}_add\")\n        return f\"{self.live_server_url}{path}\"\n\n    def change_url(self, model, id):\n        path = reverse(\n            f\"admin:{model._meta.label_lower.replace('.', '_')}_change\",\n            args=[id],\n        )\n        return f\"{self.live_server_url}{path}\"\n\n    def list_url(self, model):\n        path = reverse(f\"admin:{model._meta.label_lower.replace('.', '_')}_changelist\")\n        return f\"{self.live_server_url}{path}\"\n\n    def get_object_ids(self, model):\n        self.page.goto(self.list_url(model))\n        return self.page.eval_on_selector_all(\n            \"input[name='_selected_action']\", \"elements => elements.map(e => e.value)\"\n        )\n\n    @classmethod\n    def setUpClass(cls):\n        \"\"\"Set up the test class with a live server and Playwright instance.\"\"\"\n        os.environ[\"DJANGO_ALLOW_ASYNC_UNSAFE\"] = \"1\"\n        super().setUpClass()\n        try:\n            cls.playwright = sync_playwright().start()\n            cls.browser = cls.playwright.chromium.launch(headless=cls.HEADLESS)\n        except Exception as e:\n            if \"asyncio loop\" in str(e) or \"executable\" in str(e).lower():\n                raise RuntimeError(\n                    \"Playwright failed to start. This often happens if browser drivers are missing. \"\n                    \"Please run 'just install-playwright' to install them.\"\n                ) from e\n            raise\n\n    @classmethod\n    def tearDownClass(cls):\n        \"\"\"Clean up Playwright instance after tests.\"\"\"\n        cls.browser.close()\n        cls.playwright.stop()\n        super().tearDownClass()\n        del os.environ[\"DJANGO_ALLOW_ASYNC_UNSAFE\"]\n\n    def setUp(self):\n        \"\"\"Create an admin user before running tests.\"\"\"\n        self.admin = get_user_model().objects.create_superuser(\n            username=self.admin_username, email=\"admin@example.com\", password=self.admin_password\n        )\n        self.page = self.browser.new_page()\n        # Log in to the Django admin\n        self.page.goto(f\"{self.live_server_url}/admin/login/\")\n        self.page.fill(\"input[name='username']\", self.admin_username)\n        self.page.fill(\"input[name='password']\", self.admin_password)\n        self.page.click(\"input[type='submit']\")\n\n        # Ensure login is successful\n        expect(self.page).to_have_url(f\"{self.live_server_url}/admin/\")\n\n    def tearDown(self):\n        if self.page:\n            self.page.close()\n"
  },
  {
    "path": "src/polymorphic/utils.py",
    "content": "from __future__ import annotations\n\nfrom collections import defaultdict\nfrom dataclasses import dataclass\nfrom functools import lru_cache\nfrom typing import Any, cast\n\nfrom django.apps import apps\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.core.exceptions import FieldError\nfrom django.db import DEFAULT_DB_ALIAS, models\nfrom django.db.models import Model, Q, Subquery\n\n\n@dataclass(frozen=True)\nclass ParentLinkInfo:\n    \"\"\"\n    Information about a parent table link in a polymorphic model.\n    \"\"\"\n\n    model: type[models.Model]\n    link: models.Field[Any, Any]\n\n\ndef reset_polymorphic_ctype(*models: type[models.Model], **filters: Any) -> None:\n    \"\"\"\n    Set the polymorphic content-type ID field to the proper model\n    Sort the ``*models`` from base class to descending class,\n    to make sure the content types are properly assigned.\n\n    Add ``ignore_existing=True`` to skip models which already\n    have a polymorphic content type.\n    \"\"\"\n    using = filters.pop(\"using\", DEFAULT_DB_ALIAS)\n    ignore_existing = filters.pop(\"ignore_existing\", False)\n\n    models_list: list[type[Model]] = sort_by_subclass(*models)\n    if ignore_existing:\n        # When excluding models, make sure we don't ignore the models we\n        # just assigned the an content type to. hence, start with child first.\n        models_list = list(reversed(models_list))\n\n    for new_model in models_list:\n        new_ct = ContentType.objects.db_manager(using).get_for_model(\n            new_model, for_concrete_model=False\n        )\n\n        qs = new_model.objects.db_manager(using)  # type: ignore[attr-defined]\n        if ignore_existing:\n            qs = qs.filter(polymorphic_ctype__isnull=True)\n        if filters:\n            qs = qs.filter(**filters)\n        qs.update(polymorphic_ctype=new_ct)\n\n\ndef _compare_mro(cls1: type, cls2: type) -> int:\n    if cls1 is cls2:\n        return 0\n\n    try:\n        index1 = cls1.mro().index(cls2)\n    except ValueError:\n        return -1  # cls2 not inherited by 1\n\n    try:\n        index2 = cls2.mro().index(cls1)\n    except ValueError:\n        return 1  # cls1 not inherited by 2\n\n    return (index1 > index2) - (index1 < index2)  # python 3 compatible cmp.\n\n\ndef sort_by_subclass(*classes: type[models.Model]) -> list[type[models.Model]]:\n    \"\"\"\n    Sort a series of models by their inheritance order.\n    \"\"\"\n    from functools import cmp_to_key\n\n    return sorted(classes, key=cmp_to_key(_compare_mro))\n\n\n@lru_cache(maxsize=None)\ndef get_base_polymorphic_model(\n    ChildModel: type[models.Model], allow_abstract: bool = False\n) -> type[models.Model] | None:\n    \"\"\"\n    First the first concrete model in the inheritance chain that inherited from the\n    PolymorphicModel.\n    \"\"\"\n    from polymorphic.models import PolymorphicModel\n\n    for base_class in reversed(ChildModel.mro()):\n        if (\n            issubclass(base_class, PolymorphicModel)\n            and base_class is not PolymorphicModel\n            and (allow_abstract or not base_class._meta.abstract)\n        ):\n            return base_class\n    return None\n\n\n@lru_cache(maxsize=None)\ndef route_to_ancestor(\n    model_class: type[models.Model], ancestor_model: type[models.Model]\n) -> list[ParentLinkInfo]:\n    \"\"\"\n    Returns the first (highest mro precedence - depth first on parents) model\n    inheritance route to the given ancestor model - or an empty list if no such\n    route exists. Results are cached\n\n    .. warning::\n\n        This only works for concrete ancestors!\n\n    Returns a :class:`list` of :class:`ParentLinkInfo`\n    \"\"\"\n    route: list[ParentLinkInfo] = []\n\n    def find_route(\n        model: type[models.Model],\n        target_model: type[models.Model],\n        current_route: list[ParentLinkInfo],\n    ) -> list[ParentLinkInfo] | None:\n        if model is target_model:\n            return current_route\n\n        for parent_model, field_to_parent in model._meta.parents.items():\n            if field_to_parent is not None:\n                new_route = current_route + [ParentLinkInfo(parent_model, field_to_parent)]\n                found_route = find_route(parent_model, target_model, new_route)\n                if found_route is not None:\n                    return found_route\n            else:\n                return find_route(parent_model, target_model, current_route)\n        return None\n\n    found_route = find_route(model_class, ancestor_model, route)\n    if found_route is None:\n        return []\n    return found_route\n\n\ndef is_model_loaded(model: type[models.Model]) -> bool:\n    try:\n        apps.get_model(model._meta.app_label, model._meta.model_name)\n        return True\n    except LookupError:\n        return False\n\n\n@lru_cache(maxsize=None)\ndef concrete_descendants(\n    model_class: type[models.Model], include_proxy: bool = False\n) -> list[type[models.Model]]:\n    \"\"\"\n    Get a list of all concrete (non-abstract, non-proxy) descendant model classes in\n    tree order with leaf descendants last. Results are cached.\n    \"\"\"\n    from django.apps import apps\n\n    apps.check_models_ready()\n\n    def add_concrete_descendants(\n        model: type[models.Model], result: list[type[models.Model]]\n    ) -> None:\n        \"\"\"Add concrete descendants in tree order (ancestors before descendants).\"\"\"\n        for sub_cls in model.__subclasses__():\n            # Add concrete models in pre-order (parent before children)\n            if not sub_cls._meta.abstract and (include_proxy or not sub_cls._meta.proxy):\n                if is_model_loaded(sub_cls):\n                    result.append(sub_cls)\n\n            # Always recurse to find descendants through abstract and proxy models\n            add_concrete_descendants(sub_cls, result)\n\n    result: list[type[models.Model]] = []\n    add_concrete_descendants(model_class, result)\n    return result\n\n\ndef prepare_for_copy(obj: models.Model) -> None:\n    \"\"\"\n    Prepare a model instance for copying by resetting all primary keys and parent table\n    pointers in the inheritance chain. **Copy semantics are application specific.** This\n    function only resets the fields required to create a new instance when saved, it\n    does not deep copy related objects or save the new instance\n    (See :ref:`copying discussion in the Django documentation.\n    <topics/db/queries:copying model instances>`):\n\n    .. code-block:: python\n\n        from polymorphic.utils import prepare_for_copy\n\n        original = YourModel.objects.get(pk=1)\n        prepare_for_copy(original)\n        # update any related fields here as needed\n        original.save()  # creates a new object in the database\n\n    .. tip::\n\n        Preparation is at the inheritance level of the passed in model. This means you\n        can copy and upcast at the same time. Suppose you have A->B->C inheritance chain,\n        and you have an instance of C that you want to copy as a B instance:\n\n        .. code-block:: python\n\n            c = C.objects.create()\n            c_as_b = B.objects.non_polymorphic().get(pk=c.pk)\n\n            # copy c as a b instance\n            prepare_for_copy(c_as_b)\n            c_as_b.save()\n\n            assert B.objects.count() == 2\n            assert C.objects.count() == 1\n\n        If you want polymorphic copying instead:\n\n        .. code-block:: python\n\n            prepare_for_copy(b_instance.get_real_instance())\n\n    **This function also works for non-polymorphic multi-table models.**\n\n    :param obj: The model instance to prepare for copying.\n    \"\"\"\n    from polymorphic.models import PolymorphicModel\n\n    obj.pk = None\n    if isinstance(obj, PolymorphicModel):\n        # we might be upcasting - allow ctype to be reset automatically on save\n        obj.polymorphic_ctype_id = None\n\n    def reset_parent_pointers(mdl):\n        \"\"\"\n        Reset all parent table pointers and pks in the inheritance chain.\n        \"\"\"\n        for parent, ptr in mdl._meta.parents.items():\n            reset_parent_pointers(parent)\n            if ptr is not None:\n                setattr(obj, ptr.attname, None)\n                setattr(obj, parent._meta.pk.attname, None)\n\n    reset_parent_pointers(obj)\n    obj._state.adding = True  # Mark as new object\n\n\ndef _lazy_ctype(model: type[models.Model], using: str = DEFAULT_DB_ALIAS) -> ContentType | Q:\n    mgr = ContentType.objects.db_manager(using=using)\n    if apps.models_ready and (\n        cid := mgr._cache.get(using, {}).get((model._meta.app_label, model._meta.model_name))  # type: ignore[attr-defined]\n    ):\n        # cast needed because _cache returns Any\n        return cast(ContentType, cid)\n    return Q(app_label=model._meta.app_label) & Q(model=model._meta.model_name)\n\n\ndef lazy_ctype(model: type[models.Model], using: str = DEFAULT_DB_ALIAS) -> ContentType | Subquery:\n    \"\"\"\n    Return the content type id for the given model class if it is in the cache,\n    otherwise return a subquery that can be used to match the content type as part\n    of a larger query. Safe to call before apps are fully loaded.\n\n    :param model: The model class to get the content type for.\n    :return: The content type for the model class.\n    :rtype: ContentType object or Subquery\n    \"\"\"\n    ctype = _lazy_ctype(model, using=using)\n    return (\n        ctype\n        if isinstance(ctype, ContentType)\n        else Subquery(ContentType.objects.db_manager(using=using).filter(ctype).values(\"pk\")[:1])\n    )\n\n\n@lru_cache(maxsize=None)\ndef _map_queryname_to_class(base_model: type[models.Model], qry_name: str) -> type[models.Model]:\n    \"\"\"Try to match a model name in a query to a model class\"\"\"\n    name_map: dict[str, list[type[models.Model]]] = defaultdict(list)\n    name_map[base_model.__name__.lower()].append(base_model)\n    for cls in concrete_descendants(base_model, include_proxy=True):\n        name_map[cls.__name__.lower()].append(cls)\n\n    matches = name_map.get(qry_name.lower(), [])\n    if len(matches) == 1:\n        return matches[0]\n    elif len(matches) > 1:\n        raise FieldError(\n            f\"{qry_name} could refer to any of {[m._meta.label for m in matches]}. In \"\n            f\"this case, please use the syntax: applabel__ModelName___field\"\n        )\n\n    # FIXME: raise a FieldError - upstream code currently relies on this AssertionError\n    # and it will be thrown in legitimate cases because this function ends up being\n    # called on subclasses of the original query model in _get_real_instances. That\n    # code should be refactored to avoiid this.\n    raise AssertionError(f\"{qry_name} is not a subclass of {base_model._meta.label}\")\n\n\ndef _clear_utility_caches() -> None:\n    \"\"\"Clear all lru_cache caches in this module.\"\"\"\n    get_base_polymorphic_model.cache_clear()\n    route_to_ancestor.cache_clear()\n    concrete_descendants.cache_clear()\n    _map_queryname_to_class.cache_clear()\n"
  }
]