Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
43 changes: 42 additions & 1 deletion geonode_mapstore_client/admin.py
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')
66 changes: 66 additions & 0 deletions geonode_mapstore_client/migrations/0005_extension.py
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",),
},
),
]
39 changes: 38 additions & 1 deletion geonode_mapstore_client/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from django.dispatch import receiver
from django.db.models import signals
from django.core.cache import caches

from django.db import models
from geonode_mapstore_client.utils import validate_zip_file
from geonode_mapstore_client.templatetags.get_search_services import (
populate_search_service_options,
)
Expand Down Expand Up @@ -73,3 +74,39 @@ def post_save_search_service(instance, sender, created, **kwargs):
services_cache.delete("search_services")

services_cache.set("search_services", populate_search_service_options(), 300)



def extension_upload_path(instance, filename):
return f'mapstore_extensions/{filename}'

class Extension(models.Model):
name = models.CharField(
max_length=255,
unique=True,
blank=True, # Will be populated from the zip filename
help_text="Name of the extension, derived from the zip file name. Must be unique."
)
uploaded_file = models.FileField(
upload_to=extension_upload_path,
validators=[validate_zip_file],
help_text="Upload the MapStore extension as a zip folder."
)
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)

def __str__(self):
return self.name

class Meta:
ordering = ('name',)
verbose_name = "MapStore Extension"
verbose_name_plural = "MapStore Extensions"
19 changes: 18 additions & 1 deletion geonode_mapstore_client/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import json

import zipfile
from django.core.exceptions import ValidationError
from geoserver.catalog import FailedRequestError
from geonode.geoserver.helpers import gs_catalog
from geonode.layers.models import Dataset
Expand All @@ -25,3 +26,19 @@ def set_default_style_to_open_in_visual_mode(instance, **kwargs):
style.name, resp.status_code, resp.text
)
)


def validate_zip_file(file):
"""
Validates that the uploaded file is a zip and contains the required structure.
"""
if not zipfile.is_zipfile(file):
raise ValidationError("File is not a valid zip archive.")

file.seek(0)
with zipfile.ZipFile(file, 'r') as zip_ref:
filenames = zip_ref.namelist()
required_files = ['index.js', 'index.json']
if not all(f in filenames for f in required_files):
raise ValidationError("The zip file must contain index.js and index.json at its root.")
file.seek(0)