I am working on a legacy django project, in there somewhere there is a class defined as follows;

from django.http import HttpResponse class Response(HttpResponse): def __init__(self, template='', calling_context='' status=None): self.template = template self.calling_context = calling_context HttpResponse.__init__(self, get_template(template).render(calling_context), status) 

and this class is used in views as follows

def some_view(request): #do some stuff return Response('some_template.html', RequestContext(request, {'some keys': 'some values'})) 

this class was mainly created so that they could use it to perform assertions in the unit tests .i.e they are not using django.test.Client to test the views but rather they create a mock request and pass that to view as(calling the view as a callable) in the tests as follows

def test_for_some_view(self): mock_request = create_a_mock_request() #call the view, as a function response = some_view(mock_request) #returns an instance of the response class above self.assertEquals('some_template.html', response.template) self.assertEquals({}, response.context) 

The problem is that half way through the test suite(quite a huge test suite), some tests begin blowing up when executing the

return Response('some_template.html', RequestContext(request, {'some keys': 'some values'})) 

and the stack trace is

self.template = template AttributeError: can't set attribute 

the full stack trace looks something like

====================================================================== ERROR: test_should_list_all_users_for_that_specific_sales_office ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/austiine/Projects/mped/console/metrics/tests/unit/views/sales_office_views_test.py", line 106, in test_should_list_all_users_for_that_specific_sales_office response = show(request, sales_office_id=sales_office.id) File "/Users/austiine/Projects/mped/console/metrics/views/sales_office_views.py", line 63, in show "sales_office_users": sales_office_users})) File "/Users/austiine/Projects/mped/console/metrics/utils/response.py", line 9, in __init__ self.template = template AttributeError: can't set attribute 

the actual failing test is

def test_should_list_all_users_for_that_specific_sales_office(self): user_company = CompanyFactory.create() request = self.mock_request(user_company) #some other stuff #calling the view response = show(request, sales_office_id=sales_office.id) self.assertIn(user, response.calling_context["sales_office_users"]) self.assertNotIn(user2, response.calling_context["sales_office_users"]) 

code for the show view

def show(request, sales_office_id): user = request.user sales_office = [] sales_office_users = [] associated_market_names = [] try: sales_office = SalesOffice.objects.get(id=sales_office_id) sales_office_users = User.objects.filter(userprofile__sales_office=sales_office) associated_market_names = Market.objects.filter(id__in= (sales_office.associated_markets.all())).values_list("name", flat=True) if user.groups.all()[0].name == UserProfile.COMPANY_AO: associated_market_names = [market.name for market in sales_office.get_sales_office_user_specific_markets(user)] except: pass return Response("sales_office/show.html", RequestContext(request, {'keys': 'values'})) 
10

3 Answers

This answer doesn't address the specifics of this question, but explains the underlying issue. This specific exception "AttributeError: can't set attribute" is raised (see source) when the attribute you're attempting to change is actually a property that doesn't have a setter. If you have access to the library's code, adding a setter would solve the problem.

EDIT: updated source link to new location in the code.

Edit2:

Example of a setter:

class MAMLMetaLearner(nn.Module): def __init__( self, args, base_model, inner_debug=False, target_type='classification' ): super().__init__() self.args = args # args for experiment self.base_model = base_model assert base_model is args.model self.inner_debug = inner_debug self.target_type = target_type @property def lr_inner(self) -> float: return self.args.inner_lr @lr_inner.setter def lr_inner(self, new_val: float): self.args.inner_lr = new_val 
8

It looks like you don't use self.template in Response class. Try like this:

class Response(HttpResponse): def __init__(self, template='', calling_context='' status=None): HttpResponse.__init__(self, get_template(template).render(calling_context), status) 
4

I took a look to django source code I've no idea where template or templates attribute come from in HttpResponse. But I can propose to you to change your test approach and migrate to mock framework. You can rewrite your test like:

@patch("qualified_path_of_response_module.response.Response", spec=Response) def test_should_list_all_users_for_that_specific_sales_office(self,mock_resp): user_company = CompanyFactory.create() request = self.mock_request(user_company) #some other stuff #calling the view response = show(request, sales_office_id=sales_office.id) self.assertTrue(mock_resp.called) context = mock_resp.call_args[0][2] self.assertIn(user, context["sales_office_users"]) self.assertNotIn(user2, context["sales_office_users"]) 

@patch decorator replace your Response() class by a MagicMock() and pass it to your test method as mock_resp variable. You can also use patch as context manager by with construct but decorators are the cleaner way to do it. I don't know if Response is just a stub class for testing but in that case you can patch directly HttpResponce, but it depends from your code.

You can find details about call_args here. Maybe you need to use spec attribute because django make some type checking... but try with and without it (I'm not a django expert). Explore mock framework: it'll give to you lot of powerful tools to make simple tests.

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy