Skip to content

create openedx envs common settings #36941

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

wgu-taylor-payne
Copy link
Contributor

@wgu-taylor-payne wgu-taylor-payne commented Jun 23, 2025

Description

Pull settings explicitly shared by the LMS and CMS into a common settings module at openedx/envs/common.py that lms/envs/common.py and cms/envs/common.py inherit from. For now, only settings explicitly shared settings (via import in cms/envs/common.py) were targeted to move up into the new shared module.

This is one of the initial steps in the effort to simplify Django settings in the edx-platform (see the related ADR: 0022-settings-simplification).

Documented settings in openedx/envs/common.py will be included on the settings reference page.

Supporting information

Resolves #36889.

Testing instructions

I've used the diff_settings.sh script as a foundation to create this diff_settings.py script that I used to test the settings changes.

diff_settings.py

#!/usr/bin/env python3
"""
Script to dump Django settings for different configurations.
Compares main branch with feature branch settings.

Compatible with Python 3.11+
"""

import os
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Tuple

import click
import re


# Configuration tuples: (django_settings_module, config_file)
CONFIGS = [
    ("lms.envs.production", "lms/envs/minimal.yml"),
    ("lms.envs.production", "lms/envs/mock.yml"),
    ("lms.envs.tutor.production", "/openedx/config/lms.env.yml"),
    ("lms.envs.tutor.development", "/openedx/config/lms.env.yml"),
    ("cms.envs.production", "cms/envs/mock.yml"),
    ("cms.envs.tutor.production", "/openedx/config/cms.env.yml"),
    ("cms.envs.tutor.development", "/openedx/config/cms.env.yml"),
]

# Output directories (defaults)
DEFAULT_MAIN_DIR = "main_settings_dump"
DEFAULT_FEATURE_DIR = "feature_settings_dump"


def run_command(cmd: list[str], verbose: bool = False, **kwargs) -> subprocess.CompletedProcess:
    """Run a command and return the result."""
    # DJANGO_SETTINGS_MODULE=cms.envs.tutor.development CMS_CFG=/openedx/config/cms.env.yml ./manage.py cms dump_settings
    try:
        if verbose:
            # Show command output in real-time
            result = subprocess.run(cmd, check=True, text=True, **kwargs)
        else:
            # Capture output silently
            result = subprocess.run(cmd, check=True, capture_output=True, text=True, **kwargs)
        return result
    except subprocess.CalledProcessError as e:
        if verbose:
            click.echo(click.style(f"Error running command: {' '.join(cmd)}", fg='red'), err=True)
            click.echo(click.style(f"Return code: {e.returncode}", fg='red'), err=True)
            if e.stdout:
                click.echo(click.style(f"STDOUT: {e.stdout}", fg='red'), err=True)
            if e.stderr:
                click.echo(click.style(f"STDERR: {e.stderr}", fg='red'), err=True)
        raise


def normalize_filename(text: str) -> str:
    """
    Normalize a string to be used as a filename component.
    
    Converts characters like '/', '-' to underscores and removes file extensions,
    but preserves dots in module paths like 'cms.envs.production'.
    """
    # Only remove file extensions from actual file paths (containing '/')
    if '/' in text:
        text = re.sub(r'\.[^/]*$', '', text)
    
    # Replace forward slashes and hyphens with underscores
    text = re.sub(r'[/-]', '_', text)
    
    # Replace multiple underscores with single underscore
    text = re.sub(r'_+', '_', text)

    # Remove leading/trailing underscores
    text = text.strip('_')

    return text


def generate_output_filename(django_settings_module: str, config_file: str) -> str:
    """Generate output filename from django settings module and config file path."""
    normalized_settings = normalize_filename(django_settings_module)
    normalized_config = normalize_filename(config_file)
    return f"{normalized_settings}__{normalized_config}"


def get_app_type_and_env_var(django_settings_module: str) -> Tuple[str, str]:
    """Determine app type and environment variable name from settings module."""
    app_type = django_settings_module.split(".")[0]
    
    if app_type in ("lms", "cms"):
        env_var = f"{app_type.upper()}_CFG"
        return app_type, env_var
    else:
        raise ValueError(f"Unknown settings module type: {django_settings_module}")


def dump_settings(django_settings_module: str, config_file: str, output_file: Path, verbose: bool = False) -> None:
    """Dump settings for a given configuration."""
    app_type, config_env_var = get_app_type_and_env_var(django_settings_module)
    
    if verbose:
        click.echo(f"Dumping {django_settings_module} with {config_file} -> {output_file}")
    
    # Set up environment
    env = os.environ.copy()
    env["DJANGO_SETTINGS_MODULE"] = django_settings_module
    env["SERVICE_VARIANT"] = app_type
    env[config_env_var] = config_file
    
    # Run the dump command
    cmd = ["./manage.py", app_type, "dump_settings"]

    if verbose:
        click.echo(click.style(f"", fg='blue'), err=True)
        click.echo(click.style(f"DJANGO_SETTINGS_MODULE: {django_settings_module} \\", fg='blue'), err=True)
        click.echo(click.style(f"SERVICE_VARIANT: {env.get('SERVICE_VARIANT')} \\", fg='blue'), err=True)
        click.echo(click.style(f"{config_env_var}: {config_file} \\", fg='blue'), err=True)
        click.echo(click.style(f"{' '.join(cmd)} > {output_file}", fg='blue'), err=True)

    result = run_command(cmd, verbose=verbose, env=env)
    
    # Write output to file
    output_file.write_text(result.stdout)


def switch_branch(branch: str, verbose: bool = False) -> None:
    """Switch to the specified git branch."""
    if verbose:
        click.echo(f"Switching to branch: {branch}")
    run_command(["git", "switch", branch], verbose=verbose)


def process_branch(branch: str, output_dir: Path, verbose: bool = False) -> None:
    """Process all configurations for a branch."""
    click.echo(click.style(f"=== Processing branch: {branch} ===", fg='blue', bold=True))
    switch_branch(branch, verbose)
    
    # Ensure output directory exists
    output_dir.mkdir(exist_ok=True)
    
    with click.progressbar(CONFIGS, label='Processing configurations') as configs:
        for django_settings_module, config_file in configs:
            output_filename = generate_output_filename(django_settings_module, config_file)
            output_file = output_dir / f"{output_filename}.json"
            
            try:
                dump_settings(django_settings_module, config_file, output_file, verbose)
            except Exception as e:
                if verbose:
                    click.echo(click.style(
                        f"Error processing {django_settings_module} with {config_file}: {e}", 
                        fg='red'
                    ), err=True)
                continue
    
    click.echo(click.style(f"Completed processing branch: {branch}", fg='green'))
    click.echo()


