Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1542a0c
UNIFICATION: get_current_organization don't assume single Organization
bryanlandia Dec 12, 2020
b5ab596
Use FEATURE switch to determine how we get current Org by by Site
bryanlandia Dec 14, 2020
2d216b6
Fix factory.fuzzy import in appsembler api test factories
bryanlandia Dec 17, 2020
d298926
TestCase covering appsembler.sites.utils get_current_organization
bryanlandia Dec 17, 2020
7db75ba
Allow auto-generation of certs (e.g., on passing grade) for Honor mode
bryanlandia Dec 23, 2020
bc00049
Merge pull request #794 from appsembler/bryan/allow-auto-generated-ce…
bryanlandia Dec 28, 2020
42d2a46
tracks static page
OmarIthawi Dec 29, 2020
e9f577b
Merge pull request #796 from appsembler/omar/tracks
OmarIthawi Dec 29, 2020
0eae98c
Merge pull request #785 from appsembler/bryan/tahoe-standalone-compat…
bryanlandia Dec 29, 2020
17648d7
Update translations (autogenerated message)
OmarIthawi Jan 6, 2021
45d0fa6
Merge pull request #797 from appsembler/omar/i18n-2021
OmarIthawi Jan 6, 2021
14e11b9
lms organization middleware to make tiers expiration work in lms
OmarIthawi Jan 19, 2021
8a4c2fa
RedirectMiddleware: redirect function variable unshadow
OmarIthawi Jan 19, 2021
eef9ea3
Merge pull request #804 from appsembler/omar/dupl-var
OmarIthawi Jan 19, 2021
2ea4d3a
Merge pull request #803 from appsembler/omar/lms-org-middleware
OmarIthawi Jan 20, 2021
26e2503
RED-1431: bump django-tiers to v0.2.3 for trial expiration fixes
OmarIthawi Jan 20, 2021
614cdd1
Merge pull request #806 from appsembler/omar/tiers-bump
OmarIthawi Jan 20, 2021
efb1e17
add honeycomb tracing and context on registration API
thraxil Feb 2, 2021
d20ff8a
Merge pull request #812 from appsembler/registration-honeycomb
OmarIthawi Feb 3, 2021
6afa0cf
get_active_sites() to return QuerySet
OmarIthawi Feb 4, 2021
6d453b2
beeline: fix unhashable type: 'dict'
OmarIthawi Feb 4, 2021
2da4dbf
Merge pull request #815 from appsembler/omar/sites-queryset
OmarIthawi Feb 4, 2021
0de24d8
Merge pull request #816 from appsembler/omar/beeline-fix
OmarIthawi Feb 4, 2021
298d314
FEATURE FLAG-gate the inclusion of Appsembler Redirect middlewares
bryanlandia Feb 4, 2021
f9cae26
Tahoe RedirectMiddleware use TAHOE_MAINSITE_REDIRECT_URL ENV_TOKEN
bryanlandia Feb 4, 2021
2fb2af2
RedirectMiddleware tests, import cleanup
bryanlandia Feb 4, 2021
e603c70
Tahoe redirect middleware tests updates
bryanlandia Feb 4, 2021
e4bf2b3
constant for TAHOE_MARKETING_SITE_URL in settings
bryanlandia Feb 4, 2021
baccd3a
Clarify variable name and fix insert ordering of Tahoe redir middleware
bryanlandia Feb 19, 2021
d7c4265
Merge pull request #817 from appsembler/appsembler/tahoe-move-middlew…
bryanlandia Feb 19, 2021
577fd48
get_lms_link_from_course_key fallback to LMS base if no Site found
bryanlandia Feb 2, 2021
f209638
Removed unused preview property and add unit tests for get_lms_link_f…
bryanlandia Feb 22, 2021
ad6e1b0
Merge pull request #813 from appsembler/bryan/lms-link-fallback-no-co…
bryanlandia Feb 24, 2021
7960ef1
Merge branch 'main' into omar/hawthorn-sync
OmarIthawi Mar 12, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions lms/djangoapps/certificates/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import logging

import six
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver

