I am trying to create a multilingual website, which would get content from DB based on selected language. For that I use i18n_patterns and so I can get the selected language easily using get_language() in the view.
Right now, my models look like this (simplified):
class Article(models.Model):
author = models.CharField("Author", max_length=255)
class ArticleText(models.Model):
id = models.OneToOneField(Article, on_delete=models.CASCADE, primary_key=True)
en = models.TextField("English")
fr = models.TextField("French")
My idea (based on research here on SO) was that I would store the article's text content elsewhere, linking it's primary key to the actual Article object's primary key.
The Article model contains just general info (publish date, author, etc.), which is the same for all languages. However, I am not really sure how to access the data from my template. Right now I use:
context["articles"] = models.Content.objects.select_related("id")
I can then access the various fields in my template using
{% for article in articles %}
<h3>{{ article.author }} - {{ article.fr }}</h3>
{% endfor %}
However, I want the language ({{ article.$LANGUAGE }}) to change based on the selected language. Something like
{{ article.$get_current_language }} --> {{ article.fr }}
I guess I could do the following in the template:
{% if get_current_language == en %}
{{ article.en }}
{% elif get_current_language == fr %}
{{ article.fr }}
{% endif %}
But I think it's ugly to do this in the template - I'd rather do this in the view and just pass it into to the template. Unfortunately, I don't have any idea on how to pass all the article objects and their relative texts in the correct language, and in a way that I would be able to for-loop through them.
Am I overthinking things and should I just use the if-block in the template, or is there a better way to do this?
This seems a good use case for writing a custom template filter which will return the appropriate article version given the current language.
For detailed code explanation about how you can write a custom filter, see Django docs: Writing custom template tags and filters.
And here's a code sample to get you started:
from django import template
from django.template.defaultfilters import stringfilter
register = template.Library()
#register.filter
def localize(value, arg):
"""Returns appropriate language version
of the given article.
value: the article
arg: the current language code
"""
article = value
lang = arg
try:
# `getattr` will call `article.en` or `article.fr`
# depending on the value of `lang`
text = getattr(article, lang)
except AttributeError:
# if this code runs, this means that
# the `article` doesn't have a version
# for the given `lang`
# so, return a default version
text = article.en
return text
Now, you need to create a directory called templatetags inside your app and create a file in the directory and put this code in that file. The detailed instructions can be found in the Code layout section of the docs.
When you've read the docs about where to put the above code, this is how you can use this filter:
{% get_current_language as LANG %}
{{ article|localize:LANG }}
Related
Given this apphook:
class NewsHook(CMSApp):
name = _("News")
urls = ["apps.news.urls"]
apphook_pool.register(NewsHook)
and this model inside apps.news.models:
class Article(models.Model):
title = models.CharField(max_length=255)
...
Is it possible to reference the page associated by apphook in, say, a method on Article?
From the model side I've gotten as far as article._meta.app_label or article._meta.app_config.verbose_name, but that only yields 'news' and 'News', respectively.
And I know from https://github.com/divio/django-cms/blob/7888ab8421bb836c8f7a1127d9a2bf4d4bbdf23e/cms/models/pagemodel.py#L82 that a page's apphook is accessible with page.application_urls, which gives me 'u'NewsHook'.
But I'm missing a link.
I suppose I could filter Pages by the application_urls field and look for a match with my article._meta.app_config.verbose_name, but that would be neither neither failsafe nor pretty.
Any ideas for a better way?
I know this question is a year old and probably OP have figured it out, but I had a similar problem, which I solved by referencing the apphook directly on the method.
from applications.cms_apps import ApplicationAppHook
from cms.models.pagemodel import Page
class Application(models.Model):
def related_cms_page(self):
return Page.objects.filter(application_namespace=ApplicationAppHook.app_name).public().first()
I've gone somewhat further and created a templatetag that uses the application_namespace value to retrieve the page
from cms.models.pagemodel import Page
#register.assignment_tag()
def get_page_by_namespace(application_namespace_str):
try:
return Page.objects.filter(application_namespace=application_namespace_str).public().first()
except AttributeError:
# EAFP ;)
return None
And on the template:
{% get_page_by_namespace 'applications_apphook' as page %}
{% if page %}
{{ page.get_menu_title }}
{# Official Django CMS templatetags also works in this instance, i.e. {% page_attribute "page_title" page %} but it seems a bit redundant to me #}
{% endif %}
I don't seem to be able to get the more_like_this tag in Haystack to return any results. Not sure if it's down to the data I've input, but I've tried it with some documents that should very similar.
I've verified it doesn't work even with a fairly simple index like this:
class PaperIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
def get_model(self):
return Paper
Again the simplified model looks like:
class Paper(Publishable):
title = models.CharField(max_length=255)
abstract = models.TextField()
def __unicode__(self):
return self.title
The search template looks like:
{% autoescape off %}
{{ object.title }}
{{ object.abstract|striptags }}
{% endautoescape %}
At the moment I'm just looking to see that's in the returned result from the tag like this:
{% more_like_this paper as related_papers limit 1 %}
{{ related_papers }}
Not sure if there is anything else I need to do. The elastic search docs mention "In order to use the mlt feature a mlt_field needs to be either be stored, store term_vector or source needs to be enabled." However I'm not sure what that equates to in Haystack terms.
I should note that I'm not seeing any errors - it's just that I get back an empty list/result set from more_like_this.
Oh and Elastic Search is version 1.1.1.
Old question, but still a recurring problem.
The solution is simple: provide enough information in your search template for Elastic Search to be able to calculate similarities.
If you have tags, full description, genres, languages, or anything else that could serve to find matches, add them to the search template.
To add a list (of tags for instance), you could do something like this in your search template:
{% load search_tags %}
{% render_tags object %}
And in the search_tags add a template tag called render_tags:
from django.template import Library
from django.utils.safestring import mark_safe
register = Library()
#register.simple_tag
def render_tags(obj):
return mark_safe(' '.join([t.tag for t in obj.tags.all()]))
This assumes you have a ManyToMany relation to tags in your model.
I am trying to integrate external data in my metatags. What I want to achieve - by entering in Title field (in admin) "Details for {{ product.name }}" I would like to get to get automaticaly "Details for Hadron Collider" as a result in a browser. I have this in my template
{% load seo %}
{% get_metadata for product as metadata %}
{{ metadata.title }}
and I am passing Product object to the template, but what I get as a result is unchanged "Details for {{ product.name }}", so {{ value }} won't be populated / parsed? I have read the docs http://django-seo.readthedocs.org/en/latest/reference/administrators.html, it looks so simple
If you would like to reference the relevant model instance, you type the name of the model surrounded by two braces. For example Buy {{ product }} today!. You can go further and reference different fields from the relevant instance, for example By {{ product.name }} today!.
Though it doesn't work for me. Please, help. Probably I am doing something wrong.
Ok, after days of depression :) I solved it, at least for my project and currently only for Models, but for ModelInstance and Views it's almost I will make it later, it has the same problem in my case. I am quite new to Django, so a better solution may exist, I found this one.
I use Django 1.6.4, Python 2.7, multilingual project (Django Transmeta for model translation)
The problems:
1. variable substitution in models, model instances and views doesn't work. So, if we pass an object to DjangoSEO it won't substitute e.g. {{ product }}, {{ product.name }} variables. According to documentation it should. The problem, for Model is in backends.py, ModelBackend class. Function _resolve_value doesn't pass object to the function _resolve, which is supposed to populate meta tags with object properties. Here are the functions with slight changes that work for me.
def _resolve_value(self, name):
value = super(ModelMetadataBase, self)._resolve_value(name)
try:
return _resolve(value, self._content_object)
except AttributeError:
return value
def _resolve(value, model_instance=None, context=None):
""" Resolves any template references in the given value. """
if isinstance(value, basestring) and "{" in value:
if context is None:
context = Context()
if model_instance is not None:
context[model_instance.__class__.__name__.lower()] = model_instance
t = Template(value)
value = t.render(context)
return value
Also, in the file base.py, function get_linked_metadata we have to attach our object to Metadata instances like this:
if ModelMetadata is not None:
try:
model_md = ModelMetadata.objects.get(_content_type=content_type, _language=language)
except ModelMetadata.DoesNotExist:
model_md = ModelMetadata(_content_type=content_type, _language=language)
model_md._content_object = obj
instances.append(model_md)
And the problem 2 - DjangoSEO was using one Metadata instance for any language, even with option use_i18n = True. So, add _language=language as mentioned above.
Django SEO app is quite good, it has everything SEO needs (if it works :), so there is no reason to reinvent the wheel.
You need to wrap your variables in html tags.
{% load seo %}
{% get_metadata for product as metadata %}
<html>
<head>
<title>{{ metadata.title}}</title>
</head>
<body></body>
</html>
I am hitting a brick wall when it comes to solving this problem. I have a template that is being included in an other template, but I am unable to render any template variables in the included template. I have a separate template tag file for the included template. I am at a total loss right now how to resolve this problem.
I would like to be able to render the field values from my model in the template (which consist of an image, a short description, etc.) I am fairly certain that I am going about this in the wrong way.
Below is my code:
The model:
class AddOnItem(models.Model):
base_product = models.ForeignKey(Product)
base_price = models.DecimalField(max_digits=8, decimal_places=2, default=0.0)
addon_image = models.ImageField(upload_to='uploads/shop/product/images/',
default='uploads/shop/product/images/', help_text='Max width: 450px')
short_description = models.CharField(max_length=255, blank=True)
def __unicode__(self):
return self.base_product.name
The template:
{% load addon_tags %}
{% if items_to_add %}
{% for items in items_to_add %}
<div id="addon-container">
<div id="left-addon" class="addon-container">
<img src="#" class="addon-image">
<p class="addon-description">{{items.short_description}}</p>
</div>
</div>
{% endfor %}
{% endif %}
addon_tags.py:
from django import template
from sho.models import AddOnItem
register = template.Library()
#register.inclusion_tag("foo/templates/v/sho/addon.html", takes_context=True)
def add_on_render():
context['items_to_add'] = AddOnItem()
return context
I imagine I am doing either a lot wrong (my assumption at the moment) or I am missing some minor bit. I have been working on this for several days and have gone over the docs repeatedly. I am simply completely missing something and this has become a major blocker for me. Any help would be appreciated.
Django version 1.4
Edit:
I ended up rewriting the view and did away with the templatetag. Thanks to both Daniel Roseman and Odif Yltsaeb for their replies.
1) From your post you are adding empty, new item into the context in add_on_render templateag.
2) I cant see in your post, WHERE you are using {% add_on_render %} templatetag. You have created templattag, but do not seem to be using it anywhere.
It is bit hard to understand what exactly are you trying to do or why you even need templatetag there.
If you want to display models field value, you do not need templateag for this and all the stuff that you show in your "template" part of this post, could be very well in your main template, which i assume, is not shown in your post.
If you want to use templatetag, then this templatetag should probably recieve AddOnItem istance as parameter like this:
#register.inclusion_tag("foo/templates/v/sho/addon.html", takes_context=True)
def add_on_render(item):
context['items_to_add'] = item
return context
And You could use it in some template like this:
{% load addon_tags %}
{% if items_to_add %}
{% for items in items_to_add %}
<div id="addon-container">
<div id="left-addon" class="addon-container">
<img src="#" class="addon-image">
<p class="addon-description">{% add_on_render items %}</p>
</div>
</div>
{% endfor %}
{% endif %}
and your foo/templates/v/sho/addon.html
would like like this:
{{ items_to_add.short_description }}
But doing it this way seems very unnecessary as you could achieve all that without templatag by using the template code that you already have outside your "main" template.
You haven't posted the template that you are attempting to include the tag in. I suspect you're not calling it at all, because there are a couple of errors that would cause exceptions if you did try and use the tag. You need to do {% add_on_render %} somewhere in your main template.
As I say though there are a couple of errors. Firstly, you don't define context (as an empty dict) before adding the items_to_add key. You can shortcut this by just doing it in one go.
Secondly you've made items_to_add a single, blank, AddOnItem. So in your included template, iterating through items_to_add does nothing at all. Not sure what you are trying to do there. Perhaps you want to pass all AddOnItem instances?
context = {'items_to_add': AddOnItem.objects.all()}
Or maybe you want to filter them by some criteria, in which case you probably want to pass those criteria to the inclusion tag itself:
def add_on_render(product):
context = {'items_to_add': AddOnItem.objects.filter(base_product=product)}
and you would call it from the main template like this:
{% add_on_render my_product %}
if you set "takes_context=True" you should take context as the first argument in decorated function:
#register.inclusion_tag("foo/templates/v/sho/addon.html", takes_context=True)
def add_on_render(context):
context['items_to_add'] = AddOnItem()
....
So the title is a bit obtuse, I know, but I couldn't think of a more succinct way to state it. Here's the issue:
I've created two proxy models for "user types", both inheriting from django.contrib.auth.User. Each has a custom manager limiting the queryset to items belonging to a particular Group. Specifically, there's a PressUser which is any user belonging to the "Press" group and StaffUser which is any user in any other group than "Press".
The issue is that when I add 'groups' to list_filters on my StaffUsers modeladmin, the resulting filter options are every group available, including "Press", and not just groups available to StaffUsers.
I've research a bit online and came up with a custom filterspec that should produce the behavior I want, but the problem is that the User model's 'groups' attribute is actually a related_name applied from the Group model. As a result, I can't attach my filterspec to 'groups' in my proxy model.
Is there any other way to apply the filterspec? Alternatively, is there a better approach to filtering the items returned by the default filterspec?
So, I was able to solve my own problem. For those that might run into a similar situation, here are the steps:
The approach I took is to modify the change_list.html template and manually filter out the items I didn't want to be included. There's quite a number of changes to make, though.
First, add a changelist_view method to your ModelAdmin:
# myproject/account/admin.py
class StaffUserAdmin(models.ModelAdmin):
...
def changelist_view(self, request, extra_context=None):
groups = Group.objects.exclude(name__in=['Press',]).values_list('name')
extra_context = {
'groups': [x[0] for x in groups],
}
return super(StaffUserAdmin, self).changelist_view(request,
extra_context=extra_context)
Basically, all we're doing here is passing in the filtered list of Groups we want to use into the context for the template.
Second, create a change_list.html template for your app.
# myproject/templates/admin/auth/staffuser/change_list.html
{% extends "admin/change_list.html" %}
{% load admin_list %}
{% load i18n %}
{% load account_admin %}
{% block filters %}
{% if cl.has_filters %}
<div id="changelist-filter">
<h2>{% trans 'Filter' %}</h2>
{% for spec in cl.filter_specs %}
{% ifequal spec.title 'group' %}
{% admin_list_group_filter cl spec groups %}
{% else %}
{% admin_list_filter cl spec %}
{% endifequal %}
{% endfor %}
</div>
{% endif %}
{% endblock filters %}
This one deserves a little explanation. First, the template tag loads: admin_list is used for the default Django template tag responsible for rendering the filters, admin_list_filter, i18n is used for trans, and account_admin is for my custom template tag (discussed in a sec), admin_list_group_filter.
The variable spec.title holds the title of the field that's being filtered on. Since I'm trying to alter how the Groups filter is displayed, I'm checking if it equals 'groups'. If it does, then I use my custom template tag, otherwise, it falls back to the default Django template tag.
Third, we create the template tag. I basically just copied the default Django template tag and made the necessary modifications.
# myproject/account/templatetags/account_admin.py
from django.template import Library
register = Library()
def admin_list_group_filter(cl, spec, groups):
return {'title': spec.title, 'choices' : list(spec.choices(cl)), 'groups': groups }
admin_list_group_filter = register.inclusion_tag('admin/auth/group_filter.html')(admin_list_group_filter)
The only things that I've changed here are adding a new argument to the method called 'groups' so I can pass in my filtered list of groups from before, as well as adding a new key to the dictionary to pass that list into the context for the template tag. I've also changed the template the tag uses to a new one that we're about to create now.
Fourth, create the template for the template tag.
# myproject/templates/admin/auth/group_filter.html
{% load i18n %}
<h3>{% blocktrans with title as filter_title %} By {{ filter_title }} {% endblocktrans %}</h3>
<ul>
{% for choice in choices %}
{% if choice.display in groups %}
<li{% if choice.selected %} class="selected"{% endif %}>
{{ choice.display }}</li>
{% endif %}
{% endfor %}
</ul>
No big surprises here. All we're doing is putting all the pieces together. Each choice is a dictionary with all the values needed to construct the filter link. Specifically, choice.display holds the actual name of the instance that will be filtered by. Obviously enough, I've set up a check to see if this value is in my filtered list of groups I want to show, and only render the link if it is.
So, it's a bit involved but works remarkably well. Just like that, you have a list of filters that is exactly what you want instead of the default ones generated by Django.
I'm going to tell you off the bat that I've never done this before myself, so take it with a grain of salt.
What I'd suggest would be to override get_changelist on your ModelAdmin, to return a custom ChangeList class, which you can define somewhere in your admin module.
Your custom ChangeList class would simply override get_filters, so you can map your custom FilterSpec for the group field.
Another thing that might interest you are patches from the feature request ticket for specifying custom filter specs. The latest patch doesn't work for Django 1.3rc1 yet, although #bendavis78 recently posted that he's working on a new one, but depending on your version of Django it may apply cleanly.
It looks like it barely missed the cut to get included into the 1.3 milestone, so I figure it's going to make it into the trunk as soon as work beings on Django 1.4.