Skip to content

Commit 6bdf39f

Browse files
committed
Merge remote-tracking branch 'origin/master' into braden/units-api
2 parents 556fb78 + 8d00b09 commit 6bdf39f

22 files changed

Lines changed: 265 additions & 116 deletions

File tree

cms/djangoapps/contentstore/tasks.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,11 +1297,14 @@ def _convert_to_standard_url(url, course_key):
12971297
...asset-v1:edX+DemoX+Demo_Course+type@asset+block/getting-started_x250.png
12981298
/static/getting-started_x250.png
12991299
/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@2152d4a4aadc4cb0af5256394a3d1fc7
1300+
/jump_to_id/2152d4a4aadc4cb0af5256394a3d1fc7
13001301
"""
13011302
if _is_studio_url_without_base(url):
13021303
if url.startswith('/static/'):
13031304
processed_url = replace_static_urls(f'\"{url}\"', course_id=course_key)[1:-1]
13041305
return 'https://' + settings.CMS_BASE + processed_url
1306+
elif url.startswith('/jump_to_id/'):
1307+
return f'https://{settings.LMS_BASE}/courses/{course_key}{url}'
13051308
elif url.startswith('/'):
13061309
return 'https://' + settings.CMS_BASE + url
13071310
else:

cms/djangoapps/contentstore/tests/test_tasks.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
_get_urls,
4040
_check_broken_links,
4141
_is_studio_url,
42-
_scan_course_for_links
42+
_scan_course_for_links,
43+
_convert_to_standard_url
4344
)
4445

4546
logging = logging.getLogger(__name__)
@@ -548,3 +549,31 @@ def __init__(self):
548549
mock_filter.assert_called_once_with(validated_url_list)
549550
if retry_list:
550551
mock_retry_validation.assert_called_once_with(retry_list, course_key, retry_count=3)
552+
553+
def test_convert_to_standard_url(self):
554+
"""Test _convert_to_standard_url function with expected URLs."""
555+
course_key = CourseKey.from_string("course-v1:test+course1+run1")
556+
test_cases = [
557+
(
558+
"/static/getting-started_x250.png",
559+
f"https://{settings.CMS_BASE}/asset-v1:test+course1+run1+type@asset+block/getting-started_x250.png",
560+
),
561+
(
562+
"/jump_to_id/123abc",
563+
f"https://{settings.LMS_BASE}/courses/{course_key}/jump_to_id/123abc",
564+
),
565+
(
566+
"/container/block-v1:test+course1+type@vertical+block@123",
567+
f"https://{settings.CMS_BASE}/container/block-v1:test+course1+type@vertical+block@123",
568+
),
569+
("/unknown/path", f"https://{settings.CMS_BASE}/unknown/path"),
570+
("https://external.com/some/path", "https://external.com/some/path"),
571+
("studio-url", "https://localhost:8001/container/studio-url"),
572+
]
573+
574+
for url, expected in test_cases:
575+
self.assertEqual(
576+
_convert_to_standard_url(url, course_key),
577+
expected,
578+
f"Failed for URL: {url}",
579+
)

cms/envs/common.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2920,13 +2920,6 @@ def _should_send_learning_badge_events(settings):
29202920
MEILISEARCH_INDEX_PREFIX = ""
29212921
MEILISEARCH_API_KEY = "devkey"
29222922

2923-
# .. setting_name: DISABLED_COUNTRIES
2924-
# .. setting_default: []
2925-
# .. setting_description: List of country codes that should be disabled
2926-
# .. for now it wil impact country listing in auth flow and user profile.
2927-
# .. eg ['US', 'CA']
2928-
DISABLED_COUNTRIES = []
2929-
29302923
# .. setting_name: LIBRARY_ENABLED_BLOCKS
29312924
# .. setting_default: ['problem', 'video', 'html', 'drag-and-drop-v2']
29322925
# .. setting_description: List of block types that are ready/enabled to be created/used

lms/envs/common.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1624,10 +1624,6 @@ def _make_mako_template_dirs(settings):
16241624
######################## HOTJAR ###########################
16251625
HOTJAR_SITE_ID = 00000
16261626

1627-
######################## ALGOLIA SEARCH ###########################
1628-
ALGOLIA_APP_ID = None
1629-
ALGOLIA_SEARCH_API_KEY = None
1630-
16311627
######################## subdomain specific settings ###########################
16321628
COURSE_LISTINGS = {}
16331629

@@ -5550,15 +5546,6 @@ def _should_send_learning_badge_events(settings):
55505546
# .. setting_description: Dictionary with additional information that you want to share in the report.
55515547
SURVEY_REPORT_EXTRA_DATA = {}
55525548

5553-
5554-
# .. setting_name: DISABLED_COUNTRIES
5555-
# .. setting_default: []
5556-
# .. setting_description: List of country codes that should be disabled
5557-
# .. for now it wil impact country listing in auth flow and user profile.
5558-
# .. eg ['US', 'CA']
5559-
DISABLED_COUNTRIES = []
5560-
5561-
55625549
LMS_COMM_DEFAULT_FROM_EMAIL = "no-reply@example.com"
55635550

55645551

lms/envs/mock.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ ADMIN_PORTAL_MICROFRONTEND_URL: https://admin-portal-deploy_host
2727
AFFILIATE_COOKIE_NAME: sandbox.edx.affiliate_id
2828
AI_TRANSLATIONS_API_URL: https://ai-translations.localhost/api/v1
2929
AI_TRANSLATIONS_URL_ROOT: https://ai-translations.localhost
30-
ALGOLIA_APP_ID: hello
31-
ALGOLIA_SEARCH_API_KEY: '[encrypted]'
3230
ALLOWED_HOSTS:
3331
- hello
3432
ALLOW_ORPHANED_CONTENT_REMOVAL: true

openedx/core/djangoapps/content/block_structure/store.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,11 @@ def _add_to_cache(self, serialized_data, bs_model):
134134
to the cache.
135135
"""
136136
cache_key = self._encode_root_cache_key(bs_model)
137-
self._cache.set(cache_key, serialized_data, timeout=config.cache_timeout_in_seconds())
138-
logger.info("BlockStructure: Added to cache; %s, size: %d", bs_model, len(serialized_data))
137+
total_bytes_in_one_mb = 1024 * 1024
138+
if len(serialized_data) < total_bytes_in_one_mb * 2:
139+
self._cache.set(cache_key, serialized_data, timeout=config.cache_timeout_in_seconds())
140+
data_size_in_mbs = round(len(serialized_data) / total_bytes_in_one_mb, 2)
141+
logger.info("BlockStructure: Added to cache; %s, size: %dMB", bs_model, data_size_in_mbs)
139142

