-
Notifications
You must be signed in to change notification settings - Fork 125
feat: extension model and form validation #2176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
1856105
c34562b
9c20234
c856097
b6ef418
96ffd67
be2c6f2
fe58e6d
c588fb0
b9369df
0ba87b7
e11ab64
62033f4
aafac52
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,48 @@ | ||
| import os | ||
| from django import forms | ||
| from django.contrib import admin | ||
| from geonode_mapstore_client.models import SearchService | ||
| from geonode_mapstore_client.models import SearchService, Extension | ||
|
|
||
|
|
||
| @admin.register(SearchService) | ||
| class SearchServiceAdmin(admin.ModelAdmin): | ||
| pass | ||
|
|
||
|
|
||
| class ExtensionAdminForm(forms.ModelForm): | ||
| class Meta: | ||
| model = Extension | ||
| fields = '__all__' | ||
|
|
||
| def clean_uploaded_file(self): | ||
| """ | ||
| It checks the uploaded file's name for uniqueness before the model is saved. | ||
| """ | ||
| uploaded_file = self.cleaned_data.get('uploaded_file') | ||
|
|
||
| if uploaded_file: | ||
| extension_name = os.path.splitext(os.path.basename(uploaded_file.name))[0] | ||
|
|
||
| queryset = Extension.objects.filter(name=extension_name) | ||
|
|
||
| # If we are updating an existing instance, we can exclude it from the check | ||
| if self.instance.pk: | ||
| queryset = queryset.exclude(pk=self.instance.pk) | ||
|
|
||
| # If the queryset finds any conflicting extension, raise a validation error | ||
| if queryset.exists(): | ||
| raise forms.ValidationError( | ||
| f"An extension with the name '{extension_name}' already exists. Please upload a file with a different name." | ||
| ) | ||
|
|
||
| return uploaded_file | ||
|
|
||
|
|
||
| @admin.register(Extension) | ||
| class ExtensionAdmin(admin.ModelAdmin): | ||
|
|
||
| form = ExtensionAdminForm | ||
| list_display = ('name', 'active', 'is_map_extension', 'updated_at') | ||
| list_filter = ('active', 'is_map_extension') | ||
| search_fields = ('name',) | ||
| readonly_fields = ('name', 'created_at', 'updated_at') |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,6 +77,9 @@ def run_setup_hooks(*args, **kwargs): | |
| pass | ||
|
|
||
| urlpatterns += [ | ||
| re_path("/client/extension", views.ExtensionsView.as_view(), name="mapstore-extension"), | ||
nrjadkry marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| re_path("/client/pluginsconfig", views.PluginsConfigView.as_view(), name="mapstore-pluginsconfig"), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tested
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’m currently using this path for the static plugins. Is this different from what it should be, @allyoucanmap?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| re_path( | ||
| r"^catalogue/", | ||
| TemplateView.as_view( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| # Generated by Django 4.2.23 on 2025-10-03 12:14 | ||
|
|
||
| from django.db import migrations, models | ||
| import geonode_mapstore_client.models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("geonode_mapstore_client", "0004_auto_20231114_1705"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.CreateModel( | ||
| name="Extension", | ||
| fields=[ | ||
| ( | ||
| "id", | ||
| models.AutoField( | ||
| auto_created=True, | ||
| primary_key=True, | ||
| serialize=False, | ||
| verbose_name="ID", | ||
| ), | ||
| ), | ||
| ( | ||
| "name", | ||
| models.CharField( | ||
| blank=True, | ||
| help_text="Name of the extension, derived from the zip file name. Must be unique.", | ||
| max_length=255, | ||
| unique=True, | ||
| ), | ||
| ), | ||
| ( | ||
| "uploaded_file", | ||
| models.FileField( | ||
| help_text="Upload the MapStore extension as a zip folder.", | ||
| upload_to=geonode_mapstore_client.models.extension_upload_path, | ||
| validators=[geonode_mapstore_client.models.validate_zip_file], | ||
| ), | ||
| ), | ||
| ( | ||
| "active", | ||
| models.BooleanField( | ||
| default=True, | ||
| help_text="Whether the extension is active and should be included in the index.", | ||
| ), | ||
| ), | ||
| ( | ||
| "is_map_extension", | ||
| models.BooleanField( | ||
| default=False, | ||
| help_text="Check if this extension is a map-specific plugin for Map Viewers.", | ||
| ), | ||
| ), | ||
| ("created_at", models.DateTimeField(auto_now_add=True)), | ||
| ("updated_at", models.DateTimeField(auto_now=True)), | ||
| ], | ||
| options={ | ||
| "verbose_name": "MapStore Extension", | ||
| "verbose_name_plural": "MapStore Extensions", | ||
| "ordering": ("name",), | ||
| }, | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,15 @@ | ||
| import os | ||
| import json | ||
| from rest_framework.views import APIView | ||
| from django.shortcuts import render | ||
| from django.http import Http404 | ||
| from django.utils.translation.trans_real import get_language_from_request | ||
| from dateutil import parser | ||
| from django.conf import settings | ||
| from django.templatetags.static import static | ||
| from rest_framework.response import Response | ||
| from django.core.cache import cache | ||
|
|
||
|
|
||
| def _parse_value(value, schema): | ||
| schema_type = schema.get('type') | ||
|
|
@@ -70,3 +78,98 @@ def metadata(request, pk, template="geonode-mapstore-client/metadata.html"): | |
|
|
||
| def metadata_embed(request, pk): | ||
| return metadata(request, pk, template="geonode-mapstore-client/metadata_embed.html") | ||
|
|
||
|
|
||
|
|
||
| class ExtensionsView(APIView): | ||
| permission_classes = [] | ||
|
|
||
| def get(self, request, *args, **kwargs): | ||
| from geonode_mapstore_client.models import Extension | ||
| from geonode_mapstore_client.utils import ( | ||
giohappy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| MAPSTORE_EXTENSIONS_CACHE_KEY, | ||
| MAPSTORE_EXTENSION_CACHE_TIMEOUT, | ||
| ) | ||
|
|
||
| cached_data = cache.get(MAPSTORE_EXTENSIONS_CACHE_KEY) | ||
| if cached_data: | ||
| return Response(cached_data) | ||
|
|
||
| final_extensions = {} | ||
| legacy_file_path = os.path.join( | ||
| settings.STATIC_ROOT, "geonode", "js", "extensions.json" | ||
| ) | ||
|
|
||
| try: | ||
| with open(legacy_file_path, "r") as f: | ||
| final_extensions = json.load(f) | ||
| except (FileNotFoundError, json.JSONDecodeError): | ||
| pass | ||
|
|
||
| active_extensions = Extension.objects.filter(active=True) | ||
| dynamic_extensions = {} | ||
| for ext in active_extensions: | ||
| dynamic_extensions[ext.name] = { | ||
| "bundle": static(f"extensions/{ext.name}/index.js"), | ||
| "translations": static(f"extensions/{ext.name}/translations"), | ||
| "assets": static(f"extensions/{ext.name}/assets"), | ||
|
||
| } | ||
|
|
||
| final_extensions.update(dynamic_extensions) | ||
|
|
||
| cache.set( | ||
| MAPSTORE_EXTENSIONS_CACHE_KEY, | ||
| final_extensions, | ||
| timeout=MAPSTORE_EXTENSION_CACHE_TIMEOUT, | ||
| ) | ||
|
|
||
| return Response(final_extensions) | ||
|
|
||
|
|
||
| class PluginsConfigView(APIView): | ||
| permission_classes = [] | ||
|
|
||
| def get(self, request, *args, **kwargs): | ||
| from geonode_mapstore_client.models import Extension | ||
| from geonode_mapstore_client.utils import ( | ||
| MAPSTORE_PLUGINS_CACHE_KEY, | ||
| MAPSTORE_EXTENSION_CACHE_TIMEOUT, | ||
| ) | ||
|
|
||
| cached_data = cache.get(MAPSTORE_PLUGINS_CACHE_KEY) | ||
| if cached_data: | ||
| return Response(cached_data) | ||
|
|
||
| base_config_path = os.path.join( | ||
| settings.PROJECT_ROOT, 'static', 'mapstore', 'configs', 'pluginsConfig.json' | ||
| ) | ||
|
|
||
| config_data = {"plugins": []} | ||
|
|
||
| try: | ||
| with open(base_config_path, 'r') as f: | ||
| config_data = json.load(f) | ||
| except (FileNotFoundError, json.JSONDecodeError): | ||
| pass | ||
|
|
||
| plugins = config_data.get("plugins", []) | ||
| existing_plugin_names = {p.get("name") for p in plugins if isinstance(p, dict)} | ||
|
|
||
| map_extensions = Extension.objects.filter(active=True, is_map_extension=True) | ||
|
|
||
| for ext in map_extensions: | ||
| if ext.name not in existing_plugin_names: | ||
| plugins.append({ | ||
| "name": ext.name, | ||
| "bundle": static(f'extensions/{ext.name}/index.js'), | ||
| "translations": static(f'extensions/{ext.name}/translations'), | ||
| "assets": static(f'extensions/{ext.name}/assets'), | ||
| }) | ||
|
|
||
| cache.set( | ||
| MAPSTORE_PLUGINS_CACHE_KEY, | ||
| config_data, | ||
| timeout=MAPSTORE_EXTENSION_CACHE_TIMEOUT, | ||
| ) | ||
|
|
||
| return Response({"plugins": plugins}) | ||



Uh oh!
There was an error while loading. Please reload this page.