Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
d460291
adjust layout
KatKatKateryna Aug 5, 2025
17bbedd
formatting
KatKatKateryna Aug 5, 2025
5bfbf0b
create bbox preview
KatKatKateryna Aug 5, 2025
b00e656
better syntax
KatKatKateryna Aug 5, 2025
851489b
load bbox on resource selection
KatKatKateryna Aug 5, 2025
7282155
update resources props
KatKatKateryna Aug 5, 2025
5e87822
add provider subclasses
KatKatKateryna Aug 5, 2025
b1dc2e5
specify provider types
KatKatKateryna Aug 5, 2025
5bbd2e9
set defaults
KatKatKateryna Aug 6, 2025
8dc0683
don't report missing optional fields; type check for list content
KatKatKateryna Aug 6, 2025
d238989
deserialize supported providers on load
KatKatKateryna Aug 6, 2025
c7cfeb7
rearrange functions and bbox
KatKatKateryna Aug 7, 2025
fcb42a3
placeholder functions
KatKatKateryna Aug 7, 2025
81c7501
more UI details
KatKatKateryna Aug 7, 2025
c15c677
default values
KatKatKateryna Aug 7, 2025
2a7fcec
save data in correct formats; write yaml as strings
KatKatKateryna Aug 7, 2025
07f5da8
optimize functions
KatKatKateryna Aug 7, 2025
bbf55c1
list add-delete functionality
KatKatKateryna Aug 7, 2025
642c9f0
UI navigation on Save or Cancel
KatKatKateryna Aug 7, 2025
d19e302
resource data saving and updating
KatKatKateryna Aug 8, 2025
141250f
delete collections
KatKatKateryna Aug 8, 2025
5eb6330
hide resource selection on edit
KatKatKateryna Aug 8, 2025
5968bc4
more UI
KatKatKateryna Aug 8, 2025
fa691df
fix map canvas behavior
KatKatKateryna Aug 8, 2025
e98800d
bbox validator
KatKatKateryna Aug 8, 2025
4ab9db8
more placeholders
KatKatKateryna Aug 8, 2025
b470576
fix lists parsing
KatKatKateryna Aug 8, 2025
09be440
UI rearranged
KatKatKateryna Aug 8, 2025
bc9d22d
fix bbox layout
KatKatKateryna Aug 8, 2025
b1873cc
set UI from data(resources extents)
KatKatKateryna Aug 8, 2025
1e298fb
set all data except links and providers
KatKatKateryna Aug 8, 2025
d843315
fix inline lists
KatKatKateryna Aug 8, 2025
8a5ee40
fix temporal extent field
KatKatKateryna Aug 8, 2025
3d12f1f
ignore None values on Save
KatKatKateryna Aug 8, 2025
58afd05
make visibility optional
KatKatKateryna Aug 8, 2025
54c4249
logging as optional
KatKatKateryna Aug 8, 2025
67c884b
server.templates as optional
KatKatKateryna Aug 8, 2025
169a677
load and save links
KatKatKateryna Aug 8, 2025
1549f38
resource ui validation before save
KatKatKateryna Aug 8, 2025
fe852de
load and save Postgresql resource
KatKatKateryna Aug 8, 2025
0c71f65
update ui
KatKatKateryna Aug 8, 2025
a27b61f
rename clearly
KatKatKateryna Aug 8, 2025
338584f
fix provider saving logic
KatKatKateryna Aug 8, 2025
6b3fd8c
rename btn
KatKatKateryna Aug 8, 2025
d4805aa
validate providers before save
KatKatKateryna Aug 8, 2025
c0b393d
deserialize all supported providers
KatKatKateryna Aug 8, 2025
a8d321f
rearrange files
KatKatKateryna Aug 8, 2025
13b3fc5
ui setter class
KatKatKateryna Aug 8, 2025
5050b1f
add DataSetter from UI; add Resource validator on Save
KatKatKateryna Aug 8, 2025
2cfa4c8
better UI
KatKatKateryna Aug 8, 2025
ef6ef82
delegate methods from Dialog
KatKatKateryna Aug 9, 2025
d5d416e
more methods moved
KatKatKateryna Aug 9, 2025
0eaa13a
fix InlineList
KatKatKateryna Aug 9, 2025
35b9624
bigger indent in YAML
KatKatKateryna Aug 9, 2025
7fec620
arrange imports
KatKatKateryna Aug 9, 2025
391a3a7
remove unused
KatKatKateryna Aug 9, 2025
a5b3436
handle Enum deserialization when its a Union type
KatKatKateryna Aug 9, 2025
d3b0291
make UiSetter non-static
KatKatKateryna Aug 11, 2025
549de07
make DataFromUiSetter non-static
KatKatKateryna Aug 11, 2025
09a5e69
moving provider-specific logic out of configData (via abstract method…
KatKatKateryna Aug 20, 2025
efa9035
make hreflang a Dropdown
KatKatKateryna Aug 20, 2025
af54120
_setup all UI dropdowns in one place
KatKatKateryna Aug 20, 2025
f8d904f
split bbox into 4 inputs
KatKatKateryna Aug 20, 2025
60e8beb
trs as a dropdown
KatKatKateryna Aug 20, 2025
95a5739
_clean up fields on resource load
KatKatKateryna Aug 20, 2025
57d2ee0
validate CRS
KatKatKateryna Aug 21, 2025
440c768
add storage_crs
KatKatKateryna Aug 21, 2025
0552a30
pack and unpack provider data in their own classes; add storage_crs
KatKatKateryna Aug 21, 2025
6d50e0f
provider window always on top
KatKatKateryna Aug 21, 2025
47712f1
provider_crs as array + corresponding widget
KatKatKateryna Aug 21, 2025
2ace6d4
_warning stays on top
KatKatKateryna Aug 21, 2025
4bfc9fe
scrollable warning message on open/save
KatKatKateryna Aug 21, 2025
bbf2ef8
support 2 other providers
KatKatKateryna Aug 21, 2025
a497e50
editing providers
KatKatKateryna Aug 21, 2025
1298b81
_align names; fix crs validation; fix zoom validation
KatKatKateryna Aug 21, 2025
ebc3848
handle no-internet requests
KatKatKateryna Aug 26, 2025
4914bcf
fix '0' value packing
KatKatKateryna Aug 26, 2025
a2bd9a5
set provider names as dropdowns
KatKatKateryna Aug 26, 2025
6b2f0a4
save read-only providers to a separate widget
KatKatKateryna Aug 26, 2025
7496b84
keep provider CRS optional
KatKatKateryna Aug 26, 2025
1457e7d
properly delete providers on Delete Selected
KatKatKateryna Aug 26, 2025
fd64e7a
reorder read-only providers to the end of list; enable scroll for rea…
KatKatKateryna Aug 26, 2025
f98c714
add ui folder to zip build
KatKatKateryna Aug 26, 2025
87a8857
add CRS verification for Provider.CRS list
KatKatKateryna Aug 26, 2025
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
376 changes: 78 additions & 298 deletions models/ConfigData.py

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions models/top_level/LoggingConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class LoggingLevel(Enum):

# data classes
@dataclass(kw_only=True)
class RotationConfig:
class LoggingRotationConfig:
# Not currently used in the UI
mode: str | None = None
when: str | None = None
Expand All @@ -29,10 +29,10 @@ class LoggingConfig:

# fields with default values:
level: LoggingLevel = field(default_factory=lambda: LoggingLevel.ERROR)
logfile: str = field(default="")

# optional fields:
logfile: str | None = None
logformat: str | None = None
dateformat: str | None = None
# TODO: Not currently used in the UI
# rotation: RotationConfig | None = None
# rotation: LoggingRotationConfig | None = None
36 changes: 23 additions & 13 deletions models/top_level/MetadataConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@


# records
class KeywordType(Enum):
class MetadataKeywordTypeEnum(Enum):
DISCIPLINE = "discipline"
TEMPORAL = "temporal"
PLACE = "place"
THEME = "theme"
STRATUM = "stratum"


class Role(Enum):
class MetadataRoleEnum(Enum):
AUTHOR = "author"
COAUTHOR = "coAuthor"
COLLABORATOR = "collaborator"
Expand All @@ -37,31 +37,33 @@ class Role(Enum):

# data classes
@dataclass(kw_only=True)
class IdentificationConfig:
class MetadataIdentificationConfig:
title: str | dict = field(default_factory=lambda: "")
description: str | dict = field(default_factory=lambda: "")
keywords: list | dict = field(default_factory=lambda: [])
keywords_type: KeywordType = field(default_factory=lambda: KeywordType.THEME)
keywords_type: MetadataKeywordTypeEnum = field(
default_factory=lambda: MetadataKeywordTypeEnum.THEME
)
terms_of_service: str = field(
default="https://creativecommons.org/licenses/by/4.0/"
)
url: str = field(default="https://example.org")


@dataclass(kw_only=True)
class LicenseConfig:
class MetadataLicenseConfig:
name: str = field(default="CC-BY 4.0 license")
url: str = field(default="https://creativecommons.org/licenses/by/4.0/")


@dataclass(kw_only=True)
class ProviderConfig:
class MetadataProviderConfig:
name: str = field(default="Organization Name")
url: str = field(default="https://pygeoapi.io")


@dataclass(kw_only=True)
class ContactConfig:
class MetadataContactConfig:
name: str = field(default="Lastname, Firstname")
position: str = field(default="Position Title")
address: str = field(default="Mailing Address")
Expand All @@ -75,19 +77,27 @@ class ContactConfig:
url: str = field(default="Contact URL")
hours: str = field(default="Mo-Fr 08:00-17:00")
instructions: str = field(default="During hours of service. Off on weekends.")
role: Role = field(default_factory=lambda: Role.POINTOFCONTACT)
role: MetadataRoleEnum = field(
default_factory=lambda: MetadataRoleEnum.POINTOFCONTACT
)


@dataclass(kw_only=True)
class MetadataConfig:
"""Placeholder class for Metadata configuration data."""

identification: IdentificationConfig = field(
default_factory=lambda: IdentificationConfig()
identification: MetadataIdentificationConfig = field(
default_factory=lambda: MetadataIdentificationConfig()
)
license: MetadataLicenseConfig = field(
default_factory=lambda: MetadataLicenseConfig()
)
provider: MetadataProviderConfig = field(
default_factory=lambda: MetadataProviderConfig()
)
contact: MetadataContactConfig = field(
default_factory=lambda: MetadataContactConfig()
)
license: LicenseConfig = field(default_factory=lambda: LicenseConfig())
provider: ProviderConfig = field(default_factory=lambda: ProviderConfig())
contact: ContactConfig = field(default_factory=lambda: ContactConfig())

def get_invalid_properties(self):
"""Checks the values of mandatory fields: identification (title, description, keywords)."""
Expand Down
145 changes: 106 additions & 39 deletions models/top_level/ResourceConfigTemplate.py
Original file line number Diff line number Diff line change
@@ -1,92 +1,128 @@
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum

from .utils import InlineList
from .providers import ProviderPostgresql, ProviderMvtProxy, ProviderWmsFacade
from .utils import (
InlineList,
bbox_from_list,
get_enum_value_from_string,
is_valid_string,
)
from .providers.records import CrsAuthorities
from .providers.records import Languages


# records
class ResourceTypes(Enum):
class ResourceTypesEnum(Enum):
COLLECTION = "collection"
STAC = "stac-collection"


class ProviderTypes(Enum):
FEATURE = "feature"
MAP = "map"
TILE = "tile"
class ResourceVisibilityEnum(Enum):
NONE = ""
DEFAULT = "default"
HIDDEN = "hidden"


# data classes
@dataclass(kw_only=True)
class ProviderTemplate:
"""Class to represent a Provider configuration template."""
class ResourceLinkTemplate:
"""Class to represent a Link configuration template."""

type: ProviderTypes
name: str
data: str
type: str = ""
rel: str = ""
href: str = ""

# optional fields:
id_field: str | None = None
title_field: str | None = None
geometry: dict | None = None
options: dict | None = None
format: dict | None = None
# optional
title: str | None = None
hreflang: Languages | None = None
length: int | None = None


@dataclass(kw_only=True)
class LinkTemplate:
"""Class to represent a Link configuration template."""
class ResourceSpatialConfig:
bbox: InlineList = field(default_factory=lambda: InlineList([-180, -90, 180, 90]))

# optional, but with assumed default value:
crs: str = field(default="http://www.opengis.net/def/crs/OGC/1.3/CRS84")

# we need these as separate properties so that Enum class values can be set&selected in the UI
@property
def crs_authority(self):
crs_auth_id = self.crs.split("http://www.opengis.net/def/crs/")[
-1
] # OGC/1.3/CRS84
auth_string = "/".join(crs_auth_id.split("/")[:-1])
return get_enum_value_from_string(CrsAuthorities, auth_string)

type: str
rel: str
title: str
href: str
hreflang: str
@property
def crs_id(self):
return self.crs.split("/")[-1]


@dataclass(kw_only=True)
class SpatialConfig:
bbox: InlineList = field(default_factory=lambda: InlineList([-180, -90, 180, 90]))
crs: str = field(default="http://www.opengis.net/def/crs/OGC/1.3/CRS84")
class ResourceTemporalConfig:

# optional
begin: str | datetime | None = None
end: str | datetime | None = None
trs: str | None = None


@dataclass(kw_only=True)
class ExtentsConfig:
class ResourceExtentsConfig:
"""Class to represent Extents configuration template."""

# fields with default values:
spatial: dict = field(default_factory=lambda: SpatialConfig())
spatial: ResourceSpatialConfig = field(
default_factory=lambda: ResourceSpatialConfig()
)

# optional fields:
temporal: dict | None = None
# optional
temporal: ResourceTemporalConfig | None = None


@dataclass(kw_only=True)
class ResourceConfigTemplate:
"""Class to represent a Resource configuration template."""

# fields with default values:
type: ResourceTypes = field(default_factory=lambda: ResourceTypes.COLLECTION)
type: ResourceTypesEnum = field(
default_factory=lambda: ResourceTypesEnum.COLLECTION
)
title: str | dict = field(default="")
description: str | dict = field(default="")
keywords: list | dict = field(default_factory=lambda: [])
links: list[LinkTemplate] = field(default_factory=lambda: [])
extents: ExtentsConfig = field(default_factory=lambda: ExtentsConfig())
providers: list[ProviderTemplate] = field(default_factory=lambda: [])
links: list[ResourceLinkTemplate] = field(default_factory=lambda: [])
extents: ResourceExtentsConfig = field(
default_factory=lambda: ResourceExtentsConfig()
)
# for providers, the types have to be explicitly listed so they are picked up on deserialization
providers: list[
ProviderPostgresql | ProviderMvtProxy | ProviderWmsFacade | dict
] = field(default_factory=lambda: [])

# optional
visibility: ResourceVisibilityEnum | None = None
# limits, linked-data: ignored for now

# Overwriding __init__ method to pass 'instance_name' as an input but not make it an instance property
# This will allow to have a clean 'asdict(class)' output without 'instance_name' in it
def __init__(
self,
*,
instance_name: str,
type: str = "collection",
type: ResourceTypesEnum = ResourceTypesEnum.COLLECTION,
title: str = "",
description: str = "",
keywords: dict = None,
links: list[LinkTemplate] = None,
extents: ExtentsConfig = None,
providers: list[ProviderTemplate] = None
links: list[ResourceLinkTemplate] = None,
extents: ResourceExtentsConfig = None,
providers: list[
ProviderPostgresql | ProviderMvtProxy | ProviderWmsFacade
] = None,
visibility: ResourceVisibilityEnum | None = None
):
self._instance_name = instance_name
self.type = type
Expand Down Expand Up @@ -115,7 +151,38 @@ def __init__(
self.links = links
self.extents = extents
self.providers = providers
self.visibility = visibility

@property
def instance_name(self):
return self._instance_name

def validate_reassign_bbox(self) -> bool:
"""Validates the bbox values and converts to int/float if needed."""
try:
self.extents.spatial.bbox = bbox_from_list(self.extents.spatial.bbox)
return True
except ValueError:
self.extents.spatial.bbox = InlineList([-180, -90, 180, 90])
return False

def get_invalid_properties(self):
"""Checks the values of mandatory fields: identification (title, description, keywords)."""
all_invalid_fields = []

if not isinstance(self.type, ResourceTypesEnum):
all_invalid_fields.append("type")
if len(self.title) == 0:
all_invalid_fields.append("title")
if len(self.description) == 0:
all_invalid_fields.append("description")
if len(self.keywords) == 0:
all_invalid_fields.append("keywords")
if len(self.providers) == 0:
all_invalid_fields.append("providers")
if not is_valid_string(self.extents.spatial.crs):
all_invalid_fields.append("extents.spatial.crs")
if len(self.extents.spatial.bbox) < 4:
all_invalid_fields.append("extents.spatial.bbox")

return all_invalid_fields
Loading