def hotwire_multisite(): """ This is a quick and dirty implementation of a single site in multisite mode """ params = dict( name='Foo Organization', short_name='FOO', description='Foo org description', logo=None, active=True, ) org = Organization.objects.create(**params) if is_multisite(): org.sites = [get_site()] org.save() for course in CourseOverview.objects.all(): OrganizationCourse.objects.create(course_id=str(course.id), organization=org, active=True) for user in get_user_model().objects.all(): # For now, not setting is_amc_admin roles UserOrganizationMapping.objects.create(user=user, organization=org, is_active=True)
def get_users_for_site(site): if is_multisite(): users = get_user_model().objects.filter( organizations__sites__in=[site]) else: users = get_user_model().objects.all() return users
def seed_all(): print("\nseeding mock platform models") print("----------------------------") print("seeding course overviews...") seed_course_overviews() print("seeding users...") seed_users() print("seeding course enrollments...") seed_course_enrollments() if is_multisite(): print("Hotwiring multisite...") hotwire_multisite() print("- skipping seeding seed_course_access_roles, broken") # print("seeding course enrollments...") # seed_course_access_roles() print("seeding student modules...") seed_student_modules() print("seeding course completions...") seed_course_completions() print("\nseeding figures metrics models") print("------------------------------") print(("backfilling course daily metrics for {} days back...".format( DAYS_BACK))) print(" (this may take serveral minutes)") seed_course_daily_metrics() print("seeding site daily metrics...") seed_site_daily_metrics() print('Backfilling Figures EnrollmentData models...') backfill_figures_ed()
def setup(self, db): self.date_for = datetime.date(2018, 10, 1) self.site = Site.objects.first() self.users = [ UserFactory(date_joined=as_datetime(self.date_for - datetime.timedelta(days=60))) for i in range(0, 3) ] self.course_overviews = [ CourseOverviewFactory( created=as_datetime(self.date_for - datetime.timedelta(days=60))) for i in range(0, 3) ] self.cdm_recs = [ CourseDailyMetricsFactory(site=self.site, date_for=self.date_for, **cdm) for cdm in CDM_INPUT_TEST_DATA ] self.prev_day_sdm = SiteDailyMetricsFactory(site=self.site, date_for=prev_day( self.date_for), **SDM_DATA[1]) if is_multisite(): self.organization = OrganizationFactory(sites=[self.site]) for co in self.course_overviews: OrganizationCourseFactory(organization=self.organization, course_id=str(co.id)) if organizations_support_sites(): for user in self.users: UserOrganizationMappingFactory( user=user, organization=self.organization)
def get_course_enrollments_for_site(site): if is_multisite(): course_enrollments = CourseEnrollment.objects.filter( user__organizations__sites__in=[site]) else: course_enrollments = CourseEnrollment.objects.all() return course_enrollments
def setup(self, db): super(TestCoursesIndexViewSet, self).setup(db) self.course_overviews = [make_course(**data) for data in COURSE_DATA] if is_multisite(): self.organization = OrganizationFactory(sites=[self.site]) for co in self.course_overviews: OrganizationCourseFactory(organization=self.organization, course_id=str(co.id))
def get_student_modules_for_course_in_site(site, course_id): if is_multisite(): site_id = site.id check_site = get_site_for_course(course_id) if not check_site or site_id != check_site.id: CourseNotInSiteError( 'course "{}"" does not belong to site "{}"'.format( course_id, site_id)) return StudentModule.objects.filter(course_id=as_course_key(course_id))
def student_modules_for_course_enrollment(site, course_enrollment): """Return a queryset of all `StudentModule` records for a `CourseEnrollment` """ qs = StudentModule.objects.filter(student=course_enrollment.user, course_id=course_enrollment.course_id) if is_multisite(): # We _could eamake this generic if 'StudentModule' didn't go all snowflake # and decided that 'user' had to be 'student' qs = qs.filter(student__organizations__sites__in=[site]) return qs
def get_organizations_for_site(site): """ TODO: Refactor the functions in this module that make this call """ if is_multisite(): return organizations.models.Organization.objects.filter( sites__in=[site]) else: return organizations.models.Organization.all()
def setup(self, db): self.date_for = DEFAULT_END_DATE self.site = Site.objects.first() self.course_count = 4 self.course_overviews = [CourseOverviewFactory( created=self.date_for) for i in range(self.course_count)] if is_multisite(): self.organization = OrganizationFactory(sites=[self.site]) for co in self.course_overviews: OrganizationCourseFactory(organization=self.organization, course_id=str(co.id))
def get_courses_for_site(site): """Returns the courses accessible by the user on the site This function relies on Appsembler's fork of edx-organizations """ if is_multisite(): course_keys = get_course_keys_for_site(site) courses = CourseOverview.objects.filter(id__in=course_keys) else: courses = CourseOverview.objects.all() return courses
def get_course_keys_for_site(site): """ Developer note: We could improve this function with caching Question is which is the most efficient way to know cache expiry We may also be able to reduce the queries here to also improve performance """ if is_multisite(): course_ids = site_course_ids(site) else: course_ids = CourseOverview.objects.all().values_list('id', flat=True) return [as_course_key(cid) for cid in course_ids]
def site_course_ids(site): """Return a list of string course ids for the site """ if is_multisite(): return organizations.models.OrganizationCourse.objects.filter( organization__sites__in=[site]).values_list('course_id', flat=True) else: # Needs work. See about returning a queryset return [ str(key) for key in CourseOverview.objects.all().values_list('id', flat=True) ]
def wipe(): print('Wiping synthetic data...') clear_non_admin_users() CourseEnrollment.objects.all().delete() StudentModule.objects.all().delete() CourseOverview.objects.all().delete() CourseDailyMetrics.objects.all().delete() SiteDailyMetrics.objects.all().delete() LearnerCourseGradeMetrics.objects.all().delete() Organization.objects.all().delete() OrganizationCourse.objects.all().delete() if is_multisite(): UserOrganizationMapping.objects.all().delete()
def setup(self, db): super(TestGeneralCourseDataViewSet, self).setup(db) self.users = [make_user(**data) for data in USER_DATA] self.course_overviews = [make_course(**data) for data in COURSE_DATA] self.expected_result_keys = [ 'course_id', 'course_name', 'course_code','org', 'start_date', 'end_date', 'self_paced', 'staff', 'metrics', ] if is_multisite(): self.organization = OrganizationFactory(sites=[self.site]) for co in self.course_overviews: OrganizationCourseFactory(organization=self.organization, course_id=str(co.id))
def setup(self, db): super(TestCourseEnrollmentViewSet, self).setup(db) self.special_fields = ( 'create', 'user', ) self.course_overview = CourseOverviewFactory() self.course_enrollments = [ CourseEnrollmentFactory(course_id=self.course_overview.id) for i in range(1, 5) ] self.sample_course_id = self.course_enrollments[0].course_id if is_multisite(): self.organization = OrganizationFactory(sites=[self.site]) OrganizationCourseFactory(organization=self.organization, course_id=str(self.course_overview.id))
def get_site_for_course(course_id): """ Given a course, return the related site or None For standalone mode, will always return the site For multisite mode, will return the site if there is a mapping between the course and the site. Otherwise `None` is returned # Implementation notes There should be only one organization per course. TODO: Figure out how we want to handle ``DoesNotExist`` whether to let it raise back up raw or handle with a custom exception Figures 0.4.dev7 - This is called in the pipeline and in the serializers and views. The pipeline does exception handling. It should only fail for sites that have multiple organizations For Figures views and serializers, this should only fail when called for a specific site that has mulitple orgs """ if is_multisite(): org_courses = organizations.models.OrganizationCourse.objects.filter( course_id=str(course_id)) if org_courses: # Keep until this assumption analyzed msg = 'Multiple orgs found for course: {}' assert org_courses.count() == 1, msg.format(course_id) first_org = org_courses.first().organization if hasattr(first_org, 'sites'): msg = 'Must have one and only one site. Org is "{}"' assert first_org.sites.count() == 1, msg.format(first_org.name) site = first_org.sites.first() else: site = None else: # We don't want to make assumptions of who our consumers are # TODO: handle no organizations found for the course site = None else: # Operating in single site / standalone mode, return the default site site = Site.objects.get(id=settings.SITE_ID) return site
def site_certificates(site): """ If we want to be clever, we can abstract a function: ``` def site_user_related(site, model_class): if is_multisite(): return model_class.objects.filter(user__organizations__sites__in=[site]) else: return model_class.objects.all() def site_certificates(site): return site_user_related(GeneratedCertificate) ``` Then: """ if is_multisite(): return GeneratedCertificate.objects.filter( user__organizations__sites__in=[site]) else: return GeneratedCertificate.objects.all()
def test_get_search(self, search_term): """ Based on a SEARCH_TERMS data set, we query the endpoint with search terms and we compare with the expected results. """ request_path = self.request_path + '?search=' + search_term['term'] request = APIRequestFactory().get(request_path) force_authenticate(request, user=self.staff_user) view = self.view_class.as_view({'get': 'list'}) response = view(request) assert response.status_code == 200 if not is_multisite(): assert response.data['count'] == search_term['expected_result'] assert len(response.data['results']) == \ search_term['expected_result'] else: # the defaul site is example.com so it won't be find users, and # that the expected outcome, we should test with different # sites expecting different results, but is out of scope for # now. assert response.data['count'] == 0 assert len(response.data['results']) == 0
def test_get_average_progress(self): """ [John] This test needs work. The function it is testing needs work too for testability. We don't want to reproduce the function's behavior, we just want to be able to set up the source data with expected output and go. """ with mock.patch.dict('figures.helpers.settings.FEATURES', {'FIGURES_IS_MULTISITE': False}): assert not is_multisite() course_enrollments = CourseEnrollment.objects.filter( course_id=self.course_overview.id) actual = pipeline_cdm.get_average_progress( course_id=self.course_overview.id, date_for=self.today, course_enrollments=course_enrollments) # See tests/mocks/lms/djangoapps/grades/course_grade.py for # the source subsection grades that # TODO: make the mock data more configurable so we don't have to # hardcode the expected value assert actual == 0.5
def test_is_multisite(features, expected): """ Test features work properly for Figures multisite settings """ with mock.patch('figures.helpers.settings.FEATURES', features): assert figures_helpers.is_multisite() == expected
} def mock_metrics(course_enrollment, **_kwargs): return mapping[course_enrollment.course_id] monkeypatch.setattr( 'figures.pipeline.enrollment_metrics.get_site_for_course', lambda val: SiteFactory()) monkeypatch.setattr( 'figures.pipeline.enrollment_metrics.collect_metrics_for_enrollment', mock_metrics) data = bulk_calculate_course_progress_data(course_overview.id) assert data['average_progress'] == 0.5 @pytest.mark.skipif(not is_multisite(), reason='Standalone always returns a site') @pytest.mark.django_db def test_bulk_calculate_course_progress_unlinked_course_error(db, monkeypatch): """Tests 'bulk_calculate_course_progress_data' function The function under test iterates over a set of course enrollment records, So we create a couple of records to iterate over and mock the collect function """ course_overview = CourseOverviewFactory() course_enrollments = [ CourseEnrollmentFactory(course_id=course_overview.id) for i in range(2) ] mapping = { ce.course_id:
) from figures.helpers import ( as_course_key, as_datetime, days_from, prev_day, is_multisite, ) from figures.pipeline import course_daily_metrics as pipeline_cdm from figures.pipeline import site_daily_metrics as pipeline_sdm from figures.sites import get_organizations_for_site from devsite import cans from six.moves import range if is_multisite(): # First trying this without capturing 'ImportError' from organizations.models import UserOrganizationMapping FAKE = faker.Faker() LAST_DAY = days_from(datetime.datetime.now(), -2).replace(tzinfo=utc) DAYS_BACK = settings.DEVSITE_SEED['DAYS_BACK'] NUM_LEARNERS_PER_COURSE = settings.DEVSITE_SEED['NUM_LEARNERS_PER_COURSE'] # Quick and dirty debuging VERBOSE = False FOO_ORG = 'FOO'
def get_user_ids_for_site(site): if is_multisite(): user_ids = get_users_for_site(site).values_list('id', flat=True) else: user_ids = get_user_model().objects.all().values_list('id', flat=True) return user_ids