Merge pull request #19675 from edx/juliasq/add_pii_annotations
Add PII annotations, un-pin stevedore, upgrade deps.
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:
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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',)
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -34,6 +34,8 @@ from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID
|
||||
class ContentTypeGatingConfig(StackedConfigurationModel):
|
||||
"""
|
||||
A ConfigurationModel used to manage configuration for Content Type Gating (Feature Based Enrollments).
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
STACKABLE_FIELDS = ('enabled', 'enabled_as_of', 'studio_override_enabled')
|
||||
|
||||
@@ -34,6 +34,8 @@ from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID
|
||||
class CourseDurationLimitConfig(StackedConfigurationModel):
|
||||
"""
|
||||
Configuration to manage the Course Duration Limit facility.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
STACKABLE_FIELDS = ('enabled', 'enabled_as_of')
|
||||
|
||||
@@ -756,6 +756,31 @@ def _get_xsscommitlint_count(filename):
|
||||
return None
|
||||
|
||||
|
||||
@task
|
||||
@needs('pavelib.prereqs.install_python_prereqs')
|
||||
@timed
|
||||
def run_pii_check(options): # pylint: disable=unused-argument
|
||||
"""
|
||||
Guarantee that all Django models are PII-annotated.
|
||||
"""
|
||||
for env_name, env_settings_file in (("CMS", "cms.envs.test"), ("LMS", "lms.envs.test")):
|
||||
try:
|
||||
print()
|
||||
print("Running {} PII Annotation check and report".format(env_name))
|
||||
print("-" * 45)
|
||||
sh(
|
||||
"export DJANGO_SETTINGS_MODULE={}; "
|
||||
"code_annotations django_find_annotations "
|
||||
"--config_file .pii_annotations.yml --report_path pii_report/ "
|
||||
"--lint --report --coverage".format(env_settings_file)
|
||||
)
|
||||
|
||||
except BuildFailure as error_message:
|
||||
fail_quality('pii_check', 'FAILURE: {}'.format(error_message))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@task
|
||||
@needs('pavelib.prereqs.install_python_prereqs')
|
||||
@cmdopts([
|
||||
|
||||
@@ -44,7 +44,6 @@ aniso8601==4.1.0 # via tincan
|
||||
anyjson==0.3.3 # via kombu
|
||||
appdirs==1.4.3 # via fs
|
||||
argh==0.26.2
|
||||
argparse==1.4.0
|
||||
asn1crypto==0.24.0
|
||||
attrs==17.4.0
|
||||
babel==1.3
|
||||
@@ -228,7 +227,7 @@ sorl-thumbnail==12.3
|
||||
sortedcontainers==0.9.2
|
||||
soupsieve==1.8 # via beautifulsoup4
|
||||
sqlparse==0.2.4
|
||||
stevedore==1.10.0
|
||||
stevedore==1.30.0
|
||||
sympy==0.7.1
|
||||
tincan==0.0.5 # via edx-enterprise
|
||||
unicodecsv==0.14.1
|
||||
|
||||
@@ -71,6 +71,7 @@ cffi==1.12.1
|
||||
chardet==3.0.4
|
||||
click-log==0.1.8
|
||||
click==7.0
|
||||
code-annotations==0.2.3
|
||||
colorama==0.4.1
|
||||
configparser==3.7.1
|
||||
constantly==15.1.0
|
||||
@@ -321,7 +322,7 @@ sphinx==1.8.4
|
||||
sphinxcontrib-websupport==1.1.0 # via sphinx
|
||||
splinter==0.9.0
|
||||
sqlparse==0.2.4
|
||||
stevedore==1.10.0
|
||||
stevedore==1.30.0
|
||||
sure==1.4.11
|
||||
sympy==0.7.1
|
||||
testfixtures==6.5.2
|
||||
|
||||
@@ -21,7 +21,7 @@ psutil==1.2.1 # Library for retrieving information on runn
|
||||
pymongo==2.9.1 # via edx-opaque-keys
|
||||
python-memcached==1.48 # Python interface to the memcached memory cache daemon
|
||||
requests # Simple interface for making HTTP requests
|
||||
stevedore==1.10.0 # via edx-opaque-keys
|
||||
stevedore # Support for runtime plugins, used for XBlocks and edx-platform Django app plugins
|
||||
watchdog # Used in paver watch_assets
|
||||
wrapt==1.10.5 # Decorator utilities used in the @timed paver task decorator
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
# make upgrade
|
||||
#
|
||||
argh==0.26.2 # via watchdog
|
||||
argparse==1.4.0 # via stevedore
|
||||
certifi==2018.11.29 # via requests
|
||||
chardet==3.0.4 # via requests
|
||||
edx-opaque-keys==0.4.4
|
||||
@@ -24,7 +23,7 @@ python-memcached==1.48
|
||||
pyyaml==3.13 # via watchdog
|
||||
requests==2.21.0
|
||||
six==1.11.0
|
||||
stevedore==1.10.0
|
||||
stevedore==1.30.0
|
||||
urllib3==1.23 # via requests
|
||||
watchdog==0.9.0
|
||||
wrapt==1.10.5
|
||||
|
||||
@@ -21,6 +21,7 @@ beautifulsoup4 # Library for extracting data from HTML and XML files
|
||||
before_after # Syntactic sugar for mock, only used in one test case, not Python 3 compatible
|
||||
bok-choy # Framework for browser automation tests, based on selenium
|
||||
caniusepython3 # Library for checking the ability to upgrade to python3
|
||||
code-annotations # Perform code annotation checking, such as for PII annotations
|
||||
cssselect # Used to extract HTML fragments via CSS selectors in 2 test cases and pyquery
|
||||
ddt # Run a test case multiple times with different input; used in many, many of our tests
|
||||
edx-i18n-tools>=0.4.6 # Commands for developers and translators to extract, compile and validate translations
|
||||
|
||||
@@ -46,7 +46,7 @@ anyjson==0.3.3
|
||||
apipkg==1.5 # via execnet
|
||||
appdirs==1.4.3
|
||||
argh==0.26.2
|
||||
argparse==1.4.0
|
||||
argparse==1.4.0 # via unittest2
|
||||
asn1crypto==0.24.0
|
||||
astroid==1.5.3 # via edx-lint, pylint, pylint-celery
|
||||
atomicwrites==1.3.0 # via pytest
|
||||
@@ -69,6 +69,7 @@ cffi==1.12.1
|
||||
chardet==3.0.4
|
||||
click-log==0.1.8 # via edx-lint
|
||||
click==7.0
|
||||
code-annotations==0.2.3
|
||||
colorama==0.4.1 # via radon
|
||||
configparser==3.7.1 # via entrypoints, flake8, pylint
|
||||
constantly==15.1.0 # via twisted
|
||||
@@ -308,7 +309,7 @@ sortedcontainers==0.9.2
|
||||
soupsieve==1.8
|
||||
splinter==0.9.0
|
||||
sqlparse==0.2.4
|
||||
stevedore==1.10.0
|
||||
stevedore==1.30.0
|
||||
sure==1.4.11
|
||||
sympy==0.7.1
|
||||
testfixtures==6.5.2
|
||||
|
||||
Reference in New Issue
Block a user