Merge pull request #79 from ARUP-CAS/agents/claude/sync-main-do-v2.2.0

Srovnat version/v2.2.0 s main + opravy BOM, E501, CITATION
This commit is contained in:
david-spacil authored and GitHub committed 2026-10-02 11:52:05 +02:00
commit e5b0716fd6
19 files changed
+307 -185

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
# Jednotné kódování a konce řádků napříč editory.
# Vzor: aiscr-management (quality_baseline/foundations/editorconfig.ini)
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# Zdrojáky bez BOM – pyqgis4-checker na BOM spadne (viz AGENTS.md)
[*.py]
indent_style = space
indent_size = 4
[*.md]
trim_trailing_whitespace = false
# Heslář zapisuje plugin s BOM a CRLF (kvůli Excelu) – needitovat ručně
[amcr_viewer/codelists/heslar.csv]
charset = utf-8-bom
end_of_line = crlf
insert_final_newline = unset
trim_trailing_whitespace = unset
+10
View File
@@ -0,0 +1,10 @@
# Textové soubory v repozitáři s LF; vzor: aiscr-management
# (quality_baseline/foundations/gitattributes.fragment)
* text=auto eol=lf
# Heslář generuje plugin (BOM + CRLF kvůli Excelu) – ukládat bajt po bajtu
amcr_viewer/codelists/heslar.csv -text
# Binární soubory
*.png binary
*.zip binary
+16
View File
@@ -0,0 +1,16 @@
# Dependabot hlídá jen GitHub Actions: akce jsou ve workflow připnuté
# na SHA s komentářem verze a Dependabot umí obojí povýšit naráz.
# Plugin nemá pip/npm manifest a verze nástrojů (ruff, flake8 …) se
# v workflow drží napevno vědomě – viz AGENTS.md.
# Vzor: aiscr-management/.github/dependabot.yml
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
target-branch: main
commit-message:
prefix: "ci"
+1 -1
View File
@@ -25,7 +25,7 @@ Nepotřebné body můžeš smazat. Komentáře (<!-- ... -->) se v PR nezobrazuj
## Kontrolní seznam ## Kontrolní seznam
- [ ] Změny jsou v souladu se stylem projektu (viz `AGENTS.md`) - [ ] Změny jsou v souladu se stylem projektu (viz `AGENTS.md`)
- [ ] Při změně funkcí povýšena verze v `amcr_viewer/metadata.txt` a doplněn `changelog` - [ ] Při změně funkcí povýšena verze v `amcr_viewer/metadata.txt` (+ `changelog`) a v `CITATION.cff` (`version`, `date-released`)
- [ ] Otestováno v QGIS (min. podporovaná verze 3.44) - [ ] Otestováno v QGIS (min. podporovaná verze 3.44)
- [ ] PR míří do správné cílové větve - [ ] PR míří do správné cílové větve
- [ ] Větev odpovídá konvenci (`feat/ fix/ docs/ chore/<téma>`, AI `agents/<jméno>/<téma>`) - [ ] Větev odpovídá konvenci (`feat/ fix/ docs/ chore/<téma>`, AI `agents/<jméno>/<téma>`)
+39 -7
View File
@@ -28,6 +28,7 @@ env:
DETECT_SECRETS: detect-secrets==1.5.0 DETECT_SECRETS: detect-secrets==1.5.0
FLAKE8: flake8==7.3.0 FLAKE8: flake8==7.3.0
RUFF: ruff==0.16.5 RUFF: ruff==0.16.5
OPENSPEC: 1.14.0
jobs: jobs:
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@@ -74,10 +75,10 @@ jobs:
print('detect-secrets: bez nálezů') print('detect-secrets: bez nálezů')
" "
# Na plugins.qgis.org je informativní, tady blokuje – konfigurace # Na plugins.qgis.org je informativní, tady blokuje. Bez konfigurace,
# v amcr_viewer/.flake8 je stejná pro obě místa. # tj. se stejnými výchozími pravidly jako scanner.
- name: Flake8 - name: Flake8
run: flake8 --config amcr_viewer/.flake8 amcr_viewer/ run: flake8 --isolated amcr_viewer/
# Nad rámec plugins.qgis.org; konfigurace v pyproject.toml # Nad rámec plugins.qgis.org; konfigurace v pyproject.toml
- name: Ruff - name: Ruff
@@ -110,6 +111,24 @@ jobs:
exit 1 exit 1
fi fi
# --------------------------------------------------------------------
# OpenSpec – artefakty změn v openspec/ musí projít validací
# --------------------------------------------------------------------
openspec:
name: OpenSpec
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
# Verze CLI napevno – formát validace se mezi verzemi mění.
# Node je v ubuntu-latest předinstalovaný.
- name: openspec validate
run: |
npx --yes @fission-ai/openspec@${{ env.OPENSPEC }} \
validate --all --strict --no-interactive
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# 3. Načtení pluginu ve skutečném QGIS, v obou podporovaných verzích # 3. Načtení pluginu ve skutečném QGIS, v obou podporovaných verzích
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@@ -147,14 +166,27 @@ jobs:
- name: Zip plugin - name: Zip plugin
run: zip -r amcr_viewer.zip amcr_viewer -x "*.git*" run: zip -r amcr_viewer.zip amcr_viewer -x "*.git*"
# Kontrola obsahu ZIPu. Config soubory pro scanner musí být uvnitř # Verze v CITATION.cff se povyšuje ručně spolu s metadata.txt a snadno
# vedle metadata.txt, jinak je plugins.qgis.org nenajde – a některé # se zapomene – tag by pak nesl v citaci jinou verzi než plugin
# nástroje skryté soubory tiše vynechávají. - name: Verify CITATION.cff version
run: |
plugin=$(sed -n 's/^version=//p' amcr_viewer/metadata.txt \
| tr -d "\r\"' ")
citace=$(sed -n 's/^version://p' CITATION.cff \
| tr -d "\r\"' ")
echo "metadata.txt: '$plugin', CITATION.cff: '$citace'"
if [ -z "$plugin" ] || [ "$plugin" != "$citace" ]; then
echo "::error file=CITATION.cff::verze '$citace' neodpovídá" \
"metadata.txt ('$plugin')"
exit 1
fi
# Kontrola obsahu ZIPu: povinné soubory jsou uvnitř, git soubory ne.
- name: Verify archive contents - name: Verify archive contents
run: | run: |
unzip -l amcr_viewer.zip unzip -l amcr_viewer.zip
for soubor in amcr_viewer/metadata.txt amcr_viewer/__init__.py \ for soubor in amcr_viewer/metadata.txt amcr_viewer/__init__.py \
amcr_viewer/.flake8; do amcr_viewer/LICENSE; do
unzip -l amcr_viewer.zip | grep -qF " $soubor" \ unzip -l amcr_viewer.zip | grep -qF " $soubor" \
|| { echo "::error::v ZIPu chybí $soubor"; exit 1; } || { echo "::error::v ZIPu chybí $soubor"; exit 1; }
done done
+44 -11
View File
@@ -17,9 +17,36 @@ Zdroj dat: https://digiarchiv.aiscr.cz/ · Nápověda: https://amcr-help.aiscr.c
Tento repozitář je jedním ze **sourozeneckých repozitářů** ekosystému AIS CR. Tento repozitář je jedním ze **sourozeneckých repozitářů** ekosystému AIS CR.
Centrální governance a AI konfigurace spravuje hub **`aiscr-management`**; konvence Centrální governance a AI konfigurace spravuje hub **`aiscr-management`**; konvence
v tomto souboru jsou s tímto vzorem sladěné a zjednodušené pro potřeby jednoho v tomto souboru jsou s tímto vzorem sladěné a zjednodušené pro potřeby jednoho
QGIS pluginu. Těžkou mašinerii hubu (složka `.agents/`, OpenSpec, sync skripty, QGIS pluginu. Z hubu přebírá **OpenSpec** ve stupni `change-tracked` (viz
multi-assistant generování) tento repozitář **záměrně nepřebírá**. Při širších níže). Ostatní mašinerii hubu (složka `.agents/`, sync skripty, vlastní
otázkách governance má přednost vzor z `aiscr-management`. schémata OpenSpec, multi-assistant generování) tento repozitář **záměrně
nepřebírá**. Při širších otázkách governance má přednost vzor
z `aiscr-management`.
## OpenSpec
Repozitář používá OpenSpec ve stupni **`change-tracked`**: plánovací
artefakty změn (`proposal.md`, delta spec, `design.md`, `tasks.md`) žijí
v `openspec/changes/<slug>/`, trvalé specifikace v `openspec/specs/` se
**neudržují**. Stupeň a kontext pro agenty jsou v `openspec/config.yaml`;
změna stupně se dělá vědomě společně s hubem, ne v rámci rozpracované práce.
- **Kdy založit změnu:** práce, která mění chování (co uživatel vidí,
atributy vrstev, kontrakt s API digiarchivu, uložená nastavení), zasahuje
víc repozitářů nebo mění pravidla / AI konfiguraci / CI.
- **Kdy ne:** překlepy a formátování, bump závislostí či pinů nástrojů bez
změny chování, přegenerování odvozených souborů.
- **Postup:** `openspec new change <slug>` → artefakty → `openspec validate
<slug> --strict` → implementace (až na výslovný pokyn) → po merge
`openspec archive <slug> --skip-specs` (archiv
`openspec/changes/archive/RRRR-MM-DD-<slug>/`).
- Artefakty změny jdou **ve stejném PR** jako implementace; v popisu PR
odkaž na adresář změny.
- Používá se vestavěné schéma `spec-driven`; vlastní schémata hubu se sem
nepřenášejí. CLI: `npx @fission-ai/openspec@1.14.0` (nebo lokálně
nainstalované `openspec`); bez CLI lze artefakty psát i ručně.
- Asistentské povrchy (`.claude/`, `.github/prompts/` …) doručuje sync
z hubu; v tomto repozitáři se ručně nezakládají ani necommitují.
## Struktura repozitáře ## Struktura repozitáře
@@ -33,7 +60,8 @@ amcr_viewer/ # vlastní kód pluginu (toto se balí do releasu)
metadata.txt # metadata pluginu + verze + changelog metadata.txt # metadata pluginu + verze + changelog
i18n/ # překlady (.ts) i18n/ # překlady (.ts)
*.png # ikony *.png # ikony
.github/workflows/ # CI – release pluginu .github/workflows/ # CI – kontroly kvality a release pluginu
openspec/ # OpenSpec – konfigurace a plánovací artefakty změn
README.md # uživatelská dokumentace (anglicky) README.md # uživatelská dokumentace (anglicky)
``` ```
@@ -140,6 +168,9 @@ flatpak run --command=sh org.qgis.qgis -c \
- Verze pluginu žije v **`amcr_viewer/metadata.txt`** (`version=`). - Verze pluginu žije v **`amcr_viewer/metadata.txt`** (`version=`).
- **Při každé změně chování / nové funkci** povyš verzi a doplň položku do - **Při každé změně chování / nové funkci** povyš verzi a doplň položku do
`changelog=` v `metadata.txt` (formát `vX.Y.Z (RRRR-MM-DD)` + odrážky). `changelog=` v `metadata.txt` (formát `vX.Y.Z (RRRR-MM-DD)` + odrážky).
- Současně povyš i **`CITATION.cff`** v kořeni repozitáře: `version:` na
stejnou verzi jako v `metadata.txt` a `date-released:` na datum releasu.
Oba soubory musí mít stejnou verzi, než se založí tag.
- Datum v changelogu ber z **deterministického zdroje**, ne z paměti, např. - Datum v changelogu ber z **deterministického zdroje**, ne z paměti, např.
`python -c "import datetime; print(datetime.date.today().isoformat())"`. `python -c "import datetime; print(datetime.date.today().isoformat())"`.
- Release se spouští **pushnutím tagu `vX.Y.Z`**, ne publikací releasu - Release se spouští **pushnutím tagu `vX.Y.Z`**, ne publikací releasu
@@ -160,6 +191,7 @@ flatpak run --command=sh org.qgis.qgis -c \
- PR musí mířit do správné `version/v2.x.y` větve. - PR musí mířit do správné `version/v2.x.y` větve.
- Před požádáním o review projdi kontrolní seznam v šabloně (zejména bump verze - Před požádáním o review projdi kontrolní seznam v šabloně (zejména bump verze
v `metadata.txt`, pokud měníš chování). v `metadata.txt`, pokud měníš chování).
- Mění-li PR chování, obsahuje i odpovídající změnu v `openspec/changes/`.
- V popisu PR uveď **podíl AI** (např. „text navržen AI, ručně zkontrolováno") - V popisu PR uveď **podíl AI** (např. „text navržen AI, ručně zkontrolováno")
a odkaz na související issue, pokud existuje. a odkaz na související issue, pokud existuje.
@@ -189,7 +221,7 @@ tohle:
| **Lint a bezpečnost** | `check_sources.py`, bandit, detect-secrets, flake8, ruff | | **Lint a bezpečnost** | `check_sources.py`, bandit, detect-secrets, flake8, ruff |
| **Kompatibilita s Qt6** | `pyqgis4-checker` v dockeru | | **Kompatibilita s Qt6** | `pyqgis4-checker` v dockeru |
| **Smoke test** | `smoke_test.py` v `qgis/qgis:ltr` i `qgis/qgis:stable` | | **Smoke test** | `smoke_test.py` v `qgis/qgis:ltr` i `qgis/qgis:stable` |
| **Balíček pluginu** | sestaví ZIP, ověří obsah, přiloží jako artefakt | | **Balíček pluginu** | ověří shodu verze v `CITATION.cff` a `metadata.txt`, sestaví ZIP, ověří obsah, přiloží jako artefakt |
Smoke test běží v obou podporovaných řadách: `ltr` je QGIS 3.44 na Qt5, Smoke test běží v obou podporovaných řadách: `ltr` je QGIS 3.44 na Qt5,
`stable` je QGIS 4.x na Qt6. `stable` je QGIS 4.x na Qt6.
@@ -205,7 +237,7 @@ pip install bandit detect-secrets flake8 ruff
python3 tests/check_sources.py python3 tests/check_sources.py
bandit -r amcr_viewer/ bandit -r amcr_viewer/
detect-secrets scan --all-files amcr_viewer/ detect-secrets scan --all-files amcr_viewer/
flake8 --config amcr_viewer/.flake8 amcr_viewer/ flake8 --isolated amcr_viewer/
ruff check . ruff check .
# smoke test v obou verzích QGIS (docker, bez instalace čehokoli) # smoke test v obou verzích QGIS (docker, bez instalace čehokoli)
@@ -222,11 +254,12 @@ Na co si dát pozor:
v logu. Workflow proto kontroluje, že log obsahuje jen hlavičku. v logu. Workflow proto kontroluje, že log obsahuje jen hlavičku.
- **`detect-secrets` bez `--all-files` prohledá jen soubory sledované - **`detect-secrets` bez `--all-files` prohledá jen soubory sledované
gitem** a o nesledovaném souboru mlčí. Vypadá to jako čistý výsledek. gitem** a o nesledovaném souboru mlčí. Vypadá to jako čistý výsledek.
- **Konfigurace lintů je rozdělená schválně.** `amcr_viewer/.flake8` leží - **Flake8 běží bez konfigurace** (`--isolated`), tedy se stejnými
vedle `metadata.txt`, protože scanner na plugins.qgis.org hledá config výchozími pravidly jako scanner na plugins.qgis.org. Do balíčku nepatří
soubory jen v kořeni balíčku uvnitř ZIPu; díky tomu platí stejná pravidla `.flake8`, `.bandit` ani `.secrets.baseline`: scanner by plugin označil
v CI, lokálně i při uploadu. Konfigurace ruffu je naopak v kořenovém jako „Validated (configured)“ a nález je lepší opravit v kódu.
`pyproject.toml` – ruff se do balíčku pluginu nedistribuuje. Konfigurace ruffu je v kořenovém `pyproject.toml` – ruff se do balíčku
pluginu nedistribuuje.
Viz https://plugins.qgis.org/docs/security-scanning/config-files Viz https://plugins.qgis.org/docs/security-scanning/config-files
- **Verze nástrojů jsou v workflow napevno.** Výchozí sada pravidel ruffu se - **Verze nástrojů jsou v workflow napevno.** Výchozí sada pravidel ruffu se
mezi verzemi mění, takže bez pinu by CI začalo padat samo od sebe. mezi verzemi mění, takže bez pinu by CI začalo padat samo od sebe.
+2 -2
View File
@@ -25,5 +25,5 @@ abstract: >-
the Digital archive of the Archaeological Map of the the Digital archive of the Archaeological Map of the
Czech Republic (https://digiarchiv.aiscr.cz/). Czech Republic (https://digiarchiv.aiscr.cz/).
license: GPL-3.0 license: GPL-3.0
version: '2.1.2' version: '2.2.0'
date-released: '2026-09-01' date-released: '2026-10-01'
+1 -3
View File
@@ -333,9 +333,7 @@ amcr_viewer/ the plugin package (this is what gets zipped)
codelists/heslar.csv cached controlled vocabularies codelists/heslar.csv cached controlled vocabularies
i18n/ Qt translation files i18n/ Qt translation files
*.png toolbar and menu icons *.png toolbar and menu icons
resources.py generated by pyrcc, currently unused
metadata.txt plugin metadata and changelog metadata.txt plugin metadata and changelog
.flake8 lint config, read by the plugins.qgis.org scanner
tests/ tests/
check_sources.py source hygiene checks (no QGIS needed) check_sources.py source hygiene checks (no QGIS needed)
smoke_test.py loads the plugin in a real, headless QGIS smoke_test.py loads the plugin in a real, headless QGIS
@@ -404,7 +402,7 @@ Reproducing them locally:
```bash ```bash
python3 tests/check_sources.py python3 tests/check_sources.py
ruff check . ruff check .
flake8 --config amcr_viewer/.flake8 amcr_viewer/ flake8 --isolated amcr_viewer/
bandit -r amcr_viewer/ bandit -r amcr_viewer/
docker run --rm -v "$PWD:/work:ro" -w /work --user "$(id -u):$(id -g)" \ docker run --rm -v "$PWD:/work:ro" -w /work --user "$(id -u):$(id -g)" \
-e HOME=/tmp qgis/qgis:stable python3 tests/smoke_test.py -e HOME=/tmp qgis/qgis:stable python3 tests/smoke_test.py
-12
View File
@@ -1,12 +0,0 @@
# Konfigurace flake8 pro plugin AMČR Viewer.
#
# Soubor leží vedle metadata.txt schválně: scanner na plugins.qgis.org
# hledá .flake8 pouze v kořeni balíčku uvnitř ZIPu, takže stejná pravidla
# platí v CI, lokálně i při uploadu.
# https://plugins.qgis.org/docs/security-scanning/config-files
[flake8]
# resources.py je vygenerovaný výstup pyrcc ("All changes made in this
# file will be lost"), není nikde importovaný a zdrojový .qrc v repu není.
# Ručně se neformátuje.
per-file-ignores =
*resources.py: E302,E305,E501
+66 -5
View File
@@ -104,6 +104,21 @@ def load_all_data():
return categorized_data return categorized_data
def _facet_name(item):
"""
Returns the value of one facet item from the Digiarchive API.
Digiarchive v4.1.0 (Solr 10, json.nl=arrarr) returns facet items as
["value", count] pairs; older versions returned {"name": "value", ...}
objects. Both shapes are accepted so the plugin works against either.
"""
if isinstance(item, dict):
return item.get("name")
if isinstance(item, (list, tuple)) and item:
return item[0]
return None
def fetch_set(base_url, internal_name, api_set, task=None): def fetch_set(base_url, internal_name, api_set, task=None):
dataset = [] dataset = []
params_amcr = { params_amcr = {
@@ -206,7 +221,9 @@ def fetch_set(base_url, internal_name, api_set, task=None):
for r in records: for r in records:
nazev = r["name"] nazev = _facet_name(r)
if not nazev:
continue
dataset.append({ dataset.append({
'Název': nazev, 'Název': nazev,
@@ -217,17 +234,48 @@ def fetch_set(base_url, internal_name, api_set, task=None):
break break
except Exception as e: except Exception as e:
# A partial set (e.g. pagination interrupted halfway) would
# silently drop codes – report the whole set as failed instead
# and let the caller keep the previous values
QgsMessageLog.logMessage( QgsMessageLog.logMessage(
f"Chyba u setu {api_set}: {e}", f"Chyba u setu {api_set}: {e}",
"AMČR", Qgis.MessageLevel.Warning) "AMČR", Qgis.MessageLevel.Warning)
break return []
return dataset return dataset
def download_heslare(task=None): def _read_existing_rows():
"""Fetches the codelists from the AMČR API and saves it to a CSV file.""" """
Returns the rows of the current heslar.csv grouped by category, so a set
that fails to download can keep its previous values.
"""
rows = {}
if not os.path.exists(OUTPUT_FILE):
return rows
try:
with open(OUTPUT_FILE, encoding='utf-8-sig', newline='') as f:
for row in csv.DictReader(f, delimiter=';'):
cat = (row.get('Kategorie') or '').strip()
if cat:
rows.setdefault(cat, []).append(row)
except Exception as e:
QgsMessageLog.logMessage(
f"Nelze načíst stávající hesláře: {e}",
"AMČR", Qgis.MessageLevel.Warning)
return rows
def download_heslare(task=None, failed=None):
"""
Fetches the codelists from the AMČR API and saves it to a CSV file.
A set that fails or comes back empty keeps its rows from the current
heslar.csv instead of being wiped; its name is appended to ``failed``
(if given) so the caller can warn the user.
"""
ensure_codelists_dir() ensure_codelists_dir()
existing = _read_existing_rows()
all_data = [] all_data = []
total_sets = len(slovnicek) total_sets = len(slovnicek)
# index, (interni, api_nazev) # index, (interni, api_nazev)
@@ -251,6 +299,18 @@ def download_heslare(task=None):
if data is None: if data is None:
return False # Cancelled mid-download return False # Cancelled mid-download
if not data:
# Never replace a working codelist with nothing – an API change
# would otherwise silently empty the filter in the dialog
old = existing.get(interni, [])
QgsMessageLog.logMessage(
f"Heslář '{interni}' se nepodařilo stáhnout, "
f"ponechávám předchozí hodnoty ({len(old)} položek).",
"AMČR", Qgis.MessageLevel.Warning)
if failed is not None:
failed.append(interni)
data = old
all_data.extend(data) all_data.extend(data)
# Report progress (0-100) # Report progress (0-100)
@@ -261,7 +321,8 @@ def download_heslare(task=None):
# Save to CSV # Save to CSV
with open(OUTPUT_FILE, 'w', newline='', encoding='utf-8-sig') as f: with open(OUTPUT_FILE, 'w', newline='', encoding='utf-8-sig') as f:
fieldnames = ['Název', 'Kód', 'Kategorie'] fieldnames = ['Název', 'Kód', 'Kategorie']
writer = csv.DictWriter(f, fieldnames=fieldnames, delimiter=';') writer = csv.DictWriter(f, fieldnames=fieldnames, delimiter=';',
extrasaction='ignore')
writer.writeheader() writer.writeheader()
writer.writerows(all_data) writer.writerows(all_data)
+25 -5
View File
@@ -75,12 +75,15 @@ class UpdateCodelistsTask(QgsTask):
super().__init__(description, QgsTask.Flag.CanCancel) super().__init__(description, QgsTask.Flag.CanCancel)
self.success = False self.success = False
self.exception = None self.exception = None
# Codelists that failed to download and kept their previous values
self.failed_sets = []
def run(self): def run(self):
"""Runs in a background thread.""" """Runs in a background thread."""
try: try:
# Call the download function with the task reference # Call the download function with the task reference
self.success = download_heslare(task=self) self.success = download_heslare(
task=self, failed=self.failed_sets)
return self.success return self.success
except Exception as e: except Exception as e:
self.exception = e self.exception = e
@@ -91,10 +94,17 @@ class UpdateCodelistsTask(QgsTask):
if result: if result:
# Safely update the global variables in the main thread # Safely update the global variables in the main thread
refresh_globals() refresh_globals()
QgsMessageLog.logMessage( if self.failed_sets:
"Hesláře AMČR byly úspěšně aktualizovány.", QgsMessageLog.logMessage(
"AMČR", Qgis.MessageLevel.Info "Hesláře AMČR aktualizovány částečně, beze změny "
) f"zůstaly: {', '.join(self.failed_sets)}",
"AMČR", Qgis.MessageLevel.Warning
)
else:
QgsMessageLog.logMessage(
"Hesláře AMČR byly úspěšně aktualizovány.",
"AMČR", Qgis.MessageLevel.Info
)
else: else:
if self.isCanceled(): if self.isCanceled():
QgsMessageLog.logMessage( QgsMessageLog.logMessage(
@@ -629,6 +639,16 @@ class AmcrFilterDialog(QDialog):
def on_completed(): def on_completed():
_cleanup() _cleanup()
if task.failed_sets:
QMessageBox.warning(
parent_win,
"Hesláře aktualizovány částečně",
"Některé hesláře se nepodařilo stáhnout, "
"ponechány byly jejich předchozí hodnoty:\n"
+ "\n".join(f"• {name}" for name in task.failed_sets)
+ "\n\nPodrobnosti jsou v panelu Zprávy, záložka AMČR."
)
return
QMessageBox.information( QMessageBox.information(
parent_win, parent_win,
"Hotovo", "Hotovo",
+1 -1
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import json import json
import requests import requests
+5
View File
@@ -28,6 +28,11 @@ changelog=
* Filter labels unified: "Specifikace nálezu" renamed to "Materiál" to match the attribute alias * Filter labels unified: "Specifikace nálezu" renamed to "Materiál" to match the attribute alias
* README rewritten to match the current state of the code * README rewritten to match the current state of the code
* Tables for Akce and Lokality contain a field with feature weight, when Komponenty rendering is enabled * Tables for Akce and Lokality contain a field with feature weight, when Komponenty rendering is enabled
v2.1.4 (2026-10-01)
* Removed unused generated resources.py and the bundled flake8 config, so the plugin passes the plugins.qgis.org scan without custom configuration
v2.1.3 (2026-10-01)
* Fixed empty person codelists (excavation leaders, finders) after updating codelists against Digiarchive v4.1.0
* A codelist that fails to download keeps its previous values and the user is warned
v2.1.2 (2026-09-01) v2.1.2 (2026-09-01)
* Qt6 compatibility * Qt6 compatibility
* Code clean-up * Code clean-up
-128
View File
@@ -1,128 +0,0 @@
# -*- coding: utf-8 -*-
# Resource object code
#
# Created by: The Resource Compiler for PyQt5 (Qt v5.15.13)
#
# WARNING! All changes made in this file will be lost!
from qgis.PyQt import QtCore
qt_resource_data = b"\
\x00\x00\x04\x0a\
\x89\
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
\x00\x00\x17\x00\x00\x00\x18\x08\x06\x00\x00\x00\x11\x7c\x66\x75\
\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\
\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\
\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xd9\x02\x15\
\x16\x11\x2c\x9d\x48\x83\xbb\x00\x00\x03\x8a\x49\x44\x41\x54\x48\
\xc7\xad\x95\x4b\x68\x5c\x55\x18\xc7\x7f\xe7\xdc\x7b\x67\xe6\xce\
\x4c\x66\x26\x49\xd3\x24\x26\xa6\xc6\xf8\x40\x21\xa5\x04\xb3\x28\
\xda\x98\x20\xa5\x0b\xad\x55\xa8\x2b\xc5\x50\x1f\xa0\x6e\x34\x2b\
\x45\x30\x14\x02\xba\x52\x69\x15\x17\x66\x63\x45\x97\x95\xa0\xad\
\x0b\xfb\xc0\x06\x25\xb6\x71\x61\x12\x41\x50\xdb\x2a\x21\xd1\xe2\
\x24\xf3\x9e\xc9\xcc\xbd\xe7\x1c\x17\x35\x43\x1e\x33\x21\xb6\xfd\
\x56\x87\xf3\x9d\xfb\xfb\x1e\xf7\xff\x9d\x23\x8c\x31\x43\x95\xf4\
\x85\x1e\x3f\x3b\x35\xac\xfd\xcc\x43\xdc\xa4\x49\x3b\xfe\x9d\x1d\
\xdb\x7b\x22\x90\x78\xf8\xb2\x28\xa7\xbe\x7d\xc1\x4b\x9d\x79\xdf\
\x18\x15\xe5\x16\x99\x10\x56\xde\x69\xdc\x3f\x22\xfd\xec\xd4\xf0\
\xad\x04\x03\x18\xa3\xa2\x7e\x76\x6a\x58\xde\x68\x2b\xb4\x36\xf8\
\xbe\xc6\x18\x53\xdb\xef\xe7\xfa\xec\xed\x67\x63\x10\x42\x00\xf0\
\xfb\xd5\x65\x2a\x15\x45\xc7\x6d\x0d\x00\xc4\xa2\xc1\xaa\x6f\x0d\
\x3e\x6c\xab\xc2\x1c\x56\xa4\x77\x4b\xb0\xf2\x35\x15\x5f\x21\x85\
\xe0\xc8\x6b\x5f\x92\x2d\x37\x33\x39\xf9\x03\x27\x8e\x1f\xa2\xf7\
\xbe\x9d\x04\x1c\x0b\x37\xe4\xac\xff\xa6\x30\x87\xbd\xba\x00\x6a\
\x06\x79\xe5\xf5\xaf\x89\xd9\x92\xc5\xcc\x0a\xd9\x7c\x19\xcf\xe9\
\xe2\xe4\xa9\x2f\x78\x7c\xff\x01\x72\x85\x0a\x2b\x65\x1f\xa5\x4c\
\xb5\xb2\x55\x16\x80\xbd\x31\xda\xda\x20\x1f\x7d\x3e\xcd\xc2\xfd\
\x59\xa6\x93\x39\x92\xd1\x22\xea\x9b\x16\xce\x9d\x3f\xce\xe0\x83\
\x03\x24\x82\x59\x3a\xdb\x7b\x88\xc7\x82\x68\x63\x58\xc9\xcc\x62\
\x8c\x21\x18\xb0\x6a\xc3\x37\x06\x49\x16\xff\x24\x6b\xa5\x49\xbb\
\x25\xbc\xa2\xa6\x21\xbb\x40\x7f\xdf\x00\x83\xbd\x01\x8e\x3c\xd5\
\x45\xd7\x8e\x6b\x9c\x9c\x98\x25\x1a\xb6\xe8\xbe\x3d\xc2\xdd\x77\
\x44\x48\xc4\x1c\x22\xe1\xeb\x58\x59\xaf\xcf\xd3\x33\x29\x2e\x34\
\x2d\x91\x93\x3e\xbe\x34\x78\x01\xc5\xe2\x61\xc5\xae\x72\x8e\x70\
\xc8\xc2\x0d\x5a\xbc\xf5\xee\x2f\x9c\xfa\x3e\x86\x69\x7a\x8e\xcf\
\x26\xe6\xf9\x63\xa1\x44\xa1\xa4\xd0\xda\x6c\x0d\x2f\x15\x7c\xb4\
\x67\x28\x59\x0a\xcf\xd6\x54\xe2\x06\x13\x87\x2b\x6f\x68\xa6\x27\
\xaf\x31\x32\x36\xc7\xb2\x7f\x17\xef\x7d\x7c\x8c\x33\x67\xcf\x12\
\x70\x24\x4a\x69\xd6\x6a\x46\xd6\xd3\x70\x72\xa9\x82\x67\x34\x45\
\xad\x28\xdb\x1a\x15\x34\x98\xff\x46\xed\xef\x37\x0d\x99\xbf\x4a\
\x3c\x30\x38\xc0\xc8\x4b\xaf\x92\x5a\x9c\xe2\xe0\x23\x6d\x74\xb4\
\xba\x84\x5d\x0b\x29\x45\x7d\xb8\x94\x82\x96\xb6\x10\xf3\xc5\x12\
\x2a\xef\x53\x11\x1a\x63\xad\x3f\x93\x19\x85\xf1\xb1\x77\x58\x5a\
\xf8\x99\x97\x9f\xe9\xa6\x75\x47\x90\xc6\xb8\x43\xd8\xb5\xb6\xce\
\xfc\xfa\xfd\x00\xfb\x3e\xf4\xc8\x05\x35\xba\x5e\xeb\x46\x21\xf9\
\xcf\x0a\xa9\x8c\x87\xe3\x48\xdc\x90\xb5\x6e\x98\x6a\xaa\x65\xf2\
\x52\x92\x43\x2f\x5e\xc2\x8c\x02\x1a\x10\xf5\x07\xac\xc3\x75\x70\
\x83\x92\x80\xb3\xf9\xd0\x26\xf8\x8f\xb3\x29\xc6\x3e\xb8\x8c\x19\
\x35\x75\x6b\x7b\x7e\x3c\xca\x45\x0c\x7e\x49\x31\xf4\x58\x3b\xf7\
\xf6\x34\x90\x88\x39\x04\x1c\x59\x1f\xfe\xdb\xd5\x3c\x5f\x9d\x4b\
\x32\xfd\x44\xb2\xba\xd7\xfa\xb6\x60\xcf\xde\x16\xdc\x90\x45\x4c\
\x4a\x2a\x9e\x62\xfe\x4e\xc5\xc8\xc1\x4e\xda\x76\x86\xe8\xe9\x0a\
\xe3\xd8\x92\x58\xd4\xc6\xb2\x44\x6d\x78\x2a\x53\xe1\xca\x7c\x99\
\x63\x5d\xbf\x56\x9d\xbd\x9f\x44\x18\x7a\xba\x95\x27\x0f\xb4\xd3\
\xdc\x18\xc0\xf3\x0d\x52\x40\xd8\xb5\xb0\xa4\x20\x14\xb2\x70\x6c\
\x81\x63\xcb\xaa\x42\xd6\xfd\xb7\xf4\xec\xa3\x06\xa0\x50\x52\xd8\
\x4e\x1b\x7e\x4a\xd3\x31\xf9\x29\xcf\xfe\xd4\x49\x7f\x5f\x13\xfb\
\xfa\x9b\x71\x43\x92\x58\xd4\x21\x18\x90\xac\xde\xb0\x42\x50\x13\
\x58\x33\xf3\x88\x6b\xa1\xfd\x65\x96\xf2\x79\xc6\x43\x7b\xd8\x75\
\x38\xcc\x3d\xdd\xd1\xaa\xcf\x71\xe4\xff\x7f\x91\x56\x33\xaf\xea\
\x37\xe7\xa1\x94\x21\x16\xb5\xd1\x06\x2c\x29\x36\xf5\x72\x9b\x96\
\x95\xc0\xc4\xda\x9d\x78\x83\x43\x53\x22\x80\x65\x09\x1c\xfb\x86\
\xc1\x00\xe7\x25\x70\x14\x48\x6f\x1e\x22\x51\xe3\x75\xd9\xb6\xa5\
\x81\xa3\x32\xb1\xfb\xf4\x0c\x30\xb8\xb1\x82\x9b\xb0\x09\x60\x30\
\xb1\xfb\xf4\xcc\xbf\xa0\xe9\x6e\xae\x5a\xdf\x4b\x81\x00\x00\x00\
\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
"
qt_resource_name = b"\
\x00\x07\
\x07\x3b\xe0\xb3\
\x00\x70\
\x00\x6c\x00\x75\x00\x67\x00\x69\x00\x6e\x00\x73\
\x00\x0b\
\x06\x1f\xb8\xc2\
\x00\x61\
\x00\x6d\x00\x63\x00\x72\x00\x5f\x00\x76\x00\x69\x00\x65\x00\x77\x00\x65\x00\x72\
\x00\x08\
\x0a\x61\x5a\xa7\
\x00\x69\
\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\
"
qt_resource_struct_v1 = b"\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
"
qt_resource_struct_v2 = b"\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x01\x9c\x23\xfd\x16\x70\
"
qt_version = [int(v) for v in QtCore.qVersion().split('.')]
if qt_version < [5, 8, 0]:
rcc_version = 1
qt_resource_struct = qt_resource_struct_v1
else:
rcc_version = 2
qt_resource_struct = qt_resource_struct_v2
def qInitResources():
QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
def qCleanupResources():
QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
qInitResources()
View File
Whitespace-only changes.
+65
View File
@@ -0,0 +1,65 @@
schema: spec-driven
# Seeded from aiscr-management
# .agents/canonical_configs/templates/openspec/config_seed.yaml;
# from now on the content is owned by this repository.
context: |
Repository: aiscr-qgis-amcr-viewer — QGIS plugin (AMČR Viewer) for
downloading and visualising data from the AMČR Digital Archive
(digiarchiv.aiscr.cz).
OpenSpec posture: change-tracked.
This is the local reading of a posture declared and owned by the
management hub (aiscr-management, .agents/sync/repos.toml):
- change-tracked — change-scoped planning artifacts live under
`openspec/changes/`; no durable capability specs are maintained here.
Do not edit this line to unblock work in progress. Changing posture is
a decision taken deliberately with the hub and then applied here.
When OpenSpec fires: behaviour-changing work (user-visible behaviour,
layer attributes, the digiarchiv API contract the plugin relies on,
stored settings), cross-repo work, and governance-touching work. Not
typo and formatting fixes, dependency or tool-pin bumps that change no
behaviour, or refreshing a generated surface from its source.
Repository conventions: `AGENTS.md` is the single source of truth
(Qt5/Qt6 rules, versioning, branches, checks). Plugin code lives in
`amcr_viewer/` (entry point `amcr_viewer.py`, API and layers in
`amcr_tools.py`, dialogs in `amcr_dialog.py`, codelists in
`amcr_codelists.py`). User documentation is `README.md` (English);
planning artifacts, commits and PRs are in Czech or English as the
author prefers, code and identifiers in English.
rules:
proposal:
- State what changes for a user of the plugin, not only in the code
- Name the affected modules under amcr_viewer/ explicitly
- Say when the change depends on or affects the digiarchiv API or
another AIS CR repository, and where
specs:
- Use RFC 2119 keywords (SHALL/MUST/SHOULD/MAY)
- Use Given/When/Then scenarios for testable contracts
- Describe behaviour the plugin guarantees, not how the code does it
design:
- Record the alternatives considered and why the chosen one won
- Respect the QGIS 3.44 minimum and Qt5/Qt6 rules from AGENTS.md
- Name the verification that will show the change worked
tasks:
- Order tasks so each one is independently verifiable
- Name the command or check that proves each group is done
- Include the version bump (metadata.txt + CITATION.cff) when
behaviour changes
- Include a final verification task that runs the checks from
AGENTS.md (check_sources, bandit, detect-secrets, flake8, ruff,
pyqgis4-checker, smoke test in qgis/qgis:ltr and :stable)
operations:
apply:
guidance:
- Completed artifacts are not approval to implement; wait for an
explicit request to apply the change
archive:
guidance:
- Posture is change-tracked, so archive with --skip-specs (no
openspec/specs/ tree is maintained)
+3 -4
View File
@@ -5,15 +5,14 @@
# jen k tomu, aby ruff choval stejně v CI, lokálně i za rok. Bez explicitní # jen k tomu, aby ruff choval stejně v CI, lokálně i za rok. Bez explicitní
# konfigurace se výchozí sada pravidel mezi verzemi ruffu mění. # konfigurace se výchozí sada pravidel mezi verzemi ruffu mění.
# #
# Konfigurace flake8 je záměrně jinde: v amcr_viewer/.flake8, protože ji # Flake8 záměrně žádnou konfiguraci nemá a běží s výchozími pravidly – stejně
# musí najít i scanner na plugins.qgis.org. # jako scanner na plugins.qgis.org. Config soubor v balíčku by plugin
# označil jako „Validated (configured)“.
[tool.ruff] [tool.ruff]
line-length = 79 line-length = 79
# QGIS 3.44 běží na Pythonu 3.9 a novějším # QGIS 3.44 běží na Pythonu 3.9 a novějším
target-version = "py39" target-version = "py39"
# Generovaný výstup pyrcc, "All changes made in this file will be lost"
extend-exclude = ["amcr_viewer/resources.py"]
[tool.ruff.lint] [tool.ruff.lint]
select = [ select = [
+3 -5
View File
@@ -23,10 +23,6 @@ import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BALICEK = os.path.join(ROOT, "amcr_viewer") BALICEK = os.path.join(ROOT, "amcr_viewer")
# Files that belong in the plugin package even though the upload scanner
# would otherwise call them hidden
POVOLENE_SKRYTE = {".flake8", ".bandit", ".secrets.baseline"}
# Extensions that have no business inside a plugin package # Extensions that have no business inside a plugin package
PODEZRELE = {".exe", ".dll", ".so", ".dylib", ".sh", ".bat", ".cmd", PODEZRELE = {".exe", ".dll", ".so", ".dylib", ".sh", ".bat", ".cmd",
".pyc", ".pyd", ".jar", ".bin"} ".pyc", ".pyd", ".jar", ".bin"}
@@ -72,7 +68,9 @@ for cesta in vsechny_soubory():
if rezim & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH): if rezim & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
nalezy.append(f"{zkratka(cesta)}: spustitelná práva " nalezy.append(f"{zkratka(cesta)}: spustitelná práva "
f"({stat.filemode(rezim)})") f"({stat.filemode(rezim)})")
if jmeno.startswith(".") and jmeno not in POVOLENE_SKRYTE: # No exceptions: scanner config files (.flake8, .bandit,
# .secrets.baseline) would mark the upload "Validated (configured)"
if jmeno.startswith("."):
nalezy.append(f"{zkratka(cesta)}: skrytý soubor v balíčku pluginu") nalezy.append(f"{zkratka(cesta)}: skrytý soubor v balíčku pluginu")
if os.path.splitext(jmeno)[1].lower() in PODEZRELE: if os.path.splitext(jmeno)[1].lower() in PODEZRELE:
nalezy.append(f"{zkratka(cesta)}: podezřelý typ souboru") nalezy.append(f"{zkratka(cesta)}: podezřelý typ souboru")
+2 -1
View File
@@ -161,7 +161,8 @@ def main():
nalezy.append( nalezy.append(
f"{CITATION}: date-released couvlo ({stare_datum} -> " f"{CITATION}: date-released couvlo ({stare_datum} -> "
f"{nove_datum})") f"{nove_datum})")
elif nove_datum == stare_datum and stara_citation_verze != nova_citation_verze: elif (nove_datum == stare_datum
and stara_citation_verze != nova_citation_verze):
nalezy.append( nalezy.append(
f"{CITATION}: version se změnila, ale date-released zůstalo " f"{CITATION}: version se změnila, ale date-released zůstalo "
f"na {stare_datum}") f"na {stare_datum}")