Repository: django-cms/django-cms

Issue:
Adding a plugin triggers bad request (HTTP 400), showing `<django.utils.functional.__proxy__ object at 0x1eed4d0>` error dialog
When trying to add a plugin to a placeholder of a page, a browser error dialog pops up with the message:
    `<django.utils.functional.__proxy__ object at 0x1eed4d0>`

The browser console shows a bad HTTP POST request (400) to `/admin/cms/page/4/add-plugin/`

The corresponding JavaScript backtrace of the browser console triggering this request is:

```
POST http://mydomain/admin/cms/page/4/add-plugin/ 400 (BAD REQUEST) jquery.min.js:130
c.extend.ajax jquery.min.js:130
(anonymous function) plugin_editor.js:30
c.event.handle jquery.min.js:55
j.handle.o jquery.min.js:49
```

Some probably related settings:

```
USE_I18N = False
LANGUAGE_CODE = 'en-us'
LANGUAGES = (
    ('en', 'English'),
    ('de', 'German'),
)
MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'cms.middleware.page.CurrentPageMiddleware',
    'cms.middleware.user.CurrentUserMiddleware',
    'cms.middleware.toolbar.ToolbarMiddleware',
)
```

There are no non-ASCII values in the config (or somewhere else) which could trigger lazy translation related issues.