140143
def _get_from_cache(self, bs_model):
141144
"""

openedx/core/djangoapps/embargo/admin.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from django.contrib import admin
99

1010
from .forms import IPFilterForm, RestrictedCourseForm
11-
from .models import CountryAccessRule, IPFilter, RestrictedCourse
11+
from .models import CountryAccessRule, GlobalRestrictedCountry, IPFilter, RestrictedCourse
1212

1313

1414
class IPFilterAdmin(ConfigurationModelAdmin):
@@ -41,5 +41,20 @@ class RestrictedCourseAdmin(admin.ModelAdmin):
4141
search_fields = ('course_key',)
4242

4343

44+
class GlobalRestrictedCountryAdmin(admin.ModelAdmin):
45+
"""
46+
Admin configuration for the Global Country Restriction model.
47+
"""
48+
list_display = ("country",)
49+
50+
def delete_queryset(self, request, queryset):
51+
"""
52+
Override the delete_queryset method to clear the cache when objects are deleted in bulk.
53+
"""
54+
super().delete_queryset(request, queryset)
55+
GlobalRestrictedCountry.update_cache()
56+
57+
4458
admin.site.register(IPFilter, IPFilterAdmin)
4559
admin.site.register(RestrictedCourse, RestrictedCourseAdmin)
60+
admin.site.register(GlobalRestrictedCountry, GlobalRestrictedCountryAdmin)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Generated by Django 4.2.18 on 2025-01-29 08:19
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('embargo', '0002_data__add_countries'),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='GlobalRestrictedCountry',
16+
fields=[
17+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18+
('country', models.ForeignKey(help_text='The country to be restricted.', on_delete=django.db.models.deletion.CASCADE, to='embargo.country', unique=True)),
19+
],
20+
options={
21+
'verbose_name': 'Global Restricted Country',
22+
'verbose_name_plural': 'Global Restricted Countries',
23+
},
24+
),
25+
]

openedx/core/djangoapps/embargo/models.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,81 @@ class Meta:
662662
get_latest_by = 'timestamp'
663663

664664

665+
class GlobalRestrictedCountry(models.Model):
666+
"""
667+
Model to restrict access to specific countries globally.
668+
"""
669+
country = models.ForeignKey(
670+
"Country",
671+
help_text="The country to be restricted.",
672+
on_delete=models.CASCADE,
673+
unique=True
674+
)
675+
676+
CACHE_KEY = "embargo.global.restricted_countries"
677+
678+
@classmethod
679+
def get_countries(cls):
680+
"""
681+
Retrieve the set of restricted country codes from the cache or refresh it if not available.
682+
683+
Returns:
684+
set: A set of restricted country codes.
685+
"""
686+
return cache.get_or_set(cls.CACHE_KEY, cls._fetch_restricted_countries)
687+
688+
@classmethod
689+
def is_country_restricted(cls, country_code):
690+
"""
691+
Check if the given country code is restricted.
692+
693+
Args:
694+
country_code (str): The country code to check.
695+
696+
Returns:
697+
bool: True if the country is restricted, False otherwise.
698+
"""
699+
return country_code in cls.get_countries()
700+
701+
@classmethod
702+
def _fetch_restricted_countries(cls):
703+
"""
704+
Fetch the set of restricted country codes from the database.
705+
706+
Returns:
707+
set: A set of restricted country codes.
708+
"""
709+
return set(cls.objects.values_list("country__country", flat=True))
710+
711+
@classmethod
712+
def update_cache(cls):
713+
"""
714+
Update the cache with the latest restricted country codes.
715+
"""
716+
cache.set(cls.CACHE_KEY, cls._fetch_restricted_countries())
717+
718+
def save(self, *args, **kwargs):
719+
"""
720+
Override save method to update cache on insert/update.
721+
"""
722+
super().save(*args, **kwargs)
723+
self.update_cache()
724+
725+
def delete(self, *args, **kwargs):
726+
"""
727+
Override delete method to update cache on deletion.
728+
"""
729+
super().delete(*args, **kwargs)
730+
self.update_cache()
731+
732+
def __str__(self):
733+
return f"{self.country.country.name} ({self.country.country})"
734+
735+
class Meta:
736+
verbose_name = "Global Restricted Country"
737+
verbose_name_plural = "Global Restricted Countries"
738+
739+
665740
# Connect the signals to the receivers so we record a history
666741
# of changes to the course access rules.
667742
post_save.connect(CourseAccessRuleHistory.snapshot_post_save_receiver, sender=RestrictedCourse)

openedx/core/djangoapps/user_api/accounts/api.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
from django.conf import settings
1010
from django.core.exceptions import ObjectDoesNotExist
1111
from django.core.validators import ValidationError, validate_email
12-
from django.utils.translation import override as override_language
1312
from django.utils.translation import gettext as _
13+
from django.utils.translation import override as override_language
1414
from eventtracking import tracker
1515
from pytz import UTC
16+
1617
from common.djangoapps.student import views as student_views
1718
from common.djangoapps.student.models import (
1819
AccountRecovery,
@@ -25,7 +26,7 @@
2526
from common.djangoapps.util.password_policy_validators import validate_password
2627
from lms.djangoapps.certificates.api import get_certificates_for_user
2728
from lms.djangoapps.certificates.data import CertificateStatuses
28-
29+
from openedx.core.djangoapps.embargo.models import GlobalRestrictedCountry
2930
from openedx.core.djangoapps.enrollments.api import get_verified_enrollments
3031
from openedx.core.djangoapps.user_api import accounts, errors, helpers
3132
from openedx.core.djangoapps.user_api.errors import (
@@ -39,6 +40,7 @@
3940
from openedx.core.lib.api.view_utils import add_serializer_errors
4041
from openedx.features.enterprise_support.utils import get_enterprise_readonly_account_fields
4142
from openedx.features.name_affirmation_api.utils import is_name_affirmation_installed
43+
4244
from .serializers import AccountLegacyProfileSerializer, AccountUserSerializer, UserReadOnlySerializer, _visible_fields
4345

4446
name_affirmation_installed = is_name_affirmation_installed()
@@ -151,7 +153,10 @@ def update_account_settings(requesting_user, update, username=None):
151153

152154
_validate_email_change(user, update, field_errors)
153155
_validate_secondary_email(user, update, field_errors)
154-
if update.get('country', '') in settings.DISABLED_COUNTRIES:
156+
if (
157+
settings.FEATURES.get('EMBARGO', False) and
158+
GlobalRestrictedCountry.is_country_restricted(update.get('country', ''))
159+
):
155160
field_errors['country'] = {
156161
'developer_message': 'Country is disabled for registration',
157162
'user_message': 'This country cannot be selected for user registration'

0 commit comments

Comments
 (0)