pyupgrade on lms edxnotes,email_marketing,experiments apps (#26531)
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-25 12:00
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,4 +6,4 @@ class ExperimentsConfig(AppConfig):
|
||||
"""
|
||||
Application Configuration for experiments.
|
||||
"""
|
||||
name = u'lms.djangoapps.experiments'
|
||||
name = 'lms.djangoapps.experiments'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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', ]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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')},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.28 on 2020-03-03 16:26
|
||||
|
||||
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user