Merge branch 'master' into aehsan/LEARNER-6943/adding_logs_to_check_discovery_response
This commit is contained in:
320
.annotation_safe_list.yml
Normal file
320
.annotation_safe_list.yml
Normal file
@@ -0,0 +1,320 @@
|
||||
# This is a Code Annotations automatically-generated Django model safelist file.
|
||||
# These models must be annotated as follows in order to be counted in the coverage report.
|
||||
# See https://code-annotations.readthedocs.io/en/latest/safelist.html for more information.
|
||||
#
|
||||
# fake_app_1.FakeModelName:
|
||||
# ".. no_pii::": "This model has no PII"
|
||||
# fake_app_2.FakeModel2:
|
||||
# ".. choice_annotation::": foo, bar, baz
|
||||
|
||||
# Via Django
|
||||
auth.Group:
|
||||
".. no_pii:" : "No PII"
|
||||
auth.Permission:
|
||||
".. no_pii:" : "No PII"
|
||||
auth.User:
|
||||
".. pii:": "Contains username, password, and email address, retired in AccountRetirementView"
|
||||
".. pii_types:" : username, email_address, password
|
||||
".. pii_retirement:" : local_api
|
||||
contenttypes.ContentType:
|
||||
".. no_pii:": "No PII"
|
||||
admin.LogEntry:
|
||||
".. no_pii:": "No PII"
|
||||
redirects.Redirect:
|
||||
".. no_pii:": "No PII"
|
||||
sessions.Session:
|
||||
".. no_pii:": "No PII"
|
||||
sites.Site:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Automatically generated models in edx-enterprise that can't be annotated there
|
||||
consent.HistoricalDataSharingConsent:
|
||||
".. pii:": "The username field inherited from Consent contains PII."
|
||||
".. pii_types:": username
|
||||
".. pii_retirement:": consumer_api
|
||||
degreed.HistoricalDegreedEnterpriseCustomerConfiguration:
|
||||
".. no_pii:": "No PII"
|
||||
enterprise.HistoricalEnrollmentNotificationEmailTemplate:
|
||||
".. no_pii:": "No PII"
|
||||
enterprise.HistoricalEnterpriseCourseEnrollment:
|
||||
".. no_pii:": "No PII"
|
||||
enterprise.HistoricalEnterpriseCustomer:
|
||||
".. no_pii:": "No PII"
|
||||
enterprise.HistoricalEnterpriseCustomerCatalog:
|
||||
".. no_pii:": "No PII"
|
||||
enterprise.HistoricalEnterpriseCustomerEntitlement:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via ORA2
|
||||
assessment.Assessment:
|
||||
".. no_pii:": "No PII"
|
||||
assessment.AssessmentFeedback:
|
||||
".. no_pii:": "No PII"
|
||||
assessment.AssessmentFeedbackOption:
|
||||
".. no_pii:": "No PII"
|
||||
assessment.AssessmentPart:
|
||||
".. no_pii:": "No PII"
|
||||
assessment.Criterion:
|
||||
".. no_pii:": "No PII"
|
||||
assessment.CriterionOption:
|
||||
".. no_pii:": "No PII"
|
||||
assessment.PeerWorkflow:
|
||||
".. no_pii:": "No PII"
|
||||
assessment.PeerWorkflowItem:
|
||||
".. no_pii:": "No PII"
|
||||
assessment.Rubric:
|
||||
".. no_pii:": "No PII"
|
||||
assessment.StaffWorkflow:
|
||||
".. no_pii:": "No PII"
|
||||
assessment.StudentTrainingWorkflow:
|
||||
".. no_pii:": "No PII"
|
||||
assessment.StudentTrainingWorkflowItem:
|
||||
".. no_pii:": "No PII"
|
||||
assessment.TrainingExample:
|
||||
".. no_pii:": "No PII"
|
||||
workflow.AssessmentWorkflow:
|
||||
".. no_pii:": "No PII"
|
||||
workflow.AssessmentWorkflowCancellation:
|
||||
".. no_pii:": "No PII"
|
||||
workflow.AssessmentWorkflowStep:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via edx-celeryutils
|
||||
celery_utils.ChordData:
|
||||
".. no_pii:": "No PII"
|
||||
celery_utils.FailedTask:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via completion XBlock
|
||||
completion.BlockCompletion:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via django_notify (required / installed by wiki)
|
||||
django_notify.Notification:
|
||||
".. no_pii:": "No PII"
|
||||
django_notify.NotificationType:
|
||||
".. no_pii:": "No PII"
|
||||
django_notify.Settings:
|
||||
".. no_pii:": "No PII"
|
||||
django_notify.Subscription:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via django-openid-auth https://github.com/edx/django-openid-auth
|
||||
django_openid_auth.Association:
|
||||
".. no_pii:": "No PII"
|
||||
django_openid_auth.Nonce:
|
||||
".. no_pii:": "No PII"
|
||||
django_openid_auth.UserOpenID:
|
||||
".. pii:": "User OpenID associations. Not used and empty on edx.org, therefore not retired."
|
||||
".. pii_types:": external_service, password
|
||||
".. pii_retirement:": retained
|
||||
|
||||
# Via django-celery
|
||||
djcelery.CrontabSchedule:
|
||||
".. no_pii:": "No PII"
|
||||
djcelery.IntervalSchedule:
|
||||
".. no_pii:": "No PII"
|
||||
djcelery.PeriodicTask:
|
||||
".. no_pii:": "No PII"
|
||||
djcelery.PeriodicTasks:
|
||||
".. no_pii:": "No PII"
|
||||
djcelery.TaskMeta:
|
||||
".. no_pii:": "No PII"
|
||||
djcelery.TaskSetMeta:
|
||||
".. no_pii:": "No PII"
|
||||
djcelery.TaskState:
|
||||
".. no_pii:": "No PII"
|
||||
djcelery.WorkerState:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via edx-oauth2-provider https://github.com/edx/edx-oauth2-provider
|
||||
edx_oauth2_provider.TrustedClient:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via Proctoring
|
||||
edx_proctoring.ProctoredExam:
|
||||
".. no_pii:": "No PII"
|
||||
edx_proctoring.ProctoredExamReviewPolicy:
|
||||
".. no_pii:": "No PII"
|
||||
edx_proctoring.ProctoredExamReviewPolicyHistory:
|
||||
".. no_pii:": "No PII"
|
||||
edx_proctoring.ProctoredExamSoftwareSecureComment:
|
||||
".. no_pii:": "No PII"
|
||||
edx_proctoring.ProctoredExamSoftwareSecureReview:
|
||||
".. pii:": "Proctored exam review feedback from Software Secure, contains video_url. Retained for record keeping."
|
||||
".. pii_types:": video
|
||||
".. pii_retirement:": retained
|
||||
edx_proctoring.ProctoredExamSoftwareSecureReviewHistory:
|
||||
".. pii:": "Proctored exam review feedback from Software Secure, contains video_url. Retained for record keeping."
|
||||
".. pii_types:": video
|
||||
".. pii_retirement:": retained
|
||||
edx_proctoring.ProctoredExamStudentAllowance:
|
||||
".. no_pii:": "No PII"
|
||||
edx_proctoring.ProctoredExamStudentAllowanceHistory:
|
||||
".. no_pii:": "No PII"
|
||||
edx_proctoring.ProctoredExamStudentAttempt:
|
||||
".. pii:": "Tracks attempts by a user to take a proctored exam. Contains student_name. Retained for record keeping."
|
||||
".. pii_types:": name
|
||||
".. pii_retirement:": retained
|
||||
edx_proctoring.ProctoredExamStudentAttemptHistory:
|
||||
".. pii:": "Tracks attempts by a user to take a proctored exam. Contains student_name. Retained for record keeping."
|
||||
".. pii_types:": name
|
||||
".. pii_retirement:": retained
|
||||
|
||||
# Via VAL
|
||||
edxval.CourseVideo:
|
||||
".. no_pii:": "No PII"
|
||||
edxval.EncodedVideo:
|
||||
".. no_pii:": "No PII"
|
||||
edxval.Profile:
|
||||
".. no_pii:": "No PII"
|
||||
edxval.ThirdPartyTranscriptCredentialsState:
|
||||
".. no_pii:": "No PII"
|
||||
edxval.TranscriptPreference:
|
||||
".. no_pii:": "No PII"
|
||||
edxval.Video:
|
||||
".. no_pii:": "No PII"
|
||||
edxval.VideoImage:
|
||||
".. no_pii:": "No PII"
|
||||
edxval.VideoTranscript:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via Milestones
|
||||
milestones.CourseContentMilestone:
|
||||
".. no_pii:": "No PII"
|
||||
milestones.CourseMilestone:
|
||||
".. no_pii:": "No PII"
|
||||
milestones.Milestone:
|
||||
".. no_pii:": "No PII"
|
||||
milestones.MilestoneRelationshipType:
|
||||
".. no_pii:": "No PII"
|
||||
milestones.UserMilestone:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via Django OAuth2 Provider https://github.com/edx/django-oauth2-provider
|
||||
oauth2.Client:
|
||||
".. no_pii:": "No PII"
|
||||
oauth2.AccessToken:
|
||||
".. pii:": "Contains 3rd party authentication secrets. Retired in DeactivateLogoutView."
|
||||
".. pii_types:": password, other
|
||||
".. pii_retirement:": local_api
|
||||
oauth2.Grant:
|
||||
".. pii:": "Contains 3rd party authentication secrets. Retired in DeactivateLogoutView."
|
||||
".. pii_types:": password, other
|
||||
".. pii_retirement:": local_api
|
||||
oauth2.RefreshToken:
|
||||
".. pii:": "Contains 3rd party authentication secrets. Retired in DeactivateLogoutView."
|
||||
".. pii_types:": password, other
|
||||
".. pii_retirement:": local_api
|
||||
|
||||
# Via Django OAuth Toolkit https://github.com/evonove/django-oauth-toolkit
|
||||
oauth2_provider.AccessToken:
|
||||
".. pii:": "Contains 3rd party authentication secrets. Retired in DeactivateLogoutView."
|
||||
".. pii_types:": password, other
|
||||
".. pii_retirement:": local_api
|
||||
oauth2_provider.Application:
|
||||
".. pii:": "Contains 3rd party authentication secrets. Retired in DeactivateLogoutView."
|
||||
".. pii_types:": password, other
|
||||
".. pii_retirement:": local_api
|
||||
oauth2_provider.Grant:
|
||||
".. pii:": "Contains 3rd party authentication secrets. Retired in DeactivateLogoutView."
|
||||
".. pii_types:": password, other
|
||||
".. pii_retirement:": local_api
|
||||
oauth2_provider.RefreshToken:
|
||||
".. pii:": "Contains 3rd party authentication secrets. Retired in DeactivateLogoutView."
|
||||
".. pii_types:": password, other
|
||||
".. pii_retirement:": local_api
|
||||
|
||||
# Via Django OAuth Plus https://bitbucket.org/david/django-oauth-plus
|
||||
oauth_provider.Consumer:
|
||||
".. no_pii:": "No PII, unused and empty in edx.org"
|
||||
oauth_provider.Nonce:
|
||||
".. no_pii:": "No PII, unused and empty in edx.org"
|
||||
oauth_provider.Scope:
|
||||
".. no_pii:": "No PII, unused and empty in edx.org"
|
||||
oauth_provider.Token:
|
||||
".. pii:": "User OAuth associations. Not used and empty on edx.org, therefore not retired."
|
||||
".. pii_types:": external_service, password
|
||||
".. pii_retirement:": retained
|
||||
|
||||
# Via edx-organizations
|
||||
organizations.Organization:
|
||||
".. no_pii:": "No PII"
|
||||
organizations.OrganizationCourse:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via Problem Builder XBlock
|
||||
problem_builder.Answer:
|
||||
".. no_pii:": "No PII"
|
||||
problem_builder.Share:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via Social Django https://github.com/python-social-auth/social-app-django
|
||||
social_django.Association:
|
||||
".. no_pii:": "No PII"
|
||||
social_django.Code:
|
||||
".. pii:": "Transient - email address stored with email authentication code, removed automatically so not retired"
|
||||
".. pii_types:": email_address
|
||||
".. pii_retirement:": local_api
|
||||
social_django.Nonce:
|
||||
".. no_pii:": "No PII"
|
||||
social_django.Partial:
|
||||
".. no_pii:": "No PII"
|
||||
social_django.UserSocialAuth:
|
||||
".. pii:": "3rd party authentication data, retired in DeactivateLogoutView"
|
||||
".. pii_types:": external_service
|
||||
".. pii_retirement:": local_api
|
||||
|
||||
# Via Splash https://github.com/edx/django-splash
|
||||
splash.SplashConfig:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via edx-submissions
|
||||
submissions.Score:
|
||||
".. no_pii:": "No PII"
|
||||
submissions.ScoreAnnotation:
|
||||
".. no_pii:": "No PII"
|
||||
submissions.ScoreSummary:
|
||||
".. no_pii:": "No PII"
|
||||
submissions.StudentItem:
|
||||
".. no_pii:": "No PII"
|
||||
submissions.Submission:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via sorl-thumbnail https://github.com/jazzband/sorl-thumbnail
|
||||
thumbnail.KVStore:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via django-user-tasks
|
||||
user_tasks.UserTaskArtifact:
|
||||
".. no_pii:": "No PII"
|
||||
user_tasks.UserTaskStatus:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via waffle
|
||||
waffle.Flag:
|
||||
".. no_pii:": "No PII"
|
||||
waffle.Sample:
|
||||
".. no_pii:": "No PII"
|
||||
waffle.Switch:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via django-wiki https://github.com/edx/django-wiki
|
||||
wiki.Article:
|
||||
".. no_pii:": "No PII"
|
||||
wiki.ArticleForObject:
|
||||
".. no_pii:": "No PII"
|
||||
wiki.ArticlePlugin:
|
||||
".. no_pii:": "No PII"
|
||||
wiki.ArticleRevision:
|
||||
".. no_pii:": "No PII"
|
||||
wiki.ReusablePlugin:
|
||||
".. no_pii:": "No PII"
|
||||
wiki.RevisionPlugin:
|
||||
".. no_pii:": "No PII"
|
||||
wiki.RevisionPluginRevision:
|
||||
".. no_pii:": "No PII"
|
||||
wiki.SimplePlugin:
|
||||
".. no_pii:": "No PII"
|
||||
wiki.URLPath:
|
||||
".. no_pii:": "No PII"
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -140,3 +140,6 @@ dist
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode
|
||||
|
||||
# Locally generated PII reports
|
||||
pii_report
|
||||
|
||||
37
.pii_annotations.yml
Normal file
37
.pii_annotations.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
source_path: ./
|
||||
report_path: pii_report
|
||||
safelist_path: .annotation_safe_list.yml
|
||||
coverage_target: 100.0
|
||||
# See OEP-30 for more information on these values and what they mean:
|
||||
# https://open-edx-proposals.readthedocs.io/en/latest/oep-0030-arch-pii-markup-and-auditing.html#docstring-annotations
|
||||
annotations:
|
||||
".. no_pii:":
|
||||
"pii_group":
|
||||
- ".. pii:":
|
||||
- ".. pii_types:":
|
||||
choices:
|
||||
- id
|
||||
- name
|
||||
- username
|
||||
- password
|
||||
- location
|
||||
- phone_number
|
||||
- email_address
|
||||
- birth_date
|
||||
- ip
|
||||
- external_service
|
||||
- biography
|
||||
- gender
|
||||
- sex
|
||||
- image
|
||||
- video
|
||||
- other
|
||||
- ".. pii_retirement:":
|
||||
choices:
|
||||
- retained
|
||||
- local_api
|
||||
- consumer_api
|
||||
- third_party
|
||||
extensions:
|
||||
python:
|
||||
- py
|
||||
@@ -7,7 +7,11 @@ from django.db.models.fields import TextField
|
||||
|
||||
|
||||
class VideoUploadConfig(ConfigurationModel):
|
||||
"""Configuration for the video upload feature."""
|
||||
"""
|
||||
Configuration for the video upload feature.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
profile_whitelist = TextField(
|
||||
blank=True,
|
||||
help_text="A comma-separated list of names of profiles to include in video encoding downloads."
|
||||
@@ -20,4 +24,8 @@ class VideoUploadConfig(ConfigurationModel):
|
||||
|
||||
|
||||
class PushNotificationConfig(ConfigurationModel):
|
||||
"""Configuration for mobile push notifications."""
|
||||
"""
|
||||
Configuration for mobile push notifications.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
@@ -109,7 +109,7 @@ def reorder_tabs_handler(course_item, request):
|
||||
# validate the tabs to make sure everything is Ok (e.g., did the client try to reorder unmovable tabs?)
|
||||
try:
|
||||
CourseTabList.validate_tabs(new_tab_list)
|
||||
except InvalidTabsException, exception:
|
||||
except InvalidTabsException as exception:
|
||||
return JsonResponse(
|
||||
{"error": u"New list of tabs is not valid: {0}.".format(str(exception))}, status=400
|
||||
)
|
||||
|
||||
@@ -21,6 +21,8 @@ send_user_notification = Signal(providing_args=["user", "state"])
|
||||
class CourseCreator(models.Model):
|
||||
"""
|
||||
Creates the database table model.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
UNREQUESTED = 'unrequested'
|
||||
PENDING = 'pending'
|
||||
|
||||
@@ -15,6 +15,8 @@ from openedx.core.lib.cache_utils import request_cached
|
||||
class StudioConfig(ConfigurationModel):
|
||||
"""
|
||||
Configuration for XBlockAsides.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
disabled_blocks = TextField(
|
||||
default="about course_info static_tab",
|
||||
@@ -36,6 +38,8 @@ class CourseEditLTIFieldsEnabledFlag(ConfigurationModel):
|
||||
"""
|
||||
Enables the editing of "request username" and "request email" fields
|
||||
of LTI consumer for a specific course.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
KEY_FIELDS = ('course_id',)
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ from django.db import models
|
||||
class TagCategories(models.Model):
|
||||
"""
|
||||
This model represents tag categories.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
name = models.CharField(max_length=255, unique=True)
|
||||
title = models.CharField(max_length=255)
|
||||
@@ -30,6 +32,8 @@ class TagCategories(models.Model):
|
||||
class TagAvailableValues(models.Model):
|
||||
"""
|
||||
This model represents available values for tags.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
category = models.ForeignKey(TagCategories, db_index=True, on_delete=models.CASCADE)
|
||||
value = models.CharField(max_length=255)
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<% var online_proctoring_rules = xblockInfo.get('online_proctoring_rules'); %>
|
||||
<p class='field-message' id='review-rules-description'>
|
||||
<% if (online_proctoring_rules) { %>
|
||||
% // xss-lint: disable=underscore-not-escaped %>
|
||||
<% // xss-lint: disable=underscore-not-escaped %>
|
||||
<%= edx.HtmlUtils.interpolateHtml(
|
||||
gettext('Specify any rules or rule exceptions that the proctoring review team should enforce when reviewing the videos. For example, you could specify that calculators are allowed. These specified rules are visible to learners before the learners start the exam, along with the {linkStart}general proctored exam rules{linkEnd}.'),
|
||||
{
|
||||
|
||||
@@ -101,6 +101,8 @@ class CourseActionUIState(CourseActionState):
|
||||
class CourseRerunState(CourseActionUIState):
|
||||
"""
|
||||
A concrete django model for maintaining state specifically for the Action Course Reruns.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
"""
|
||||
|
||||
@@ -38,6 +38,7 @@ class CourseMode(models.Model):
|
||||
"""
|
||||
We would like to offer a course in a variety of modes.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "course_modes"
|
||||
@@ -819,6 +820,8 @@ class CourseModesArchive(models.Model):
|
||||
separate model, because there is a uniqueness contraint on (course_mode, course_id)
|
||||
field pair in CourseModes. Having a separate table allows us to have an audit trail of any changes
|
||||
such as course price changes
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "course_modes"
|
||||
@@ -852,6 +855,8 @@ class CourseModesArchive(models.Model):
|
||||
class CourseModeExpirationConfig(ConfigurationModel):
|
||||
"""
|
||||
Configuration for time period from end of course to auto-expire a course mode.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "course_modes"
|
||||
|
||||
@@ -442,7 +442,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
for mode in ["honor", "verified"]:
|
||||
CourseModeFactory(mode_slug=mode, course_id=self.course.id)
|
||||
|
||||
self.course.enrollment_end = datetime(2015, 01, 01)
|
||||
self.course.enrollment_end = datetime(2015, 1, 1)
|
||||
modulestore().update_item(self.course, self.user.id)
|
||||
|
||||
url = reverse('course_modes_choose', args=[unicode(self.course.id)])
|
||||
|
||||
@@ -65,6 +65,11 @@ def assign_role(course_id, user, rolename):
|
||||
|
||||
|
||||
class Role(models.Model):
|
||||
"""
|
||||
Maps users to django_comment_client roles for a given course
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
objects = NoneToEmptyManager()
|
||||
|
||||
@@ -100,7 +105,9 @@ class Role(models.Model):
|
||||
self.permissions.add(Permission.objects.get_or_create(name=permission)[0])
|
||||
|
||||
def has_permission(self, permission):
|
||||
"""Returns True if this role has the given permission, False otherwise."""
|
||||
"""
|
||||
Returns True if this role has the given permission, False otherwise.
|
||||
"""
|
||||
course = modulestore().get_course(self.course_id)
|
||||
if course is None:
|
||||
raise ItemNotFoundError(self.course_id)
|
||||
@@ -118,6 +125,11 @@ class Role(models.Model):
|
||||
|
||||
|
||||
class Permission(models.Model):
|
||||
"""
|
||||
Permissions for django_comment_client
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
name = models.CharField(max_length=30, null=False, blank=False, primary_key=True)
|
||||
roles = models.ManyToManyField(Role, related_name="permissions")
|
||||
|
||||
@@ -130,7 +142,8 @@ class Permission(models.Model):
|
||||
|
||||
|
||||
def permission_blacked_out(course, role_names, permission_name):
|
||||
"""Returns true if a user in course with the given roles would have permission_name blacked out.
|
||||
"""
|
||||
Returns true if a user in course with the given roles would have permission_name blacked out.
|
||||
|
||||
This will return true if it is a permission that the user might have normally had for the course, but does not have
|
||||
right this moment because we are in a discussion blackout period (as defined by the settings on the course module).
|
||||
@@ -145,7 +158,9 @@ def permission_blacked_out(course, role_names, permission_name):
|
||||
|
||||
|
||||
def all_permissions_for_user_in_course(user, course_id): # pylint: disable=invalid-name
|
||||
"""Returns all the permissions the user has in the given course."""
|
||||
"""
|
||||
Returns all the permissions the user has in the given course.
|
||||
"""
|
||||
if not user.is_authenticated:
|
||||
return {}
|
||||
|
||||
@@ -176,7 +191,11 @@ def all_permissions_for_user_in_course(user, course_id): # pylint: disable=inva
|
||||
|
||||
|
||||
class ForumsConfig(ConfigurationModel):
|
||||
"""Config for the connection to the cs_comments_service forums backend."""
|
||||
"""
|
||||
Config for the connection to the cs_comments_service forums backend.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
connection_timeout = models.FloatField(
|
||||
default=5.0,
|
||||
@@ -189,11 +208,18 @@ class ForumsConfig(ConfigurationModel):
|
||||
return getattr(settings, "COMMENTS_SERVICE_KEY", None)
|
||||
|
||||
def __unicode__(self):
|
||||
"""Simple representation so the admin screen looks less ugly."""
|
||||
"""
|
||||
Simple representation so the admin screen looks less ugly.
|
||||
"""
|
||||
return u"ForumsConfig: timeout={}".format(self.connection_timeout)
|
||||
|
||||
|
||||
class CourseDiscussionSettings(models.Model):
|
||||
"""
|
||||
Settings for course discussions
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
course_id = CourseKeyField(
|
||||
unique=True,
|
||||
max_length=255,
|
||||
@@ -216,17 +242,25 @@ class CourseDiscussionSettings(models.Model):
|
||||
|
||||
@property
|
||||
def divided_discussions(self):
|
||||
"""Jsonify the divided_discussions"""
|
||||
"""
|
||||
Jsonify the divided_discussions
|
||||
"""
|
||||
return json.loads(self._divided_discussions)
|
||||
|
||||
@divided_discussions.setter
|
||||
def divided_discussions(self, value):
|
||||
"""Un-Jsonify the divided_discussions"""
|
||||
"""
|
||||
Un-Jsonify the divided_discussions
|
||||
"""
|
||||
self._divided_discussions = json.dumps(value)
|
||||
|
||||
|
||||
class DiscussionsIdMapping(models.Model):
|
||||
"""This model is a performance optimization, updated on course publish."""
|
||||
"""
|
||||
This model is a performance optimization, updated on course publish.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
course_id = CourseKeyField(db_index=True, primary_key=True, max_length=255)
|
||||
mapping = JSONField(
|
||||
help_text="Key/value store mapping discussion IDs to discussion XBlock usage keys.",
|
||||
|
||||
@@ -26,6 +26,8 @@ log = logging.getLogger("common.entitlements.models")
|
||||
class CourseEntitlementPolicy(models.Model):
|
||||
"""
|
||||
Represents the Entitlement's policy for expiration, refunds, and regaining a used certificate
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
DEFAULT_EXPIRATION_PERIOD_DAYS = 730
|
||||
@@ -146,6 +148,8 @@ class CourseEntitlementPolicy(models.Model):
|
||||
class CourseEntitlement(TimeStampedModel):
|
||||
"""
|
||||
Represents a Student's Entitlement to a Course Run for a given Course.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||
uuid = models.UUIDField(default=uuid_tools.uuid4, editable=False, unique=True)
|
||||
@@ -442,6 +446,8 @@ class CourseEntitlement(TimeStampedModel):
|
||||
class CourseEntitlementSupportDetail(TimeStampedModel):
|
||||
"""
|
||||
Table recording support interactions with an entitlement
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
# Reasons deprecated
|
||||
LEAVE_SESSION = 'LEAVE'
|
||||
|
||||
@@ -28,6 +28,8 @@ class Microsite(models.Model):
|
||||
- The site field is django site.
|
||||
- The values field must be validated on save to prevent the platform from crashing
|
||||
badly in the case the string is not able to be loaded as json.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
site = models.OneToOneField(Site, related_name='microsite', on_delete=models.CASCADE)
|
||||
key = models.CharField(max_length=63, db_index=True, unique=True)
|
||||
@@ -60,6 +62,8 @@ class MicrositeHistory(TimeStampedModel):
|
||||
"""
|
||||
This is an archive table for Microsites model, so that we can maintain a history of changes. Note that the
|
||||
key field is no longer unique
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
site = models.ForeignKey(Site, related_name='microsite_history', on_delete=models.CASCADE)
|
||||
key = models.CharField(max_length=63, db_index=True)
|
||||
@@ -109,6 +113,8 @@ def on_microsite_updated(sender, instance, **kwargs): # pylint: disable=unused-
|
||||
class MicrositeOrganizationMapping(models.Model):
|
||||
"""
|
||||
Mapping of Organization to which Microsite it belongs
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
organization = models.CharField(max_length=63, db_index=True, unique=True)
|
||||
@@ -145,6 +151,8 @@ class MicrositeOrganizationMapping(models.Model):
|
||||
class MicrositeTemplate(models.Model):
|
||||
"""
|
||||
A HTML template that a microsite can use
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
microsite = models.ForeignKey(Microsite, db_index=True, on_delete=models.CASCADE)
|
||||
|
||||
@@ -7,7 +7,11 @@ from django.db.models.fields import TextField
|
||||
|
||||
|
||||
class AssetBaseUrlConfig(ConfigurationModel):
|
||||
"""Configuration for the base URL used for static assets."""
|
||||
"""
|
||||
Configuration for the base URL used for static assets.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
app_label = 'static_replace'
|
||||
@@ -30,7 +34,11 @@ class AssetBaseUrlConfig(ConfigurationModel):
|
||||
|
||||
|
||||
class AssetExcludedExtensionsConfig(ConfigurationModel):
|
||||
"""Configuration for the the excluded file extensions when canonicalizing static asset paths."""
|
||||
"""
|
||||
Configuration for the the excluded file extensions when canonicalizing static asset paths.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
app_label = 'static_replace'
|
||||
|
||||
@@ -9,10 +9,14 @@ from django.core.cache import cache
|
||||
from django.db import models
|
||||
from opaque_keys.edx.django.models import CourseKeyField
|
||||
|
||||
from openedx.core.djangolib.markup import HTML
|
||||
|
||||
|
||||
class GlobalStatusMessage(ConfigurationModel):
|
||||
"""
|
||||
Model that represents the current status message.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
message = models.TextField(
|
||||
blank=True,
|
||||
@@ -37,7 +41,7 @@ class GlobalStatusMessage(ConfigurationModel):
|
||||
course_home_message = self.coursemessage_set.get(course_key=course_key)
|
||||
# Don't override the message if course_home_message is blank.
|
||||
if course_home_message:
|
||||
msg = u"{} <br /> {}".format(msg, course_home_message.message)
|
||||
msg = HTML(u"{} <br /> {}").format(HTML(msg), HTML(course_home_message.message))
|
||||
except CourseMessage.DoesNotExist:
|
||||
# We don't have a course-specific message, so pass.
|
||||
pass
|
||||
@@ -54,6 +58,8 @@ class CourseMessage(models.Model):
|
||||
|
||||
This is not a ConfigurationModel because using it's not designed to support multiple configurations at once,
|
||||
which would be problematic if separate courses need separate error messages.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
global_message = models.ForeignKey(GlobalStatusMessage, on_delete=models.CASCADE)
|
||||
course_key = CourseKeyField(max_length=255, blank=True, db_index=True)
|
||||
|
||||
@@ -126,6 +126,8 @@ class AnonymousUserId(models.Model):
|
||||
|
||||
We generate anonymous_user_id using md5 algorithm,
|
||||
and use result in hex form, so its length is equal to 32 bytes.
|
||||
|
||||
.. no_pii: We store anonymous_user_ids here, but do not consider them PII under OEP-30.
|
||||
"""
|
||||
|
||||
objects = NoneToEmptyManager()
|
||||
@@ -376,6 +378,8 @@ class UserStanding(models.Model):
|
||||
Currently, we're only disabling accounts; in the future we can imagine
|
||||
taking away more specific privileges, like forums access, or adding
|
||||
more specific karma levels or probationary stages.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
ACCOUNT_DISABLED = "disabled"
|
||||
ACCOUNT_ENABLED = "enabled"
|
||||
@@ -409,6 +413,10 @@ class UserProfile(models.Model):
|
||||
|
||||
Some of the fields are legacy ones that were captured during the initial
|
||||
MITx fall prototype.
|
||||
|
||||
.. pii: Contains many PII fields. Retired in AccountRetirementView.
|
||||
.. pii_types: name, location, birth_date, gender, biography
|
||||
.. pii_retirement: local_api
|
||||
"""
|
||||
# cache key format e.g user.<user_id>.profile.country = 'SG'
|
||||
PROFILE_COUNTRY_CACHE_KEY = u"user.{user_id}.profile.country"
|
||||
@@ -701,6 +709,8 @@ class UserSignupSource(models.Model):
|
||||
"""
|
||||
This table contains information about users registering
|
||||
via Micro-Sites
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE)
|
||||
site = models.CharField(max_length=255, db_index=True)
|
||||
@@ -722,16 +732,23 @@ def unique_id_for_user(user, save=True):
|
||||
# TODO: Should be renamed to generic UserGroup, and possibly
|
||||
# Given an optional field for type of group
|
||||
class UserTestGroup(models.Model):
|
||||
"""
|
||||
.. no_pii:
|
||||
"""
|
||||
users = models.ManyToManyField(User, db_index=True)
|
||||
name = models.CharField(blank=False, max_length=32, db_index=True)
|
||||
description = models.TextField(blank=True)
|
||||
|
||||
|
||||
class Registration(models.Model):
|
||||
''' Allows us to wait for e-mail before user is registered. A
|
||||
registration profile is created when the user creates an
|
||||
account, but that account is inactive. Once the user clicks
|
||||
on the activation key, it becomes active. '''
|
||||
"""
|
||||
Allows us to wait for e-mail before user is registered. A
|
||||
registration profile is created when the user creates an
|
||||
account, but that account is inactive. Once the user clicks
|
||||
on the activation key, it becomes active.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
db_table = "auth_registration"
|
||||
@@ -752,10 +769,15 @@ class Registration(models.Model):
|
||||
log.info(u'User %s (%s) account is successfully activated.', self.user.username, self.user.email)
|
||||
|
||||
def _track_activation(self):
|
||||
""" Update the isActive flag in mailchimp for activated users."""
|
||||
"""
|
||||
Update the isActive flag in mailchimp for activated users.
|
||||
"""
|
||||
has_segment_key = getattr(settings, 'LMS_SEGMENT_KEY', None)
|
||||
has_mailchimp_id = hasattr(settings, 'MAILCHIMP_NEW_USER_LIST_ID')
|
||||
if has_segment_key and has_mailchimp_id:
|
||||
# .. pii: Username and email are sent to Segment here. Retired directly through Segment API call in Tubular.
|
||||
# .. pii_types: email_address, username
|
||||
# .. pii_retirement: third_party
|
||||
segment.identify(
|
||||
self.user.id, # pylint: disable=no-member
|
||||
{
|
||||
@@ -772,6 +794,13 @@ class Registration(models.Model):
|
||||
|
||||
|
||||
class PendingNameChange(DeletableByUserValue, models.Model):
|
||||
"""
|
||||
This model keeps track of pending requested changes to a user's email address.
|
||||
|
||||
.. pii: Contains new_name, retired in LMSAccountRetirementView
|
||||
.. pii_types: name
|
||||
.. pii_retirement: local_api
|
||||
"""
|
||||
user = models.OneToOneField(User, unique=True, db_index=True, on_delete=models.CASCADE)
|
||||
new_name = models.CharField(blank=True, max_length=255)
|
||||
rationale = models.CharField(blank=True, max_length=1024)
|
||||
@@ -780,6 +809,10 @@ class PendingNameChange(DeletableByUserValue, models.Model):
|
||||
class PendingEmailChange(DeletableByUserValue, models.Model):
|
||||
"""
|
||||
This model keeps track of pending requested changes to a user's email address.
|
||||
|
||||
.. pii: Contains new_email, retired in AccountRetirementView
|
||||
.. pii_types: email_address
|
||||
.. pii_retirement: local_api
|
||||
"""
|
||||
user = models.OneToOneField(User, unique=True, db_index=True, on_delete=models.CASCADE)
|
||||
new_email = models.CharField(blank=True, max_length=255, db_index=True)
|
||||
@@ -806,6 +839,10 @@ class PendingEmailChange(DeletableByUserValue, models.Model):
|
||||
class PendingSecondaryEmailChange(DeletableByUserValue, models.Model):
|
||||
"""
|
||||
This model keeps track of pending requested changes to a user's secondary email address.
|
||||
|
||||
.. pii: Contains new_secondary_email, not currently retired
|
||||
.. pii_types: email_address
|
||||
.. pii_retirement: retained
|
||||
"""
|
||||
user = models.OneToOneField(User, unique=True, db_index=True, on_delete=models.CASCADE)
|
||||
new_secondary_email = models.CharField(blank=True, max_length=255, db_index=True)
|
||||
@@ -819,7 +856,9 @@ EVENT_NAME_ENROLLMENT_MODE_CHANGED = 'edx.course.enrollment.mode_changed'
|
||||
|
||||
class LoginFailures(models.Model):
|
||||
"""
|
||||
This model will keep track of failed login attempts
|
||||
This model will keep track of failed login attempts.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
failure_count = models.IntegerField(default=0)
|
||||
@@ -1023,6 +1062,8 @@ class CourseEnrollment(models.Model):
|
||||
more should be brought in (such as checking against CourseEnrollmentAllowed,
|
||||
checking course dates, user permissions, etc.) This logic is currently
|
||||
scattered across our views.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
MODEL_TAGS = ['course', 'is_active', 'mode']
|
||||
|
||||
@@ -1938,7 +1979,9 @@ class CourseEnrollment(models.Model):
|
||||
@receiver(models.signals.post_save, sender=CourseEnrollment)
|
||||
@receiver(models.signals.post_delete, sender=CourseEnrollment)
|
||||
def invalidate_enrollment_mode_cache(sender, instance, **kwargs): # pylint: disable=unused-argument, invalid-name
|
||||
"""Invalidate the cache of CourseEnrollment model. """
|
||||
"""
|
||||
Invalidate the cache of CourseEnrollment model.
|
||||
"""
|
||||
|
||||
cache_key = CourseEnrollment.cache_key_name(
|
||||
instance.user.id,
|
||||
@@ -1950,6 +1993,10 @@ def invalidate_enrollment_mode_cache(sender, instance, **kwargs): # pylint: dis
|
||||
class ManualEnrollmentAudit(models.Model):
|
||||
"""
|
||||
Table for tracking which enrollments were performed through manual enrollment.
|
||||
|
||||
.. pii: Contains enrolled_email, retired in LMSAccountRetirementView
|
||||
.. pii_types: email_address
|
||||
.. pii_retirement: local_api
|
||||
"""
|
||||
enrollment = models.ForeignKey(CourseEnrollment, null=True, on_delete=models.CASCADE)
|
||||
enrolled_by = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
|
||||
@@ -2016,6 +2063,8 @@ class CourseEnrollmentAllowed(DeletableByUserValue, models.Model):
|
||||
even if the enrollment time window is past. Once an enrollment from this list effectively happens,
|
||||
the object is marked with the student who enrolled, to prevent students from changing e-mails and
|
||||
enrolling many accounts through the same e-mail.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
email = models.CharField(max_length=255, db_index=True)
|
||||
course_id = CourseKeyField(max_length=255, db_index=True)
|
||||
@@ -2072,6 +2121,8 @@ class CourseAccessRole(models.Model):
|
||||
Maps users to org, courses, and roles. Used by student.roles.CourseRole and OrgRole.
|
||||
To establish a user as having a specific role over all courses in the org, create an entry
|
||||
without a course_id.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
objects = NoneToEmptyManager()
|
||||
@@ -2285,10 +2336,12 @@ def enforce_single_login(sender, request, user, signal, **kwargs): # pylint:
|
||||
|
||||
|
||||
class DashboardConfiguration(ConfigurationModel):
|
||||
"""Dashboard Configuration settings.
|
||||
"""
|
||||
Dashboard Configuration settings.
|
||||
|
||||
Includes configuration options for the dashboard, which impact behavior and rendering for the application.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
recent_enrollment_time_delta = models.PositiveIntegerField(
|
||||
default=0,
|
||||
@@ -2311,6 +2364,8 @@ class LinkedInAddToProfileConfiguration(ConfigurationModel):
|
||||
users are sent to the LinkedIn site with a pre-filled
|
||||
form allowing them to add the certificate to their
|
||||
LinkedIn profile.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
MODE_TO_CERT_NAME = {
|
||||
@@ -2437,6 +2492,8 @@ class LinkedInAddToProfileConfiguration(ConfigurationModel):
|
||||
class EntranceExamConfiguration(models.Model):
|
||||
"""
|
||||
Represents a Student's entrance exam specific data for a single Course
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE)
|
||||
@@ -2504,6 +2561,8 @@ class LanguageProficiency(models.Model):
|
||||
to go through the accounts API (AccountsView) defined in
|
||||
/edx-platform/openedx/core/djangoapps/user_api/accounts/views.py or its associated api method
|
||||
(update_account_settings) so that the events are emitted.
|
||||
|
||||
.. no_pii: Language is not PII value according to OEP-30.
|
||||
"""
|
||||
class Meta(object):
|
||||
unique_together = (('code', 'user_profile'),)
|
||||
@@ -2527,6 +2586,10 @@ class SocialLink(models.Model): # pylint: disable=model-missing-unicode
|
||||
component of the stored URL and an example of a valid URL.
|
||||
|
||||
The stored social_link value must adhere to the form 'https://www.[url_stub][username]'.
|
||||
|
||||
.. pii: Stores linkage from User to a learner's social media profiles. Retired in AccountRetirementView.
|
||||
.. pii_types: external_service
|
||||
.. pii_retirement: local_api
|
||||
"""
|
||||
user_profile = models.ForeignKey(UserProfile, db_index=True, related_name='social_links', on_delete=models.CASCADE)
|
||||
platform = models.CharField(max_length=30)
|
||||
@@ -2536,6 +2599,8 @@ class SocialLink(models.Model): # pylint: disable=model-missing-unicode
|
||||
class CourseEnrollmentAttribute(models.Model):
|
||||
"""
|
||||
Provide additional information about the user's enrollment.
|
||||
|
||||
.. no_pii: This stores key/value pairs, of which there is no full list, but the ones currently in use are not PII
|
||||
"""
|
||||
enrollment = models.ForeignKey(CourseEnrollment, related_name="attributes", on_delete=models.CASCADE)
|
||||
namespace = models.CharField(
|
||||
@@ -2561,12 +2626,13 @@ class CourseEnrollmentAttribute(models.Model):
|
||||
|
||||
@classmethod
|
||||
def add_enrollment_attr(cls, enrollment, data_list):
|
||||
"""Delete all the enrollment attributes for the given enrollment and
|
||||
"""
|
||||
Delete all the enrollment attributes for the given enrollment and
|
||||
add new attributes.
|
||||
|
||||
Args:
|
||||
enrollment(CourseEnrollment): 'CourseEnrollment' for which attribute is to be added
|
||||
data(list): list of dictionaries containing data to save
|
||||
enrollment (CourseEnrollment): 'CourseEnrollment' for which attribute is to be added
|
||||
data_list: list of dictionaries containing data to save
|
||||
"""
|
||||
cls.objects.filter(enrollment=enrollment).delete()
|
||||
attributes = [
|
||||
@@ -2607,6 +2673,8 @@ class CourseEnrollmentAttribute(models.Model):
|
||||
class EnrollmentRefundConfiguration(ConfigurationModel):
|
||||
"""
|
||||
Configuration for course enrollment refunds.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
# TODO: Django 1.8 introduces a DurationField
|
||||
@@ -2638,6 +2706,8 @@ class EnrollmentRefundConfiguration(ConfigurationModel):
|
||||
class RegistrationCookieConfiguration(ConfigurationModel):
|
||||
"""
|
||||
Configuration for registration cookies.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
utm_cookie_name = models.CharField(
|
||||
max_length=255,
|
||||
@@ -2660,6 +2730,8 @@ class RegistrationCookieConfiguration(ConfigurationModel):
|
||||
class UserAttribute(TimeStampedModel):
|
||||
"""
|
||||
Record additional metadata about a user, stored as key/value pairs of text.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
@@ -2700,10 +2772,16 @@ class UserAttribute(TimeStampedModel):
|
||||
|
||||
|
||||
class LogoutViewConfiguration(ConfigurationModel):
|
||||
""" DEPRECATED: Configuration for the logout view. """
|
||||
"""
|
||||
DEPRECATED: Configuration for the logout view.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
def __unicode__(self):
|
||||
"""Unicode representation of the instance. """
|
||||
"""
|
||||
Unicode representation of the instance.
|
||||
"""
|
||||
return u'Logout view configuration: {enabled}'.format(enabled=self.enabled)
|
||||
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@ class AuthNotConfigured(SocialAuthBaseException):
|
||||
class ProviderConfig(ConfigurationModel):
|
||||
"""
|
||||
Abstract Base Class for configuring a third_party_auth provider
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
KEY_FIELDS = ('slug',)
|
||||
|
||||
@@ -327,6 +329,8 @@ class OAuth2ProviderConfig(ProviderConfig):
|
||||
"""
|
||||
Configuration Entry for an OAuth2 based provider.
|
||||
Also works for OAuth1 providers.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
prefix = 'oa2'
|
||||
backend_name = models.CharField(
|
||||
@@ -381,6 +385,8 @@ class SAMLConfiguration(ConfigurationModel):
|
||||
General configuration required for this edX instance to act as a SAML
|
||||
Service Provider and allow users to authenticate via third party SAML
|
||||
Identity Providers (IdPs)
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
KEY_FIELDS = ('site_id', 'slug')
|
||||
site = models.ForeignKey(
|
||||
@@ -523,6 +529,8 @@ def active_saml_configurations_filter():
|
||||
class SAMLProviderConfig(ProviderConfig):
|
||||
"""
|
||||
Configuration Entry for a SAML/Shibboleth provider.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
prefix = 'saml'
|
||||
backend_name = models.CharField(
|
||||
@@ -704,6 +712,8 @@ class SAMLProviderData(models.Model):
|
||||
Data about a SAML IdP that is fetched automatically by 'manage.py saml pull'
|
||||
|
||||
This data is only required during the actual authentication process.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
cache_timeout = 600
|
||||
fetched_at = models.DateTimeField(db_index=True, null=False)
|
||||
@@ -754,6 +764,8 @@ class LTIProviderConfig(ProviderConfig):
|
||||
Configuration required for this edX instance to act as a LTI
|
||||
Tool Provider and allow users to authenticate and be enrolled in a
|
||||
course via third party LTI Tool Consumers.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
prefix = 'lti'
|
||||
backend_name = 'lti'
|
||||
@@ -844,6 +856,8 @@ class ProviderApiPermissions(models.Model):
|
||||
This model links OAuth2 client with provider Id.
|
||||
|
||||
It gives permission for a OAuth2 client to access the information under certain IdPs.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
client = models.ForeignKey(Client, on_delete=models.CASCADE)
|
||||
provider_id = models.CharField(
|
||||
|
||||
@@ -32,7 +32,13 @@ LOGFIELDS = [
|
||||
|
||||
|
||||
class TrackingLog(models.Model):
|
||||
"""Defines the fields that are stored in the tracking log database."""
|
||||
"""
|
||||
Defines the fields that are stored in the tracking log database.
|
||||
|
||||
.. pii: Stores a great deal of PII as it is an event tracker of browsing history, unused and empty on edx.org
|
||||
.. pii_types: username, ip, other
|
||||
.. pii_retirement: retained
|
||||
"""
|
||||
|
||||
dtcreated = models.DateTimeField('creation date', auto_now_add=True)
|
||||
username = models.CharField(max_length=32, blank=True)
|
||||
|
||||
@@ -16,7 +16,9 @@ from eventtracking import tracker
|
||||
|
||||
|
||||
def track(user_id, event_name, properties=None, context=None):
|
||||
"""Wrapper for emitting Segment track event, including augmenting context information from middleware."""
|
||||
"""
|
||||
Wrapper for emitting Segment track event, including augmenting context information from middleware.
|
||||
"""
|
||||
|
||||
if event_name is not None and hasattr(settings, 'LMS_SEGMENT_KEY') and settings.LMS_SEGMENT_KEY:
|
||||
properties = properties or {}
|
||||
@@ -60,7 +62,9 @@ def track(user_id, event_name, properties=None, context=None):
|
||||
|
||||
|
||||
def identify(user_id, properties, context=None):
|
||||
"""Wrapper for emitting Segment identify event."""
|
||||
"""
|
||||
Wrapper for emitting Segment identify event.
|
||||
"""
|
||||
if hasattr(settings, 'LMS_SEGMENT_KEY') and settings.LMS_SEGMENT_KEY:
|
||||
segment_context = dict(context) if context else {}
|
||||
analytics.identify(user_id, properties, segment_context)
|
||||
|
||||
@@ -9,8 +9,8 @@ from track.utils import DateTimeJSONEncoder
|
||||
|
||||
class TestDateTimeJSONEncoder(TestCase):
|
||||
def test_datetime_encoding(self):
|
||||
a_naive_datetime = datetime(2012, 05, 01, 07, 27, 10, 20000)
|
||||
a_tz_datetime = datetime(2012, 05, 01, 07, 27, 10, 20000, tzinfo=UTC)
|
||||
a_naive_datetime = datetime(2012, 5, 1, 7, 27, 10, 20000)
|
||||
a_tz_datetime = datetime(2012, 5, 1, 7, 27, 10, 20000, tzinfo=UTC)
|
||||
a_date = a_naive_datetime.date()
|
||||
an_iso_datetime = '2012-05-01T07:27:10.020000+00:00'
|
||||
an_iso_date = '2012-05-01'
|
||||
|
||||
@@ -13,13 +13,16 @@ logger = logging.getLogger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
class RateLimitConfiguration(ConfigurationModel):
|
||||
"""Configuration flag to enable/disable rate limiting.
|
||||
"""
|
||||
Configuration flag to enable/disable rate limiting.
|
||||
|
||||
Applies to Django Rest Framework views.
|
||||
|
||||
This is useful for disabling rate limiting for performance tests.
|
||||
When enabled, it will disable rate limiting on any view decorated
|
||||
with the `can_disable_rate_limit` class decorator.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(ConfigurationModel.Meta):
|
||||
app_label = "util"
|
||||
@@ -43,11 +46,15 @@ def decompress_string(value):
|
||||
|
||||
|
||||
class CompressedTextField(CreatorMixin, models.TextField):
|
||||
""" TextField that transparently compresses data when saving to the database, and decompresses the data
|
||||
when retrieving it from the database. """
|
||||
"""
|
||||
TextField that transparently compresses data when saving to the database, and decompresses the data
|
||||
when retrieving it from the database.
|
||||
"""
|
||||
|
||||
def get_prep_value(self, value):
|
||||
""" Compress the text data. """
|
||||
"""
|
||||
Compress the text data.
|
||||
"""
|
||||
if value is not None:
|
||||
if isinstance(value, unicode):
|
||||
value = value.encode('utf8')
|
||||
@@ -56,7 +63,9 @@ class CompressedTextField(CreatorMixin, models.TextField):
|
||||
return value
|
||||
|
||||
def to_python(self, value):
|
||||
""" Decompresses the value from the database. """
|
||||
"""
|
||||
Decompresses the value from the database.
|
||||
"""
|
||||
if isinstance(value, unicode):
|
||||
value = decompress_string(value)
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ from django.utils.translation import ugettext_lazy as _
|
||||
class XBlockConfiguration(ConfigurationModel):
|
||||
"""
|
||||
XBlock configuration used by both LMS and Studio, and not specific to a particular template.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
KEY_FIELDS = ('name',) # xblock name is unique
|
||||
@@ -33,6 +35,8 @@ class XBlockConfiguration(ConfigurationModel):
|
||||
class XBlockStudioConfigurationFlag(ConfigurationModel):
|
||||
"""
|
||||
Enables site-wide Studio configuration for XBlocks.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
@@ -47,6 +51,8 @@ class XBlockStudioConfigurationFlag(ConfigurationModel):
|
||||
class XBlockStudioConfiguration(ConfigurationModel):
|
||||
"""
|
||||
Studio editing configuration for a specific XBlock/template combination.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
KEY_FIELDS = ('name', 'template') # xblock name/template combination is unique
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ def parse_xreply(xreply):
|
||||
"""
|
||||
try:
|
||||
xreply = json.loads(xreply)
|
||||
except ValueError, err:
|
||||
except ValueError as err:
|
||||
log.error(err)
|
||||
return (1, 'unexpected reply from server')
|
||||
|
||||
@@ -135,11 +135,11 @@ class XQueueInterface(object):
|
||||
response = self.session.post(
|
||||
url, data=data, files=files, timeout=(CONNECT_TIMEOUT, READ_TIMEOUT)
|
||||
)
|
||||
except requests.exceptions.ConnectionError, err:
|
||||
except requests.exceptions.ConnectionError as err:
|
||||
log.error(err)
|
||||
return (1, 'cannot connect to server')
|
||||
|
||||
except requests.exceptions.ReadTimeout, err:
|
||||
except requests.exceptions.ReadTimeout as err:
|
||||
log.error(err)
|
||||
return (1, 'failed to read from the server')
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ def lc_choose(index, *args):
|
||||
'''
|
||||
try:
|
||||
return args[int(index) - 1]
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
pass
|
||||
if len(args):
|
||||
return args[0]
|
||||
|
||||
@@ -451,7 +451,7 @@ class formula(object):
|
||||
try:
|
||||
cmml = self.cmathml
|
||||
xml = etree.fromstring(str(cmml))
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
if 'conversion from Presentation MathML to Content MathML was not successful' in cmml:
|
||||
msg = "Illegal math expression"
|
||||
else:
|
||||
@@ -538,7 +538,7 @@ class formula(object):
|
||||
args = [self.make_sympy(expr) for expr in xml[1:]]
|
||||
try:
|
||||
res = op(*args)
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
self.args = args # pylint: disable=attribute-defined-outside-init
|
||||
self.op = op # pylint: disable=attribute-defined-outside-init, invalid-name
|
||||
raise Exception('[formula] error=%s failed to apply %s to args=%s' % (err, opstr, args))
|
||||
|
||||
@@ -46,7 +46,7 @@ def symmath_check_simple(expect, ans, adict={}, symtab=None, extra_options=None)
|
||||
abcsym=options['__ABC__'],
|
||||
symtab=symtab,
|
||||
)
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
return {'ok': False,
|
||||
'msg': 'Error %s<br/>Failed in evaluating check(%s,%s)' % (err, expect, ans)
|
||||
}
|
||||
@@ -92,22 +92,22 @@ def check(expect, given, numerical=False, matrix=False, normphase=False, abcsym=
|
||||
|
||||
try:
|
||||
xgiven = my_sympify(given, normphase, matrix, do_qubit=do_qubit, abcsym=abcsym, symtab=symtab)
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
return {'ok': False, 'msg': 'Error %s<br/> in evaluating your expression "%s"' % (err, given)}
|
||||
|
||||
try:
|
||||
xexpect = my_sympify(expect, normphase, matrix, do_qubit=do_qubit, abcsym=abcsym, symtab=symtab)
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
return {'ok': False, 'msg': 'Error %s<br/> in evaluating OUR expression "%s"' % (err, expect)}
|
||||
|
||||
if 'autonorm' in flags: # normalize trace of matrices
|
||||
try:
|
||||
xgiven /= xgiven.trace()
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
return {'ok': False, 'msg': 'Error %s<br/> in normalizing trace of your expression %s' % (err, to_latex(xgiven))}
|
||||
try:
|
||||
xexpect /= xexpect.trace()
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
return {'ok': False, 'msg': 'Error %s<br/> in normalizing trace of OUR expression %s' % (err, to_latex(xexpect))}
|
||||
|
||||
msg = 'Your expression was evaluated as ' + to_latex(xgiven)
|
||||
@@ -208,7 +208,7 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None
|
||||
# parse expected answer
|
||||
try:
|
||||
fexpect = my_sympify(str(expect), matrix=do_matrix, do_qubit=do_qubit)
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
msg += '<p>Error %s in parsing OUR expected answer "%s"</p>' % (err, expect)
|
||||
return {'ok': False, 'msg': make_error_message(msg)}
|
||||
|
||||
@@ -216,7 +216,7 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None
|
||||
# if expected answer is a number, try parsing provided answer as a number also
|
||||
try:
|
||||
fans = my_sympify(str(ans), matrix=do_matrix, do_qubit=do_qubit)
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
fans = None
|
||||
|
||||
# do a numerical comparison if both expected and answer are numbers
|
||||
@@ -243,7 +243,7 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None
|
||||
# convert mathml answer to formula
|
||||
try:
|
||||
mmlans = dynamath[0] if dynamath else None
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
mmlans = None
|
||||
if not mmlans:
|
||||
return {'ok': False, 'msg': '[symmath_check] failed to get MathML for input; dynamath=%s' % dynamath}
|
||||
@@ -255,7 +255,7 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None
|
||||
try:
|
||||
fsym = f.sympy
|
||||
msg += '<p>You entered: %s</p>' % to_latex(f.sympy)
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
log.exception("Error evaluating expression '%s' as a valid equation", ans)
|
||||
msg += "<p>Error in evaluating your expression '%s' as a valid equation</p>" % (ans)
|
||||
if "Illegal math" in str(err):
|
||||
@@ -298,7 +298,7 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None
|
||||
except sympy.ShapeError:
|
||||
msg += "<p>Error - your input vector or matrix has the wrong dimensions"
|
||||
return {'ok': False, 'msg': make_error_message(msg)}
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
msg += "<p>Error %s in comparing expected (a list) and your answer</p>" % str(err).replace('<', '<')
|
||||
if DEBUG:
|
||||
msg += "<p/><pre>%s</pre>" % traceback.format_exc()
|
||||
@@ -309,7 +309,7 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None
|
||||
#fexpect = fexpect.simplify()
|
||||
try:
|
||||
diff = (fexpect - fsym)
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
diff = None
|
||||
|
||||
if DEBUG:
|
||||
|
||||
@@ -455,7 +455,7 @@ class ContentStore(object):
|
||||
|
||||
self.save(thumbnail_content)
|
||||
|
||||
except Exception, exc: # pylint: disable=broad-except
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
# log and continue as thumbnails are generally considered as optional
|
||||
logging.exception(
|
||||
u"Failed to generate thumbnail for {0}. Exception: {1}".format(content.location, str(exc))
|
||||
|
||||
@@ -196,7 +196,7 @@ class ToyCourseFactory(SampleCourseFactory):
|
||||
'graded': True,
|
||||
'discussion_topics': {"General": {"id": "i4x-edX-toy-course-2012_Fall"}},
|
||||
'graceperiod': datetime.timedelta(days=2, seconds=21599),
|
||||
'start': datetime.datetime(2015, 07, 17, 12, tzinfo=pytz.utc),
|
||||
'start': datetime.datetime(2015, 7, 17, 12, tzinfo=pytz.utc),
|
||||
'xml_attributes': {"filename": ["course/2012_Fall.xml", "course/2012_Fall.xml"]},
|
||||
'pdf_textbooks': [
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@ from opaque_keys.edx.django.models import CourseKeyField
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from badges.utils import deserialize_count_specs
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
|
||||
@@ -47,6 +48,8 @@ class CourseBadgesDisabledError(Exception):
|
||||
class BadgeClass(models.Model):
|
||||
"""
|
||||
Specifies a badge class to be registered with a backend.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
slug = models.SlugField(max_length=255, validators=[validate_lowercase])
|
||||
issuing_component = models.SlugField(max_length=50, default='', blank=True, validators=[validate_lowercase])
|
||||
@@ -59,8 +62,8 @@ class BadgeClass(models.Model):
|
||||
image = models.ImageField(upload_to='badge_classes', validators=[validate_badge_image])
|
||||
|
||||
def __unicode__(self):
|
||||
return u"<Badge '{slug}' for '{issuing_component}'>".format(
|
||||
slug=self.slug, issuing_component=self.issuing_component
|
||||
return HTML(u"<Badge '{slug}' for '{issuing_component}'>").format(
|
||||
slug=HTML(self.slug), issuing_component=HTML(self.issuing_component)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -140,6 +143,8 @@ class BadgeClass(models.Model):
|
||||
class BadgeAssertion(TimeStampedModel):
|
||||
"""
|
||||
Tracks badges on our side of the badge baking transaction
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
badge_class = models.ForeignKey(BadgeClass, on_delete=models.CASCADE)
|
||||
@@ -149,9 +154,10 @@ class BadgeAssertion(TimeStampedModel):
|
||||
assertion_url = models.URLField()
|
||||
|
||||
def __unicode__(self):
|
||||
return u"<{username} Badge Assertion for {slug} for {issuing_component}".format(
|
||||
username=self.user.username, slug=self.badge_class.slug,
|
||||
issuing_component=self.badge_class.issuing_component,
|
||||
return HTML(u"<{username} Badge Assertion for {slug} for {issuing_component}").format(
|
||||
username=HTML(self.user.username),
|
||||
slug=HTML(self.badge_class.slug),
|
||||
issuing_component=HTML(self.badge_class.issuing_component),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -174,6 +180,8 @@ BadgeAssertion._meta.get_field('created').db_index = True
|
||||
class CourseCompleteImageConfiguration(models.Model):
|
||||
"""
|
||||
Contains the icon configuration for badges for a specific course mode.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
mode = models.CharField(
|
||||
max_length=125,
|
||||
@@ -197,9 +205,9 @@ class CourseCompleteImageConfiguration(models.Model):
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
return u"<CourseCompleteImageConfiguration for '{mode}'{default}>".format(
|
||||
mode=self.mode,
|
||||
default=u" (default)" if self.default else u''
|
||||
return HTML(u"<CourseCompleteImageConfiguration for '{mode}'{default}>").format(
|
||||
mode=HTML(self.mode),
|
||||
default=HTML(u" (default)") if self.default else HTML(u'')
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
@@ -228,6 +236,8 @@ class CourseEventBadgesConfiguration(ConfigurationModel):
|
||||
"""
|
||||
Determines the settings for meta course awards-- such as completing a certain
|
||||
number of courses or enrolling in a certain number of them.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
courses_completed = models.TextField(
|
||||
blank=True, default='',
|
||||
@@ -256,7 +266,9 @@ class CourseEventBadgesConfiguration(ConfigurationModel):
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
return u"<CourseEventBadgesConfiguration ({})>".format(u"Enabled" if self.enabled else u"Disabled")
|
||||
return HTML(u"<CourseEventBadgesConfiguration ({})>").format(
|
||||
Text(u"Enabled") if self.enabled else Text(u"Disabled")
|
||||
)
|
||||
|
||||
@property
|
||||
def completed_settings(self):
|
||||
|
||||
@@ -24,6 +24,8 @@ class BrandingInfoConfig(ConfigurationModel):
|
||||
"logo_tag": "Video hosted by XuetangX.com"
|
||||
}
|
||||
}
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(ConfigurationModel.Meta):
|
||||
app_label = "branding"
|
||||
@@ -57,6 +59,8 @@ class BrandingApiConfig(ConfigurationModel):
|
||||
When this flag is disabled, the api will return 404.
|
||||
|
||||
When the flag is enabled, the api will returns the valid reponse.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(ConfigurationModel.Meta):
|
||||
app_label = "branding"
|
||||
|
||||
@@ -27,6 +27,8 @@ log = logging.getLogger(__name__)
|
||||
class Email(models.Model):
|
||||
"""
|
||||
Abstract base class for common information for an email.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
sender = models.ForeignKey(User, default=1, blank=True, null=True, on_delete=models.CASCADE)
|
||||
slug = models.CharField(max_length=128, db_index=True)
|
||||
@@ -65,6 +67,8 @@ class Target(models.Model):
|
||||
SEND_TO_COHORT), then explicitly call the method on self.cohorttarget, which is created
|
||||
by django as part of this inheritance setup. These calls require pylint disable no-member in
|
||||
several locations in this class.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
target_type = models.CharField(max_length=64, choices=EMAIL_TARGET_CHOICES)
|
||||
|
||||
@@ -138,6 +142,8 @@ class Target(models.Model):
|
||||
class CohortTarget(Target):
|
||||
"""
|
||||
Subclass of Target, specifically referring to a cohort.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
cohort = models.ForeignKey('course_groups.CourseUserGroup', on_delete=models.CASCADE)
|
||||
|
||||
@@ -181,6 +187,8 @@ class CohortTarget(Target):
|
||||
class CourseModeTarget(Target):
|
||||
"""
|
||||
Subclass of Target, specifically for course modes.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
track = models.ForeignKey('course_modes.CourseMode', on_delete=models.CASCADE)
|
||||
|
||||
@@ -226,6 +234,8 @@ class CourseModeTarget(Target):
|
||||
class CourseEmail(Email):
|
||||
"""
|
||||
Stores information for an email to a course.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "bulk_email"
|
||||
@@ -302,6 +312,8 @@ class CourseEmail(Email):
|
||||
class Optout(models.Model):
|
||||
"""
|
||||
Stores users that have opted out of receiving emails from a course.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
# Allowing null=True to support data migration from email->user.
|
||||
# We need to first create the 'user' column with some sort of default in order to run the data migration,
|
||||
@@ -327,6 +339,8 @@ class CourseEmailTemplate(models.Model):
|
||||
Initialization takes place in a migration that in turn loads a fixture.
|
||||
The admin console interface disables add and delete operations.
|
||||
Validation is handled in the CourseEmailTemplateForm class.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "bulk_email"
|
||||
@@ -407,6 +421,8 @@ class CourseEmailTemplate(models.Model):
|
||||
class CourseAuthorization(models.Model):
|
||||
"""
|
||||
Enable the course email feature on a course-by-course basis.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "bulk_email"
|
||||
@@ -442,6 +458,8 @@ class BulkEmailFlag(ConfigurationModel):
|
||||
Staff can only send bulk email for a course if all the following conditions are true:
|
||||
1. BulkEmailFlag is enabled.
|
||||
2. Course-specific authorization not required, or course authorized to use bulk email.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
# boolean field 'enabled' inherited from parent ConfigurationModel
|
||||
require_course_email_auth = models.BooleanField(default=True)
|
||||
|
||||
@@ -23,6 +23,8 @@ log = logging.getLogger("edx.ccx")
|
||||
class CustomCourseForEdX(models.Model):
|
||||
"""
|
||||
A Custom Course.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
course_id = CourseKeyField(max_length=255, db_index=True)
|
||||
display_name = models.CharField(max_length=255)
|
||||
@@ -106,6 +108,8 @@ class CustomCourseForEdX(models.Model):
|
||||
class CcxFieldOverride(models.Model):
|
||||
"""
|
||||
Field overrides for custom courses.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
ccx = models.ForeignKey(CustomCourseForEdX, db_index=True, on_delete=models.CASCADE)
|
||||
location = UsageKeyField(max_length=255, db_index=True)
|
||||
|
||||
@@ -130,6 +130,8 @@ class CertificateWhitelist(models.Model):
|
||||
regardless of their grade unless they are on the
|
||||
embargoed country restriction list
|
||||
(allow_certificate set to False in userprofile).
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "certificates"
|
||||
@@ -213,6 +215,10 @@ class EligibleCertificateManager(models.Manager):
|
||||
class GeneratedCertificate(models.Model):
|
||||
"""
|
||||
Base model for generated certificates
|
||||
|
||||
.. pii: PII can exist in the generated certificate linked to in this model. Certificate data is currently retained.
|
||||
.. pii_types: name, username
|
||||
.. pii_retirement: retained
|
||||
"""
|
||||
# Import here instead of top of file since this module gets imported before
|
||||
# the course_modes app is loaded, resulting in a Django deprecation warning.
|
||||
@@ -374,6 +380,8 @@ class GeneratedCertificate(models.Model):
|
||||
class CertificateGenerationHistory(TimeStampedModel):
|
||||
"""
|
||||
Model for storing Certificate Generation History.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
course_id = CourseKeyField(max_length=255)
|
||||
@@ -436,6 +444,8 @@ class CertificateGenerationHistory(TimeStampedModel):
|
||||
class CertificateInvalidation(TimeStampedModel):
|
||||
"""
|
||||
Model for storing Certificate Invalidation.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
generated_certificate = models.ForeignKey(GeneratedCertificate, on_delete=models.CASCADE)
|
||||
invalidated_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
@@ -527,7 +537,7 @@ def certificate_status_for_student(student, course_id):
|
||||
|
||||
|
||||
def certificate_status(generated_certificate):
|
||||
'''
|
||||
"""
|
||||
This returns a dictionary with a key for status, and other information.
|
||||
The status is one of the following:
|
||||
|
||||
@@ -554,7 +564,7 @@ def certificate_status(generated_certificate):
|
||||
|
||||
If the student has been graded, the dictionary also contains their
|
||||
grade for the course with the key "grade".
|
||||
'''
|
||||
"""
|
||||
# Import here instead of top of file since this module gets imported before
|
||||
# the course_modes app is loaded, resulting in a Django deprecation warning.
|
||||
from course_modes.models import CourseMode
|
||||
@@ -610,7 +620,8 @@ def certificate_info_for_user(user, course_id, grade, user_is_whitelisted, user_
|
||||
|
||||
|
||||
class ExampleCertificateSet(TimeStampedModel):
|
||||
"""A set of example certificates.
|
||||
"""
|
||||
A set of example certificates.
|
||||
|
||||
Example certificates are used to verify that certificate
|
||||
generation is working for a particular course.
|
||||
@@ -619,6 +630,7 @@ class ExampleCertificateSet(TimeStampedModel):
|
||||
(e.g. honor and verified), in which case we generate
|
||||
multiple example certificates for the course.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
course_key = CourseKeyField(max_length=255, db_index=True)
|
||||
|
||||
@@ -701,7 +713,8 @@ def _make_uuid():
|
||||
|
||||
|
||||
class ExampleCertificate(TimeStampedModel):
|
||||
"""Example certificate.
|
||||
"""
|
||||
Example certificate.
|
||||
|
||||
Example certificates are used to verify that certificate
|
||||
generation is working for a particular course.
|
||||
@@ -717,6 +730,7 @@ class ExampleCertificate(TimeStampedModel):
|
||||
|
||||
3) We use dummy values.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "certificates"
|
||||
@@ -870,12 +884,15 @@ class ExampleCertificate(TimeStampedModel):
|
||||
|
||||
|
||||
class CertificateGenerationCourseSetting(TimeStampedModel):
|
||||
"""Enable or disable certificate generation for a particular course.
|
||||
"""
|
||||
Enable or disable certificate generation for a particular course.
|
||||
|
||||
In general, we should only enable self-generated certificates
|
||||
for a course once we successfully generate example certificates
|
||||
for the course. This is enforced in the UI layer, but
|
||||
not in the data layer.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
course_key = CourseKeyField(max_length=255, db_index=True)
|
||||
|
||||
@@ -973,6 +990,7 @@ class CertificateGenerationConfiguration(ConfigurationModel):
|
||||
will appear for courses that have enabled self-generated
|
||||
certificates.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(ConfigurationModel.Meta):
|
||||
app_label = "certificates"
|
||||
@@ -993,6 +1011,8 @@ class CertificateHtmlViewConfiguration(ConfigurationModel):
|
||||
"logo_src": "http://www.edx.org/static/images/honor-logo.png"
|
||||
}
|
||||
}
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(ConfigurationModel.Meta):
|
||||
app_label = "certificates"
|
||||
@@ -1029,6 +1049,7 @@ class CertificateTemplate(TimeStampedModel):
|
||||
A particular course may have several kinds of certificate templates
|
||||
(e.g. honor and verified).
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
name = models.CharField(
|
||||
max_length=255,
|
||||
@@ -1071,7 +1092,8 @@ class CertificateTemplate(TimeStampedModel):
|
||||
max_length=2,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=u'Only certificates for courses in the selected language will be rendered using this template. Course language is determined by the first two letters of the language code.'
|
||||
help_text=u'Only certificates for courses in the selected language will be rendered using this template. '
|
||||
u'Course language is determined by the first two letters of the language code.'
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
@@ -1104,6 +1126,7 @@ class CertificateTemplateAsset(TimeStampedModel):
|
||||
This model stores assets used in custom web certificate templates
|
||||
such as image, css files.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
description = models.CharField(
|
||||
max_length=255,
|
||||
|
||||
@@ -679,6 +679,27 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase)
|
||||
response = self.client.get(test_url)
|
||||
self.assertIn('<html class="no-js" lang="ar">', response.content)
|
||||
|
||||
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
|
||||
def test_html_lang_attribute_is_dynamic_for_certificate_html_view(self):
|
||||
"""
|
||||
Tests that Certificate HTML Web View's lang attribute is based on user language.
|
||||
"""
|
||||
self._add_course_certificates(count=1, signatory_count=2)
|
||||
test_url = get_certificate_url(
|
||||
user_id=self.user.id,
|
||||
course_id=unicode(self.course.id)
|
||||
)
|
||||
|
||||
user_language = 'fr'
|
||||
self.client.cookies[settings.LANGUAGE_COOKIE] = user_language
|
||||
response = self.client.get(test_url)
|
||||
self.assertIn('<html class="no-js" lang="fr">', response.content)
|
||||
|
||||
user_language = 'ar'
|
||||
self.client.cookies[settings.LANGUAGE_COOKIE] = user_language
|
||||
response = self.client.get(test_url)
|
||||
self.assertIn('<html class="no-js" lang="ar">', response.content)
|
||||
|
||||
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
|
||||
def test_html_view_for_non_viewable_certificate_and_for_student_user(self):
|
||||
"""
|
||||
|
||||
@@ -664,9 +664,10 @@ def _get_custom_template_and_language(course_id, course_mode, course_language):
|
||||
course_id=course_id, language=template.language))
|
||||
return (template, closest_released_language)
|
||||
elif template:
|
||||
user_language = translation.get_language()
|
||||
log.info("Returning template for course: {course_id} and template language is returned from settings"
|
||||
" {language}".format(course_id=course_id, language=settings.LANGUAGE_CODE))
|
||||
return (template, settings.LANGUAGE_CODE)
|
||||
" {language}".format(course_id=course_id, language=user_language))
|
||||
return (template, user_language)
|
||||
else:
|
||||
log.info("No template found for course: {course_id}" .format(course_id=course_id))
|
||||
return (None, None)
|
||||
|
||||
@@ -56,7 +56,7 @@ class BasketsView(APIView):
|
||||
try:
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
courses.get_course(course_key)
|
||||
except (InvalidKeyError, ValueError)as ex:
|
||||
except (InvalidKeyError, ValueError) as ex:
|
||||
log.exception(u'Unable to locate course matching %s.', course_id)
|
||||
return False, None, text_type(ex)
|
||||
|
||||
|
||||
@@ -7,7 +7,11 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class CommerceConfiguration(ConfigurationModel):
|
||||
""" Commerce configuration """
|
||||
"""
|
||||
Commerce configuration
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
app_label = "commerce"
|
||||
|
||||
@@ -25,6 +25,8 @@ GOAL_KEY_CHOICES = Choices(
|
||||
class CourseGoal(models.Model):
|
||||
"""
|
||||
Represents a course goal set by a user on the course home page.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
user = models.ForeignKey(User, blank=False, on_delete=models.CASCADE)
|
||||
course_key = CourseKeyField(max_length=255, db_index=True)
|
||||
|
||||
@@ -97,7 +97,7 @@ class CommandsTestBase(SharedModuleStoreTestCase):
|
||||
|
||||
try:
|
||||
output = self.call_command('dump_course_structure', *args, **kwargs)
|
||||
except TypeError, exception:
|
||||
except TypeError as exception:
|
||||
self.fail(exception)
|
||||
|
||||
dump = json.loads(output)
|
||||
|
||||
@@ -27,6 +27,8 @@ from six import text_type
|
||||
import coursewarehistoryextended
|
||||
from opaque_keys.edx.django.models import BlockTypeKeyField, CourseKeyField, UsageKeyField
|
||||
|
||||
from openedx.core.djangolib.markup import HTML
|
||||
|
||||
log = logging.getLogger("edx.courseware")
|
||||
|
||||
|
||||
@@ -74,6 +76,8 @@ class ChunkingManager(models.Manager):
|
||||
class StudentModule(models.Model):
|
||||
"""
|
||||
Keeps student state for a particular module in a particular course.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
objects = ChunkingManager()
|
||||
MODEL_TAGS = ['course_id', 'module_type']
|
||||
@@ -175,8 +179,11 @@ class StudentModule(models.Model):
|
||||
|
||||
|
||||
class BaseStudentModuleHistory(models.Model):
|
||||
"""Abstract class containing most fields used by any class
|
||||
storing Student Module History"""
|
||||
"""
|
||||
Abstract class containing most fields used by any class storing Student Module History
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
objects = ChunkingManager()
|
||||
HISTORY_SAVING_TYPES = {'problem'}
|
||||
|
||||
@@ -265,6 +272,8 @@ class StudentModuleHistory(BaseStudentModuleHistory):
|
||||
class XBlockFieldBase(models.Model):
|
||||
"""
|
||||
Base class for all XBlock field storage.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
objects = ChunkingManager()
|
||||
|
||||
@@ -283,7 +292,10 @@ class XBlockFieldBase(models.Model):
|
||||
|
||||
def __unicode__(self):
|
||||
keys = [field.name for field in self._meta.get_fields() if field.name not in ('created', 'modified')]
|
||||
return u'{}<{!r}'.format(self.__class__.__name__, {key: getattr(self, key) for key in keys})
|
||||
return HTML(u'{}<{!r}').format(
|
||||
HTML(self.__class__.__name__),
|
||||
{key: HTML(getattr(self, key)) for key in keys}
|
||||
)
|
||||
|
||||
|
||||
class XModuleUserStateSummaryField(XBlockFieldBase):
|
||||
@@ -329,6 +341,8 @@ class XModuleStudentInfoField(XBlockFieldBase):
|
||||
class OfflineComputedGrade(models.Model):
|
||||
"""
|
||||
Table of grades computed offline for a given user and course.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE)
|
||||
course_id = CourseKeyField(max_length=255, db_index=True)
|
||||
@@ -350,6 +364,8 @@ class OfflineComputedGradeLog(models.Model):
|
||||
"""
|
||||
Log of when offline grades are computed.
|
||||
Use this to be able to show instructor when the last computed grades were done.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
@@ -371,6 +387,8 @@ class StudentFieldOverride(TimeStampedModel):
|
||||
Holds the value of a specific field overriden for a student. This is used
|
||||
by the code in the `lms.djangoapps.courseware.student_field_overrides` module to provide
|
||||
overrides of xblock fields on a per user basis.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
course_id = CourseKeyField(max_length=255, db_index=True)
|
||||
location = UsageKeyField(max_length=255, db_index=True)
|
||||
@@ -385,9 +403,12 @@ class StudentFieldOverride(TimeStampedModel):
|
||||
|
||||
|
||||
class DynamicUpgradeDeadlineConfiguration(ConfigurationModel):
|
||||
""" Dynamic upgrade deadline configuration.
|
||||
"""
|
||||
Dynamic upgrade deadline configuration.
|
||||
|
||||
This model controls the behavior of the dynamic upgrade deadline for self-paced courses.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = 'courseware'
|
||||
@@ -418,6 +439,8 @@ class CourseDynamicUpgradeDeadlineConfiguration(OptOutDynamicUpgradeDeadlineMixi
|
||||
|
||||
This model controls dynamic upgrade deadlines on a per-course run level, allowing course runs to
|
||||
have different deadlines or opt out of the functionality altogether.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
KEY_FIELDS = ('course_id',)
|
||||
|
||||
@@ -440,6 +463,8 @@ class OrgDynamicUpgradeDeadlineConfiguration(OptOutDynamicUpgradeDeadlineMixin,
|
||||
|
||||
This model controls dynamic upgrade deadlines on a per-org level, allowing organizations to
|
||||
have different deadlines or opt out of the functionality altogether.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
KEY_FIELDS = ('org_id',)
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@ def _add_timed_exam_info(user, course, section, section_context):
|
||||
unicode(course.id),
|
||||
unicode(section.location)
|
||||
)
|
||||
except Exception, ex: # pylint: disable=broad-except
|
||||
except Exception as ex: # pylint: disable=broad-except
|
||||
# safety net in case something blows up in edx_proctoring
|
||||
# as this is just informational descriptions, it is better
|
||||
# to log and continue (which is safe) than to have it be an
|
||||
|
||||
@@ -489,7 +489,7 @@ class ProfileImageTestMixin(object):
|
||||
Mixin with utility methods for user profile image
|
||||
"""
|
||||
|
||||
TEST_PROFILE_IMAGE_UPLOADED_AT = datetime(2002, 1, 9, 15, 43, 01, tzinfo=UTC)
|
||||
TEST_PROFILE_IMAGE_UPLOADED_AT = datetime(2002, 1, 9, 15, 43, 1, tzinfo=UTC)
|
||||
|
||||
def create_profile_image(self, user, storage):
|
||||
"""
|
||||
|
||||
@@ -770,9 +770,9 @@ def upload(request, course_id): # ajax upload file to a question or answer
|
||||
max_file_size=cc_settings.MAX_UPLOAD_FILE_SIZE
|
||||
)
|
||||
|
||||
except exceptions.PermissionDenied, err:
|
||||
except exceptions.PermissionDenied as err:
|
||||
error = unicode(err)
|
||||
except Exception, err: # pylint: disable=broad-except
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
print(err)
|
||||
logging.critical(unicode(err))
|
||||
error = _('Error uploading file. Please contact the site administrator. Thank you.')
|
||||
|
||||
@@ -671,13 +671,13 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
|
||||
u"quote": u"quote text1",
|
||||
u"text": u"text1",
|
||||
u"usage_id": unicode(self.html_module_1.location),
|
||||
u"updated": datetime(2016, 01, 26, 8, 5, 16, 00000).isoformat(),
|
||||
u"updated": datetime(2016, 1, 26, 8, 5, 16, 00000).isoformat(),
|
||||
},
|
||||
{
|
||||
u"quote": u"quote text2",
|
||||
u"text": u"text2",
|
||||
u"usage_id": unicode(self.html_module_2.location),
|
||||
u"updated": datetime(2016, 01, 26, 9, 6, 17, 00000).isoformat(),
|
||||
u"updated": datetime(2016, 1, 26, 9, 6, 17, 00000).isoformat(),
|
||||
},
|
||||
]
|
||||
|
||||
@@ -695,7 +695,7 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
|
||||
u'text': u'text1',
|
||||
u'quote': u'quote text1',
|
||||
u'usage_id': unicode(self.html_module_1.location),
|
||||
u'updated': datetime(2016, 01, 26, 8, 5, 16)
|
||||
u'updated': datetime(2016, 1, 26, 8, 5, 16)
|
||||
},
|
||||
{
|
||||
'section': {},
|
||||
@@ -708,7 +708,7 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
|
||||
u'text': u'text2',
|
||||
u'quote': u'quote text2',
|
||||
u'usage_id': unicode(self.html_module_2.location),
|
||||
u'updated': datetime(2016, 01, 26, 9, 6, 17)
|
||||
u'updated': datetime(2016, 1, 26, 9, 6, 17)
|
||||
}
|
||||
],
|
||||
helpers.preprocess_collection(self.user, self.course, initial_collection)
|
||||
|
||||
@@ -7,7 +7,11 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class EmailMarketingConfiguration(ConfigurationModel):
|
||||
""" Email marketing configuration """
|
||||
"""
|
||||
Email marketing configuration
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
app_label = "email_marketing"
|
||||
|
||||
@@ -4,6 +4,9 @@ from model_utils.models import TimeStampedModel
|
||||
|
||||
|
||||
class ExperimentData(TimeStampedModel):
|
||||
"""
|
||||
.. no_pii:
|
||||
"""
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||
experiment_id = models.PositiveSmallIntegerField(
|
||||
null=False, blank=False, db_index=True, verbose_name='Experiment ID'
|
||||
@@ -23,6 +26,9 @@ class ExperimentData(TimeStampedModel):
|
||||
|
||||
|
||||
class ExperimentKeyValue(TimeStampedModel):
|
||||
"""
|
||||
.. no_pii:
|
||||
"""
|
||||
experiment_id = models.PositiveSmallIntegerField(
|
||||
null=False, blank=False, db_index=True, verbose_name='Experiment ID'
|
||||
)
|
||||
|
||||
@@ -17,6 +17,8 @@ class PersistentGradesEnabledFlag(ConfigurationModel):
|
||||
When this feature flag is set to true, individual courses
|
||||
must also have persistent grades enabled for the
|
||||
feature to take effect.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
# this field overrides course-specific settings to enable the feature for all courses
|
||||
enabled_for_all_courses = BooleanField(default=False)
|
||||
@@ -58,6 +60,8 @@ class CoursePersistentGradesFlag(ConfigurationModel):
|
||||
Enables persistent grades for a specific
|
||||
course. Only has an effect if the general
|
||||
flag above is set to True.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
KEY_FIELDS = ('course_id',)
|
||||
|
||||
@@ -76,7 +80,7 @@ class CoursePersistentGradesFlag(ConfigurationModel):
|
||||
|
||||
class ComputeGradesSetting(ConfigurationModel):
|
||||
"""
|
||||
...
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "grades"
|
||||
|
||||
@@ -131,6 +131,8 @@ class VisibleBlocks(models.Model):
|
||||
This state is represented using an array of BlockRecord, stored
|
||||
in the blocks_json field. A hash of this json array is used for lookup
|
||||
purposes.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
blocks_json = models.TextField()
|
||||
hashed = models.CharField(max_length=100, unique=True)
|
||||
@@ -259,6 +261,8 @@ class VisibleBlocks(models.Model):
|
||||
class PersistentSubsectionGrade(TimeStampedModel):
|
||||
"""
|
||||
A django model tracking persistent grades at the subsection level.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
@@ -492,6 +496,8 @@ class PersistentSubsectionGrade(TimeStampedModel):
|
||||
class PersistentCourseGrade(TimeStampedModel):
|
||||
"""
|
||||
A django model tracking persistent course grades.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
@@ -626,6 +632,8 @@ class PersistentCourseGrade(TimeStampedModel):
|
||||
class PersistentSubsectionGradeOverride(models.Model):
|
||||
"""
|
||||
A django model tracking persistent grades overrides at the subsection level.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "grades"
|
||||
@@ -732,6 +740,8 @@ class PersistentSubsectionGradeOverride(models.Model):
|
||||
class PersistentSubsectionGradeOverrideHistory(models.Model):
|
||||
"""
|
||||
A django model tracking persistent grades override audit records.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
PROCTORING = 'PROCTORING'
|
||||
GRADEBOOK = 'GRADEBOOK'
|
||||
|
||||
@@ -48,7 +48,7 @@ def handle_dashboard_error(view):
|
||||
"""
|
||||
try:
|
||||
return view(request, course_id=course_id)
|
||||
except DashboardError, error:
|
||||
except DashboardError as error:
|
||||
return error.response()
|
||||
|
||||
return wrapper
|
||||
|
||||
@@ -10,5 +10,7 @@ class GradeReportSetting(ConfigurationModel):
|
||||
"""
|
||||
Sets the batch size used when running grade reports
|
||||
with multiple celery workers.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
batch_size = IntegerField(default=100)
|
||||
|
||||
@@ -58,6 +58,8 @@ class InstructorTask(models.Model):
|
||||
`requester` stores id of user who submitted the task
|
||||
`created` stores date that entry was first created
|
||||
`updated` stores date that entry was last modified
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "instructor_task"
|
||||
|
||||
@@ -14,6 +14,8 @@ from xblock.core import XBlockAside
|
||||
class XBlockAsidesConfig(ConfigurationModel):
|
||||
"""
|
||||
Configuration for XBlockAsides.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(ConfigurationModel.Meta):
|
||||
|
||||
@@ -25,6 +25,8 @@ class LtiConsumer(models.Model):
|
||||
Database model representing an LTI consumer. This model stores the consumer
|
||||
specific settings, such as the OAuth key/secret pair and any LTI fields
|
||||
that must be persisted.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
consumer_name = models.CharField(max_length=255, unique=True)
|
||||
consumer_key = models.CharField(max_length=32, unique=True, db_index=True, default=short_token)
|
||||
@@ -91,6 +93,8 @@ class OutcomeService(models.Model):
|
||||
Some LTI-specified fields use the prefix lis_; this refers to the IMS
|
||||
Learning Information Services standard from which LTI inherits some
|
||||
properties
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
lis_outcome_service_url = models.CharField(max_length=255, unique=True)
|
||||
lti_consumer = models.ForeignKey(LtiConsumer, on_delete=models.CASCADE)
|
||||
@@ -109,6 +113,8 @@ class GradedAssignment(models.Model):
|
||||
Some LTI-specified fields use the prefix lis_; this refers to the IMS
|
||||
Learning Information Services standard from which LTI inherits some
|
||||
properties
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE)
|
||||
course_key = CourseKeyField(max_length=255, db_index=True)
|
||||
@@ -127,6 +133,8 @@ class LtiUser(models.Model):
|
||||
The LTI user_id field is guaranteed to be unique per LTI consumer (per
|
||||
to the LTI spec), so we guarantee a unique mapping from LTI to edX account
|
||||
by using the lti_consumer/lti_user_id tuple.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
lti_consumer = models.ForeignKey(LtiConsumer, on_delete=models.CASCADE)
|
||||
lti_user_id = models.CharField(max_length=255)
|
||||
|
||||
@@ -14,6 +14,8 @@ class MobileApiConfig(ConfigurationModel):
|
||||
|
||||
The order in which the comma-separated list of names of profiles are given
|
||||
is in priority order.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
video_profiles = models.TextField(
|
||||
blank=True,
|
||||
@@ -34,6 +36,8 @@ class MobileApiConfig(ConfigurationModel):
|
||||
class AppVersionConfig(models.Model):
|
||||
"""
|
||||
Configuration for mobile app versions available.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
PLATFORM_CHOICES = tuple([
|
||||
(platform, platform)
|
||||
@@ -90,6 +94,8 @@ class IgnoreMobileAvailableFlagConfig(ConfigurationModel): # pylint: disable=W5
|
||||
Enabling this configuration will cause the mobile_available flag check in
|
||||
access.py._is_descriptor_mobile_available to ignore the mobile_available
|
||||
flag.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
|
||||
@@ -33,13 +33,13 @@ class TestAppVersionUpgradeMiddleware(CacheIsolationTestCase):
|
||||
AppVersionConfig(
|
||||
platform="iOS",
|
||||
version="2.2.2",
|
||||
expire_at=datetime(2014, 01, 01, tzinfo=UTC),
|
||||
expire_at=datetime(2014, 1, 1, tzinfo=UTC),
|
||||
enabled=True
|
||||
).save()
|
||||
AppVersionConfig(
|
||||
platform="iOS",
|
||||
version="4.4.4",
|
||||
expire_at=datetime(9000, 01, 01, tzinfo=UTC),
|
||||
expire_at=datetime(9000, 1, 1, tzinfo=UTC),
|
||||
enabled=True
|
||||
).save()
|
||||
AppVersionConfig(platform="iOS", version="6.6.6", expire_at=None, enabled=True).save()
|
||||
@@ -48,13 +48,13 @@ class TestAppVersionUpgradeMiddleware(CacheIsolationTestCase):
|
||||
AppVersionConfig(
|
||||
platform="Android",
|
||||
version="2.2.2",
|
||||
expire_at=datetime(2014, 01, 01, tzinfo=UTC),
|
||||
expire_at=datetime(2014, 1, 1, tzinfo=UTC),
|
||||
enabled=True
|
||||
).save()
|
||||
AppVersionConfig(
|
||||
platform="Android",
|
||||
version="4.4.4",
|
||||
expire_at=datetime(5000, 01, 01, tzinfo=UTC),
|
||||
expire_at=datetime(5000, 1, 1, tzinfo=UTC),
|
||||
enabled=True
|
||||
).save()
|
||||
AppVersionConfig(platform="Android", version="8.8.8", expire_at=None, enabled=True).save()
|
||||
|
||||
@@ -22,19 +22,19 @@ class TestAppVersionConfigModel(TestCase):
|
||||
AppVersionConfig(
|
||||
platform="ios",
|
||||
version="2.2.2",
|
||||
expire_at=datetime(2014, 01, 01, tzinfo=UTC),
|
||||
expire_at=datetime(2014, 1, 1, tzinfo=UTC),
|
||||
enabled=True
|
||||
).save()
|
||||
AppVersionConfig(
|
||||
platform="ios",
|
||||
version="4.1.1",
|
||||
expire_at=datetime(5000, 01, 01, tzinfo=UTC),
|
||||
expire_at=datetime(5000, 1, 1, tzinfo=UTC),
|
||||
enabled=False
|
||||
).save()
|
||||
AppVersionConfig(
|
||||
platform="ios",
|
||||
version="4.4.4",
|
||||
expire_at=datetime(9000, 01, 01, tzinfo=UTC),
|
||||
expire_at=datetime(9000, 1, 1, tzinfo=UTC),
|
||||
enabled=True
|
||||
).save()
|
||||
AppVersionConfig(platform="ios", version="6.6.6", expire_at=None, enabled=True).save()
|
||||
@@ -44,13 +44,13 @@ class TestAppVersionConfigModel(TestCase):
|
||||
AppVersionConfig(
|
||||
platform="android",
|
||||
version="2.2.2",
|
||||
expire_at=datetime(2014, 01, 01, tzinfo=UTC),
|
||||
expire_at=datetime(2014, 1, 1, tzinfo=UTC),
|
||||
enabled=True
|
||||
).save()
|
||||
AppVersionConfig(
|
||||
platform="android",
|
||||
version="4.4.4",
|
||||
expire_at=datetime(9000, 01, 01, tzinfo=UTC),
|
||||
expire_at=datetime(9000, 1, 1, tzinfo=UTC),
|
||||
enabled=True
|
||||
).save()
|
||||
AppVersionConfig(platform="android", version="8.8.8", expire_at=None, enabled=True).save()
|
||||
|
||||
@@ -10,6 +10,14 @@ from six import text_type
|
||||
|
||||
|
||||
class Note(models.Model):
|
||||
"""
|
||||
Stores user Notes for the LMS local Notes service.
|
||||
|
||||
.. pii: Legacy model for an app that edx.org hasn't used since 2013
|
||||
.. pii_types: other
|
||||
.. pii_retirement: retained
|
||||
"""
|
||||
|
||||
user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE)
|
||||
course_id = CourseKeyField(max_length=255, db_index=True)
|
||||
uri = models.CharField(max_length=255, db_index=True)
|
||||
|
||||
@@ -9,6 +9,8 @@ class WhitelistedRssUrl(TimeStampedModel):
|
||||
"""
|
||||
Model for persisting RSS feed URLs which are whitelisted
|
||||
for proxying via this rss_proxy djangoapp.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
url = models.CharField(max_length=255, unique=True, db_index=True)
|
||||
|
||||
|
||||
@@ -110,6 +110,10 @@ class Order(models.Model):
|
||||
This is the model for an order. Before purchase, an Order and its related OrderItems are used
|
||||
as the shopping cart.
|
||||
FOR ANY USER, THERE SHOULD ONLY EVER BE ZERO OR ONE ORDER WITH STATUS='cart'.
|
||||
|
||||
.. pii: Contains many PII fields in an app edx.org does not currently use. "other" data is payment information.
|
||||
.. pii_types: name, location, email_address, other
|
||||
.. pii_retirement: retained
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "shoppingcart"
|
||||
@@ -639,6 +643,8 @@ class OrderItem(TimeStampedModel):
|
||||
|
||||
Each implementation of OrderItem should provide its own purchased_callback as
|
||||
a method.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "shoppingcart"
|
||||
@@ -824,6 +830,10 @@ class Invoice(TimeStampedModel):
|
||||
This table capture all the information needed to support "invoicing"
|
||||
which is when a user wants to purchase Registration Codes,
|
||||
but will not do so via a Credit Card transaction.
|
||||
|
||||
.. pii: Contains many PII fields in an app edx.org does not currently use
|
||||
.. pii_types: name, location, email_address
|
||||
.. pii_retirement: retained
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "shoppingcart"
|
||||
@@ -996,6 +1006,7 @@ class InvoiceTransaction(TimeStampedModel):
|
||||
create a transaction with a negative amount to represent
|
||||
the refund.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "shoppingcart"
|
||||
@@ -1086,6 +1097,8 @@ class InvoiceItem(TimeStampedModel):
|
||||
there might be an invoice item representing 10 registration
|
||||
codes for the DemoX course.
|
||||
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "shoppingcart"
|
||||
@@ -1130,6 +1143,7 @@ class CourseRegistrationCodeInvoiceItem(InvoiceItem):
|
||||
This is an invoice item that represents a payment for
|
||||
a course registration.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "shoppingcart"
|
||||
@@ -1166,6 +1180,7 @@ class InvoiceHistory(models.Model):
|
||||
transaction, so the history record is created only
|
||||
if the invoice change is successfully persisted.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
|
||||
invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE)
|
||||
@@ -1225,6 +1240,8 @@ class CourseRegistrationCode(models.Model):
|
||||
"""
|
||||
This table contains registration codes
|
||||
With registration code, a user can register for a course for free
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "shoppingcart"
|
||||
@@ -1263,6 +1280,8 @@ class CourseRegistrationCode(models.Model):
|
||||
class RegistrationCodeRedemption(models.Model):
|
||||
"""
|
||||
This model contains the registration-code redemption info
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "shoppingcart"
|
||||
@@ -1323,6 +1342,8 @@ class Coupon(models.Model):
|
||||
"""
|
||||
This table contains coupon codes
|
||||
A user can get a discount offer on course if provide coupon code
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "shoppingcart"
|
||||
@@ -1350,6 +1371,8 @@ class Coupon(models.Model):
|
||||
class CouponRedemption(models.Model):
|
||||
"""
|
||||
This table contain coupon redemption info
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "shoppingcart"
|
||||
@@ -1466,6 +1489,8 @@ class CouponRedemption(models.Model):
|
||||
class PaidCourseRegistration(OrderItem):
|
||||
"""
|
||||
This is an inventory item for paying for a course registration
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "shoppingcart"
|
||||
@@ -1660,6 +1685,8 @@ class CourseRegCodeItem(OrderItem):
|
||||
"""
|
||||
This is an inventory item for paying for
|
||||
generating course registration codes
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "shoppingcart"
|
||||
@@ -1827,6 +1854,8 @@ class CourseRegCodeItemAnnotation(models.Model):
|
||||
generates report for the paid courses, each report item must contain the payment account associated with a course.
|
||||
And unfortunately we didn't have the concept of a "SKU" or stock item where we could keep this association,
|
||||
so this is to retrofit it.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "shoppingcart"
|
||||
@@ -1844,6 +1873,8 @@ class PaidCourseRegistrationAnnotation(models.Model):
|
||||
generates report for the paid courses, each report item must contain the payment account associated with a course.
|
||||
And unfortunately we didn't have the concept of a "SKU" or stock item where we could keep this association,
|
||||
so this is to retrofit it.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "shoppingcart"
|
||||
@@ -1858,6 +1889,8 @@ class PaidCourseRegistrationAnnotation(models.Model):
|
||||
class CertificateItem(OrderItem):
|
||||
"""
|
||||
This is an inventory item for purchasing certificates
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "shoppingcart"
|
||||
@@ -2082,16 +2115,23 @@ class CertificateItem(OrderItem):
|
||||
|
||||
|
||||
class DonationConfiguration(ConfigurationModel):
|
||||
"""Configure whether donations are enabled on the site."""
|
||||
"""
|
||||
Configure whether donations are enabled on the site.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(ConfigurationModel.Meta):
|
||||
app_label = "shoppingcart"
|
||||
|
||||
|
||||
class Donation(OrderItem):
|
||||
"""A donation made by a user.
|
||||
"""
|
||||
A donation made by a user.
|
||||
|
||||
Donations can be made for a specific course or to the organization as a whole.
|
||||
Users can choose the donation amount.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
|
||||
@@ -180,7 +180,7 @@ class PDFInvoice(object):
|
||||
"""
|
||||
try:
|
||||
img = Image.open(img_path)
|
||||
except IOError, ex:
|
||||
except IOError as ex:
|
||||
log.exception(u'Pdf unable to open the image file: %s', str(ex))
|
||||
img = None
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ class SurveyForm(TimeStampedModel):
|
||||
that is presented to the end user. A SurveyForm is not tied to
|
||||
a particular run of a course, to allow for sharing of Surveys
|
||||
across courses
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
name = models.CharField(max_length=255, db_index=True, unique=True)
|
||||
form = models.TextField()
|
||||
@@ -162,8 +164,13 @@ class SurveyForm(TimeStampedModel):
|
||||
|
||||
|
||||
class SurveyAnswer(TimeStampedModel):
|
||||
# pylint: disable=line-too-long
|
||||
"""
|
||||
Model for the answers that a user gives for a particular form in a course
|
||||
|
||||
.. pii: These are free-form questions asked by course authors. Types below are current as of Feb 2019, new ones could be added. "other" PII currently includes "company", "job title", and "work experience".
|
||||
.. pii_types: name, location, other
|
||||
.. pii_retirement: retained
|
||||
"""
|
||||
user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE)
|
||||
form = models.ForeignKey(SurveyForm, db_index=True, on_delete=models.CASCADE)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
"""Django models related to teams functionality."""
|
||||
"""
|
||||
Django models related to teams functionality.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
@@ -39,16 +41,20 @@ from .errors import AlreadyOnTeamInCourse, ImmutableMembershipFieldException, No
|
||||
@receiver(comment_voted)
|
||||
@receiver(comment_created)
|
||||
def post_create_vote_handler(sender, **kwargs): # pylint: disable=unused-argument
|
||||
"""Update the user's last activity date upon creating or voting for a
|
||||
post."""
|
||||
"""
|
||||
Update the user's last activity date upon creating or voting for a
|
||||
post.
|
||||
"""
|
||||
handle_activity(kwargs['user'], kwargs['post'])
|
||||
|
||||
|
||||
@receiver(thread_followed)
|
||||
@receiver(thread_unfollowed)
|
||||
def post_followed_unfollowed_handler(sender, **kwargs): # pylint: disable=unused-argument
|
||||
"""Update the user's last activity date upon followed or unfollowed of a
|
||||
post."""
|
||||
"""
|
||||
Update the user's last activity date upon followed or unfollowed of a
|
||||
post.
|
||||
"""
|
||||
handle_activity(kwargs['user'], kwargs['post'])
|
||||
|
||||
|
||||
@@ -57,21 +63,26 @@ def post_followed_unfollowed_handler(sender, **kwargs): # pylint: disable=unuse
|
||||
@receiver(comment_edited)
|
||||
@receiver(comment_deleted)
|
||||
def post_edit_delete_handler(sender, **kwargs): # pylint: disable=unused-argument
|
||||
"""Update the user's last activity date upon editing or deleting a
|
||||
post."""
|
||||
"""
|
||||
Update the user's last activity date upon editing or deleting a
|
||||
post.
|
||||
"""
|
||||
post = kwargs['post']
|
||||
handle_activity(kwargs['user'], post, long(post.user_id))
|
||||
|
||||
|
||||
@receiver(comment_endorsed)
|
||||
def comment_endorsed_handler(sender, **kwargs): # pylint: disable=unused-argument
|
||||
"""Update the user's last activity date upon endorsing a comment."""
|
||||
"""
|
||||
Update the user's last activity date upon endorsing a comment.
|
||||
"""
|
||||
comment = kwargs['post']
|
||||
handle_activity(kwargs['user'], comment, long(comment.thread.user_id))
|
||||
|
||||
|
||||
def handle_activity(user, post, original_author_id=None):
|
||||
"""Handle user activity from django_comment_client and discussion_api
|
||||
"""
|
||||
Handle user activity from django_comment_client and discussion_api
|
||||
and update the user's last activity date. Checks if the user who
|
||||
performed the action is the original author, and that the
|
||||
discussion has the team context.
|
||||
@@ -83,7 +94,11 @@ def handle_activity(user, post, original_author_id=None):
|
||||
|
||||
|
||||
class CourseTeam(models.Model):
|
||||
"""This model represents team related info."""
|
||||
"""
|
||||
This model represents team related info.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
app_label = "teams"
|
||||
@@ -165,7 +180,11 @@ class CourseTeam(models.Model):
|
||||
|
||||
|
||||
class CourseTeamMembership(models.Model):
|
||||
"""This model represents the membership of a single user in a single team."""
|
||||
"""
|
||||
This model represents the membership of a single user in a single team.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
app_label = "teams"
|
||||
|
||||
@@ -93,6 +93,10 @@ class IDVerificationAttempt(StatusModel):
|
||||
Each IDVerificationAttempt represents a Student's attempt to establish
|
||||
their identity through one of several methods that inherit from this Model,
|
||||
including PhotoVerification and SSOVerification.
|
||||
|
||||
.. pii: The User's name is stored in this and sub-models
|
||||
.. pii_types: name
|
||||
.. pii_retirement: retained
|
||||
"""
|
||||
STATUS = Choices('created', 'ready', 'submitted', 'must_retry', 'approved', 'denied')
|
||||
user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE)
|
||||
@@ -142,6 +146,10 @@ class ManualVerification(IDVerificationAttempt):
|
||||
"""
|
||||
Each ManualVerification represents a user's verification that bypasses the need for
|
||||
any other verification.
|
||||
|
||||
.. pii: The User's name is stored in the parent model
|
||||
.. pii_types: name
|
||||
.. pii_retirement: retained
|
||||
"""
|
||||
|
||||
reason = models.CharField(
|
||||
@@ -173,6 +181,8 @@ class SSOVerification(IDVerificationAttempt):
|
||||
Each SSOVerification represents a Student's attempt to establish their identity
|
||||
by signing in with SSO. ID verification through SSO bypasses the need for
|
||||
photo verification.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
OAUTH2 = 'third_party_auth.models.OAuth2ProviderConfig'
|
||||
@@ -254,6 +264,10 @@ class PhotoVerification(IDVerificationAttempt):
|
||||
attempt.status == PhotoVerification.STATUS.created
|
||||
attempt.status == "created"
|
||||
pending_requests = PhotoVerification.submitted.all()
|
||||
|
||||
.. pii: The User's name is stored in the parent model, this one stores links to face and photo ID images
|
||||
.. pii_types: name, image
|
||||
.. pii_retirement: retained
|
||||
"""
|
||||
######################## Fields Set During Creation ########################
|
||||
# See class docstring for description of status states
|
||||
@@ -526,6 +540,10 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
|
||||
Note: this model handles *inital* verifications (which you must perform
|
||||
at the time you register for a verified cert).
|
||||
|
||||
.. pii: The User's name is stored in the parent model, this one stores links to face and photo ID images
|
||||
.. pii_types: name, image
|
||||
.. pii_retirement: retained
|
||||
"""
|
||||
# This is a base64.urlsafe_encode(rsa_encrypt(photo_id_aes_key), ss_pub_key)
|
||||
# So first we generate a random AES-256 key to encrypt our photo ID with.
|
||||
@@ -909,6 +927,8 @@ class VerificationDeadline(TimeStampedModel):
|
||||
If no verification deadline record exists for a course,
|
||||
then that course does not have a deadline. This means that users
|
||||
can submit photos at any time.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "verify_student"
|
||||
|
||||
@@ -3452,11 +3452,17 @@ RETIREMENT_STATES = [
|
||||
'COMPLETE',
|
||||
]
|
||||
|
||||
############## Settings for Writable Gradebook #########################
|
||||
############## Settings for Microfrontends #########################
|
||||
# If running a Gradebook container locally,
|
||||
# modify lms/envs/private.py to give it a non-null value
|
||||
WRITABLE_GRADEBOOK_URL = None
|
||||
|
||||
# TODO (DEPR-17)
|
||||
# This URL value is needed to redirect the old profile page to a new
|
||||
# micro-frontend based implementation. Once the old implementation is
|
||||
# completely removed and this redirect is no longer needed, we can remove this.
|
||||
PROFILE_MICROFRONTEND_URL = "http://some.profile.spa/u/"
|
||||
|
||||
############### Settings for django-fernet-fields ##################
|
||||
FERNET_KEYS = [
|
||||
'DUMMY KEY CHANGE BEFORE GOING TO PRODUCTION',
|
||||
|
||||
@@ -1102,8 +1102,9 @@ RETIREMENT_STATES = ENV_TOKENS.get('RETIREMENT_STATES', RETIREMENT_STATES)
|
||||
############## Settings for Course Enrollment Modes ######################
|
||||
COURSE_ENROLLMENT_MODES = ENV_TOKENS.get('COURSE_ENROLLMENT_MODES', COURSE_ENROLLMENT_MODES)
|
||||
|
||||
############## Settings for Writable Gradebook #########################
|
||||
############## Settings for Microfrontend URLS #########################
|
||||
WRITABLE_GRADEBOOK_URL = ENV_TOKENS.get('WRITABLE_GRADEBOOK_URL', WRITABLE_GRADEBOOK_URL)
|
||||
PROFILE_MICROFRONTEND_URL = ENV_TOKENS.get('PROFILE_MICROFRONTEND_URL', PROFILE_MICROFRONTEND_URL)
|
||||
|
||||
############################### Plugin Settings ###############################
|
||||
|
||||
|
||||
@@ -22,7 +22,13 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ApiAccessRequest(TimeStampedModel):
|
||||
"""Model to track API access for a user."""
|
||||
"""
|
||||
Model to track API access for a user.
|
||||
|
||||
.. pii: Stores a website, company name, company address for this user
|
||||
.. pii_types: location, external_service, other
|
||||
.. pii_retirement: local_api
|
||||
"""
|
||||
|
||||
PENDING = 'pending'
|
||||
DENIED = 'denied'
|
||||
@@ -121,7 +127,11 @@ class ApiAccessRequest(TimeStampedModel):
|
||||
|
||||
|
||||
class ApiAccessConfig(ConfigurationModel):
|
||||
"""Configuration for API management."""
|
||||
"""
|
||||
Configuration for API management.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
def __unicode__(self):
|
||||
return u'ApiAccessConfig [enabled={}]'.format(self.enabled)
|
||||
@@ -208,7 +218,11 @@ def _send_decision_email(instance):
|
||||
|
||||
|
||||
class Catalog(models.Model):
|
||||
"""A (non-Django-managed) model for Catalogs in the course discovery service."""
|
||||
"""
|
||||
A (non-Django-managed) model for Catalogs in the course discovery service.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
id = models.IntegerField(primary_key=True) # pylint: disable=invalid-name
|
||||
name = models.CharField(max_length=255, null=False, blank=False)
|
||||
|
||||
@@ -41,6 +41,8 @@ def parse_path_data(path_data):
|
||||
class Bookmark(TimeStampedModel):
|
||||
"""
|
||||
Bookmarks model.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE)
|
||||
course_key = CourseKeyField(max_length=255, db_index=True)
|
||||
@@ -189,6 +191,8 @@ class Bookmark(TimeStampedModel):
|
||||
class XBlockCache(TimeStampedModel):
|
||||
"""
|
||||
XBlockCache model to store info about xblocks.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
course_key = CourseKeyField(max_length=255, db_index=True)
|
||||
|
||||
@@ -9,7 +9,11 @@ from openedx.core.djangoapps.site_configuration import helpers
|
||||
|
||||
|
||||
class CatalogIntegration(ConfigurationModel):
|
||||
"""Manages configuration for connecting to the catalog service and using its API."""
|
||||
"""
|
||||
Manages configuration for connecting to the catalog service and using its API.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
API_NAME = 'catalog'
|
||||
CACHE_KEY = 'catalog.api.data'
|
||||
|
||||
|
||||
@@ -7,9 +7,10 @@ from django.db import models
|
||||
|
||||
class CCXCon(models.Model):
|
||||
"""
|
||||
The definition of the CCXCon model.
|
||||
This will store the url and the oauth key to access the REST APIs
|
||||
on the CCX Connector.
|
||||
Definition of the CCXCon model.
|
||||
Stores the url and the oauth key to access the REST APIs on the CCX Connector.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
url = models.URLField(unique=True, db_index=True)
|
||||
oauth_client_id = models.CharField(max_length=255)
|
||||
|
||||
@@ -8,6 +8,8 @@ from config_models.models import ConfigurationModel
|
||||
class BlockStructureConfiguration(ConfigurationModel):
|
||||
"""
|
||||
Configuration model for Block Structures.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
DEFAULT_PRUNE_KEEP_COUNT = 5
|
||||
DEFAULT_CACHE_TIMEOUT_IN_SECONDS = 60 * 60 * 24 # 24 hours
|
||||
|
||||
@@ -124,6 +124,8 @@ def _storage_error_handling(bs_model, operation, is_read_operation=False):
|
||||
class BlockStructureModel(TimeStampedModel):
|
||||
"""
|
||||
Model for storing Block Structure information.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
VERSION_FIELDS = [
|
||||
u'data_version',
|
||||
|
||||
@@ -40,6 +40,8 @@ class CourseOverview(TimeStampedModel):
|
||||
user dashboard (enrolled courses)
|
||||
course catalog (courses to enroll in)
|
||||
course about (meta data about the course)
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
@@ -707,6 +709,8 @@ class CourseOverview(TimeStampedModel):
|
||||
class CourseOverviewTab(models.Model):
|
||||
"""
|
||||
Model for storing and caching tabs information of a course.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
tab_id = models.CharField(max_length=50)
|
||||
course_overview = models.ForeignKey(CourseOverview, db_index=True, related_name="tabs", on_delete=models.CASCADE)
|
||||
@@ -779,6 +783,8 @@ class CourseOverviewImageSet(TimeStampedModel):
|
||||
process to do it, and it can happen in a follow-on PR if anyone is
|
||||
interested in extending this functionality.
|
||||
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
course_overview = models.OneToOneField(CourseOverview, db_index=True, related_name="image_set",
|
||||
on_delete=models.CASCADE)
|
||||
@@ -860,6 +866,8 @@ class CourseOverviewImageConfig(ConfigurationModel):
|
||||
to take effect. You might want to do this if you're doing precise theming of
|
||||
your install of edx-platform... but really, you probably don't want to do this
|
||||
at all at the moment, given how new this is. :-P
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
# Small thumbnail, for things like the student dashboard
|
||||
small_width = models.IntegerField(default=375)
|
||||
|
||||
@@ -7,7 +7,11 @@ from django.db.models.fields import PositiveIntegerField, TextField
|
||||
|
||||
|
||||
class CourseAssetCacheTtlConfig(ConfigurationModel):
|
||||
"""Configuration for the TTL of course assets."""
|
||||
"""
|
||||
Configuration for the TTL of course assets.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
app_label = 'contentserver'
|
||||
@@ -30,7 +34,11 @@ class CourseAssetCacheTtlConfig(ConfigurationModel):
|
||||
|
||||
|
||||
class CdnUserAgentsConfig(ConfigurationModel):
|
||||
"""Configuration for the user agents we expect to see from CDNs."""
|
||||
"""
|
||||
Configuration for the user agents we expect to see from CDNs.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
app_label = 'contentserver'
|
||||
|
||||
@@ -5,10 +5,12 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class XDomainProxyConfiguration(ConfigurationModel):
|
||||
"""Cross-domain proxy configuration.
|
||||
"""
|
||||
Cross-domain proxy configuration.
|
||||
|
||||
See `openedx.core.djangoapps.cors_csrf.views.xdomain_proxy` for an explanation of how this works.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
whitelist = models.fields.TextField(
|
||||
|
||||
@@ -22,6 +22,8 @@ class CourseUserGroup(models.Model):
|
||||
This model represents groups of users in a course. Groups may have different types,
|
||||
which may be treated specially. For example, a user can be in at most one cohort per
|
||||
course, and cohorts are used to split up the forums by group.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
unique_together = (('name', 'course_id'), )
|
||||
@@ -67,8 +69,11 @@ class CourseUserGroup(models.Model):
|
||||
|
||||
|
||||
class CohortMembership(models.Model):
|
||||
"""Used internally to enforce our particular definition of uniqueness"""
|
||||
"""
|
||||
Used internally to enforce our particular definition of uniqueness.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
course_user_group = models.ForeignKey(CourseUserGroup, on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
course_id = CourseKeyField(max_length=255)
|
||||
@@ -143,6 +148,8 @@ def remove_user_from_cohort(sender, instance, **kwargs): # pylint: disable=unus
|
||||
class CourseUserGroupPartitionGroup(models.Model):
|
||||
"""
|
||||
Create User Partition Info.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
course_user_group = models.OneToOneField(CourseUserGroup, on_delete=models.CASCADE)
|
||||
partition_id = models.IntegerField(
|
||||
@@ -159,6 +166,8 @@ class CourseCohortsSettings(models.Model):
|
||||
"""
|
||||
This model represents cohort settings for courses.
|
||||
The only non-deprecated fields are `is_cohorted` and `course_id`.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
is_cohorted = models.BooleanField(default=False)
|
||||
|
||||
@@ -197,6 +206,8 @@ class CourseCohortsSettings(models.Model):
|
||||
class CourseCohort(models.Model):
|
||||
"""
|
||||
This model represents cohort related info.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
course_user_group = models.OneToOneField(CourseUserGroup, unique=True, related_name='cohort',
|
||||
on_delete=models.CASCADE)
|
||||
@@ -231,6 +242,10 @@ class CourseCohort(models.Model):
|
||||
class UnregisteredLearnerCohortAssignments(DeletableByUserValue, models.Model):
|
||||
"""
|
||||
Tracks the assignment of an unregistered learner to a course's cohort.
|
||||
|
||||
.. pii: The email field stores PII.
|
||||
.. pii_types: email_address
|
||||
.. pii_retirement: local_api
|
||||
"""
|
||||
# pylint: disable=model-missing-unicode
|
||||
class Meta(object):
|
||||
|
||||
@@ -8,7 +8,11 @@ from django.db import models
|
||||
|
||||
|
||||
class CrawlersConfig(ConfigurationModel):
|
||||
"""Configuration for the crawlers django app."""
|
||||
"""
|
||||
Configuration for the crawlers django app.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "crawlers"
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ class CredentialsApiConfig(ConfigurationModel):
|
||||
"""
|
||||
Manages configuration for connecting to the Credential service and using its
|
||||
API.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
@@ -113,6 +115,8 @@ class CredentialsApiConfig(ConfigurationModel):
|
||||
class NotifyCredentialsConfig(ConfigurationModel):
|
||||
"""
|
||||
Manages configuration for a run of the notify_credentials management command.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
|
||||
@@ -38,6 +38,8 @@ class CreditProvider(TimeStampedModel):
|
||||
includes a `url` where the student will be sent when he/she will try to
|
||||
get credit for course. Eligibility duration will be use to set duration
|
||||
for which credit eligible message appears on dashboard.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
provider_id = models.CharField(
|
||||
max_length=255,
|
||||
@@ -213,6 +215,8 @@ def invalidate_provider_cache(sender, **kwargs): # pylint: disable=unused-argum
|
||||
class CreditCourse(models.Model):
|
||||
"""
|
||||
Model for tracking a credit course.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
course_key = CourseKeyField(max_length=255, db_index=True, unique=True)
|
||||
@@ -282,6 +286,8 @@ class CreditRequirement(TimeStampedModel):
|
||||
The 'display_name' field stores the display name of the requirement.
|
||||
The 'criteria' field dictionary provides additional information, clients
|
||||
may need to determine whether a user has satisfied the requirement.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
course = models.ForeignKey(CreditCourse, related_name="credit_requirements", on_delete=models.CASCADE)
|
||||
@@ -418,6 +424,7 @@ class CreditRequirementStatus(TimeStampedModel):
|
||||
|
||||
In case (3), no CreditRequirementStatus record will exist for the requirement and user.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
REQUIREMENT_STATUS_CHOICES = (
|
||||
@@ -527,14 +534,20 @@ class CreditRequirementStatus(TimeStampedModel):
|
||||
|
||||
|
||||
def default_deadline_for_credit_eligibility():
|
||||
""" The default deadline to use when creating a new CreditEligibility model. """
|
||||
"""
|
||||
The default deadline to use when creating a new CreditEligibility model.
|
||||
"""
|
||||
return datetime.datetime.now(pytz.UTC) + datetime.timedelta(
|
||||
days=getattr(settings, "CREDIT_ELIGIBILITY_EXPIRATION_DAYS", 365)
|
||||
)
|
||||
|
||||
|
||||
class CreditEligibility(TimeStampedModel):
|
||||
""" A record of a user's eligibility for credit for a specific course. """
|
||||
"""
|
||||
A record of a user's eligibility for credit for a specific course.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
username = models.CharField(max_length=255, db_index=True)
|
||||
course = models.ForeignKey(CreditCourse, related_name="eligibilities", on_delete=models.CASCADE)
|
||||
|
||||
@@ -645,6 +658,8 @@ class CreditRequest(TimeStampedModel):
|
||||
at the time the request is made. If the user re-issues the request
|
||||
(perhaps because the user did not finish filling in forms on the credit provider's site),
|
||||
the request record will be updated, but the UUID will remain the same.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
uuid = models.CharField(max_length=32, unique=True, db_index=True)
|
||||
@@ -760,7 +775,11 @@ class CreditRequest(TimeStampedModel):
|
||||
|
||||
|
||||
class CreditConfig(ConfigurationModel):
|
||||
""" Manage credit configuration """
|
||||
"""
|
||||
Manage credit configuration
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
CACHE_KEY = 'credit.providers.api.data'
|
||||
|
||||
cache_ttl = models.PositiveIntegerField(
|
||||
|
||||
@@ -8,6 +8,8 @@ from django.db import models
|
||||
class DarkLangConfig(ConfigurationModel):
|
||||
"""
|
||||
Configuration for the dark_lang django app.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
released_languages = models.TextField(
|
||||
blank=True,
|
||||
|
||||
@@ -40,6 +40,8 @@ class EmbargoedCourse(models.Model):
|
||||
Enable course embargo on a course-by-course basis.
|
||||
|
||||
Deprecated by `RestrictedCourse`
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
objects = NoneToEmptyManager()
|
||||
|
||||
@@ -74,6 +76,8 @@ class EmbargoedState(ConfigurationModel):
|
||||
Register countries to be embargoed.
|
||||
|
||||
Deprecated by `Country`.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
# The countries to embargo
|
||||
embargoed_countries = models.TextField(
|
||||
@@ -95,7 +99,8 @@ class EmbargoedState(ConfigurationModel):
|
||||
|
||||
|
||||
class RestrictedCourse(models.Model):
|
||||
"""Course with access restrictions.
|
||||
"""
|
||||
Course with access restrictions.
|
||||
|
||||
Restricted courses can block users at two points:
|
||||
|
||||
@@ -110,6 +115,7 @@ class RestrictedCourse(models.Model):
|
||||
messages to users when they are blocked.
|
||||
These displayed on pages served by the embargo app.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
COURSE_LIST_CACHE_KEY = 'embargo.restricted_courses'
|
||||
MESSAGE_URL_CACHE_KEY = 'embargo.message_url_path.{access_point}.{course_key}'
|
||||
@@ -370,6 +376,7 @@ class Country(models.Model):
|
||||
There is a data migration that creates entries for
|
||||
each country code.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
country = CountryField(
|
||||
db_index=True, unique=True,
|
||||
@@ -403,6 +410,7 @@ class CountryAccessRule(models.Model):
|
||||
2) From the initial list, remove all blacklisted countries
|
||||
for the course.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
WHITELIST_RULE = 'whitelist'
|
||||
@@ -579,7 +587,11 @@ post_delete.connect(invalidate_country_rule_cache, sender=RestrictedCourse)
|
||||
|
||||
|
||||
class CourseAccessRuleHistory(models.Model):
|
||||
"""History of course access rule changes. """
|
||||
"""
|
||||
History of course access rule changes.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
# pylint: disable=model-missing-unicode
|
||||
|
||||
timestamp = models.DateTimeField(db_index=True, auto_now_add=True)
|
||||
@@ -667,6 +679,8 @@ post_delete.connect(CourseAccessRuleHistory.snapshot_post_delete_receiver, sende
|
||||
class IPFilter(ConfigurationModel):
|
||||
"""
|
||||
Register specific IP addresses to explicitly block or unblock.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
whitelist = models.TextField(
|
||||
blank=True,
|
||||
|
||||
@@ -16,6 +16,10 @@ from django.db import models
|
||||
class ExternalAuthMap(models.Model):
|
||||
"""
|
||||
Model class for external auth.
|
||||
|
||||
.. pii: Contains PII used in mapping external auth. Unused and empty on edx.org.
|
||||
.. pii_types: name, email_address, password, external_service
|
||||
.. pii_retirement: retained
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "external_auth"
|
||||
|
||||
@@ -7,6 +7,8 @@ import copy
|
||||
from unittest import skip
|
||||
from mock import Mock, patch
|
||||
|
||||
from six import text_type
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import SESSION_KEY
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
@@ -97,7 +99,7 @@ class SSLClientTest(ModuleStoreTestCase):
|
||||
self.assertIn('<form role="form" id="register-form" method="post"', response.content)
|
||||
try:
|
||||
ExternalAuthMap.objects.get(external_id=self.USER_EMAIL)
|
||||
except ExternalAuthMap.DoesNotExist, ex:
|
||||
except ExternalAuthMap.DoesNotExist as ex:
|
||||
self.fail(u'User did not get properly added to external auth map, exception was {0}'.format(str(ex)))
|
||||
|
||||
with self.assertRaises(User.DoesNotExist):
|
||||
@@ -116,7 +118,7 @@ class SSLClientTest(ModuleStoreTestCase):
|
||||
|
||||
try:
|
||||
ExternalAuthMap.objects.get(external_id=self.USER_EMAIL)
|
||||
except ExternalAuthMap.DoesNotExist, ex:
|
||||
except ExternalAuthMap.DoesNotExist as ex:
|
||||
self.fail(u'User did not get properly added to external auth map, exception was {0}'.format(str(ex)))
|
||||
|
||||
with self.assertRaises(User.DoesNotExist):
|
||||
@@ -135,11 +137,11 @@ class SSLClientTest(ModuleStoreTestCase):
|
||||
# Assert our user exists in both eamap and Users, and that we are logged in
|
||||
try:
|
||||
ExternalAuthMap.objects.get(external_id=self.USER_EMAIL)
|
||||
except ExternalAuthMap.DoesNotExist, ex:
|
||||
except ExternalAuthMap.DoesNotExist as ex:
|
||||
self.fail(u'User did not get properly added to external auth map, exception was {0}'.format(str(ex)))
|
||||
try:
|
||||
User.objects.get(email=self.USER_EMAIL)
|
||||
except ExternalAuthMap.DoesNotExist, ex:
|
||||
except ExternalAuthMap.DoesNotExist as ex:
|
||||
self.fail(u'User did not get properly added to internal users, exception was {0}'.format(str(ex)))
|
||||
|
||||
@skip_unless_cms
|
||||
@@ -161,11 +163,11 @@ class SSLClientTest(ModuleStoreTestCase):
|
||||
# Assert our user exists in both eamap and Users, and that we are logged in
|
||||
try:
|
||||
ExternalAuthMap.objects.get(external_id=self.USER_EMAIL)
|
||||
except ExternalAuthMap.DoesNotExist, ex:
|
||||
except ExternalAuthMap.DoesNotExist as ex:
|
||||
self.fail(u'User did not get properly added to external auth map, exception was {0}'.format(str(ex)))
|
||||
try:
|
||||
User.objects.get(email=self.USER_EMAIL)
|
||||
except ExternalAuthMap.DoesNotExist, ex:
|
||||
except ExternalAuthMap.DoesNotExist as ex:
|
||||
self.fail(u'User did not get properly added to internal users, exception was {0}'.format(str(ex)))
|
||||
|
||||
@skip_unless_lms
|
||||
@@ -322,11 +324,11 @@ class SSLClientTest(ModuleStoreTestCase):
|
||||
# Assert our user exists in both eamap and Users
|
||||
try:
|
||||
ExternalAuthMap.objects.get(external_id=self.USER_EMAIL)
|
||||
except ExternalAuthMap.DoesNotExist, ex:
|
||||
except ExternalAuthMap.DoesNotExist as ex:
|
||||
self.fail(u'User did not get properly added to external auth map, exception was {0}'.format(str(ex)))
|
||||
try:
|
||||
User.objects.get(email=self.USER_EMAIL)
|
||||
except ExternalAuthMap.DoesNotExist, ex:
|
||||
except ExternalAuthMap.DoesNotExist as ex:
|
||||
self.fail(u'User did not get properly added to internal users, exception was {0}'.format(str(ex)))
|
||||
self.assertEqual(1, len(ExternalAuthMap.objects.all()))
|
||||
|
||||
@@ -382,7 +384,7 @@ class SSLClientTest(ModuleStoreTestCase):
|
||||
CourseEnrollment.enroll(user, course.id)
|
||||
|
||||
CourseStaffRole(course.id).add_users(user)
|
||||
course_private_url = reverse('course_handler', args=(unicode(course.id),))
|
||||
course_private_url = reverse('course_handler', args=(text_type(course.id),))
|
||||
self.assertNotIn(SESSION_KEY, self.client.session)
|
||||
|
||||
response = self.client.get(
|
||||
|
||||
@@ -12,6 +12,7 @@ from organizations.models import Organization
|
||||
from pytz import utc
|
||||
|
||||
from openedx.core.djangoapps.oauth_dispatch.toggles import ENFORCE_JWT_SCOPES
|
||||
from openedx.core.djangolib.markup import HTML
|
||||
from openedx.core.lib.request_utils import get_request_or_stub
|
||||
|
||||
|
||||
@@ -22,6 +23,8 @@ class RestrictedApplication(models.Model):
|
||||
|
||||
A restricted Application will only get expired token/JWT payloads
|
||||
so that they cannot be used to call into APIs.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, null=False, on_delete=models.CASCADE)
|
||||
@@ -33,8 +36,8 @@ class RestrictedApplication(models.Model):
|
||||
"""
|
||||
Return a unicode representation of this object
|
||||
"""
|
||||
return u"<RestrictedApplication '{name}'>".format(
|
||||
name=self.application.name
|
||||
return HTML(u"<RestrictedApplication '{name}'>").format(
|
||||
name=HTML(self.application.name)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -56,6 +59,8 @@ class RestrictedApplication(models.Model):
|
||||
class ApplicationAccess(models.Model):
|
||||
"""
|
||||
Specifies access control information for the associated Application.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
application = models.OneToOneField(oauth2_settings.APPLICATION_MODEL, related_name='access')
|
||||
@@ -89,6 +94,8 @@ class ApplicationOrganization(models.Model):
|
||||
|
||||
See openedx/core/djangoapps/oauth_dispatch/docs/decisions/0007-include-organizations-in-tokens.rst
|
||||
for the intended use of this model.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
RELATION_TYPE_CONTENT_ORG = 'content_org'
|
||||
RELATION_TYPES = (
|
||||
|
||||
@@ -28,8 +28,8 @@ from ..views import LOG_MESSAGE_CREATE, LOG_MESSAGE_DELETE
|
||||
from .helpers import make_image_file
|
||||
|
||||
TEST_PASSWORD = "test"
|
||||
TEST_UPLOAD_DT = datetime.datetime(2002, 1, 9, 15, 43, 01, tzinfo=UTC)
|
||||
TEST_UPLOAD_DT2 = datetime.datetime(2003, 1, 9, 15, 43, 01, tzinfo=UTC)
|
||||
TEST_UPLOAD_DT = datetime.datetime(2002, 1, 9, 15, 43, 1, tzinfo=UTC)
|
||||
TEST_UPLOAD_DT2 = datetime.datetime(2003, 1, 9, 15, 43, 1, tzinfo=UTC)
|
||||
|
||||
|
||||
class ProfileImageEndpointMixin(UserSettingsEventTestMixin):
|
||||
|
||||
@@ -10,6 +10,8 @@ class ProgramsApiConfig(ConfigurationModel):
|
||||
This model no longer fronts an API, but now sets a few config-related values for the idea of programs in general.
|
||||
|
||||
A rename to ProgramsConfig would be more accurate, but costly in terms of developer time.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "programs"
|
||||
|
||||
@@ -8,6 +8,10 @@ from config_models.models import ConfigurationModel
|
||||
|
||||
|
||||
class Schedule(TimeStampedModel):
|
||||
"""
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
enrollment = models.OneToOneField('student.CourseEnrollment', null=False, on_delete=models.CASCADE)
|
||||
active = models.BooleanField(
|
||||
default=True,
|
||||
@@ -36,6 +40,9 @@ class Schedule(TimeStampedModel):
|
||||
|
||||
|
||||
class ScheduleConfig(ConfigurationModel):
|
||||
"""
|
||||
.. no_pii:
|
||||
"""
|
||||
KEY_FIELDS = ('site',)
|
||||
|
||||
site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
||||
@@ -50,6 +57,9 @@ class ScheduleConfig(ConfigurationModel):
|
||||
|
||||
|
||||
class ScheduleExperience(models.Model):
|
||||
"""
|
||||
.. no_pii:
|
||||
"""
|
||||
EXPERIENCES = Choices(
|
||||
(0, 'default', 'Recurring Nudge and Upgrade Reminder'),
|
||||
(1, 'course_updates', 'Course Updates')
|
||||
|
||||
@@ -10,6 +10,8 @@ from django.utils.translation import ugettext_lazy as _
|
||||
class SelfPacedConfiguration(ConfigurationModel):
|
||||
"""
|
||||
Configuration for self-paced courses.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
enable_course_home_improvements = BooleanField(
|
||||
|
||||
@@ -22,6 +22,8 @@ class SiteConfiguration(models.Model):
|
||||
Fields:
|
||||
site (OneToOneField): one to one field relating each configuration to a single site
|
||||
values (JSONField): json field to store configurations for a site
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
site = models.OneToOneField(Site, related_name='configuration', on_delete=models.CASCADE)
|
||||
enabled = models.BooleanField(default=False, verbose_name="Enabled")
|
||||
@@ -140,6 +142,8 @@ class SiteConfigurationHistory(TimeStampedModel):
|
||||
Fields:
|
||||
site (ForeignKey): foreign-key to django Site
|
||||
values (JSONField): json field to store configurations for a site
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
site = models.ForeignKey(Site, related_name='configuration_histories', on_delete=models.CASCADE)
|
||||
enabled = models.BooleanField(default=False, verbose_name="Enabled")
|
||||
|
||||
@@ -11,6 +11,8 @@ class SiteTheme(models.Model):
|
||||
|
||||
`site` field is foreignkey to django Site model
|
||||
`theme_dir_name` contains directory name having Site's theme
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
site = models.ForeignKey(Site, related_name='themes', on_delete=models.CASCADE)
|
||||
theme_dir_name = models.CharField(max_length=255)
|
||||
|
||||
@@ -13,7 +13,7 @@ from student.tests.factories import UserFactory
|
||||
from ..image_helpers import get_profile_image_urls_for_user
|
||||
|
||||
TEST_SIZES = {'full': 50, 'small': 10}
|
||||
TEST_PROFILE_IMAGE_UPLOAD_DT = datetime.datetime(2002, 1, 9, 15, 43, 01, tzinfo=UTC)
|
||||
TEST_PROFILE_IMAGE_UPLOAD_DT = datetime.datetime(2002, 1, 9, 15, 43, 1, tzinfo=UTC)
|
||||
|
||||
|
||||
@patch.dict('django.conf.settings.PROFILE_IMAGE_SIZES_MAP', TEST_SIZES, clear=True)
|
||||
|
||||
@@ -398,6 +398,7 @@ class DeactivateLogoutView(APIView):
|
||||
if verify_user_password_response.status_code != status.HTTP_204_NO_CONTENT:
|
||||
return verify_user_password_response
|
||||
with transaction.atomic():
|
||||
# Add user to retirement queue.
|
||||
UserRetirementStatus.create_retirement(request.user)
|
||||
# Unlink LMS social auth accounts
|
||||
UserSocialAuth.objects.filter(user_id=request.user.id).delete()
|
||||
@@ -406,10 +407,11 @@ class DeactivateLogoutView(APIView):
|
||||
request.user.email = get_retired_email_by_email(request.user.email)
|
||||
request.user.save()
|
||||
_set_unusable_password(request.user)
|
||||
|
||||
# TODO: Unlink social accounts & change password on each IDA.
|
||||
# Remove the activation keys sent by email to the user for account activation.
|
||||
Registration.objects.filter(user=request.user).delete()
|
||||
# Add user to retirement queue.
|
||||
|
||||
# Delete OAuth tokens associated with the user.
|
||||
retire_dop_oauth2_models(request.user)
|
||||
retire_dot_oauth2_models(request.user)
|
||||
|
||||
@@ -33,7 +33,11 @@ class RetirementStateError(Exception):
|
||||
|
||||
|
||||
class UserPreference(models.Model):
|
||||
"""A user's preference, stored as generic text to be processed by client"""
|
||||
"""
|
||||
A user's preference, stored as generic text to be processed by client
|
||||
|
||||
.. no_pii: Stores arbitrary key/value pairs, currently none are PII. If that changes, update this annotation.
|
||||
"""
|
||||
KEY_REGEX = r"[-_a-zA-Z0-9]+"
|
||||
user = models.ForeignKey(User, db_index=True, related_name="preferences", on_delete=models.CASCADE)
|
||||
key = models.CharField(max_length=255, db_index=True, validators=[RegexValidator(KEY_REGEX)])
|
||||
@@ -112,6 +116,8 @@ class UserCourseTag(models.Model):
|
||||
"""
|
||||
Per-course user tags, to be used by various things that want to store tags about
|
||||
the user. Added initially to store assignment to experimental groups.
|
||||
|
||||
.. no_pii: Stores arbitrary key/value pairs about users, but does not currently store any PII. This may change!
|
||||
"""
|
||||
user = models.ForeignKey(User, db_index=True, related_name="+", on_delete=models.CASCADE)
|
||||
key = models.CharField(max_length=255, db_index=True)
|
||||
@@ -128,6 +134,9 @@ class UserOrgTag(TimeStampedModel, DeletableByUserValue): # pylint: disable=mod
|
||||
|
||||
Allows settings to be configured at an organization level.
|
||||
|
||||
.. pii: Does not strictly store PII, but maintains the email-optin flag and so is retired in AccountRetirementView.
|
||||
.. pii_types: other
|
||||
.. pii_retirement: local_api
|
||||
"""
|
||||
user = models.ForeignKey(User, db_index=True, related_name="+", on_delete=models.CASCADE)
|
||||
key = models.CharField(max_length=255, db_index=True)
|
||||
@@ -142,6 +151,8 @@ class RetirementState(models.Model):
|
||||
"""
|
||||
Stores the list and ordering of the steps of retirement, this should almost never change
|
||||
as updating it can break the retirement process of users already in the queue.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
state_name = models.CharField(max_length=30, unique=True)
|
||||
state_execution_order = models.SmallIntegerField(unique=True)
|
||||
@@ -174,6 +185,10 @@ class UserRetirementPartnerReportingStatus(TimeStampedModel):
|
||||
and asynchronous, timeline than LMS retirement and only impacts a subset of learners
|
||||
so it maintains a queue. This queue is populated as part of the LMS retirement
|
||||
process.
|
||||
|
||||
.. pii: Contains a retiring user's name, username, and email. Retired in AccountRetirementPartnerReportView.
|
||||
.. pii_types: name, username, email_address
|
||||
.. pii_retirement: local_api
|
||||
"""
|
||||
user = models.OneToOneField(User)
|
||||
original_username = models.CharField(max_length=150, db_index=True)
|
||||
@@ -197,6 +212,8 @@ class UserRetirementRequest(TimeStampedModel):
|
||||
Records and perists every user retirement request.
|
||||
Users that have requested to cancel their retirement before retirement begins can be removed.
|
||||
All other retired users persist in this table forever.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||
|
||||
@@ -227,6 +244,10 @@ class UserRetirementRequest(TimeStampedModel):
|
||||
class UserRetirementStatus(TimeStampedModel):
|
||||
"""
|
||||
Tracks the progress of a user's retirement request
|
||||
|
||||
.. pii: Contains a retiring user's name, username, and email. Retired in AccountRetirementStatusView.cleanup().
|
||||
.. pii_types: name, username, email_address
|
||||
.. pii_retirement: local_api
|
||||
"""
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||
original_username = models.CharField(max_length=150, db_index=True)
|
||||
|
||||
@@ -260,6 +260,9 @@ def _track_user_login(user, request):
|
||||
"""
|
||||
Sends a tracking event for a successful login.
|
||||
"""
|
||||
# .. pii: Username and email are sent to Segment here. Retired directly through Segment API call in Tubular.
|
||||
# .. pii_types: email_address, username
|
||||
# .. pii_retirement: third_party
|
||||
segment.identify(
|
||||
user.id,
|
||||
{
|
||||
|
||||
@@ -335,6 +335,9 @@ def _track_user_registration(user, profile, params, third_party_provider):
|
||||
}
|
||||
})
|
||||
|
||||
# .. pii: Many pieces of PII are sent to Segment here. Retired directly through Segment API call in Tubular.
|
||||
# .. pii_types: email_address, username, name, birth_date, location, gender
|
||||
# .. pii_retirement: third_party
|
||||
segment.identify(*identity_args)
|
||||
segment.track(
|
||||
user.id,
|
||||
|
||||
@@ -92,6 +92,8 @@ def pre_save_callback(sender, instance, **kwargs): # pylint: disable=unused-arg
|
||||
class VerifiedTrackCohortedCourse(models.Model):
|
||||
"""
|
||||
Tracks which courses have verified track auto-cohorting enabled.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
course_key = CourseKeyField(
|
||||
max_length=255, db_index=True, unique=True,
|
||||
@@ -154,6 +156,8 @@ def invalidate_verified_track_cache(sender, **kwargs): # pylint: disable=unuse
|
||||
class MigrateVerifiedTrackCohortsSetting(ConfigurationModel):
|
||||
"""
|
||||
Configuration for the swap_from_auto_track_cohorts management command.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "verified_track_content"
|
||||
|
||||
@@ -17,6 +17,8 @@ class HLSPlaybackEnabledFlag(ConfigurationModel):
|
||||
When this feature flag is set to true, individual courses
|
||||
must also have HLS Playback enabled for this feature to
|
||||
take effect.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
# this field overrides course-specific settings
|
||||
enabled_for_all_courses = BooleanField(default=False)
|
||||
@@ -56,6 +58,8 @@ class CourseHLSPlaybackEnabledFlag(ConfigurationModel):
|
||||
"""
|
||||
Enables HLS Playback for a specific course. Global feature must be
|
||||
enabled for this to take effect.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
KEY_FIELDS = ('course_id',)
|
||||
|
||||
@@ -80,6 +84,8 @@ class VideoTranscriptEnabledFlag(ConfigurationModel):
|
||||
take effect.
|
||||
When this feature is enabled, 3rd party transcript integration functionality would be available accross all
|
||||
courses or some specific courses and S3 video transcript would be served (currently as a fallback).
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
# this field overrides course-specific settings
|
||||
enabled_for_all_courses = BooleanField(default=False)
|
||||
@@ -121,6 +127,8 @@ class CourseVideoTranscriptEnabledFlag(ConfigurationModel):
|
||||
enabled for this to take effect.
|
||||
When this feature is enabled, 3rd party transcript integration functionality would be available for the
|
||||
specific course and S3 video transcript would be served (currently as a fallback).
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
KEY_FIELDS = ('course_id',)
|
||||
|
||||
@@ -140,6 +148,8 @@ class CourseVideoTranscriptEnabledFlag(ConfigurationModel):
|
||||
class TranscriptMigrationSetting(ConfigurationModel):
|
||||
"""
|
||||
Arguments for the Transcript Migration management command
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
def __unicode__(self):
|
||||
return (
|
||||
@@ -181,6 +191,8 @@ class TranscriptMigrationSetting(ConfigurationModel):
|
||||
class MigrationEnqueuedCourse(TimeStampedModel):
|
||||
"""
|
||||
Temporary model to persist the course IDs who has been enqueued for transcripts migration to S3.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
course_id = CourseKeyField(db_index=True, primary_key=True, max_length=255)
|
||||
command_run = PositiveIntegerField(default=0)
|
||||
@@ -194,6 +206,8 @@ class MigrationEnqueuedCourse(TimeStampedModel):
|
||||
class VideoThumbnailSetting(ConfigurationModel):
|
||||
"""
|
||||
Arguments for the Video Thumbnail management command
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
command_run = PositiveIntegerField(default=0)
|
||||
offset = PositiveIntegerField(default=0)
|
||||
@@ -234,6 +248,8 @@ class VideoThumbnailSetting(ConfigurationModel):
|
||||
class UpdatedCourseVideos(TimeStampedModel):
|
||||
"""
|
||||
Temporary model to persist the course videos which have been enqueued to update video thumbnails.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
course_id = CourseKeyField(db_index=True, max_length=255)
|
||||
edx_video_id = models.CharField(max_length=100)
|
||||
|
||||
@@ -11,6 +11,8 @@ from opaque_keys.edx.django.models import CourseKeyField
|
||||
class VideoPipelineIntegration(ConfigurationModel):
|
||||
"""
|
||||
Manages configuration for connecting to the edx-video-pipeline service and using its API.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
client_name = models.CharField(
|
||||
max_length=100,
|
||||
@@ -43,6 +45,8 @@ class VideoPipelineIntegration(ConfigurationModel):
|
||||
class VideoUploadsEnabledByDefault(ConfigurationModel):
|
||||
"""
|
||||
Enables video uploads enabled By default feature across the platform.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
# this field overrides course-specific settings
|
||||
enabled_for_all_courses = models.BooleanField(default=False)
|
||||
@@ -83,6 +87,8 @@ class CourseVideoUploadsEnabledByDefault(ConfigurationModel):
|
||||
"""
|
||||
Enables video uploads enabled by default feature for a specific course. Its global feature must be
|
||||
enabled for this to take effect.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
KEY_FIELDS = ('course_id',)
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ from openedx.core.lib.cache_utils import request_cached
|
||||
class WaffleFlagCourseOverrideModel(ConfigurationModel):
|
||||
"""
|
||||
Used to force a waffle flag on or off for a course.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
OVERRIDE_CHOICES = Choices(('on', _('Force On')), ('off', _('Force Off')))
|
||||
ALL_CHOICES = OVERRIDE_CHOICES + Choices('unset')
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user