Source code for codegrade.models.all_site_settings

"""The module that defines the ``AllSiteSettings`` model.

SPDX-License-Identifier: AGPL-3.0-only OR BSD-3-Clause-Clear
"""

from __future__ import annotations

import datetime
import typing as t
from dataclasses import dataclass, field

import cg_request_args as rqa
from cg_maybe import Maybe, Nothing
from cg_maybe.utils import maybe_from_nullable

from ..utils import to_dict
from .frontend_site_settings import FrontendSiteSettings


[docs] @dataclass class AllSiteSettings(FrontendSiteSettings): """The JSON representation of all options.""" #: The default OS that should be used for new ATv2 configurations. new_auto_test_default_os: Maybe[ t.Literal["Ubuntu 20.04", "Ubuntu 24.04"] ] = Nothing #: The minimum strength passwords by users should have. The higher this #: value the stronger the password should be. When increasing the strength #: all users with too weak passwords will be shown a warning on the next #: login. min_password_score: Maybe[int] = Nothing #: The maximum size of uploaded files that are mostly uploaded by "trusted" #: users. Examples of these kind of files include AutoTest fixtures and #: plagiarism base code. max_large_upload_size: Maybe[int] = Nothing #: The maximum total size of uploaded files that are uploaded by normal #: users. This is also the maximum total size of submissions. Increasing #: this size might cause a hosting costs to increase. max_normal_upload_size: Maybe[int] = Nothing #: The maximum size of a single file uploaded by normal users. This limit #: is really here to prevent users from uploading extremely large files #: which can't really be downloaded/shown anyway. max_file_size: Maybe[int] = Nothing #: The time a login session is valid. After this amount of time a user will #: always need to re-authenticate. jwt_access_token_expires: Maybe[datetime.timedelta] = Nothing #: Whether username decollision - adding a number after the username if it #: already exists - should be enabled for SSO tenants. sso_username_decollision_enabled: Maybe[bool] = Nothing #: Should a registration email be sent to new users upon registration. send_registration_email: Maybe[bool] = Nothing #: Also look at context roles when determining the system role for a new #: user in LMSes that have an `extra_roles_mapping` defined in their #: `lms_capabilities`. lti_1p3_system_role_from_context_role: Maybe[bool] = Nothing #: Enable logging of LTI launch data. NEVER ENABLE THIS SITE-WIDE, only for #: a single tenant, and disable this feature after you've gotten the data #: you need. lti_launch_data_logging: Maybe[bool] = Nothing #: Whether or not pearson templates should be enabled. pearson_templates: Maybe[bool] = Nothing #: The teacher role to be used for teachers in new courses. Existing #: courses will not be affected. default_course_teacher_role: Maybe[ t.Literal["Full Teacher", "Non-Editing Teacher"] ] = Nothing #: The TA role to be used for TAs in new courses. Existing courses will not #: be affected. default_course_ta_role: Maybe[t.Literal["Full TA", "Non-Editing TA"]] = ( Nothing ) #: Whether LTI 1.3 launches using cookies (and sessions) should check for #: correct nonce and state. When disabling this feature flag, please be #: mindful. These validations protect us from certain attacks. If unsure, #: consult with the Security Officer before disabling. lti_1p3_nonce_and_state_validation_enabled: Maybe[bool] = Nothing #: Do not store names and emails of users, but always retrieve them through #: NRPS. name_and_email_from_nrps_only: Maybe[bool] = Nothing #: Should we sync teachers to Hubspot. hubspot_syncing_enabled: Maybe[bool] = Nothing #: Whether we are running Codegrade for Pearson specifically, flag aligns #: all the smallest adjustments needed that cannot be made into an actual #: feature. is_pearson: Maybe[bool] = Nothing #: This enables if we change the role of a user in a course when a launch #: is done for a different role than the user currently has. lti_role_switching: Maybe[bool] = Nothing #: Controls whether users are also allowed to pay for courses individually. #: Tenant-wide access passes can always be used if available. If this #: setting is disabled, purchasing an access pass is the only way to pay #: for course access. If there are no tenant passes this setting has no #: influence. per_course_payment_allowed: Maybe[bool] = Nothing #: The payment provider to use for student payments. This should usually be #: configured at the tenant level, and will make new prices created in that #: tenant use this provider. Any existing prices and transactions will not #: be affected. payments_provider: Maybe[t.Literal["paddle", "stripe"]] = Nothing raw_data: t.Optional[t.Dict[str, t.Any]] = field(init=False, repr=False) data_parser: t.ClassVar[t.Any] = rqa.Lazy( lambda: FrontendSiteSettings.data_parser.parser.combine( rqa.FixedMapping( rqa.OptionalArgument( "NEW_AUTO_TEST_DEFAULT_OS", rqa.StringEnum("Ubuntu 20.04", "Ubuntu 24.04"), doc="The default OS that should be used for new ATv2 configurations.", ), rqa.OptionalArgument( "MIN_PASSWORD_SCORE", rqa.SimpleValue.int, doc="The minimum strength passwords by users should have. The higher this value the stronger the password should be. When increasing the strength all users with too weak passwords will be shown a warning on the next login.", ), rqa.OptionalArgument( "MAX_LARGE_UPLOAD_SIZE", rqa.SimpleValue.int, doc='The maximum size of uploaded files that are mostly uploaded by "trusted" users. Examples of these kind of files include AutoTest fixtures and plagiarism base code.', ), rqa.OptionalArgument( "MAX_NORMAL_UPLOAD_SIZE", rqa.SimpleValue.int, doc="The maximum total size of uploaded files that are uploaded by normal users. This is also the maximum total size of submissions. Increasing this size might cause a hosting costs to increase.", ), rqa.OptionalArgument( "MAX_FILE_SIZE", rqa.SimpleValue.int, doc="The maximum size of a single file uploaded by normal users. This limit is really here to prevent users from uploading extremely large files which can't really be downloaded/shown anyway.", ), rqa.OptionalArgument( "JWT_ACCESS_TOKEN_EXPIRES", rqa.RichValue.TimeDelta, doc="The time a login session is valid. After this amount of time a user will always need to re-authenticate.", ), rqa.OptionalArgument( "SSO_USERNAME_DECOLLISION_ENABLED", rqa.SimpleValue.bool, doc="Whether username decollision - adding a number after the username if it already exists - should be enabled for SSO tenants.", ), rqa.OptionalArgument( "SEND_REGISTRATION_EMAIL", rqa.SimpleValue.bool, doc="Should a registration email be sent to new users upon registration.", ), rqa.OptionalArgument( "LTI_1P3_SYSTEM_ROLE_FROM_CONTEXT_ROLE", rqa.SimpleValue.bool, doc="Also look at context roles when determining the system role for a new user in LMSes that have an `extra_roles_mapping` defined in their `lms_capabilities`.", ), rqa.OptionalArgument( "LTI_LAUNCH_DATA_LOGGING", rqa.SimpleValue.bool, doc="Enable logging of LTI launch data. NEVER ENABLE THIS SITE-WIDE, only for a single tenant, and disable this feature after you've gotten the data you need.", ), rqa.OptionalArgument( "PEARSON_TEMPLATES", rqa.SimpleValue.bool, doc="Whether or not pearson templates should be enabled.", ), rqa.OptionalArgument( "DEFAULT_COURSE_TEACHER_ROLE", rqa.StringEnum("Full Teacher", "Non-Editing Teacher"), doc="The teacher role to be used for teachers in new courses. Existing courses will not be affected.", ), rqa.OptionalArgument( "DEFAULT_COURSE_TA_ROLE", rqa.StringEnum("Full TA", "Non-Editing TA"), doc="The TA role to be used for TAs in new courses. Existing courses will not be affected.", ), rqa.OptionalArgument( "LTI_1P3_NONCE_AND_STATE_VALIDATION_ENABLED", rqa.SimpleValue.bool, doc="Whether LTI 1.3 launches using cookies (and sessions) should check for correct nonce and state. When disabling this feature flag, please be mindful. These validations protect us from certain attacks. If unsure, consult with the Security Officer before disabling.", ), rqa.OptionalArgument( "NAME_AND_EMAIL_FROM_NRPS_ONLY", rqa.SimpleValue.bool, doc="Do not store names and emails of users, but always retrieve them through NRPS.", ), rqa.OptionalArgument( "HUBSPOT_SYNCING_ENABLED", rqa.SimpleValue.bool, doc="Should we sync teachers to Hubspot.", ), rqa.OptionalArgument( "IS_PEARSON", rqa.SimpleValue.bool, doc="Whether we are running Codegrade for Pearson specifically, flag aligns all the smallest adjustments needed that cannot be made into an actual feature.", ), rqa.OptionalArgument( "LTI_ROLE_SWITCHING", rqa.SimpleValue.bool, doc="This enables if we change the role of a user in a course when a launch is done for a different role than the user currently has.", ), rqa.OptionalArgument( "PER_COURSE_PAYMENT_ALLOWED", rqa.SimpleValue.bool, doc="Controls whether users are also allowed to pay for courses individually. Tenant-wide access passes can always be used if available. If this setting is disabled, purchasing an access pass is the only way to pay for course access. If there are no tenant passes this setting has no influence.", ), rqa.OptionalArgument( "PAYMENTS_PROVIDER", rqa.StringEnum("paddle", "stripe"), doc="The payment provider to use for student payments. This should usually be configured at the tenant level, and will make new prices created in that tenant use this provider. Any existing prices and transactions will not be affected.", ), ) ).use_readable_describe(True) ) def __post_init__(self) -> None: getattr(super(), "__post_init__", lambda: None)() self.new_auto_test_default_os = maybe_from_nullable( self.new_auto_test_default_os ) self.min_password_score = maybe_from_nullable(self.min_password_score) self.max_large_upload_size = maybe_from_nullable( self.max_large_upload_size ) self.max_normal_upload_size = maybe_from_nullable( self.max_normal_upload_size ) self.max_file_size = maybe_from_nullable(self.max_file_size) self.jwt_access_token_expires = maybe_from_nullable( self.jwt_access_token_expires ) self.sso_username_decollision_enabled = maybe_from_nullable( self.sso_username_decollision_enabled ) self.send_registration_email = maybe_from_nullable( self.send_registration_email ) self.lti_1p3_system_role_from_context_role = maybe_from_nullable( self.lti_1p3_system_role_from_context_role ) self.lti_launch_data_logging = maybe_from_nullable( self.lti_launch_data_logging ) self.pearson_templates = maybe_from_nullable(self.pearson_templates) self.default_course_teacher_role = maybe_from_nullable( self.default_course_teacher_role ) self.default_course_ta_role = maybe_from_nullable( self.default_course_ta_role ) self.lti_1p3_nonce_and_state_validation_enabled = maybe_from_nullable( self.lti_1p3_nonce_and_state_validation_enabled ) self.name_and_email_from_nrps_only = maybe_from_nullable( self.name_and_email_from_nrps_only ) self.hubspot_syncing_enabled = maybe_from_nullable( self.hubspot_syncing_enabled ) self.is_pearson = maybe_from_nullable(self.is_pearson) self.lti_role_switching = maybe_from_nullable(self.lti_role_switching) self.per_course_payment_allowed = maybe_from_nullable( self.per_course_payment_allowed ) self.payments_provider = maybe_from_nullable(self.payments_provider) def to_dict(self) -> t.Dict[str, t.Any]: res: t.Dict[str, t.Any] = {} if self.new_auto_test_ubuntu_20_04_base_image_ids.is_just: res["NEW_AUTO_TEST_UBUNTU_20_04_BASE_IMAGE_IDS"] = to_dict( self.new_auto_test_ubuntu_20_04_base_image_ids.value ) if self.new_auto_test_ubuntu_24_04_base_image_ids.is_just: res["NEW_AUTO_TEST_UBUNTU_24_04_BASE_IMAGE_IDS"] = to_dict( self.new_auto_test_ubuntu_24_04_base_image_ids.value ) if self.quiz_minimum_questions_for_dropdown.is_just: res["QUIZ_MINIMUM_QUESTIONS_FOR_DROPDOWN"] = to_dict( self.quiz_minimum_questions_for_dropdown.value ) if self.login_token_before_time.is_just: res["LOGIN_TOKEN_BEFORE_TIME"] = to_dict( self.login_token_before_time.value ) if self.access_token_toast_warning_time.is_just: res["ACCESS_TOKEN_TOAST_WARNING_TIME"] = to_dict( self.access_token_toast_warning_time.value ) if self.access_token_modal_warning_time.is_just: res["ACCESS_TOKEN_MODAL_WARNING_TIME"] = to_dict( self.access_token_modal_warning_time.value ) if self.max_plagiarism_matches.is_just: res["MAX_PLAGIARISM_MATCHES"] = to_dict( self.max_plagiarism_matches.value ) if self.assignment_default_grading_scale.is_just: res["ASSIGNMENT_DEFAULT_GRADING_SCALE"] = to_dict( self.assignment_default_grading_scale.value ) if self.assignment_default_grading_scale_points.is_just: res["ASSIGNMENT_DEFAULT_GRADING_SCALE_POINTS"] = to_dict( self.assignment_default_grading_scale_points.value ) if self.blackboard_zip_upload_enabled.is_just: res["BLACKBOARD_ZIP_UPLOAD_ENABLED"] = to_dict( self.blackboard_zip_upload_enabled.value ) if self.register_enabled.is_just: res["REGISTER_ENABLED"] = to_dict(self.register_enabled.value) if self.student_payment_main_option.is_just: res["STUDENT_PAYMENT_MAIN_OPTION"] = to_dict( self.student_payment_main_option.value ) if self.grading_notifications_enabled.is_just: res["GRADING_NOTIFICATIONS_ENABLED"] = to_dict( self.grading_notifications_enabled.value ) if self.assignment_percentage_decimals.is_just: res["ASSIGNMENT_PERCENTAGE_DECIMALS"] = to_dict( self.assignment_percentage_decimals.value ) if self.assignment_point_decimals.is_just: res["ASSIGNMENT_POINT_DECIMALS"] = to_dict( self.assignment_point_decimals.value ) if self.lti_lock_date_copying_enabled.is_just: res["LTI_LOCK_DATE_COPYING_ENABLED"] = to_dict( self.lti_lock_date_copying_enabled.value ) if self.assignment_max_points_enabled.is_just: res["ASSIGNMENT_MAX_POINTS_ENABLED"] = to_dict( self.assignment_max_points_enabled.value ) if self.inline_rubric_viewer_enabled.is_just: res["INLINE_RUBRIC_VIEWER_ENABLED"] = to_dict( self.inline_rubric_viewer_enabled.value ) if self.hide_code_editor_output_viewer_with_only_quiz_steps.is_just: res["HIDE_CODE_EDITOR_OUTPUT_VIEWER_WITH_ONLY_QUIZ_STEPS"] = ( to_dict( self.hide_code_editor_output_viewer_with_only_quiz_steps.value ) ) if self.hide_code_editor_filetree_controls_with_only_quiz_steps.is_just: res["HIDE_CODE_EDITOR_FILETREE_CONTROLS_WITH_ONLY_QUIZ_STEPS"] = ( to_dict( self.hide_code_editor_filetree_controls_with_only_quiz_steps.value ) ) if self.simple_submission_navigate_to_latest_editor_session.is_just: res["SIMPLE_SUBMISSION_NAVIGATE_TO_LATEST_EDITOR_SESSION"] = ( to_dict( self.simple_submission_navigate_to_latest_editor_session.value ) ) if self.display_grades_enabled.is_just: res["DISPLAY_GRADES_ENABLED"] = to_dict( self.display_grades_enabled.value ) if self.default_submission_page_tab.is_just: res["DEFAULT_SUBMISSION_PAGE_TAB"] = to_dict( self.default_submission_page_tab.value ) if self.hide_no_deadline_enabled.is_just: res["HIDE_NO_DEADLINE_ENABLED"] = to_dict( self.hide_no_deadline_enabled.value ) if self.code_editor_start_on_assignment_description.is_just: res["CODE_EDITOR_START_ON_ASSIGNMENT_DESCRIPTION"] = to_dict( self.code_editor_start_on_assignment_description.value ) if self.ai_assistant_enabled.is_just: res["AI_ASSISTANT_ENABLED"] = to_dict( self.ai_assistant_enabled.value ) if self.assistant_models.is_just: res["ASSISTANT_MODELS"] = to_dict(self.assistant_models.value) if self.prompt_engineering_step_enabled.is_just: res["PROMPT_ENGINEERING_STEP_ENABLED"] = to_dict( self.prompt_engineering_step_enabled.value ) if self.download_editor_files_enabled.is_just: res["DOWNLOAD_EDITOR_FILES_ENABLED"] = to_dict( self.download_editor_files_enabled.value ) if self.new_auto_test_default_os.is_just: res["NEW_AUTO_TEST_DEFAULT_OS"] = to_dict( self.new_auto_test_default_os.value ) if self.min_password_score.is_just: res["MIN_PASSWORD_SCORE"] = to_dict(self.min_password_score.value) if self.max_large_upload_size.is_just: res["MAX_LARGE_UPLOAD_SIZE"] = to_dict( self.max_large_upload_size.value ) if self.max_normal_upload_size.is_just: res["MAX_NORMAL_UPLOAD_SIZE"] = to_dict( self.max_normal_upload_size.value ) if self.max_file_size.is_just: res["MAX_FILE_SIZE"] = to_dict(self.max_file_size.value) if self.jwt_access_token_expires.is_just: res["JWT_ACCESS_TOKEN_EXPIRES"] = to_dict( self.jwt_access_token_expires.value ) if self.sso_username_decollision_enabled.is_just: res["SSO_USERNAME_DECOLLISION_ENABLED"] = to_dict( self.sso_username_decollision_enabled.value ) if self.send_registration_email.is_just: res["SEND_REGISTRATION_EMAIL"] = to_dict( self.send_registration_email.value ) if self.lti_1p3_system_role_from_context_role.is_just: res["LTI_1P3_SYSTEM_ROLE_FROM_CONTEXT_ROLE"] = to_dict( self.lti_1p3_system_role_from_context_role.value ) if self.lti_launch_data_logging.is_just: res["LTI_LAUNCH_DATA_LOGGING"] = to_dict( self.lti_launch_data_logging.value ) if self.pearson_templates.is_just: res["PEARSON_TEMPLATES"] = to_dict(self.pearson_templates.value) if self.default_course_teacher_role.is_just: res["DEFAULT_COURSE_TEACHER_ROLE"] = to_dict( self.default_course_teacher_role.value ) if self.default_course_ta_role.is_just: res["DEFAULT_COURSE_TA_ROLE"] = to_dict( self.default_course_ta_role.value ) if self.lti_1p3_nonce_and_state_validation_enabled.is_just: res["LTI_1P3_NONCE_AND_STATE_VALIDATION_ENABLED"] = to_dict( self.lti_1p3_nonce_and_state_validation_enabled.value ) if self.name_and_email_from_nrps_only.is_just: res["NAME_AND_EMAIL_FROM_NRPS_ONLY"] = to_dict( self.name_and_email_from_nrps_only.value ) if self.hubspot_syncing_enabled.is_just: res["HUBSPOT_SYNCING_ENABLED"] = to_dict( self.hubspot_syncing_enabled.value ) if self.is_pearson.is_just: res["IS_PEARSON"] = to_dict(self.is_pearson.value) if self.lti_role_switching.is_just: res["LTI_ROLE_SWITCHING"] = to_dict(self.lti_role_switching.value) if self.per_course_payment_allowed.is_just: res["PER_COURSE_PAYMENT_ALLOWED"] = to_dict( self.per_course_payment_allowed.value ) if self.payments_provider.is_just: res["PAYMENTS_PROVIDER"] = to_dict(self.payments_provider.value) return res @classmethod def from_dict( cls: t.Type[AllSiteSettings], d: t.Dict[str, t.Any] ) -> AllSiteSettings: parsed = cls.data_parser.try_parse(d) res = cls( new_auto_test_ubuntu_20_04_base_image_ids=parsed.NEW_AUTO_TEST_UBUNTU_20_04_BASE_IMAGE_IDS, new_auto_test_ubuntu_24_04_base_image_ids=parsed.NEW_AUTO_TEST_UBUNTU_24_04_BASE_IMAGE_IDS, quiz_minimum_questions_for_dropdown=parsed.QUIZ_MINIMUM_QUESTIONS_FOR_DROPDOWN, login_token_before_time=parsed.LOGIN_TOKEN_BEFORE_TIME, access_token_toast_warning_time=parsed.ACCESS_TOKEN_TOAST_WARNING_TIME, access_token_modal_warning_time=parsed.ACCESS_TOKEN_MODAL_WARNING_TIME, max_plagiarism_matches=parsed.MAX_PLAGIARISM_MATCHES, assignment_default_grading_scale=parsed.ASSIGNMENT_DEFAULT_GRADING_SCALE, assignment_default_grading_scale_points=parsed.ASSIGNMENT_DEFAULT_GRADING_SCALE_POINTS, blackboard_zip_upload_enabled=parsed.BLACKBOARD_ZIP_UPLOAD_ENABLED, register_enabled=parsed.REGISTER_ENABLED, student_payment_main_option=parsed.STUDENT_PAYMENT_MAIN_OPTION, grading_notifications_enabled=parsed.GRADING_NOTIFICATIONS_ENABLED, assignment_percentage_decimals=parsed.ASSIGNMENT_PERCENTAGE_DECIMALS, assignment_point_decimals=parsed.ASSIGNMENT_POINT_DECIMALS, lti_lock_date_copying_enabled=parsed.LTI_LOCK_DATE_COPYING_ENABLED, assignment_max_points_enabled=parsed.ASSIGNMENT_MAX_POINTS_ENABLED, inline_rubric_viewer_enabled=parsed.INLINE_RUBRIC_VIEWER_ENABLED, hide_code_editor_output_viewer_with_only_quiz_steps=parsed.HIDE_CODE_EDITOR_OUTPUT_VIEWER_WITH_ONLY_QUIZ_STEPS, hide_code_editor_filetree_controls_with_only_quiz_steps=parsed.HIDE_CODE_EDITOR_FILETREE_CONTROLS_WITH_ONLY_QUIZ_STEPS, simple_submission_navigate_to_latest_editor_session=parsed.SIMPLE_SUBMISSION_NAVIGATE_TO_LATEST_EDITOR_SESSION, display_grades_enabled=parsed.DISPLAY_GRADES_ENABLED, default_submission_page_tab=parsed.DEFAULT_SUBMISSION_PAGE_TAB, hide_no_deadline_enabled=parsed.HIDE_NO_DEADLINE_ENABLED, code_editor_start_on_assignment_description=parsed.CODE_EDITOR_START_ON_ASSIGNMENT_DESCRIPTION, ai_assistant_enabled=parsed.AI_ASSISTANT_ENABLED, assistant_models=parsed.ASSISTANT_MODELS, prompt_engineering_step_enabled=parsed.PROMPT_ENGINEERING_STEP_ENABLED, download_editor_files_enabled=parsed.DOWNLOAD_EDITOR_FILES_ENABLED, new_auto_test_default_os=parsed.NEW_AUTO_TEST_DEFAULT_OS, min_password_score=parsed.MIN_PASSWORD_SCORE, max_large_upload_size=parsed.MAX_LARGE_UPLOAD_SIZE, max_normal_upload_size=parsed.MAX_NORMAL_UPLOAD_SIZE, max_file_size=parsed.MAX_FILE_SIZE, jwt_access_token_expires=parsed.JWT_ACCESS_TOKEN_EXPIRES, sso_username_decollision_enabled=parsed.SSO_USERNAME_DECOLLISION_ENABLED, send_registration_email=parsed.SEND_REGISTRATION_EMAIL, lti_1p3_system_role_from_context_role=parsed.LTI_1P3_SYSTEM_ROLE_FROM_CONTEXT_ROLE, lti_launch_data_logging=parsed.LTI_LAUNCH_DATA_LOGGING, pearson_templates=parsed.PEARSON_TEMPLATES, default_course_teacher_role=parsed.DEFAULT_COURSE_TEACHER_ROLE, default_course_ta_role=parsed.DEFAULT_COURSE_TA_ROLE, lti_1p3_nonce_and_state_validation_enabled=parsed.LTI_1P3_NONCE_AND_STATE_VALIDATION_ENABLED, name_and_email_from_nrps_only=parsed.NAME_AND_EMAIL_FROM_NRPS_ONLY, hubspot_syncing_enabled=parsed.HUBSPOT_SYNCING_ENABLED, is_pearson=parsed.IS_PEARSON, lti_role_switching=parsed.LTI_ROLE_SWITCHING, per_course_payment_allowed=parsed.PER_COURSE_PAYMENT_ALLOWED, payments_provider=parsed.PAYMENTS_PROVIDER, ) res.raw_data = d return res
import os if os.getenv("CG_GENERATING_DOCS", "False").lower() in ("", "true"): from .fraction import Fraction