Solving ‘str’ object has no attribute ‘strategy’ in django Oscar

When I started working in Oscar(version 1.4), an ecommerce application developed on top of django framework, I faced a very strange bug.

'str' object has no attribute 'strategy'

Whenever I add a automatic product list promotion or user selected product list promotions and visit my pages, it raises the above mentioned error.

Promotions are a predefined set of templates in Oscar that can be added on a page in your website. Usually promotions are added in home page.

The issue was in the template tag render_promotion at /templatetags/promotion_tags.py line no. 21 inside oscar source code.

Oscar source code probably in your virtualenv.

The render_promotion tag of oscar uses a PromotionNode class to render HTML template to display a product. Even though the Node class does render template using RequestContext class, the template rendered does not have request object in context.

django has two classes Context and RequestContext which is used to render templates. A template must be rendered using RequestContext class if we need to have access to django request object within the template.
For more information on rendering a template, see:
* https://docs.djangoproject.com/en/1.11/ref/templates/api/#rendering-a-context
* https://docs.djangoproject.com/en/1.11/ref/templates/api/#using-requestcontext

Since the template tag does not pass request object in context, and the template expects a request to be present, it raises the error whenever we take a page that renders a product. So, it was not only promotions, but also could have affected me if I had visited a product page or listing.

Solution

I fixed it by writing my own promotion_tags.py in the templatetags directory inside my project directory.

Oscar allows overriding its classes and modules. For more information, see:
https://django-oscar.readthedocs.io/en/releases-1.4/topics/customisation.html

I changed the PromotionNode class as following,

class PromotionNode(Node):
def __init__(self, promotion):
self.promotion_var = Variable(promotion)
def render(self, context):
promotion = self.promotion_var.resolve(context)
template = select_template([
promotion.template_name(),
'promotions/default.html'
])
args = {'promotion': promotion}
args.update(
**promotion.template_context(
request=context['request']
)
)
# removed below line from oscar.
#
ctx = RequestContext(context['request'], args)
# return template.render(ctx)
        return template.render(args, context['request'])

See the commented line in code. I removed wrapping args as a RequestContext object. Instead I passed args as is and the request as the second argument to template.render.

Django creates an instance of Template class from the HTML file supplied. The HTML file will have variables that needs to be substituted. This is done by the render method of Template instance and returns the final plain HTML, which can be given as an HTTP response to browser/client.

template.render always converts its first argument as a normal Context object, even if it is an instance of RequestContext. But, if template.render is invoked with request object as second argument, then it will convert the dict argument into an instance of RequestContext and subsequently the rendered template will have access to the request. That is the first stage of solution.

Now, since we have overridden the promotion_tags.py file of oscar, we have to tell that to django, by including our module into TEMPLATES variable in settings.py file.

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
location('templates'),
OSCAR_MAIN_TEMPLATE_DIR
],
'OPTIONS': {
'context_processors': [
..
],
'loaders': [
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
],
'libraries': {
'promotion_tags': 'templatetags.promotion_tags'
}
},
},
]

Hence the error is fixed.

I hope Oscar will fix the error in their new version(s).
I have answered this in Oscar google groups forum. https://groups.google.com/forum/?fromgroups#!topic/django-oscar/fupWjcajIYY