Probably a circular import in django app? - django

There is a filter that adds to form django_crispy_form functionality. All arguments passing as string divided by ",".
# -*- coding: utf-8 -*-
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from django.core.urlresolvers import reverse
from django.template import Library
register = Library()
#args=[url_name, submit_button_text, optional_<pk>]
#register.filter
def with_submit(form, args):
sargs = args.split(',')
action, name = sargs[:2]
if len(sargs) > 2:
args = sargs[2:]
else:
args = None
helper = FormHelper()
helper.form_method = 'POST'
if args:
#there exception throwed every time if len of args > 2
helper.form_action = reverse(action, int(args[0]))
else:
helper.form_action = reverse(action)
helper.add_input(Submit(action, name, css_class='btn btn-primary'))
form.helper = helper
return form
When I use this filter without third optional argument - it works fine, but when I add third <pk> argument - it crashes and says:
The included URLconf '1' does not appear to have any patterns in it. If you see valid patterns in the file then the issue is probably caused by a circular import.
And I don't know where I should find circular import.

The signature of the reverse method is:
reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)
So, your second argument of int(args[0]) is being passed in as urlconf, which is leading to the exception. You need to use
reverse(action, args=int(args[0]))
Whenever you write template tags or filters, you have to be very careful that you code is as simple and as robust as possible. Custom tags and filters are the most difficult parts of a django application to debug.
Consider this line:
action, name = sargs[:2]
This line will raise ValueError if there aren't exactly two items in sargs[:2]. You should do a check first before executing this statement.

Related

Django: prefix/postfix language slug in i18n_urls

I have a django-cms site, that uses i18n_patterns in urls.py. This works, urls are built like /lang/here-starts-the-normal/etc/.
Now, I would like to have urls like this: /prefix-lang/here-starts.... As there will be a couple of country specific domains, this wille be like /ch-de/here-... for Switzerland/.ch domain, /us-en/here-starts.... for the states, and some more. So, when the url would be /ch-de/..., the LANGUAGE would still be de. Hope this is clear?
As the content is filled with existing LANGUAGES=(('de', 'DE'), ('en', 'EN'), ...), I cannot change LANGUAGES for every domain - no content would be found in the cms, modeltranslation, only to mention those two.
How can I prefix the language slug in i18n_patterns? Is it possible at all?
I think a way without hacking Django too much would be to use URL rewrite facility provided by the webserver you run, for example, for mod_wsgi you can use mod_rewrite, similar facility exists also for uWSGI.
You may need to also post-process the output from Django to make sure that any links are also correctly re-written to follow the new schema. Not the cleanest approach but seems doable.
Working example, though the country/language order is reversed (en-ch instead of ch-en), to have it as django expects it when trying to find a language (ie, setting language to "en-ch", it will find "en", if available).
This solution involves a modified LocaleMiddleware, i18n_patterns, LocaleRegexResolver. It supports no country, or a 2 char country code, setup with settings.SITE_COUNTRY. It works by changing urls to a lang-country mode, but the found language code in middleware will still be language only, 2 chars, and work perfectly with existing LANGUAGES, that contain 2 chars language codes.
custom_i18n_patterns.py - this just uses our new resolver, see below
from django.conf import settings
from ceco.resolvers import CountryLocaleRegexURLResolver
def country_i18n_patterns(*urls, **kwargs):
"""
Adds the language code prefix to every URL pattern within this
function. This may only be used in the root URLconf, not in an included
URLconf.
"""
if not settings.USE_I18N:
return list(urls)
prefix_default_language = kwargs.pop('prefix_default_language', True)
assert not kwargs, 'Unexpected kwargs for i18n_patterns(): %s' % kwargs
return [CountryLocaleRegexURLResolver(list(urls), prefix_default_language=prefix_default_language)]
resolvers.py
import re
from django.conf import settings
from django.urls import LocaleRegexURLResolver
from modeltranslation.utils import get_language
class CountryLocaleRegexURLResolver(LocaleRegexURLResolver):
"""
A URL resolver that always matches the active language code as URL prefix.
extended, to support custom country postfixes as well.
"""
#property
def regex(self):
language_code = get_language() or settings.LANGUAGE_CODE
if language_code not in self._regex_dict:
if language_code == settings.LANGUAGE_CODE and not self.prefix_default_language:
regex_string = ''
else:
# start country changes
country_postfix = ''
if getattr(settings, 'SITE_COUNTRY', None):
country_postfix = '-{}'.format(settings.SITE_COUNTRY)
regex_string = '^%s%s/' % (language_code, country_postfix)
# end country changes
self._regex_dict[language_code] = re.compile(regex_string, re.UNICODE)
return self._regex_dict[language_code]
middleware.py - only very few lines changed, but had to replace the complete process_response.
from django.middleware.locale import LocaleMiddleware
from django.conf import settings
from django.conf.urls.i18n import is_language_prefix_patterns_used
from django.http import HttpResponseRedirect
from django.urls import get_script_prefix, is_valid_path
from django.utils import translation
from django.utils.cache import patch_vary_headers
class CountryLocaleMiddleware(LocaleMiddleware):
"""
This is a very simple middleware that parses a request
and decides what translation object to install in the current
thread context. This allows pages to be dynamically
translated to the language the user desires (if the language
is available, of course).
"""
response_redirect_class = HttpResponseRedirect
def process_response(self, request, response):
language = translation.get_language()
language_from_path = translation.get_language_from_path(request.path_info)
urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF)
i18n_patterns_used, prefixed_default_language = is_language_prefix_patterns_used(urlconf)
if (response.status_code == 404 and not language_from_path and
i18n_patterns_used and prefixed_default_language):
# Maybe the language code is missing in the URL? Try adding the
# language prefix and redirecting to that URL.
# start country changes
language_country = language
if getattr(settings, 'SITE_COUNTRY', None):
language_country = '{}-{}'.format(language, settings.SITE_COUNTRY)
language_path = '/%s%s' % (language_country, request.path_info)
# end country changes!
path_valid = is_valid_path(language_path, urlconf)
path_needs_slash = (
not path_valid and (
settings.APPEND_SLASH and not language_path.endswith('/') and
is_valid_path('%s/' % language_path, urlconf)
)
)
if path_valid or path_needs_slash:
script_prefix = get_script_prefix()
# Insert language after the script prefix and before the
# rest of the URL
language_url = request.get_full_path(force_append_slash=path_needs_slash).replace(
script_prefix,
'%s%s/' % (script_prefix, language_country),
1
)
return self.response_redirect_class(language_url)
if not (i18n_patterns_used and language_from_path):
patch_vary_headers(response, ('Accept-Language',))
if 'Content-Language' not in response:
response['Content-Language'] = language
return response

