pyupgrade on lms edxnotes,email_marketing,experiments apps (#26531)

This commit is contained in:
M. Zulqarnain
2021-02-22 15:42:32 +05:00
committed by GitHub
parent f33f12bbea
commit 16f600910b
38 changed files with 408 additions and 479 deletions

View File

@@ -5,7 +5,6 @@ Decorators related to edXNotes.
import json
import six
from django.conf import settings
from common.djangoapps.edxmako.shortcuts import render_to_string
@@ -51,8 +50,8 @@ def edxnotes(cls):
),
"params": {
# Use camelCase to name keys.
"usageId": six.text_type(self.scope_ids.usage_id),
"courseId": six.text_type(self.runtime.course_id),
"usageId": str(self.scope_ids.usage_id),
"courseId": str(self.runtime.course_id),
"token": get_edxnotes_id_token(user),
"tokenUrl": get_token_url(self.runtime.course_id),
"endpoint": get_public_endpoint(),

View File

@@ -7,11 +7,10 @@ import json
import logging
from datetime import datetime
from json import JSONEncoder
from urllib.parse import parse_qs, urlencode, urlparse
from uuid import uuid4
import requests
import six
from six.moves.urllib.parse import urlencode, urlparse, parse_qs
from dateutil.parser import parse as dateutil_parse
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
@@ -21,6 +20,8 @@ from oauth2_provider.models import Application
from opaque_keys.edx.keys import UsageKey
from requests.exceptions import RequestException
from common.djangoapps.student.models import anonymous_id_for_user
from common.djangoapps.util.date_utils import get_default_time_display
from lms.djangoapps.courseware.access import has_access
from lms.djangoapps.courseware.courses import get_current_child
from lms.djangoapps.edxnotes.exceptions import EdxNotesParseError, EdxNotesServiceUnavailable
@@ -28,8 +29,6 @@ from lms.djangoapps.edxnotes.plugins import EdxNotesTab
from lms.lib.utils import get_parent_unit
from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_for_user
from openedx.core.djangolib.markup import Text
from common.djangoapps.student.models import anonymous_id_for_user
from common.djangoapps.util.date_utils import get_default_time_display
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
@@ -58,7 +57,7 @@ def get_edxnotes_id_token(user):
notes_application = Application.objects.get(name=settings.EDXNOTES_CLIENT_NAME)
except Application.DoesNotExist:
raise ImproperlyConfigured( # lint-amnesty, pylint: disable=raise-missing-from
u'OAuth2 Client with name [{}] does not exist.'.format(settings.EDXNOTES_CLIENT_NAME)
f'OAuth2 Client with name [{settings.EDXNOTES_CLIENT_NAME}] does not exist.'
)
return create_jwt_for_user(
user, secret=notes_application.client_secret, aud=notes_application.client_id
@@ -70,7 +69,7 @@ def get_token_url(course_id):
Returns token url for the course.
"""
return reverse("get_token", kwargs={
"course_id": six.text_type(course_id),
"course_id": str(course_id),
})
@@ -92,7 +91,7 @@ def send_request(user, course_id, page, page_size, path="", text=None):
url = get_internal_endpoint(path)
params = {
"user": anonymous_id_for_user(user, None),
"course_id": six.text_type(course_id),
"course_id": str(course_id),
"page": page,
"page_size": page_size,
}
@@ -113,7 +112,7 @@ def send_request(user, course_id, page, page_size, path="", text=None):
timeout=(settings.EDXNOTES_CONNECT_TIMEOUT, settings.EDXNOTES_READ_TIMEOUT)
)
except RequestException:
log.error(u"Failed to connect to edx-notes-api: url=%s, params=%s", url, str(params))
log.error("Failed to connect to edx-notes-api: url=%s, params=%s", url, str(params))
raise EdxNotesServiceUnavailable(_("EdxNotes Service is unavailable. Please try again in a few minutes.")) # lint-amnesty, pylint: disable=raise-missing-from
return response
@@ -144,7 +143,7 @@ def delete_all_notes_for_user(user):
timeout=(settings.EDXNOTES_CONNECT_TIMEOUT, settings.EDXNOTES_READ_TIMEOUT)
)
except RequestException:
log.error(u"Failed to connect to edx-notes-api: url=%s, params=%s", url, str(headers))
log.error("Failed to connect to edx-notes-api: url=%s, params=%s", url, str(headers))
raise EdxNotesServiceUnavailable(_("EdxNotes Service is unavailable. Please try again in a few minutes.")) # lint-amnesty, pylint: disable=raise-missing-from
return response
@@ -169,7 +168,7 @@ def preprocess_collection(user, course, collection):
with store.bulk_operations(course.id):
for model in collection:
update = {
u"updated": dateutil_parse(model["updated"]),
"updated": dateutil_parse(model["updated"]),
}
model.update(update)
@@ -186,22 +185,22 @@ def preprocess_collection(user, course, collection):
try:
item = store.get_item(usage_key)
except ItemNotFoundError:
log.debug(u"Module not found: %s", usage_key)
log.debug("Module not found: %s", usage_key)
continue
if not has_access(user, "load", item, course_key=course.id):
log.debug(u"User %s does not have an access to %s", user, item)
log.debug("User %s does not have an access to %s", user, item)
continue
unit = get_parent_unit(item)
if unit is None:
log.debug(u"Unit not found: %s", usage_key)
log.debug("Unit not found: %s", usage_key)
continue
if include_path_info:
section = unit.get_parent()
if not section:
log.debug(u"Section not found: %s", usage_key)
log.debug("Section not found: %s", usage_key)
continue
if section.location in list(cache.keys()):
usage_context = cache[section.location]
@@ -215,7 +214,7 @@ def preprocess_collection(user, course, collection):
chapter = section.get_parent()
if not chapter:
log.debug(u"Chapter not found: %s", usage_key)
log.debug("Chapter not found: %s", usage_key)
continue
if chapter.location in list(cache.keys()):
usage_context = cache[chapter.location]
@@ -248,7 +247,7 @@ def get_module_context(course, item):
Returns dispay_name and url for the parent module.
"""
item_dict = {
'location': six.text_type(item.location),
'location': str(item.location),
'display_name': Text(item.display_name_with_default),
}
if item.category == 'chapter' and item.get_parent():
@@ -260,15 +259,15 @@ def get_module_context(course, item):
section = item.get_parent()
chapter = section.get_parent()
# Position starts from 1, that's why we add 1.
position = get_index(six.text_type(item.location), section.children) + 1
position = get_index(str(item.location), section.children) + 1
item_dict['url'] = reverse('courseware_position', kwargs={
'course_id': six.text_type(course.id),
'course_id': str(course.id),
'chapter': chapter.url_name,
'section': section.url_name,
'position': position,
})
if item.category in ('chapter', 'sequential'):
item_dict['children'] = [six.text_type(child) for child in item.children]
item_dict['children'] = [str(child) for child in item.children]
return item_dict
@@ -277,7 +276,7 @@ def get_index(usage_key, children):
"""
Returns an index of the child with `usage_key`.
"""
children = [six.text_type(child) for child in children]
children = [str(child) for child in children]
return children.index(usage_key)
@@ -345,14 +344,14 @@ def get_notes(request, course, page=DEFAULT_PAGE, page_size=DEFAULT_PAGE_SIZE, t
try:
collection = json.loads(response.content.decode('utf-8'))
except ValueError:
log.error(u"Invalid JSON response received from notes api: response_content=%s", response.content)
log.error("Invalid JSON response received from notes api: response_content=%s", response.content)
raise EdxNotesParseError(_("Invalid JSON response received from notes api.")) # lint-amnesty, pylint: disable=raise-missing-from
# Verify response dict structure
expected_keys = ['total', 'rows', 'num_pages', 'start', 'next', 'previous', 'current_page']
keys = list(collection.keys())
if not keys or not all(key in expected_keys for key in keys):
log.error(u"Incorrect data received from notes api: collection_data=%s", str(collection))
log.error("Incorrect data received from notes api: collection_data=%s", str(collection))
raise EdxNotesParseError(_("Incorrect data received from notes api."))
filtered_results = preprocess_collection(request.user, course, collection['rows'])
@@ -419,7 +418,7 @@ def get_course_position(course_module):
If there is no current position in the course or chapter, then selects
the first child.
"""
urlargs = {'course_id': six.text_type(course_module.id)}
urlargs = {'course_id': str(course_module.id)}
chapter = get_current_child(course_module, min_depth=1)
if chapter is None:
log.debug("No chapter found when loading current position in course")

View File

@@ -26,7 +26,7 @@ class EdxNotesTab(EnrolledTab):
course (CourseDescriptor): the course using the feature
user (User): the user interacting with the course
"""
if not super(EdxNotesTab, cls).is_enabled(course, user=user):
if not super().is_enabled(course, user=user):
return False
if not settings.FEATURES.get("ENABLE_EDXNOTES"):

View File

@@ -6,39 +6,39 @@ import json
from contextlib import contextmanager
from datetime import datetime
from unittest import skipUnless
import pytest
from unittest.mock import MagicMock, patch
import ddt
import jwt
import pytest
import six
from six import text_type
from six.moves.urllib.parse import urlparse, parse_qs
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import ImproperlyConfigured
from django.test.client import RequestFactory
from django.test.utils import override_settings
from django.urls import reverse
from mock import MagicMock, patch
from oauth2_provider.models import Application
from six.moves.urllib.parse import parse_qs, urlparse
from common.djangoapps.edxmako.shortcuts import render_to_string
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, SuperuserFactory, UserFactory
from lms.djangoapps.courseware.model_data import FieldDataCache
from lms.djangoapps.courseware.module_render import get_module_for_descriptor
from lms.djangoapps.courseware.tabs import get_course_tab_list
from common.djangoapps.edxmako.shortcuts import render_to_string
from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_for_user
from openedx.core.djangoapps.oauth_dispatch.tests.factories import ApplicationFactory
from openedx.core.djangoapps.user_api.models import RetirementState, UserRetirementStatus
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.tabs import CourseTab
from . import helpers
from .decorators import edxnotes
from .exceptions import EdxNotesParseError, EdxNotesServiceUnavailable
from .plugins import EdxNotesTab
from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_for_user # lint-amnesty, pylint: disable=wrong-import-order
from openedx.core.djangoapps.oauth_dispatch.tests.factories import ApplicationFactory # lint-amnesty, pylint: disable=wrong-import-order
from openedx.core.djangoapps.user_api.models import RetirementState, UserRetirementStatus # lint-amnesty, pylint: disable=wrong-import-order
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, SuperuserFactory, UserFactory # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.tabs import CourseTab # lint-amnesty, pylint: disable=wrong-import-order
FEATURES = settings.FEATURES.copy()
@@ -72,7 +72,7 @@ def enable_edxnotes_for_the_course(course, user_id):
@edxnotes
class TestProblem(object):
class TestProblem:
"""
Test class (fake problem) decorated by edxnotes decorator.
@@ -100,7 +100,7 @@ class EdxNotesDecoratorTest(ModuleStoreTestCase):
"""
def setUp(self):
super(EdxNotesDecoratorTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
ApplicationFactory(name="edx-notes")
# Using old mongo because of locator comparison issues (see longer
@@ -202,7 +202,7 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
"""
Setup a dummy course content.
"""
super(EdxNotesHelpersTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
# There are many tests that are comparing locators as returned from helper methods. When using
# the split modulestore, some of those locators have version and branch information, but the
@@ -324,23 +324,22 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
"num_pages": 1,
"rows": [
{
u"quote": u"quote text",
u"text": u"text",
u"usage_id": text_type(self.html_module_1.location),
u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat(),
"quote": "quote text",
"text": "text",
"usage_id": str(self.html_module_1.location),
"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat(),
},
{
u"quote": u"quote text",
u"text": u"text",
u"usage_id": text_type(self.html_module_2.location),
u"updated": datetime(2014, 11, 19, 8, 6, 16, 00000).isoformat(),
"quote": "quote text",
"text": "text",
"usage_id": str(self.html_module_2.location),
"updated": datetime(2014, 11, 19, 8, 6, 16, 00000).isoformat(),
}
]
}
).encode('utf-8')
six.assertCountEqual(
self,
assert len(
{
"count": 2,
"current_page": 1,
@@ -350,57 +349,55 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
"num_pages": 1,
"results": [
{
u"quote": u"quote text",
u"text": u"text",
u"chapter": {
u"display_name": self.chapter.display_name_with_default,
u"index": 0,
u"location": text_type(self.chapter.location),
u"children": [text_type(self.sequential.location)]
"quote": "quote text",
"text": "text",
"chapter": {
"display_name": self.chapter.display_name_with_default,
"index": 0,
"location": str(self.chapter.location),
"children": [str(self.sequential.location)]
},
u"section": {
u"display_name": self.sequential.display_name_with_default,
u"location": text_type(self.sequential.location),
u"children": [
text_type(self.vertical.location), text_type(self.vertical_with_container.location)
"section": {
"display_name": self.sequential.display_name_with_default,
"location": str(self.sequential.location),
"children": [
str(self.vertical.location), str(self.vertical_with_container.location)
]
},
u"unit": {
u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
u"display_name": self.vertical.display_name_with_default,
u"location": text_type(self.vertical.location),
"unit": {
"url": self._get_unit_url(self.course, self.chapter, self.sequential),
"display_name": self.vertical.display_name_with_default,
"location": str(self.vertical.location),
},
u"usage_id": text_type(self.html_module_2.location),
u"updated": "Nov 19, 2014 at 08:06 UTC",
"usage_id": str(self.html_module_2.location),
"updated": "Nov 19, 2014 at 08:06 UTC",
},
{
u"quote": u"quote text",
u"text": u"text",
u"chapter": {
u"display_name": self.chapter.display_name_with_default,
u"index": 0,
u"location": text_type(self.chapter.location),
u"children": [text_type(self.sequential.location)]
"quote": "quote text",
"text": "text",
"chapter": {
"display_name": self.chapter.display_name_with_default,
"index": 0,
"location": str(self.chapter.location),
"children": [str(self.sequential.location)]
},
u"section": {
u"display_name": self.sequential.display_name_with_default,
u"location": text_type(self.sequential.location),
u"children": [
text_type(self.vertical.location),
text_type(self.vertical_with_container.location)]
"section": {
"display_name": self.sequential.display_name_with_default,
"location": str(self.sequential.location),
"children": [
str(self.vertical.location),
str(self.vertical_with_container.location)]
},
u"unit": {
u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
u"display_name": self.vertical.display_name_with_default,
u"location": text_type(self.vertical.location),
"unit": {
"url": self._get_unit_url(self.course, self.chapter, self.sequential),
"display_name": self.vertical.display_name_with_default,
"location": str(self.vertical.location),
},
u"usage_id": text_type(self.html_module_1.location),
u"updated": "Nov 19, 2014 at 08:05 UTC",
"usage_id": str(self.html_module_1.location),
"updated": "Nov 19, 2014 at 08:05 UTC",
},
]
},
helpers.get_notes(self.request, self.course)
)
}) == len(helpers.get_notes(self.request, self.course))
@patch("lms.djangoapps.edxnotes.helpers.requests.get", autospec=True)
def test_get_notes_json_error(self, mock_get):
@@ -432,22 +429,21 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
"num_pages": 1,
"rows": [
{
u"quote": u"quote text",
u"text": u"text",
u"usage_id": text_type(self.html_module_1.location),
u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat(),
"quote": "quote text",
"text": "text",
"usage_id": str(self.html_module_1.location),
"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat(),
},
{
u"quote": u"quote text",
u"text": u"text",
u"usage_id": text_type(self.html_module_2.location),
u"updated": datetime(2014, 11, 19, 8, 6, 16, 00000).isoformat(),
"quote": "quote text",
"text": "text",
"usage_id": str(self.html_module_2.location),
"updated": datetime(2014, 11, 19, 8, 6, 16, 00000).isoformat(),
}
]
}).encode('utf-8')
six.assertCountEqual(
self,
assert len(
{
"count": 2,
"current_page": 1,
@@ -457,57 +453,55 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
"num_pages": 1,
"results": [
{
u"quote": u"quote text",
u"text": u"text",
u"chapter": {
u"display_name": self.chapter.display_name_with_default,
u"index": 0,
u"location": text_type(self.chapter.location),
u"children": [text_type(self.sequential.location)]
"quote": "quote text",
"text": "text",
"chapter": {
"display_name": self.chapter.display_name_with_default,
"index": 0,
"location": str(self.chapter.location),
"children": [str(self.sequential.location)]
},
u"section": {
u"display_name": self.sequential.display_name_with_default,
u"location": text_type(self.sequential.location),
u"children": [
text_type(self.vertical.location),
text_type(self.vertical_with_container.location)]
"section": {
"display_name": self.sequential.display_name_with_default,
"location": str(self.sequential.location),
"children": [
str(self.vertical.location),
str(self.vertical_with_container.location)]
},
u"unit": {
u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
u"display_name": self.vertical.display_name_with_default,
u"location": text_type(self.vertical.location),
"unit": {
"url": self._get_unit_url(self.course, self.chapter, self.sequential),
"display_name": self.vertical.display_name_with_default,
"location": str(self.vertical.location),
},
u"usage_id": text_type(self.html_module_2.location),
u"updated": "Nov 19, 2014 at 08:06 UTC",
"usage_id": str(self.html_module_2.location),
"updated": "Nov 19, 2014 at 08:06 UTC",
},
{
u"quote": u"quote text",
u"text": u"text",
u"chapter": {
u"display_name": self.chapter.display_name_with_default,
u"index": 0,
u"location": text_type(self.chapter.location),
u"children": [text_type(self.sequential.location)]
"quote": "quote text",
"text": "text",
"chapter": {
"display_name": self.chapter.display_name_with_default,
"index": 0,
"location": str(self.chapter.location),
"children": [str(self.sequential.location)]
},
u"section": {
u"display_name": self.sequential.display_name_with_default,
u"location": text_type(self.sequential.location),
u"children": [
text_type(self.vertical.location),
text_type(self.vertical_with_container.location)]
"section": {
"display_name": self.sequential.display_name_with_default,
"location": str(self.sequential.location),
"children": [
str(self.vertical.location),
str(self.vertical_with_container.location)]
},
u"unit": {
u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
u"display_name": self.vertical.display_name_with_default,
u"location": text_type(self.vertical.location),
"unit": {
"url": self._get_unit_url(self.course, self.chapter, self.sequential),
"display_name": self.vertical.display_name_with_default,
"location": str(self.vertical.location),
},
u"usage_id": text_type(self.html_module_1.location),
u"updated": "Nov 19, 2014 at 08:05 UTC",
"usage_id": str(self.html_module_1.location),
"updated": "Nov 19, 2014 at 08:05 UTC",
},
]
},
helpers.get_notes(self.request, self.course)
)
}) == len(helpers.get_notes(self.request, self.course))
@patch("lms.djangoapps.edxnotes.helpers.requests.get", autospec=True)
def test_search_json_error(self, mock_get):
@@ -531,11 +525,7 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
Tests no results.
"""
mock_get.return_value.content = json.dumps(NOTES_API_EMPTY_RESPONSE).encode('utf-8')
six.assertCountEqual(
self,
NOTES_VIEW_EMPTY_RESPONSE,
helpers.get_notes(self.request, self.course)
)
assert len(NOTES_VIEW_EMPTY_RESPONSE) == len(helpers.get_notes(self.request, self.course))
@override_settings(EDXNOTES_PUBLIC_API="http://example.com")
@override_settings(EDXNOTES_INTERNAL_API="http://example.com")
@@ -566,45 +556,42 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
"""
initial_collection = [
{
u"quote": u"quote text",
u"text": u"text",
u"usage_id": text_type(self.html_module_1.location),
u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat()
"quote": "quote text",
"text": "text",
"usage_id": str(self.html_module_1.location),
"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat()
},
{
u"quote": u"quote text",
u"text": u"text",
u"usage_id": text_type(self.course.id.make_usage_key("html", "test_item")),
u"updated": datetime(2014, 11, 19, 8, 6, 16, 00000).isoformat()
"quote": "quote text",
"text": "text",
"usage_id": str(self.course.id.make_usage_key("html", "test_item")),
"updated": datetime(2014, 11, 19, 8, 6, 16, 00000).isoformat()
},
]
six.assertCountEqual(
self,
assert len(
[{
u"quote": u"quote text",
u"text": u"text",
u"chapter": {
u"display_name": self.chapter.display_name_with_default,
u"index": 0,
u"location": text_type(self.chapter.location),
u"children": [text_type(self.sequential.location)]
"quote": "quote text",
"text": "text",
"chapter": {
"display_name": self.chapter.display_name_with_default,
"index": 0,
"location": str(self.chapter.location),
"children": [str(self.sequential.location)]
},
u"section": {
u"display_name": self.sequential.display_name_with_default,
u"location": text_type(self.sequential.location),
u"children": [text_type(self.vertical.location), text_type(self.vertical_with_container.location)]
"section": {
"display_name": self.sequential.display_name_with_default,
"location": str(self.sequential.location),
"children": [str(self.vertical.location), str(self.vertical_with_container.location)]
},
u"unit": {
u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
u"display_name": self.vertical.display_name_with_default,
u"location": text_type(self.vertical.location),
"unit": {
"url": self._get_unit_url(self.course, self.chapter, self.sequential),
"display_name": self.vertical.display_name_with_default,
"location": str(self.vertical.location),
},
u"usage_id": text_type(self.html_module_1.location),
u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000),
}],
helpers.preprocess_collection(self.user, self.course, initial_collection)
)
"usage_id": str(self.html_module_1.location),
"updated": datetime(2014, 11, 19, 8, 5, 16, 00000),
}]) == len(helpers.preprocess_collection(self.user, self.course, initial_collection))
def test_preprocess_collection_has_access(self):
"""
@@ -612,46 +599,43 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
"""
initial_collection = [
{
u"quote": u"quote text",
u"text": u"text",
u"usage_id": text_type(self.html_module_1.location),
u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat(),
"quote": "quote text",
"text": "text",
"usage_id": str(self.html_module_1.location),
"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat(),
},
{
u"quote": u"quote text",
u"text": u"text",
u"usage_id": text_type(self.html_module_2.location),
u"updated": datetime(2014, 11, 19, 8, 6, 16, 00000).isoformat(),
"quote": "quote text",
"text": "text",
"usage_id": str(self.html_module_2.location),
"updated": datetime(2014, 11, 19, 8, 6, 16, 00000).isoformat(),
},
]
self.html_module_2.visible_to_staff_only = True
self.store.update_item(self.html_module_2, self.user.id)
six.assertCountEqual(
self,
assert len(
[{
u"quote": u"quote text",
u"text": u"text",
u"chapter": {
u"display_name": self.chapter.display_name_with_default,
u"index": 0,
u"location": text_type(self.chapter.location),
u"children": [text_type(self.sequential.location)]
"quote": "quote text",
"text": "text",
"chapter": {
"display_name": self.chapter.display_name_with_default,
"index": 0,
"location": str(self.chapter.location),
"children": [str(self.sequential.location)]
},
u"section": {
u"display_name": self.sequential.display_name_with_default,
u"location": text_type(self.sequential.location),
u"children": [text_type(self.vertical.location), text_type(self.vertical_with_container.location)]
"section": {
"display_name": self.sequential.display_name_with_default,
"location": str(self.sequential.location),
"children": [str(self.vertical.location), str(self.vertical_with_container.location)]
},
u"unit": {
u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
u"display_name": self.vertical.display_name_with_default,
u"location": text_type(self.vertical.location),
"unit": {
"url": self._get_unit_url(self.course, self.chapter, self.sequential),
"display_name": self.vertical.display_name_with_default,
"location": str(self.vertical.location),
},
u"usage_id": text_type(self.html_module_1.location),
u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000),
}],
helpers.preprocess_collection(self.user, self.course, initial_collection)
)
"usage_id": str(self.html_module_1.location),
"updated": datetime(2014, 11, 19, 8, 5, 16, 00000),
}]) == len(helpers.preprocess_collection(self.user, self.course, initial_collection))
@patch("lms.djangoapps.edxnotes.helpers.has_access", autospec=True)
@patch("lms.djangoapps.edxnotes.helpers.modulestore", autospec=True)
@@ -664,17 +648,13 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
mock_modulestore.return_value = store
mock_has_access.return_value = True
initial_collection = [{
u"quote": u"quote text",
u"text": u"text",
u"usage_id": text_type(self.html_module_1.location),
u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat(),
"quote": "quote text",
"text": "text",
"usage_id": str(self.html_module_1.location),
"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat(),
}]
six.assertCountEqual(
self,
[],
helpers.preprocess_collection(self.user, self.course, initial_collection)
)
assert not helpers.preprocess_collection(self.user, self.course, initial_collection)
@override_settings(NOTES_DISABLED_TABS=['course_structure', 'tags'])
def test_preprocess_collection_with_disabled_tabs(self, ):
@@ -683,52 +663,49 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
"""
initial_collection = [
{
u"quote": u"quote text1",
u"text": u"text1",
u"usage_id": text_type(self.html_module_1.location),
u"updated": datetime(2016, 1, 26, 8, 5, 16, 00000).isoformat(),
"quote": "quote text1",
"text": "text1",
"usage_id": str(self.html_module_1.location),
"updated": datetime(2016, 1, 26, 8, 5, 16, 00000).isoformat(),
},
{
u"quote": u"quote text2",
u"text": u"text2",
u"usage_id": text_type(self.html_module_2.location),
u"updated": datetime(2016, 1, 26, 9, 6, 17, 00000).isoformat(),
"quote": "quote text2",
"text": "text2",
"usage_id": str(self.html_module_2.location),
"updated": datetime(2016, 1, 26, 9, 6, 17, 00000).isoformat(),
},
]
six.assertCountEqual(
self,
assert len(
[
{
'section': {},
'chapter': {},
"unit": {
u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
u"display_name": self.vertical.display_name_with_default,
u"location": text_type(self.vertical.location),
"url": self._get_unit_url(self.course, self.chapter, self.sequential),
"display_name": self.vertical.display_name_with_default,
"location": str(self.vertical.location),
},
u'text': u'text1',
u'quote': u'quote text1',
u'usage_id': text_type(self.html_module_1.location),
u'updated': datetime(2016, 1, 26, 8, 5, 16)
'text': 'text1',
'quote': 'quote text1',
'usage_id': str(self.html_module_1.location),
'updated': datetime(2016, 1, 26, 8, 5, 16)
},
{
'section': {},
'chapter': {},
"unit": {
u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
u"display_name": self.vertical.display_name_with_default,
u"location": text_type(self.vertical.location),
"url": self._get_unit_url(self.course, self.chapter, self.sequential),
"display_name": self.vertical.display_name_with_default,
"location": str(self.vertical.location),
},
u'text': u'text2',
u'quote': u'quote text2',
u'usage_id': text_type(self.html_module_2.location),
u'updated': datetime(2016, 1, 26, 9, 6, 17)
'text': 'text2',
'quote': 'quote text2',
'usage_id': str(self.html_module_2.location),
'updated': datetime(2016, 1, 26, 9, 6, 17)
}
],
helpers.preprocess_collection(self.user, self.course, initial_collection)
)
]) == len(helpers.preprocess_collection(self.user, self.course, initial_collection))
def test_get_module_context_sequential(self):
"""
@@ -736,9 +713,9 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
"""
self.assertDictEqual(
{
u"display_name": self.sequential.display_name_with_default,
u"location": text_type(self.sequential.location),
u"children": [text_type(self.vertical.location), text_type(self.vertical_with_container.location)],
"display_name": self.sequential.display_name_with_default,
"location": str(self.sequential.location),
"children": [str(self.vertical.location), str(self.vertical_with_container.location)],
},
helpers.get_module_context(self.course, self.sequential)
)
@@ -749,8 +726,8 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
"""
self.assertDictEqual(
{
u"display_name": self.html_module_1.display_name_with_default,
u"location": text_type(self.html_module_1.location),
"display_name": self.html_module_1.display_name_with_default,
"location": str(self.html_module_1.location),
},
helpers.get_module_context(self.course, self.html_module_1)
)
@@ -761,19 +738,19 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
"""
self.assertDictEqual(
{
u"display_name": self.chapter.display_name_with_default,
u"index": 0,
u"location": text_type(self.chapter.location),
u"children": [text_type(self.sequential.location)],
"display_name": self.chapter.display_name_with_default,
"index": 0,
"location": str(self.chapter.location),
"children": [str(self.sequential.location)],
},
helpers.get_module_context(self.course, self.chapter)
)
self.assertDictEqual(
{
u"display_name": self.chapter_2.display_name_with_default,
u"index": 1,
u"location": text_type(self.chapter_2.location),
u"children": [],
"display_name": self.chapter_2.display_name_with_default,
"index": 1,
"location": str(self.chapter_2.location),
"children": [],
},
helpers.get_module_context(self.course, self.chapter_2)
)
@@ -804,7 +781,7 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
},
params={
"user": "anonymous_id",
"course_id": text_type(self.course.id),
"course_id": str(self.course.id),
"text": "text",
"highlight": True,
'page': 1,
@@ -834,7 +811,7 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
},
params={
"user": "anonymous_id",
"course_id": text_type(self.course.id),
"course_id": str(self.course.id),
'page': helpers.DEFAULT_PAGE,
'page_size': helpers.DEFAULT_PAGE_SIZE,
},
@@ -865,7 +842,7 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
assert helpers.get_course_position(mock_course_module) == {
'display_name': 'Test Chapter Display Name',
'url': '/courses/{}/courseware/chapter_url_name/'.format(self.course.id)
'url': f'/courses/{self.course.id}/courseware/chapter_url_name/',
}
def test_get_course_position_no_section(self):
@@ -896,7 +873,7 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
assert helpers.get_course_position(mock_course_module) == {
'display_name': 'Test Section Display Name',
'url': '/courses/{}/courseware/chapter_url_name/section_url_name/'.format(self.course.id)
'url': f'/courses/{self.course.id}/courseware/chapter_url_name/section_url_name/',
}
def test_get_index(self):
@@ -904,8 +881,8 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
Tests `get_index` method returns unit url.
"""
children = self.sequential.children
assert 0 == helpers.get_index(text_type(self.vertical.location), children)
assert 1 == helpers.get_index(text_type(self.vertical_with_container.location), children)
assert 0 == helpers.get_index(str(self.vertical.location), children)
assert 1 == helpers.get_index(str(self.vertical_with_container.location), children)
@ddt.unpack
@ddt.data(
@@ -930,7 +907,7 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
host = 'https://' + self.request.get_host()
else:
host = 'http://' + self.request.get_host()
notes_url = host + reverse("notes", args=[text_type(self.course.id)])
notes_url = host + reverse("notes", args=[str(self.course.id)])
def verify_url(constructed, expected):
"""
@@ -975,15 +952,15 @@ class EdxNotesViewsTest(ModuleStoreTestCase):
"""
def setUp(self):
ApplicationFactory(name="edx-notes")
super(EdxNotesViewsTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.course = CourseFactory(edxnotes=True)
self.user = UserFactory()
CourseEnrollmentFactory(user=self.user, course_id=self.course.id) # lint-amnesty, pylint: disable=no-member
self.client.login(username=self.user.username, password=UserFactory._DEFAULT_PASSWORD) # lint-amnesty, pylint: disable=protected-access
self.notes_page_url = reverse("edxnotes", args=[text_type(self.course.id)]) # lint-amnesty, pylint: disable=no-member
self.notes_url = reverse("notes", args=[text_type(self.course.id)]) # lint-amnesty, pylint: disable=no-member
self.get_token_url = reverse("get_token", args=[text_type(self.course.id)]) # lint-amnesty, pylint: disable=no-member
self.visibility_url = reverse("edxnotes_visibility", args=[text_type(self.course.id)]) # lint-amnesty, pylint: disable=no-member
self.notes_page_url = reverse("edxnotes", args=[str(self.course.id)]) # lint-amnesty, pylint: disable=no-member
self.notes_url = reverse("notes", args=[str(self.course.id)]) # lint-amnesty, pylint: disable=no-member
self.get_token_url = reverse("get_token", args=[str(self.course.id)]) # lint-amnesty, pylint: disable=no-member
self.visibility_url = reverse("edxnotes_visibility", args=[str(self.course.id)]) # lint-amnesty, pylint: disable=no-member
def _get_course_module(self):
"""
@@ -1167,7 +1144,7 @@ class EdxNotesRetireAPITest(ModuleStoreTestCase):
"""
def setUp(self):
ApplicationFactory(name="edx-notes")
super(EdxNotesRetireAPITest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
# setup relevant states
RetirementState.objects.create(state_name='PENDING', state_execution_order=1)
@@ -1282,7 +1259,7 @@ class EdxNotesPluginTest(ModuleStoreTestCase):
"""
def setUp(self):
super(EdxNotesPluginTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.course = CourseFactory.create(edxnotes=True)
self.user = UserFactory()
CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id)

View File

@@ -16,12 +16,12 @@ from opaque_keys.edx.keys import CourseKey
from rest_framework import permissions, status
from rest_framework.response import Response
from rest_framework.views import APIView
from six import text_type
from common.djangoapps.edxmako.shortcuts import render_to_response
from common.djangoapps.util.json_request import JsonResponse, JsonResponseBadRequest
from lms.djangoapps.courseware.courses import get_course_with_access
from lms.djangoapps.courseware.model_data import FieldDataCache
from lms.djangoapps.courseware.module_render import get_module_for_descriptor
from common.djangoapps.edxmako.shortcuts import render_to_response
from lms.djangoapps.edxnotes.exceptions import EdxNotesParseError, EdxNotesServiceUnavailable
from lms.djangoapps.edxnotes.helpers import (
DEFAULT_PAGE,
@@ -35,7 +35,6 @@ from lms.djangoapps.edxnotes.helpers import (
)
from openedx.core.djangoapps.user_api.accounts.permissions import CanRetireUser
from openedx.core.djangoapps.user_api.models import RetirementStateError, UserRetirementStatus
from common.djangoapps.util.json_request import JsonResponse, JsonResponseBadRequest
log = logging.getLogger(__name__)
@@ -175,7 +174,7 @@ def notes(request, course_id):
text=text
)
except (EdxNotesParseError, EdxNotesServiceUnavailable) as err:
return JsonResponseBadRequest({"error": text_type(err)}, status=500)
return JsonResponseBadRequest({"error": str(err)}, status=500)
return HttpResponse(json.dumps(notes_info, cls=NoteJSONEncoder), content_type="application/json") # lint-amnesty, pylint: disable=http-response-with-content-type-json, http-response-with-json-dumps
@@ -210,7 +209,7 @@ def edxnotes_visibility(request, course_id):
return JsonResponse(status=200)
except (ValueError, KeyError):
log.warning(
u"Could not decode request body as JSON and find a boolean visibility field: '%s'", request.body
"Could not decode request body as JSON and find a boolean visibility field: '%s'", request.body
)
return JsonResponseBadRequest()
@@ -259,8 +258,8 @@ class RetireUserView(APIView):
except UserRetirementStatus.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
except RetirementStateError as exc:
return Response(text_type(exc), status=status.HTTP_405_METHOD_NOT_ALLOWED)
return Response(str(exc), status=status.HTTP_405_METHOD_NOT_ALLOWED)
except Exception as exc: # pylint: disable=broad-except
return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(str(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(status=status.HTTP_204_NO_CONTENT)

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.12 on 2018-04-25 12:00

View File

@@ -17,7 +17,7 @@ class EmailMarketingConfiguration(ConfigurationModel):
.. no_pii:
"""
class Meta(object):
class Meta:
app_label = "email_marketing"
sailthru_key = models.fields.CharField(
@@ -169,5 +169,5 @@ class EmailMarketingConfiguration(ConfigurationModel):
)
def __str__(self):
return u"Email marketing configuration: New user list %s, Welcome template: %s" % \
return "Email marketing configuration: New user list %s, Welcome template: %s" % \
(self.sailthru_new_user_list, self.sailthru_welcome_template)

View File

@@ -6,18 +6,22 @@ This module contains signals needed for email integration
import datetime
import logging
from random import randint
from typing import Dict, Any, Optional, Tuple
from typing import Any, Dict, Optional, Tuple
import crum
from celery.exceptions import TimeoutError as CeleryTimeoutError
from django.conf import settings
from django.dispatch import receiver
from edx_toggles.toggles import LegacyWaffleSwitchNamespace
from sailthru.sailthru_error import SailthruClientError
from six import text_type
from common.djangoapps import third_party_auth
from common.djangoapps.course_modes.models import CourseMode
from edx_toggles.toggles import LegacyWaffleSwitchNamespace # lint-amnesty, pylint: disable=wrong-import-order
from common.djangoapps.student.helpers import does_user_profile_exist
from common.djangoapps.student.models import UserProfile
from common.djangoapps.student.signals import SAILTHRU_AUDIT_PURCHASE
from common.djangoapps.track import segment
from common.djangoapps.util.model_utils import USER_FIELD_CHANGED, USER_FIELDS_CHANGED
from lms.djangoapps.email_marketing.tasks import (
get_email_cookies_via_sailthru,
update_course_enrollment,
@@ -27,11 +31,6 @@ from lms.djangoapps.email_marketing.tasks import (
from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY
from openedx.core.djangoapps.user_authn.cookies import CREATE_LOGON_COOKIE
from openedx.core.djangoapps.user_authn.views.register import REGISTER_USER
from common.djangoapps.student.helpers import does_user_profile_exist
from common.djangoapps.student.signals import SAILTHRU_AUDIT_PURCHASE
from common.djangoapps.student.models import UserProfile
from common.djangoapps.track import segment
from common.djangoapps.util.model_utils import USER_FIELD_CHANGED, USER_FIELDS_CHANGED
from .models import EmailMarketingConfiguration
@@ -109,17 +108,17 @@ def add_email_marketing_cookies(sender, response=None, user=None,
_log_sailthru_api_call_time(time_before_call)
except CeleryTimeoutError as exc:
log.error(u"Timeout error while attempting to obtain cookie from Sailthru: %s", text_type(exc))
log.error("Timeout error while attempting to obtain cookie from Sailthru: %s", str(exc))
return response
except SailthruClientError as exc:
log.error(u"Exception attempting to obtain cookie from Sailthru: %s", text_type(exc))
log.error("Exception attempting to obtain cookie from Sailthru: %s", str(exc))
return response
except Exception: # lint-amnesty, pylint: disable=broad-except
log.error(u"Exception Connecting to celery task for %s", user.email)
log.error("Exception Connecting to celery task for %s", user.email)
return response
if not cookie:
log.error(u"No cookie returned attempting to obtain cookie from Sailthru for %s", user.email)
log.error("No cookie returned attempting to obtain cookie from Sailthru for %s", user.email)
return response
else:
response.set_cookie(
@@ -130,7 +129,7 @@ def add_email_marketing_cookies(sender, response=None, user=None,
path='/',
secure=request.is_secure()
)
log.info(u"sailthru_hid cookie:%s successfully retrieved for user %s", cookie, user.email)
log.info("sailthru_hid cookie:%s successfully retrieved for user %s", cookie, user.email)
return response
@@ -287,7 +286,7 @@ def _create_sailthru_user_vars(user, profile, registration=None):
if profile.year_of_birth:
sailthru_vars['year_of_birth'] = profile.year_of_birth
sailthru_vars['country'] = text_type(profile.country.code)
sailthru_vars['country'] = str(profile.country.code)
if registration:
sailthru_vars['activation_key'] = registration.activation_key
@@ -315,7 +314,7 @@ def _log_sailthru_api_call_time(time_before_call):
time_after_call = datetime.datetime.now()
delta_sailthru_api_call_time = time_after_call - time_before_call
log.info(u"Started at %s and ended at %s, time spent:%s milliseconds",
log.info("Started at %s and ended at %s, time spent:%s milliseconds",
time_before_call.isoformat(' '),
time_after_call.isoformat(' '),
delta_sailthru_api_call_time.microseconds / 1000)

View File

@@ -7,7 +7,6 @@ import logging
import time
from datetime import datetime, timedelta
import six
from celery import shared_task
from django.conf import settings
from django.core.cache import cache
@@ -40,13 +39,13 @@ def get_email_cookies_via_sailthru(self, user_email, post_parms): # lint-amnest
try:
sailthru_client = SailthruClient(email_config.sailthru_key, email_config.sailthru_secret)
log.info(
u'Sending to Sailthru the user interest cookie [%s] for user [%s]',
'Sending to Sailthru the user interest cookie [%s] for user [%s]',
post_parms.get('cookies', ''),
user_email
)
sailthru_response = sailthru_client.api_post("user", post_parms)
except SailthruClientError as exc:
log.error(u"Exception attempting to obtain cookie from Sailthru: %s", six.text_type(exc))
log.error("Exception attempting to obtain cookie from Sailthru: %s", str(exc))
raise SailthruClientError # lint-amnesty, pylint: disable=raise-missing-from
if sailthru_response.is_ok():
@@ -54,11 +53,11 @@ def get_email_cookies_via_sailthru(self, user_email, post_parms): # lint-amnest
cookie = sailthru_response.json['keys']['cookie']
return cookie
else:
log.error(u"No cookie returned attempting to obtain cookie from Sailthru for %s", user_email)
log.error("No cookie returned attempting to obtain cookie from Sailthru for %s", user_email)
else:
error = sailthru_response.get_error()
# generally invalid email address
log.info(u"Error attempting to obtain cookie from Sailthru: %s", error.get_message())
log.info("Error attempting to obtain cookie from Sailthru: %s", error.get_message())
return None
@@ -93,14 +92,14 @@ def update_user(self, sailthru_vars, email, site=None, new_user=False, activatio
site=site))
except SailthruClientError as exc:
log.error(u"Exception attempting to add/update user %s in Sailthru - %s", email, six.text_type(exc))
log.error("Exception attempting to add/update user %s in Sailthru - %s", email, str(exc))
raise self.retry(exc=exc,
countdown=email_config.sailthru_retry_interval,
max_retries=email_config.sailthru_max_retries)
if not sailthru_response.is_ok():
error = sailthru_response.get_error()
log.error(u"Error attempting to add/update user in Sailthru: %s", error.get_message())
log.error("Error attempting to add/update user in Sailthru: %s", error.get_message())
if _retryable_sailthru_error(error):
raise self.retry(countdown=email_config.sailthru_retry_interval,
max_retries=email_config.sailthru_max_retries)
@@ -120,9 +119,9 @@ def update_user(self, sailthru_vars, email, site=None, new_user=False, activatio
)
except SailthruClientError as exc:
log.error(
u"Exception attempting to send welcome email to user %s in Sailthru - %s",
"Exception attempting to send welcome email to user %s in Sailthru - %s",
email,
six.text_type(exc)
str(exc)
)
raise self.retry(exc=exc,
countdown=email_config.sailthru_retry_interval,
@@ -130,7 +129,7 @@ def update_user(self, sailthru_vars, email, site=None, new_user=False, activatio
if not sailthru_response.is_ok():
error = sailthru_response.get_error()
log.error(u"Error attempting to send welcome email to user in Sailthru: %s", error.get_message())
log.error("Error attempting to send welcome email to user in Sailthru: %s", error.get_message())
if _retryable_sailthru_error(error):
raise self.retry(countdown=email_config.sailthru_retry_interval,
max_retries=email_config.sailthru_max_retries)
@@ -173,14 +172,14 @@ def update_user_email(self, new_email, old_email):
sailthru_client = SailthruClient(email_config.sailthru_key, email_config.sailthru_secret)
sailthru_response = sailthru_client.api_post("user", sailthru_parms)
except SailthruClientError as exc:
log.error(u"Exception attempting to update email for %s in Sailthru - %s", old_email, six.text_type(exc))
log.error("Exception attempting to update email for %s in Sailthru - %s", old_email, str(exc))
raise self.retry(exc=exc,
countdown=email_config.sailthru_retry_interval,
max_retries=email_config.sailthru_max_retries)
if not sailthru_response.is_ok():
error = sailthru_response.get_error()
log.error(u"Error attempting to update user email address in Sailthru: %s", error.get_message())
log.error("Error attempting to update user email address in Sailthru: %s", error.get_message())
if _retryable_sailthru_error(error):
raise self.retry(countdown=email_config.sailthru_retry_interval,
max_retries=email_config.sailthru_max_retries)
@@ -262,12 +261,12 @@ def _get_list_from_email_marketing_provider(sailthru_client):
try:
sailthru_get_response = sailthru_client.api_get("list", {})
except SailthruClientError as exc:
log.error(u"Exception attempting to get list from Sailthru - %s", six.text_type(exc))
log.error("Exception attempting to get list from Sailthru - %s", str(exc))
return {}
if not sailthru_get_response.is_ok():
error = sailthru_get_response.get_error()
log.info(u"Error attempting to read list record from Sailthru: %s", error.get_message())
log.info("Error attempting to read list record from Sailthru: %s", error.get_message())
return {}
list_map = dict()
@@ -288,12 +287,12 @@ def _create_user_list(sailthru_client, list_name):
try:
sailthru_response = sailthru_client.api_post("list", list_params)
except SailthruClientError as exc:
log.error(u"Exception attempting to list record for key %s in Sailthru - %s", list_name, six.text_type(exc))
log.error("Exception attempting to list record for key %s in Sailthru - %s", list_name, str(exc))
return False
if not sailthru_response.is_ok():
error = sailthru_response.get_error()
log.error(u"Error attempting to create list in Sailthru: %s", error.get_message())
log.error("Error attempting to create list in Sailthru: %s", error.get_message())
return False
return True
@@ -365,7 +364,7 @@ def build_course_url(course_key):
a complete url of the course info page
"""
return '{base_url}/courses/{course_key}/info'.format(base_url=settings.LMS_ROOT_URL,
course_key=six.text_type(course_key))
course_key=str(course_key))
# TODO: Remove in AA-607
@@ -384,7 +383,7 @@ def update_unenrolled_list(sailthru_client, email, course_url, unenroll):
sailthru_response = sailthru_client.api_get("user", {"id": email, "fields": {"vars": 1}})
if not sailthru_response.is_ok():
error = sailthru_response.get_error()
log.error(u"Error attempting to read user record from Sailthru: %s", error.get_message())
log.error("Error attempting to read user record from Sailthru: %s", error.get_message())
return not _retryable_sailthru_error(error)
response_json = sailthru_response.json
@@ -413,13 +412,13 @@ def update_unenrolled_list(sailthru_client, email, course_url, unenroll):
if not sailthru_response.is_ok():
error = sailthru_response.get_error()
log.error(u"Error attempting to update user record in Sailthru: %s", error.get_message())
log.error("Error attempting to update user record in Sailthru: %s", error.get_message())
return not _retryable_sailthru_error(error)
return True
except SailthruClientError as exc:
log.exception(u"Exception attempting to update user record for %s in Sailthru - %s", email, six.text_type(exc))
log.exception("Exception attempting to update user record for %s in Sailthru - %s", email, str(exc))
return False
@@ -444,7 +443,7 @@ def _get_course_content(course_id, course_url, sailthru_client, config):
"""
# check cache first
cache_key = "{}:{}".format(course_id, course_url)
cache_key = f"{course_id}:{course_url}"
response = cache.get(cache_key)
if not response:
try:
@@ -468,7 +467,7 @@ def _build_purchase_item(course_id, course_url, cost_in_cents, mode, course_data
# build item description
item = {
'id': "{}-{}".format(course_id, mode),
'id': f"{course_id}-{mode}",
'url': course_url,
'price': cost_in_cents,
'qty': 1,
@@ -479,13 +478,13 @@ def _build_purchase_item(course_id, course_url, cost_in_cents, mode, course_data
item['title'] = course_data['title']
else:
# can't find, just invent title
item['title'] = u'Course {} mode: {}'.format(course_id, mode)
item['title'] = f'Course {course_id} mode: {mode}'
if 'tags' in course_data:
item['tags'] = course_data['tags']
# add vars to item
item['vars'] = dict(course_data.get('vars', {}), mode=mode, course_run_id=six.text_type(course_id))
item['vars'] = dict(course_data.get('vars', {}), mode=mode, course_run_id=str(course_id))
return item
@@ -508,10 +507,10 @@ def _record_purchase(sailthru_client, email, item, options):
if not sailthru_response.is_ok():
error = sailthru_response.get_error()
log.error(u"Error attempting to record purchase in Sailthru: %s", error.get_message())
log.error("Error attempting to record purchase in Sailthru: %s", error.get_message())
return not _retryable_sailthru_error(error)
except SailthruClientError as exc:
log.exception(u"Exception attempting to record purchase for %s in Sailthru - %s", email, six.text_type(exc))
log.exception("Exception attempting to record purchase for %s in Sailthru - %s", email, str(exc))
return False
return True

View File

@@ -1,25 +1,29 @@
# -*- coding: utf-8 -*-
"""Tests of email marketing signal handlers."""
import datetime
import logging
from unittest.mock import ANY, Mock, patch
import ddt
import six
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.contrib.sites.models import Site
from django.test import TestCase
from django.test.client import RequestFactory
from freezegun import freeze_time
from mock import ANY, Mock, patch
from opaque_keys.edx.keys import CourseKey
from sailthru.sailthru_error import SailthruClientError
from sailthru.sailthru_response import SailthruResponse
from testfixtures import LogCapture
from common.djangoapps.student.models import Registration, User
from common.djangoapps.student.tests.factories import ( # lint-amnesty, pylint: disable=unused-import
CourseEnrollmentFactory,
UserFactory,
UserProfileFactory
)
from common.djangoapps.util.json_request import JsonResponse
from lms.djangoapps.email_marketing.tasks import ( # lint-amnesty, pylint: disable=unused-import
_create_user_list,
_get_list_from_email_marketing_provider,
@@ -30,9 +34,6 @@ from lms.djangoapps.email_marketing.tasks import ( # lint-amnesty, pylint: disa
update_user_email
)
from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY
from common.djangoapps.student.models import Registration, User
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory, UserProfileFactory # lint-amnesty, pylint: disable=unused-import
from common.djangoapps.util.json_request import JsonResponse
from ..models import EmailMarketingConfiguration
from ..signals import (
@@ -92,7 +93,7 @@ class EmailMarketingTests(TestCase):
self.site = Site.objects.get_current()
self.request.site = self.site
super(EmailMarketingTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
@freeze_time(datetime.datetime.now())
@patch('lms.djangoapps.email_marketing.signals.crum.get_current_request')
@@ -118,7 +119,7 @@ class EmailMarketingTests(TestCase):
'Started at {start} and ended at {end}, time spent:{delta} milliseconds'.format(
start=datetime.datetime.now().isoformat(' '),
end=datetime.datetime.now().isoformat(' '),
delta=0 if six.PY2 else 0.0)
delta=0.0)
),
(LOGGER_NAME, 'INFO',
'sailthru_hid cookie:{cookies[cookie]} successfully retrieved for user {user}'.format(
@@ -561,7 +562,7 @@ class EmailMarketingTests(TestCase):
assert not mock_update_user.called
class MockSailthruResponse(object):
class MockSailthruResponse:
"""
Mock object for SailthruResponse
"""
@@ -584,7 +585,7 @@ class MockSailthruResponse(object):
return MockSailthruError(self.error, self.code)
class MockSailthruError(object):
class MockSailthruError:
"""
Mock object for Sailthru Error
"""
@@ -612,7 +613,7 @@ class SailthruTests(TestCase):
"""
def setUp(self):
super(SailthruTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.user = UserFactory()
self.course_id = CourseKey.from_string('edX/toy/2012_Fall')
self.course_url = 'http://lms.testserver.fake/courses/edX/toy/2012_Fall/info'
@@ -664,6 +665,6 @@ class SailthruTests(TestCase):
while sending it to sail through.
"""
switch.return_value = True
self.user.email = u'tèst@edx.org'
self.user.email = 'tèst@edx.org'
update_sailthru(None, self.user, 'audit', str(self.course_id))
assert mock_sailthru_purchase.called

View File

@@ -6,4 +6,4 @@ class ExperimentsConfig(AppConfig):
"""
Application Configuration for experiments.
"""
name = u'lms.djangoapps.experiments'
name = 'lms.djangoapps.experiments'

View File

@@ -6,12 +6,12 @@ Experimentation factories
import factory
import factory.fuzzy
from lms.djangoapps.experiments.models import ExperimentData, ExperimentKeyValue
from common.djangoapps.student.tests.factories import UserFactory
from lms.djangoapps.experiments.models import ExperimentData, ExperimentKeyValue
class ExperimentDataFactory(factory.DjangoModelFactory): # lint-amnesty, pylint: disable=missing-class-docstring
class Meta(object):
class Meta:
model = ExperimentData
user = factory.SubFactory(UserFactory)
@@ -21,7 +21,7 @@ class ExperimentDataFactory(factory.DjangoModelFactory): # lint-amnesty, pylint
class ExperimentKeyValueFactory(factory.DjangoModelFactory): # lint-amnesty, pylint: disable=missing-class-docstring
class Meta(object):
class Meta:
model = ExperimentKeyValue
experiment_id = factory.fuzzy.FuzzyInteger(0)

View File

@@ -9,12 +9,12 @@ from lms.djangoapps.experiments.models import ExperimentData, ExperimentKeyValue
class ExperimentDataFilter(django_filters.FilterSet):
class Meta(object):
class Meta:
model = ExperimentData
fields = ['experiment_id', 'key', ]
class ExperimentKeyValueFilter(django_filters.FilterSet):
class Meta(object):
class Meta:
model = ExperimentKeyValue
fields = ['experiment_id', 'key', ]

View File

@@ -10,9 +10,9 @@ import pytz
from crum import get_current_request
from edx_django_utils.cache import RequestCache
from common.djangoapps.track import segment
from lms.djangoapps.experiments.stable_bucketing import stable_bucketing_hash_group
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
from common.djangoapps.track import segment
log = logging.getLogger(__name__)
@@ -76,7 +76,7 @@ class ExperimentWaffleFlag(CourseWaffleFlag):
self.num_buckets = num_buckets
self.experiment_id = experiment_id
self.bucket_flags = [
CourseWaffleFlag(waffle_namespace, '{}.{}'.format(flag_name, bucket), module_name)
CourseWaffleFlag(waffle_namespace, f'{flag_name}.{bucket}', module_name)
for bucket in range(num_buckets)
]
self.use_course_aware_bucketing = use_course_aware_bucketing
@@ -205,9 +205,9 @@ class ExperimentWaffleFlag(CourseWaffleFlag):
# buckets for different course-runs.
experiment_name = bucketing_group_name = self.name
if course_key:
experiment_name += ".{}".format(course_key)
experiment_name += f".{course_key}"
if course_key and self.use_course_aware_bucketing:
bucketing_group_name += ".{}".format(course_key)
bucketing_group_name += f".{course_key}"
# Check if we have a cache for this request already
request_cache = RequestCache('experiments')
@@ -239,7 +239,7 @@ class ExperimentWaffleFlag(CourseWaffleFlag):
bucketing_group_name, self.num_buckets, user
)
session_key = 'tracked.{}'.format(experiment_name)
session_key = f'tracked.{experiment_name}'
anonymous = not hasattr(request, 'user') or not request.user.id
if (
track and hasattr(request, 'session') and

View File

@@ -1,7 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models
from django.conf import settings
import django.utils.timezone
@@ -21,7 +17,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('experiment_id', models.PositiveSmallIntegerField(verbose_name=u'Experiment ID', db_index=True)),
('experiment_id', models.PositiveSmallIntegerField(verbose_name='Experiment ID', db_index=True)),
('key', models.CharField(max_length=255)),
('value', models.TextField()),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
@@ -33,10 +29,10 @@ class Migration(migrations.Migration):
),
migrations.AlterUniqueTogether(
name='experimentdata',
unique_together=set([('user', 'experiment_id', 'key')]),
unique_together={('user', 'experiment_id', 'key')},
),
migrations.AlterIndexTogether(
name='experimentdata',
index_together=set([('user', 'experiment_id')]),
index_together={('user', 'experiment_id')},
),
]

View File

@@ -1,7 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models
import django.utils.timezone
import model_utils.fields
@@ -20,7 +16,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('experiment_id', models.PositiveSmallIntegerField(verbose_name=u'Experiment ID', db_index=True)),
('experiment_id', models.PositiveSmallIntegerField(verbose_name='Experiment ID', db_index=True)),
('key', models.CharField(max_length=255)),
('value', models.TextField()),
],
@@ -31,6 +27,6 @@ class Migration(migrations.Migration):
),
migrations.AlterUniqueTogether(
name='experimentkeyvalue',
unique_together=set([('experiment_id', 'key')]),
unique_together={('experiment_id', 'key')},
),
]

View File

@@ -1,7 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.28 on 2020-03-03 16:26

View File

@@ -17,12 +17,12 @@ class ExperimentData(TimeStampedModel):
"""
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
experiment_id = models.PositiveSmallIntegerField(
null=False, blank=False, db_index=True, verbose_name=u'Experiment ID'
null=False, blank=False, db_index=True, verbose_name='Experiment ID'
)
key = models.CharField(null=False, blank=False, max_length=255)
value = models.TextField()
class Meta(object):
class Meta:
index_together = (
('user', 'experiment_id'),
)
@@ -40,14 +40,14 @@ class ExperimentKeyValue(TimeStampedModel):
.. no_pii:
"""
experiment_id = models.PositiveSmallIntegerField(
null=False, blank=False, db_index=True, verbose_name=u'Experiment ID'
null=False, blank=False, db_index=True, verbose_name='Experiment ID'
)
key = models.CharField(null=False, blank=False, max_length=255)
value = models.TextField()
history = HistoricalRecords()
class Meta(object):
class Meta:
verbose_name = 'Experiment Key-Value Pair'
verbose_name_plural = 'Experiment Key-Value Pairs'
unique_together = (

View File

@@ -19,7 +19,7 @@ class IsStaffOrOwner(permissions.IsStaffOrOwner):
# Non-staff users can only create data for themselves.
if view.action == 'create':
username = request.user.username
return super(IsStaffOrOwner, self).has_permission(request, view) or ( # lint-amnesty, pylint: disable=super-with-arguments
return super().has_permission(request, view) or (
username == request.data.get('user', username))
# The view will handle filtering for the current user

View File

@@ -15,7 +15,7 @@ class ExperimentDataCreateSerializer(serializers.ModelSerializer): # lint-amnes
user = serializers.SlugRelatedField(slug_field='username', default=serializers.CurrentUserDefault(), required=False,
queryset=User.objects.all())
class Meta(object):
class Meta:
model = ExperimentData
fields = ('id', 'experiment_id', 'user', 'key', 'value', 'created', 'modified',)
@@ -28,6 +28,6 @@ class ExperimentDataSerializer(serializers.ModelSerializer):
class ExperimentKeyValueSerializer(serializers.ModelSerializer):
class Meta(object):
class Meta:
model = ExperimentKeyValue
fields = ('id', 'experiment_id', 'key', 'value', 'created', 'modified',)

View File

@@ -2,23 +2,24 @@
Tests for experimentation feature flags
"""
from unittest.mock import patch
import ddt
import pytz
from crum import set_current_request
from dateutil import parser
from django.test.client import RequestFactory
from edx_django_utils.cache import RequestCache
from mock import patch
from edx_toggles.toggles.testutils import override_waffle_flag
from opaque_keys.edx.keys import CourseKey
from edx_toggles.toggles.testutils import override_waffle_flag
from lms.djangoapps.experiments.testutils import override_experiment_waffle_flag
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from lms.djangoapps.experiments.factories import ExperimentKeyValueFactory
from lms.djangoapps.experiments.flags import ExperimentWaffleFlag
from lms.djangoapps.experiments.testutils import override_experiment_waffle_flag
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase

View File

@@ -4,10 +4,12 @@ Tests of experiment functionality
from datetime import timedelta
from decimal import Decimal
from django.utils.timezone import now
from unittest import TestCase # lint-amnesty, pylint: disable=wrong-import-order
from unittest import TestCase
from django.utils.timezone import now
from opaque_keys.edx.keys import CourseKey
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from lms.djangoapps.course_blocks.transformers.tests.helpers import ModuleStoreTestCase
from lms.djangoapps.courseware import courses # lint-amnesty, pylint: disable=unused-import
from lms.djangoapps.experiments.utils import (
@@ -17,7 +19,6 @@ from lms.djangoapps.experiments.utils import (
get_unenrolled_courses,
is_enrolled_in_course_run
)
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from xmodule.modulestore.tests.factories import CourseFactory
@@ -27,7 +28,7 @@ class ExperimentUtilsTests(ModuleStoreTestCase, TestCase):
"""
def setUp(self):
super(ExperimentUtilsTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
# Create a course run
self.run_a_price = '86.00'
@@ -103,7 +104,7 @@ class ExperimentUtilsTests(ModuleStoreTestCase, TestCase):
courses = [course_a] # lint-amnesty, pylint: disable=redefined-outer-name
price, skus = get_program_price_and_skus(courses)
expected_price = u'$199.23'
expected_price = '$199.23'
assert expected_price == price
assert 1 == len(skus)
assert self.entitlement_a_sku in skus
@@ -116,7 +117,7 @@ class ExperimentUtilsTests(ModuleStoreTestCase, TestCase):
courses = [course_a, course_b] # lint-amnesty, pylint: disable=redefined-outer-name
price, skus = get_program_price_and_skus(courses)
expected_price = u'$285.23'
expected_price = '$285.23'
assert expected_price == price
assert 2 == len(skus)
assert self.run_a_sku in skus

View File

@@ -4,25 +4,27 @@ Tests for experimentation views
import unittest
from datetime import timedelta
from unittest.mock import patch
import six.moves.urllib.error
import six.moves.urllib.parse
import six.moves.urllib.request
from datetime import timedelta
from django.conf import settings
from django.core.handlers.wsgi import WSGIRequest
from django.utils.timezone import now
from django.test.utils import override_settings
from django.urls import reverse
from lms.djangoapps.course_blocks.transformers.tests.helpers import ModuleStoreTestCase
from mock import patch # lint-amnesty, pylint: disable=wrong-import-order
from rest_framework.test import APITestCase # lint-amnesty, pylint: disable=wrong-import-order
from django.utils.timezone import now
from rest_framework.test import APITestCase
from lms.djangoapps.experiments.factories import ExperimentDataFactory, ExperimentKeyValueFactory
from lms.djangoapps.experiments.models import ExperimentData, ExperimentKeyValue # lint-amnesty, pylint: disable=unused-import
from lms.djangoapps.experiments.serializers import ExperimentDataSerializer
from common.djangoapps.student.tests.factories import UserFactory
from lms.djangoapps.course_blocks.transformers.tests.helpers import ModuleStoreTestCase
from lms.djangoapps.experiments.factories import ExperimentDataFactory, ExperimentKeyValueFactory
from lms.djangoapps.experiments.models import ( # lint-amnesty, pylint: disable=unused-import
ExperimentData,
ExperimentKeyValue
)
from lms.djangoapps.experiments.serializers import ExperimentDataSerializer
from xmodule.modulestore.tests.factories import CourseFactory
CROSS_DOMAIN_REFERER = 'https://ecommerce.edx.org'
@@ -75,18 +77,18 @@ class ExperimentDataViewSetTests(APITestCase, ModuleStoreTestCase): # lint-amne
data = ExperimentDataFactory.create_batch(3, user=user, experiment_id=experiment_id)
qs = six.moves.urllib.parse.urlencode({'experiment_id': experiment_id})
response = self.client.get('{url}?{qs}'.format(url=url, qs=qs))
response = self.client.get(f'{url}?{qs}')
assert response.status_code == 200
assert response.data['results'] == ExperimentDataSerializer(data, many=True).data
datum = data[0]
qs = six.moves.urllib.parse.urlencode({'key': datum.key})
response = self.client.get('{url}?{qs}'.format(url=url, qs=qs))
response = self.client.get(f'{url}?{qs}')
assert response.status_code == 200
assert response.data['results'] == ExperimentDataSerializer([datum], many=True).data
qs = six.moves.urllib.parse.urlencode({'experiment_id': experiment_id, 'key': datum.key})
response = self.client.get('{url}?{qs}'.format(url=url, qs=qs))
response = self.client.get(f'{url}?{qs}')
assert response.status_code == 200
assert response.data['results'] == ExperimentDataSerializer([datum], many=True).data
@@ -197,7 +199,7 @@ class ExperimentCrossDomainTests(APITestCase):
"""Tests for handling cross-domain requests"""
def setUp(self):
super(ExperimentCrossDomainTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.client = self.client_class(enforce_csrf_checks=True)
@cross_domain_config

View File

@@ -6,17 +6,16 @@ Tests for experimentation views
from datetime import timedelta
from uuid import uuid4
import six
from django.urls import reverse
from django.utils.timezone import now
from edx_toggles.toggles.testutils import override_waffle_flag
from rest_framework.test import APITestCase
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
from edx_toggles.toggles.testutils import override_waffle_flag # lint-amnesty, pylint: disable=wrong-import-order
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from lms.djangoapps.course_blocks.transformers.tests.helpers import ModuleStoreTestCase
from lms.djangoapps.experiments.views_custom import MOBILE_UPSELL_FLAG
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from xmodule.modulestore.tests.factories import CourseFactory
CROSS_DOMAIN_REFERER = 'https://ecommerce.edx.org'
@@ -36,11 +35,11 @@ class Rev934Tests(APITestCase, ModuleStoreTestCase):
"""Test mobile app upsell API"""
@classmethod
def setUpClass(cls):
super(Rev934Tests, cls).setUpClass()
super().setUpClass()
cls.url = reverse('api_experiments:rev_934')
def setUp(self):
super(Rev934Tests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.user = UserFactory(username='robot-mue-1-6pnjv') # Username that hashes to bucket 1
self.client.login(
username=self.user.username,
@@ -92,7 +91,7 @@ class Rev934Tests(APITestCase, ModuleStoreTestCase):
mode_slug=CourseMode.VERIFIED,
course_id=course.id,
min_price=10,
sku=six.text_type(uuid4().hex)
sku=str(uuid4().hex)
)
response = self.client.get(self.url, {'course_id': str(course.id)})
@@ -102,7 +101,7 @@ class Rev934Tests(APITestCase, ModuleStoreTestCase):
assert bool(result['basket_url'])
expected = {
'show_upsell': True,
'price': u'$10',
'price': '$10',
'basket_url': result['basket_url'],
# Example basket_url: u'/verify_student/upgrade/org.0/course_0/test/'
}
@@ -119,7 +118,7 @@ class Rev934Tests(APITestCase, ModuleStoreTestCase):
mode_slug=CourseMode.VERIFIED,
course_id=course.id,
min_price=10,
sku=six.text_type(uuid4().hex),
sku=str(uuid4().hex),
expiration_datetime=now() - timedelta(days=30),
)
@@ -146,7 +145,7 @@ class Rev934Tests(APITestCase, ModuleStoreTestCase):
mode_slug=CourseMode.VERIFIED,
course_id=course.id,
min_price=10,
sku=six.text_type(uuid4().hex)
sku=str(uuid4().hex)
)
response = self.client.get(self.url, {'course_id': str(course.id)})
@@ -170,7 +169,7 @@ class Rev934Tests(APITestCase, ModuleStoreTestCase):
mode_slug=CourseMode.VERIFIED,
course_id=course.id,
min_price=10,
sku=six.text_type(uuid4().hex)
sku=str(uuid4().hex)
)
response = self.client.get(self.url, {'course_id': str(course.id)})
@@ -193,7 +192,7 @@ class Rev934Tests(APITestCase, ModuleStoreTestCase):
mode_slug=CourseMode.VERIFIED,
course_id=course.id,
min_price=10,
sku=six.text_type(uuid4().hex)
sku=str(uuid4().hex)
)
CourseEnrollmentFactory.create(
is_active=True,

View File

@@ -1,7 +1,6 @@
# lint-amnesty, pylint: disable=missing-module-docstring
from contextlib import contextmanager
from mock import patch
from unittest.mock import patch
from edx_toggles.toggles.testutils import override_waffle_flag

View File

@@ -6,14 +6,14 @@ Utilities to facilitate experimentation
import logging
from decimal import Decimal
import six
from django.utils.timezone import now
from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from common.djangoapps.course_modes.models import format_course_price, get_cosmetic_verified_display_price, CourseMode
from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace # lint-amnesty, pylint: disable=wrong-import-order
from common.djangoapps.course_modes.models import CourseMode, format_course_price, get_cosmetic_verified_display_price
from common.djangoapps.entitlements.models import CourseEntitlement
from common.djangoapps.student.models import CourseEnrollment
from lms.djangoapps.commerce.utils import EcommerceService
from lms.djangoapps.courseware.access import has_staff_access_to_preview_mode
from lms.djangoapps.courseware.utils import can_show_verified_upgrade, verified_upgrade_deadline_link
@@ -21,7 +21,6 @@ from openedx.core.djangoapps.catalog.utils import get_programs
from openedx.core.djangoapps.django_comment_common.models import Role
from openedx.core.djangoapps.schedules.models import Schedule
from openedx.features.course_duration_limits.access import get_user_course_duration, get_user_course_expiration_date
from common.djangoapps.student.models import CourseEnrollment
from xmodule.partitions.partitions_service import get_all_partitions_for_course, get_user_partition_groups
# Import this for backwards compatibility (so that anyone importing this function from here doesn't break)
@@ -31,7 +30,7 @@ logger = logging.getLogger(__name__)
# TODO: clean up as part of REVEM-199 (START)
experiments_namespace = LegacyWaffleFlagNamespace(name=u'experiments')
experiments_namespace = LegacyWaffleFlagNamespace(name='experiments')
# .. toggle_name: experiments.add_programs
# .. toggle_implementation: WaffleFlag
@@ -44,7 +43,7 @@ experiments_namespace = LegacyWaffleFlagNamespace(name=u'experiments')
# .. toggle_warnings: This temporary feature toggle does not have a target removal date.
PROGRAM_INFO_FLAG = LegacyWaffleFlag(
waffle_namespace=experiments_namespace,
flag_name=u'add_programs',
flag_name='add_programs',
module_name=__name__,
)
@@ -57,7 +56,7 @@ PROGRAM_INFO_FLAG = LegacyWaffleFlag(
# .. toggle_target_removal_date: None
# .. toggle_tickets: REVEM-118
# .. toggle_warnings: This temporary feature toggle does not have a target removal date.
DASHBOARD_INFO_FLAG = LegacyWaffleFlag(experiments_namespace, u'add_dashboard_info', __name__)
DASHBOARD_INFO_FLAG = LegacyWaffleFlag(experiments_namespace, 'add_dashboard_info', __name__)
# TODO END: clean up as part of REVEM-199 (End)
# TODO: Clean up as part of REV-1205 (START)
@@ -72,7 +71,7 @@ DASHBOARD_INFO_FLAG = LegacyWaffleFlag(experiments_namespace, u'add_dashboard_in
# .. toggle_warnings: This temporary feature toggle does not have a target removal date.
UPSELL_TRACKING_FLAG = LegacyWaffleFlag(
waffle_namespace=experiments_namespace,
flag_name=u'add_upsell_tracking',
flag_name='add_upsell_tracking',
module_name=__name__,
)
# TODO END: Clean up as part of REV-1205 (End)
@@ -88,13 +87,13 @@ def check_and_get_upgrade_link_and_date(user, enrollment=None, course=None):
otherwise, returns None for both the link and date.
"""
if enrollment is None and course is None:
logger.warning(u'Must specify either an enrollment or a course')
logger.warning('Must specify either an enrollment or a course')
return (None, None, None)
if enrollment:
if course and enrollment.course_id != course.id:
logger.warning(u'{} refers to a different course than {} which was supplied. Enrollment course id={}, '
u'repr={!r}, deprecated={}. Course id={}, repr={!r}, deprecated={}.'
logger.warning('{} refers to a different course than {} which was supplied. Enrollment course id={}, '
'repr={!r}, deprecated={}. Course id={}, repr={!r}, deprecated={}.'
.format(enrollment,
course,
enrollment.course_id,
@@ -108,15 +107,15 @@ def check_and_get_upgrade_link_and_date(user, enrollment=None, course=None):
return (None, None, None)
if enrollment.user_id != user.id:
logger.warning(u'{} refers to a different user than {} which was supplied. '
u'Enrollment user id={}, repr={!r}. '
u'User id={}, repr={!r}.'.format(enrollment,
user,
enrollment.user_id,
enrollment.user_id,
user.id,
user.id,
)
logger.warning('{} refers to a different user than {} which was supplied. '
'Enrollment user id={}, repr={!r}. '
'User id={}, repr={!r}.'.format(enrollment,
user,
enrollment.user_id,
enrollment.user_id,
user.id,
user.id,
)
)
return (None, None, None)
@@ -154,7 +153,7 @@ def get_program_price_and_skus(courses):
skus = None
else:
program_price = format_course_price(program_price)
program_price = six.text_type(program_price)
program_price = str(program_price)
return program_price, skus
@@ -237,7 +236,7 @@ def is_enrolled_in_course_run(course_run, enrollment_course_ids):
return course_run_key in enrollment_course_ids
except InvalidKeyError:
logger.warning(
u'Unable to determine if user was enrolled since the course key {} is invalid'.format(key)
f'Unable to determine if user was enrolled since the course key {key} is invalid'
)
return False # Invalid course run key. Assume user is not enrolled.
@@ -373,8 +372,8 @@ def get_experiment_user_metadata_context(course, user):
}
if not context.get('course_id'):
user_metadata['course_id'] = six.text_type(course_key)
elif isinstance(course_key, six.string_types):
user_metadata['course_id'] = str(course_key)
elif isinstance(course_key, str):
user_metadata['course_id'] = course_key
context['user_metadata'] = user_metadata
@@ -411,7 +410,7 @@ def get_base_experiment_metadata_context(course, user, enrollment, user_enrollme
return {
'upgrade_link': upgrade_link,
'upgrade_price': six.text_type(get_cosmetic_verified_display_price(course)),
'upgrade_price': str(get_cosmetic_verified_display_price(course)),
'enrollment_mode': enrollment_mode,
'enrollment_time': enrollment_time,
'schedule_start': schedule_start,

View File

@@ -5,23 +5,23 @@ Experimentation views
from django.contrib.auth import get_user_model
from django.db import transaction # lint-amnesty, pylint: disable=unused-import
from django_filters.rest_framework import DjangoFilterBackend
from django.http import Http404
from django_filters.rest_framework import DjangoFilterBackend
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
from lms.djangoapps.courseware import courses
from opaque_keys.edx.keys import CourseKey # lint-amnesty, pylint: disable=wrong-import-order
from rest_framework import permissions, viewsets # lint-amnesty, pylint: disable=wrong-import-order
from opaque_keys.edx.keys import CourseKey
from rest_framework import permissions, viewsets
from rest_framework.response import Response # lint-amnesty, pylint: disable=unused-import, wrong-import-order
from rest_framework.views import APIView # lint-amnesty, pylint: disable=wrong-import-order
from common.djangoapps.util.json_request import JsonResponse
from rest_framework.views import APIView
from common.djangoapps.student.models import get_user_by_username_or_email
from common.djangoapps.util.json_request import JsonResponse
from lms.djangoapps.courseware import courses
from lms.djangoapps.experiments import filters, serializers
from lms.djangoapps.experiments.models import ExperimentData, ExperimentKeyValue
from lms.djangoapps.experiments.permissions import IsStaffOrOwner, IsStaffOrReadOnly, IsStaffOrReadOnlyForSelf
from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context
from openedx.core.djangoapps.cors_csrf.authentication import SessionAuthenticationCrossDomainCsrf
from common.djangoapps.student.models import get_user_by_username_or_email
User = get_user_model() # pylint: disable=invalid-name
@@ -42,7 +42,7 @@ class ExperimentDataViewSet(viewsets.ModelViewSet): # lint-amnesty, pylint: dis
def filter_queryset(self, queryset):
queryset = queryset.filter(user=self.request.user)
return super(ExperimentDataViewSet, self).filter_queryset(queryset) # lint-amnesty, pylint: disable=super-with-arguments
return super().filter_queryset(queryset)
def get_serializer_class(self):
if self.action == 'create':

View File

@@ -2,21 +2,19 @@
The Discount API Views should return information about discounts that apply to the user and course.
"""
# -*- coding: utf-8 -*-
import six
from django.http import HttpResponseBadRequest
from django.utils.decorators import method_decorator
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from rest_framework.response import Response
from rest_framework.views import APIView
from common.djangoapps.course_modes.models import get_cosmetic_verified_display_price
from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace # lint-amnesty, pylint: disable=wrong-import-order
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.track import segment
from lms.djangoapps.commerce.utils import EcommerceService
from lms.djangoapps.courseware.utils import can_show_verified_upgrade
from lms.djangoapps.experiments.stable_bucketing import stable_bucketing_hash_group
@@ -25,8 +23,6 @@ from openedx.core.djangoapps.cors_csrf.decorators import ensure_csrf_cookie_cros
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
from openedx.core.lib.api.permissions import ApiKeyHeaderPermissionIsAuthenticated
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.track import segment
# .. toggle_name: experiments.mobile_upsell_rev934
# .. toggle_implementation: WaffleFlag
@@ -38,8 +34,8 @@ from common.djangoapps.track import segment
# .. toggle_tickets: REV-934
# .. toggle_warnings: This temporary feature toggle does not have a target removal date.
MOBILE_UPSELL_FLAG = LegacyWaffleFlag(
waffle_namespace=LegacyWaffleFlagNamespace(name=u'experiments'),
flag_name=u'mobile_upsell_rev934',
waffle_namespace=LegacyWaffleFlagNamespace(name='experiments'),
flag_name='mobile_upsell_rev934',
module_name=__name__,
)
MOBILE_UPSELL_EXPERIMENT = 'mobile_upsell_experiment'
@@ -135,7 +131,7 @@ class Rev934(DeveloperErrorViewMixin, APIView):
user_upsell = True
basket_url = EcommerceService().upgrade_url(user, course.id)
upgrade_price = six.text_type(get_cosmetic_verified_display_price(course))
upgrade_price = str(get_cosmetic_verified_display_price(course))
could_upsell = bool(user_upsell and basket_url)
bucket = stable_bucketing_hash_group(MOBILE_UPSELL_EXPERIMENT, 2, user)