Proposal: add a get_for_template method to ContextList

137 views
Skip to first unread message

germano guerrini

unread,
Sep 4, 2014, 9:02:34 AM9/4/14
to django-d...@googlegroups.com
Hello everyone,

while writing a test for a view, I found that one of the rendered templates context had a key used by the main template of the view itself, but with a different value.
Given the rendering order, calling

response.context[key]

would not return the view template context value, but the included template one.

Let me write some code to help myself explaining:

def my_view(request):
    context
= {'foo': 'bar'}
   
template = 'my_template.html'
   
return render_to_response(template, context)


def test_my_view(self):
    url
= reverse('url_pattern_name')
    response
= self.client.get(url)
   
self.assertEqual(response.context['foo'], 'bar') # Fails, due to another template rendered in my_template redefine 'foo'


So I was wondering whether it might be useful to enrich the ContextList object by annotating somehow the template name for each sub context.
The store_rendered_templates method in django/test/client.py seems the right place to do it, as it receives both the template and the context:

def store_rendered_templates(store, signal, sender, template, context, **kwargs):
   
"""
    Stores templates and contexts that are rendered.


    The context is copied so that it is an accurate representation at the time
    of rendering.
    """

    store
.setdefault('templates', []).append(template)
    store
.setdefault('context', ContextList()).append({template.name: copy(context)})

and of course the ContextList class has to be modified as well:

class ContextList(list):
   
"""A wrapper that provides direct key access to context items contained
    in a list of context objects.
    """

   
def __getitem__(self, key):
       
if isinstance(key, six.string_types):
           
for template_context in self:
               
for subcontext in template_context.itervalues():
                   
if key in subcontext:
                       
return subcontext[key]
           
raise KeyError(key)
       
else:
           
return super(ContextList, self).__getitem__(key)


   
def __contains__(self, key):
       
try:
           
self[key]
       
except KeyError:
           
return False
       
return True


   
def keys(self):
       
"""
        Flattened keys of subcontexts.
        """

        keys
= set()
       
for template_context in self:
           
for subcontext in template_context.itervalues():
               
for dict in subcontext:
                    keys
|= set(dict.keys())
       
return keys


   
def get_for_template(self, template_name):
       
# Cannot use self[template_name] because of __getitem__
       
for template_context in self:
           
if template_context.keys()[0] == template_name:
               
return template_context.values()[0]
       
raise KeyError

The implementation is a bit cumbersome because ContextList should be a ContextDict instead, but that should do the trick nonetheless.
What do you think about it?

Thanks,
Germano


germano guerrini

unread,
Sep 15, 2014, 9:24:19 AM9/15/14
to django-d...@googlegroups.com
I'd like to bump this. While there might be better solutions, I think the issue is not so wacky after all.

Florian Apolloner

unread,
Sep 15, 2014, 12:35:23 PM9/15/14
to django-d...@googlegroups.com
On Monday, September 15, 2014 3:24:19 PM UTC+2, germano guerrini wrote:
I'd like to bump this. While there might be better solutions, I think the issue is not so wacky after all.

To be honest, I don't see an issue here at all. What the view returns should end up in the template. If tags/whatever in your template override variables at will that's a bug in your application imo. Also your code example fails to show where the key is overridden -- ie an include wouldn't override cause the stack is popped after the include. Do you have a concrete example causing this "issue"?

Cheers,
Florian

germano guerrini

unread,
Sep 16, 2014, 6:37:55 AM9/16/14
to django-d...@googlegroups.com
Hi Florian, thanks for your reply.

I clearly didn't express myself properly, so let me try again with a simple example.

Suppose you are dealing with a detail view, whose context has an "object" key that refers to the object being displayed. Then, in the rendered template, you might have an inclusion tag that defines another context with an "object" key that refers to a different object (maybe one related to the "main" one). I don't see this as a bug, otherwise every tag should be aware of all the views (contexts and templates) where it is going to be used. Sure enough, if the tag takes the context, it is its responsibility to push and pop from the stack, but that's not what I was talking about.

The fact is that when the test client gets a page, for every template that gets rendered, the store_rendered_templates is called through a signal and the context appended to the ContextList. This list is then flattened when you try to get an item from one of the context, and if you have multiple context with the same key, the first one rendered will be the one that you get, given the current implementation of ContextList.__getitem__.

Hope that clears it up: ContextList is a list of dictionaries and I think it's not safe to assume that there are no overlapping keys - and probably it was already a knows "issue" since the keys method returns a set.

Let me know if I completely missed something else,
Germano
Reply all
Reply to author
Forward
0 new messages