I'm using Django CMS with Django Parler and have run into a problem that is driving me mad, so if anybody could help, it would be much appreciated!
So I'm creating a simple blog app that has the slug as a translatable field. Here is the model simplified:
from parler.models import TranslatableModel, TranslatedFields
class Article(TranslatableModel):
...
translations = TranslatedFields(
...
slug = models.SlugField(_('slug'), max_length=255, blank=True, allow_unicode=True),
meta = {'unique_together': (('language_code', 'slug'),)}
)
...
def get_absolute_url(self):
return reverse('blog:article_detail', kwargs={'slug': self.slug})
Here are the urls:
from django.conf.urls import include, url
from .views import ArticleDetailView
urlpatterns = [
...
url(r'^(?P<slug>\w[-\w]*)/$', ArticleDetailView.as_view(), name='article_detail'),
]
And finally here is the view:
from django.views.generic import DetailView
from parler.views import TranslatableSlugMixin
from .models import Article
class ArticleDetailView(TranslatableSlugMixin, DetailView):
model = Article
template_name = 'blog/_article.html'
I've created an article that is in English, French & German, with a different slug for each language, lets call those:
/en/blog/english-slug
/fr/blog/french-slug
/de/blog/german-slug
I can navigate to these all correctly, but in Django CMS you have the language menu at the top that on the English page shows the links as:
/en/blog/english-slug
/fr/blog/english-slug
/de/blog/english-slug
This is fine, as that's what the TranslatableSlugMixin in the view handles (see here http://django-parler.readthedocs.io/en/latest/api/parler.views.html).
So when I click one of the links (say the French one) the view correctly finds the correct article and redirects me to the correct url. So clicking:
/fr/blog/english-slug
Has taken me correctly to:
/fr/blog/french-slug
But here's where it's all going wrong. I now want to navigate back to the English page, which is showing as:
/en/blog/french-slug
But when I click the link it navigates to a 404. This is the same if I navigate to the German URL from the French one. However if I go from English to German straight away it works.
Sorry, I know this is confusing to explain but it seems the translation works one way from base/default to other language but doesn't work correctly when swapping between languages or back to the base/default.
Surely TranslatableSlugMixin is designed to allow this to happen?! So am I missing something here?
Any help would be much appreciated. Happy to provide more info if necessary.
Thanks
Ok so I've figured out how to make this work and it turns out it's a combination of things...
Using the default Django CMS chooser was a mistake:
{% language_chooser "menu/language_chooser.html" %}
This leads to the URLs I described above:
/en/blog/english-slug
/fr/blog/english-slug
/de/blog/english-slug
Reading the Django Parler docs led me to using their language navigation menu:
{% for lang_code, title in LANGUAGES %}
{% get_language_info for lang_code as lang %}
{% get_translated_url lang_code as tr_url %}
{% if tr_url %}<li{% if lang_code == LANGUAGE_CODE %} class="is-selected"{% endif %}><a href="{{ tr_url }}" hreflang="{{ lang_code }}">{{ lang.name_local|capfirst }}</a></li>{% endif %}
{% endfor %}
This leads to the urls pointing to the correct location:
/en/blog/english-slug
/fr/blog/french-slug
/de/blog/german-slug
For the Django Parler navigation to work I needed to update the get_absolute_url() in the model to handle different languages. I did that as follows:
from django.utils.translation import get_language
from parler.models import TranslatableModel, TranslatedFields
class Article(TranslatableModel):
...
def get_absolute_url(self):
language = get_language()
if self.has_translation(language):
slug = self.safe_translation_getter('slug', language_code=language)
return reverse('blog:article_detail', kwargs={'slug': slug})
# no translation so fallback to all article list
return reverse('blog:article_list')
Phew! That was a headache! Hopefully this helps somebody else in the future!
P.S. During my research I came across this blog app that seems really great:
https://github.com/nephila/djangocms-blog
It helped me get to the bottom of this nightmare!
Looking at the get_absolute_url() in djangocms-blog (link above), they have a far better solution to the problem. Their implementation is:
from django.utils.translation import get_language
from parler.models import TranslatableModel, TranslatedFields
class Article(TranslatableModel):
...
def get_absolute_url(self, lang=None):
if not lang or lang not in self.get_available_languages():
lang = self.get_current_language()
if not lang or lang not in self.get_available_languages():
lang = get_language()
with switch_language(self, lang):
slug = self.safe_translation_getter('slug', language_code=lang, any_language=True)
return reverse('blog:article_detail', kwargs={'slug': slug})
Thanks nephila, this has saved me from a lot of cursing and frustration :)