Expand Down Expand Up @@ -156,6 +157,12 @@ def fire_ungenerated_certificate_task(user, course_key, expected_verification_st
CourseMode.NO_ID_PROFESSIONAL_MODE,
CourseMode.MASTERS,
]
if settings.FEATURES.get('TAHOE_AUTO_GENERATE_HONOR_CERTS', False):
# Appsembler change: allow for Honor mode
allowed_enrollment_modes_list += [
CourseMode.HONOR
]

enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(user, course_key)
cert = GeneratedCertificate.certificate_for_student(user, course_key)

Expand Down
5 changes: 4 additions & 1 deletion lms/djangoapps/certificates/tests/test_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import mock
import six

from django.conf import settings

from lms.djangoapps.certificates import api as certs_api
from lms.djangoapps.certificates.models import (
CertificateGenerationConfiguration,
Expand Down Expand Up @@ -367,9 +369,10 @@ def setUp(self):
('credit', True),
('masters', True),
('audit', False),
('honor', False),
('honor', True), # TAHOE_AUTO_GENERATE_HONOR_CERTS feature: allows for honor
)
@ddt.unpack
@mock.patch.dict(settings.FEATURES, {'TAHOE_AUTO_GENERATE_HONOR_CERTS': True})
def test_fire_ungenerated_certificate_task_allowed_modes(self, enrollment_mode, should_create):
"""
Test that certificate generation task is fired for only modes that are
Expand Down
1 change: 1 addition & 0 deletions lms/djangoapps/static_template_view/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
url(r'^500$', views.render_500, name='static_template_view.views.render_500'),

url(r'^blog$', views.render, {'template': 'blog.html'}, name="blog"),
url(r'^tracks$', views.render, {'template': 'tracks.html'}, name="tracks"),
url(r'^contact$', views.render, {'template': 'contact.html'}, name="contact"),
url(r'^donate$', views.render, {'template': 'donate.html'}, name="donate"),
url(r'^faq$', views.render, {'template': 'faq.html'}, name="faq"),
Expand Down
3 changes: 3 additions & 0 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1515,6 +1515,9 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring
'django.contrib.sites.middleware.CurrentSiteMiddleware',
'edx_rest_framework_extensions.auth.jwt.middleware.JwtAuthCookieMiddleware',

# Allows us to define redirects via Django admin
'django_sites_extensions.middleware.RedirectMiddleware',

# Instead of SessionMiddleware, we use a more secure version
# 'django.contrib.sessions.middleware.SessionMiddleware',
'openedx.core.djangoapps.safe_sessions.middleware.SafeSessionMiddleware',
Expand Down
16 changes: 16 additions & 0 deletions lms/templates/static_templates/tracks.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<%page expression_filter="h"/>
<%! from django.utils.translation import ugettext as _ %>
<%inherit file="../main.html" />

<%block name="pagetitle">${_("Tracks")}</%block>

<main id="main" aria-label="Content" tabindex="-1">
<section class="container tracks">
<h1>
<%block name="pageheader">${page_header or _("Tracks")}</%block>
</h1>
<p>
<%block name="pagecontent">${page_content or _("This page left intentionally blank. Feel free to add your own content.")}</%block>
</p>
</section>
</main>
2 changes: 2 additions & 0 deletions openedx/core/djangoapps/appsembler/api/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import datetime

import factory
import factory.fuzzy

from openedx.core.djangoapps.content.course_overviews.models import (
CourseOverview,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,6 @@ def plugin_settings(settings):
settings.CUSTOM_DOMAINS_REDIRECT_CACHE_KEY_PREFIX = 'custom_domains_redirects'

settings.COPY_SEGMENT_EVENT_PROPERTIES_TO_TOP_LEVEL = False

# Appsembler allows generating honor certs
settings.FEATURES['TAHOE_AUTO_GENERATE_HONOR_CERTS'] = True
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ def plugin_settings(settings):

# This is used in the appsembler_sites.middleware.RedirectMiddleware to exclude certain paths
# from the redirect mechanics.
settings.MAIN_SITE_REDIRECT_WHITELIST += ['/media/']
if settings.APPSEMBLER_FEATURES.get("TAHOE_ENABLE_DOMAIN_REDIRECT_MIDDLEWARE", True):
settings.MAIN_SITE_REDIRECT_WHITELIST += ['/media/']
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
from openedx.core.djangoapps.appsembler.settings.settings import production_common


EDX_SITE_REDIRECT_MIDDLEWARE = "django_sites_extensions.middleware.RedirectMiddleware"
TAHOE_MARKETING_SITE_URL = "https://appsembler.com/tahoe"


def _add_theme_static_dirs(settings):
"""
Appsembler Themes static files customizations.
Expand Down Expand Up @@ -43,8 +47,39 @@ def plugin_settings(settings):

