richie 2.34.1.dev10__py2.py3-none-any.whl → 2.34.1.dev16__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of richie might be problematic. Click here for more details.
- frontend/scss/colors/_theme.scss +8 -0
- frontend/scss/components/_header.scss +103 -14
- frontend/scss/objects/_selector.scss +1 -0
- richie/apps/core/cache.py +175 -2
- richie/apps/core/storage.py +63 -0
- richie/apps/core/templates/menu/header_menu.html +31 -11
- richie/apps/core/templates/richie/base.html +149 -6
- richie/apps/core/tests/test_cache.py +159 -0
- richie/apps/core/tests/test_settings.py +52 -0
- richie/apps/core/utils.py +60 -0
- richie/apps/courses/migrations/0037_alter_blogpostpluginmodel_cmsplugin_ptr_and_more.py +230 -0
- richie/apps/courses/migrations/0038_alter_mainmenuentry_menu_color.py +25 -0
- richie/apps/courses/models/menuentry.py +1 -1
- richie/plugins/glimpse/migrations/0004_alter_glimpse_cmsplugin_ptr_alter_glimpse_variant.py +49 -0
- richie/plugins/html_sitemap/migrations/0002_alter_htmlsitemappage_cmsplugin_ptr.py +28 -0
- richie/plugins/large_banner/migrations/0004_alter_largebanner_cmsplugin_ptr.py +28 -0
- richie/plugins/lti_consumer/migrations/0004_alter_lticonsumer_cmsplugin_ptr.py +28 -0
- richie/plugins/nesteditem/migrations/0004_alter_nesteditem_cmsplugin_ptr.py +28 -0
- richie/plugins/plain_text/migrations/0002_alter_plaintext_cmsplugin_ptr.py +28 -0
- richie/static/richie/css/main.css +1 -1
- {richie-2.34.1.dev10.dist-info → richie-2.34.1.dev16.dist-info}/METADATA +3 -1
- {richie-2.34.1.dev10.dist-info → richie-2.34.1.dev16.dist-info}/RECORD +26 -14
- {richie-2.34.1.dev10.dist-info → richie-2.34.1.dev16.dist-info}/WHEEL +1 -1
- {richie-2.34.1.dev10.dist-info → richie-2.34.1.dev16.dist-info}/LICENSE +0 -0
- {richie-2.34.1.dev10.dist-info → richie-2.34.1.dev16.dist-info}/top_level.txt +0 -0
- {richie-2.34.1.dev10.dist-info → richie-2.34.1.dev16.dist-info}/zip-safe +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""Test site cache plugin"""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
from unittest import mock
|
|
5
|
+
|
|
6
|
+
from django.core.cache.backends.dummy import DummyCache
|
|
7
|
+
from django.test import TestCase, override_settings
|
|
8
|
+
|
|
9
|
+
from django_redis.cache import RedisCache
|
|
10
|
+
|
|
11
|
+
from richie.apps.core.cache import RedisCacheWithFallback
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RedisCacheWithFallbackTestCase(TestCase):
|
|
15
|
+
"""
|
|
16
|
+
Test suite for the RedisCacheWithFallback
|
|
17
|
+
|
|
18
|
+
Credits:
|
|
19
|
+
- https://github.com/Kub-AT/django-cache-fallback
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@override_settings(
|
|
23
|
+
CACHES={
|
|
24
|
+
"default": {
|
|
25
|
+
"BACKEND": "apps.core.cache.RedisCacheWithFallback",
|
|
26
|
+
"LOCATION": "mymaster/redis-sentinel:26379,redis-sentinel:26379/0",
|
|
27
|
+
"OPTIONS": {
|
|
28
|
+
"CLIENT_CLASS": "richie.apps.core.cache.SentinelClient",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
"memory_cache": {
|
|
32
|
+
"BACKEND": "django.core.cache.backends.dummy.DummyCache",
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
def test_client(self):
|
|
37
|
+
"""Test class instance of caches"""
|
|
38
|
+
client = RedisCacheWithFallback(None, {})
|
|
39
|
+
self.assertIs(type(client), RedisCacheWithFallback)
|
|
40
|
+
self.assertIs(type(client.redis_cache), RedisCache)
|
|
41
|
+
self.assertIs(type(client.fallback_cache), DummyCache)
|
|
42
|
+
|
|
43
|
+
@mock.patch.object(RedisCacheWithFallback, "_call_fallback_cache")
|
|
44
|
+
@mock.patch.object(RedisCacheWithFallback, "_call_redis_cache")
|
|
45
|
+
def test_get_redis_cache(self, redis_cache_mock, fallback_cache_mock):
|
|
46
|
+
"""Test that redis_cache is used by default."""
|
|
47
|
+
client = RedisCacheWithFallback(None, {})
|
|
48
|
+
client.get("irrelevent")
|
|
49
|
+
|
|
50
|
+
redis_cache_mock.assert_called_once()
|
|
51
|
+
fallback_cache_mock.assert_not_called()
|
|
52
|
+
|
|
53
|
+
@mock.patch("apps.core.cache.logger")
|
|
54
|
+
@mock.patch.object(RedisCacheWithFallback, "_call_fallback_cache")
|
|
55
|
+
@mock.patch.object(RedisCacheWithFallback, "_call_redis_cache")
|
|
56
|
+
def test_get_fallback_cache(
|
|
57
|
+
self, redis_cache_mock, fallback_cache_mock, logger_mock
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
Test case when redis_cache raises an exception,
|
|
61
|
+
logger logs the exception then fallback_cache takes over
|
|
62
|
+
"""
|
|
63
|
+
client = RedisCacheWithFallback(None, {})
|
|
64
|
+
client.get("irrelevent")
|
|
65
|
+
|
|
66
|
+
redis_cache_mock.assert_called_once()
|
|
67
|
+
fallback_cache_mock.assert_not_called()
|
|
68
|
+
|
|
69
|
+
redis_cache_mock.side_effect = Exception()
|
|
70
|
+
client.get("irrelevent")
|
|
71
|
+
|
|
72
|
+
logger_mock.warning.assert_called_with(
|
|
73
|
+
"[DEGRADED CACHE MODE] - Switch to fallback cache"
|
|
74
|
+
)
|
|
75
|
+
logger_mock.exception.assert_called_once()
|
|
76
|
+
fallback_cache_mock.assert_called_once()
|
|
77
|
+
|
|
78
|
+
@override_settings(
|
|
79
|
+
CACHES={
|
|
80
|
+
"default": {
|
|
81
|
+
"BACKEND": "apps.core.cache.RedisCacheWithFallback",
|
|
82
|
+
"LOCATION": "mymaster/redis-sentinel:26379,redis-sentinel:26379/0",
|
|
83
|
+
"OPTIONS": {
|
|
84
|
+
"CLIENT_CLASS": "richie.apps.core.cache.SentinelClient",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
"memory_cache": {
|
|
88
|
+
"BACKEND": "django.core.cache.backends.dummy.DummyCache",
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
@mock.patch.object(DummyCache, "delete")
|
|
93
|
+
@mock.patch.object(RedisCacheWithFallback, "_call_redis_cache")
|
|
94
|
+
def test_invalidate_fallback_cache(self, redis_cache_mock, delete_mock):
|
|
95
|
+
"""
|
|
96
|
+
Test that fallback_cache is invalidated every 60 seconds
|
|
97
|
+
when Redis is up
|
|
98
|
+
"""
|
|
99
|
+
now = datetime.datetime.now()
|
|
100
|
+
|
|
101
|
+
client = RedisCacheWithFallback(None, {})
|
|
102
|
+
client.get("round_0")
|
|
103
|
+
redis_cache_mock.reset_mock()
|
|
104
|
+
delete_mock.reset_mock()
|
|
105
|
+
|
|
106
|
+
# A second cache access should not delete the fallback cache again
|
|
107
|
+
client.get("round_1")
|
|
108
|
+
redis_cache_mock.assert_called_once()
|
|
109
|
+
delete_mock.assert_not_called()
|
|
110
|
+
redis_cache_mock.reset_mock()
|
|
111
|
+
delete_mock.reset_mock()
|
|
112
|
+
|
|
113
|
+
# Another call to Redis cache 120 seconds later
|
|
114
|
+
# should delete the fallback cache again
|
|
115
|
+
read_time = now + datetime.timedelta(seconds=120)
|
|
116
|
+
with mock.patch("base.utils.datetime") as mock_datetime:
|
|
117
|
+
mock_datetime.now.return_value = read_time
|
|
118
|
+
client.get("round_2")
|
|
119
|
+
|
|
120
|
+
redis_cache_mock.assert_called_once()
|
|
121
|
+
delete_mock.assert_called_once()
|
|
122
|
+
redis_cache_mock.reset_mock()
|
|
123
|
+
delete_mock.reset_mock()
|
|
124
|
+
|
|
125
|
+
# Another call to Redis cache 30 seconds later (<1 minute)
|
|
126
|
+
# should not delete the fallback cache again
|
|
127
|
+
read_time += datetime.timedelta(seconds=30)
|
|
128
|
+
with mock.patch("base.utils.datetime") as mock_datetime:
|
|
129
|
+
mock_datetime.now.return_value = read_time
|
|
130
|
+
client.get("round_3")
|
|
131
|
+
|
|
132
|
+
redis_cache_mock.assert_called_once()
|
|
133
|
+
delete_mock.assert_not_called()
|
|
134
|
+
redis_cache_mock.reset_mock()
|
|
135
|
+
delete_mock.reset_mock()
|
|
136
|
+
|
|
137
|
+
# Another call to Redis cache exactly 1 minute after
|
|
138
|
+
# the latest call should not delete the fallback cache again
|
|
139
|
+
read_time += datetime.timedelta(seconds=30)
|
|
140
|
+
with mock.patch("base.utils.datetime") as mock_datetime:
|
|
141
|
+
mock_datetime.now.return_value = read_time
|
|
142
|
+
client.get("round_4")
|
|
143
|
+
|
|
144
|
+
redis_cache_mock.assert_called_once()
|
|
145
|
+
delete_mock.assert_not_called()
|
|
146
|
+
redis_cache_mock.reset_mock()
|
|
147
|
+
delete_mock.reset_mock()
|
|
148
|
+
|
|
149
|
+
# Another call to Redis cache exactly 1 minute and 1 second after
|
|
150
|
+
# the latest call should delete the fallback cache again
|
|
151
|
+
read_time += datetime.timedelta(seconds=1)
|
|
152
|
+
with mock.patch("base.utils.datetime") as mock_datetime:
|
|
153
|
+
mock_datetime.now.return_value = read_time
|
|
154
|
+
client.get("round_5")
|
|
155
|
+
|
|
156
|
+
redis_cache_mock.assert_called_once()
|
|
157
|
+
delete_mock.assert_called_once()
|
|
158
|
+
redis_cache_mock.reset_mock()
|
|
159
|
+
delete_mock.reset_mock()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Test cnfpt configuration."""
|
|
2
|
+
|
|
3
|
+
from unittest import mock
|
|
4
|
+
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
from django.test import TestCase, override_settings
|
|
7
|
+
|
|
8
|
+
from rest_framework.response import Response
|
|
9
|
+
from rest_framework.settings import DEFAULTS, api_settings
|
|
10
|
+
|
|
11
|
+
from richie.apps.search.viewsets.courses import CoursesViewSet
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConfigurationTestCase(TestCase):
|
|
15
|
+
"""Validate that our configuration works as expected."""
|
|
16
|
+
|
|
17
|
+
@mock.patch.object(CoursesViewSet, "list", spec=True, return_value=Response({}))
|
|
18
|
+
def test_configuration_restframework_htaccess(self, _mock_list):
|
|
19
|
+
"""The search API endpoint should work behind an htaccess."""
|
|
20
|
+
# First, check that API calls were broken with the default DRF configuration
|
|
21
|
+
# What was happening is that DRF defines Basic Authentication as a fallback by default
|
|
22
|
+
# and our query has a basic auth header with the username and password of the htaccess
|
|
23
|
+
# defined in nginx. Django was trying to authenticate a user with these credentials,
|
|
24
|
+
# which of course failed.
|
|
25
|
+
with override_settings(
|
|
26
|
+
REST_FRAMEWORK={
|
|
27
|
+
**settings.REST_FRAMEWORK,
|
|
28
|
+
"DEFAULT_AUTHENTICATION_CLASSES": DEFAULTS[
|
|
29
|
+
"DEFAULT_AUTHENTICATION_CLASSES"
|
|
30
|
+
],
|
|
31
|
+
}
|
|
32
|
+
):
|
|
33
|
+
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
|
|
34
|
+
|
|
35
|
+
# The authentication classes are loaded before settings are overriden so we need
|
|
36
|
+
# to mock them on the APIView
|
|
37
|
+
with mock.patch(
|
|
38
|
+
"rest_framework.views.APIView.authentication_classes",
|
|
39
|
+
new_callable=mock.PropertyMock,
|
|
40
|
+
return_value=authentication_classes,
|
|
41
|
+
):
|
|
42
|
+
response = self.client.get(
|
|
43
|
+
"/api/v1.0/courses/",
|
|
44
|
+
HTTP_AUTHORIZATION="Basic dXNlcm5hbWU6cGFzc3dvcmQ=",
|
|
45
|
+
)
|
|
46
|
+
self.assertEqual(response.status_code, 403)
|
|
47
|
+
|
|
48
|
+
# Check that the project configuration solves it
|
|
49
|
+
response = self.client.get(
|
|
50
|
+
"/api/v1.0/courses/", HTTP_AUTHORIZATION="Basic dXNlcm5hbWU6cGFzc3dvcmQ="
|
|
51
|
+
)
|
|
52
|
+
self.assertEqual(response.status_code, 200)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains utility function and the `Throttle` decorator used across the Richie project.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import collections.abc
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from functools import wraps
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def merge_dict(base_dict, update_dict):
|
|
11
|
+
"""Utility for deep dictionary updates.
|
|
12
|
+
|
|
13
|
+
>>> d1 = {'k1':{'k11':{'a': 0, 'b': 1}}}
|
|
14
|
+
>>> d2 = {'k1':{'k11':{'b': 10}, 'k12':{'a': 3}}}
|
|
15
|
+
>>> merge_dict(d1, d2)
|
|
16
|
+
{'k1': {'k11': {'a': 0, 'b': 10}, 'k12':{'a': 3}}}
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
for key, value in update_dict.items():
|
|
20
|
+
if isinstance(value, collections.abc.Mapping):
|
|
21
|
+
base_dict[key] = merge_dict(base_dict.get(key, {}), value)
|
|
22
|
+
else:
|
|
23
|
+
base_dict[key] = value
|
|
24
|
+
return base_dict
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Throttle:
|
|
28
|
+
"""
|
|
29
|
+
Throttle Decorator
|
|
30
|
+
|
|
31
|
+
Limit execution of a function to a defined interval
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, interval):
|
|
35
|
+
"""
|
|
36
|
+
Initialize throttle decorator
|
|
37
|
+
Define the throttle_interval with the provided interval (in seconds)
|
|
38
|
+
and set time_of_last_call to the earliest representable datetime
|
|
39
|
+
"""
|
|
40
|
+
self.throttle_interval = timedelta(seconds=interval)
|
|
41
|
+
self.time_of_last_call = datetime.min
|
|
42
|
+
|
|
43
|
+
def __call__(self, callback):
|
|
44
|
+
"""
|
|
45
|
+
Process `elapsed_since_last_call`,
|
|
46
|
+
if it is greater than `throttle_interval`, callback is executed.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
@wraps(callback)
|
|
50
|
+
def wrapper(*args, **kwargs):
|
|
51
|
+
now = datetime.now()
|
|
52
|
+
elapsed_since_last_call = now - self.time_of_last_call
|
|
53
|
+
|
|
54
|
+
if elapsed_since_last_call < self.throttle_interval:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
self.time_of_last_call = now
|
|
58
|
+
return callback(*args, **kwargs)
|
|
59
|
+
|
|
60
|
+
return wrapper
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Generated by Django 4.2.19 on 2025-02-25 09:38
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
import richie.apps.core.fields.multiselect
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Migration(migrations.Migration):
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
("cms", "0022_auto_20180620_1551"),
|
|
13
|
+
("courses", "0036_courserun_certificate_offer_and_more"),
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
operations = [
|
|
17
|
+
migrations.AlterField(
|
|
18
|
+
model_name="blogpostpluginmodel",
|
|
19
|
+
name="cmsplugin_ptr",
|
|
20
|
+
field=models.OneToOneField(
|
|
21
|
+
auto_created=True,
|
|
22
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
23
|
+
parent_link=True,
|
|
24
|
+
primary_key=True,
|
|
25
|
+
related_name="%(app_label)s_%(class)s",
|
|
26
|
+
serialize=False,
|
|
27
|
+
to="cms.cmsplugin",
|
|
28
|
+
),
|
|
29
|
+
),
|
|
30
|
+
migrations.AlterField(
|
|
31
|
+
model_name="categorypluginmodel",
|
|
32
|
+
name="cmsplugin_ptr",
|
|
33
|
+
field=models.OneToOneField(
|
|
34
|
+
auto_created=True,
|
|
35
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
36
|
+
parent_link=True,
|
|
37
|
+
primary_key=True,
|
|
38
|
+
related_name="%(app_label)s_%(class)s",
|
|
39
|
+
serialize=False,
|
|
40
|
+
to="cms.cmsplugin",
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
migrations.AlterField(
|
|
44
|
+
model_name="coursepluginmodel",
|
|
45
|
+
name="cmsplugin_ptr",
|
|
46
|
+
field=models.OneToOneField(
|
|
47
|
+
auto_created=True,
|
|
48
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
49
|
+
parent_link=True,
|
|
50
|
+
primary_key=True,
|
|
51
|
+
related_name="%(app_label)s_%(class)s",
|
|
52
|
+
serialize=False,
|
|
53
|
+
to="cms.cmsplugin",
|
|
54
|
+
),
|
|
55
|
+
),
|
|
56
|
+
migrations.AlterField(
|
|
57
|
+
model_name="courserun",
|
|
58
|
+
name="languages",
|
|
59
|
+
field=richie.apps.core.fields.multiselect.MultiSelectField(
|
|
60
|
+
choices=[
|
|
61
|
+
("af", "Afrikaans"),
|
|
62
|
+
("ar", "Arabic"),
|
|
63
|
+
("ar-dz", "Algerian Arabic"),
|
|
64
|
+
("ast", "Asturian"),
|
|
65
|
+
("az", "Azerbaijani"),
|
|
66
|
+
("bg", "Bulgarian"),
|
|
67
|
+
("be", "Belarusian"),
|
|
68
|
+
("bn", "Bengali"),
|
|
69
|
+
("br", "Breton"),
|
|
70
|
+
("bs", "Bosnian"),
|
|
71
|
+
("ca", "Catalan"),
|
|
72
|
+
("ckb", "Central Kurdish (Sorani)"),
|
|
73
|
+
("cs", "Czech"),
|
|
74
|
+
("cy", "Welsh"),
|
|
75
|
+
("da", "Danish"),
|
|
76
|
+
("de", "German"),
|
|
77
|
+
("dsb", "Lower Sorbian"),
|
|
78
|
+
("el", "Greek"),
|
|
79
|
+
("en", "English"),
|
|
80
|
+
("en-au", "Australian English"),
|
|
81
|
+
("en-gb", "British English"),
|
|
82
|
+
("eo", "Esperanto"),
|
|
83
|
+
("es", "Spanish"),
|
|
84
|
+
("es-ar", "Argentinian Spanish"),
|
|
85
|
+
("es-co", "Colombian Spanish"),
|
|
86
|
+
("es-mx", "Mexican Spanish"),
|
|
87
|
+
("es-ni", "Nicaraguan Spanish"),
|
|
88
|
+
("es-ve", "Venezuelan Spanish"),
|
|
89
|
+
("et", "Estonian"),
|
|
90
|
+
("eu", "Basque"),
|
|
91
|
+
("fa", "Persian"),
|
|
92
|
+
("fi", "Finnish"),
|
|
93
|
+
("fr", "French"),
|
|
94
|
+
("fy", "Frisian"),
|
|
95
|
+
("ga", "Irish"),
|
|
96
|
+
("gd", "Scottish Gaelic"),
|
|
97
|
+
("gl", "Galician"),
|
|
98
|
+
("he", "Hebrew"),
|
|
99
|
+
("hi", "Hindi"),
|
|
100
|
+
("hr", "Croatian"),
|
|
101
|
+
("hsb", "Upper Sorbian"),
|
|
102
|
+
("hu", "Hungarian"),
|
|
103
|
+
("hy", "Armenian"),
|
|
104
|
+
("ia", "Interlingua"),
|
|
105
|
+
("id", "Indonesian"),
|
|
106
|
+
("ig", "Igbo"),
|
|
107
|
+
("io", "Ido"),
|
|
108
|
+
("is", "Icelandic"),
|
|
109
|
+
("it", "Italian"),
|
|
110
|
+
("ja", "Japanese"),
|
|
111
|
+
("ka", "Georgian"),
|
|
112
|
+
("kab", "Kabyle"),
|
|
113
|
+
("kk", "Kazakh"),
|
|
114
|
+
("km", "Khmer"),
|
|
115
|
+
("kn", "Kannada"),
|
|
116
|
+
("ko", "Korean"),
|
|
117
|
+
("ky", "Kyrgyz"),
|
|
118
|
+
("lb", "Luxembourgish"),
|
|
119
|
+
("lt", "Lithuanian"),
|
|
120
|
+
("lv", "Latvian"),
|
|
121
|
+
("mk", "Macedonian"),
|
|
122
|
+
("ml", "Malayalam"),
|
|
123
|
+
("mn", "Mongolian"),
|
|
124
|
+
("mr", "Marathi"),
|
|
125
|
+
("ms", "Malay"),
|
|
126
|
+
("my", "Burmese"),
|
|
127
|
+
("nb", "Norwegian Bokmål"),
|
|
128
|
+
("ne", "Nepali"),
|
|
129
|
+
("nl", "Dutch"),
|
|
130
|
+
("nn", "Norwegian Nynorsk"),
|
|
131
|
+
("os", "Ossetic"),
|
|
132
|
+
("pa", "Punjabi"),
|
|
133
|
+
("pl", "Polish"),
|
|
134
|
+
("pt", "Portuguese"),
|
|
135
|
+
("pt-br", "Brazilian Portuguese"),
|
|
136
|
+
("ro", "Romanian"),
|
|
137
|
+
("ru", "Russian"),
|
|
138
|
+
("sk", "Slovak"),
|
|
139
|
+
("sl", "Slovenian"),
|
|
140
|
+
("sq", "Albanian"),
|
|
141
|
+
("sr", "Serbian"),
|
|
142
|
+
("sr-latn", "Serbian Latin"),
|
|
143
|
+
("sv", "Swedish"),
|
|
144
|
+
("sw", "Swahili"),
|
|
145
|
+
("ta", "Tamil"),
|
|
146
|
+
("te", "Telugu"),
|
|
147
|
+
("tg", "Tajik"),
|
|
148
|
+
("th", "Thai"),
|
|
149
|
+
("tk", "Turkmen"),
|
|
150
|
+
("tr", "Turkish"),
|
|
151
|
+
("tt", "Tatar"),
|
|
152
|
+
("udm", "Udmurt"),
|
|
153
|
+
("uk", "Ukrainian"),
|
|
154
|
+
("ur", "Urdu"),
|
|
155
|
+
("uz", "Uzbek"),
|
|
156
|
+
("vi", "Vietnamese"),
|
|
157
|
+
("zh-hans", "Simplified Chinese"),
|
|
158
|
+
("zh-hant", "Traditional Chinese"),
|
|
159
|
+
],
|
|
160
|
+
help_text="The list of languages in which the course content is available.",
|
|
161
|
+
max_choices=50,
|
|
162
|
+
max_length=255,
|
|
163
|
+
),
|
|
164
|
+
),
|
|
165
|
+
migrations.AlterField(
|
|
166
|
+
model_name="licencepluginmodel",
|
|
167
|
+
name="cmsplugin_ptr",
|
|
168
|
+
field=models.OneToOneField(
|
|
169
|
+
auto_created=True,
|
|
170
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
171
|
+
parent_link=True,
|
|
172
|
+
primary_key=True,
|
|
173
|
+
related_name="%(app_label)s_%(class)s",
|
|
174
|
+
serialize=False,
|
|
175
|
+
to="cms.cmsplugin",
|
|
176
|
+
),
|
|
177
|
+
),
|
|
178
|
+
migrations.AlterField(
|
|
179
|
+
model_name="organizationpluginmodel",
|
|
180
|
+
name="cmsplugin_ptr",
|
|
181
|
+
field=models.OneToOneField(
|
|
182
|
+
auto_created=True,
|
|
183
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
184
|
+
parent_link=True,
|
|
185
|
+
primary_key=True,
|
|
186
|
+
related_name="%(app_label)s_%(class)s",
|
|
187
|
+
serialize=False,
|
|
188
|
+
to="cms.cmsplugin",
|
|
189
|
+
),
|
|
190
|
+
),
|
|
191
|
+
migrations.AlterField(
|
|
192
|
+
model_name="organizationsbycategorypluginmodel",
|
|
193
|
+
name="cmsplugin_ptr",
|
|
194
|
+
field=models.OneToOneField(
|
|
195
|
+
auto_created=True,
|
|
196
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
197
|
+
parent_link=True,
|
|
198
|
+
primary_key=True,
|
|
199
|
+
related_name="%(app_label)s_%(class)s",
|
|
200
|
+
serialize=False,
|
|
201
|
+
to="cms.cmsplugin",
|
|
202
|
+
),
|
|
203
|
+
),
|
|
204
|
+
migrations.AlterField(
|
|
205
|
+
model_name="personpluginmodel",
|
|
206
|
+
name="cmsplugin_ptr",
|
|
207
|
+
field=models.OneToOneField(
|
|
208
|
+
auto_created=True,
|
|
209
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
210
|
+
parent_link=True,
|
|
211
|
+
primary_key=True,
|
|
212
|
+
related_name="%(app_label)s_%(class)s",
|
|
213
|
+
serialize=False,
|
|
214
|
+
to="cms.cmsplugin",
|
|
215
|
+
),
|
|
216
|
+
),
|
|
217
|
+
migrations.AlterField(
|
|
218
|
+
model_name="programpluginmodel",
|
|
219
|
+
name="cmsplugin_ptr",
|
|
220
|
+
field=models.OneToOneField(
|
|
221
|
+
auto_created=True,
|
|
222
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
223
|
+
parent_link=True,
|
|
224
|
+
primary_key=True,
|
|
225
|
+
related_name="%(app_label)s_%(class)s",
|
|
226
|
+
serialize=False,
|
|
227
|
+
to="cms.cmsplugin",
|
|
228
|
+
),
|
|
229
|
+
),
|
|
230
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Generated by Django 4.2.19 on 2025-02-25 09:40
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("courses", "0037_alter_blogpostpluginmodel_cmsplugin_ptr_and_more"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterField(
|
|
14
|
+
model_name="mainmenuentry",
|
|
15
|
+
name="menu_color",
|
|
16
|
+
field=models.CharField(
|
|
17
|
+
blank=True,
|
|
18
|
+
choices=[("", "None")],
|
|
19
|
+
default="",
|
|
20
|
+
help_text="A color used to display page entry in menu.",
|
|
21
|
+
max_length=50,
|
|
22
|
+
verbose_name="Color in menu",
|
|
23
|
+
),
|
|
24
|
+
),
|
|
25
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Generated by Django 4.2.19 on 2025-02-19 16:43
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
("cms", "0022_auto_20180620_1551"),
|
|
11
|
+
("glimpse", "0003_auto_20201118_1153"),
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
operations = [
|
|
15
|
+
migrations.AlterField(
|
|
16
|
+
model_name="glimpse",
|
|
17
|
+
name="cmsplugin_ptr",
|
|
18
|
+
field=models.OneToOneField(
|
|
19
|
+
auto_created=True,
|
|
20
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
21
|
+
parent_link=True,
|
|
22
|
+
primary_key=True,
|
|
23
|
+
related_name="%(app_label)s_%(class)s",
|
|
24
|
+
serialize=False,
|
|
25
|
+
to="cms.cmsplugin",
|
|
26
|
+
),
|
|
27
|
+
),
|
|
28
|
+
migrations.AlterField(
|
|
29
|
+
model_name="glimpse",
|
|
30
|
+
name="variant",
|
|
31
|
+
field=models.CharField(
|
|
32
|
+
blank=True,
|
|
33
|
+
choices=[
|
|
34
|
+
(None, "Inherit"),
|
|
35
|
+
("badge", "Badge"),
|
|
36
|
+
("card_square", "Square card"),
|
|
37
|
+
("person", "Person"),
|
|
38
|
+
("quote", "Quote"),
|
|
39
|
+
("row_half", "Half row"),
|
|
40
|
+
("row_full", "Full row"),
|
|
41
|
+
],
|
|
42
|
+
default=None,
|
|
43
|
+
help_text="Form factor variant",
|
|
44
|
+
max_length=50,
|
|
45
|
+
null=True,
|
|
46
|
+
verbose_name="Variant",
|
|
47
|
+
),
|
|
48
|
+
),
|
|
49
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Generated by Django 4.2.19 on 2025-02-25 09:35
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
("cms", "0022_auto_20180620_1551"),
|
|
11
|
+
("html_sitemap", "0001_initial"),
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
operations = [
|
|
15
|
+
migrations.AlterField(
|
|
16
|
+
model_name="htmlsitemappage",
|
|
17
|
+
name="cmsplugin_ptr",
|
|
18
|
+
field=models.OneToOneField(
|
|
19
|
+
auto_created=True,
|
|
20
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
21
|
+
parent_link=True,
|
|
22
|
+
primary_key=True,
|
|
23
|
+
related_name="%(app_label)s_%(class)s",
|
|
24
|
+
serialize=False,
|
|
25
|
+
to="cms.cmsplugin",
|
|
26
|
+
),
|
|
27
|
+
),
|
|
28
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Generated by Django 4.2.19 on 2025-02-25 09:35
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
("cms", "0022_auto_20180620_1551"),
|
|
11
|
+
("large_banner", "0003_make_logo_optional"),
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
operations = [
|
|
15
|
+
migrations.AlterField(
|
|
16
|
+
model_name="largebanner",
|
|
17
|
+
name="cmsplugin_ptr",
|
|
18
|
+
field=models.OneToOneField(
|
|
19
|
+
auto_created=True,
|
|
20
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
21
|
+
parent_link=True,
|
|
22
|
+
primary_key=True,
|
|
23
|
+
related_name="%(app_label)s_%(class)s",
|
|
24
|
+
serialize=False,
|
|
25
|
+
to="cms.cmsplugin",
|
|
26
|
+
),
|
|
27
|
+
),
|
|
28
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Generated by Django 4.2.19 on 2025-02-25 09:35
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
("cms", "0022_auto_20180620_1551"),
|
|
11
|
+
("lti_consumer", "0003_auto_20221005_0931"),
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
operations = [
|
|
15
|
+
migrations.AlterField(
|
|
16
|
+
model_name="lticonsumer",
|
|
17
|
+
name="cmsplugin_ptr",
|
|
18
|
+
field=models.OneToOneField(
|
|
19
|
+
auto_created=True,
|
|
20
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
21
|
+
parent_link=True,
|
|
22
|
+
primary_key=True,
|
|
23
|
+
related_name="%(app_label)s_%(class)s",
|
|
24
|
+
serialize=False,
|
|
25
|
+
to="cms.cmsplugin",
|
|
26
|
+
),
|
|
27
|
+
),
|
|
28
|
+
]
|