is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. xml to make Docutils-native XML files
37 | echo. pseudoxml to make pseudoxml-XML files for display purposes
38 | echo. linkcheck to check all external links for integrity
39 | echo. doctest to run all doctests embedded in the documentation if enabled
40 | goto end
41 | )
42 |
43 | if "%1" == "clean" (
44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
45 | del /q /s %BUILDDIR%\*
46 | goto end
47 | )
48 |
49 |
50 | %SPHINXBUILD% 2> nul
51 | if errorlevel 9009 (
52 | echo.
53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
54 | echo.installed, then set the SPHINXBUILD environment variable to point
55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
56 | echo.may add the Sphinx directory to PATH.
57 | echo.
58 | echo.If you don't have Sphinx installed, grab it from
59 | echo.http://sphinx-doc.org/
60 | exit /b 1
61 | )
62 |
63 | if "%1" == "html" (
64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
68 | goto end
69 | )
70 |
71 | if "%1" == "dirhtml" (
72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
76 | goto end
77 | )
78 |
79 | if "%1" == "singlehtml" (
80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
84 | goto end
85 | )
86 |
87 | if "%1" == "pickle" (
88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can process the pickle files.
92 | goto end
93 | )
94 |
95 | if "%1" == "json" (
96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
97 | if errorlevel 1 exit /b 1
98 | echo.
99 | echo.Build finished; now you can process the JSON files.
100 | goto end
101 | )
102 |
103 | if "%1" == "htmlhelp" (
104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
105 | if errorlevel 1 exit /b 1
106 | echo.
107 | echo.Build finished; now you can run HTML Help Workshop with the ^
108 | .hhp project file in %BUILDDIR%/htmlhelp.
109 | goto end
110 | )
111 |
112 | if "%1" == "qthelp" (
113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
114 | if errorlevel 1 exit /b 1
115 | echo.
116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
117 | .qhcp project file in %BUILDDIR%/qthelp, like this:
118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\complexity.qhcp
119 | echo.To view the help file:
120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\complexity.ghc
121 | goto end
122 | )
123 |
124 | if "%1" == "devhelp" (
125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished.
129 | goto end
130 | )
131 |
132 | if "%1" == "epub" (
133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
137 | goto end
138 | )
139 |
140 | if "%1" == "latex" (
141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
145 | goto end
146 | )
147 |
148 | if "%1" == "latexpdf" (
149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
150 | cd %BUILDDIR%/latex
151 | make all-pdf
152 | cd %BUILDDIR%/..
153 | echo.
154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
155 | goto end
156 | )
157 |
158 | if "%1" == "latexpdfja" (
159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
160 | cd %BUILDDIR%/latex
161 | make all-pdf-ja
162 | cd %BUILDDIR%/..
163 | echo.
164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
165 | goto end
166 | )
167 |
168 | if "%1" == "text" (
169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
170 | if errorlevel 1 exit /b 1
171 | echo.
172 | echo.Build finished. The text files are in %BUILDDIR%/text.
173 | goto end
174 | )
175 |
176 | if "%1" == "man" (
177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
178 | if errorlevel 1 exit /b 1
179 | echo.
180 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
181 | goto end
182 | )
183 |
184 | if "%1" == "texinfo" (
185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
186 | if errorlevel 1 exit /b 1
187 | echo.
188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
189 | goto end
190 | )
191 |
192 | if "%1" == "gettext" (
193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
194 | if errorlevel 1 exit /b 1
195 | echo.
196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
197 | goto end
198 | )
199 |
200 | if "%1" == "changes" (
201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
202 | if errorlevel 1 exit /b 1
203 | echo.
204 | echo.The overview file is in %BUILDDIR%/changes.
205 | goto end
206 | )
207 |
208 | if "%1" == "linkcheck" (
209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
210 | if errorlevel 1 exit /b 1
211 | echo.
212 | echo.Link check complete; look for any errors in the above output ^
213 | or in %BUILDDIR%/linkcheck/output.txt.
214 | goto end
215 | )
216 |
217 | if "%1" == "doctest" (
218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
219 | if errorlevel 1 exit /b 1
220 | echo.
221 | echo.Testing of doctests in the sources finished, look at the ^
222 | results in %BUILDDIR%/doctest/output.txt.
223 | goto end
224 | )
225 |
226 | if "%1" == "xml" (
227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
228 | if errorlevel 1 exit /b 1
229 | echo.
230 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
231 | goto end
232 | )
233 |
234 | if "%1" == "pseudoxml" (
235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
236 | if errorlevel 1 exit /b 1
237 | echo.
238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
239 | goto end
240 | )
241 |
242 | :end
243 |
--------------------------------------------------------------------------------
/docs/readme.rst:
--------------------------------------------------------------------------------
1 | .. include:: ../README.rst
2 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["flit_core >=3.2,<4"]
3 | build-backend = "flit_core.buildapi"
4 |
5 | [project]
6 | name = "wagtailmath"
7 | authors = [{name = "James Ramm", email = "jamessramm@gmail.com"}]
8 | description = "Wagtail StreamField block for rendering mathematical equations"
9 | readme = "README.md"
10 | license = {file = "LICENSE"}
11 | classifiers = [
12 | "Development Status :: 3 - Alpha",
13 | "Intended Audience :: Developers",
14 | "License :: OSI Approved :: BSD License",
15 | "Operating System :: OS Independent",
16 | "Programming Language :: Python",
17 | "Programming Language :: Python :: 3",
18 | "Programming Language :: Python :: 3.8",
19 | "Programming Language :: Python :: 3.9",
20 | "Programming Language :: Python :: 3.10",
21 | "Programming Language :: Python :: 3.11",
22 | "Programming Language :: Python :: 3.12",
23 | "Framework :: Django",
24 | "Framework :: Django :: 4.2",
25 | "Framework :: Django :: 5.0",
26 | "Framework :: Wagtail",
27 | "Framework :: Wagtail :: 5",
28 | "Framework :: Wagtail :: 6",
29 | ]
30 | requires-python = ">=3.8"
31 | dynamic = ["version"]
32 | dependencies = [
33 | "Django>=4.2",
34 | "Wagtail>=5.2"
35 | ]
36 | [project.optional-dependencies]
37 | linting = ["pre-commit>=3.6.0,<4",]
38 | testing = [
39 | "dj-database-url==2.1.0",
40 | "pytest==8.1.1",
41 | "pytest-cov==5.0.0",
42 | "pytest-django==4.8.0",
43 | ]
44 | ci = [
45 | "tox>=4.15.1,<5",
46 | "tox-gh-actions>=3.2,<3.3",
47 | ]
48 |
49 | [project.urls]
50 | Home = "https://github.com/wagtail-nest/wagtail-polymath"
51 | Changelog = "https://github.com/wagtail-nest/wagtail-polymath/blob/main/CHANGELOG.md"
52 | Documentation = "https://github.com/wagtail-nest/wagtail-polymath/blob/main/docs/"
53 |
54 | [tool.flit.module]
55 | name = "wagtailmath"
56 |
57 | [tool.flit.sdist]
58 | exclude = [
59 | "docs",
60 | "tests",
61 | ".*",
62 | "*.js",
63 | "*.json",
64 | "*.ini",
65 | "*.yml",
66 | "ruff.toml",
67 | "Makefile",
68 | ]
69 |
70 | [tool.pytest.ini_options]
71 | pythonpath = ". tests"
72 | DJANGO_SETTINGS_MODULE = "testproject.settings"
73 |
--------------------------------------------------------------------------------
/ruff.toml:
--------------------------------------------------------------------------------
1 | extend-exclude = [
2 | "LC_MESSAGES",
3 | "locale",
4 | ]
5 | line-length = 88
6 |
7 |
8 | [lint]
9 | select = [
10 | "B", # flake8-bugbear
11 | "C4", # flake8-comprehensions
12 | "DJ", # flake8-django
13 | "E", # pycodestyle errors
14 | "F", # pyflakes
15 | "I", # isort
16 | "RUF100", # unused noqa
17 | "S", # flake8-bandit
18 | "UP", # pyupgrade
19 | "W", # warning
20 | ]
21 | fixable = ["C4", "E", "F", "I", "UP"]
22 |
23 | # E501: Line too long
24 | ignore = ["E501"]
25 |
26 |
27 | [lint.isort]
28 | known-first-party = ["wagtail_polymath"]
29 | lines-after-imports = 2
30 | lines-between-types = 1
31 |
32 |
33 | [lint.per-file-ignores]
34 | "tests/**/*.py" = [
35 | "S101", # asserts allowed in tests
36 | "ARG", # unused function args (pytest fixtures)
37 | "FBT", # booleans as positional arguments (@pytest.mark.parametrize)
38 | "PLR2004", # magic value used in comparison
39 | "S311", # standard pseudo-random generators are not suitable for cryptographic purposes
40 | ]
41 | "setup.py" = ["S605", "S607"]
42 |
43 |
44 | [format]
45 | docstring-code-format = true
46 |
--------------------------------------------------------------------------------
/src/wagtailmath/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "1.3.0"
2 |
--------------------------------------------------------------------------------
/src/wagtailmath/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class WagtailMathConfig(AppConfig):
5 | name = "wagtailmath"
6 |
--------------------------------------------------------------------------------
/src/wagtailmath/blocks.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.utils.functional import cached_property
3 | from wagtail.blocks import TextBlock
4 |
5 | from .widgets import MathJaxWidget
6 |
7 |
8 | class MathBlock(TextBlock):
9 | @cached_property
10 | def field(self):
11 | field_kwargs = {"widget": MathJaxWidget(attrs={"rows": self.rows})}
12 | field_kwargs.update(self.field_options)
13 | return forms.CharField(**field_kwargs)
14 |
--------------------------------------------------------------------------------
/src/wagtailmath/models.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wagtail-nest/wagtail-polymath/2a977b5feb2416b755fde168e7ca4a0872665c6a/src/wagtailmath/models.py
--------------------------------------------------------------------------------
/src/wagtailmath/static/wagtailmath/js/mathjax-textarea-adapter.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | function MathJaxTextarea(html, config) {
3 | this.html = html;
4 | this.baseConfig = config;
5 | }
6 | MathJaxTextarea.prototype.render = function(placeholder, name, id, initialState) {
7 | placeholder.outerHTML = this.html.replace(/__NAME__/g, name).replace(/__ID__/g, id);
8 |
9 | var element = document.getElementById(id);
10 | element.value = initialState;
11 |
12 | initMathJaxPreview(id);
13 |
14 | // define public API functions for the widget:
15 | // https://docs.wagtail.io/en/latest/reference/streamfield/widget_api.html
16 | return {
17 | idForLabel: null,
18 | getValue: function() {
19 | return element.value;
20 | },
21 | getState: function() {
22 | return element.value;
23 | },
24 | setState: function() {
25 | throw new Error('MathJaxTextarea.setState is not implemented');
26 | },
27 | getTextLabel: function(opts) {
28 | if (!element.value) return '';
29 | var maxLength = opts && opts.maxLength,
30 | result = element.value;
31 | if (maxLength && result.length > maxLength) {
32 | return result.substring(0, maxLength - 1) + '…';
33 | }
34 | return result;
35 | },
36 | };
37 | };
38 |
39 | window.telepath.register('wagtailmath.widgets.MathJaxWidget', MathJaxTextarea);
40 | })();
41 |
--------------------------------------------------------------------------------
/src/wagtailmath/static/wagtailmath/js/wagtailmath-mathjax-controller.js:
--------------------------------------------------------------------------------
1 | class WagtailMathJaxController extends window.StimulusModule.Controller {
2 | connect() {
3 | initMathJaxPreview(this.element.id);
4 | }
5 | }
6 |
7 | window.wagtail.app.register('wagtailmathjax', WagtailMathJaxController);
8 |
--------------------------------------------------------------------------------
/src/wagtailmath/static/wagtailmath/js/wagtailmath.js:
--------------------------------------------------------------------------------
1 | // Update the preview area on input. Lifted directly from mathjax website examples
2 | class Preview {
3 |
4 | // Get the preview and buffer DIV's
5 | //
6 | constructor(previewId, bufferId, inputId) {
7 | this.preview = document.getElementById(previewId);
8 | this.buffer = document.getElementById(bufferId);
9 | this.input = document.getElementById(inputId);
10 | this.delay = 150; // delay after keystroke before updating
11 | this.timeout = null; // store setTimout id
12 | this.mjRunning = false; // true when MathJax is processing
13 | this.mjPending = false; // true when a typeset has been queued
14 | this.oldText = null; // used to check if an update is needed
15 | }
16 |
17 | //
18 | // Switch the buffer and preview, and display the right one.
19 | // (We use visibility:hidden rather than display:none since
20 | // the results of running MathJax are more accurate that way.)
21 | //
22 | SwapBuffers() {
23 | var buffer = this.preview, preview = this.buffer;
24 | this.buffer = buffer;
25 | this.preview = preview;
26 | buffer.style.visibility = "hidden";
27 | buffer.style.position = "absolute";
28 | preview.style.position = "";
29 | preview.style.visibility = "";
30 | }
31 |
32 | //
33 | // This gets called when a key is pressed in the textarea.
34 | // We check if there is already a pending update and clear it if so.
35 | // Then set up an update to occur after a small delay (so if more keys
36 | // are pressed, the update won't occur until after there has been
37 | // a pause in the typing).
38 | // The callback function is set up below, after the Preview object is set up.
39 | //
40 | Update() {
41 | if (this.timeout) {
42 | clearTimeout(this.timeout);
43 | }
44 | this.timeout = setTimeout(this.callback, this.delay);
45 | }
46 |
47 | //
48 | // Creates the preview and runs MathJax on it.
49 | // If MathJax is already trying to render the code, return
50 | // If the text hasn't changed, return
51 | // Otherwise, indicate that MathJax is running, and start the
52 | // typesetting. After it is done, call PreviewDone.
53 | //
54 | CreatePreview() {
55 | this.timeout = null;
56 | if (this.mjPending) return;
57 | var text = this.input.value;
58 | if (text === this.oldtext) return;
59 | if (this.mjRunning) {
60 | this.mjPending = true;
61 | MathJax.Hub.Queue(["CreatePreview", this]);
62 | } else {
63 | this.buffer.innerHTML = this.oldtext = text;
64 | this.mjRunning = true;
65 | MathJax.Hub.Queue(
66 | ["Typeset", MathJax.Hub, this.buffer],
67 | ["PreviewDone", this]
68 | );
69 | }
70 | }
71 |
72 | //
73 | // Indicate that MathJax is no longer running,
74 | // and swap the buffers to show the results.
75 | //
76 | PreviewDone() {
77 | this.mjRunning = this.mjPending = false;
78 | this.SwapBuffers();
79 | }
80 | }
81 |
82 |
83 | function initMathJaxPreview(id) {
84 | window.wagtailMathPreviews = window.wagtailMathPreviews || {};
85 |
86 | window.wagtailMathPreviews[id] = new Preview(
87 | "MathPreview-" + id,
88 | "MathBuffer-" + id,
89 | id
90 | );
91 |
92 | // Cache a callback to the CreatePreview action
93 | window.wagtailMathPreviews[id].callback = MathJax.Callback(["CreatePreview", window.wagtailMathPreviews[id]]);
94 | window.wagtailMathPreviews[id].callback.autoReset = true; // make sure it can run more than once
95 | window.wagtailMathPreviews[id].Update();
96 |
97 | // attach a keyup event listener so we update the preview
98 | const target = document.getElementById(id);
99 | if (target) {
100 | target.addEventListener("keyup", function() {
101 | window.wagtailMathPreviews[id].Update();
102 | })
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/wagtailmath/templates/wagtailmath/mathjaxwidget.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 | {% translate "Enter the MathJax markup" %}
4 |
5 |
7 |
8 | {% translate "Preview" %}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/wagtailmath/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wagtail-nest/wagtail-polymath/2a977b5feb2416b755fde168e7ca4a0872665c6a/src/wagtailmath/templatetags/__init__.py
--------------------------------------------------------------------------------
/src/wagtailmath/templatetags/wagtailmath.py:
--------------------------------------------------------------------------------
1 | from django import template
2 |
3 | from wagtailmath.widgets import MATHJAX_VERSION
4 |
5 |
6 | register = template.Library()
7 |
8 |
9 | @register.simple_tag
10 | def mathjax(config="TeX-MML-AM_CHTML"):
11 | return f"https://cdnjs.cloudflare.com/ajax/libs/mathjax/{MATHJAX_VERSION}/MathJax.js?config={config}"
12 |
--------------------------------------------------------------------------------
/src/wagtailmath/widgets.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from wagtail import VERSION as WAGTAIL_VERSION
3 | from wagtail.admin.staticfiles import versioned_static
4 |
5 |
6 | MATHJAX_VERSION = "2.7.9"
7 |
8 |
9 | class MathJaxWidgetBase(forms.Textarea):
10 | template_name = "wagtailmath/mathjaxwidget.html"
11 |
12 | def _get_media_js(self):
13 | return (
14 | f"https://cdnjs.cloudflare.com/ajax/libs/mathjax/{MATHJAX_VERSION}/MathJax.js?config=TeX-MML-AM_HTMLorMML",
15 | versioned_static("wagtailmath/js/wagtailmath.js"),
16 | )
17 |
18 | @property
19 | def media(self):
20 | return forms.Media(js=self._get_media_js())
21 |
22 |
23 | if WAGTAIL_VERSION >= (6, 0):
24 |
25 | class MathJaxWidget(MathJaxWidgetBase):
26 | def build_attrs(self, *args, **kwargs):
27 | attrs = super().build_attrs(*args, **kwargs)
28 | attrs["data-controller"] = "wagtailmathjax"
29 |
30 | return attrs
31 |
32 | def _get_media_js(self):
33 | return (
34 | *super()._get_media_js(),
35 | versioned_static("wagtailmath/js/wagtailmath-mathjax-controller.js"),
36 | )
37 | else:
38 | from wagtail.telepath import register
39 | from wagtail.utils.widgets import WidgetWithScript
40 | from wagtail.widget_adapters import WidgetAdapter
41 |
42 | class MathJaxWidget(WidgetWithScript, MathJaxWidgetBase):
43 | def render_js_init(self, id_, name, value):
44 | return f'initMathJaxPreview("{id_}");'
45 |
46 | class MathJaxAdapter(WidgetAdapter):
47 | js_constructor = "wagtailmath.widgets.MathJaxWidget"
48 |
49 | class Media:
50 | # TODO: remove the adapter when dropping support for Wagtail 5.2
51 | js = ["wagtailmath/js/mathjax-textarea-adapter.js"]
52 |
53 | register(MathJaxAdapter(), MathJaxWidget)
54 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wagtail-nest/wagtail-polymath/2a977b5feb2416b755fde168e7ca4a0872665c6a/tests/__init__.py
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | @pytest.fixture(autouse=True)
5 | def temporary_media_dir(settings, tmp_path: pytest.TempdirFactory):
6 | settings.MEDIA_ROOT = tmp_path / "media"
7 |
--------------------------------------------------------------------------------
/tests/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import argparse
4 | import os
5 | import shutil
6 | import sys
7 | import warnings
8 |
9 | from django.core.management import execute_from_command_line
10 |
11 |
12 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproject.settings")
13 | sys.path.append("tests")
14 |
15 |
16 | def make_parser():
17 | parser = argparse.ArgumentParser()
18 | parser.add_argument(
19 | "--deprecation",
20 | choices=["all", "pending", "imminent", "none"],
21 | default="imminent",
22 | )
23 | return parser
24 |
25 |
26 | def parse_args(args=None):
27 | return make_parser().parse_known_args(args)
28 |
29 |
30 | def runtests():
31 | args, rest = parse_args()
32 |
33 | only_wagtail = r"^wagtail(\.|$)"
34 | if args.deprecation == "all":
35 | # Show all deprecation warnings from all packages
36 | warnings.simplefilter("default", DeprecationWarning)
37 | warnings.simplefilter("default", PendingDeprecationWarning)
38 | elif args.deprecation == "pending":
39 | # Show all deprecation warnings from wagtail
40 | warnings.filterwarnings(
41 | "default", category=DeprecationWarning, module=only_wagtail
42 | )
43 | warnings.filterwarnings(
44 | "default", category=PendingDeprecationWarning, module=only_wagtail
45 | )
46 | elif args.deprecation == "imminent":
47 | # Show only imminent deprecation warnings from wagtail
48 | warnings.filterwarnings(
49 | "default", category=DeprecationWarning, module=only_wagtail
50 | )
51 | elif args.deprecation == "none":
52 | # Deprecation warnings are ignored by default
53 | pass
54 |
55 | argv = [sys.argv[0]] + rest
56 |
57 | try:
58 | execute_from_command_line(argv)
59 | finally:
60 | from wagtail.test.settings import MEDIA_ROOT, STATIC_ROOT
61 |
62 | shutil.rmtree(STATIC_ROOT, ignore_errors=True)
63 | shutil.rmtree(MEDIA_ROOT, ignore_errors=True)
64 |
65 |
66 | if __name__ == "__main__":
67 | runtests()
68 |
--------------------------------------------------------------------------------
/tests/test_placeholder.py:
--------------------------------------------------------------------------------
1 | """
2 | Placeholder test, so that pytest doesn't fail with an empty testsuite. Feel free to
3 | remove this when you start writing tests.
4 | https://github.com/pytest-dev/pytest/issues/2393
5 | """
6 |
7 | import pytest
8 |
9 |
10 | pytestmark = pytest.mark.django_db
11 |
12 |
13 | def test_homepage(client):
14 | assert client.get("/").status_code == 200
15 |
--------------------------------------------------------------------------------
/tests/testapp/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wagtail-nest/wagtail-polymath/2a977b5feb2416b755fde168e7ca4a0872665c6a/tests/testapp/__init__.py
--------------------------------------------------------------------------------
/tests/testapp/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.0.6 on 2024-06-21 09:50
2 |
3 | import django.db.models.deletion
4 | import wagtail.blocks
5 | import wagtail.fields
6 | import wagtailmath.blocks
7 |
8 | from django.db import migrations, models
9 |
10 |
11 | class Migration(migrations.Migration):
12 | initial = True
13 |
14 | dependencies = [
15 | ("wagtailcore", "0089_log_entry_data_json_null_to_object"),
16 | ]
17 |
18 | operations = [
19 | migrations.CreateModel(
20 | name="MathPage",
21 | fields=[
22 | (
23 | "page_ptr",
24 | models.OneToOneField(
25 | auto_created=True,
26 | on_delete=django.db.models.deletion.CASCADE,
27 | parent_link=True,
28 | primary_key=True,
29 | serialize=False,
30 | to="wagtailcore.page",
31 | ),
32 | ),
33 | (
34 | "body",
35 | wagtail.fields.StreamField(
36 | [
37 | (
38 | "heading",
39 | wagtail.blocks.CharBlock(form_classname="full title"),
40 | ),
41 | ("paragraph", wagtail.blocks.RichTextBlock()),
42 | ("equation", wagtailmath.blocks.MathBlock()),
43 | ],
44 | use_json_field=True,
45 | ),
46 | ),
47 | ],
48 | options={
49 | "abstract": False,
50 | },
51 | bases=("wagtailcore.page",),
52 | ),
53 | ]
54 |
--------------------------------------------------------------------------------
/tests/testapp/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wagtail-nest/wagtail-polymath/2a977b5feb2416b755fde168e7ca4a0872665c6a/tests/testapp/migrations/__init__.py
--------------------------------------------------------------------------------
/tests/testapp/models.py:
--------------------------------------------------------------------------------
1 | from wagtail import blocks
2 | from wagtail.admin.panels import FieldPanel
3 | from wagtail.fields import StreamField
4 | from wagtail.models import Page
5 | from wagtailmath.blocks import MathBlock
6 |
7 |
8 | class MathPage(Page):
9 | body = StreamField(
10 | [
11 | ("heading", blocks.CharBlock(classname="full title")),
12 | ("paragraph", blocks.RichTextBlock()),
13 | ("equation", MathBlock()),
14 | ],
15 | use_json_field=True,
16 | )
17 |
18 | content_panels = Page.content_panels + [FieldPanel("body")]
19 |
--------------------------------------------------------------------------------
/tests/testapp/templates/testapp/math_page.html:
--------------------------------------------------------------------------------
1 | {% load wagtailmath %}
2 |
3 |
4 |
5 |
6 |
7 | {{ page.title }}
8 |
9 |
10 | {{ page.body }}
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/tests/testproject/__init__.py:
--------------------------------------------------------------------------------
1 | default_app_config = "tests.apps.WagtailPolymathTestAppConfig"
2 |
--------------------------------------------------------------------------------
/tests/testproject/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for temp project.
3 |
4 | For more information on this file, see
5 | https://docs.djangoproject.com/en/stable/topics/settings/
6 |
7 | For the full list of settings and their values, see
8 | https://docs.djangoproject.com/en/stable/ref/settings/
9 | """
10 |
11 | import os
12 |
13 | import dj_database_url
14 |
15 |
16 | # Build paths inside the project like this: os.path.join(PROJECT_DIR, ...)
17 | PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18 | BASE_DIR = os.path.dirname(PROJECT_DIR)
19 |
20 | # Quick-start development settings - unsuitable for production
21 | # See https://docs.djangoproject.com/en/stable/howto/deployment/checklist/
22 |
23 | # SECURITY WARNING: keep the secret key used in production secret!
24 | SECRET_KEY = "not-a-secure-key" # noqa: S105
25 |
26 | # SECURITY WARNING: don't run with debug turned on in production!
27 | DEBUG = True
28 |
29 | ALLOWED_HOSTS = ["localhost", "testserver"]
30 |
31 | IS_INTERACTIVE = "INTERACTIVE" in os.environ
32 |
33 | # Application definition
34 |
35 | INSTALLED_APPS = [
36 | "wagtailmath",
37 | "testapp",
38 | "wagtail.users",
39 | "wagtail.snippets",
40 | "wagtail.documents",
41 | "wagtail.images",
42 | "wagtail.search",
43 | "wagtail.admin",
44 | "wagtail.contrib.routable_page",
45 | "wagtail.contrib.styleguide",
46 | "wagtail.sites",
47 | "wagtail",
48 | "taggit",
49 | "django.contrib.admin",
50 | "django.contrib.auth",
51 | "django.contrib.contenttypes",
52 | "django.contrib.sessions",
53 | "django.contrib.messages",
54 | "django.contrib.staticfiles",
55 | ]
56 |
57 | MIDDLEWARE = [
58 | "django.middleware.security.SecurityMiddleware",
59 | "django.contrib.sessions.middleware.SessionMiddleware",
60 | "django.middleware.common.CommonMiddleware",
61 | "django.middleware.csrf.CsrfViewMiddleware",
62 | "django.contrib.auth.middleware.AuthenticationMiddleware",
63 | "django.contrib.messages.middleware.MessageMiddleware",
64 | "django.middleware.clickjacking.XFrameOptionsMiddleware",
65 | "wagtail.contrib.redirects.middleware.RedirectMiddleware",
66 | ]
67 |
68 | ROOT_URLCONF = "testproject.urls"
69 |
70 | TEMPLATES = [
71 | {
72 | "BACKEND": "django.template.backends.django.DjangoTemplates",
73 | "DIRS": [],
74 | "APP_DIRS": True,
75 | "OPTIONS": {
76 | "context_processors": [
77 | "django.template.context_processors.debug",
78 | "django.template.context_processors.request",
79 | "django.contrib.auth.context_processors.auth",
80 | "django.contrib.messages.context_processors.messages",
81 | ]
82 | },
83 | }
84 | ]
85 |
86 |
87 | # Using DatabaseCache to make sure that the cache is cleared between tests.
88 | # This prevents false-positives in some wagtail core tests where we are
89 | # changing the 'wagtail_root_paths' key which may cause future tests to fail.
90 | CACHES = {
91 | "default": {
92 | "BACKEND": "django.core.cache.backends.db.DatabaseCache",
93 | "LOCATION": "cache",
94 | }
95 | }
96 |
97 |
98 | # don't use the intentionally slow default password hasher
99 | PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",)
100 |
101 |
102 | # Database
103 | # https://docs.djangoproject.com/en/stable/ref/settings/#databases
104 |
105 | DATABASES = {
106 | "default": dj_database_url.config(default="sqlite:///test_wagtail_polymath.db"),
107 | }
108 |
109 |
110 | # Password validation
111 | # https://docs.djangoproject.com/en/stable/ref/settings/#auth-password-validators
112 |
113 | AUTH_PASSWORD_VALIDATORS = [
114 | {
115 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
116 | },
117 | {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
118 | {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
119 | {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
120 | ]
121 |
122 |
123 | # Internationalization
124 | # https://docs.djangoproject.com/en/stable/topics/i18n/
125 |
126 | LANGUAGE_CODE = "en-us"
127 | TIME_ZONE = "UTC"
128 | USE_I18N = True
129 | USE_L10N = True
130 | USE_TZ = True
131 |
132 |
133 | # Static files (CSS, JavaScript, Images)
134 | # https://docs.djangoproject.com/en/stable/howto/static-files/
135 |
136 | STATICFILES_FINDERS = [
137 | "django.contrib.staticfiles.finders.FileSystemFinder",
138 | "django.contrib.staticfiles.finders.AppDirectoriesFinder",
139 | ]
140 |
141 | STATICFILES_DIRS = [os.path.join(PROJECT_DIR, "static")]
142 |
143 | STATIC_ROOT = os.path.join(BASE_DIR, "test-static")
144 | STATIC_URL = "/static/"
145 |
146 | MEDIA_ROOT = os.path.join(BASE_DIR, "test-media")
147 |
148 |
149 | # Wagtail settings
150 |
151 | WAGTAIL_SITE_NAME = "Wagtail Polymath test site"
152 | WAGTAILADMIN_BASE_URL = "http://localhost:8020"
153 |
--------------------------------------------------------------------------------
/tests/testproject/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import include, path
2 | from wagtail import urls as wagtail_urls
3 | from wagtail.admin import urls as wagtailadmin_urls
4 | from wagtail.documents import urls as wagtaildocs_urls
5 |
6 |
7 | urlpatterns = [
8 | path("admin/", include(wagtailadmin_urls)),
9 | path("documents/", include(wagtaildocs_urls)),
10 | path("", include(wagtail_urls)),
11 | ]
12 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | min_version = 4.11
3 |
4 | envlist =
5 | python{3.8,3.9,3.10,3.11}-django{4.2}-wagtail{5.2,6.1}-{sqlite}
6 | python{3.10,3.11,3.12}-django{5.0}-wagtail{5.2,6.1}-{sqlite}
7 |
8 | [gh-actions]
9 | python =
10 | 3.8: python3.8
11 | 3.9: python3.9
12 | 3.10: python3.10
13 | 3.11: python3.11
14 | 3.12: python3.12
15 |
16 | [testenv]
17 | package = wheel
18 | wheel_build_env = .pkg
19 | use_frozen_constraints = true
20 | constrain_package_deps = true
21 |
22 | pass_env =
23 | FORCE_COLOR
24 | NO_COLOR
25 |
26 | setenv =
27 | PYTHONPATH = {toxinidir}/tests:{toxinidir}
28 | PYTHONDEVMODE = 1
29 | # will use the Python 3.12+ sys.monitoring when available
30 | COVERAGE_CORE=sysmon
31 | COVERAGE_FILE = .coverage.{envname}
32 |
33 | deps =
34 | coverage>=7.0,<8.0
35 |
36 | django4.2: Django>=4.2,<4.3
37 | django5.0: Django>=5.0,<5.1
38 |
39 | wagtail5.2: wagtail>=5.2,<5.3
40 | wagtail6.1: wagtail>=6.1,<6.2
41 |
42 | extras = testing
43 |
44 | install_command = python -Im pip install -U --pre {opts} {packages}
45 |
46 | allowlist_externals = pytest
47 | commands_pre = python -I {toxinidir}/tests/manage.py migrate
48 | commands = python -Im pytest --cov --cov-append --ignore=docs/ {posargs: -vv}
49 |
50 | [testenv:interactive]
51 | description = An interactive environment for local testing purposes
52 | basepython = python3.12
53 | package = editable
54 |
55 | deps =
56 | wagtail >= 5.2, <6.0
57 |
58 | commands_pre =
59 | python {toxinidir}/tests/manage.py makemigrations
60 | python {toxinidir}/tests/manage.py migrate
61 | python {toxinidir}/tests/manage.py shell -c "from django.contrib.auth.models import User;(not User.objects.filter(username='admin').exists()) and User.objects.create_superuser('admin', 'super@example.com', 'changeme')"
62 | python {toxinidir}/tests/manage.py createcachetable
63 |
64 | commands =
65 | {posargs:python -I {toxinidir}/tests/manage.py runserver 0.0.0.0:8020}
66 |
67 | setenv =
68 | INTERACTIVE = 1
69 |
70 | [testenv:wagtailmain]
71 | deps =
72 | flit>=3.8
73 | coverage>=7.0,<8.0
74 | wagtailmain: git+https://github.com/wagtail/wagtail.git@main#egg=Wagtail
75 |
--------------------------------------------------------------------------------