@click.command()
@click.argument('main_branch')
@click.argument('feature_branch')
@click.option(
    '--main-dir', 
    default=DEFAULT_MAIN_DIR,
    help='Output directory for main branch settings',
    show_default=True
)
@click.option(
    '--branch-dir',
    default=DEFAULT_FEATURE_DIR,
    help='Output directory for feature branch settings',
    show_default=True
)
@click.option(
    '--verbose', '-v',
    is_flag=True,
    help='Enable verbose output and show command output in real-time'
)
def main(main_branch: str, feature_branch: str, main_dir: str, branch_dir: str, verbose: bool) -> None:
    """
    Dump Django settings for different configurations and compare branches.
    
    MAIN_BRANCH: The main/master branch to compare against
    FEATURE_BRANCH: The feature branch to compare
    """
    # Set up directories
    main_dir_path = Path(main_dir)
    branch_dir_path = Path(branch_dir)
    
    click.echo("Starting Django settings dump comparison...")
    click.echo(f"Comparing {main_branch} -> {feature_branch}")
    click.echo(f"Output: {main_dir_path}/ and {branch_dir_path}/")
    click.echo()
    
    try:
        # Clean and create output directories
        if main_dir_path.exists():
            click.echo(f"Cleaning existing directory: {main_dir_path}")
            shutil.rmtree(main_dir_path)
        main_dir_path.mkdir(parents=True)
        
        if branch_dir_path.exists():
            click.echo(f"Cleaning existing directory: {branch_dir_path}")
            shutil.rmtree(branch_dir_path)
        branch_dir_path.mkdir(parents=True)
        
        # Process main branch
        process_branch(main_branch, main_dir_path, verbose)
        
        # Process feature branch
        process_branch(feature_branch, branch_dir_path, verbose)
        
        # Run diff comparison
        click.echo(click.style("=== Running diff comparison ===", fg='blue', bold=True))
        try:
            diff_cmd = ["diff", str(main_dir_path), str(branch_dir_path)]
            # Always show diff output, don't suppress it
            result = run_command(diff_cmd, verbose=verbose)
            
            # diff returns 0 if files are identical, 1 if different, but run_command
            # will raise an exception for non-zero return codes, so we handle it differently
            click.echo(click.style("No differences found between branches", fg='green'))
                
        except subprocess.CalledProcessError as e:
            # diff returns 1 when differences are found, which is normal
            if e.returncode == 1:
                if e.stdout:
                    click.echo(e.stdout)
                click.echo(click.style("Differences found between branches (shown above)", fg='yellow'))
            else:
                click.echo(click.style(f"Diff command failed with return code {e.returncode}", fg='red'), err=True)
                if e.stderr:
                    click.echo(click.style(e.stderr, fg='red'), err=True)
        except FileNotFoundError:
            click.echo(click.style("diff command not found - skipping comparison", fg='yellow'), err=True)
        except Exception as e:
            click.echo(click.style(f"Error running diff: {e}", fg='red'), err=True)
        
        click.echo()
        click.echo(click.style("=== Settings dump complete ===", fg='green', bold=True))
        click.echo(f"{main_branch} branch settings: {main_dir_path}/")
        click.echo(f"{feature_branch} branch settings: {branch_dir_path}/")
        click.echo()
        click.echo("To compare settings manually, you can use:")
        click.echo(f"  # Diff all settings at once:")
        click.echo(f"  diff -u {main_dir_path} {branch_dir_path}")
        click.echo()

        # Show available files for comparison
        master_files = sorted(main_dir_path.glob("*.json"))
        if master_files:
            click.echo(f"  # Diff settings by file:")
            for file in master_files:
                click.echo(f"  echo {file.name} | xargs -I {{}} diff -u {main_dir_path}/{{}} {branch_dir_path}/{{}}")
        
    except KeyboardInterrupt:
        click.echo("\nOperation cancelled by user")
        sys.exit(1)
    except Exception as e:
        click.echo(click.style(f"Error: {e}", fg='red'), err=True)
        sys.exit(1)


if __name__ == "__main__":
    main()

Modifications required

To get all runs of dump_settings to work (specifically running the command with DJANGO_SETTINGS_MODULE=cms.envs.production and CMS_CFG=cms/envs/mock.yml) I needed to make a small patch to cms/envs/production.py in both master and my branch:

-CSRF_TRUSTED_ORIGINS = _YAML_TOKENS.get("CSRF_TRUSTED_ORIGINS", [])
+CSRF_TRUSTED_ORIGINS = _YAML_TOKENS.get('CSRF_TRUSTED_ORIGINS_WITH_SCHEME', [])

Without this, the command would fail and output this error:

