pax_global_header 0000666 0000000 0000000 00000000064 14060101625 0014505 g ustar 00root root 0000000 0000000 52 comment=e405586eef4be0474b719a994de91c154e847589
django-extra-views-0.14.0/ 0000775 0000000 0000000 00000000000 14060101625 0015305 5 ustar 00root root 0000000 0000000 django-extra-views-0.14.0/.editorconfig 0000664 0000000 0000000 00000000413 14060101625 0017760 0 ustar 00root root 0000000 0000000 # EditorConfig: http://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
quote_type = double
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 88
[*.json]
insert_final_newline = false
django-extra-views-0.14.0/.github/ 0000775 0000000 0000000 00000000000 14060101625 0016645 5 ustar 00root root 0000000 0000000 django-extra-views-0.14.0/.github/SUPPORT.md 0000664 0000000 0000000 00000001222 14060101625 0020340 0 ustar 00root root 0000000 0000000 # A note about django-extra-views maintenance
Dear fellow Pythonista: This project is currently in maintenance mode -- it was abandoned by the original author and taken into custody by me, @jonashaag.
I can't spend a lot of time on the project, but will do my best to triage any issues and give guidance for those who want to contribute patches to the project.
If you find any bugs in this library, **please consider submitting a pull request** that fixes your bug, even if it's trivial to fix.
On a related note: If you've ever wanted to become a core member of an open-source project, this is your chance :-) Contact me at jonas %AT% lophus %DOT% org.
django-extra-views-0.14.0/.gitignore 0000664 0000000 0000000 00000000227 14060101625 0017276 0 ustar 00root root 0000000 0000000 *.pyc
.project
.pydevproject
.coverage
.DS_Store
django_extra_views.egg-info/
htmlcov/
build/
docs/_build/
dist/
.idea
nosetests.xml
*.sqlite3
/.tox/
django-extra-views-0.14.0/.travis.yml 0000664 0000000 0000000 00000001227 14060101625 0017420 0 ustar 00root root 0000000 0000000 sudo: false
language: python
python:
- "3.5"
- "3.6"
- "3.7"
- "3.8"
env:
- DJANGO=django21
- DJANGO=django22
- DJANGO=django30
- DJANGO=django31
- DJANGO=djangomaster
matrix:
exclude:
- python: "3.5"
env: DJANGO=django30
- python: "3.5"
env: DJANGO=django31
- python: "3.5"
env: DJANGO=djangomaster
- python: "3.8"
env: DJANGO=django21
allow_failures:
- env: DJANGO=djangomaster
before_install:
- pip install codecov
install:
- pip install tox
script:
- TOX_TEST_PASSENV="TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH" tox -e py${TRAVIS_PYTHON_VERSION//[.]/}-$DJANGO
after_success:
- codecov
django-extra-views-0.14.0/AUTHORS.rst 0000664 0000000 0000000 00000000720 14060101625 0017163 0 ustar 00root root 0000000 0000000 Primary Author(s):
* Andrew Ingram (https://github.com/AndrewIngram)
Other Contributors:
* Sergey Fursov (https://github.com/GeyseR)
* Pavel Zhukov (https://github.com/zeus)
* Pi Delport (https://github.com/pjdelport)
* jgsogo (https://github.com/jgsogo)
* Krzysiek Szularz (https://github.com/szuliq)
* Miguel Restrepo (https://github.com/miguelrestrepo)
* Henry Ward (https://bitbucket.org/henward0)
* Mark Gensler (https://github.com/sdolemelipone)
django-extra-views-0.14.0/CHANGELOG.rst 0000664 0000000 0000000 00000011543 14060101625 0017332 0 ustar 00root root 0000000 0000000 Change History
==============
0.14.0 (2021-06-08)
-------------------------
Changes:
~~~~~~~~
Supported Versions:
======== ==========
Python Django
======== ==========
3.5 2.1–2.2
3.6-3.7 2.1–3.1
3.8 2.2–3.1
======== ==========
- Removed support for Python 2.7.
- Added support for Python 3.8 and Django 3.1.
- Removed the following classes (use the class in parentheses instead):
- ``BaseFormSetMixin`` (use ``BaseFormSetFactory``).
- ``BaseInlineFormSetMixin`` (use ``BaseInlineFormSetFactory``).
- ``InlineFormSet`` (use ``InlineFormSetFactory``).
- ``BaseGenericInlineFormSetMixin`` (use ``BaseGenericInlineFormSetFactory``).
- ``GenericInlineFormSet`` (use ``GenericInlineFormSetFactory``).
0.13.0 (2019-12-20)
-------------------------
Changes:
~~~~~~~~
Supported Versions:
======== ==========
Python Django
======== ==========
2.7 1.11
3.5 1.11–2.2
3.6-3.7 1.11–3.0
======== ==========
- Added ``SuccessMessageMixin`` and ``FormSetSuccessMessageMixin``.
- ``CreateWithInlinesView`` and ``UpdateWithInlinesView`` now call ``self.form_valid``
method within ``self.forms_valid``.
- Revert ``view.object`` back to it's original value from the GET request if
validation fails for the inline formsets in ``CreateWithInlinesView`` and
``UpdateWithInlinesview``.
- Added support for Django 3.0.
0.12.0 (2018-10-21)
-------------------
Supported Versions:
======== ==========
Python Django
======== ==========
2.7 1.11
3.4 1.11–2.0
3.5-3.7 1.11–2.1
======== ==========
Changes:
~~~~~~~~
- Removed setting of ``BaseInlineFormSetMixin.formset_class`` and
``GenericInlineFormSetMixin.formset_class`` so that ``formset`` can be set in
``factory_kwargs`` instead.
- Removed ``ModelFormSetMixin.get_context_data`` and
``BaseInlineFormSetMixin.get_context_data`` as this code was duplicated from
Django's ``MultipleObjectMixin`` and ``SingleObjectMixin`` respectively.
- Renamed ``BaseFormSetMixin`` to ``BaseFormSetFactory``.
- Renamed ``BaseInlineFormSetMixin`` to ``BaseInlineFormSetFactory``.
- Renamed ``InlineFormSet`` to ``InlineFormSetFactory``.
- Renamed ``BaseGenericInlineFormSetMixin`` to ``BaseGenericInlineFormSetFactory``.
- Renamed ``GenericInlineFormSet`` to ``GenericInlineFormSetFactory``.
All renamed classes will be removed in a future release.
0.11.0 (2018-04-24)
-------------------
Supported Versions:
======== ==========
Python Django
======== ==========
2.7 1.11
3.4–3.6 1.11–2.0
======== ==========
Backwards-incompatible changes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Dropped support for Django 1.7–1.10.
- Removed support for factory kwargs ``extra``, ``max_num``, ``can_order``,
``can_delete``, ``ct_field``, ``formfield_callback``, ``fk_name``,
``widgets``, ``ct_fk_field`` being set on ``BaseFormSetMixin`` and its
subclasses. Use ``BaseFormSetMixin.factory_kwargs`` instead.
- Removed support for formset_kwarg ``save_as_new`` being set on
``BaseInlineFormSetMixin`` and its subclasses. Use
``BaseInlineFormSetMixin.formset_kwargs`` instead.
- Removed support for ``get_extra_form_kwargs``. This can be set in the
dictionary key ``form_kwargs`` in ``BaseFormSetMixin.formset_kwargs`` instead.
0.10.0 (2018-02-28)
------------------
New features:
- Added SuccessMessageWithInlinesMixin (#151)
- Allow the formset prefix to be overridden (#154)
Bug fixes:
- SearchableMixin: Fix reduce() of empty sequence error (#149)
- Add fields attributes (Issue #144, PR #150)
- Fix Django 1.11 AttributeError: This QueryDict instance is immutable (#156)
0.9.0 (2017-03-08)
------------------
This version supports Django 1.7, 1.8, 1.9, 1.10 (latest minor versions), and Python 2.7, 3.4, 3.5 (latest minor versions).
- Added Django 1.10 support
- Dropped Django 1.6 support
0.8 (2016-06-14)
----------------
This version supports Django 1.6, 1.7, 1.8, 1.9 (latest minor versions), and Python 2.7, 3.4, 3.5 (latest minor versions).
- Added ``widgets`` attribute setting; allow to change form widgets in the ``ModelFormSetView``.
- Added Django 1.9 support.
- Fixed ``get_context_data()`` usage of ``*args, **kwargs``.
- Fixed silent overwriting of ``ModelForm`` fields to ``__all__``.
Backwards-incompatible changes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Dropped support for Django <= 1.5 and Python 3.3.
- Removed the ``extra_views.multi`` module as it had neither documentation nor
test coverage and was broken for some of the supported Django/Python versions.
- This package no longer implicitly set ``fields = '__all__'``.
If you face ``ImproperlyConfigured`` exceptions, you should have a look at the
`Django 1.6 release notes`_ and set the ``fields`` or ``exclude`` attributes
on your ``ModelForm`` or extra-views views.
.. _Django 1.6 release notes: https://docs.djangoproject.com/en/stable/releases/1.6/#modelform-without-fields-or-exclude
0.7.1 (2015-06-15)
------------------
Beginning of this changelog.
django-extra-views-0.14.0/LICENSE 0000664 0000000 0000000 00000002061 14060101625 0016311 0 ustar 00root root 0000000 0000000 The MIT License
Copyright (c) 2012 Andrew Ingram
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. django-extra-views-0.14.0/MANIFEST.in 0000664 0000000 0000000 00000000417 14060101625 0017045 0 ustar 00root root 0000000 0000000 include LICENSE
include README.rst
recursive-include extra_views *.py
recursive-include extra_views/contrib *.py
recursive-include extra_views/tests *.py
recursive-include extra_views/tests/templates *.html
recursive-include extra_views/tests/templates/extra_views *.html django-extra-views-0.14.0/README.rst 0000664 0000000 0000000 00000012355 14060101625 0017002 0 ustar 00root root 0000000 0000000 |travis| |codecov| |docs-status|
Django Extra Views - The missing class-based generic views for Django
========================================================================
Django-extra-views is a Django package which introduces additional class-based views
in order to simplify common design patterns such as those found in the Django
admin interface.
Full documentation is available at `read the docs`_.
.. _read the docs: https://django-extra-views.readthedocs.io/
.. |travis| image:: https://secure.travis-ci.org/AndrewIngram/django-extra-views.svg?branch=master
:target: https://travis-ci.org/AndrewIngram/django-extra-views
:alt: Build Status
.. |codecov| image:: https://codecov.io/github/AndrewIngram/django-extra-views/coverage.svg?branch=master
:target: https://codecov.io/github/AndrewIngram/django-extra-views?branch=master
:alt: Coverage Status
.. |docs-status| image:: https://readthedocs.org/projects/django-extra-views/badge/?version=latest
:target: https://django-extra-views.readthedocs.io/
:alt: Documentation Status
.. installation-start
Installation
------------
Install the stable release from pypi (using pip):
.. code-block:: sh
pip install django-extra-views
Or install the current master branch from github:
.. code-block:: sh
pip install -e git://github.com/AndrewIngram/django-extra-views.git#egg=django-extra-views
Then add ``'extra_views'`` to your ``INSTALLED_APPS``:
.. code-block:: python
INSTALLED_APPS = [
...
'extra_views',
...
]
.. installation-end
.. features-start
Features
--------
- ``FormSet`` and ``ModelFormSet`` views - The formset equivalents of
``FormView`` and ``ModelFormView``.
- ``InlineFormSetView`` - Lets you edit a formset related to a model (using
Django's ``inlineformset_factory``).
- ``CreateWithInlinesView`` and ``UpdateWithInlinesView`` - Lets you edit a
model and multiple inline formsets all in one view.
- ``GenericInlineFormSetView``, the equivalent of ``InlineFormSetView`` but for
``GenericForeignKeys``.
- Support for generic inlines in ``CreateWithInlinesView`` and
``UpdateWithInlinesView``.
- Support for naming each inline or formset in the template context with
``NamedFormsetsMixin``.
- ``SortableListMixin`` - Generic mixin for sorting functionality in your views.
- ``SearchableListMixin`` - Generic mixin for search functionality in your views.
- ``SuccessMessageMixin`` and ``FormSetSuccessMessageMixin`` - Generic mixins
to display success messages after form submission.
.. features-end
Still to do
-----------
Add support for pagination in ModelFormSetView and its derivatives, the goal
being to be able to mimic the change_list view in Django's admin. Currently this
is proving difficult because of how Django's MultipleObjectMixin handles pagination.
.. quick-examples-start
Quick Examples
--------------
FormSetView
^^^^^^^^^^^^^^^^^^^^^^^
Define a :code:`FormSetView`, a view which creates a single formset from
:code:`django.forms.formset_factory` and adds it to the context.
.. code-block:: python
from extra_views import FormSetView
from my_forms import AddressForm
class AddressFormSet(FormSetView):
form_class = AddressForm
template_name = 'address_formset.html'
Then within ``address_formset.html``, render the formset like this:
.. code-block:: html
ModelFormSetView
^^^^^^^^^^^^^^^^^^^^
Define a :code:`ModelFormSetView`, a view which works as :code:`FormSetView`
but instead renders a model formset using
:code:`django.forms.modelformset_factory`.
.. code-block:: python
from extra_views import ModelFormSetView
class ItemFormSetView(ModelFormSetView):
model = Item
fields = ['name', 'sku']
template_name = 'item_formset.html'
CreateWithInlinesView or UpdateWithInlinesView
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Define :code:`CreateWithInlinesView` and :code:`UpdateWithInlinesView`,
views which render a form to create/update a model instance and its related
inline formsets. Each of the :code:`InlineFormSetFactory` classes use similar
class definitions as the :code:`ModelFormSetView`.
.. code-block:: python
from extra_views import CreateWithInlinesView, UpdateWithInlinesView, InlineFormSetFactory
class ItemInline(InlineFormSetFactory):
model = Item
fields = ['sku', 'price', 'name']
class ContactInline(InlineFormSetFactory):
model = Contact
fields = ['name', 'email']
class CreateOrderView(CreateWithInlinesView):
model = Order
inlines = [ItemInline, ContactInline]
fields = ['customer', 'name']
template_name = 'order_and_items.html'
class UpdateOrderView(UpdateWithInlinesView):
model = Order
inlines = [ItemInline, ContactInline]
fields = ['customer', 'name']
template_name = 'order_and_items.html'
Then within ``order_and_items.html``, render the formset like this:
.. code-block:: html
.. quick-examples-end django-extra-views-0.14.0/docs/ 0000775 0000000 0000000 00000000000 14060101625 0016235 5 ustar 00root root 0000000 0000000 django-extra-views-0.14.0/docs/Makefile 0000664 0000000 0000000 00000012744 14060101625 0017705 0 ustar 00root root 0000000 0000000 # 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/DjangoExtraViews.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DjangoExtraViews.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/DjangoExtraViews"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/DjangoExtraViews"
@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."
django-extra-views-0.14.0/docs/conf.py 0000664 0000000 0000000 00000020443 14060101625 0017537 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
#
# Django Extra Views documentation build configuration file, created by
# sphinx-quickstart on Sun Jan 6 03:11:50 2013.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import os
import re
import sys
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ["sphinx.ext.autodoc", "sphinx.ext.intersphinx"]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix of source filenames.
source_suffix = ".rst"
# The encoding of source files.
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = "index"
# General information about the project.
project = u"Django Extra Views"
copyright = u"2013, Andrew Ingram"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
with open("../extra_views/__init__.py", "rb") as f:
# The full version, including alpha/beta/rc tags.
release = str(re.search('__version__ = "(.+?)"', f.read().decode("utf-8")).group(1))
# The short X.Y version.
version = release.rpartition(".")[0]
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ["_build"]
# The reST default role (used for this markup: `text`) to use for all documents.
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
on_rtd = os.environ.get("READTHEDOCS", None) == "True"
if on_rtd:
html_theme = "default"
else:
html_theme = "nature"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}
# If false, no module index is generated.
# html_domain_indices = True
# If false, no index is generated.
# html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = "DjangoExtraViewsdoc"
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
(
"index",
"DjangoExtraViews.tex",
u"Django Extra Views Documentation",
u"Andrew Ingram",
"manual",
)
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False
# If true, show page references after internal links.
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
# latex_appendices = []
# If false, no module index is generated.
# latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(
"index",
"djangoextraviews",
u"Django Extra Views Documentation",
[u"Andrew Ingram"],
1,
)
]
# If true, show URL addresses after external links.
# man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
"index",
"DjangoExtraViews",
u"Django Extra Views Documentation",
u"Andrew Ingram",
"DjangoExtraViews",
"One line description of project.",
"Miscellaneous",
)
]
# Documents to append as an appendix to all manuals.
# texinfo_appendices = []
# If false, no module index is generated.
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
# texinfo_show_urls = 'footnote'
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {"http://docs.python.org/": None}
django-extra-views-0.14.0/docs/index.rst 0000664 0000000 0000000 00000001271 14060101625 0020077 0 ustar 00root root 0000000 0000000 ==============================================
django-extra-views
==============================================
Django Extra Views provides a number of additional class-based generic views to
complement those provide by Django itself. These mimic some of the functionality
available through the standard admin interface, including Model, Inline and
Generic Formsets.
.. include:: ../README.rst
:start-after: features-start
:end-before: features-end
Table of Contents
-----------------
.. toctree::
:maxdepth: 2
pages/getting-started
pages/formset-views
pages/formset-customization
pages/list-views
Reference
---------
.. toctree::
:maxdepth: 1
pages/changelog
django-extra-views-0.14.0/docs/make.bat 0000664 0000000 0000000 00000011774 14060101625 0017654 0 ustar 00root root 0000000 0000000 @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\DjangoExtraViews.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\DjangoExtraViews.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
django-extra-views-0.14.0/docs/pages/ 0000775 0000000 0000000 00000000000 14060101625 0017334 5 ustar 00root root 0000000 0000000 django-extra-views-0.14.0/docs/pages/changelog.rst 0000664 0000000 0000000 00000000041 14060101625 0022010 0 ustar 00root root 0000000 0000000 .. include:: ../../CHANGELOG.rst
django-extra-views-0.14.0/docs/pages/formset-customization.rst 0000664 0000000 0000000 00000015723 14060101625 0024463 0 ustar 00root root 0000000 0000000 Formset Customization Examples
==============================
Overriding formset_kwargs and factory_kwargs at run time
-------------------------------------------------------------------------
If the values in :code:`formset_kwargs` and :code:`factory_kwargs` need to be
modified at run time, they can be set by overloading the :code:`get_formset_kwargs()`
and :code:`get_factory_kwargs()` methods on any formset view (model, inline or generic)
and the :code:`InlineFormSetFactory` classes:
.. code-block:: python
class AddressFormSetView(FormSetView):
...
def get_formset_kwargs(self):
kwargs = super(AddressFormSetView, self).get_formset_kwargs()
# modify kwargs here
return kwargs
def get_factory_kwargs(self):
kwargs = super(AddressFormSetView, self).get_factory_kwargs()
# modify kwargs here
return kwargs
Overriding the the base formset class
-------------------------------------
The :code:`formset_class` option should be used if you intend to override the
formset methods of a view or a subclass of :code:`InlineFormSetFactory`.
For example, imagine you'd like to add your custom :code:`clean` method
for an inline formset view. Then, define a custom formset class, a subclass of
Django's :code:`BaseInlineFormSet`, like this:
.. code-block:: python
from django.forms.models import BaseInlineFormSet
class ItemInlineFormSet(BaseInlineFormSet):
def clean(self):
# ...
# Your custom clean logic goes here
Now, in your :code:`InlineFormSetView` sub-class, use your formset class via
:code:`formset_class` setting, like this:
.. code-block:: python
from extra_views import InlineFormSetView
from my_app.models import Item
from my_app.forms import ItemForm
class ItemInlineView(InlineFormSetView):
model = Item
form_class = ItemForm
formset_class = ItemInlineFormSet # enables our custom inline
This will enable :code:`clean` method being executed on the formset used by
:code:`ItemInlineView`.
Initial data for ModelFormSet and InlineFormSet
-----------------------------------------------
Passing initial data into ModelFormSet and InlineFormSet works slightly
differently to a regular FormSet. The data passed in from :code:`initial` will
be inserted into the :code:`extra` forms of the formset. Only the data from
:code:`get_queryset()` will be inserted into the initial rows:
.. code-block:: python
from extra_views import ModelFormSetView
from my_app.models import Item
class ItemFormSetView(ModelFormSetView):
template_name = 'item_formset.html'
model = Item
factory_kwargs = {'extra': 10}
initial = [{'name': 'example1'}, {'name': 'example2'}]
The above will result in a formset containing a form for each instance of
:code:`Item` in the database, followed by 2 forms containing the extra initial data,
followed by 8 empty forms.
Altenatively, initial data can be determined at run time and passed in by
overloading :code:`get_initial()`:
.. code-block:: python
...
class ItemFormSetView(ModelFormSetView):
model = Item
template_name = 'item_formset.html'
...
def get_initial(self):
# Get a list of initial values for the formset here
initial = [...]
return initial
Passing arguments to the form constructor
-----------------------------------------
In order to change the arguments which are passed into each form within the
formset, this can be done by the 'form_kwargs' argument passed in to the FormSet
constructor. For example, to give every form an initial value of 'example'
in the 'name' field:
.. code-block:: python
from extra_views import InlineFormSetFactory
class ItemInline(InlineFormSetFactory):
model = Item
formset_kwargs = {'form_kwargs': {'initial': {'name': 'example'}}}
If these need to be modified at run time, it can be done by
:code:`get_formset_kwargs()`:
.. code-block:: python
from extra_views import InlineFormSetFactory
class ItemInline(InlineFormSetFactory):
model = Item
def get_formset_kwargs(self):
kwargs = super(ItemInline, self).get_formset_kwargs()
initial = get_some_initial_values()
kwargs['form_kwargs'].update({'initial': initial})
return kwargs
Named formsets
--------------
If you want more control over the names of your formsets (as opposed to
iterating over :code:`inlines`), you can use :code:`NamedFormsetsMixin`:
.. code-block:: python
from extra_views import NamedFormsetsMixin
class CreateOrderView(NamedFormsetsMixin, CreateWithInlinesView):
model = Order
inlines = [ItemInline, TagInline]
inlines_names = ['Items', 'Tags']
fields = '__all__'
Then use the appropriate names to render them in the html template:
.. code-block:: html
...
{{ Tags }}
...
{{ Items }}
...
Success messages
----------------
When using Django's messages framework, mixins are available to send success
messages in a similar way to ``django.contrib.messages.views.SuccessMessageMixin``.
Ensure that :code:`'django.contrib.messages.middleware.MessageMiddleware'` is included
in the ``MIDDLEWARE`` section of `settings.py`.
:code:`extra_views.SuccessMessageMixin` is for use with views with multiple
inline formsets. It is used in an identical manner to Django's
SuccessMessageMixin_, making :code:`form.cleaned_data` available for string
interpolation using the :code:`%(field_name)s` syntax:
.. _SuccessMessageMixin: https://docs.djangoproject.com/en/dev/ref/contrib/messages/#django.contrib.messages.views.SuccessMessageMixin
.. code-block:: python
from extra_views import CreateWithInlinesView, SuccessMessageMixin
...
class CreateOrderView(SuccessMessageMixin, CreateWithInlinesView):
model = Order
inlines = [ItemInline, ContactInline]
success_message = 'Order %(name)s successfully created!'
...
# or instead, set at runtime:
def get_success_message(self, cleaned_data, inlines):
return 'Order with id {} successfully created'.format(self.object.pk)
Note that the success message mixins should be placed ahead of the main view in
order of class inheritance.
:code:`extra_views.FormSetSuccessMessageMixin` is for use with views which handle a single
formset. In order to parse any data from the formset, you should override the
:code:`get_success_message` method as below:
.. code-block:: python
from extra_views import FormSetView, FormSetSuccessMessageMixin
from my_app.forms import AddressForm
class AddressFormSetView(FormSetView):
form_class = AddressForm
success_url = 'success/'
...
success_message = 'Addresses Updated!'
# or instead, set at runtime
def get_success_message(self, formset)
# Here you can use the formset in the message if required
return '{} addresses were updated.'.format(len(formset.forms))
django-extra-views-0.14.0/docs/pages/formset-views.rst 0000664 0000000 0000000 00000021553 14060101625 0022706 0 ustar 00root root 0000000 0000000 Formset Views
=============
For all of these views we've tried to mimic the API of Django's existing class-based
views as closely as possible, so they should feel natural to anyone who's already
familiar with Django's views.
FormSetView
-----------
This is the formset equivalent of Django's FormView. Use it when you want to
display a single (non-model) formset on a page.
A simple formset:
.. code-block:: python
from extra_views import FormSetView
from my_app.forms import AddressForm
class AddressFormSetView(FormSetView):
template_name = 'address_formset.html'
form_class = AddressForm
success_url = 'success/'
def get_initial(self):
# return whatever you'd normally use as the initial data for your formset.
return data
def formset_valid(self, formset):
# do whatever you'd like to do with the valid formset
return super(AddressFormSetView, self).formset_valid(formset)
and in ``address_formset.html``:
.. code-block:: html
This view will render the template ``address_formset.html`` with a context variable
:code:`formset` representing the :code:`AddressFormSet`. Once POSTed and successfully
validated, :code:`formset_valid` will be called (which is where your handling logic
goes), then the view will redirect to :code:`success_url`.
Formset constructor and factory kwargs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FormSetView exposes all the parameters you'd normally be able to pass to the
:code:`django.forms.BaseFormSet` constructor and
:code:`django.forms.formset_factory()`. This can be done by setting the
respective attribute on the class, or :code:`formset_kwargs` and
:code:`factory_kwargs` at the class level.
Below is an exhaustive list of all formset-related attributes which can be set
at the class level for :code:`FormSetView`:
.. code-block:: python
...
from my_app.forms import AddressForm, BaseAddressFormSet
class AddressFormSetView(FormSetView):
template_name = 'address_formset.html'
form_class = AddressForm
formset_class = BaseAddressFormSet
initial = [{'type': 'home'}, {'type': 'work'}]
prefix = 'address-form'
success_url = 'success/'
factory_kwargs = {'extra': 2, 'max_num': None,
'can_order': False, 'can_delete': False}
formset_kwargs = {'auto_id': 'my_id_%s'}
In the above example, BaseAddressFormSet would be a subclass of
:code:`django.forms.BaseFormSet`.
ModelFormSetView
----------------
ModelFormSetView makes use of :code:`django.forms.modelformset_factory()`, using the
declarative syntax used in :code:`FormSetView` as well as Django's own class-based
views. So as you'd expect, the simplest usage is as follows:
.. code-block:: python
from extra_views import ModelFormSetView
from my_app.models import Item
class ItemFormSetView(ModelFormSetView):
model = Item
fields = ['name', 'sku', 'price']
template_name = 'item_formset.html'
Rather than setting :code:`fields`, :code:`exclude` can be defined
at the class level as a list of fields to be excluded.
It is not necessary to define :code:`fields` or :code:`exclude` if a
:code:`form_class` is defined at the class level:
.. code-block:: python
...
from django.forms import ModelForm
class ItemForm(ModelForm):
# Custom form definition goes here
fields = ['name', 'sku', 'price']
class ItemFormSetView(ModelFormSetView):
model = Item
form_class = ItemForm
template_name = 'item_formset.html'
Like :code:`FormSetView`, the :code:`formset` variable is made available in the template
context. By default this will populate the formset with all the instances of
:code:`Item` in the database. You can control this by overriding :code:`get_queryset` on
the class, which could filter on a URL kwarg (:code:`self.kwargs`), for example:
.. code-block:: python
class ItemFormSetView(ModelFormSetView):
model = Item
template_name = 'item_formset.html'
def get_queryset(self):
sku = self.kwargs['sku']
return super(ItemFormSetView, self).get_queryset().filter(sku=sku)
InlineFormSetView
-----------------
When you want to edit instances of a particular model related to a parent model
(using a ForeignKey), you'll want to use InlineFormSetView. An example use case
would be editing addresses associated with a particular contact.
.. code-block:: python
from extra_views import InlineFormSetView
class EditContactAddresses(InlineFormSetView):
model = Contact
inline_model = Address
...
Aside from the use of :code:`model` and :code:`inline_model`,
:code:`InlineFormSetView` works more-or-less in the same way as
:code:`ModelFormSetView`, instead calling :code:`django.forms.inlineformset_factory()`.
CreateWithInlinesView and UpdateWithInlinesView
-----------------------------------------------
These are the most powerful views in the library, they are effectively
replacements for Django's own :code:`CreateView` and :code:`UpdateView`. The key
difference is that they let you include any number of inline formsets (as well
as the parent model's form). This provides functionality much like the Django
Admin change forms. The API should be fairly familiar as well. The list of the
inlines will be passed to the template as context variable `inlines`.
Here is a simple example that demonstrates the use of each view with normal
inline relationships:
.. code-block:: python
from extra_views import CreateWithInlinesView, UpdateWithInlinesView, InlineFormSetFactory
class ItemInline(InlineFormSetFactory):
model = Item
fields = ['sku', 'price', 'name']
class ContactInline(InlineFormSetFactory):
model = Contact
fields = ['name', 'email']
class CreateOrderView(CreateWithInlinesView):
model = Order
inlines = [ItemInline, ContactInline]
fields = ['customer', 'name']
template_name = 'order_and_items.html'
def get_success_url(self):
return self.object.get_absolute_url()
class UpdateOrderView(UpdateWithInlinesView):
model = Order
inlines = [ItemInline, ContactInline]
fields = ['customer', 'name']
template_name = 'order_and_items.html'
def get_success_url(self):
return self.object.get_absolute_url()
and in the html template:
.. code-block:: html
InlineFormSetFactory
^^^^^^^^^^^^^^^^^^^^
This class represents all the configuration necessary to generate an inline formset
from :code:`django.inlineformset_factory()`. Each class within in
:code:`CreateWithInlines.inlines` and :code:`UpdateWithInlines.inlines`
should be a subclass of :code:`InlineFormSetFactory`. All the
same methods and attributes as :code:`InlineFormSetView` are available, with the
exception of any view-related attributes and methods, such as :code:`success_url`
or :code:`formset_valid()`:
.. code-block:: python
from my_app.forms import ItemForm, BaseItemFormSet
from extra_views import InlineFormSetFactory
class ItemInline(InlineFormSetFactory):
model = Item
form_class = ItemForm
formset_class = BaseItemFormSet
initial = [{'name': 'example1'}, {'name', 'example2'}]
prefix = 'item-form'
factory_kwargs = {'extra': 2, 'max_num': None,
'can_order': False, 'can_delete': False}
formset_kwargs = {'auto_id': 'my_id_%s'}
**IMPORTANT**: Note that when using :code:`InlineFormSetFactory`, :code:`model` should be the
*inline* model and **not** the parent model.
GenericInlineFormSetView
------------------------
In the specific case when you would usually use Django's
:code:`django.contrib.contenttypes.forms.generic_inlineformset_factory()`, you
should use :code:`GenericInlineFormSetView`. The kwargs :code:`ct_field` and
:code:`fk_field` should be set in :code:`factory_kwargs` if they need to be
changed from their default values:
.. code-block:: python
from extra_views.generic import GenericInlineFormSetView
class EditOrderTags(GenericInlineFormSetView):
model = Order
inline_model = Tag
factory_kwargs = {'ct_field': 'content_type', 'fk_field': 'object_id',
'max_num': 1}
formset_kwargs = {'save_as_new': True}
...
There is a :code:`GenericInlineFormSetFactory` which is analogous to
:code:`InlineFormSetFactory` for use with generic inline formsets.
:code:`GenericInlineFormSetFactory` can be used in
:code:`CreateWithInlines.inlines` and :code:`UpdateWithInlines.inlines` in the
obvious way.
django-extra-views-0.14.0/docs/pages/getting-started.rst 0000664 0000000 0000000 00000000143 14060101625 0023171 0 ustar 00root root 0000000 0000000 Getting Started
===============
.. include:: ./installation.rst
.. include:: ./quick-examples.rst django-extra-views-0.14.0/docs/pages/installation.rst 0000664 0000000 0000000 00000000143 14060101625 0022565 0 ustar 00root root 0000000 0000000 .. include:: ../../README.rst
:start-after: installation-start
:end-before: installation-end
django-extra-views-0.14.0/docs/pages/list-views.rst 0000664 0000000 0000000 00000003444 14060101625 0022201 0 ustar 00root root 0000000 0000000 List Views
==========
Searchable List Views
---------------------
You can add search functionality to your ListViews by adding SearchableListMixin
and by setting search_fields:
.. code-block:: python
from django.views.generic import ListView
from extra_views import SearchableListMixin
class SearchableItemListView(SearchableListMixin, ListView):
template_name = 'extra_views/item_list.html'
search_fields = ['name', 'sku']
model = Item
In this case ``object_list`` will be filtered if the 'q' query string is provided
(like /searchable/?q=query), or you can manually override ``get_search_query``
method, to define your own search functionality.
Also you can define some items in ``search_fields`` as tuple (e.g.
``[('name', 'iexact', ), 'sku']``) to provide custom lookups for searching.
Default lookup is ``icontains``. We strongly recommend to use only string lookups,
when number fields will convert to strings before comparison to prevent converting errors.
This controlled by ``check_lookups`` setting of SearchableMixin.
Sortable List View
------------------
.. code-block:: python
from django.views.generic import ListView
from extra_views import SortableListMixin
class SortableItemListView(SortableListMixin, ListView):
sort_fields_aliases = [('name', 'by_name'), ('id', 'by_id'), ]
model = Item
You can hide real field names in query string by define sort_fields_aliases
attribute (see example) or show they as is by define sort_fields.
SortableListMixin adds ``sort_helper`` variable of SortHelper class,
then in template you can use helper functions:
``{{ sort_helper.get_sort_query_by_FOO }}``,
``{{ sort_helper.get_sort_query_by_FOO_asc }}``,
``{{ sort_helper.get_sort_query_by_FOO_desc }}`` and
``{{ sort_helper.is_sorted_by_FOO }}``
django-extra-views-0.14.0/docs/pages/quick-examples.rst 0000664 0000000 0000000 00000000146 14060101625 0023017 0 ustar 00root root 0000000 0000000 .. include:: ../../README.rst
:start-after: quick-examples-start
:end-before: quick-examples-end django-extra-views-0.14.0/extra_views/ 0000775 0000000 0000000 00000000000 14060101625 0017645 5 ustar 00root root 0000000 0000000 django-extra-views-0.14.0/extra_views/__init__.py 0000664 0000000 0000000 00000001414 14060101625 0021756 0 ustar 00root root 0000000 0000000 from extra_views.advanced import (
CreateWithInlinesView,
FormSetSuccessMessageMixin,
InlineFormSetFactory,
NamedFormsetsMixin,
SuccessMessageMixin,
UpdateWithInlinesView,
)
from extra_views.contrib.mixins import SearchableListMixin, SortableListMixin
from extra_views.dates import CalendarMonthView
from extra_views.formsets import (
FormSetView,
InlineFormSetView,
ModelFormSetView,
)
__version__ = "0.14.0"
__all__ = [
"CreateWithInlinesView",
"FormSetSuccessMessageMixin",
"InlineFormSetFactory",
"NamedFormsetsMixin",
"SuccessMessageMixin",
"UpdateWithInlinesView",
"SearchableListMixin",
"SortableListMixin",
"CalendarMonthView",
"FormSetView",
"InlineFormSetView",
"ModelFormSetView",
]
django-extra-views-0.14.0/extra_views/advanced.py 0000664 0000000 0000000 00000020000 14060101625 0021754 0 ustar 00root root 0000000 0000000 from django.contrib import messages
from django.forms.formsets import all_valid
from django.views.generic.base import ContextMixin
from django.views.generic.detail import SingleObjectTemplateResponseMixin
from django.views.generic.edit import FormView, ModelFormMixin
from extra_views.formsets import BaseInlineFormSetFactory
class InlineFormSetFactory(BaseInlineFormSetFactory):
"""
Class used to create an `InlineFormSet` from `inlineformset_factory` as
one of multiple `InlineFormSet`s within a single view.
Subclasses `BaseInlineFormSetFactory` and passes in the necessary view arguments.
"""
def __init__(self, parent_model, request, instance, view_kwargs=None, view=None):
self.inline_model = self.model
self.model = parent_model
self.request = request
self.object = instance
self.kwargs = view_kwargs
self.view = view
def construct_formset(self):
"""
Overrides construct_formset to attach the model class as
an attribute of the returned formset instance.
"""
formset = super().construct_formset()
formset.model = self.inline_model
return formset
class ModelFormWithInlinesMixin(ModelFormMixin):
"""
A mixin that provides a way to show and handle a modelform and inline
formsets in a request.
The inlines should be subclasses of `InlineFormSetFactory`.
"""
inlines = []
def get_inlines(self):
"""
Returns the inline formset classes
"""
return self.inlines
def forms_valid(self, form, inlines):
"""
If the form and formsets are valid, save the associated models.
"""
response = self.form_valid(form)
for formset in inlines:
formset.save()
return response
def forms_invalid(self, form, inlines):
"""
If the form or formsets are invalid, re-render the context data with the
data-filled form and formsets and errors.
"""
return self.render_to_response(
self.get_context_data(form=form, inlines=inlines)
)
def construct_inlines(self):
"""
Returns the inline formset instances
"""
inline_formsets = []
for inline_class in self.get_inlines():
inline_instance = inline_class(
self.model, self.request, self.object, self.kwargs, self
)
inline_formset = inline_instance.construct_formset()
inline_formsets.append(inline_formset)
return inline_formsets
class ProcessFormWithInlinesView(FormView):
"""
A mixin that renders a form and inline formsets on GET and processes it on POST.
"""
def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates a blank version of the form and formsets.
"""
form_class = self.get_form_class()
form = self.get_form(form_class)
inlines = self.construct_inlines()
return self.render_to_response(
self.get_context_data(form=form, inlines=inlines, **kwargs)
)
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form and formset instances with the
passed POST variables and then checked for validity.
"""
form_class = self.get_form_class()
form = self.get_form(form_class)
initial_object = self.object
if form.is_valid():
self.object = form.save(commit=False)
form_validated = True
else:
form_validated = False
inlines = self.construct_inlines()
if all_valid(inlines) and form_validated:
return self.forms_valid(form, inlines)
self.object = initial_object
return self.forms_invalid(form, inlines)
# PUT is a valid HTTP verb for creating (with a known URL) or editing an
# object, note that browsers only support POST for now.
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
class BaseCreateWithInlinesView(ModelFormWithInlinesMixin, ProcessFormWithInlinesView):
"""
Base view for creating an new object instance with related model instances.
Using this base class requires subclassing to provide a response mixin.
"""
def get(self, request, *args, **kwargs):
self.object = None
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = None
return super().post(request, *args, **kwargs)
class CreateWithInlinesView(
SingleObjectTemplateResponseMixin, BaseCreateWithInlinesView
):
"""
View for creating a new object instance with related model instances,
with a response rendered by template.
"""
template_name_suffix = "_form"
class BaseUpdateWithInlinesView(ModelFormWithInlinesMixin, ProcessFormWithInlinesView):
"""
Base view for updating an existing object with related model instances.
Using this base class requires subclassing to provide a response mixin.
"""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super().post(request, *args, **kwargs)
class UpdateWithInlinesView(
SingleObjectTemplateResponseMixin, BaseUpdateWithInlinesView
):
"""
View for updating an object with related model instances,
with a response rendered by template.
"""
template_name_suffix = "_form"
class NamedFormsetsMixin(ContextMixin):
"""
A mixin for use with `CreateWithInlinesView` or `UpdateWithInlinesView` that lets
you define the context variable for each inline.
"""
inlines_names = []
def get_inlines_names(self):
"""
Returns a list of names of context variables for each inline in `inlines`.
"""
return self.inlines_names
def get_context_data(self, **kwargs):
"""
If `inlines_names` has been defined, add each formset to the context under
its corresponding entry in `inlines_names`
"""
context = {}
inlines_names = self.get_inlines_names()
if inlines_names:
# We have formset or inlines in context, but never both
context.update(zip(inlines_names, kwargs.get("inlines", [])))
if "formset" in kwargs:
context[inlines_names[0]] = kwargs["formset"]
context.update(kwargs)
return super().get_context_data(**context)
class SuccessMessageMixin(object):
"""
Adds success message on views with inlines if django.contrib.messages framework
is used.
In order to use just add mixin in to inheritance before main class, e.g.:
class MyCreateWithInlinesView (SuccessMessageMixin, CreateWithInlinesView):
success_message='Something was created!'
"""
success_message = ""
def forms_valid(self, form, inlines):
response = super().forms_valid(form, inlines)
success_message = self.get_success_message(form.cleaned_data, inlines)
if success_message:
messages.success(self.request, success_message)
return response
def get_success_message(self, cleaned_data, inlines):
return self.success_message % cleaned_data
class FormSetSuccessMessageMixin(object):
"""
Adds success message on FormSet views if django.contrib.messages framework
is used. In order to use just add mixin in to inheritance before main
class, e.g.:
class MyFormSetView (FormSetSuccessMessageMixin, ModelFormSetView):
success_message='Something was created!'
"""
success_message = ""
def formset_valid(self, formset):
response = super().formset_valid(formset)
success_message = self.get_success_message(formset)
if success_message:
messages.success(self.request, success_message)
return response
def get_success_message(self, formset):
return self.success_message
django-extra-views-0.14.0/extra_views/contrib/ 0000775 0000000 0000000 00000000000 14060101625 0021305 5 ustar 00root root 0000000 0000000 django-extra-views-0.14.0/extra_views/contrib/__init__.py 0000664 0000000 0000000 00000000000 14060101625 0023404 0 ustar 00root root 0000000 0000000 django-extra-views-0.14.0/extra_views/contrib/mixins.py 0000664 0000000 0000000 00000020506 14060101625 0023171 0 ustar 00root root 0000000 0000000 import datetime
import functools
import operator
from django.contrib import messages
from django.core.exceptions import ImproperlyConfigured
from django.db.models import Q
from django.views.generic.base import ContextMixin
VALID_STRING_LOOKUPS = (
"iexact",
"contains",
"icontains",
"startswith",
"istartswith",
"endswith",
"iendswith",
"search",
"regex",
"iregex",
)
class SearchableListMixin(object):
"""
Filter queryset like a django admin search_fields does, but with little
more intelligence:
if self.search_split is set to True (by default) it will split query
to words (by whitespace)
Also tries to convert each word to date with self.search_date_formats and
then search each word in separate field
e.g. with query 'foo bar' you can find object with
obj.field1__icontains='foo' and obj.field2__icontains=='bar'
To provide custom lookup just set one of the search_fields to tuple,
e.g. search_fields = [('field1', 'iexact'), 'field2', ('field3', 'startswith')]
This class is designed to be used with django.generic.ListView
You could specify query by overriding get_search_query method
by default this method will try to get 'q' key from request.GET
(this can be disabled with search_use_q=False)
"""
search_fields = ["id"]
search_date_fields = None
search_date_formats = ["%d.%m.%y", "%d.%m.%Y"]
search_split = True
search_use_q = True
check_lookups = True
def get_words(self, query):
if self.search_split:
return query.split()
return [query]
def get_search_fields_with_filters(self):
fields = []
for sf in self.search_fields:
if isinstance(sf, str):
fields.append((sf, "icontains"))
else:
if self.check_lookups and sf[1] not in VALID_STRING_LOOKUPS:
raise ValueError("Invalid string lookup - %s" % sf[1])
fields.append(sf)
return fields
def try_convert_to_date(self, word):
"""
Tries to convert word to date(datetime) using search_date_formats
Return None if word fits no one format
"""
for frm in self.search_date_formats:
try:
return datetime.datetime.strptime(word, frm).date()
except ValueError:
pass
return None
def get_search_query(self):
"""
Get query from request.GET 'q' parameter when search_use_q is set to True
Override this method to provide your own query to search
"""
return self.search_use_q and self.request.GET.get("q", "").strip()
def get_queryset(self):
qs = super(SearchableListMixin, self).get_queryset()
query = self.get_search_query()
if query:
w_qs = []
search_pairs = self.get_search_fields_with_filters()
for word in self.get_words(query):
filters = [
Q(**{"%s__%s" % (pair[0], pair[1]): word}) for pair in search_pairs
]
if self.search_date_fields:
dt = self.try_convert_to_date(word)
if dt:
filters.extend(
[
Q(**{field_name: dt})
for field_name in self.search_date_fields
]
)
w_qs.append(functools.reduce(operator.or_, filters))
qs = qs.filter(functools.reduce(operator.and_, w_qs))
qs = qs.distinct()
return qs
class SortHelper(object):
def __init__(
self, request, sort_fields_aliases, sort_param_name, sort_type_param_name
):
# Create a list from sort_fields_aliases, in case it is a generator,
# since we want to iterate through it multiple times.
sort_fields_aliases = list(sort_fields_aliases)
self.initial_params = request.GET.copy()
self.sort_fields = dict(sort_fields_aliases)
self.inv_sort_fields = dict((v, k) for k, v in sort_fields_aliases)
self.initial_sort = self.inv_sort_fields.get(
self.initial_params.get(sort_param_name), None
)
self.initial_sort_type = self.initial_params.get(sort_type_param_name, "asc")
self.sort_param_name = sort_param_name
self.sort_type_param_name = sort_type_param_name
for field, alias in self.sort_fields.items():
setattr(
self,
"get_sort_query_by_%s" % alias,
functools.partial(self.get_params_for_field, field),
)
setattr(
self,
"get_sort_query_by_%s_asc" % alias,
functools.partial(self.get_params_for_field, field, "asc"),
)
setattr(
self,
"get_sort_query_by_%s_desc" % alias,
functools.partial(self.get_params_for_field, field, "desc"),
)
setattr(
self,
"is_sorted_by_%s" % alias,
functools.partial(self.is_sorted_by, field),
)
def is_sorted_by(self, field_name):
return field_name == self.initial_sort and self.initial_sort_type or False
def get_params_for_field(self, field_name, sort_type=None):
"""
If sort_type is None - inverse current sort for field, if no sorted - use asc
"""
if not sort_type:
if self.initial_sort == field_name:
sort_type = "desc" if self.initial_sort_type == "asc" else "asc"
else:
sort_type = "asc"
self.initial_params[self.sort_param_name] = self.sort_fields[field_name]
self.initial_params[self.sort_type_param_name] = sort_type
return "?%s" % self.initial_params.urlencode()
def get_sort(self):
if not self.initial_sort:
return None
sort = "%s" % self.initial_sort
if self.initial_sort_type == "desc":
sort = "-%s" % sort
return sort
class SortableListMixin(ContextMixin):
"""
You can provide either sort_fields as a plain list like
['id', 'some', 'foo__bar', ...]
or, if you want to hide original field names you can provide list of tuples with
alias that will be used:
[('id', 'by_id'), ('some', 'show_this'), ('foo__bar', 'bar')]
If sort_param_name exists in query but sort_type_param_name is omitted queryset
will be sorted as 'asc'
"""
sort_fields = []
sort_fields_aliases = []
sort_param_name = "o"
sort_type_param_name = "ot"
def get_sort_fields(self):
if self.sort_fields:
return zip(self.sort_fields, self.sort_fields)
return self.sort_fields_aliases
def get_sort_helper(self):
return SortHelper(
self.request,
self.get_sort_fields(),
self.sort_param_name,
self.sort_type_param_name,
)
def _sort_queryset(self, queryset):
self.sort_helper = self.get_sort_helper()
sort = self.sort_helper.get_sort()
if sort:
queryset = queryset.order_by(sort)
return queryset
def get_queryset(self):
qs = super(SortableListMixin, self).get_queryset()
if self.sort_fields and self.sort_fields_aliases:
raise ImproperlyConfigured(
"You should provide sort_fields or sort_fields_aliaces but not both"
)
return self._sort_queryset(qs)
def get_context_data(self, **kwargs):
context = {}
if hasattr(self, "sort_helper"):
context["sort_helper"] = self.sort_helper
context.update(kwargs)
return super(SortableListMixin, self).get_context_data(**context)
class SuccessMessageWithInlinesMixin(object):
"""
Adds a success message on successful form submission.
"""
success_message = ""
def forms_valid(self, form, inlines):
response = super(SuccessMessageWithInlinesMixin, self).forms_valid(
form, inlines
)
success_message = self.get_success_message(form.cleaned_data)
if success_message:
messages.success(self.request, success_message)
return response
def get_success_message(self, cleaned_data):
return self.success_message % cleaned_data
django-extra-views-0.14.0/extra_views/dates.py 0000664 0000000 0000000 00000021543 14060101625 0021324 0 ustar 00root root 0000000 0000000 import datetime
import math
from calendar import Calendar
from collections import defaultdict
from django.core.exceptions import ImproperlyConfigured
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from django.views.generic.dates import (
DateMixin,
MonthMixin,
YearMixin,
_date_from_string,
)
from django.views.generic.list import BaseListView, MultipleObjectTemplateResponseMixin
DAYS = (
_("Monday"),
_("Tuesday"),
_("Wednesday"),
_("Thursday"),
_("Friday"),
_("Saturday"),
_("Sunday"),
)
def daterange(start_date, end_date):
"""
Returns an iterator of dates between two provided ones
"""
for n in range(int((end_date - start_date).days + 1)):
yield start_date + datetime.timedelta(n)
class BaseCalendarMonthView(DateMixin, YearMixin, MonthMixin, BaseListView):
"""
A base view for displaying a calendar month
"""
first_of_week = 0 # 0 = Monday, 6 = Sunday
paginate_by = None # We don't want to use this part of MultipleObjectMixin
date_field = None
end_date_field = None # For supporting events with duration
def get_paginate_by(self, queryset):
if self.paginate_by is not None:
raise ImproperlyConfigured(
"'%s' cannot be paginated, it is a calendar view"
% self.__class__.__name__
)
return None
def get_allow_future(self):
return True
def get_end_date_field(self):
"""
Returns the model field to use for end dates
"""
return self.end_date_field
def get_start_date(self, obj):
"""
Returns the start date for a model instance
"""
obj_date = getattr(obj, self.get_date_field())
try:
obj_date = obj_date.date()
except AttributeError:
# It's a date rather than datetime, so we use it as is
pass
return obj_date
def get_end_date(self, obj):
"""
Returns the end date for a model instance
"""
obj_date = getattr(obj, self.get_end_date_field())
try:
obj_date = obj_date.date()
except AttributeError:
# It's a date rather than datetime, so we use it as is
pass
return obj_date
def get_first_of_week(self):
"""
Returns an integer representing the first day of the week.
0 represents Monday, 6 represents Sunday.
"""
if self.first_of_week is None:
raise ImproperlyConfigured(
"%s.first_of_week is required." % self.__class__.__name__
)
if self.first_of_week not in range(7):
raise ImproperlyConfigured(
"%s.first_of_week must be an integer between 0 and 6."
% self.__class__.__name__
)
return self.first_of_week
def get_queryset(self):
"""
Returns a queryset of models for the month requested
"""
qs = super().get_queryset()
year = self.get_year()
month = self.get_month()
date_field = self.get_date_field()
end_date_field = self.get_end_date_field()
date = _date_from_string(
year, self.get_year_format(), month, self.get_month_format()
)
since = date
until = self.get_next_month(date)
# Adjust our start and end dates to allow for next and previous
# month edges
if since.weekday() != self.get_first_of_week():
diff = math.fabs(since.weekday() - self.get_first_of_week())
since = since - datetime.timedelta(days=diff)
if until.weekday() != ((self.get_first_of_week() + 6) % 7):
diff = math.fabs(((self.get_first_of_week() + 6) % 7) - until.weekday())
until = until + datetime.timedelta(days=diff)
if end_date_field:
# 5 possible conditions for showing an event:
# 1) Single day event, starts after 'since'
# 2) Multi-day event, starts after 'since' and ends before 'until'
# 3) Starts before 'since' and ends after 'since' and before 'until'
# 4) Starts after 'since' but before 'until' and ends after 'until'
# 5) Starts before 'since' and ends after 'until'
predicate1 = Q(**{"%s__gte" % date_field: since, end_date_field: None})
predicate2 = Q(
**{"%s__gte" % date_field: since, "%s__lt" % end_date_field: until}
)
predicate3 = Q(
**{
"%s__lt" % date_field: since,
"%s__gte" % end_date_field: since,
"%s__lt" % end_date_field: until,
}
)
predicate4 = Q(
**{
"%s__gte" % date_field: since,
"%s__lt" % date_field: until,
"%s__gte" % end_date_field: until,
}
)
predicate5 = Q(
**{"%s__lt" % date_field: since, "%s__gte" % end_date_field: until}
)
return qs.filter(
predicate1 | predicate2 | predicate3 | predicate4 | predicate5
)
return qs.filter(**{"%s__gte" % date_field: since})
def get_context_data(self, **kwargs):
"""
Injects variables necessary for rendering the calendar into the context.
Variables added are: `calendar`, `weekdays`, `month`, `next_month` and
`previous_month`.
"""
data = super().get_context_data(**kwargs)
year = self.get_year()
month = self.get_month()
date = _date_from_string(
year, self.get_year_format(), month, self.get_month_format()
)
cal = Calendar(self.get_first_of_week())
month_calendar = []
now = datetime.datetime.utcnow()
date_lists = defaultdict(list)
multidate_objs = []
for obj in data["object_list"]:
obj_date = self.get_start_date(obj)
end_date_field = self.get_end_date_field()
if end_date_field:
end_date = self.get_end_date(obj)
if end_date and end_date != obj_date:
multidate_objs.append(
{
"obj": obj,
"range": [x for x in daterange(obj_date, end_date)],
}
)
continue # We don't put multi-day events in date_lists
date_lists[obj_date].append(obj)
for week in cal.monthdatescalendar(date.year, date.month):
week_range = set(daterange(week[0], week[6]))
week_events = []
for val in multidate_objs:
intersect_length = len(week_range.intersection(val["range"]))
if intersect_length:
# Event happens during this week
slot = 1
width = (
intersect_length
) # How many days is the event during this week?
nowrap_previous = (
True
) # Does the event continue from the previous week?
nowrap_next = True # Does the event continue to the next week?
if val["range"][0] >= week[0]:
slot = 1 + (val["range"][0] - week[0]).days
else:
nowrap_previous = False
if val["range"][-1] > week[6]:
nowrap_next = False
week_events.append(
{
"event": val["obj"],
"slot": slot,
"width": width,
"nowrap_previous": nowrap_previous,
"nowrap_next": nowrap_next,
}
)
week_calendar = {"events": week_events, "date_list": []}
for day in week:
week_calendar["date_list"].append(
{
"day": day,
"events": date_lists[day],
"today": day == now.date(),
"is_current_month": day.month == date.month,
}
)
month_calendar.append(week_calendar)
data["calendar"] = month_calendar
data["weekdays"] = [DAYS[x] for x in cal.iterweekdays()]
data["month"] = date
data["next_month"] = self.get_next_month(date)
data["previous_month"] = self.get_previous_month(date)
return data
class CalendarMonthView(MultipleObjectTemplateResponseMixin, BaseCalendarMonthView):
"""
A view for displaying a calendar month, and rendering a template response
"""
template_name_suffix = "_calendar_month"
django-extra-views-0.14.0/extra_views/formsets.py 0000664 0000000 0000000 00000021206 14060101625 0022062 0 ustar 00root root 0000000 0000000 from django.forms.formsets import formset_factory
from django.forms.models import inlineformset_factory, modelformset_factory
from django.http import HttpResponseRedirect
from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
from django.views.generic.detail import (
SingleObjectMixin,
SingleObjectTemplateResponseMixin,
)
from django.views.generic.list import (
MultipleObjectMixin,
MultipleObjectTemplateResponseMixin,
)
class BaseFormSetFactory(object):
"""
Base class for constructing a FormSet from `formset_factory` in a view.
Calling `construct_formset` calls all other methods.
"""
initial = []
form_class = None
formset_class = None
prefix = None
formset_kwargs = {}
factory_kwargs = {}
def construct_formset(self):
"""
Returns an instance of the formset
"""
formset_class = self.get_formset()
return formset_class(**self.get_formset_kwargs())
def get_initial(self):
"""
Returns a copy of the initial data to use for formsets on this view.
"""
return self.initial[:]
def get_prefix(self):
"""
Returns the prefix used for formsets on this view.
"""
return self.prefix
def get_formset_class(self):
"""
Returns the formset class to use in the formset factory
"""
return self.formset_class
def get_form_class(self):
"""
Returns the form class to use with the formset in this view
"""
return self.form_class
def get_formset(self):
"""
Returns the formset class from the formset factory
"""
return formset_factory(self.get_form_class(), **self.get_factory_kwargs())
def get_formset_kwargs(self):
"""
Returns the keyword arguments for instantiating the formset.
"""
kwargs = self.formset_kwargs.copy()
kwargs.update({"initial": self.get_initial(), "prefix": self.get_prefix()})
if self.request.method in ("POST", "PUT"):
kwargs.update(
{"data": self.request.POST.copy(), "files": self.request.FILES}
)
return kwargs
def get_factory_kwargs(self):
"""
Returns the keyword arguments for calling the formset factory
"""
kwargs = self.factory_kwargs.copy()
if self.get_formset_class():
kwargs["formset"] = self.get_formset_class()
return kwargs
class FormSetMixin(BaseFormSetFactory, ContextMixin):
"""
A view mixin that provides a way to show and handle a single formset in a request.
"""
success_url = None
def get_success_url(self):
"""
Returns the supplied URL.
"""
if self.success_url:
url = self.success_url
else:
# Default to returning to the same page
url = self.request.get_full_path()
return url
def formset_valid(self, formset):
"""
If the formset is valid redirect to the supplied URL
"""
return HttpResponseRedirect(self.get_success_url())
def formset_invalid(self, formset):
"""
If the formset is invalid, re-render the context data with the
data-filled formset and errors.
"""
return self.render_to_response(self.get_context_data(formset=formset))
class ModelFormSetMixin(FormSetMixin, MultipleObjectMixin):
"""
A view mixin that provides a way to show and handle a single model formset
in a request.
Uses `modelformset_factory`.
"""
exclude = None
fields = None
def get_formset_kwargs(self):
"""
Returns the keyword arguments for instantiating the formset.
"""
kwargs = super().get_formset_kwargs()
kwargs["queryset"] = self.get_queryset()
return kwargs
def get_factory_kwargs(self):
"""
Returns the keyword arguments for calling the formset factory
"""
kwargs = super().get_factory_kwargs()
kwargs.setdefault("fields", self.fields)
kwargs.setdefault("exclude", self.exclude)
if self.get_form_class():
kwargs["form"] = self.get_form_class()
return kwargs
def get_formset(self):
"""
Returns the formset class from the model formset factory
"""
return modelformset_factory(self.model, **self.get_factory_kwargs())
def formset_valid(self, formset):
"""
If the formset is valid, save the associated models.
"""
self.object_list = formset.save()
return super().formset_valid(formset)
class BaseInlineFormSetFactory(BaseFormSetFactory):
"""
Base class for constructing a FormSet from `inlineformset_factory` in a view.
Calling `construct_formset` calls all other methods.
"""
model = None
inline_model = None
exclude = None
fields = None
def get_inline_model(self):
"""
Returns the inline model to use with the inline formset
"""
return self.inline_model
def get_formset_kwargs(self):
"""
Returns the keyword arguments for instantiating the formset.
"""
kwargs = super().get_formset_kwargs()
kwargs["instance"] = self.object
return kwargs
def get_factory_kwargs(self):
"""
Returns the keyword arguments for calling the formset factory
"""
kwargs = super().get_factory_kwargs()
kwargs.setdefault("fields", self.fields)
kwargs.setdefault("exclude", self.exclude)
if self.get_form_class():
kwargs["form"] = self.get_form_class()
return kwargs
def get_formset(self):
"""
Returns the formset class from the inline formset factory
"""
return inlineformset_factory(
self.model, self.get_inline_model(), **self.get_factory_kwargs()
)
class InlineFormSetMixin(BaseInlineFormSetFactory, SingleObjectMixin, FormSetMixin):
"""
A view mixin that provides a way to show and handle a single inline formset
in a request.
"""
def formset_valid(self, formset):
self.object_list = formset.save()
return super().formset_valid(formset)
class ProcessFormSetView(View):
"""
A mixin that processes a formset on POST.
"""
def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates a blank version of the formset.
"""
formset = self.construct_formset()
return self.render_to_response(self.get_context_data(formset=formset))
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a formset instance with the passed
POST variables and then checked for validity.
"""
formset = self.construct_formset()
if formset.is_valid():
return self.formset_valid(formset)
else:
return self.formset_invalid(formset)
# PUT is a valid HTTP verb for creating (with a known URL) or editing an
# object, note that browsers only support POST for now.
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
class BaseFormSetView(FormSetMixin, ProcessFormSetView):
"""
A base view for displaying a formset
"""
class FormSetView(TemplateResponseMixin, BaseFormSetView):
"""
A view for displaying a formset, and rendering a template response
"""
class BaseModelFormSetView(ModelFormSetMixin, ProcessFormSetView):
"""
A base view for displaying a model formset
"""
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
return super().post(request, *args, **kwargs)
class ModelFormSetView(MultipleObjectTemplateResponseMixin, BaseModelFormSetView):
"""
A view for displaying a model formset, and rendering a template response
"""
class BaseInlineFormSetView(InlineFormSetMixin, ProcessFormSetView):
"""
A base view for displaying an inline formset for a queryset belonging to
a parent model
"""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super().post(request, *args, **kwargs)
class InlineFormSetView(SingleObjectTemplateResponseMixin, BaseInlineFormSetView):
"""
A view for displaying an inline formset for a queryset belonging to a parent model
"""
django-extra-views-0.14.0/extra_views/generic.py 0000664 0000000 0000000 00000003473 14060101625 0021642 0 ustar 00root root 0000000 0000000 from django.contrib.contenttypes.forms import generic_inlineformset_factory
from extra_views.formsets import (
BaseInlineFormSetFactory,
BaseInlineFormSetView,
InlineFormSetMixin,
InlineFormSetView,
)
class BaseGenericInlineFormSetFactory(BaseInlineFormSetFactory):
"""
Base class for constructing a GenericInlineFormSet from
`generic_inlineformset_factory` in a view.
"""
def get_formset(self):
"""
Returns the final formset class from generic_inlineformset_factory.
"""
result = generic_inlineformset_factory(
self.inline_model, **self.get_factory_kwargs()
)
return result
class GenericInlineFormSetFactory(BaseGenericInlineFormSetFactory):
"""
Class used to create a `GenericInlineFormSet` from `generic_inlineformset_factory`
as one of multiple `GenericInlineFormSet`s within a single view.
Subclasses `BaseGenericInlineFormSetFactory` and passes in the necessary view
arguments.
"""
def __init__(self, parent_model, request, instance, view_kwargs=None, view=None):
self.inline_model = self.model
self.model = parent_model
self.request = request
self.object = instance
self.kwargs = view_kwargs
self.view = view
class GenericInlineFormSetMixin(BaseGenericInlineFormSetFactory, InlineFormSetMixin):
"""
A mixin that provides a way to show and handle a generic inline formset in a
request.
"""
class BaseGenericInlineFormSetView(GenericInlineFormSetMixin, BaseInlineFormSetView):
"""
A base view for displaying a generic inline formset
"""
class GenericInlineFormSetView(BaseGenericInlineFormSetView, InlineFormSetView):
"""
A view for displaying a generic inline formset for a queryset belonging to a
parent model
"""
django-extra-views-0.14.0/extra_views/models.py 0000664 0000000 0000000 00000000000 14060101625 0021470 0 ustar 00root root 0000000 0000000 django-extra-views-0.14.0/extra_views_tests/ 0000775 0000000 0000000 00000000000 14060101625 0021067 5 ustar 00root root 0000000 0000000 django-extra-views-0.14.0/extra_views_tests/__init__.py 0000664 0000000 0000000 00000000000 14060101625 0023166 0 ustar 00root root 0000000 0000000 django-extra-views-0.14.0/extra_views_tests/forms.py 0000664 0000000 0000000 00000001664 14060101625 0022576 0 ustar 00root root 0000000 0000000 from django import forms
from .models import Item, Order
class OrderForm(forms.ModelForm):
class Meta:
model = Order
fields = ["name"]
def save(self, commit=True):
instance = super().save(commit=commit)
if commit:
instance.action_on_save = True
instance.save()
return instance
class ItemForm(forms.ModelForm):
flag = forms.BooleanField(initial=True)
class Meta:
model = Item
fields = ["name", "sku", "price", "order", "status"]
class AddressForm(forms.Form):
name = forms.CharField(max_length=255, required=True)
line1 = forms.CharField(max_length=255, required=False)
line2 = forms.CharField(max_length=255, required=False)
city = forms.CharField(max_length=255, required=False)
postcode = forms.CharField(max_length=10, required=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
django-extra-views-0.14.0/extra_views_tests/formsets.py 0000664 0000000 0000000 00000001250 14060101625 0023301 0 ustar 00root root 0000000 0000000 from django import forms
from django.forms.formsets import BaseFormSet
from django.forms.models import BaseModelFormSet
COUNTRY_CHOICES = (
("gb", "Great Britain"),
("us", "United States"),
("ca", "Canada"),
("au", "Australia"),
("nz", "New Zealand"),
)
class AddressFormSet(BaseFormSet):
def add_fields(self, form, index):
super().add_fields(form, index)
form.fields["county"] = forms.ChoiceField(choices=COUNTRY_CHOICES, initial="gb")
class BaseArticleFormSet(BaseModelFormSet):
def add_fields(self, form, index):
super().add_fields(form, index)
form.fields["notes"] = forms.CharField(initial="Write notes here")
django-extra-views-0.14.0/extra_views_tests/migrations/ 0000775 0000000 0000000 00000000000 14060101625 0023243 5 ustar 00root root 0000000 0000000 django-extra-views-0.14.0/extra_views_tests/migrations/0001_initial.py 0000664 0000000 0000000 00000011361 14060101625 0025710 0 ustar 00root root 0000000 0000000 # Generated by Django 2.1.13 on 2019-10-08 04:42
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [("contenttypes", "0002_remove_content_type_name")]
operations = [
migrations.CreateModel(
name="Contact",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("email", models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name="Event",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("date", models.DateField()),
],
),
migrations.CreateModel(
name="Item",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("sku", models.CharField(max_length=13)),
(
"price",
models.DecimalField(db_index=True, decimal_places=2, max_digits=12),
),
(
"status",
models.SmallIntegerField(
choices=[
(0, "Placed"),
(1, "Charged"),
(2, "Shipped"),
(3, "Cancelled"),
],
db_index=True,
default=0,
),
),
(
"date_placed",
models.DateField(
blank=True, default=django.utils.timezone.now, null=True
),
),
],
),
migrations.CreateModel(
name="Order",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("customer", models.CharField(blank=True, default="", max_length=255)),
("date_created", models.DateTimeField(auto_now_add=True)),
("date_modified", models.DateTimeField(auto_now=True)),
("action_on_save", models.BooleanField(default=False)),
],
),
migrations.CreateModel(
name="Tag",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("object_id", models.PositiveIntegerField(null=True)),
(
"content_type",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="contenttypes.ContentType",
),
),
],
),
migrations.AddField(
model_name="item",
name="order",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="items",
to="extra_views_tests.Order",
),
),
migrations.AddField(
model_name="contact",
name="order",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="contacts",
to="extra_views_tests.Order",
),
),
]
django-extra-views-0.14.0/extra_views_tests/migrations/__init__.py 0000664 0000000 0000000 00000000000 14060101625 0025342 0 ustar 00root root 0000000 0000000 django-extra-views-0.14.0/extra_views_tests/models.py 0000664 0000000 0000000 00000003773 14060101625 0022736 0 ustar 00root root 0000000 0000000 import datetime
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
try:
from django.utils.timezone import now
except ImportError:
now = datetime.datetime.now
STATUS_CHOICES = ((0, "Placed"), (1, "Charged"), (2, "Shipped"), (3, "Cancelled"))
class Order(models.Model):
name = models.CharField(max_length=255)
customer = models.CharField(max_length=255, default="", blank=True)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
action_on_save = models.BooleanField(default=False)
def get_absolute_url(self):
return "/inlines/%i/" % self.pk
def __str__(self):
return self.name
class Item(models.Model):
name = models.CharField(max_length=255)
sku = models.CharField(max_length=13)
price = models.DecimalField(decimal_places=2, max_digits=12, db_index=True)
order = models.ForeignKey(Order, related_name="items", on_delete=models.CASCADE)
status = models.SmallIntegerField(default=0, choices=STATUS_CHOICES, db_index=True)
date_placed = models.DateField(default=now, null=True, blank=True)
def __str__(self):
return "%s (%s)" % (self.name, self.sku)
class Contact(models.Model):
name = models.CharField(max_length=255)
email = models.CharField(max_length=255)
order = models.ForeignKey(Order, related_name="contacts", on_delete=models.CASCADE)
def __str__(self):
return self.name
class Tag(models.Model):
name = models.CharField(max_length=255)
content_type = models.ForeignKey(ContentType, null=True, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(null=True)
content_object = GenericForeignKey("content_type", "object_id")
def __str__(self):
return self.name
class Event(models.Model):
name = models.CharField(max_length=255)
date = models.DateField()
def __str__(self):
return self.name
django-extra-views-0.14.0/extra_views_tests/settings.py 0000664 0000000 0000000 00000001721 14060101625 0023302 0 ustar 00root root 0000000 0000000 import os
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
BASE_DIR = os.path.dirname(PROJECT_DIR)
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}
}
TEMPLATES = [
{"BACKEND": "django.template.backends.django.DjangoTemplates", "APP_DIRS": True}
]
INSTALLED_APPS = [
"django.contrib.contenttypes",
"django.contrib.auth",
"extra_views",
"extra_views_tests",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "extra_views_tests.urls"
SECRET_KEY = "something not very secret"
django-extra-views-0.14.0/extra_views_tests/templates/ 0000775 0000000 0000000 00000000000 14060101625 0023065 5 ustar 00root root 0000000 0000000 django-extra-views-0.14.0/extra_views_tests/templates/404.html 0000664 0000000 0000000 00000000003 14060101625 0024253 0 ustar 00root root 0000000 0000000 404 django-extra-views-0.14.0/extra_views_tests/templates/extra_views/ 0000775 0000000 0000000 00000000000 14060101625 0025425 5 ustar 00root root 0000000 0000000 django-extra-views-0.14.0/extra_views_tests/templates/extra_views/address_formset.html 0000664 0000000 0000000 00000000337 14060101625 0031502 0 ustar 00root root 0000000 0000000
Address Formset