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
5 changes: 5 additions & 0 deletions .changeset/0000-django60-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
type: minor
---

Add Django 6.0 compatibility. Django 6.0 removed `id` and `name` from template context in `BaseGeometryWidget.get_context()`. Re-inject these in `BasePointFieldInteractiveWidget.get_context()` for all interactive widgets (Google Maps, Leaflet, Mapbox). Also lift `js_widget_data` and `is_formset_empty_form_template` attrs to top-level context in `PointFieldInlineWidgetMixin.get_context()` for inline formset widgets.
45 changes: 45 additions & 0 deletions .claude/commands/add-widget.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# /add-widget — Add a new map widget

Scaffold a new map widget across all required files (Python, JS, CSS, templates, docs).

## What it does
Interactively prompts for widget details, then generates:
1. **Python widget class** in `mapwidgets/widgets/<provider>.py`
- Extends `BasePointFieldInteractiveWidget` or `BaseStaticWidget`
- Registers in widget's `__init__.py` and main `__init__.py`

2. **Settings** in `mapwidgets/settings.py`
- Adds provider config to `DEFAULT_SETTINGS`
- Includes apiKey, mapOptions, media paths

3. **JavaScript** in `mapwidgets/static/mapwidgets/js/pointfield/interactive/<provider>/`
- Creates `mw_pointfield.js` (unminified)
- Runs `/minify` to generate `.min.js`

4. **CSS** in `mapwidgets/static/mapwidgets/css/` (if needed)

5. **HTML template** in `mapwidgets/templates/mapwidgets/`
- Django form widget template

6. **Documentation** stub in `docs/widgets/`
- Includes provider name, installation, configuration, usage examples

## Usage
```
/add-widget
```

Prompts you for:
- **Provider name** (e.g., "Google Maps", "Mapbox", "Leaflet")
- **Widget types** to create (interactive, static, inline formsets)
- **API/auth requirements** (apiKey, token, etc.)

## Follow-up
After running, you should:
- Update widget JS logic for your specific provider
- Test widget rendering in demo project
- Run `/minify` if JS was modified
- Commit changes with meaningful message

## Reference
See `docs/contribution/index.rst` for the full new widget checklist.
20 changes: 20 additions & 0 deletions .claude/commands/minify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# /minify — Minify JS & CSS assets

Minify all JavaScript and CSS files in the mapwidgets static assets using the build script.

## What it does
- Runs `python scripts.py` to minify JS files in `mapwidgets/static/mapwidgets/js/`
- Minifies CSS in `mapwidgets/static/mapwidgets/css/`
- Generates `.min.js` and `.min.css` files
- Verifies all minified assets were created successfully

## Usage
```
/minify
```

## When to use
- After creating a new widget JS file
- After modifying any `.js` or `.css` files in `mapwidgets/static/`
- Before committing changes to static assets
- As part of the release process
37 changes: 37 additions & 0 deletions .claude/commands/release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# /release — Cut a new release

Automate the release process for PyPI and GitHub.

## What it does
1. **Bump version** in `setup.py` (prompts for semver: patch/minor/major)
2. **Create changeset** in `.changeset/` directory
- Auto-generates filename `0000-<type>-release.md` with semver and changelog
3. **Update docs** in `docs/releases/` with release notes
4. **Git operations**
- Stage changes
- Commit with message: "release: v{version}"
- Create git tag: `v{version}`
- Push to remote
5. **Create GitHub Release**
- Creates release on GitHub with changelog from changeset
- Marks as latest if on main branch

## Usage
```
/release
```

Prompts you for:
- **Semver type**: patch, minor, or major
- **Release notes**: changes, fixes, new features

## Reference
- Current version in `setup.py`
- Changelog format follows `.changeset/` convention
- Requires `gh` CLI for GitHub operations
- Public repo: `erdem/django-map-widgets`

## Notes
- Will not push if CI checks haven't passed
- Always creates a new commit (never amends)
- Version string synced with `pyproject.toml` if needed
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: "3.12"

Expand All @@ -27,14 +27,14 @@ jobs:
- name: Install Poetry
run: |
python -m pip install --upgrade pip
pip install poetry
pip install --upgrade poetry

- name: Configure Poetry
run: |
poetry config virtualenvs.in-project true

- name: Cache Poetry virtualenv
uses: actions/cache@v2
uses: actions/cache@v4
id: cache
with:
path: .venv
Expand All @@ -47,7 +47,7 @@ jobs:
run: poetry install

- name: Validate Poetry lock file
run: poetry lock --check
run: poetry check --lock

- name: Run isort
run: poetry run isort .
Expand Down
2 changes: 1 addition & 1 deletion mapwidgets/static/mapwidgets/css/map_widgets.css
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
text-decoration: none;
text-decoration: none !important;
}

