Repository: akvo/akvo-rsr

Issue:
UnicodeError when handling errors in project editor
http://sentry.support.akvo-ops.org/rsr/live/group/1169/

```
Stacktrace (most recent call last):

  File "django/core/handlers/base.py", line 111, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "django/views/decorators/csrf.py", line 57, in wrapped_view
    return view_func(*args, **kwargs)
  File "django/views/generic/base.py", line 69, in view
    return self.dispatch(request, *args, **kwargs)
  File "rest_framework/views.py", line 466, in dispatch
    response = self.handle_exception(exc)
  File "rest_framework/views.py", line 463, in dispatch
    response = handler(request, *args, **kwargs)
  File "rest_framework/decorators.py", line 53, in handler
    return func(*args, **kwargs)
  File "akvo/rest/views/project_editor.py", line 439, in project_editor
    obj_data, errors = pre_process_data(key, data[key], errors)
  File "akvo/rest/views/project_editor.py", line 184, in pre_process_data
    errors = add_error(errors, e, key)
  File "akvo/rest/views/project_editor.py", line 67, in add_error
    'error': str(message).capitalize()}
```



Discussion:

Resolving patch:
diff --git a/akvo/rest/views/project_editor.py b/akvo/rest/views/project_editor.py
--- a/akvo/rest/views/project_editor.py
+++ b/akvo/rest/views/project_editor.py
@@ -64,7 +64,7 @@ def add_error(errors, message, field_name):
     """Appends a new error to the errors list."""
     errors.append(
         {'name': field_name,
-         'error': str(message).capitalize()}
+         'error': message.encode('utf8').capitalize()}
     )
     return errors
 
diff --git a/akvo/rsr/views/project.py b/akvo/rsr/views/project.py
--- a/akvo/rsr/views/project.py
+++ b/akvo/rsr/views/project.py
@@ -40,7 +40,7 @@ def _project_directory_coll(request):
     """Dig out and pass correct projects to the view."""
     page = request.rsr_page
     return (
-        page.organisation.published_projects() if page is not None
+        page.projects() if page is not None
         else Project.objects.public().published()
     )
 


Test patch:
diff --git a/akvo/rsr/tests/rest/test_project_editor.py b/akvo/rsr/tests/rest/test_project_editor.py
--- a/akvo/rsr/tests/rest/test_project_editor.py
+++ b/akvo/rsr/tests/rest/test_project_editor.py
@@ -14,6 +14,8 @@
 from django.contrib.auth.models import Group
 from django.test import TestCase, Client
 
+from akvo.rest.views.project_editor import add_error
+
 
 class BaseReorderTestCase(object):
 
@@ -284,3 +286,31 @@ def create_item(self, order=None):
 
     def get_items(self):
         return Indicator.objects.filter(result_id=self.result.id).order_by('id')
+
+
+class ErrorHandlerTestCase(TestCase):
+    """Tests for the error handler used by project editor."""
+
+    def test_should_handle_unicode_errors(self):
+        # Given
+        message = u"""Il n'est pas permis d'utiliser une virgule, utilisez un point pour indiquer les décimales."""
+        errors = []
+        field_name = u'rsr_budgetitem.amount.5966_new-0'
+
+        # When
+        add_error(errors, message, field_name)
+
+        # Then
+        self.assertEquals(1, len(errors))
+
+    def test_should_handle_str_errors(self):
+        # Given
+        message = "It is not allowed to use a comma, use a period to denote decimals."
+        errors = []
+        field_name = u'rsr_budgetitem.amount.5966_new-0'
+
+        # When
+        add_error(errors, message, field_name)
+
+        # Then
+        self.assertEquals(1, len(errors))
diff --git a/akvo/rsr/tests/views/test_project.py b/akvo/rsr/tests/views/test_project.py
--- a/akvo/rsr/tests/views/test_project.py
+++ b/akvo/rsr/tests/views/test_project.py
@@ -15,7 +15,7 @@
 from django.conf import settings
 from django.test import Client, TestCase
 
