diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index c42f017..6486dee 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -10,7 +10,7 @@ on: defaults: run: - working-directory: "./backend" + working-directory: "./backend-plugin-sample" jobs: run_tests: @@ -46,4 +46,4 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} flags: unittests fail_ci_if_error: true - working-directory: "./backend" + working-directory: "./backend-plugin-sample" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bbf5477..315f97c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,7 @@ jobs: git_committer_name: "github-actions" git_committer_email: "actions@users.noreply.github.com" changelog: "false" - directory: './backend' + directory: './backend-plugin-sample' - name: Publish | Upload to GitHub Release Assets uses: python-semantic-release/publish-action@v10.5.3 @@ -52,14 +52,14 @@ jobs: with: github_token: ${{ secrets.OPENEDX_SEMANTIC_RELEASE_GITHUB_TOKEN }} tag: ${{ steps.release.outputs.tag }} - directory: './backend' + directory: './backend-plugin-sample' - name: Upload | Backend Distribution Artifacts uses: actions/upload-artifact@v4 if: steps.release.outputs.released == 'true' with: name: backend-distribution-artifacts - path: backend/dist + path: backend-plugin-sample/dist if-no-files-found: error - name: Build | Tutor Plugin @@ -69,14 +69,14 @@ jobs: # setuptools-scm picks it up at build time. if: steps.release.outputs.released == 'true' run: pip install build && SETUPTOOLS_SCM_PRETEND_VERSION=${{ steps.release.outputs.version }} python -m build - working-directory: './tutor' + working-directory: './tutor-contrib-sample' - name: Upload | Tutor Plugin Distribution Artifacts uses: actions/upload-artifact@v4 if: steps.release.outputs.released == 'true' with: name: tutor-distribution-artifacts - path: tutor/dist + path: tutor-contrib-sample/dist if-no-files-found: error outputs: @@ -102,12 +102,12 @@ jobs: id: artifact-download with: name: backend-distribution-artifacts - path: backend/dist + path: backend-plugin-sample/dist - name: Publish to PyPi uses: pypa/gh-action-pypi-publish@release/v1 with: - packages-dir: backend/dist + packages-dir: backend-plugin-sample/dist user: __token__ password: ${{ secrets.PYPI_UPLOAD_TOKEN }} @@ -125,12 +125,12 @@ jobs: uses: actions/download-artifact@v4 with: name: tutor-distribution-artifacts - path: tutor/dist + path: tutor-contrib-sample/dist - name: Publish to PyPi uses: pypa/gh-action-pypi-publish@release/v1 with: - packages-dir: tutor/dist + packages-dir: tutor-contrib-sample/dist user: __token__ password: ${{ secrets.PYPI_UPLOAD_TOKEN }} @@ -151,7 +151,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version-file: './frontend/.nvmrc' + node-version-file: './frontend-plugin-sample/.nvmrc' - name: Update the package version and publish run: | @@ -159,4 +159,4 @@ jobs: npm version ${{ needs.release.outputs.version }} npm run build npm publish - working-directory: './frontend' + working-directory: './frontend-plugin-sample' diff --git a/CLAUDE.md b/CLAUDE.md index 7519b28..4107427 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,9 +13,9 @@ This is a **sample plugin repository** that demonstrates all major Open edX plug - **Target Audience**: Developers new to Open edX plugin development **Repository Structure:** -- `backend/` - Django app plugin with models, APIs, events, and filters -- `frontend/` - React component for MFE slot customization -- `tutor/` - Tutor plugin for easy deployment +- `backend-plugin-sample/` - Django app plugin with models, APIs, events, and filters +- `frontend-plugin-sample/` - React component for MFE slot customization +- `tutor-contrib-sample/` - Tutor plugin for easy deployment - Each directory has comprehensive README.md files with TOCs **When Making Changes:** @@ -25,21 +25,21 @@ This is a **sample plugin repository** that demonstrates all major Open edX plug - Keep examples realistic but not overly complex **Key Files and Their Relationships:** -- `backend/sample_plugin/apps.py` - Plugin registration and Django integration -- `backend/sample_plugin/signals.py` - Open edX Events handlers -- `backend/sample_plugin/pipeline.py` - Open edX Filters implementation -- `backend/sample_plugin/models.py` - CourseArchiveStatus model (business logic) -- `backend/sample_plugin/views.py` - REST API endpoints consumed by frontend -- `frontend/src/plugin.jsx` - React component that replaces course list slot -- `tutor/sample_plugin.py` - Deployment configuration (currently basic template) +- `backend-plugin-sample/openedx_plugin_sample/apps.py` - Plugin registration and Django integration +- `backend-plugin-sample/openedx_plugin_sample/signals.py` - Open edX Events handlers +- `backend-plugin-sample/openedx_plugin_sample/pipeline.py` - Open edX Filters implementation +- `backend-plugin-sample/openedx_plugin_sample/models.py` - CourseArchiveStatus model (business logic) +- `backend-plugin-sample/openedx_plugin_sample/views.py` - REST API endpoints consumed by frontend +- `frontend-plugin-sample/src/plugin.jsx` - React component that replaces course list slot +- `tutor-contrib-sample/tutorsample/plugin.py` - Deployment configuration (currently basic template) ## Build/Lint/Test Commands - Make sure to set the following so that test output is not too verbose: `export PYTEST_ADDOPTS="--disable-warnings --no-header --tb=short"` -- Backend testing: `cd backend && pytest` or `cd backend && make test` -- Run a single test: `cd backend && pytest tests/test_models.py::test_placeholder` -- Quality checks: `cd backend && make quality` -- Install requirements: `cd backend && make requirements` -- Compile requirements: `cd backend && make compile-requirements` +- Backend testing: `cd backend-plugin-sample && pytest` or `cd backend-plugin-sample && make test` +- Run a single test: `cd backend-plugin-sample && pytest tests/test_models.py::test_placeholder` +- Quality checks: `cd backend-plugin-sample && make quality` +- Install requirements: `cd backend-plugin-sample && make requirements` +- Compile requirements: `cd backend-plugin-sample && make compile-requirements` ## Code Style Guidelines - Python: Follow PEP 8 with max line length of 120 @@ -61,7 +61,7 @@ Always run `make quality` and fix issues before creating a PR to ensure consiste - **Realistic Complexity**: Keep examples practical but not overly complex ### Code Relationships to Preserve -- **Backend ↔ Frontend**: CourseArchiveStatus API in `views.py` consumed by `frontend/src/plugin.jsx` +- **Backend ↔ Frontend**: CourseArchiveStatus API in `views.py` consumed by `frontend-plugin-sample/src/plugin.jsx` - **Events ↔ Models**: Signal handlers in `signals.py` can update models in `models.py` - **Settings ↔ Filters**: Filter registration in `settings/common.py` must match classes in `pipeline.py` - **Apps.py ↔ All**: Plugin configuration affects URL routing, settings, and signal registration diff --git a/README.md b/README.md index 0a65bd1..fe57c61 100644 --- a/README.md +++ b/README.md @@ -29,30 +29,31 @@ This sample plugin showcases the **Open edX Hooks Extension Framework**, which a | Plugin Type | What It Does | Official Documentation | Sample Code | When To Use | |-------------|--------------|------------------------|-------------|-------------| -| **Django App Plugin** | Add models, APIs, views, and business logic | [How to create a plugin app](https://docs.openedx.org/projects/edx-django-utils/en/latest/plugins/how_tos/how_to_create_a_plugin_app.html) | [`backend/`](./backend/) | Adding new functionality, APIs, or data models | -| **Events (Signals)** | React to platform events | [Open edX Events Guide](https://docs.openedx.org/projects/openedx-events/en/latest/) | [`backend/sample_plugin/signals.py`](./backend/sample_plugin/signals.py) | Integrating with external systems, audit logging | -| **Filters** | Modify platform behavior | [Using Open edX Filters](https://docs.openedx.org/projects/openedx-filters/en/latest/how-tos/using-filters.html) | [`backend/sample_plugin/pipeline.py`](./backend/sample_plugin/pipeline.py) | Customizing business logic, URL redirects | -| **Frontend Slots** | Customize MFE interfaces | [Frontend Plugin Slots](https://docs.openedx.org/en/latest/site_ops/how-tos/use-frontend-plugin-slots.html) | [`frontend/`](./frontend/) | UI customization, adding new components | -| **Brand Packages** | Customize theming | [Open edX Brand Package Interface](https://github.com/openedx/brand-openedx) | [`brand/`](./brand/) | UI theming | -| **Tutor Plugin** | Deploy plugins easily | [Tutor Plugin Development](https://docs.tutor.edly.io/) | [`tutor/`](./tutor/) | Simplified deployment and configuration | +| **Django App Plugin** | Add models, APIs, views, and business logic | [How to create a plugin app](https://docs.openedx.org/projects/edx-django-utils/en/latest/plugins/how_tos/how_to_create_a_plugin_app.html) | [`backend-plugin-sample/`](./backend-plugin-sample/) | Adding new functionality, APIs, or data models | +| **Events (Signals)** | React to platform events | [Open edX Events Guide](https://docs.openedx.org/projects/openedx-events/en/latest/) | [`backend-plugin-sample/openedx_plugin_sample/signals.py`](./backend-plugin-sample/openedx_plugin_sample/signals.py) | Integrating with external systems, audit logging | +| **Filters** | Modify platform behavior | [Using Open edX Filters](https://docs.openedx.org/projects/openedx-filters/en/latest/how-tos/using-filters.html) | [`backend-plugin-sample/openedx_plugin_sample/pipeline.py`](./backend-plugin-sample/openedx_plugin_sample/pipeline.py) | Customizing business logic, URL redirects | +| **Frontend Slots** | Customize MFE interfaces | [Frontend Plugin Slots](https://docs.openedx.org/en/latest/site_ops/how-tos/use-frontend-plugin-slots.html) | [`frontend-plugin-sample/`](./frontend-plugin-sample/) | UI customization, adding new components | +| **Brand Packages** | Customize theming | [Open edX Brand Package Interface](https://github.com/openedx/brand-openedx) | [`brand-sample/`](./brand-sample/) | UI theming | +| **Tutor Plugin** | Deploy plugins easily | [Tutor Plugin Development](https://docs.tutor.edly.io/) | [`tutor-contrib-sample/`](./tutor-contrib-sample/) | Simplified deployment and configuration | ## Quick Start Guide ### Prerequisites - 1. **Platform Setup**: Follow the [Open edX Development Setup](https://docs.openedx.org/en/latest/developers/how-tos/get-ready-for-python-dev.html) 2. **Understanding**: Read the [Platform Overview](https://docs.openedx.org/en/latest/developers/concepts/platform_overview.html) ### Option 1: Development with Tutor (Recommended) ```bash -# Backend plugin setup -tutor mounts add "$PWD/backend" -tutor dev launch # Rebuilds image, runs migrations, reboots containers. +# Bind-mount backend source into Tutor image and containers. +tutor mounts add "$PWD/backend-plugin-sample" + +# Rebuild image, run migrations, reboot containers: +tutor dev launch # Frontend Plugin Setup (for learner-dashboard MFE development) -npm install $PWD/frontend -# Add env.config.jsx and module.config.js (see frontend/README.md) +npm install $PWD/frontend-plugin-sample +# Add env.config.jsx and module.config.js (see frontend-plugin-sample/README.md) npm start ``` @@ -60,7 +61,7 @@ npm start ```bash # In your edx-platform directory -pip install -e /path/to/sample-plugin/backend +pip install -e /path/to/sample-plugin/backend-plugin-sample # Enable Learner Dashboard MFE # Go to http://localhost:18000/admin/waffle/flag/ @@ -87,10 +88,10 @@ python manage.py lms migrate Use the table above to identify which type of plugin matches your needs. You can combine multiple types in one plugin. ### 3. Study the Sample Code -- **Backend**: Start with [`backend/sample_plugin/apps.py`](./backend/sample_plugin/apps.py) to understand plugin registration -- **Events**: Examine [`backend/sample_plugin/signals.py`](./backend/sample_plugin/signals.py) for event handling patterns -- **Filters**: Review [`backend/sample_plugin/pipeline.py`](./backend/sample_plugin/pipeline.py) for behavior modification -- **Frontend**: Explore [`frontend/src/plugin.jsx`](./frontend/src/plugin.jsx) for UI customization +- **Backend**: Start with [`backend-plugin-sample/openedx_plugin_sample/apps.py`](./backend-plugin-sample/openedx_plugin_sample/apps.py) to understand plugin registration +- **Events**: Examine [`backend-plugin-sample/openedx_plugin_sample/signals.py`](./backend-plugin-sample/openedx_plugin_sample/signals.py) for event handling patterns +- **Filters**: Review [`backend-plugin-sample/openedx_plugin_sample/pipeline.py`](./backend-plugin-sample/openedx_plugin_sample/pipeline.py) for behavior modification +- **Frontend**: Explore [`frontend-plugin-sample/src/plugin.jsx`](./frontend-plugin-sample/src/plugin.jsx) for UI customization ### 4. Run This Sample Follow the [Quick Start Guide](#quick-start-guide) to see everything working together. @@ -103,9 +104,9 @@ Each directory contains detailed README.md files with adaptation guidance. ``` sample-plugin/ ├── README.md # This file - overview and quick start -├── backend/ +├── backend-plugin-sample/ │ ├── README.md # Backend plugin detailed guide -│ ├── sample_plugin/ +│ ├── openedx_plugin_sample/ │ │ ├── apps.py # Django plugin configuration │ │ ├── models.py # Database models example │ │ ├── views.py # REST API endpoints @@ -114,15 +115,15 @@ sample-plugin/ │ │ ├── settings/ # Plugin settings configuration │ │ └── urls.py # URL routing │ └── tests/ # Comprehensive test examples -├── frontend/ +├── frontend-plugin-sample/ │ ├── README.md # Frontend plugin detailed guide │ ├── src/ │ │ ├── plugin.jsx # React component for MFE slot │ │ └── index.jsx # Export configuration │ └── package.json # NPM package configuration -└── tutor/ +└── tutor-contrib-sample/ ├── README.md # Tutor deployment guide - └── sample_plugin.py # Tutor plugin configuration + └── sample.py # Tutor plugin configuration ``` ## Development Workflows @@ -135,20 +136,20 @@ sample-plugin/ - Add API endpoints in `views.py` - Implement event handlers in `signals.py` - Create filters in `pipeline.py` -3. **Testing**: `cd backend && make test` -4. **Quality**: `cd backend && make quality` +3. **Testing**: `cd backend-plugin-sample && make test` +4. **Quality**: `cd backend-plugin-sample && make quality` -**Detailed Guide**: See [`backend/README.md`](./backend/README.md) +**Detailed Guide**: See [`backend-plugin-sample/README.md`](./backend-plugin-sample/README.md) ### Frontend Plugin Development 1. **Setup**: Follow frontend setup in [Quick Start](#quick-start-guide) 2. **Development**: - - Modify React components in `frontend/src/` + - Modify React components in `frontend-plugin-sample/src/` - Test with local MFE development server 3. **Testing**: Integration testing with MFE -**Detailed Guide**: See [`frontend/README.md`](./frontend/README.md) +**Detailed Guide**: See [`frontend-plugin-sample/README.md`](./frontend-plugin-sample/README.md) ### Full-Stack Plugin Development @@ -164,13 +165,13 @@ This sample shows how backend and frontend plugins work together: ### Backend + Frontend Integration ```python -# backend/sample_plugin/views.py - Provides API +# backend-plugin-sample/openedx_plugin_sample/views.py - Provides API class CourseArchiveStatusViewSet(viewsets.ModelViewSet): # API implementation ``` ```jsx -// frontend/src/plugin.jsx - Consumes API +// frontend-plugin-sample/src/plugin.jsx - Consumes API const response = await client.get( `${lmsBaseUrl}/sample-plugin/api/v1/course-archive-status/` ); diff --git a/backend/.annotation_safe_list.yml b/backend-plugin-sample/.annotation_safe_list.yml similarity index 100% rename from backend/.annotation_safe_list.yml rename to backend-plugin-sample/.annotation_safe_list.yml diff --git a/backend/.coveragerc b/backend-plugin-sample/.coveragerc similarity index 100% rename from backend/.coveragerc rename to backend-plugin-sample/.coveragerc diff --git a/backend/.editorconfig b/backend-plugin-sample/.editorconfig similarity index 100% rename from backend/.editorconfig rename to backend-plugin-sample/.editorconfig diff --git a/backend/.gitignore b/backend-plugin-sample/.gitignore similarity index 100% rename from backend/.gitignore rename to backend-plugin-sample/.gitignore diff --git a/backend/.pii_annotations.yml b/backend-plugin-sample/.pii_annotations.yml similarity index 100% rename from backend/.pii_annotations.yml rename to backend-plugin-sample/.pii_annotations.yml diff --git a/backend/.readthedocs.yaml b/backend-plugin-sample/.readthedocs.yaml similarity index 100% rename from backend/.readthedocs.yaml rename to backend-plugin-sample/.readthedocs.yaml diff --git a/backend/LICENSE.txt b/backend-plugin-sample/LICENSE.txt similarity index 100% rename from backend/LICENSE.txt rename to backend-plugin-sample/LICENSE.txt diff --git a/backend/Makefile b/backend-plugin-sample/Makefile similarity index 100% rename from backend/Makefile rename to backend-plugin-sample/Makefile diff --git a/backend/README.md b/backend-plugin-sample/README.md similarity index 88% rename from backend/README.md rename to backend-plugin-sample/README.md index c962695..8191dd1 100644 --- a/backend/README.md +++ b/backend-plugin-sample/README.md @@ -34,7 +34,7 @@ This backend plugin demonstrates the **Open edX Django App Plugin** pattern, whi ## Django App Plugin Configuration -**File**: [`sample_plugin/apps.py`](./sample_plugin/apps.py) +**File**: [`openedx_plugin_sample/apps.py`](./openedx_plugin_sample/apps.py) ### Plugin Registration @@ -42,12 +42,12 @@ The `SamplePluginConfig` class configures this app as an edx-platform plugin: ```python class SamplePluginConfig(AppConfig): - name = "sample_plugin" + name = "openedx_plugin_sample" plugin_app = { "url_config": { # Register URLs for both LMS and CMS "lms.djangoapp": { - PluginURLs.NAMESPACE: "sample_plugin", + PluginURLs.NAMESPACE: "openedx_plugin_sample", PluginURLs.REGEX: r"^sample-plugin/", PluginURLs.RELATIVE_PATH: "urls", }, @@ -74,21 +74,21 @@ class SamplePluginConfig(AppConfig): ### Entry Points Configuration -In [`pyproject.toml`](./backend/pyproject.toml), the plugin registers itself with edx-platform: +In [`pyproject.toml`](./pyproject.toml), the plugin registers itself with edx-platform: ```python [project.entry-points."lms.djangoapp"] -sample_plugin = "sample_plugin.apps:SamplePluginConfig" +openedx_plugin_sample = "openedx_plugin_sample.apps:SamplePluginConfig" [project.entry-points."cms.djangoapp"] -sample_plugin = "sample_plugin.apps:SamplePluginConfig" +openedx_plugin_sample = "openedx_plugin_sample.apps:SamplePluginConfig" ``` **Why this works**: The platform automatically discovers and loads any Django app registered in these entry points. ## Models & Database -**File**: [`sample_plugin/models.py`](./sample_plugin/models.py) +**File**: [`openedx_plugin_sample/models.py`](./openedx_plugin_sample/models.py) **Official Docs**: [OEP-49: Django App Patterns](https://docs.openedx.org/projects/openedx-proposals/en/latest/best-practices/oep-0049-django-app-patterns.html) ### CourseArchiveStatus Model @@ -112,12 +112,12 @@ class CourseArchiveStatus(models.Model): ```bash # After modifying models.py -cd backend -python manage.py makemigrations sample_plugin +cd backend-plugin-sample +python manage.py makemigrations openedx_plugin_sample python manage.py migrate ``` -**Migration files**: Generated in [`sample_plugin/migrations/`](./sample_plugin/migrations/) +**Migration files**: Generated in [`openedx_plugin_sample/migrations/`](./openedx_plugin_sample/migrations/) ### PII Annotations @@ -130,8 +130,8 @@ The model includes PII documentation: ## API Endpoints -**File**: [`sample_plugin/views.py`](./sample_plugin/views.py) -**URLs**: [`sample_plugin/urls.py`](./sample_plugin/urls.py) +**File**: [`openedx_plugin_sample/views.py`](./openedx_plugin_sample/views.py) +**URLs**: [`openedx_plugin_sample/urls.py`](./openedx_plugin_sample/urls.py) ### REST API Implementation @@ -179,7 +179,7 @@ def perform_create(self, serializer): ## Events & Signals -**File**: [`sample_plugin/signals.py`](./sample_plugin/signals.py) +**File**: [`openedx_plugin_sample/signals.py`](./openedx_plugin_sample/signals.py) **Official Docs**: [Open edX Events Guide](https://docs.openedx.org/projects/openedx-events/en/latest/) ### Event Handler Example @@ -221,7 +221,7 @@ def log_course_info_changed(signal, sender, catalog_info: CourseCatalogData, **k ### Signal Handler Registration -Handlers are automatically registered via the `ready()` method in [`apps.py`](./sample_plugin/apps.py): +Handlers are automatically registered via the `ready()` method in [`apps.py`](./openedx_plugin_sample/apps.py): ```python def ready(self): @@ -238,7 +238,7 @@ def ready(self): ## Filters & Pipeline Steps -**File**: [`sample_plugin/pipeline.py`](./sample_plugin/pipeline.py) +**File**: [`openedx_plugin_sample/pipeline.py`](./openedx_plugin_sample/pipeline.py) **Official Docs**: [Using Open edX Filters](https://docs.openedx.org/projects/openedx-filters/en/latest/how-tos/using-filters.html) ### Filter Implementation @@ -292,7 +292,7 @@ Filters must be registered in Django settings. This happens automatically via th ## Settings Configuration -**Files**: [`sample_plugin/settings/`](./sample_plugin/settings/) +**Files**: [`openedx_plugin_sample/settings/`](./openedx_plugin_sample/settings/) ### Settings Structure @@ -321,7 +321,7 @@ def plugin_settings(settings): settings.OPEN_EDX_FILTERS_CONFIG = { "org.openedx.learning.course.about.render.started.v1": { "pipeline": [ - "sample_plugin.pipeline.ChangeCourseAboutPageUrl" + "openedx_plugin_sample.pipeline.ChangeCourseAboutPageUrl" ], "fail_silently": False, } @@ -368,7 +368,7 @@ tutor dev restart lms ```bash # In your edx-platform directory -pip install -e /path/to/sample-plugin/backend +pip install -e /path/to/sample-plugin/backend-plugin-sample # Run migrations python manage.py lms migrate @@ -380,7 +380,7 @@ python manage.py cms migrate 1. **Check Installation**: ```bash python manage.py lms shell - >>> from sample_plugin.models import CourseArchiveStatus + >>> from openedx_plugin_sample.models import CourseArchiveStatus >>> print("Plugin installed successfully!") ``` @@ -393,7 +393,7 @@ python manage.py cms migrate ### Running Tests ```bash -cd backend +cd backend-plugin-sample # Install test dependencies make requirements @@ -420,7 +420,7 @@ make test-coverage **Model Testing Pattern:** ```python from django.test import TestCase -from sample_plugin.models import CourseArchiveStatus +from openedx_plugin_sample.models import CourseArchiveStatus class TestCourseArchiveStatus(TestCase): def test_create_archive_status(self): @@ -449,9 +449,9 @@ class TestCourseArchiveStatusAPI(APITestCase): make quality # Individual tools -pylint sample_plugin/ -isort --check-only sample_plugin/ -black --check sample_plugin/ +pylint openedx_plugin_sample/ +isort --check-only openedx_plugin_sample/ +black --check openedx_plugin_sample/ ``` ## Integration Examples @@ -464,7 +464,7 @@ class CourseArchiveStatusViewSet(viewsets.ModelViewSet): # Provides data for frontend consumption ``` -**Frontend Consumption** (see [`../frontend/src/plugin.jsx`](../frontend/src/plugin.jsx)): +**Frontend Consumption** (see [`../frontend-plugin-sample/src/plugin.jsx`](../frontend-plugin-sample/src/plugin.jsx)): ```javascript const response = await client.get( `${lmsBaseUrl}/sample-plugin/api/v1/course-archive-status/` @@ -502,11 +502,11 @@ class ChangeCourseAboutPageUrl(PipelineStep): ### For Your Use Case -1. **Models**: Modify [`models.py`](./sample_plugin/models.py) for your data structure -2. **APIs**: Update [`views.py`](./sample_plugin/views.py) and [`serializers.py`](./sample_plugin/serializers.py) -3. **Events**: Change event handlers in [`signals.py`](./sample_plugin/signals.py) -4. **Filters**: Implement your business logic in [`pipeline.py`](./sample_plugin/pipeline.py) -5. **Settings**: Configure plugin behavior in [`settings/`](./sample_plugin/settings/) +1. **Models**: Modify [`models.py`](./openedx_plugin_sample/models.py) for your data structure +2. **APIs**: Update [`views.py`](./openedx_plugin_sample/views.py) and [`serializers.py`](./openedx_plugin_sample/serializers.py) +3. **Events**: Change event handlers in [`signals.py`](./openedx_plugin_sample/signals.py) +4. **Filters**: Implement your business logic in [`pipeline.py`](./openedx_plugin_sample/pipeline.py) +5. **Settings**: Configure plugin behavior in [`settings/`](./openedx_plugin_sample/settings/) ### Plugin Development Checklist diff --git a/backend/codecov.yml b/backend-plugin-sample/codecov.yml similarity index 100% rename from backend/codecov.yml rename to backend-plugin-sample/codecov.yml diff --git a/backend/docs/Makefile b/backend-plugin-sample/docs/Makefile similarity index 100% rename from backend/docs/Makefile rename to backend-plugin-sample/docs/Makefile diff --git a/backend/docs/_static/theme_overrides.css b/backend-plugin-sample/docs/_static/theme_overrides.css similarity index 100% rename from backend/docs/_static/theme_overrides.css rename to backend-plugin-sample/docs/_static/theme_overrides.css diff --git a/backend/docs/concepts/index.rst b/backend-plugin-sample/docs/concepts/index.rst similarity index 100% rename from backend/docs/concepts/index.rst rename to backend-plugin-sample/docs/concepts/index.rst diff --git a/backend/docs/conf.py b/backend-plugin-sample/docs/conf.py similarity index 97% rename from backend/docs/conf.py rename to backend-plugin-sample/docs/conf.py index 446cb06..8d6be40 100644 --- a/backend/docs/conf.py +++ b/backend-plugin-sample/docs/conf.py @@ -1,6 +1,6 @@ # pylint: disable=invalid-name """ -sample_plugin documentation build configuration file. +openedx_plugin_sample documentation build configuration file. This file is execfile()d with the current directory set to its containing dir. @@ -24,7 +24,7 @@ REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(REPO_ROOT) -VERSION = get_version('openedx-sample-plugin') +VERSION = get_version('openedx-plugin-sample') # Configure Django for autodoc usage os.environ['DJANGO_SETTINGS_MODULE'] = 'test_settings' django_setup() @@ -76,10 +76,10 @@ top_level_doc = 'index' # General information about the project. -project = 'sample_plugin' +project = 'openedx_plugin_sample' copyright = f'{datetime.now().year}, Axim Collaborative, Inc.' # pylint: disable=redefined-builtin author = 'Axim Collaborative, Inc.' -project_title = 'sample_plugin' +project_title = 'openedx_plugin_sample' documentation_title = f"{project_title}" # Set display_github to False if you don't want "edit on Github" button @@ -208,7 +208,7 @@ # The name for this set of Sphinx documents. # " v documentation" by default. # -# html_title = 'sample_plugin v0.1.0' +# html_title = 'openedx_plugin_sample v0.1.0' # A shorter title for the navigation bar. Default is the same as html_title. # @@ -522,8 +522,8 @@ def on_init(app): # pylint: disable=unused-argument # If we are, assemble the path manually bin_path = os.path.abspath(os.path.join(sys.prefix, 'bin')) apidoc_path = os.path.join(bin_path, apidoc_path) - check_call([apidoc_path, '-o', docs_path, os.path.join(root_path, 'src/sample_plugin'), - os.path.join(root_path, 'src/sample_plugin/migrations')]) + check_call([apidoc_path, '-o', docs_path, os.path.join(root_path, 'src/openedx_plugin_sample'), + os.path.join(root_path, 'src/openedx_plugin_sample/migrations')]) def setup(app): diff --git a/backend/docs/decisions.rst b/backend-plugin-sample/docs/decisions.rst similarity index 100% rename from backend/docs/decisions.rst rename to backend-plugin-sample/docs/decisions.rst diff --git a/backend/docs/decisions/0001-purpose-of-this-repo.rst b/backend-plugin-sample/docs/decisions/0001-purpose-of-this-repo.rst similarity index 100% rename from backend/docs/decisions/0001-purpose-of-this-repo.rst rename to backend-plugin-sample/docs/decisions/0001-purpose-of-this-repo.rst diff --git a/backend/docs/decisions/README.rst b/backend-plugin-sample/docs/decisions/README.rst similarity index 100% rename from backend/docs/decisions/README.rst rename to backend-plugin-sample/docs/decisions/README.rst diff --git a/backend/docs/getting_started.rst b/backend-plugin-sample/docs/getting_started.rst similarity index 100% rename from backend/docs/getting_started.rst rename to backend-plugin-sample/docs/getting_started.rst diff --git a/backend/docs/how-tos/index.rst b/backend-plugin-sample/docs/how-tos/index.rst similarity index 100% rename from backend/docs/how-tos/index.rst rename to backend-plugin-sample/docs/how-tos/index.rst diff --git a/backend/docs/index.rst b/backend-plugin-sample/docs/index.rst similarity index 82% rename from backend/docs/index.rst rename to backend-plugin-sample/docs/index.rst index 08858a7..4f7bc16 100644 --- a/backend/docs/index.rst +++ b/backend-plugin-sample/docs/index.rst @@ -1,10 +1,10 @@ -.. sample_plugin documentation top level file, created by +.. openedx_plugin_sample documentation top level file, created by sphinx-quickstart on Fri Apr 11 10:00:56 2025. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -sample_plugin -============= +openedx_plugin_sample +===================== A sample backend plugin for the Open edX Platform diff --git a/backend/docs/internationalization.rst b/backend-plugin-sample/docs/internationalization.rst similarity index 100% rename from backend/docs/internationalization.rst rename to backend-plugin-sample/docs/internationalization.rst diff --git a/backend/docs/make.bat b/backend-plugin-sample/docs/make.bat similarity index 100% rename from backend/docs/make.bat rename to backend-plugin-sample/docs/make.bat diff --git a/backend-plugin-sample/docs/openedx_plugin_sample.rst b/backend-plugin-sample/docs/openedx_plugin_sample.rst new file mode 100644 index 0000000..7581fea --- /dev/null +++ b/backend-plugin-sample/docs/openedx_plugin_sample.rst @@ -0,0 +1,69 @@ +openedx\_plugin\_sample package +=============================== + +Submodules +---------- + +openedx\_plugin\_sample.apps module +----------------------------------- + +.. automodule:: openedx_plugin_sample.apps + :members: + :show-inheritance: + :undoc-members: + +openedx\_plugin\_sample.models module +------------------------------------- + +.. automodule:: openedx_plugin_sample.models + :members: + :show-inheritance: + :undoc-members: + +openedx\_plugin\_sample.pipeline module +--------------------------------------- + +.. automodule:: openedx_plugin_sample.pipeline + :members: + :show-inheritance: + :undoc-members: + +openedx\_plugin\_sample.serializers module +------------------------------------------ + +.. automodule:: openedx_plugin_sample.serializers + :members: + :show-inheritance: + :undoc-members: + +openedx\_plugin\_sample.signals module +-------------------------------------- + +.. automodule:: openedx_plugin_sample.signals + :members: + :show-inheritance: + :undoc-members: + +openedx\_plugin\_sample.urls module +----------------------------------- + +.. automodule:: openedx_plugin_sample.urls + :members: + :show-inheritance: + :undoc-members: + +openedx\_plugin\_sample.views module +------------------------------------ + +.. automodule:: openedx_plugin_sample.views + :members: + :show-inheritance: + :undoc-members: + +Module contents +--------------- + +.. automodule:: openedx_plugin_sample + :members: + :show-inheritance: + :undoc-members: diff --git a/backend/docs/quickstarts/index.rst b/backend-plugin-sample/docs/quickstarts/index.rst similarity index 100% rename from backend/docs/quickstarts/index.rst rename to backend-plugin-sample/docs/quickstarts/index.rst diff --git a/backend/docs/references/index.rst b/backend-plugin-sample/docs/references/index.rst similarity index 100% rename from backend/docs/references/index.rst rename to backend-plugin-sample/docs/references/index.rst diff --git a/backend/docs/testing.rst b/backend-plugin-sample/docs/testing.rst similarity index 90% rename from backend/docs/testing.rst rename to backend-plugin-sample/docs/testing.rst index b3a9b21..713e3ba 100644 --- a/backend/docs/testing.rst +++ b/backend-plugin-sample/docs/testing.rst @@ -3,7 +3,7 @@ Testing ####### -sample_plugin has an assortment of test cases and code quality +openedx_plugin_sample has an assortment of test cases and code quality checks to catch potential problems during development. To run them all in the version of Python you chose for your virtualenv: diff --git a/backend/manage.py b/backend-plugin-sample/manage.py similarity index 100% rename from backend/manage.py rename to backend-plugin-sample/manage.py diff --git a/backend/pylintrc b/backend-plugin-sample/pylintrc similarity index 100% rename from backend/pylintrc rename to backend-plugin-sample/pylintrc diff --git a/backend/pylintrc_tweaks b/backend-plugin-sample/pylintrc_tweaks similarity index 100% rename from backend/pylintrc_tweaks rename to backend-plugin-sample/pylintrc_tweaks diff --git a/backend/pyproject.toml b/backend-plugin-sample/pyproject.toml similarity index 92% rename from backend/pyproject.toml rename to backend-plugin-sample/pyproject.toml index 7b720df..9595618 100644 --- a/backend/pyproject.toml +++ b/backend-plugin-sample/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools", "setuptools-scm>8.1"] build-backend = "setuptools.build_meta" [project] -name = "openedx-sample-plugin" +name = "openedx-plugin-sample" description = "A sample backend plugin for the Open edX Platform" requires-python = ">=3.12" license = "Apache-2.0" @@ -38,10 +38,10 @@ dependencies = [ ] [project.entry-points."lms.djangoapp"] -sample_plugin = "sample_plugin.apps:SamplePluginConfig" +openedx_plugin_sample = "openedx_plugin_sample.apps:SamplePluginConfig" [project.entry-points."cms.djangoapp"] -sample_plugin = "sample_plugin.apps:SamplePluginConfig" +openedx_plugin_sample = "openedx_plugin_sample.apps:SamplePluginConfig" [project.urls] Homepage = "https://openedx.org/openedx/sample-plugin" @@ -120,6 +120,10 @@ root = ".." version_scheme = 'only-version' local_scheme = 'no-local-version' +# Dummy version number for when ../.git isn't accessible. +# This happens, for example, when bind-mounting the backend-plugin- into a Docker image. +fallback_version = "0.0.0.dev0" + [tool.uv] # Each entry lists groups with mutually exclusive version requirements so uv can # produce a single uv.lock that contains a separate resolution for each. Add a diff --git a/backend/setup.cfg b/backend-plugin-sample/setup.cfg similarity index 100% rename from backend/setup.cfg rename to backend-plugin-sample/setup.cfg diff --git a/backend-plugin-sample/src/openedx_plugin_sample/__init__.py b/backend-plugin-sample/src/openedx_plugin_sample/__init__.py new file mode 100644 index 0000000..20b9e8a --- /dev/null +++ b/backend-plugin-sample/src/openedx_plugin_sample/__init__.py @@ -0,0 +1,7 @@ +""" +A sample backend plugin for the Open edX Platform. +""" + +from importlib.metadata import version as get_version + +__version__ = get_version(__package__) diff --git a/backend/src/sample_plugin/apps.py b/backend-plugin-sample/src/openedx_plugin_sample/apps.py similarity index 93% rename from backend/src/sample_plugin/apps.py rename to backend-plugin-sample/src/openedx_plugin_sample/apps.py index 0c4de1f..de6346a 100644 --- a/backend/src/sample_plugin/apps.py +++ b/backend-plugin-sample/src/openedx_plugin_sample/apps.py @@ -1,5 +1,5 @@ """ -sample_plugin Django application initialization. +openedx_plugin_sample Django application initialization. """ from django.apps import AppConfig @@ -36,25 +36,25 @@ class SamplePluginConfig(AppConfig): This plugin is registered in pyproject.toml as:: [project.entry-points."lms.djangoapp"] - sample_plugin = "sample_plugin.apps:SamplePluginConfig" + openedx_plugin_sample = "openedx_plugin_sample.apps:SamplePluginConfig" [project.entry-points."cms.djangoapp"] - sample_plugin = "sample_plugin.apps:SamplePluginConfig" + openedx_plugin_sample = "openedx_plugin_sample.apps:SamplePluginConfig" The platform automatically discovers and loads plugins registered in these entry points. """ # pylint: disable=line-too-long # noqa: E501 default_auto_field = "django.db.models.BigAutoField" - name = "sample_plugin" + name = "openedx_plugin_sample" plugin_app = { "url_config": { "lms.djangoapp": { - PluginURLs.NAMESPACE: "sample_plugin", + PluginURLs.NAMESPACE: "openedx_plugin_sample", PluginURLs.REGEX: r"^sample-plugin/", PluginURLs.RELATIVE_PATH: "urls", }, "cms.djangoapp": { - PluginURLs.NAMESPACE: "sample_plugin", + PluginURLs.NAMESPACE: "openedx_plugin_sample", PluginURLs.REGEX: r"^sample-plugin/", PluginURLs.RELATIVE_PATH: "urls", }, diff --git a/backend/src/sample_plugin/conf/locale/config.yaml b/backend-plugin-sample/src/openedx_plugin_sample/conf/locale/config.yaml similarity index 100% rename from backend/src/sample_plugin/conf/locale/config.yaml rename to backend-plugin-sample/src/openedx_plugin_sample/conf/locale/config.yaml diff --git a/backend/src/sample_plugin/migrations/0001_initial.py b/backend-plugin-sample/src/openedx_plugin_sample/migrations/0001_initial.py similarity index 100% rename from backend/src/sample_plugin/migrations/0001_initial.py rename to backend-plugin-sample/src/openedx_plugin_sample/migrations/0001_initial.py diff --git a/backend/src/sample_plugin/migrations/__init__.py b/backend-plugin-sample/src/openedx_plugin_sample/migrations/__init__.py similarity index 100% rename from backend/src/sample_plugin/migrations/__init__.py rename to backend-plugin-sample/src/openedx_plugin_sample/migrations/__init__.py diff --git a/backend/src/sample_plugin/models.py b/backend-plugin-sample/src/openedx_plugin_sample/models.py similarity index 97% rename from backend/src/sample_plugin/models.py rename to backend-plugin-sample/src/openedx_plugin_sample/models.py index 240b1c4..5e80e25 100644 --- a/backend/src/sample_plugin/models.py +++ b/backend-plugin-sample/src/openedx_plugin_sample/models.py @@ -1,5 +1,5 @@ """ -Database models for sample_plugin. +Database models for openedx_plugin_sample. """ from django.contrib.auth import get_user_model diff --git a/backend/src/sample_plugin/pipeline.py b/backend-plugin-sample/src/openedx_plugin_sample/pipeline.py similarity index 97% rename from backend/src/sample_plugin/pipeline.py rename to backend-plugin-sample/src/openedx_plugin_sample/pipeline.py index 8b5dc3d..d159377 100644 --- a/backend/src/sample_plugin/pipeline.py +++ b/backend-plugin-sample/src/openedx_plugin_sample/pipeline.py @@ -1,5 +1,5 @@ """ -Open edX Filters implementation for the sample_plugin application. +Open edX Filters implementation for the openedx_plugin_sample application. This module demonstrates how to use Open edX Filters to modify platform behavior without changing core code. Filters are part of the Hooks Extension Framework @@ -62,7 +62,7 @@ def plugin_settings(settings): settings.OPEN_EDX_FILTERS_CONFIG = { "org.openedx.learning.course.about.render.started.v1": { "pipeline": [ - "sample_plugin.pipeline.ChangeCourseAboutPageUrl" + "openedx_plugin_sample.pipeline.ChangeCourseAboutPageUrl" ], "fail_silently": False, } diff --git a/backend/src/sample_plugin/py.typed b/backend-plugin-sample/src/openedx_plugin_sample/py.typed similarity index 100% rename from backend/src/sample_plugin/py.typed rename to backend-plugin-sample/src/openedx_plugin_sample/py.typed diff --git a/backend/src/sample_plugin/serializers.py b/backend-plugin-sample/src/openedx_plugin_sample/serializers.py similarity index 88% rename from backend/src/sample_plugin/serializers.py rename to backend-plugin-sample/src/openedx_plugin_sample/serializers.py index b723acf..ea98181 100644 --- a/backend/src/sample_plugin/serializers.py +++ b/backend-plugin-sample/src/openedx_plugin_sample/serializers.py @@ -1,11 +1,11 @@ """ -Serializers for the sample_plugin app. +Serializers for the openedx_plugin_sample app. """ from django.contrib.auth import get_user_model from rest_framework import serializers -from sample_plugin.models import CourseArchiveStatus +from openedx_plugin_sample.models import CourseArchiveStatus User = get_user_model() diff --git a/backend/src/sample_plugin/settings/common.py b/backend-plugin-sample/src/openedx_plugin_sample/settings/common.py similarity index 97% rename from backend/src/sample_plugin/settings/common.py rename to backend-plugin-sample/src/openedx_plugin_sample/settings/common.py index a935984..acf817c 100644 --- a/backend/src/sample_plugin/settings/common.py +++ b/backend-plugin-sample/src/openedx_plugin_sample/settings/common.py @@ -1,5 +1,5 @@ """ -Common settings for the sample_plugin application. +Common settings for the openedx_plugin_sample application. This module demonstrates how Django App Plugins integrate with the platform's settings system. Plugin settings are merged with the main settings during @@ -97,7 +97,7 @@ def _configure_openedx_filters(settings): # Filter we want to register filter_name = "org.openedx.learning.course_about.page.url.requested.v1" - our_pipeline_step = "sample_plugin.pipeline.ChangeCourseAboutPageUrl" + our_pipeline_step = "openedx_plugin_sample.pipeline.ChangeCourseAboutPageUrl" # Check if this filter already has configuration if filter_name in filters_config: diff --git a/backend/src/sample_plugin/settings/production.py b/backend-plugin-sample/src/openedx_plugin_sample/settings/production.py similarity index 59% rename from backend/src/sample_plugin/settings/production.py rename to backend-plugin-sample/src/openedx_plugin_sample/settings/production.py index 561bbe0..258a2d7 100644 --- a/backend/src/sample_plugin/settings/production.py +++ b/backend-plugin-sample/src/openedx_plugin_sample/settings/production.py @@ -1,8 +1,8 @@ """ -Production settings for the sample_plugin application. +Production settings for the openedx_plugin_sample application. """ -from sample_plugin.settings.common import plugin_settings as common_settings +from openedx_plugin_sample.settings.common import plugin_settings as common_settings def plugin_settings(settings): diff --git a/backend/src/sample_plugin/settings/test.py b/backend-plugin-sample/src/openedx_plugin_sample/settings/test.py similarity index 59% rename from backend/src/sample_plugin/settings/test.py rename to backend-plugin-sample/src/openedx_plugin_sample/settings/test.py index bf28df3..09a6a68 100644 --- a/backend/src/sample_plugin/settings/test.py +++ b/backend-plugin-sample/src/openedx_plugin_sample/settings/test.py @@ -1,8 +1,8 @@ """ -Test settings for the sample_plugin application. +Test settings for the openedx_plugin_sample application. """ -from sample_plugin.settings.common import plugin_settings as common_settings +from openedx_plugin_sample.settings.common import plugin_settings as common_settings def plugin_settings(settings): diff --git a/backend/src/sample_plugin/signals.py b/backend-plugin-sample/src/openedx_plugin_sample/signals.py similarity index 98% rename from backend/src/sample_plugin/signals.py rename to backend-plugin-sample/src/openedx_plugin_sample/signals.py index 0e707e5..c7934ee 100644 --- a/backend/src/sample_plugin/signals.py +++ b/backend-plugin-sample/src/openedx_plugin_sample/signals.py @@ -1,5 +1,5 @@ """ -Open edX Events signal handlers for the sample_plugin application. +Open edX Events signal handlers for the openedx_plugin_sample application. This module demonstrates how to consume Open edX Events (signals) to react to platform activities and integrate with external systems. Events are part of diff --git a/backend/src/sample_plugin/templates/sample_plugin/base.html b/backend-plugin-sample/src/openedx_plugin_sample/templates/openedx_plugin_sample/base.html similarity index 100% rename from backend/src/sample_plugin/templates/sample_plugin/base.html rename to backend-plugin-sample/src/openedx_plugin_sample/templates/openedx_plugin_sample/base.html diff --git a/backend/src/sample_plugin/urls.py b/backend-plugin-sample/src/openedx_plugin_sample/urls.py similarity index 80% rename from backend/src/sample_plugin/urls.py rename to backend-plugin-sample/src/openedx_plugin_sample/urls.py index 7f64ce0..0d4df6a 100644 --- a/backend/src/sample_plugin/urls.py +++ b/backend-plugin-sample/src/openedx_plugin_sample/urls.py @@ -1,11 +1,11 @@ """ -URLs for sample_plugin. +URLs for openedx_plugin_sample. """ from django.urls import include, path from rest_framework.routers import DefaultRouter -from sample_plugin.views import CourseArchiveStatusViewSet +from openedx_plugin_sample.views import CourseArchiveStatusViewSet # Create a router and register our viewsets with it router = DefaultRouter() diff --git a/backend/src/sample_plugin/views.py b/backend-plugin-sample/src/openedx_plugin_sample/views.py similarity index 97% rename from backend/src/sample_plugin/views.py rename to backend-plugin-sample/src/openedx_plugin_sample/views.py index 855fb9f..d08eaaa 100644 --- a/backend/src/sample_plugin/views.py +++ b/backend-plugin-sample/src/openedx_plugin_sample/views.py @@ -1,5 +1,5 @@ """ -Views for the sample_plugin app. +Views for the openedx_plugin_sample app. """ import logging @@ -13,8 +13,8 @@ from rest_framework.pagination import PageNumberPagination from rest_framework.throttling import UserRateThrottle -from sample_plugin.models import CourseArchiveStatus -from sample_plugin.serializers import CourseArchiveStatusSerializer +from openedx_plugin_sample.models import CourseArchiveStatus +from openedx_plugin_sample.serializers import CourseArchiveStatusSerializer logger = logging.getLogger(__name__) diff --git a/backend/test_settings.py b/backend-plugin-sample/test_settings.py similarity index 98% rename from backend/test_settings.py rename to backend-plugin-sample/test_settings.py index 88894fc..c556490 100644 --- a/backend/test_settings.py +++ b/backend-plugin-sample/test_settings.py @@ -54,7 +54,7 @@ def root(*args): INSTALLED_APPS.extend(plugin_apps) LOCALE_PATHS = [ - root("sample_plugin", "conf", "locale"), + root("openedx_plugin_sample", "conf", "locale"), ] ROOT_URLCONF = "tests.urls" diff --git a/backend/test_utils/__init__.py b/backend-plugin-sample/test_utils/__init__.py similarity index 100% rename from backend/test_utils/__init__.py rename to backend-plugin-sample/test_utils/__init__.py diff --git a/backend/tests/test_api.py b/backend-plugin-sample/tests/test_api.py similarity index 90% rename from backend/tests/test_api.py rename to backend-plugin-sample/tests/test_api.py index 0dc1b56..bc23abd 100644 --- a/backend/tests/test_api.py +++ b/backend-plugin-sample/tests/test_api.py @@ -11,7 +11,7 @@ from rest_framework import status from rest_framework.test import APIClient -from sample_plugin.models import CourseArchiveStatus +from openedx_plugin_sample.models import CourseArchiveStatus User = get_user_model() @@ -87,7 +87,7 @@ def test_list_course_archive_status_authenticated( Test that an authenticated user can list their own course archive statuses. """ api_client.force_authenticate(user=user) - url = reverse("sample_plugin:course-archive-status-list") + url = reverse("openedx_plugin_sample:course-archive-status-list") response = api_client.get(url) assert response.status_code == status.HTTP_200_OK @@ -106,7 +106,7 @@ def test_list_course_archive_status_unauthenticated(api_client): """ Test that an unauthenticated user cannot list course archive statuses. """ - url = reverse("sample_plugin:course-archive-status-list") + url = reverse("openedx_plugin_sample:course-archive-status-list") response = api_client.get(url) assert response.status_code == status.HTTP_403_FORBIDDEN @@ -130,7 +130,7 @@ def test_list_course_archive_status_staff_can_see_all( ) api_client.force_authenticate(user=staff_user) - url = reverse("sample_plugin:course-archive-status-list") + url = reverse("openedx_plugin_sample:course-archive-status-list") response = api_client.get(url) assert response.status_code == status.HTTP_200_OK @@ -143,7 +143,7 @@ def test_create_course_archive_status(api_client, user, course_key): Test that a user can create a course archive status. """ api_client.force_authenticate(user=user) - url = reverse("sample_plugin:course-archive-status-list") + url = reverse("openedx_plugin_sample:course-archive-status-list") data = { "course_id": str(course_key), "user": user.id, @@ -173,7 +173,7 @@ def test_create_course_archive_status_for_another_user( Test that a regular user cannot create a course archive status for another user. """ api_client.force_authenticate(user=user) - url = reverse("sample_plugin:course-archive-status-list") + url = reverse("openedx_plugin_sample:course-archive-status-list") data = { "course_id": str(course_key), "user": another_user.id, @@ -192,7 +192,7 @@ def test_staff_create_course_archive_status_for_another_user( Test that a staff user can create a course archive status for another user. """ api_client.force_authenticate(user=staff_user) - url = reverse("sample_plugin:course-archive-status-list") + url = reverse("openedx_plugin_sample:course-archive-status-list") data = { "course_id": str(course_key), "user": user.id, @@ -214,7 +214,7 @@ def test_update_course_archive_status(api_client, user, course_archive_status): """ api_client.force_authenticate(user=user) url = reverse( - "sample_plugin:course-archive-status-detail", args=[course_archive_status.id] + "openedx_plugin_sample:course-archive-status-detail", args=[course_archive_status.id] ) data = {"is_archived": True} response = api_client.patch(url, data, format="json") @@ -236,7 +236,7 @@ def test_delete_course_archive_status(api_client, user, course_archive_status): """ api_client.force_authenticate(user=user) url = reverse( - "sample_plugin:course-archive-status-detail", args=[course_archive_status.id] + "openedx_plugin_sample:course-archive-status-detail", args=[course_archive_status.id] ) response = api_client.delete(url) @@ -253,7 +253,7 @@ def test_cannot_update_other_user_course_archive_status( """ api_client.force_authenticate(user=another_user) url = reverse( - "sample_plugin:course-archive-status-detail", args=[course_archive_status.id] + "openedx_plugin_sample:course-archive-status-detail", args=[course_archive_status.id] ) data = {"is_archived": True} response = api_client.patch(url, data, format="json") @@ -270,7 +270,7 @@ def test_staff_can_update_other_user_course_archive_status( """ api_client.force_authenticate(user=staff_user) url = reverse( - "sample_plugin:course-archive-status-detail", args=[course_archive_status.id] + "openedx_plugin_sample:course-archive-status-detail", args=[course_archive_status.id] ) data = {"is_archived": True} response = api_client.patch(url, data, format="json") @@ -287,7 +287,7 @@ def test_create_course_archive_status_without_user_field(api_client, user, cours The user field should default to the current user. """ api_client.force_authenticate(user=user) - url = reverse("sample_plugin:course-archive-status-list") + url = reverse("openedx_plugin_sample:course-archive-status-list") data = { "course_id": str(course_key), "is_archived": True, @@ -321,7 +321,7 @@ def test_update_course_archive_status_without_user_field(api_client, user, cours """ api_client.force_authenticate(user=user) url = reverse( - "sample_plugin:course-archive-status-detail", args=[course_archive_status.id] + "openedx_plugin_sample:course-archive-status-detail", args=[course_archive_status.id] ) data = {"is_archived": True} # Note: No "user" field in data @@ -346,7 +346,7 @@ def test_staff_create_with_explicit_user_override( Test that staff can explicitly set user field to override default behavior. """ api_client.force_authenticate(user=staff_user) - url = reverse("sample_plugin:course-archive-status-list") + url = reverse("openedx_plugin_sample:course-archive-status-list") data = { "course_id": str(course_key), "user": user.id, @@ -381,7 +381,7 @@ def test_staff_update_with_explicit_user_override( api_client.force_authenticate(user=staff_user) url = reverse( - "sample_plugin:course-archive-status-detail", args=[initial_status.id] + "openedx_plugin_sample:course-archive-status-detail", args=[initial_status.id] ) data = { "user": another_user.id, @@ -407,7 +407,7 @@ def test_regular_user_cannot_override_user_field_create( Test that regular users cannot override user field to create records for other users. """ api_client.force_authenticate(user=user) - url = reverse("sample_plugin:course-archive-status-list") + url = reverse("openedx_plugin_sample:course-archive-status-list") data = { "course_id": str(course_key), "user": another_user.id, # Try to create for another user @@ -426,7 +426,7 @@ def test_staff_create_without_user_field_defaults_to_current_user( Test that even staff users get records created for themselves when no user specified. """ api_client.force_authenticate(user=staff_user) - url = reverse("sample_plugin:course-archive-status-list") + url = reverse("openedx_plugin_sample:course-archive-status-list") data = { "course_id": str(course_key), "is_archived": True, diff --git a/backend/tests/test_models.py b/backend-plugin-sample/tests/test_models.py similarity index 97% rename from backend/tests/test_models.py rename to backend-plugin-sample/tests/test_models.py index 331598f..ebc025e 100644 --- a/backend/tests/test_models.py +++ b/backend-plugin-sample/tests/test_models.py @@ -9,7 +9,7 @@ from django.db.utils import IntegrityError from opaque_keys.edx.keys import CourseKey -from sample_plugin.models import CourseArchiveStatus +from openedx_plugin_sample.models import CourseArchiveStatus User = get_user_model() diff --git a/backend/tests/test_plugin_integration.py b/backend-plugin-sample/tests/test_plugin_integration.py similarity index 77% rename from backend/tests/test_plugin_integration.py rename to backend-plugin-sample/tests/test_plugin_integration.py index 765b9d0..085552d 100644 --- a/backend/tests/test_plugin_integration.py +++ b/backend-plugin-sample/tests/test_plugin_integration.py @@ -15,8 +15,8 @@ def test_app_is_installed(): INSTALLED_APPS """ - assert "sample_plugin.apps.SamplePluginConfig" in settings.INSTALLED_APPS - assert apps.get_app_config("sample_plugin") is not None + assert "openedx_plugin_sample.apps.SamplePluginConfig" in settings.INSTALLED_APPS + assert apps.get_app_config("openedx_plugin_sample") is not None # We don't do a test for the URLs because the namespaced urls which should be auto registered are tested in the diff --git a/backend/tests/urls.py b/backend-plugin-sample/tests/urls.py similarity index 100% rename from backend/tests/urls.py rename to backend-plugin-sample/tests/urls.py diff --git a/backend/tox.ini b/backend-plugin-sample/tox.ini similarity index 86% rename from backend/tox.ini rename to backend-plugin-sample/tox.ini index 9523b16..c7ac4b4 100644 --- a/backend/tox.ini +++ b/backend-plugin-sample/tox.ini @@ -33,7 +33,7 @@ match-dir = (?!migrations) [pytest] DJANGO_SETTINGS_MODULE = test_settings -addopts = --cov sample_plugin --cov tests --cov-report term-missing --cov-report xml +addopts = --cov openedx_plugin_sample --cov tests --cov-report term-missing --cov-report xml norecursedirs = .* docs requirements site-packages [testenv] @@ -59,7 +59,7 @@ allowlist_externals = rm commands = doc8 --ignore-path docs/_build docs - rm -f docs/sample_plugin.rst + rm -f docs/openedx_plugin_sample.rst rm -f docs/modules.rst make -e -C docs clean make -e -C docs html @@ -76,11 +76,11 @@ allowlist_externals = touch commands = touch tests/__init__.py - pylint src/sample_plugin tests test_utils manage.py + pylint src/openedx_plugin_sample tests test_utils manage.py rm tests/__init__.py - pycodestyle src/sample_plugin tests manage.py - pydocstyle src/sample_plugin tests manage.py - isort --check-only --diff tests test_utils src/sample_plugin manage.py test_settings.py + pycodestyle src/openedx_plugin_sample tests manage.py + pydocstyle src/openedx_plugin_sample tests manage.py + isort --check-only --diff tests test_utils src/openedx_plugin_sample manage.py test_settings.py make selfcheck [testenv:pii_check] diff --git a/backend/uv.lock b/backend-plugin-sample/uv.lock similarity index 96% rename from backend/uv.lock rename to backend-plugin-sample/uv.lock index ebee56b..9f09c99 100644 --- a/backend/uv.lock +++ b/backend-plugin-sample/uv.lock @@ -2,17 +2,17 @@ version = 1 revision = 3 requires-python = ">=3.12" conflicts = [[ - { package = "openedx-sample-plugin", group = "django60" }, - { package = "openedx-sample-plugin", group = "test" }, + { package = "platform-plugin-sample", group = "django60" }, + { package = "platform-plugin-sample", group = "test" }, ], [ - { package = "openedx-sample-plugin", group = "django60" }, - { package = "openedx-sample-plugin", group = "quality" }, + { package = "platform-plugin-sample", group = "django60" }, + { package = "platform-plugin-sample", group = "doc" }, ], [ - { package = "openedx-sample-plugin", group = "django60" }, - { package = "openedx-sample-plugin", group = "doc" }, + { package = "platform-plugin-sample", group = "django60" }, + { package = "platform-plugin-sample", group = "quality" }, ], [ - { package = "openedx-sample-plugin", group = "dev" }, - { package = "openedx-sample-plugin", group = "django60" }, + { package = "platform-plugin-sample", group = "dev" }, + { package = "platform-plugin-sample", group = "django60" }, ]] [manifest] @@ -128,7 +128,7 @@ name = "cffi" version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy' or (extra == 'group-21-openedx-sample-plugin-dev' and extra == 'group-21-openedx-sample-plugin-django60') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, + { name = "pycparser", marker = "implementation_name != 'PyPy' or (extra == 'group-22-platform-plugin-sample-dev' and extra == 'group-22-platform-plugin-sample-django60') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ @@ -289,7 +289,7 @@ name = "click" version = "8.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'group-21-openedx-sample-plugin-dev' and extra == 'group-21-openedx-sample-plugin-django60') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'group-22-platform-plugin-sample-dev' and extra == 'group-22-platform-plugin-sample-django60') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } wheels = [ @@ -508,9 +508,9 @@ name = "django" version = "5.2.13" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "asgiref", marker = "extra == 'group-21-openedx-sample-plugin-dev' or extra != 'group-21-openedx-sample-plugin-django60' or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, - { name = "sqlparse", marker = "extra == 'group-21-openedx-sample-plugin-dev' or extra != 'group-21-openedx-sample-plugin-django60' or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, - { name = "tzdata", marker = "(sys_platform == 'win32' and extra == 'group-21-openedx-sample-plugin-dev') or (sys_platform == 'win32' and extra != 'group-21-openedx-sample-plugin-django60') or (extra == 'group-21-openedx-sample-plugin-dev' and extra == 'group-21-openedx-sample-plugin-django60') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, + { name = "asgiref", marker = "extra == 'group-22-platform-plugin-sample-dev' or extra != 'group-22-platform-plugin-sample-django60' or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, + { name = "sqlparse", marker = "extra == 'group-22-platform-plugin-sample-dev' or extra != 'group-22-platform-plugin-sample-django60' or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, + { name = "tzdata", marker = "(sys_platform == 'win32' and extra == 'group-22-platform-plugin-sample-dev') or (sys_platform == 'win32' and extra != 'group-22-platform-plugin-sample-django60') or (extra == 'group-22-platform-plugin-sample-dev' and extra == 'group-22-platform-plugin-sample-django60') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1f/c5/c69e338eb2959f641045802e5ea87ca4bf5ac90c5fd08953ca10742fad51/django-5.2.13.tar.gz", hash = "sha256:a31589db5188d074c63f0945c3888fad104627dfcc236fb2b97f71f89da33bc4", size = 10890368, upload-time = "2026-04-07T14:02:15.072Z" } wheels = [ @@ -522,9 +522,9 @@ name = "django" version = "6.0.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "asgiref", marker = "extra == 'group-21-openedx-sample-plugin-django60'" }, - { name = "sqlparse", marker = "extra == 'group-21-openedx-sample-plugin-django60'" }, - { name = "tzdata", marker = "(sys_platform == 'win32' and extra == 'group-21-openedx-sample-plugin-django60') or (extra == 'group-21-openedx-sample-plugin-dev' and extra == 'group-21-openedx-sample-plugin-django60') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, + { name = "asgiref", marker = "extra == 'group-22-platform-plugin-sample-django60'" }, + { name = "sqlparse", marker = "extra == 'group-22-platform-plugin-sample-django60'" }, + { name = "tzdata", marker = "(sys_platform == 'win32' and extra == 'group-22-platform-plugin-sample-django60') or (extra == 'group-22-platform-plugin-sample-dev' and extra == 'group-22-platform-plugin-sample-django60') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/60/b9/4155091ad1788b38563bd77a7258c0834e8c12a7f56f6975deaf54f8b61d/django-6.0.4.tar.gz", hash = "sha256:8cfa2572b3f2768b2e84983cf3c4811877a01edb64e817986ec5d60751c113ac", size = 10907407, upload-time = "2026-04-07T13:55:44.961Z" } wheels = [ @@ -536,8 +536,8 @@ name = "django-crum" version = "0.7.9" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-dev' or extra != 'group-21-openedx-sample-plugin-django60' or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, - { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-django60'" }, + { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-dev' or extra != 'group-22-platform-plugin-sample-django60' or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, + { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-django60'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/34/1d/c56588f67130aeef8828e47535e8551337d2ae02f91f1414da61bc5e49fb/django-crum-0.7.9.tar.gz", hash = "sha256:65e9bc0f070a663fafc4d9e357f45fd4e6f01838b20a9e2fb7670f5706754288", size = 5168, upload-time = "2020-11-10T17:15:35.124Z" } wheels = [ @@ -549,8 +549,8 @@ name = "django-extensions" version = "4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-dev' or extra != 'group-21-openedx-sample-plugin-django60' or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, - { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-django60'" }, + { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-dev' or extra != 'group-22-platform-plugin-sample-django60' or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, + { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-django60'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6d/b3/ed0f54ed706ec0b54fd251cc0364a249c6cd6c6ec97f04dc34be5e929eac/django_extensions-4.1.tar.gz", hash = "sha256:7b70a4d28e9b840f44694e3f7feb54f55d495f8b3fa6c5c0e5e12bcb2aa3cdeb", size = 283078, upload-time = "2025-04-11T01:15:39.617Z" } wheels = [ @@ -562,8 +562,8 @@ name = "django-filter" version = "25.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-dev' or extra != 'group-21-openedx-sample-plugin-django60' or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, - { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-django60'" }, + { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-dev' or extra != 'group-22-platform-plugin-sample-django60' or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, + { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-django60'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2c/e4/465d2699cd388c0005fb8d6ae6709f239917c6d8790ac35719676fffdcf3/django_filter-25.2.tar.gz", hash = "sha256:760e984a931f4468d096f5541787efb8998c61217b73006163bf2f9523fe8f23", size = 143818, upload-time = "2025-10-05T09:51:31.521Z" } wheels = [ @@ -575,8 +575,8 @@ name = "django-waffle" version = "5.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-dev' or extra != 'group-21-openedx-sample-plugin-django60' or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, - { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-django60'" }, + { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-dev' or extra != 'group-22-platform-plugin-sample-django60' or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, + { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-django60'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/22/e1/6f533da0d4ac89f427dfd9410e39bfc14ae3a23335ecd549d76be4b2a834/django_waffle-5.0.0.tar.gz", hash = "sha256:62f9d00eedf68dafb82657beab56e601bddedc1ea1ccfef91d83df8658708509", size = 37761, upload-time = "2025-06-12T07:38:54.895Z" } wheels = [ @@ -588,8 +588,8 @@ name = "djangorestframework" version = "3.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-dev' or extra != 'group-21-openedx-sample-plugin-django60' or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, - { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-django60'" }, + { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-dev' or extra != 'group-22-platform-plugin-sample-django60' or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, + { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-django60'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ca/d7/c016e69fac19ff8afdc89db9d31d9ae43ae031e4d1993b20aca179b8301a/djangorestframework-3.17.1.tar.gz", hash = "sha256:a6def5f447fe78ff853bff1d47a3c59bf38f5434b031780b351b0c73a62db1a5", size = 905742, upload-time = "2026-03-24T16:58:33.705Z" } wheels = [ @@ -648,8 +648,8 @@ version = "8.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, - { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-dev' or extra != 'group-21-openedx-sample-plugin-django60' or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, - { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-django60'" }, + { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-dev' or extra != 'group-22-platform-plugin-sample-django60' or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, + { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-django60'" }, { name = "django-crum" }, { name = "django-waffle" }, { name = "psutil" }, @@ -667,7 +667,7 @@ version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" } }, - { name = "lxml", extra = ["html-clean"], marker = "extra == 'group-21-openedx-sample-plugin-dev' or extra != 'group-21-openedx-sample-plugin-django60' or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, + { name = "lxml", extra = ["html-clean"], marker = "extra == 'group-22-platform-plugin-sample-dev' or extra != 'group-22-platform-plugin-sample-django60' or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, { name = "path" }, { name = "polib" }, { name = "pyyaml" }, @@ -1120,8 +1120,8 @@ version = "11.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, - { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-dev' or extra != 'group-21-openedx-sample-plugin-django60' or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, - { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-django60'" }, + { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-dev' or extra != 'group-22-platform-plugin-sample-django60' or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, + { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-django60'" }, { name = "edx-ccx-keys" }, { name = "edx-django-utils" }, { name = "edx-opaque-keys" }, @@ -1138,8 +1138,8 @@ name = "openedx-filters" version = "3.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-dev' or extra != 'group-21-openedx-sample-plugin-django60' or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, - { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-django60'" }, + { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-dev' or extra != 'group-22-platform-plugin-sample-django60' or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, + { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-django60'" }, { name = "edx-opaque-keys" }, { name = "setuptools" }, ] @@ -1149,11 +1149,29 @@ wheels = [ ] [[package]] -name = "openedx-sample-plugin" +name = "packaging" +version = "26.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" }, +] + +[[package]] +name = "path" +version = "16.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/1c/3950c87aa25437af5f1663cc8627d44ff26f8c5117a5053c9fc3f641027c/path-16.16.0.tar.gz", hash = "sha256:a6a6d916c910dc17e0ddc883358756c5a33d1b6dbdf5d6de86554f399053af58", size = 50905, upload-time = "2024-07-27T09:37:45.926Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/0f/ddd60b4794cc8a8c086150e13ffbff438dbf306b2739918e65ddb706208f/path-16.16.0-py3-none-any.whl", hash = "sha256:d981989cf87598adc9f5b71ec5192d314a384836e81b4b1f34197138dc4ae659", size = 25531, upload-time = "2024-07-27T09:37:44.312Z" }, +] + +[[package]] +name = "platform-plugin-sample" source = { editable = "." } dependencies = [ - { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-dev' or extra != 'group-21-openedx-sample-plugin-django60' or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, - { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-21-openedx-sample-plugin-django60'" }, + { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-dev' or extra != 'group-22-platform-plugin-sample-django60' or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, + { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-22-platform-plugin-sample-django60'" }, { name = "django-filter" }, { name = "djangorestframework" }, { name = "edx-opaque-keys" }, @@ -1313,24 +1331,6 @@ test-base = [ { name = "pytest-django" }, ] -[[package]] -name = "packaging" -version = "26.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" }, -] - -[[package]] -name = "path" -version = "16.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/1c/3950c87aa25437af5f1663cc8627d44ff26f8c5117a5053c9fc3f641027c/path-16.16.0.tar.gz", hash = "sha256:a6a6d916c910dc17e0ddc883358756c5a33d1b6dbdf5d6de86554f399053af58", size = 50905, upload-time = "2024-07-27T09:37:45.926Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/0f/ddd60b4794cc8a8c086150e13ffbff438dbf306b2739918e65ddb706208f/path-16.16.0-py3-none-any.whl", hash = "sha256:d981989cf87598adc9f5b71ec5192d314a384836e81b4b1f34197138dc4ae659", size = 25531, upload-time = "2024-07-27T09:37:44.312Z" }, -] - [[package]] name = "platformdirs" version = "4.9.6" @@ -1552,7 +1552,7 @@ name = "pynacl" version = "1.6.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy' or (extra == 'group-21-openedx-sample-plugin-dev' and extra == 'group-21-openedx-sample-plugin-django60') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, + { name = "cffi", marker = "platform_python_implementation != 'PyPy' or (extra == 'group-22-platform-plugin-sample-dev' and extra == 'group-22-platform-plugin-sample-django60') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/4019b524b03a13438637b11538c82781a5eda427394380381af8f04f467a/pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c", size = 3511692, upload-time = "2026-01-01T17:48:10.851Z" } wheels = [ @@ -1608,7 +1608,7 @@ name = "pytest" version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'group-21-openedx-sample-plugin-dev' and extra == 'group-21-openedx-sample-plugin-django60') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-doc') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-quality') or (extra == 'group-21-openedx-sample-plugin-django60' and extra == 'group-21-openedx-sample-plugin-test')" }, + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'group-22-platform-plugin-sample-dev' and extra == 'group-22-platform-plugin-sample-django60') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-doc') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-quality') or (extra == 'group-22-platform-plugin-sample-django60' and extra == 'group-22-platform-plugin-sample-test')" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, diff --git a/backend/src/sample_plugin/__init__.py b/backend/src/sample_plugin/__init__.py deleted file mode 100644 index 000bb65..0000000 --- a/backend/src/sample_plugin/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -A sample backend plugin for the Open edX Platform. -""" - -from importlib.metadata import version as get_version - -# The name of the package is `openedx-sample-plugin` but __package__ is `sample_plugin` so we hardcode the name of the -# package here so that the version fetching works correctly. A lot of examples will show using `__package__`. -__version__ = get_version('openedx-sample-plugin') diff --git a/brand/README.md b/brand-sample/README.md similarity index 93% rename from brand/README.md rename to brand-sample/README.md index ae580af..5ec1be8 100644 --- a/brand/README.md +++ b/brand-sample/README.md @@ -1,4 +1,4 @@ -# brand-example-purple +# brand-sample **This is a simple example brand package that changes the `brand` color to purple.** @@ -16,13 +16,13 @@ > * **Open edX "Teak" release or later (Tutor >= 20)** > * **Tutor >= 20** -### Configure `tutor` to use this theme +## TODO #### Using the [`jsdelivr`](https://www.jsdelivr.com/) CDN (Recommended) 1. Stop `tutor` (`tutor dev stop` or `tutor local stop`) 2. Navigate to your local tutor plugins directory (`tutor plugins printroot`) -3. Create a new `purple-jsdelivr.py` plugin file with the following content +3. Create a new `sample-jsdelivr.py` plugin file with the following content ```py import json @@ -33,7 +33,7 @@ paragon_theme_urls = { "light": { "urls": { "default": "https://cdn.jsdelivr.net/npm/@openedx/paragon@$paragonVersion/dist/light.min.css", - "brandOverride": "https://cdn.jsdelivr.net/gh/openedx/sample-plugin@main/brand/dist/light.min.css" + "brandOverride": "https://cdn.jsdelivr.net/gh/openedx/sample-plugin@main/brand-sample/dist/light.min.css" } } } @@ -51,7 +51,7 @@ hooks.Filters.ENV_PATCHES.add_item( ) ``` -4. Enable the plugin (`tutor plugins enable purple-jsdelivr`) +4. Enable the plugin (`tutor plugins enable sample-jsdelivr`) 5. Start `tutor` (`tutor dev start lms cms mfe` or `tutor local start lms cms mfe`) #### Using the [Tutor Paragon Plugin](https://github.com/openedx/openedx-tutor-plugins/tree/main/plugins/tutor-contrib-paragon) (Recommended) diff --git a/brand/docs/images/authn-with-theme.png b/brand-sample/docs/images/authn-with-theme.png similarity index 100% rename from brand/docs/images/authn-with-theme.png rename to brand-sample/docs/images/authn-with-theme.png diff --git a/brand/docs/images/authn-without-theme.png b/brand-sample/docs/images/authn-without-theme.png similarity index 100% rename from brand/docs/images/authn-without-theme.png rename to brand-sample/docs/images/authn-without-theme.png diff --git a/brand/src/.gitignore b/brand-sample/src/.gitignore similarity index 100% rename from brand/src/.gitignore rename to brand-sample/src/.gitignore diff --git a/brand/src/.nvmrc b/brand-sample/src/.nvmrc similarity index 100% rename from brand/src/.nvmrc rename to brand-sample/src/.nvmrc diff --git a/brand/src/Makefile b/brand-sample/src/Makefile similarity index 100% rename from brand/src/Makefile rename to brand-sample/src/Makefile diff --git a/brand/src/README.md b/brand-sample/src/README.md similarity index 85% rename from brand/src/README.md rename to brand-sample/src/README.md index b955d30..a97e44e 100644 --- a/brand/src/README.md +++ b/brand-sample/src/README.md @@ -1,4 +1,4 @@ -# brand-example-purple +# brand-sample This directory contains the source design tokens for the example brand package. diff --git a/brand/src/package-lock.json b/brand-sample/src/package-lock.json similarity index 99% rename from brand/src/package-lock.json rename to brand-sample/src/package-lock.json index 6dced28..448af16 100644 --- a/brand/src/package-lock.json +++ b/brand-sample/src/package-lock.json @@ -1,11 +1,11 @@ { - "name": "brand-example-purple", + "name": "brand-sample", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "brand-example-purple", + "name": "brand-sample", "version": "0.0.1", "license": "MIT", "devDependencies": { diff --git a/brand/src/package.json b/brand-sample/src/package.json similarity index 91% rename from brand/src/package.json rename to brand-sample/src/package.json index 1790d13..5e47d86 100644 --- a/brand/src/package.json +++ b/brand-sample/src/package.json @@ -1,18 +1,18 @@ { - "name": "brand-example-purple", + "name": "brand-sample", "version": "0.0.1", "description": "An example design-token-based brand package for Open edX applications.", "repository": { "type": "git", "url": "git+https://github.com/openedx/sample-plugin.git", - "directory": "brand" + "directory": "brand-sample" }, "author": "Brian Smith", "license": "MIT", "bugs": { "url": "https://github.com/openedx/sample-plugin/issues" }, - "homepage": "https://github.com/openedx/sample-plugin/tree/main/brand#readme", + "homepage": "https://github.com/openedx/sample-plugin/tree/main/brand-sample#readme", "scripts": { "build-tokens": "paragon build-tokens --source ./tokens/src --build-dir ./paragon/css --source-tokens-only --exclude-core", "build-scss": "paragon build-scss --themesPath ./paragon/css/themes --defaultThemeVariants light --excludeCore", diff --git a/brand/src/tokens/src/themes/light/global/color.json b/brand-sample/src/tokens/src/themes/light/global/color.json similarity index 100% rename from brand/src/tokens/src/themes/light/global/color.json rename to brand-sample/src/tokens/src/themes/light/global/color.json diff --git a/brand/dist/README.md b/brand/dist/README.md deleted file mode 100644 index 56d553c..0000000 --- a/brand/dist/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Compiled Theme - -This directory contains the compiled theme stylesheets referenced by the [`jsdelivr`](https://www.jsdelivr.com/) example plugin. diff --git a/brand/dist/light.css b/brand/dist/light.css deleted file mode 100644 index 7c6555b..0000000 --- a/brand/dist/light.css +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Do not edit directly, this file was auto-generated. - * See /tokens/README.md for more details. - */ - -:root { - --pgn-size-btn-focus-width: 2px; - --pgn-size-input-btn-focus-width: 1px; - --pgn-spacing-input-btn-padding-y: 0.5625rem; - --pgn-color-brand-100: #E9D4FFFF; - --pgn-color-brand-200: #DAB2FFFF; - --pgn-color-brand-300: #C27AFFFF; - --pgn-color-brand-400: #AD46FFFF; - --pgn-color-brand-600: #8200DBFF; - --pgn-color-brand-700: #6E11B0FF; - --pgn-color-brand-800: #59168BFF; - --pgn-color-brand-900: #3C0366FF; - --pgn-color-brand-base: #9810FAFF; - --pgn-spacing-form-input-padding-y-base: var(--pgn-spacing-input-btn-padding-y); - --pgn-color-brand-500: var(--pgn-color-brand-base); /** Brand color of level 500. */ - --pgn-color-action-default-brand-100: #CFA1FFFF; - --pgn-color-action-default-brand-200: #C17FFFFF; - --pgn-color-action-default-brand-300: #AB47FFFF; - --pgn-color-action-default-brand-400: #9613FFFF; - --pgn-color-action-default-brand-600: #6400A8FF; - --pgn-color-action-default-brand-700: #510D81FF; - --pgn-color-action-default-brand-800: #3D0F5FFF; - --pgn-color-action-default-brand-900: #1F0234FF; - --pgn-color-action-default-brand-base: #7C04D3FF; - --pgn-color-action-default-brand-500: #7C04D3FF; -} \ No newline at end of file diff --git a/brand/dist/light.css.map b/brand/dist/light.css.map deleted file mode 100644 index 8645f31..0000000 --- a/brand/dist/light.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["light.css"],"names":[],"mappings":"AAKA,MACE,8BAA+B,CAC/B,oCAAqC,CACrC,2CAA4C,CAC5C,+BAAgC,CAChC,+BAAgC,CAChC,+BAAgC,CAChC,+BAAgC,CAChC,+BAAgC,CAChC,+BAAgC,CAChC,+BAAgC,CAChC,+BAAgC,CAChC,gCAAiC,CACjC,8EAA+E,CAC/E,iDAAkD,CAClD,8CAA+C,CAC/C,8CAA+C,CAC/C,8CAA+C,CAC/C,8CAA+C,CAC/C,8CAA+C,CAC/C,8CAA+C,CAC/C,8CAA+C,CAC/C,8CAA+C,CAC/C,+CAAgD,CAChD,8CACF","file":"light.css","sourcesContent":["/**\n * Do not edit directly, this file was auto-generated.\n * See /tokens/README.md for more details.\n */\n\n:root {\n --pgn-size-btn-focus-width: 2px;\n --pgn-size-input-btn-focus-width: 1px;\n --pgn-spacing-input-btn-padding-y: 0.5625rem;\n --pgn-color-brand-100: #E9D4FFFF;\n --pgn-color-brand-200: #DAB2FFFF;\n --pgn-color-brand-300: #C27AFFFF;\n --pgn-color-brand-400: #AD46FFFF;\n --pgn-color-brand-600: #8200DBFF;\n --pgn-color-brand-700: #6E11B0FF;\n --pgn-color-brand-800: #59168BFF;\n --pgn-color-brand-900: #3C0366FF;\n --pgn-color-brand-base: #9810FAFF;\n --pgn-spacing-form-input-padding-y-base: var(--pgn-spacing-input-btn-padding-y);\n --pgn-color-brand-500: var(--pgn-color-brand-base); /** Brand color of level 500. */\n --pgn-color-action-default-brand-100: #CFA1FFFF;\n --pgn-color-action-default-brand-200: #C17FFFFF;\n --pgn-color-action-default-brand-300: #AB47FFFF;\n --pgn-color-action-default-brand-400: #9613FFFF;\n --pgn-color-action-default-brand-600: #6400A8FF;\n --pgn-color-action-default-brand-700: #510D81FF;\n --pgn-color-action-default-brand-800: #3D0F5FFF;\n --pgn-color-action-default-brand-900: #1F0234FF;\n --pgn-color-action-default-brand-base: #7C04D3FF;\n --pgn-color-action-default-brand-500: #7C04D3FF;\n}"]} \ No newline at end of file diff --git a/brand/dist/light.min.css b/brand/dist/light.min.css deleted file mode 100644 index 6a0c6e0..0000000 --- a/brand/dist/light.min.css +++ /dev/null @@ -1,2 +0,0 @@ -:root{--pgn-size-btn-focus-width:2px;--pgn-size-input-btn-focus-width:1px;--pgn-spacing-input-btn-padding-y:0.5625rem;--pgn-color-brand-100:#E9D4FFFF;--pgn-color-brand-200:#DAB2FFFF;--pgn-color-brand-300:#C27AFFFF;--pgn-color-brand-400:#AD46FFFF;--pgn-color-brand-600:#8200DBFF;--pgn-color-brand-700:#6E11B0FF;--pgn-color-brand-800:#59168BFF;--pgn-color-brand-900:#3C0366FF;--pgn-color-brand-base:#9810FAFF;--pgn-spacing-form-input-padding-y-base:var(--pgn-spacing-input-btn-padding-y);--pgn-color-brand-500:var(--pgn-color-brand-base);--pgn-color-action-default-brand-100:#CFA1FFFF;--pgn-color-action-default-brand-200:#C17FFFFF;--pgn-color-action-default-brand-300:#AB47FFFF;--pgn-color-action-default-brand-400:#9613FFFF;--pgn-color-action-default-brand-600:#6400A8FF;--pgn-color-action-default-brand-700:#510D81FF;--pgn-color-action-default-brand-800:#3D0F5FFF;--pgn-color-action-default-brand-900:#1F0234FF;--pgn-color-action-default-brand-base:#7C04D3FF;--pgn-color-action-default-brand-500:#7C04D3FF} -/*# sourceMappingURL=light.css.map */ \ No newline at end of file diff --git a/brand/dist/theme-urls.json b/brand/dist/theme-urls.json deleted file mode 100644 index 6d6b110..0000000 --- a/brand/dist/theme-urls.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "themeUrls": { - "defaults": { - "light": "light" - }, - "variants": { - "light": { - "paths": { - "default": "./light.css", - "minified": "./light.min.css" - } - } - } - } -} diff --git a/frontend/.gitignore b/frontend-plugin-sample/.gitignore similarity index 100% rename from frontend/.gitignore rename to frontend-plugin-sample/.gitignore diff --git a/frontend/.nvmrc b/frontend-plugin-sample/.nvmrc similarity index 100% rename from frontend/.nvmrc rename to frontend-plugin-sample/.nvmrc diff --git a/frontend/README.md b/frontend-plugin-sample/README.md similarity index 96% rename from frontend/README.md rename to frontend-plugin-sample/README.md index 973714a..fc94641 100644 --- a/frontend/README.md +++ b/frontend-plugin-sample/README.md @@ -195,7 +195,7 @@ const handleArchiveToggle = async (courseId, isCurrentlyArchived) => { ```javascript import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; -import { CourseList } from '@openedx/sample-plugin'; +import { CourseList } from '@openedx/plugin-sample'; const config = { pluginSlots: { @@ -305,16 +305,23 @@ if (response.data && Array.isArray(response.data)) { ### Prerequisites 1. **MFE Setup**: Have a learner dashboard MFE running locally -2. **Backend Plugin**: Install the backend plugin (see [`../backend/README.md`](../backend/README.md)) +2. **Backend Plugin**: Install the backend plugin (see [`../backend-plugin-sample/README.md`](../backend-plugin-sample/README.md)) 3. **Node.js**: Version 16+ with npm or yarn ### Local Development Setup +#### Step 0: Build Plugin + +```bash +cd /path/to/sample-plugin/frontend-plugin-sample +npm run build +``` + #### Step 1: Install Plugin Package ```bash # In your MFE directory (e.g., frontend-app-learner-dashboard) -npm install /path/to/sample-plugin/frontend +npm install /path/to/sample-plugin/frontend-plugin-sample ``` #### Step 2: Create env.config.jsx @@ -323,7 +330,7 @@ Create `env.config.jsx` in your MFE root (not committed to repo): ```javascript import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; -import { CourseList } from '@openedx/sample-plugin'; +import { CourseList } from '@openedx/plugin-sample'; const config = { pluginSlots: { @@ -355,8 +362,8 @@ Create `module.config.js` for local development: module.exports = { localModules: [ { - moduleName: '@openedx/sample-plugin', - dir: '/path/to/sample-plugin/frontend' + moduleName: '@openedx/plugin-sample', + dir: '/path/to/sample-plugin/frontend-plugin-sample' }, ], }; @@ -420,7 +427,7 @@ Test within the actual MFE environment: ### Production Deployment with Tutor -**Tutor Plugin Configuration** (see [`../tutor/README.md`](../tutor/README.md)): +**Tutor Plugin Configuration** (see [`../tutor-contrib-sample/README.md`](../tutor-contrib-sample/README.md)): ```python # In tutor plugin @@ -547,7 +554,7 @@ const CourseList = ({ courseListData }) => ( **Plugin Not Loading**: - Check `env.config.jsx` slot name matches target slot -- Verify plugin is installed (`npm list @openedx/sample-plugin`) +- Verify plugin is installed (`npm list @openedx/plugin-sample`) - Ensure MFE supports the plugin framework version - Check browser console for JavaScript errors diff --git a/frontend/package-lock.json b/frontend-plugin-sample/package-lock.json similarity index 99% rename from frontend/package-lock.json rename to frontend-plugin-sample/package-lock.json index 74f7364..b88da17 100644 --- a/frontend/package-lock.json +++ b/frontend-plugin-sample/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@openedx/sample-plugin", + "name": "@openedx/plugin-sample", "version": "__semantically__released__", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@openedx/sample-plugin", + "name": "@openedx/plugin-sample", "version": "__semantically__released__", "devDependencies": { "@openedx/frontend-build": "*" diff --git a/frontend/package.json b/frontend-plugin-sample/package.json similarity index 92% rename from frontend/package.json rename to frontend-plugin-sample/package.json index 978d0f7..f62a827 100644 --- a/frontend/package.json +++ b/frontend-plugin-sample/package.json @@ -1,5 +1,5 @@ { - "name": "@openedx/sample-plugin", + "name": "@openedx/plugin-sample", "version": "__semantically__released__", "main": "dist/index.js", "files": ["dist"], diff --git a/frontend/src/index.jsx b/frontend-plugin-sample/src/index.jsx similarity index 100% rename from frontend/src/index.jsx rename to frontend-plugin-sample/src/index.jsx diff --git a/frontend/src/plugin.jsx b/frontend-plugin-sample/src/plugin.jsx similarity index 100% rename from frontend/src/plugin.jsx rename to frontend-plugin-sample/src/plugin.jsx diff --git a/tutor/README.md b/tutor-contrib-sample/README.md similarity index 93% rename from tutor/README.md rename to tutor-contrib-sample/README.md index 4116b58..a4acfa0 100644 --- a/tutor/README.md +++ b/tutor-contrib-sample/README.md @@ -29,7 +29,7 @@ This Tutor plugin simplifies the deployment of the sample plugin by: ## Plugin Configuration -**File**: [`sample_plugin.py`](./sample_plugin.py) +**File**: [`sample.py`](./sample.py) ### Current Configuration @@ -68,7 +68,7 @@ __version__ = "1.0.0" # Backend plugin installation @hooks.Filters.IMAGES_BUILD_MOUNTS.add() -def _mount_sample_plugin(mounts): +def _mount_openedx_plugin_sample(mounts): """Mount the sample plugin source code for development.""" mounts.append(("sample-plugin-backend", "/openedx/sample-plugin-backend")) return mounts @@ -122,18 +122,18 @@ PLUGIN_SLOTS.add_items([ ```bash # Method 1: Install from local directory -pip install -e /path/to/sample-plugin/tutor/ +pip install -e /path/to/sample-plugin/tutor-contrib-sample/ # Method 2: Copy plugin file (simpler for development) mkdir -p "$(tutor plugins printroot)" -cp sample_plugin.py "$(tutor plugins printroot)/sample_plugin.py" +cp sample.py "$(tutor plugins printroot)/sample.py" ``` ### Step 2: Enable Plugin ```bash # Enable the plugin -tutor plugins enable sample_plugin +tutor plugins enable sample # Verify plugin is enabled tutor plugins list @@ -161,7 +161,7 @@ tutor local launch ```bash # Check backend plugin -tutor dev exec lms python manage.py shell -c "from sample_plugin.models import CourseArchiveStatus; print('Backend plugin loaded')" +tutor dev exec lms python manage.py shell -c "from openedx_plugin_sample.models import CourseArchiveStatus; print('Backend plugin loaded')" # Check frontend plugin (visit learner dashboard in browser) # Should see custom course list with archive functionality @@ -202,7 +202,7 @@ tutor dev restart lms **Setup Pattern:** ```bash # Enable plugin -tutor plugins enable sample_plugin +tutor plugins enable sample # Build and deploy tutor local launch @@ -236,7 +236,7 @@ def _add_backend_settings(env): "OPEN_EDX_FILTERS_CONFIG": { "org.openedx.learning.course.about.render.started.v1": { "pipeline": [ - "sample_plugin.pipeline.ChangeCourseAboutPageUrl" + "openedx_plugin_sample.pipeline.ChangeCourseAboutPageUrl" ], "fail_silently": False, } @@ -296,7 +296,7 @@ def _configure_by_environment(env): tutor plugins list # Check plugin syntax -python -m py_compile sample_plugin.py +python -m py_compile sample.py # Verify plugin location tutor plugins printroot @@ -310,10 +310,10 @@ tutor images build lms # Manual installation for debugging tutor dev exec lms pip install -e ../sample-plugin-backend -tutor dev exec lms python -c "import sample_plugin; print('Success')" +tutor dev exec lms python -c "import openedx_plugin_sample; print('Success')" # Check migrations -tutor dev exec lms python manage.py showmigrations sample_plugin +tutor dev exec lms python manage.py showmigrations openedx_plugin_sample ``` **Frontend Plugin Not Appearing:** @@ -340,7 +340,7 @@ tutor dev exec lms python manage.py shell -c "from django.conf import settings; ```bash # View plugin configuration -tutor plugins show sample_plugin +tutor plugins show sample # Check generated configuration tutor config printvalue PLUGINS @@ -418,7 +418,7 @@ def _build_custom_image(build_config): def _run_plugin_migrations(): """Run plugin migrations when platform is ready.""" from django.core.management import call_command - call_command("migrate", "sample_plugin") + call_command("migrate", "openedx_plugin_sample") ``` ### Plugin Dependencies diff --git a/tutor/pyproject.toml b/tutor-contrib-sample/pyproject.toml similarity index 90% rename from tutor/pyproject.toml rename to tutor-contrib-sample/pyproject.toml index 2852ef6..8792b69 100644 --- a/tutor/pyproject.toml +++ b/tutor-contrib-sample/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools", "setuptools-scm>8.1"] build-backend = "setuptools.build_meta" [project] -name = "tutor-contrib-sample-plugin" +name = "tutor-contrib-sample" description = "Tutor plugin for the Open edX Sample Plugin" requires-python = ">=3.11" license = "Apache-2.0" @@ -23,7 +23,7 @@ dependencies = ["tutor>=17.0.0"] dynamic = ["readme", "version"] [project.entry-points."tutor.plugin.v1"] -sample_plugin = "tutorsampleplugin.plugin" +sample = "tutorsample.plugin" [project.urls] Homepage = "https://github.com/openedx/sample-plugin" @@ -34,7 +34,7 @@ readme = {file = ["README.md"], content-type = "text/markdown"} [tool.setuptools.packages.find] where = ["."] -include = ["tutorsampleplugin*"] +include = ["tutorsample*"] [tool.setuptools_scm] # The root for the git repo is one directory up. diff --git a/tutor/tutorsampleplugin/__init__.py b/tutor-contrib-sample/tutorsample/__init__.py similarity index 100% rename from tutor/tutorsampleplugin/__init__.py rename to tutor-contrib-sample/tutorsample/__init__.py diff --git a/tutor/tutorsampleplugin/plugin.py b/tutor-contrib-sample/tutorsample/plugin.py similarity index 55% rename from tutor/tutorsampleplugin/plugin.py rename to tutor-contrib-sample/tutorsample/plugin.py index 0c6e7e4..16e5c7e 100644 --- a/tutor/tutorsampleplugin/plugin.py +++ b/tutor-contrib-sample/tutorsample/plugin.py @@ -1,14 +1,15 @@ """ Tutor plugin for the Open edX Sample Plugin. -Installs the backend Django app (openedx-sample-plugin from PyPI) into LMS/CMS -and configures the frontend MFE slot (from @openedx/sample-plugin on npm) in the +Installs the backend Django app (openedx-plugin-sample from PyPI) into LMS/CMS +and configures the frontend MFE slot (from @openedx/plugin-sample on npm) in the learner-dashboard. Requirements: tutor>=17.0.0 tutor-mfe (for frontend slot configuration) """ +import json from tutor import hooks @@ -22,29 +23,33 @@ # Backend: Install the Django app plugin into LMS and CMS images # --------------------------------------------------------------------------- -# If a directory named "backend" has been moutned with `tutor mount add`, then -# this line will ensure that the directory gets mapped into the openedx[-dev] -# image and into the container's virtualenv. When there is no such directory -# mounted, this line has no effect (thus it's safe for it to exist in the -# production version of the plugin). -hooks.Filters.MOUNTED_DIRECTORIES.add_item(("openedx", "backend")) - # The openedx-dockerfile-post-python-requirements patch runs after pip # installs the base Open edX requirements. Plugins installed here are # available in both LMS and CMS containers. -hooks.Filters.ENV_PATCHES.add_item(( - "openedx-dockerfile-post-python-requirements", - "RUN pip install openedx-sample-plugin", -)) +# @@TODO: reinstate once published +#hooks.Filters.ENV_PATCHES.add_item(( +# "openedx-dockerfile-post-python-requirements", +# "RUN pip install openedx-plugin-sample", +#)) + +# Ensure that *if* backend-plugin-sample is bind-mounted, then it is mapped +# to /mnt/backend-plugin-sample and pip-installed as part of the openedx +# and openedx-dev image builds + +hooks.Filters.MOUNTED_DIRECTORIES.add_item(("openedx", "backend-plugin-sample")) # --------------------------------------------------------------------------- -# Migrations: Run sample_plugin migrations on init +# Migrations: Run openedx_plugin_sample migrations on init # --------------------------------------------------------------------------- hooks.Filters.CLI_DO_INIT_TASKS.add_item(( "lms", - "./manage.py lms migrate sample_plugin", + "./manage.py lms migrate openedx_plugin_sample", +)) +hooks.Filters.CLI_DO_INIT_TASKS.add_item(( + "cms", + "./manage.py cms migrate openedx_plugin_sample", )) # --------------------------------------------------------------------------- @@ -63,7 +68,7 @@ # The plugin slot config is still scoped to learner-dashboard at runtime. hooks.Filters.ENV_PATCHES.add_item(( "mfe-dockerfile-post-npm-install", - "RUN npm install @openedx/sample-plugin", + "RUN npm install @openedx/plugin-sample", )) # Step 2: Import the CourseList component in the MFE env config so it is @@ -72,7 +77,7 @@ # into the generated env.config.jsx file. hooks.Filters.ENV_PATCHES.add_item(( "mfe-env-config-buildtime-imports", - "import { CourseList } from '@openedx/sample-plugin';", + "import { CourseList } from '@openedx/plugin-sample';", )) # Step 3: Configure the course list plugin slot. @@ -93,10 +98,48 @@ { op: PLUGIN_OPERATIONS.Insert, widget: { - id: 'sample_plugin_course_list', + id: 'openedx_plugin_sample_course_list', type: DIRECT_PLUGIN, priority: 50, RenderWidget: CourseList, }, }""", )) + +# --------------------------------------------------------------------------- +# Brand: Override Paragon theme CSS with brand-sample +# --------------------------------------------------------------------------- +# Open edX MFEs load their Paragon design-system CSS at runtime via the +# PARAGON_THEME_URLS setting. The "default" URL points at upstream Paragon; +# the optional "brandOverride" URL is loaded on top to recolor/restyle the +# theme. We point brandOverride at the compiled CSS in this repo's +# brand-sample/ directory, served via jsDelivr straight from GitHub. +# +# TODO: This assumes brand-sample has been pushed to jsdelivr. +# Is it possible to set this up for dev so that it loads the brand from +# the filesystem instead? +# ANSWER: Yes, it is possible using the tutor-contrib-paragon plugin. +# We should update this section to use that. +# --------------------------------------------------------------------------- + +paragon_theme_urls = { + "variants": { + "light": { + "urls": { + "default": "https://cdn.jsdelivr.net/npm/@openedx/paragon@$paragonVersion/dist/light.min.css", + "brandOverride": "https://cdn.jsdelivr.net/gh/openedx/sample-plugin@main/brand-sample/dist/light.min.css" + } + } + } +} + +brand_settings_lines = f""" +MFE_CONFIG["PARAGON_THEME_URLS"] = {json.dumps(paragon_theme_urls)} +""" + +hooks.Filters.ENV_PATCHES.add_item( + ( + "mfe-lms-common-settings", + brand_settings_lines, + ) +)