Pass a lazy translation string including variable to function in Django

Inside a Django view, I create a subject like that:
subject = _(u"%(user)s has posted a comment") % { 'user': user }
Then I pass this subject to a function, which handles email notifications:
send_notifications(request, subject, url)
In send_notifications, I iterate over all subscriptions and send emails. However, each user can have a different language, so I activate the user's language dynamically via Django's activate:
def send_notifications(request, subject, url):
from django.utils.translation import activate
for s in Subscription.objects.filter(url=url):
activate(s.user.userprofile.lang)
send_mail(subject, render_to_string('notification_email.txt', locals()), settings.SERVER_EMAIL, [s.user.email])
The template gets rendered in the correct language of each user. However, the subject is passed as an evaluated and translated string to send_notifications and thus, is not translated.
I played around with lazy translations and lambda functions as parameters, but without success. Any help appreciated :)
Instead of passing the translated subject, just pass it non translated:
subject = '%(user)s has posted a comment'
context = {'user': user}
def send_notifications(request, subject, url, context):
from django.utils.translation import activate
for s in Subscription.objects.filter(url=url):
activate(s.user.userprofile.lang)
send_mail(_(subject) % context, render_to_string('notification_email.txt', locals()), settings.SERVER_EMAIL, [s.user.email])
If you're not going to personalize the contents per user, then you might as well limit the number of renderings because that's a little confusing:
# also do your imports at the top to catch import issues early
from django.utils.translation import activate
from django.utils.translation import ugettext as _
def send_notifications(request, url,
translatable_subject, context,
body_template='notification_template.txt'):
previous_lang = None
for s in Subscription.objects.filter(url=url).order_by('user__userprofile__lang'):
if s.user.userprofile.lang != previous_lang:
activate(s.user.userprofile.lang)
subject = _(translatable_subject) % context
body = render_to_string(body_template, locals())
send_mail(subject, body, settings.SERVER_EMAIL, [s.user.email])
previous_lang = s.user.userprofile.lang
As such, it is much more obvious that you're not going to render emails per usage.
This slight rewrite should make you doubt about the original choice of a couple of names (locals, notification_template).
The above sample code is barely an "educated guess" and you should double check it and make sure you understand everything before you paste it.
Ok, found a solution myself. In case anybody runs into a similar problem:
from django.utils.translation import ugettext as _
# create subject as raw string in Django view
raw_subject = r"%(username)s has posted a comment"
# for the sake of generic variables, create a dictionary to pass to function
extra_context = { 'user': user }
# call function with raw string and dictionary as params
send_notifications(request, raw_subject, url, extra_context)
# translate the raw string inside send_notifications into the temporarily activated language
translated_subject = _(raw_subject) % extra_context
Appears to be working as desired :) Since we are working with several different notifications, I tried to avoid an extra template for each kind. However, calling a template with extra_context is also a possible solution.
I ended up creating a class that will handle the translation, passing the context of the string to this class.
from django.utils.translation import gettext_lazy as _
class Translatable(object):
def __init__(self, text, context):
self.text = text
self.context = context
def __str__(self):
return _(self.text).format(**self.context)
In your function, just call str(subject):
def send_notifications(request, subject, url):
from django.utils.translation import activate
for s in Subscription.objects.filter(url=url):
activate(s.user.userprofile.lang)
send_mail(str(subject), render_to_string('notification_email.txt', locals()), settings.SERVER_EMAIL, [s.user.email])
And passes subject to your function as following:
subject = Translatable(_("{user} has posted a comment"), context={'user': user})
send_notifications(request, subject, url)