.mw-btn:focus,
Expand Down
1 change: 0 additions & 1 deletion mapwidgets/static/mapwidgets/css/map_widgets.min.css
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
@font-face{font-family:'fontello';src:url('../font/fontello.eot?93477804');src:url('../font/fontello.eot?93477804#iefix') format('embedded-opentype'),url('../font/fontello.woff2?93477804') format('woff2'),url('../font/fontello.woff?93477804') format('woff'),url('../font/fontello.ttf?93477804') format('truetype'),url('../font/fontello.svg?93477804#fontello') format('svg');font-weight:normal;font-style:normal}.mw-wrap input:focus{outline:0}.mw-wrap [class^="icon-"]:before,.mw-wrap [class*=" icon-"]:before{font-family:"fontello";font-style:normal;font-weight:normal;speak:never;display:inline-block;text-decoration:inherit;width:1em;margin-right:.2em;text-align:center;font-variant:normal;text-transform:none;line-height:1em;margin-left:.2em;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.mw-wrap .icon-location:before{content:'\e800'}.mw-wrap .icon-down-dir:before{content:'\e801'}.mw-wrap .icon-trash-empty:before{content:'\e802'}.mw-wrap .icon-down-open:before{content:'\e803'}.mw-wrap .icon-up-open:before{content:'\e804'}.mw-wrap .icon-up-dir:before{content:'\e805'}.mw-wrap .icon-direction:before{content:'\f124'}.mw-wrap{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;width:80%;min-width:500px;margin-bottom:10px;display:block;overflow:hidden}.tabular td .mw-wrap{width:100%}.mw-wrap .form-control,.mw-wrap .mapboxgl-ctrl-geocoder--input{border:1px solid #ccc;font-family:inherit;margin:0;box-sizing:border-box;display:block;width:100%;height:33px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}@media screen and (min-width:640px){.mw-wrap .mapboxgl-ctrl-geocoder{width:100%;max-width:100%}}@media screen and (max-width:1024px){.mw-wrap .button-text{display:none}}.mw-wrap .mapboxgl-ctrl-geocoder--icon{display:none}.mw-wrap .mapboxgl-ctrl-geocoder{z-index:10}.mw-wrap .form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.mw-wrap *{border-radius:0 !important}.mw-header{margin-bottom:6px}.mw-header .mw-adress-input-wrap{width:35%}.mw-header .mw-coordinates-wrap{position:relative;display:inline-block}.mw-coordinates-overlay{width:180px;background:#e7e7e7;margin-top:3px;position:absolute;z-index:10000;padding:10px;-webkit-box-shadow:0 6px 20px rgba(0,0,0,.325);box-shadow:0 6px 20px rgba(0,0,0,.325)}.mw-coordinates-overlay label{float:none;width:100%;height:60px;font-weight:500;color:#36454f;overflow:hidden}.mw-coordinates-overlay input.form-control.error{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.mw-coordinates-overlay input.error:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.mw-header .mw-coordinates-container label{cursor:pointer}.mw-map{height:360px;display:block}.mw-footer{padding:10px 0}.mw-map-wrapper{height:360px;position:relative}.mw-loader{background:url("../images/ripple.gif") no-repeat center center;background-size:cover;width:64px;height:64px;position:absolute;left:50%;top:50%;margin:-32px 0 0 -32px}.mw-loader-overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:99999;background:rgba(255,255,255,.7);text-align:center}.mw-map.click,.mw-map.click div{cursor:crosshair}.mw-map.click div.gmnoprint div{cursor:pointer}.mw-help-text.help-text{font-size:11px;color:#5c5c5c}.mw-btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-decoration:none}.mw-btn:focus,.mw-btn:active:focus,.mw-btn.active:focus,.mw-btn.focus,.mw-btn:active.focus,.mw-btn.active.focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.mw-btn:hover,.mw-btn:focus,.mw-btn.focus{color:#333;text-decoration:none}.mw-btn:active,.mw-btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.mw-btn.disabled,.mw-btn[disabled],fieldset[disabled] .mw-btn{cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}a.mw-btn.disabled,fieldset[disabled] a.mw-btn{pointer-events:none}.mw-btn-default{color:#333;background-color:#f8f9fa;border-color:#ccc;text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.mw-btn-default:focus,.mw-btn-default.focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.mw-btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.mw-btn-default:active,.mw-btn-default.active,.open>.dropdown-toggle.mw-btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.mw-btn-default:active:hover,.mw-btn-default.active:hover,.open>.dropdown-toggle.mw-btn-default:hover,.mw-btn-default:active:focus,.mw-btn-default.active:focus,.open>.dropdown-toggle.mw-btn-default:focus,.mw-btn-default:active.focus,.mw-btn-default.active.focus,.open>.dropdown-toggle.mw-btn-default.focus{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.mw-btn-default:active,.mw-btn-default.active,.open>.dropdown-toggle.mw-btn-default{background-image:none}.mw-btn-default.disabled:hover,.mw-btn-default[disabled]:hover,fieldset[disabled] .mw-btn-default:hover,.mw-btn-default.disabled:focus,.mw-btn-default[disabled]:focus,fieldset[disabled] .mw-btn-default:focus,.mw-btn-default.disabled.focus,.mw-btn-default[disabled].focus,fieldset[disabled] .mw-btn-default.focus{background-color:#fff;border-color:#ccc}.mw-btn-default .badge{color:#fff;background-color:#333}.mw-btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.mw-btn-primary:focus,.mw-btn-primary.focus{color:#fff;background-color:#286090;border-color:#122b40}.mw-btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.mw-btn-primary:active,.mw-btn-primary.active,.open>.dropdown-toggle.mw-btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.mw-btn-primary:active:hover,.mw-btn-primary.active:hover,.open>.dropdown-toggle.mw-btn-primary:hover,.mw-btn-primary:active:focus,.mw-btn-primary.active:focus,.open>.dropdown-toggle.mw-btn-primary:focus,.mw-btn-primary:active.focus,.mw-btn-primary.active.focus,.open>.dropdown-toggle.mw-btn-primary.focus{color:#fff;background-color:#204d74;border-color:#122b40}.mw-btn-primary:active,.mw-btn-primary.active,.open>.dropdown-toggle.mw-btn-primary{background-image:none}.mw-btn-primary.disabled:hover,.mw-btn-primary[disabled]:hover,fieldset[disabled] .mw-btn-primary:hover,.mw-btn-primary.disabled:focus,.mw-btn-primary[disabled]:focus,fieldset[disabled] .mw-btn-primary:focus,.mw-btn-primary.disabled.focus,.mw-btn-primary[disabled].focus,fieldset[disabled] .mw-btn-primary.focus{background-color:#337ab7;border-color:#2e6da4}.mw-btn-primary .badge{color:#337ab7;background-color:#fff}.mw-btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.mw-btn-success:focus,.mw-btn-success.focus{color:#fff;background-color:#449d44;border-color:#255625}.mw-btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.mw-btn-success:active,.mw-btn-success.active,.open>.dropdown-toggle.mw-btn-success{color:#fff;background-color:#449d44;border-color:#398439}.mw-btn-success:active:hover,.mw-btn-success.active:hover,.open>.dropdown-toggle.mw-btn-success:hover,.mw-btn-success:active:focus,.mw-btn-success.active:focus,.open>.dropdown-toggle.mw-btn-success:focus,.mw-btn-success:active.focus,.mw-btn-success.active.focus,.open>.dropdown-toggle.mw-btn-success.focus{color:#fff;background-color:#398439;border-color:#255625}.mw-btn-success:active,.mw-btn-success.active,.open>.dropdown-toggle.mw-btn-success{background-image:none}.mw-btn-success.disabled:hover,.mw-btn-success[disabled]:hover,fieldset[disabled] .mw-btn-success:hover,.mw-btn-success.disabled:focus,.mw-btn-success[disabled]:focus,fieldset[disabled] .mw-btn-success:focus,.mw-btn-success.disabled.focus,.mw-btn-success[disabled].focus,fieldset[disabled] .mw-btn-success.focus{background-color:#5cb85c;border-color:#4cae4c}.mw-btn-success .badge{color:#5cb85c;background-color:#fff}.mw-btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.mw-btn-info:focus,.mw-btn-info.focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.mw-btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.mw-btn-info:active,.mw-btn-info.active,.open>.dropdown-toggle.mw-btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.mw-btn-info:active:hover,.mw-btn-info.active:hover,.open>.dropdown-toggle.mw-btn-info:hover,.mw-btn-info:active:focus,.mw-btn-info.active:focus,.open>.dropdown-toggle.mw-btn-info:focus,.mw-btn-info:active.focus,.mw-btn-info.active.focus,.open>.dropdown-toggle.mw-btn-info.focus{color:#fff;background-color:#269abc;border-color:#1b6d85}.mw-btn-info:active,.mw-btn-info.active,.open>.dropdown-toggle.mw-btn-info{background-image:none}.mw-btn-info.disabled:hover,.mw-btn-info[disabled]:hover,fieldset[disabled] .mw-btn-info:hover,.mw-btn-info.disabled:focus,.mw-btn-info[disabled]:focus,fieldset[disabled] .mw-btn-info:focus,.mw-btn-info.disabled.focus,.mw-btn-info[disabled].focus,fieldset[disabled] .mw-btn-info.focus{background-color:#5bc0de;border-color:#46b8da}.mw-btn-info .badge{color:#5bc0de;background-color:#fff}.mw-btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.mw-btn-warning:focus,.mw-btn-warning.focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.mw-btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.mw-btn-warning:active,.mw-btn-warning.active,.open>.dropdown-toggle.mw-btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.mw-btn-warning:active:hover,.mw-btn-warning.active:hover,.open>.dropdown-toggle.mw-btn-warning:hover,.mw-btn-warning:active:focus,.mw-btn-warning.active:focus,.open>.dropdown-toggle.mw-btn-warning:focus,.mw-btn-warning:active.focus,.mw-btn-warning.active.focus,.open>.dropdown-toggle.mw-btn-warning.focus{color:#fff;background-color:#d58512;border-color:#985f0d}.mw-btn-warning:active,.mw-btn-warning.active,.open>.dropdown-toggle.mw-btn-warning{background-image:none}.mw-btn-warning.disabled:hover,.mw-btn-warning[disabled]:hover,fieldset[disabled] .mw-btn-warning:hover,.mw-btn-warning.disabled:focus,.mw-btn-warning[disabled]:focus,fieldset[disabled] .mw-btn-warning:focus,.mw-btn-warning.disabled.focus,.mw-btn-warning[disabled].focus,fieldset[disabled] .mw-btn-warning.focus{background-color:#f0ad4e;border-color:#eea236}.mw-btn-warning .badge{color:#f0ad4e;background-color:#fff}.mw-btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.mw-btn-danger:focus,.mw-btn-danger.focus{color:#fff;background-color:#c9302c;border-color:#761c19}.mw-btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.mw-btn-danger:active,.mw-btn-danger.active,.open>.dropdown-toggle.mw-btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.mw-btn-danger:active:hover,.mw-btn-danger.active:hover,.open>.dropdown-toggle.mw-btn-danger:hover,.mw-btn-danger:active:focus,.mw-btn-danger.active:focus,.open>.dropdown-toggle.mw-btn-danger:focus,.mw-btn-danger:active.focus,.mw-btn-danger.active.focus,.open>.dropdown-toggle.mw-btn-danger.focus{color:#fff;background-color:#ac2925;border-color:#761c19}.mw-btn-danger:active,.mw-btn-danger.active,.open>.dropdown-toggle.mw-btn-danger{background-image:none}.mw-btn-danger.disabled:hover,.mw-btn-danger[disabled]:hover,fieldset[disabled] .mw-btn-danger:hover,.mw-btn-danger.disabled:focus,.mw-btn-danger[disabled]:focus,fieldset[disabled] .mw-btn-danger:focus,.mw-btn-danger.disabled.focus,.mw-btn-danger[disabled].focus,fieldset[disabled] .mw-btn-danger.focus{background-color:#d9534f;border-color:#d43f3a}.mw-btn-danger .badge{color:#d9534f;background-color:#fff}.mw-btn-link{color:#337ab7;font-weight:normal;border-radius:0}.mw-btn-link,.mw-btn-link:active,.mw-btn-link.active,.mw-btn-link[disabled],fieldset[disabled] .mw-btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.mw-btn-link,.mw-btn-link:hover,.mw-btn-link:focus,.mw-btn-link:active{border-color:transparent}.mw-btn-link:hover,.mw-btn-link:focus{color:#23527c;text-decoration:underline;background-color:transparent}.mw-btn-link[disabled]:hover,fieldset[disabled] .mw-btn-link:hover,.mw-btn-link[disabled]:focus,fieldset[disabled] .mw-btn-link:focus{color:#777;text-decoration:none}.mw-btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.mw-btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.mw-btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.mw-btn-block{display:block;width:100%}.mw-btn-block+.mw-btn-block{margin-top:5px}.mw-btn.active,.mw-btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.mw-btn-warning.active,.mw-btn-warning:active{color:#fff;border-color:#d58512;background:#ec971f none}.map-widget-overlay-link{cursor:-webkit-zoom-in;cursor:-moz-zoom-in;cursor:zoom-in;border-bottom:0;display:inline-block}.clearfix:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}
5 changes: 5 additions & 0 deletions mapwidgets/widgets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ def geos_to_dict(self, geom: GEOSGeometry):

def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
# Django 6.0 removed id and name from context; re-inject them
context["name"] = name
widget_attrs = context.get("widget", {}).get("attrs", {})
context["id"] = widget_attrs.get("id", name)

field_value = context["serialized"]
if field_value:
field_value = self.geos_to_dict(self.deserialize(field_value))
Expand Down
Loading