def get_ordered_updates(self, request, course): """ Returns any course updates in reverse chronological order. """ info_module = get_course_info_section_module(request, request.user, course, 'updates') updates = info_module.items if info_module else [] info_block = getattr(info_module, '_xmodule', info_module) if info_module else None ordered_updates = [update for update in updates if update.get('status') == self.STATUS_VISIBLE] ordered_updates.sort( key=lambda item: (self.safe_parse_date(item['date']), item['id']), reverse=True ) keyword_context = { 'username': request.user.username, 'user_id': request.user.id, 'name': request.user.profile.name, 'course_title': course.display_name, 'course_id': course.id, 'course_start_date': get_default_time_display(course.start), 'course_end_date': get_default_time_display(course.end), } for update in ordered_updates: update['content'] = info_block.system.replace_urls(update['content']) update['content'] = info_block.system.substitute_keywords_with_data(update['content'], keyword_context) return ordered_updates
def get_ordered_updates(self, request, course): """ Returns any course updates in reverse chronological order. """ info_module = get_course_info_section_module(request, request.user, course, 'updates') updates = info_module.items if info_module else [] info_block = getattr(info_module, '_xmodule', info_module) if info_module else None ordered_updates = [ update for update in updates if update.get('status') == self.STATUS_VISIBLE ] ordered_updates.sort(key=lambda item: (self.safe_parse_date(item['date']), item['id']), reverse=True) keyword_context = { 'username': request.user.username, 'user_id': request.user.id, 'name': request.user.profile.name, 'course_title': course.display_name, 'course_id': course.id, 'course_start_date': get_default_time_display(course.start), 'course_end_date': get_default_time_display(course.end), } for update in ordered_updates: update['content'] = info_block.system.replace_urls( update['content']) update[ 'content'] = info_block.system.substitute_keywords_with_data( update['content'], keyword_context) return ordered_updates
def get_course_info_section(request, course, section_key): """ This returns the snippet of html to be rendered on the course info page, given the key for the section. Valid keys: - handouts - guest_handouts - updates - guest_updates """ info_module = get_course_info_section_module(request, course, section_key) html = '' if info_module is not None: try: html = info_module.render(STUDENT_VIEW).content context = { 'username': request.user.username, 'user_id': request.user.id, 'name': request.user.profile.name, 'course_title': course.display_name, 'course_id': course.id, 'course_start_date': get_default_time_display(course.start), 'course_end_date': get_default_time_display(course.end), } html = substitute_keywords_with_data(html, context) except Exception: # pylint: disable=broad-except html = render_to_string('courseware/error-message.html', None) log.exception( u"Error rendering course=%s, section_key=%s", course, section_key ) return html
def _get_course_email_context(course): """ Returns context arguments to apply to all emails, independent of recipient. """ course_id = course.id.to_deprecated_string() course_title = course.display_name course_start_date = get_default_time_display(course.start) course_end_date = get_default_time_display(course.end) course_root = reverse('course_root', kwargs={'course_id': course_id}) course_url = '{}{}'.format( settings.LMS_ROOT_URL, course_root ) image_url = u'{}{}'.format(settings.LMS_ROOT_URL, course_image_url(course)) email_context = { 'course_title': course_title, 'course_root': course_root, 'course_language': course.language, 'course_url': course_url, 'course_image_url': image_url, 'course_start_date': course_start_date, 'course_end_date': course_end_date, 'account_settings_url': '{}{}'.format(settings.LMS_ROOT_URL, reverse('account_settings')), 'email_settings_url': '{}{}'.format(settings.LMS_ROOT_URL, reverse('dashboard')), 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), } return email_context
def _get_course_email_context(course): """ Returns context arguments to apply to all emails, independent of recipient. """ course_id = course.id.to_deprecated_string() course_title = course.display_name course_start_date = get_default_time_display(course.start) course_end_date = get_default_time_display(course.end) course_root = reverse('course_root', kwargs={'course_id': course_id}) course_url = '{}{}'.format(settings.LMS_ROOT_URL, course_root) image_url = u'{}{}'.format(settings.LMS_ROOT_URL, course_image_url(course)) email_context = { 'course_title': course_title, 'course_root': course_root, 'course_language': course.language, 'course_url': course_url, 'course_image_url': image_url, 'course_start_date': course_start_date, 'course_end_date': course_end_date, 'account_settings_url': '{}{}'.format(settings.LMS_ROOT_URL, reverse('account_settings')), 'email_settings_url': '{}{}'.format(settings.LMS_ROOT_URL, reverse('dashboard')), 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), } return email_context
def get_course_info_section(request, user, course, section_key): """ This returns the snippet of html to be rendered on the course info page, given the key for the section. Valid keys: - handouts - guest_handouts - updates - guest_updates """ info_module = get_course_info_section_module(request, user, course, section_key) html = '' if info_module is not None: try: html = info_module.render(STUDENT_VIEW).content context = { 'username': request.user.username, 'user_id': request.user.id, 'name': request.user.profile.name, 'course_title': course.display_name, 'course_id': course.id, 'course_start_date': get_default_time_display(course.start), 'course_end_date': get_default_time_display(course.end), } html = substitute_keywords_with_data(html, context) except Exception: # pylint: disable=broad-except html = render_to_string('courseware/error-message.html', None) log.exception(u"Error rendering course_id=%s, section_key=%s", unicode(course.id), section_key) return html
def get_html(self): if self.system.substitute_keywords_with_data: course = self.descriptor.runtime.modulestore.get_course(self.course_id) user = self.system.get_real_user(self.system.anonymous_student_id) context = { "user_id": user.id if user else None, "name": user.profile.name if user else "", "course_title": course.display_name, "course_id": self.course_id, "course_start_date": get_default_time_display(course.start), "course_end_date": get_default_time_display(course.end), } return self.system.substitute_keywords_with_data(self.data, context) return self.data
def _get_asset_json_for_editorjs(display_name, content_type, date, location, thumbnail_location, locked): ''' Helper method for formatting the asset information to send to client. ''' asset_url = StaticContent.serialize_asset_key_with_slash(location) external_url = settings.LMS_BASE + asset_url return { "success": 1, "file": { 'url': asset_url, 'display_name': display_name, 'content_type': content_type, 'date_added': get_default_time_display(date), 'asset_url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': StaticContent.serialize_asset_key_with_slash(thumbnail_location) if thumbnail_location else None, 'locked': locked, 'id': six.text_type(location) } }
def test_start_date_on_page(self): """ Verify that the course start date is included on the course outline page. """ def _get_release_date(response): """Return the release date from the course page""" parsed_html = lxml.html.fromstring(response.content) return parsed_html.find_class('course-status')[0].find_class('status-release-value')[0].text_content() def _assert_settings_link_present(response): """ Asserts there's a course settings link on the course page by the course release date. """ parsed_html = lxml.html.fromstring(response.content) settings_link = parsed_html.find_class('course-status')[0].find_class('action-edit')[0].find('a') self.assertIsNotNone(settings_link) self.assertEqual(settings_link.get('href'), reverse_course_url('settings_handler', self.course.id)) outline_url = reverse_course_url('course_handler', self.course.id) response = self.client.get(outline_url, {}, HTTP_ACCEPT='text/html') # A course with the default release date should display as "Unscheduled" self.assertEqual(_get_release_date(response), 'Unscheduled') _assert_settings_link_present(response) self.course.start = datetime.datetime(2014, 1, 1, tzinfo=pytz.utc) modulestore().update_item(self.course, ModuleStoreEnum.UserID.test) response = self.client.get(outline_url, {}, HTTP_ACCEPT='text/html') self.assertEqual(_get_release_date(response), get_default_time_display(self.course.start)) _assert_settings_link_present(response)
def _get_release_date(xblock): """ Returns the release date for the xblock, or None if the release date has never been set. """ # Treat DEFAULT_START_DATE as a magic number that means the release date has not been set return get_default_time_display( xblock.start) if xblock.start != DEFAULT_START_DATE else None
def _get_course_email_context(course): """ Returns context arguments to apply to all emails, independent of recipient. """ course_id = course.id.to_deprecated_string() course_title = course.display_name course_end_date = get_default_time_display(course.end) course_url = 'https://{}{}'.format( settings.SITE_NAME, reverse('course_root', kwargs={'course_id': course_id})) image_url = u'https://{}{}'.format(settings.SITE_NAME, course_image_url(course)) email_context = { 'course_title': course_title, 'course_url': course_url, 'course_image_url': image_url, 'course_end_date': course_end_date, 'account_settings_url': 'https://{}{}'.format(settings.SITE_NAME, reverse('account_settings')), 'email_settings_url': 'https://{}{}'.format(settings.SITE_NAME, reverse('dashboard')), 'platform_name': settings.PLATFORM_NAME, } return email_context
def _get_asset_json(display_name, content_type, date, location, thumbnail_location, locked): ''' Helper method for formatting the asset information to send to client. ''' # Colaraz Custom: overriding this to get site base web link of the asset(s) asset_url = StaticContent.serialize_asset_key_with_slash(location) external_url = 'https://' + configuration_helpers.get_value( 'LMS_BASE') + asset_url return { 'display_name': display_name, 'content_type': content_type, 'date_added': get_default_time_display(date), 'url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': StaticContent.serialize_asset_key_with_slash(thumbnail_location) if thumbnail_location else None, 'locked': locked, # needed for Backbone delete/update. 'id': unicode(location) }
def _get_asset_json(display_name, content_type, date, location, thumbnail_location, locked): """ Helper method for formatting the asset information to send to client. """ asset_url = StaticContent.serialize_asset_key_with_slash(location) # eduNEXT 14.07.2016 lms_base = microsite.get_value_for_org(location.org, 'SITE_NAME', settings.LMS_BASE) external_url = lms_base + asset_url return { 'display_name': display_name, 'content_type': content_type, 'date_added': get_default_time_display(date), 'url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': StaticContent.serialize_asset_key_with_slash(thumbnail_location) if thumbnail_location else None, 'locked': locked, # Needed for Backbone delete/update. 'id': unicode(location) }
def _get_asset_json(display_name, content_type, date, location, thumbnail_location, locked): ''' Helper method for formatting the asset information to send to client. ''' asset_url = StaticContent.serialize_asset_key_with_slash(location) external_url = settings.LMS_BASE + asset_url return { 'display_name': display_name, 'content_type': content_type, 'date_added': get_default_time_display(date), 'url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': StaticContent.serialize_asset_key_with_slash(thumbnail_location) if thumbnail_location else None, 'locked': locked, # needed for Backbone delete/update. 'id': unicode(location) }
def _get_asset_json(display_name, date, location, thumbnail_location, locked): """ Helper method for formatting the asset information to send to client. """ asset_url = location.to_deprecated_string() external_url = settings.LMS_BASE + asset_url return { 'display_name': display_name, 'date_added': get_default_time_display(date), 'url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': thumbnail_location.to_deprecated_string() if thumbnail_location is not None else None, 'locked': locked, # Needed for Backbone delete/update. 'id': unicode(location) }
def get_html(self): if self.system.substitute_keywords_with_data: course = self.descriptor.runtime.modulestore.get_course( self.course_id) user = self.system.get_real_user(self.system.anonymous_student_id) context = { 'username': user.username if user else '', 'user_id': user.id if user else None, 'name': user.profile.name if user else '', 'course_title': course.display_name, 'course_id': self.course_id, 'course_start_date': get_default_time_display(course.start), 'course_end_date': get_default_time_display(course.end), } return self.system.substitute_keywords_with_data( self.data, context) return self.data
def _section_course_info(course, access): """ Provide data for the corresponding dashboard section """ course_key = course.id section_data = { 'section_key': 'course_info', 'section_display_name': _('Course Info'), 'access': access, 'course_id': course_key, 'course_display_name': course.display_name, 'has_started': course.has_started(), 'has_ended': course.has_ended(), 'start_date': get_default_time_display(course.start), 'end_date': get_default_time_display(course.end) or _('No end date set'), 'num_sections': len(course.children), 'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': unicode(course_key)}), } if settings.FEATURES.get('DISPLAY_ANALYTICS_ENROLLMENTS'): section_data['enrollment_count'] = CourseEnrollment.objects.enrollment_counts(course_key) if settings.ANALYTICS_DASHBOARD_URL: # dashboard_link is already made safe in _get_dashboard_link dashboard_link = _get_dashboard_link(course_key) # so we can use Text() here so it's not double-escaped and rendering HTML on the front-end message = Text(_("Enrollment data is now available in {dashboard_link}.")).format(dashboard_link=dashboard_link) section_data['enrollment_message'] = message if settings.FEATURES.get('ENABLE_SYSADMIN_DASHBOARD'): section_data['detailed_gitlogs_url'] = reverse('gitlogs_detail', kwargs={'course_id': unicode(course_key)}) try: sorted_cutoffs = sorted(course.grade_cutoffs.items(), key=lambda i: i[1], reverse=True) advance = lambda memo, (letter, score): "{}: {}, ".format(letter, score) + memo section_data['grade_cutoffs'] = reduce(advance, sorted_cutoffs, "")[:-2] except Exception: # pylint: disable=broad-except section_data['grade_cutoffs'] = "Not Available" # section_data['offline_grades'] = offline_grades_available(course_key) try: section_data['course_errors'] = [(escape(a), '') for (a, _unused) in modulestore().get_course_errors(course.id)] except Exception: # pylint: disable=broad-except section_data['course_errors'] = [('Error fetching errors', '')] return section_data
def __init__(self, fake_email, num_sent): super(FakeEmailInfo, self).__init__() self.created = get_default_time_display(fake_email.created) self.number_sent = num_sent fake_email_dict = fake_email.to_dict() self.email = { feature: fake_email_dict[feature] for feature in self.EMAIL_FEATURES }
def extract_email_features(email_task): """ From the given task, extract email content information Expects that the given task has the following attributes: * task_input (dict containing email_id and to_option) * task_output (optional, dict containing total emails sent) With this information, gets the corresponding email object from the bulk emails table, and loads up a dict containing the following: * created, the time the email was sent displayed in default time display * sent_to, the group the email was delivered to * email, dict containing the subject, id, and html_message of an email * number_sent, int number of emails sent If task_input cannot be loaded, then the email cannot be loaded and None is returned for these fields. """ # Load the task input info to get email id try: task_input_information = json.loads(email_task.task_input) except ValueError: log.error("Could not parse task input as valid json; task input: %s", email_task.task_input) return email_error_information() email = CourseEmail.objects.get(id=task_input_information['email_id']) creation_time = get_default_time_display(email.created) email_feature_dict = { 'created': creation_time, 'sent_to': task_input_information['to_option'] } features = ['subject', 'html_message', 'id'] email_info = { feature: unicode(getattr(email, feature)) for feature in features } # Pass along email as an object with the information we desire email_feature_dict['email'] = email_info number_sent = None if hasattr(email_task, 'task_output') and email_task.task_output is not None: try: task_output = json.loads(email_task.task_output) except ValueError: log.error( "Could not parse task output as valid json; task output: %s", email_task.task_output) else: if 'total' in task_output: number_sent = int(task_output['total']) email_feature_dict['number_sent'] = number_sent return email_feature_dict
def _section_course_info(course, access): """ Provide data for the corresponding dashboard section """ course_key = course.id section_data = { "section_key": "course_info", "section_display_name": _("Course Info"), "access": access, "course_id": course_key, "course_display_name": course.display_name, "has_started": course.has_started(), "has_ended": course.has_ended(), "start_date": get_default_time_display(course.start), "end_date": get_default_time_display(course.end) or _("No end date set"), "num_sections": len(course.children), "list_instructor_tasks_url": reverse("list_instructor_tasks", kwargs={"course_id": unicode(course_key)}), } if settings.FEATURES.get("DISPLAY_ANALYTICS_ENROLLMENTS"): section_data["enrollment_count"] = CourseEnrollment.objects.enrollment_counts(course_key) if settings.ANALYTICS_DASHBOARD_URL: dashboard_link = _get_dashboard_link(course_key) message = _("Enrollment data is now available in {dashboard_link}.").format(dashboard_link=dashboard_link) section_data["enrollment_message"] = message if settings.FEATURES.get("ENABLE_SYSADMIN_DASHBOARD"): section_data["detailed_gitlogs_url"] = reverse("gitlogs_detail", kwargs={"course_id": unicode(course_key)}) try: sorted_cutoffs = sorted(course.grade_cutoffs.items(), key=lambda i: i[1], reverse=True) advance = lambda memo, (letter, score): "{}: {}, ".format(letter, score) + memo section_data["grade_cutoffs"] = reduce(advance, sorted_cutoffs, "")[:-2] except Exception: # pylint: disable=broad-except section_data["grade_cutoffs"] = "Not Available" # section_data['offline_grades'] = offline_grades_available(course_key) try: section_data["course_errors"] = [(escape(a), "") for (a, _unused) in modulestore().get_course_errors(course.id)] except Exception: # pylint: disable=broad-except section_data["course_errors"] = [("Error fetching errors", "")] return section_data
def get_plain_html_updates(self, request, course): """ Returns any course updates in an html chunk. Used for older implementations and a few tests that store a single html object representing all the updates. """ info_module = get_course_info_section_module(request, request.user, course, 'updates') info_block = getattr(info_module, '_xmodule', info_module) update_content = info_block.system.replace_urls(info_module.data) if info_module else '' keyword_context = { 'username': request.user.username, 'user_id': request.user.id, 'name': request.user.profile.name, 'course_title': course.display_name, 'course_id': course.id, 'course_start_date': get_default_time_display(course.start), 'course_end_date': get_default_time_display(course.end), } update_content = info_block.system.substitute_keywords_with_data(update_content, keyword_context) return update_content
def _get_release_date(xblock, user=None): """ Returns the release date for the xblock, or None if the release date has never been set. """ # If year of start date is less than 1900 then reset the start date to DEFAULT_START_DATE if xblock.start.year < 1900 and user: xblock.start = DEFAULT_START_DATE xblock = _update_with_callback(xblock, user) # Treat DEFAULT_START_DATE as a magic number that means the release date has not been set return get_default_time_display(xblock.start) if xblock.start != DEFAULT_START_DATE else None
def __init__(self, fake_email, num_sent, num_failed): super(FakeEmailInfo, self).__init__() self.created = get_default_time_display(fake_email.created) number_sent = str(num_sent) + ' sent' if num_failed > 0: number_sent += ', ' + str(num_failed) + " failed" self.number_sent = number_sent fake_email_dict = fake_email.to_dict() self.email = {feature: fake_email_dict[feature] for feature in self.EMAIL_FEATURES} self.requester = u'expected'
def _get_course_email_context(course): """ Returns context arguments to apply to all emails, independent of recipient. """ course_id = course.id.to_deprecated_string() course_title = course.display_name course_url = 'https://{}{}'.format( settings.SITE_NAME, reverse('course_root', kwargs={'course_id': course_id}) ) image_url = 'https://{}{}'.format(settings.SITE_NAME, course_image_url(course)) email_context = { 'course_title': course_title, 'course_url': course_url, 'course_image_url': image_url, 'course_start_date': get_default_time_display(course.start), 'course_end_date': get_default_time_display(course.end), 'account_settings_url': 'https://{}{}'.format(settings.SITE_NAME, reverse('dashboard')), 'platform_name': settings.PLATFORM_NAME, } return email_context
def test_sub_mutiltple_tags(self, test_info): """ Test that subbing works with multiple subtags """ anon_id = '123456789' patched_dict = { '%%USER_ID%%': lambda x, y: anon_id, '%%USER_FULLNAME%%': lambda x, y: self.user.profile.name, '%%COURSE_DISPLAY_NAME': lambda x, y: self.course.display_name, '%%COURSE_END_DATE': lambda x, y: get_default_time_display(self.course.end) } with patch.dict(Ks.KEYWORD_FUNCTION_MAP, patched_dict): result = Ks.substitute_keywords_with_data(test_info['test_string'], self.user.id, self.course.id) self.assertEqual(result, test_info['expected'])
def studio_view(self, context=None): html = self.resource_string("static/html/studio.html") frag = Fragment() file_uploaded_date = get_default_time_display( self.file_uploaded_date) if self.file_uploaded_date else '' context = {'block': self, 'file_uploaded_date': file_uploaded_date} frag.add_content(MakoTemplate(text=html).render_unicode(**context)) frag.add_css(self.resource_string("static/css/scormxblock.css")) frag.add_javascript(self.resource_string("static/js/src/studio.js")) frag.add_javascript_url( self.runtime.local_resource_url(self, 'public/jquery.fileupload.js')) frag.initialize_js('ScormStudioXBlock') return frag
def get_plain_html_updates(self, request, course): """ Returns any course updates in an html chunk. Used for older implementations and a few tests that store a single html object representing all the updates. """ info_module = get_course_info_section_module(request, request.user, course, 'updates') info_block = getattr(info_module, '_xmodule', info_module) update_content = info_block.system.replace_urls( info_module.data) if info_module else '' keyword_context = { 'username': request.user.username, 'user_id': request.user.id, 'name': request.user.profile.name, 'course_title': course.display_name, 'course_id': course.id, 'course_start_date': get_default_time_display(course.start), 'course_end_date': get_default_time_display(course.end), } update_content = info_block.system.substitute_keywords_with_data( update_content, keyword_context) return update_content
def _show_receipt_json(order): """Render the receipt page as JSON. The included information is deliberately minimal: as much as possible, the included information should be common to *all* order items, so the client doesn't need to handle different item types differently. Arguments: request (HttpRequest): The request for the receipt. order (Order): The order model to display. Returns: HttpResponse """ order_info = { 'orderNum': order.id, 'currency': order.currency, 'status': order.status, 'purchase_datetime': get_default_time_display(order.purchase_time) if order.purchase_time else None, 'billed_to': { 'first_name': order.bill_to_first, 'last_name': order.bill_to_last, 'street1': order.bill_to_street1, 'street2': order.bill_to_street2, 'city': order.bill_to_city, 'state': order.bill_to_state, 'postal_code': order.bill_to_postalcode, 'country': order.bill_to_country, }, 'total_cost': order.total_cost, 'items': [{ 'quantity': item.qty, 'unit_cost': item.unit_cost, 'line_cost': item.line_cost, 'line_desc': item.line_desc, 'course_key': six.text_type(item.course_id) } for item in OrderItem.objects.filter(order=order).select_subclasses()] } return JsonResponse(order_info)
def setUp(self): super(KeywordSubTest, self).setUp() self.user = UserFactory.create(email="*****@*****.**", username="******", profile__name="Test User") self.course = CourseFactory.create(org='edx', course='999', display_name='test_course') self.context = { 'user_id': self.user.id, 'course_title': self.course.display_name, 'name': self.user.profile.name, 'course_end_date': get_default_time_display(self.course.end), }
def _get_asset_json(display_name, date, location, thumbnail_location, locked): """ Helper method for formatting the asset information to send to client. """ asset_url = StaticContent.get_url_path_from_location(location) return { 'display_name': display_name, 'date_added': get_default_time_display(date), 'url': asset_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_location is not None else None, 'locked': locked, # Needed for Backbone delete/update. 'id': asset_url }
def __init__(self, fake_email, num_sent, num_failed): super(FakeEmailInfo, self).__init__() self.created = get_default_time_display(fake_email.created) number_sent = str(num_sent) + ' sent' if num_failed > 0: number_sent += ', ' + str(num_failed) + " failed" self.number_sent = number_sent fake_email_dict = fake_email.to_dict() self.email = { feature: fake_email_dict[feature] for feature in self.EMAIL_FEATURES } self.requester = u'expected'
def _get_asset_json(display_name, content_type, date, location, thumbnail_location, locked,name_creater): """ Helper method for formatting the asset information to send to client. """ asset_url = StaticContent.serialize_asset_key_with_slash(location) external_url = 'localhost:8000' + asset_url return { 'display_name': display_name, 'content_type': content_type, 'date_added': get_default_time_display(date), 'url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': StaticContent.serialize_asset_key_with_slash(thumbnail_location) if thumbnail_location else None, 'locked': locked, 'name_creater':name_creater }
def _get_asset_json(display_name, date, location, thumbnail_location, locked): """ Helper method for formatting the asset information to send to client. """ asset_url = _add_slash(location.to_deprecated_string()) external_url = settings.LMS_BASE + asset_url return { 'display_name': display_name, 'date_added': get_default_time_display(date), 'url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': _add_slash(unicode(thumbnail_location)) if thumbnail_location else None, 'locked': locked, # Needed for Backbone delete/update. 'id': unicode(location) }
def extract_email_features(email_task): """ From the given task, extract email content information Expects that the given task has the following attributes: * task_input (dict containing email_id and to_option) * task_output (optional, dict containing total emails sent) With this information, gets the corresponding email object from the bulk emails table, and loads up a dict containing the following: * created, the time the email was sent displayed in default time display * sent_to, the group the email was delivered to * email, dict containing the subject, id, and html_message of an email * number_sent, int number of emails sent If task_input cannot be loaded, then the email cannot be loaded and None is returned for these fields. """ # Load the task input info to get email id try: task_input_information = json.loads(email_task.task_input) except ValueError: log.error("Could not parse task input as valid json; task input: %s", email_task.task_input) return email_error_information() email = CourseEmail.objects.get(id=task_input_information['email_id']) creation_time = get_default_time_display(email.created) email_feature_dict = {'created': creation_time, 'sent_to': task_input_information['to_option']} features = ['subject', 'html_message', 'id'] email_info = {feature: unicode(getattr(email, feature)) for feature in features} # Pass along email as an object with the information we desire email_feature_dict['email'] = email_info number_sent = None if hasattr(email_task, 'task_output') and email_task.task_output is not None: try: task_output = json.loads(email_task.task_output) except ValueError: log.error("Could not parse task output as valid json; task output: %s", email_task.task_output) else: if 'total' in task_output: number_sent = int(task_output['total']) email_feature_dict['number_sent'] = number_sent return email_feature_dict
def _show_receipt_json(order): """Render the receipt page as JSON. The included information is deliberately minimal: as much as possible, the included information should be common to *all* order items, so the client doesn't need to handle different item types differently. Arguments: request (HttpRequest): The request for the receipt. order (Order): The order model to display. Returns: HttpResponse """ order_info = { "orderNum": order.id, "currency": order.currency, "status": order.status, "purchase_datetime": get_default_time_display(order.purchase_time) if order.purchase_time else None, "billed_to": { "first_name": order.bill_to_first, "last_name": order.bill_to_last, "street1": order.bill_to_street1, "street2": order.bill_to_street2, "city": order.bill_to_city, "state": order.bill_to_state, "postal_code": order.bill_to_postalcode, "country": order.bill_to_country, }, "total_cost": order.total_cost, "items": [ { "quantity": item.qty, "unit_cost": item.unit_cost, "line_cost": item.line_cost, "line_desc": item.line_desc, "course_key": unicode(item.course_id), } for item in OrderItem.objects.filter(order=order).select_subclasses() ], } return JsonResponse(order_info)
def _show_receipt_json(order): """Render the receipt page as JSON. The included information is deliberately minimal: as much as possible, the included information should be common to *all* order items, so the client doesn't need to handle different item types differently. Arguments: request (HttpRequest): The request for the receipt. order (Order): The order model to display. Returns: HttpResponse """ order_info = { 'orderNum': order.id, 'currency': order.currency, 'status': order.status, 'purchase_datetime': get_default_time_display(order.purchase_time) if order.purchase_time else None, 'billed_to': { 'first_name': order.bill_to_first, 'last_name': order.bill_to_last, 'street1': order.bill_to_street1, 'street2': order.bill_to_street2, 'city': order.bill_to_city, 'state': order.bill_to_state, 'postal_code': order.bill_to_postalcode, 'country': order.bill_to_country, }, 'total_cost': order.total_cost, 'items': [ { 'quantity': item.qty, 'unit_cost': item.unit_cost, 'line_cost': item.line_cost, 'line_desc': item.line_desc, 'course_key': unicode(item.course_id) } for item in OrderItem.objects.filter(order=order).select_subclasses() ] } return JsonResponse(order_info)
def setUp(self): super(KeywordSubTest, self).setUp(create_user=False) self.user = UserFactory.create( email="*****@*****.**", username="******", profile__name="Test User" ) self.course = CourseFactory.create( org='edx', course='999', display_name='test_course' ) self.context = { 'user_id': self.user.id, 'course_title': self.course.display_name, 'name': self.user.profile.name, 'course_end_date': get_default_time_display(self.course.end), }
def _get_release_date(xblock, user=None): """ Returns the release date for the xblock, or None if the release date has never been set. """ # If year of start date is less than 1900 then reset the start date to DEFAULT_START_DATE reset_to_default = False try: reset_to_default = xblock.start.year < 1900 except ValueError: # For old mongo courses, accessing the start attribute calls `to_json()`, # which raises a `ValueError` for years < 1900. reset_to_default = True if reset_to_default and user: xblock.start = DEFAULT_START_DATE xblock = _update_with_callback(xblock, user) # Treat DEFAULT_START_DATE as a magic number that means the release date has not been set return get_default_time_display(xblock.start) if xblock.start != DEFAULT_START_DATE else None
def _get_course_email_context(course): """ Returns context arguments to apply to all emails, independent of recipient. """ course_id = course.id.to_deprecated_string() course_title = course.display_name course_end_date = get_default_time_display(course.end) scheme = u"https" if settings.HTTPS == "on" else u"http" course_url = '{}://{}{}'.format( scheme, settings.SITE_NAME, reverse('course_root', kwargs={'course_id': course_id}) ) image_url = u'{}://{}{}'.format(scheme, settings.SITE_NAME, course_image_url(course)) email_context = { 'course_title': course_title, 'course_url': course_url, 'course_image_url': image_url, 'course_end_date': course_end_date, 'account_settings_url': '{}://{}{}'.format(scheme, settings.SITE_NAME, reverse('account_settings')), 'platform_name': microsite.get_value('platform_name', settings.PLATFORM_NAME), } return email_context
def default(self, obj): if isinstance(obj, datetime): return get_default_time_display(obj) return json.JSONEncoder.default(self, obj)
def unit_handler(request, usage_key_string): """ The restful handler for unit-specific requests. GET html: return html page for editing a unit json: not currently supported """ if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'): usage_key = UsageKey.from_string(usage_key_string) try: course, item, lms_link = _get_item_in_course(request, usage_key) except ItemNotFoundError: return HttpResponseBadRequest() component_templates = _get_component_templates(course) xblocks = item.get_children() # TODO (cpennington): If we share units between courses, # this will need to change to check permissions correctly so as # to pick the correct parent subsection containing_subsection = get_parent_xblock(item) containing_section = get_parent_xblock(containing_subsection) # cdodge hack. We're having trouble previewing drafts via jump_to redirect # so let's generate the link url here # need to figure out where this item is in the list of children as the # preview will need this index = 1 for child in containing_subsection.get_children(): if child.location == item.location: break index = index + 1 preview_lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE') preview_lms_link = ( u'//{preview_lms_base}/courses/{org}/{course}/{course_name}/courseware/{section}/{subsection}/{index}' ).format( preview_lms_base=preview_lms_base, lms_base=settings.LMS_BASE, org=course.location.org, course=course.location.course, course_name=course.location.name, section=containing_section.location.name, subsection=containing_subsection.location.name, index=index ) return render_to_response('unit.html', { 'context_course': course, 'unit': item, 'unit_usage_key': usage_key, 'child_usage_keys': [block.scope_ids.usage_id for block in xblocks], 'component_templates': json.dumps(component_templates), 'draft_preview_link': preview_lms_link, 'published_preview_link': lms_link, 'subsection': containing_subsection, 'release_date': ( get_default_time_display(containing_subsection.start) if containing_subsection.start is not None else None ), 'section': containing_section, 'new_unit_category': 'vertical', 'unit_state': compute_publish_state(item), 'published_date': ( get_default_time_display(item.published_date) if item.published_date is not None else None ), }) else: return HttpResponseBadRequest("Only supports html requests")
def unit_handler(request, tag=None, package_id=None, branch=None, version_guid=None, block=None): """ The restful handler for unit-specific requests. GET html: return html page for editing a unit json: not currently supported """ if "text/html" in request.META.get("HTTP_ACCEPT", "text/html"): locator = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block) try: old_location, course, item, lms_link = _get_item_in_course(request, locator) except ItemNotFoundError: return HttpResponseBadRequest() component_templates = defaultdict(list) for category in COMPONENT_TYPES: component_class = _load_mixed_class(category) # add the default template # TODO: Once mixins are defined per-application, rather than per-runtime, # this should use a cms mixed-in class. (cpennington) if hasattr(component_class, "display_name"): display_name = component_class.display_name.default or "Blank" else: display_name = "Blank" component_templates[category].append( ( display_name, category, False, # No defaults have markdown (hardcoded current default) None, # no boilerplate for overrides ) ) # add boilerplates if hasattr(component_class, "templates"): for template in component_class.templates(): filter_templates = getattr(component_class, "filter_templates", None) if not filter_templates or filter_templates(template, course): component_templates[category].append( ( template["metadata"].get("display_name"), category, template["metadata"].get("markdown") is not None, template.get("template_id"), ) ) # Check if there are any advanced modules specified in the course policy. # These modules should be specified as a list of strings, where the strings # are the names of the modules in ADVANCED_COMPONENT_TYPES that should be # enabled for the course. course_advanced_keys = course.advanced_modules # Set component types according to course policy file if isinstance(course_advanced_keys, list): for category in course_advanced_keys: if category in ADVANCED_COMPONENT_TYPES: # Do I need to allow for boilerplates or just defaults on the # class? i.e., can an advanced have more than one entry in the # menu? one for default and others for prefilled boilerplates? try: component_class = _load_mixed_class(category) component_templates["advanced"].append( ( component_class.display_name.default or category, category, False, None, # don't override default data ) ) except PluginMissingError: # dhm: I got this once but it can happen any time the # course author configures an advanced component which does # not exist on the server. This code here merely # prevents any authors from trying to instantiate the # non-existent component type by not showing it in the menu pass else: log.error("Improper format for course advanced keys! %s", course_advanced_keys) xblocks = item.get_children() locators = [ loc_mapper().translate_location(course.location.course_id, xblock.location, False, True) for xblock in xblocks ] # TODO (cpennington): If we share units between courses, # this will need to change to check permissions correctly so as # to pick the correct parent subsection containing_subsection = get_parent_xblock(item) containing_section = get_parent_xblock(containing_subsection) # cdodge hack. We're having trouble previewing drafts via jump_to redirect # so let's generate the link url here # need to figure out where this item is in the list of children as the # preview will need this index = 1 for child in containing_subsection.get_children(): if child.location == item.location: break index = index + 1 preview_lms_base = settings.FEATURES.get("PREVIEW_LMS_BASE") preview_lms_link = ( u"//{preview_lms_base}/courses/{org}/{course}/{course_name}/courseware/{section}/{subsection}/{index}" ).format( preview_lms_base=preview_lms_base, lms_base=settings.LMS_BASE, org=course.location.org, course=course.location.course, course_name=course.location.name, section=containing_section.location.name, subsection=containing_subsection.location.name, index=index, ) return render_to_response( "unit.html", { "context_course": course, "unit": item, "unit_locator": locator, "locators": locators, "component_templates": component_templates, "draft_preview_link": preview_lms_link, "published_preview_link": lms_link, "subsection": containing_subsection, "release_date": ( get_default_time_display(containing_subsection.start) if containing_subsection.start is not None else None ), "section": containing_section, "new_unit_category": "vertical", "unit_state": compute_unit_state(item), "published_date": ( get_default_time_display(item.published_date) if item.published_date is not None else None ), }, ) else: return HttpResponseBadRequest("Only supports html requests")
def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=False, include_child_info=False, course_outline=False, include_children_predicate=NEVER, parent_xblock=None, graders=None): """ Creates the information needed for client-side XBlockInfo. If data or metadata are not specified, their information will not be added (regardless of whether or not the xblock actually has data or metadata). There are three optional boolean parameters: include_ancestor_info - if true, ancestor info is added to the response include_child_info - if true, direct child info is included in the response course_outline - if true, the xblock is being rendered on behalf of the course outline. There are certain expensive computations that do not need to be included in this case. In addition, an optional include_children_predicate argument can be provided to define whether or not a particular xblock should have its children included. """ def safe_get_username(user_id): """ Guard against bad user_ids, like the infamous "**replace_user**". Note that this will ignore our special known IDs (ModuleStoreEnum.UserID). We should consider adding special handling for those values. :param user_id: the user id to get the username of :return: username, or None if the user does not exist or user_id is None """ if user_id: try: return User.objects.get(id=user_id).username except: # pylint: disable=bare-except pass return None is_xblock_unit = is_unit(xblock, parent_xblock) # this should not be calculated for Sections and Subsections on Unit page has_changes = modulestore().has_changes(xblock) if ( is_xblock_unit or course_outline) else None if graders is None: graders = CourseGradingModel.fetch(xblock.location.course_key).graders # Compute the child info first so it can be included in aggregate information for the parent should_visit_children = include_child_info and ( course_outline and not is_xblock_unit or not course_outline) if should_visit_children and xblock.has_children: child_info = _create_xblock_child_info( xblock, course_outline, graders, include_children_predicate=include_children_predicate, ) else: child_info = None # Treat DEFAULT_START_DATE as a magic number that means the release date has not been set release_date = get_default_time_display( xblock.start) if xblock.start != DEFAULT_START_DATE else None if xblock.category != 'course': visibility_state = _compute_visibility_state( xblock, child_info, is_xblock_unit and has_changes) else: visibility_state = None published = modulestore().has_published_version(xblock) xblock_info = { "id": unicode(xblock.location), "display_name": xblock.display_name_with_default, "category": xblock.category, "edited_on": get_default_time_display(xblock.subtree_edited_on) if xblock.subtree_edited_on else None, "published": published, "published_on": get_default_time_display(xblock.published_on) if xblock.published_on else None, "studio_url": xblock_studio_url(xblock, parent_xblock), "released_to_students": datetime.now(UTC) > xblock.start, "release_date": release_date, "visibility_state": visibility_state, "lti_enabled": xblock.fields['lti_enabled'].is_set_on(xblock), "lti_url": "", "lti_key": "", "lti_secret": "", "has_explicit_staff_lock": xblock.fields['visible_to_staff_only'].is_set_on(xblock), "start": xblock.fields['start'].to_json(xblock.start), "graded": xblock.graded, "due_date": get_default_time_display(xblock.due), "due": xblock.fields['due'].to_json(xblock.due), "format": xblock.format, "course_graders": json.dumps([grader.get('type') for grader in graders]), "has_changes": has_changes, } if settings.FEATURES.get('ENABLE_AS_LTI_TOOL_PROVIDER', False): store = modulestore() course = store.get_course(xblock.location.course_key) if course.lti_enabled: course_id = xblock.location.course_key usr = int(xblock.subtree_edited_by) existing_components = LTIComponent.objects.filter( course_id=course_id, module_id=xblock.location, user_id=usr) if xblock.fields['lti_enabled'].is_set_on(xblock): if len(existing_components) == 0: key = ''.join( random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(8)) secret = ''.join( random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(16)) lti_component = LTIComponent(user_id=usr, course_id=course_id, module_id=str( xblock.location), key=key, secret=secret) lti_component.save() else: key = existing_components[0].key secret = existing_components[0].secret xblock_info["lti_url"] = str(xblock.location) xblock_info["lti_key"] = key xblock_info["lti_secret"] = secret else: if len(existing_components) > 0: for existing_component in existing_components: existing_components.delete() else: xblock_info["lti_url"] = "disabled" xblock_info["lti_key"] = "disabled" xblock_info["lti_secret"] = "disabled" else: xblock_info["lti_url"] = "disabled" xblock_info["lti_key"] = "disabled" xblock_info["lti_secret"] = "disabled" if data is not None: xblock_info["data"] = data if metadata is not None: xblock_info["metadata"] = metadata if include_ancestor_info: xblock_info['ancestor_info'] = _create_xblock_ancestor_info( xblock, course_outline) if child_info: xblock_info['child_info'] = child_info if visibility_state == VisibilityState.staff_only: xblock_info["ancestor_has_staff_lock"] = ancestor_has_staff_lock( xblock, parent_xblock) else: xblock_info["ancestor_has_staff_lock"] = False # Currently, 'edited_by', 'published_by', and 'release_date_from' are only used by the # container page when rendering a unit. Since they are expensive to compute, only include them for units # that are not being rendered on the course outline. if is_xblock_unit and not course_outline: xblock_info["edited_by"] = safe_get_username(xblock.subtree_edited_by) xblock_info["published_by"] = safe_get_username(xblock.published_by) xblock_info[ "currently_visible_to_students"] = is_currently_visible_to_students( xblock) if release_date: xblock_info["release_date_from"] = _get_release_date_from(xblock) if visibility_state == VisibilityState.staff_only: xblock_info["staff_lock_from"] = _get_staff_lock_from(xblock) else: xblock_info["staff_lock_from"] = None if course_outline: if xblock_info["has_explicit_staff_lock"]: xblock_info["staff_only_message"] = True elif child_info and child_info["children"]: xblock_info["staff_only_message"] = all([ child["staff_only_message"] for child in child_info["children"] ]) else: xblock_info["staff_only_message"] = False return xblock_info
def test_get_default_time_display_no_tzname(): assert_equals("", get_default_time_display(None)) test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=NamelessTZ()) assert_equals("Mar 12, 1992 at 15:03-0300", get_default_time_display(test_time))
def test_get_dflt_time_disp_notz(): test_time = datetime(1992, 3, 12, 15, 3, 30) assert_equals("Mar 12, 1992 at 15:03 UTC", get_default_time_display(test_time))
def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=False, include_child_info=False, course_outline=False, include_children_predicate=NEVER, parent_xblock=None, graders=None, user=None): """ Creates the information needed for client-side XBlockInfo. If data or metadata are not specified, their information will not be added (regardless of whether or not the xblock actually has data or metadata). There are three optional boolean parameters: include_ancestor_info - if true, ancestor info is added to the response include_child_info - if true, direct child info is included in the response course_outline - if true, the xblock is being rendered on behalf of the course outline. There are certain expensive computations that do not need to be included in this case. In addition, an optional include_children_predicate argument can be provided to define whether or not a particular xblock should have its children included. """ is_library_block = isinstance(xblock.location, LibraryUsageLocator) is_xblock_unit = is_unit(xblock, parent_xblock) # this should not be calculated for Sections and Subsections on Unit page or for library blocks has_changes = None if (is_xblock_unit or course_outline) and not is_library_block: has_changes = modulestore().has_changes(xblock) if graders is None: if not is_library_block: graders = CourseGradingModel.fetch(xblock.location.course_key).graders else: graders = [] # Filter the graders data as needed graders = _filter_entrance_exam_grader(graders) # Compute the child info first so it can be included in aggregate information for the parent should_visit_children = include_child_info and (course_outline and not is_xblock_unit or not course_outline) if should_visit_children and xblock.has_children: child_info = _create_xblock_child_info( xblock, course_outline, graders, include_children_predicate=include_children_predicate, user=user ) else: child_info = None if xblock.category != 'course': visibility_state = _compute_visibility_state(xblock, child_info, is_xblock_unit and has_changes) else: visibility_state = None published = modulestore().has_published_version(xblock) if not is_library_block else None # defining the default value 'True' for delete, drag and add new child actions in xblock_actions for each xblock. xblock_actions = {'deletable': True, 'draggable': True, 'childAddable': True} explanatory_message = None # is_entrance_exam is inherited metadata. if xblock.category == 'chapter' and getattr(xblock, "is_entrance_exam", None): # Entrance exam section should not be deletable, draggable and not have 'New Subsection' button. xblock_actions['deletable'] = xblock_actions['childAddable'] = xblock_actions['draggable'] = False if parent_xblock is None: parent_xblock = get_parent_xblock(xblock) # Translators: The {pct_sign} here represents the percent sign, i.e., '%' # in many languages. This is used to avoid Transifex's misinterpreting of # '% o'. The percent sign is also translatable as a standalone string. explanatory_message = _('Students must score {score}{pct_sign} or higher to access course materials.').format( score=int(parent_xblock.entrance_exam_minimum_score_pct * 100), # Translators: This is the percent sign. It will be used to represent # a percent value out of 100, e.g. "58%" means "58/100". pct_sign=_('%')) xblock_info = { "id": unicode(xblock.location), "display_name": xblock.display_name_with_default, "category": xblock.category, "edited_on": get_default_time_display(xblock.subtree_edited_on) if xblock.subtree_edited_on else None, "published": published, "published_on": get_default_time_display(xblock.published_on) if published and xblock.published_on else None, "studio_url": xblock_studio_url(xblock, parent_xblock), "released_to_students": datetime.now(UTC) > xblock.start, "release_date": _get_release_date(xblock, user), "visibility_state": visibility_state, "has_explicit_staff_lock": xblock.fields['visible_to_staff_only'].is_set_on(xblock), "start": xblock.fields['start'].to_json(xblock.start), "graded": xblock.graded, "due_date": get_default_time_display(xblock.due), "due": xblock.fields['due'].to_json(xblock.due), "format": xblock.format, "course_graders": json.dumps([grader.get('type') for grader in graders]), "has_changes": has_changes, "actions": xblock_actions, "explanatory_message": explanatory_message } # Entrance exam subsection should be hidden. in_entrance_exam is inherited metadata, all children will have it. if xblock.category == 'sequential' and getattr(xblock, "in_entrance_exam", False): xblock_info["is_header_visible"] = False if data is not None: xblock_info["data"] = data if metadata is not None: xblock_info["metadata"] = metadata if include_ancestor_info: xblock_info['ancestor_info'] = _create_xblock_ancestor_info(xblock, course_outline) if child_info: xblock_info['child_info'] = child_info if visibility_state == VisibilityState.staff_only: xblock_info["ancestor_has_staff_lock"] = ancestor_has_staff_lock(xblock, parent_xblock) else: xblock_info["ancestor_has_staff_lock"] = False if course_outline: if xblock_info["has_explicit_staff_lock"]: xblock_info["staff_only_message"] = True elif child_info and child_info["children"]: xblock_info["staff_only_message"] = all([child["staff_only_message"] for child in child_info["children"]]) else: xblock_info["staff_only_message"] = False return xblock_info
def _compose_message_reverification_email( course_key, user_id, related_assessment_location, status, request ): # pylint: disable=invalid-name """ Compose subject and message for photo reverification email. Args: course_key(CourseKey): CourseKey object user_id(str): User Id related_assessment_location(str): Location of reverification XBlock photo_verification(QuerySet): Queryset of SoftwareSecure objects status(str): Approval status is_secure(Bool): Is running on secure protocol or not Returns: None if any error occurred else Tuple of subject and message strings """ try: usage_key = UsageKey.from_string(related_assessment_location) reverification_block = modulestore().get_item(usage_key) course = modulestore().get_course(course_key) redirect_url = get_redirect_url(course_key, usage_key.replace(course_key=course_key)) subject = "Re-verification Status" context = { "status": status, "course_name": course.display_name_with_default, "assessment": reverification_block.related_assessment } # Allowed attempts is 1 if not set on verification block allowed_attempts = reverification_block.attempts + 1 used_attempts = VerificationStatus.get_user_attempts(user_id, course_key, related_assessment_location) left_attempts = allowed_attempts - used_attempts is_attempt_allowed = left_attempts > 0 verification_open = True if reverification_block.due: verification_open = timezone.now() <= reverification_block.due context["left_attempts"] = left_attempts context["is_attempt_allowed"] = is_attempt_allowed context["verification_open"] = verification_open context["due_date"] = get_default_time_display(reverification_block.due) context['platform_name'] = settings.PLATFORM_NAME context["used_attempts"] = used_attempts context["allowed_attempts"] = allowed_attempts context["support_link"] = microsite.get_value('email_from_address', settings.CONTACT_EMAIL) re_verification_link = reverse( 'verify_student_incourse_reverify', args=( unicode(course_key), related_assessment_location ) ) context["course_link"] = request.build_absolute_uri(redirect_url) context["reverify_link"] = request.build_absolute_uri(re_verification_link) message = render_to_string('emails/reverification_processed.txt', context) log.info( "Sending email to User_Id=%s. Attempts left for this user are %s. " "Allowed attempts %s. " "Due Date %s", str(user_id), left_attempts, allowed_attempts, str(reverification_block.due) ) return subject, message # Catch all exception to avoid raising back to view except: # pylint: disable=bare-except log.exception("The email for re-verification sending failed for user_id %s", user_id)
def course_end_date_sub(user, course): """ Returns the course end date in the default display """ return get_default_time_display(course.end)
def __init__(self, fake_email, num_sent): super(FakeEmailInfo, self).__init__() self.created = get_default_time_display(fake_email.created) self.number_sent = num_sent fake_email_dict = fake_email.to_dict() self.email = {feature: fake_email_dict[feature] for feature in self.EMAIL_FEATURES}
def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=False, include_child_info=False, course_outline=False, include_children_predicate=NEVER, parent_xblock=None, graders=None): """ Creates the information needed for client-side XBlockInfo. If data or metadata are not specified, their information will not be added (regardless of whether or not the xblock actually has data or metadata). There are three optional boolean parameters: include_ancestor_info - if true, ancestor info is added to the response include_child_info - if true, direct child info is included in the response course_outline - if true, the xblock is being rendered on behalf of the course outline. There are certain expensive computations that do not need to be included in this case. In addition, an optional include_children_predicate argument can be provided to define whether or not a particular xblock should have its children included. """ is_library_block = isinstance(xblock.location, LibraryUsageLocator) is_xblock_unit = is_unit(xblock, parent_xblock) # this should not be calculated for Sections and Subsections on Unit page or for library blocks has_changes = None if (is_xblock_unit or course_outline) and not is_library_block: has_changes = modulestore().has_changes(xblock) if graders is None: if not is_library_block: graders = CourseGradingModel.fetch( xblock.location.course_key).graders else: graders = [] # Filter the graders data as needed graders = _filter_entrance_exam_grader(graders) # Compute the child info first so it can be included in aggregate information for the parent should_visit_children = include_child_info and ( course_outline and not is_xblock_unit or not course_outline) if should_visit_children and xblock.has_children: child_info = _create_xblock_child_info( xblock, course_outline, graders, include_children_predicate=include_children_predicate, ) else: child_info = None if xblock.category != 'course': visibility_state = _compute_visibility_state( xblock, child_info, is_xblock_unit and has_changes) else: visibility_state = None published = modulestore().has_published_version( xblock) if not is_library_block else None # defining the default value 'True' for delete, drag and add new child actions in xblock_actions for each xblock. xblock_actions = { 'deletable': True, 'draggable': True, 'childAddable': True } explanatory_message = None # is_entrance_exam is inherited metadata. if xblock.category == 'chapter' and getattr(xblock, "is_entrance_exam", None): # Entrance exam section should not be deletable, draggable and not have 'New Subsection' button. xblock_actions['deletable'] = xblock_actions[ 'childAddable'] = xblock_actions['draggable'] = False if parent_xblock is None: parent_xblock = get_parent_xblock(xblock) explanatory_message = _( 'Students must score {score}% or higher to access course materials.' ).format(score=int(parent_xblock.entrance_exam_minimum_score_pct * 100)) xblock_info = { "id": unicode(xblock.location), "display_name": xblock.display_name_with_default, "category": xblock.category, "edited_on": get_default_time_display(xblock.subtree_edited_on) if xblock.subtree_edited_on else None, "published": published, "published_on": get_default_time_display(xblock.published_on) if published and xblock.published_on else None, "studio_url": xblock_studio_url(xblock, parent_xblock), "released_to_students": datetime.now(UTC) > xblock.start, "release_date": _get_release_date(xblock), "visibility_state": visibility_state, "has_explicit_staff_lock": xblock.fields['visible_to_staff_only'].is_set_on(xblock), "start": xblock.fields['start'].to_json(xblock.start), "graded": xblock.graded, "due_date": get_default_time_display(xblock.due), "due": xblock.fields['due'].to_json(xblock.due), "format": xblock.format, "course_graders": json.dumps([grader.get('type') for grader in graders]), "has_changes": has_changes, "actions": xblock_actions, "explanatory_message": explanatory_message } # Entrance exam subsection should be hidden. in_entrance_exam is inherited metadata, all children will have it. if xblock.category == 'sequential' and getattr(xblock, "in_entrance_exam", False): xblock_info["is_header_visible"] = False if data is not None: xblock_info["data"] = data if metadata is not None: xblock_info["metadata"] = metadata if include_ancestor_info: xblock_info['ancestor_info'] = _create_xblock_ancestor_info( xblock, course_outline) if child_info: xblock_info['child_info'] = child_info if visibility_state == VisibilityState.staff_only: xblock_info["ancestor_has_staff_lock"] = ancestor_has_staff_lock( xblock, parent_xblock) else: xblock_info["ancestor_has_staff_lock"] = False if course_outline: if xblock_info["has_explicit_staff_lock"]: xblock_info["staff_only_message"] = True elif child_info and child_info["children"]: xblock_info["staff_only_message"] = all([ child["staff_only_message"] for child in child_info["children"] ]) else: xblock_info["staff_only_message"] = False return xblock_info
def extract_email_features(email_task): """ From the given task, extract email content information Expects that the given task has the following attributes: * task_input (dict containing email_id) * task_output (optional, dict containing total emails sent) * requester, the user who executed the task With this information, gets the corresponding email object from the bulk emails table, and loads up a dict containing the following: * created, the time the email was sent displayed in default time display * sent_to, the group the email was delivered to * email, dict containing the subject, id, and html_message of an email * number_sent, int number of emails sent * requester, the user who sent the emails If task_input cannot be loaded, then the email cannot be loaded and None is returned for these fields. """ # Load the task input info to get email id try: task_input_information = json.loads(email_task.task_input) except ValueError: log.error("Could not parse task input as valid json; task input: %s", email_task.task_input) return email_error_information() email = CourseEmail.objects.get(id=task_input_information['email_id']) email_feature_dict = { 'created': get_default_time_display(email.created), 'sent_to': [target.long_display() for target in email.targets.all()], 'requester': str(email_task.requester), } features = ['subject', 'html_message', 'id'] email_info = { feature: unicode(getattr(email, feature)) for feature in features } # Pass along email as an object with the information we desire email_feature_dict['email'] = email_info # Translators: number sent refers to the number of emails sent number_sent = _('0 sent') if hasattr(email_task, 'task_output') and email_task.task_output is not None: try: task_output = json.loads(email_task.task_output) except ValueError: log.error( "Could not parse task output as valid json; task output: %s", email_task.task_output) else: if 'succeeded' in task_output and task_output['succeeded'] > 0: num_emails = task_output['succeeded'] number_sent = ungettext( "{num_emails} sent", "{num_emails} sent", num_emails).format(num_emails=num_emails) if 'failed' in task_output and task_output['failed'] > 0: num_emails = task_output['failed'] number_sent += ", " number_sent += ungettext( "{num_emails} failed", "{num_emails} failed", num_emails).format(num_emails=num_emails) email_feature_dict['number_sent'] = number_sent return email_feature_dict
def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=False, include_child_info=False, course_outline=False, include_children_predicate=NEVER, parent_xblock=None, graders=None): """ Creates the information needed for client-side XBlockInfo. If data or metadata are not specified, their information will not be added (regardless of whether or not the xblock actually has data or metadata). There are three optional boolean parameters: include_ancestor_info - if true, ancestor info is added to the response include_child_info - if true, direct child info is included in the response course_outline - if true, the xblock is being rendered on behalf of the course outline. There are certain expensive computations that do not need to be included in this case. In addition, an optional include_children_predicate argument can be provided to define whether or not a particular xblock should have its children included. """ def safe_get_username(user_id): """ Guard against bad user_ids, like the infamous "**replace_user**". Note that this will ignore our special known IDs (ModuleStoreEnum.UserID). We should consider adding special handling for those values. :param user_id: the user id to get the username of :return: username, or None if the user does not exist or user_id is None """ if user_id: try: return User.objects.get(id=user_id).username except: # pylint: disable=bare-except pass return None is_xblock_unit = is_unit(xblock, parent_xblock) has_changes = modulestore().has_changes(xblock) if graders is None: graders = CourseGradingModel.fetch(xblock.location.course_key).graders # Compute the child info first so it can be included in aggregate information for the parent should_visit_children = include_child_info and (course_outline and not is_xblock_unit or not course_outline) if should_visit_children and xblock.has_children: child_info = _create_xblock_child_info( xblock, course_outline, graders, include_children_predicate=include_children_predicate, ) else: child_info = None # Treat DEFAULT_START_DATE as a magic number that means the release date has not been set release_date = get_default_time_display(xblock.start) if xblock.start != DEFAULT_START_DATE else None if xblock.category != 'course': visibility_state = _compute_visibility_state(xblock, child_info, is_xblock_unit and has_changes) else: visibility_state = None published = modulestore().has_published_version(xblock) xblock_info = { "id": unicode(xblock.location), "display_name": xblock.display_name_with_default, "category": xblock.category, "edited_on": get_default_time_display(xblock.subtree_edited_on) if xblock.subtree_edited_on else None, "published": published, "published_on": get_default_time_display(xblock.published_date) if xblock.published_date else None, "studio_url": xblock_studio_url(xblock, parent_xblock), "released_to_students": datetime.now(UTC) > xblock.start, "release_date": release_date, "visibility_state": visibility_state, "has_explicit_staff_lock": xblock.fields['visible_to_staff_only'].is_set_on(xblock), "start": xblock.fields['start'].to_json(xblock.start), "graded": xblock.graded, "due_date": get_default_time_display(xblock.due), "due": xblock.fields['due'].to_json(xblock.due), "format": xblock.format, "course_graders": json.dumps([grader.get('type') for grader in graders]), "has_changes": has_changes, } if data is not None: xblock_info["data"] = data if metadata is not None: xblock_info["metadata"] = metadata if include_ancestor_info: xblock_info['ancestor_info'] = _create_xblock_ancestor_info(xblock, course_outline) if child_info: xblock_info['child_info'] = child_info if visibility_state == VisibilityState.staff_only: xblock_info["ancestor_has_staff_lock"] = ancestor_has_staff_lock(xblock, parent_xblock) else: xblock_info["ancestor_has_staff_lock"] = False # Currently, 'edited_by', 'published_by', and 'release_date_from' are only used by the # container page when rendering a unit. Since they are expensive to compute, only include them for units # that are not being rendered on the course outline. if is_xblock_unit and not course_outline: xblock_info["edited_by"] = safe_get_username(xblock.subtree_edited_by) xblock_info["published_by"] = safe_get_username(xblock.published_by) xblock_info["currently_visible_to_students"] = is_currently_visible_to_students(xblock) if release_date: xblock_info["release_date_from"] = _get_release_date_from(xblock) if visibility_state == VisibilityState.staff_only: xblock_info["staff_lock_from"] = _get_staff_lock_from(xblock) else: xblock_info["staff_lock_from"] = None if course_outline: if xblock_info["has_explicit_staff_lock"]: xblock_info["staff_only_message"] = True elif child_info and child_info["children"]: xblock_info["staff_only_message"] = all([child["staff_only_message"] for child in child_info["children"]]) else: xblock_info["staff_only_message"] = False return xblock_info
def get( self, request, course_id, always_show_payment=False, current_step=None, message=FIRST_TIME_VERIFY_MSG ): """Render the pay/verify requirements page. Arguments: request (HttpRequest): The request object. course_id (unicode): The ID of the course the user is trying to enroll in. Keyword Arguments: always_show_payment (bool): If True, show the payment steps even if the user has already paid. This is useful for users returning to the flow after paying. current_step (string): The current step in the flow. message (string): The messaging to display. Returns: HttpResponse Raises: Http404: The course does not exist or does not have a verified mode. """ # Parse the course key # The URL regex should guarantee that the key format is valid. course_key = CourseKey.from_string(course_id) course = modulestore().get_course(course_key) # Verify that the course exists and has a verified mode if course is None: log.warn(u"No course specified for verification flow request.") raise Http404 # Check whether the user has access to this course # based on country access rules. redirect_url = embargo_api.redirect_if_blocked( course_key, user=request.user, ip_address=get_ip(request), url=request.path ) if redirect_url: return redirect(redirect_url) expired_verified_course_mode, unexpired_paid_course_mode = self._get_expired_verified_and_paid_mode(course_key) # Check that the course has an unexpired paid mode if unexpired_paid_course_mode is not None: if CourseMode.is_verified_mode(unexpired_paid_course_mode): log.info( u"Entering verified workflow for user '%s', course '%s', with current step '%s'.", request.user.id, course_id, current_step ) elif expired_verified_course_mode is not None: # Check if there is an *expired* verified course mode; # if so, we should show a message explaining that the verification # deadline has passed. log.info(u"Verification deadline for '%s' has passed.", course_id) context = { 'course': course, 'deadline': ( get_default_time_display(expired_verified_course_mode.expiration_datetime) if expired_verified_course_mode.expiration_datetime else "" ) } return render_to_response("verify_student/missed_verification_deadline.html", context) else: # Otherwise, there has never been a verified/paid mode, # so return a page not found response. log.warn( u"No paid/verified course mode found for course '%s' for verification/payment flow request", course_id ) raise Http404 # Check whether the user has verified, paid, and enrolled. # A user is considered "paid" if he or she has an enrollment # with a paid course mode (such as "verified"). # For this reason, every paid user is enrolled, but not # every enrolled user is paid. # If the course mode is not verified(i.e only paid) then already_verified is always True already_verified = self._check_already_verified(request.user) \ if CourseMode.is_verified_mode(unexpired_paid_course_mode) else True already_paid, is_enrolled = self._check_enrollment(request.user, course_key) # Redirect the user to a more appropriate page if the # messaging won't make sense based on the user's # enrollment / payment / verification status. redirect_response = self._redirect_if_necessary( message, already_verified, already_paid, is_enrolled, course_key ) if redirect_response is not None: return redirect_response display_steps = self._display_steps( always_show_payment, already_verified, already_paid, unexpired_paid_course_mode ) requirements = self._requirements(display_steps, request.user.is_active) if current_step is None: current_step = display_steps[0]['name'] # Allow the caller to skip the first page # This is useful if we want the user to be able to # use the "back" button to return to the previous step. # This parameter should only work for known skip-able steps if request.GET.get('skip-first-step') and current_step in self.SKIP_STEPS: display_step_names = [step['name'] for step in display_steps] current_step_idx = display_step_names.index(current_step) if (current_step_idx + 1) < len(display_steps): current_step = display_steps[current_step_idx + 1]['name'] courseware_url = "" if not course.start or course.start < datetime.datetime.today().replace(tzinfo=UTC): courseware_url = reverse( 'course_root', kwargs={'course_id': unicode(course_key)} ) full_name = ( request.user.profile.name if request.user.profile.name else "" ) # If the user set a contribution amount on another page, # use that amount to pre-fill the price selection form. contribution_amount = request.session.get( 'donation_for_course', {} ).get(unicode(course_key), '') # Remember whether the user is upgrading # so we can fire an analytics event upon payment. request.session['attempting_upgrade'] = (message == self.UPGRADE_MSG) # Determine the photo verification status verification_good_until = self._verification_valid_until(request.user) # Render the top-level page context = { 'contribution_amount': contribution_amount, 'course': course, 'course_key': unicode(course_key), 'course_mode': unexpired_paid_course_mode, 'courseware_url': courseware_url, 'current_step': current_step, 'disable_courseware_js': True, 'display_steps': display_steps, 'is_active': json.dumps(request.user.is_active), 'message_key': message, 'platform_name': settings.PLATFORM_NAME, 'purchase_endpoint': get_purchase_endpoint(), 'requirements': requirements, 'user_full_name': full_name, 'verification_deadline': ( get_default_time_display(unexpired_paid_course_mode.expiration_datetime) if unexpired_paid_course_mode.expiration_datetime else "" ), 'already_verified': already_verified, 'verification_good_until': verification_good_until, } return render_to_response("verify_student/pay_and_verify.html", context)