Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Each directory contains detailed README.md files with adaptation guidance.
```
sample-plugin/
├── README.md # This file - overview and quick start
├── backend/
├── backend/
│ ├── README.md # Backend plugin detailed guide
│ ├── sample_plugin/
│ │ ├── apps.py # Django plugin configuration
Expand All @@ -133,7 +133,7 @@ sample-plugin/
### Backend Plugin Development

1. **Setup**: Follow backend setup in [Quick Start](#quick-start-guide)
2. **Development**:
2. **Development**:
- Modify models in `models.py`
- Add API endpoints in `views.py`
- Implement event handlers in `signals.py`
Expand Down Expand Up @@ -187,7 +187,7 @@ const response = await client.get(
def log_course_info_changed(signal, sender, catalog_info, **kwargs):
logging.info(f"{catalog_info.course_key} has been updated!")

# Filters: Modify course about URLs
# Filters: Modify course about URLs
class ChangeCourseAboutPageUrl(PipelineStep):
def run_filter(self, url, org, **kwargs):
# Custom URL logic
Expand All @@ -198,7 +198,7 @@ class ChangeCourseAboutPageUrl(PipelineStep):
### Common Issues

**Plugin not loading:**
- Verify `setup.py` entry points are correct
- Verify `pyproject.toml` entry points are correct
- Check that plugin app is in INSTALLED_APPS (should be automatic)
- Review Django app plugin configuration in `apps.py`

Expand Down Expand Up @@ -242,4 +242,4 @@ class ChangeCourseAboutPageUrl(PipelineStep):
- **Working Integration**: Complete example showing all plugin types working together
- **Real Business Logic**: Realistic course archiving functionality vs. hello-world examples
- **Development Workflow**: End-to-end development and testing process
- **Troubleshooting**: Common plugin development issues and solutions
- **Troubleshooting**: Common plugin development issues and solutions
2 changes: 1 addition & 1 deletion backend/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ compile-requirements: export CUSTOM_COMPILE_COMMAND=make upgrade
compile-requirements: piptools ## compile the requirements/*.txt files with the latest packages satisfying requirements/*.in
pip-compile -v ${PIP_COMPILE_OPTS} --allow-unsafe --rebuild -o requirements/pip-tools.txt requirements/pip-tools.in
pip install -qr requirements/pip-tools.txt
$(PIP_COMPILE) -o requirements/base.txt requirements/base.in
$(PIP_COMPILE) -c requirements/constraints.txt -o requirements/base.txt requirements/base.in
$(PIP_COMPILE) -o requirements/test.txt requirements/test.in
$(PIP_COMPILE) -o requirements/doc.txt requirements/doc.in
$(PIP_COMPILE) -o requirements/quality.txt requirements/quality.in
Expand Down
47 changes: 22 additions & 25 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class SamplePluginConfig(AppConfig):
"common": {PluginURLs.RELATIVE_PATH: "settings.common"},
"production": {PluginURLs.RELATIVE_PATH: "settings.production"},
},
# ... CMS configuration
# ... CMS configuration
}
}
```
Expand All @@ -74,24 +74,21 @@ class SamplePluginConfig(AppConfig):

### Entry Points Configuration

In [`setup.py`](./setup.py), the plugin registers itself with edx-platform:
In [`pyproject.toml`](./backend/pyproject.toml), the plugin registers itself with edx-platform:

```python
entry_points={
"lms.djangoapp": [
"sample_plugin = sample_plugin.apps:SamplePluginConfig",
],
"cms.djangoapp": [
"sample_plugin = sample_plugin.apps:SamplePluginConfig",
],
}
[project.entry-points."lms.djangoapp"]
sample_plugin = "sample_plugin.apps:SamplePluginConfig"

[project.entry-points."cms.djangoapp"]
sample_plugin = "sample_plugin.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**: [`sample_plugin/models.py`](./sample_plugin/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
Expand Down Expand Up @@ -133,7 +130,7 @@ The model includes PII documentation:

## API Endpoints

**File**: [`sample_plugin/views.py`](./sample_plugin/views.py)
**File**: [`sample_plugin/views.py`](./sample_plugin/views.py)
**URLs**: [`sample_plugin/urls.py`](./sample_plugin/urls.py)

### REST API Implementation
Expand Down Expand Up @@ -182,7 +179,7 @@ def perform_create(self, serializer):

## Events & Signals

**File**: [`sample_plugin/signals.py`](./sample_plugin/signals.py)
**File**: [`sample_plugin/signals.py`](./sample_plugin/signals.py)
**Official Docs**: [Open edX Events Guide](https://docs.openedx.org/projects/openedx-events/en/latest/)

### Event Handler Example
Expand Down Expand Up @@ -241,7 +238,7 @@ def ready(self):

## Filters & Pipeline Steps

**File**: [`sample_plugin/pipeline.py`](./sample_plugin/pipeline.py)
**File**: [`sample_plugin/pipeline.py`](./sample_plugin/pipeline.py)
**Official Docs**: [Using Open edX Filters](https://docs.openedx.org/projects/openedx-filters/en/latest/how-tos/using-filters.html)

### Filter Implementation
Expand All @@ -254,12 +251,12 @@ class ChangeCourseAboutPageUrl(PipelineStep):
# Extract course ID from URL
pattern = r'(?P<course_id>course-v1:[^/]+)'
match = re.search(pattern, url)

if match:
course_id = match.group('course_id')
new_url = f"https://example.com/new_about_page/{course_id}"
return {"url": new_url, "org": org}

# Return original data if no match
return {"url": url, "org": org}
```
Expand All @@ -278,7 +275,7 @@ class ChangeCourseAboutPageUrl(PipelineStep):

**Common Filters:**
- Course enrollment filters
- Authentication filters
- Authentication filters
- Certificate generation filters
- Course discovery filters

Expand All @@ -289,7 +286,7 @@ Filters must be registered in Django settings. This happens automatically via th
### Real-World Use Cases

- **URL Redirection**: Send users to custom course pages
- **Access Control**: Implement custom enrollment restrictions
- **Access Control**: Implement custom enrollment restrictions
- **Data Transformation**: Modify course data before display
- **Integration**: Add custom fields to API responses

Expand Down Expand Up @@ -380,7 +377,7 @@ python manage.py cms migrate

### Verification Steps

1. **Check Installation**:
1. **Check Installation**:
```bash
python manage.py lms shell
>>> from sample_plugin.models import CourseArchiveStatus
Expand Down Expand Up @@ -439,7 +436,7 @@ from django.contrib.auth import get_user_model
class TestCourseArchiveStatusAPI(APITestCase):
def setUp(self):
self.user = get_user_model().objects.create_user(username="testuser")

def test_list_archive_statuses(self):
# Test API endpoints
pass
Expand Down Expand Up @@ -492,7 +489,7 @@ Settings configure filter behavior:
# settings/common.py
def plugin_settings(settings):
settings.SAMPLE_PLUGIN_REDIRECT_DOMAIN = "custom-domain.com"

# pipeline.py - Uses setting
class ChangeCourseAboutPageUrl(PipelineStep):
def run_filter(self, url, org, **kwargs):
Expand All @@ -506,15 +503,15 @@ 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)
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/)

### Plugin Development Checklist

- [ ] Update `setup.py` with your plugin name and dependencies
- [ ] Modify `apps.py` with your app configuration
- [ ] Update `pyproject.toml` with your plugin name and dependencies
- [ ] Modify `apps.py` with your app configuration
- [ ] Design your models in `models.py`
- [ ] Create and run database migrations
- [ ] Implement API endpoints in `views.py`
Expand Down Expand Up @@ -551,4 +548,4 @@ def handle_your_event(signal, sender, event_data, **kwargs):
pass
```

This backend plugin provides a solid foundation for any Open edX extension. Focus on adapting the business logic while keeping the proven patterns for authentication, permissions, and integration.
This backend plugin provides a solid foundation for any Open edX extension. Focus on adapting the business logic while keeping the proven patterns for authentication, permissions, and integration.
46 changes: 46 additions & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "openedx-sample-plugin"
description = "A sample backend plugin for the Open edX Platform"
requires-python = ">=3.11"
license="Apache-2.0"
authors = [
{name = "Open edX Project", email = "[email protected]"},
]
classifiers = [
'Development Status :: 3 - Alpha',
'Framework :: Django',
'Framework :: Django :: 4.2',
'Intended Audience :: Developers',
'Natural Language :: English',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.12',
]
keywords= [
"Python",
"edx",
]

dynamic = ["version", "readme", "dependencies"]

[project.entry-points."lms.djangoapp"]
sample_plugin = "sample_plugin.apps:SamplePluginConfig"

[project.entry-points."cms.djangoapp"]
sample_plugin = "sample_plugin.apps:SamplePluginConfig"

[project.urls]
Homepage = "https://openedx.org/openedx/sample-plugin"
Repository = "https://openedx.org/openedx/sample-plugin"

[tool.setuptools.dynamic]
version = {attr = "sample_plugin.__version__"}
readme = {file = ["README.rst", "CHANGELOG.rst"]}
dependencies = {file = "requirements/base.in"}

[tool.setuptools.packages.find]
include = ["sample_plugin*"]
exclude = ["sample_plugin.tests*"]
4 changes: 1 addition & 3 deletions backend/requirements/base.in
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
# Core requirements for using this application
-c constraints.txt

Django # Web application framework
djangorestframework # REST API framework
django-filter # Filtering for DRF
edx-opaque-keys # Open edX CourseKeyField
openedx-events # Be able to listen to openedx events and respond to them.
openedx-filters # Be able to hook into openedx filters and change behavior.

openedx-atlas
openedx-atlas # For translations related tooling
11 changes: 6 additions & 5 deletions backend/sample_plugin/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ class SamplePluginConfig(AppConfig):
- Add custom business logic

Entry Point Configuration:
This plugin is registered in setup.py as::
This plugin is registered in pyproject.toml as::

entry_points={
"lms.djangoapp": ["sample_plugin = sample_plugin.apps:SamplePluginConfig"],
"cms.djangoapp": ["sample_plugin = sample_plugin.apps:SamplePluginConfig"],
}
[project.entry-points."lms.djangoapp"]
sample_plugin = "sample_plugin.apps:SamplePluginConfig"

[project.entry-points."cms.djangoapp"]
sample_plugin = "sample_plugin.apps:SamplePluginConfig"

The platform automatically discovers and loads plugins registered in these entry points.
""" # pylint: disable=line-too-long # noqa: E501
Expand Down
Loading