-from akvo.rsr.models import Organisation
+from akvo.rsr.models import Keyword, Organisation, PartnerSite, Partnership, Project
 
 
 @skip('Needs Django >= 1.8')
@@ -84,3 +84,95 @@ def test_should_replace_page_size_limit_parameter(self):
         self.assertIn(page_limit_1, response.content)
         self.assertIn(page_limit_2, response.content)
         self.assertIn(page_limit_3, response.content)
+
+    def test_should_show_keyword_projects_in_partner_site(self):
+        # Given
+        hostname = 'akvo'
+        partner_projects = False
+        org = Organisation.objects.create(name=hostname)
+        keyword = Keyword.objects.create(label=hostname)
+        partner_site = PartnerSite.objects.create(
+            hostname=hostname,
+            partner_projects=partner_projects,
+            organisation=org,
+            piwik_id=10,
+        )
+        partner_site.keywords.add(keyword)
+        project_title = '{} awesome project'.format(hostname)
+        project = Project.objects.create(title=project_title)
+        project.keywords.add(keyword)
+        project.publishingstatus.status = 'published'
+        project.publishingstatus.save()
+        url = '/en/projects/'
+        self.c = Client(HTTP_HOST='{}.{}'.format(hostname, settings.AKVOAPP_DOMAIN))
+
+        # When
+        response = self.c.get(url, follow=True)
+
+        # Then
+        self.assertIn(project_title, response.content)
+
+    def test_should_show_all_partner_projects(self):
+        # Given
+        hostname = 'akvo'
+        partner_projects = True
+        org = Organisation.objects.create(name=hostname)
+        PartnerSite.objects.create(
+            hostname=hostname,
+            partner_projects=partner_projects,
+            organisation=org,
+            piwik_id=10,
+        )
+        project_title1 = '{} awesome project {}'.format(hostname, 1)
+        project1 = Project.objects.create(title=project_title1)
+        project1.publishingstatus.status = 'published'
+        project1.publishingstatus.save()
+        Partnership.objects.create(organisation=org, project=project1)
+        project_title2 = '{} awesome project {}'.format(hostname, 2)
+        project2 = Project.objects.create(title=project_title2)
+        project2.publishingstatus.status = 'published'
+        project2.publishingstatus.save()
+        Partnership.objects.create(organisation=org, project=project2)
+        url = '/en/projects/'
+        self.c = Client(HTTP_HOST='{}.{}'.format(hostname, settings.AKVOAPP_DOMAIN))
+
+        # When
+        response = self.c.get(url, follow=True)
+
+        # Then
+        self.assertIn(project_title1, response.content)
+        self.assertIn(project_title2, response.content)
+
+    def test_should_show_partner_projects_with_keyword(self):
+        # Given
+        hostname = 'akvo'
+        partner_projects = True
+        org = Organisation.objects.create(name=hostname)
+        keyword = Keyword.objects.create(label=hostname)
+        partner_site = PartnerSite.objects.create(
+            hostname=hostname,
+            partner_projects=partner_projects,
+            organisation=org,
+            piwik_id=10,
+        )
+        partner_site.keywords.add(keyword)
+        project_title1 = '{} awesome project {}'.format(hostname, 1)
+        project1 = Project.objects.create(title=project_title1)
+        project1.keywords.add(keyword)
+        project1.publishingstatus.status = 'published'
+        project1.publishingstatus.save()
+        Partnership.objects.create(organisation=org, project=project1)
+        project_title2 = '{} awesome project {}'.format(hostname, 2)
+        project2 = Project.objects.create(title=project_title2)
+        project2.publishingstatus.status = 'published'
+        project2.publishingstatus.save()
+        Partnership.objects.create(organisation=org, project=project2)
+        url = '/en/projects/'
+        self.c = Client(HTTP_HOST='{}.{}'.format(hostname, settings.AKVOAPP_DOMAIN))
+
+        # When
+        response = self.c.get(url, follow=True)
+
+        # Then
+        self.assertIn(project_title1, response.content)
+        self.assertNotIn(project_title2, response.content)