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
# {{{ 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>
You may paste in Markdown-with-math (as accepted by
<a
href="https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/writing-mathematical-expressions"
>Github</a>,
<a href="https://pandoc.org/MANUAL.html#math">Pandoc</a>, or
<a href="https://meta.discourse.org/t/discourse-math/65770">Discourse</a>).
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
<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: str | list[str] | None) -> 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()