diff --git a/.travis.yml b/.travis.yml index 11d7097..3cb8f75 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,15 @@ sudo: false language: python python: - '2.7' + - '3.5' cache: pip before_install: - pip install --upgrade pip install: + # FIXME: Test with released versions once they're available + - pip install git+https://github.com/praekelt/django-google-analytics.git@py3 + - pip install git+https://github.com/praekelt/molo.git@py3 + - pip install -r requirements-dev.txt - pip install coveralls script: diff --git a/molo/profiles/admin.py b/molo/profiles/admin.py index b9e628c..48fb2a4 100644 --- a/molo/profiles/admin.py +++ b/molo/profiles/admin.py @@ -1,4 +1,4 @@ -import csv +import sys from django.contrib import admin from django.http import HttpResponse @@ -11,6 +11,11 @@ from wagtail.contrib.modeladmin.options import ModelAdmin as WagtailModelAdmin from molo.profiles.admin_views import FrontendUsersAdminView +if sys.version_info[0] < 3: + from backports import csv +else: + import csv + try: admin.site.unregister(User) except NotRegistered: @@ -26,9 +31,6 @@ def download_as_csv(ProfileUserAdmin, request, queryset): field_names = user_model_fields + profile_fields writer.writerow(field_names) for obj in queryset: - if obj.profile.alias: - obj.profile.alias = obj.profile.alias.encode('utf-8') - obj.username = obj.username.encode('utf-8') obj.date_joined = obj.date_joined.strftime("%Y-%m-%d %H:%M") writer.writerow( [getattr(obj, field) for field in user_model_fields] + diff --git a/molo/profiles/admin_views.py b/molo/profiles/admin_views.py index 127df1c..816a822 100644 --- a/molo/profiles/admin_views.py +++ b/molo/profiles/admin_views.py @@ -1,8 +1,11 @@ +from __future__ import absolute_import + +from django.shortcuts import redirect +from django.utils.translation import ugettext as _ from wagtail.contrib.modeladmin.views import IndexView from wagtail.wagtailadmin import messages -from django.utils.translation import ugettext as _ -from task import send_export_email -from django.shortcuts import redirect + +from .task import send_export_email class FrontendUsersAdminView(IndexView): diff --git a/molo/profiles/tests/test_admin.py b/molo/profiles/tests/test_admin.py index 07b51a4..cf6c279 100644 --- a/molo/profiles/tests/test_admin.py +++ b/molo/profiles/tests/test_admin.py @@ -1,14 +1,25 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import cgi +import sys +from datetime import date + from django.test import TestCase, override_settings from django.contrib.auth.models import User from django.test.client import Client from django.core.urlresolvers import reverse -from datetime import date + from molo.core.tests.base import MoloTestCaseMixin from molo.core.models import Main, Languages, SiteLanguageRelation from molo.profiles.admin import ProfileUserAdmin, download_as_csv from molo.profiles.models import UserProfile +if sys.version_info[0] < 3: + from backports import csv +else: + import csv + class ModelsTestCase(TestCase, MoloTestCaseMixin): def setUp(self): @@ -25,6 +36,20 @@ def setUp(self): locale='en', is_active=True) + def assert_csv_download(self, response, filename, rows): + self.assertEquals(response.status_code, 200) + + self.assertEquals(response['Content-Type'], 'text/csv') + + disposition, params = cgi.parse_header(response['Content-Disposition']) + self.assertEquals(disposition, 'attachment') + self.assertEquals(params, {'filename': filename}) + + response_lines = response.content.decode('utf-8').split('\r\n') + self.assertEquals(response_lines.pop(), '') + + self.assertEquals([row for row in csv.reader(response_lines)], rows) + def test_download_csv(self): profile = self.user.profile profile.alias = 'The Alias' @@ -34,13 +59,14 @@ def test_download_csv(self): response = download_as_csv(ProfileUserAdmin(UserProfile, self.site), None, User.objects.all()) - date = str(self.user.date_joined.strftime("%Y-%m-%d %H:%M")) - expected_output = ('Content-Type: text/csv\r\nContent-Disposition: ' - 'attachment;filename=export.csv\r\n\r\nusername,' - 'email,first_name,last_name,is_staff,date_joined,' - 'alias,mobile_number\r\ntester,tester@example.com,' - ',,False,' + date + ',The Alias,+27784667723\r\n') - self.assertEquals(str(response), expected_output) + date = self.user.date_joined.strftime("%Y-%m-%d %H:%M") + + self.assert_csv_download(response, 'export.csv', [ + ['username', 'email', 'first_name', 'last_name', 'is_staff', + 'date_joined', 'alias', 'mobile_number'], + ['tester', 'tester@example.com', '', '', 'False', date, + 'The Alias', '+27784667723'], + ]) def test_download_csv_with_an_alias_contains_ascii_code(self): profile = self.user.profile @@ -51,14 +77,14 @@ def test_download_csv_with_an_alias_contains_ascii_code(self): response = download_as_csv(ProfileUserAdmin(UserProfile, self.site), None, User.objects.all()) - date = str(self.user.date_joined.strftime("%Y-%m-%d %H:%M")) - expected_output = ('Content-Type: text/csv\r\nContent-Disposition: ' - 'attachment;filename=export.csv\r\n\r\nusername,' - 'email,first_name,last_name,is_staff,date_joined,' - 'alias,mobile_number\r\ntester,tester@example.com,' - ',,False,' + date + ',The Alias \xf0\x9f\x98\x81,' - '+27784667723\r\n') - self.assertEquals(str(response), expected_output) + date = self.user.date_joined.strftime("%Y-%m-%d %H:%M") + + self.assert_csv_download(response, 'export.csv', [ + ['username', 'email', 'first_name', 'last_name', 'is_staff', + 'date_joined', 'alias', 'mobile_number'], + ['tester', 'tester@example.com', '', '', 'False', date, + 'The Alias 😁', '+27784667723'], + ]) def test_download_csv_with_an_username_contains_ascii_code(self): self.user.username = '사이네' @@ -67,14 +93,13 @@ def test_download_csv_with_an_username_contains_ascii_code(self): response = download_as_csv(ProfileUserAdmin(UserProfile, self.site), None, User.objects.all()) - date = str(self.user.date_joined.strftime("%Y-%m-%d %H:%M")) - expected_output = ('Content-Type: text/csv\r\nContent-Disposition: ' - 'attachment;filename=export.csv\r\n\r\nusername,' - 'email,first_name,last_name,is_staff,date_joined,' - 'alias,mobile_number\r\n\xec\x82\xac\xec\x9d\xb4' - '\xeb\x84\xa4,tester@example.com,' - ',,False,' + date + ',,\r\n') - self.assertEquals(str(response), expected_output) + date = self.user.date_joined.strftime("%Y-%m-%d %H:%M") + + self.assert_csv_download(response, 'export.csv', [ + ['username', 'email', 'first_name', 'last_name', 'is_staff', + 'date_joined', 'alias', 'mobile_number'], + ['사이네', 'tester@example.com', '', '', 'False', date, '', ''], + ]) class TestFrontendUsersAdminView(TestCase, MoloTestCaseMixin): diff --git a/molo/profiles/tests/test_user_info_task.py b/molo/profiles/tests/test_user_info_task.py index 2f2fcbb..bbab5d7 100644 --- a/molo/profiles/tests/test_user_info_task.py +++ b/molo/profiles/tests/test_user_info_task.py @@ -1,6 +1,5 @@ from datetime import datetime, timedelta import random -from itertools import izip from django.test import TestCase, override_settings from django.contrib.auth.models import User from molo.core.tests.base import MoloTestCaseMixin @@ -63,8 +62,8 @@ def setUp(self): # create the users count = 10 - for join_datetime, login_datetime in izip(join_datetimes, - login_datetimes): + for join_datetime, login_datetime in zip(join_datetimes, + login_datetimes): user = User.objects.create_user( username='tester' + str(count), email='tester' + str(count) + '@example.com', diff --git a/molo/profiles/tests/test_views.py b/molo/profiles/tests/test_views.py index 06d991a..ff0dac1 100644 --- a/molo/profiles/tests/test_views.py +++ b/molo/profiles/tests/test_views.py @@ -1509,8 +1509,8 @@ def test_view(self): self.assertTrue(isinstance(form, ForgotPasswordForm)) def test_unidentified_user_gets_error(self): - error_message = "The username and security question(s) combination " \ - "do not match." + error_message = b"The username and security question(s) combination " \ + b"do not match." response = self.client.post( reverse("molo.profiles:forgot_password"), { "username": "bogus", @@ -1520,8 +1520,8 @@ def test_unidentified_user_gets_error(self): self.failUnless(error_message in response.content) def test_suspended_user_gets_error(self): - error_message = "The username and security question(s) combination " \ - "do not match." + error_message = b"The username and security question(s) combination " \ + b"do not match." self.user.is_active = False self.user.save() response = self.client.post( @@ -1535,8 +1535,8 @@ def test_suspended_user_gets_error(self): self.user.save() def test_incorrect_security_answer_gets_error(self): - error_message = "The username and security question(s) combination " \ - "do not match." + error_message = b"The username and security question(s) combination " \ + b"do not match." response = self.client.post( reverse("molo.profiles:forgot_password"), { "username": "tester", @@ -1546,7 +1546,7 @@ def test_incorrect_security_answer_gets_error(self): self.failUnless(error_message in response.content) def test_too_many_retries_result_in_error(self): - error_message = "Too many attempts" + error_message = b"Too many attempts" site = Site.objects.get(is_default_site=True) profile_settings = UserProfilesSettings.for_site(site) @@ -1628,7 +1628,7 @@ def test_translated_question_appears_on_registration(self): # is asked self.client.get('/locale/en/') response = self.client.get(reverse("molo.profiles:forgot_password")) - self.failIf("How old are you in french" in response.content) + self.failIf(b"How old are you in french" in response.content) class ResetPasswordViewTest(TestCase, MoloTestCaseMixin): diff --git a/setup.py b/setup.py index 608f865..7e37ba2 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ import codecs import os +import sys from setuptools import setup, find_packages @@ -18,6 +19,8 @@ def read(*parts): 'django-import-export', 'django-phonenumber-field', ] +if sys.version_info[0] < 3: + install_requires.append('backports.csv') setup( name='molo.profiles',