This file, however, won't run in test environments.
"""
settings.MIDDLEWARE += [
# LmsCurrentOrganizationMiddleware needs to go before `TiersMiddleware` in aws_common.plugin_settings()
'openedx.core.djangoapps.appsembler.sites.middleware.LmsCurrentOrganizationMiddleware',
]

production_common.plugin_settings(settings)

if settings.APPSEMBLER_FEATURES.get("TAHOE_ENABLE_DOMAIN_REDIRECT_MIDDLEWARE", True):
redir_middleware_index = settings.MIDDLEWARE.index(EDX_SITE_REDIRECT_MIDDLEWARE)
settings.MIDDLEWARE.insert(
redir_middleware_index, # Insert after Django RedirectMiddleware
'openedx.core.djangoapps.appsembler.sites.middleware.CustomDomainsRedirectMiddleware'
)
settings.MIDDLEWARE.insert(
redir_middleware_index + 1, # Insert after CustomDomainsRedirectMiddleware
'openedx.core.djangoapps.appsembler.sites.middleware.RedirectMiddleware'
)

settings.TAHOE_MAIN_SITE_REDIRECT_URL = settings.ENV_TOKENS.get(
'TAHOE_MAIN_SITE_REDIRECT_URL', TAHOE_MARKETING_SITE_URL
)
# This is used in the appsembler_sites.middleware.RedirectMiddleware to exclude certain paths
# from the redirect mechanics.
settings.MAIN_SITE_REDIRECT_WHITELIST = [
'api',
'admin',
'oauth',
'status',
'/heartbeat',
'/accounts/manage_user_standing',
'/accounts/disable_account_ajax',
]

settings.LMS_BASE = settings.ENV_TOKENS.get('LMS_BASE')

settings.INSTALLED_APPS += (
Expand Down Expand Up @@ -97,23 +132,4 @@ def plugin_settings(settings):
settings.ACCESS_CONTROL_BACKENDS = settings.ENV_TOKENS.get('ACCESS_CONTROL_BACKENDS', {})
settings.LMS_SEGMENT_SITE = settings.AUTH_TOKENS.get('SEGMENT_SITE')

_after_site_mdlwr = settings.MIDDLEWARE.index('django.contrib.sites.middleware.CurrentSiteMiddleware') + 1
settings.MIDDLEWARE = settings.MIDDLEWARE[:_after_site_mdlwr] + [
# Allows us to define redirects via Django admin
'openedx.core.djangoapps.appsembler.sites.middleware.CustomDomainsRedirectMiddleware',
'openedx.core.djangoapps.appsembler.sites.middleware.RedirectMiddleware',
] + settings.MIDDLEWARE[_after_site_mdlwr:]

# This is used in the appsembler_sites.middleware.RedirectMiddleware to exclude certain paths
# from the redirect mechanics.
settings.MAIN_SITE_REDIRECT_WHITELIST = [
'api',
'admin',
'oauth',
'status',
'/heartbeat',
'/accounts/manage_user_standing',
'/accounts/disable_account_ajax',
]

_add_theme_static_dirs(settings)
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def get_faked_settings():
settings.APPSEMBLER_FEATURES = {}
settings.MIDDLEWARE = [
'django.contrib.sites.middleware.CurrentSiteMiddleware',
'django_sites_extensions.middleware.RedirectMiddleware',
]
settings.STATICFILES_DIRS = []
settings.CACHES = {}
Expand Down
37 changes: 22 additions & 15 deletions openedx/core/djangoapps/appsembler/sites/middleware.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import beeline
import logging

from django.conf import settings
from django.core.cache import cache, caches
from django.contrib.redirects.models import Redirect
from django.core.cache import caches
from django.shortcuts import redirect
from django.utils.deprecation import MiddlewareMixin

from openedx.core.djangoapps.appsembler.sites.models import AlternativeDomain
from openedx.core.djangoapps.appsembler.sites.utils import get_current_organization


import beeline
import logging
log = logging.getLogger(__name__)


Expand Down Expand Up @@ -38,8 +40,7 @@ def process_request(self, request):

class RedirectMiddleware(MiddlewareMixin):
"""
Redirects requests for URLs persisted using the django.contrib.redirects.models.Redirect model.
With the exception of the main site.
Redirects requests for main site to Tahoe marketing page, except whitelisted.
"""
def process_request(self, request):
"""
Expand All @@ -51,17 +52,23 @@ def process_request(self, request):
beeline.add_trace_field("site_id", site.id)
in_whitelist = any([p in request.path for p in settings.MAIN_SITE_REDIRECT_WHITELIST])
if (site.id == settings.SITE_ID) and not in_whitelist:
return redirect("https://appsembler.com/tahoe/")
return redirect(settings.TAHOE_MAIN_SITE_REDIRECT_URL)
except Exception:
# I'm not entirely sure this middleware get's called only in LMS or in other apps as well.
# Soooo just in case
beeline.add_trace_field("redirect_middleware_exception", True)
pass
cache_key = '{prefix}-{site}'.format(prefix=settings.REDIRECT_CACHE_KEY_PREFIX, site=site.domain)
redirects = cache.get(cache_key)
if redirects is None:
redirects = {redirect.old_path: redirect.new_path for redirect in Redirect.objects.filter(site=site)}
cache.set(cache_key, redirects, settings.REDIRECT_CACHE_TIMEOUT)
redirect_to = redirects.get(request.path)
if redirect_to:
return redirect(redirect_to, permanent=True)


class LmsCurrentOrganizationMiddleware(object):
"""
Get the current middleware for the LMS.

This middleware replaces the default `organizations.OrganizationMiddleware` to
use a better get_current_organization() helper.
"""
def process_request(self, request):
# Note: This does _not_ support multiple organizations per user.
organization = get_current_organization(failure_return_none=True)
beeline.add_trace_field('session_current_organization', organization)
request.session['organization'] = organization
16 changes: 16 additions & 0 deletions openedx/core/djangoapps/appsembler/sites/serializers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import beeline
from django.conf import settings
from django.contrib.sites.models import Site
from django.db import transaction
Expand Down Expand Up @@ -34,7 +35,9 @@ class Meta:
model = SiteConfiguration
fields = ('id', 'values', 'sassVariables', 'pageElements')

@beeline.traced(name="SiteConfigurationSerializer.update")
def update(self, instance, validated_data):
beeline.add_context_field("validated_data", validated_data)
object = super(SiteConfigurationSerializer, self).update(instance, validated_data)
return object

Expand All @@ -51,13 +54,17 @@ class Meta:
model = AlternativeDomain
fields = ('id', 'site', 'domain')

@beeline.traced(name="AlternativeDomainSerializer.create")
def create(self, validated_data):
"""
Allow only one alternative domain per Site model.
"""
beeline.add_context_field("validated_data", validated_data)
domain, created = AlternativeDomain.objects.get_or_create(
site=validated_data.get('site', None),
defaults={'domain': validated_data.get('domain', None)})
beeline.add_context_field("domain", domain)
beeline.add_context_field("created", created)
if not created:
domain.domain = validated_data.get('domain', None)
domain.save()
Expand All @@ -73,11 +80,14 @@ class Meta:
model = Site
fields = ('id', 'name', 'domain', 'configuration', 'alternativeDomain', 'customDomainStatus')