As of Django 4.0, the values in the CSRF_TRUSTED_ORIGINS setting must start with a scheme (usually http:// or https://) but found .localhost. See the release notes for details.

Results from running diff_settings.py

Running python diff_settings.py master tpayne/create-openedx-envs-common-settings in a tutor dev environment, I get the following results:

Starting Django settings dump comparison...
Comparing master -> tpayne/create-openedx-envs-common-settings
Output: main_settings_dump/ and feature_settings_dump/

Cleaning existing directory: main_settings_dump
Cleaning existing directory: feature_settings_dump
=== Processing branch: master ===
Processing configurations  [####################################]  100%
Completed processing branch: master

=== Processing branch: tpayne/create-openedx-envs-common-settings ===
Processing configurations  [####################################]  100%
Completed processing branch: tpayne/create-openedx-envs-common-settings

=== Running diff comparison ===
diff main_settings_dump/cms.envs.production__cms_envs_mock.json feature_settings_dump/cms.envs.production__cms_envs_mock.json
4360c4360
<             "module": "lms.envs.common",
---
>             "module": "openedx.envs.common",
4889c4889
<                 "stream": "<colorama.ansitowin32.StreamWrapper object at 0xffff86001fd0>"
---
>                 "stream": "<colorama.ansitowin32.StreamWrapper object at 0xffff7e80bf90>"
5847a5848
>     "USAGE_ID_PATTERN": "(?P<usage_id>(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))",
diff main_settings_dump/cms.envs.tutor.development__openedx_config_cms.json feature_settings_dump/cms.envs.tutor.development__openedx_config_cms.json
2084c2084
<             "module": "lms.envs.common",
---
>             "module": "openedx.envs.common",
2607c2607
<                 "stream": "<colorama.ansitowin32.StreamWrapper object at 0xffffa6389fd0>"
---
>                 "stream": "<colorama.ansitowin32.StreamWrapper object at 0xffff828d9fd0>"
3477a3478
>     "USAGE_ID_PATTERN": "(?P<usage_id>(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))",
diff main_settings_dump/cms.envs.tutor.production__openedx_config_cms.json feature_settings_dump/cms.envs.tutor.production__openedx_config_cms.json
2048c2048
<             "module": "lms.envs.common",
---
>             "module": "openedx.envs.common",
2571c2571
<                 "stream": "<colorama.ansitowin32.StreamWrapper object at 0xffff7f8f9fd0>"
---
>                 "stream": "<colorama.ansitowin32.StreamWrapper object at 0xffffaa8f9fd0>"
3440a3441
>     "USAGE_ID_PATTERN": "(?P<usage_id>(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))",
diff main_settings_dump/lms.envs.production__lms_envs_minimal.json feature_settings_dump/lms.envs.production__lms_envs_minimal.json
2365c2365
<             "module": "lms.envs.common",
---
>             "module": "openedx.envs.common",
2863c2863
<                 "stream": "<colorama.ansitowin32.StreamWrapper object at 0xffffa753af90>"
---
>                 "stream": "<colorama.ansitowin32.StreamWrapper object at 0xffff8c23dc90>"
diff main_settings_dump/lms.envs.production__lms_envs_mock.json feature_settings_dump/lms.envs.production__lms_envs_mock.json
5856c5856
<             "module": "lms.envs.common",
---
>             "module": "openedx.envs.common",
6365c6365
<                 "stream": "<colorama.ansitowin32.StreamWrapper object at 0xffff91eadc50>"
---
>                 "stream": "<colorama.ansitowin32.StreamWrapper object at 0xffffbcef3910>"
diff main_settings_dump/lms.envs.tutor.development__openedx_config_lms.json feature_settings_dump/lms.envs.tutor.development__openedx_config_lms.json
2689c2689
<             "module": "lms.envs.common",
---
>             "module": "openedx.envs.common",
3381c3381
<                 "stream": "<colorama.ansitowin32.StreamWrapper object at 0xffffba7d9e90>"
---
>                 "stream": "<colorama.ansitowin32.StreamWrapper object at 0xffff9e9cdc50>"
diff main_settings_dump/lms.envs.tutor.production__openedx_config_lms.json feature_settings_dump/lms.envs.tutor.production__openedx_config_lms.json
2414c2414
<             "module": "lms.envs.common",
---
>             "module": "openedx.envs.common",
2914c2914
<                 "stream": "<colorama.ansitowin32.StreamWrapper object at 0xffffb61cafd0>"
---
>                 "stream": "<colorama.ansitowin32.StreamWrapper object at 0xffffa4a3ed50>"

Differences found between branches (shown above)

The "module" lines are describing where the lambda for the JWT_AUTH setting JWT_PAYLOAD_GET_USERNAME_HANDLER is being defined.

Also, I decided to bring USAGE_ID_PATTERN, which historically hasn't been imported in the CMS settings, into openedx/envs/common.py along with USAGE_KEY_PATTERN and ASSET_KEY_PATTERN, which have been imported into the CMS settings. These settings are similar and I thought it would make sense to declare them in the same place.

I also made a modification to HEARTBEAT_CELERY_ROUTING_KEY in cms/envs/common.py where it will contain 'cms' rather than 'lms' in the value, if SERVICE_VARIANT is not set (I don't know if this would be a common scenario or not). To test the difference with this setting I had to modify manage.py to not set SERVICE_VARIANT if unset. I also had to modify diff_settings.py to unset the SERVICE_VARIANT before running the dump_settings management command. With those changes in place these differences appeared:

diff main_settings_dump/cms.envs.production__cms_envs_mock.json feature_settings_dump/cms.envs.production__cms_envs_mock.json
4154c4154
<     "HEARTBEAT_CELERY_ROUTING_KEY": "edx.lms.core.high",
---
>     "HEARTBEAT_CELERY_ROUTING_KEY": "edx.cms.core.high",

diff main_settings_dump/cms.envs.tutor.development__openedx_config_cms.json feature_settings_dump/cms.envs.tutor.development__openedx_config_cms.json
1883c1883
<     "HEARTBEAT_CELERY_ROUTING_KEY": "edx.lms.core.high",
---
>     "HEARTBEAT_CELERY_ROUTING_KEY": "edx.cms.core.high",

diff main_settings_dump/cms.envs.tutor.production__openedx_config_cms.json feature_settings_dump/cms.envs.tutor.production__openedx_config_cms.json
1853c1853
<     "HEARTBEAT_CELERY_ROUTING_KEY": "edx.lms.core.high",
---
>     "HEARTBEAT_CELERY_ROUTING_KEY": "edx.cms.core.high",

dump_settings invocation information

The code in diff_settings.py will run commands like these for both branches (replacing {branch_dump_dir} depending on which branch is checked out):

# lms
SERVICE_VARIANT=lms DJANGO_SETTINGS_MODULE=lms.envs.production LMS_CFG=lms/envs/minimal.yml ./manage.py lms dump_settings > {branch_dump_dir}/lms.envs.production__lms_envs_minimal.json
SERVICE_VARIANT=lms DJANGO_SETTINGS_MODULE=lms.envs.production LMS_CFG=lms/envs/mock.yml ./manage.py lms dump_settings > {branch_dump_dir}/lms.envs.production__lms_envs_mock.json
SERVICE_VARIANT=lms DJANGO_SETTINGS_MODULE=lms.envs.tutor.production LMS_CFG=/openedx/config/lms.env.yml ./manage.py lms dump_settings > {branch_dump_dir}/lms.envs.tutor.production__openedx_config_lms.json
SERVICE_VARIANT=lms DJANGO_SETTINGS_MODULE=lms.envs.tutor.development LMS_CFG=/openedx/config/lms.env.yml ./manage.py lms dump_settings > {branch_dump_dir}/lms.envs.tutor.development__openedx_config_lms.json

# cms
SERVICE_VARIANT=cms DJANGO_SETTINGS_MODULE=cms.envs.production CMS_CFG=cms/envs/mock.yml ./manage.py cms dump_settings > {branch_dump_dir}/cms.envs.production__cms_envs_mock.json
SERVICE_VARIANT=cms DJANGO_SETTINGS_MODULE=cms.envs.tutor.production CMS_CFG=/openedx/config/cms.env.yml ./manage.py cms dump_settings > {branch_dump_dir}/cms.envs.tutor.production__openedx_config_cms.json
SERVICE_VARIANT=cms DJANGO_SETTINGS_MODULE=cms.envs.tutor.development CMS_CFG=/openedx/config/cms.env.yml ./manage.py cms dump_settings > {branch_dump_dir}/cms.envs.tutor.development__openedx_config_cms.json

And here is a table of the variables involved:

|-----------------+----------------------------+-----------------------------+------------------------------------------------|
| SERVICE_VARIANT | DJANGO_SETTINGS_MODULE     | {SERVICE}_CFG               | output file (.json)                            |
|-----------------+----------------------------+-----------------------------+------------------------------------------------|
| lms             | lms.envs.production        | lms/envs/minimal.yml        | lms.envs.production__lms_envs_minimal          |
| lms             | lms.envs.production        | lms/envs/mock.yml           | lms.envs.production__lms_envs_mock             |
| lms             | lms.envs.tutor.production  | /openedx/config/lms.env.yml | lms.envs.production__lms_envs_mock             |
| lms             | lms.envs.tutor.development | /openedx/config/lms.env.yml | lms.envs.tutor.development__openedx_config_lms |
|-----------------+----------------------------+-----------------------------+------------------------------------------------|
| cms             | cms.envs.production        | cms/envs/mock.yml           | cms.envs.production__cms_envs_mock             |
| cms             | cms.envs.tutor.production  | /openedx/config/cms.env.yml | cms.envs.tutor.production__openedx_config_cms  |
| cms             | cms.envs.tutor.development | /openedx/config/cms.env.yml | cms.envs.tutor.development__openedx_config_cms |
|-----------------+----------------------------+-----------------------------+------------------------------------------------|

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Jun 23, 2025
@openedx-webhooks
Copy link

openedx-webhooks commented Jun 23, 2025

Thanks for the pull request, @wgu-taylor-payne!

This repository is currently maintained by @openedx/wg-maintenance-edx-platform.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.


Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@github-project-automation github-project-automation bot moved this to Needs Triage in Contributions Jun 23, 2025
@wgu-taylor-payne wgu-taylor-payne marked this pull request as draft June 23, 2025 15:50
@wgu-taylor-payne wgu-taylor-payne self-assigned this Jun 23, 2025
@wgu-taylor-payne wgu-taylor-payne force-pushed the tpayne/create-openedx-envs-common-settings branch from 48c86a1 to 9c610f5 Compare June 23, 2025 19:52

from openedx.core.constants import COURSE_KEY_REGEX, COURSE_KEY_PATTERN, COURSE_ID_PATTERN
from openedx.envs.common import * # pylint: disable=wildcard-import

from lms.envs.common import (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't move these up, since it is unclear whether they belong in the CMS long term.

@wgu-taylor-payne wgu-taylor-payne force-pushed the tpayne/create-openedx-envs-common-settings branch from 8b509eb to eaea6f7 Compare June 24, 2025 18:43
@mphilbrick211 mphilbrick211 added the mao-onboarding Reviewing this will help onboard devs from an Axim mission-aligned organization (MAO). label Jun 24, 2025
Comment on lines +13 to +18
To create section headers in this file, use the following function:

```python
def center_with_hashes(text: str, width: int = 76):
print(f"{f' {text} ':#^{width}}")
```
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this should be included, but if we decide to continue marking sections in this way, it is nice to be able to create them in a standardized style.

MEDIA_URL = '/media/'

# Dummy secret key for dev/test
SECRET_KEY = 'dev key'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a variable which wasn't explicitly imported into the cms settings from the lms settings, but it is used by such a setting (JWT_AUTH).


DEBUG_TOOLBAR_PATCH_SETTINGS = False

################ Shared Functions for Derived Configuration ################
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have decided to improve the docstrings for the two functions in this section. Also, since they are declared here and then imported into the lms and cms settings modules, I have removed the leading _ in their names which had marked them as private.

Doing this also makes them available if we use from openedx.envs.common import * to import the things defined in this file into downstream settings files.

Comment on lines +497 to +509
# .. setting_name: PARENTAL_CONSENT_AGE_LIMIT
# .. setting_default: 13
# .. setting_description: The age at which a learner no longer requires parental consent,
# or ``None`` if parental consent is never required.
PARENTAL_CONSENT_AGE_LIMIT = 13

############################### Registration ###############################

# .. setting_name: REGISTRATION_EMAIL_PATTERNS_ALLOWED
# .. setting_default: None
# .. setting_description: Optional setting to restrict registration / account creation
# to only emails that match a regex in this list. Set to ``None`` to allow any email (default).
REGISTRATION_EMAIL_PATTERNS_ALLOWED = None
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This documentation did not exist, but I added it to make sure that such documentation declared in this module would appear in the settings reference docs page.

'small': 30
}

############################## Miscellaneous ###############################
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't readily sure how to categorize the settings added here.

# They are used so that URLs with deprecated-format strings still work.
USAGE_KEY_PATTERN = r'(?P<usage_key_string>(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))'
ASSET_KEY_PATTERN = r'(?P<asset_key_string>(?:/?c4x(:/)?/[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))'
USAGE_ID_PATTERN = r'(?P<usage_id>(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

USAGE_KEY_PATTERN and ASSET_KEY_PATTERN were being imported into the cms settings from the lms settings, but USAGE_ID_PATTERN was not. I decided to add USAGE_ID_PATTERN here since it is closely related to the other two settings. If we don't care as much about the co-location of these settings, then we can move USAGE_ID_PATTERN back into lms/envs/common.py.

@@ -6,6 +6,12 @@ This is the list of (non-toggle) Django settings defined in the ``common.py`` mo
.. note::
Toggle settings, which enable or disable a specific feature, are documented in the :ref:`feature toggles <featuretoggles>` section.

Open edX settings
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a good section title for the settings reference page?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's call it "Platform-Wide Settings". "Open edX" includes things outside of edx-platform (and the openedx/ directory is poorly named).

@@ -1539,6 +1458,10 @@
CELERY_BROKER_USE_SSL = False
CELERY_EVENT_QUEUE_TTL = None

############################## HEARTBEAT ######################################

HEARTBEAT_CELERY_ROUTING_KEY = HIGH_PRIORITY_QUEUE
Copy link
Contributor Author

@wgu-taylor-payne wgu-taylor-payne Jun 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HEARTBEAT_CELERY_ROUTING_KEY has previously been imported from lms/envs/common.py, but when I was looking into it, that LMS setting can/will contain lms in this str in the event that SERVICE_VARIANT is not set (I'm not sure how rare this scenario would be).

These variables follow a similar pattern in both lms/envs/common.py and cms/envs/common.py. Here is how HEARTBEAT_CELERY_ROUTING_KEY gets its value in lms/envs/common.py:

SERVICE_VARIANT = os.environ.get('SERVICE_VARIANT', 'lms')  # 'cms' in cms/envs/common.py
CONFIG_PREFIX = SERVICE_VARIANT + "." if SERVICE_VARIANT else ""
QUEUE_VARIANT = CONFIG_PREFIX.lower()
HIGH_PRIORITY_QUEUE = f'edx.{QUEUE_VARIANT}core.high'
HEARTBEAT_CELERY_ROUTING_KEY = HIGH_PRIORITY_QUEUE

So, cms/envs/common.py has the same pattern (except it defaults to 'cms' for the SERVICE_VARIANT), but then it imports HEARTBEAT_CELERY_ROUTING_KEY from the lms settings rather than using HIGH_PRIORITY_QUEUE as the value like in lms/envs/common.py.

Is that how we should keep it, or should the HEARTBEAT_CELERY_ROUTING_KEY value in the cms settings contain 'cms' rather than 'lms' in the key when/if the SERVICE_VARIANT is unset?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing to add here is I can't find anywhere the cms uses HEARTBEAT_CELERY_ROUTING_KEY.

@wgu-taylor-payne
Copy link
Contributor Author

I have left in 'DIRS': lms.envs.common.MAKO_TEMPLATE_DIRS_BASE, (see here) in cms/envs/common.py:664 because it is using a setting that is explicitly set for the LMS.

@wgu-taylor-payne wgu-taylor-payne marked this pull request as ready for review June 24, 2025 20:40
@wgu-taylor-payne wgu-taylor-payne requested a review from a team as a code owner June 24, 2025 20:40
@wgu-taylor-payne wgu-taylor-payne marked this pull request as draft June 25, 2025 18:08
@wgu-taylor-payne wgu-taylor-payne marked this pull request as ready for review June 26, 2025 01:37
@kdmccormick kdmccormick moved this from Needs Triage to Ready for Review in Contributions Jun 26, 2025
@kdmccormick
Copy link
Member

Excellent work. I'm busy for the next couple days with conference preparation, so I'll see if any other CCs are available to review. If not, I'll review as soon as I'm able to.

@kdmccormick kdmccormick added the create-sandbox open-craft-grove should create a sandbox environment from this PR label Jun 26, 2025
@open-craft-grove
Copy link

Sandbox deployment failed 💥
Please check the settings and requirements.
Retry deployment by pushing a new commit or updating the requirements/settings in the pull request's description.
📜 Failure Logs
ℹ️ Grove Config, Tutor Config, Tutor Requirements

@open-craft-grove
Copy link

Sandbox deployment successful 🚀
🎓 LMS
📝 Studio
ℹ️ Grove Config, Tutor Config, Tutor Requirements

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
create-sandbox open-craft-grove should create a sandbox environment from this PR mao-onboarding Reviewing this will help onboard devs from an Axim mission-aligned organization (MAO). open-source-contribution PR author is not from Axim or 2U
Projects
Status: Ready for Review
Development

Successfully merging this pull request may close these issues.

Create initial openedx/envs/common.py
5 participants