Compare commits

...

228 commits

Author SHA1 Message Date
Benedikt Willi
765ac7a19d Rewrote JavaScript to be compatible with EcmaScript < 6. Clarified js docs in version_compare.js 2019-11-11 10:41:14 +01:00
Benedikt Willi
57d0a348bf Updated to work with Wagtail 2.7. 2019-11-08 16:39:17 +01:00
Benedikt Willi
154acd006c Added tests for new Wagtail 2.7 and Python 3.8 2019-11-08 16:39:00 +01:00
Diogo Marques
18ed10a2a9
Merge pull request #258 from easherma/bindtomodel-version
different bindto model function depending on wagtail version
2019-10-25 19:26:16 +01:00
Eric Sherman
a92d287a6e nested if 2019-10-25 13:17:44 -05:00
Eric Sherman
187e524ed2 import verision constant 2019-10-25 13:14:29 -05:00
Eric Sherman
e742a93f4c different bindto model function depending on wagtail version 2019-10-25 13:06:15 -05:00
Diogo Marques
1366c86151
Merge pull request #252 from Hopiu/master
Fixed missing copy buttons for Wagtail 2.6.x.
2019-10-23 12:27:43 +01:00
Diogo Marques
8b945cec37
Merge branch 'master' into master 2019-10-23 12:22:29 +01:00
Diogo Marques
40b2b01648 Bumped version 0.10.5 2019-09-09 15:20:50 +01:00
Diogo Marques
a170f7a213 Hotfix: include templates in instalation 2019-09-09 15:16:14 +01:00
Diogo Marques
4456f6f902 Bumped version 0.10.4 2019-09-09 14:42:06 +01:00
Diogo Marques
80def8ecde
Merge pull request #253 from floese/master
fixed import statement to match wagtails reorganised wagtail.admin.fo…
2019-09-09 14:21:55 +01:00
Floroni
29404348cb fixed import statement to match wagtails reorganised wagtail.admin.forms module 2019-09-09 11:40:44 +02:00
Miguel Silva
1495f7278a Added the templates directory to the installation 2019-09-09 10:19:27 +01:00
Miguel Silva
6fb4725f65 Merge remote-tracking branch 'upstream/master' 2019-09-06 15:39:38 +01:00
Miguel Silva
3191abde46 Merge branch 'ghost-pull_request-fix_page_copy' 2019-09-06 15:32:35 +01:00
Miguel Silva
86c4711e48 Raise ValidationError if save has errors 2019-09-06 15:15:41 +01:00
Miguel Silva
6cbd98ee54 Fixed imports 2019-09-06 14:19:57 +01:00
Erwhann-Rouge Guilhem MAS-PAITRAULT
3034d5807c Corrected template rendering "can publish" field twice 2019-09-06 12:43:40 +01:00
Erwhann-Rouge Guilhem MAS-PAITRAULT
07e0e98b47 Use SlugField and old slug to fill new page slugs 2019-09-06 12:43:40 +01:00
Erwhann-Rouge Guilhem MAS-PAITRAULT
88e5895c5b Removed hardcoded language codes, replaced by settings.LANGUAGES 2019-09-06 12:43:40 +01:00
Erwhann-Rouge Guilhem MAS-PAITRAULT
55fff643d3 Patched copy view from wagtail in before_page_copy hook 2019-09-06 12:43:40 +01:00
Erwhann-Rouge Guilhem MAS-PAITRAULT
d630f9c988 Created new form to override Wagtail's CopyForm 2019-09-06 12:42:24 +01:00
Erwhann-Rouge Guilhem MAS-PAITRAULT
fe0cd4c0b6 Template now load titles and slugs fields in all languages 2019-09-06 12:42:24 +01:00
Erwhann-Rouge Guilhem MAS-PAITRAULT
8a1a9eb94b Added copy form template to override the Wagtail one 2019-09-06 12:42:24 +01:00
Erwhann-Rouge Guilhem MAS-PAITRAULT
425438acd1 Added primitive hook to before_page_copy 2019-09-06 12:42:24 +01:00
Benedikt Willi
ff8a75479c Added tox.ini environments 2019-09-05 17:06:06 +02:00
Benedikt Willi
af888092bf Fixed missing copy buttons for Wagtail 2.6.x. 2019-09-05 16:33:04 +02:00
Diogo Marques
34e67bd0ed
Merge pull request #251 from Hopiu/master
Updated travis-ci configuration
2019-09-05 14:48:50 +01:00
Benedikt Willi
da68dd1e40 Updated travis-ci configuration 2019-09-05 14:54:47 +02:00
Miguel Silva
eb5eee8092
Merge pull request #248 from onkruid/static-tag
Replace direct use of STATIC_URL with static templatetag
2019-07-23 15:59:15 +01:00
Thomas Lagae
5ec163b020 Replace direct use of STATIC_URL with static templatetag 2019-07-20 17:53:39 +02:00
Alexandre Silva
75facc5f22 Bump version: 0.10.1 → 0.10.2 2019-06-06 17:22:18 +01:00
Alexandre Silva
f9afc8dd17 Adds dependencies for release process 2019-06-06 17:18:27 +01:00
Diogo Marques
f52993d73c
Merge pull request #239 from lucasmoeskops/fix-edit-handler-iterator
Fix invalid iterator
2019-06-06 16:29:31 +01:00
Diogo Marques
58fdc7148b
Merge pull request #246 from msilvapor/master
Allows patching admin interface for all models registered for translation
2019-06-06 16:23:29 +01:00
Miguel Silva
f1b04f9202 Hotfix: fixes psycopg2 binary installation 2019-06-06 16:14:48 +01:00
Miguel Silva
30a6ada4de Fixes psycopg2 bug 2019-06-06 16:06:43 +01:00
Miguel Silva
bbe19792c9 Allows patching admin interface for all models registered for translation 2019-06-06 15:59:25 +01:00
Miguel Silva
640d5202c9 Linter fix 2019-06-06 15:58:12 +01:00
Diogo Marques
5ef04f30f2 Fixes #242
Set a default url_path for old instances
2019-05-09 11:41:34 +01:00
Lucas Moeskops
b883f051f3 Fix invalid iterator 2019-04-01 15:46:37 +02:00
Diogo Marques
8deb0424f1 Updated docs;
bump bersion 0.10.1
2019-03-21 17:10:58 +00:00
Diogo Marques
7d9b42f542
Merge pull request #235 from Pomax/patch-1
Fixes #234
2019-03-21 12:04:09 +00:00
Diogo Marques
4f0b07789c Fixes error on apps.py: TypeError: can only concatenate list (not "tuple") to list 2019-03-21 10:47:32 +00:00
Pomax
372a2f31df
Update apps.py 2019-03-19 11:36:42 -07:00
Pomax
304554f405 Update apps.py
From https://github.com/infoportugal/wagtail-modeltranslation/issues/234#issuecomment-474096604, the timing between django-modeltranslation and wagtail-modeltranslation leads to wmt updating the settings variable for dmt too late (namely, after dmt already read it out). This PR ensures that the `CUSTOM_FIELDS` value is set directly on dmt's settings. This fixes the (original) issue reported in https://github.com/infoportugal/wagtail-modeltranslation/issues/234
2019-03-19 11:18:48 -07:00
Diogo Marques
d0034364d2
Merge pull request #229 from infoportugal/populate_slug_not_translated
Populate slug not translated
2019-03-12 17:18:36 +00:00
Diogo Marques
89d285555e .gitignore updated 2019-02-05 11:00:01 +00:00
Diogo Marques
1db9f3947c Auto populate slug when not translated 2019-02-05 10:59:51 +00:00
Diogo Marques
1193528e75 Merge branch 'django2' 2019-02-01 17:00:32 +00:00
Diogo Marques
466d88fdf2 Version 0.10b1 2019-02-01 16:59:31 +00:00
Diogo Marques
2b09b06eb3
Merge pull request #223 from Sempiternal02/patch-1
Update translation.py
2019-02-01 16:17:05 +00:00
Diogo Marques
b0a4e61d4a
Merge pull request #227 from tomazursic/fix-doc-setup
Add migrate module to INSTALLED_APPS setting list
2019-02-01 15:14:46 +00:00
Diogo Marques
3f9ac92bc8
Merge pull request #228 from infoportugal/django2
Django2
2019-02-01 15:14:19 +00:00
Diogo Marques
24df39a0d8 Hotfix: django 2.1 only supports postgres 9.4+, so we need to override
version 9.2 of travis
2019-02-01 14:53:56 +00:00
Diogo Marques
2c10234f80 Hotfix: travis does not have python 3.7 2019-02-01 14:38:06 +00:00
Diogo Marques
3d84c4a78f Hotfix: missing import 2019-02-01 14:36:48 +00:00
Diogo Marques
1cb2a41a08 Hotfix: missing import 2019-02-01 14:28:41 +00:00
Diogo Marques
8b8f64a7cc
Merge branch 'master' into django2 2019-02-01 14:22:48 +00:00
Diogo Marques
293d518f8a v0.10b1, wagtail support 1.12+ 2019-02-01 14:15:14 +00:00
Tomaz
46059f791b Add migrate module to INSTALLED_APPS setting list 2019-02-01 09:48:22 +01:00
Alexandre Silva
d9a8c2c06d
Merge pull request #224 from OndrejSodek/patch-2
Fix diacritics downcoding in slugs
2018-12-13 17:21:48 +00:00
Ondřej Šodek
4614b5faa4
Fix diacritics downcoding in slugs
This change correctly downcodes diacritics to ASCII if user has WAGTAIL_ALLOW_UNICODE_SLUGS = False (e.g. abčďéfg -> abcdefg)
Without this change, the slug simply strips all diacritics which is not ideal (e.g. abčďěfg -> abfg)
2018-12-12 19:09:56 +01:00
Sempiternal02
dcf2651ee7
Update translation.py 2018-10-16 23:42:50 +03:00
Diogo Marques
e7adf51d2a
Merge pull request #214 from infoportugal/210_hide_old_fields
Hide old fields for snippets without panels defenition
2018-08-21 17:18:49 +01:00
Diogo Marques
060491a75e
Merge pull request #215 from infoportugal/213_slug_translation_optional
Adds setting to translate slugs (default True)
2018-08-21 17:17:48 +01:00
DiogoMarques
7658d2e8d8 Merge branch 'master' into 213_slug_translation_optional 2018-08-21 15:33:07 +01:00
Diogo Marques
cd8a9ad5f0
Merge pull request #220 from infoportugal/python_accepted_version_cleanup
Droped python version 3.3 and added 3.6
2018-08-21 15:31:39 +01:00
DiogoMarques
54a2729283 Droped python version 3.3 and added 3.6 2018-08-21 15:08:23 +01:00
DiogoMarques
f7d748b7ee Moving translatable Page fields definition to the class 2018-08-21 14:20:10 +01:00
Diogo Marques
d5ecd24d70
Merge pull request #217 from infoportugal/document-migrations
Update README.rst
2018-06-26 12:55:55 +01:00
nulopes
e84cdc6acb
Update README.rst
Fixes #193
2018-06-26 11:46:46 +00:00
DiogoMarques
531a7494c2 Adds setting to translate slugs (default True) 2018-06-21 12:10:17 +01:00
DiogoMarques
7929589e7a Hotfix: import for wagtail >= 2.0 2018-06-14 11:27:06 +01:00
DiogoMarques
49246457cb Hide old fields for snippets without panels defenition 2018-06-14 11:20:12 +01:00
Dario Marcelino
11cbedfe13 #191, drop support for Wagtail < 1.8
And inherently Django < 1.8
2018-04-24 16:52:15 +01:00
Dario Marcelino
7d69a7135d #191, Django 2 compatibility 2018-04-24 16:42:07 +01:00
Dario Marcelino
adeb645a69 #191, update django-modeltranslation to 0.13b1 and update version to 0.10b1 2018-04-24 15:40:17 +01:00
DiogoMarques
0be5939cdc Bump version: 0.8.1 → 0.9.0 2018-04-24 11:39:45 +01:00
Diogo Marques
f795622109
Merge pull request #200 from kakulukia/master
added Makefile for easy releases
2018-04-24 11:36:12 +01:00
Diogo Marques
77a86fb894
Merge pull request #201 from akmiller01/master
Patch wagtail hooks to account for unpredictable dict internal table
2018-04-17 16:20:46 +01:00
Alex Miller
50d78a1826 Patch wagtail hooks to account for unpredictable dict internal table 2018-04-16 15:10:57 -04:00
Andy Grabow
e40c776da3 new option to skip the version bump 2018-04-16 13:37:26 +02:00
Andy Grabow
5d42d99430 not needed either 2018-04-13 23:51:49 +02:00
Andy Grabow
eac95ec38a the import is not needed - bad PyCharm! 2018-04-13 23:50:45 +02:00
Andy Grabow
be57e57077 added Makefile for easy releases
Bump version: 0.8.1 → 0.8.2

Bump version: 0.8.2 → 0.8.3

included version in package
DRY version from package

revert the version to the current value

remove the breakpoint

change version in the new location

typo

activate compilation and uploading
2018-04-13 12:16:01 +02:00
Diogo Marques
a91a158612
Merge pull request #199 from kakulukia/master
lets be pep compliant
2018-04-10 10:05:14 +01:00
kakulukia
7613c7bd0f
lets be pep compliant
https://www.python.org/dev/peps/pep-0508/

otherwise pipenv cant install this package correctly
2018-04-09 16:20:36 +02:00
Diogo Marques
3f1882bad0
Merge pull request #197 from dmarcelino/root_page_slug
_get_site_root_paths: clear cache when changing languages
2018-04-09 11:42:25 +01:00
Dario Marcelino
a92cf200a3 #195: cache each language's site root paths individually
instead of evicting the cache when language changes.
Patch `Page._get_site_root_paths()` and `Page.get_url_parts()` on Wagtail<1.11
Update `test_root_page_slug` to cover more scenarios
2018-04-03 09:04:02 +01:00
Dario Marcelino
2621e6e407 #195: replace _new_get_site_root_paths with decorator
While not as robust, this allows greater flexibility as we can re-use the decorator to decorate Model.get_url_parts() on wagtail<1.11
2018-04-02 19:24:54 +01:00
Dario Marcelino
c63eee4506 #195: if cached site_root_paths has different language clear cache and refetch them 2018-04-02 18:32:36 +01:00
Dario Marcelino
5648c0b164 #195: failing test for translated root_page.slug 2018-04-02 17:15:15 +01:00
Dario Marcelino
c78cac792b #195, PageFactory.create_page_tree: add a top root node to mimic Wagtail's behaviour 2018-04-02 17:13:06 +01:00
Dario Marcelino
16e08c30fc chore(gitignore): PyCharm 2018-04-02 16:41:36 +01:00
Diogo Marques
a61136e028
Merge pull request #188 from dmarcelino/wt2_dj111
Force django<2 while allowing Wagtail 2
2018-03-27 12:16:48 +01:00
Dario Marcelino
52e0c5aba5 Setup.py: force django<2 while allowing Wagtail 2 2018-03-21 03:29:19 +00:00
Tiago Costa
7591c015c9 Updated package information 2018-03-15 10:45:41 +00:00
Tiago Costa
6ef46d6cdc HotFix: Fixed validation for wagtail_search whenever there was more than one parameter on the 'change_lang' template tag 2018-03-14 15:14:48 +00:00
Diogo Marques
a323535582
Merge pull request #178 from dmarcelino/migrate
Add Migrate command
2018-03-14 15:13:00 +00:00
Diogo Marques
8c45ea32fd
Merge pull request #185 from dmarcelino/wagtail2
Add support for Wagtail 2
2018-03-14 14:11:08 +00:00
Diogo Marques
a8b2921b69
Merge pull request #181 from dmarcelino/travis_django_version
Travis: make clear which django version is used
2018-03-14 12:52:58 +00:00
Stuart Axon
03e8b0e38c Fix version to work in early wagtail 2018-03-14 12:06:41 +00:00
DESKTOP-QK6PGF0\dario
b26759205d Travis: test against Wagtail 2 2018-03-14 11:59:08 +00:00
Stuart Axon
f3ec60184a Use django.urls for newer django 2018-03-14 11:45:13 +00:00
Stuart Axon
cc83d9881a Add a missing wagtail2 import 2018-03-14 11:44:25 +00:00
Stuart Axon
45e7b4adf0 Fix more imports 2018-03-14 11:39:36 +00:00
Stuart Axon
ef1e0bf5ee Missing import 2018-03-14 11:39:23 +00:00
Stuart Axon
cf2bcc5a7f Update tests to work on wt2 2018-03-14 11:38:36 +00:00
Stuart Axon
7cc4eece20 Change imports for wagtail2 2018-03-14 11:38:10 +00:00
Jason Abbott
19ad0226f3 ensure wagtail version < 2 2018-03-14 11:27:59 +00:00
Dário
093b251008 Make clearer which django version is being tested
- by printing it to the console;
- by outputting `pip install $WAGTAIL` output.
- Also remove `--process-dependency-links` as it's no longer needed.
2018-02-28 17:40:17 +00:00
Diogo Marques
f8c3dd369f
Merge pull request #164 from sylvainblot/patch-1
Fix slug auto-population for translation with dash
2018-02-28 15:04:22 +00:00
Diogo Marques
51091640fa
Merge pull request #179 from stuaxo/patch-1
Update setup.py
2018-02-28 14:21:16 +00:00
Stuart Axon
ebfd3a67dc
Update setup.py
Move from distutils to setuptools so that ```python setup.py develop``` can be used.
2018-02-28 11:06:05 +00:00
Dário
1f747cfd6f
Document migrate command 2018-02-23 16:37:10 +00:00
Dario Marcelino
69ae7dfbd8 #175: Override Migrate command
To additionaly run `sync_page_translation_fields`
To silence any missing migrations warnings
2018-02-23 15:35:32 +00:00
Alexandre Silva
2135d7d081
Merge pull request #177 from dmarcelino/safer_slugurl
Make slugurl_trans tag resilient to missing request
2018-02-21 09:59:33 +00:00
Dario Marcelino
d542dacbb3 #176: remove context['request'] to prevent KeyError 2018-02-20 18:13:38 +00:00
Diogo Marques
06fd1e8bfa
Merge pull request #172 from dmarcelino/streamfield_required_2
Make localized StreamField optional (Wagtail >= 1.12)
2018-02-15 12:34:57 +00:00
Dario Marcelino
b3d654eba5 #170: only test StreamField required for wagtail 1.12 and above
Since Wagtail didn't honour StreamField blank before 1.12 tests would
fail
2018-02-14 19:09:50 +00:00
Dario Marcelino
080bd89b6f #170: make localized StreamFields default to optional 2018-02-14 18:08:22 +00:00
Dario Marcelino
eec0108f33 #170: failing test for StreamField required 2018-02-14 17:59:08 +00:00
Diogo Marques
77b352e53c
Merge pull request #168 from dmarcelino/modeltranslation_0_12_2
Update django-modeltranslation to v0.12.2 and bump version to 0.8
2018-01-30 10:06:48 +00:00
Dario Marcelino
358dc259e1 Update django-modeltranslation to v0.12.2 and bump version to 0.8 2018-01-29 15:39:43 +00:00
DiogoMarques
0b81be9d6e Final version for setup.py 0.8 alpha 2018-01-15 14:26:03 +00:00
DiogoMarques
7d02d760cd Added version 0.8 alpha 1 2018-01-15 12:53:09 +00:00
DiogoMarques
2377538f1a Rollback some changes to setup.py 2018-01-15 12:35:43 +00:00
DiogoMarques
b591fc1047 Fixed link to django-modeltranslation commit 2018-01-15 12:10:57 +00:00
DiogoMarques
064625cebd Dependency 'django-modeltranslation' installed via github commit 2018-01-15 12:00:09 +00:00
DiogoMarques
feedd129bd Added sub package 'makemigrations' to setup.py 2018-01-15 11:54:06 +00:00
Sylvain BLOT
4ed78783e6
Fix slug auto-population for translation with dash 2018-01-15 12:12:22 +01:00
Diogo Marques
b677f43dca
Merge pull request #163 from dmarcelino/docs
Update docs: sync README with Installation
2018-01-08 12:52:33 +00:00
Dario Marcelino
3c70fd4a0a Update docs: sync README with Installation
Replace TranslationOption with TranslationOptions
Update examples with latest Django syntax:
 - MIDDLEWARE instead of MIDDLEWARE_CLASSES
 - _('language') instead of u'language'
2018-01-08 12:06:15 +00:00
Diogo Marques
03cd69e8b9
Merge pull request #162 from dmarcelino/travis_matrix
Travis: reduce number of jobs from 40 to 15
2018-01-08 11:06:21 +00:00
Diogo Marques
3d95413a4c
Merge pull request #161 from dmarcelino/page_tr_options
Remove extraneous Meta class from PageTR
2018-01-06 12:20:34 +00:00
Diogo Marques
e859632484
Merge pull request #160 from dmarcelino/fix_merge
Remove _new_relative_url and _new_url methods
2018-01-06 12:20:08 +00:00
Dario Marcelino
a34e079305 Travis: fix mysql and python 3.3 jobs 2018-01-06 12:15:29 +00:00
Dario Marcelino
539254316c Travis: reduce the number of jobs by using matrix include
And test mysql and postgres
2018-01-06 12:05:05 +00:00
Dario Marcelino
2205fd719f Remove _new_relative_url and _new_url methods
These had been removed before but were reintroduced during experimental
branch merge
2018-01-05 19:14:24 +00:00
Dario Marcelino
ee3bda2248 Remove extraneous Meta class from PageTR 2018-01-05 19:02:25 +00:00
DiogoMarques
8c246f7f89 Merge branch 'experimental' 2018-01-05 18:36:44 +00:00
Diogo Marques
7b05253a39
Merge pull request #159 from dmarcelino/wag_1_13
Add support for Wagtail 1.13
2018-01-05 18:01:59 +00:00
Diogo Marques
4e38d8b654
Merge pull request #158 from infoportugal/docs
Document caveats and management commands
2018-01-05 17:50:51 +00:00
Dario Marcelino
f02b9294c3 Merge branch 'travis_wagtail' into wag_1_13 2018-01-05 16:36:13 +00:00
Dario Marcelino
b62be23616 Patches _update_descendant_url_paths to add .rewrite(False) to queryset
- _localized_update_descendant_url_paths: replaces raw SQL query for
Django queryset
- Refactors some *_descendant_url_paths methods for better clarity
2018-01-05 16:35:58 +00:00
Dario Marcelino
abeadb6871 Docs: add caveats
[ci skip]
2018-01-05 11:47:46 +00:00
Dario Marcelino
8e3d549a66 Merge branch 'experimental' into docs 2018-01-05 11:33:52 +00:00
Diogo Marques
6aaa9fbeda
Merge pull request #157 from dmarcelino/remove_pre_save
Remove slug .pre_save() patch
2018-01-04 17:11:58 +00:00
Diogo Marques
d6ed53c0fc
Merge pull request #156 from dmarcelino/travis
Make experimental build on travis
2018-01-04 17:09:27 +00:00
Dario Marcelino
0bca45dcbb Docs: add management commands to index
[ci skip]
2018-01-04 15:12:32 +00:00
Dário
45c7c9e2c5
Update management commands.rst
[ci skip]
2018-01-04 15:09:57 +00:00
Dario Marcelino
487741ecbe Drop python 3.3 in favor of 3.6
Note that Wagtail tests from python 3.4 to 3.6:
9926745262/.travis.yml
2018-01-04 11:38:57 +00:00
Dario Marcelino
787bbf4344 Travis: add the latest versions of wagtail 2018-01-04 11:18:27 +00:00
Dario Marcelino
3b463f7ee7 Remove slug .pre_save() patch since it's no longer relevant
Update docs accordingly
2018-01-03 19:01:12 +00:00
Dario Marcelino
8000594114 Add --dependency-links flag to pip install 2018-01-03 17:52:21 +00:00
Dario Marcelino
7a9750cbd7 Please travis by adding latest commit of django-modeltranslation to
dependencies
2018-01-03 17:22:35 +00:00
Diogo Marques
238e265ec7
Merge pull request #153 from dmarcelino/fix_makemigrations
Fix makemigrations bug when Page has dependencies
2018-01-03 15:05:02 +00:00
Diogo Marques
ff8a7f7afe
Merge pull request #140 from dmarcelino/patch-2
Fix update_translation_fields command
2018-01-03 14:34:00 +00:00
Diogo Marques
7f028ffac8
Merge pull request #125 from WnP/fix-RemovedInWagtail113Warning
relative_url should accept a 'request' keyword argument.
2018-01-03 12:58:32 +00:00
Diogo Marques
90433d18b8
Merge pull request #123 from rodnsi/master
New tag: 'get_available_languages_wmt'.
2018-01-03 12:56:29 +00:00
Dário
fd1cbe2432
Merge pull request #155 from dmarcelino/set_translation_url_paths
Remove .specific calls from set_translation_url_paths command
2018-01-03 12:22:21 +00:00
Dario Marcelino
9e2c2358ae Remove .specific calls from set_translation_url_paths command 2018-01-03 12:04:16 +00:00
Dario Marcelino
066d00b20b Add (working) test for set_translation_url_paths command 2018-01-02 19:53:41 +00:00
Dario Marcelino
a90d0faf29 update_untranslated_descendants_url_paths: reduce number of DB queries
Instead of performing a query for each changed language, aggregate all
conditions into a single one using Q and run a single query per changed
page.
2018-01-02 18:29:53 +00:00
Dario Marcelino
f4d89944f1 update_untranslated_descendants_url_paths: add update_fields to .save()
Since we only need to update the 'localized_url_path' and nothing else.
Additional changes to `LocalizedSaveDescriptor` were made to ensure
descendants were updated when they needed to.
2018-01-02 16:12:38 +00:00
Dário
bfb62c0c90
Adds caveat to README
[ci skip]
2017-12-28 19:48:15 +00:00
Dario Marcelino
271920d6eb Fix makemigrations bug detected by @DiogoMarques29 when Page has
dependencies
2017-12-28 16:44:23 +00:00
Dário
bd93e06adc
Merge pull request #152 from dmarcelino/translation_options
Remove WagtailTranslationOptions and update docs
2017-12-27 19:49:12 +00:00
Dario Marcelino
17bac99fdd Remove WagtailTranslationOptions and update docs 2017-12-27 19:43:09 +00:00
Dário
b58544400a
Merge pull request #151 from dmarcelino/remove_specific
Remove .specific calls to reduce DB queries
2017-12-27 18:26:40 +00:00
Dario Marcelino
127603cd92 Removes .specific calls from clean() 2017-12-27 18:05:43 +00:00
Dario Marcelino
acf20e2533 Adds (working) test for clean 2017-12-27 18:05:17 +00:00
Dario Marcelino
17b2fe375c Remove model.url and model.get_url_parts() patches
since there no need to use .specific anymore
2017-12-27 17:20:48 +00:00
Dario Marcelino
d3cec5590d Adds (working) test for model.url and model.get_url_parts() 2017-12-27 17:20:08 +00:00
Dario Marcelino
2af22eda36 Remove call to .specific from _new_route() method 2017-12-27 16:19:59 +00:00
Dario Marcelino
e6488400c2 Adds RoutablePage test 2017-12-27 16:19:51 +00:00
Dario Marcelino
a1c90e9e97 Working test for Page.route() 2017-12-26 15:05:48 +00:00
Dario Marcelino
39acb713dc Refactor PageFactory to be simpler and more explicit 2017-12-26 15:04:51 +00:00
Dario Marcelino
5076788eb4 Please Travis: fix "ImportError: No module named contextlib" 2017-12-23 17:19:02 +00:00
Dario Marcelino
efd47b87e1 Fixes update_descendant_url_paths
for when children don't have their translated slugs set up.
2017-12-23 01:04:42 +00:00
Dario Marcelino
8250693208 Adds failing test for updating Page slug with untranslated children
Assert set_url_path works correctly when a Page with untranslated
children has its translated slug changed.
2017-12-23 01:02:00 +00:00
Dario Marcelino
2ab997dab5 Adds tag slugurl_trans and drop attempts to patch slugurl
- set_language context manager renamed to use_language
- updated docs about tags
2017-12-22 21:58:25 +00:00
Dario Marcelino
4bcd6086e0 Patch slugurl to accept original language slug
no matter what is the user's current language
2017-12-22 18:46:57 +00:00
Dario Marcelino
d22fc4c89c Adds failing test for slugurl tag 2017-12-22 18:42:19 +00:00
Dario Marcelino
252f142493 Adds new Page.move() descriptor to fix children URL paths 2017-12-22 15:57:33 +00:00
Dario Marcelino
93ffbb09ce Adds failing test for Page.move() within test_set_url_path() 2017-12-22 15:56:16 +00:00
Dario Marcelino
51216693d5 Merge branch 'page_i18n_fields' of
https://github.com/dmarcelino/wagtail-modeltranslation.git into
page_i18n_fields

Conflicts:
	wagtail_modeltranslation/tests/tests.py
2017-12-22 11:47:27 +00:00
Dario Marcelino
e28c00668a Gitignore: add GitEye's/Eclipse's project 2017-12-22 11:40:03 +00:00
Dario Marcelino
d3cb322c33 Adds sub-module makemigrations to override django's 'makemigrations'
command

New 'makemigrations' is a proxy for 'makemigrations_translation', it's
role is only to override django's own command and skip any
wagtailcore_page migrations caused by translation. Adding this to it's
own submodule makes this override optional for the user. The original
Django 'makemigrations' command is available by running
'makemigrations_original'
2017-12-22 11:40:03 +00:00
Dario Marcelino
333c392dc8 Move Page translation fields to Page table (breaking changes!)
- Bumps version to 0.8.0-alpha
- Introduces 'makemigrations_translation' command to avoid generating
wagtailcore_page migrations
- Adds 'sync_page_translation_fields' which mimicks
'sync_translation_fields' but restricts it to Page model
- This change introduces breaking changes and has no migration script to
move from 0.6 to 0.8
- Updates tests
2017-12-22 11:40:03 +00:00
Dario Marcelino
1a656b98da Adds failing test showing that saved Page fields are not retrieved
correctly

See
https://github.com/infoportugal/wagtail-modeltranslation/issues/103#issuecomment-352006610
for more details
2017-12-22 11:25:12 +00:00
Dario Marcelino
3a2c3f9b00 Gitignore: add GitEye's/Eclipse's project 2017-12-21 19:05:20 +00:00
Dario Marcelino
fe8bd7e9a4 Adds sub-module makemigrations to override django's 'makemigrations'
command

New 'makemigrations' is a proxy for 'makemigrations_translation', it's
role is only to override django's own command and skip any
wagtailcore_page migrations caused by translation. Adding this to it's
own submodule makes this override optional for the user. The original
Django 'makemigrations' command is available by running
'makemigrations_original'
2017-12-21 19:05:12 +00:00
Dario Marcelino
931ee25931 Move Page translation fields to Page table (breaking changes!)
- Bumps version to 0.8.0-alpha
- Introduces 'makemigrations_translation' command to avoid generating
wagtailcore_page migrations
- Adds 'sync_page_translation_fields' which mimicks
'sync_translation_fields' but restricts it to Page model
- This change introduces breaking changes and has no migration script to
move from 0.6 to 0.8
- Updates tests
2017-12-21 18:58:07 +00:00
Diogo Marques
eecab72275
Merge pull request #148 from dmarcelino/child_url_paths
Fixes descendant_url_path on page.save()
2017-12-19 14:56:48 +00:00
Diogo Marques
a60d1888cc
Merge pull request #146 from dmarcelino/url_paths
Patch save() so we ensure set_url_path is called if slug_xx changes (Fixes #145)
2017-12-19 14:53:05 +00:00
Dario Marcelino
d10d56a202 Fixes descendant_url_path on .save
Uses similar logic as Wagtail's .save() but adjusted for different
languages
2017-12-18 17:22:48 +00:00
Dario Marcelino
8751517403 Adds failing test for descendant URL path update on .save 2017-12-18 17:21:39 +00:00
Dario Marcelino
fc0e803dc5 Patch save() so we ensure set_url_path is called if slug_xx changes
Wagtail can only detect changes in slug for current language so we need
to check the others
2017-12-16 11:46:39 +00:00
Dario Marcelino
6a483eae19 Adds failing test for set_url_path
If slug for non-current language changes set_url_path is never called
2017-12-16 11:06:57 +00:00
Diogo Marques
ffaa485d83
Merge pull request #144 from dmarcelino/streamfield_fallback
Fix StreamField fallback (addresses #143)
2017-12-14 12:03:26 +00:00
Diogo Marques
bd1dbc6758
Merge branch 'master' into streamfield_fallback 2017-12-14 12:02:52 +00:00
Diogo Marques
53ad48e799
Merge branch 'master' into streamfield_fallback 2017-12-14 12:01:44 +00:00
Diogo Marques
37eec32f7a
Merge pull request #142 from dmarcelino/original_slug
#141: Ensure original slug field is always saved in the same language
2017-12-14 11:53:46 +00:00
Dario Marcelino
1f36b55f0c #143: Fixes StreamField fallback behaviour 2017-12-12 20:21:07 +00:00
Dario Marcelino
1e03faf76e #143: adds failing tests for StreamField fallback 2017-12-12 20:19:17 +00:00
Dario Marcelino
e91101977e #141: documentation for original slug field language changes 2017-12-12 12:26:25 +00:00
Dario Marcelino
7cf94069b6 #141: patch Slug Field pre_save() to ensure slug value consistency 2017-12-12 12:26:02 +00:00
Dario Marcelino
271bbe513f #141 Failing tests for original slug field issue 2017-12-12 12:22:25 +00:00
Dário
27ac5491f2
update_translation_fields: patch model instead of obj 2017-12-04 14:34:04 +00:00
Diogo Marques
dca220cb0c
Merge pull request #135 from WnP/i18n-slugs
[fix] i18n slugs required specific page in order to retrieve good url
2017-11-30 16:34:52 +00:00
Dário
86caf9a816
update_translation_fields: remove unused method 2017-11-30 15:16:25 +00:00
Dário
421a52e155
#103: fix update_translation_fields command 2017-11-30 14:59:03 +00:00
Diogo Marques
da76ec44a2
Merge pull request #137 from dmarcelino/patch-1
#126, Docs: fix translation.py example
2017-11-28 09:59:12 +00:00
Dário
c114d0d326
#126, Docs: fix translation.py example
Update the imports to reflect latest changes
2017-11-27 19:06:54 +00:00
Steeve Chailloux
dd473f1497 [fix] i18n slugs required specific page in order to retrieve good url path 2017-10-27 15:17:38 +02:00
Steeve Chailloux
de3797cc8f relative_url should accept a 'request' keyword argument. 2017-08-24 18:45:10 +02:00
Rodrigue Nsiangani (rodnsi)
adf68c5a3b New tag: 'get_available_languages_wmt'. Use this template tag to get the current languages from MODELTRANSLATION_LANGUAGES (or LANGUAGES) from your setting file (or the default settings). 2017-07-17 14:40:41 +02:00
Alexandre Silva
281e2f7002 Merge pull request #115 from v1kku/master
rename handle_noargs to handle in management commands
2017-05-09 12:45:29 +01:00
Alexandre Silva
791a9c003a Changed validation on _new_route to check if the object is an instance of RoutablePageMixin 2017-05-09 12:44:12 +01:00
Alexandre Silva
5361ae777f Merge pull request #119 from dwasyl/routing-for-routablepages
Added routing for RoutablePages
2017-05-09 12:42:24 +01:00
Alexandre Silva
500d9e7932 Fixed docs 2017-05-08 17:33:52 +01:00
David
2950a8f75b Added routing for RoutablePages 2017-04-27 23:26:00 -06:00
Victor Munene
b38d6b67a4 rename set_translation_url_paths.Command.handle_noargs to handle 2017-04-25 10:13:17 +03:00
Victor Munene
0c8f54e529 rename update_translation_fields.Command.handle_noargs to handle 2017-04-25 09:42:23 +03:00
58 changed files with 2717 additions and 564 deletions

16
.gitignore vendored
View file

@ -31,3 +31,19 @@ nosetests.xml
# Sphinx
_build
# GitEye / Eclipse project file
/.project
# PyCharm
/.idea
# vim
*~
*.swp
# vscode
.vscode
# pyenv
.python-version

View file

@ -1,20 +1,56 @@
language: python
python:
- "2.7"
- "3.3"
- "3.4"
- "3.5"
- "3.8"
env:
- WAGTAIL="wagtail>=1.4,<1.5"
- WAGTAIL="wagtail>=1.5,<1.6"
- WAGTAIL="wagtail>=1.6,<1.7"
- WAGTAIL="wagtail>=1.7,<1.8"
- WAGTAIL="wagtail>=1.8,<1.9"
- WAGTAIL="wagtail>=1.9,<1.10"
- WAGTAIL="wagtail>=2.7,<2.8" DB=sqlite
matrix:
include:
# Latest Wagtail version
- env: WAGTAIL="wagtail>=2.7,<2.8" DB=postgres
- env: WAGTAIL="wagtail>=2.7,<2.8" DB=mysql
- python: "3.8"
- python: "3.7"
- python: "3.6"
- python: "3.5"
# Past Wagtail versions
- python: "3.7"
env: WAGTAIL="wagtail>=2.6,<2.7"
- python: "3.7"
env: WAGTAIL="wagtail>=2.5,<2.6"
- python: "3.4" # Wagtail 2.5 was the last to support python 3.4
env: WAGTAIL="wagtail>=2.5,<2.6"
- python: "3.7"
env: WAGTAIL="wagtail>=2.4,<2.5"
- python: "3.7"
env: WAGTAIL="wagtail>=2.3,<2.4"
- python: "3.7"
env: WAGTAIL="wagtail>=2.2,<2.3"
- python: "3.7"
env: WAGTAIL="wagtail>=2.1,<2.2"
- python: "3.7"
env: WAGTAIL="wagtail>=2.0,<2.1"
- python: "2.7" # Wagtail 1.13 was the latest tested against 2.7
env: WAGTAIL="wagtail>=1.13,<1.14"
- python: "3.7"
env: WAGTAIL="wagtail>=1.13,<1.14"
- python: "2.7"
env: WAGTAIL="wagtail>=1.12,<1.13"
- python: "3.7"
env: WAGTAIL="wagtail>=1.12,<1.13"
services:
- mysql
- postgresql
addons:
postgresql: "9.4"
before_script:
- mysql -e 'create database wagtail_modeltranslation;'
- psql -c 'create database wagtail_modeltranslation;' -U postgres
install:
- pip install --upgrade -q pip setuptools
- if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install 'Django>=1.8,<1.9'; fi
- pip install -q $WAGTAIL
- if [[ $DB == mysql ]] && [[ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]]; then pip install -q mysql-python; elif [[ $DB == mysql ]] && [[ ${TRAVIS_PYTHON_VERSION:0:1} == "3" ]]; then pip install -q mysqlclient; fi
- if [[ $DB == postgres ]]; then pip install -q 'psycopg2-binary'; fi
- pip install $WAGTAIL
- pip install -e .
script:
- echo "DJANGO VERSION = `python -m django --version`"
- ./runtests.py

View file

@ -22,6 +22,7 @@ Contributors
* Raphael Grill
* Tom Dyson
* Tim Tan
* Benedikt Willi
.. _Django-modeltranslation: https://github.com/deschler/django-modeltranslation

View file

@ -1,3 +1,11 @@
v0.6.0rc2:
- added RichTextFieldPanel to the default list of patched panels
- added settings to allow the patching of custom panels
- slug auto-population is now made the same way as wagtail (no changes in live pages)
- Fixed: When adding a page link in a translated RichTextField the link was always to the default language version of that page
- Fixed: Copy content of streamfield fails with 414 Request-URI Too Long
- Fixed: Panel patching failed with the error "AttributeError: 'list' object has no attribute 'children'"
v0.6.0rc1:
- django-modeltranslation is now a dependency.
- added compatibility with Python 3 (3.3, 3.4, 3.5).

View file

@ -3,5 +3,6 @@ recursive-include docs *.rst conf.py Makefile make.bat
recursive-include wagtail_modeltranslation/static *
recursive-include wagtail_modeltranslation/management *
recursive-include wagtail_modeltranslation/templatetags *
recursive-include wagtail_modeltranslation/templates *
global-exclude *.pyc
global-exclude *.DS_Store

43
Makefile Normal file
View file

@ -0,0 +1,43 @@
.PHONY: clean-pyc clean-build help test
.DEFAULT_GOAL := help
help: ## print this help screen
@perl -nle'print $& if m{^[a-zA-Z0-9_-]+:.*?## .*$$}' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}'
clean: clean-build clean-pyc
@echo "all clean now .."
clean-build: ## remove build artifacts
@rm -fr build/
@rm -fr dist/
@rm -fr htmlcov/
@rm -fr *.egg-info
@rm -rf .coverage
clean-pyc: ## remove Python file artifacts
@find . -name '*.pyc' -exec rm -f {} +
@find . -name '*.pyo' -exec rm -f {} +
@find . -name '*.orig' -exec rm -f {} +
@find . -name '*~' -exec rm -f {} +
release: clean ## package and upload a release (working dir must be clean)
@while true; do \
CURRENT=`python -c "import wagtail_modeltranslation; print(wagtail_modeltranslation.__version__)"`; \
echo ""; \
echo "=== The current version is $$CURRENT - what's the next one?"; \
echo "==========================================================="; \
echo "1 - new major version"; \
echo "2 - new minor version"; \
echo "3 - patch"; \
echo "4 - keep the current version"; \
echo ""; \
read yn; \
case $$yn in \
1 ) bumpversion major; break;; \
2 ) bumpversion minor; break;; \
3 ) bumpversion patch; break;; \
4 ) break;; \
* ) echo "Please answer 1-3.";; \
esac \
done
@python setup.py bdist_wheel && twine upload dist/*

View file

@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: wagtail-modeltranslation
Version: 0.6.0rc1
Version: 0.10.6
Summary: Translates Wagtail CMS models using a registration approach.
Home-page: https://github.com/infoportugal/wagtail-modeltranslation
Author: InfoPortugal S.A.

11
Pipfile Normal file
View file

@ -0,0 +1,11 @@
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[packages]
bumpversion = "*"
wheel = "*"
[dev-packages]
twine = "*"

143
Pipfile.lock generated Normal file
View file

@ -0,0 +1,143 @@
{
"_meta": {
"hash": {
"sha256": "191a6f860a13836c57a16587784ceca36f791bfae1270cf937286a496e891114"
},
"pipfile-spec": 6,
"requires": {},
"sources": [
{
"name": "pypi",
"url": "https://pypi.python.org/simple",
"verify_ssl": true
}
]
},
"default": {
"bumpversion": {
"hashes": [
"sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e",
"sha256:6753d9ff3552013e2130f7bc03c1007e24473b4835952679653fb132367bdd57"
],
"index": "pypi",
"version": "==0.5.3"
},
"wheel": {
"hashes": [
"sha256:5e79117472686ac0c4aef5bad5172ea73a1c2d1646b808c35926bd26bdfb0c08",
"sha256:62fcfa03d45b5b722539ccbc07b190e4bfff4bb9e3a4d470dd9f6a0981002565"
],
"index": "pypi",
"version": "==0.33.4"
}
},
"develop": {
"bleach": {
"hashes": [
"sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16",
"sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa"
],
"version": "==3.1.0"
},
"certifi": {
"hashes": [
"sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5",
"sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae"
],
"version": "==2019.3.9"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"docutils": {
"hashes": [
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
"sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274",
"sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
],
"version": "==0.14"
},
"idna": {
"hashes": [
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
],
"version": "==2.8"
},
"pkginfo": {
"hashes": [
"sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb",
"sha256:a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32"
],
"version": "==1.5.0.1"
},
"pygments": {
"hashes": [
"sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127",
"sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"
],
"version": "==2.4.2"
},
"readme-renderer": {
"hashes": [
"sha256:bb16f55b259f27f75f640acf5e00cf897845a8b3e4731b5c1a436e4b8529202f",
"sha256:c8532b79afc0375a85f10433eca157d6b50f7d6990f337fa498c96cd4bfc203d"
],
"version": "==24.0"
},
"requests": {
"hashes": [
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
],
"version": "==2.22.0"
},
"requests-toolbelt": {
"hashes": [
"sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f",
"sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"
],
"version": "==0.9.1"
},
"six": {
"hashes": [
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
],
"version": "==1.12.0"
},
"tqdm": {
"hashes": [
"sha256:0a860bf2683fdbb4812fe539a6c22ea3f1777843ea985cb8c3807db448a0f7ab",
"sha256:e288416eecd4df19d12407d0c913cbf77aa8009d7fddb18f632aded3bdbdda6b"
],
"version": "==4.32.1"
},
"twine": {
"hashes": [
"sha256:0fb0bfa3df4f62076cab5def36b1a71a2e4acb4d1fa5c97475b048117b1a6446",
"sha256:d6c29c933ecfc74e9b1d9fa13aa1f87c5d5770e119f5a4ce032092f0ff5b14dc"
],
"index": "pypi",
"version": "==1.13.0"
},
"urllib3": {
"hashes": [
"sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1",
"sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"
],
"version": "==1.25.3"
},
"webencodings": {
"hashes": [
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
],
"version": "==0.5.1"
}
}
}

View file

@ -35,6 +35,16 @@ Features
- StreamFields are now supported!
Caveats
======
:code:`wagtail-modeltranslation` patches Wagtail's :code:`Page` model with translation fields
:code:`title_xx`, :code:`slug_xx`, :code:`seo_title_xx`, :code:`search_description_xx` and :code:`url_path_xx` where "xx" represents the language code for each translated language. This
is done without migrations through command :code:`sync_page_translation_fields`. Since :code:`Page` model belongs to
Wagtail it's within the realm of possibility that one day Wagtail may add a conflicting field to :code:`Page` thus interfering with :code:`wagtail-modeltranslation`.
Wagtail's :code:`slugurl` tag does not work across languages. :code:`wagtail-modeltranslation` provides a drop-in replacement named :code:`slugurl_trans` which by default takes the slug parameter in the default language.
Quick start
===========
@ -42,47 +52,69 @@ Quick start
pip install wagtail-modeltranslation
2. Add "wagtail_modeltranslation" to your INSTALLED_APPS setting like this (before all apps that you want to translate)::
2. Add 'wagtail_modeltranslation' to your ``INSTALLED_APPS`` setting like this (before all apps that you want to translate)::
INSTALLED_APPS = (
...
'wagtail_modeltranslation',
'wagtail_modeltranslation.makemigrations',
'wagtail_modeltranslation.migrate',
)
3. Add "django.middleware.locale.LocaleMiddleware" to MIDDLEWARE_CLASSES on your settings.py::
3. Add 'django.middleware.locale.LocaleMiddleware' to ``MIDDLEWARE`` on your ``settings.py``::
MIDDLEWARE_CLASSES = (
MIDDLEWARE = (
...
'django.middleware.locale.LocaleMiddleware',
'django.middleware.locale.LocaleMiddleware', # should be after SessionMiddleware and before CommonMiddleware
)
4. Enable i18n on settings.py::
4. Enable i18n on ``settings.py``::
USE_I18N = True
5. Define available languages on settings.py::
5. Define available languages on ``settings.py``::
from django.utils.translation import gettext_lazy as _
LANGUAGES = (
('pt', u'Português'),
('es', u'Espanhol'),
('fr', u'Francês'),
('pt', _('Portuguese')),
('es', _('Spanish')),
('fr', _('French')),
)
6. Create translation.py inside the root folder of the app where the model you want to translate exists::
6. Create ``translation.py`` inside the root folder of the app where the model you want to translate exists::
from .models import Foo
from wagtail_modeltranslation.translator import WagtailTranslationOptions
from modeltranslation.translator import TranslationOptions
from modeltranslation.decorators import register
@register(Foo)
class FooTR(WagtailTranslationOptions):
class FooTR(TranslationOptions):
fields = (
'body',
)
7. Run :code:`python manage.py makemigrations` followed by :code:`python manage.py migrate`
7. Run :code:`python manage.py makemigrations` followed by :code:`python manage.py migrate` (repeat every time you add a new language or register a new model)
8. Run :code:`python manage.py sync_page_translation_fields` (repeat every time you add a new language)
9. If you're adding :code:`wagtail-modeltranslation` to an existing site run :code:`python manage.py update_translation_fields`
Upgrade considerations (v0.8)
======================
This version includes breaking changes as some key parts of the app have been re-written:
- The most important change is that ``Page`` is now patched with translation fields.
- ``WAGTAILMODELTRANSLATION_ORIGINAL_SLUG_LANGUAGE`` setting has been deprecated.
To upgrade to this version you need to:
- Replace the ``WagtailTranslationOptions`` with ``TranslationOptions`` in all translation.py files
- Run :code:`python manage.py sync_page_translation_fields` at least once to create ``Page``'s translation fields
- Replace any usages of Wagtail's ``{% slugurl ... %}`` for :code:`wagtail-modeltranslation`'s own ``{% slugurl_trans ... %}``
- While optional it's recommended to add ``'wagtail_modeltranslation.makemigrations'`` to your INSTALLED_APPS. This will override Django's ``makemigrations`` command to avoid creating spurious ``Page`` migrations.
Upgrade considerations (v0.6)
======================
@ -95,7 +127,7 @@ Most of the changes are related to imports as they change from wagtail-modeltran
To upgrade to this version you need to:
- Replace the ``TranslationOption`` with ``WagtailTranslationOptions`` in all translation.py files
- Replace the ``TranslationOptions`` with ``WagtailTranslationOptions`` in all translation.py files
- The import of the register decorator is now ``from modeltranslation.decorators import register``
- The import of translator is now ``from modeltranslation.translator import translator``

View file

@ -7,7 +7,7 @@ Installation
Requirements
============
* Wagtail >= 1.4
* Wagtail >= 1.12
@ -34,76 +34,79 @@ Installing using the source
Quick Setup
=====
===========
To setup the application please follow these steps:
1. In the settings/base.py file:
1. In your settings file:
- Add wagtail_modeltranslation to the INSTALLED_APPS
- Add 'wagtail_modeltranslation' to ``INSTALLED_APPS``
.. code-block:: console
.. code-block:: console
INSTALLED_APPS = (
...
'wagtail_modeltranslation',
)
INSTALLED_APPS = (
...
'wagtail_modeltranslation',
'wagtail_modeltranslation.makemigrations',
'wagtail_modeltranslation.migrate',
)
- Add 'django.middleware.locale.LocaleMiddleware' to ``MIDDLEWARE`` (``MIDDLEWARE_CLASSES`` before django 1.10).
- Add django.middleware.locale.LocaleMiddleware to MIDDLEWARE_CLASSES.
.. code-block:: console
MIDDLEWARE_CLASSES = (
...
'django.middleware.locale.LocaleMiddleware',
)
.. code-block:: console
MIDDLEWARE = (
...
'django.middleware.locale.LocaleMiddleware', # should be after SessionMiddleware and before CommonMiddleware
)
- Set ``USE_I18N = True``
.. _language_settings:
- Configure your LANGUAGES.
- Configure your ``LANGUAGES`` setting.
The LANGUAGES variable must contain all languages you will use for translation. The first language is treated as the
*default language*.
The ``LANGUAGES`` variable must contain all languages you will use for translation. The first language is treated as the *default language*.
Modeltranslation uses the list of languages to add localized fields to the models registered for translation.
For example, to use the languages Portuguese, Spanish and French in your project, set the LANGUAGES variable like this
(where ``pt`` is the default language). In required fields the one for the default language is marked as required (for more advanced usage check `django-modeltranslation required_languages <http://django-modeltranslation.readthedocs.io/en/latest/registration.html#required-fields>`_.)
Modeltranslation uses the list of languages to add localized fields to the models registered for translation.
For example, to use the languages Portuguese, Spanish and French in your project, set the ``LANGUAGES`` variable like this
(where ``pt`` is the default language). In required fields the one for the default language is marked as required (for more advanced usage check `django-modeltranslation required_languages <http://django-modeltranslation.readthedocs.io/en/latest/registration.html#required-fields>`_.)
.. code-block:: console
.. code-block:: console
from django.utils.translation import gettext_lazy as _
LANGUAGES = (
('pt', u'Portugese'),
('es', u'Spanish'),
('fr', u'French'),
)
LANGUAGES = (
('pt', _('Portuguese')),
('es', _('Spanish')),
('fr', _('French')),
)
.. warning::
.. warning::
When the LANGUAGES setting isn't present in ``settings/base.py`` (and neither is ``MODELTRANSLATION_LANGUAGES``), it defaults to Django's global LANGUAGES setting instead, and there are quite a few languages in the default!
When the ``LANGUAGES`` setting isn't present in ``settings.py`` (and neither is ``MODELTRANSLATION_LANGUAGES``), it defaults to Django's global LANGUAGES setting instead, and there are quite a few languages in the default!
.. note::
2. Create a ``translation.py`` file in your app directory and register ``WagtailTranslationOptions`` for every model you want to translate.
To learn more about preparing Wagtail for Internationalisation check the `Wagtail i18n docs <http://docs.wagtail.io/en/latest/advanced_topics/i18n/>`_.
.. code-block:: console
2. Create a ``translation.py`` file in your app directory and register ``TranslationOptions`` for every model you want to translate and for all subclasses of Page model.
from .models import foo
from wagtail_modeltranslation.translation import WagtailTranslationOptions
from modeltranslation.decorators import register
.. code-block:: console
@register(foo)
class FooTR(WagtailTranslationOptions):
fields = (
'body',
)
from .models import foo
from modeltranslation.translator import TranslationOptions
from modeltranslation.decorators import register
@register(foo)
class FooTR(TranslationOptions):
fields = (
'body',
)
3. Run ``python manage.py makemigrations`` followed by ``python manage.py migrate``. This will add extra fields in the database.
3. Run ``python manage.py makemigrations`` followed by ``python manage.py migrate``. This will add the tranlation fields to the database, repeat every time you add a new language or register a new model.
4. Run ``python manage.py sync_page_translation_fields``. This will add translation fields to Wagtail's ``Page`` table, repeat every time you add a new language.
4. Define the panels for the original fields, as you normally would, as wagtail-modeltranslation would generate the panels for the translated fields.
5. If you're adding ``wagtail-modeltranslation`` to an existing site run ``python manage.py update_translation_fields``.
6. Define the panels for the original fields, as you normally would, as wagtail-modeltranslation will generate the panels for the translated fields.

View file

@ -9,14 +9,14 @@ Registering models for translation
Registering models and their fields used for translation requires the following steps:
1. Create **translation.py** in your app directory.
2. Define the models you want to use, import wagtail-modeltranslation's **WagtailTranslationOptions** and the django-modeltranslation **register** decorator
2. Define the models you want to use, import django-modeltranslation's **TranslationOptions** and the django-modeltranslation **register** decorator
3. Create a translation option class for every model you want to translate and precede the class with the **@register** decorator.
The django-modeltranslation application reads the **translation.py** file in your app directory thereby triggering the registration
of the translation options found in the file.
A translation option is a class that declares which model fields are needed for translation. The class must derive from
**wagtail_modeltranslation.translator.WagtailTranslationOptions** and it must provide a **field** attribute storing the list of
**modeltranslation.translator.TranslationOptions** and it must provide a **field** attribute storing the list of
field names. The option class must be registered with the **modeltranslation.decorators.register** instance.
To illustrate this let's have a look at a simple example using a **Foo** model. The example only contains an **introduction**
@ -27,11 +27,11 @@ Instead of a **Foo** model, this could be any Wagtail model class:
.. code-block:: console
from .models import Foo
from wagtail_modeltranslation.translation import WagtailTranslationOptions
from modeltranslation.translator import TranslationOptions
from modeltranslation.decorators import register
@register(Foo)
class FooTR(WagtailTranslationOptions):
class FooTR(TranslationOptions):
fields = (
'introduction',
'body',
@ -96,8 +96,8 @@ Committing fields to database
Modeltranslation supports the migration system introduced by Django 1.7. Besides the normal workflow as described in Django's
`Migration Docs <https://docs.djangoproject.com/en/1.8/topics/migrations/>`__, you should do a migration whenever one of the following changes have been made to your project:
- Added or removed a language through ``settings.LANGUAGES`` or ``settings.MODELTRANSLATION LANGUAGES``.
- Registered or unregistered a field through ``WagtailTranslationOptions``.
- Added or removed a language through ``settings.LANGUAGES`` or ``settings.MODELTRANSLATION LANGUAGES``.
- Registered or unregistered a field through ``TranslationOptions``.
It doesn't matter if you are starting a fresh project or change an existing one, it's always:
@ -106,6 +106,8 @@ It doesn't matter if you are starting a fresh project or change an existing one,
2. ``python manage.py migrate`` to apply the changes.
3. If you've added a new language ``python manage.py sync_page_translation_fields`` to add `Page` translation fields.
.. _required_langs:

View file

@ -13,7 +13,8 @@ Default: ``[]`` (empty list)
This setting is used to add custom "simple panel" classes (all panels that contain directly a field value, like FieldPanel or ImageChooserPanel) that need patching but are not included by default, resulting in not being created translated versions of that panel in wagtail admin.
If, for example, you're using wagtail-embedvideos the EmbedVideoChooserPanel is not patched by default so you'd need to include the fully qualified class name like the example below. This setting must be a list of fully qualified class names as strings.
.. code-block:: console
.. code-block:: python
WAGTAILMODELTRANSLATION_CUSTOM_SIMPLE_PANELS = ['wagtail_embed_videos.edit_handlers.EmbedVideoChooserPanel']
@ -22,7 +23,20 @@ If, for example, you're using wagtail-embedvideos the EmbedVideoChooserPanel is
Default: ``[]`` (empty list)
This settings behaves as the above but should be used for panels that are composed by other panels (MultiFieldPanel or FieldRowPanel for example).
This setting behaves as the above but should be used for panels that are composed by other panels (MultiFieldPanel or FieldRowPanel for example).
.. code-block:: python
.. code-block:: console
WAGTAILMODELTRANSLATION_CUSTOM_COMPOSED_PANELS = ['app_x.module_y.PanelZ']
``WAGTAILMODELTRANSLATION_TRANSLATE_SLUGS``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Default: ``True``
This setting makes slug and url_path localized. If True, each page will have a slug and url_path per language.
.. code-block:: python
WAGTAILMODELTRANSLATION_TRANSLATE_SLUGS = True

33
docs/caveats.rst Normal file
View file

@ -0,0 +1,33 @@
.. _caveats:
Caveats
=======
Wagtail's ``Page`` patch
------------------------
``wagtail-modeltranslation`` patches Wagtail's ``Page`` model with translation fields
``title_xx``, ``slug_xx``, ``seo_title_xx``, ``search_description_xx`` and ``url_path_xx``
where "xx" represents the language code for each translated language. This is done without
migrations through :ref:`management_commands-sync_page_translation_fields`. Since
``Page`` model belongs to Wagtail it's within the realm of possibility that one day Wagtail
may add a conflicting field to ``Page`` thus interfering with ``wagtail-modeltranslation``.
See also :ref:`management_commands-makemigrations_translation` to better understand how
migrations are managed with ``wagtail-modeltranslation``.
Wagtail's slugurl
-----------------
Wagtail's ``slugurl`` tag does not work across languages. To work around this
``wagtail-modeltranslation`` provides a drop-in replacement tag named
:ref:`template tags-slugurl_trans` which by default takes the slug parameter in the
default language.
Replace any usages of Wagtail's ``{% slugurl 'default_lang_slug' %}`` for
.. code-block:: django
{% load wagtail_modeltranslation %}
...
{% slugurl_trans 'default_lang_slug' %}

View file

@ -50,6 +50,8 @@ Contents
Registering Models
advanced settings
template tags
management commands
caveats
recommended reading
releases/index
AUTHORS

View file

@ -0,0 +1,166 @@
.. _management_commands:
Management Commands
===================
.. _management_commands-wagtail_modeltranslation:
wagtail_modeltranslation
------------------------
wagtail_modeltranslation module adds the following management commands.
.. _management_commands-update_translation_fields:
The ``update_translation_fields`` Command
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This command is a proxy to ``django-modeltranslation``'s own ``update_translation_fields``, for more details read the
corresponding documentation on `django-modeltranslation docs
<http://django-modeltranslation.readthedocs.io/en/latest/commands.html#the-update-translation-fields-command>`_.
In case modeltranslation was installed in an existing project and you
have specified to translate fields of models which are already synced to the
database, you have to update your database schema.
Unfortunately the newly added translation fields on the model will be empty
then, and your templates will show the translated value of the fields which
will be empty in this case. To correctly initialize the default translation
field you can use the ``update_translation_fields`` command:
.. code-block:: console
$ python manage.py update_translation_fields
.. _management_commands-sync_page_translation_fields:
The ``sync_page_translation_fields`` Command
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 0.8
This command compares the database and translated Page model definition (finding new translation
fields) and provides SQL statements to alter ``wagtailcore_page`` table. You should run this command
after installation and after adding a new language to your ``settings.LANGUAGES``.
.. code-block:: console
$ python manage.py sync_page_translation_fields
.. _management_commands-makemigrations_translation:
The ``makemigrations_translation`` Command
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 0.8
``wagtail-modeltranslation`` patches Wagtail's ``Page`` model and as consequence Django's original
``makemigrations`` commmand will create migrations for ``Page`` which may create conflicts with
other migrations. To circumvent this issue ``makemigrations_translation`` hides any ``Page`` model changes
and creates all other migrations as usual. Use this command as an alternative to Django's own
``makemigrations`` or consider using :ref:`management_commands-makemigrations`.
.. code-block:: console
$ python manage.py makemigrations_translation
.. _management_commands-migrate_translation:
The ``migrate_translation`` Command
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 0.8
Since :ref:`management_commands-makemigrations_translation` hides any ``Page`` model changes, Django's own
``migrate`` command won't be able to update ``wagtailcore_page`` table with new translation fields. In order to
correctly update the database schema a combination of ``migrate`` followed by :ref:`sync_page_translation_fields`
is usually required. ``migrate_translation`` provides a shortcut to running these two commands. Use this
as an alternative to Django's own ``migrate`` or consider using :ref:`management_commands-migrate`.
.. code-block:: console
$ python manage.py migrate_translation
.. _management_commands-set_translation_url_paths:
The ``set_translation_url_paths`` Command
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Updates url_path translation fields for all pages.
.. code-block:: console
$ python manage.py set_translation_url_paths
.. _management_commands-wagtail_modeltranslation.makemigrations:
wagtail_modeltranslation.makemigrations
---------------------------------------
To use ``wagtail_modeltranslation.makemigrations`` module commands add ``'wagtail_modeltranslation.makemigrations,'``
to ``INSTALLED_APPS``. This module adds the following management commands.
.. _management_commands-makemigrations:
The ``makemigrations`` Command
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This command is a proxy for :ref:`management_commands-makemigrations_translation`. It has the added benefit of
overriding Django's own ``makemigrations`` allowing you to run ``makemigrations`` safely without creating
spurious ``Page`` migrations.
.. code-block:: console
$ python manage.py makemigrations
.. _management_commands-makemigrations_original:
The ``makemigrations_original`` Command
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Since Django's ``makemigrations`` is overriden by ``wagtail-modeltranslation``'s version use
``makemigrations_original`` to run the Django's original ``makemigrations`` command. Please note
this will likely create invalid ``Page`` migrations, do this only if you know what you're doing.
.. code-block:: console
$ python manage.py makemigrations_original
.. _management_commands-wagtail_modeltranslation.migrate:
wagtail_modeltranslation.migrate
---------------------------------
To use ``wagtail_modeltranslation.migrate`` module commands add ``'wagtail_modeltranslation.migrate,'``
to ``INSTALLED_APPS``. This module adds the following management commands.
.. _management_commands-migrate:
The ``migrate`` Command
~~~~~~~~~~~~~~~~~~~~~~~
This command is a proxy for :ref:`management_commands-migrate_translation`. It has the added benefit of
overriding Django's own ``migrate`` saving the need to additionally run :ref:`sync_page_translation_fields`.
See `issue #175
<https://github.com/infoportugal/wagtail-modeltranslation/issues/175#issuecomment-368046055>`_ to understand
how this command can be used to create translation fields in a test database.
.. code-block:: console
$ python manage.py migrate
.. _management_commands-migrate_original:
The ``migrate_original`` Command
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Since Django's ``migrate`` is overriden by ``wagtail-modeltranslation``'s version use
``migrate_original`` to run the Django's original ``migrate`` command. Please note
this will not update ``wagtailcore_page`` table with new translation fields, use
:ref:`sync_page_translation_fields` for that.
.. code-block:: console
$ python manage.py migrate_original

View file

@ -1,9 +1,41 @@
.. _template tags:
=============
Template Tags
=============
change_lang
===========
Use this template tag to get the url of the current page in another language. The only parameter of this template tag is the language code the we want to get the url. Below is an example where we want to get the url of the current page in portuguese.
.. code-block:: console
.. code-block:: django
{% load wagtail_modeltranslation %}
{% change_lang 'pt' %}
.. _template tags-slugurl_trans:
slugurl_trans
=============
Use this template tag as a replacement for ``slugurl``.
.. code-block:: django
{% load wagtail_modeltranslation %}
{% slugurl_trans 'default_lang_slug' %}
{# or #}
get_available_languages_wmt
===========================
Use this template tag to get the current languages from MODELTRANSLATION_LANGUAGES (or LANGUAGES) from your setting file (or the default settings).
.. code-block:: django
{% get_available_languages_wmt as languages %}
{% for language in languages %}
...
{% endfor %}
{% slugurl_trans 'pt_lang_slug' 'pt' %}

View file

@ -5,6 +5,7 @@ import sys
import django
from django.conf import settings
from django.core.management import call_command
from wagtail import VERSION
def runtests():
@ -31,14 +32,9 @@ def runtests():
})
# Configure test environment
settings.configure(
DATABASES=DATABASES,
INSTALLED_APPS=(
'django.contrib.contenttypes',
'django.contrib.auth',
'taggit',
'rest_framework',
import wagtail
if VERSION < (2,):
WAGTAIL_MODULES = [
'wagtail.wagtailcore',
'wagtail.wagtailadmin',
'wagtail.wagtaildocs',
@ -52,10 +48,41 @@ def runtests():
'wagtail.wagtailsites',
'wagtail.contrib.settings',
'wagtail.contrib.wagtailapi',
]
WAGTAIL_CORE = 'wagtail.wagtailcore'
else:
WAGTAIL_MODULES = [
'wagtail.core',
'wagtail.admin',
'wagtail.documents',
'wagtail.snippets',
'wagtail.users',
'wagtail.images',
'wagtail.embeds',
'wagtail.search',
'wagtail.contrib.redirects',
'wagtail.contrib.forms',
'wagtail.sites',
'wagtail.contrib.settings',
'wagtail.api'
]
WAGTAIL_CORE = 'wagtail.core'
settings.configure(
DATABASES=DATABASES,
INSTALLED_APPS=[
'django.contrib.contenttypes',
'django.contrib.auth',
'taggit',
'rest_framework'] +
WAGTAIL_MODULES + [
'wagtail_modeltranslation.makemigrations',
'wagtail_modeltranslation',
),
],
# remove wagtailcore from serialization as translation columns have not been created at this point
# (which causes OperationalError: no such column)
TEST_NON_SERIALIZED_APPS=[WAGTAIL_CORE],
ROOT_URLCONF=None, # tests override urlconf, but it still needs to be defined
LANGUAGES=(
('en', 'English'),
@ -63,8 +90,7 @@ def runtests():
MIDDLEWARE_CLASSES=(),
)
if django.VERSION >= (1, 7):
django.setup()
django.setup()
failures = call_command(
'test', 'wagtail_modeltranslation', interactive=False, failfast=False, verbosity=2)

7
setup.cfg Normal file
View file

@ -0,0 +1,7 @@
[bumpversion]
current_version = 0.10.6
commit = True
tag = True
[bumpversion:file:wagtail_modeltranslation/__init__.py]

View file

@ -1,9 +1,24 @@
#!/usr/bin/env python
from distutils.core import setup
import re
import os
from setuptools import setup
def get_version(*file_paths):
filename = os.path.join(os.path.dirname(__file__), *file_paths)
version_file = open(filename).read()
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError('Please assure that the package version is defined as "__version__ = x.x.x" in ' + filename)
version = get_version("wagtail_modeltranslation", "__init__.py")
setup(
name='wagtail-modeltranslation',
version='0.6.0rc2',
version=version,
description='Translates Wagtail CMS models using a registration approach.',
long_description=(
'The modeltranslation application can be used to translate dynamic '
@ -21,19 +36,26 @@ setup(
'wagtail_modeltranslation',
'wagtail_modeltranslation.management',
'wagtail_modeltranslation.management.commands',
'wagtail_modeltranslation.templatetags'],
'wagtail_modeltranslation.templatetags',
'wagtail_modeltranslation.makemigrations',
'wagtail_modeltranslation.makemigrations.management',
'wagtail_modeltranslation.makemigrations.management.commands',
'wagtail_modeltranslation.migrate',
'wagtail_modeltranslation.migrate.management',
'wagtail_modeltranslation.migrate.management.commands'],
package_data={'wagtail_modeltranslation': ['static/wagtail_modeltranslation/css/*.css',
'static/wagtail_modeltranslation/js/*.js']},
install_requires=['wagtail(>=1.4)', 'django-modeltranslation(<=0.12.99)'],
download_url='https://github.com/infoportugal/wagtail-modeltranslation/archive/v0.6rc2.tar.gz',
'static/wagtail_modeltranslation/js/*.js',
'templates/*.html']},
install_requires=['wagtail>=1.12', 'django-modeltranslation>=0.13'],
classifiers=[
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Operating System :: OS Independent',
'Environment :: Web Environment',
'Intended Audience :: Developers',

261
tox.ini
View file

@ -1,158 +1,217 @@
[tox]
envlist =
py35-1.9.X,
py34-1.9.X,
py33-1.9.X,
py27-1.9.X,
py35-1.8.X,
py34-1.8.X,
py33-1.8.X,
py27-1.8.X,
py35-1.7.X,
py34-1.7.X,
py33-1.7.X,
py27-1.7.X,
py35-1.6.X,
py34-1.6.X,
py33-1.6.X,
py27-1.6.X,
py35-1.5.X,
py34-1.5.X,
py33-1.5.X,
py27-1.5.X,
py35-1.4.X,
py34-1.4.X,
py33-1.4.X,
py27-1.4.X,
py38-2.7.X,
py37-2.7.X,
py36-2.7.X,
py35-2.7.X,
py34-2.7.X,
py37-2.6.X,
py36-2.6.X,
py35-2.6.X,
py37-2.5.X,
py36-2.5.X,
py35-2.5.X,
py34-2.5.X,
py37-2.4.X,
py36-2.4.X,
py35-2.4.X,
py34-2.4.X,
py36-2.3.X,
py35-2.3.X,
py34-2.3.X,
py36-2.2.X,
py35-2.2.X,
py34-2.2.X,
py36-2.1.X,
py35-2.1.X,
py34-2.1.X,
py36-2.0.X,
py35-2.0.X,
py34-2.0.X,
py36-1.13.X,
py35-1.13.X,
py34-1.13.X,
py27-1.13.X,
py36-1.12.X,
py35-1.12.X,
py34-1.12.X,
py27-1.12.X,
[testenv]
commands =
{envpython} runtests.py
[testenv:py35-1.9.X]
[testenv:py38-2.7.X]
basepython = python3.8
deps =
wagtail>=2.7,<2.8
[testenv:py37-2.7.X]
basepython = python3.7
deps =
wagtail>=2.7,<2.8
[testenv:py36-2.7.X]
basepython = python3.6
deps =
wagtail>=2.7,<2.8
[testenv:py35-2.7.X]
basepython = python3.5
deps =
wagtail>=1.9,<1.10
wagtail>=2.7,<2.8
[testenv:py34-1.9.X]
basepython = python3.4
[testenv:py37-2.6.X]
basepython = python3.7
deps =
wagtail>=1.9,<1.10
wagtail>=2.6,<2.7
[testenv:py33-1.9.X]
basepython = python3.3
[testenv:py36-2.6.X]
basepython = python3.6
deps =
Django>=1.8,<1.9
wagtail>=1.9,<1.10
wagtail>=2.6,<2.7
[testenv:py27-1.9.X]
basepython = python2.7
deps =
wagtail>=1.9,<1.10
[testenv:py35-1.8.X]
[testenv:py35-2.6.X]
basepython = python3.5
deps =
wagtail>=1.8,<1.9
wagtail>=2.6,<2.7
[testenv:py34-1.8.X]
basepython = python3.4
[testenv:py37-2.5.X]
basepython = python3.7
deps =
wagtail>=1.8,<1.9
wagtail>=2.5,<2.6
[testenv:py33-1.8.X]
basepython = python3.3
[testenv:py36-2.5.X]
basepython = python3.6
deps =
Django>=1.8,<1.9
wagtail>=1.8,<1.9
wagtail>=2.5,<2.6
[testenv:py27-1.8.X]
basepython = python2.7
deps =
wagtail>=1.8,<1.9
[testenv:py35-1.7.X]
[testenv:py35-2.5.X]
basepython = python3.5
deps =
wagtail>=1.7,<1.8
wagtail>=2.5,<2.6
[testenv:py34-1.7.X]
[testenv:py34-2.5.X]
basepython = python3.4
deps =
wagtail>=1.7,<1.8
wagtail>=2.5,<2.6
[testenv:py33-1.7.X]
basepython = python3.3
[testenv:py37-2.4.X]
basepython = python3.7
deps =
Django>=1.8,<1.9
wagtail>=1.7,<1.8
wagtail>=2.4,<2.5
[testenv:py27-1.7.X]
basepython = python2.7
[testenv:py36-2.4.X]
basepython = python3.6
deps =
wagtail>=1.7,<1.8
wagtail>=2.4,<2.5
[testenv:py35-1.6.X]
[testenv:py35-2.4.X]
basepython = python3.5
deps =
wagtail>=1.6,<1.7
wagtail>=2.4,<2.5
[testenv:py34-1.6.X]
[testenv:py34-2.4.X]
basepython = python3.4
deps =
wagtail>=1.6,<1.7
wagtail>=2.4,<2.5
[testenv:py33-1.6.X]
basepython = python3.3
[testenv:py36-2.3.X]
basepython = python3.6
deps =
Django>=1.8,<1.9
wagtail>=1.6,<1.7
wagtail>=2.3,<2.4
[testenv:py27-1.6.X]
basepython = python2.7
deps =
wagtail>=1.6,<1.7
[testenv:py35-1.5.X]
[testenv:py35-2.3.X]
basepython = python3.5
deps =
wagtail>=1.5,<1.6
wagtail>=2.3,<2.4
[testenv:py34-1.5.X]
[testenv:py34-2.3.X]
basepython = python3.4
deps =
wagtail>=1.5,<1.6
wagtail>=2.3,<2.4
[testenv:py33-1.5.X]
basepython = python3.3
[testenv:py36-2.2.X]
basepython = python3.6
deps =
Django>=1.8,<1.9
wagtail>=1.5,<1.6
wagtail>=2.2,<2.3
[testenv:py27-1.5.X]
basepython = python2.7
deps =
wagtail>=1.5,<1.6
[testenv:py35-1.4.X]
[testenv:py35-2.2.X]
basepython = python3.5
deps =
wagtail>=1.4,<1.5
wagtail>=2.2,<2.3
[testenv:py34-1.4.X]
[testenv:py34-2.2.X]
basepython = python3.4
deps =
wagtail>=1.4,<1.5
wagtail>=2.2,<2.3
[testenv:py33-1.4.X]
basepython = python3.3
[testenv:py36-2.1.X]
basepython = python3.6
deps =
Django>=1.8,<1.9
wagtail>=1.4,<1.5
wagtail>=2.1,<2.2
[testenv:py27-1.4.X]
[testenv:py35-2.1.X]
basepython = python3.5
deps =
wagtail>=2.1,<2.2
[testenv:py34-2.1.X]
basepython = python3.4
deps =
wagtail>=2.1,<2.2
[testenv:py36-2.0.X]
basepython = python3.6
deps =
wagtail>=2.0,<2.1
[testenv:py35-2.0.X]
basepython = python3.5
deps =
wagtail>=2.0,<2.1
[testenv:py34-2.0.X]
basepython = python3.4
deps =
wagtail>=2.0,<2.1
[testenv:py36-1.13.X]
basepython = python3.6
deps =
wagtail>=1.13,<2.0
[testenv:py35-1.13.X]
basepython = python3.5
deps =
wagtail>=1.13,<2.0
[testenv:py34-1.13.X]
basepython = python3.4
deps =
wagtail>=1.13,<2.0
[testenv:py27-1.13.X]
basepython = python2.7
deps =
wagtail>=1.4,<1.5
wagtail>=1.13,<2.0
[testenv:py36-1.12.X]
basepython = python3.6
deps =
wagtail>=1.12,<1.13
[testenv:py35-1.12.X]
basepython = python3.5
deps =
wagtail>=1.12,<1.13
[testenv:py34-1.12.X]
basepython = python3.4
deps =
wagtail>=1.12,<1.13
[testenv:py27-1.12.X]
basepython = python2.7
deps =
wagtail>=1.12,<1.13

View file

@ -1,3 +1,3 @@
# coding: utf-8
__version__ = '0.10.6'
default_app_config = 'wagtail_modeltranslation.apps.ModeltranslationConfig'

View file

@ -10,9 +10,22 @@ class ModeltranslationConfig(AppConfig):
def ready(self):
from django.conf import settings
from modeltranslation import settings as mt_settings
# Add Wagtail defined fields as modeltranslation custom fields
setattr(settings, 'MODELTRANSLATION_CUSTOM_FIELDS', getattr(settings, 'MODELTRANSLATION_CUSTOM_FIELDS', ()) + (
'StreamField', 'RichTextField'))
wagtail_fields = (
'StreamField',
'RichTextField',
)
# update both the standard settings and the modeltranslation settings,
# as we cannot guarantee the load order, and so django_modeltranslation
# may bootstrap itself either before, or after, our ready() gets called.
custom_fields = getattr(settings, 'MODELTRANSLATION_CUSTOM_FIELDS', tuple())
setattr(settings, 'MODELTRANSLATION_CUSTOM_FIELDS', tuple(set(custom_fields + wagtail_fields)))
mt_custom_fields = getattr(mt_settings, 'CUSTOM_FIELDS', tuple())
setattr(mt_settings, 'CUSTOM_FIELDS', tuple(set(mt_custom_fields + wagtail_fields)))
from modeltranslation.models import handle_translation_registrations
handle_translation_registrations()

View file

@ -0,0 +1,21 @@
from django.utils.translation import activate
from modeltranslation.utils import get_language
class use_language:
"""
Context manager to safely change language momentarily
Usage:
with use_language('en'):
en_url = obj.get_absolute_url()
"""
def __init__(self, lang):
self.language = lang
self.current_language = get_language()
def __enter__(self):
activate(self.language)
def __exit__(self, exctype, excinst, exctb):
activate(self.current_language)

View file

@ -0,0 +1,3 @@
# coding: utf-8
default_app_config = 'wagtail_modeltranslation.makemigrations.apps.MakemigrationsConfig'

View file

@ -0,0 +1,7 @@
from django.apps import AppConfig
class MakemigrationsConfig(AppConfig):
name = 'wagtail_modeltranslation.makemigrations'
label = 'wagtail_modeltranslation_makemigrations'
verbose_name = "Wagtail Modeltranslation makemigrations"

View file

@ -0,0 +1,5 @@
from wagtail_modeltranslation.management.commands.makemigrations_translation import Command as MakeMigrationsCommand
class Command(MakeMigrationsCommand):
pass

View file

@ -0,0 +1,5 @@
from django.core.management.commands.makemigrations import Command as MakeMigrationsCommand
class Command(MakeMigrationsCommand):
pass

View file

@ -0,0 +1,30 @@
from django.core.management.commands.makemigrations import Command as MakeMigrationsCommand
from django.db.migrations.autodetector import MigrationAutodetector
import copy
def autodetector_decorator(func):
def wrapper(self, from_state, to_state, questioner=None):
# Replace to_state.app_configs.models and to_state.models' version of page with the old one
# so no changes are detected by MigrationAutodetector
from_state_page = from_state.concrete_apps.get_model('wagtailcore', 'page')
new_to_state = copy.deepcopy(to_state)
new_to_state.apps.app_configs['wagtailcore'].models['page'] = from_state_page
new_to_state.models['wagtailcore', 'page'] = from_state.models['wagtailcore', 'page']
return func(self, from_state, new_to_state, questioner)
return wrapper
class Command(MakeMigrationsCommand):
help = "Creates new migration(s) for apps except wagtailcore's Page."
def handle(self, *args, **options):
old_autodetector_init = MigrationAutodetector.__init__
MigrationAutodetector.__init__ = autodetector_decorator(MigrationAutodetector.__init__)
try:
super(Command, self).handle(*args, **options)
finally:
MigrationAutodetector.__init__ = old_autodetector_init

View file

@ -0,0 +1,33 @@
from django.core.management.commands.migrate import Command as MigrateCommand
from django.db.migrations.autodetector import MigrationAutodetector
from .sync_page_translation_fields import Command as SyncPageTranslationFieldsCommand
# decorate MigrationAutodetector.changes so we can silence any wagtailcore migrations missing warnings
def changes_decorator(func):
def wrapper(self, graph, trim_to_apps=None, convert_apps=None, migration_name=None):
changes = func(self, graph, trim_to_apps, convert_apps, migration_name)
if 'wagtailcore' in changes:
del changes['wagtailcore']
return changes
return wrapper
class Command(MigrateCommand):
help = "Updates database schema. Manages both apps with migrations and those without. " \
"Updates Wagtail Page translation fields"
def handle(self, *args, **options):
old_autodetector_changes = MigrationAutodetector.changes
MigrationAutodetector.changes = changes_decorator(MigrationAutodetector.changes)
try:
super(Command, self).handle(*args, **options)
finally:
MigrationAutodetector.changes = old_autodetector_changes
# Run sync_page_translation_fields command
sync_page_command = SyncPageTranslationFieldsCommand()
# Update the dict of sync_page_command with the content of this one
sync_page_command.__dict__.update(self.__dict__)
sync_page_command.handle(*args, **options)

View file

@ -1,36 +1,31 @@
# coding: utf-8
from django.conf import settings
from django.core.management.base import BaseCommand
from wagtail.wagtailcore.models import Page
from modeltranslation import settings as mt_settings
from modeltranslation.utils import build_localized_fieldname
from wagtail_modeltranslation.contextlib import use_language
try:
from wagtail.core.models import Page
except ImportError:
from wagtail.wagtailcore.models import Page
class Command(BaseCommand):
def set_subtree(self, root, root_path, lang=None):
update_fields = ['url_path_' + lang] if hasattr(root.specific, 'url_path_' + lang) else ['url_path']
def __init__(self):
super(Command, self).__init__()
update_fields = ['url_path']
for language in mt_settings.AVAILABLE_LANGUAGES:
localized_url_path = build_localized_fieldname('url_path', language)
update_fields.append(localized_url_path)
self.update_fields = update_fields
if hasattr(root.specific, 'url_path_' + lang):
setattr(root.specific, 'url_path_' + lang, root_path)
else:
setattr(root, 'url_path', root_path)
if lang == settings.LANGUAGE_CODE:
setattr(root, 'url_path', root_path)
update_fields.append('url_path')
root.specific.save(update_fields=update_fields)
def set_subtree(self, root, parent):
root.set_url_path(parent)
root.save(update_fields=self.update_fields)
for child in root.get_children():
slug = getattr(
child.specific, 'slug_' + lang) if hasattr(
child.specific, 'slug_' + lang) else getattr(child, 'slug')
if not slug or slug == '':
slug = getattr(
child.specific, 'slug_' + settings.LANGUAGE_CODE) if hasattr(child.specific,
'slug_' + settings.LANGUAGE_CODE) and getattr(
child.specific, 'slug_' + settings.LANGUAGE_CODE) else getattr(child, 'slug')
self.set_subtree(child, root)
self.set_subtree(child, root_path + slug + '/', lang)
def handle_noargs(self, **options):
for node in Page.get_root_nodes():
for lang in settings.LANGUAGES:
self.set_subtree(node, '/', lang=lang[0])
def handle(self, **options):
with use_language(mt_settings.DEFAULT_LANGUAGE):
for node in Page.get_root_nodes():
self.set_subtree(node, None)

View file

@ -0,0 +1,29 @@
from modeltranslation.management.commands.sync_translation_fields import Command as SyncTranslationsFieldsCommand
from modeltranslation.translator import translator
try:
from wagtail.core.models import Page
except ImportError:
from wagtail.wagtailcore.models import Page
old_get_registered_models = translator.get_registered_models
# Monkey patching, only return a model if it's Page
def get_page_model(self, abstract=True):
models = old_get_registered_models(abstract)
return [x for x in models if x is Page]
class Command(SyncTranslationsFieldsCommand):
help = ("Detect new translatable fields or new available languages and"
" sync Wagtail's Page database structure. Does not remove "
" columns of removed languages or undeclared fields.")
def handle(self, *args, **options):
translator.get_registered_models = get_page_model.__get__(translator)
try:
super(Command, self).handle(*args, **options)
finally:
translator.get_registered_models = old_get_registered_models

View file

@ -1,77 +1,9 @@
# coding: utf-8
from django.core.management.base import BaseCommand
from django.db import connection
from django.db.models import F, Q
from modeltranslation.settings import DEFAULT_LANGUAGE
from modeltranslation.translator import translator
from modeltranslation.utils import build_localized_fieldname
from wagtail.wagtailcore.models import Page
from modeltranslation.management.commands.update_translation_fields import Command as UpdateTranslationFieldsCommand
class Command(BaseCommand):
help = ('Updates empty values of default translation fields using'
' values from original fields (in all translated models).')
class Command(UpdateTranslationFieldsCommand):
pass
def handle_noargs(self, **options):
verbosity = int(options['verbosity'])
if verbosity > 0:
self.stdout.write(
"Using default language: %s\n" % DEFAULT_LANGUAGE)
models = translator.get_registered_models(abstract=False)
for model in models:
if verbosity > 0:
self.stdout.write("Updating data of model '%s'\n" % model)
opts = translator.get_options_for_model(model)
for field_name in opts.fields.keys():
def_lang_fieldname = build_localized_fieldname(
field_name, DEFAULT_LANGUAGE)
# We'll only update fields which do not have an existing value
q = Q(**{def_lang_fieldname: None})
field = model._meta.get_field(field_name)
if field.empty_strings_allowed:
q |= Q(**{def_lang_fieldname: ''})
if issubclass(model, Page):
for obj in model._default_manager.filter(q):
# Get table description in order to know if field is
# in child or parent table
# TODO: Tested only on PostgreSQL engine
db_table = model._meta.db_table
db_table_desc = connection.introspection. \
get_table_description(
connection.cursor(), db_table)
# original field in child class
if field_name in [x.name for x in db_table_desc]:
raw = model._default_manager.raw(
'SELECT *, %s AS original_field FROM %s \
WHERE page_ptr_id=%d LIMIT 1' % (
field_name, db_table, obj.page_ptr_id))[0]
setattr(
obj, def_lang_fieldname, raw.original_field)
# field is a foreign key
elif (field_name + '_id') in \
[x.name for x in db_table_desc]:
raw = model._default_manager.raw(
'SELECT *, %s AS original_field FROM %s \
WHERE page_ptr_id=%d LIMIT 1' % (
field_name + '_id',
db_table,
obj.page_ptr_id))[0]
setattr(
obj,
def_lang_fieldname + '_id',
raw.original_field)
# original field parent class
else:
raw = Page._default_manager.raw(
'SELECT *, %s AS original_field FROM \
wagtailcore_page WHERE id=%d LIMIT 1' % (
field_name, obj.page_ptr_id))[0]
setattr(
obj, def_lang_fieldname, raw.original_field)
obj.save(update_fields=[def_lang_fieldname])
else:
model._default_manager.filter(q).rewrite(False).update(
**{def_lang_fieldname: F(field_name)})

View file

@ -0,0 +1,3 @@
# coding: utf-8
default_app_config = 'wagtail_modeltranslation.migrate.apps.MigrateConfig'

View file

@ -0,0 +1,7 @@
from django.apps import AppConfig
class MigrateConfig(AppConfig):
name = 'wagtail_modeltranslation.migrate'
label = 'wagtail_modeltranslation_migrate'
verbose_name = "Wagtail Modeltranslation migrate"

View file

@ -0,0 +1,5 @@
from wagtail_modeltranslation.management.commands.migrate_translation import Command as MigrateCommand
class Command(MigrateCommand):
pass

View file

@ -0,0 +1,5 @@
from django.core.management.commands.migrate import Command as MigrateCommand
class Command(MigrateCommand):
pass

View file

@ -1,29 +1,51 @@
# coding: utf-8
import copy
import logging
import types
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.db import transaction
try:
from django.urls import reverse
except ImportError:
from django.core.urlresolvers import reverse
from django.db import transaction, connection
from django.db.models import Q, Value
from django.db.models.functions import Concat, Substr
from django.http import Http404
from django.utils.translation import trans_real
from django.utils.translation import ugettext_lazy as _
from modeltranslation import settings as mt_settings
from modeltranslation.translator import translator, NotRegistered
from modeltranslation.utils import build_localized_fieldname, get_language
from wagtail.contrib.settings.models import BaseSetting
from wagtail.contrib.settings.views import get_setting_edit_handler
from wagtail.wagtailadmin.edit_handlers import FieldPanel, \
MultiFieldPanel, FieldRowPanel, InlinePanel, StreamFieldPanel, RichTextFieldPanel
from wagtail.wagtailcore.models import Page, Site
from wagtail.wagtailcore.url_routing import RouteResult
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
from wagtail.wagtailsearch.index import SearchField
from wagtail.wagtailsnippets.models import get_snippet_models
from wagtail.wagtailsnippets.views.snippets import SNIPPET_EDIT_HANDLERS
from wagtail_modeltranslation.settings import CUSTOM_SIMPLE_PANELS, CUSTOM_COMPOSED_PANELS
try:
from wagtail.contrib.routable_page.models import RoutablePageMixin
from wagtail.admin.edit_handlers import FieldPanel, \
MultiFieldPanel, FieldRowPanel, InlinePanel, StreamFieldPanel, RichTextFieldPanel,\
extract_panel_definitions_from_model_class, ObjectList
from wagtail.core.models import Page, Site
from wagtail.core.fields import StreamField, StreamValue
from wagtail.core.url_routing import RouteResult
from wagtail.core.utils import WAGTAIL_APPEND_SLASH
from wagtail.images.edit_handlers import ImageChooserPanel
from wagtail.search.index import SearchField
from wagtail.snippets.views.snippets import SNIPPET_EDIT_HANDLERS
except ImportError:
from wagtail.contrib.wagtailroutablepage.models import RoutablePageMixin
from wagtail.wagtailadmin.edit_handlers import FieldPanel, \
MultiFieldPanel, FieldRowPanel, InlinePanel, StreamFieldPanel, RichTextFieldPanel,\
extract_panel_definitions_from_model_class, ObjectList
from wagtail.wagtailcore.models import Page, Site
from wagtail.wagtailcore.fields import StreamField, StreamValue
from wagtail.wagtailcore.url_routing import RouteResult
from wagtail.wagtailcore.utils import WAGTAIL_APPEND_SLASH
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
from wagtail.wagtailsearch.index import SearchField
from wagtail.wagtailsnippets.views.snippets import SNIPPET_EDIT_HANDLERS
from wagtail_modeltranslation.settings import CUSTOM_SIMPLE_PANELS, CUSTOM_COMPOSED_PANELS, TRANSLATE_SLUGS
from wagtail_modeltranslation.utils import compare_class_tree_depth
from wagtail import VERSION
logger = logging.getLogger('wagtail.core')
@ -87,28 +109,43 @@ class WagtailTranslator(object):
translated_field.field_name = build_localized_fieldname(field.field_name, language)
model.search_fields = list(model.search_fields) + [translated_field]
# OVERRIDE PAGE METHODS
# OVERRIDE FIELDS
model_fields = model._meta.get_fields()
for field in model_fields:
if isinstance(field, StreamField) and field.name in translation_registered_fields:
descriptor = getattr(model, field.name)
_patch_stream_field_meaningful_value(descriptor)
model.move = _new_move
model.set_url_path = _new_set_url_path
model.route = _new_route
model.get_site_root_paths = _new_get_site_root_paths
model.relative_url = _new_relative_url
model.url = _new_url
_patch_clean(model)
# OVERRIDE PAGE METHODS
if TRANSLATE_SLUGS:
model.set_url_path = _new_set_url_path
model.route = _new_route
model._update_descendant_url_paths = _new_update_descendant_url_paths
if not hasattr(model, '_get_site_root_paths'):
model.get_url_parts = _new_get_url_parts # Wagtail<1.11
model._get_site_root_paths = _new_get_site_root_paths
_patch_clean(model)
if not model.save.__name__.startswith('localized'):
setattr(model, 'save', LocalizedSaveDescriptor(model.save))
def _patch_other_models(self, model):
if hasattr(model, 'edit_handler'):
edit_handler = model.edit_handler
for tab in edit_handler:
for tab in edit_handler.children:
tab.children = self._patch_panels(tab.children)
elif hasattr(model, 'panels'):
model.panels = self._patch_panels(model.panels)
if model in get_snippet_models() and model in SNIPPET_EDIT_HANDLERS:
del SNIPPET_EDIT_HANDLERS[model]
else:
get_setting_edit_handler.cache_clear()
panels = extract_panel_definitions_from_model_class(model)
translation_registered_fields = translator.get_options_for_model(model).fields
panels = filter(lambda field: field.field_name not in translation_registered_fields, panels)
edit_handler = ObjectList(panels)
if VERSION < (2, 5):
SNIPPET_EDIT_HANDLERS[model] = edit_handler.bind_to_model(model)
else:
SNIPPET_EDIT_HANDLERS[model] = edit_handler.bind_to(model=model)
def _patch_panels(self, panels_list, related_model=None):
"""
@ -149,6 +186,14 @@ class WagtailTranslator(object):
if not original_field.blank and language == mt_settings.DEFAULT_LANGUAGE:
localized_field = model._meta.get_field(localized_field_name)
localized_field.blank = False
elif isinstance(original_field, StreamField):
# otherwise the field is optional and
# if it's a StreamField the stream_block need to be changed to non required
localized_field = model._meta.get_field(localized_field_name)
new_stream_block = copy.copy(localized_field.stream_block)
new_stream_block.meta = copy.copy(localized_field.stream_block.meta)
new_stream_block.meta.required = False
localized_field.stream_block = new_stream_block
localized_panel = panel_class(localized_field_name)
@ -200,26 +245,33 @@ class WagtailTranslator(object):
# patched, leaving the original untouched
return panel
# Overridden Page methods adapted to the translated fields
@transaction.atomic # only commit when all descendants are properly updated
def _new_move(self, target, pos=None):
"""
Extension to the treebeard 'move' method to ensure that url_path is updated too.
"""
old_url_path = Page.objects.get(id=self.id).url_path
super(Page, self).move(target, pos=pos)
# treebeard's move method doesn't actually update the in-memory instance, so we need to work
# with a freshly loaded one now
# added .specific to use the most specific class so that url_paths are updated to all languages
new_self = Page.objects.get(id=self.id).specific
new_url_path = new_self.set_url_path(new_self.get_parent())
new_self.save()
new_self._update_descendant_url_paths(old_url_path, new_url_path)
# Log
logger.info("Page moved: \"%s\" id=%d path=%s", self.title, self.id, new_url_path)
def _localized_set_url_path(page, parent, language):
"""
Updates a localized url_path for a given language
"""
localized_slug_field = build_localized_fieldname('slug', language)
default_localized_slug_field = build_localized_fieldname('slug', mt_settings.DEFAULT_LANGUAGE)
localized_url_path_field = build_localized_fieldname('url_path', language)
default_localized_url_path_field = build_localized_fieldname('url_path', mt_settings.DEFAULT_LANGUAGE)
if parent:
# Emulate the default behavior of django-modeltranslation to get the slug and url path
# for the current language. If the value for the current language is invalid we get the one
# for the default fallback language
slug = getattr(page, localized_slug_field, None) or \
getattr(page, default_localized_slug_field, None) or page.slug
parent_url_path = getattr(parent, localized_url_path_field, None) or \
getattr(parent, default_localized_url_path_field, None) or parent.url_path
setattr(page, localized_url_path_field, parent_url_path + slug + '/')
else:
# a page without a parent is the tree root,
# which always has a url_path of '/'
setattr(page, localized_url_path_field, '/')
def _new_set_url_path(self, parent):
@ -229,32 +281,7 @@ def _new_set_url_path(self, parent):
by page slug.
"""
for language in mt_settings.AVAILABLE_LANGUAGES:
localized_slug_field = build_localized_fieldname('slug', language)
default_localized_slug_field = build_localized_fieldname('slug', mt_settings.DEFAULT_LANGUAGE)
localized_url_path_field = build_localized_fieldname('url_path', language)
default_localized_url_path_field = build_localized_fieldname('url_path', mt_settings.DEFAULT_LANGUAGE)
if parent:
parent = parent.specific
# Emulate the default behavior of django-modeltranslation to get the slug and url path
# for the current language. If the value for the current language is invalid we get the one
# for the default fallback language
slug = getattr(self, localized_slug_field, None) or getattr(self, default_localized_slug_field, self.slug)
parent_url_path = getattr(parent, localized_url_path_field, None) or \
getattr(parent, default_localized_url_path_field, parent.url_path)
setattr(self, localized_url_path_field, parent_url_path + slug + '/')
else:
# a page without a parent is the tree root,
# which always has a url_path of '/'
setattr(self, localized_url_path_field, '/')
# update url_path for children pages
for child in self.get_children().specific():
child.set_url_path(self.specific)
child.save()
_localized_set_url_path(self, parent, language)
return self.url_path
@ -263,6 +290,20 @@ def _new_route(self, request, path_components):
"""
Rewrite route method in order to handle languages fallbacks
"""
# copied from wagtail/contrib/wagtailroutablepage/models.py mixin ##
# Override route when Page is also RoutablePage
if isinstance(self, RoutablePageMixin):
if self.live:
try:
path = '/'
if path_components:
path += '/'.join(path_components) + '/'
view, args, kwargs = self.resolve_subpage(path)
return RouteResult(self, args=(view, args, kwargs))
except Http404:
pass
if path_components:
# request is for a child of this page
child_slug = path_components[0]
@ -270,7 +311,7 @@ def _new_route(self, request, path_components):
subpages = self.get_children()
for page in subpages:
if page.specific.slug == child_slug:
if page.slug == child_slug:
return page.specific.route(request, remaining_components)
raise Http404
@ -282,61 +323,6 @@ def _new_route(self, request, path_components):
raise Http404
@staticmethod
def _new_get_site_root_paths():
"""
Return a list of (root_path, root_url) tuples, most specific path first -
used to translate url_paths into actual URLs with hostnames
Same method as Site.get_site_root_paths() but without cache
TODO: remake this method with cache and think of his integration in
Site.get_site_root_paths()
"""
result = [
(site.id, site.root_page.specific.url_path, site.root_url)
for site in Site.objects.select_related('root_page').order_by('-root_page__url_path')
]
return result
def _new_relative_url(self, current_site):
"""
Return the 'most appropriate' URL for this page taking into account the site we're currently on;
a local URL if the site matches, or a fully qualified one otherwise.
Return None if the page is not routable.
Override for using custom get_site_root_paths() instead of
Site.get_site_root_paths()
"""
for (id, root_path, root_url) in self.get_site_root_paths():
if self.url_path.startswith(root_path):
return ('' if current_site.id == id else root_url) + reverse('wagtail_serve',
args=(self.url_path[len(root_path):],))
@property
def _new_url(self):
"""
Return the 'most appropriate' URL for referring to this page from the pages we serve,
within the Wagtail backend and actual website templates;
this is the local URL (starting with '/') if we're only running a single site
(i.e. we know that whatever the current page is being served from, this link will be on the
same domain), and the full URL (with domain) if not.
Return None if the page is not routable.
Override for using custom get_site_root_paths() instead of
Site.get_site_root_paths()
"""
root_paths = self.get_site_root_paths()
for (id, root_path, root_url) in root_paths:
if self.url_path.startswith(root_path):
return ('' if len(root_paths) == 1 else root_url) + reverse(
'wagtail_serve', args=(self.url_path[len(root_path):],))
def _validate_slugs(page):
"""
Determine whether the given slug is available for use on a child page of
@ -351,7 +337,7 @@ def _validate_slugs(page):
# Save the current active language
current_language = get_language()
siblings = page.get_siblings(inclusive=False).specific()
siblings = page.get_siblings(inclusive=False)
errors = {}
@ -364,7 +350,7 @@ def _validate_slugs(page):
siblings_slugs = [sibling.slug for sibling in siblings]
if page.specific.slug in siblings_slugs:
if page.slug in siblings_slugs:
errors[build_localized_fieldname('slug', language)] = _("This slug is already in use")
# Re-enable the original language
@ -388,6 +374,195 @@ def _patch_clean(model):
model.clean = clean
def _new_update_descendant_url_paths(self, old_url_path, new_url_path):
return _localized_update_descendant_url_paths(self, old_url_path, new_url_path)
def _localized_update_descendant_url_paths(page, old_url_path, new_url_path, language=None):
localized_url_path = 'url_path'
if language:
localized_url_path = build_localized_fieldname('url_path', language)
if connection.vendor in ('mssql', 'microsoft'):
cursor = connection.cursor()
update_statement = """
UPDATE wagtailcore_page
SET {localized_url_path}= CONCAT(%s, (SUBSTRING({localized_url_path}, 0, %s)))
WHERE path LIKE %s AND id <> %s
""".format(localized_url_path=localized_url_path)
cursor.execute(update_statement, [new_url_path, len(old_url_path) + 1, page.path + '%', page.id])
else:
(Page.objects
.rewrite(False)
.filter(path__startswith=page.path)
.exclude(**{localized_url_path: None}) # url_path_xx may not be set yet
.exclude(pk=page.pk)
.update(**{localized_url_path: Concat(
Value(new_url_path),
Substr(localized_url_path, len(old_url_path) + 1))}))
def _localized_site_get_site_root_paths():
"""
Localized version of ``Site.get_site_root_paths()``
"""
current_language = get_language()
cache_key = 'wagtail_site_root_paths_{}'.format(current_language)
result = cache.get(cache_key)
if result is None:
result = [
(site.id, site.root_page.url_path, site.root_url)
for site in Site.objects.select_related('root_page').order_by('-root_page__url_path')
]
cache.set(cache_key, result, 3600)
return result
def _new_get_site_root_paths(self, request=None):
"""
Return localized site_root_paths, using the cached copy on the
request object if available and if language is the same.
"""
# if we have a request, use that to cache site_root_paths; otherwise, use self
current_language = get_language()
cache_object = request if request else self
if not hasattr(cache_object, '_wagtail_cached_site_root_paths_language') or \
cache_object._wagtail_cached_site_root_paths_language != current_language:
cache_object._wagtail_cached_site_root_paths_language = current_language
cache_object._wagtail_cached_site_root_paths = _localized_site_get_site_root_paths()
return cache_object._wagtail_cached_site_root_paths
def _new_get_url_parts(self, request=None):
"""
For Wagtail<1.11 ``Page.get_url_parts()`` is patched so it uses ``self._get_site_root_paths(request)``
"""
for (site_id, root_path, root_url) in self._get_site_root_paths(request):
if self.url_path.startswith(root_path):
page_path = reverse('wagtail_serve', args=(self.url_path[len(root_path):],))
# Remove the trailing slash from the URL reverse generates if
# WAGTAIL_APPEND_SLASH is False and we're not trying to serve
# the root path
if not WAGTAIL_APPEND_SLASH and page_path != '/':
page_path = page_path.rstrip('/')
return (site_id, root_url, page_path)
def _update_translation_descendant_url_paths(old_record, page):
# update children paths, must be done for all languages to ensure fallbacks are applied
languages_changed = []
default_localized_url_path = build_localized_fieldname('url_path', mt_settings.DEFAULT_LANGUAGE)
for language in mt_settings.AVAILABLE_LANGUAGES:
localized_url_path = build_localized_fieldname('url_path', language)
old_url_path = getattr(old_record, localized_url_path) or getattr(old_record, default_localized_url_path) or ''
new_url_path = getattr(page, localized_url_path) or getattr(page, default_localized_url_path)
if old_url_path == new_url_path:
# nothing to do
continue
languages_changed.append(language)
_localized_update_descendant_url_paths(page, old_url_path, new_url_path, language)
_update_untranslated_descendants_url_paths(page, languages_changed)
def _update_untranslated_descendants_url_paths(page, languages_changed):
"""
Updates localized URL Paths for child pages that don't have their localized URL Paths set yet
"""
if not languages_changed:
return
condition = Q()
update_fields = []
for language in languages_changed:
localized_url_path = build_localized_fieldname('url_path', language)
condition |= Q(**{localized_url_path: None})
update_fields.append(localized_url_path)
# let's restrict the query to children who don't have localized_url_path set yet
children = page.get_children().filter(condition)
for child in children:
for language in languages_changed:
_localized_set_url_path(child, page, language)
child.save(update_fields=update_fields) # this will trigger any required saves downstream
class LocalizedSaveDescriptor(object):
def __init__(self, f):
self.func = f
self.__name__ = 'localized_{}'.format(f.__name__)
@transaction.atomic # only commit when all descendants are properly updated
def __call__(self, instance, *args, **kwargs):
# when updating, save doesn't check if slug_xx has changed so it can only detect changes in slug
# from current language. We need to ensure that if a given localized slug changes we call set_url_path
if not instance.id: # creating a record, wagtail will call set_url_path, nothing to do.
return self.func(instance, *args, **kwargs)
old_record = None
change_url_path = change_descendant_url_path = False
for language in mt_settings.AVAILABLE_LANGUAGES:
localized_slug = build_localized_fieldname('slug', language)
# similar logic used in save
if not ('update_fields' in kwargs and localized_slug not in kwargs['update_fields']):
old_record = old_record or Page.objects.get(id=instance.id)
if getattr(old_record, localized_slug) != getattr(instance, localized_slug):
change_descendant_url_path = True
if language != get_language():
change_url_path = True
break
# Pages may have have their url_path_xx changed upstream when a parent has its url_path changed.
# If that's the case let's propagate the change to children
if not change_descendant_url_path:
localized_url_path = build_localized_fieldname('url_path', language)
if not ('update_fields' in kwargs and localized_url_path not in kwargs['update_fields']):
old_record = old_record or Page.objects.get(id=instance.id)
if getattr(old_record, localized_url_path) != getattr(instance, localized_url_path):
change_descendant_url_path = True
# if any language other than current language had it slug changed set_url_path will be executed
if change_url_path:
instance.set_url_path(instance.get_parent())
result = self.func(instance, *args, **kwargs)
# update children localized paths if any language had it slug changed
if change_descendant_url_path:
_update_translation_descendant_url_paths(old_record, instance)
# Check if this is a root page of any sites and clear the 'wagtail_site_root_paths_XX' key if so
if Site.objects.filter(root_page=instance).exists():
for language in mt_settings.AVAILABLE_LANGUAGES:
cache.delete('wagtail_site_root_paths_{}'.format(language))
return result
def __get__(self, instance, owner=None):
return types.MethodType(self, instance) if instance else self
def _patch_stream_field_meaningful_value(field):
old_meaningful_value = field.meaningful_value
def meaningful_value(self, val, undefined):
"""
Check if val is considered non-empty.
"""
if isinstance(val, StreamValue):
return len(val.stream_data) != 0
return old_meaningful_value(self, val, undefined)
field.meaningful_value = meaningful_value.__get__(field)
def patch_wagtail_models():
# After all models being registered the Page or BaseSetting subclasses and snippets are patched
registered_models = translator.get_registered_models()
@ -398,5 +573,4 @@ def patch_wagtail_models():
registered_models.sort(key=compare_class_tree_depth)
for model_class in registered_models:
if issubclass(model_class, Page) or model_class in get_snippet_models() or issubclass(model_class, BaseSetting):
WagtailTranslator(model_class)
WagtailTranslator(model_class)

View file

@ -0,0 +1,107 @@
# coding: utf-8
from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
try:
from wagtail.core.models import Page
from wagtail.admin import widgets
from wagtail.admin.forms.pages import CopyForm
except ImportError:
from wagtail.wagtailcore.models import Page
from wagtail.wagtailadmin import widgets
from wagtail.wagtailadmin.forms import CopyForm
class PatchedCopyForm(CopyForm):
def __init__(self, *args, **kwargs):
# CopyPage must be passed a 'page' kwarg indicating the page to be copied
self.page = kwargs.pop('page')
self.user = kwargs.pop('user', None)
can_publish = kwargs.pop('can_publish')
super(CopyForm, self).__init__(*args, **kwargs)
#self.fields['new_title'] = forms.CharField(initial=self.page.title, label=_("New title"))
for code, name in settings.LANGUAGES:
locale_title = "new_title_{}".format(code)
locale_label = "{} [{}]".format(_("New title"), code)
self.fields[locale_title] = forms.CharField(initial=self.page.title, label=locale_label)
#self.fields['new_slug'] = forms.SlugField(initial=self.page.slug, label=_("New slug"))
for code, name in settings.LANGUAGES:
locale_title = "new_slug_{}".format(code)
locale_label = "{} [{}]".format(_("New slug"), code)
self.fields[locale_title] = forms.SlugField(initial=self.page.slug, label=locale_label)
self.fields['new_parent_page'] = forms.ModelChoiceField(
initial=self.page.get_parent(),
queryset=Page.objects.all(),
widget=widgets.AdminPageChooser(can_choose_root=True, user_perms='copy_to'),
label=_("New parent page"),
help_text=_("This copy will be a child of this given parent page.")
)
pages_to_copy = self.page.get_descendants(inclusive=True)
subpage_count = pages_to_copy.count() - 1
if subpage_count > 0:
self.fields['copy_subpages'] = forms.BooleanField(
required=False, initial=True, label=_("Copy subpages"),
help_text=ungettext(
"This will copy %(count)s subpage.",
"This will copy %(count)s subpages.",
subpage_count) % {'count': subpage_count})
if can_publish:
pages_to_publish_count = pages_to_copy.live().count()
if pages_to_publish_count > 0:
# In the specific case that there are no subpages, customise the field label and help text
if subpage_count == 0:
label = _("Publish copied page")
help_text = _("This page is live. Would you like to publish its copy as well?")
else:
label = _("Publish copies")
help_text = ungettext(
"%(count)s of the pages being copied is live. Would you like to publish its copy?",
"%(count)s of the pages being copied are live. Would you like to publish their copies?",
pages_to_publish_count) % {'count': pages_to_publish_count}
self.fields['publish_copies'] = forms.BooleanField(
required=False, initial=True, label=label, help_text=help_text
)
def clean(self):
cleaned_data = super(CopyForm, self).clean()
# Make sure the slug isn't already in use
# slug = cleaned_data.get('new_slug')
# New parent page given in form or parent of source, if parent_page is empty
parent_page = cleaned_data.get('new_parent_page') or self.page.get_parent()
# check if user is allowed to create a page at given location.
if not parent_page.permissions_for_user(self.user).can_add_subpage():
raise ValidationError({
'new_parent_page': _("You do not have permission to copy to page \"%(page_title)s\"") % {'page_title': parent_page.get_admin_display_title()}
})
# Count the pages with the same slug within the context of our copy's parent page
for code, name in settings.LANGUAGES:
locale_slug = "new_slug_{}".format(code)
slug = cleaned_data.get(locale_slug)
param = 'slug_' + code
query = {param: slug}
if slug and parent_page.get_children().filter(**query).count():
raise ValidationError({
locale_slug: _("This slug is already in use within the context of its parent page \"%s\"" % parent_page)
})
# Don't allow recursive copies into self
if cleaned_data.get('copy_subpages') and (self.page == parent_page or parent_page.is_descendant_of(self.page)):
raise ValidationError({
'new_parent_page': _("You cannot copy a page into itself when copying subpages")
})
return cleaned_data

View file

@ -10,3 +10,4 @@ CUSTOM_SIMPLE_PANELS = [import_from_string(panel_class) for panel_class in
getattr(settings, 'WAGTAILMODELTRANSLATION_CUSTOM_SIMPLE_PANELS', [])]
CUSTOM_COMPOSED_PANELS = [import_from_string(panel_class) for panel_class in
getattr(settings, 'WAGTAILMODELTRANSLATION_CUSTOM_COMPOSED_PANELS', [])]
TRANSLATE_SLUGS = getattr(settings, 'WAGTAILMODELTRANSLATION_TRANSLATE_SLUGS', True)

View file

@ -7,22 +7,47 @@ $(document).ready(function(){
for (var i = 0; i < allStreamFields.length; i++) {
//Current Field with all content
var currentStreamField = allStreamFields[i];
//Current Field header
var header = $(currentStreamField).children('h2')[0];
//Search for the input field so that we can get is id to know the field's name.
var streamFieldDiv = $(currentStreamField).find('div.sequence-container.sequence-type-stream')[0];
var fieldInfos = $(streamFieldDiv).children('input')[0].id.split('-')[0];
var lastUnderscore = fieldInfos.lastIndexOf("_");
var fieldName = fieldInfos.substring(0, lastUnderscore);
var fieldLang = fieldInfos.substring(lastUnderscore + 1, fieldInfos.length);
//Current field header
var header;
//Current field name
var fieldLang = "";
//Current field language
var fieldName = "";
if(versionCompare(WAGTAIL_VERSION,'2.6.0', {zeroExtend: true})===-1){
// Wagtail < 2.6
header = $(currentStreamField).children('h2')[0];
//Search for the input field so that we can get is id to know the field's name.
var streamFieldDiv = $(currentStreamField).find('div.sequence-container.sequence-type-stream')[0];
var fieldInfos = $(streamFieldDiv).find('input')[0].id.split('-')[0];
var lastUnderscore = fieldInfos.lastIndexOf("_");
fieldName = fieldInfos.substring(0, lastUnderscore);
fieldLang = fieldInfos.substring(lastUnderscore + 1, fieldInfos.length);
} else if(versionCompare(WAGTAIL_VERSION,'2.7.0', {zeroExtend: true})===-1){
// Wagtail < 2.7
header = $(currentStreamField).children('.title-wrapper')[0];
//Search for the input field so that we can get is id to know the field's name.
var streamFieldDiv = $(currentStreamField).find('div.sequence-container.sequence-type-stream')[0];
var fieldInfos = $(streamFieldDiv).find('input')[0].id.split('-')[0];
var lastUnderscore = fieldInfos.lastIndexOf("_");
fieldName = fieldInfos.substring(0, lastUnderscore);
fieldLang = fieldInfos.substring(lastUnderscore + 1, fieldInfos.length);
} else {
// Wagtail >= 2.7
header = $(currentStreamField).children('.title-wrapper')[0];
//Search for the input field so that we can get is id to know the field's name.
var streamFieldDiv = $(currentStreamField).find('.field-content')[0];
var fieldInfos = $(streamFieldDiv).find('input')[0].id.split('-')[0];
var lastUnderscore = fieldInfos.lastIndexOf("_");
fieldName = fieldInfos.substring(0, lastUnderscore);
fieldLang = fieldInfos.substring(lastUnderscore + 1, fieldInfos.length);
}
//The cycle to create the buttons for copy each language field
var copyContentString = 'Copy content from';
header.innerHTML += '<div class="translation-field-copy-wrapper">'+copyContentString+': </div>';
header.innerHTML += '<div class="translation-field-copy-wrapper">Copy content from: </div>';
for (var j = 0; j < langs.length; j++) {
if (fieldLang != langs[j]) {
var currentFieldID = fieldName + '_' + fieldLang;
var targetFieldID = fieldName + '_' + langs [j];
$(header).children('.translation-field-copy-wrapper')[0].innerHTML += '<button class="translation-field-copy" current-lang-code="'+ currentFieldID +'" data-lang-code="'+ targetFieldID +'">'+langs[j]+'</button>';
$(header).children('.translation-field-copy-wrapper')[0].innerHTML += '<button class="button translation-field-copy" current-lang-code="' + currentFieldID + '" data-lang-code="' + targetFieldID + '">' + langs[j] + '</button>';
};
};
};
@ -55,11 +80,11 @@ function requestCopyField(originID, targetID) {
})
.done(function(data) {
/* Put the html data in the targetID field */
var wrapperDiv = $("#"+targetID+"-count").parents('.input')[0];
var wrapperDiv = $('#' + targetID + '-count').parents('.input')[0];
$(wrapperDiv).html(data);
})
.fail(function(error) {
console.log("wagtail-modeltranslation error: %s", error.responseText);
console.log('wagtail-modeltranslation error: ' + error.responseText);
})
}

View file

@ -0,0 +1,78 @@
/**
* Compares two software version numbers (e.g. "1.7.1" or "1.2b").
*
* This function was born in http://stackoverflow.com/a/6832721.
*
* @param {string} v1 The first version to be compared.
* @param {string} v2 The second version to be compared.
* @param {object} [options] Optional flags that affect comparison behavior.
* @param {boolean} [options.lexicographical = false] Switch to compare version strings lexicographically instead of naturally.
* @param {boolean} [options.zeroExtend = false] Switch to pad version with "zero" parts instead to be considered smaller.
* <ul>
* <li>
* <tt>lexicographical: true</tt> compares each part of the version strings lexicographically instead of
* naturally; this allows suffixes such as "b" or "dev" but will cause "1.10" to be considered smaller than
* "1.2".
* </li>
* <li>
* <tt>zeroExtend: true</tt> changes the result if one version string has less parts than the other. In
* this case the shorter string will be padded with "zero" parts instead of being considered smaller.
* </li>
* </ul>
* @returns {number|NaN}
* <ul>
* <li>0 if the versions are equal</li>
* <li>a negative integer (-1) if v1 < v2</li>
* <li>a positive integer (1) if v1 > v2</li>
* <li>NaN if either version string is in the wrong format</li>
* </ul>
*
* @copyright by Jon Papaioannou (["john", "papaioannou"].join(".") + "@gmail.com")
* @license This function is in the public domain. Do what you want with it, no strings attached.
*/
function versionCompare(v1, v2, options) {
var lexicographical = options && options.lexicographical,
zeroExtend = options && options.zeroExtend,
v1parts = v1.split('.'),
v2parts = v2.split('.');
function isValidPart(x) {
return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
}
if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
return NaN;
}
if (zeroExtend) {
while (v1parts.length < v2parts.length) v1parts.push("0");
while (v2parts.length < v1parts.length) v2parts.push("0");
}
if (!lexicographical) {
v1parts = v1parts.map(Number);
v2parts = v2parts.map(Number);
}
for (var i = 0; i < v1parts.length; ++i) {
if (v2parts.length == i) {
return 1;
}
if (v1parts[i] == v2parts[i]) {
continue;
}
else if (v1parts[i] > v2parts[i]) {
return 1;
}
else {
return -1;
}
}
if (v1parts.length != v2parts.length) {
return -1;
}
return 0;
}

View file

@ -1,22 +1,36 @@
$(document).ready(function () {
/* Only non-live pages should auto-populate the slug from the title */
if (!$('body').hasClass('page-is-live')) {
var slugFollowsTitle = false;
$.each(langs, function (idx, lang_code) {
$('#id_title_' + lang_code).on('focus', function () {
/* slug should only follow the title field if its value matched the title's value at the time of focus */
var currentSlug = $('#id_slug_' + lang_code).val();
var slugifiedTitle = cleanForSlug(this.value);
slugFollowsTitle = (currentSlug == slugifiedTitle);
if(!translate_slugs) {
lang_code = default_lang.replace("-", "_");
title_selector = '#id_title_' + lang_code;
slug_selector = '#id_slug';
slugAutoPopulateTranslation(title_selector, slug_selector);
} else {
$.each(langs, function (idx, lang_code) {
lang_code = lang_code.replace("-", "_");
title_selector = '#id_title_' + lang_code;
slug_selector = '#id_slug_' + lang_code;
slugAutoPopulateTranslation(title_selector, slug_selector);
});
$('#id_title_' + lang_code).on('keyup keydown keypress blur', function () {
if (slugFollowsTitle) {
var slugifiedTitle = cleanForSlug(this.value);
$('#id_slug_' + lang_code).val(slugifiedTitle);
}
});
});
}
}
});
});
function slugAutoPopulateTranslation(title_selector, slug_selector) {
var slugFollowsTitle = false;
$(title_selector).on('focus', function () {
/* slug should only follow the title field if its value matched the title's value at the time of focus */
var currentSlug = $(slug_selector).val();
var slugifiedTitle = cleanForSlug(this.value, true);
slugFollowsTitle = (currentSlug == slugifiedTitle);
});
$(title_selector).on('keyup keydown keypress blur', function () {
if (slugFollowsTitle) {
var slugifiedTitle = cleanForSlug(this.value, true);
$(slug_selector).val(slugifiedTitle);
}
});
}

View file

@ -0,0 +1,31 @@
{% extends "wagtailadmin/base.html" %}
{% load i18n %}
{% block titletag %}{% blocktrans with title=page.get_admin_display_title %}Copy {{ title }}{% endblocktrans %}{% endblock %}
{% block content %}
{% trans "Copy" as copy_str %}
{% include "wagtailadmin/shared/header.html" with title=copy_str subtitle=page.get_admin_display_title icon="doc-empty-inverse" %}
<div class="nice-padding">
<form action="{% url 'wagtailadmin_pages:copy' page.id %}" method="POST" novalidate>
{% csrf_token %}
<input type="hidden" name="next" value="{{ next }}" />
<ul class="fields">
{% for field in form.visible_fields %}
{% include "wagtailadmin/shared/field_as_li.html" with field=field %}
{% endfor %}
{% if form.copy_subpages %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.copy_subpages %}
{% endif %}
</ul>
<input type="submit" value="{% trans 'Copy this page' %}" class="button">
</form>
</div>
{% endblock %}
{% block extra_js %}
{{ block.super }}
{% include "wagtailadmin/pages/_editor_js.html" %}
{% endblock %}

View file

@ -3,10 +3,27 @@
import re
from django import template
from django.core.urlresolvers import resolve
from django.utils.translation import activate, get_language
try:
from django.urls import resolve
except ImportError:
from django.core.urlresolvers import resolve
from six import iteritems
try:
from wagtail.core.models import Page
from wagtail.core.templatetags.wagtailcore_tags import pageurl
except ImportError:
from wagtail.wagtailcore.models import Page
from wagtail.wagtailcore.templatetags.wagtailcore_tags import pageurl
from modeltranslation import settings as mt_settings
from modeltranslation.settings import DEFAULT_LANGUAGE
from ..contextlib import use_language
register = template.Library()
@ -38,8 +55,65 @@ def change_lang(context, lang=None, *args, **kwargs):
translated_url = '/' + lang + '/' + path_components[0] + '/'
if request.GET:
translated_url += '?'
for key, value in iteritems(request.GET):
for count, (key, value) in enumerate(iteritems(request.GET)):
if count != 0:
translated_url += "&"
translated_url += key + '=' + value
return translated_url
return ''
class GetAvailableLanguagesNode(template.Node):
"""Get available languages."""
def __init__(self, variable):
self.variable = variable
def render(self, context):
"""Rendering."""
context[self.variable] = mt_settings.AVAILABLE_LANGUAGES
return ''
# Alternative to slugurl which uses chosen or default language for language
@register.simple_tag(takes_context=True)
def slugurl_trans(context, slug, language=None):
"""
Examples:
{% slugurl_trans 'default_lang_slug' %}
{% slugurl_trans 'de_lang_slug' 'de' %}
Returns the URL for the page that has the given slug.
"""
language = language or DEFAULT_LANGUAGE
with use_language(language):
page = Page.objects.filter(slug=slug).first()
if page:
# call pageurl() instead of page.relative_url() here so we get the ``accepts_kwarg`` logic
return pageurl(context, page)
@register.tag('get_available_languages_wmt')
def do_get_available_languages(unused_parser, token):
"""
Store a list of available languages in the context.
Usage::
{% get_available_languages_wmt as languages %}
{% for language in languages %}
...
{% endfor %}
This will just pull the MODELTRANSLATION_LANGUAGES (or LANGUAGES) setting
from your setting file (or the default settings) and
put it into the named variable.
"""
args = token.contents.split()
if len(args) != 3 or args[1] != 'as':
raise template.TemplateSyntaxError(
"'get_available_languages_wmt' requires 'as variable' "
"(got %r)" % args)
return GetAvailableLanguagesNode(args[2])

View file