No errors are logged by this bad POST request on the server side (Apache's error_log).

Using:
python-2.7
django-1.4.5
django-cms-2.4.1



Discussion:
The error is triggered by `return HttpResponseBadRequest(_("Language must be set to a supported language!"))` in cms/admin/pageadmin.py:1149.

I wonder why it returns the string `<django.utils.functional.__proxy__ object at 0x1eed4d0>` instead of `Language must be set to a supported language!`.

Does anyone have an idea?

Removing the wrapping `_()` from line 1149 makes it work as expected - returning the error string `Language must be set to a supported language!`.

I wonder whether this is a bug of django.utils.functional doing some weird lazy translation stuff with this string and then fails.

I can clearly reproduce this issue now on the django shell:

```
>>> from django.utils.translation import ugettext_lazy as _
>>> print _("Language must be set to a supported language!")
<django.utils.functional.__proxy__ object at 0x171af10>
```

It should use `ugettext` instead of `ugettext_lazy` in that line.

On a sidenote, in case you haven't found your initial issue, you have `LANGUAGE_CODE` `'en-us'` but `'en'` in `LANGUAGES`.

According to the comments in `settings.py`, `LANGUAGE_CODE = 'en-us'` should be correct:

```
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
```

While the [django-cms documentation](http://docs.django-cms.org/en/2.4.0/getting_started/tutorial.html#installing-and-configuring-django-cms-in-your-django-project) suggests:

> The django CMS allows you to edit all languages for which Django has built in translations. Since these are numerous, we’ll limit it to English for now:

```
LANGUAGES = [
    ('en', 'English'),
]
```

Correcting the configuration to use the full ISO language code for both (`LANGUAGE_CODE` and `LANGUAGES`) works around this issue, but only because the if-branch of line **1149** isn't entered anymore.

So a proper solution to this issue is still needed and is very likely to use `ugettext` instead of `ugettext_lazy` at this point but as I'm rather new to Django/django-cms I can't tell yet how I'd properly use it at this point (is there a shortcut like `_()` for `ugettext` too?).

Correct, my suggestion above was to fix your setup in the meantime with a hotfix. This still needs to be fixed in the CMS.

Ok, so what still confuses me is that there seem to be different ideas which kind of language code should be used:

[django documentation, settings → LANGUAGES](https://django.readthedocs.org/en/latest/ref/settings.html#std:setting-LANGUAGES):

> The list is a tuple of two-tuples in the format ([language code](https://django.readthedocs.org/en/latest/topics/i18n/index.html#term-language-code), _language name_) – for example, (_'ja'_, _'Japanese'_). This specifies which languages are available for language selection. See [Internationalization and localization](https://django.readthedocs.org/en/latest/topics/i18n/index.html).

This collides with the comment in generated `settings.py` which suggests something different:

```
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
```

The URL references ISO style language codes (`en-us`, `de-de`, etc.).
Some quick skimming of the documentation suggests, that `en` style language codes are to be used and it's just the comment in the generated `settings.py` which is outdated.
Does anyone know which one is correct?

First of all, that list linked is not an ISO list, it's a mix between ISO 639 and RFC 4646 (which is good, since that's basically how browsers/HTTP work).

Technicalities aside, both are correct.

However `en` and `en-us` are **not the same**. To make this more clear, `en-us` and `en-gb` are not the same (Color vs colour).

But to your question which one should be used: Always use the more generic (`en`) one unless you really know why you're not. Django will happily fall back `en-us` users to `en`. 

I'm getting the same cryptic error: `<django.utils.functional.__proxy__ object at 0x7ff4645d0250>`, but the issue appears to be related to permissions, not languages.  This is invoked when a non-superuser staff attempts to add a CMS Plugin.  A Superuser can add it on the identical page and the same language without issue.

Any work-arounds for this (except for assigning super-user to all users)?  I can reproduce this at will, if I can help.


Resolving patch:
diff --git a/cms/admin/pageadmin.py b/cms/admin/pageadmin.py
--- a/cms/admin/pageadmin.py
+++ b/cms/admin/pageadmin.py
@@ -597,7 +597,7 @@ def changelist_view(self, request, extra_context=None):
         opts = self.model._meta
         app_label = opts.app_label
         if not self.has_change_permission(request, None):
-            return HttpResponseForbidden(_("You do not have permission to change pages."))
+            return HttpResponseForbidden(force_unicode(_("You do not have permission to change pages.")))
         try:
             cl = CMSChangeList(request, self.model, self.list_display, self.list_display_links, self.list_filter,
                                self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page,
@@ -705,7 +705,7 @@ def undo(self, request, object_id):
         if not page.publisher_is_draft:
             page = page.publisher_draft
         if not page.has_change_permission(request):
-            return HttpResponseForbidden(_("You do not have permission to change this page"))
+            return HttpResponseForbidden(force_unicode(_("You do not have permission to change this page")))
         versions = reversion.get_for_object(page)
         if page.revision_id:
             current_revision = Revision.objects.get(pk=page.revision_id)
@@ -746,7 +746,7 @@ def redo(self, request, object_id):
         if not page.publisher_is_draft:
             page = page.publisher_draft
         if not page.has_change_permission(request):
-            return HttpResponseForbidden(_("You do not have permission to change this page"))
+            return HttpResponseForbidden(force_unicode(_("You do not have permission to change this page")))
         versions = reversion.get_for_object(page)
         if page.revision_id:
             current_revision = Revision.objects.get(pk=page.revision_id)
@@ -781,18 +781,18 @@ def redo(self, request, object_id):
     def change_template(self, request, object_id):
         page = get_object_or_404(Page, pk=object_id)
         if not page.has_change_permission(request):
-            return HttpResponseForbidden(_("You do not have permission to change the template"))
+            return HttpResponseForbidden(force_unicode(_("You do not have permission to change the template")))
 
         to_template = request.POST.get("template", None)
         if to_template not in dict(get_cms_setting('TEMPLATES')):
-            return HttpResponseBadRequest(_("Template not valid"))
+            return HttpResponseBadRequest(force_unicode(_("Template not valid")))
 
         page.template = to_template
         page.save()
         if "reversion" in settings.INSTALLED_APPS:
             message = _("Template changed to %s") % dict(get_cms_setting('TEMPLATES'))[to_template]
             helpers.make_revision_with_plugins(page, request.user, message)
-        return HttpResponse(_("The template was successfully changed"))
+        return HttpResponse(force_unicode(_("The template was successfully changed")))
 
     @transaction.commit_on_success
     def move_page(self, request, page_id, extra_context=None):
@@ -814,7 +814,7 @@ def move_page(self, request, page_id, extra_context=None):
         if not page.has_move_page_permission(request) or \
                 not target.has_add_permission(request):
             return jsonify_request(
-                HttpResponseForbidden(_("Error! You don't have permissions to move this page. Please reload the page")))
+                HttpResponseForbidden(force_unicode(_("Error! You don't have permissions to move this page. Please reload the page"))))
             # move page
         page.move_page(target, position)
         if "reversion" in settings.INSTALLED_APPS:
@@ -864,12 +864,12 @@ def copy_language(self, request, page_id):
             placeholders = page.placeholders.all()
 
             if not target_language or not target_language in get_language_list():
-                return HttpResponseBadRequest(_("Language must be set to a supported language!"))
+                return HttpResponseBadRequest(force_unicode(_("Language must be set to a supported language!")))
             for placeholder in placeholders:
                 plugins = list(
                     placeholder.cmsplugin_set.filter(language=source_language).order_by('tree_id', 'level', 'position'))
                 if not self.has_copy_plugin_permission(request, placeholder, placeholder, plugins):
-                    return HttpResponseForbidden(_('You do not have permission to copy these plugins.'))
+                    return HttpResponseForbidden(force_unicode(_('You do not have permission to copy these plugins.')))
                 copy_plugins.copy_plugins_to(plugins, placeholder, target_language)
             if page and "reversion" in settings.INSTALLED_APPS:
                 message = _(u"Copied plugins from %(source_language)s to %(target_language)s") % {
@@ -924,7 +924,7 @@ def publish_page(self, request, page_id, language):
         all_published = True
         if page:
             if not page.has_publish_permission(request):
-                return HttpResponseForbidden(_("You do not have permission to publish this page"))
+                return HttpResponseForbidden(force_unicode(_("You do not have permission to publish this page")))
             published = page.publish(language)
             if not published:
                 all_published = False
@@ -999,9 +999,9 @@ def unpublish(self, request, page_id, language):
         site = Site.objects.get_current()
         page = get_object_or_404(Page, pk=page_id)
         if not page.has_publish_permission(request):
-            return HttpResponseForbidden(_("You do not have permission to unpublish this page"))
+            return HttpResponseForbidden(force_unicode(_("You do not have permission to unpublish this page")))
         if not page.publisher_public_id:
-            return HttpResponseForbidden(_("This page was never published"))
+            return HttpResponseForbidden(force_unicode(_("This page was never published")))
         try:
             page.unpublish(language)
             message = _('The %(language)s page "%(page)s" was successfully unpublished') % {
@@ -1030,7 +1030,7 @@ def revert_page(self, request, page_id, language):
         page = get_object_or_404(Page, id=page_id)
         # ensure user has permissions to publish this page
         if not page.has_change_permission(request):
-            return HttpResponseForbidden(_("You do not have permission to change this page"))
+            return HttpResponseForbidden(force_unicode(_("You do not have permission to change this page")))
 
         page.revert(language)
 
@@ -1066,7 +1066,7 @@ def delete_translation(self, request, object_id, extra_context=None):
             obj = None
 
         if not self.has_delete_permission(request, obj):
-            return HttpResponseForbidden(_("You do not have permission to change this page"))
+            return HttpResponseForbidden(force_unicode(_("You do not have permission to change this page")))
 
         if obj is None:
             raise Http404(
@@ -1170,7 +1170,7 @@ def change_innavigation(self, request, page_id):
             page.in_navigation = not page.in_navigation
             page.save()
             return admin_utils.render_admin_menu_item(request, page)
-        return HttpResponseForbidden(_("You do not have permission to change this page's in_navigation status"))
+        return HttpResponseForbidden(force_unicode(_("You do not have permission to change this page's in_navigation status")))
 
     def descendants(self, request, page_id):
         """
@@ -1203,7 +1203,7 @@ def edit_title_fields(self, request, page_id, language):
 
         if not has_generic_permission(title.page.pk, request.user, "change",
                                       title.page.site.pk):
-            return HttpResponseForbidden(_("You do not have permission to edit this page"))
+            return HttpResponseForbidden(force_unicode(_("You do not have permission to edit this page")))
 
         class PageTitleForm(django.forms.ModelForm):
             """
diff --git a/cms/admin/placeholderadmin.py b/cms/admin/placeholderadmin.py
--- a/cms/admin/placeholderadmin.py
+++ b/cms/admin/placeholderadmin.py
@@ -69,9 +69,9 @@ def edit_field(self, request, object_id, language):
         raw_fields = request.GET.get("edit_fields")
         fields = [field for field in raw_fields.split(",") if field in self.frontend_editable_fields]
         if not fields:
-            return HttpResponseBadRequest(_("Fields %s not editabled in the frontend") % raw_fields)
+            return HttpResponseBadRequest(force_unicode(_("Fields %s not editabled in the frontend")) % raw_fields)
         if not request.user.has_perm("%s_change" % self.model._meta.module_name):
-            return HttpResponseForbidden(_("You do not have permission to edit this item"))
+            return HttpResponseForbidden(force_unicode(_("You do not have permission to edit this item")))
             # Dinamically creates the form class with only `field_name` field
         # enabled
         form_class = self.get_form(request, obj, fields=fields)
@@ -218,7 +218,7 @@ def add_plugin(self, request):
             parent_id = request.POST.get('plugin_parent', None)
         placeholder = get_object_or_404(Placeholder, pk=placeholder_id)
         if not self.has_add_plugin_permission(request, placeholder, plugin_type):
-            return HttpResponseForbidden(_('You do not have permission to add a plugin'))
+            return HttpResponseForbidden(force_unicode(_('You do not have permission to add a plugin')))
         parent = None
         language = request.POST.get('plugin_language') or get_language_from_request(request)
         try:
@@ -242,9 +242,9 @@ def add_plugin(self, request):
         # Sanity check to make sure we're not getting bogus values from JavaScript:
         if settings.USE_I18N:
             if not language or not language in [lang[0] for lang in settings.LANGUAGES]:
-                return HttpResponseBadRequest(_("Language must be set to a supported language!"))
+                return HttpResponseBadRequest(force_unicode(_("Language must be set to a supported language!")))
             if parent and parent.language != language:
-                return HttpResponseBadRequest(_("Parent plugin language must be same as language!"))
+                return HttpResponseBadRequest(force_unicode(_("Parent plugin language must be same as language!")))
         else:
             language = settings.LANGUAGE_CODE
         plugin = CMSPlugin(language=language, plugin_type=plugin_type, position=position, placeholder=placeholder)
@@ -288,7 +288,7 @@ def copy_plugins(self, request):
         source_placeholder = get_object_or_404(Placeholder, pk=source_placeholder_id)
         target_placeholder = get_object_or_404(Placeholder, pk=target_placeholder_id)
         if not target_language or not target_language in get_language_list():
-            return HttpResponseBadRequest(_("Language must be set to a supported language!"))
+            return HttpResponseBadRequest(force_unicode(_("Language must be set to a supported language!")))
         if source_plugin_id:
             source_plugin = get_object_or_404(CMSPlugin, pk=source_plugin_id)
             reload_required = requires_reload(PLUGIN_COPY_ACTION, [source_plugin])
@@ -307,7 +307,7 @@ def copy_plugins(self, request):
                                                                                            'position'))
             reload_required = requires_reload(PLUGIN_COPY_ACTION, plugins)
         if not self.has_copy_plugin_permission(request, source_placeholder, target_placeholder, plugins):
-            return HttpResponseForbidden(_('You do not have permission to copy these plugins.'))
+            return HttpResponseForbidden(force_unicode(_('You do not have permission to copy these plugins.')))
         if target_placeholder.pk == request.toolbar.clipboard.pk and not source_plugin_id and not target_plugin_id:
             # if we copy a whole placeholder to the clipboard create PlaceholderReference plugin instead and fill it
             # the content of the source_placeholder.
@@ -342,7 +342,7 @@ def edit_plugin(self, request, plugin_id):
 
         instance, plugin_admin = cms_plugin.get_plugin_instance(self.admin_site)
         if not self.has_change_plugin_permission(request, cms_plugin):
-            return HttpResponseForbidden(_("You do not have permission to edit this plugin"))
+            return HttpResponseForbidden(force_unicode(_("You do not have permission to edit this plugin")))
         plugin_admin.cms_plugin_instance = cms_plugin
         try:
             plugin_admin.placeholder = cms_plugin.placeholder
@@ -423,14 +423,14 @@ def move_plugin(self, request):
             parent_id = int(parent_id)
         order = request.POST.getlist("plugin_order[]")
         if not self.has_move_plugin_permission(request, plugin, placeholder):
-            return HttpResponseForbidden(_("You have no permission to move this plugin"))
+            return HttpResponseForbidden(force_unicode(_("You have no permission to move this plugin")))
         if plugin.parent_id != parent_id:
             if parent_id:
                 parent = CMSPlugin.objects.get(pk=parent_id)
                 if parent.placeholder_id != placeholder.pk:
-                    return HttpResponseBadRequest('parent must be in the same placeholder')
+                    return HttpResponseBadRequest(force_unicode('parent must be in the same placeholder'))
                 if parent.language != language:
-                    return HttpResponseBadRequest('parent must be in the same language as plugin_language')
+                    return HttpResponseBadRequest(force_unicode('parent must be in the same language as plugin_language'))
             else:
                 parent = None
             plugin.move_to(parent, position='last-child')
@@ -471,7 +471,7 @@ def move_plugin(self, request):
     def delete_plugin(self, request, plugin_id):
         plugin = get_object_or_404(CMSPlugin.objects.select_related('placeholder'), pk=plugin_id)
         if not self.has_delete_plugin_permission(request, plugin):
-            return HttpResponseForbidden(_("You do not have permission to delete this plugin"))
+            return HttpResponseForbidden(force_unicode(_("You do not have permission to delete this plugin")))
         plugin_cms_class = plugin.get_plugin_class()
         plugin_class = plugin_cms_class.model
         opts = plugin_class._meta
@@ -512,7 +512,7 @@ def delete_plugin(self, request, plugin_id):
     def clear_placeholder(self, request, placeholder_id):
         placeholder = get_object_or_404(Placeholder, pk=placeholder_id)
         if not self.has_clear_placeholder_permission(request, placeholder):
-            return HttpResponseForbidden(_("You do not have permission to clear this placeholder"))
+            return HttpResponseForbidden(force_unicode(_("You do not have permission to clear this placeholder")))
         language = request.GET.get('language', None)
         plugins = placeholder.get_plugins(language)
         opts = Placeholder._meta
@@ -523,7 +523,7 @@ def clear_placeholder(self, request, placeholder_id):
         obj_display = force_unicode(placeholder)
         if request.POST:  # The user has already confirmed the deletion.
             if perms_needed:
-                return HttpResponseForbidden(_("You do not have permission to clear this placeholder"))
+                return HttpResponseForbidden(force_unicode(_("You do not have permission to clear this placeholder")))
             self.log_deletion(request, placeholder, obj_display)
             for plugin in plugins:
                 plugin.delete()


Test patch: