def _generate_not_activated_message(user): """ Generates the message displayed on the sign-in screen when a learner attempts to access the system with an inactive account. """ support_url = configuration_helpers.get_value( 'SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK ) platform_name = configuration_helpers.get_value( 'PLATFORM_NAME', settings.PLATFORM_NAME ) not_activated_message = Text(_( u'In order to sign in, you need to activate your account.{blank_lines}' u'We just sent an activation link to {email_strong}. If ' u'you do not receive an email, check your spam folders or ' u'{link_start}contact {platform_name} Support{link_end}.' )).format( platform_name=platform_name, blank_lines=HTML('<br/><br/>'), email_strong=HTML('<strong>{email}</strong>').format(email=user.email), link_start=HTML(u'<a href="{support_url}">').format( support_url=support_url, ), link_end=HTML("</a>"), ) return not_activated_message
def view_student_survey(user, survey_name, course=None, redirect_url=None, is_required=False, skip_redirect_url=None): """ Shared utility method to render a survey form NOTE: This method is shared between the Survey and Courseware Djangoapps """ redirect_url = redirect_url if redirect_url else reverse('dashboard') dashboard_redirect_url = reverse('dashboard') skip_redirect_url = skip_redirect_url if skip_redirect_url else dashboard_redirect_url survey = SurveyForm.get(survey_name, throw_if_not_found=False) if not survey: return HttpResponseRedirect(redirect_url) # the result set from get_answers, has an outer key with the user_id # just remove that outer key to make the JSON payload simplier existing_answers = survey.get_answers(user=user).get(user.id, {}) platform_name = configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME) context = { 'existing_data_json': json.dumps(existing_answers), 'postback_url': reverse('submit_answers', args=[survey_name]), 'redirect_url': redirect_url, 'skip_redirect_url': skip_redirect_url, 'dashboard_redirect_url': dashboard_redirect_url, 'survey_form': survey.form, 'is_required': is_required, 'mail_to_link': configuration_helpers.get_value('email_from_address', settings.CONTACT_EMAIL), 'platform_name': platform_name, 'course': course, } return render_to_response("survey/survey.html", context)
def get(self, request): if not configuration_helpers.get_value('CONTACT_US_PAGE', True): raise Http404 context = { 'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME), 'support_email': configuration_helpers.get_value('CONTACT_EMAIL', settings.CONTACT_EMAIL), 'custom_fields': settings.ZENDESK_CUSTOM_FIELDS } # Tag all issues with LMS to distinguish channel which received the request tags = ['LMS'] # Per edX support, we would like to be able to route feedback items by site via tagging current_site_name = configuration_helpers.get_value("SITE_NAME") if current_site_name: current_site_name = current_site_name.replace(".", "_") tags.append("site_name_{site}".format(site=current_site_name)) if request.user.is_authenticated: context['course_id'] = request.session.get('course_id', '') context['user_enrollments'] = CourseEnrollment.enrollments_for_user_with_overviews_preload(request.user) enterprise_learner_data = enterprise_api.get_enterprise_learner_data(user=request.user) if enterprise_learner_data: tags.append('enterprise_learner') context['tags'] = tags return render_to_response("support/contact_us.html", context)
def _generate_not_activated_message(user): """ Generates the message displayed on the sign-in screen when a learner attempts to access the system with an inactive account. """ support_url = configuration_helpers.get_value( 'SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK ) platform_name = configuration_helpers.get_value( 'PLATFORM_NAME', settings.PLATFORM_NAME ) not_activated_msg_template = _('In order to sign in, you need to activate your account.<br /><br />' 'We just sent an activation link to <strong>{email}</strong>. If ' 'you do not receive an email, check your spam folders or ' '<a href="{support_url}">contact {platform} Support</a>.') not_activated_message = not_activated_msg_template.format( email=user.email, support_url=support_url, platform=platform_name ) return not_activated_message
def get(self, request): context = { 'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME), 'zendesk_api_host': settings.ZENDESK_URL, 'access_token': 'DUMMY_ACCESS_TOKEN', # LEARNER-3450 'custom_fields': settings.ZENDESK_CUSTOM_FIELDS } # Tag all issues with LMS to distinguish channel in Zendesk; requested by student support team zendesk_tags = ['LMS'] # Per edX support, we would like to be able to route feedback items by site via tagging current_site_name = configuration_helpers.get_value("SITE_NAME") if current_site_name: current_site_name = current_site_name.replace(".", "_") zendesk_tags.append("site_name_{site}".format(site=current_site_name)) if request.user.is_authenticated(): context['user_enrollments'] = CourseEnrollment.enrollments_for_user(request.user) enterprise_learner_data = enterprise_api.get_enterprise_learner_data(site=request.site, user=request.user) if enterprise_learner_data: zendesk_tags.append('enterprise_learner') context['zendesk_tags'] = zendesk_tags return render_to_response("support/contact_us.html", context)
def show_cart(request): """ This view shows cart items. """ cart = Order.get_cart_for_user(request.user) is_any_course_expired, expired_cart_items, expired_cart_item_names, valid_cart_item_tuples = \ verify_for_closed_enrollment(request.user, cart) site_name = configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME) if is_any_course_expired: for expired_item in expired_cart_items: Order.remove_cart_item_from_order(expired_item, request.user) cart.update_order_type() callback_url = request.build_absolute_uri( reverse("shoppingcart.views.postpay_callback") ) form_html = render_purchase_form_html(cart, callback_url=callback_url) context = { 'order': cart, 'shoppingcart_items': valid_cart_item_tuples, 'amount': cart.total_cost, 'is_course_enrollment_closed': is_any_course_expired, 'expired_course_names': expired_cart_item_names, 'site_name': site_name, 'form_html': form_html, 'currency_symbol': settings.PAID_COURSE_REGISTRATION_CURRENCY[1], 'currency': settings.PAID_COURSE_REGISTRATION_CURRENCY[0], 'enable_bulk_purchase': configuration_helpers.get_value('ENABLE_SHOPPING_CART_BULK_PURCHASE', True) } return render_to_response("shoppingcart/shopping_cart.html", context)
def __init__(self): # Backwards compatibility: Honor code is required by default, unless # explicitly set to "optional" in Django settings. self._extra_fields_setting = copy.deepcopy(configuration_helpers.get_value('REGISTRATION_EXTRA_FIELDS')) if not self._extra_fields_setting: self._extra_fields_setting = copy.deepcopy(settings.REGISTRATION_EXTRA_FIELDS) self._extra_fields_setting["honor_code"] = self._extra_fields_setting.get("honor_code", "required") # Check that the setting is configured correctly for field_name in self.EXTRA_FIELDS: if self._extra_fields_setting.get(field_name, "hidden") not in ["required", "optional", "hidden"]: msg = u"Setting REGISTRATION_EXTRA_FIELDS values must be either required, optional, or hidden." raise ImproperlyConfigured(msg) # Map field names to the instance method used to add the field to the form self.field_handlers = {} valid_fields = self.DEFAULT_FIELDS + self.EXTRA_FIELDS for field_name in valid_fields: handler = getattr(self, "_add_{field_name}_field".format(field_name=field_name)) self.field_handlers[field_name] = handler field_order = configuration_helpers.get_value('REGISTRATION_FIELD_ORDER') if not field_order: field_order = settings.REGISTRATION_FIELD_ORDER or valid_fields # Check that all of the valid_fields are in the field order and vice versa, if not set to the default order if set(valid_fields) != set(field_order): field_order = valid_fields self.field_order = field_order
def save( self, subject_template_name='registration/password_reset_subject.txt', email_template_name='registration/password_reset_email.html', use_https=False, token_generator=default_token_generator, from_email=configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL), request=None ): """ Generates a one-use only link for resetting password and sends to the user. """ # This import is here because we are copying and modifying the .save from Django 1.4.5's # django.contrib.auth.forms.PasswordResetForm directly, which has this import in this place. from django.core.mail import send_mail for user in self.users_cache: site_name = configuration_helpers.get_value( 'SITE_NAME', settings.SITE_NAME ) context = { 'email': user.email, 'site_name': site_name, 'uid': int_to_base36(user.id), 'user': user, 'token': token_generator.make_token(user), 'protocol': 'https' if use_https else 'http', 'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME) } subject = loader.render_to_string(subject_template_name, context) # Email subject *must not* contain newlines subject = subject.replace('\n', '') email = loader.render_to_string(email_template_name, context) send_mail(subject, email, from_email, [user.email])
def marketing_link(name): """Returns the correct URL for a link to the marketing site depending on if the marketing site is enabled Since the marketing site is enabled by a setting, we have two possible URLs for certain links. This function is to decides which URL should be provided. """ # link_map maps URLs from the marketing site to the old equivalent on # the Django site link_map = settings.MKTG_URL_LINK_MAP enable_mktg_site = configuration_helpers.get_value( "ENABLE_MKTG_SITE", settings.FEATURES.get("ENABLE_MKTG_SITE", False) ) marketing_urls = configuration_helpers.get_value("MKTG_URLS", settings.MKTG_URLS) if enable_mktg_site and name in marketing_urls: # special case for when we only want the root marketing URL if name == "ROOT": return marketing_urls.get("ROOT") # Using urljoin here allows us to enable a marketing site and set # a site ROOT, but still specify absolute URLs for other marketing # URLs in the MKTG_URLS setting # e.g. urljoin('http://marketing.com', 'http://open-edx.org/about') >>> 'http://open-edx.org/about' return urljoin(marketing_urls.get("ROOT"), marketing_urls.get(name)) # only link to the old pages when the marketing site isn't on elif not enable_mktg_site and name in link_map: # don't try to reverse disabled marketing links if link_map[name] is not None: return reverse(link_map[name]) else: log.debug("Cannot find corresponding link for name: %s", name) return "#"
def save(self, # pylint: disable=arguments-differ use_https=False, token_generator=default_token_generator, request=None, **_kwargs): """ Generates a one-use only link for resetting password and sends to the user. """ for user in self.users_cache: site = get_current_site() message_context = get_base_template_context(site) message_context.update({ 'request': request, # Used by google_analytics_tracking_pixel # TODO: This overrides `platform_name` from `get_base_template_context` to make the tests passes 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'reset_link': '{protocol}://{site}{link}'.format( protocol='https' if use_https else 'http', site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME), link=reverse('password_reset_confirm', kwargs={ 'uidb36': int_to_base36(user.id), 'token': token_generator.make_token(user), }), ) }) msg = PasswordReset().personalize( recipient=Recipient(user.username, user.email), language=get_user_preference(user, LANGUAGE_KEY), user_context=message_context, ) ace.send(msg)
def compose_and_send_activation_email(user, profile, user_registration=None): """ Construct all the required params and send the activation email through celery task Arguments: user: current logged-in user profile: profile object of the current logged-in user user_registration: registration of the current logged-in user """ dest_addr = user.email if user_registration is None: user_registration = Registration.objects.get(user=user) context = generate_activation_email_context(user, user_registration) subject = render_to_string('emails/activation_email_subject.txt', context) # Email subject *must not* contain newlines subject = ''.join(subject.splitlines()) message_for_activation = render_to_string('emails/activation_email.txt', context) from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) from_address = configuration_helpers.get_value('ACTIVATION_EMAIL_FROM_ADDRESS', from_address) if settings.FEATURES.get('REROUTE_ACTIVATION_EMAIL'): dest_addr = settings.FEATURES['REROUTE_ACTIVATION_EMAIL'] message_for_activation = ("Activation for %s (%s): %s\n" % (user, user.email, profile.name) + '-' * 80 + '\n\n' + message_for_activation) send_activation_email.delay(subject, message_for_activation, from_address, dest_addr)
def send_account_recovery_email_for_user(user, request, email=None): """ Send out a account recovery email for the given user. Arguments: user (User): Django User object request (HttpRequest): Django request object email (str): Send email to this address. """ site = get_current_site() message_context = get_base_template_context(site) message_context.update({ 'request': request, # Used by google_analytics_tracking_pixel 'email': email, 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'reset_link': '{protocol}://{site}{link}?is_account_recovery=true'.format( protocol='https' if request.is_secure() else 'http', site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME), link=reverse('password_reset_confirm', kwargs={ 'uidb36': int_to_base36(user.id), 'token': default_token_generator.make_token(user), }), ) }) msg = AccountRecoveryMessage().personalize( recipient=Recipient(user.username, email), language=get_user_preference(user, LANGUAGE_KEY), user_context=message_context, ) ace.send(msg)
def send_password_reset_email_for_user(user, request, preferred_email=None): """ Send out a password reset email for the given user. Arguments: user (User): Django User object request (HttpRequest): Django request object preferred_email (str): Send email to this address if present, otherwise fallback to user's email address. """ site = get_current_site() message_context = get_base_template_context(site) message_context.update({ 'request': request, # Used by google_analytics_tracking_pixel # TODO: This overrides `platform_name` from `get_base_template_context` to make the tests passes 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'reset_link': '{protocol}://{site}{link}'.format( protocol='https' if request.is_secure() else 'http', site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME), link=reverse('password_reset_confirm', kwargs={ 'uidb36': int_to_base36(user.id), 'token': default_token_generator.make_token(user), }), ) }) msg = PasswordReset().personalize( recipient=Recipient(user.username, preferred_email or user.email), language=get_user_preference(user, LANGUAGE_KEY), user_context=message_context, ) ace.send(msg)
def render(request, template): """ This view function renders the template sent without checking that it exists. Do not expose template as a regex part of the url. The user should not be able to ender any arbitray template name. The correct usage would be: url(r'^jobs$', 'static_template_view.views.render', {'template': 'jobs.html'}, name="jobs") """ # Guess content type from file extension content_type, __ = mimetypes.guess_type(template) try: context = {} # This is necessary for the dialog presented with the TOS in /register if template == 'honor.html': context['allow_iframing'] = True # Format Examples: static_template_about_header configuration_base = 'static_template_' + template.replace('.html', '').replace('-', '_') page_header = configuration_helpers.get_value(configuration_base + '_header') page_content = configuration_helpers.get_value(configuration_base + '_content') if page_header: context['page_header'] = mark_safe(page_header) if page_content: context['page_content'] = mark_safe(page_content) result = render_to_response('static_templates/' + template, context, content_type=content_type) return result except TopLevelLookupException: raise Http404 except TemplateDoesNotExist: raise Http404
def get_id_token(user): """ Return a JWT for `user`, suitable for use with the course discovery service. Arguments: user (User): User for whom to generate the JWT. Returns: str: The JWT. """ try: # Service users may not have user profiles. full_name = UserProfile.objects.get(user=user).name except UserProfile.DoesNotExist: full_name = None now = datetime.datetime.utcnow() expires_in = getattr(settings, 'OAUTH_ID_TOKEN_EXPIRATION', 30) payload = { 'preferred_username': user.username, 'name': full_name, 'email': user.email, 'administrator': user.is_staff, 'iss': configuration_helpers.get_value('OAUTH_OIDC_ISSUER', settings.OAUTH_OIDC_ISSUER), 'exp': now + datetime.timedelta(seconds=expires_in), 'iat': now, 'aud': configuration_helpers.get_value('JWT_AUTH', settings.JWT_AUTH)['JWT_AUDIENCE'], 'sub': anonymous_id_for_user(user, None), } secret_key = configuration_helpers.get_value('JWT_AUTH', settings.JWT_AUTH)['JWT_SECRET_KEY'] return jwt.encode(payload, secret_key).decode('utf-8')
def get_setting(self, name): """ Get the value of a setting, or raise KeyError """ default_saml_contact = { # Default contact information to put into the SAML metadata that gets generated by python-saml. "givenName": _("{platform_name} Support").format( platform_name=configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME) ), "emailAddress": configuration_helpers.get_value('TECH_SUPPORT_EMAIL', settings.TECH_SUPPORT_EMAIL), } if name == "ORG_INFO": return json.loads(self.org_info_str) if name == "SP_ENTITY_ID": return self.entity_id if name == "SP_PUBLIC_CERT": if self.public_key: return self.public_key # To allow instances to avoid storing keys in the DB, the key pair can also be set via Django: return getattr(settings, 'SOCIAL_AUTH_SAML_SP_PUBLIC_CERT', '') if name == "SP_PRIVATE_KEY": if self.private_key: return self.private_key # To allow instances to avoid storing keys in the DB, the private key can also be set via Django: return getattr(settings, 'SOCIAL_AUTH_SAML_SP_PRIVATE_KEY', '') other_config = { # These defaults can be overriden by self.other_config_str "GET_ALL_EXTRA_DATA": True, # Save all attribute values the IdP sends into the UserSocialAuth table "TECHNICAL_CONTACT": default_saml_contact, "SUPPORT_CONTACT": default_saml_contact, } other_config.update(json.loads(self.other_config_str)) return other_config[name] # SECURITY_CONFIG, SP_EXTRA, or similar extra settings
def test_get_value(self): """ Test that get_value returns correct value for any given key. """ # Make sure entry is saved and retrieved correctly self.assertEqual(configuration_helpers.get_value("university"), test_config['university']) self.assertEqual(configuration_helpers.get_value("platform_name"), test_config['platform_name']) self.assertEqual(configuration_helpers.get_value("SITE_NAME"), test_config['SITE_NAME']) self.assertEqual(configuration_helpers.get_value("course_org_filter"), test_config['course_org_filter']) self.assertEqual(configuration_helpers.get_value("css_overrides_file"), test_config['css_overrides_file']) self.assertEqual(configuration_helpers.get_value("ENABLE_MKTG_SITE"), test_config['ENABLE_MKTG_SITE']) self.assertEqual(configuration_helpers.get_value("favicon_path"), test_config['favicon_path']) self.assertEqual( configuration_helpers.get_value("ENABLE_THIRD_PARTY_AUTH"), test_config['ENABLE_THIRD_PARTY_AUTH'], ) self.assertEqual( configuration_helpers.get_value("course_about_show_social_links"), test_config['course_about_show_social_links'], ) # Test that the default value is returned if the value for the given key is not found in the configuration self.assertEqual( configuration_helpers.get_value("non_existent_name", "dummy-default-value"), "dummy-default-value", )
def header_language_selector_is_enabled(): """Return true if the header language selector has been enabled via settings or site-specific configuration.""" setting = get_value('SHOW_HEADER_LANGUAGE_SELECTOR', settings.FEATURES.get('SHOW_HEADER_LANGUAGE_SELECTOR', False)) # The SHOW_LANGUAGE_SELECTOR setting is deprecated, but might still be in use on some installations. deprecated_setting = get_value('SHOW_LANGUAGE_SELECTOR', settings.FEATURES.get('SHOW_LANGUAGE_SELECTOR', False)) return setting or deprecated_setting
def _get_extended_profile_fields(): """Retrieve the extended profile fields from site configuration to be shown on the Account Settings page Returns: A list of dicts. Each dict corresponds to a single field. The keys per field are: "field_name" : name of the field stored in user_profile.meta "field_label" : The label of the field. "field_type" : TextField or ListField "field_options": a list of tuples for options in the dropdown in case of ListField """ extended_profile_fields = [] fields_already_showing = ['username', 'name', 'email', 'pref-lang', 'country', 'time_zone', 'level_of_education', 'gender', 'year_of_birth', 'language_proficiencies', 'social_links'] field_labels_map = { "first_name": _(u"First Name"), "last_name": _(u"Last Name"), "city": _(u"City"), "state": _(u"State/Province/Region"), "company": _(u"Company"), "title": _(u"Title"), "job_title": _(u"Job Title"), "mailing_address": _(u"Mailing address"), "goals": _(u"Tell us why you're interested in {platform_name}").format( platform_name=configuration_helpers.get_value("PLATFORM_NAME", settings.PLATFORM_NAME) ), "profession": _(u"Profession"), "specialty": _(u"Specialty") } extended_profile_field_names = configuration_helpers.get_value('extended_profile_fields', []) for field_to_exclude in fields_already_showing: if field_to_exclude in extended_profile_field_names: extended_profile_field_names.remove(field_to_exclude) extended_profile_field_options = configuration_helpers.get_value('EXTRA_FIELD_OPTIONS', []) extended_profile_field_option_tuples = {} for field in extended_profile_field_options.keys(): field_options = extended_profile_field_options[field] extended_profile_field_option_tuples[field] = [(option.lower(), option) for option in field_options] for field in extended_profile_field_names: field_dict = { "field_name": field, "field_label": field_labels_map.get(field, field), } field_options = extended_profile_field_option_tuples.get(field) if field_options: field_dict["field_type"] = "ListField" field_dict["field_options"] = field_options else: field_dict["field_type"] = "TextField" extended_profile_fields.append(field_dict) return extended_profile_fields
def public_records_url(self): """ Publicly-accessible Records URL root. """ # Not every site wants the Learner Records feature, so we allow opting out. if not helpers.get_value('ENABLE_LEARNER_RECORDS', True): return None root = helpers.get_value('CREDENTIALS_PUBLIC_SERVICE_URL', settings.CREDENTIALS_PUBLIC_SERVICE_URL) return urljoin(root, '/records/')
def is_commerce_service_configured(): """ Return a Boolean indicating whether or not configuration is present to use the external commerce service. """ ecommerce_api_url = configuration_helpers.get_value("ECOMMERCE_API_URL", settings.ECOMMERCE_API_URL) ecommerce_api_signing_key = configuration_helpers.get_value( "ECOMMERCE_API_SIGNING_KEY", settings.ECOMMERCE_API_SIGNING_KEY, ) return bool(ecommerce_api_url and ecommerce_api_signing_key)
def checkout_receipt(request): """ Receipt view. """ page_title = _('Receipt') is_payment_complete = True payment_support_email = configuration_helpers.get_value('payment_support_email', settings.PAYMENT_SUPPORT_EMAIL) payment_support_link = '<a href=\"mailto:{email}\">{email}</a>'.format(email=payment_support_email) is_cybersource = all(k in request.POST for k in ('signed_field_names', 'decision', 'reason_code')) if is_cybersource and request.POST['decision'] != 'ACCEPT': # Cybersource may redirect users to this view if it couldn't recover # from an error while capturing payment info. is_payment_complete = False page_title = _('Payment Failed') reason_code = request.POST['reason_code'] # if the problem was with the info submitted by the user, we present more detailed messages. if is_user_payment_error(reason_code): error_summary = _("There was a problem with this transaction. You have not been charged.") error_text = _( "Make sure your information is correct, or try again with a different card or another form of payment." ) else: error_summary = _("A system error occurred while processing your payment. You have not been charged.") error_text = _("Please wait a few minutes and then try again.") for_help_text = _("For help, contact {payment_support_link}.").format(payment_support_link=payment_support_link) else: # if anything goes wrong rendering the receipt, it indicates a problem fetching order data. error_summary = _("An error occurred while creating your receipt.") error_text = None # nothing particularly helpful to say if this happens. for_help_text = _( "If your course does not appear on your dashboard, contact {payment_support_link}." ).format(payment_support_link=payment_support_link) commerce_configuration = CommerceConfiguration.current() # user order cache should be cleared when a new order is placed # so user can see new order in their order history. if is_payment_complete and commerce_configuration.enabled and commerce_configuration.is_cache_enabled: cache_key = commerce_configuration.CACHE_KEY + '.' + str(request.user.id) cache.delete(cache_key) context = { 'page_title': page_title, 'is_payment_complete': is_payment_complete, 'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME), 'verified': SoftwareSecurePhotoVerification.verification_valid_or_pending(request.user).exists(), 'error_summary': error_summary, 'error_text': error_text, 'for_help_text': for_help_text, 'payment_support_email': payment_support_email, 'username': request.user.username, 'nav_hidden': True, 'is_request_in_themed_site': is_request_in_themed_site() } return render_to_response('commerce/checkout_receipt.html', context)
def ecommerce_api_client(user): """ Returns an E-Commerce API client setup with authentication for the specified user. """ jwt_auth = configuration_helpers.get_value("JWT_AUTH", settings.JWT_AUTH) return EdxRestApiClient( configuration_helpers.get_value("ECOMMERCE_API_URL", settings.ECOMMERCE_API_URL), configuration_helpers.get_value("ECOMMERCE_API_SIGNING_KEY", settings.ECOMMERCE_API_SIGNING_KEY), user.username, user.profile.name if hasattr(user, 'profile') else None, user.email, tracking_context=create_tracking_context(user), issuer=jwt_auth['JWT_ISSUER'], expires_in=jwt_auth['JWT_EXPIRATION'] )
def is_marketing_link_set(name): """ Returns a boolean if a given named marketing link is configured. """ enable_mktg_site = configuration_helpers.get_value( "ENABLE_MKTG_SITE", settings.FEATURES.get("ENABLE_MKTG_SITE", False) ) marketing_urls = configuration_helpers.get_value("MKTG_URLS", settings.MKTG_URLS) if enable_mktg_site: return name in marketing_urls else: return name in settings.MKTG_URL_LINK_MAP
def _add_honor_code_field(self, form_desc, required=True): """Add an honor code field to a form description. Arguments: form_desc: A form description Keyword Arguments: required (bool): Whether this field is required; defaults to True """ # Separate terms of service and honor code checkboxes if self._is_field_visible("terms_of_service"): terms_label = _(u"Honor Code") terms_link = marketing_link("HONOR") terms_text = _(u"Review the Honor Code") # Combine terms of service and honor code checkboxes else: # Translators: This is a legal document users must agree to # in order to register a new account. terms_label = _(u"Terms of Service and Honor Code") terms_link = marketing_link("HONOR") terms_text = _(u"Review the Terms of Service and Honor Code") # Translators: "Terms of Service" is a legal document users must agree to # in order to register a new account. label = _(u"I agree to the {platform_name} {terms_of_service}").format( platform_name=configuration_helpers.get_value("PLATFORM_NAME", settings.PLATFORM_NAME), terms_of_service=terms_label ) # Translators: "Terms of Service" is a legal document users must agree to # in order to register a new account. error_msg = _(u"You must agree to the {platform_name} {terms_of_service}").format( platform_name=configuration_helpers.get_value("PLATFORM_NAME", settings.PLATFORM_NAME), terms_of_service=terms_label ) form_desc.add_field( "honor_code", label=label, field_type="checkbox", default=False, required=required, error_messages={ "required": error_msg }, supplementalLink=terms_link, supplementalText=terms_text )
def get_visible_courses(org=None, filter_=None): """ Return the set of CourseOverviews that should be visible in this branded instance. Arguments: org (string): Optional parameter that allows case-insensitive filtering by organization. filter_ (dict): Optional parameter that allows custom filtering by fields on the course. """ current_site_org = configuration_helpers.get_value('course_org_filter') if org and current_site_org: # Return an empty result if the org passed by the caller does not match the designated site org. courses = CourseOverview.get_all_courses( org=org, filter_=filter_, ) if org == current_site_org else [] else: # We only make it to this point if one of org or current_site_org is defined. # If both org and current_site_org were defined, the code would have fallen into the # first branch of the conditional above, wherein an equality check is performed. target_org = org or current_site_org courses = CourseOverview.get_all_courses(org=target_org, filter_=filter_) courses = sorted(courses, key=lambda course: course.number) # Filtering can stop here. if current_site_org: return courses # See if we have filtered course listings in this domain filtered_visible_ids = None # this is legacy format, which also handle dev case, which should not filter subdomain = configuration_helpers.get_value('subdomain', 'default') if hasattr(settings, 'COURSE_LISTINGS') and subdomain in settings.COURSE_LISTINGS and not settings.DEBUG: filtered_visible_ids = frozenset( [SlashSeparatedCourseKey.from_deprecated_string(c) for c in settings.COURSE_LISTINGS[subdomain]] ) if filtered_visible_ids: return [course for course in courses if course.id in filtered_visible_ids] else: # Filter out any courses based on current org, to avoid leaking these. orgs = configuration_helpers.get_all_orgs() return [course for course in courses if course.location.org not in orgs]
def update_account_settings_context_for_enterprise(context, enterprise_customer): """ Take processed context for account settings page and update it taking enterprise customer into account. Arguments: context (dict): Context for account settings page. enterprise_customer (dict): data for enterprise customer """ enterprise_context = { 'enterprise_name': None, 'sync_learner_profile_data': False, 'edx_support_url': configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK), 'enterprise_readonly_account_fields': { 'fields': settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS } } if enterprise_customer: enterprise_context['enterprise_name'] = enterprise_customer['name'] identity_provider = third_party_auth.provider.Registry.get( provider_id=enterprise_customer['identity_provider'], ) if identity_provider: enterprise_context['sync_learner_profile_data'] = identity_provider.sync_learner_profile_data context.update(enterprise_context)
def signin_user(request): """Deprecated. To be replaced by :class:`student_account.views.login_and_registration_form`.""" external_auth_response = external_auth_login(request) if external_auth_response is not None: return external_auth_response # Determine the URL to redirect to following login: redirect_to = get_next_url_for_login_page(request) if request.user.is_authenticated: return redirect(redirect_to) third_party_auth_error = None for msg in messages.get_messages(request): if msg.extra_tags.split()[0] == "social-auth": # msg may or may not be translated. Try translating [again] in case we are able to: third_party_auth_error = _(text_type(msg)) # pylint: disable=translation-of-non-string break context = { 'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in the header # Bool injected into JS to submit form if we're inside a running third- # party auth pipeline; distinct from the actual instance of the running # pipeline, if any. 'pipeline_running': 'true' if pipeline.running(request) else 'false', 'pipeline_url': auth_pipeline_urls(pipeline.AUTH_ENTRY_LOGIN, redirect_url=redirect_to), 'platform_name': configuration_helpers.get_value( 'platform_name', settings.PLATFORM_NAME ), 'third_party_auth_error': third_party_auth_error } return render_to_response('login.html', context)
def get_feedback_form_context(request): """ Extract the submitted form fields to be used as a context for feedback submission. """ context = {} context["subject"] = request.POST["subject"] context["details"] = request.POST["details"] context["tags"] = dict( [(tag, request.POST[tag]) for tag in ["issue_type", "course_id"] if request.POST.get(tag)] ) context["additional_info"] = {} if request.user.is_authenticated(): context["realname"] = request.user.profile.name context["email"] = request.user.email context["additional_info"]["username"] = request.user.username else: context["realname"] = request.POST["name"] context["email"] = request.POST["email"] for header, pretty in [("HTTP_REFERER", "Page"), ("HTTP_USER_AGENT", "Browser"), ("REMOTE_ADDR", "Client IP"), ("SERVER_NAME", "Host")]: context["additional_info"][pretty] = request.META.get(header) context["support_email"] = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) return context
def _send_decision_email(instance): """ Send an email to requesting user with the decision made about their request. """ context = { 'name': instance.user.username, 'api_management_url': urlunsplit( ( 'https' if settings.HTTPS == 'on' else 'http', instance.site.domain, reverse('api_admin:api-status'), '', '', ) ), 'authentication_docs_url': settings.AUTH_DOCUMENTATION_URL, 'api_docs_url': settings.API_DOCUMENTATION_URL, 'support_email_address': settings.API_ACCESS_FROM_EMAIL, 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME) } message = render_to_string( 'api_admin/api_access_request_email_{status}.txt'.format(status=instance.status), context ) try: send_mail( _('API access request'), message, settings.API_ACCESS_FROM_EMAIL, [instance.user.email], fail_silently=False ) instance.contacted = True except SMTPException: log.exception('Error sending API user notification email for request [%s].', instance.id)
def __init__(self, items_data, item_id, date, is_invoice, total_cost, payment_received, balance): """ Accepts the following positional arguments items_data - A list having the following items for each row. item_description - String quantity - Integer list_price - float discount - float item_total - float id - String date - datetime is_invoice - boolean - True (for invoice) or False (for Receipt) total_cost - float payment_received - float balance - float """ # From settings self.currency = settings.PAID_COURSE_REGISTRATION_CURRENCY[1] self.logo_path = configuration_helpers.get_value( "PDF_RECEIPT_LOGO_PATH", settings.PDF_RECEIPT_LOGO_PATH) self.cobrand_logo_path = configuration_helpers.get_value( "PDF_RECEIPT_COBRAND_LOGO_PATH", settings.PDF_RECEIPT_COBRAND_LOGO_PATH) self.tax_label = configuration_helpers.get_value( "PDF_RECEIPT_TAX_ID_LABEL", settings.PDF_RECEIPT_TAX_ID_LABEL) self.tax_id = configuration_helpers.get_value( "PDF_RECEIPT_TAX_ID", settings.PDF_RECEIPT_TAX_ID) self.footer_text = configuration_helpers.get_value( "PDF_RECEIPT_FOOTER_TEXT", settings.PDF_RECEIPT_FOOTER_TEXT) self.disclaimer_text = configuration_helpers.get_value( "PDF_RECEIPT_DISCLAIMER_TEXT", settings.PDF_RECEIPT_DISCLAIMER_TEXT, ) self.billing_address_text = configuration_helpers.get_value( "PDF_RECEIPT_BILLING_ADDRESS", settings.PDF_RECEIPT_BILLING_ADDRESS) self.terms_conditions_text = configuration_helpers.get_value( "PDF_RECEIPT_TERMS_AND_CONDITIONS", settings.PDF_RECEIPT_TERMS_AND_CONDITIONS) self.brand_logo_height = configuration_helpers.get_value( "PDF_RECEIPT_LOGO_HEIGHT_MM", settings.PDF_RECEIPT_LOGO_HEIGHT_MM) * mm self.cobrand_logo_height = configuration_helpers.get_value( "PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM", settings.PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM) * mm # From Context self.items_data = items_data self.item_id = item_id self.date = ModuleI18nService().strftime(date, 'SHORT_DATE') self.is_invoice = is_invoice self.total_cost = '{currency}{amount:.2f}'.format( currency=self.currency, amount=total_cost) self.payment_received = '{currency}{amount:.2f}'.format( currency=self.currency, amount=payment_received) self.balance = '{currency}{amount:.2f}'.format(currency=self.currency, amount=balance) # initialize the pdf variables self.margin = 15 * mm self.page_width = letter[0] self.page_height = letter[1] self.min_clearance = 3 * mm self.second_page_available_height = '' self.second_page_start_y_pos = '' self.first_page_available_height = '' self.pdf = None
def activate_account(request, key): """ When link in activation e-mail is clicked """ # If request is in Studio call the appropriate view if theming_helpers.get_project_root_name().lower() == 'cms': monitoring_utils.set_custom_attribute('student_activate_account', 'cms') return activate_account_studio(request, key) # TODO: Use custom attribute to determine if there are any `activate_account` calls for cms in Production. # If not, the templates wouldn't be needed for cms, but we still need a way to activate for cms tests. monitoring_utils.set_custom_attribute('student_activate_account', 'lms') activation_message_type = None try: registration = Registration.objects.get(activation_key=key) except (Registration.DoesNotExist, Registration.MultipleObjectsReturned): activation_message_type = 'error' messages.error( request, HTML(_( '{html_start}Your account could not be activated{html_end}' 'Something went wrong, please <a href="{support_url}">contact support</a> to resolve this issue.' )).format( support_url=configuration_helpers.get_value( 'ACTIVATION_EMAIL_SUPPORT_LINK', settings.ACTIVATION_EMAIL_SUPPORT_LINK ) or settings.SUPPORT_SITE_LINK, html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon' ) else: if registration.user.is_active: activation_message_type = 'info' messages.info( request, HTML(_('{html_start}This account has already been activated.{html_end}')).format( html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon', ) else: registration.activate() # Success message for logged in users. message = _('{html_start}Success{html_end} You have activated your account.') tracker.emit( USER_ACCOUNT_ACTIVATED, { "user_id": registration.user.id, "activation_timestamp": registration.activation_timestamp } ) if not request.user.is_authenticated: # Success message for logged out users message = _( '{html_start}Success! You have activated your account.{html_end}' 'You will now receive email updates and alerts from us related to' ' the courses you are enrolled in. Sign In to continue.' ) # Add message for later use. activation_message_type = 'success' messages.success( request, HTML(message).format( html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon', ) if should_redirect_to_authn_microfrontend() and not request.user.is_authenticated: url_path = f'/login?account_activation_status={activation_message_type}' return redirect(settings.AUTHN_MICROFRONTEND_URL + url_path) return redirect('dashboard')
def do_email_change_request(user, new_email, activation_key=None, secondary_email_change_request=False): """ Given a new email for a user, does some basic verification of the new address and sends an activation message to the new address. If any issues are encountered with verification or sending the message, a ValueError will be thrown. """ # if activation_key is not passing as an argument, generate a random key if not activation_key: activation_key = uuid.uuid4().hex confirm_link = reverse('confirm_email_change', kwargs={'key': activation_key, }) if secondary_email_change_request: PendingSecondaryEmailChange.objects.update_or_create( user=user, defaults={ 'new_secondary_email': new_email, 'activation_key': activation_key, } ) confirm_link = reverse('activate_secondary_email', kwargs={'key': activation_key}) else: PendingEmailChange.objects.update_or_create( user=user, defaults={ 'new_email': new_email, 'activation_key': activation_key, } ) use_https = theming_helpers.get_current_request().is_secure() site = Site.objects.get_current() message_context = get_base_template_context(site) message_context.update({ 'old_email': user.email, 'new_email': new_email, 'confirm_link': '{protocol}://{site}{link}'.format( protocol='https' if use_https else 'http', site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME), link=confirm_link, ), }) if secondary_email_change_request: msg = RecoveryEmailCreate().personalize( recipient=Recipient(user.id, new_email), language=preferences_api.get_user_preference(user, LANGUAGE_KEY), user_context=message_context, ) else: msg = EmailChange().personalize( recipient=Recipient(user.id, new_email), language=preferences_api.get_user_preference(user, LANGUAGE_KEY), user_context=message_context, ) try: ace.send(msg) except Exception: from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) log.error('Unable to send email activation link to user from "%s"', from_address, exc_info=True) raise ValueError(_('Unable to send email activation link. Please try again later.')) # lint-amnesty, pylint: disable=raise-missing-from if not secondary_email_change_request: # When the email address change is complete, a "edx.user.settings.changed" event will be emitted. # But because changing the email address is multi-step, we also emit an event here so that we can # track where the request was initiated. tracker.emit( SETTING_CHANGE_INITIATED, { "setting": "email", "old": message_context['old_email'], "new": message_context['new_email'], "user_id": user.id, } )
def login_and_registration_form(request, initial_mode="login"): """Render the combined login/registration form, defaulting to login This relies on the JS to asynchronously load the actual form from the user_api. Keyword Args: initial_mode (string): Either "login" or "register". """ # Determine the URL to redirect to following login/registration/third_party_auth redirect_to = get_next_url_for_login_page(request) # If we're already logged in, redirect to the dashboard if request.user.is_authenticated: return redirect(redirect_to) # Retrieve the form descriptions from the user API form_descriptions = _get_form_descriptions(request) # Our ?next= URL may itself contain a parameter 'tpa_hint=x' that we need to check. # If present, we display a login page focused on third-party auth with that provider. third_party_auth_hint = None if '?' in redirect_to: try: next_args = urlparse.parse_qs(urlparse.urlparse(redirect_to).query) provider_id = next_args['tpa_hint'][0] tpa_hint_provider = third_party_auth.provider.Registry.get( provider_id=provider_id) if tpa_hint_provider: if tpa_hint_provider.skip_hinted_login_dialog: # Forward the user directly to the provider's login URL when the provider is configured # to skip the dialog. if initial_mode == "register": auth_entry = pipeline.AUTH_ENTRY_REGISTER else: auth_entry = pipeline.AUTH_ENTRY_LOGIN return redirect( pipeline.get_login_url(provider_id, auth_entry, redirect_url=redirect_to)) third_party_auth_hint = provider_id initial_mode = "hinted_login" except (KeyError, ValueError, IndexError) as ex: log.exception("Unknown tpa_hint provider: %s", ex) # If this is a themed site, revert to the old login/registration pages. # We need to do this for now to support existing themes. # Themed sites can use the new logistration page by setting # 'ENABLE_COMBINED_LOGIN_REGISTRATION' in their # configuration settings. if is_request_in_themed_site() and not configuration_helpers.get_value( 'ENABLE_COMBINED_LOGIN_REGISTRATION', False): if initial_mode == "login": return old_login_view(request) elif initial_mode == "register": return old_register_view(request) # Allow external auth to intercept and handle the request ext_auth_response = _external_auth_intercept(request, initial_mode) if ext_auth_response is not None: return ext_auth_response # Account activation message account_activation_messages = [{ 'message': message.message, 'tags': message.tags } for message in messages.get_messages(request) if 'account-activation' in message.tags] # Otherwise, render the combined login/registration page context = { 'data': { 'login_redirect_url': redirect_to, 'initial_mode': initial_mode, 'third_party_auth': _third_party_auth_context(request, redirect_to, third_party_auth_hint), 'third_party_auth_hint': third_party_auth_hint or '', 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'support_link': configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK), 'password_reset_support_link': configuration_helpers.get_value( 'PASSWORD_RESET_SUPPORT_LINK', settings.PASSWORD_RESET_SUPPORT_LINK) or settings.SUPPORT_SITE_LINK, 'account_activation_messages': account_activation_messages, # Include form descriptions retrieved from the user API. # We could have the JS client make these requests directly, # but we include them in the initial page load to avoid # the additional round-trip to the server. 'login_form_desc': json.loads(form_descriptions['login']), 'registration_form_desc': json.loads(form_descriptions['registration']), 'password_reset_form_desc': json.loads(form_descriptions['password_reset']), 'account_creation_allowed': configuration_helpers.get_value( 'ALLOW_PUBLIC_ACCOUNT_CREATION', settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)) }, 'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in header 'responsive': True, 'allow_iframing': True, 'disable_courseware_js': True, 'combined_login_and_register': True, 'disable_footer': not configuration_helpers.get_value( 'ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER', settings.FEATURES['ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER']), } enterprise_customer = enterprise_customer_for_request(request) update_logistration_context_for_enterprise(request, context, enterprise_customer) response = render_to_response('student_account/login_and_register.html', context) handle_enterprise_cookies_for_logistration(request, response, context) return response
def send_mail_to_student(student, param_dict, language=None): """ Construct the email using templates and then send it. `student` is the student's email address (a `str`), `param_dict` is a `dict` with keys [ `site_name`: name given to edX instance (a `str`) `registration_url`: url for registration (a `str`) `display_name` : display name of a course (a `str`) `course_id`: id of course (a `str`) `auto_enroll`: user input option (a `str`) `course_url`: url of course (a `str`) `email_address`: email of student (a `str`) `full_name`: student full name (a `str`) `message`: type of email to send and template to use (a `str`) `is_shib_course`: (a `boolean`) ] `language` is the language used to render the email. If None the language of the currently-logged in user (that is, the user sending the email) will be used. Returns a boolean indicating whether the email was sent successfully. """ # add some helpers and microconfig subsitutions if 'display_name' in param_dict: param_dict['course_name'] = param_dict['display_name'] param_dict['site_name'] = configuration_helpers.get_value( 'SITE_NAME', param_dict['site_name']) subject = None message = None # see if there is an activation email template definition available as configuration, # if so, then render that message_type = param_dict['message'] email_template_dict = { 'allowed_enroll': ('emails/enroll_email_allowedsubject.txt', 'emails/enroll_email_allowedmessage.txt'), 'enrolled_enroll': ('emails/enroll_email_enrolledsubject.txt', 'emails/enroll_email_enrolledmessage.txt'), 'allowed_unenroll': ('emails/unenroll_email_subject.txt', 'emails/unenroll_email_allowedmessage.txt'), 'enrolled_unenroll': ('emails/unenroll_email_subject.txt', 'emails/unenroll_email_enrolledmessage.txt'), 'add_beta_tester': ('emails/add_beta_tester_email_subject.txt', 'emails/add_beta_tester_email_message.txt'), 'remove_beta_tester': ('emails/remove_beta_tester_email_subject.txt', 'emails/remove_beta_tester_email_message.txt'), 'account_creation_and_enrollment': ('emails/enroll_email_enrolledsubject.txt', 'emails/account_creation_and_enroll_emailMessage.txt'), } subject_template, message_template = email_template_dict.get( message_type, (None, None)) if subject_template is not None and message_template is not None: subject, message = render_message_to_string(subject_template, message_template, param_dict, language=language) if subject and message: # Remove leading and trailing whitespace from body message = message.strip() # Email subject *must not* contain newlines subject = ''.join(subject.splitlines()) from_address = configuration_helpers.get_value( 'email_from_address', settings.DEFAULT_FROM_EMAIL) send_mail(subject, message, from_address, [student], fail_silently=False)
def student_dashboard(request): """ Provides the LMS dashboard view TODO: This is lms specific and does not belong in common code. Arguments: request: The request object. Returns: The dashboard response. """ user = request.user if not UserProfile.objects.filter(user=user).exists(): return redirect(reverse('account_settings')) platform_name = configuration_helpers.get_value("platform_name", settings.PLATFORM_NAME) enable_verified_certificates = configuration_helpers.get_value( 'ENABLE_VERIFIED_CERTIFICATES', settings.FEATURES.get('ENABLE_VERIFIED_CERTIFICATES')) display_course_modes_on_dashboard = configuration_helpers.get_value( 'DISPLAY_COURSE_MODES_ON_DASHBOARD', settings.FEATURES.get('DISPLAY_COURSE_MODES_ON_DASHBOARD', True)) activation_email_support_link = configuration_helpers.get_value( 'ACTIVATION_EMAIL_SUPPORT_LINK', settings.ACTIVATION_EMAIL_SUPPORT_LINK) or settings.SUPPORT_SITE_LINK hide_dashboard_courses_until_activated = configuration_helpers.get_value( 'HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED', settings.FEATURES.get('HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED', False)) empty_dashboard_message = configuration_helpers.get_value( 'EMPTY_DASHBOARD_MESSAGE', None) # Get the org whitelist or the org blacklist for the current site site_org_whitelist, site_org_blacklist = get_org_black_and_whitelist_for_site( ) course_enrollments = list( get_course_enrollments(user, site_org_whitelist, site_org_blacklist)) # Get the entitlements for the user and a mapping to all available sessions for that entitlement # If an entitlement has no available sessions, pass through a mock course overview object (course_entitlements, course_entitlement_available_sessions, unfulfilled_entitlement_pseudo_sessions ) = get_filtered_course_entitlements(user, site_org_whitelist, site_org_blacklist) # Record how many courses there are so that we can get a better # understanding of usage patterns on prod. monitoring_utils.accumulate('num_courses', len(course_enrollments)) # Sort the enrollment pairs by the enrollment date course_enrollments.sort(key=lambda x: x.created, reverse=True) # Retrieve the course modes for each course enrolled_course_ids = [ enrollment.course_id for enrollment in course_enrollments ] __, unexpired_course_modes = CourseMode.all_and_unexpired_modes_for_courses( enrolled_course_ids) course_modes_by_course = { course_id: {mode.slug: mode for mode in modes} for course_id, modes in iteritems(unexpired_course_modes) } # Check to see if the student has recently enrolled in a course. # If so, display a notification message confirming the enrollment. enrollment_message = _create_recent_enrollment_message( course_enrollments, course_modes_by_course) course_optouts = Optout.objects.filter(user=user).values_list('course_id', flat=True) # Display activation message activate_account_message = '' if not user.is_active: activate_account_message = Text( _("Check your {email_start}{email}{email_end} inbox for an account activation link from {platform_name}. " "If you need help, contact {link_start}{platform_name} Support{link_end}." ) ).format( platform_name=platform_name, email_start=HTML("<strong>"), email_end=HTML("</strong>"), email=user.email, link_start=HTML( "<a target='_blank' href='{activation_email_support_link}'>"). format( activation_email_support_link=activation_email_support_link, ), link_end=HTML("</a>"), ) enterprise_message = get_dashboard_consent_notification( request, user, course_enrollments) recovery_email_message = recovery_email_activation_message = None if is_secondary_email_feature_enabled_for_user(user=user): try: account_recovery_obj = AccountRecovery.objects.get(user=user) except AccountRecovery.DoesNotExist: recovery_email_message = Text( _("Add a recovery email to retain access when single-sign on is not available. " "Go to {link_start}your Account Settings{link_end}.") ).format(link_start=HTML( "<a target='_blank' href='{account_setting_page}'>").format( account_setting_page=reverse('account_settings'), ), link_end=HTML("</a>")) else: if not account_recovery_obj.is_active: recovery_email_activation_message = Text( _("Recovery email is not activated yet. " "Kindly visit your email and follow the instructions to activate it." )) # Disable lookup of Enterprise consent_required_course due to ENT-727 # Will re-enable after fixing WL-1315 consent_required_courses = set() enterprise_customer_name = None # Account activation message account_activation_messages = [ message for message in messages.get_messages(request) if 'account-activation' in message.tags ] # Global staff can see what courses encountered an error on their dashboard staff_access = False errored_courses = {} if has_access(user, 'staff', 'global'): # Show any courses that encountered an error on load staff_access = True errored_courses = modulestore().get_errored_courses() show_courseware_links_for = { enrollment.course_id: has_access(request.user, 'load', enrollment.course_overview) for enrollment in course_enrollments } # Find programs associated with course runs being displayed. This information # is passed in the template context to allow rendering of program-related # information on the dashboard. meter = ProgramProgressMeter(request.site, user, enrollments=course_enrollments) ecommerce_service = EcommerceService() inverted_programs = meter.invert_programs() urls, programs_data = {}, {} bundles_on_dashboard_flag = WaffleFlag( WaffleFlagNamespace(name=u'student.experiments'), u'bundles_on_dashboard') # TODO: Delete this code and the relevant HTML code after testing LEARNER-3072 is complete if bundles_on_dashboard_flag.is_enabled( ) and inverted_programs and inverted_programs.items(): if len(course_enrollments) < 4: for program in inverted_programs.values(): try: program_uuid = program[0]['uuid'] program_data = get_programs(request.site, uuid=program_uuid) program_data = ProgramDataExtender(program_data, request.user).extend() skus = program_data.get('skus') checkout_page_url = ecommerce_service.get_checkout_page_url( *skus) program_data[ 'completeProgramURL'] = checkout_page_url + '&bundle=' + program_data.get( 'uuid') programs_data[program_uuid] = program_data except: # pylint: disable=bare-except pass # Construct a dictionary of course mode information # used to render the course list. We re-use the course modes dict # we loaded earlier to avoid hitting the database. course_mode_info = { enrollment.course_id: complete_course_mode_info( enrollment.course_id, enrollment, modes=course_modes_by_course[enrollment.course_id]) for enrollment in course_enrollments } # Determine the per-course verification status # This is a dictionary in which the keys are course locators # and the values are one of: # # VERIFY_STATUS_NEED_TO_VERIFY # VERIFY_STATUS_SUBMITTED # VERIFY_STATUS_APPROVED # VERIFY_STATUS_MISSED_DEADLINE # # Each of which correspond to a particular message to display # next to the course on the dashboard. # # If a course is not included in this dictionary, # there is no verification messaging to display. verify_status_by_course = check_verify_status_by_course( user, course_enrollments) cert_statuses = { enrollment.course_id: cert_info(request.user, enrollment.course_overview) for enrollment in course_enrollments } # only show email settings for Mongo course and when bulk email is turned on show_email_settings_for = frozenset( enrollment.course_id for enrollment in course_enrollments if (BulkEmailFlag.feature_enabled(enrollment.course_id))) # Verification Attempts # Used to generate the "you must reverify for course x" banner verification_status = IDVerificationService.user_status(user) verification_errors = get_verification_error_reasons_for_display( verification_status['error']) # Gets data for midcourse reverifications, if any are necessary or have failed statuses = ["approved", "denied", "pending", "must_reverify"] reverifications = reverification_info(statuses) block_courses = frozenset( enrollment.course_id for enrollment in course_enrollments if is_course_blocked( request, CourseRegistrationCode.objects.filter( course_id=enrollment.course_id, registrationcoderedemption__redeemed_by=request.user), enrollment.course_id)) enrolled_courses_either_paid = frozenset( enrollment.course_id for enrollment in course_enrollments if enrollment.is_paid_course()) # If there are *any* denied reverifications that have not been toggled off, # we'll display the banner denied_banner = any(item.display for item in reverifications["denied"]) # Populate the Order History for the side-bar. order_history_list = order_history(user, course_org_filter=site_org_whitelist, org_filter_out_set=site_org_blacklist) # get list of courses having pre-requisites yet to be completed courses_having_prerequisites = frozenset( enrollment.course_id for enrollment in course_enrollments if enrollment.course_overview.pre_requisite_courses) courses_requirements_not_met = get_pre_requisite_courses_not_completed( user, courses_having_prerequisites) if 'notlive' in request.GET: redirect_message = _( "The course you are looking for does not start until {date}." ).format(date=request.GET['notlive']) elif 'course_closed' in request.GET: redirect_message = _( "The course you are looking for is closed for enrollment as of {date}." ).format(date=request.GET['course_closed']) elif 'access_response_error' in request.GET: # This can be populated in a generalized way with fields from access response errors redirect_message = request.GET['access_response_error'] else: redirect_message = '' valid_verification_statuses = [ 'approved', 'must_reverify', 'pending', 'expired' ] display_sidebar_on_dashboard = ( len(order_history_list) or (verification_status['status'] in valid_verification_statuses and verification_status['should_display'])) # Filter out any course enrollment course cards that are associated with fulfilled entitlements for entitlement in [ e for e in course_entitlements if e.enrollment_course_run is not None ]: course_enrollments = [ enr for enr in course_enrollments if entitlement.enrollment_course_run.course_id != enr.course_id ] context = { 'urls': urls, 'programs_data': programs_data, 'enterprise_message': enterprise_message, 'consent_required_courses': consent_required_courses, 'enterprise_customer_name': enterprise_customer_name, 'enrollment_message': enrollment_message, 'redirect_message': redirect_message, 'account_activation_messages': account_activation_messages, 'activate_account_message': activate_account_message, 'course_enrollments': course_enrollments, 'course_entitlements': course_entitlements, 'course_entitlement_available_sessions': course_entitlement_available_sessions, 'unfulfilled_entitlement_pseudo_sessions': unfulfilled_entitlement_pseudo_sessions, 'course_optouts': course_optouts, 'staff_access': staff_access, 'errored_courses': errored_courses, 'show_courseware_links_for': show_courseware_links_for, 'all_course_modes': course_mode_info, 'cert_statuses': cert_statuses, 'credit_statuses': _credit_statuses(user, course_enrollments), 'show_email_settings_for': show_email_settings_for, 'reverifications': reverifications, 'verification_display': verification_status['should_display'], 'verification_status': verification_status['status'], 'verification_status_by_course': verify_status_by_course, 'verification_errors': verification_errors, 'block_courses': block_courses, 'denied_banner': denied_banner, 'billing_email': settings.PAYMENT_SUPPORT_EMAIL, 'user': user, 'logout_url': reverse('logout'), 'platform_name': platform_name, 'enrolled_courses_either_paid': enrolled_courses_either_paid, 'provider_states': [], 'order_history_list': order_history_list, 'courses_requirements_not_met': courses_requirements_not_met, 'nav_hidden': True, 'inverted_programs': inverted_programs, 'show_program_listing': ProgramsApiConfig.is_enabled(), 'show_journal_listing': journals_enabled(), # TODO: Dashboard Plugin required 'show_dashboard_tabs': True, 'disable_courseware_js': True, 'display_course_modes_on_dashboard': enable_verified_certificates and display_course_modes_on_dashboard, 'display_sidebar_on_dashboard': display_sidebar_on_dashboard, 'display_sidebar_account_activation_message': not (user.is_active or hide_dashboard_courses_until_activated), 'display_dashboard_courses': (user.is_active or not hide_dashboard_courses_until_activated), 'empty_dashboard_message': empty_dashboard_message, 'recovery_email_message': recovery_email_message, 'recovery_email_activation_message': recovery_email_activation_message, } if ecommerce_service.is_enabled(request.user): context.update({ 'use_ecommerce_payment_flow': True, 'ecommerce_payment_page': ecommerce_service.payment_page_url(), }) # Gather urls for course card resume buttons. resume_button_urls = ['' for entitlement in course_entitlements] for url in _get_urls_for_resume_buttons(user, course_enrollments): resume_button_urls.append(url) # eliteu membership if settings.FEATURES.get('ENABLE_MEMBERSHIP_INTEGRATION', False): from membership.models import VIPCourseEnrollment, VIPInfo vip_info = VIPInfo.objects.filter(user=user).order_by('-id').first() vces = VIPCourseEnrollment.objects.filter(user=user, is_active=True) vip_course_enrollment_ids = [v.course_id.html_id() for v in vces] context.update({ 'is_vip': VIPInfo.is_vip(user), 'vip_expired_at': vip_info and vip_info.expired_at or None, 'vip_purchase_url': reverse('membership_card'), 'vip_course_enrollment_ids': vip_course_enrollment_ids, 'display_sidebar_on_dashboard': True }) # There must be enough urls for dashboard.html. Template creates course # cards for "enrollments + entitlements". context.update({'resume_button_urls': resume_button_urls}) response = render_to_response('dashboard.html', context) set_logged_in_cookies(request, response, user) return response
def _record_feedback_in_zendesk( realname, email, subject, details, tags, additional_info, group_name=None, require_update=False, support_email=None ): """ Create a new user-requested Zendesk ticket. Once created, the ticket will be updated with a private comment containing additional information from the browser and server, such as HTTP headers and user state. Returns a boolean value indicating whether ticket creation was successful, regardless of whether the private comment update succeeded. If `group_name` is provided, attaches the ticket to the matching Zendesk group. If `require_update` is provided, returns False when the update does not succeed. This allows using the private comment to add necessary information which the user will not see in followup emails from support. """ zendesk_api = _ZendeskApi() additional_info_string = ( u"Additional information:\n\n" + u"\n".join(u"%s: %s" % (key, value) for (key, value) in additional_info.items() if value is not None) ) # Tag all issues with LMS to distinguish channel in Zendesk; requested by student support team zendesk_tags = list(tags.values()) + ["LMS"] # Per edX support, we would like to be able to route white label feedback items # via tagging white_label_org = configuration_helpers.get_value('course_org_filter') if white_label_org: zendesk_tags = zendesk_tags + ["whitelabel_{org}".format(org=white_label_org)] new_ticket = { "ticket": { "requester": {"name": realname, "email": email}, "subject": subject, "comment": {"body": details}, "tags": zendesk_tags } } group = None if group_name is not None: group = zendesk_api.get_group(group_name) if group is not None: new_ticket['ticket']['group_id'] = group['id'] if support_email is not None: # If we do not include the `recipient` key here, Zendesk will default to using its default reply # email address when support agents respond to tickets. By setting the `recipient` key here, # we can ensure that WL site users are responded to via the correct Zendesk support email address. new_ticket['ticket']['recipient'] = support_email try: ticket_id = zendesk_api.create_ticket(new_ticket) if group_name is not None and group is None: # Support uses Zendesk groups to track tickets. In case we # haven't been able to correctly group this ticket, log its ID # so it can be found later. log.warning('Unable to find group named %s for Zendesk ticket with ID %s.', group_name, ticket_id) except zendesk.ZendeskError: log.exception("Error creating Zendesk ticket") return False # Additional information is provided as a private update so the information # is not visible to the user. ticket_update = {"ticket": {"comment": {"public": False, "body": additional_info_string}}} try: zendesk_api.update_ticket(ticket_id, ticket_update) except zendesk.ZendeskError: log.exception("Error updating Zendesk ticket with ID %s.", ticket_id) # The update is not strictly necessary, so do not indicate # failure to the user unless it has been requested with # `require_update`. if require_update: return False return True
def login_and_registration_form(request, initial_mode="login"): """Render the combined login/registration form, defaulting to login This relies on the JS to asynchronously load the actual form from the user_api. Keyword Args: initial_mode (string): Either "login" or "register". """ # Determine the URL to redirect to following login/registration/third_party_auth redirect_to = get_next_url_for_login_page(request) # If we're already logged in, redirect to the dashboard if request.user.is_authenticated(): return redirect(redirect_to) # Retrieve the form descriptions from the user API form_descriptions = _get_form_descriptions(request) # If this is a themed site, revert to the old login/registration pages. # We need to do this for now to support existing themes. # Themed sites can use the new logistration page by setting # 'ENABLE_COMBINED_LOGIN_REGISTRATION' in their # configuration settings. if is_request_in_themed_site() and not configuration_helpers.get_value( 'ENABLE_COMBINED_LOGIN_REGISTRATION', False): if initial_mode == "login": return old_login_view(request) elif initial_mode == "register": return old_register_view(request) # Allow external auth to intercept and handle the request ext_auth_response = _external_auth_intercept(request, initial_mode) if ext_auth_response is not None: return ext_auth_response # Our ?next= URL may itself contain a parameter 'tpa_hint=x' that we need to check. # If present, we display a login page focused on third-party auth with that provider. third_party_auth_hint = None if '?' in redirect_to: try: next_args = urlparse.parse_qs(urlparse.urlparse(redirect_to).query) provider_id = next_args['tpa_hint'][0] if third_party_auth.provider.Registry.get(provider_id=provider_id): third_party_auth_hint = provider_id initial_mode = "hinted_login" except (KeyError, ValueError, IndexError): pass # Otherwise, render the combined login/registration page context = { 'data': { 'login_redirect_url': redirect_to, 'initial_mode': initial_mode, 'third_party_auth': _third_party_auth_context(request, redirect_to), 'third_party_auth_hint': third_party_auth_hint or '', 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'support_link': configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK), # Include form descriptions retrieved from the user API. # We could have the JS client make these requests directly, # but we include them in the initial page load to avoid # the additional round-trip to the server. 'login_form_desc': json.loads(form_descriptions['login']), 'registration_form_desc': json.loads(form_descriptions['registration']), 'password_reset_form_desc': json.loads(form_descriptions['password_reset']), }, 'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in header 'responsive': True, 'allow_iframing': True, 'disable_courseware_js': True, 'disable_footer': not configuration_helpers.get_value( 'ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER', settings.FEATURES['ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER']), } return render_to_response('student_account/login_and_register.html', context)
def account_settings_context(request): """ Context for the account settings page. Args: request: The request object. Returns: dict """ user = request.user year_of_birth_options = [(unicode(year), unicode(year)) for year in UserProfile.VALID_YEARS] try: user_orders = get_user_orders(user) except: # pylint: disable=bare-except log.exception('Error fetching order history from Otto.') # Return empty order list as account settings page expect a list and # it will be broken if exception raised user_orders = [] context = { 'auth': {}, 'duplicate_provider': None, 'nav_hidden': True, 'fields': { 'country': { 'options': list(countries), }, 'gender': { 'options': [(choice[0], _(choice[1])) for choice in UserProfile.GENDER_CHOICES], # pylint: disable=translation-of-non-string }, 'language': { 'options': released_languages(), }, 'level_of_education': { 'options': [(choice[0], _(choice[1])) for choice in UserProfile.LEVEL_OF_EDUCATION_CHOICES], # pylint: disable=translation-of-non-string }, 'password': { 'url': reverse('password_reset'), }, 'year_of_birth': { 'options': year_of_birth_options, }, 'preferred_language': { 'options': all_languages(), }, 'time_zone': { 'options': TIME_ZONE_CHOICES, } }, 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'user_accounts_api_url': reverse("accounts_api", kwargs={'username': user.username}), 'user_preferences_api_url': reverse('preferences_api', kwargs={'username': user.username}), 'disable_courseware_js': True, 'show_program_listing': ProgramsApiConfig.current().show_program_listing, 'order_history': user_orders } if third_party_auth.is_enabled(): # If the account on the third party provider is already connected with another edX account, # we display a message to the user. context['duplicate_provider'] = pipeline.get_duplicate_provider( messages.get_messages(request)) auth_states = pipeline.get_provider_user_states(user) context['auth']['providers'] = [ { 'id': state.provider.provider_id, 'name': state.provider.name, # The name of the provider e.g. Facebook 'connected': state. has_account, # Whether the user's edX account is connected with the provider. # If the user is not connected, they should be directed to this page to authenticate # with the particular provider, as long as the provider supports initiating a login. 'connect_url': pipeline.get_login_url( state.provider.provider_id, pipeline.AUTH_ENTRY_ACCOUNT_SETTINGS, # The url the user should be directed to after the auth process has completed. redirect_url=reverse('account_settings'), ), 'accepts_logins': state.provider.accepts_logins, # If the user is connected, sending a POST request to this url removes the connection # information for this provider from their edX account. 'disconnect_url': pipeline.get_disconnect_url(state.provider.provider_id, state.association_id), # We only want to include providers if they are either currently available to be logged # in with, or if the user is already authenticated with them. } for state in auth_states if state.provider.display_for_login or state.has_account ] return context
'packages': ('openassessment', ), } urlpatterns += (url(r'^openassessment/fileupload/', include('openassessment.fileupload.urls')), ) # sysadmin dashboard, to see what courses are loaded, to delete & load courses if settings.FEATURES["ENABLE_SYSADMIN_DASHBOARD"]: urlpatterns += (url(r'^sysadmin/', include('dashboard.sysadmin_urls')), ) urlpatterns += (url( r'^support/', include('support.urls', app_name="support", namespace='support')), ) # Favicon favicon_path = configuration_helpers.get_value('favicon_path', settings.FAVICON_PATH) # pylint: disable=invalid-name urlpatterns += (url( r'^favicon\.ico$', RedirectView.as_view(url=settings.STATIC_URL + favicon_path, permanent=True)), ) # Multicourse wiki (Note: wiki urls must be above the courseware ones because of # the custom tab catch-all) if settings.WIKI_ENABLED: from wiki.urls import get_pattern as wiki_pattern from django_notify.urls import get_pattern as notify_pattern urlpatterns += ( # First we include views from course_wiki that we use to override the default views. # They come first in the urlpatterns so they get resolved first url('^wiki/create-root/$',
def create_account(username, password, email): """Create a new user account. This will implicitly create an empty profile for the user. WARNING: This function does NOT yet implement all the features in `student/views.py`. Until it does, please use this method ONLY for tests of the account API, not in production code. In particular, these are currently missing: * 3rd party auth * External auth (shibboleth) * Complex password policies (ENFORCE_PASSWORD_POLICY) In addition, we assume that some functionality is handled at higher layers: * Analytics events * Activation email * Terms of service / honor code checking * Recording demographic info (use profile API) * Auto-enrollment in courses (if invited via instructor dash) Args: username (unicode): The username for the new account. password (unicode): The user's password. email (unicode): The email address associated with the account. Returns: unicode: an activation key for the account. Raises: errors.AccountUserAlreadyExists errors.AccountUsernameInvalid errors.AccountEmailInvalid errors.AccountPasswordInvalid errors.UserAPIInternalError: the operation failed due to an unexpected error. """ # Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation if not configuration_helpers.get_value( 'ALLOW_PUBLIC_ACCOUNT_CREATION', settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True) ): return HttpResponseForbidden(_("Account creation not allowed.")) if waffle().is_enabled(PREVENT_AUTH_USER_WRITES): raise errors.UserAPIInternalError(SYSTEM_MAINTENANCE_MSG) # Validate the username, password, and email # This will raise an exception if any of these are not in a valid format. _validate_username(username) _validate_password(password, username) _validate_email(email) # Create the user account, setting them to "inactive" until they activate their account. user = User(username=username, email=email, is_active=False) user.set_password(password) try: user.save() except IntegrityError: raise errors.AccountUserAlreadyExists # Create a registration to track the activation process # This implicitly saves the registration. registration = Registration() registration.register(user) # Create an empty user profile with default values UserProfile(user=user).save() # Return the activation key, which the caller should send to the user return registration.activation_key
def public_api_url(self): """ Publicly-accessible API URL root. """ root = helpers.get_value('CREDENTIALS_PUBLIC_SERVICE_URL', settings.CREDENTIALS_PUBLIC_SERVICE_URL) return urljoin(root, '/api/{}/'.format(API_VERSION))
def internal_api_url(self): """ Internally-accessible API URL root. """ root = helpers.get_value('CREDENTIALS_INTERNAL_SERVICE_URL', settings.CREDENTIALS_INTERNAL_SERVICE_URL) return urljoin(root, '/api/{}/'.format(API_VERSION))
def get(self, request): """Return a description of the login form. This decouples clients from the API definition: if the API decides to modify the form, clients won't need to be updated. See `user_api.helpers.FormDescription` for examples of the JSON-encoded form description. Returns: HttpResponse """ form_desc = FormDescription("post", reverse("user_api_login_session")) # Translators: This label appears above a field on the login form # meant to hold the user's email address. email_label = _(u"Email") # Translators: This example email address is used as a placeholder in # a field on the login form meant to hold the user's email address. email_placeholder = _(u"*****@*****.**") # Translators: These instructions appear on the login form, immediately # below a field meant to hold the user's email address. email_instructions = _( "The email address you used to register with {platform_name}" ).format(platform_name=configuration_helpers.get_value( 'PLATFORM_NAME', settings.PLATFORM_NAME)) form_desc.add_field("email", field_type="email", label=email_label, placeholder=email_placeholder, instructions=email_instructions, restrictions={ "min_length": accounts.EMAIL_MIN_LENGTH, "max_length": accounts.EMAIL_MAX_LENGTH, }) # Translators: This label appears above a field on the login form # meant to hold the user's password. password_label = _(u"Password") form_desc.add_field("password", label=password_label, field_type="password", restrictions={ "min_length": accounts.PASSWORD_MIN_LENGTH, "max_length": accounts.PASSWORD_MAX_LENGTH, }) form_desc.add_field( "remember", field_type="checkbox", label=_("Remember me"), default=False, required=False, ) return HttpResponse(form_desc.to_json(), content_type="application/json")
def submit_feedback(request): """ Create a Zendesk ticket or if not available, send an email with the feedback form fields. If feedback submission is not enabled, any request will raise `Http404`. If any configuration parameter (`ZENDESK_URL`, `ZENDESK_USER`, or `ZENDESK_API_KEY`) is missing, any request will raise an `Exception`. The request must be a POST request specifying `subject` and `details`. If the user is not authenticated, the request must also specify `name` and `email`. If the user is authenticated, the `name` and `email` will be populated from the user's information. If any required parameter is missing, a 400 error will be returned indicating which field is missing and providing an error message. If Zendesk ticket creation fails, 500 error will be returned with no body; if ticket creation succeeds, an empty successful response (200) will be returned. """ if not settings.FEATURES.get('ENABLE_FEEDBACK_SUBMISSION', False): raise Http404() if request.method != "POST": return HttpResponseNotAllowed(["POST"]) def build_error_response(status_code, field, err_msg): return HttpResponse(json.dumps({"field": field, "error": err_msg}), status=status_code) required_fields = ["subject", "details"] if not UserProfile.has_registered(request.user): required_fields += ["name", "email"] required_field_errs = { "subject": "Please provide a subject.", "details": "Please provide details.", "name": "Please provide your name.", "email": "Please provide a valid e-mail.", } for field in required_fields: if field not in request.POST or not request.POST[field]: return build_error_response(400, field, required_field_errs[field]) if not UserProfile.has_registered(request.user): try: validate_email(request.POST["email"]) except ValidationError: return build_error_response(400, "email", required_field_errs["email"]) success = False context = get_feedback_form_context(request) support_backend = configuration_helpers.get_value('CONTACT_FORM_SUBMISSION_BACKEND', SUPPORT_BACKEND_ZENDESK) if support_backend == SUPPORT_BACKEND_EMAIL: try: send_mail( subject=render_to_string('emails/contact_us_feedback_email_subject.txt', context), message=render_to_string('emails/contact_us_feedback_email_body.txt', context), from_email=context["support_email"], recipient_list=[context["support_email"]], fail_silently=False ) success = True except SMTPException: log.exception('Error sending feedback to contact_us email address.') success = False else: if not settings.ZENDESK_URL or not settings.ZENDESK_USER or not settings.ZENDESK_API_KEY: raise Exception("Zendesk enabled but not configured") success = _record_feedback_in_zendesk( context["realname"], context["email"], context["subject"], context["details"], context["tags"], context["additional_info"], support_email=context["support_email"] ) _record_feedback_in_datadog(context["tags"]) return HttpResponse(status=(200 if success else 500))
def get( self, request, course_id, always_show_payment=False, current_step=None, message=FIRST_TIME_VERIFY_MSG ): """ Render the payment and verification flow. 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 if course is None: log.warn(u"Could not find course with ID %s.", course_id) 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) # If the verification deadline has passed # then show the user a message that he/she can't verify. # # We're making the assumptions (enforced in Django admin) that: # # 1) Only verified modes have verification deadlines. # # 2) If set, verification deadlines are always AFTER upgrade deadlines, because why would you # let someone upgrade into a verified track if they can't complete verification? # verification_deadline = VerificationDeadline.deadline_for_course(course.id) response = self._response_if_deadline_passed(course, self.VERIFICATION_DEADLINE, verification_deadline) if response is not None: log.info(u"Verification deadline for '%s' has passed.", course.id) return response # Retrieve the relevant course mode for the payment/verification flow. # # WARNING: this is technical debt! A much better way to do this would be to # separate out the payment flow and use the product SKU to figure out what # the user is trying to purchase. # # Nonetheless, for the time being we continue to make the really ugly assumption # that at some point there was a paid course mode we can query for the price. relevant_course_mode = self._get_paid_mode(course_key) # If we can find a relevant course mode, then log that we're entering the flow # Otherwise, this course does not support payment/verification, so respond with a 404. if relevant_course_mode is not None: if CourseMode.is_verified_mode(relevant_course_mode): log.info( u"Entering payment and verification flow for user '%s', course '%s', with current step '%s'.", request.user.id, course_id, current_step ) else: log.info( u"Entering payment flow for user '%s', course '%s', with current step '%s'", request.user.id, course_id, current_step ) 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 # If the user is trying to *pay* and the upgrade deadline has passed, # then they shouldn't be able to enter the flow. # # NOTE: This should match the availability dates used by the E-Commerce service # to determine whether a user can purchase a product. The idea is that if the service # won't fulfill the order, we shouldn't even let the user get into the payment flow. # user_is_trying_to_pay = message in [self.FIRST_TIME_VERIFY_MSG, self.UPGRADE_MSG] if user_is_trying_to_pay: upgrade_deadline = relevant_course_mode.expiration_datetime response = self._response_if_deadline_passed(course, self.UPGRADE_DEADLINE, upgrade_deadline) if response is not None: log.info(u"Upgrade deadline for '%s' has passed.", course.id) return response # 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(relevant_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. sku_to_use = relevant_course_mode.sku purchase_workflow = request.GET.get('purchase_workflow', 'single') if purchase_workflow == 'bulk' and relevant_course_mode.bulk_sku: sku_to_use = relevant_course_mode.bulk_sku redirect_response = self._redirect_if_necessary( message, already_verified, already_paid, is_enrolled, course_key, user_is_trying_to_pay, request.user, sku_to_use ) if redirect_response is not None: return redirect_response display_steps = self._display_steps( always_show_payment, already_verified, already_paid, relevant_course_mode ) # Override the actual value if account activation has been disabled # Also see the reference to this parameter in context dictionary further down user_is_active = self._get_user_active_status(request.user) requirements = self._requirements(display_steps, 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) # get available payment processors if relevant_course_mode.sku: # transaction will be conducted via ecommerce service processors = ecommerce_api_client(request.user).payment.processors.get() else: # transaction will be conducted using legacy shopping cart processors = [settings.CC_PROCESSOR_NAME] # Render the top-level page context = { 'contribution_amount': contribution_amount, 'course': course, 'course_key': unicode(course_key), 'checkpoint_location': request.GET.get('checkpoint'), 'course_mode': relevant_course_mode, 'courseware_url': courseware_url, 'current_step': current_step, 'disable_courseware_js': True, 'display_steps': display_steps, 'is_active': json.dumps(user_is_active), 'user_email': request.user.email, 'message_key': message, 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'processors': processors, 'requirements': requirements, 'user_full_name': full_name, 'verification_deadline': verification_deadline or "", 'already_verified': already_verified, 'verification_good_until': verification_good_until, 'capture_sound': staticfiles_storage.url("audio/camera_capture.wav"), 'nav_hidden': True, 'is_ab_testing': 'begin-flow' in request.path, } return render_to_response("verify_student/pay_and_verify.html", context)
def tma_users_registered(self): context = {} enrolled_to_current_course = False user_email = self.request.POST['user_email'] if User.objects.filter(email=user_email).exists(): user = User.objects.get(email=user_email) #Get preprofile info userprofile = UserProfile.objects.get(user=user) try: custom_field = json.loads(user.profile.custom_field) first_name = custom_field['first_name'] last_name = custom_field['last_name'] except: custom_field = {} last_name = 'Undefined' first_name = 'Undefined' #Get courses enrollments microsite_courses = get_courses( user=user, org=configuration_helpers.get_value('course_org_filter')) user_ms_course_list = {} for course in microsite_courses: if CourseEnrollment.objects.filter( user=user, course_id=course.id).exists(): user_ms_course_list[str(course.id)] = { 'course_name': course.display_name_with_default, 'course_grades': '/courses/' + str(course.id) + '/progress/' + str(user.id), 'opened_enrollments': is_enrollment_opened(course), 'opened_course': is_course_opened(course), 'on_invitation': course.invitation_only, } #Get user grade for this course course_key = SlashSeparatedCourseKey.from_deprecated_string( self.course_id) current_course = get_course_by_id(course_key) grade = CourseGradeFactory().create(user, current_course) #User dates if user.last_login is not None: last_login = user.date_joined.strftime("%d-%b-%Y %H:%M:%S") else: last_login = _('User has not logged in yet') context = { 'email': str(user_email), 'id': str(user.id), 'inscription': str(user.date_joined.strftime("%d-%b-%Y %H:%M:%S")), 'last_login': last_login, 'first_name': first_name, 'last_name': last_name, 'user_ms_course_list': user_ms_course_list, 'custom_field': custom_field, 'grade': grade.grade_value['percent'], 'passed': grade.grade_value['grade'], 'active': user.is_active, 'login_failure': LoginFailures.is_user_locked_out(user), } #Check if user is registered to this course course_key = SlashSeparatedCourseKey.from_deprecated_string( self.course_id) if CourseEnrollment.objects.filter(user=user, course_id=course_key, is_active=1).exists(): current_course_grades = '/courses/' + str( self.course_id) + '/progress/' + str(user.id) context['enrolled_to_current_course'] = True context['current_course_grades'] = current_course_grades else: context['enrolled_to_current_course'] = False else: context = { 'error': 'Le participant n\'a pas de compte sur nos plateformes.' } return context
def instructor_dashboard_2(request, course_id): # lint-amnesty, pylint: disable=too-many-statements """ Display the instructor dashboard for a course. """ try: course_key = CourseKey.from_string(course_id) except InvalidKeyError: log.error( "Unable to find course with course key %s while loading the Instructor Dashboard.", course_id) return HttpResponseServerError() course = get_course_by_id(course_key, depth=0) access = { 'admin': request.user.is_staff, 'instructor': bool(has_access(request.user, 'instructor', course)), 'finance_admin': CourseFinanceAdminRole(course_key).has_user(request.user), 'sales_admin': CourseSalesAdminRole(course_key).has_user(request.user), 'staff': bool(has_access(request.user, 'staff', course)), 'forum_admin': has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR), 'data_researcher': request.user.has_perm(permissions.CAN_RESEARCH, course_key), } if not request.user.has_perm(permissions.VIEW_DASHBOARD, course_key): raise Http404() is_white_label = CourseMode.is_white_label(course_key) # lint-amnesty, pylint: disable=unused-variable reports_enabled = configuration_helpers.get_value('SHOW_ECOMMERCE_REPORTS', False) # lint-amnesty, pylint: disable=unused-variable sections = [] if access['staff']: sections.extend([ _section_course_info(course, access), _section_membership(course, access), _section_cohort_management(course, access), _section_discussions_management(course, access), _section_student_admin(course, access), ]) if access['data_researcher']: sections.append(_section_data_download(course, access)) analytics_dashboard_message = None if show_analytics_dashboard_message(course_key) and (access['staff'] or access['instructor']): # Construct a URL to the external analytics dashboard analytics_dashboard_url = '{}/courses/{}'.format( settings.ANALYTICS_DASHBOARD_URL, str(course_key)) link_start = HTML("<a href=\"{}\" rel=\"noopener\" target=\"_blank\">" ).format(analytics_dashboard_url) analytics_dashboard_message = _( "To gain insights into student enrollment and participation {link_start}" "visit {analytics_dashboard_name}, our new course analytics product{link_end}." ) analytics_dashboard_message = Text(analytics_dashboard_message).format( link_start=link_start, link_end=HTML("</a>"), analytics_dashboard_name=settings.ANALYTICS_DASHBOARD_NAME) # Temporarily show the "Analytics" section until we have a better way of linking to Insights sections.append(_section_analytics(course, access)) # Check if there is corresponding entry in the CourseMode Table related to the Instructor Dashboard course course_mode_has_price = False # lint-amnesty, pylint: disable=unused-variable paid_modes = CourseMode.paid_modes_for_course(course_key) if len(paid_modes) == 1: course_mode_has_price = True elif len(paid_modes) > 1: log.error( "Course %s has %s course modes with payment options. Course must only have " "one paid course mode to enable eCommerce options.", str(course_key), len(paid_modes)) if access['instructor'] and is_enabled_for_course(course_key): sections.insert(3, _section_extensions(course)) # Gate access to course email by feature flag & by course-specific authorization if is_bulk_email_feature_enabled(course_key) and (access['staff'] or access['instructor']): sections.append(_section_send_email(course, access)) # Gate access to Special Exam tab depending if either timed exams or proctored exams # are enabled in the course user_has_access = any([ request.user.is_staff, CourseStaffRole(course_key).has_user(request.user), CourseInstructorRole(course_key).has_user(request.user) ]) course_has_special_exams = course.enable_proctored_exams or course.enable_timed_exams can_see_special_exams = course_has_special_exams and user_has_access and settings.FEATURES.get( 'ENABLE_SPECIAL_EXAMS', False) if can_see_special_exams: sections.append(_section_special_exams(course, access)) # Certificates panel # This is used to generate example certificates # and enable self-generated certificates for a course. # Note: This is hidden for all CCXs certs_enabled = CertificateGenerationConfiguration.current( ).enabled and not hasattr(course_key, 'ccx') if certs_enabled and access['admin']: sections.append(_section_certificates(course)) openassessment_blocks = modulestore().get_items( course_key, qualifiers={'category': 'openassessment'}) # filter out orphaned openassessment blocks openassessment_blocks = [ block for block in openassessment_blocks if block.parent is not None ] if len(openassessment_blocks) > 0 and access['staff']: sections.append( _section_open_response_assessment(request, course, openassessment_blocks, access)) disable_buttons = not CourseEnrollment.objects.is_small_course(course_key) certificate_white_list = CertificateWhitelist.get_certificate_white_list( course_key) generate_certificate_exceptions_url = reverse( 'generate_certificate_exceptions', kwargs={ 'course_id': str(course_key), 'generate_for': '' }) generate_bulk_certificate_exceptions_url = reverse( 'generate_bulk_certificate_exceptions', kwargs={'course_id': str(course_key)}) certificate_exception_view_url = reverse( 'certificate_exception_view', kwargs={'course_id': str(course_key)}) certificate_invalidation_view_url = reverse( 'certificate_invalidation_view', kwargs={'course_id': str(course_key)}) certificate_invalidations = CertificateInvalidation.get_certificate_invalidations( course_key) context = { 'course': course, 'studio_url': get_studio_url(course, 'course'), 'sections': sections, 'disable_buttons': disable_buttons, 'analytics_dashboard_message': analytics_dashboard_message, 'certificate_white_list': certificate_white_list, 'certificate_invalidations': certificate_invalidations, 'generate_certificate_exceptions_url': generate_certificate_exceptions_url, 'generate_bulk_certificate_exceptions_url': generate_bulk_certificate_exceptions_url, 'certificate_exception_view_url': certificate_exception_view_url, 'certificate_invalidation_view_url': certificate_invalidation_view_url, 'xqa_server': settings.FEATURES.get('XQA_SERVER', "http://your_xqa_server.com"), } return render_to_response( 'instructor/instructor_dashboard_2/instructor_dashboard_2.html', context)
def get_enrollment_info(self, user, course_id): """ Returns the User Enrollment information. """ course = get_course_by_id(course_id, depth=0) is_course_staff = bool(has_access(user, 'staff', course)) manual_enrollment_reason = 'N/A' # check the user enrollment role if user.is_staff: platform_name = configuration_helpers.get_value( 'platform_name', settings.PLATFORM_NAME) enrollment_role = _('{platform_name} Staff').format( platform_name=platform_name) elif is_course_staff: enrollment_role = _('Course Staff') else: enrollment_role = _('Student') course_enrollment = CourseEnrollment.get_enrollment( user=user, course_key=course_id) if is_course_staff: enrollment_source = _('Staff') else: # get the registration_code_redemption object if exists registration_code_redemption = RegistrationCodeRedemption.registration_code_used_for_enrollment( course_enrollment) # get the paid_course registration item if exists paid_course_reg_item = PaidCourseRegistration.get_course_item_for_user_enrollment( user=user, course_id=course_id, course_enrollment=course_enrollment) # from where the user get here if registration_code_redemption is not None: enrollment_source = _('Used Registration Code') elif paid_course_reg_item is not None: enrollment_source = _('Credit Card - Individual') else: manual_enrollment = ManualEnrollmentAudit.get_manual_enrollment( course_enrollment) if manual_enrollment is not None: enrollment_source = _( 'manually enrolled by username: {username}').format( username=manual_enrollment.enrolled_by.username) manual_enrollment_reason = manual_enrollment.reason else: enrollment_source = _('Manually Enrolled') enrollment_date = course_enrollment.created.strftime("%B %d, %Y") currently_enrolled = course_enrollment.is_active course_enrollment_data = collections.OrderedDict() course_enrollment_data['Enrollment Date'] = enrollment_date course_enrollment_data['Currently Enrolled'] = currently_enrolled course_enrollment_data['Enrollment Source'] = enrollment_source course_enrollment_data[ 'Manual (Un)Enrollment Reason'] = manual_enrollment_reason course_enrollment_data['Enrollment Role'] = enrollment_role return course_enrollment_data
def do_create_account(form, custom_form=None): """ Given cleaned post variables, create the User and UserProfile objects, as well as the registration for this user. Returns a tuple (User, UserProfile, Registration). Note: this function is also used for creating test users. """ # Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation if not configuration_helpers.get_value( 'ALLOW_PUBLIC_ACCOUNT_CREATION', settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True) ): raise PermissionDenied() errors = {} errors.update(form.errors) if custom_form: errors.update(custom_form.errors) if errors: raise ValidationError(errors) proposed_username = form.cleaned_data["username"] user = User( username=proposed_username, email=form.cleaned_data["email"], is_active=False ) user.set_password(form.cleaned_data["password"]) registration = Registration() # TODO: Rearrange so that if part of the process fails, the whole process fails. # Right now, we can have e.g. no registration e-mail sent out and a zombie account try: with transaction.atomic(): user.save() if custom_form: custom_model = custom_form.save(commit=False) custom_model.user = user custom_model.save() except IntegrityError: # Figure out the cause of the integrity error # TODO duplicate email is already handled by form.errors above as a ValidationError. # The checks for duplicate email/username should occur in the same place with an # AccountValidationError and a consistent user message returned (i.e. both should # return "It looks like {username} belongs to an existing account. Try again with a # different username.") if User.objects.filter(username=user.username): raise AccountValidationError( USERNAME_EXISTS_MSG_FMT.format(username=proposed_username), field="username" ) elif email_exists_or_retired(user.email): raise AccountValidationError( _("An account with the Email '{email}' already exists.").format(email=user.email), field="email" ) else: raise # add this account creation to password history # NOTE, this will be a NOP unless the feature has been turned on in configuration password_history_entry = PasswordHistory() password_history_entry.create(user) registration.register(user) profile_fields = [ "name", "level_of_education", "gender", "mailing_address", "city", "country", "goals", "year_of_birth" ] profile = UserProfile( user=user, **{key: form.cleaned_data.get(key) for key in profile_fields} ) extended_profile = form.cleaned_extended_profile if extended_profile: profile.meta = json.dumps(extended_profile) try: profile.save() except Exception: log.exception("UserProfile creation failed for user {id}.".format(id=user.id)) raise return user, profile, registration
def third_party_auth_context(request, redirect_to, tpa_hint=None): """ Context for third party auth providers and the currently running pipeline. Arguments: request (HttpRequest): The request, used to determine if a pipeline is currently running. redirect_to: The URL to send the user to following successful authentication. tpa_hint (string): An override flag that will return a matching provider as long as its configuration has been enabled Returns: dict """ context = { "currentProvider": None, "platformName": configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), "providers": [], "secondaryProviders": [], "finishAuthUrl": None, "errorMessage": None, "registerFormSubmitButtonText": _("Create Account"), "syncLearnerProfileData": False, "pipeline_user_details": {} } if third_party_auth.is_enabled(): for enabled in third_party_auth.provider.Registry.displayed_for_login( tpa_hint=tpa_hint): info = { "id": enabled.provider_id, "name": enabled.name, "iconClass": enabled.icon_class or None, "iconImage": enabled.icon_image.url if enabled.icon_image else None, "skipHintedLogin": enabled.skip_hinted_login_dialog, "loginUrl": pipeline.get_login_url( enabled.provider_id, pipeline.AUTH_ENTRY_LOGIN, redirect_url=redirect_to, ), "registerUrl": pipeline.get_login_url( enabled.provider_id, pipeline.AUTH_ENTRY_REGISTER, redirect_url=redirect_to, ), } context["providers" if not enabled. secondary else "secondaryProviders"].append(info) running_pipeline = pipeline.get(request) if running_pipeline is not None: current_provider = third_party_auth.provider.Registry.get_from_pipeline( running_pipeline) user_details = running_pipeline['kwargs']['details'] if user_details: context['pipeline_user_details'] = user_details if current_provider is not None: context["currentProvider"] = current_provider.name context["finishAuthUrl"] = pipeline.get_complete_url( current_provider.backend_name) context[ "syncLearnerProfileData"] = current_provider.sync_learner_profile_data if current_provider.skip_registration_form: # As a reliable way of "skipping" the registration form, we just submit it automatically context["autoSubmitRegForm"] = True # Check for any error messages we may want to display: for msg in messages.get_messages(request): if msg.extra_tags.split()[0] == "social-auth": # msg may or may not be translated. Try translating [again] in case we are able to: context["errorMessage"] = _(six.text_type(msg)) # pylint: disable=E7610 break return context
def should_redirect_to_profile_microfrontend(): return (configuration_helpers.get_value('ENABLE_PROFILE_MICROFRONTEND') and REDIRECT_TO_PROFILE_MICROFRONTEND.is_enabled())
def instructor_dashboard_2(request, course_id): """ Display the instructor dashboard for a course. """ try: course_key = CourseKey.from_string(course_id) except InvalidKeyError: log.error( u"Unable to find course with course key %s while loading the Instructor Dashboard.", course_id) return HttpResponseServerError() course = get_course_by_id(course_key, depth=0) access = { 'admin': request.user.is_staff, 'instructor': bool(has_access(request.user, 'instructor', course)), 'finance_admin': CourseFinanceAdminRole(course_key).has_user(request.user), 'sales_admin': CourseSalesAdminRole(course_key).has_user(request.user), 'staff': bool(has_access(request.user, 'staff', course)), 'forum_admin': has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR), } if not access['staff']: raise Http404() is_white_label = CourseMode.is_white_label(course_key) reports_enabled = configuration_helpers.get_value('SHOW_ECOMMERCE_REPORTS', False) sections = [ _section_course_info(course, access), _section_membership(course, access, is_white_label), _section_cohort_management(course, access), _section_discussions_management(course, access), _section_student_admin(course, access), _section_data_download(course, access), ] analytics_dashboard_message = None if show_analytics_dashboard_message(course_key): # Construct a URL to the external analytics dashboard analytics_dashboard_url = '{0}/courses/{1}'.format( settings.ANALYTICS_DASHBOARD_URL, unicode(course_key)) link_start = HTML("<a href=\"{}\" target=\"_blank\">").format( analytics_dashboard_url) analytics_dashboard_message = _( "To gain insights into student enrollment and participation {link_start}" "visit {analytics_dashboard_name}, our new course analytics product{link_end}." ) analytics_dashboard_message = Text(analytics_dashboard_message).format( link_start=link_start, link_end=HTML("</a>"), analytics_dashboard_name=settings.ANALYTICS_DASHBOARD_NAME) # Temporarily show the "Analytics" section until we have a better way of linking to Insights sections.append(_section_analytics(course, access)) # Check if there is corresponding entry in the CourseMode Table related to the Instructor Dashboard course course_mode_has_price = False paid_modes = CourseMode.paid_modes_for_course(course_key) if len(paid_modes) == 1: course_mode_has_price = True elif len(paid_modes) > 1: log.error( u"Course %s has %s course modes with payment options. Course must only have " u"one paid course mode to enable eCommerce options.", unicode(course_key), len(paid_modes)) if settings.FEATURES.get('INDIVIDUAL_DUE_DATES') and access['instructor']: sections.insert(3, _section_extensions(course)) # Gate access to course email by feature flag & by course-specific authorization if BulkEmailFlag.feature_enabled(course_key): sections.append(_section_send_email(course, access)) # Gate access to Metrics tab by featue flag and staff authorization if settings.FEATURES['CLASS_DASHBOARD'] and access['staff']: sections.append(_section_metrics(course, access)) # Gate access to Ecommerce tab if course_mode_has_price and (access['finance_admin'] or access['sales_admin']): sections.append( _section_e_commerce(course, access, paid_modes[0], is_white_label, reports_enabled)) # Gate access to Special Exam tab depending if either timed exams or proctored exams # are enabled in the course # NOTE: For now, if we only have procotred exams enabled, then only platform Staff # (user.is_staff) will be able to view the special exams tab. This may # change in the future can_see_special_exams = ( ((course.enable_proctored_exams and request.user.is_staff) or course.enable_timed_exams) and settings.FEATURES.get('ENABLE_SPECIAL_EXAMS', False)) if can_see_special_exams: sections.append(_section_special_exams(course, access)) # Certificates panel # This is used to generate example certificates # and enable self-generated certificates for a course. # Note: This is hidden for all CCXs certs_enabled = CertificateGenerationConfiguration.current( ).enabled and not hasattr(course_key, 'ccx') if certs_enabled and access['admin']: sections.append(_section_certificates(course)) openassessment_blocks = modulestore().get_items( course_key, qualifiers={'category': 'openassessment'}) # filter out orphaned openassessment blocks openassessment_blocks = [ block for block in openassessment_blocks if block.parent is not None ] if len(openassessment_blocks) > 0: sections.append( _section_open_response_assessment(request, course, openassessment_blocks, access)) disable_buttons = not _is_small_course(course_key) certificate_white_list = CertificateWhitelist.get_certificate_white_list( course_key) generate_certificate_exceptions_url = reverse( # pylint: disable=invalid-name 'generate_certificate_exceptions', kwargs={ 'course_id': unicode(course_key), 'generate_for': '' }) generate_bulk_certificate_exceptions_url = reverse( # pylint: disable=invalid-name 'generate_bulk_certificate_exceptions', kwargs={'course_id': unicode(course_key)}) certificate_exception_view_url = reverse( 'certificate_exception_view', kwargs={'course_id': unicode(course_key)}) certificate_invalidation_view_url = reverse( # pylint: disable=invalid-name 'certificate_invalidation_view', kwargs={'course_id': unicode(course_key)}) certificate_invalidations = CertificateInvalidation.get_certificate_invalidations( course_key) context = { 'course': course, 'studio_url': get_studio_url(course, 'course'), 'sections': sections, 'disable_buttons': disable_buttons, 'analytics_dashboard_message': analytics_dashboard_message, 'certificate_white_list': certificate_white_list, 'certificate_invalidations': certificate_invalidations, 'generate_certificate_exceptions_url': generate_certificate_exceptions_url, 'generate_bulk_certificate_exceptions_url': generate_bulk_certificate_exceptions_url, 'certificate_exception_view_url': certificate_exception_view_url, 'certificate_invalidation_view_url': certificate_invalidation_view_url, } return render_to_response( 'instructor/instructor_dashboard_2/instructor_dashboard_2.html', context)
def confirm_email_change(request, key): """ User requested a new e-mail. This is called when the activation link is clicked. We confirm with the old e-mail, and update """ with transaction.atomic(): try: pec = PendingEmailChange.objects.get(activation_key=key) except PendingEmailChange.DoesNotExist: response = render_to_response("invalid_email_key.html", {}) transaction.set_rollback(True) return response user = pec.user address_context = { 'old_email': user.email, 'new_email': pec.new_email } if len(User.objects.filter(email=pec.new_email)) != 0: response = render_to_response("email_exists.html", {}) transaction.set_rollback(True) return response use_https = request.is_secure() if settings.FEATURES['ENABLE_MKTG_SITE']: contact_link = marketing_link('CONTACT') else: contact_link = '{protocol}://{site}{link}'.format( protocol='https' if use_https else 'http', site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME), link=reverse('contact'), ) site = Site.objects.get_current() message_context = get_base_template_context(site) message_context.update({ 'old_email': user.email, 'new_email': pec.new_email, 'contact_link': contact_link, 'from_address': configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL), }) msg = EmailChangeConfirmation().personalize( recipient=Recipient(user.id, user.email), language=preferences_api.get_user_preference(user, LANGUAGE_KEY), user_context=message_context, ) u_prof = UserProfile.objects.get(user=user) meta = u_prof.get_meta() if 'old_emails' not in meta: meta['old_emails'] = [] meta['old_emails'].append([user.email, datetime.datetime.now(UTC).isoformat()]) u_prof.set_meta(meta) u_prof.save() # Send it to the old email... try: ace.send(msg) except Exception: # pylint: disable=broad-except log.warning('Unable to send confirmation email to old address', exc_info=True) response = render_to_response("email_change_failed.html", {'email': user.email}) transaction.set_rollback(True) return response user.email = pec.new_email user.save() pec.delete() # And send it to the new email... msg.recipient = Recipient(user.id, pec.new_email) try: ace.send(msg) except Exception: # pylint: disable=broad-except log.warning('Unable to send confirmation email to new address', exc_info=True) response = render_to_response("email_change_failed.html", {'email': pec.new_email}) transaction.set_rollback(True) return response response = render_to_response("email_change_successful.html", address_context) return response
def _get_extended_profile_fields(): """Retrieve the extended profile fields from site configuration to be shown on the Account Settings page Returns: A list of dicts. Each dict corresponds to a single field. The keys per field are: "field_name" : name of the field stored in user_profile.meta "field_label" : The label of the field. "field_type" : TextField or ListField "field_options": a list of tuples for options in the dropdown in case of ListField """ extended_profile_fields = [] fields_already_showing = [ 'username', 'name', 'email', 'pref-lang', 'country', 'time_zone', 'level_of_education', 'gender', 'year_of_birth', 'language_proficiencies', 'social_links' ] field_labels_map = { "first_name": _(u"First Name"), "last_name": _(u"Last Name"), "city": _(u"City"), "state": _(u"State/Province/Region"), "company": _(u"Company"), "title": _(u"Title"), "job_title": _(u"Job Title"), "mailing_address": _(u"Mailing address"), "goals": _(u"Tell us why you're interested in {platform_name}").format( platform_name=configuration_helpers.get_value( "PLATFORM_NAME", settings.PLATFORM_NAME)), "profession": _(u"Profession"), "specialty": _(u"Specialty") } extended_profile_field_names = configuration_helpers.get_value( 'extended_profile_fields', []) for field_to_exclude in fields_already_showing: if field_to_exclude in extended_profile_field_names: extended_profile_field_names.remove(field_to_exclude) extended_profile_field_options = configuration_helpers.get_value( 'EXTRA_FIELD_OPTIONS', []) extended_profile_field_option_tuples = {} for field in extended_profile_field_options.keys(): field_options = extended_profile_field_options[field] extended_profile_field_option_tuples[field] = [ (option.lower(), option) for option in field_options ] for field in extended_profile_field_names: field_dict = { "field_name": field, "field_label": field_labels_map.get(field, field), } field_options = extended_profile_field_option_tuples.get(field) if field_options: field_dict["field_type"] = "ListField" field_dict["field_options"] = field_options else: field_dict["field_type"] = "TextField" extended_profile_fields.append(field_dict) return extended_profile_fields
def get_next_url_for_login_page(request, include_host=False): """ Determine the URL to redirect to following login/registration/third_party_auth The user is currently on a login or registration page. If 'course_id' is set, or other POST_AUTH_PARAMS, we will need to send the user to the /account/finish_auth/ view following login, which will take care of auto-enrollment in the specified course. Otherwise, we go to the ?next= query param or the configured custom redirection url (the default behaviour is to go to /dashboard). If THIRD_PARTY_AUTH_HINT is set, then `tpa_hint=<hint>` is added as a query parameter. This works with both GET and POST requests. """ request_params = request.GET if request.method == 'GET' else request.POST redirect_to = _get_redirect_to( request_host=request.get_host(), request_headers=request.META, request_params=request_params, request_is_https=request.is_secure(), ) root_url = configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL) if not redirect_to: if settings.ROOT_URLCONF == 'lms.urls': login_redirect_url = configuration_helpers.get_value('DEFAULT_REDIRECT_AFTER_LOGIN') if login_redirect_url: try: redirect_to = reverse(login_redirect_url) except NoReverseMatch: log.warning( 'Default redirect after login doesn\'t exist: %(login_redirect_url)r. ' 'Check the value set on DEFAULT_REDIRECT_AFTER_LOGIN configuration variable.', {"login_redirect_url": login_redirect_url} ) # If redirect url isn't set, reverse to dashboard if not redirect_to: # Tries reversing the LMS dashboard if the url doesn't exist redirect_to = reverse('dashboard') elif settings.ROOT_URLCONF == 'cms.urls': redirect_to = reverse('home') scheme = "https" if settings.HTTPS == "on" else "http" root_url = f'{scheme}://{settings.CMS_BASE}' if any(param in request_params for param in POST_AUTH_PARAMS): # Before we redirect to next/dashboard, we need to handle auto-enrollment: params = [(param, request_params[param]) for param in POST_AUTH_PARAMS if param in request_params] params.append(('next', redirect_to)) # After auto-enrollment, user will be sent to payment page or to this URL redirect_to = '{}?{}'.format(reverse('finish_auth'), urllib.parse.urlencode(params)) # Note: if we are resuming a third party auth pipeline, then the next URL will already # be saved in the session as part of the pipeline state. That URL will take priority # over this one. # Append a tpa_hint query parameter, if one is configured tpa_hint = configuration_helpers.get_value( "THIRD_PARTY_AUTH_HINT", settings.FEATURES.get("THIRD_PARTY_AUTH_HINT", '') ) if tpa_hint: # Don't add tpa_hint if we're already in the TPA pipeline (prevent infinite loop), # and don't overwrite any existing tpa_hint params (allow tpa_hint override). running_pipeline = third_party_auth.pipeline.get(request) (scheme, netloc, path, query, fragment) = list(urllib.parse.urlsplit(redirect_to)) if not running_pipeline and 'tpa_hint' not in query: params = urllib.parse.parse_qs(query) params['tpa_hint'] = [tpa_hint] query = urllib.parse.urlencode(params, doseq=True) redirect_to = urllib.parse.urlunsplit((scheme, netloc, path, query, fragment)) if include_host: return redirect_to, root_url return redirect_to
def create_account_with_params(request, params): """ Given a request and a dict of parameters (which may or may not have come from the request), create an account for the requesting user, including creating a comments service user object and sending an activation email. This also takes external/third-party auth into account, updates that as necessary, and authenticates the user for the request's session. Does not return anything. Raises AccountValidationError if an account with the username or email specified by params already exists, or ValidationError if any of the given parameters is invalid for any other reason. Issues with this code: * It is non-transactional except where explicitly wrapped in atomic to alleviate deadlocks and improve performance. This means failures at different places in registration can leave users in inconsistent states. * Third-party auth passwords are not verified. There is a comment that they are unused, but it would be helpful to have a sanity check that they are sane. * The user-facing text is rather unfriendly (e.g. "Username must be a minimum of two characters long" rather than "Please use a username of at least two characters"). * Duplicate email raises a ValidationError (rather than the expected AccountValidationError). Duplicate username returns an inconsistent user message (i.e. "An account with the Public Username '{username}' already exists." rather than "It looks like {username} belongs to an existing account. Try again with a different username.") The two checks occur at different places in the code; as a result, registering with both a duplicate username and email raises only a ValidationError for email only. """ # Copy params so we can modify it; we can't just do dict(params) because if # params is request.POST, that results in a dict containing lists of values params = dict(params.items()) # allow to define custom set of required/optional/hidden fields via configuration extra_fields = configuration_helpers.get_value( 'REGISTRATION_EXTRA_FIELDS', getattr(settings, 'REGISTRATION_EXTRA_FIELDS', {}) ) # registration via third party (Google, Facebook) using mobile application # doesn't use social auth pipeline (no redirect uri(s) etc involved). # In this case all related info (required for account linking) # is sent in params. # `third_party_auth_credentials_in_api` essentially means 'request # is made from mobile application' third_party_auth_credentials_in_api = 'provider' in params is_third_party_auth_enabled = third_party_auth.is_enabled() if is_third_party_auth_enabled and (pipeline.running(request) or third_party_auth_credentials_in_api): params["password"] = generate_password() # in case user is registering via third party (Google, Facebook) and pipeline has expired, show appropriate # error message if is_third_party_auth_enabled and ('social_auth_provider' in params and not pipeline.running(request)): raise ValidationError( {'session_expired': [ _(u"Registration using {provider} has timed out.").format( provider=params.get('social_auth_provider')) ]} ) extended_profile_fields = configuration_helpers.get_value('extended_profile_fields', []) # Can't have terms of service for certain SHIB users, like at Stanford registration_fields = getattr(settings, 'REGISTRATION_EXTRA_FIELDS', {}) tos_required = ( registration_fields.get('terms_of_service') != 'hidden' or registration_fields.get('honor_code') != 'hidden' ) form = AccountCreationForm( data=params, extra_fields=extra_fields, extended_profile_fields=extended_profile_fields, do_third_party_auth=False, tos_required=tos_required, ) custom_form = get_registration_extension_form(data=params) # Perform operations within a transaction that are critical to account creation with outer_atomic(read_committed=True): # first, create the account (user, profile, registration) = do_create_account(form, custom_form) third_party_provider, running_pipeline = _link_user_to_third_party_provider( is_third_party_auth_enabled, third_party_auth_credentials_in_api, user, request, params, ) new_user = authenticate_new_user(request, user.username, params['password']) django_login(request, new_user) request.session.set_expiry(0) # Check if system is configured to skip activation email for the current user. skip_email = _skip_activation_email( user, running_pipeline, third_party_provider, ) if skip_email: registration.activate() else: compose_and_send_activation_email(user, profile, registration) # Perform operations that are non-critical parts of account creation create_or_set_user_attribute_created_on_site(user, request.site) preferences_api.set_user_preference(user, LANGUAGE_KEY, get_language()) if settings.FEATURES.get('ENABLE_DISCUSSION_EMAIL_DIGEST'): try: enable_notifications(user) except Exception: # pylint: disable=broad-except log.exception(u"Enable discussion notifications failed for user {id}.".format(id=user.id)) _track_user_registration(user, profile, params, third_party_provider) # Announce registration REGISTER_USER.send(sender=None, user=user, registration=registration) create_comments_service_user(user) try: _record_registration_attributions(request, new_user) # Don't prevent a user from registering due to attribution errors. except Exception: # pylint: disable=broad-except log.exception('Error while attributing cookies to user registration.') # TODO: there is no error checking here to see that the user actually logged in successfully, # and is not yet an active user. if new_user is not None: AUDIT_LOG.info(u"Login success on new account creation - {0}".format(new_user.username)) return new_user
def activate_account(request, key): """ When link in activation e-mail is clicked """ # If request is in Studio call the appropriate view if theming_helpers.get_project_root_name().lower() == 'cms': monitoring_utils.set_custom_attribute('student_activate_account', 'cms') return activate_account_studio(request, key) # TODO: Use custom attribute to determine if there are any `activate_account` calls for cms in Production. # If not, the templates wouldn't be needed for cms, but we still need a way to activate for cms tests. monitoring_utils.set_custom_attribute('student_activate_account', 'lms') activation_message_type = None invalid_message = HTML( _('{html_start}Your account could not be activated{html_end}' 'Something went wrong, please <a href="{support_url}">contact support</a> to resolve this issue.' )).format( support_url=configuration_helpers.get_value( 'ACTIVATION_EMAIL_SUPPORT_LINK', settings.ACTIVATION_EMAIL_SUPPORT_LINK) or settings.SUPPORT_SITE_LINK, html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ) try: registration = Registration.objects.get(activation_key=key) except (Registration.DoesNotExist, Registration.MultipleObjectsReturned): activation_message_type = 'error' messages.error(request, invalid_message, extra_tags='account-activation aa-icon') else: if request.user.is_authenticated and request.user.id != registration.user.id: activation_message_type = 'error' messages.error(request, invalid_message, extra_tags='account-activation aa-icon') elif registration.user.is_active: activation_message_type = 'info' messages.info( request, HTML( _('{html_start}This account has already been activated.{html_end}' )).format( html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon', ) else: registration.activate() # Success message for logged in users. message = _( '{html_start}Success{html_end} You have activated your account.' ) tracker.emit( USER_ACCOUNT_ACTIVATED, { "user_id": registration.user.id, "activation_timestamp": registration.activation_timestamp }) if not request.user.is_authenticated: # Success message for logged out users message = _( '{html_start}Success! You have activated your account.{html_end}' 'You will now receive email updates and alerts from us related to' ' the courses you are enrolled in. Sign In to continue.') # Add message for later use. activation_message_type = 'success' messages.success( request, HTML(message).format( html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon', ) # If a safe `next` parameter is provided in the request # and it's not the same as the dashboard, redirect there. # The `get_next_url_for_login_page()` function will only return a safe redirect URL. # If the provided `next` URL is not safe, that function will fill `redirect_to` # with a value of `reverse('dashboard')`. redirect_url = None if request.GET.get('next'): redirect_to, root_login_url = get_next_url_for_login_page( request, include_host=True) # Don't automatically redirect authenticated users to the redirect_url # if the `next` value is either: # 1. "/dashboard" or # 2. "https://{LMS_ROOT_URL}/dashboard" (which we might provide as a value from the AuthN MFE) if redirect_to not in (root_login_url + reverse('dashboard'), reverse('dashboard')): redirect_url = get_redirect_url_with_host(root_login_url, redirect_to) if should_redirect_to_authn_microfrontend( ) and not request.user.is_authenticated: params = {'account_activation_status': activation_message_type} if redirect_url: params['next'] = redirect_url url_path = '/login?{}'.format(urllib.parse.urlencode(params)) return redirect(settings.AUTHN_MICROFRONTEND_URL + url_path) return redirect(redirect_url) if redirect_url and is_enterprise_learner( request.user) else redirect('dashboard')
def marketing_link(name): """Returns the correct URL for a link to the marketing site depending on if the marketing site is enabled Since the marketing site is enabled by a setting, we have two possible URLs for certain links. This function is to decides which URL should be provided. """ # link_map maps URLs from the marketing site to the old equivalent on # the Django site link_map = settings.MKTG_URL_LINK_MAP enable_mktg_site = configuration_helpers.get_value( 'ENABLE_MKTG_SITE', settings.FEATURES.get('ENABLE_MKTG_SITE', False)) marketing_urls = configuration_helpers.get_value('MKTG_URLS', settings.MKTG_URLS) marketing_url_overrides = configuration_helpers.get_value( 'MKTG_URL_OVERRIDES', settings.MKTG_URL_OVERRIDES) if name in marketing_url_overrides: validate = URLValidator() url = marketing_url_overrides.get(name) try: validate(url) return url except ValidationError as err: log.debug("Invalid link set for link %s: %s", name, err) return '#' if enable_mktg_site and name in marketing_urls: # special case for when we only want the root marketing URL if name == 'ROOT': return marketing_urls.get('ROOT') # special case for new enterprise marketing url with custom tracking query params if name == 'ENTERPRISE': enterprise_url = marketing_urls.get(name) # if url is not relative, then return it without joining to root if not enterprise_url.startswith('/'): return enterprise_url # Using urljoin here allows us to enable a marketing site and set # a site ROOT, but still specify absolute URLs for other marketing # URLs in the MKTG_URLS setting # e.g. urljoin('https://marketing.com', 'https://open-edx.org/about') >>> 'https://open-edx.org/about' return urljoin(marketing_urls.get('ROOT'), marketing_urls.get(name)) # only link to the old pages when the marketing site isn't on elif not enable_mktg_site and name in link_map: # don't try to reverse disabled marketing links if link_map[name] is not None: host_name = get_current_request_hostname() if link_map[name].startswith('http'): return link_map[name] else: try: return reverse(link_map[name]) except NoReverseMatch: log.debug(u"Cannot find corresponding link for name: %s", name) set_custom_attribute('unresolved_marketing_link', name) return '#' else: log.debug(u"Cannot find corresponding link for name: %s", name) return '#'
def ecommerce_url_root(self): """ Retrieve Ecommerce service public url root. """ return configuration_helpers.get_value('ECOMMERCE_PUBLIC_URL_ROOT', settings.ECOMMERCE_PUBLIC_URL_ROOT)