diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index e11785b..0000000 --- a/.coveragerc +++ /dev/null @@ -1,4 +0,0 @@ -[run] -source = polymorphic/ -omit = - */migrations/* diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 275313a..b5e6080 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,19 +1,88 @@ name: Lint + +permissions: + contents: read + on: push: + tags-ignore: + - '*' branches: - - master - tags: - - 'v*' + - '*' pull_request: - branches: - - master + workflow_call: + workflow_dispatch: + inputs: + debug: + description: 'Open ssh debug session.' + required: true + default: false + type: boolean + jobs: - Lint: + + linting: runs-on: ubuntu-latest + strategy: + matrix: + # run static analysis on bleeding and trailing edges + python-version: [ '3.9', '3.10', '3.13' ] + django-version: + - '3.2' # LTS April 2024 + - '4.2' # LTS April 2026 + - '5.2' # December 2025 + exclude: + - python-version: '3.9' + django-version: '4.2' + - python-version: '3.13' + django-version: '4.2' + - python-version: '3.13' + django-version: '3.2' + - python-version: '3.10' + django-version: '3.2' + - python-version: '3.9' + django-version: '5.2' + - python-version: '3.10' + django-version: '5.2' + + env: + TEST_PYTHON_VERSION: ${{ matrix.python-version }} + TEST_DJANGO_VERSION: ${{ matrix.django-version }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v5 with: - python-version: "3.13" - - uses: pre-commit/action@v3.0.1 + persist-credentials: false + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + id: sp + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + - name: Install Just + uses: extractions/setup-just@v3 + - name: Install Dependencies + run: | + just setup ${{ steps.sp.outputs.python-path }} + uv add Django~=${{ matrix.django-version }}.0 + just install-docs + - name: Install Emacs + if: ${{ github.event.inputs.debug == 'true' }} + run: | + sudo apt install emacs + - name: Setup tmate session + if: ${{ github.event.inputs.debug == 'true' }} + uses: mxschmitt/action-tmate@v3.22 + with: + detached: true + timeout-minutes: 60 + - name: Run Static Analysis + run: | + just check-lint + just check-format + just check-types + just check-package + just check-readme diff --git a/.gitignore b/.gitignore index c18fed3..1442624 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,218 @@ -*.pyc -*.pyo -*.mo -*.db -*.egg-info/ -*.egg/ -.coverage -coverage.xml -.project -.idea/ -.pydevproject -.idea/workspace.xml -.tox/ -.DS_Store +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python build/ +develop-eggs/ dist/ -docs/_build/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock +#poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +#pdm.lock +#pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +#pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ venv/ -.venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml + +.DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0be7041..529cd40 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,15 +1,13 @@ repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 + - repo: local hooks: - - id: check-yaml - - id: end-of-file-fixer - - id: trailing-whitespace - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.9 - hooks: - - id: ruff - args: - - --fix - - id: ruff-format - exclude: '.*migrations.*' + - id: lint + name: Lint + entry: just lint + language: system + pass_filenames: false + - id: format + name: Format + entry: just format + language: system + pass_filenames: false diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 7ee42f0..152fe39 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,16 +1,24 @@ -# Read the Docs configuration file for Sphinx projects +# .readthedocs.yaml +# Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details +# Required version: 2 +# Set the version of Python and other tools you might need build: os: ubuntu-22.04 tools: python: "3.12" + jobs: + post_install: + - pip install uv + - UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --all-extras --group docs --link-mode=copy +# Build documentation in the docs/ directory with Sphinx sphinx: - configuration: docs/conf.py - -python: - install: - - requirements: docs/_ext/djangodummy/requirements.txt + configuration: docs/conf.py + +# Optionally build your docs in additional formats such as PDF and ePub +formats: + - pdf diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b1eae2b..65fb4aa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,23 +14,87 @@ We are actively seeking additional maintainers. If you're interested, [contact m We provide a platform independent justfile with recipes for all the development tasks. You should [install just](https://just.systems/man/en/installation.html) if it is not on your system already. -`[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: +[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: -```shell + ```bash just setup ``` +Setup also may take a python version: + +```bash +just setup 3.12 +``` + +If you already have uv and python installed running install will just install the development dependencies: + + ```bash +just install +``` + +**To run pre-commit checks you will have to install just.** + ## Documentation -TODO +`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: + +```bash +just install-docs # install the doc dependencies +just docs # builds docs +just check-docs # lint the docs +just check-docs-links # check for broken links in the docs +``` + +Run the docs with auto rebuild using: + +```bash +just docs-live +``` ## Static Analysis -TODO +`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: + +To fix formatting and linting problems that are fixable run: + +```bash +just fix +``` + +To run all static analysis without automated fixing you can run: + +```bash +just check +``` + +To format source files you can run: + +```bash +just format +``` ## Running Tests -TODO +`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. + +To run the full suite: + + ```bash +just test +``` + +To run a single test, or group of tests in a class: + + ```bash +just test ::ClassName::FunctionName +``` + +For instance, to run all admin tests, and then just the test_admin_registration test you would do: + + ```bash +just test src/polymorphic/tests/test_admin.py +just test src/polymorphic/tests/test_admin.py::PolymorphicAdminTests::test_admin_registration +``` ## Versioning @@ -47,5 +111,40 @@ just release x.x.x ## Just Recipes ```bash - + build # build docs and package + build-docs # build the docs + build-docs-html # build html documentation + build-docs-pdf # build pdf documentation + check # run all static checks + check-docs # lint the documentation + check-docs-links # check the documentation links for broken links + check-format # check if the code needs formatting + check-lint # lint the code + check-package # run package checks + check-readme # check that the readme renders + check-types # run static type checking + clean # remove all non repository artifacts + clean-docs # remove doc build artifacts + clean-env # remove the virtual environment + clean-git-ignored # remove all git ignored files + coverage # generate the test coverage report + docs # build and open the documentation + docs-live # serve the documentation, with auto-reload + fetch-refs LIB + fix # fix formatting, linting issues and import sorting + format # format the code and sort imports + install *OPTS # update and install development dependencies + install-docs # install documentation dependencies + install-precommit # install git pre-commit hooks + install_uv # install the uv package manager + lint # sort the imports and fix linting issues + manage *COMMAND # run the django admin + open-docs # open the html documentation + precommit # run the pre-commit checks + release VERSION # issue a release for the given semver string (e.g. 2.1.0) + run +ARGS # run the command in the virtual environment + setup python="python" # setup the venv and pre-commit hooks + sort-imports # sort the python imports + test *TESTS # run tests + validate_version VERSION # validate the given version string against the lib version ``` diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 33ac4b5..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,153 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-polymorphic.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-polymorphic.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/django-polymorphic" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-polymorphic" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/admin.rst b/docs/admin.rst index 26b2bdc..37eb3f8 100644 --- a/docs/admin.rst +++ b/docs/admin.rst @@ -9,18 +9,22 @@ Setup Both the parent model and child model need to have a ``ModelAdmin`` class. -The shared base model should use the :class:`~polymorphic.admin.PolymorphicParentModelAdmin` as base class. +The shared base model should use the :class:`~polymorphic.admin.PolymorphicParentModelAdmin` as base +class. * :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.base_model` should be set * :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.child_models` or - :meth:`~polymorphic.admin.PolymorphicParentModelAdmin.get_child_models` should return an iterable of Model classes. + :meth:`~polymorphic.admin.PolymorphicParentModelAdmin.get_child_models` should return an iterable + of Model classes. -The admin class for every child model should inherit from :class:`~polymorphic.admin.PolymorphicChildModelAdmin` +The admin class for every child model should inherit from +:class:`~polymorphic.admin.PolymorphicChildModelAdmin` * :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_model` should be set. Although the child models are registered too, they won't be shown in the admin index page. -This only happens when :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show_in_index` is set to ``True``. +This only happens when :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show_in_index` is set to +``True``. Fieldset configuration ~~~~~~~~~~~~~~~~~~~~~~ @@ -41,11 +45,12 @@ Hence, the fieldset configuration should be placed on the child admin. .. versionchanged:: 1.0 It's now needed to register the child model classes too. - In *django-polymorphic* 0.9 and below, the ``child_models`` was a tuple of a ``(Model, ChildModelAdmin)``. - The admin classes were registered in an internal class, and kept away from the main admin site. - This caused various subtle problems with the ``ManyToManyField`` and related field wrappers, - which are fixed by registering the child admin classes too. Note that they are hidden from - the main view, unless :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show_in_index` is set. + In *django-polymorphic* 0.9 and below, the ``child_models`` was a tuple of a + ``(Model, ChildModelAdmin)``. The admin classes were registered in an internal class, and kept + away from the main admin site. This caused various subtle problems with the ``ManyToManyField`` + and related field wrappers, which are fixed by registering the child admin classes too. Note + that they are hidden from the main view, unless + :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show_in_index` is set. .. _admin-example: @@ -98,8 +103,9 @@ The models are taken from :ref:`advanced-features`. Filtering child types --------------------- -Child model types can be filtered by adding a :class:`~polymorphic.admin.PolymorphicChildModelFilter` -to the ``list_filter`` attribute. See the example above. +Child model types can be filtered by adding a +:class:`~polymorphic.admin.PolymorphicChildModelFilter` to the ``list_filter`` attribute. See the +example above. Inline models @@ -109,7 +115,8 @@ Inline models Inline models are handled via a special :class:`~polymorphic.admin.StackedPolymorphicInline` class. -For models with a generic foreign key, there is a :class:`~polymorphic.admin.GenericStackedPolymorphicInline` class available. +For models with a generic foreign key, there is a +:class:`~polymorphic.admin.GenericStackedPolymorphicInline` class available. When the inline is included to a normal :class:`~django.contrib.admin.ModelAdmin`, make sure the :class:`~polymorphic.admin.PolymorphicInlineSupportMixin` is included. @@ -168,9 +175,10 @@ The child classes can be nested for clarity, but this is not a requirement. Using polymorphic models in standard inlines ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To add a polymorphic child model as an Inline for another model, add a field to the inline's ``readonly_fields`` list -formed by the lowercased name of the polymorphic parent model with the string ``_ptr`` appended to it. -Otherwise, trying to save that model in the admin will raise an AttributeError with the message "can't set attribute". +To add a polymorphic child model as an Inline for another model, add a field to the inline's +``readonly_fields`` list formed by the lowercased name of the polymorphic parent model with the +string ``_ptr`` appended to it. Otherwise, trying to save that model in the admin will raise an +AttributeError with the message "can't set attribute". .. code-block:: python @@ -200,16 +208,19 @@ The polymorphic admin interface works in a simple way: * The list screen still displays all objects of the base class. The polymorphic admin is implemented via a parent admin that redirects the *edit* and *delete* views -to the ``ModelAdmin`` of the derived child model. The *list* page is still implemented by the parent model admin. +to the ``ModelAdmin`` of the derived child model. The *list* page is still implemented by the parent +model admin. The parent model ~~~~~~~~~~~~~~~~ -The parent model needs to inherit :class:`~polymorphic.admin.PolymorphicParentModelAdmin`, and implement the following: +The parent model needs to inherit :class:`~polymorphic.admin.PolymorphicParentModelAdmin`, and +implement the following: * :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.base_model` should be set * :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.child_models` or - :meth:`~polymorphic.admin.PolymorphicParentModelAdmin.get_child_models` should return an iterable of Model classes. + :meth:`~polymorphic.admin.PolymorphicParentModelAdmin.get_child_models` should return an iterable + of Model classes. The exact implementation can depend on the way your module is structured. For simple inheritance situations, ``child_models`` is the best solution. @@ -222,7 +233,8 @@ the performance hit of retrieving child models. This can be controlled by setting the ``polymorphic_list`` property on the parent admin. Setting it to True will provide child models to the list template. -If you use other applications such as django-reversion_ or django-mptt_, please check +:ref:`third-party`. +If you use other applications such as django-reversion_ or django-mptt_, please check ++:ref:`third-party`. Note: If you are using non-integer primary keys in your model, you have to edit ``pk_regex``, for example ``pk_regex = '([\w-]+)'`` if you use UUIDs. Otherwise you cannot change model entries. @@ -230,16 +242,21 @@ for example ``pk_regex = '([\w-]+)'`` if you use UUIDs. Otherwise you cannot cha The child models ~~~~~~~~~~~~~~~~ -The admin interface of the derived models should inherit from :class:`~polymorphic.admin.PolymorphicChildModelAdmin`. -Again, :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_model` should be set in this class as well. -This class implements the following features: +The admin interface of the derived models should inherit from +:class:`~polymorphic.admin.PolymorphicChildModelAdmin`. Again, +:attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_model` should be set in this class as +well. This class implements the following features: * It corrects the breadcrumbs in the admin pages. -* It extends the template lookup paths, to look for both the parent model and child model in the ``admin/app/model/change_form.html`` path. -* It allows to set :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_form` so the derived class will automatically include other fields in the form. -* It allows to set :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_fieldsets` so the derived class will automatically display any extra fields. +* It extends the template lookup paths, to look for both the parent model and child model in the + ``admin/app/model/change_form.html`` path. +* It allows to set :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_form` so the derived + class will automatically include other fields in the form. +* It allows to set :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_fieldsets` so the + derived class will automatically display any extra fields. * Although it must be registered with admin site, by default it's hidden from admin site index page. - This can be overridden by adding :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show_in_index` = ``True`` in admin class. + This can be overridden by adding + :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show_in_index` = ``True`` in admin class. .. _django-reversion: https://github.com/etianen/django-reversion diff --git a/docs/changelog.rst b/docs/changelog.rst index f5e4f75..52ccb39 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -44,7 +44,8 @@ v3.1.0 (2021-11-18) * Fixed including ``polymorphic.tests.migrations`` in the sdist. * Fixed non-polymorphic parent handling, which has no ``_base_objects``. * Fixed missing ``widgets`` support for ``modelform_factory()``. -* Fixed ``has_changed`` handling for ``polymorphic_ctype_id`` due to implicit str to int conversions. +* Fixed ``has_changed`` handling for ``polymorphic_ctype_id`` due to implicit str to int + conversions. * Fixed ``Q`` object handling when lists are used (e.g. in django-advanced-filters_). * Fixed Django Admin support when using a script-prefix. @@ -75,14 +76,16 @@ v2.1 (2019-07-15) ----------------- * Added Django 2.2 support. -* Changed ``.non_polymorphic()``, to use a different iterable class that completely cirvumvent polymorphic. +* Changed ``.non_polymorphic()``, to use a different iterable class that completely circumvent + polymorphic. * Changed SQL for ``instance_of`` filter: use ``IN`` statement instead of ``OR`` clauses. * Changed queryset iteration to implement ``prefetch_related()`` support. * Fixed Django 3.0 alpha compatibility. * Fixed compatibility with current django-extra-views_ in ``polymorphic.contrib.extra_views``. * Fixed ``prefetch_related()`` support on polymorphic M2M relations. * Fixed model subclass ``___`` selector for abstract/proxy models. -* Fixed model subclass ``___`` selector for models with a custom ``OneToOneField(parent_link=True)``. +* Fixed model subclass ``___`` selector for models with a custom + ``OneToOneField(parent_link=True)``. * Fixed unwanted results on calling ``queryset.get_real_instances([])``. * Fixed unwanted ``TypeError`` exception when ``PolymorphicTypeInvalid`` should have raised. * Fixed hiding the add-button of polymorphic lines in the Django admin. @@ -98,8 +101,9 @@ v2.0.3 (2018-08-24) v2.0.2 (2018-02-05) ------------------- -* Fixed manager inheritance behavior for Django 1.11, by automatically enabling ``Meta.manager_inheritance_from_future`` if it's not defined. - This restores the manager inheritance behavior that *django-polymorphic 1.3* provided for Django 1.x projects. +* Fixed manager inheritance behavior for Django 1.11, by automatically enabling + ``Meta.manager_inheritance_from_future`` if it's not defined. This restores the manager + inheritance behavior that *django-polymorphic 1.3* provided for Django 1.x projects. * Fixed internal ``base_objects`` usage. @@ -123,8 +127,8 @@ v2.0.0 (2018-01-22) * **BACKWARDS INCOMPATIBILITY:** Removed old deprecated code from 1.0, thus: * Import managers from ``polymorphic.managers`` (plural), not ``polymorphic.manager``. - * Register child models to the admin as well using ``@admin.register()`` or ``admin.site.register()``, - as this is no longer done automatically. + * Register child models to the admin as well using ``@admin.register()`` or + ``admin.site.register()``, as this is no longer done automatically. * Added Django 2.0 support. @@ -177,7 +181,8 @@ v1.3.0 (2017-08-01) * **BACKWARDS INCOMPATIBILITY:** Dropped Django 1.4, 1.5, 1.6, 1.7, 1.9 and Python 2.6 support. Only official Django releases (1.8, 1.10, 1.11) are supported now. * Allow expressions to pass unchanged in ``.order_by()`` -* Fixed Django 1.11 accessor checks (to support subclasses of ``ForwardManyToOneDescriptor``, like ``ForwardOneToOneDescriptor``) +* Fixed Django 1.11 accessor checks (to support subclasses of ``ForwardManyToOneDescriptor``, like + ``ForwardOneToOneDescriptor``) * Fixed polib syntax error messages in translations. @@ -210,7 +215,8 @@ v1.0.2 (2016-10-14) * Fixed ``polymorphic_modelformset_factory()`` usage. * Fixed Python 3 bug for inline formsets. * Fixed CSS for Grappelli, so model choice menu properly overlaps. -* Fixed ``ParentAdminNotRegistered`` exception for models that are registered via a proxy model instead of the real base model. +* Fixed ``ParentAdminNotRegistered`` exception for models that are registered via a proxy model + instead of the real base model. v1.0.1 (2016-09-11) @@ -226,7 +232,8 @@ v1.0.0 (2016-09-02) * Added **admin inline** support for polymorphic models. * Added **formset** support for polymorphic models. * Added support for polymorphic queryset limiting effects on *proxy models*. -* Added support for multiple databases with the ``.using()`` method and ``using=..`` keyword argument. +* Added support for multiple databases with the ``.using()`` method and ``using=..`` keyword + argument. * Fixed modifying passed ``Q()`` objects in place. .. note:: @@ -235,8 +242,8 @@ v1.0.0 (2016-09-02) The new registration style improves the compatibility in the Django admin. * Register each ``PolymorphicChildModelAdmin`` with the admin site too. - * The ``child_models`` attribute of the ``PolymorphicParentModelAdmin`` should be a flat list of all child models. - The ``(model, admin)`` tuple is obsolete. + * The ``child_models`` attribute of the ``PolymorphicParentModelAdmin`` should be a flat list of + all child models. The ``(model, admin)`` tuple is obsolete. Also note that proxy models will now limit the queryset too. @@ -260,8 +267,9 @@ v0.9.1 (2016-02-18) ------------------- * Fixed support for ``PolymorphicManager.from_queryset()`` for custom query sets. -* Fixed Django 1.7 ``changeform_view()`` redirection to the child admin site. - This fixes custom admin code that uses these views, such as django-reversion_'s ``revision_view()`` / ``recover_view()``. +* Fixed Django 1.7 ``changeform_view()`` redirection to the child admin site. This fixes custom + admin code that uses these views, such as django-reversion_'s ``revision_view()`` / + ``recover_view()``. * Fixed ``.only('pk')`` field support. * Fixed ``object_history_template`` breadcrumb. **NOTE:** when using django-reversion_ / django-reversion-compare_, make sure to implement @@ -278,15 +286,16 @@ v0.9.0 (2016-02-17) The new change-URL redirect overlapped any custom URLs defined in the child admin. * Fix Django 1.9 support in the admin. * Fix setting an extra custom manager without overriding the ``_default_manager``. -* Fix missing ``history_view()`` redirection to the child admin, which is important for django-reversion_ support. - See the documentation for hints for :ref:`django-reversion-compare support `. +* Fix missing ``history_view()`` redirection to the child admin, which is important for + django-reversion_ support. See the documentation for hints for + :ref:`django-reversion-compare support `. v0.8.1 (2015-12-29) ------------------- -* Fixed support for reverse relations for ``relname___field`` when the field starts with an ``_`` character. - Otherwise, the query will be interpreted as subclass lookup (``ClassName___field``). +* Fixed support for reverse relations for ``relname___field`` when the field starts with an ``_`` + character. Otherwise, the query will be interpreted as subclass lookup (``ClassName___field``). v0.8.0 (2015-12-28) @@ -304,7 +313,8 @@ v0.8.0 (2015-12-28) from polymorphic.managers import PolymorphicManager, PolymorphicQuerySet from polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent -* **BACKWARDS INCOMPATIBILITY:** Removed ``__version__.py`` in favor of a standard ``__version__`` in ``polymorphic/__init__.py``. +* **BACKWARDS INCOMPATIBILITY:** Removed ``__version__.py`` in favor of a standard ``__version__`` + in ``polymorphic/__init__.py``. * **BACKWARDS INCOMPATIBILITY:** Removed automatic proxying of method calls to the queryset class. Use the standard Django methods instead: @@ -322,7 +332,8 @@ v0.7.2 (2015-10-01) ------------------- * Added ``queryset.as_manager()`` support for Django 1.7/1.8 -* Optimize model access for non-dumpdata usage; avoid ``__getattribute__()`` call each time to access the manager. +* Optimize model access for non-dumpdata usage; avoid ``__getattribute__()`` call each time to + access the manager. * Fixed 500 error when using invalid PK's in the admin URL, return 404 instead. * Fixed possible issues when using an custom ``AdminSite`` class for the parent object. * Fixed Pickle exception when polymorphic model is cached. @@ -338,9 +349,11 @@ v0.7.0 (2015-04-08) ------------------- * Added Django 1.8 support -* Added support for custom primary key defined using ``mybase_ptr = models.OneToOneField(BaseClass, parent_link=True, related_name="...")``. +* Added support for custom primary key defined using + ``mybase_ptr = models.OneToOneField(BaseClass, parent_link=True, related_name="...")``. * Fixed Python 3 issue in the admin -* Fixed ``_default_manager`` to be consistent with Django, it's now assigned directly instead of using ``add_to_class()`` +* Fixed ``_default_manager`` to be consistent with Django, it's now assigned directly instead of + using ``add_to_class()`` * Fixed 500 error for admin URLs without a '/', e.g. ``admin/app/parentmodel/id``. * Fixed preserved filter for Django admin in delete views * Removed test noise for diamond inheritance problem (which Django 1.7 detects) @@ -350,8 +363,8 @@ v0.6.1 (2014-12-30) ------------------- * Remove Django 1.7 warnings -* Fix Django 1.4/1.5 queryset calls on related objects for unknown methods. - The ``RelatedManager`` code overrides ``get_query_set()`` while ``__getattr__()`` used the new-style ``get_queryset()``. +* Fix Django 1.4/1.5 queryset calls on related objects for unknown methods. The ``RelatedManager`` + code overrides ``get_query_set()`` while ``__getattr__()`` used the new-style ``get_queryset()``. * Fix validate_model_fields(), caused errors when metaclass raises errors @@ -360,15 +373,17 @@ v0.6.0 (2014-10-14) * Added Django 1.7 support. * Added permission check for all child types. -* **BACKWARDS INCOMPATIBILITY:** the ``get_child_type_choices()`` method receives 2 arguments now (request, action). - If you have overwritten this method in your code, make sure the method signature is updated accordingly. +* **BACKWARDS INCOMPATIBILITY:** the ``get_child_type_choices()`` method receives 2 arguments now + (request, action). If you have overwritten this method in your code, make sure the method + signature is updated accordingly. v0.5.6 (2014-07-21) ------------------- * Added ``pk_regex`` to the ``PolymorphicParentModelAdmin`` to support non-integer primary keys. -* Fixed passing ``?ct_id=`` to the add view for Django 1.6 (fixes compatibility with django-parler_). +* Fixed passing ``?ct_id=`` to the add view for Django 1.6 (fixes compatibility with + django-parler_). v0.5.5 (2014-04-29) @@ -380,7 +395,8 @@ v0.5.5 (2014-04-29) v0.5.4 (2014-04-09) ------------------- -* Fix ``.non_polymorphic()`` to returns a clone of the queryset, instead of effecting the existing queryset. +* Fix ``.non_polymorphic()`` to returns a clone of the queryset, instead of effecting the existing + queryset. * Fix missing ``alters_data = True`` annotations on the overwritten ``save()`` methods. * Fix infinite recursion bug in the admin with Django 1.6+ * Added detection of bad ``ContentType`` table data. @@ -432,7 +448,8 @@ v0.4.1 (2013-04-10) * Add default admin ``list_filter`` for polymorphic model type. * Fix queryset support of related objects. * Performed an overall cleanup of the project -* **Deprecated** the ``queryset_class`` argument of the ``PolymorphicManager`` constructor, use the class attribute instead. +* **Deprecated** the ``queryset_class`` argument of the ``PolymorphicManager`` constructor, use the + class attribute instead. * **Dropped** Django 1.1, 1.2 and 1.3 support diff --git a/docs/conf.py b/docs/conf.py index c8abfb0..7ab7bd6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -52,7 +52,7 @@ master_doc = "index" # General information about the project. project = "django-polymorphic" -copyright = "2013, Bert Constantin, Chris Glass, Diederik van der Boor" +copyright = "2013, Bert Constantin, Chris Glass, Diederik van der Boor, Brian Kohan" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -196,7 +196,7 @@ latex_documents = [ "index", "django-polymorphic.tex", "django-polymorphic Documentation", - "Bert Constantin, Chris Glass, Diederik van der Boor", + "Bert Constantin, Chris Glass, Diederik van der Boor, Brian Kohan", "manual", ) ] @@ -231,7 +231,7 @@ man_pages = [ "index", "django-polymorphic", "django-polymorphic Documentation", - ["Bert Constantin, Chris Glass, Diederik van der Boor"], + ["Bert Constantin, Chris Glass, Diederik van der Boor", "Brian Kohan"], 1, ) ] @@ -250,7 +250,7 @@ texinfo_documents = [ "index", "django-polymorphic", "django-polymorphic Documentation", - "Bert Constantin, Chris Glass, Diederik van der Boor", + "Bert Constantin, Chris Glass, Diederik van der Boor, Brian Kohan", "django-polymorphic", "One line description of project.", "Miscellaneous", diff --git a/docs/formsets.rst b/docs/formsets.rst index 0618433..9d75587 100644 --- a/docs/formsets.rst +++ b/docs/formsets.rst @@ -33,7 +33,8 @@ Like standard Django formsets, there are 3 factory methods available: * :func:`~polymorphic.formsets.polymorphic_modelformset_factory` - create a regular model formset. * :func:`~polymorphic.formsets.polymorphic_inlineformset_factory` - create a inline model formset. -* :func:`~polymorphic.formsets.generic_polymorphic_inlineformset_factory` - create an inline formset for a generic foreign key. +* :func:`~polymorphic.formsets.generic_polymorphic_inlineformset_factory` - create an inline formset + for a generic foreign key. Each one uses a different base class: @@ -41,4 +42,5 @@ Each one uses a different base class: * :class:`~polymorphic.formsets.BasePolymorphicInlineFormSet` * :class:`~polymorphic.formsets.BaseGenericPolymorphicInlineFormSet` -When needed, the base class can be overwritten and provided to the factory via the ``formset`` parameter. +When needed, the base class can be overwritten and provided to the factory via the ``formset`` +parameter. diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 9b9249c..0000000 --- a/docs/make.bat +++ /dev/null @@ -1,190 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-polymorphic.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-polymorphic.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -:end diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 9abcd42..13c1b5c 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -60,9 +60,11 @@ Use ``instance_of`` or ``not_instance_of`` for narrowing the result to specific Polymorphic filtering: Get all projects where Mr. Turner is involved as an artist or supervisor (note the three underscores): ->>> Project.objects.filter(Q(ArtProject___artist='T. Turner') | Q(ResearchProject___supervisor='T. Turner')) -[ , - ] +.. code-block:: python + + >>> Project.objects.filter(Q(ArtProject___artist='T. Turner') | Q(ResearchProject___supervisor='T. Turner')) + [ , + ] This is basically all you need to know, as *django-polymorphic* mostly works fully automatic and just delivers the expected results. diff --git a/docs/third-party.rst b/docs/third-party.rst index bbcf9d3..43790ae 100644 --- a/docs/third-party.rst +++ b/docs/third-party.rst @@ -14,14 +14,16 @@ Add this option to your settings: GUARDIAN_GET_CONTENT_TYPE = 'polymorphic.contrib.guardian.get_polymorphic_base_content_type' -This option requires django-guardian_ >= 1.4.6. Details about how this option works are available in the -`django-guardian documentation `_. +This option requires django-guardian_ >= 1.4.6. Details about how this option works are available in +the `django-guardian documentation +`_. django-rest-framework support ----------------------------- -The django-rest-polymorphic_ package provides polymorphic serializers that help you integrate your polymorphic models with `django-rest-framework`. +The django-rest-polymorphic_ package provides polymorphic serializers that help you integrate your +polymorphic models with `django-rest-framework`. Example @@ -104,9 +106,12 @@ django-reversion support Support for django-reversion_ works as expected with polymorphic models. However, they require more setup than standard models. That's become: -* Manually register the child models with django-reversion_, so their ``follow`` parameter can be set. -* Polymorphic models use `multi-table inheritance `_. - See the `reversion documentation `_ +* Manually register the child models with django-reversion_, so their ``follow`` parameter can be + set. +* Polymorphic models use `multi-table inheritance + `_. + See the `reversion documentation + `_ how to deal with this by adding a ``follow`` field for the primary key. * Both admin classes redefine ``object_history_template``. @@ -161,7 +166,8 @@ Redefine a :file:`admin/polymorphic/object_history.html` template, so it combine {% breadcrumb_scope base_opts %}{{ block.super }}{% endbreadcrumb_scope %} {% endblock %} -This makes sure both the reversion template is used, and the breadcrumb is corrected for the polymorphic model. +This makes sure both the reversion template is used, and the breadcrumb is corrected for the +polymorphic model. .. _django-reversion-compare-support: @@ -180,7 +186,8 @@ In your parent admin, include the following method: As the compare view resolves the the parent admin, it uses it's base model to find revisions. This doesn't work, since it needs to look for revisions of the child model. Using this tweak, -the view of the actual child model is used, similar to the way the regular change and delete views are redirected. +the view of the actual child model is used, similar to the way the regular change and delete views +are redirected. .. _django-extra-views: https://github.com/AndrewIngram/django-extra-views diff --git a/justfile b/justfile new file mode 100644 index 0000000..8045f21 --- /dev/null +++ b/justfile @@ -0,0 +1,219 @@ +set windows-shell := ["powershell.exe", "-NoLogo", "-Command"] +set unstable := true +set script-interpreter := ['uv', 'run', '--script'] + +export PYTHONPATH := source_directory() + +[private] +default: + @just --list --list-submodules + +# run the django admin +[script] +manage *COMMAND: + import os + import sys + from django.core import management + sys.path.append(os.getcwd()) + os.environ["DJANGO_SETTINGS_MODULE"] = "polymorphic.tests.settings" + management.execute_from_command_line(sys.argv + "{{ COMMAND }}".split(" ")) + +# install the uv package manager +[linux] +[macos] +install_uv: + curl -LsSf https://astral.sh/uv/install.sh | sh + +# install the uv package manager +[windows] +install_uv: + powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" + +# setup the venv and pre-commit hooks +setup python="python": + uv venv -p {{ python }} + @just run pre-commit install + +# install git pre-commit hooks +install-precommit: + @just run pre-commit install + +# update and install development dependencies +install *OPTS: + uv sync {{ OPTS }} + @just run pre-commit install + +# install documentation dependencies +install-docs: + uv sync --group docs --all-extras + +# run static type checking +check-types: + #TODO @just run mypy src/polymorphic + +# run package checks +check-package: + @just run pip check + +# remove doc build artifacts- +[script] +clean-docs: + import shutil + shutil.rmtree('./docs/_build', ignore_errors=True) + +# remove the virtual environment +[script] +clean-env: + import shutil + import sys + shutil.rmtree(".venv", ignore_errors=True) + +# remove all git ignored files +clean-git-ignored: + git clean -fdX + +# remove all non repository artifacts +clean: clean-docs clean-env clean-git-ignored + +# build html documentation +build-docs-html: install-docs + @just run sphinx-build --fresh-env --builder html --doctree-dir ./docs/_build/doctrees ./docs/ ./docs/_build/html + +# build pdf documentation +build-docs-pdf: install-docs + @just run sphinx-build --fresh-env --builder latex --doctree-dir ./docs/_build/doctrees ./docs/ ./docs/_build/pdf + cd docs/_build/pdf && make + +# build the docs +build-docs: build-docs-html + +# build docs and package +build: build-docs-html + uv build + +# open the html documentation +[script] +open-docs: + import os + import webbrowser + webbrowser.open(f'file://{os.getcwd()}/docs/_build/html/index.html') + +# build and open the documentation +docs: build-docs-html open-docs + +# serve the documentation, with auto-reload +docs-live: install-docs + @just run sphinx-autobuild docs docs/_build --open-browser --watch src --port 8000 --delay 1 + +_link_check: + -uv run sphinx-build -b linkcheck -Q -D linkcheck_timeout=10 ./docs/ ./docs/_build + +# check the documentation links for broken links +[script] +check-docs-links: _link_check + import os + import sys + import json + from pathlib import Path + # The json output isn't valid, so we have to fix it before we can process. + data = json.loads(f"[{','.join((Path(os.getcwd()) / 'docs/_build/output.json').read_text().splitlines())}]") + broken_links = [link for link in data if link["status"] not in {"working", "redirected", "unchecked", "ignored"}] + if broken_links: + for link in broken_links: + print(f"[{link['status']}] {link['filename']}:{link['lineno']} -> {link['uri']}", file=sys.stderr) + sys.exit(1) + +# lint the documentation +check-docs: + @just run doc8 --ignore-path ./docs/_build --max-line-length 100 -q ./docs + +# lint the code +check-lint: + @just run ruff check --select I + @just run ruff check + +# check if the code needs formatting +check-format: + @just run ruff format --check + +# check that the readme renders +check-readme: + @just run -m readme_renderer ./README.md -o /tmp/README.html + +_check-readme-quiet: + @just --quiet check-readme + +# sort the python imports +sort-imports: + @just run ruff check --fix --select I + +# format the code and sort imports +format: sort-imports + just --fmt --unstable + @just run ruff format + +# sort the imports and fix linting issues +lint: sort-imports + @just run ruff check --fix + +# fix formatting, linting issues and import sorting +fix: lint format + +# run all static checks +check: check-lint check-format check-types check-package check-docs check-docs-links _check-readme-quiet + +# run tests +test *TESTS: + @just run pytest --cov-append {{ TESTS }} + +# run the pre-commit checks +precommit: + @just run pre-commit + +# generate the test coverage report +coverage: + @just run coverage combine --keep *.coverage + @just run coverage report + @just run coverage xml + +[script] +fetch-refs LIB: install-docs + import os + from pathlib import Path + import logging as _logging + import sys + import runpy + from sphinx.ext.intersphinx import inspect_main + _logging.basicConfig() + + libs = runpy.run_path(Path(os.getcwd()) / "docs/conf.py").get("intersphinx_mapping") + url = libs.get("{{ LIB }}", None) + if not url: + sys.exit(f"Unrecognized {{ LIB }}, must be one of: {', '.join(libs.keys())}") + if url[1] is None: + url = f"{url[0].rstrip('/')}/objects.inv" + else: + url = url[1] + + raise SystemExit(inspect_main([url])) + +# run the command in the virtual environment +run +ARGS: + uv run {{ ARGS }} + +# validate the given version string against the lib version +[script] +validate_version VERSION: + import re + import tomllib + import polymorphic + version = re.match(r"v?(\d+[.]\d+[.]\w+)", "{{ VERSION }}").groups()[0] + assert version == tomllib.load(open('pyproject.toml', 'rb'))['project']['version'] + assert version == polymorphic.__version__ + print(version) + +# issue a release for the given semver string (e.g. 2.1.0) +release VERSION: + @just validate_version v{{ VERSION }} + git tag -s v{{ VERSION }} -m "{{ VERSION }} Release" + git push origin v{{ VERSION }} diff --git a/pyproject.toml b/pyproject.toml index dea1b73..cbf3e5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,6 +88,10 @@ sphinx = true [tool.ruff] line-length = 99 +exclude = [ + "**/migrations/*.py", + "**/migrations/**", +] [tool.ruff.lint] extend-ignore = [ @@ -132,9 +136,9 @@ addopts = [ [tool.coverage.run] source = [ - "polymorphic" + "src/polymorphic" ] -omit = ["*/tests/*", "src/polymorphic/tests/*"] +omit = ["*/migrations/*", "*/tests/*", "src/polymorphic/tests/*"] branch = true relative_files = true diff --git a/src/polymorphic/contrib/guardian.py b/src/polymorphic/contrib/guardian.py index a22ed96..cf56cb1 100644 --- a/src/polymorphic/contrib/guardian.py +++ b/src/polymorphic/contrib/guardian.py @@ -3,12 +3,12 @@ from django.contrib.contenttypes.models import ContentType def get_polymorphic_base_content_type(obj): """ - Helper function to return the base polymorphic content type id. This should used with django-guardian and the - GUARDIAN_GET_CONTENT_TYPE option. + Helper function to return the base polymorphic content type id. This should used + with django-guardian and the ``GUARDIAN_GET_CONTENT_TYPE`` option. See the django-guardian documentation for more information: - https://django-guardian.readthedocs.io/en/latest/configuration.html#guardian-get-content-type + https://django-guardian.readthedocs.io/en/latest/configuration """ if hasattr(obj, "polymorphic_model_marker"): try: