wagtail-modeltranslation/wagtail_modeltranslation/models.py
Rui Manuel da Silva Martins 383348e00b New version v0.2:
- Support for StreamField;
- Independant from django-modeltranslation (code merged);
2015-08-04 17:42:43 +01:00

421 lines
No EOL
15 KiB
Python
Executable file

# -*- coding: utf-8 -*-
import django
import copy
from django.conf import settings
from django.http import Http404
from django.db.models import Q
from wagtail.wagtailcore.models import Page
from wagtail.wagtailadmin.edit_handlers import FieldPanel,\
MultiFieldPanel, FieldRowPanel
from wagtail.wagtailadmin.views.pages import get_page_edit_handler,\
PAGE_EDIT_HANDLERS
from wagtail.wagtailsnippets.views.snippets import get_snippet_edit_handler,\
SNIPPET_EDIT_HANDLERS
from wagtail.wagtailcore.url_routing import RouteResult
from wagtail.wagtailadmin.edit_handlers import StreamFieldPanel
from wagtail_modeltranslation.translator import translator, NotRegistered
def autodiscover():
"""
Auto-discover INSTALLED_APPS translation.py modules and fail silently when
not present. This forces an import on them to register.
Also import explicit modules.
"""
import os
import sys
import copy
from django.conf import settings
from django.utils.module_loading import module_has_submodule
from wagtail_modeltranslation.translator import translator
from wagtail_modeltranslation.settings import TRANSLATION_FILES, DEBUG
if django.VERSION < (1, 7):
from django.utils.importlib import import_module
mods = [(app, import_module(app)) for app in settings.INSTALLED_APPS]
else:
from importlib import import_module
from django.apps import apps
mods = [(app_config.name, app_config.module) for app_config in apps.get_app_configs()]
for (app, mod) in mods:
# Attempt to import the app's translation module.
module = '%s.translation' % app
before_import_registry = copy.copy(translator._registry)
try:
import_module(module)
except:
# Reset the model registry to the state before the last import as
# this import will have to reoccur on the next request and this
# could raise NotRegistered and AlreadyRegistered exceptions
translator._registry = before_import_registry
# Decide whether to bubble up this error. If the app just
# doesn't have an translation module, we can ignore the error
# attempting to import it, otherwise we want it to bubble up.
if module_has_submodule(mod, 'translation'):
raise
for module in TRANSLATION_FILES:
import_module(module)
# In debug mode, print a list of registered models and pid to stdout.
# Note: Differing model order is fine, we don't rely on a particular
# order, as far as base classes are registered before subclasses.
if DEBUG:
try:
if sys.argv[1] in ('runserver', 'runserver_plus'):
models = translator.get_registered_models()
names = ', '.join(m.__name__ for m in models)
print('wagtail_modeltranslation: Registered %d models for translation'
' (%s) [pid: %d].' % (len(models), names, os.getpid()))
except IndexError:
pass
def handle_translation_registrations(*args, **kwargs):
"""
Ensures that any configuration of the TranslationOption(s) are handled when
importing wagtail_modeltranslation.
This makes it possible for scripts/management commands that affect models
but know nothing of wagtail_modeltranslation.
"""
from wagtail_modeltranslation.settings import ENABLE_REGISTRATIONS
if not ENABLE_REGISTRATIONS:
# If the user really wants to disable this, they can, possibly at their
# own expense. This is generally only required in cases where other
# apps generate import errors and requires extra work on the user's
# part to make things work.
return
# Trigger autodiscover, causing any TranslationOption initialization
# code to execute.
autodiscover()
if django.VERSION < (1, 7):
handle_translation_registrations()
# WAGTAIL MODELTRANSLATION MIXIN
####################################
class TranslationMixin(object):
_translation_options = None
_wgform_class = None
_translated = False
_required_fields = []
def __init__(self, *args, **kwargs):
super(TranslationMixin, self).__init__(*args, **kwargs)
TranslationMixin._translation_options = translator.\
get_options_for_model(
self.__class__)
if self.__class__._translated:
return
# CONSTRUCT TEMPORARY EDIT HANDLER
if issubclass(self.__class__, Page):
edit_handler_class = get_page_edit_handler(self.__class__)
else:
edit_handler_class = get_snippet_edit_handler(self.__class__)
TranslationMixin._wgform_class = edit_handler_class.get_form_class(
self.__class__)
defined_tabs = TranslationMixin._fetch_defined_tabs(self.__class__)
for tab_name, tab in defined_tabs:
patched_tab = []
for panel in tab:
trtab = TranslationMixin._patch_panel(self, panel)
if trtab:
for x in trtab:
patched_tab.append(x)
setattr(self.__class__, tab_name, patched_tab)
# DELETE TEMPORARY EDIT HANDLER IN ORDER TO LET WAGTAIL RECONSTRUCT
# NEW EDIT HANDLER BASED ON NEW TRANSLATION PANELS
if issubclass(self.__class__, Page):
if self.__class__ in PAGE_EDIT_HANDLERS:
del PAGE_EDIT_HANDLERS[self.__class__]
edit_handler_class = get_page_edit_handler(self.__class__)
else:
if self.__class__ in SNIPPET_EDIT_HANDLERS:
del SNIPPET_EDIT_HANDLERS[self.__class__]
edit_handler_class = get_snippet_edit_handler(self.__class__)
form = edit_handler_class.get_form_class(self.__class__)
for fname, f in form.base_fields.items():
# set field required on formset level if original field is required
# as well
if fname in self._required_fields:
f.required = True
if fname in TranslationMixin._translation_options.fields and TranslationMixin._is_orig_required(fname):
f.required = False
self.__class__._translated = True
@staticmethod
def _fetch_defined_tabs(defined_class):
"""
Fetch tabs defined by user in models.py
"""
tabs = ()
# If user has defined panels dict on models.py
if hasattr(defined_class, 'panels'):
# TEST !!!
tabs += (('panels',
copy.deepcopy(defined_class.panels)),)
# Check for common tabs
else:
if hasattr(defined_class, 'content_panels'):
tabs += (('content_panels',
copy.deepcopy(defined_class.content_panels)),)
if hasattr(defined_class, 'promote_panels'):
tabs += (('promote_panels',
copy.deepcopy(defined_class.promote_panels)),)
if hasattr(defined_class, 'settings_panels'):
tabs += (('settings_panels',
copy.deepcopy(defined_class.settings_panels)),)
return tabs
@staticmethod
def _patch_panel(instance, panel, inline_tr_options=None):
"""
Generic panel patching function
"""
trpanels = None
if panel.__class__.__name__ == 'FieldPanel':
trpanels = TranslationMixin._patch_fieldpanel(
panel, inline_tr_options)
elif panel.__class__.__name__ == 'MultiFieldPanel':
trpanels = [TranslationMixin._patch_multifieldpanel(
panel, inline_tr_options)]
elif panel.__class__.__name__ == 'InlinePanel':
TranslationMixin._patch_inlinepanel(instance, panel)
trpanels = [panel]
elif panel.__class__.__name__ == 'StreamFieldPanel':
trpanels = TranslationMixin._patch_streamfieldpanel(panel)
else:
trpanels = [panel]
return trpanels
@classmethod
def _is_orig_required(cls, field_name, formset=None):
"""
check if original field is required
TODO:
if formset is given, example for inline models.
"""
required = False
if not formset:
for fname, f in cls._wgform_class.base_fields.items():
if fname == field_name:
if f.required:
required = True
break
return required
# FieldPanel
####################################
@classmethod
def _patch_fieldpanel(cls, fieldpanel, inline_tr_options=None):
"""
Patch FieldPanels and return one per available language
"""
tr_fields = []
if inline_tr_options:
tr_fields = inline_tr_options
else:
tr_fields = cls._translation_options.fields
translated_fieldpanels = []
if fieldpanel.field_name in tr_fields:
for lang in settings.LANGUAGES:
classes = fieldpanel.classname
if cls._is_orig_required(fieldpanel.field_name) and\
(lang[0] == settings.LANGUAGE_CODE):
if ("%s_%s" % (fieldpanel.field_name, lang[0]) not in cls._required_fields):
cls._required_fields.append("%s_%s" % (
fieldpanel.field_name, lang[0]))
translated_field_name = "%s_%s" % (
fieldpanel.field_name, lang[0])
translated_fieldpanels.append(
FieldPanel(
translated_field_name,
classname=classes))
# delete original field from form
if fieldpanel.field_name in cls._wgform_class._meta.fields:
cls._wgform_class._meta.fields.remove(fieldpanel.field_name)
else:
return [fieldpanel]
return translated_fieldpanels
# StreamFieldPanel
####################################
@classmethod
def _patch_streamfieldpanel(cls, fieldpanel, inline_tr_options=None):
"""
Patch StreamFieldPanels and return one per available language
"""
tr_fields = []
if inline_tr_options:
tr_fields = inline_tr_options
else:
tr_fields = cls._translation_options.fields
translated_fieldpanels = []
if fieldpanel.field_name in tr_fields:
for lang in settings.LANGUAGES:
if cls._is_orig_required(fieldpanel.field_name) and\
(lang[0] == settings.LANGUAGE_CODE):
if ("%s_%s" % (fieldpanel.field_name, lang[0]) not in cls._required_fields):
cls._required_fields.append("%s_%s" % (
fieldpanel.field_name, lang[0]))
translated_field_name = "%s_%s" % (
fieldpanel.field_name, lang[0])
translated_fieldpanels.append(
StreamFieldPanel(
translated_field_name))
# delete original field from form
if fieldpanel.field_name in cls._wgform_class._meta.fields:
cls._wgform_class._meta.fields.remove(fieldpanel.field_name)
else:
return [fieldpanel]
return translated_fieldpanels
@classmethod
def _patch_multifieldpanel(cls, mfpanel, inline_tr_options=None):
"""
Patch MultiFieldPanel
"""
patched_fields = []
for panel in mfpanel.children:
if panel.__class__.__name__ == 'FieldPanel':
for item in cls._patch_fieldpanel(panel, inline_tr_options):
patched_fields.append(item)
elif panel.__class__.__name__ == 'FieldRowPanel':
patched_fields.append(
cls._patch_fieldrowpanel(panel, inline_tr_options))
else:
patched_fields.append(panel)
return MultiFieldPanel(
patched_fields,
classname=mfpanel.classname,
heading=mfpanel.heading)
@classmethod
def _patch_fieldrowpanel(cls, frpanel, inline_tr_options=None):
"""
Patch FieldRowPanel
"""
patched_fields = []
for panel in frpanel.children:
if panel.__class__.__name__ == 'FieldPanel':
for item in cls._patch_fieldpanel(panel, inline_tr_options):
patched_fields.append(item)
else:
patched_fields.append(panel)
return FieldRowPanel(
patched_fields,
classname=frpanel.classname)
@classmethod
def _patch_inlinepanel(cls, instance, panel):
inline_panels = getattr(
instance.__class__, panel.relation_name).related.model.panels
try:
inline_model_tr_fields = translator.get_options_for_model(
getattr(
instance.__class__, panel.relation_name).related.model).fields
except NotRegistered:
return None
translated_inline = []
for inlinepanel in inline_panels:
for item in cls._patch_fieldpanel(inlinepanel, inline_model_tr_fields):
translated_inline.append(item)
getattr(instance.__class__, panel.relation_name).related.model.panels = translated_inline
def set_url_path(self, parent):
"""
This method override populates url_path for each specified language.
This way we can get different urls for each language, defined
by page slug.
"""
for lang in settings.LANGUAGES:
if parent:
tr_slug = getattr(self, 'slug_'+lang[0])
if not tr_slug:
tr_slug = getattr(self, 'slug_'+settings.LANGUAGE_CODE)
parent_url_path = getattr(parent, 'url_path_'+lang[0])
if not parent_url_path:
parent_url_path = getattr(parent, 'url_path')
setattr(self, 'url_path_'+lang[0], parent_url_path + tr_slug + '/')
else:
# a page without a parent is the tree root,
# which always has a url_path of '/'
setattr(self, 'url_path_'+lang[0], '/')
return self.url_path
def route(self, request, path_components):
"""
Rewrite route method in order to handle languages fallbacks
"""
if path_components:
# request is for a child of this page
child_slug = path_components[0]
remaining_components = path_components[1:]
try:
q = Q()
for lang in settings.LANGUAGES:
tr_field_name = 'slug_%s' % (lang[0])
condition = {tr_field_name: child_slug}
q |= Q(**condition)
subpage = self.get_children().get(q)
except Page.DoesNotExist:
raise Http404
return subpage.specific.route(request, remaining_components)
else:
# request is for this very page
if self.live:
return RouteResult(self)
else:
raise Http404