@beeline.traced(name="SiteSerializer.create")
def create(self, validated_data):
beeline.add_context_field("validated_data", validated_data)
site = super(SiteSerializer, self).create(validated_data)
_organization, site, _user = bootstrap_site(site)
return site

@beeline.traced(name="SiteSerializer.custom_domain_status")
def custom_domain_status(self, obj):
if not hasattr(obj, 'alternative_domain'):
return 'inactive'
Expand All @@ -89,7 +99,9 @@ class Meta:
model = Organization
fields = ('id', 'name', 'short_name', 'edx_uuid')

@beeline.traced(name="OrganizationSerializer.create")
def create(self, validated_data):
beeline.add_context_field("validated_data", validated_data)
return organizations_api.add_organization(**validated_data)


Expand All @@ -101,7 +113,9 @@ class RegistrationSerializer(serializers.Serializer):
password = serializers.CharField(required=False)
initial_values = serializers.DictField(required=False)

@beeline.traced(name="RegistrationSerializer.create")
def create(self, validated_data):
beeline.add_context_field('validated_data', validated_data)
site_data = validated_data.pop('site')
site = Site.objects.create(**site_data)
organization_data = validated_data.pop('organization')
Expand Down Expand Up @@ -143,6 +157,8 @@ def create(self, validated_data):

# clone course
if settings.FEATURES.get("APPSEMBLER_IMPORT_DEFAULT_COURSE_ON_SITE_CREATION", False):
beeline.add_context_field("default_course_on_site_creation_flag", True)

def import_task_on_commit():
"""
Run the import task after the commit to avoid Organization.DoesNotExist error on the Celery.
Expand Down
66 changes: 66 additions & 0 deletions openedx/core/djangoapps/appsembler/sites/tests/test_middlewares.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""
Tests for the sites.middlewares module.
"""

from mock import patch, Mock
from django.test import TestCase, override_settings
from django.test.client import RequestFactory

from openedx.core.djangoapps.appsembler.sites.middleware import LmsCurrentOrganizationMiddleware, RedirectMiddleware
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory


@patch('openedx.core.djangoapps.appsembler.sites.middleware.get_current_organization')
class LmsCurrentOrganizationMiddlewareTests(TestCase):
def test_with_organization(self, get_current_organization):
middleware = LmsCurrentOrganizationMiddleware()
request = Mock(session={})
fake_org = Mock()
get_current_organization.return_value = fake_org

middleware.process_request(request)
assert request.session['organization'] is fake_org

def test_with_no_organization(self, get_current_organization):
middleware = LmsCurrentOrganizationMiddleware()
request = Mock(session={})
get_current_organization.return_value = None

middleware.process_request(request)
assert request.session['organization'] is None


@override_settings(TAHOE_MAIN_SITE_REDIRECT_URL='https://foo.bar')
@override_settings(MAIN_SITE_REDIRECT_WHITELIST=['/baz'])
class RedirectMiddlewareTests(TestCase):
def setUp(self):
super(RedirectMiddlewareTests, self).setUp()
self.default_site = SiteFactory.create()
self.other_site = SiteFactory.create()
patcher = patch("django.conf.settings.SITE_ID", self.default_site.id)
patcher.start()
self.addCleanup(patcher.stop)

def test_redirects_default_site(self):
request = RequestFactory().get('/path', HTTP_HOST=self.default_site.domain)
request.site = self.default_site
processed = RedirectMiddleware().process_request(request)
self.assertEquals(processed.url, 'https://foo.bar') # middleware drops the path

def test_with_no_site_found(self):
request = RequestFactory().get('/')
request.site = None
processed = RedirectMiddleware().process_request(request)
self.assertIsNone(processed)

def test_no_redirect_other_site(self):
request = RequestFactory().get('/', HTTP_HOST=self.other_site.domain)
request.site = self.other_site
processed = RedirectMiddleware().process_request(request)
self.assertIsNone(processed)

def test_url_in_whitelist(self):
request = RequestFactory().get('/baz', HTTP_HOST=self.default_site.domain)
request.site = self.default_site
processed = RedirectMiddleware().process_request(request)
self.assertIsNone(processed)
Loading