Django DRY Feeds

I'm using the Django Feeds Framework and it's really nice, very intuitive and easy to use. But, I think there is a problem when creating links to feeds in HTML.
For example:
<link rel="alternate" type="application/rss+xml" title="{{ feed_title }}" href="{{ url_of_feed }}" />
Link's HREF attribute can be easily found out, just use reverse()
But, what about the TITLE attribute? Where the template engine should look for this? Even more, what if the feed is build up dinamically and the title depends on parameters (like this)?
I can't come up with a solution that "seems" DRY to me... All that I can come up with is using context processors o template tags, but it gets messy when the context procesor/template tag has to find parameters to construct the Feed class, and writing this I realize I don't even know how to create a Feed instance myself within the view.
If I put all this logic in the view, it would not be just one view. Also, the value for TITLE would be in the view AND in the feed.
Just a guess (as I have not used feeds yet in my django app), but you could add a special template_context for your feed with your feed object and use it in your base.html.
I'm not fully satisfied with this solution, it may break feeds using Request and depends on a magic method. There it goes:
#coding:utf-8
# Author: Armando Pérez Marqués <mandx#rbol.org>
# Purpose: Django TemplateTag to output feed links in templates in a DRY way
# Created: 05/07/2010
import re
from django import template
from django.conf import settings
from django.contrib.syndication.views import Feed
from django.core.urlresolvers import reverse, resolve, NoReverseMatch
from django.template import Node
from django.template import TemplateSyntaxError
from django.utils.encoding import smart_str
from django.utils.html import escape as html_escape
from django.utils.safestring import mark_safe
register = template.Library()
kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
class FeedInfoNode(Node):
def __init__(self, view_name, args, kwargs, asvar):
self.view_name = view_name
self.args = args
self.kwargs = kwargs
self.asvar = asvar
def render(self, context):
args = [arg.resolve(context) for arg in self.args]
kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
for k, v in self.kwargs.items()])
# Try to look up the URL twice: once given the view name, and again
# relative to what we guess is the "main" app. If they both fail,
# re-raise the NoReverseMatch unless we're using the
# {% feed_info ... as var %} construct in which cause return nothing.
url = ''
try:
url = reverse(self.view_name, args=args, kwargs=kwargs, current_app=context.current_app)
except NoReverseMatch, e:
if settings.SETTINGS_MODULE:
project_name = settings.SETTINGS_MODULE.split('.')[0]
try:
url = reverse(project_name + '.' + self.view_name,
args=args, kwargs=kwargs, current_app=context.current_app)
except NoReverseMatch:
if self.asvar is None:
# Re-raise the original exception, not the one with
# the path relative to the project. This makes a
# better error message.
raise e
else:
if self.asvar is None:
raise e
if 'request' in context:
request = context['request']
else:
request = None
feed_instance, feed_args, feed_kwargs = resolve(url)
if not isinstance(feed_instance, Feed):
raise NoReverseMatch, \
'feed_info can only reverse class-based feeds'
feed_obj = feed_instance.get_object(request, *feed_args, **feed_kwargs)
feed_data = {
'url': url,
'obj': feed_instance,
'args': feed_args,
'kwargs': feed_kwargs,
#'title': html_escape(feed_instance.__get_dynamic_attr('title', obj)),
'title': html_escape(
feed_instance._Feed__get_dynamic_attr('title', feed_obj)
),
'type': feed_instance.feed_type.mime_type,
}
if self.asvar:
context[self.asvar] = feed_data
return ''
else:
return mark_safe(
'<link rel="alternate" type="%(type)s" title="%(title)s" href="%(url)s" />' \
% feed_data
)
def feed_info(parser, token):
"""
Returns an mapping containing populated info about the reversed feed
Works exactly as the url tag, but the mapping is not returned, instead
a variable is always set in the context.
This is a way to define links that aren't tied to a particular URL
configuration::
{% feed_info path.to.some_feed_view_class arg1 arg2 as feed_info_var %}
or
{% feed_info path.to.some_feed_view_class name1=value1 name2=value2 as feed_info_var %}
"""
bits = token.split_contents()
if len(bits) < 2:
raise TemplateSyntaxError("'%s' takes at least one argument"
" (path to a feed view)" % bits[0])
viewname = bits[1]
args = []
kwargs = {}
asvar = None
bits = bits[2:]
if len(bits) >= 2 and bits[-2] == 'as':
asvar = bits[-1]
bits = bits[:-2]
# Backwards compatibility: check for the old comma separated format
# {% url urlname arg1,arg2 %}
# Initial check - that the first space separated bit has a comma in it
if bits and ',' in bits[0]:
check_old_format = True
# In order to *really* be old format, there must be a comma
# in *every* space separated bit, except the last.
for bit in bits[1:-1]:
if ',' not in bit:
# No comma in this bit. Either the comma we found
# in bit 1 was a false positive (e.g., comma in a string),
# or there is a syntax problem with missing commas
check_old_format = False
break
else:
# No comma found - must be new format.
check_old_format = False
if check_old_format:
# Confirm that this is old format by trying to parse the first
# argument. An exception will be raised if the comma is
# unexpected (i.e. outside of a static string).
match = kwarg_re.match(bits[0])
if match:
value = match.groups()[1]
try:
parser.compile_filter(value)
except TemplateSyntaxError:
bits = ''.join(bits).split(',')
# Now all the bits are parsed into new format,
# process them as template vars
if len(bits):
for bit in bits:
match = kwarg_re.match(bit)
if not match:
raise TemplateSyntaxError("Malformed arguments to url tag")
name, value = match.groups()
if name:
kwargs[name] = parser.compile_filter(value)
else:
args.append(parser.compile_filter(value))
return FeedInfoNode(viewname, args, kwargs, asvar)
feed_info = register.tag(feed_info)
I'm starting with the code of the {% url %} template tag, and then, after obtaining the feed's URL, use resolve() to get the Feed subclass instance, and then get the needed attributes.
Caveats
Requires Django 1.2 Class Feeds, don't know exactly how to do this with the old way of feeds.
If the feed class uses the request object, the request context processor must be configured, since None is passed if it isn't present in the context.
There's an oddity with Feed.__get_dynamic_attr(). The Feed subclass instance doesn't have this method; instead, it appears with another name. Don't know how to figure the name out at runtime...