@ -1,13 +1,26 @@
# coding: utf-8
from django.db import models
from django.http import HttpResponse
from modelcluster.fields import ParentalKey
from wagtail.wagtailadmin.edit_handlers import FieldPanel, MultiFieldPanel, FieldRowPanel, InlinePanel, StreamFieldPanel
from wagtail.wagtailcore import blocks
from wagtail.wagtailcore.fields import StreamField
from wagtail.wagtailcore.models import Page as WagtailPage
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
from wagtail.wagtailsearch import index
from wagtail.wagtailsnippets.models import register_snippet
try:
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
from wagtail.admin.edit_handlers import FieldPanel, MultiFieldPanel, FieldRowPanel, InlinePanel, StreamFieldPanel
from wagtail.core import blocks
from wagtail.core.fields import StreamField
from wagtail.core.models import Page as WagtailPage
from wagtail.images.edit_handlers import ImageChooserPanel
from wagtail.search import index
from wagtail.snippets.models import register_snippet
except ImportError:
from wagtail.contrib.wagtailroutablepage.models import RoutablePageMixin, route
from wagtail.wagtailadmin.edit_handlers import FieldPanel, MultiFieldPanel, FieldRowPanel, InlinePanel, \
StreamFieldPanel
from wagtail.wagtailcore import blocks
from wagtail.wagtailcore.fields import StreamField
from wagtail.wagtailcore.models import Page as WagtailPage
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
from wagtail.wagtailsearch import index
from wagtail.wagtailsnippets.models import register_snippet
# Wagtail Models
@ -25,6 +38,10 @@ class TestSlugPage2(WagtailPage):
pass
class TestSlugPage1Subclass(TestSlugPage1):
pass
class PatchTestPage(WagtailPage):
description = models.CharField(max_length=50)
@ -35,9 +52,13 @@ class PatchTestPage(WagtailPage):
@register_snippet
class PatchTestSnippet(models.Model):
class PatchTestSnippetNoPanels(models.Model):
name = models.CharField(max_length=10)
@register_snippet
class PatchTestSnippet(PatchTestSnippetNoPanels):
panels = [
FieldPanel('name')
]
@ -57,7 +78,8 @@ class FieldPanelSnippet(models.Model):
@register_snippet
class ImageChooserPanelSnippet(models.Model):
image = models.ForeignKey(
'wagtailimages.Image'
'wagtailimages.Image',
on_delete=models.CASCADE,
)
panels = [
@ -105,7 +127,10 @@ class MultiFieldPanelSnippet(FieldPanelSnippet, ImageChooserPanelSnippet, FieldR
class BaseInlineModel(MultiFieldPanelSnippet):
field_name = models.CharField(max_length=10)
image_chooser = models.ForeignKey('wagtailimages.Image')
image_chooser = models.ForeignKey(
'wagtailimages.Image',
on_delete=models.CASCADE,
)
fieldrow_name = models.CharField(max_length=10)
@ -141,7 +166,8 @@ class FieldPanelPage(WagtailPage):
class ImageChooserPanelPage(WagtailPage):
image = models.ForeignKey(
'wagtailimages.Image'
'wagtailimages.Image',
on_delete=models.CASCADE,
)
content_panels = [
@ -162,7 +188,7 @@ class FieldRowPanelPage(WagtailPage):
class StreamFieldPanelPage(WagtailPage):
body = StreamField([
('text', blocks.CharBlock(max_length=10))
])
], blank=False) # since wagtail 1.12 StreamField's blank defaults to False
content_panels = [
StreamFieldPanel('body')
@ -191,3 +217,13 @@ class InlinePanelPage(WagtailPage):
content_panels = [
InlinePanel('related_page_model')
]
class RoutablePageTest(RoutablePageMixin, WagtailPage):
@route(r'^archive/year/1984/$')
def archive_for_1984(self, request):
return HttpResponse("we were always at war with eastasia")
@route(r'^archive/year/(\d+)/$')
def archive_by_year(self, request, year):
return HttpResponse("ARCHIVE BY YEAR: " + str(year))

View file

@ -18,6 +18,8 @@ USE_TZ = False
MIDDLEWARE_CLASSES = ()
MODELTRANSLATION_AUTO_POPULATE = False
MODELTRANSLATION_FALLBACK_LANGUAGES = ()
MODELTRANSLATION_FALLBACK_LANGUAGES = {'default': (MODELTRANSLATION_DEFAULT_LANGUAGE,)}
ROOT_URLCONF = 'wagtail_modeltranslation.tests.urls'
TRANSLATE_SLUGS = True

View file

@ -3,17 +3,26 @@ import imp
import django
from django.apps import apps as django_apps
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.core.management import call_command
from django.http import HttpRequest
from django.test import TestCase, TransactionTestCase
from django.test.client import RequestFactory
from django.test.utils import override_settings
from django.utils.translation import get_language, trans_real
from modeltranslation import settings as mt_settings, translator
try:
from wagtail.snippets.views.snippets import get_snippet_edit_handler
except ImportError:
from wagtail.wagtailsnippets.views.snippets import get_snippet_edit_handler
from wagtail import VERSION
from .util import page_factory
from wagtail_modeltranslation.tests.test_settings import TEST_SETTINGS
models = translation = None
request_factory = RequestFactory()
class dummy_context_mgr():
def __enter__(self):
@ -39,8 +48,7 @@ class WagtailModeltranslationTransactionTestBase(TransactionTestCase):
if not WagtailModeltranslationTransactionTestBase.synced:
# In order to perform only one syncdb
WagtailModeltranslationTransactionTestBase.synced = True
mgr = (override_settings(**TEST_SETTINGS) if django.VERSION < (1, 8)
else dummy_context_mgr())
mgr = dummy_context_mgr()
with mgr:
# 1. Reload translation in case USE_I18N was False
from django.utils import translation as dj_trans
@ -51,9 +59,23 @@ class WagtailModeltranslationTransactionTestBase(TransactionTestCase):
imp.reload(translator)
# reload the translation module to register the Page model
from wagtail_modeltranslation import translation as wag_translation, translator as wag_translator
# and also edit_handlers so any patches made to Page are reapplied
import sys
del cls.cache.all_models['wagtailcore']
sys.modules.pop('wagtail_modeltranslation.translation.pagetr', None)
from wagtail_modeltranslation import translation as wag_translation
try:
from wagtail.admin import edit_handlers
sys.modules.pop('wagtail.core.models', None)
except ImportError:
from wagtail.wagtailadmin import edit_handlers
sys.modules.pop('wagtail.wagtailcore.models', None)
imp.reload(wag_translation)
imp.reload(wag_translator)
imp.reload(edit_handlers) # so Page can be repatched by edit_handlers
wagtailcore_args = []
if django.VERSION < (1, 11):
wagtailcore_args = [cls.cache.all_models['wagtailcore']]
cls.cache.get_app_config('wagtailcore').import_models(*wagtailcore_args)
# Reload the patching class to update the imported translator
# in order to include the newly registered models
@ -64,10 +86,12 @@ class WagtailModeltranslationTransactionTestBase(TransactionTestCase):
# have translation fields, but for languages previously defined. We want
# to be sure that 'de' and 'en' are available)
del cls.cache.all_models['tests']
import sys
sys.modules.pop('wagtail_modeltranslation.tests.models', None)
sys.modules.pop('wagtail_modeltranslation.tests.translation', None)
cls.cache.get_app_config('tests').import_models(cls.cache.all_models['tests'])
tests_args = []
if django.VERSION < (1, 11):
tests_args = [cls.cache.all_models['tests']]
cls.cache.get_app_config('tests').import_models(*tests_args)
# 4. Autodiscover
from modeltranslation.models import handle_translation_registrations
@ -75,14 +99,16 @@ class WagtailModeltranslationTransactionTestBase(TransactionTestCase):
# 5. makemigrations
from django.db import connections, DEFAULT_DB_ALIAS
call_command('makemigrations', verbosity=2, interactive=False,
database=connections[DEFAULT_DB_ALIAS].alias)
call_command('makemigrations', verbosity=2, interactive=False)
# 6. Syncdb
call_command('migrate', verbosity=0, migrate=False, interactive=False, run_syncdb=True,
database=connections[DEFAULT_DB_ALIAS].alias, load_initial_data=False)
call_command('migrate', verbosity=0, interactive=False, run_syncdb=True,
database=connections[DEFAULT_DB_ALIAS].alias)
# 7. patch wagtail models
# 7. Make sure Page translation fields are created
call_command('sync_page_translation_fields', interactive=False, verbosity=0)
# 8. patch wagtail models
from wagtail_modeltranslation.patch_wagtailadmin import patch_wagtail_models
patch_wagtail_models()
@ -90,12 +116,16 @@ class WagtailModeltranslationTransactionTestBase(TransactionTestCase):
# tests app has been added into INSTALLED_APPS and loaded
# (that's why this is not imported in normal import section)
global models, translation
from wagtail_modeltranslation.tests import models, translation
from wagtail_modeltranslation.tests import models, translation # NOQA
def setUp(self):
self._old_language = get_language()
trans_real.activate('de')
# ensure we have a fresh site cache
for language in mt_settings.AVAILABLE_LANGUAGES:
cache.delete('wagtail_site_root_paths_{}'.format(language))
def tearDown(self):
trans_real.activate(self._old_language)
@ -114,7 +144,10 @@ class WagtailModeltranslationTest(WagtailModeltranslationTestBase):
super(WagtailModeltranslationTest, cls).setUpClass()
# Delete the default wagtail pages from db
from wagtail.wagtailcore.models import Page
try:
from wagtail.core.models import Page
except ImportError:
from wagtail.wagtailcore.models import Page
Page.objects.delete()
def test_page_fields(self):
@ -148,7 +181,11 @@ class WagtailModeltranslationTest(WagtailModeltranslationTestBase):
self.assertEquals(len(panels), 2)
# Validate if the created panels are instances of FieldPanel
from wagtail.wagtailadmin.edit_handlers import FieldPanel
try:
from wagtail.admin.edit_handlers import FieldPanel
except ImportError:
from wagtail.wagtailadmin.edit_handlers import FieldPanel
self.assertIsInstance(panels[0], FieldPanel)
self.assertIsInstance(panels[1], FieldPanel)
@ -160,7 +197,10 @@ class WagtailModeltranslationTest(WagtailModeltranslationTestBase):
# Check if there is one panel per language
self.assertEquals(len(panels), 2)
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
try:
from wagtail.images.edit_handlers import ImageChooserPanel
except ImportError:
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
self.assertIsInstance(panels[0], ImageChooserPanel)
self.assertIsInstance(panels[1], ImageChooserPanel)
@ -172,7 +212,10 @@ class WagtailModeltranslationTest(WagtailModeltranslationTestBase):
# Check if the fieldrowpanel still exists
self.assertEqual(len(panels), 1)
from wagtail.wagtailadmin.edit_handlers import FieldRowPanel
try:
from wagtail.admin.edit_handlers import FieldRowPanel
except ImportError:
from wagtail.wagtailadmin.edit_handlers import FieldRowPanel
self.assertIsInstance(panels[0], FieldRowPanel)
# Check if the children were correctly patched using the fieldpanel test
@ -184,7 +227,10 @@ class WagtailModeltranslationTest(WagtailModeltranslationTestBase):
# Check if there is one panel per language
self.assertEquals(len(panels), 2)
from wagtail.wagtailadmin.edit_handlers import StreamFieldPanel
try:
from wagtail.admin.edit_handlers import StreamFieldPanel
except ImportError:
from wagtail.wagtailadmin.edit_handlers import StreamFieldPanel
self.assertIsInstance(panels[0], StreamFieldPanel)
self.assertIsInstance(panels[1], StreamFieldPanel)
@ -197,16 +243,33 @@ class WagtailModeltranslationTest(WagtailModeltranslationTestBase):
self.assertEquals(len(child_block), 1)
from wagtail.wagtailcore.blocks import CharBlock
try:
from wagtail.core.blocks import CharBlock
except ImportError:
from wagtail.wagtailcore.blocks import CharBlock
self.assertEquals(child_block[0][0], 'text')
self.assertIsInstance(child_block[0][1], CharBlock)
if VERSION >= (1, 12):
# Original and Default language StreamFields are required
self.assertFalse(models.StreamFieldPanelPage.body.field.blank)
self.assertTrue(models.StreamFieldPanelPage.body.field.stream_block.required)
self.assertFalse(models.StreamFieldPanelPage.body_de.field.blank)
self.assertTrue(models.StreamFieldPanelPage.body_de.field.stream_block.required)
# Translated StreamField is optional
self.assertTrue(models.StreamFieldPanelPage.body_en.field.blank)
self.assertFalse(models.StreamFieldPanelPage.body_en.field.stream_block.required)
def check_multipanel_patching(self, panels):
# There are three multifield panels, one for each of the available
# children panels
self.assertEquals(len(panels), 3)
from wagtail.wagtailadmin.edit_handlers import MultiFieldPanel
try:
from wagtail.admin.edit_handlers import MultiFieldPanel
except ImportError:
from wagtail.wagtailadmin.edit_handlers import MultiFieldPanel
self.assertIsInstance(panels[0], MultiFieldPanel)
self.assertIsInstance(panels[1], MultiFieldPanel)
self.assertIsInstance(panels[2], MultiFieldPanel)
@ -244,6 +307,8 @@ class WagtailModeltranslationTest(WagtailModeltranslationTestBase):
def test_snippet_patching(self):
self.check_fieldpanel_patching(panels=models.FieldPanelSnippet.panels)
self.check_panels_patching(models.FieldPanelSnippet, ['name_de', 'name_en'])
self.check_imagechooserpanel_patching(panels=models.ImageChooserPanelSnippet.panels)
self.check_fieldrowpanel_patching(panels=models.FieldRowPanelSnippet.panels)
self.check_streamfieldpanel_patching(panels=models.StreamFieldPanelSnippet.panels)
@ -253,6 +318,24 @@ class WagtailModeltranslationTest(WagtailModeltranslationTestBase):
# which is the SnippetInlineModel
self.check_inlinepanel_patching(panels=models.SnippetInlineModel.panels)
# Case we don't define panels on snippet
self.check_panels_patching(models.PatchTestSnippetNoPanels, ['name_de', 'name_en'])
def check_panels_patching(self, model, model_fields):
patched_edit_handler = get_snippet_edit_handler(model)
if VERSION[0] < 2:
form = patched_edit_handler.get_form_class(model)
else:
form = patched_edit_handler.get_form_class()
try:
# python 3
self.assertEqual(model_fields, list(form.base_fields.keys()))
except AttributeError:
# python 2.7
self.assertItemsEqual(model_fields, form.base_fields.keys())
def test_page_form(self):
"""
In this test we use the InlinePanelPage model because it has all the possible "patchable" fields
@ -261,7 +344,10 @@ class WagtailModeltranslationTest(WagtailModeltranslationTestBase):
page_edit_handler = models.InlinePanelPage.get_edit_handler()
form = page_edit_handler.get_form_class(models.InlinePanelPage)
if VERSION < (2,):
form = page_edit_handler.get_form_class(models.InlinePanelPage)
else:
form = page_edit_handler.get_form_class()
page_base_fields = ['slug_de', 'slug_en', 'seo_title_de', 'seo_title_en', 'search_description_de',
'search_description_en', u'show_in_menus', u'go_live_at', u'expire_at']
@ -291,10 +377,12 @@ class WagtailModeltranslationTest(WagtailModeltranslationTestBase):
In this test we use the InlinePanelSnippet model because it has all the possible "patchable" fields
so if the created form has all fields the the form was correctly patched
"""
from wagtail.wagtailsnippets.views.snippets import get_snippet_edit_handler
snippet_edit_handler = get_snippet_edit_handler(models.InlinePanelSnippet)
form = snippet_edit_handler.get_form_class(models.InlinePanelSnippet)
if VERSION < (2,):
form = snippet_edit_handler.get_form_class(models.InlinePanelSnippet)
else:
form = snippet_edit_handler.get_form_class()
inline_model_fields = ['field_name_de', 'field_name_en', 'image_chooser_de', 'image_chooser_en',
'fieldrow_name_de', 'fieldrow_name_en', 'name_de', 'name_en', 'image_de', 'image_en',
@ -310,7 +398,10 @@ class WagtailModeltranslationTest(WagtailModeltranslationTestBase):
self.assertItemsEqual(inline_model_fields, related_formset_form.base_fields.keys())
def test_duplicate_slug(self):
from wagtail.wagtailcore.models import Site
try:
from wagtail.core.models import Site
except ImportError:
from wagtail.wagtailcore.models import Site
# Create a test Site with a root page
root = models.TestRootPage(title='title', depth=1, path='0001', slug_en='slug_en', slug_de='slug_de')
root.save()
@ -333,8 +424,92 @@ class WagtailModeltranslationTest(WagtailModeltranslationTestBase):
# Make the slug equal to test if the duplicate is detected
child2.slug_de = 'child'
self.assertRaises(ValidationError, child2.clean)
child2.slug_de = 'child-2'
# Make the translated slug equal to test if the duplicate is detected
child2.slug_en = 'child-en'
self.assertRaises(ValidationError, child2.clean)
def test_slugurl_trans(self):
"""
Assert tag slugurl_trans is immune to user's current language
"""
from wagtail_modeltranslation.templatetags.wagtail_modeltranslation import slugurl_trans
site_pages = {
'model': models.TestRootPage,
'kwargs': {'title': 'root slugurl', },
'children': {
'child': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'child slugurl', 'slug': 'child-slugurl', 'slug_en': 'child-slugurl-en'},
'children': {},
},
},
}
site = page_factory.create_page_tree(site_pages)
request_mock = request_factory.get('/')
setattr(request_mock, 'site', site)
context = {'request': request_mock}
self.assertEqual(slugurl_trans(context, 'root-slugurl'), '/de/')
self.assertEqual(slugurl_trans(context, 'child-slugurl'), '/de/child-slugurl/')
self.assertEqual(slugurl_trans(context, 'child-slugurl-en', 'en'), '/de/child-slugurl/')
trans_real.activate('en')
self.assertEqual(slugurl_trans(context, 'root-slugurl'), '/en/')
self.assertEqual(slugurl_trans(context, 'child-slugurl'), '/en/child-slugurl-en/')
self.assertEqual(slugurl_trans(context, 'child-slugurl-en', 'en'), '/en/child-slugurl-en/')
def test_relative_url(self):
try:
from wagtail.core.models import Site
except ImportError:
from wagtail.wagtailcore.models import Site
# Create a test Site with a root page
root = models.TestRootPage(title='title slugurl', depth=1, path='0004',
slug_en='title_slugurl_en', slug_de='title_slugurl_de')
root.save()
site = Site(root_page=root)
site.save()
# Add children to the root
child = root.add_child(
instance=models.TestSlugPage1(title='child1 slugurl',
slug_en='child-slugurl-en', slug_de='child-slugurl-de',
depth=2, path='00040001')
)
child.save_revision().publish()
url_1_de = child.relative_url(site)
self.assertEqual(url_1_de, '/de/child-slugurl-de/',
'When using the default language, slugurl produces the wrong url.')
trans_real.activate('en')
url_1_en = child.relative_url(site)
self.assertEqual(url_1_en, '/en/child-slugurl-en/',
'When using non-default language, slugurl produces the wrong url.')
# Add children using non-default language
child2 = root.add_child(
instance=models.TestSlugPage2(title='child2 slugurl', title_de='child2 slugurl DE',
slug_de='child2-slugurl-de', slug_en='child2-slugurl-en',
depth=2, path='00040002')
)
child2.save_revision().publish()
url_2_en = child2.relative_url(site)
self.assertEqual(url_2_en, '/en/child2-slugurl-en/',
'When using non-default language, slugurl produces the wrong url.')
trans_real.activate('de')
url_2_de = child2.relative_url(site)
self.assertEqual(url_2_de, '/de/child2-slugurl-de/',
'When using non-default language, slugurl produces the wrong url.')
def test_searchfield_patching(self):
# Check if the search fields have the original field plus the translated ones
@ -348,3 +523,474 @@ class WagtailModeltranslationTest(WagtailModeltranslationTestBase):
except AttributeError:
# python 2.7
self.assertItemsEqual(expected_fields, model_search_fields)
def test_streamfield_fallback(self):
body_text = '[{"value": "Some text", "type": "text"}]'
page = models.StreamFieldPanelPage(title='Streamfield Fallback', slug='streamfield_fallback',
depth=1, path='0005', body=body_text)
page.save()
self.assertEqual(str(page.body), '<div class="block-text">Some text</div>')
trans_real.activate('en')
self.assertEqual(str(page.body), '<div class="block-text">Some text</div>',
'page.body did not fallback to original language.')
def test_set_url_path(self):
"""
Assert translation URL Paths are correctly set in page and descendants for a slug change and
page move operations
"""
try:
from wagtail.core.models import Site
except ImportError:
from wagtail.wagtailcore.models import Site
# Create a test Site with a root page
root = models.TestRootPage.objects.create(title='url paths', depth=1, path='0006', slug='url-path-slug')
Site.objects.create(root_page=root)
# Add children to the root
child = root.add_child(
instance=models.TestSlugPage1(title='child', slug='child', depth=2, path='00060001')
)
child.save()
# Add grandchildren to the root
grandchild = child.add_child(
instance=models.TestSlugPage1(title='grandchild', slug='grandchild', depth=2, path='000600010001')
)
grandchild.save()
# check everything is as expected
self.assertEqual(child.url_path_de, '/child/')
self.assertEqual(child.url_path_en, '/child/')
self.assertEqual(grandchild.url_path_de, '/child/grandchild/')
self.assertEqual(grandchild.url_path_en, '/child/grandchild/')
# PAGE SLUG CHANGE
grandchild.slug_de = 'grandchild1'
grandchild.save()
self.assertEqual(grandchild.url_path_de, '/child/grandchild1/')
self.assertEqual(grandchild.url_path_en, '/child/grandchild1/')
grandchild.slug_en = 'grandchild1_en'
grandchild.save()
self.assertEqual(grandchild.url_path_de, '/child/grandchild1/')
self.assertEqual(grandchild.url_path_en, '/child/grandchild1_en/')
# Children url paths should update when parent changes
child.slug_en = 'child_en'
child.save()
self.assertEqual(child.url_path_de, '/child/')
self.assertEqual(child.url_path_en, '/child_en/')
# Retrieve grandchild from DB:
grandchild_new = models.TestSlugPage1.objects.get(id=grandchild.id)
self.assertEqual(grandchild_new.url_path_en, '/child_en/grandchild1_en/')
self.assertEqual(grandchild_new.url_path_de, '/child/grandchild1/')
# Add 2nd child to the root
child2 = root.add_child(
instance=models.TestSlugPage1(title='child2', slug='child2', depth=2, path='00060002')
)
child2.save()
# Add grandchildren
grandchild2 = child2.add_child(
instance=models.TestSlugPage1(title='grandchild2', slug='grandchild2', depth=3, path='000600020001')
)
grandchild2.save()
# PAGE MOVE
child2.move(child, pos='last-child')
# re-fetch child2 to confirm db fields have been updated
child2 = models.TestSlugPage1.objects.get(id=child2.id)
self.assertEqual(child2.depth, 3)
self.assertEqual(child2.get_parent().id, child.id)
self.assertEqual(child2.url_path_de, '/child/child2/')
self.assertEqual(child2.url_path_en, '/child_en/child2/')
# children of child2 should also have been updated
grandchild2 = child2.get_children().get(slug='grandchild2').specific
self.assertEqual(grandchild2.depth, 4)
self.assertEqual(grandchild2.url_path_de, '/child/child2/grandchild2/')
self.assertEqual(grandchild2.url_path_en, '/child_en/child2/grandchild2/')
def test_set_url_path_non_translated_descendants(self):
"""
Assert set_url_path works correctly when a Page with untranslated children
has its translated slug changed.
"""
site_pages = {
'model': models.TestRootPage,
'kwargs': {'title': 'root untranslated', },
'children': {
'child': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'child untranslated'},
'children': {
'grandchild1': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'grandchild1 untranslated'},
'children': {
'grandgrandchild': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'grandgrandchild untranslated'},
},
},
},
'grandchild2': {
'model': models.TestSlugPage2,
'kwargs': {'title': 'grandchild2 untranslated'},
},
},
},
},
}
page_factory.create_page_tree(site_pages)
# Revert grandchild1 and grandgrandchild url_path_en to their initial untranslated states
# to simulate pages that haven't been translated yet
models.TestSlugPage1.objects.filter(slug_de__in=['grandchild1-untranslated', 'grandgrandchild-untranslated'])\
.rewrite(False).update(slug_en=None, url_path_en=None)
# re-fetch to pick up latest from DB
grandchild1 = models.TestSlugPage1.objects.get(slug_de='grandchild1-untranslated')
self.assertEqual(grandchild1.url_path_de, '/root-untranslated/child-untranslated/grandchild1-untranslated/')
self.assertEqual(grandchild1.slug_en, None)
self.assertEqual(grandchild1.url_path_en, None)
grandgrandchild = models.TestSlugPage1.objects.get(slug_de='grandgrandchild-untranslated')
self.assertEqual(grandgrandchild.url_path_de,
'/root-untranslated/child-untranslated/grandchild1-untranslated/grandgrandchild-untranslated/')
self.assertEqual(grandgrandchild.slug_en, None)
self.assertEqual(grandgrandchild.url_path_en, None)
trans_real.activate('en')
child = site_pages['children']['child']['instance']
child.slug_en = 'child-translated'
child.save()
self.assertEqual(child.url_path_de, '/root-untranslated/child-untranslated/')
self.assertEqual(child.url_path_en, '/root-untranslated/child-translated/')
grandchild1 = models.TestSlugPage1.objects.get(slug_de='grandchild1-untranslated')
self.assertEqual(grandchild1.url_path_de, '/root-untranslated/child-untranslated/grandchild1-untranslated/')
self.assertEqual(grandchild1.url_path_en, '/root-untranslated/child-translated/grandchild1-untranslated/')
grandgrandchild = models.TestSlugPage1.objects.get(slug_de='grandgrandchild-untranslated')
self.assertEqual(grandgrandchild.url_path_de,
'/root-untranslated/child-untranslated/grandchild1-untranslated/grandgrandchild-untranslated/')
self.assertEqual(grandgrandchild.url_path_en,
'/root-untranslated/child-translated/grandchild1-untranslated/grandgrandchild-untranslated/')
def test_fetch_translation_records(self):
"""
Assert that saved translation fields are retrieved correctly
See: https://github.com/infoportugal/wagtail-modeltranslation/issues/103#issuecomment-352006610
"""
page = models.StreamFieldPanelPage.objects.create(title_de='Fetch DE', title_en='Fetch EN',
slug_de='fetch_de', slug_en='fetch_en',
body_de=[('text', 'fetch de')], body_en=[('text', 'fetch en')],
depth=1, path='0007')
page.save()
page_db = models.StreamFieldPanelPage.objects.get(id=page.id)
self.assertEqual(page_db.title_de, 'Fetch DE')
self.assertEqual(page_db.slug_de, 'fetch_de')
self.assertEqual(str(page_db.body_de), '<div class="block-text">fetch de</div>')
self.assertEqual(page_db.title_en, 'Fetch EN')
self.assertEqual(page_db.slug_en, 'fetch_en')
self.assertEqual(str(page_db.body_en), '<div class="block-text">fetch en</div>')
def check_route_request(self, root_page, components, expected_page):
request = HttpRequest()
request.path = '/' + '/'.join(components) + '/'
(found_page, args, kwargs) = root_page.route(request, components)
self.assertEqual(found_page, expected_page)
def test_request_routing(self):
"""
Assert .route works for translated slugs
"""
site_pages = {
'model': models.TestRootPage,
'kwargs': {'title': 'root routing', },
'children': {
'child1': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'child1 routing', 'slug_de': 'routing-de-01', 'slug_en': 'routing-en-01'},
'children': {
'grandchild1': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'grandchild1 routing',
'slug_de': 'routing-de-0101', 'slug_en': 'routing-en-0101'},
},
},
},
'child2': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'child2 routing', 'slug': 'routing-de-02'},
'children': {
'grandchild1': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'grandchild1 routing', 'slug': 'routing-de-0201'},
},
},
},
'routable_page': {
'model': models.RoutablePageTest,
'kwargs': {'title': 'Routable Page', 'live': True,
'slug_de': 'routing-de-03', 'slug_en': 'routing-en-03'},
'children': {
'grandchild1': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'grandchild1 routing',
'slug_de': 'routing-de-0301', 'slug_en': 'routing-en-0301'},
},
},
},
},
}
page_factory.create_page_tree(site_pages)
root_page = site_pages['instance']
page_0101 = site_pages['children']['child1']['children']['grandchild1']['instance']
page_0201 = site_pages['children']['child2']['children']['grandchild1']['instance']
page_0301 = site_pages['children']['routable_page']['children']['grandchild1']['instance']
self.check_route_request(root_page, ['routing-de-01', 'routing-de-0101'], page_0101)
self.check_route_request(root_page, ['routing-de-02', 'routing-de-0201'], page_0201)
# routable page test
routable_page = site_pages['children']['routable_page']['instance']
view, args, kwargs = routable_page.resolve_subpage('/archive/year/2014/')
self.assertEqual(view, routable_page.archive_by_year)
self.assertEqual(args, ('2014',))
self.assertEqual(kwargs, {})
self.check_route_request(root_page, ['routing-de-03', 'routing-de-0301'], page_0301)
trans_real.activate('en')
# assert translated slugs fetch the correct page
self.check_route_request(root_page, ['routing-en-01', 'routing-en-0101'], page_0101)
# in the absence of translated slugs assert the default ones work
self.check_route_request(root_page, ['routing-de-02', 'routing-de-0201'], page_0201)
view, args, kwargs = routable_page.resolve_subpage('/archive/year/2014/')
self.assertEqual(view, routable_page.archive_by_year)
self.assertEqual(args, ('2014',))
self.assertEqual(kwargs, {})
self.check_route_request(root_page, ['routing-en-03', 'routing-en-0301'], page_0301)
def test_get_url_parts(self):
site_pages = {
'model': models.TestRootPage,
'kwargs': {'title': 'root URL parts', },
'children': {
'child1': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'child1 URL parts', 'slug_de': 'url-parts-de-01', 'slug_en': 'url-parts-en-01'},
},
'child2': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'child2 URL parts', 'slug': 'url-parts-de-02'},
},
},
}
site = page_factory.create_page_tree(site_pages)
root_page = site_pages['instance']
page_01 = site_pages['children']['child1']['instance']
page_02 = site_pages['children']['child2']['instance']
self.assertEqual(root_page.relative_url(site), '/de/')
self.assertEqual(page_01.relative_url(site), '/de/url-parts-de-01/')
self.assertEqual(page_02.relative_url(site), '/de/url-parts-de-02/')
trans_real.activate('en')
self.assertEqual(root_page.relative_url(site), '/en/')
self.assertEqual(page_01.relative_url(site), '/en/url-parts-en-01/')
self.assertEqual(page_02.relative_url(site), '/en/url-parts-de-02/')
def test_url(self):
site_pages = {
'model': models.TestRootPage,
'kwargs': {'title': 'root URL', },
'children': {
'child1': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'child1 URL', 'slug_de': 'url-de-01', 'slug_en': 'url-en-01'},
},
'child2': {
'model': models.TestSlugPage2,
'kwargs': {'title': 'child2 URL', 'slug': 'url-de-02'},
},
},
}
page_factory.create_page_tree(site_pages)
root_page = site_pages['instance']
page_01 = site_pages['children']['child1']['instance']
page_02 = site_pages['children']['child2']['instance']
self.assertEqual(root_page.url, '/de/')
self.assertEqual(page_01.url, '/de/url-de-01/')
self.assertEqual(page_02.url, '/de/url-de-02/')
trans_real.activate('en')
self.assertEqual(root_page.url, '/en/')
self.assertEqual(page_01.url, '/en/url-en-01/')
self.assertEqual(page_02.url, '/en/url-de-02/')
def test_root_page_slug(self):
site_pages = {
'model': models.TestRootPage,
'kwargs': {'title': 'root URL', 'slug_de': 'root-de', 'slug_en': 'root-en'},
'children': {
'child1': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'child1 URL', 'slug_de': 'url-de-01', 'slug_en': 'url-en-01'},
},
'child2': {
'model': models.TestSlugPage2,
'kwargs': {'title': 'child2 URL', 'slug': 'url-de-02'},
},
'child3': {
'model': models.TestSlugPage2,
'kwargs': {'title': 'child3 URL', 'slug': 'url-de-03'},
},
},
}
page_factory.create_page_tree(site_pages)
request = HttpRequest()
site_root_page = site_pages['instance']
wagtail_page_01 = site_pages['children']['child1']['instance']
wagtail_page_02 = site_pages['children']['child2']['instance']
wagtail_page_03 = site_pages['children']['child3']['instance']
self.assertEqual(wagtail_page_01.url, '/de/url-de-01/')
self.assertEqual(wagtail_page_01.url_path, '/root-de/url-de-01/')
if VERSION >= (1, 11):
self.assertEqual(wagtail_page_02.get_url(request=request), '/de/url-de-02/') # with request
trans_real.activate('en')
self.assertEqual(wagtail_page_01.url, '/en/url-en-01/')
self.assertEqual(wagtail_page_01.url_path, '/root-en/url-en-01/')
if VERSION >= (1, 11):
self.assertEqual(wagtail_page_02.get_url(request=request), '/en/url-de-02/')
trans_real.activate('de')
# new request after changing language
self.assertEqual(wagtail_page_03.url, '/de/url-de-03/')
if VERSION >= (1, 11):
self.assertEqual(wagtail_page_01.get_url(request=HttpRequest()), '/de/url-de-01/')
# URL should not be broken after updating the root_page (ensure the cache is evicted)
self.assertEqual(wagtail_page_01.url, '/de/url-de-01/')
site_root_page.slug = 'new-root-de'
site_root_page.save()
wagtail_page_01_new = site_root_page.get_children().get(id=wagtail_page_01.id)
self.assertEqual(wagtail_page_01_new.url, '/de/url-de-01/')
def test_set_translation_url_paths_command(self):
"""
Assert set_translation_url_paths management command works correctly
"""
site_pages = {
'model': models.TestRootPage,
'kwargs': {'title': 'root untranslated', },
'children': {
'child': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'child untranslated'},
'children': {
'grandchild1': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'grandchild1 untranslated'},
'children': {
'grandgrandchild': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'grandgrandchild untranslated'},
},
},
},
'grandchild2': {
'model': models.TestSlugPage2,
'kwargs': {'title': 'grandchild2 untranslated'},
},
},
},
'child2': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'child2 translated', 'slug_en': 'child2-translated-en'},
'children': {
'grandchild1': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'grandchild1 translated', 'slug_en': 'grandchild1-translated-en'},
'children': {
'grandgrandchild': {
'model': models.TestSlugPage1,
'kwargs': {'title': 'grandgrandchild1 translated',
'slug_en': 'grandgrandchild1-translated-en'},
},
},
},
},
},
},
}
page_factory.create_page_tree(site_pages)
# Revert grandchild1 and grandgrandchild url_path_en to their initial untranslated states
# to simulate pages that haven't been translated yet
models.TestSlugPage1.objects.filter(slug_de__in=['grandchild1-untranslated', 'grandgrandchild-untranslated']) \
.rewrite(False).update(slug_en=None, url_path_en=None)
# re-fetch to pick up latest from DB
grandchild1 = models.TestSlugPage1.objects.get(slug_de='grandchild1-untranslated')
self.assertEqual(grandchild1.url_path_en, None)
grandgrandchild = models.TestSlugPage1.objects.get(slug_de='grandgrandchild-untranslated')
self.assertEqual(grandgrandchild.url_path_en, None)
# change grandchild2 url_path to corrupt it in order to simulate Wagtail's 0.7 corruption bug:
# http://docs.wagtail.io/en/latest/releases/0.8.html#corrupted-url-paths-may-need-fixing
models.TestSlugPage2.objects.filter(slug_de__in=['grandchild2-untranslated',]) \
.rewrite(False).update(url_path='corrupted', url_path_de='corrupted')
grandchild2 = models.TestSlugPage2.objects.get(slug_de='grandchild2-untranslated')
self.assertEqual(grandchild2.__dict__['url_path'], 'corrupted')
call_command('set_translation_url_paths', verbosity=0)
grandchild1 = models.TestSlugPage1.objects.get(slug_de='grandchild1-untranslated')
self.assertEqual(grandchild1.url_path_de, '/root-untranslated/child-untranslated/grandchild1-untranslated/')
self.assertEqual(grandchild1.url_path_en, '/root-untranslated/child-untranslated/grandchild1-untranslated/')
grandgrandchild = models.TestSlugPage1.objects.get(slug_de='grandgrandchild-untranslated')
self.assertEqual(grandgrandchild.url_path_de,
'/root-untranslated/child-untranslated/grandchild1-untranslated/grandgrandchild-untranslated/')
self.assertEqual(grandgrandchild.url_path_en,
'/root-untranslated/child-untranslated/grandchild1-untranslated/grandgrandchild-untranslated/')
grandchild2 = models.TestSlugPage2.objects.get(slug_de='grandchild2-untranslated')
self.assertEqual(grandchild2.__dict__['url_path'], '/root-untranslated/child-untranslated/grandchild2-untranslated/')
self.assertEqual(grandchild2.url_path_de, '/root-untranslated/child-untranslated/grandchild2-untranslated/')
self.assertEqual(grandchild2.url_path_en, '/root-untranslated/child-untranslated/grandchild2-untranslated/')
grandgrandchild_translated = models.TestSlugPage1.objects.get(slug_de='grandgrandchild1-translated')
self.assertEqual(grandgrandchild_translated.url_path_de,
'/root-untranslated/child2-translated/grandchild1-translated/grandgrandchild1-translated/')
self.assertEqual(grandgrandchild_translated.url_path_en,
'/root-untranslated/child2-translated-en/grandchild1-translated-en/grandgrandchild1-translated-en/')

View file

@ -1,45 +1,70 @@
# coding: utf-8
from modeltranslation.translator import translator, register, TranslationOptions
from wagtail_modeltranslation.tests.models import TestRootPage, TestSlugPage1, TestSlugPage2, PatchTestPage, \
PatchTestSnippet, FieldPanelPage, ImageChooserPanelPage, FieldRowPanelPage, MultiFieldPanelPage, InlinePanelPage, \
FieldPanelSnippet, ImageChooserPanelSnippet, FieldRowPanelSnippet, MultiFieldPanelSnippet, PageInlineModel, \
BaseInlineModel, StreamFieldPanelPage, StreamFieldPanelSnippet, SnippetInlineModel, InlinePanelSnippet
from wagtail_modeltranslation.translator import WagtailTranslationOptions
from modeltranslation.translator import (TranslationOptions, register,
translator)
from wagtail_modeltranslation.tests.models import (BaseInlineModel,
FieldPanelPage,
FieldPanelSnippet,
FieldRowPanelPage,
FieldRowPanelSnippet,
ImageChooserPanelPage,
ImageChooserPanelSnippet,
InlinePanelPage,
InlinePanelSnippet,
MultiFieldPanelPage,
MultiFieldPanelSnippet,
PageInlineModel,
PatchTestPage,
PatchTestSnippet,
PatchTestSnippetNoPanels,
RoutablePageTest,
SnippetInlineModel,
StreamFieldPanelPage,
StreamFieldPanelSnippet,
TestRootPage, TestSlugPage1,
TestSlugPage1Subclass,
TestSlugPage2)
# Wagtail Models
@register(TestRootPage)
class TestRootPagePageTranslationOptions(WagtailTranslationOptions):
class TestRootPagePageTranslationOptions(TranslationOptions):
fields = ()
@register(TestSlugPage1)
class TestSlugPage1TranslationOptions(WagtailTranslationOptions):
class TestSlugPage1TranslationOptions(TranslationOptions):
fields = ()
@register(TestSlugPage2)
class TestSlugPage2TranslationOptions(WagtailTranslationOptions):
class TestSlugPage2TranslationOptions(TranslationOptions):
fields = ()
@register(TestSlugPage1Subclass)
class TestSlugPage1SubclassTranslationOptions(TranslationOptions):
pass
@register(PatchTestPage)
class PatchTestPageTranslationOptions(WagtailTranslationOptions):
class PatchTestPageTranslationOptions(TranslationOptions):
fields = ('description',)
class PatchTestSnippetTranslationOptions(WagtailTranslationOptions):
@register(PatchTestSnippetNoPanels)
class PatchTestSnippetNoPanelsTranslationOptions(TranslationOptions):
fields = ('name',)
translator.register(PatchTestSnippet, PatchTestSnippetTranslationOptions)
@register(PatchTestSnippet)
class PatchTestSnippetTranslationOptions(TranslationOptions):
pass
# Panel Patching Models
class FieldPanelTranslationOptions(WagtailTranslationOptions):
class FieldPanelTranslationOptions(TranslationOptions):
fields = ('name',)
@ -47,7 +72,7 @@ translator.register(FieldPanelPage, FieldPanelTranslationOptions)
translator.register(FieldPanelSnippet, FieldPanelTranslationOptions)
class ImageChooserPanelTranslationOptions(WagtailTranslationOptions):
class ImageChooserPanelTranslationOptions(TranslationOptions):
fields = ('image',)
@ -55,7 +80,7 @@ translator.register(ImageChooserPanelPage, ImageChooserPanelTranslationOptions)
translator.register(ImageChooserPanelSnippet, ImageChooserPanelTranslationOptions)
class FieldRowPanelTranslationOptions(WagtailTranslationOptions):
class FieldRowPanelTranslationOptions(TranslationOptions):
fields = ('other_name',)
@ -63,7 +88,7 @@ translator.register(FieldRowPanelPage, FieldRowPanelTranslationOptions)
translator.register(FieldRowPanelSnippet, FieldRowPanelTranslationOptions)
class StreamFieldPanelTranslationOptions(WagtailTranslationOptions):
class StreamFieldPanelTranslationOptions(TranslationOptions):
fields = ('body',)
@ -71,7 +96,7 @@ translator.register(StreamFieldPanelPage, StreamFieldPanelTranslationOptions)
translator.register(StreamFieldPanelSnippet, StreamFieldPanelTranslationOptions)
class MultiFieldPanelTranslationOptions(WagtailTranslationOptions):
class MultiFieldPanelTranslationOptions(TranslationOptions):
fields = ()
@ -79,14 +104,14 @@ translator.register(MultiFieldPanelPage, MultiFieldPanelTranslationOptions)
translator.register(MultiFieldPanelSnippet, MultiFieldPanelTranslationOptions)
class InlinePanelTranslationOptions(WagtailTranslationOptions):
class InlinePanelTranslationOptions(TranslationOptions):
fields = ('field_name', 'image_chooser', 'fieldrow_name',)
translator.register(BaseInlineModel, InlinePanelTranslationOptions)
class InlinePanelTranslationOptions(WagtailTranslationOptions):
class InlinePanelTranslationOptions(TranslationOptions):
fields = ()
@ -95,8 +120,13 @@ translator.register(SnippetInlineModel, InlinePanelTranslationOptions)
@register(InlinePanelPage)
class InlinePanelModelTranslationOptions(WagtailTranslationOptions):
class InlinePanelModelTranslationOptions(TranslationOptions):
fields = ()
translator.register(InlinePanelSnippet, InlinePanelModelTranslationOptions)
@register(RoutablePageTest)
class RoutablePageTestTranslationOptions(TranslationOptions):
fields = ()

View file

@ -2,7 +2,10 @@
from django.conf.urls import include, url
from django.conf.urls.i18n import i18n_patterns
from django.views.i18n import set_language
from wagtail.wagtailcore import urls as wagtail_urls
try:
from wagtail.core import urls as wagtail_urls
except ImportError:
from wagtail.wagtailcore import urls as wagtail_urls
urlpatterns = [
url(r'^set_language/$', set_language, {},

View file

@ -0,0 +1,82 @@
class PageFactory(object):
def __init__(self, initial_path=0):
self.root_path = initial_path
@property
def path(self):
self.root_path += 1
return self.root_path
def create_page_tree(self, nodes=None):
"""
Creates a page tree with a dict of page nodes following the below structure:
{
'model': Page,
'args': [],
'kwargs': {'title': 'root',},
'children': {
'child': {
'model': Page,
'args': [],
'kwargs': {'title': 'child',},
'children': {},
},
},
},
:param nodes: representing a page tree
:return: site
"""
if not nodes:
return None
from .models import TestRootPage
try:
from wagtail.core.models import Site
except ImportError:
from wagtail.wagtailcore.models import Site
# add a top root node to mimic Wagtail's real behaviour
all_nodes = {
'model': TestRootPage,
'kwargs': {'title': 'Root', 'slug': 'root', },
'children': {
'site_root': nodes,
},
}
self.create_instance(all_nodes)
site_root_node = nodes['instance']
site = Site.objects.create(root_page=site_root_node, hostname='localhost', port=80, is_default_site=True)
return site
def create_instance(self, node, parent=None, order=None):
if parent:
path = "{}{}".format(parent.path, "%04d" % (order,))
depth = parent.depth + 1
else:
path = "%04d" % (self.path,)
depth = 1
args = node.get('args', [])
kwargs = node.get('kwargs', {})
kwargs['path'] = kwargs.get('path', path)
kwargs['depth'] = kwargs.get('depth', depth)
if parent:
node_page = parent.add_child(instance=node['model'](*args, **kwargs))
node_page.save()
else:
node_page = node['model'].objects.create(*args, **kwargs)
node_page.save_revision().publish()
node['instance'] = node_page
for n, child in enumerate(node.get('children', {}).values()):
self.create_instance(child, node_page, n+1)
return node_page
page_factory = PageFactory()

View file

@ -2,9 +2,23 @@
from modeltranslation.decorators import register
from modeltranslation.translator import TranslationOptions
from wagtail.wagtailcore.models import Page
from wagtail_modeltranslation import settings
try:
from wagtail.core.models import Page
except ImportError:
from wagtail.wagtailcore.models import Page
@register(Page)
class PageTR(TranslationOptions):
pass
fields = (
'title',
'seo_title',
'search_description',
)
if settings.TRANSLATE_SLUGS:
fields += (
'slug',
'url_path',
)

View file

@ -1,15 +0,0 @@
from modeltranslation.translator import TranslationOptions
class WagtailTranslationOptions(TranslationOptions):
def __init__(self, model):
from wagtail.wagtailcore.models import Page
if Page in model.__bases__:
self.fields += (
'title',
'slug',
'seo_title',
'search_description',
'url_path',)
super(WagtailTranslationOptions, self).__init__(model)

View file

@ -2,16 +2,35 @@
import json
from django.core.exceptions import PermissionDenied
from six import iteritems
from django.conf import settings
from django.conf.urls import url
from django.http import HttpResponse
from django.http import QueryDict
from django.utils.html import format_html, format_html_join, escape
from django.http import HttpResponse, QueryDict
from django.shortcuts import redirect, render
from django.templatetags.static import static
from django.utils.html import escape, format_html, format_html_join
from django.utils.translation import ugettext as _
from django.views.decorators.csrf import csrf_exempt
from six import iteritems
from wagtail.wagtailcore import hooks
from wagtail.wagtailcore.models import Page
from wagtail.wagtailcore.rich_text import PageLinkHandler
from wagtail_modeltranslation import settings as wmt_settings
from modeltranslation import settings as mt_settings
from .patch_wagtailadmin_forms import PatchedCopyForm
try:
from wagtail.core import hooks
from wagtail.core.models import Page
from wagtail.core.rich_text.pages import PageLinkHandler
from wagtail.core import __version__ as WAGTAIL_VERSION
from wagtail.admin import messages
from wagtail.admin.views.pages import get_valid_next_url_from_request
except ImportError:
from wagtail.wagtailcore import hooks
from wagtail.wagtailcore.models import Page
from wagtail.wagtailcore.rich_text import PageLinkHandler
from wagtail.wagtailcore import __version__ as WAGTAIL_VERSION
from wagtail.wagtailadmin import messages
from wagtail.wagtailadmin.views.pages import get_valid_next_url_from_request
@hooks.register('insert_editor_js')
@ -20,15 +39,25 @@ def translated_slugs():
'wagtail_modeltranslation/js/wagtail_translated_slugs.js',
]
js_includes = format_html_join('\n', '<script src="{0}{1}"></script>', (
(settings.STATIC_URL, filename) for filename in js_files)
)
js_includes = format_html_join('\n', '<script src="{0}"></script>', (
(static(filename),) for filename in js_files)
)
lang_codes = []
for lang in settings.LANGUAGES:
lang_codes.append("'%s'" % lang[0])
js_languages = "<script>var langs=[%s];</script>" % (", ".join(lang_codes))
js_languages = """
<script>
var langs=[{langs}];
var default_lang='{default_lang}';
var translate_slugs={translate_slugs};
</script>
""".format(
langs=", ".join(lang_codes),
default_lang=mt_settings.DEFAULT_LANGUAGE,
translate_slugs='true' if wmt_settings.TRANSLATE_SLUGS else 'false'
)
return format_html(js_languages) + js_includes
@ -54,13 +83,13 @@ def return_translation_target_field_rendered_html(request, page_id):
# Patch field prefixes from origin field to target field
target_field_patched = []
for item in origin_field_serialized:
patched_item = None
patched_item = {'name': None, 'value': None}
for att in iteritems(item):
target_value = att[1]
if att[0] == 'name':
target_value = att[1].replace(
origin_field_name, target_field_name)
patched_item = {"name": target_value}
patched_item["name"] = target_value
else:
patched_item["value"] = att[1]
@ -102,21 +131,26 @@ def streamfields_translation_copy():
# includes the javascript file in the html file
js_files = [
'wagtail_modeltranslation/js/version_compare.js',
'wagtail_modeltranslation/js/copy_stream_fields.js',
]
js_includes = format_html_join('\n', '<script src="{0}{1}"></script>', (
(settings.STATIC_URL, filename) for filename in js_files)
)
js_includes = format_html_join('\n', '<script src="{0}"></script>', (
(static(filename),) for filename in js_files)
)
return js_includes
js_wagtail_version = """
<script>
var WAGTAIL_VERSION='{wagtail_version}';
</script>
""".format(wagtail_version=WAGTAIL_VERSION)
return format_html(js_wagtail_version) + js_includes
@hooks.register('insert_editor_css')
def modeltranslation_page_editor_css():
return format_html('<link rel="stylesheet" href="'
+ settings.STATIC_URL
+ 'wagtail_modeltranslation/css/page_editor_modeltranslation.css" >')
filename = 'wagtail_modeltranslation/css/page_editor_modeltranslation.css'
return format_html('<link rel="stylesheet" href="{}" >'.format(static(filename)))
@hooks.register('register_rich_text_link_handler')
@ -142,3 +176,70 @@ def register_localized_page_link_handler():
return "<a>"
return ('page', LocalizedPageLinkHandler)
@hooks.register('before_copy_page')
def before_copy_page(request, page):
parent_page = page.get_parent()
can_publish = parent_page.permissions_for_user(request.user).can_publish_subpage()
form = PatchedCopyForm(request.POST or None, user=request.user, page=page, can_publish=can_publish)
next_url = get_valid_next_url_from_request(request)
if request.method == 'POST':
# Prefill parent_page in case the form is invalid (as prepopulated value for the form field,
# because ModelChoiceField seems to not fall back to the user given value)
parent_page = Page.objects.get(id=request.POST['new_parent_page'])
if form.is_valid():
# Receive the parent page (this should never be empty)
if form.cleaned_data['new_parent_page']:
parent_page = form.cleaned_data['new_parent_page']
if not page.permissions_for_user(request.user).can_copy_to(parent_page,
form.cleaned_data.get('copy_subpages')):
raise PermissionDenied
# Re-check if the user has permission to publish subpages on the new parent
can_publish = parent_page.permissions_for_user(request.user).can_publish_subpage()
update_attrs = {}
for code, name in settings.LANGUAGES:
slug = "slug_{}".format(code)
title = "title_{}".format(code)
update_attrs[slug] = form.cleaned_data["new_{}".format(slug)]
update_attrs[title] = form.cleaned_data["new_{}".format(title)]
# Copy the page
new_page = page.copy(
recursive=form.cleaned_data.get('copy_subpages'),
to=parent_page,
update_attrs=update_attrs,
keep_live=(can_publish and form.cleaned_data.get('publish_copies')),
user=request.user,
)
# Give a success message back to the user
if form.cleaned_data.get('copy_subpages'):
messages.success(
request,
_("Page '{0}' and {1} subpages copied.").format(
page.get_admin_display_title(), new_page.get_descendants().count())
)
else:
messages.success(request, _("Page '{0}' copied.").format(page.get_admin_display_title()))
for fn in hooks.get_hooks('after_copy_page'):
result = fn(request, page, new_page)
if hasattr(result, 'status_code'):
return result
# Redirect to explore of parent page
if next_url:
return redirect(next_url)
return redirect('wagtailadmin_explore', parent_page.id)
return render(request, 'modeltranslation_copy.html', {
'page': page,
'form': form,
'next': next_url
})