Newer
Older
f"""
<script type="text/javascript">
rlCodemirror.editorFromTextArea(
document.getElementById('id_{name}'),
{repr_js(extensions)},
{repr_js(self.autofocus)},
{repr_js(additional_keys)}
)
</script>
"""]
return mark_safe("\n".join(output))
def get_codemirror_widget(
language_mode: str,
interaction_mode: str | None,
autofocus: bool = False,
additional_keys: dict[str, JsLiteral] | None = None,
) -> tuple[CodeMirrorTextarea, str]:
if additional_keys is None:
additional_keys = {}
from django.urls import reverse
help_text = (_("Press Esc then Tab to leave the editor. ")
+ _("Set editor mode in <a href='%s'>user profile</a>.")
% reverse("relate-user_profile"))
Andreas Klöckner
committed
Andreas Klöckner
committed
indent_unit = 4
else:
indent_unit = 2
return CodeMirrorTextarea(
language_mode=language_mode,
interaction_mode=interaction_mode,
indent_unit=indent_unit,
autofocus=autofocus,
additional_keys=additional_keys,
), help_text
Andreas Klöckner
committed
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
# {{{ prosemirror
class ProseMirrorTextarea(forms.Textarea):
@property
def media(self):
return forms.Media(js=["bundle-prosemirror.js"])
def render(self, name, value, attrs=None, renderer=None) -> SafeString:
output = [super().render(
name, value, attrs, renderer),
f"""
<script type="text/javascript">
rlProsemirror.editorFromTextArea(
document.getElementById('id_{name}'),
)
</script>
"""]
return mark_safe("\n".join(output))
math_help_text = mark_safe(r"""
See the <a href="https://katex.org/docs/supported.html"
>list of supported math commands</a>.
More tips for using this editor to type math:
<ul>
<li>
Inline math nodes are delimited with <code>$</code>.
After typing the closing dollar sign in
an expression like <code>$\int_a^b f(x) dx$</code>, a math node will appear.
</li>
<li>
To start a block math node, press Enter to create a blank line,
then type <code>$$</code> followed by Space. You can type multi-line math
expressions, and the result will render in display style.
</li>
<li>
Math nodes behave like regular text when using arrow keys or Backspace.
From within a math node, press Ctrl-Backspace to delete the entire node.
You can select, copy, and paste math nodes just like regular text!
</li>
</ul>
""")
# }}}
# {{{ facility processing
def get_facilities_config(
request: http.HttpRequest | None = None
) -> dict[str, dict[str, Any]] | None:
from django.conf import settings
# This is called during offline validation, where Django isn't really set up.
# The getattr makes this usable.
facilities = getattr(settings, "RELATE_FACILITIES", None)
if facilities is None:
# Only happens during offline validation. Suppresses errors there.
return None
if callable(facilities):
from course.views import get_now_or_fake_time
now_datetime = get_now_or_fake_time(request)
result = facilities(now_datetime)
if not isinstance(result, dict):
raise RuntimeError("RELATE_FACILITIES must return a dictionary")
return result
else:
return facilities
class FacilityFindingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request: http.HttpRequest) -> http.HttpResponse:
pretend_facilities = request.session.get("relate_pretend_facilities")
if pretend_facilities is not None:
facilities = pretend_facilities
else:
remote_address = remote_address_from_request(request)
facilities = set()
facilities_config = get_facilities_config(request)
if facilities_config is None:
facilities_config = {}
from ipaddress import ip_network
for name, props in facilities_config.items():
ip_ranges = props.get("ip_ranges", [])
for ir in ip_ranges:
if remote_address in ip_network(str(ir)):
facilities.add(name)
request = cast(RelateHttpRequest, request)
request.relate_facilities = frozenset(facilities)
return self.get_response(request)
def get_col_contents_or_empty(row, index):
if index >= len(row):
return ""
else:
return row[index]
def csv_data_importable(file_contents, column_idx_list, header_count):
import csv
spamreader = csv.reader(file_contents)
n_header_row = 0
except Exception as e:
err_msg = type(e).__name__
err_str = str(e)
if err_msg == "Error":
err_msg = ""
else:
err_msg += ": "
err_msg += err_str
if "line contains NUL" in err_str:
# This message changed over time.
# Make the message uniform to please the tests.
err_msg = err_msg.replace("NULL byte", "NUL")
err_msg += _("Are you sure the file is a CSV file other "
"than a Microsoft Excel file?")
return False, (
string_concat(
pgettext_lazy("Starting of Error message", "Error"),
from itertools import chain
for row in chain([row0], spamreader):
n_header_row += 1
if n_header_row <= header_count:
continue
try:
for column_idx in column_idx_list:
if column_idx is not None:
str(get_col_contents_or_empty(row, column_idx-1))
except UnicodeDecodeError:
return False, (
_("Error: Columns to be imported contain "
"non-ASCII characters. "
"Please save your CSV file as utf-8 encoded "
"and import again.")
)
except Exception as e:
return False, (
string_concat(
pgettext_lazy("Starting of Error message",
"Error"),
": %(err_type)s: %(err_str)s")
% {
"err_type": type(e).__name__,
"err_str": str(e)}
)
return True, ""
def will_use_masked_profile_for_email(
recipient_email: None | str | list[str]) -> bool:
if not recipient_email:
return False
if not isinstance(recipient_email, list):
recipient_email = [recipient_email]
from course.models import Participation
Participation.objects.filter(
user__email__in=recipient_email
))
from course.constants import participation_permission as pperm
if part.has_permission(pperm.view_participant_masked_profile):
return True
return False
def get_course_specific_language_choices() -> tuple[tuple[str, Any], ...]:
from collections import OrderedDict
all_options = ((settings.LANGUAGE_CODE, None), *tuple(settings.LANGUAGES))
filtered_options_dict = OrderedDict(all_options)
def get_default_option() -> tuple[str, str]:
# For the default language used, if USE_I18N is True, display
# "Disabled". Otherwise display its lang info.
if not settings.USE_I18N:
formatted_descr = (
get_formatted_options(settings.LANGUAGE_CODE, None)[1])
else:
formatted_descr = _("disabled (i.e., displayed language is "
"determined by user's browser preference)")
return "", string_concat("{}: ".format(_("Default")), formatted_descr)
def get_formatted_options(
lang_code: str, lang_descr: str | None) -> tuple[str, str]:
if lang_descr is None:
lang_descr = OrderedDict(settings.LANGUAGES).get(lang_code)
if lang_descr is None:
try:
lang_info = translation.get_language_info(lang_code)
lang_descr = lang_info["name_translated"]
except KeyError:
return (lang_code.strip(), lang_code)
return (lang_code.strip(),
string_concat(_(lang_descr), f" ({lang_code})"))
filtered_options = (
[get_default_option()]
+ [get_formatted_options(k, v)
for k, v in filtered_options_dict.items()])
# filtered_options[1] is the option for settings.LANGUAGE_CODE
# it's already displayed when settings.USE_I18N is False
if not settings.USE_I18N:
filtered_options.pop(1)
return tuple(filtered_options)
class LanguageOverride(ContextDecorator):
def __init__(self, course: Course, deactivate: bool = False) -> None:
self.course = course
self.deactivate = deactivate
if course.force_lang:
self.language = course.force_lang
else:
from django.conf import settings
self.language = settings.RELATE_ADMIN_EMAIL_LOCALE
def __enter__(self) -> None:
self.old_language = translation.get_language()
if self.language is not None:
translation.activate(self.language)
else:
translation.deactivate_all()
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
if self.old_language is None:
translation.deactivate_all()
elif self.deactivate:
translation.deactivate()
else:
translation.activate(self.old_language)
class RelateJinjaMacroBase:
def __init__(
self,
course: Course | None,
repo: Repo_ish,
commit_sha: bytes) -> None:
self.course = course
self.repo = repo
self.commit_sha = commit_sha
@property
def name(self):
# The name of the method used in the template
raise NotImplementedError()
def __call__(self, *args: Any, **kwargs: Any) -> str:
raise NotImplementedError()