Django - reversing wrapped view functions

I am trying to incorporate django-schedule into my project. Django-schedule's source is here. I don't like the urls, because they all capture a slug. My project will only allow one calendar per user, so it doesn't make sense to capture the slug. So, I wrapped the django-schedule views like this (look up the slug using the current user, and pass it to django-schedule's views):
from schedule.views import calendar_by_periods
from schedule.models import Calendar
from schedule.periods import Month
def cal_by_periods_wrapper(view):
def new_view(request, *args, **kwargs):
kwargs['calendar_slug'] = Calendar.objects.get_calendars_for_object(obj=request.user, distinction="owner")[0].slug
return view(request, *args, **kwargs)
return new_view
And here is the relevant section from urls.py:
urlpatterns = patterns('',
url(r'^$',
cal_by_periods_wrapper(calendar_by_periods),
name = "month_calendar",
kwargs={'periods': [Month], 'template_name': 'schedule/calendar_month.html'}),
This works fine until it hits one of the template tags included with django-schedule, prev_url:
#register.simple_tag
def prev_url(target, slug, period):
return '%s%s' % (
reverse(target, kwargs=dict(calendar_slug=slug)),
querystring_for_date(period.prev().start))
This function raises:
TemplateSyntaxError at /teacher/calendar/
Caught an exception while rendering: Reverse for 'month_calendar' with arguments
'()' and keyword arguments '{'calendar_slug': u'asdf'}' not found.
How can I wrap this view and still make the reverse call work?
This has nothing to do with wrapping the function. It's just that you no longer have a URL with the name 'month_calendar' which takes a 'calendar_slug' argument. Either define one in your urlconf, or edit the templatetag.
Edit after comment Yes but the 'reverse' call is still passing a slug argument, and there's no 'month_calendar' url which takes one, so the reverse match fails.

Django inclusion tag with configurable template

I've created an inclusion tag, however I'd like to be able to make the template optionally configurable. There doesn't seem to be support for this out of the box, so I'd like to see how people did this - maybe a method search the templates directory first for a specific template name and then falling back to the default template.
#register.inclusion_tag('foo.html', takes_context=True)
I use simple_tag when i need to do that:
from django.template import Library, loader, Context
#register.simple_tag(takes_context=True)
def my_tag(context, template_name):
var1 = ...
t = loader.get_template(template_name)
return t.render(Context({
'var1': var1,
...
}))
This post saved my life: http://djangosnippets.org/snippets/1329/
The key is to add to a "dummy template":
{% extends template %}
The inclusion_tag decorator is just a shortcut - it's meant as a simple way of rendering a specific template with a specific context. As soon as you want to move outside of that, it can no longer help you. But that just means you'll have to write the tag the long way, as explained in the documentation, and pass the template you want as a parameter.
I had to do something like this for a project and since we needed more than one of this kind of inclusion tag I made a decorator based on django inclusion_tag decorator. This is the code:
# -*- coding: utf-8 -*-
from django import template
from inspect import getargspec
from django.template.context import Context
from django.template import Node, generic_tag_compiler, Variable
from django.utils.functional import curry
def inclusion_tag(register, context_class=Context, takes_context=False):
def dec(func):
params, xx, xxx, defaults = getargspec(func)
if takes_context:
if params[0] == 'context':
params = params[1:]
else:
raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
class InclusionNode(Node):
def __init__(self, vars_to_resolve):
self.vars_to_resolve = map(Variable, vars_to_resolve)
def render(self, context):
resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
if takes_context:
args = [context] + resolved_vars
else:
args = resolved_vars
file_name, extra_context = func(*args)
from django.template.loader import get_template, select_template
if not isinstance(file_name, basestring) and is_iterable(file_name):
t = select_template(file_name)
else:
t = get_template(file_name)
self.nodelist = t.nodelist
new_context = context_class(extra_context, autoescape=context.autoescape)
# Copy across the CSRF token, if present, because inclusion
# tags are often used for forms, and we need instructions
# for using CSRF protection to be as simple as possible.
csrf_token = context.get('csrf_token', None)
if csrf_token is not None:
new_context['csrf_token'] = csrf_token
return self.nodelist.render(new_context)
compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
compile_func.__doc__ = func.__doc__
register.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
return func
return dec
You have to return a tuple with the template (or template list) and the context dict. Note that you have to pass the register (Library instance) in the decorator call:
from somewhere import inclusion_tag
#inclusion_tag(register)
def display_formset(formset):
template_name = FORMSET_TEMPLATES.get(formset.model,
'includes/inline_formset.html')
return (template_name, {'formset': formset})
Hope this helps
A solution could be a regular inclusion_tag which pass dynamic template name to context.
Like this :
# templatetags/tags.py
#register.inclusion_tag('include_tag.html', takes_context=True)
def tag_manager(context):
context.update({
'dynamic_template': resolve_template(context),
})
return context
And the template:
<!-- include_tag.html -->
{% include dynamic_template %}
The tricks here is, when I call {% tag_manager %}, it includes include_tag.html which in turn includes the template returned by resolve_template() (not included for brevity).
Hope this helps...