Skip to content
This repository has been archived by the owner on Oct 27, 2022. It is now read-only.

WIP: Python 3 support #78

Open
wants to merge 6 commits into
base: packaging-updates
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 6 additions & 4 deletions molo/profiles/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import csv
import sys

from django.contrib import admin
from django.http import HttpResponse
Expand All @@ -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:
Expand All @@ -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] +
Expand Down
9 changes: 6 additions & 3 deletions molo/profiles/admin_views.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down
73 changes: 49 additions & 24 deletions molo/profiles/tests/test_admin.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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'
Expand All @@ -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,[email protected],'
',,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', '[email protected]', '', '', 'False', date,
'The Alias', '+27784667723'],
])

def test_download_csv_with_an_alias_contains_ascii_code(self):
profile = self.user.profile
Expand All @@ -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,[email protected],'
',,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', '[email protected]', '', '', 'False', date,
'The Alias 😁', '+27784667723'],
])

def test_download_csv_with_an_username_contains_ascii_code(self):
self.user.username = '사이네'
Expand All @@ -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,[email protected],'
',,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'],
['사이네', '[email protected]', '', '', 'False', date, '', ''],
])


class TestFrontendUsersAdminView(TestCase, MoloTestCaseMixin):
Expand Down
5 changes: 2 additions & 3 deletions molo/profiles/tests/test_user_info_task.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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',
Expand Down
16 changes: 8 additions & 8 deletions molo/profiles/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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(
Expand All @@ -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",
Expand All @@ -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)

Expand Down Expand Up @@ -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):
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import codecs
import os
import sys

from setuptools import setup, find_packages

Expand All @@ -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',
Expand Down