mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-05-04 13:34:46 +00:00
Merge commit '4cce28b1f58bee3a2e698ef110fd855571704667' as 'wagtail/vendor/django-treebeard'
This commit is contained in:
commit
69cb9f7d33
75 changed files with 11410 additions and 0 deletions
17
wagtail/vendor/django-treebeard/.coveragerc
vendored
Normal file
17
wagtail/vendor/django-treebeard/.coveragerc
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[run]
|
||||
branch = True
|
||||
source = treebeard
|
||||
parallel = True
|
||||
|
||||
[paths]
|
||||
source =
|
||||
./
|
||||
*\workspace\django-treebeard\tox_db\*\tox_django\*\tox_python\*\os\windows/
|
||||
*/jobs/django-treebeard/workspace/TOX_DB/*/TOX_DJANGO/*/TOX_PYTHON/*/os/osx/
|
||||
*/workspace/django-treebeard/TOX_DB/*/TOX_DJANGO/*/TOX_PYTHON/*/os/linux/
|
||||
|
||||
[report]
|
||||
omit =
|
||||
*/tests/*
|
||||
*/numconv.py
|
||||
precision = 2
|
||||
20
wagtail/vendor/django-treebeard/.hgignore
vendored
Normal file
20
wagtail/vendor/django-treebeard/.hgignore
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
syntax: glob
|
||||
|
||||
.DS_Store
|
||||
.buildinfo
|
||||
*.pyc
|
||||
*.orig
|
||||
*.swp
|
||||
.coverage
|
||||
build
|
||||
dist
|
||||
_build
|
||||
MANIFEST
|
||||
.project
|
||||
.pydevproject
|
||||
.settings
|
||||
htmlcov
|
||||
.tox
|
||||
*.xml
|
||||
.coverage.*
|
||||
*.egg-info
|
||||
14
wagtail/vendor/django-treebeard/.hgtags
vendored
Normal file
14
wagtail/vendor/django-treebeard/.hgtags
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
29b76a1f6042e63bf9f234bb48c95dcfbd0afc8d 1.0
|
||||
5e39c474d8ea24993777332f8d7ccfd0da1014ad 1.1
|
||||
859f2a36845426d0ff8914cbc58a8b5c52f07256 1.5
|
||||
859f2a36845426d0ff8914cbc58a8b5c52f07256 1.5
|
||||
630024c53f5fac1f5aae412fcfc8c207e5a9d3da 1.5
|
||||
3fe083f135c7e36c08e76448368355af30125e50 1.51
|
||||
0ea8c876d30783ef3a0e8b6f9565371c0f13e8a5 1.52
|
||||
d73b1298ef049d6ddc5fbfc665f51a3c7b376494 1.6
|
||||
d73b1298ef049d6ddc5fbfc665f51a3c7b376494 1.6
|
||||
b510c7559b915a59f647276affd460e24c85ae9c 1.6
|
||||
0b95d619fc8a264ac93ed6de6b1e34886e7d5d07 1.60
|
||||
1af1b23d695d27f963d6393327f6a3c0bdd7df31 1.61
|
||||
23f4629de5d7df21f03fdf26abed3baea786ca8b 2.0b1
|
||||
87a28ab0b44063a2989d805c56483dacbb637532 2.0b2
|
||||
19
wagtail/vendor/django-treebeard/AUTHORS
vendored
Normal file
19
wagtail/vendor/django-treebeard/AUTHORS
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
Treebeard was created in 2008 by Gustavo Picon.
|
||||
|
||||
Contributions made by:
|
||||
|
||||
* Aureal
|
||||
* Jean-Matthieu Barbier
|
||||
* Jesus del Carpio
|
||||
* chembervint
|
||||
* Matt Hoskins
|
||||
* Rob Hudson
|
||||
* Alexey Kinyov
|
||||
* omad
|
||||
* Oregon Center for Applied Science
|
||||
* Alejandro Peralta
|
||||
* Jaap Roes
|
||||
* Alexei Vlasov
|
||||
* moberley
|
||||
* czare1
|
||||
* Fernando Gutierrez (xbito)
|
||||
150
wagtail/vendor/django-treebeard/CHANGES
vendored
Normal file
150
wagtail/vendor/django-treebeard/CHANGES
vendored
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
|
||||
Release 2.0b2 (December, 2013)
|
||||
------------------------------
|
||||
|
||||
* Dropped support for Python 2.5
|
||||
|
||||
|
||||
Release 2.0b1 (May 29, 2013)
|
||||
----------------------------
|
||||
|
||||
This is a beta release.
|
||||
|
||||
* Added support for Django 1.5 and Python 3.X
|
||||
* Updated docs: the library supports python 2.5+ and Django 1.4+. Dropped
|
||||
support for older versions
|
||||
* Revamped admin interface for MP and NS trees, supporting drag&drop to reorder
|
||||
nodes. Work on this patch was sponsored by the
|
||||
`Oregon Center for Applied Science`_, inspired by `FeinCMS`_ developed by
|
||||
`Jesús del Carpio`_ with tests from `Fernando Gutierrez`_. Thanks ORCAS!
|
||||
* Updated setup.py to use distribute/setuptools instead of distutils
|
||||
* Now using pytest for testing
|
||||
* Small optimization to ns_tree.is_root
|
||||
* Moved treebeard.tests to it's own directory (instead of tests.py)
|
||||
* Added the runtests.py test runner
|
||||
* Added tox support
|
||||
* Fixed drag&drop bug in the admin
|
||||
* Fixed a bug when moving MP_Nodes
|
||||
* Using .pk instead of .id when accessing nodes.
|
||||
* Removed the Benchmark (tbbench) and example (tbexample) apps.
|
||||
* Fixed url parts join issues in the admin.
|
||||
* Fixed: Now installing the static resources
|
||||
* Fixed ManyToMany form field save handling
|
||||
* In the admin, the node is now saved when moving so it can trigger handlers
|
||||
and/or signals.
|
||||
* Improved translation files, including javascript.
|
||||
* Renamed Node.get_database_engine() to Node.get_database_vendor(). As the name
|
||||
implies, it returns the database vendor instead of the engine used. Treebeard
|
||||
will get the value from Django, but you can subclass the method if needed.
|
||||
|
||||
Release 1.61 (Jul 24, 2010)
|
||||
---------------------------
|
||||
|
||||
* Added admin i18n. Included translations: es, ru
|
||||
* Fixed a bug when trying to introspect the database engine used in Django 1.2+
|
||||
while using new style db settings (DATABASES). Added
|
||||
Node.get_database_engine to deal with this.
|
||||
|
||||
Release 1.60 (Apr 18, 2010)
|
||||
---------------------------
|
||||
|
||||
* Added get_annotated_list
|
||||
* Complete revamp of the documentation. It's now divided in sections for easier
|
||||
reading, and the package includes .rst files instead of the html build.
|
||||
* Added raw id fields support in the admin
|
||||
* Fixed setup.py to make it work in 2.4 again
|
||||
* The correct ordering in NS/MP trees is now enforced in the queryset.
|
||||
* Cleaned up code, removed some unnecessary statements.
|
||||
* Tests refactoring, to make it easier to spot the model being tested.
|
||||
* Fixed support of trees using proxied models. It was broken due to a bug in
|
||||
Django.
|
||||
* Fixed a bug in add_child when adding nodes to a non-leaf in sorted MP.
|
||||
* There are now 648 unit tests. Test coverage is 96%
|
||||
* This will be the last version compatible with Django 1.0. There will be a
|
||||
a 1.6.X branch maintained for urgent bug fixes, but the main development will
|
||||
focus on recent Django versions.
|
||||
|
||||
|
||||
Release 1.52 (Dec 18, 2009)
|
||||
---------------------------
|
||||
|
||||
* Really fixed the installation of templates.
|
||||
|
||||
|
||||
Release 1.51 (Dec 16, 2009)
|
||||
---------------------------
|
||||
|
||||
* Forgot to include treebeard/tempates/\*.html in MANIFEST.in
|
||||
|
||||
|
||||
Release 1.5 (Dec 15, 2009)
|
||||
--------------------------
|
||||
|
||||
New features added
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Forms
|
||||
|
||||
- Added MoveNodeForm
|
||||
|
||||
* Django Admin
|
||||
|
||||
- Added TreeAdmin
|
||||
|
||||
* MP_Node
|
||||
|
||||
- Added 2 new checks in MP_Node.find_problems():
|
||||
|
||||
4. a list of ids of nodes with the wrong depth value for
|
||||
their path
|
||||
5. a list of ids nodes that report a wrong number of children
|
||||
|
||||
- Added a new (safer and faster but less comprehensive) MP_Node.fix_tree()
|
||||
approach.
|
||||
|
||||
* Documentation
|
||||
|
||||
- Added warnings in the documentation when subclassing MP_Node or NS_Node
|
||||
and adding a new Meta.
|
||||
|
||||
- HTML documentation is now included in the package.
|
||||
|
||||
- CHANGES file and section in the docs.
|
||||
|
||||
* Other changes:
|
||||
|
||||
- script to build documentation
|
||||
|
||||
- updated numconv.py
|
||||
|
||||
|
||||
Bugs fixed
|
||||
~~~~~~~~~~
|
||||
|
||||
* Added table quoting to all the sql queries that bypass the ORM.
|
||||
Solves bug in postgres when the table isn't created by syncdb.
|
||||
|
||||
* Removing unused method NS_Node._find_next_node
|
||||
|
||||
* Fixed MP_Node.get_tree to include the given parent when given a leaf node
|
||||
|
||||
|
||||
Release 1.1 (Nov 20, 2008)
|
||||
--------------------------
|
||||
|
||||
Bugs fixed
|
||||
~~~~~~~~~~
|
||||
|
||||
* Added exceptions.py
|
||||
|
||||
|
||||
Release 1.0 (Nov 19, 2008)
|
||||
--------------------------
|
||||
|
||||
* First public release.
|
||||
|
||||
|
||||
.. _Oregon Center for Applied Science: http://www.orcasinc.com/
|
||||
.. _FeinCMS: http://www.feincms.org
|
||||
.. _Jesús del Carpio: http://www.isgeek.net
|
||||
.. _Fernando Gutierrez: http://xbito.pe
|
||||
203
wagtail/vendor/django-treebeard/LICENSE
vendored
Normal file
203
wagtail/vendor/django-treebeard/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
3
wagtail/vendor/django-treebeard/MANIFEST.in
vendored
Normal file
3
wagtail/vendor/django-treebeard/MANIFEST.in
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
include CHANGES LICENSE NOTICE README.rst UPDATING MANIFEST.in
|
||||
recursive-include docs Makefile README.rst *.py *.rst
|
||||
recursive-include treebeard *.py *.html *.js *.css *.png
|
||||
0
wagtail/vendor/django-treebeard/NOTICE
vendored
Normal file
0
wagtail/vendor/django-treebeard/NOTICE
vendored
Normal file
29
wagtail/vendor/django-treebeard/README.rst
vendored
Normal file
29
wagtail/vendor/django-treebeard/README.rst
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
|
||||
django-treebeard
|
||||
================
|
||||
|
||||
django-treebeard is a library that implements efficient tree implementations
|
||||
for the Django Web Framework 1.4+, written by Gustavo Picón and licensed under
|
||||
the Apache License 2.0.
|
||||
|
||||
django-treebeard is:
|
||||
|
||||
- **Flexible**: Includes 3 different tree implementations with the same API:
|
||||
|
||||
1. Adjacency List
|
||||
2. Materialized Path
|
||||
3. Nested Sets
|
||||
|
||||
- **Fast**: Optimized non-naive tree operations
|
||||
- **Easy**: Uses Django Model Inheritance with abstract classes to define your own
|
||||
models.
|
||||
- **Clean**: Testable and well tested code base. Code/branch test coverage is above
|
||||
96%. Tests are available in Jenkins:
|
||||
|
||||
- Test suite running on different versions of Python, Django and database
|
||||
engine: https://tabo.pe/jenkins/job/django-treebeard/
|
||||
- Code quality: https://tabo.pe/jenkins/job/django-treebeard-quality/
|
||||
|
||||
You can find the documentation in
|
||||
|
||||
https://tabo.pe/projects/django-treebeard/docs/tip/
|
||||
16
wagtail/vendor/django-treebeard/UPDATING
vendored
Normal file
16
wagtail/vendor/django-treebeard/UPDATING
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
This file documents problems you may encounter when upgrading django-treebeard
|
||||
(potential backward incompatible changes).
|
||||
|
||||
20081117:
|
||||
|
||||
Cleaned __init__.py, if you need Node you'll have to call it from it's
|
||||
original location (treebeard.models.Node instead of treebeard.Node). Also
|
||||
exceptions have been moved to treebeard.exceptions.
|
||||
|
||||
|
||||
|
||||
20100316:
|
||||
|
||||
Queryset ordering in NS/MP trees is now enforced by the library. Previous
|
||||
ordering settings in META no longer work.
|
||||
|
||||
96
wagtail/vendor/django-treebeard/docs/Makefile
vendored
Normal file
96
wagtail/vendor/django-treebeard/docs/Makefile
vendored
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# 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) .
|
||||
|
||||
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@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 " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@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)"
|
||||
@echo " coverage Coverage"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
mkdir -p _static
|
||||
$(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."
|
||||
|
||||
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-treebeard.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-treebeard.qhc"
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||
"run these through (pdf)latex."
|
||||
|
||||
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."
|
||||
|
||||
coverage:
|
||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
||||
@echo "Coverage, " \
|
||||
"results in $(BUILDDIR)/coverage"
|
||||
7
wagtail/vendor/django-treebeard/docs/README
vendored
Normal file
7
wagtail/vendor/django-treebeard/docs/README
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
This is the documentation source for django-treebeard.
|
||||
You can read the documentation in:
|
||||
|
||||
http://docs.tabo.pe/django-treebeard/tip/
|
||||
|
||||
Or read the documentation for this version re reading the .rst files in this
|
||||
directory.
|
||||
10
wagtail/vendor/django-treebeard/docs/_ext/djangodocs.py
vendored
Normal file
10
wagtail/vendor/django-treebeard/docs/_ext/djangodocs.py
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# taken from:
|
||||
# http://reinout.vanrees.org/weblog/2012/12/01/django-intersphinx.html
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_crossref_type(
|
||||
directivename="setting",
|
||||
rolename="setting",
|
||||
indextemplate="pair: %s; setting",
|
||||
)
|
||||
BIN
wagtail/vendor/django-treebeard/docs/_static/treebeard-admin-advanced.png
vendored
Normal file
BIN
wagtail/vendor/django-treebeard/docs/_static/treebeard-admin-advanced.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
BIN
wagtail/vendor/django-treebeard/docs/_static/treebeard-admin-basic.png
vendored
Normal file
BIN
wagtail/vendor/django-treebeard/docs/_static/treebeard-admin-basic.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
52
wagtail/vendor/django-treebeard/docs/admin.rst
vendored
Normal file
52
wagtail/vendor/django-treebeard/docs/admin.rst
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
Admin
|
||||
=====
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
.. module:: treebeard.admin
|
||||
|
||||
.. autoclass:: TreeAdmin
|
||||
:show-inheritance:
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.contrib import admin
|
||||
from treebeard.admin import TreeAdmin
|
||||
from treebeard.forms import movenodeform_factory
|
||||
from myproject.models import MyNode
|
||||
|
||||
class MyAdmin(TreeAdmin):
|
||||
form = movenodeform_factory(MyNode)
|
||||
|
||||
admin.site.register(MyNode, MyAdmin)
|
||||
|
||||
|
||||
.. autofunction:: admin_factory
|
||||
|
||||
|
||||
Interface
|
||||
---------
|
||||
|
||||
The features of the admin interface will depend on the tree type.
|
||||
|
||||
Advanced Interface
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:doc:`Materialized Path <mp_tree>` and :doc:`Nested Sets <ns_tree>` trees have
|
||||
an AJAX interface based on `FeinCMS`_, that includes features like
|
||||
drag&drop and an attractive interface.
|
||||
|
||||
.. image:: _static/treebeard-admin-advanced.png
|
||||
|
||||
Basic Interface
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
:doc:`Adjacency List <al_tree>` trees have a basic admin interface.
|
||||
|
||||
.. image:: _static/treebeard-admin-basic.png
|
||||
|
||||
|
||||
.. _FeinCMS: http://www.feincms.org
|
||||
106
wagtail/vendor/django-treebeard/docs/al_tree.rst
vendored
Normal file
106
wagtail/vendor/django-treebeard/docs/al_tree.rst
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
Adjacency List trees
|
||||
====================
|
||||
|
||||
.. module:: treebeard.al_tree
|
||||
|
||||
This is a simple implementation of the traditional Adjacency List Model for
|
||||
storing trees in relational databases.
|
||||
|
||||
In the adjacency list model, every node will have a
|
||||
":attr:`~AL_Node.parent`" key, that will be NULL for root nodes.
|
||||
|
||||
Since ``django-treebeard`` must return trees ordered in a predictable way,
|
||||
the ordering for models without the :attr:`~AL_Node.node_order_by`
|
||||
attribute will have an extra attribute that will store the relative
|
||||
position of a node between it's siblings: :attr:`~AL_Node.sib_order`.
|
||||
|
||||
The adjacency list model has the advantage of fast writes at the cost of
|
||||
slow reads. If you read more than you write, use
|
||||
:class:`~treebeard.mp_tree.MP_Node` instead.
|
||||
|
||||
.. warning::
|
||||
|
||||
As with all tree implementations, please be aware of the
|
||||
:doc:`caveats`.
|
||||
|
||||
|
||||
.. inheritance-diagram:: AL_Node
|
||||
.. autoclass:: AL_Node
|
||||
:show-inheritance:
|
||||
|
||||
.. warning::
|
||||
|
||||
If you need to define your own
|
||||
:py:class:`~django.db.models.Manager` class,
|
||||
you'll need to subclass
|
||||
:py:class:`~AL_NodeManager`.
|
||||
|
||||
|
||||
.. attribute:: node_order_by
|
||||
|
||||
Attribute: a list of model fields that will be used for node
|
||||
ordering. When enabled, all tree operations will assume this ordering.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node_order_by = ['field1', 'field2', 'field3']
|
||||
|
||||
.. attribute:: parent
|
||||
|
||||
``ForeignKey`` to itself. This attribute **MUST** be defined in the
|
||||
subclass (sadly, this isn't inherited correctly from the ABC in
|
||||
`Django 1.0`). Just copy&paste these lines to your model:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
parent = models.ForeignKey('self',
|
||||
related_name='children_set',
|
||||
null=True,
|
||||
db_index=True)
|
||||
|
||||
.. attribute:: sib_order
|
||||
|
||||
``PositiveIntegerField`` used to store the relative position of a node
|
||||
between it's siblings. This attribute is mandatory *ONLY* if you don't
|
||||
set a :attr:`node_order_by` field. You can define it copy&pasting this
|
||||
line in your model:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
sib_order = models.PositiveIntegerField()
|
||||
|
||||
Examples:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class AL_TestNode(AL_Node):
|
||||
parent = models.ForeignKey('self',
|
||||
related_name='children_set',
|
||||
null=True,
|
||||
db_index=True)
|
||||
sib_order = models.PositiveIntegerField()
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
class AL_TestNodeSorted(AL_Node):
|
||||
parent = models.ForeignKey('self',
|
||||
related_name='children_set',
|
||||
null=True,
|
||||
db_index=True)
|
||||
node_order_by = ['val1', 'val2', 'desc']
|
||||
val1 = models.IntegerField()
|
||||
val2 = models.IntegerField()
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
|
||||
Read the API reference of :class:`treebeard.Node` for info on methods
|
||||
available in this class, or read the following section for methods with
|
||||
particular arguments or exceptions.
|
||||
|
||||
.. automethod:: get_depth
|
||||
|
||||
See: :meth:`treebeard.Node.get_depth`
|
||||
|
||||
.. autoclass:: AL_NodeManager
|
||||
:show-inheritance:
|
||||
405
wagtail/vendor/django-treebeard/docs/api.rst
vendored
Normal file
405
wagtail/vendor/django-treebeard/docs/api.rst
vendored
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
API
|
||||
===
|
||||
|
||||
.. module:: treebeard.models
|
||||
|
||||
.. inheritance-diagram:: Node
|
||||
.. autoclass:: Node
|
||||
:show-inheritance:
|
||||
|
||||
This is the base class that defines the API of all tree models in this
|
||||
library:
|
||||
|
||||
- :class:`treebeard.mp_tree.MP_Node` (materialized path)
|
||||
- :class:`treebeard.ns_tree.NS_Node` (nested sets)
|
||||
- :class:`treebeard.al_tree.AL_Node` (adjacency list)
|
||||
|
||||
.. warning::
|
||||
|
||||
Please be aware of the :doc:`caveats` when using this library.
|
||||
|
||||
.. automethod:: Node.add_root
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
MyNode.add_root(numval=1, strval='abcd')
|
||||
|
||||
.. automethod:: add_child
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.add_child(numval=1, strval='abcd')
|
||||
|
||||
.. automethod:: add_sibling
|
||||
|
||||
Examples:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.add_sibling('sorted-sibling', numval=1, strval='abc')
|
||||
|
||||
.. automethod:: delete
|
||||
|
||||
.. note::
|
||||
|
||||
Call our queryset's delete to handle children removal. Subclasses
|
||||
will handle extra maintenance.
|
||||
|
||||
.. automethod:: get_tree
|
||||
|
||||
.. automethod:: get_depth
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.get_depth()
|
||||
|
||||
.. automethod:: get_ancestors
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.get_ancestors()
|
||||
|
||||
.. automethod:: get_children
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.get_children()
|
||||
|
||||
.. automethod:: get_children_count
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.get_children_count()
|
||||
|
||||
.. automethod:: get_descendants
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.get_descendants()
|
||||
|
||||
.. automethod:: get_descendant_count
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.get_descendant_count()
|
||||
|
||||
.. automethod:: get_first_child
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.get_first_child()
|
||||
|
||||
.. automethod:: get_last_child
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.get_last_child()
|
||||
|
||||
.. automethod:: get_first_sibling
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.get_first_sibling()
|
||||
|
||||
.. automethod:: get_last_sibling
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.get_last_sibling()
|
||||
|
||||
.. automethod:: get_prev_sibling
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.get_prev_sibling()
|
||||
|
||||
.. automethod:: get_next_sibling
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.get_next_sibling()
|
||||
|
||||
.. automethod:: get_parent
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.get_parent()
|
||||
|
||||
.. automethod:: get_root
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.get_root()
|
||||
|
||||
.. automethod:: get_siblings
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.get_siblings()
|
||||
|
||||
.. automethod:: is_child_of
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.is_child_of(node2)
|
||||
|
||||
.. automethod:: is_descendant_of
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.is_descendant_of(node2)
|
||||
|
||||
.. automethod:: is_sibling_of
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.is_sibling_of(node2)
|
||||
|
||||
.. automethod:: is_root
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.is_root()
|
||||
|
||||
.. automethod:: is_leaf
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.is_leaf()
|
||||
|
||||
.. automethod:: move
|
||||
|
||||
.. note:: The node can be moved under another root node.
|
||||
|
||||
Examples:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.move(node2, 'sorted-child')
|
||||
node.move(node2, 'prev-sibling')
|
||||
|
||||
.. automethod:: save
|
||||
|
||||
.. automethod:: get_first_root_node
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
MyNodeModel.get_first_root_node()
|
||||
|
||||
.. automethod:: get_last_root_node
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
MyNodeModel.get_last_root_node()
|
||||
|
||||
.. automethod:: get_root_nodes
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
MyNodeModel.get_root_nodes()
|
||||
|
||||
.. automethod:: load_bulk
|
||||
|
||||
.. note::
|
||||
|
||||
Any internal data that you may have stored in your
|
||||
nodes' data (:attr:`path`, :attr:`depth`) will be
|
||||
ignored.
|
||||
|
||||
.. note::
|
||||
|
||||
If your node model has a ForeignKey this method will try to load
|
||||
the related object before loading the data. If the related object
|
||||
doesn't exist it won't load anything and will raise a DoesNotExist
|
||||
exception. This is done because the dump_data method uses integers
|
||||
to dump related objects.
|
||||
|
||||
.. note::
|
||||
|
||||
If your node model has :attr:`node_order_by` enabled, it will
|
||||
take precedence over the order in the structure.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
data = [{'data':{'desc':'1'}},
|
||||
{'data':{'desc':'2'}, 'children':[
|
||||
{'data':{'desc':'21'}},
|
||||
{'data':{'desc':'22'}},
|
||||
{'data':{'desc':'23'}, 'children':[
|
||||
{'data':{'desc':'231'}},
|
||||
]},
|
||||
{'data':{'desc':'24'}},
|
||||
]},
|
||||
{'data':{'desc':'3'}},
|
||||
{'data':{'desc':'4'}, 'children':[
|
||||
{'data':{'desc':'41'}},
|
||||
]},
|
||||
]
|
||||
# parent = None
|
||||
MyNodeModel.load_data(data, None)
|
||||
|
||||
Will create:
|
||||
|
||||
.. digraph:: load_bulk_digraph
|
||||
|
||||
"1";
|
||||
"2";
|
||||
"2" -> "21";
|
||||
"2" -> "22";
|
||||
"2" -> "23" -> "231";
|
||||
"2" -> "24";
|
||||
"3";
|
||||
"4";
|
||||
"4" -> "41";
|
||||
|
||||
.. automethod:: dump_bulk
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
tree = MyNodeModel.dump_bulk()
|
||||
branch = MyNodeModel.dump_bulk(node_obj)
|
||||
|
||||
.. automethod:: find_problems
|
||||
|
||||
.. automethod:: fix_tree
|
||||
|
||||
.. automethod:: get_descendants_group_count
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# get a list of the root nodes
|
||||
root_nodes = MyModel.get_descendants_group_count()
|
||||
|
||||
for node in root_nodes:
|
||||
print '%s by %s (%d replies)' % (node.comment, node.author,
|
||||
node.descendants_count)
|
||||
|
||||
.. automethod:: get_annotated_list
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
annotated_list = MyModel.get_annotated_list()
|
||||
|
||||
With data:
|
||||
|
||||
.. digraph:: get_annotated_list_digraph
|
||||
|
||||
"a";
|
||||
"a" -> "ab";
|
||||
"ab" -> "aba";
|
||||
"ab" -> "abb";
|
||||
"ab" -> "abc";
|
||||
"a" -> "ac";
|
||||
|
||||
Will return:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[
|
||||
(a, {'open':True, 'close':[], 'level': 0})
|
||||
(ab, {'open':True, 'close':[], 'level': 1})
|
||||
(aba, {'open':True, 'close':[], 'level': 2})
|
||||
(abb, {'open':False, 'close':[], 'level': 2})
|
||||
(abc, {'open':False, 'close':[0,1], 'level': 2})
|
||||
(ac, {'open':False, 'close':[0], 'level': 1})
|
||||
]
|
||||
|
||||
This can be used with a template like:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% for item, info in annotated_list %}
|
||||
{% if info.open %}
|
||||
<ul><li>
|
||||
{% else %}
|
||||
</li><li>
|
||||
{% endif %}
|
||||
|
||||
{{ item }}
|
||||
|
||||
{% for close in info.close %}
|
||||
</li></ul>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
.. note::
|
||||
|
||||
This method was contributed originally by
|
||||
`Alexey Kinyov <rudi@05bit.com>`_, using an idea borrowed from
|
||||
`django-mptt`_.
|
||||
|
||||
.. versionadded:: 1.55
|
||||
|
||||
|
||||
.. automethod:: get_database_vendor
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
MyNodeModel.get_database_vendor("write")
|
||||
|
||||
|
||||
.. versionadded:: 1.61
|
||||
|
||||
|
||||
.. _django-mptt: https://github.com/django-mptt/django-mptt/
|
||||
40
wagtail/vendor/django-treebeard/docs/caveats.rst
vendored
Normal file
40
wagtail/vendor/django-treebeard/docs/caveats.rst
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
Known Caveats
|
||||
=============
|
||||
|
||||
Raw Queries
|
||||
-----------
|
||||
|
||||
``django-treebeard`` uses Django raw SQL queries for
|
||||
some write operations, and raw queries don't update the objects in the
|
||||
ORM since it's being bypassed.
|
||||
|
||||
Because of this, if you have a node in memory and plan to use it after a
|
||||
tree modification (adding/removing/moving nodes), you need to reload it.
|
||||
|
||||
|
||||
Overriding the default manager
|
||||
------------------------------
|
||||
|
||||
One of the most common source of bug reports in ``django-treebeard``
|
||||
is the overriding of the default managers in the subclasses.
|
||||
|
||||
``django-treebeard`` relies on the default manager for correctness
|
||||
and internal maintenance. If you override the default manager,
|
||||
by overriding the ``objects`` member in your subclass, you
|
||||
*WILL* have errors and inconsistencies in your tree.
|
||||
|
||||
To avoid this problem, if you need to override the default
|
||||
manager, you'll *NEED* to subclass the manager from
|
||||
the base manager class for the tree you are using.
|
||||
|
||||
Read the documentation in each tree type for details.
|
||||
|
||||
|
||||
Custom Managers
|
||||
---------------
|
||||
|
||||
Related to the previous caveat, if you need to create custom
|
||||
managers, you *NEED* to subclass the manager from the
|
||||
base manager class for the tree you are using.
|
||||
|
||||
Read the documentation in each tree type for details.
|
||||
4
wagtail/vendor/django-treebeard/docs/changes.rst
vendored
Normal file
4
wagtail/vendor/django-treebeard/docs/changes.rst
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
.. include:: ../CHANGES
|
||||
59
wagtail/vendor/django-treebeard/docs/conf.py
vendored
Normal file
59
wagtail/vendor/django-treebeard/docs/conf.py
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
|
||||
Configuration for the Sphinx documentation generator.
|
||||
|
||||
Reference: http://sphinx.pocoo.org/config.html
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def docs_dir():
|
||||
rd = os.path.dirname(__file__)
|
||||
if rd:
|
||||
return rd
|
||||
return '.'
|
||||
|
||||
|
||||
for directory in ('_ext', '..'):
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(docs_dir(), directory)))
|
||||
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'treebeard.tests.settings'
|
||||
|
||||
extensions = [
|
||||
'djangodocs',
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.graphviz',
|
||||
'sphinx.ext.inheritance_diagram',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.intersphinx',
|
||||
]
|
||||
templates_path = ['_templates']
|
||||
source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
project = 'django-treebeard'
|
||||
copyright = '2008-2013, Gustavo Picon'
|
||||
version = '2.0b2'
|
||||
release = '2.0b2'
|
||||
exclude_trees = ['_build']
|
||||
pygments_style = 'sphinx'
|
||||
html_theme = 'default'
|
||||
html_static_path = ['_static']
|
||||
htmlhelp_basename = 'django-treebearddoc'
|
||||
latex_documents = [(
|
||||
'index',
|
||||
'django-treebeard.tex',
|
||||
'django-treebeard Documentation',
|
||||
'Gustavo Picon',
|
||||
'manual')]
|
||||
intersphinx_mapping = {
|
||||
'python': ('http://docs.python.org/3', None),
|
||||
'django': (
|
||||
'https://docs.djangoproject.com/en/1.6/',
|
||||
'https://docs.djangoproject.com/en/1.6/_objects/'
|
||||
),
|
||||
}
|
||||
12
wagtail/vendor/django-treebeard/docs/exceptions.rst
vendored
Normal file
12
wagtail/vendor/django-treebeard/docs/exceptions.rst
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
Exceptions
|
||||
==========
|
||||
|
||||
.. module:: treebeard.exceptions
|
||||
|
||||
.. autoexception:: InvalidPosition
|
||||
|
||||
.. autoexception:: InvalidMoveToDescendant
|
||||
|
||||
.. autoexception:: PathOverflow
|
||||
|
||||
.. autoexception:: MissingNodeOrderBy
|
||||
29
wagtail/vendor/django-treebeard/docs/forms.rst
vendored
Normal file
29
wagtail/vendor/django-treebeard/docs/forms.rst
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
Forms
|
||||
=====
|
||||
|
||||
.. module:: treebeard.forms
|
||||
|
||||
.. autoclass:: MoveNodeForm
|
||||
:show-inheritance:
|
||||
|
||||
.. autofunction:: movenodeform_factory
|
||||
|
||||
For a full reference of this function, please read
|
||||
:py:func:`~django.forms.models.modelform_factory`
|
||||
|
||||
|
||||
Example, ``MyNode`` is a subclass of :py:class:`treebeard.al_tree.AL_Node`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
MyNodeForm = movenodeform_factory(MyNode)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyNodeForm(MoveNodeForm):
|
||||
class Meta:
|
||||
model = models.MyNode
|
||||
exclude = ('sib_order', 'parent')
|
||||
|
||||
80
wagtail/vendor/django-treebeard/docs/index.rst
vendored
Normal file
80
wagtail/vendor/django-treebeard/docs/index.rst
vendored
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
django-treebeard
|
||||
================
|
||||
|
||||
`django-treebeard <https://tabo.pe/projects/django-treebeard/>`_
|
||||
is a library that implements efficient tree implementations for the
|
||||
`Django Web Framework 1.4+ <http://www.djangoproject.com/>`_, written by
|
||||
`Gustavo Picón <https://tabo.pe>`_ and licensed under the Apache License 2.0.
|
||||
|
||||
``django-treebeard`` is:
|
||||
|
||||
- **Flexible**: Includes 3 different tree implementations with the same API:
|
||||
|
||||
1. :doc:`Adjacency List <al_tree>`
|
||||
2. :doc:`Materialized Path <mp_tree>`
|
||||
3. :doc:`Nested Sets <ns_tree>`
|
||||
|
||||
- **Fast**: Optimized non-naive tree operations
|
||||
- **Easy**: Uses Django's
|
||||
:ref:`model-inheritance` with :ref:`abstract-base-classes`.
|
||||
to define your own models.
|
||||
- **Clean**: Testable and well tested code base. Code/branch test coverage
|
||||
is above 96%. Tests are available in Jenkins:
|
||||
|
||||
- `Tests running on different versions of Python, Django and DB engines`_
|
||||
- `Code Quality`_
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
.. toctree::
|
||||
|
||||
install
|
||||
tutorial
|
||||
caveats
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
|
||||
changes
|
||||
|
||||
Reference
|
||||
---------
|
||||
|
||||
.. toctree::
|
||||
|
||||
api
|
||||
mp_tree
|
||||
ns_tree
|
||||
al_tree
|
||||
exceptions
|
||||
|
||||
Additional features
|
||||
-------------------
|
||||
|
||||
.. toctree::
|
||||
|
||||
admin
|
||||
forms
|
||||
|
||||
Development
|
||||
-----------
|
||||
|
||||
.. toctree::
|
||||
|
||||
tests
|
||||
|
||||
|
||||
|
||||
.. _`Tests running on different versions of Python, Django and DB engines`:
|
||||
https://tabo.pe/jenkins/job/django-treebeard/
|
||||
.. _`Code Quality`: https://tabo.pe/jenkins/job/django-treebeard-quality/
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
89
wagtail/vendor/django-treebeard/docs/install.rst
vendored
Normal file
89
wagtail/vendor/django-treebeard/docs/install.rst
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
``django-treebeard`` needs at least **Python 2.6** to run, and
|
||||
**Django 1.4 or better**.
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
You have several ways to install ``django-treebeard``. If you're not sure,
|
||||
`just use pip <http://guide.python-distribute.org/pip.html>`_
|
||||
|
||||
pip (or easy_install)
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can install the release versions from
|
||||
`django-treebeard's PyPI page`_ using ``pip``:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install django-treebeard
|
||||
|
||||
or if for some reason you can't use ``pip``, you can try ``easy_install``,
|
||||
(at your own risk):
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ easy_install --always-unzip django-treebeard
|
||||
|
||||
|
||||
setup.py
|
||||
~~~~~~~~
|
||||
|
||||
Download a release from the `treebeard download page`_ and unpack it, then
|
||||
run:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ python setup.py install
|
||||
|
||||
|
||||
.deb packages
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Both Debian and Ubuntu include ``django-treebeard`` as a package, so you can
|
||||
just use:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ apt-get install python-django-treebeard
|
||||
|
||||
or:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ aptitude install python-django-treebeard
|
||||
|
||||
Remember that the packages included in linux distributions are usually not the
|
||||
most recent versions.
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
Add ``'treebeard'`` to the
|
||||
:django:setting:`INSTALLED_APPS` section in your django
|
||||
settings file.
|
||||
|
||||
.. note::
|
||||
|
||||
If you are going to use the :class:`~treebeard.admin.TreeAdmin`
|
||||
class, you need to add the path to treebeard's templates in
|
||||
:django:setting:`TEMPLATE_DIRS`.
|
||||
Also you need to enable
|
||||
``django.core.context_processors.request``
|
||||
in the :django:setting:`TEMPLATE_CONTEXT_PROCESSORS`
|
||||
setting in your django settings file.
|
||||
|
||||
|
||||
.. _`django-treebeard's PyPI page`:
|
||||
http://pypi.python.org/pypi/django-treebeard
|
||||
.. _`treebeard download page`:
|
||||
https://tabo.pe/projects/django-treebeard/download/
|
||||
|
||||
262
wagtail/vendor/django-treebeard/docs/mp_tree.rst
vendored
Normal file
262
wagtail/vendor/django-treebeard/docs/mp_tree.rst
vendored
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
Materialized Path trees
|
||||
=======================
|
||||
|
||||
.. module:: treebeard.mp_tree
|
||||
|
||||
This is an efficient implementation of Materialized Path
|
||||
trees for Django 1.4+, as described by `Vadim Tropashko`_ in `SQL Design
|
||||
Patterns`_. Materialized Path is probably the fastest way of working with
|
||||
trees in SQL without the need of extra work in the database, like Oracle's
|
||||
``CONNECT BY`` or sprocs and triggers for nested intervals.
|
||||
|
||||
In a materialized path approach, every node in the tree will have a
|
||||
:attr:`~MP_Node.path` attribute, where the full path from the root
|
||||
to the node will be stored. This has the advantage of needing very simple
|
||||
and fast queries, at the risk of inconsistency because of the
|
||||
denormalization of ``parent``/``child`` foreign keys. This can be prevented
|
||||
with transactions.
|
||||
|
||||
``django-treebeard`` uses a particular approach: every step in the path has
|
||||
a fixed width and has no separators. This makes queries predictable and
|
||||
faster at the cost of using more characters to store a step. To address
|
||||
this problem, every step number is encoded.
|
||||
|
||||
Also, two extra fields are stored in every node:
|
||||
:attr:`~MP_Node.depth` and :attr:`~MP_Node.numchild`.
|
||||
This makes the read operations faster, at the cost of a little more
|
||||
maintenance on tree updates/inserts/deletes. Don't worry, even with these
|
||||
extra steps, materialized path is more efficient than other approaches.
|
||||
|
||||
.. warning::
|
||||
|
||||
As with all tree implementations, please be aware of the
|
||||
:doc:`caveats`.
|
||||
|
||||
.. note::
|
||||
|
||||
The materialized path approach makes heavy use of ``LIKE`` in your
|
||||
database, with clauses like ``WHERE path LIKE '002003%'``. If you think
|
||||
that ``LIKE`` is too slow, you're right, but in this case the
|
||||
:attr:`~MP_Node.path` field is indexed in the database, and all
|
||||
``LIKE`` clauses that don't **start** with a ``%`` character will use
|
||||
the index. This is what makes the materialized path approach so fast.
|
||||
|
||||
.. inheritance-diagram:: MP_Node
|
||||
.. autoclass:: MP_Node
|
||||
:show-inheritance:
|
||||
|
||||
.. warning::
|
||||
|
||||
Do not change the values of :attr:`path`, :attr:`depth` or
|
||||
:attr:`numchild` directly: use one of the included methods instead.
|
||||
Consider these values *read-only*.
|
||||
|
||||
.. warning::
|
||||
|
||||
Do not change the values of the :attr:`steplen`, :attr:`alphabet` or
|
||||
:attr:`node_order_by` after saving your first object. Doing so will
|
||||
corrupt the tree.
|
||||
|
||||
.. warning::
|
||||
|
||||
If you need to define your own
|
||||
:py:class:`~django.db.models.Manager` class,
|
||||
you'll need to subclass
|
||||
:py:class:`~MP_NodeManager`.
|
||||
|
||||
Also, if in your manager you need to change the default
|
||||
queryset handler, you'll need to subclass
|
||||
:py:class:`~MP_NodeQuerySet`.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class SortedNode(MP_Node):
|
||||
node_order_by = ['numval', 'strval']
|
||||
|
||||
numval = models.IntegerField()
|
||||
strval = models.CharField(max_length=255)
|
||||
|
||||
Read the API reference of :class:`treebeard.Node` for info on methods
|
||||
available in this class, or read the following section for methods with
|
||||
particular arguments or exceptions.
|
||||
|
||||
.. attribute:: steplen
|
||||
|
||||
Attribute that defines the length of each step in the :attr:`path` of
|
||||
a node. The default value of *4* allows a maximum of
|
||||
*1679615* children per node. Increase this value if you plan to store
|
||||
large trees (a ``steplen`` of *5* allows more than *60M* children per
|
||||
node). Note that increasing this value, while increasing the number of
|
||||
children per node, will decrease the max :attr:`depth` of the tree (by
|
||||
default: *63*). To increase the max :attr:`depth`, increase the
|
||||
max_length attribute of the :attr:`path` field in your model.
|
||||
|
||||
.. attribute:: alphabet
|
||||
|
||||
Attribute: the alphabet that will be used in base conversions
|
||||
when encoding the path steps into strings. The default value,
|
||||
``0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ`` is the most optimal possible
|
||||
value that is portable between the supported databases (which means:
|
||||
their default collation will order the :attr:`path` field correctly).
|
||||
|
||||
.. note::
|
||||
|
||||
In case you know what you are doing, there is a test that is
|
||||
disabled by default that can tell you the optimal default alphabet
|
||||
in your enviroment. To run the test you must enable the
|
||||
:envvar:`TREEBEARD_TEST_ALPHABET` enviroment variable:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ TREEBEARD_TEST_ALPHABET=1 python manage.py test treebeard.TestTreeAlphabet
|
||||
|
||||
On my Mountain Lion system, these are the optimal values for the
|
||||
three supported databases in their *default* configuration:
|
||||
|
||||
================ ================
|
||||
Database Optimal Alphabet
|
||||
================ ================
|
||||
MySQL 5.6.10 0-9A-Z
|
||||
PostgreSQL 9.2.4 0-9A-Z
|
||||
Sqlite3 0-9A-Z
|
||||
================ ================
|
||||
|
||||
.. attribute:: node_order_by
|
||||
|
||||
Attribute: a list of model fields that will be used for node
|
||||
ordering. When enabled, all tree operations will assume this ordering.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node_order_by = ['field1', 'field2', 'field3']
|
||||
|
||||
.. attribute:: path
|
||||
|
||||
``CharField``, stores the full materialized path for each node. The
|
||||
default value of it's max_length, *255*, is the max efficient and
|
||||
portable value for a ``varchar``. Increase it to allow deeper trees (max
|
||||
depth by default: *63*)
|
||||
|
||||
.. note::
|
||||
|
||||
`django-treebeard` uses Django's abstract model inheritance, so:
|
||||
|
||||
1. To change the max_length value of the path in your model, you
|
||||
can't just define it since you'd get a django exception, you have
|
||||
to modify the already defined attribute:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyNodeModel(MP_Node):
|
||||
pass
|
||||
|
||||
MyNodeModel._meta.get_field('path').max_length = 1024
|
||||
|
||||
2. You can't rely on Django's `auto_now` properties in date fields
|
||||
for sorting, you'll have to manually set the value before creating
|
||||
a node:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class TestNodeSortedAutoNow(MP_Node):
|
||||
desc = models.CharField(max_length=255)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
node_order_by = ['created']
|
||||
|
||||
TestNodeSortedAutoNow.add_root(desc='foo',
|
||||
created=datetime.datetime.now())
|
||||
|
||||
.. note::
|
||||
|
||||
For performance, and if your database allows it, you can safely
|
||||
define the path column as ASCII (not utf-8/unicode/iso8859-1/etc) to
|
||||
keep the index smaller (and faster). Also note that some databases
|
||||
(mysql) have a small index size limit. InnoDB for instance has a
|
||||
limit of 765 bytes per index, so that would be the limit if your path
|
||||
is ASCII encoded. If your path column in InnoDB is using unicode,
|
||||
the index limit will be 255 characters since in MySQL's indexes,
|
||||
unicode means 3 bytes per character.
|
||||
|
||||
.. note::
|
||||
|
||||
``django-treebeard`` uses `numconv`_ for path encoding.
|
||||
|
||||
|
||||
.. attribute:: depth
|
||||
|
||||
``PositiveIntegerField``, depth of a node in the tree. A root node
|
||||
has a depth of *1*.
|
||||
|
||||
.. attribute:: numchild
|
||||
|
||||
``PositiveIntegerField``, the number of children of the node.
|
||||
|
||||
.. automethod:: add_root
|
||||
|
||||
See: :meth:`treebeard.Node.add_root`
|
||||
|
||||
.. automethod:: add_child
|
||||
|
||||
See: :meth:`treebeard.Node.add_child`
|
||||
|
||||
.. automethod:: add_sibling
|
||||
|
||||
See: :meth:`treebeard.Node.add_sibling`
|
||||
|
||||
.. automethod:: move
|
||||
|
||||
See: :meth:`treebeard.Node.move`
|
||||
|
||||
.. automethod:: get_tree
|
||||
|
||||
See: :meth:`treebeard.Node.get_tree`
|
||||
|
||||
.. note::
|
||||
|
||||
This metod returns a queryset.
|
||||
|
||||
.. automethod:: find_problems
|
||||
|
||||
.. note::
|
||||
|
||||
A node won't appear in more than one list, even when it exhibits
|
||||
more than one problem. This method stops checking a node when it
|
||||
finds a problem and continues to the next node.
|
||||
|
||||
.. note::
|
||||
|
||||
Problems 1, 2 and 3 can't be solved automatically.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
MyNodeModel.find_problems()
|
||||
|
||||
.. automethod:: fix_tree
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
MyNodeModel.fix_tree()
|
||||
|
||||
|
||||
|
||||
.. autoclass:: MP_NodeManager
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: MP_NodeQuerySet
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
|
||||
.. _`Vadim Tropashko`: http://vadimtropashko.wordpress.com/
|
||||
.. _`Sql Design Patterns`:
|
||||
http://www.rampant-books.com/book_2006_1_sql_coding_styles.htm
|
||||
.. _numconv: https://tabo.pe/projects/numconv/
|
||||
81
wagtail/vendor/django-treebeard/docs/ns_tree.rst
vendored
Normal file
81
wagtail/vendor/django-treebeard/docs/ns_tree.rst
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
Nested Sets trees
|
||||
=================
|
||||
|
||||
.. module:: treebeard.ns_tree
|
||||
|
||||
An implementation of Nested Sets trees for Django 1.4+, as described by
|
||||
`Joe Celko`_ in `Trees and Hierarchies in SQL for Smarties`_.
|
||||
|
||||
Nested sets have very efficient reads at the cost of high maintenance on
|
||||
write/delete operations.
|
||||
|
||||
.. warning::
|
||||
|
||||
As with all tree implementations, please be aware of the
|
||||
:doc:`caveats`.
|
||||
|
||||
|
||||
.. inheritance-diagram:: NS_Node
|
||||
.. autoclass:: NS_Node
|
||||
:show-inheritance:
|
||||
|
||||
.. warning::
|
||||
|
||||
If you need to define your own
|
||||
:py:class:`~django.db.models.Manager` class,
|
||||
you'll need to subclass
|
||||
:py:class:`~NS_NodeManager`.
|
||||
|
||||
Also, if in your manager you need to change the default
|
||||
queryset handler, you'll need to subclass
|
||||
:py:class:`~NS_NodeQuerySet`.
|
||||
|
||||
|
||||
.. attribute:: node_order_by
|
||||
|
||||
Attribute: a list of model fields that will be used for node
|
||||
ordering. When enabled, all tree operations will assume this ordering.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node_order_by = ['field1', 'field2', 'field3']
|
||||
|
||||
.. attribute:: depth
|
||||
|
||||
``PositiveIntegerField``, depth of a node in the tree. A root node
|
||||
has a depth of *1*.
|
||||
|
||||
.. attribute:: lft
|
||||
|
||||
``PositiveIntegerField``
|
||||
|
||||
.. attribute:: rgt
|
||||
|
||||
``PositiveIntegerField``
|
||||
|
||||
.. attribute:: tree_id
|
||||
|
||||
``PositiveIntegerField``
|
||||
|
||||
.. automethod:: get_tree
|
||||
|
||||
See: :meth:`treebeard.Node.get_tree`
|
||||
|
||||
.. note::
|
||||
|
||||
This metod returns a queryset.
|
||||
|
||||
|
||||
.. autoclass:: NS_NodeManager
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: NS_NodeQuerySet
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
|
||||
.. _`Joe Celko`: http://en.wikipedia.org/wiki/Joe_Celko
|
||||
.. _`Trees and Hierarchies in SQL for Smarties`:
|
||||
http://www.elsevier.com/wps/product/cws_home/702605
|
||||
80
wagtail/vendor/django-treebeard/docs/tests.rst
vendored
Normal file
80
wagtail/vendor/django-treebeard/docs/tests.rst
vendored
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
Running the Test Suite
|
||||
======================
|
||||
|
||||
``django-treebeard`` includes a comprehensive test suite. It is highly
|
||||
recommended that you run and update the test suite when you send patches.
|
||||
|
||||
py.test
|
||||
-------
|
||||
|
||||
You will need `pytest`_ to run the test suite.
|
||||
|
||||
To run the test suite:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ py.test
|
||||
|
||||
You can use all the features and plugins of pytest this way.
|
||||
|
||||
By default the test suite will run using a sqlite3 database in RAM, but you can
|
||||
change this setting environment variables:
|
||||
|
||||
.. option:: DATABASE_ENGINE
|
||||
.. option:: DATABASE_NAME
|
||||
.. option:: DATABASE_USER
|
||||
.. option:: DATABASE_PASSWORD
|
||||
.. option:: DATABASE_HOST
|
||||
.. option:: DATABASE_PORT
|
||||
|
||||
Sets the database settings to be used by the test suite. Useful if you
|
||||
want to test the same database engine/version you use in production.
|
||||
|
||||
|
||||
tox
|
||||
---
|
||||
|
||||
``django-treebeard`` uses `tox`_ to run the test suite in all the supported
|
||||
environments:
|
||||
|
||||
- py26-dj14-sqlite
|
||||
- py26-dj14-mysql
|
||||
- py26-dj14-pgsql
|
||||
- py26-dj15-sqlite
|
||||
- py26-dj15-mysql
|
||||
- py26-dj15-pgsql
|
||||
- py26-dj16-sqlite
|
||||
- py26-dj16-mysql
|
||||
- py26-dj16-pgsql
|
||||
- py27-dj14-sqlite
|
||||
- py27-dj14-mysql
|
||||
- py27-dj14-pgsql
|
||||
- py27-dj15-sqlite
|
||||
- py27-dj15-mysql
|
||||
- py27-dj15-pgsql
|
||||
- py32-dj15-sqlite
|
||||
- py32-dj15-pgsql
|
||||
- py33-dj15-sqlite
|
||||
- py33-dj15-pgsq
|
||||
- py27-dj16-sqlite
|
||||
- py27-dj16-mysql
|
||||
- py27-dj16-pgsql
|
||||
- py32-dj16-sqlite
|
||||
- py32-dj16-pgsql
|
||||
- py33-dj16-sqlite
|
||||
- py33-dj16-pgsql
|
||||
|
||||
|
||||
This means that the test suite will run 26 times to test every
|
||||
environment supported by ``django-treebeard``. This takes a long time.
|
||||
If you want to test only one or a few environments, please use the `-e`
|
||||
option in `tox`_, like:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ tox -e py33-dj16-pgsql
|
||||
|
||||
|
||||
.. _pytest: http://pytest.org/
|
||||
.. _coverage: http://nedbatchelder.com/code/coverage/
|
||||
.. _tox: http://codespeak.net/tox/
|
||||
106
wagtail/vendor/django-treebeard/docs/tutorial.rst
vendored
Normal file
106
wagtail/vendor/django-treebeard/docs/tutorial.rst
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
Tutorial
|
||||
========
|
||||
|
||||
Create a basic model for your tree. In this example we'll use a Materialized
|
||||
Path tree:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.db import models
|
||||
from treebeard.mp_tree import MP_Node
|
||||
|
||||
class Category(MP_Node):
|
||||
name = models.CharField(max_length=30)
|
||||
|
||||
node_order_by = ['name']
|
||||
|
||||
def __unicode__(self):
|
||||
return 'Category: %s' % self.name
|
||||
|
||||
|
||||
|
||||
Run syncdb:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ python manage.py syncdb
|
||||
|
||||
|
||||
Let's create some nodes:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from treebeard_tutorial.models import Category
|
||||
>>> get = lambda node_id: Category.objects.get(pk=node_id)
|
||||
>>> root = Category.add_root(name='Computer Hardware')
|
||||
>>> node = get(root.pk).add_child(name='Memory')
|
||||
>>> get(node.pk).add_sibling(name='Hard Drives')
|
||||
<Category: Category: Hard Drives>
|
||||
>>> get(node.pk).add_sibling(name='SSD')
|
||||
<Category: Category: SSD>
|
||||
>>> get(node.pk).add_child(name='Desktop Memory')
|
||||
<Category: Category: Desktop Memory>
|
||||
>>> get(node.pk).add_child(name='Laptop Memory')
|
||||
<Category: Category: Laptop Memory>
|
||||
>>> get(node.pk).add_child(name='Server Memory')
|
||||
<Category: Category: Server Memory>
|
||||
|
||||
.. note::
|
||||
|
||||
Why retrieving every node again after the first operation? Because
|
||||
``django-treebeard`` uses raw queries for most write operations,
|
||||
and raw queries don't update the django objects of the db entries they
|
||||
modify. See: :doc:`caveats`.
|
||||
|
||||
We just created this tree:
|
||||
|
||||
|
||||
.. digraph:: introduction_digraph
|
||||
|
||||
"Computer Hardware";
|
||||
"Computer Hardware" -> "Hard Drives";
|
||||
"Computer Hardware" -> "Memory";
|
||||
"Memory" -> "Desktop Memory";
|
||||
"Memory" -> "Laptop Memory";
|
||||
"Memory" -> "Server Memory";
|
||||
"Computer Hardware" -> "SSD";
|
||||
|
||||
|
||||
You can see the tree structure with code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> Category.dump_bulk()
|
||||
[{'id': 1, 'data': {'name': u'Computer Hardware'},
|
||||
'children': [
|
||||
{'id': 3, 'data': {'name': u'Hard Drives'}},
|
||||
{'id': 2, 'data': {'name': u'Memory'},
|
||||
'children': [
|
||||
{'id': 5, 'data': {'name': u'Desktop Memory'}},
|
||||
{'id': 6, 'data': {'name': u'Laptop Memory'}},
|
||||
{'id': 7, 'data': {'name': u'Server Memory'}}]},
|
||||
{'id': 4, 'data': {'name': u'SSD'}}]}]
|
||||
>>> Category.get_annotated_list()
|
||||
[(<Category: Category: Computer Hardware>,
|
||||
{'close': [], 'level': 0, 'open': True}),
|
||||
(<Category: Category: Hard Drives>,
|
||||
{'close': [], 'level': 1, 'open': True}),
|
||||
(<Category: Category: Memory>,
|
||||
{'close': [], 'level': 1, 'open': False}),
|
||||
(<Category: Category: Desktop Memory>,
|
||||
{'close': [], 'level': 2, 'open': True}),
|
||||
(<Category: Category: Laptop Memory>,
|
||||
{'close': [], 'level': 2, 'open': False}),
|
||||
(<Category: Category: Server Memory>,
|
||||
{'close': [0], 'level': 2, 'open': False}),
|
||||
(<Category: Category: SSD>,
|
||||
{'close': [0, 1], 'level': 1, 'open': False})]
|
||||
|
||||
|
||||
|
||||
Read the :class:`treebeard.models.Node` API reference for detailed info.
|
||||
|
||||
.. _`treebeard mercurial repository`:
|
||||
http://code.tabo.pe/django-treebeard
|
||||
.. _`latest treebeard version from PyPi`:
|
||||
http://pypi.python.org/pypi/django-treebeard/
|
||||
59
wagtail/vendor/django-treebeard/setup.py
vendored
Normal file
59
wagtail/vendor/django-treebeard/setup.py
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
from setuptools import setup
|
||||
from setuptools.command.test import test
|
||||
|
||||
|
||||
def root_dir():
|
||||
rd = os.path.dirname(__file__)
|
||||
if rd:
|
||||
return rd
|
||||
return '.'
|
||||
|
||||
|
||||
class pytest_test(test):
|
||||
def finalize_options(self):
|
||||
test.finalize_options(self)
|
||||
self.test_args = []
|
||||
self.test_suite = True
|
||||
|
||||
def run_tests(self):
|
||||
import pytest
|
||||
pytest.main([])
|
||||
|
||||
|
||||
setup_args = dict(
|
||||
name='django-treebeard',
|
||||
version='2.0b2',
|
||||
url='https://tabo.pe/projects/django-treebeard/',
|
||||
author='Gustavo Picon',
|
||||
author_email='tabo@tabo.pe',
|
||||
license='Apache License 2.0',
|
||||
packages=['treebeard', 'treebeard.templatetags', 'treebeard.tests'],
|
||||
package_dir={'treebeard': 'treebeard'},
|
||||
package_data={
|
||||
'treebeard': ['templates/admin/*.html', 'static/treebeard/*']},
|
||||
description='Efficient tree implementations for Django 1.4+',
|
||||
long_description=open(root_dir() + '/README.rst').read(),
|
||||
cmdclass={'test': pytest_test},
|
||||
install_requires=['Django>=1.4'],
|
||||
tests_require=['pytest'],
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Environment :: Web Environment',
|
||||
'Framework :: Django',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Operating System :: OS Independent',
|
||||
'Topic :: Software Development :: Libraries',
|
||||
'Topic :: Utilities'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setup(**setup_args)
|
||||
302
wagtail/vendor/django-treebeard/tox.ini
vendored
Normal file
302
wagtail/vendor/django-treebeard/tox.ini
vendored
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
#
|
||||
# tox.ini for django-treebeard
|
||||
#
|
||||
# Read docs/tests for help on how to use tox to run the test suite.
|
||||
#
|
||||
|
||||
[tox]
|
||||
envlist =
|
||||
py26-dj14-sqlite,
|
||||
py26-dj14-mysql,
|
||||
py26-dj14-pgsql,
|
||||
py26-dj15-sqlite,
|
||||
py26-dj15-mysql,
|
||||
py26-dj15-pgsql,
|
||||
py26-dj16-sqlite,
|
||||
py26-dj16-mysql,
|
||||
py26-dj16-pgsql,
|
||||
py27-dj14-sqlite,
|
||||
py27-dj14-mysql,
|
||||
py27-dj14-pgsql,
|
||||
py27-dj15-sqlite,
|
||||
py27-dj15-mysql,
|
||||
py27-dj15-pgsql,
|
||||
py32-dj15-sqlite,
|
||||
py32-dj15-pgsql,
|
||||
py33-dj15-sqlite,
|
||||
py33-dj15-pgsql
|
||||
py27-dj16-sqlite,
|
||||
py27-dj16-mysql,
|
||||
py27-dj16-pgsql,
|
||||
py32-dj16-sqlite,
|
||||
py32-dj16-pgsql,
|
||||
py33-dj16-sqlite,
|
||||
py33-dj16-pgsql
|
||||
|
||||
[testenv]
|
||||
commands =
|
||||
{envpython} treebeard/tests/jenkins/toxhelper.py \
|
||||
--tb=long --fulltrace -l --junitxml junit-{envname}.xml \
|
||||
{posargs}
|
||||
|
||||
[testenv:docs]
|
||||
basepython=python
|
||||
changedir = docs
|
||||
deps =
|
||||
Sphinx
|
||||
Django
|
||||
commands =
|
||||
sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
|
||||
|
||||
[testenv:py26-dj14-sqlite]
|
||||
basepython=python2.6
|
||||
deps =
|
||||
Django>=1.4,<1.5
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=sqlite3
|
||||
|
||||
[testenv:py26-dj14-mysql]
|
||||
basepython=python2.6
|
||||
deps =
|
||||
Django>=1.4,<1.5
|
||||
MySQL-python
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=mysql
|
||||
|
||||
[testenv:py26-dj14-pgsql]
|
||||
basepython=python2.6
|
||||
deps =
|
||||
Django>=1.4,<1.5
|
||||
psycopg2>2.4.1
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=postgresql_psycopg2
|
||||
|
||||
[testenv:py26-dj15-sqlite]
|
||||
basepython=python2.6
|
||||
deps =
|
||||
Django>=1.5,<1.6
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=sqlite3
|
||||
|
||||
[testenv:py26-dj15-mysql]
|
||||
basepython=python2.6
|
||||
deps =
|
||||
Django>=1.5,<1.6
|
||||
MySQL-python
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=mysql
|
||||
|
||||
[testenv:py26-dj15-pgsql]
|
||||
basepython=python2.6
|
||||
deps =
|
||||
Django>=1.5,<1.6
|
||||
psycopg2>2.4.1
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=postgresql_psycopg2
|
||||
|
||||
[testenv:py26-dj16-sqlite]
|
||||
basepython=python2.6
|
||||
deps =
|
||||
Django>=1.6,<1.7
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=sqlite3
|
||||
|
||||
[testenv:py26-dj16-mysql]
|
||||
basepython=python2.6
|
||||
deps =
|
||||
Django>=1.6,<1.7
|
||||
MySQL-python
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=mysql
|
||||
|
||||
[testenv:py26-dj16-pgsql]
|
||||
basepython=python2.6
|
||||
deps =
|
||||
Django>=1.6,<1.7
|
||||
psycopg2>2.4.1
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=postgresql_psycopg2
|
||||
|
||||
[testenv:py27-dj14-sqlite]
|
||||
basepython=python2.7
|
||||
deps =
|
||||
Django>=1.4,<1.5
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=sqlite3
|
||||
|
||||
[testenv:py27-dj14-mysql]
|
||||
basepython=python2.7
|
||||
deps =
|
||||
Django>=1.4,<1.5
|
||||
MySQL-python
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=mysql
|
||||
|
||||
[testenv:py27-dj14-pgsql]
|
||||
basepython=python2.7
|
||||
deps =
|
||||
Django>=1.4,<1.5
|
||||
psycopg2>2.4.1
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=postgresql_psycopg2
|
||||
|
||||
[testenv:py27-dj15-sqlite]
|
||||
basepython=python2.7
|
||||
deps =
|
||||
Django>=1.5,<1.6
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=sqlite3
|
||||
|
||||
[testenv:py27-dj15-mysql]
|
||||
basepython=python2.7
|
||||
deps =
|
||||
Django>=1.5,<1.6
|
||||
MySQL-python
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=mysql
|
||||
|
||||
[testenv:py27-dj15-pgsql]
|
||||
basepython=python2.7
|
||||
deps =
|
||||
Django>=1.5,<1.6
|
||||
psycopg2>2.4.1
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=postgresql_psycopg2
|
||||
|
||||
[testenv:py32-dj15-sqlite]
|
||||
basepython=python3.2
|
||||
deps =
|
||||
Django>=1.5,<1.6
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=sqlite3
|
||||
|
||||
[testenv:py32-dj15-pgsql]
|
||||
basepython=python3.2
|
||||
deps =
|
||||
Django>=1.5,<1.6
|
||||
psycopg2
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=postgresql_psycopg2
|
||||
|
||||
[testenv:py33-dj15-sqlite]
|
||||
basepython=python3.3
|
||||
deps =
|
||||
Django>=1.5,<1.6
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=sqlite3
|
||||
|
||||
[testenv:py33-dj15-pgsql]
|
||||
basepython=python3.3
|
||||
deps =
|
||||
Django>=1.5,<1.6
|
||||
psycopg2
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=postgresql_psycopg2
|
||||
|
||||
|
||||
[testenv:py27-dj16-sqlite]
|
||||
basepython=python2.7
|
||||
deps =
|
||||
Django>=1.6,<1.7
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=sqlite3
|
||||
|
||||
[testenv:py27-dj16-mysql]
|
||||
basepython=python2.7
|
||||
deps =
|
||||
Django>=1.6,<1.7
|
||||
MySQL-python
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=mysql
|
||||
|
||||
[testenv:py27-dj16-pgsql]
|
||||
basepython=python2.7
|
||||
deps =
|
||||
Django>=1.6,<1.7
|
||||
psycopg2>2.4.1
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=postgresql_psycopg2
|
||||
|
||||
[testenv:py32-dj16-sqlite]
|
||||
basepython=python3.2
|
||||
deps =
|
||||
Django>=1.6,<1.7
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=sqlite3
|
||||
|
||||
[testenv:py32-dj16-pgsql]
|
||||
basepython=python3.2
|
||||
deps =
|
||||
Django>=1.6,<1.7
|
||||
psycopg2
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=postgresql_psycopg2
|
||||
|
||||
[testenv:py33-dj16-sqlite]
|
||||
basepython=python3.3
|
||||
deps =
|
||||
Django>=1.6,<1.7
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=sqlite3
|
||||
|
||||
[testenv:py33-dj16-pgsql]
|
||||
basepython=python3.3
|
||||
deps =
|
||||
Django>=1.6,<1.7
|
||||
psycopg2
|
||||
coverage
|
||||
pytest
|
||||
setenv =
|
||||
DATABASE_ENGINE=postgresql_psycopg2
|
||||
|
||||
|
||||
1
wagtail/vendor/django-treebeard/treebeard/__init__.py
vendored
Normal file
1
wagtail/vendor/django-treebeard/treebeard/__init__.py
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
__version__ = '2.0b2'
|
||||
111
wagtail/vendor/django-treebeard/treebeard/admin.py
vendored
Normal file
111
wagtail/vendor/django-treebeard/treebeard/admin.py
vendored
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
"""Django admin support for treebeard"""
|
||||
|
||||
import sys
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from django.contrib import admin, messages
|
||||
from django.http import HttpResponse, HttpResponseBadRequest
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
if sys.version_info >= (3, 0):
|
||||
from django.utils.encoding import force_str
|
||||
else:
|
||||
from django.utils.encoding import force_unicode as force_str
|
||||
|
||||
from treebeard.exceptions import (InvalidPosition, MissingNodeOrderBy,
|
||||
InvalidMoveToDescendant, PathOverflow)
|
||||
from treebeard.al_tree import AL_Node
|
||||
|
||||
|
||||
class TreeAdmin(admin.ModelAdmin):
|
||||
"""Django Admin class for treebeard."""
|
||||
|
||||
change_list_template = 'admin/tree_change_list.html'
|
||||
|
||||
def queryset(self, request):
|
||||
if issubclass(self.model, AL_Node):
|
||||
# AL Trees return a list instead of a QuerySet for .get_tree()
|
||||
# So we're returning the regular .queryset cause we will use
|
||||
# the old admin
|
||||
return super(TreeAdmin, self).queryset(request)
|
||||
else:
|
||||
return self.model.get_tree()
|
||||
|
||||
def changelist_view(self, request, extra_context=None):
|
||||
if issubclass(self.model, AL_Node):
|
||||
# For AL trees, use the old admin display
|
||||
self.change_list_template = 'admin/tree_list.html'
|
||||
return super(TreeAdmin, self).changelist_view(request, extra_context)
|
||||
|
||||
def get_urls(self):
|
||||
"""
|
||||
Adds a url to move nodes to this admin
|
||||
"""
|
||||
urls = super(TreeAdmin, self).get_urls()
|
||||
new_urls = patterns(
|
||||
'',
|
||||
url('^move/$', self.admin_site.admin_view(self.move_node), ),
|
||||
url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog',
|
||||
{'packages': ('treebeard',)}),
|
||||
)
|
||||
return new_urls + urls
|
||||
|
||||
def get_node(self, node_id):
|
||||
return self.model.objects.get(pk=node_id)
|
||||
|
||||
def try_to_move_node(self, as_child, node, pos, request, target):
|
||||
try:
|
||||
node.move(target, pos=pos)
|
||||
# Call the save method on the (reloaded) node in order to trigger
|
||||
# possible signal handlers etc.
|
||||
node = self.get_node(node.pk)
|
||||
node.save()
|
||||
except (MissingNodeOrderBy, PathOverflow, InvalidMoveToDescendant,
|
||||
InvalidPosition):
|
||||
e = sys.exc_info()[1]
|
||||
# An error was raised while trying to move the node, then set an
|
||||
# error message and return 400, this will cause a reload on the
|
||||
# client to show the message
|
||||
messages.error(request,
|
||||
_('Exception raised while moving node: %s') % _(
|
||||
force_str(e)))
|
||||
return HttpResponseBadRequest('Exception raised during move')
|
||||
if as_child:
|
||||
msg = _('Moved node "%(node)s" as child of "%(other)s"')
|
||||
else:
|
||||
msg = _('Moved node "%(node)s" as sibling of "%(other)s"')
|
||||
messages.info(request, msg % {'node': node, 'other': target})
|
||||
return HttpResponse('OK')
|
||||
|
||||
def move_node(self, request):
|
||||
try:
|
||||
node_id = request.POST['node_id']
|
||||
target_id = request.POST['sibling_id']
|
||||
as_child = bool(int(request.POST.get('as_child', 0)))
|
||||
except (KeyError, ValueError):
|
||||
# Some parameters were missing return a BadRequest
|
||||
return HttpResponseBadRequest('Malformed POST params')
|
||||
|
||||
node = self.get_node(node_id)
|
||||
target = self.get_node(target_id)
|
||||
is_sorted = True if node.node_order_by else False
|
||||
|
||||
pos = {
|
||||
(True, True): 'sorted-child',
|
||||
(True, False): 'last-child',
|
||||
(False, True): 'sorted-sibling',
|
||||
(False, False): 'left',
|
||||
}[as_child, is_sorted]
|
||||
return self.try_to_move_node(as_child, node, pos, request, target)
|
||||
|
||||
|
||||
def admin_factory(form_class):
|
||||
"""Dynamically build a TreeAdmin subclass for the given form class.
|
||||
|
||||
:param form_class:
|
||||
:return: A TreeAdmin subclass.
|
||||
"""
|
||||
return type(
|
||||
form_class.__name__ + 'Admin',
|
||||
(TreeAdmin,),
|
||||
dict(form=form_class))
|
||||
334
wagtail/vendor/django-treebeard/treebeard/al_tree.py
vendored
Normal file
334
wagtail/vendor/django-treebeard/treebeard/al_tree.py
vendored
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
"""Adjacency List"""
|
||||
|
||||
from django.core import serializers
|
||||
from django.db import connection, models, transaction
|
||||
from django.utils.translation import ugettext_noop as _
|
||||
|
||||
from treebeard.exceptions import InvalidMoveToDescendant
|
||||
from treebeard.models import Node
|
||||
|
||||
|
||||
class AL_NodeManager(models.Manager):
|
||||
"""Custom manager for nodes in an Adjacency List tree."""
|
||||
|
||||
def get_query_set(self):
|
||||
"""Sets the custom queryset as the default."""
|
||||
if self.model.node_order_by:
|
||||
order_by = ['parent'] + list(self.model.node_order_by)
|
||||
else:
|
||||
order_by = ['parent', 'sib_order']
|
||||
return super(AL_NodeManager, self).get_query_set().order_by(*order_by)
|
||||
|
||||
|
||||
class AL_Node(Node):
|
||||
"""Abstract model to create your own Adjacency List Trees."""
|
||||
|
||||
objects = AL_NodeManager()
|
||||
node_order_by = None
|
||||
|
||||
@classmethod
|
||||
def add_root(cls, **kwargs):
|
||||
"""Adds a root node to the tree."""
|
||||
newobj = cls(**kwargs)
|
||||
newobj._cached_depth = 1
|
||||
if not cls.node_order_by:
|
||||
try:
|
||||
max = cls.objects.filter(parent__isnull=True).order_by(
|
||||
'sib_order').reverse()[0].sib_order
|
||||
except IndexError:
|
||||
max = 0
|
||||
newobj.sib_order = max + 1
|
||||
newobj.save()
|
||||
transaction.commit_unless_managed()
|
||||
return newobj
|
||||
|
||||
@classmethod
|
||||
def get_root_nodes(cls):
|
||||
""":returns: A queryset containing the root nodes in the tree."""
|
||||
return cls.objects.filter(parent__isnull=True)
|
||||
|
||||
def get_depth(self, update=False):
|
||||
"""
|
||||
:returns: the depth (level) of the node
|
||||
Caches the result in the object itself to help in loops.
|
||||
|
||||
:param update: Updates the cached value.
|
||||
"""
|
||||
|
||||
if self.parent_id is None:
|
||||
return 1
|
||||
|
||||
try:
|
||||
if update:
|
||||
del self._cached_depth
|
||||
else:
|
||||
return self._cached_depth
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
depth = 0
|
||||
node = self
|
||||
while node:
|
||||
node = node.parent
|
||||
depth += 1
|
||||
self._cached_depth = depth
|
||||
return depth
|
||||
|
||||
def get_children(self):
|
||||
""":returns: A queryset of all the node's children"""
|
||||
return self.__class__.objects.filter(parent=self)
|
||||
|
||||
def get_parent(self, update=False):
|
||||
""":returns: the parent node of the current node object."""
|
||||
return self.parent
|
||||
|
||||
def get_ancestors(self):
|
||||
"""
|
||||
:returns: A *list* containing the current node object's ancestors,
|
||||
starting by the root node and descending to the parent.
|
||||
"""
|
||||
ancestors = []
|
||||
node = self.parent
|
||||
while node:
|
||||
ancestors.insert(0, node)
|
||||
node = node.parent
|
||||
return ancestors
|
||||
|
||||
def get_root(self):
|
||||
""":returns: the root node for the current node object."""
|
||||
ancestors = self.get_ancestors()
|
||||
if ancestors:
|
||||
return ancestors[0]
|
||||
return self
|
||||
|
||||
def is_descendant_of(self, node):
|
||||
"""
|
||||
:returns: ``True`` if the node if a descendant of another node given
|
||||
as an argument, else, returns ``False``
|
||||
"""
|
||||
return self.pk in [obj.pk for obj in node.get_descendants()]
|
||||
|
||||
@classmethod
|
||||
def dump_bulk(cls, parent=None, keep_ids=True):
|
||||
"""Dumps a tree branch to a python data structure."""
|
||||
|
||||
serializable_cls = cls._get_serializable_model()
|
||||
if (
|
||||
parent and serializable_cls != cls and
|
||||
parent.__class__ != serializable_cls
|
||||
):
|
||||
parent = serializable_cls.objects.get(pk=parent.pk)
|
||||
|
||||
# a list of nodes: not really a queryset, but it works
|
||||
objs = serializable_cls.get_tree(parent)
|
||||
|
||||
ret, lnk = [], {}
|
||||
for node, pyobj in zip(objs, serializers.serialize('python', objs)):
|
||||
depth = node.get_depth()
|
||||
# django's serializer stores the attributes in 'fields'
|
||||
fields = pyobj['fields']
|
||||
del fields['parent']
|
||||
|
||||
# non-sorted trees have this
|
||||
if 'sib_order' in fields:
|
||||
del fields['sib_order']
|
||||
|
||||
if 'id' in fields:
|
||||
del fields['id']
|
||||
|
||||
newobj = {'data': fields}
|
||||
if keep_ids:
|
||||
newobj['id'] = pyobj['pk']
|
||||
|
||||
if (not parent and depth == 1) or\
|
||||
(parent and depth == parent.get_depth()):
|
||||
ret.append(newobj)
|
||||
else:
|
||||
parentobj = lnk[node.parent_id]
|
||||
if 'children' not in parentobj:
|
||||
parentobj['children'] = []
|
||||
parentobj['children'].append(newobj)
|
||||
lnk[node.pk] = newobj
|
||||
return ret
|
||||
|
||||
def add_child(self, **kwargs):
|
||||
"""Adds a child to the node."""
|
||||
newobj = self.__class__(**kwargs)
|
||||
try:
|
||||
newobj._cached_depth = self._cached_depth + 1
|
||||
except AttributeError:
|
||||
pass
|
||||
if not self.__class__.node_order_by:
|
||||
try:
|
||||
max = self.__class__.objects.filter(parent=self).reverse(
|
||||
)[0].sib_order
|
||||
except IndexError:
|
||||
max = 0
|
||||
newobj.sib_order = max + 1
|
||||
newobj.parent = self
|
||||
newobj.save()
|
||||
transaction.commit_unless_managed()
|
||||
return newobj
|
||||
|
||||
@classmethod
|
||||
def _get_tree_recursively(cls, results, parent, depth):
|
||||
if parent:
|
||||
nodes = parent.get_children()
|
||||
else:
|
||||
nodes = cls.get_root_nodes()
|
||||
for node in nodes:
|
||||
node._cached_depth = depth
|
||||
results.append(node)
|
||||
cls._get_tree_recursively(results, node, depth + 1)
|
||||
|
||||
@classmethod
|
||||
def get_tree(cls, parent=None):
|
||||
"""
|
||||
:returns: A list of nodes ordered as DFS, including the parent. If
|
||||
no parent is given, the entire tree is returned.
|
||||
"""
|
||||
if parent:
|
||||
depth = parent.get_depth() + 1
|
||||
results = [parent]
|
||||
else:
|
||||
depth = 1
|
||||
results = []
|
||||
cls._get_tree_recursively(results, parent, depth)
|
||||
return results
|
||||
|
||||
def get_descendants(self):
|
||||
"""
|
||||
:returns: A *list* of all the node's descendants, doesn't
|
||||
include the node itself
|
||||
"""
|
||||
return self.__class__.get_tree(parent=self)[1:]
|
||||
|
||||
def get_descendant_count(self):
|
||||
""":returns: the number of descendants of a nodee"""
|
||||
return len(self.get_descendants())
|
||||
|
||||
def get_siblings(self):
|
||||
"""
|
||||
:returns: A queryset of all the node's siblings, including the node
|
||||
itself.
|
||||
"""
|
||||
if self.parent:
|
||||
return self.__class__.objects.filter(parent=self.parent)
|
||||
return self.__class__.get_root_nodes()
|
||||
|
||||
def add_sibling(self, pos=None, **kwargs):
|
||||
"""Adds a new node as a sibling to the current node object."""
|
||||
pos = self._prepare_pos_var_for_add_sibling(pos)
|
||||
newobj = self.__class__(**kwargs)
|
||||
if not self.node_order_by:
|
||||
newobj.sib_order = self.__class__._get_new_sibling_order(pos,
|
||||
self)
|
||||
newobj.parent_id = self.parent_id
|
||||
newobj.save()
|
||||
transaction.commit_unless_managed()
|
||||
return newobj
|
||||
|
||||
@classmethod
|
||||
def _is_target_pos_the_last_sibling(cls, pos, target):
|
||||
return pos == 'last-sibling' or (
|
||||
pos == 'right' and target == target.get_last_sibling())
|
||||
|
||||
@classmethod
|
||||
def _make_hole_in_db(cls, min, target_node):
|
||||
qset = cls.objects.filter(sib_order__gte=min)
|
||||
if target_node.is_root():
|
||||
qset = qset.filter(parent__isnull=True)
|
||||
else:
|
||||
qset = qset.filter(parent=target_node.parent)
|
||||
qset.update(sib_order=models.F('sib_order') + 1)
|
||||
|
||||
@classmethod
|
||||
def _make_hole_and_get_sibling_order(cls, pos, target_node):
|
||||
siblings = target_node.get_siblings()
|
||||
siblings = {
|
||||
'left': siblings.filter(sib_order__gte=target_node.sib_order),
|
||||
'right': siblings.filter(sib_order__gt=target_node.sib_order),
|
||||
'first-sibling': siblings
|
||||
}[pos]
|
||||
sib_order = {
|
||||
'left': target_node.sib_order,
|
||||
'right': target_node.sib_order + 1,
|
||||
'first-sibling': 1
|
||||
}[pos]
|
||||
try:
|
||||
min = siblings.order_by('sib_order')[0].sib_order
|
||||
except IndexError:
|
||||
min = 0
|
||||
if min:
|
||||
cls._make_hole_in_db(min, target_node)
|
||||
return sib_order
|
||||
|
||||
@classmethod
|
||||
def _get_new_sibling_order(cls, pos, target_node):
|
||||
if cls._is_target_pos_the_last_sibling(pos, target_node):
|
||||
sib_order = target_node.get_last_sibling().sib_order + 1
|
||||
else:
|
||||
sib_order = cls._make_hole_and_get_sibling_order(pos, target_node)
|
||||
return sib_order
|
||||
|
||||
def move(self, target, pos=None):
|
||||
"""
|
||||
Moves the current node and all it's descendants to a new position
|
||||
relative to another node.
|
||||
"""
|
||||
|
||||
pos = self._prepare_pos_var_for_move(pos)
|
||||
|
||||
sib_order = None
|
||||
parent = None
|
||||
|
||||
if pos in ('first-child', 'last-child', 'sorted-child'):
|
||||
# moving to a child
|
||||
if not target.is_leaf():
|
||||
target = target.get_last_child()
|
||||
pos = {'first-child': 'first-sibling',
|
||||
'last-child': 'last-sibling',
|
||||
'sorted-child': 'sorted-sibling'}[pos]
|
||||
else:
|
||||
parent = target
|
||||
if pos == 'sorted-child':
|
||||
pos = 'sorted-sibling'
|
||||
else:
|
||||
pos = 'first-sibling'
|
||||
sib_order = 1
|
||||
|
||||
if target.is_descendant_of(self):
|
||||
raise InvalidMoveToDescendant(
|
||||
_("Can't move node to a descendant."))
|
||||
|
||||
if self == target and (
|
||||
(pos == 'left') or
|
||||
(pos in ('right', 'last-sibling') and
|
||||
target == target.get_last_sibling()) or
|
||||
(pos == 'first-sibling' and
|
||||
target == target.get_first_sibling())):
|
||||
# special cases, not actually moving the node so no need to UPDATE
|
||||
return
|
||||
|
||||
if pos == 'sorted-sibling':
|
||||
if parent:
|
||||
self.parent = parent
|
||||
else:
|
||||
self.parent = target.parent
|
||||
else:
|
||||
if sib_order:
|
||||
self.sib_order = sib_order
|
||||
else:
|
||||
self.sib_order = self.__class__._get_new_sibling_order(pos,
|
||||
target)
|
||||
if parent:
|
||||
self.parent = parent
|
||||
else:
|
||||
self.parent = target.parent
|
||||
|
||||
self.save()
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
class Meta:
|
||||
"""Abstract model."""
|
||||
abstract = True
|
||||
24
wagtail/vendor/django-treebeard/treebeard/exceptions.py
vendored
Normal file
24
wagtail/vendor/django-treebeard/treebeard/exceptions.py
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
"""Treebeard exceptions"""
|
||||
|
||||
|
||||
class InvalidPosition(Exception):
|
||||
"""Raised when passing an invalid pos value"""
|
||||
|
||||
|
||||
class InvalidMoveToDescendant(Exception):
|
||||
"""Raised when attemping to move a node to one of it's descendants."""
|
||||
|
||||
|
||||
class MissingNodeOrderBy(Exception):
|
||||
"""
|
||||
Raised when an operation needs a missing
|
||||
:attr:`~treebeard.MP_Node.node_order_by` attribute
|
||||
"""
|
||||
|
||||
|
||||
class PathOverflow(Exception):
|
||||
"""
|
||||
Raised when trying to add or move a node to a position where no more nodes
|
||||
can be added (see :attr:`~treebeard.MP_Node.path` and
|
||||
:attr:`~treebeard.MP_Node.alphabet` for more info)
|
||||
"""
|
||||
229
wagtail/vendor/django-treebeard/treebeard/forms.py
vendored
Normal file
229
wagtail/vendor/django-treebeard/treebeard/forms.py
vendored
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
"""Forms for treebeard."""
|
||||
|
||||
from django import forms
|
||||
from django.db.models.query import QuerySet
|
||||
from django.forms.models import BaseModelForm, ErrorList, model_to_dict
|
||||
from django.forms.models import modelform_factory as django_modelform_factory
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from treebeard.al_tree import AL_Node
|
||||
from treebeard.mp_tree import MP_Node
|
||||
from treebeard.ns_tree import NS_Node
|
||||
|
||||
|
||||
class MoveNodeForm(forms.ModelForm):
|
||||
"""
|
||||
Form to handle moving a node in a tree.
|
||||
|
||||
Handles sorted/unsorted trees.
|
||||
|
||||
It adds two fields to the form:
|
||||
|
||||
- Relative to: The target node where the current node will
|
||||
be moved to.
|
||||
- Position: The position relative to the target node that
|
||||
will be used to move the node. These can be:
|
||||
|
||||
- For sorted trees: ``Child of`` and ``Sibling of``
|
||||
- For unsorted trees: ``First child of``, ``Before`` and
|
||||
``After``
|
||||
|
||||
.. warning::
|
||||
|
||||
Subclassing :py:class:`MoveNodeForm` directly is
|
||||
discouraged, since special care is needed to handle
|
||||
excluded fields, and these change depending on the
|
||||
tree type.
|
||||
|
||||
It is recommended that the :py:func:`movenodeform_factory`
|
||||
function is used instead.
|
||||
|
||||
"""
|
||||
|
||||
__position_choices_sorted = (
|
||||
('sorted-child', _('Child of')),
|
||||
('sorted-sibling', _('Sibling of')),
|
||||
)
|
||||
|
||||
__position_choices_unsorted = (
|
||||
('first-child', _('First child of')),
|
||||
('left', _('Before')),
|
||||
('right', _('After')),
|
||||
)
|
||||
|
||||
_position = forms.ChoiceField(required=True, label=_("Position"))
|
||||
|
||||
_ref_node_id = forms.TypedChoiceField(required=False,
|
||||
coerce=int,
|
||||
label=_("Relative to"))
|
||||
|
||||
def _get_position_ref_node(self, instance):
|
||||
if self.is_sorted:
|
||||
position = 'sorted-child'
|
||||
node_parent = instance.get_parent()
|
||||
if node_parent:
|
||||
ref_node_id = node_parent.pk
|
||||
else:
|
||||
ref_node_id = ''
|
||||
else:
|
||||
prev_sibling = instance.get_prev_sibling()
|
||||
if prev_sibling:
|
||||
position = 'right'
|
||||
ref_node_id = prev_sibling.pk
|
||||
else:
|
||||
position = 'first-child'
|
||||
if instance.is_root():
|
||||
ref_node_id = ''
|
||||
else:
|
||||
ref_node_id = instance.get_parent().pk
|
||||
return {'_ref_node_id': ref_node_id,
|
||||
'_position': position}
|
||||
|
||||
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
|
||||
initial=None, error_class=ErrorList, label_suffix=':',
|
||||
empty_permitted=False, instance=None):
|
||||
opts = self._meta
|
||||
if instance is None:
|
||||
if opts.model is None:
|
||||
raise ValueError('MoveNodeForm has no model class specified.')
|
||||
else:
|
||||
opts.model = type(instance)
|
||||
self.is_sorted = getattr(opts.model, 'node_order_by', False)
|
||||
|
||||
if self.is_sorted:
|
||||
choices_sort_mode = self.__class__.__position_choices_sorted
|
||||
else:
|
||||
choices_sort_mode = self.__class__.__position_choices_unsorted
|
||||
self.declared_fields['_position'].choices = choices_sort_mode
|
||||
|
||||
if instance is None:
|
||||
# if we didn't get an instance, instantiate a new one
|
||||
instance = opts.model()
|
||||
object_data = {}
|
||||
choices_for_node = None
|
||||
else:
|
||||
object_data = model_to_dict(instance, opts.fields, opts.exclude)
|
||||
object_data.update(self._get_position_ref_node(instance))
|
||||
choices_for_node = instance
|
||||
|
||||
choices = self.mk_dropdown_tree(opts.model, for_node=choices_for_node)
|
||||
self.declared_fields['_ref_node_id'].choices = choices
|
||||
self.instance = instance
|
||||
# if initial was provided, it should override the values from instance
|
||||
if initial is not None:
|
||||
object_data.update(initial)
|
||||
super(BaseModelForm, self).__init__(data, files, auto_id, prefix,
|
||||
object_data, error_class,
|
||||
label_suffix, empty_permitted)
|
||||
|
||||
def _clean_cleaned_data(self):
|
||||
""" delete auxilary fields not belonging to node model """
|
||||
reference_node_id = 0
|
||||
|
||||
if '_ref_node_id' in self.cleaned_data:
|
||||
reference_node_id = self.cleaned_data['_ref_node_id']
|
||||
del self.cleaned_data['_ref_node_id']
|
||||
|
||||
position_type = self.cleaned_data['_position']
|
||||
del self.cleaned_data['_position']
|
||||
|
||||
return position_type, reference_node_id
|
||||
|
||||
def save(self, commit=True):
|
||||
position_type, reference_node_id = self._clean_cleaned_data()
|
||||
|
||||
if self.instance.pk is None:
|
||||
cl_data = {}
|
||||
for field in self.cleaned_data:
|
||||
if not isinstance(self.cleaned_data[field], (list, QuerySet)):
|
||||
cl_data[field] = self.cleaned_data[field]
|
||||
if reference_node_id:
|
||||
reference_node = self._meta.model.objects.get(
|
||||
pk=reference_node_id)
|
||||
self.instance = reference_node.add_child(**cl_data)
|
||||
self.instance.move(reference_node, pos=position_type)
|
||||
else:
|
||||
self.instance = self._meta.model.add_root(**cl_data)
|
||||
else:
|
||||
self.instance.save()
|
||||
if reference_node_id:
|
||||
reference_node = self._meta.model.objects.get(
|
||||
pk=reference_node_id)
|
||||
self.instance.move(reference_node, pos=position_type)
|
||||
else:
|
||||
if self.is_sorted:
|
||||
pos = 'sorted-sibling'
|
||||
else:
|
||||
pos = 'first-sibling'
|
||||
self.instance.move(self._meta.model.get_first_root_node(), pos)
|
||||
# Reload the instance
|
||||
self.instance = self._meta.model.objects.get(pk=self.instance.pk)
|
||||
super(MoveNodeForm, self).save(commit=commit)
|
||||
return self.instance
|
||||
|
||||
@staticmethod
|
||||
def is_loop_safe(for_node, possible_parent):
|
||||
if for_node is not None:
|
||||
return not (
|
||||
possible_parent == for_node
|
||||
) or (possible_parent.is_descendant_of(for_node))
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def mk_indent(level):
|
||||
return ' ' * (level - 1)
|
||||
|
||||
@classmethod
|
||||
def add_subtree(cls, for_node, node, options):
|
||||
""" Recursively build options tree. """
|
||||
if cls.is_loop_safe(for_node, node):
|
||||
options.append(
|
||||
(node.pk,
|
||||
mark_safe(cls.mk_indent(node.get_depth()) + str(node))))
|
||||
for subnode in node.get_children():
|
||||
cls.add_subtree(for_node, subnode, options)
|
||||
|
||||
@classmethod
|
||||
def mk_dropdown_tree(cls, model, for_node=None):
|
||||
""" Creates a tree-like list of choices """
|
||||
|
||||
options = [(0, _('-- root --'))]
|
||||
for node in model.get_root_nodes():
|
||||
cls.add_subtree(for_node, node, options)
|
||||
return options
|
||||
|
||||
|
||||
def movenodeform_factory(model, form=MoveNodeForm, fields=None, exclude=None,
|
||||
formfield_callback=None, widgets=None):
|
||||
"""Dynamically build a MoveNodeForm subclass with the proper Meta.
|
||||
|
||||
:param Node model:
|
||||
|
||||
The subclass of :py:class:`Node` that will be handled
|
||||
by the form.
|
||||
|
||||
:param form:
|
||||
|
||||
The form class that will be used as a base. By
|
||||
default, :py:class:`MoveNodeForm` will be used.
|
||||
|
||||
:return: A :py:class:`MoveNodeForm` subclass
|
||||
"""
|
||||
_exclude = _get_exclude_for_model(model, exclude)
|
||||
return django_modelform_factory(
|
||||
model, form, fields, _exclude, formfield_callback, widgets)
|
||||
|
||||
|
||||
def _get_exclude_for_model(model, exclude):
|
||||
if exclude:
|
||||
_exclude = tuple(exclude)
|
||||
else:
|
||||
_exclude = ()
|
||||
if issubclass(model, AL_Node):
|
||||
_exclude += ('sib_order', 'parent')
|
||||
elif issubclass(model, MP_Node):
|
||||
_exclude += ('depth', 'numchild', 'path')
|
||||
elif issubclass(model, NS_Node):
|
||||
_exclude += ('depth', 'lft', 'rgt', 'tree_id')
|
||||
return _exclude
|
||||
BIN
wagtail/vendor/django-treebeard/treebeard/locale/es/LC_MESSAGES/django.mo
vendored
Normal file
BIN
wagtail/vendor/django-treebeard/treebeard/locale/es/LC_MESSAGES/django.mo
vendored
Normal file
Binary file not shown.
78
wagtail/vendor/django-treebeard/treebeard/locale/es/LC_MESSAGES/django.po
vendored
Normal file
78
wagtail/vendor/django-treebeard/treebeard/locale/es/LC_MESSAGES/django.po
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django-treebeard\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-07-18 17:36+0200\n"
|
||||
"PO-Revision-Date: 2010-05-03 23:40-0500\n"
|
||||
"Last-Translator: Gustavo Picon <tabo@tabo.pe>\n"
|
||||
"Language-Team: Spanish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: admin.py:113
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as child of \"%(other)s\""
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:119
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as sibling of \"%(other)s\""
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:129
|
||||
#, python-format
|
||||
msgid "Exception raised while moving node: %s"
|
||||
msgstr ""
|
||||
|
||||
#: al_tree.py:319 mp_tree.py:641 ns_tree.py:308
|
||||
msgid "Can't move node to a descendant."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:17
|
||||
msgid "Child of"
|
||||
msgstr "Hijo de"
|
||||
|
||||
#: forms.py:18
|
||||
msgid "Sibling of"
|
||||
msgstr "Hermano de"
|
||||
|
||||
#: forms.py:22
|
||||
msgid "First child of"
|
||||
msgstr "Primer hijo de"
|
||||
|
||||
#: forms.py:23
|
||||
msgid "Before"
|
||||
msgstr "Antes"
|
||||
|
||||
#: forms.py:24
|
||||
msgid "After"
|
||||
msgstr "Después"
|
||||
|
||||
#: forms.py:27
|
||||
msgid "Position"
|
||||
msgstr "Posición"
|
||||
|
||||
#: forms.py:31
|
||||
msgid "Relative to"
|
||||
msgstr "Relativo a"
|
||||
|
||||
#: forms.py:81
|
||||
msgid "-- root --"
|
||||
msgstr "-- raíz --"
|
||||
|
||||
#: mp_tree.py:521
|
||||
msgid ""
|
||||
"The new node is too deep in the tree, try increasing the path.max_length "
|
||||
"property and UPDATE your database"
|
||||
msgstr ""
|
||||
|
||||
#: mp_tree.py:702
|
||||
#, python-format
|
||||
msgid "Path Overflow from: '%s'"
|
||||
msgstr ""
|
||||
|
||||
#: templatetags/admin_tree.py:148
|
||||
msgid "Return to ordered tree"
|
||||
msgstr ""
|
||||
BIN
wagtail/vendor/django-treebeard/treebeard/locale/es/LC_MESSAGES/djangojs.mo
vendored
Normal file
BIN
wagtail/vendor/django-treebeard/treebeard/locale/es/LC_MESSAGES/djangojs.mo
vendored
Normal file
Binary file not shown.
24
wagtail/vendor/django-treebeard/treebeard/locale/es/LC_MESSAGES/djangojs.po
vendored
Normal file
24
wagtail/vendor/django-treebeard/treebeard/locale/es/LC_MESSAGES/djangojs.po
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django-treebeard\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-07-18 14:12+0200\n"
|
||||
"PO-Revision-Date: 2011-07-18 14:12+0200\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:157
|
||||
msgid "Abort"
|
||||
msgstr ""
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:172
|
||||
msgid "As Sibling"
|
||||
msgstr ""
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:190
|
||||
msgid "As child"
|
||||
msgstr ""
|
||||
BIN
wagtail/vendor/django-treebeard/treebeard/locale/nl/LC_MESSAGES/django.mo
vendored
Normal file
BIN
wagtail/vendor/django-treebeard/treebeard/locale/nl/LC_MESSAGES/django.mo
vendored
Normal file
Binary file not shown.
80
wagtail/vendor/django-treebeard/treebeard/locale/nl/LC_MESSAGES/django.po
vendored
Normal file
80
wagtail/vendor/django-treebeard/treebeard/locale/nl/LC_MESSAGES/django.po
vendored
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django-treebeard\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-07-18 17:36+0200\n"
|
||||
"PO-Revision-Date: 2011-07-18 14:11+0200\n"
|
||||
"Last-Translator: Jaap Roes <jaap@eight.nl>\n"
|
||||
"Language-Team: Dutch\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: admin.py:113
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as child of \"%(other)s\""
|
||||
msgstr "\"%(node)s\" is nu onderdeel van \"%(other)s\""
|
||||
|
||||
#: admin.py:119
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as sibling of \"%(other)s\""
|
||||
msgstr "\"%(node)s\" staat nu voor \"%(other)s\""
|
||||
|
||||
#: admin.py:129
|
||||
#, python-format
|
||||
msgid "Exception raised while moving node: %s"
|
||||
msgstr "Fatale fout tijdens het verplaatsen: %s"
|
||||
|
||||
#: al_tree.py:319 mp_tree.py:641 ns_tree.py:308
|
||||
msgid "Can't move node to a descendant."
|
||||
msgstr "Kan node niet naar eigen subnode verplaatsen"
|
||||
|
||||
#: forms.py:17
|
||||
msgid "Child of"
|
||||
msgstr "Onderdeel"
|
||||
|
||||
#: forms.py:18
|
||||
msgid "Sibling of"
|
||||
msgstr "Naast"
|
||||
|
||||
#: forms.py:22
|
||||
msgid "First child of"
|
||||
msgstr "1e onderdeel"
|
||||
|
||||
#: forms.py:23
|
||||
msgid "Before"
|
||||
msgstr "Voor"
|
||||
|
||||
#: forms.py:24
|
||||
msgid "After"
|
||||
msgstr "Na"
|
||||
|
||||
#: forms.py:27
|
||||
msgid "Position"
|
||||
msgstr "Positie"
|
||||
|
||||
#: forms.py:31
|
||||
msgid "Relative to"
|
||||
msgstr "Ten opzichte van"
|
||||
|
||||
#: forms.py:81
|
||||
msgid "-- root --"
|
||||
msgstr "-- hoofdniveau --"
|
||||
|
||||
#: mp_tree.py:521
|
||||
msgid ""
|
||||
"The new node is too deep in the tree, try increasing the path.max_length "
|
||||
"property and UPDATE your database"
|
||||
msgstr ""
|
||||
"De nieuwe node bevindt zich te diep in de boom. Verhoog de path.max_lenght "
|
||||
"waarde en UPDATE de database."
|
||||
|
||||
#: mp_tree.py:702
|
||||
#, python-format
|
||||
msgid "Path Overflow from: '%s'"
|
||||
msgstr "Path overflow van: '%s'"
|
||||
|
||||
#: templatetags/admin_tree.py:148
|
||||
msgid "Return to ordered tree"
|
||||
msgstr "Als gesorteerde boom"
|
||||
BIN
wagtail/vendor/django-treebeard/treebeard/locale/nl/LC_MESSAGES/djangojs.mo
vendored
Normal file
BIN
wagtail/vendor/django-treebeard/treebeard/locale/nl/LC_MESSAGES/djangojs.mo
vendored
Normal file
Binary file not shown.
24
wagtail/vendor/django-treebeard/treebeard/locale/nl/LC_MESSAGES/djangojs.po
vendored
Normal file
24
wagtail/vendor/django-treebeard/treebeard/locale/nl/LC_MESSAGES/djangojs.po
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django-treebeard\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-07-18 14:12+0200\n"
|
||||
"PO-Revision-Date: 2011-07-18 14:12+0200\n"
|
||||
"Last-Translator: Jaap Roes <jaap@eight.nl>\n"
|
||||
"Language-Team: Dutch\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:157
|
||||
msgid "Abort"
|
||||
msgstr "Annuleren"
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:172
|
||||
msgid "As Sibling"
|
||||
msgstr "Als naastliggend onderdeel"
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:190
|
||||
msgid "As child"
|
||||
msgstr "Als subonderdeel"
|
||||
BIN
wagtail/vendor/django-treebeard/treebeard/locale/pl/LC_MESSAGES/django.mo
vendored
Normal file
BIN
wagtail/vendor/django-treebeard/treebeard/locale/pl/LC_MESSAGES/django.mo
vendored
Normal file
Binary file not shown.
44
wagtail/vendor/django-treebeard/treebeard/locale/pl/LC_MESSAGES/django.po
vendored
Normal file
44
wagtail/vendor/django-treebeard/treebeard/locale/pl/LC_MESSAGES/django.po
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django-treebeard\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-05-03 23:53-0500\n"
|
||||
"PO-Revision-Date: 2010-05-03 23:40-0500\n"
|
||||
"Last-Translator: Bartosz Turkot <bartosz.turkot@blueservices.pl>\n"
|
||||
"Language-Team: Polish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: forms.py:16
|
||||
msgid "Child of"
|
||||
msgstr "Dziecko kategorii"
|
||||
|
||||
#: forms.py:17
|
||||
msgid "Sibling of"
|
||||
msgstr "Sąsiad kategorii"
|
||||
|
||||
#: forms.py:21
|
||||
msgid "First child of"
|
||||
msgstr "Pierwsze dziecko kategorii"
|
||||
|
||||
#: forms.py:22
|
||||
msgid "Before"
|
||||
msgstr "Przed"
|
||||
|
||||
#: forms.py:23
|
||||
msgid "After"
|
||||
msgstr "Za"
|
||||
|
||||
#: forms.py:26
|
||||
msgid "Position"
|
||||
msgstr "Pozycja"
|
||||
|
||||
#: forms.py:30
|
||||
msgid "Relative to"
|
||||
msgstr "Względem"
|
||||
|
||||
#: forms.py:80
|
||||
msgid "-- root --"
|
||||
msgstr "-- kategoria główna --"
|
||||
BIN
wagtail/vendor/django-treebeard/treebeard/locale/ru/LC_MESSAGES/django.mo
vendored
Normal file
BIN
wagtail/vendor/django-treebeard/treebeard/locale/ru/LC_MESSAGES/django.mo
vendored
Normal file
Binary file not shown.
79
wagtail/vendor/django-treebeard/treebeard/locale/ru/LC_MESSAGES/django.po
vendored
Normal file
79
wagtail/vendor/django-treebeard/treebeard/locale/ru/LC_MESSAGES/django.po
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django-treebeard\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-07-18 17:36+0200\n"
|
||||
"PO-Revision-Date: 2009-04-10 18:37+0400\n"
|
||||
"Last-Translator: chembervint <chembervint@gmail.com>\n"
|
||||
"Language-Team: Russian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%"
|
||||
"10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
|
||||
|
||||
#: admin.py:113
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as child of \"%(other)s\""
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:119
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as sibling of \"%(other)s\""
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:129
|
||||
#, python-format
|
||||
msgid "Exception raised while moving node: %s"
|
||||
msgstr ""
|
||||
|
||||
#: al_tree.py:319 mp_tree.py:641 ns_tree.py:308
|
||||
msgid "Can't move node to a descendant."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:17
|
||||
msgid "Child of"
|
||||
msgstr "Вложенный"
|
||||
|
||||
#: forms.py:18
|
||||
msgid "Sibling of"
|
||||
msgstr "Соседний к"
|
||||
|
||||
#: forms.py:22
|
||||
msgid "First child of"
|
||||
msgstr "Первый вложенный"
|
||||
|
||||
#: forms.py:23
|
||||
msgid "Before"
|
||||
msgstr "До"
|
||||
|
||||
#: forms.py:24
|
||||
msgid "After"
|
||||
msgstr "После"
|
||||
|
||||
#: forms.py:27
|
||||
msgid "Position"
|
||||
msgstr "Позиция"
|
||||
|
||||
#: forms.py:31
|
||||
msgid "Relative to"
|
||||
msgstr "Относительно"
|
||||
|
||||
#: forms.py:81
|
||||
msgid "-- root --"
|
||||
msgstr "-- корень --"
|
||||
|
||||
#: mp_tree.py:521
|
||||
msgid ""
|
||||
"The new node is too deep in the tree, try increasing the path.max_length "
|
||||
"property and UPDATE your database"
|
||||
msgstr ""
|
||||
|
||||
#: mp_tree.py:702
|
||||
#, python-format
|
||||
msgid "Path Overflow from: '%s'"
|
||||
msgstr ""
|
||||
|
||||
#: templatetags/admin_tree.py:148
|
||||
msgid "Return to ordered tree"
|
||||
msgstr ""
|
||||
BIN
wagtail/vendor/django-treebeard/treebeard/locale/ru/LC_MESSAGES/djangojs.mo
vendored
Normal file
BIN
wagtail/vendor/django-treebeard/treebeard/locale/ru/LC_MESSAGES/djangojs.mo
vendored
Normal file
Binary file not shown.
25
wagtail/vendor/django-treebeard/treebeard/locale/ru/LC_MESSAGES/djangojs.po
vendored
Normal file
25
wagtail/vendor/django-treebeard/treebeard/locale/ru/LC_MESSAGES/djangojs.po
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django-treebeard\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-07-18 14:12+0200\n"
|
||||
"PO-Revision-Date: 2011-07-18 14:12+0200\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Russian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%"
|
||||
"10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:157
|
||||
msgid "Abort"
|
||||
msgstr ""
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:172
|
||||
msgid "As Sibling"
|
||||
msgstr ""
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:190
|
||||
msgid "As child"
|
||||
msgstr ""
|
||||
620
wagtail/vendor/django-treebeard/treebeard/models.py
vendored
Normal file
620
wagtail/vendor/django-treebeard/treebeard/models.py
vendored
Normal file
|
|
@ -0,0 +1,620 @@
|
|||
"""Models and base API"""
|
||||
|
||||
import sys
|
||||
import operator
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
from functools import reduce
|
||||
|
||||
from django.db.models import Q
|
||||
from django.db import models, transaction, router, connections
|
||||
|
||||
from treebeard.exceptions import InvalidPosition, MissingNodeOrderBy
|
||||
|
||||
|
||||
class Node(models.Model):
|
||||
"""Node class"""
|
||||
|
||||
_db_connection = None
|
||||
|
||||
@classmethod
|
||||
def add_root(cls, **kwargs): # pragma: no cover
|
||||
"""
|
||||
Adds a root node to the tree. The new root node will be the new
|
||||
rightmost root node. If you want to insert a root node at a specific
|
||||
position, use :meth:`add_sibling` in an already existing root node
|
||||
instead.
|
||||
|
||||
:param \*\*kwargs: object creation data that will be passed to the
|
||||
inherited Node model
|
||||
|
||||
:returns: the created node object. It will be save()d by this method.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def get_foreign_keys(cls):
|
||||
""" Get foreign keys and models they refer to, so we can pre-process the
|
||||
data for load_bulk """
|
||||
foreign_keys = {}
|
||||
for field in cls._meta.fields:
|
||||
if (
|
||||
field.get_internal_type() == 'ForeignKey' and
|
||||
field.name != 'parent'
|
||||
):
|
||||
foreign_keys[field.name] = field.rel.to
|
||||
return foreign_keys
|
||||
|
||||
@classmethod
|
||||
def _process_foreign_keys(cls, foreign_keys, node_data):
|
||||
""" For each foreign key try to load the actual object so load_bulk
|
||||
doesn't fail trying to load an int where django expects a model instance
|
||||
"""
|
||||
for key in foreign_keys.keys():
|
||||
if key in node_data:
|
||||
node_data[key] = foreign_keys[key].objects.get(
|
||||
pk=node_data[key])
|
||||
|
||||
@classmethod
|
||||
def load_bulk(cls, bulk_data, parent=None, keep_ids=False):
|
||||
"""
|
||||
Loads a list/dictionary structure to the tree.
|
||||
|
||||
|
||||
:param bulk_data:
|
||||
|
||||
The data that will be loaded, the structure is a list of
|
||||
dictionaries with 2 keys:
|
||||
|
||||
- ``data``: will store arguments that will be passed for object
|
||||
creation, and
|
||||
|
||||
- ``children``: a list of dictionaries, each one has it's own
|
||||
``data`` and ``children`` keys (a recursive structure)
|
||||
|
||||
|
||||
:param parent:
|
||||
|
||||
The node that will receive the structure as children, if not
|
||||
specified the first level of the structure will be loaded as root
|
||||
nodes
|
||||
|
||||
|
||||
:param keep_ids:
|
||||
|
||||
If enabled, loads the nodes with the same id that are given in the
|
||||
structure. Will error if there are nodes without id info or if the
|
||||
ids are already used.
|
||||
|
||||
|
||||
:returns: A list of the added node ids.
|
||||
"""
|
||||
|
||||
# tree, iterative preorder
|
||||
added = []
|
||||
# stack of nodes to analize
|
||||
stack = [(parent, node) for node in bulk_data[::-1]]
|
||||
foreign_keys = cls.get_foreign_keys()
|
||||
|
||||
while stack:
|
||||
parent, node_struct = stack.pop()
|
||||
# shallow copy of the data strucure so it doesn't persist...
|
||||
node_data = node_struct['data'].copy()
|
||||
cls._process_foreign_keys(foreign_keys, node_data)
|
||||
if keep_ids:
|
||||
node_data['id'] = node_struct['id']
|
||||
if parent:
|
||||
node_obj = parent.add_child(**node_data)
|
||||
else:
|
||||
node_obj = cls.add_root(**node_data)
|
||||
added.append(node_obj.pk)
|
||||
if 'children' in node_struct:
|
||||
# extending the stack with the current node as the parent of
|
||||
# the new nodes
|
||||
stack.extend([
|
||||
(node_obj, node)
|
||||
for node in node_struct['children'][::-1]
|
||||
])
|
||||
transaction.commit_unless_managed()
|
||||
return added
|
||||
|
||||
@classmethod
|
||||
def dump_bulk(cls, parent=None, keep_ids=True): # pragma: no cover
|
||||
"""
|
||||
Dumps a tree branch to a python data structure.
|
||||
|
||||
:param parent:
|
||||
|
||||
The node whose descendants will be dumped. The node itself will be
|
||||
included in the dump. If not given, the entire tree will be dumped.
|
||||
|
||||
:param keep_ids:
|
||||
|
||||
Stores the id value (primary key) of every node. Enabled by
|
||||
default.
|
||||
|
||||
:returns: A python data structure, described with detail in
|
||||
:meth:`load_bulk`
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def get_root_nodes(cls): # pragma: no cover
|
||||
""":returns: A queryset containing the root nodes in the tree."""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def get_first_root_node(cls):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
The first root node in the tree or ``None`` if it is empty.
|
||||
"""
|
||||
try:
|
||||
return cls.get_root_nodes()[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_last_root_node(cls):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
The last root node in the tree or ``None`` if it is empty.
|
||||
"""
|
||||
try:
|
||||
return cls.get_root_nodes().reverse()[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def find_problems(cls): # pragma: no cover
|
||||
"""Checks for problems in the tree structure."""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def fix_tree(cls): # pragma: no cover
|
||||
"""
|
||||
Solves problems that can appear when transactions are not used and
|
||||
a piece of code breaks, leaving the tree in an inconsistent state.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def get_tree(cls, parent=None):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
A list of nodes ordered as DFS, including the parent. If
|
||||
no parent is given, the entire tree is returned.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def get_descendants_group_count(cls, parent=None):
|
||||
"""
|
||||
Helper for a very common case: get a group of siblings and the number
|
||||
of *descendants* (not only children) in every sibling.
|
||||
|
||||
:param parent:
|
||||
|
||||
The parent of the siblings to return. If no parent is given, the
|
||||
root nodes will be returned.
|
||||
|
||||
:returns:
|
||||
|
||||
A `list` (**NOT** a Queryset) of node objects with an extra
|
||||
attribute: `descendants_count`.
|
||||
"""
|
||||
if parent is None:
|
||||
qset = cls.get_root_nodes()
|
||||
else:
|
||||
qset = parent.get_children()
|
||||
nodes = list(qset)
|
||||
for node in nodes:
|
||||
node.descendants_count = node.get_descendant_count()
|
||||
return nodes
|
||||
|
||||
def get_depth(self): # pragma: no cover
|
||||
""":returns: the depth (level) of the node"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_siblings(self): # pragma: no cover
|
||||
"""
|
||||
:returns:
|
||||
|
||||
A queryset of all the node's siblings, including the node
|
||||
itself.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_children(self): # pragma: no cover
|
||||
""":returns: A queryset of all the node's children"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_children_count(self):
|
||||
""":returns: The number of the node's children"""
|
||||
return self.get_children().count()
|
||||
|
||||
def get_descendants(self):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
A queryset of all the node's descendants, doesn't
|
||||
include the node itself (some subclasses may return a list).
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_descendant_count(self):
|
||||
""":returns: the number of descendants of a node."""
|
||||
return self.get_descendants().count()
|
||||
|
||||
def get_first_child(self):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
The leftmost node's child, or None if it has no children.
|
||||
"""
|
||||
try:
|
||||
return self.get_children()[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def get_last_child(self):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
The rightmost node's child, or None if it has no children.
|
||||
"""
|
||||
try:
|
||||
return self.get_children().reverse()[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def get_first_sibling(self):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
The leftmost node's sibling, can return the node itself if
|
||||
it was the leftmost sibling.
|
||||
"""
|
||||
return self.get_siblings()[0]
|
||||
|
||||
def get_last_sibling(self):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
The rightmost node's sibling, can return the node itself if
|
||||
it was the rightmost sibling.
|
||||
"""
|
||||
return self.get_siblings().reverse()[0]
|
||||
|
||||
def get_prev_sibling(self):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
The previous node's sibling, or None if it was the leftmost
|
||||
sibling.
|
||||
"""
|
||||
siblings = self.get_siblings()
|
||||
ids = [obj.pk for obj in siblings]
|
||||
if self.pk in ids:
|
||||
idx = ids.index(self.pk)
|
||||
if idx > 0:
|
||||
return siblings[idx - 1]
|
||||
|
||||
def get_next_sibling(self):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
The next node's sibling, or None if it was the rightmost
|
||||
sibling.
|
||||
"""
|
||||
siblings = self.get_siblings()
|
||||
ids = [obj.pk for obj in siblings]
|
||||
if self.pk in ids:
|
||||
idx = ids.index(self.pk)
|
||||
if idx < len(siblings) - 1:
|
||||
return siblings[idx + 1]
|
||||
|
||||
def is_sibling_of(self, node):
|
||||
"""
|
||||
:returns: ``True`` if the node is a sibling of another node given as an
|
||||
argument, else, returns ``False``
|
||||
|
||||
:param node:
|
||||
|
||||
The node that will be checked as a sibling
|
||||
"""
|
||||
return self.get_siblings().filter(pk=node.pk).exists()
|
||||
|
||||
def is_child_of(self, node):
|
||||
"""
|
||||
:returns: ``True`` if the node is a child of another node given as an
|
||||
argument, else, returns ``False``
|
||||
|
||||
:param node:
|
||||
|
||||
The node that will be checked as a parent
|
||||
"""
|
||||
return node.get_children().filter(pk=self.pk).exists()
|
||||
|
||||
def is_descendant_of(self, node): # pragma: no cover
|
||||
"""
|
||||
:returns: ``True`` if the node is a descendant of another node given
|
||||
as an argument, else, returns ``False``
|
||||
|
||||
:param node:
|
||||
|
||||
The node that will be checked as an ancestor
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def add_child(self, **kwargs): # pragma: no cover
|
||||
"""
|
||||
Adds a child to the node. The new node will be the new rightmost
|
||||
child. If you want to insert a node at a specific position,
|
||||
use the :meth:`add_sibling` method of an already existing
|
||||
child node instead.
|
||||
|
||||
:param \*\*kwargs:
|
||||
|
||||
Object creation data that will be passed to the inherited Node
|
||||
model
|
||||
|
||||
:returns: The created node object. It will be save()d by this method.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def add_sibling(self, pos=None, **kwargs): # pragma: no cover
|
||||
"""
|
||||
Adds a new node as a sibling to the current node object.
|
||||
|
||||
|
||||
:param pos:
|
||||
The position, relative to the current node object, where the
|
||||
new node will be inserted, can be one of:
|
||||
|
||||
- ``first-sibling``: the new node will be the new leftmost sibling
|
||||
- ``left``: the new node will take the node's place, which will be
|
||||
moved to the right 1 position
|
||||
- ``right``: the new node will be inserted at the right of the node
|
||||
- ``last-sibling``: the new node will be the new rightmost sibling
|
||||
- ``sorted-sibling``: the new node will be at the right position
|
||||
according to the value of node_order_by
|
||||
|
||||
:param \*\*kwargs:
|
||||
|
||||
Object creation data that will be passed to the inherited
|
||||
Node model
|
||||
|
||||
:returns:
|
||||
|
||||
The created node object. It will be saved by this method.
|
||||
|
||||
:raise InvalidPosition: when passing an invalid ``pos`` parm
|
||||
:raise InvalidPosition: when :attr:`node_order_by` is enabled and the
|
||||
``pos`` parm wasn't ``sorted-sibling``
|
||||
:raise MissingNodeOrderBy: when passing ``sorted-sibling`` as ``pos``
|
||||
and the :attr:`node_order_by` attribute is missing
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_root(self): # pragma: no cover
|
||||
""":returns: the root node for the current node object."""
|
||||
raise NotImplementedError
|
||||
|
||||
def is_root(self):
|
||||
""":returns: True if the node is a root node (else, returns False)"""
|
||||
return self.get_root().pk == self.pk
|
||||
|
||||
def is_leaf(self):
|
||||
""":returns: True if the node is a leaf node (else, returns False)"""
|
||||
return not self.get_children().exists()
|
||||
|
||||
def get_ancestors(self): # pragma: no cover
|
||||
"""
|
||||
:returns:
|
||||
|
||||
A queryset containing the current node object's ancestors,
|
||||
starting by the root node and descending to the parent.
|
||||
(some subclasses may return a list)
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_parent(self, update=False): # pragma: no cover
|
||||
"""
|
||||
:returns: the parent node of the current node object.
|
||||
Caches the result in the object itself to help in loops.
|
||||
|
||||
:param update: Updates de cached value.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def move(self, target, pos=None): # pragma: no cover
|
||||
"""
|
||||
Moves the current node and all it's descendants to a new position
|
||||
relative to another node.
|
||||
|
||||
:param target:
|
||||
|
||||
The node that will be used as a relative child/sibling when moving
|
||||
|
||||
:param pos:
|
||||
|
||||
The position, relative to the target node, where the
|
||||
current node object will be moved to, can be one of:
|
||||
|
||||
- ``first-child``: the node will be the new leftmost child of the
|
||||
``target`` node
|
||||
- ``last-child``: the node will be the new rightmost child of the
|
||||
``target`` node
|
||||
- ``sorted-child``: the new node will be moved as a child of the
|
||||
``target`` node according to the value of :attr:`node_order_by`
|
||||
- ``first-sibling``: the node will be the new leftmost sibling of
|
||||
the ``target`` node
|
||||
- ``left``: the node will take the ``target`` node's place, which
|
||||
will be moved to the right 1 position
|
||||
- ``right``: the node will be moved to the right of the ``target``
|
||||
node
|
||||
- ``last-sibling``: the node will be the new rightmost sibling of
|
||||
the ``target`` node
|
||||
- ``sorted-sibling``: the new node will be moved as a sibling of
|
||||
the ``target`` node according to the value of
|
||||
:attr:`node_order_by`
|
||||
|
||||
.. note::
|
||||
|
||||
If no ``pos`` is given the library will use ``last-sibling``,
|
||||
or ``sorted-sibling`` if :attr:`node_order_by` is enabled.
|
||||
|
||||
:returns: None
|
||||
|
||||
:raise InvalidPosition: when passing an invalid ``pos`` parm
|
||||
:raise InvalidPosition: when :attr:`node_order_by` is enabled and the
|
||||
``pos`` parm wasn't ``sorted-sibling`` or ``sorted-child``
|
||||
:raise InvalidMoveToDescendant: when trying to move a node to one of
|
||||
it's own descendants
|
||||
:raise PathOverflow: when the library can't make room for the
|
||||
node's new position
|
||||
:raise MissingNodeOrderBy: when passing ``sorted-sibling`` or
|
||||
``sorted-child`` as ``pos`` and the :attr:`node_order_by`
|
||||
attribute is missing
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def delete(self):
|
||||
"""Removes a node and all it's descendants."""
|
||||
self.__class__.objects.filter(id=self.pk).delete()
|
||||
|
||||
def _prepare_pos_var(self, pos, method_name, valid_pos, valid_sorted_pos):
|
||||
if pos is None:
|
||||
if self.node_order_by:
|
||||
pos = 'sorted-sibling'
|
||||
else:
|
||||
pos = 'last-sibling'
|
||||
if pos not in valid_pos:
|
||||
raise InvalidPosition('Invalid relative position: %s' % (pos, ))
|
||||
if self.node_order_by and pos not in valid_sorted_pos:
|
||||
raise InvalidPosition(
|
||||
'Must use %s in %s when node_order_by is enabled' % (
|
||||
' or '.join(valid_sorted_pos), method_name))
|
||||
if pos in valid_sorted_pos and not self.node_order_by:
|
||||
raise MissingNodeOrderBy('Missing node_order_by attribute.')
|
||||
return pos
|
||||
|
||||
_valid_pos_for_add_sibling = ('first-sibling', 'left', 'right',
|
||||
'last-sibling', 'sorted-sibling')
|
||||
_valid_pos_for_sorted_add_sibling = ('sorted-sibling',)
|
||||
|
||||
def _prepare_pos_var_for_add_sibling(self, pos):
|
||||
return self._prepare_pos_var(
|
||||
pos,
|
||||
'add_sibling',
|
||||
self._valid_pos_for_add_sibling,
|
||||
self._valid_pos_for_sorted_add_sibling)
|
||||
|
||||
_valid_pos_for_move = _valid_pos_for_add_sibling + (
|
||||
'first-child', 'last-child', 'sorted-child')
|
||||
_valid_pos_for_sorted_move = _valid_pos_for_sorted_add_sibling + (
|
||||
'sorted-child',)
|
||||
|
||||
def _prepare_pos_var_for_move(self, pos):
|
||||
return self._prepare_pos_var(
|
||||
pos,
|
||||
'move',
|
||||
self._valid_pos_for_move,
|
||||
self._valid_pos_for_sorted_move)
|
||||
|
||||
def get_sorted_pos_queryset(self, siblings, newobj):
|
||||
"""
|
||||
:returns: A queryset of the nodes that must be moved
|
||||
to the right. Called only for Node models with :attr:`node_order_by`
|
||||
|
||||
This function is based on _insertion_target_filters from django-mptt
|
||||
(BSD licensed) by Jonathan Buchanan:
|
||||
https://github.com/django-mptt/django-mptt/blob/0.3.0/mptt/signals.py
|
||||
"""
|
||||
|
||||
fields, filters = [], []
|
||||
for field in self.node_order_by:
|
||||
value = getattr(newobj, field)
|
||||
filters.append(
|
||||
Q(
|
||||
*[Q(**{f: v}) for f, v in fields] +
|
||||
[Q(**{'%s__gt' % field: value})]
|
||||
)
|
||||
)
|
||||
fields.append((field, value))
|
||||
return siblings.filter(reduce(operator.or_, filters))
|
||||
|
||||
@classmethod
|
||||
def get_annotated_list(cls, parent=None):
|
||||
"""
|
||||
Gets an annotated list from a tree branch.
|
||||
|
||||
:param parent:
|
||||
|
||||
The node whose descendants will be annotated. The node itself
|
||||
will be included in the list. If not given, the entire tree
|
||||
will be annotated.
|
||||
"""
|
||||
|
||||
result, info = [], {}
|
||||
start_depth, prev_depth = (None, None)
|
||||
for node in cls.get_tree(parent):
|
||||
depth = node.get_depth()
|
||||
if start_depth is None:
|
||||
start_depth = depth
|
||||
open = (depth and (prev_depth is None or depth > prev_depth))
|
||||
if prev_depth is not None and depth < prev_depth:
|
||||
info['close'] = list(range(0, prev_depth - depth))
|
||||
info = {'open': open, 'close': [], 'level': depth - start_depth}
|
||||
result.append((node, info,))
|
||||
prev_depth = depth
|
||||
if start_depth and start_depth > 0:
|
||||
info['close'] = list(range(0, prev_depth - start_depth + 1))
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def _get_serializable_model(cls):
|
||||
"""
|
||||
Returns a model with a valid _meta.local_fields (serializable).
|
||||
|
||||
Basically, this means the original model, not a proxied model.
|
||||
|
||||
(this is a workaround for a bug in django)
|
||||
"""
|
||||
current_class = cls
|
||||
while current_class._meta.proxy:
|
||||
current_class = current_class._meta.proxy_for_model
|
||||
return current_class
|
||||
|
||||
@classmethod
|
||||
def _get_database_connection(cls, action):
|
||||
return {
|
||||
'read': connections[router.db_for_read(cls)],
|
||||
'write': connections[router.db_for_write(cls)]
|
||||
}[action]
|
||||
|
||||
@classmethod
|
||||
def get_database_vendor(cls, action):
|
||||
"""
|
||||
returns the supported database vendor used by a treebeard model when
|
||||
performing read (select) or write (update, insert, delete) operations.
|
||||
|
||||
:param action:
|
||||
|
||||
`read` or `write`
|
||||
|
||||
:returns: postgresql, mysql or sqlite
|
||||
"""
|
||||
return cls._get_database_connection(action).vendor
|
||||
|
||||
@classmethod
|
||||
def _get_database_cursor(cls, action):
|
||||
return cls._get_database_connection(action).cursor()
|
||||
|
||||
class Meta:
|
||||
"""Abstract model."""
|
||||
abstract = True
|
||||
1060
wagtail/vendor/django-treebeard/treebeard/mp_tree.py
vendored
Normal file
1060
wagtail/vendor/django-treebeard/treebeard/mp_tree.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
623
wagtail/vendor/django-treebeard/treebeard/ns_tree.py
vendored
Normal file
623
wagtail/vendor/django-treebeard/treebeard/ns_tree.py
vendored
Normal file
|
|
@ -0,0 +1,623 @@
|
|||
"""Nested Sets"""
|
||||
|
||||
import sys
|
||||
import operator
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
from functools import reduce
|
||||
|
||||
from django.core import serializers
|
||||
from django.db import connection, models, transaction
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_noop as _
|
||||
|
||||
from treebeard.exceptions import InvalidMoveToDescendant
|
||||
from treebeard.models import Node
|
||||
|
||||
|
||||
class NS_NodeQuerySet(models.query.QuerySet):
|
||||
"""
|
||||
Custom queryset for the tree node manager.
|
||||
|
||||
Needed only for the customized delete method.
|
||||
"""
|
||||
|
||||
def delete(self, removed_ranges=None):
|
||||
"""
|
||||
Custom delete method, will remove all descendant nodes to ensure a
|
||||
consistent tree (no orphans)
|
||||
|
||||
:returns: ``None``
|
||||
"""
|
||||
if removed_ranges is not None:
|
||||
# we already know the children, let's call the default django
|
||||
# delete method and let it handle the removal of the user's
|
||||
# foreign keys...
|
||||
super(NS_NodeQuerySet, self).delete()
|
||||
cursor = self.model._get_database_cursor('write')
|
||||
|
||||
# Now closing the gap (Celko's trees book, page 62)
|
||||
# We do this for every gap that was left in the tree when the nodes
|
||||
# were removed. If many nodes were removed, we're going to update
|
||||
# the same nodes over and over again. This would be probably
|
||||
# cheaper precalculating the gapsize per intervals, or just do a
|
||||
# complete reordering of the tree (uses COUNT)...
|
||||
for tree_id, drop_lft, drop_rgt in sorted(removed_ranges,
|
||||
reverse=True):
|
||||
sql, params = self.model._get_close_gap_sql(drop_lft, drop_rgt,
|
||||
tree_id)
|
||||
cursor.execute(sql, params)
|
||||
else:
|
||||
# we'll have to manually run through all the nodes that are going
|
||||
# to be deleted and remove nodes from the list if an ancestor is
|
||||
# already getting removed, since that would be redundant
|
||||
removed = {}
|
||||
for node in self.order_by('tree_id', 'lft'):
|
||||
found = False
|
||||
for rid, rnode in removed.items():
|
||||
if node.is_descendant_of(rnode):
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
removed[node.pk] = node
|
||||
|
||||
# ok, got the minimal list of nodes to remove...
|
||||
# we must also remove their descendants
|
||||
toremove = []
|
||||
ranges = []
|
||||
for id, node in removed.items():
|
||||
toremove.append(Q(lft__range=(node.lft, node.rgt)) &
|
||||
Q(tree_id=node.tree_id))
|
||||
ranges.append((node.tree_id, node.lft, node.rgt))
|
||||
if toremove:
|
||||
self.model.objects.filter(
|
||||
reduce(operator.or_,
|
||||
toremove)
|
||||
).delete(removed_ranges=ranges)
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
|
||||
class NS_NodeManager(models.Manager):
|
||||
"""Custom manager for nodes in a Nested Sets tree."""
|
||||
|
||||
def get_query_set(self):
|
||||
"""Sets the custom queryset as the default."""
|
||||
return NS_NodeQuerySet(self.model).order_by('tree_id', 'lft')
|
||||
|
||||
|
||||
class NS_Node(Node):
|
||||
"""Abstract model to create your own Nested Sets Trees."""
|
||||
node_order_by = []
|
||||
|
||||
lft = models.PositiveIntegerField(db_index=True)
|
||||
rgt = models.PositiveIntegerField(db_index=True)
|
||||
tree_id = models.PositiveIntegerField(db_index=True)
|
||||
depth = models.PositiveIntegerField(db_index=True)
|
||||
|
||||
objects = NS_NodeManager()
|
||||
|
||||
@classmethod
|
||||
def add_root(cls, **kwargs):
|
||||
"""Adds a root node to the tree."""
|
||||
|
||||
# do we have a root node already?
|
||||
last_root = cls.get_last_root_node()
|
||||
|
||||
if last_root and last_root.node_order_by:
|
||||
# there are root nodes and node_order_by has been set
|
||||
# delegate sorted insertion to add_sibling
|
||||
return last_root.add_sibling('sorted-sibling', **kwargs)
|
||||
|
||||
if last_root:
|
||||
# adding the new root node as the last one
|
||||
newtree_id = last_root.tree_id + 1
|
||||
else:
|
||||
# adding the first root node
|
||||
newtree_id = 1
|
||||
|
||||
# creating the new object
|
||||
newobj = cls(**kwargs)
|
||||
newobj.depth = 1
|
||||
newobj.tree_id = newtree_id
|
||||
newobj.lft = 1
|
||||
newobj.rgt = 2
|
||||
# saving the instance before returning it
|
||||
newobj.save()
|
||||
transaction.commit_unless_managed()
|
||||
return newobj
|
||||
|
||||
@classmethod
|
||||
def _move_right(cls, tree_id, rgt, lftmove=False, incdec=2):
|
||||
if lftmove:
|
||||
lftop = '>='
|
||||
else:
|
||||
lftop = '>'
|
||||
sql = 'UPDATE %(table)s '\
|
||||
' SET lft = CASE WHEN lft %(lftop)s %(parent_rgt)d '\
|
||||
' THEN lft %(incdec)+d '\
|
||||
' ELSE lft END, '\
|
||||
' rgt = CASE WHEN rgt >= %(parent_rgt)d '\
|
||||
' THEN rgt %(incdec)+d '\
|
||||
' ELSE rgt END '\
|
||||
' WHERE rgt >= %(parent_rgt)d AND '\
|
||||
' tree_id = %(tree_id)s' % {
|
||||
'table': connection.ops.quote_name(cls._meta.db_table),
|
||||
'parent_rgt': rgt,
|
||||
'tree_id': tree_id,
|
||||
'lftop': lftop,
|
||||
'incdec': incdec}
|
||||
return sql, []
|
||||
|
||||
@classmethod
|
||||
def _move_tree_right(cls, tree_id):
|
||||
sql = 'UPDATE %(table)s '\
|
||||
' SET tree_id = tree_id+1 '\
|
||||
' WHERE tree_id >= %(tree_id)d' % {
|
||||
'table': connection.ops.quote_name(cls._meta.db_table),
|
||||
'tree_id': tree_id}
|
||||
return sql, []
|
||||
|
||||
def add_child(self, **kwargs):
|
||||
"""Adds a child to the node."""
|
||||
if not self.is_leaf():
|
||||
# there are child nodes, delegate insertion to add_sibling
|
||||
if self.node_order_by:
|
||||
pos = 'sorted-sibling'
|
||||
else:
|
||||
pos = 'last-sibling'
|
||||
last_child = self.get_last_child()
|
||||
last_child._cached_parent_obj = self
|
||||
return last_child.add_sibling(pos, **kwargs)
|
||||
|
||||
# we're adding the first child of this node
|
||||
sql, params = self.__class__._move_right(self.tree_id,
|
||||
self.rgt, False, 2)
|
||||
|
||||
# creating a new object
|
||||
newobj = self.__class__(**kwargs)
|
||||
newobj.tree_id = self.tree_id
|
||||
newobj.depth = self.depth + 1
|
||||
newobj.lft = self.lft + 1
|
||||
newobj.rgt = self.lft + 2
|
||||
|
||||
# this is just to update the cache
|
||||
self.rgt += 2
|
||||
|
||||
newobj._cached_parent_obj = self
|
||||
|
||||
cursor = self._get_database_cursor('write')
|
||||
cursor.execute(sql, params)
|
||||
|
||||
# saving the instance before returning it
|
||||
newobj.save()
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
return newobj
|
||||
|
||||
def add_sibling(self, pos=None, **kwargs):
|
||||
"""Adds a new node as a sibling to the current node object."""
|
||||
|
||||
pos = self._prepare_pos_var_for_add_sibling(pos)
|
||||
|
||||
# creating a new object
|
||||
newobj = self.__class__(**kwargs)
|
||||
newobj.depth = self.depth
|
||||
|
||||
sql = None
|
||||
target = self
|
||||
|
||||
if target.is_root():
|
||||
newobj.lft = 1
|
||||
newobj.rgt = 2
|
||||
if pos == 'sorted-sibling':
|
||||
siblings = list(target.get_sorted_pos_queryset(
|
||||
target.get_siblings(), newobj))
|
||||
if siblings:
|
||||
pos = 'left'
|
||||
target = siblings[0]
|
||||
else:
|
||||
pos = 'last-sibling'
|
||||
|
||||
last_root = target.__class__.get_last_root_node()
|
||||
if (
|
||||
(pos == 'last-sibling') or
|
||||
(pos == 'right' and target == last_root)
|
||||
):
|
||||
newobj.tree_id = last_root.tree_id + 1
|
||||
else:
|
||||
newpos = {'first-sibling': 1,
|
||||
'left': target.tree_id,
|
||||
'right': target.tree_id + 1}[pos]
|
||||
sql, params = target.__class__._move_tree_right(newpos)
|
||||
|
||||
newobj.tree_id = newpos
|
||||
else:
|
||||
newobj.tree_id = target.tree_id
|
||||
|
||||
if pos == 'sorted-sibling':
|
||||
siblings = list(target.get_sorted_pos_queryset(
|
||||
target.get_siblings(), newobj))
|
||||
if siblings:
|
||||
pos = 'left'
|
||||
target = siblings[0]
|
||||
else:
|
||||
pos = 'last-sibling'
|
||||
|
||||
if pos in ('left', 'right', 'first-sibling'):
|
||||
siblings = list(target.get_siblings())
|
||||
|
||||
if pos == 'right':
|
||||
if target == siblings[-1]:
|
||||
pos = 'last-sibling'
|
||||
else:
|
||||
pos = 'left'
|
||||
found = False
|
||||
for node in siblings:
|
||||
if found:
|
||||
target = node
|
||||
break
|
||||
elif node == target:
|
||||
found = True
|
||||
if pos == 'left':
|
||||
if target == siblings[0]:
|
||||
pos = 'first-sibling'
|
||||
if pos == 'first-sibling':
|
||||
target = siblings[0]
|
||||
|
||||
move_right = self.__class__._move_right
|
||||
|
||||
if pos == 'last-sibling':
|
||||
newpos = target.get_parent().rgt
|
||||
sql, params = move_right(target.tree_id, newpos, False, 2)
|
||||
elif pos == 'first-sibling':
|
||||
newpos = target.lft
|
||||
sql, params = move_right(target.tree_id, newpos - 1, False, 2)
|
||||
elif pos == 'left':
|
||||
newpos = target.lft
|
||||
sql, params = move_right(target.tree_id, newpos, True, 2)
|
||||
|
||||
newobj.lft = newpos
|
||||
newobj.rgt = newpos + 1
|
||||
|
||||
# saving the instance before returning it
|
||||
if sql:
|
||||
cursor = self._get_database_cursor('write')
|
||||
cursor.execute(sql, params)
|
||||
newobj.save()
|
||||
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
return newobj
|
||||
|
||||
def move(self, target, pos=None):
|
||||
"""
|
||||
Moves the current node and all it's descendants to a new position
|
||||
relative to another node.
|
||||
"""
|
||||
|
||||
pos = self._prepare_pos_var_for_move(pos)
|
||||
cls = self.__class__
|
||||
|
||||
parent = None
|
||||
|
||||
if pos in ('first-child', 'last-child', 'sorted-child'):
|
||||
# moving to a child
|
||||
if target.is_leaf():
|
||||
parent = target
|
||||
pos = 'last-child'
|
||||
else:
|
||||
target = target.get_last_child()
|
||||
pos = {'first-child': 'first-sibling',
|
||||
'last-child': 'last-sibling',
|
||||
'sorted-child': 'sorted-sibling'}[pos]
|
||||
|
||||
if target.is_descendant_of(self):
|
||||
raise InvalidMoveToDescendant(
|
||||
_("Can't move node to a descendant."))
|
||||
|
||||
if self == target and (
|
||||
(pos == 'left') or
|
||||
(pos in ('right', 'last-sibling') and
|
||||
target == target.get_last_sibling()) or
|
||||
(pos == 'first-sibling' and
|
||||
target == target.get_first_sibling())):
|
||||
# special cases, not actually moving the node so no need to UPDATE
|
||||
return
|
||||
|
||||
if pos == 'sorted-sibling':
|
||||
siblings = list(target.get_sorted_pos_queryset(
|
||||
target.get_siblings(), self))
|
||||
if siblings:
|
||||
pos = 'left'
|
||||
target = siblings[0]
|
||||
else:
|
||||
pos = 'last-sibling'
|
||||
if pos in ('left', 'right', 'first-sibling'):
|
||||
siblings = list(target.get_siblings())
|
||||
|
||||
if pos == 'right':
|
||||
if target == siblings[-1]:
|
||||
pos = 'last-sibling'
|
||||
else:
|
||||
pos = 'left'
|
||||
found = False
|
||||
for node in siblings:
|
||||
if found:
|
||||
target = node
|
||||
break
|
||||
elif node == target:
|
||||
found = True
|
||||
if pos == 'left':
|
||||
if target == siblings[0]:
|
||||
pos = 'first-sibling'
|
||||
if pos == 'first-sibling':
|
||||
target = siblings[0]
|
||||
|
||||
# ok let's move this
|
||||
cursor = self._get_database_cursor('write')
|
||||
move_right = cls._move_right
|
||||
gap = self.rgt - self.lft + 1
|
||||
sql = None
|
||||
target_tree = target.tree_id
|
||||
|
||||
# first make a hole
|
||||
if pos == 'last-child':
|
||||
newpos = parent.rgt
|
||||
sql, params = move_right(target.tree_id, newpos, False, gap)
|
||||
elif target.is_root():
|
||||
newpos = 1
|
||||
if pos == 'last-sibling':
|
||||
target_tree = target.get_siblings().reverse()[0].tree_id + 1
|
||||
elif pos == 'first-sibling':
|
||||
target_tree = 1
|
||||
sql, params = cls._move_tree_right(1)
|
||||
elif pos == 'left':
|
||||
sql, params = cls._move_tree_right(target.tree_id)
|
||||
else:
|
||||
if pos == 'last-sibling':
|
||||
newpos = target.get_parent().rgt
|
||||
sql, params = move_right(target.tree_id, newpos, False, gap)
|
||||
elif pos == 'first-sibling':
|
||||
newpos = target.lft
|
||||
sql, params = move_right(target.tree_id,
|
||||
newpos - 1, False, gap)
|
||||
elif pos == 'left':
|
||||
newpos = target.lft
|
||||
sql, params = move_right(target.tree_id, newpos, True, gap)
|
||||
|
||||
if sql:
|
||||
cursor.execute(sql, params)
|
||||
|
||||
# we reload 'self' because lft/rgt may have changed
|
||||
|
||||
fromobj = cls.objects.get(pk=self.pk)
|
||||
|
||||
depthdiff = target.depth - fromobj.depth
|
||||
if parent:
|
||||
depthdiff += 1
|
||||
|
||||
# move the tree to the hole
|
||||
sql = "UPDATE %(table)s "\
|
||||
" SET tree_id = %(target_tree)d, "\
|
||||
" lft = lft + %(jump)d , "\
|
||||
" rgt = rgt + %(jump)d , "\
|
||||
" depth = depth + %(depthdiff)d "\
|
||||
" WHERE tree_id = %(from_tree)d AND "\
|
||||
" lft BETWEEN %(fromlft)d AND %(fromrgt)d" % {
|
||||
'table': connection.ops.quote_name(cls._meta.db_table),
|
||||
'from_tree': fromobj.tree_id,
|
||||
'target_tree': target_tree,
|
||||
'jump': newpos - fromobj.lft,
|
||||
'depthdiff': depthdiff,
|
||||
'fromlft': fromobj.lft,
|
||||
'fromrgt': fromobj.rgt}
|
||||
cursor.execute(sql, [])
|
||||
|
||||
# close the gap
|
||||
sql, params = cls._get_close_gap_sql(fromobj.lft,
|
||||
fromobj.rgt, fromobj.tree_id)
|
||||
cursor.execute(sql, params)
|
||||
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
@classmethod
|
||||
def _get_close_gap_sql(cls, drop_lft, drop_rgt, tree_id):
|
||||
sql = 'UPDATE %(table)s '\
|
||||
' SET lft = CASE '\
|
||||
' WHEN lft > %(drop_lft)d '\
|
||||
' THEN lft - %(gapsize)d '\
|
||||
' ELSE lft END, '\
|
||||
' rgt = CASE '\
|
||||
' WHEN rgt > %(drop_lft)d '\
|
||||
' THEN rgt - %(gapsize)d '\
|
||||
' ELSE rgt END '\
|
||||
' WHERE (lft > %(drop_lft)d '\
|
||||
' OR rgt > %(drop_lft)d) AND '\
|
||||
' tree_id=%(tree_id)d' % {
|
||||
'table': connection.ops.quote_name(cls._meta.db_table),
|
||||
'gapsize': drop_rgt - drop_lft + 1,
|
||||
'drop_lft': drop_lft,
|
||||
'tree_id': tree_id}
|
||||
return sql, []
|
||||
|
||||
@classmethod
|
||||
def load_bulk(cls, bulk_data, parent=None, keep_ids=False):
|
||||
"""Loads a list/dictionary structure to the tree."""
|
||||
|
||||
# tree, iterative preorder
|
||||
added = []
|
||||
if parent:
|
||||
parent_id = parent.pk
|
||||
else:
|
||||
parent_id = None
|
||||
# stack of nodes to analize
|
||||
stack = [(parent_id, node) for node in bulk_data[::-1]]
|
||||
foreign_keys = cls.get_foreign_keys()
|
||||
while stack:
|
||||
parent_id, node_struct = stack.pop()
|
||||
# shallow copy of the data strucure so it doesn't persist...
|
||||
node_data = node_struct['data'].copy()
|
||||
cls._process_foreign_keys(foreign_keys, node_data)
|
||||
if keep_ids:
|
||||
node_data['id'] = node_struct['id']
|
||||
if parent_id:
|
||||
parent = cls.objects.get(pk=parent_id)
|
||||
node_obj = parent.add_child(**node_data)
|
||||
else:
|
||||
node_obj = cls.add_root(**node_data)
|
||||
added.append(node_obj.pk)
|
||||
if 'children' in node_struct:
|
||||
# extending the stack with the current node as the parent of
|
||||
# the new nodes
|
||||
stack.extend([
|
||||
(node_obj.pk, node)
|
||||
for node in node_struct['children'][::-1]
|
||||
])
|
||||
transaction.commit_unless_managed()
|
||||
return added
|
||||
|
||||
def get_children(self):
|
||||
""":returns: A queryset of all the node's children"""
|
||||
return self.get_descendants().filter(depth=self.depth + 1)
|
||||
|
||||
def get_depth(self):
|
||||
""":returns: the depth (level) of the node"""
|
||||
return self.depth
|
||||
|
||||
def is_leaf(self):
|
||||
""":returns: True if the node is a leaf node (else, returns False)"""
|
||||
return self.rgt - self.lft == 1
|
||||
|
||||
def get_root(self):
|
||||
""":returns: the root node for the current node object."""
|
||||
if self.lft == 1:
|
||||
return self
|
||||
return self.__class__.objects.get(tree_id=self.tree_id, lft=1)
|
||||
|
||||
def is_root(self):
|
||||
""":returns: True if the node is a root node (else, returns False)"""
|
||||
return self.lft == 1
|
||||
|
||||
def get_siblings(self):
|
||||
"""
|
||||
:returns: A queryset of all the node's siblings, including the node
|
||||
itself.
|
||||
"""
|
||||
if self.lft == 1:
|
||||
return self.get_root_nodes()
|
||||
return self.get_parent(True).get_children()
|
||||
|
||||
@classmethod
|
||||
def dump_bulk(cls, parent=None, keep_ids=True):
|
||||
"""Dumps a tree branch to a python data structure."""
|
||||
qset = cls._get_serializable_model().get_tree(parent)
|
||||
ret, lnk = [], {}
|
||||
for pyobj in qset:
|
||||
serobj = serializers.serialize('python', [pyobj])[0]
|
||||
# django's serializer stores the attributes in 'fields'
|
||||
fields = serobj['fields']
|
||||
depth = fields['depth']
|
||||
# this will be useless in load_bulk
|
||||
del fields['lft']
|
||||
del fields['rgt']
|
||||
del fields['depth']
|
||||
del fields['tree_id']
|
||||
if 'id' in fields:
|
||||
# this happens immediately after a load_bulk
|
||||
del fields['id']
|
||||
|
||||
newobj = {'data': fields}
|
||||
if keep_ids:
|
||||
newobj['id'] = serobj['pk']
|
||||
|
||||
if (not parent and depth == 1) or\
|
||||
(parent and depth == parent.depth):
|
||||
ret.append(newobj)
|
||||
else:
|
||||
parentobj = pyobj.get_parent()
|
||||
parentser = lnk[parentobj.pk]
|
||||
if 'children' not in parentser:
|
||||
parentser['children'] = []
|
||||
parentser['children'].append(newobj)
|
||||
lnk[pyobj.pk] = newobj
|
||||
return ret
|
||||
|
||||
@classmethod
|
||||
def get_tree(cls, parent=None):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
A *queryset* of nodes ordered as DFS, including the parent.
|
||||
If no parent is given, all trees are returned.
|
||||
"""
|
||||
if parent is None:
|
||||
# return the entire tree
|
||||
return cls.objects.all()
|
||||
if parent.is_leaf():
|
||||
return cls.objects.filter(pk=parent.pk)
|
||||
return cls.objects.filter(
|
||||
tree_id=parent.tree_id,
|
||||
lft__range=(parent.lft, parent.rgt - 1))
|
||||
|
||||
def get_descendants(self):
|
||||
"""
|
||||
:returns: A queryset of all the node's descendants as DFS, doesn't
|
||||
include the node itself
|
||||
"""
|
||||
if self.is_leaf():
|
||||
return self.__class__.objects.none()
|
||||
return self.__class__.get_tree(self).exclude(pk=self.pk)
|
||||
|
||||
def get_descendant_count(self):
|
||||
""":returns: the number of descendants of a node."""
|
||||
return (self.rgt - self.lft - 1) / 2
|
||||
|
||||
def get_ancestors(self):
|
||||
"""
|
||||
:returns: A queryset containing the current node object's ancestors,
|
||||
starting by the root node and descending to the parent.
|
||||
"""
|
||||
if self.is_root():
|
||||
return self.__class__.objects.none()
|
||||
return self.__class__.objects.filter(
|
||||
tree_id=self.tree_id,
|
||||
lft__lt=self.lft,
|
||||
rgt__gt=self.rgt)
|
||||
|
||||
def is_descendant_of(self, node):
|
||||
"""
|
||||
:returns: ``True`` if the node if a descendant of another node given
|
||||
as an argument, else, returns ``False``
|
||||
"""
|
||||
return (
|
||||
self.tree_id == node.tree_id and
|
||||
self.lft > node.lft and
|
||||
self.rgt < node.rgt
|
||||
)
|
||||
|
||||
def get_parent(self, update=False):
|
||||
"""
|
||||
:returns: the parent node of the current node object.
|
||||
Caches the result in the object itself to help in loops.
|
||||
"""
|
||||
if self.is_root():
|
||||
return
|
||||
try:
|
||||
if update:
|
||||
del self._cached_parent_obj
|
||||
else:
|
||||
return self._cached_parent_obj
|
||||
except AttributeError:
|
||||
pass
|
||||
# parent = our most direct ancestor
|
||||
self._cached_parent_obj = self.get_ancestors().reverse()[0]
|
||||
return self._cached_parent_obj
|
||||
|
||||
@classmethod
|
||||
def get_root_nodes(cls):
|
||||
""":returns: A queryset containing the root nodes in the tree."""
|
||||
return cls.objects.filter(lft=1)
|
||||
|
||||
class Meta:
|
||||
"""Abstract model."""
|
||||
abstract = True
|
||||
115
wagtail/vendor/django-treebeard/treebeard/numconv.py
vendored
Normal file
115
wagtail/vendor/django-treebeard/treebeard/numconv.py
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
"""Convert strings to numbers and numbers to strings.
|
||||
|
||||
Gustavo Picon
|
||||
https://tabo.pe/projects/numconv/
|
||||
|
||||
"""
|
||||
|
||||
|
||||
__version__ = '2.1.1'
|
||||
|
||||
# from april fool's rfc 1924
|
||||
BASE85 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' \
|
||||
'!#$%&()*+-;<=>?@^_`{|}~'
|
||||
|
||||
# rfc4648 alphabets
|
||||
BASE16 = BASE85[:16]
|
||||
BASE32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
BASE32HEX = BASE85[:32]
|
||||
BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
BASE64URL = BASE64[:62] + '-_'
|
||||
|
||||
# http://en.wikipedia.org/wiki/Base_62 useful for url shorteners
|
||||
BASE62 = BASE85[:62]
|
||||
|
||||
|
||||
class NumConv(object):
|
||||
"""Class to create converter objects.
|
||||
|
||||
:param radix: The base that will be used in the conversions.
|
||||
The default value is 10 for decimal conversions.
|
||||
:param alphabet: A string that will be used as a encoding alphabet.
|
||||
The length of the alphabet can be longer than the radix. In this
|
||||
case the alphabet will be internally truncated.
|
||||
|
||||
The default value is :data:`numconv.BASE85`
|
||||
|
||||
:raise TypeError: when *radix* isn't an integer
|
||||
:raise ValueError: when *radix* is invalid
|
||||
:raise ValueError: when *alphabet* has duplicated characters
|
||||
"""
|
||||
|
||||
def __init__(self, radix=10, alphabet=BASE85):
|
||||
"""basic validation and cached_map storage"""
|
||||
if int(radix) != radix:
|
||||
raise TypeError('radix must be an integer')
|
||||
if not 2 <= radix <= len(alphabet):
|
||||
raise ValueError('radix must be >= 2 and <= %d' % (
|
||||
len(alphabet), ))
|
||||
self.radix = radix
|
||||
self.alphabet = alphabet
|
||||
self.cached_map = dict(zip(self.alphabet, range(len(self.alphabet))))
|
||||
if len(self.cached_map) != len(self.alphabet):
|
||||
raise ValueError("duplicate characters found in '%s'" % (
|
||||
self.alphabet, ))
|
||||
|
||||
def int2str(self, num):
|
||||
"""Converts an integer into a string.
|
||||
|
||||
:param num: A numeric value to be converted to another base as a
|
||||
string.
|
||||
|
||||
:rtype: string
|
||||
|
||||
:raise TypeError: when *num* isn't an integer
|
||||
:raise ValueError: when *num* isn't positive
|
||||
"""
|
||||
if int(num) != num:
|
||||
raise TypeError('number must be an integer')
|
||||
if num < 0:
|
||||
raise ValueError('number must be positive')
|
||||
radix, alphabet = self.radix, self.alphabet
|
||||
if radix in (8, 10, 16) and \
|
||||
alphabet[:radix].lower() == BASE85[:radix].lower():
|
||||
return ({8: '%o', 10: '%d', 16: '%x'}[radix] % num).upper()
|
||||
ret = ''
|
||||
while True:
|
||||
ret = alphabet[num % radix] + ret
|
||||
if num < radix:
|
||||
break
|
||||
num //= radix
|
||||
return ret
|
||||
|
||||
def str2int(self, num):
|
||||
"""Converts a string into an integer.
|
||||
|
||||
If possible, the built-in python conversion will be used for speed
|
||||
purposes.
|
||||
|
||||
:param num: A string that will be converted to an integer.
|
||||
|
||||
:rtype: integer
|
||||
|
||||
:raise ValueError: when *num* is invalid
|
||||
"""
|
||||
radix, alphabet = self.radix, self.alphabet
|
||||
if radix <= 36 and alphabet[:radix].lower() == BASE85[:radix].lower():
|
||||
return int(num, radix)
|
||||
ret = 0
|
||||
lalphabet = alphabet[:radix]
|
||||
for char in num:
|
||||
if char not in lalphabet:
|
||||
raise ValueError("invalid literal for radix2int() with radix "
|
||||
"%d: '%s'" % (radix, num))
|
||||
ret = ret * radix + self.cached_map[char]
|
||||
return ret
|
||||
|
||||
|
||||
def int2str(num, radix=10, alphabet=BASE85):
|
||||
"""helper function for quick base conversions from integers to strings"""
|
||||
return NumConv(radix, alphabet).int2str(num)
|
||||
|
||||
|
||||
def str2int(num, radix=10, alphabet=BASE85):
|
||||
"""helper function for quick base conversions from strings to integers"""
|
||||
return NumConv(radix, alphabet).str2int(num)
|
||||
BIN
wagtail/vendor/django-treebeard/treebeard/static/treebeard/expand-collapse.png
vendored
Normal file
BIN
wagtail/vendor/django-treebeard/treebeard/static/treebeard/expand-collapse.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1,020 B |
1816
wagtail/vendor/django-treebeard/treebeard/static/treebeard/jquery-ui-1.8.5.custom.min.js
vendored
Normal file
1816
wagtail/vendor/django-treebeard/treebeard/static/treebeard/jquery-ui-1.8.5.custom.min.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
83
wagtail/vendor/django-treebeard/treebeard/static/treebeard/treebeard-admin.css
vendored
Normal file
83
wagtail/vendor/django-treebeard/treebeard/static/treebeard/treebeard-admin.css
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/* Treebeard Admin */
|
||||
|
||||
#roots {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#roots li {
|
||||
list-style: none;
|
||||
padding: 5px !important;
|
||||
line-height: 13px;
|
||||
border-bottom: 1px solid #EEE;
|
||||
}
|
||||
|
||||
#roots li a {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#roots li input {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.oder-grabber {
|
||||
width: 1.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.drag-handler span {
|
||||
width: 16px;
|
||||
background: transparent url(expand-collapse.png) no-repeat left -48px;
|
||||
height: 16px;
|
||||
margin: 0 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.drag-handler span.active {
|
||||
background: transparent url(expand-collapse.png) no-repeat left -32px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
width: 10px;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
text-indent: -999px;
|
||||
}
|
||||
|
||||
.collapsed {
|
||||
background: transparent url(expand-collapse.png) no-repeat left -16px;
|
||||
}
|
||||
|
||||
.expanded {
|
||||
background: transparent url(expand-collapse.png) no-repeat left 0;
|
||||
}
|
||||
|
||||
#drag_line {
|
||||
border-top: 5px solid #A0A;
|
||||
background: #A0A;
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#drag_line span {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100px;
|
||||
background: #FFD;
|
||||
color: #000;
|
||||
left: 100px;
|
||||
text-align: center;
|
||||
border: 1px solid #000;
|
||||
vertical-align: center;
|
||||
}
|
||||
|
||||
/*tr:target { I'm handling the highlight with js to have more control
|
||||
background-color: #FF0;
|
||||
}*/
|
||||
314
wagtail/vendor/django-treebeard/treebeard/static/treebeard/treebeard-admin.js
vendored
Normal file
314
wagtail/vendor/django-treebeard/treebeard/static/treebeard/treebeard-admin.js
vendored
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
(function ($) {
|
||||
// Ok, let's do eeet
|
||||
|
||||
ACTIVE_NODE_BG_COLOR = '#B7D7E8';
|
||||
RECENTLY_MOVED_COLOR = '#FFFF00';
|
||||
RECENTLY_MOVED_FADEOUT = '#FFFFFF';
|
||||
ABORT_COLOR = '#EECCCC';
|
||||
DRAG_LINE_COLOR = '#AA00AA';
|
||||
|
||||
RECENTLY_FADE_DURATION = 2000;
|
||||
|
||||
// This is the basic Node class, which handles UI tree operations for each 'row'
|
||||
var Node = function (elem) {
|
||||
var $elem = $(elem);
|
||||
var node_id = $elem.attr('node');
|
||||
var parent_id = $elem.attr('parent');
|
||||
var level = parseInt($elem.attr('level'));
|
||||
var children_num = parseInt($elem.attr('children-num'));
|
||||
return {
|
||||
elem: elem,
|
||||
$elem: $elem,
|
||||
node_id: node_id,
|
||||
parent_id: parent_id,
|
||||
level: level,
|
||||
has_children: function () {
|
||||
return children_num > 0;
|
||||
},
|
||||
node_name: function () {
|
||||
// Returns the text of the node
|
||||
return $elem.find('th a:not(.collapse)').text();
|
||||
},
|
||||
is_collapsed: function () {
|
||||
return $elem.find('a.collapse').hasClass('collapsed');
|
||||
},
|
||||
children: function () {
|
||||
return $('tr[parent=' + node_id + ']');
|
||||
},
|
||||
collapse: function () {
|
||||
// For each children, hide it's childrens and so on...
|
||||
$.each(this.children(),function () {
|
||||
var node = new Node(this);
|
||||
node.collapse();
|
||||
}).hide();
|
||||
// Swicth class to set the proprt expand/collapse icon
|
||||
$elem.find('a.collapse').removeClass('expanded').addClass('collapsed');
|
||||
},
|
||||
parent_node: function () {
|
||||
// Returns a Node object of the parent
|
||||
return new Node($('tr[node=' + parent_id + ']', $elem.parent())[0]);
|
||||
},
|
||||
expand: function () {
|
||||
// Display each kid (will display in collapsed state)
|
||||
this.children().show();
|
||||
// Swicth class to set the proprt expand/collapse icon
|
||||
$elem.find('a.collapse').removeClass('collapsed').addClass('expanded');
|
||||
|
||||
},
|
||||
toggle: function () {
|
||||
if (this.is_collapsed()) {
|
||||
this.expand();
|
||||
} else {
|
||||
this.collapse();
|
||||
}
|
||||
},
|
||||
clone: function () {
|
||||
return $elem.clone();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
// begin csrf token code
|
||||
// Taken from http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax
|
||||
$(document).ajaxSend(function (event, xhr, settings) {
|
||||
function getCookie(name) {
|
||||
var cookieValue = null;
|
||||
if (document.cookie && document.cookie != '') {
|
||||
var cookies = document.cookie.split(';');
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = jQuery.trim(cookies[i]);
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) == (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
|
||||
// Only send the token to relative URLs i.e. locally.
|
||||
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
|
||||
}
|
||||
});
|
||||
// end csrf token code
|
||||
|
||||
|
||||
// Don't activate drag or collapse if GET filters are set on the page
|
||||
if ($('#has-filters').val() === "1") {
|
||||
return;
|
||||
}
|
||||
|
||||
$body = $('body');
|
||||
|
||||
// Activate all rows for drag & drop
|
||||
// then bind mouse down event
|
||||
$('td.drag-handler span').addClass('active').bind('mousedown', function (evt) {
|
||||
$ghost = $('<div id="ghost"></div>');
|
||||
$drag_line = $('<div id="drag_line"><span></span></div>');
|
||||
$ghost.appendTo($body);
|
||||
$drag_line.appendTo($body);
|
||||
|
||||
var stop_drag = function () {
|
||||
$ghost.remove();
|
||||
$drag_line.remove();
|
||||
$body.enableSelection().unbind('mousemove').unbind('mouseup');
|
||||
node.elem.removeAttribute('style');
|
||||
};
|
||||
|
||||
// Create a clone create the illusion that we're moving the node
|
||||
var node = new Node($(this).closest('tr')[0]);
|
||||
cloned_node = node.clone();
|
||||
node.$elem.css({
|
||||
'background': ACTIVE_NODE_BG_COLOR
|
||||
});
|
||||
|
||||
$targetRow = null;
|
||||
as_child = false;
|
||||
|
||||
// Now make the new clone move with the mouse
|
||||
$body.disableSelection().bind('mousemove',function (evt2) {
|
||||
$ghost.html(cloned_node).css({ // from FeinCMS :P
|
||||
'opacity': .8,
|
||||
'position': 'absolute',
|
||||
'top': evt2.pageY,
|
||||
'left': evt2.pageX - 30,
|
||||
'width': 600
|
||||
});
|
||||
// Iterate through all rows and see where am I moving so I can place
|
||||
// the drag line accordingly
|
||||
rowHeight = node.$elem.height();
|
||||
$('tr', node.$elem.parent()).each(function (index, element) {
|
||||
$row = $(element);
|
||||
rtop = $row.offset().top;
|
||||
// The tooltop will display whether I'm droping the element as
|
||||
// child or sibling
|
||||
$tooltip = $drag_line.find('span');
|
||||
$tooltip.css({
|
||||
'left': node.$elem.width() - $tooltip.width(),
|
||||
'height': rowHeight,
|
||||
});
|
||||
node_top = node.$elem.offset().top;
|
||||
// Check if you are dragging over the same node
|
||||
if (evt2.pageY >= node_top && evt2.pageY <= node_top + rowHeight) {
|
||||
$targetRow = null;
|
||||
$tooltip.text(gettext('Abort'));
|
||||
$drag_line.css({
|
||||
'top': node_top,
|
||||
'height': rowHeight,
|
||||
'borderWidth': 0,
|
||||
'opacity': 0.8,
|
||||
'backgroundColor': ABORT_COLOR
|
||||
});
|
||||
} else
|
||||
// Check if mouse is over this row
|
||||
if (evt2.pageY >= rtop && evt2.pageY <= rtop + rowHeight / 2) {
|
||||
// The mouse is positioned on the top half of a $row
|
||||
$targetRow = $row;
|
||||
as_child = false;
|
||||
$drag_line.css({
|
||||
'left': node.$elem.offset().left,
|
||||
'width': node.$elem.width(),
|
||||
'top': rtop,
|
||||
'borderWidth': '5px',
|
||||
'height': 0,
|
||||
'opacity': 1
|
||||
});
|
||||
$tooltip.text(gettext('As Sibling'));
|
||||
} else if (evt2.pageY >= rtop + rowHeight / 2 && evt2.pageY <= rtop + rowHeight) {
|
||||
// The mouse is positioned on the bottom half of a row
|
||||
$targetRow = $row;
|
||||
target_node = new Node($targetRow[0]);
|
||||
if (target_node.is_collapsed()) {
|
||||
target_node.expand();
|
||||
}
|
||||
as_child = true;
|
||||
$drag_line.css({
|
||||
'top': rtop,
|
||||
'left': node.$elem.offset().left,
|
||||
'height': rowHeight,
|
||||
'opacity': 0.4,
|
||||
'width': node.$elem.width(),
|
||||
'borderWidth': 0,
|
||||
'backgroundColor': DRAG_LINE_COLOR
|
||||
});
|
||||
$tooltip.text(gettext('As child'));
|
||||
}
|
||||
});
|
||||
}).bind('mouseup',function () {
|
||||
if ($targetRow !== null) {
|
||||
target_node = new Node($targetRow[0]);
|
||||
if (target_node.node_id !== node.node_id) {
|
||||
/*alert('Insert node ' + node.node_name() + ' as child of: '
|
||||
+ target_node.parent_node().node_name() + '\n and sibling of: '
|
||||
+ target_node.node_name());*/
|
||||
// Call $.ajax so we can handle the error
|
||||
// On Drop, make an XHR call to perform the node move
|
||||
$.ajax({
|
||||
url: window.MOVE_NODE_ENDPOINT,
|
||||
type: 'POST',
|
||||
data: {
|
||||
node_id: node.node_id,
|
||||
parent_id: target_node.parent_id,
|
||||
sibling_id: target_node.node_id,
|
||||
as_child: as_child ? 1 : 0
|
||||
},
|
||||
complete: function (req, status) {
|
||||
// http://stackoverflow.com/questions/1439895/add-a-hash-with-javascript-to-url-without-scrolling-page/1439910#1439910
|
||||
node.$elem.remove();
|
||||
window.location.hash = 'node-' + node.node_id;
|
||||
window.location.reload();
|
||||
},
|
||||
error: function (req, status, error) {
|
||||
// On error (!200) also reload to display
|
||||
// the message
|
||||
node.$elem.remove();
|
||||
window.location.hash = 'node-' + node.node_id;
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
stop_drag();
|
||||
}).bind('keyup', function (kbevt) {
|
||||
// Cancel drag on escape
|
||||
if (kbevt.keyCode === 27) {
|
||||
stop_drag();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('a.collapse').click(function () {
|
||||
var node = new Node($(this).closest('tr')[0]); // send the DOM node, not jQ
|
||||
node.toggle();
|
||||
return false;
|
||||
});
|
||||
var hash = window.location.hash;
|
||||
// This is a hack, the actual element's id ends in '-id' but the url's hash
|
||||
// doesn't, I'm doing this to avoid scrolling the page... is that a good thing?
|
||||
if (hash) {
|
||||
$(hash + '-id').animate({
|
||||
backgroundColor: RECENTLY_MOVED_COLOR
|
||||
}, RECENTLY_FADE_DURATION, function () {
|
||||
$(this).animate({
|
||||
backgroundColor: RECENTLY_MOVED_FADEOUT
|
||||
}, RECENTLY_FADE_DURATION, function () {
|
||||
this.removeAttribute('style');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
})(django.jQuery);
|
||||
|
||||
// http://stackoverflow.com/questions/190560/jquery-animate-backgroundcolor/2302005#2302005
|
||||
(function (d) {
|
||||
d.each(["backgroundColor", "borderBottomColor", "borderLeftColor", "borderRightColor", "borderTopColor", "color", "outlineColor"], function (f, e) {
|
||||
d.fx.step[e] = function (g) {
|
||||
if (!g.colorInit) {
|
||||
g.start = c(g.elem, e);
|
||||
g.end = b(g.end);
|
||||
g.colorInit = true
|
||||
}
|
||||
g.elem.style[e] = "rgb(" + [Math.max(Math.min(parseInt((g.pos * (g.end[0] - g.start[0])) + g.start[0]), 255), 0), Math.max(Math.min(parseInt((g.pos * (g.end[1] - g.start[1])) + g.start[1]), 255), 0), Math.max(Math.min(parseInt((g.pos * (g.end[2] - g.start[2])) + g.start[2]), 255), 0)].join(",") + ")"
|
||||
}
|
||||
});
|
||||
function b(f) {
|
||||
var e;
|
||||
if (f && f.constructor == Array && f.length == 3) {
|
||||
return f
|
||||
}
|
||||
if (e = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(f)) {
|
||||
return[parseInt(e[1]), parseInt(e[2]), parseInt(e[3])]
|
||||
}
|
||||
if (e = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(f)) {
|
||||
return[parseFloat(e[1]) * 2.55, parseFloat(e[2]) * 2.55, parseFloat(e[3]) * 2.55]
|
||||
}
|
||||
if (e = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(f)) {
|
||||
return[parseInt(e[1], 16), parseInt(e[2], 16), parseInt(e[3], 16)]
|
||||
}
|
||||
if (e = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(f)) {
|
||||
return[parseInt(e[1] + e[1], 16), parseInt(e[2] + e[2], 16), parseInt(e[3] + e[3], 16)]
|
||||
}
|
||||
if (e = /rgba\(0, 0, 0, 0\)/.exec(f)) {
|
||||
return a.transparent
|
||||
}
|
||||
return a[d.trim(f).toLowerCase()]
|
||||
}
|
||||
|
||||
function c(g, e) {
|
||||
var f;
|
||||
do {
|
||||
f = d.css(g, e);
|
||||
if (f != "" && f != "transparent" || d.nodeName(g, "body")) {
|
||||
break
|
||||
}
|
||||
e = "backgroundColor"
|
||||
} while (g = g.parentNode);
|
||||
return b(f)
|
||||
}
|
||||
|
||||
var a = {aqua: [0, 255, 255], azure: [240, 255, 255], beige: [245, 245, 220], black: [0, 0, 0], blue: [0, 0, 255], brown: [165, 42, 42], cyan: [0, 255, 255], darkblue: [0, 0, 139], darkcyan: [0, 139, 139], darkgrey: [169, 169, 169], darkgreen: [0, 100, 0], darkkhaki: [189, 183, 107], darkmagenta: [139, 0, 139], darkolivegreen: [85, 107, 47], darkorange: [255, 140, 0], darkorchid: [153, 50, 204], darkred: [139, 0, 0], darksalmon: [233, 150, 122], darkviolet: [148, 0, 211], fuchsia: [255, 0, 255], gold: [255, 215, 0], green: [0, 128, 0], indigo: [75, 0, 130], khaki: [240, 230, 140], lightblue: [173, 216, 230], lightcyan: [224, 255, 255], lightgreen: [144, 238, 144], lightgrey: [211, 211, 211], lightpink: [255, 182, 193], lightyellow: [255, 255, 224], lime: [0, 255, 0], magenta: [255, 0, 255], maroon: [128, 0, 0], navy: [0, 0, 128], olive: [128, 128, 0], orange: [255, 165, 0], pink: [255, 192, 203], purple: [128, 0, 128], violet: [128, 0, 128], red: [255, 0, 0], silver: [192, 192, 192], white: [255, 255, 255], yellow: [255, 255, 0], transparent: [255, 255, 255]}
|
||||
})(django.jQuery);
|
||||
23
wagtail/vendor/django-treebeard/treebeard/templates/admin/tree_change_list.html
vendored
Normal file
23
wagtail/vendor/django-treebeard/treebeard/templates/admin/tree_change_list.html
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{# Used for MP and NS trees #}
|
||||
{% extends "admin/change_list.html" %}
|
||||
{% load admin_list admin_tree i18n %}
|
||||
|
||||
{% block extrastyle %}
|
||||
{{ block.super }}
|
||||
{% treebeard_css %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
{% treebeard_js %}
|
||||
{% endblock %}
|
||||
|
||||
{% block result_list %}
|
||||
{% if action_form and actions_on_top and cl.full_result_count %}
|
||||
{% admin_actions %}
|
||||
{% endif %}
|
||||
{% result_tree cl request %}
|
||||
{% if action_form and actions_on_bottom and cl.full_result_count %}
|
||||
{% admin_actions %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
38
wagtail/vendor/django-treebeard/treebeard/templates/admin/tree_change_list_results.html
vendored
Normal file
38
wagtail/vendor/django-treebeard/treebeard/templates/admin/tree_change_list_results.html
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{% if result_hidden_fields %}
|
||||
<div class="hiddenfields"> {# DIV for HTML validation #}
|
||||
{% for item in result_hidden_fields %}{{ item }}{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if results %}
|
||||
<table cellspacing="0" id="result_list">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for header in result_headers %}
|
||||
<th{{ header.class_attrib }}>
|
||||
{% if header.sortable %}<a href="{{ header.url }}"
|
||||
{% if header.tooltip %}title="{{ header.tooltip }}"{% endif %}>{% endif %}
|
||||
{{ header.text|capfirst }}
|
||||
{% if header.sortable %}</a>{% endif %}</th>{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for node_id, parent_id, node_level, has_children, result in results %}
|
||||
<tr id="node-{{ node_id }}-id" class="{% cycle 'row1' 'row2' %}"
|
||||
level="{{ node_level }}" children-num="{{ children_num }}"
|
||||
parent="{{ parent_id }}" node="{{ node_id }}">
|
||||
{% for item in result %}
|
||||
{% if forloop.counter == 1 %}
|
||||
{% for spacer in item.depth %}<span class="grab">
|
||||
</span>{% endfor %}
|
||||
{% endif %}
|
||||
{{ item }}
|
||||
{% endfor %}</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<input type="hidden" id="has-filters" value="{{ filtered|yesno:"1,0" }}"/>
|
||||
<script>
|
||||
var MOVE_NODE_ENDPOINT = 'move/';
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
21
wagtail/vendor/django-treebeard/treebeard/templates/admin/tree_list.html
vendored
Normal file
21
wagtail/vendor/django-treebeard/treebeard/templates/admin/tree_list.html
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{# Used for AL trees #}
|
||||
{% extends "admin/change_list.html" %}
|
||||
{% load admin_list admin_tree_list i18n %}
|
||||
|
||||
{% block extrastyle %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block result_list %}
|
||||
{% if action_form and actions_on_top and cl.full_result_count %}
|
||||
{% admin_actions %}
|
||||
{% endif %}
|
||||
{% result_tree cl request %}
|
||||
{% if action_form and actions_on_bottom and cl.full_result_count %}
|
||||
{% admin_actions %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
7
wagtail/vendor/django-treebeard/treebeard/templates/admin/tree_list_results.html
vendored
Normal file
7
wagtail/vendor/django-treebeard/treebeard/templates/admin/tree_list_results.html
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{% if results %}
|
||||
<ul>
|
||||
{% for result in results %}
|
||||
<li class="{% cycle 'row1' 'row2' %}">{{ result }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
51
wagtail/vendor/django-treebeard/treebeard/templatetags/__init__.py
vendored
Normal file
51
wagtail/vendor/django-treebeard/treebeard/templatetags/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import datetime
|
||||
import decimal
|
||||
|
||||
from django.template import Variable, VariableDoesNotExist
|
||||
from django.utils import formats, timezone, six
|
||||
from django.utils.encoding import smart_text
|
||||
from django.utils.html import conditional_escape
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
action_form_var = Variable('action_form')
|
||||
|
||||
|
||||
def needs_checkboxes(context):
|
||||
try:
|
||||
return action_form_var.resolve(context) is not None
|
||||
except VariableDoesNotExist:
|
||||
return False
|
||||
|
||||
|
||||
def display_for_value(value, boolean=False): # pragma: no cover
|
||||
""" Added for compatibility with django 1.4, copied from django trunk.
|
||||
"""
|
||||
from django.contrib.admin.templatetags.admin_list import _boolean_icon
|
||||
from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
|
||||
|
||||
if boolean:
|
||||
return _boolean_icon(value)
|
||||
elif value is None:
|
||||
return EMPTY_CHANGELIST_VALUE
|
||||
elif isinstance(value, datetime.datetime):
|
||||
return formats.localize(timezone.template_localtime(value))
|
||||
elif isinstance(value, (datetime.date, datetime.time)):
|
||||
return formats.localize(value)
|
||||
elif isinstance(value, six.integer_types + (decimal.Decimal, float)):
|
||||
return formats.number_format(value)
|
||||
else:
|
||||
return smart_text(value)
|
||||
|
||||
|
||||
def format_html(format_string, *args, **kwargs): # pragma: no cover
|
||||
"""
|
||||
Added for compatibility with django 1.4, copied from django trunk.
|
||||
|
||||
Similar to str.format, but passes all arguments through conditional_escape,
|
||||
and calls 'mark_safe' on the result. This function should be used instead
|
||||
of str.format or % interpolation to build up small HTML fragments.
|
||||
"""
|
||||
args_safe = map(conditional_escape, args)
|
||||
kwargs_safe = dict([(k, conditional_escape(v)) for (k, v) in
|
||||
six.iteritems(kwargs)])
|
||||
return mark_safe(format_string.format(*args_safe, **kwargs_safe))
|
||||
279
wagtail/vendor/django-treebeard/treebeard/templatetags/admin_tree.py
vendored
Normal file
279
wagtail/vendor/django-treebeard/treebeard/templatetags/admin_tree.py
vendored
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Templatetags for django-treebeard to add drag and drop capabilities to the
|
||||
nodes change list - @jjdelc
|
||||
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import sys
|
||||
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.contrib.admin.templatetags.admin_list import (
|
||||
result_headers, result_hidden_fields)
|
||||
from django.contrib.admin.util import lookup_field, display_for_field
|
||||
from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.template import Library
|
||||
from django.utils.html import conditional_escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
if sys.version < '3':
|
||||
import codecs
|
||||
|
||||
def u(x):
|
||||
return codecs.unicode_escape_decode(x)[0]
|
||||
else:
|
||||
def u(x):
|
||||
return x
|
||||
|
||||
register = Library()
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
from django.utils.encoding import force_str, smart_str
|
||||
from urllib.parse import urljoin
|
||||
else:
|
||||
from django.utils.encoding import force_unicode as force_str
|
||||
from django.utils.encoding import smart_unicode as smart_str
|
||||
from urlparse import urljoin
|
||||
|
||||
|
||||
try:
|
||||
from django.contrib.admin.util import display_for_value
|
||||
from django.utils.html import format_html
|
||||
except ImportError:
|
||||
from treebeard.templatetags import display_for_value, format_html
|
||||
|
||||
from treebeard.templatetags import needs_checkboxes
|
||||
|
||||
|
||||
def get_result_and_row_class(cl, field_name, result):
|
||||
row_class = ''
|
||||
try:
|
||||
f, attr, value = lookup_field(field_name, result, cl.model_admin)
|
||||
except ObjectDoesNotExist:
|
||||
result_repr = EMPTY_CHANGELIST_VALUE
|
||||
else:
|
||||
if f is None:
|
||||
if field_name == 'action_checkbox':
|
||||
row_class = mark_safe(' class="action-checkbox"')
|
||||
allow_tags = getattr(attr, 'allow_tags', False)
|
||||
boolean = getattr(attr, 'boolean', False)
|
||||
if boolean:
|
||||
allow_tags = True
|
||||
result_repr = display_for_value(value, boolean)
|
||||
# Strip HTML tags in the resulting text, except if the
|
||||
# function has an "allow_tags" attribute set to True.
|
||||
if allow_tags:
|
||||
result_repr = mark_safe(result_repr)
|
||||
if isinstance(value, (datetime.date, datetime.time)):
|
||||
row_class = mark_safe(' class="nowrap"')
|
||||
else:
|
||||
if isinstance(f.rel, models.ManyToOneRel):
|
||||
field_val = getattr(result, f.name)
|
||||
if field_val is None:
|
||||
result_repr = EMPTY_CHANGELIST_VALUE
|
||||
else:
|
||||
result_repr = field_val
|
||||
else:
|
||||
result_repr = display_for_field(value, f)
|
||||
if isinstance(f, (models.DateField, models.TimeField,
|
||||
models.ForeignKey)):
|
||||
row_class = mark_safe(' class="nowrap"')
|
||||
if force_str(result_repr) == '':
|
||||
result_repr = mark_safe(' ')
|
||||
return result_repr, row_class
|
||||
|
||||
|
||||
def get_spacer(first, result):
|
||||
if first:
|
||||
spacer = '<span class="spacer"> </span>' * (
|
||||
result.get_depth() - 1)
|
||||
else:
|
||||
spacer = ''
|
||||
|
||||
return spacer
|
||||
|
||||
|
||||
def get_collapse(result):
|
||||
if result.get_children_count():
|
||||
collapse = ('<a href="#" title="" class="collapse expanded">'
|
||||
'-</a>')
|
||||
else:
|
||||
collapse = '<span class="collapse"> </span>'
|
||||
|
||||
return collapse
|
||||
|
||||
|
||||
def get_drag_handler(first):
|
||||
drag_handler = ''
|
||||
if first:
|
||||
drag_handler = ('<td class="drag-handler">'
|
||||
'<span> </span></td>')
|
||||
return drag_handler
|
||||
|
||||
|
||||
def items_for_result(cl, result, form):
|
||||
"""
|
||||
Generates the actual list of data.
|
||||
|
||||
@jjdelc:
|
||||
This has been shamelessly copied from original
|
||||
django.contrib.admin.templatetags.admin_list.items_for_result
|
||||
in order to alter the dispay for the first element
|
||||
"""
|
||||
first = True
|
||||
pk = cl.lookup_opts.pk.attname
|
||||
for field_name in cl.list_display:
|
||||
result_repr, row_class = get_result_and_row_class(cl, field_name,
|
||||
result)
|
||||
# If list_display_links not defined, add the link tag to the
|
||||
# first field
|
||||
if (first and not cl.list_display_links) or \
|
||||
field_name in cl.list_display_links:
|
||||
table_tag = {True: 'th', False: 'td'}[first]
|
||||
# This spacer indents the nodes based on their depth
|
||||
spacer = get_spacer(first, result)
|
||||
# This shows a collapse or expand link for nodes with childs
|
||||
collapse = get_collapse(result)
|
||||
# Add a <td/> before the first col to show the drag handler
|
||||
drag_handler = get_drag_handler(first)
|
||||
first = False
|
||||
url = cl.url_for_result(result)
|
||||
# Convert the pk to something that can be used in Javascript.
|
||||
# Problem cases are long ints (23L) and non-ASCII strings.
|
||||
if cl.to_field:
|
||||
attr = str(cl.to_field)
|
||||
else:
|
||||
attr = pk
|
||||
value = result.serializable_value(attr)
|
||||
result_id = repr(force_str(value))[1:]
|
||||
onclickstr = (
|
||||
' onclick="opener.dismissRelatedLookupPopup(window, %s);'
|
||||
' return false;"')
|
||||
yield mark_safe(
|
||||
u('%s<%s%s>%s %s <a href="%s"%s>%s</a></%s>') % (
|
||||
drag_handler, table_tag, row_class, spacer, collapse, url,
|
||||
(cl.is_popup and onclickstr % result_id or ''),
|
||||
conditional_escape(result_repr), table_tag))
|
||||
else:
|
||||
# By default the fields come from ModelAdmin.list_editable, but if
|
||||
# we pull the fields out of the form instead of list_editable
|
||||
# custom admins can provide fields on a per request basis
|
||||
if (form and field_name in form.fields and not (
|
||||
field_name == cl.model._meta.pk.name and
|
||||
form[cl.model._meta.pk.name].is_hidden)):
|
||||
bf = form[field_name]
|
||||
result_repr = mark_safe(force_str(bf.errors) + force_str(bf))
|
||||
yield format_html(u('<td{0}>{1}</td>'), row_class, result_repr)
|
||||
if form and not form[cl.model._meta.pk.name].is_hidden:
|
||||
yield format_html(u('<td>{0}</td>'),
|
||||
force_str(form[cl.model._meta.pk.name]))
|
||||
|
||||
|
||||
def get_parent_id(node):
|
||||
"""Return the node's parent id or 0 if node is a root node."""
|
||||
if node.is_root():
|
||||
return 0
|
||||
return node.get_parent().pk
|
||||
|
||||
|
||||
def results(cl):
|
||||
if cl.formset:
|
||||
for res, form in zip(cl.result_list, cl.formset.forms):
|
||||
yield (res.pk, get_parent_id(res), res.get_depth(),
|
||||
res.get_children_count(),
|
||||
list(items_for_result(cl, res, form)))
|
||||
else:
|
||||
for res in cl.result_list:
|
||||
yield (res.pk, get_parent_id(res), res.get_depth(),
|
||||
res.get_children_count(),
|
||||
list(items_for_result(cl, res, None)))
|
||||
|
||||
|
||||
def check_empty_dict(GET_dict):
|
||||
"""
|
||||
Returns True if the GET querstring contains on values, but it can contain
|
||||
empty keys.
|
||||
This is better than doing not bool(request.GET) as an empty key will return
|
||||
True
|
||||
"""
|
||||
empty = True
|
||||
for k, v in GET_dict.items():
|
||||
# Don't disable on p(age) or 'all' GET param
|
||||
if v and k != 'p' and k != 'all':
|
||||
empty = False
|
||||
return empty
|
||||
|
||||
|
||||
@register.inclusion_tag(
|
||||
'admin/tree_change_list_results.html', takes_context=True)
|
||||
def result_tree(context, cl, request):
|
||||
"""
|
||||
Added 'filtered' param, so the template's js knows whether the results have
|
||||
been affected by a GET param or not. Only when the results are not filtered
|
||||
you can drag and sort the tree
|
||||
"""
|
||||
|
||||
# Here I'm adding an extra col on pos 2 for the drag handlers
|
||||
headers = list(result_headers(cl))
|
||||
headers.insert(1 if needs_checkboxes(context) else 0, {
|
||||
'text': '+',
|
||||
'sortable': True,
|
||||
'url': request.path,
|
||||
'tooltip': _('Return to ordered tree'),
|
||||
'class_attrib': mark_safe(' class="oder-grabber"')
|
||||
})
|
||||
return {
|
||||
'filtered': not check_empty_dict(request.GET),
|
||||
'result_hidden_fields': list(result_hidden_fields(cl)),
|
||||
'result_headers': headers,
|
||||
'results': list(results(cl)),
|
||||
}
|
||||
|
||||
|
||||
def get_static_url():
|
||||
"""Return a base static url, always ending with a /"""
|
||||
path = getattr(settings, 'STATIC_URL', None)
|
||||
if not path:
|
||||
path = getattr(settings, 'MEDIA_URL', None)
|
||||
if not path:
|
||||
path = '/'
|
||||
return path
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def treebeard_css():
|
||||
"""
|
||||
Template tag to print out the proper <link/> tag to include a custom .css
|
||||
"""
|
||||
LINK_HTML = """<link rel="stylesheet" type="text/css" href="%s"/>"""
|
||||
css_file = urljoin(get_static_url(), 'treebeard/treebeard-admin.css')
|
||||
return LINK_HTML % css_file
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def treebeard_js():
|
||||
"""
|
||||
Template tag to print out the proper <script/> tag to include a custom .js
|
||||
"""
|
||||
path = get_static_url()
|
||||
SCRIPT_HTML = """<script type="text/javascript" src="%s"></script>"""
|
||||
js_file = '/'.join([path.rstrip('/'), 'treebeard', 'treebeard-admin.js'])
|
||||
|
||||
# Jquery UI is needed to call disableSelection() on drag and drop so
|
||||
# text selections arent marked while dragging a table row
|
||||
# http://www.lokkju.com/blog/archives/143
|
||||
JQUERY_UI = ("<script>"
|
||||
"(function($){jQuery = $.noConflict(true);})(django.jQuery);"
|
||||
"</script>"
|
||||
"<script type=\"text/javascript\" src=\"%s\"></script>")
|
||||
jquery_ui = urljoin(path, 'treebeard/jquery-ui-1.8.5.custom.min.js')
|
||||
|
||||
scripts = [SCRIPT_HTML % 'jsi18n',
|
||||
SCRIPT_HTML % js_file,
|
||||
JQUERY_UI % jquery_ui]
|
||||
return ''.join(scripts)
|
||||
40
wagtail/vendor/django-treebeard/treebeard/templatetags/admin_tree_list.py
vendored
Normal file
40
wagtail/vendor/django-treebeard/treebeard/templatetags/admin_tree_list.py
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.template import Library
|
||||
from treebeard.templatetags import needs_checkboxes
|
||||
|
||||
|
||||
register = Library()
|
||||
CHECKBOX_TMPL = ('<input type="checkbox" class="action-select" value="%d" '
|
||||
'name="_selected_action" />')
|
||||
|
||||
|
||||
def _line(context, node, request):
|
||||
if 't' in request.GET and request.GET['t'] == 'id':
|
||||
raw_id_fields = """
|
||||
onclick="opener.dismissRelatedLookupPopup(window, '%d'); return false;"
|
||||
""" % (node.pk,)
|
||||
else:
|
||||
raw_id_fields = ''
|
||||
output = ''
|
||||
if needs_checkboxes(context):
|
||||
output += CHECKBOX_TMPL % node.pk
|
||||
return output + '<a href="%d/" %s>%s</a>' % (
|
||||
node.pk, raw_id_fields, str(node))
|
||||
|
||||
|
||||
def _subtree(context, node, request):
|
||||
tree = ''
|
||||
for subnode in node.get_children():
|
||||
tree += '<li>%s</li>' % _subtree(context, subnode, request)
|
||||
if tree:
|
||||
tree = '<ul>%s</ul>' % tree
|
||||
return _line(context, node, request) + tree
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def result_tree(context, cl, request):
|
||||
tree = ''
|
||||
for root_node in cl.model.get_root_nodes():
|
||||
tree += '<li>%s</li>' % _subtree(context, root_node, request)
|
||||
return "<ul>%s</ul>" % tree
|
||||
0
wagtail/vendor/django-treebeard/treebeard/tests/__init__.py
vendored
Normal file
0
wagtail/vendor/django-treebeard/treebeard/tests/__init__.py
vendored
Normal file
21
wagtail/vendor/django-treebeard/treebeard/tests/admin.py
vendored
Normal file
21
wagtail/vendor/django-treebeard/treebeard/tests/admin.py
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import datetime
|
||||
|
||||
from django.contrib import admin
|
||||
from treebeard.admin import admin_factory
|
||||
from treebeard.forms import movenodeform_factory
|
||||
|
||||
from treebeard.tests.models import BASE_MODELS, UNICODE_MODELS
|
||||
|
||||
|
||||
def register(model):
|
||||
form_class = movenodeform_factory(model)
|
||||
admin_class = admin_factory(form_class)
|
||||
admin.site.register(model, admin_class)
|
||||
|
||||
|
||||
for model in BASE_MODELS:
|
||||
register(model)
|
||||
|
||||
|
||||
for model in UNICODE_MODELS:
|
||||
register(model)
|
||||
80
wagtail/vendor/django-treebeard/treebeard/tests/conftest.py
vendored
Normal file
80
wagtail/vendor/django-treebeard/treebeard/tests/conftest.py
vendored
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'treebeard.tests.settings'
|
||||
|
||||
import django
|
||||
from django.conf import settings
|
||||
from django.test.utils import (setup_test_environment,
|
||||
teardown_test_environment)
|
||||
from django.test.client import Client
|
||||
from django.core.management import call_command
|
||||
from django.core import mail
|
||||
from django.db import connection
|
||||
from django.db.models.base import ModelBase
|
||||
from _pytest import python as _pytest_python
|
||||
|
||||
|
||||
def idmaker(argnames, argvalues):
|
||||
idlist = []
|
||||
for valindex, valset in enumerate(argvalues):
|
||||
this_id = []
|
||||
for nameindex, val in enumerate(valset):
|
||||
argname = argnames[nameindex]
|
||||
if isinstance(val, (float, int, str)):
|
||||
this_id.append(str(val))
|
||||
elif isinstance(val, ModelBase):
|
||||
this_id.append(val.__name__)
|
||||
else:
|
||||
this_id.append("{0}-{1}={2!s}".format(argname, valindex))
|
||||
idlist.append("][".join(this_id))
|
||||
return idlist
|
||||
_pytest_python.idmaker = idmaker
|
||||
|
||||
|
||||
def pytest_report_header(config):
|
||||
return 'Django: ' + django.get_version()
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
setup_test_environment()
|
||||
connection.creation.create_test_db(verbosity=2, autoclobber=True)
|
||||
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
dbsettings = settings.DATABASES['default']
|
||||
dbtestname = dbsettings['TEST_NAME']
|
||||
connection.close()
|
||||
if dbsettings['ENGINE'].split('.')[-1] == 'postgresql_psycopg2':
|
||||
connection.connection = None
|
||||
connection.settings_dict['NAME'] = dbtestname.split('_')[1]
|
||||
cursor = connection.cursor()
|
||||
connection.autocommit = True
|
||||
if django.VERSION < (1, 6):
|
||||
connection._set_isolation_level(0)
|
||||
else:
|
||||
connection._set_autocommit(True)
|
||||
time.sleep(1)
|
||||
sys.stdout.write(
|
||||
"Destroying test database for alias '%s' (%s)...\n" % (
|
||||
connection.alias, dbtestname)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
cursor.execute(
|
||||
'DROP DATABASE %s' % connection.ops.quote_name(dbtestname))
|
||||
else:
|
||||
connection.creation.destroy_test_db(dbtestname, verbosity=2)
|
||||
teardown_test_environment()
|
||||
|
||||
|
||||
def pytest_funcarg__client(request):
|
||||
def setup():
|
||||
mail.outbox = []
|
||||
return Client()
|
||||
|
||||
def teardown(client):
|
||||
call_command('flush', verbosity=0, interactive=False)
|
||||
|
||||
return request.cached_setup(setup, teardown, 'function')
|
||||
22
wagtail/vendor/django-treebeard/treebeard/tests/jenkins/quality.sh
vendored
Normal file
22
wagtail/vendor/django-treebeard/treebeard/tests/jenkins/quality.sh
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Script that takes source code quality measurements.
|
||||
# In shell because it will run in a *NIX node.
|
||||
|
||||
pip install pylint pep8 pytest coverage
|
||||
coverage erase
|
||||
|
||||
# Combining the coverage data of all the test runs
|
||||
# in the different OS/Python combinations.
|
||||
find $WORKSPACE -mindepth 1 -maxdepth 2 -name '.coverage.*' -exec cp -v \{\} . \;
|
||||
coverage combine
|
||||
coverage report
|
||||
coverage xml
|
||||
coverage erase
|
||||
|
||||
SRCFILES=treebeard/*.py
|
||||
PYFILES=`find . -name '*.py'`
|
||||
ALLFILES=`find . -type f`
|
||||
sloccount --duplicates --wide --details $ALLFILES > sloccount.sc 2>&1
|
||||
pep8 $PYFILES > violations-pep8.txt 2>&1
|
||||
pylint -f parseable $SRCFILES > violations-pylint.txt 2>&1
|
||||
22
wagtail/vendor/django-treebeard/treebeard/tests/jenkins/rm_workspace_coverage.py
vendored
Normal file
22
wagtail/vendor/django-treebeard/treebeard/tests/jenkins/rm_workspace_coverage.py
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
"""Remove `.coverage.$HOST.$ID` files from previous runs.
|
||||
|
||||
In Python because of portability with Windows.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import os
|
||||
|
||||
|
||||
def main():
|
||||
workspace = os.environ['WORKSPACE']
|
||||
for filename in os.listdir(workspace):
|
||||
if filename.startswith('.coverage.'):
|
||||
file_full_name = os.path.join(workspace, filename)
|
||||
sys.stdout.write(
|
||||
'* Removing old .coverage file: `%s`\n' % file_full_name)
|
||||
os.unlink(file_full_name)
|
||||
sys.stdout.flush()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
33
wagtail/vendor/django-treebeard/treebeard/tests/jenkins/toxhelper.py
vendored
Normal file
33
wagtail/vendor/django-treebeard/treebeard/tests/jenkins/toxhelper.py
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env python
|
||||
""" toxhelper is a simple wrapper of pytest and coverage to be used with tox.
|
||||
|
||||
It is specially useful to avoid path and interpreter problems while running
|
||||
tests with jenkins in OS X, Linux and Windows using the same configuration.
|
||||
|
||||
See https://tabo.pe/jenkins/ for the results.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from coverage import coverage
|
||||
|
||||
|
||||
def run_the_tests():
|
||||
if 'TOX_DB' in os.environ:
|
||||
os.environ['DATABASE_HOST'], os.environ['DATABASE_PORT'] = {
|
||||
'pgsql': ('dummy_test_database_server', '5434'),
|
||||
'mysql': ('dummy_test_database_server', '3308'),
|
||||
'sqlite': ('', ''),
|
||||
}[os.environ['TOX_DB']]
|
||||
cov = coverage()
|
||||
cov.start()
|
||||
test_result = pytest.main(sys.argv[1:])
|
||||
cov.stop()
|
||||
cov.save()
|
||||
return test_result
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(run_the_tests())
|
||||
246
wagtail/vendor/django-treebeard/treebeard/tests/models.py
vendored
Normal file
246
wagtail/vendor/django-treebeard/treebeard/tests/models.py
vendored
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
from django.db import models, connection
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from treebeard.mp_tree import MP_Node
|
||||
from treebeard.al_tree import AL_Node
|
||||
from treebeard.ns_tree import NS_Node
|
||||
|
||||
|
||||
class RelatedModel(models.Model):
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self):
|
||||
return self.desc
|
||||
|
||||
|
||||
class MP_TestNode(MP_Node):
|
||||
steplen = 3
|
||||
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
class MP_UnicodeNode(MP_Node):
|
||||
steplen = 3
|
||||
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return self.desc
|
||||
|
||||
|
||||
class MP_TestNodeSomeDep(models.Model):
|
||||
node = models.ForeignKey(MP_TestNode)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
class MP_TestNodeRelated(MP_Node):
|
||||
steplen = 3
|
||||
|
||||
desc = models.CharField(max_length=255)
|
||||
related = models.ForeignKey(RelatedModel)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
class NS_TestNode(NS_Node):
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
class NS_UnicodetNode(NS_Node):
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return self.desc
|
||||
|
||||
|
||||
class NS_TestNodeSomeDep(models.Model):
|
||||
node = models.ForeignKey(NS_TestNode)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
class NS_TestNodeRelated(NS_Node):
|
||||
desc = models.CharField(max_length=255)
|
||||
related = models.ForeignKey(RelatedModel)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
class AL_TestNode(AL_Node):
|
||||
parent = models.ForeignKey('self',
|
||||
related_name='children_set',
|
||||
null=True,
|
||||
db_index=True)
|
||||
sib_order = models.PositiveIntegerField()
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
class AL_UnicodeNode(AL_Node):
|
||||
parent = models.ForeignKey('self',
|
||||
related_name='children_set',
|
||||
null=True,
|
||||
db_index=True)
|
||||
sib_order = models.PositiveIntegerField()
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return self.desc
|
||||
|
||||
|
||||
class AL_TestNodeSomeDep(models.Model):
|
||||
node = models.ForeignKey(AL_TestNode)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
class AL_TestNodeRelated(AL_Node):
|
||||
parent = models.ForeignKey('self',
|
||||
related_name='children_set',
|
||||
null=True,
|
||||
db_index=True)
|
||||
sib_order = models.PositiveIntegerField()
|
||||
desc = models.CharField(max_length=255)
|
||||
related = models.ForeignKey(RelatedModel)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
class MP_TestNodeSorted(MP_Node):
|
||||
steplen = 1
|
||||
node_order_by = ['val1', 'val2', 'desc']
|
||||
val1 = models.IntegerField()
|
||||
val2 = models.IntegerField()
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
class NS_TestNodeSorted(NS_Node):
|
||||
node_order_by = ['val1', 'val2', 'desc']
|
||||
val1 = models.IntegerField()
|
||||
val2 = models.IntegerField()
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
class AL_TestNodeSorted(AL_Node):
|
||||
parent = models.ForeignKey('self',
|
||||
related_name='children_set',
|
||||
null=True,
|
||||
db_index=True)
|
||||
node_order_by = ['val1', 'val2', 'desc']
|
||||
val1 = models.IntegerField()
|
||||
val2 = models.IntegerField()
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
class MP_TestNodeAlphabet(MP_Node):
|
||||
steplen = 2
|
||||
|
||||
numval = models.IntegerField()
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
class MP_TestNodeSmallStep(MP_Node):
|
||||
steplen = 1
|
||||
alphabet = '0123456789'
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
class MP_TestNodeSortedAutoNow(MP_Node):
|
||||
desc = models.CharField(max_length=255)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
node_order_by = ['created']
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
class MP_TestNodeShortPath(MP_Node):
|
||||
steplen = 1
|
||||
alphabet = '01234'
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
# This is how you change the default fields defined in a Django abstract class
|
||||
# (in this case, MP_Node), since Django doesn't allow overriding fields, only
|
||||
# mehods and attributes
|
||||
MP_TestNodeShortPath._meta.get_field('path').max_length = 4
|
||||
|
||||
|
||||
class MP_TestNode_Proxy(MP_TestNode):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
|
||||
class NS_TestNode_Proxy(NS_TestNode):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
|
||||
class AL_TestNode_Proxy(AL_TestNode):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
|
||||
class MP_TestSortedNodeShortPath(MP_Node):
|
||||
steplen = 1
|
||||
alphabet = '01234'
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
node_order_by = ['desc']
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return 'Node %d' % self.pk
|
||||
|
||||
|
||||
MP_TestSortedNodeShortPath._meta.get_field('path').max_length = 4
|
||||
|
||||
|
||||
class MP_TestManyToManyWithUser(MP_Node):
|
||||
name = models.CharField(max_length=255)
|
||||
users = models.ManyToManyField(User)
|
||||
|
||||
|
||||
BASE_MODELS = AL_TestNode, MP_TestNode, NS_TestNode
|
||||
PROXY_MODELS = AL_TestNode_Proxy, MP_TestNode_Proxy, NS_TestNode_Proxy
|
||||
SORTED_MODELS = AL_TestNodeSorted, MP_TestNodeSorted, NS_TestNodeSorted
|
||||
DEP_MODELS = AL_TestNodeSomeDep, MP_TestNodeSomeDep, NS_TestNodeSomeDep
|
||||
MP_SHORTPATH_MODELS = MP_TestNodeShortPath, MP_TestSortedNodeShortPath
|
||||
RELATED_MODELS = AL_TestNodeRelated, MP_TestNodeRelated, NS_TestNodeRelated
|
||||
UNICODE_MODELS = AL_UnicodeNode, MP_UnicodeNode, NS_UnicodetNode
|
||||
|
||||
|
||||
def empty_models_tables(models):
|
||||
for model in models:
|
||||
model.objects.all().delete()
|
||||
61
wagtail/vendor/django-treebeard/treebeard/tests/settings.py
vendored
Normal file
61
wagtail/vendor/django-treebeard/treebeard/tests/settings.py
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
"""Django settings for testing treebeard"""
|
||||
|
||||
import random
|
||||
import string
|
||||
|
||||
import os
|
||||
|
||||
|
||||
def get_db_conf():
|
||||
conf, options = {}, {}
|
||||
for name in ('ENGINE', 'NAME', 'USER', 'PASSWORD', 'HOST', 'PORT'):
|
||||
conf[name] = os.environ.get('DATABASE_' + name, '')
|
||||
engine = conf['ENGINE']
|
||||
if engine == '':
|
||||
engine = 'sqlite3'
|
||||
elif engine in ('pgsql', 'postgres', 'postgresql', 'psycopg2'):
|
||||
engine = 'postgresql_psycopg2'
|
||||
if '.' not in engine:
|
||||
engine = 'django.db.backends.' + engine
|
||||
conf['ENGINE'] = engine
|
||||
|
||||
if engine == 'django.db.backends.sqlite3':
|
||||
conf['TEST_NAME'] = conf['NAME'] = ':memory:'
|
||||
elif engine in ('django.db.backends.mysql',
|
||||
'django.db.backends.postgresql_psycopg2'):
|
||||
if not conf['NAME']:
|
||||
conf['NAME'] = 'treebeard'
|
||||
|
||||
# randomizing the test db name,
|
||||
# so we can safely run multiple
|
||||
# tests at the same time
|
||||
conf['TEST_NAME'] = "test_%s_%s" % (
|
||||
conf['NAME'],
|
||||
''.join(random.choice(string.ascii_letters) for _ in range(15))
|
||||
)
|
||||
|
||||
if conf['USER'] == '':
|
||||
conf['USER'] = {
|
||||
'django.db.backends.mysql': 'root',
|
||||
'django.db.backends.postgresql_psycopg2': 'postgres'
|
||||
}[engine]
|
||||
if engine == 'django.db.backends.mysql':
|
||||
conf['OPTIONS'] = {
|
||||
'init_command': 'SET storage_engine=INNODB,'
|
||||
'character_set_connection=utf8,'
|
||||
'collation_connection=utf8_unicode_ci'}
|
||||
return conf
|
||||
|
||||
DATABASES = {'default': get_db_conf()}
|
||||
SECRET_KEY = '7r33b34rd'
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.messages',
|
||||
'treebeard',
|
||||
'treebeard.tests']
|
||||
|
||||
ROOT_URLCONF = 'treebeard.tests.urls'
|
||||
2423
wagtail/vendor/django-treebeard/treebeard/tests/test_treebeard.py
vendored
Normal file
2423
wagtail/vendor/django-treebeard/treebeard/tests/test_treebeard.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
9
wagtail/vendor/django-treebeard/treebeard/tests/urls.py
vendored
Normal file
9
wagtail/vendor/django-treebeard/treebeard/tests/urls.py
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from django.conf.urls import patterns, include, url
|
||||
from django.contrib import admin
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
# Uncomment the next line to enable the admin:
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
)
|
||||
Loading…
Reference in a new issue