mirror of
https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer.git
synced 2026-10-11 13:27:33 +02:00
Compare commits
55
Commits
v1.3.2
...
70aba60d12
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70aba60d12 | ||
|
|
3591ac4c53 | ||
|
|
f4b1c39e6c | ||
|
|
bcb0bc2c86 | ||
|
|
39dac12bad | ||
|
|
b708930ca2 | ||
|
|
3f7839c818 | ||
|
|
9aba28317a | ||
|
|
048ffe4e2a | ||
|
|
d398d2cd1b | ||
|
|
2218719f98 | ||
|
|
6300919c36 | ||
|
|
7cffcdb235 | ||
|
|
0af4a0cd92 | ||
|
|
9b25863031 | ||
|
|
9ec71c7ed3 | ||
|
|
4ed99d73d9 | ||
|
|
4699dd9c95 | ||
|
|
7ea2a99ada | ||
|
|
6772a99ead | ||
|
|
9e8863b879 | ||
|
|
0eb8008e27 | ||
|
|
bc799452a3 | ||
|
|
ee558aa718 | ||
|
|
eebd7668a5 | ||
|
|
fd11bee274 | ||
|
|
93ed0ca810 | ||
|
|
64ec1ea7fd | ||
|
|
27e5fe02ac | ||
|
|
46c09c4a09 | ||
|
|
444d1c4826 | ||
|
|
d9f5d2ae6e | ||
|
|
d417a78b85 | ||
|
|
ca827321d8 | ||
|
|
b2001c625c | ||
|
|
88018aa432 | ||
|
|
45b6ab09b2 | ||
|
|
89e596802f | ||
|
|
4ea679ec9e | ||
|
|
830537f1a4 | ||
|
|
785b83c9c5 | ||
|
|
493696c67b | ||
|
|
b313fc6db0 | ||
|
|
9a935261e6 | ||
|
|
a4e30bf334 | ||
|
|
56389e27d7 | ||
|
|
c8d42e2459 | ||
|
|
a6ebbce4cf | ||
|
|
88149fbb30 | ||
|
|
c0d054d22a | ||
|
|
ba41039468 | ||
|
|
499b3b3f0a | ||
|
|
54f154b264 | ||
|
|
c679e776df | ||
|
|
a5604dfaa8 |
No files matched your search
@@ -0,0 +1,35 @@
|
||||
<!--
|
||||
Děkujeme za příspěvek! Vyplň prosím sekce níže.
|
||||
Nepotřebné body můžeš smazat. Komentáře (<!-- ... -->) se v PR nezobrazují.
|
||||
-->
|
||||
|
||||
## Souhrn
|
||||
|
||||
<!-- Stručně co a proč. 1–3 věty. -->
|
||||
|
||||
## Změny
|
||||
|
||||
<!-- Co se konkrétně mění, ideálně po souborech / oblastech. -->
|
||||
-
|
||||
|
||||
## Testování
|
||||
|
||||
<!-- Jak ověřit, že to funguje (kroky v QGIS, scénáře). -->
|
||||
-
|
||||
|
||||
## Zapojení AI
|
||||
|
||||
<!-- Byla u změny použita AI? Jak? Např. "text navržen AI, ručně zkontrolováno
|
||||
a upraveno". Smaž, pokud AI použita nebyla. -->
|
||||
-
|
||||
|
||||
## Kontrolní seznam
|
||||
- [ ] 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`
|
||||
- [ ] Otestováno v QGIS (min. podporovaná verze 3.44)
|
||||
- [ ] 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>`)
|
||||
|
||||
## Související issue
|
||||
|
||||
<!-- Např. "Closes #123". Smaž, pokud se neváže k issue. -->
|
||||
@@ -0,0 +1,171 @@
|
||||
name: Code Quality
|
||||
|
||||
# Pouští při každém PR tytéž kontroly, které se dosud dělaly ručně:
|
||||
#
|
||||
# * co spouští plugins.qgis.org při uploadu (bandit, detect-secrets,
|
||||
# flake8, analýza souborů) – https://plugins.qgis.org/docs/security-scanning
|
||||
# * oficiální kontrolu kompatibility s Qt6 (pyqgis4-checker)
|
||||
# * skutečné načtení pluginu v QGIS 3.44 (Qt5) i v QGIS 4 (Qt6)
|
||||
# * sestavení ZIPu, který si recenzent stáhne a nainstaluje přímo z PR
|
||||
#
|
||||
# CodeQL a GitGuardian běží zvlášť, nastavené na úrovni organizace –
|
||||
# tady se schválně neduplikují.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
# Verze se drží napevno, aby se výsledek nezměnil sám od sebe. Výchozí
|
||||
# sada pravidel ruffu se mezi verzemi mění; povýšení je vědomý krok.
|
||||
BANDIT: bandit==1.9.4
|
||||
DETECT_SECRETS: detect-secrets==1.5.0
|
||||
FLAKE8: flake8==7.3.0
|
||||
RUFF: ruff==0.16.5
|
||||
|
||||
jobs:
|
||||
# --------------------------------------------------------------------
|
||||
# 1. Statické kontroly – běží první, protože trvají desítky sekund
|
||||
# --------------------------------------------------------------------
|
||||
lint:
|
||||
name: Lint a bezpečnost
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install tools
|
||||
run: pip install "$BANDIT" "$DETECT_SECRETS" "$FLAKE8" "$RUFF"
|
||||
|
||||
# Hygiena repozitáře: BOM, přímé importy z PyQt5/PyQt6, spustitelná
|
||||
# práva a podezřelé typy souborů. Padá jako první, protože BOM
|
||||
# zneviditelní soubor pro kontrolu níž.
|
||||
- name: Source hygiene
|
||||
run: python3 tests/check_sources.py
|
||||
|
||||
# Blokující kontrola na plugins.qgis.org
|
||||
- name: Bandit
|
||||
run: bandit -r amcr_viewer/
|
||||
|
||||
# Blokující kontrola na plugins.qgis.org.
|
||||
# --all-files je podstatné: bez něj detect-secrets prohledá jen
|
||||
# soubory sledované gitem a nesledovaný soubor tiše přeskočí.
|
||||
- name: detect-secrets
|
||||
run: |
|
||||
detect-secrets scan --all-files amcr_viewer/ > vysledek.json
|
||||
python3 -c "
|
||||
import json, sys
|
||||
nalezy = json.load(open('vysledek.json'))['results']
|
||||
if nalezy:
|
||||
print(json.dumps(nalezy, indent=2))
|
||||
sys.exit(1)
|
||||
print('detect-secrets: bez nálezů')
|
||||
"
|
||||
|
||||
# Na plugins.qgis.org je informativní, tady blokuje – konfigurace
|
||||
# v amcr_viewer/.flake8 je stejná pro obě místa.
|
||||
- name: Flake8
|
||||
run: flake8 --config amcr_viewer/.flake8 amcr_viewer/
|
||||
|
||||
# Nad rámec plugins.qgis.org; konfigurace v pyproject.toml
|
||||
- name: Ruff
|
||||
run: ruff check .
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# 2. Kompatibilita s Qt6 – oficiální skript z QGISu
|
||||
# --------------------------------------------------------------------
|
||||
qt6:
|
||||
name: Kompatibilita s Qt6
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
# Pozor: skript končí kódem 0 i když něco najde, výsledek je jen
|
||||
# v logu. Prázdný log (samotná hlavička) znamená čisto.
|
||||
- name: pyqgis4-checker
|
||||
run: |
|
||||
docker run --rm --user "$(id -u):$(id -g)" \
|
||||
--workdir /workspace/ -v "$PWD:/workspace/" \
|
||||
ghcr.io/qgis/pyqgis4-checker:main-ubuntu \
|
||||
pyqt5_to_pyqt6.py --dry_run --logfile /workspace/pyqt6_checker.log .
|
||||
echo "--- pyqt6_checker.log ---"
|
||||
cat pyqt6_checker.log
|
||||
nalezu=$(grep -v '=== dry_run mode | Start Logs ===' pyqt6_checker.log | wc -l)
|
||||
if [ "$nalezu" -ne 0 ]; then
|
||||
echo "::error::pyqgis4-checker nahlásil nálezy, viz log výše"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# 3. Načtení pluginu ve skutečném QGIS, v obou podporovaných verzích
|
||||
# --------------------------------------------------------------------
|
||||
qgis:
|
||||
name: Smoke test (QGIS ${{ matrix.qgis }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# ltr = 3.44 na Qt5, stable = 4.x na Qt6. Tagy se posouvají
|
||||
# schválně: chceme vědět, že plugin drží krok s aktuálním QGISem.
|
||||
qgis: [ltr, stable]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Smoke test
|
||||
run: |
|
||||
docker run --rm -v "$PWD:/work:ro" -w /work \
|
||||
--user "$(id -u):$(id -g)" -e HOME=/tmp \
|
||||
"qgis/qgis:${{ matrix.qgis }}" python3 tests/smoke_test.py
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# 4. ZIP k instalaci – stejný postup jako v release_plugin.yml
|
||||
# --------------------------------------------------------------------
|
||||
package:
|
||||
name: Balíček pluginu
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Zip plugin
|
||||
run: zip -r amcr_viewer.zip amcr_viewer -x "*.git*"
|
||||
|
||||
# Kontrola obsahu ZIPu. Config soubory pro scanner musí být uvnitř
|
||||
# vedle metadata.txt, jinak je plugins.qgis.org nenajde – a některé
|
||||
# nástroje skryté soubory tiše vynechávají.
|
||||
- name: Verify archive contents
|
||||
run: |
|
||||
unzip -l amcr_viewer.zip
|
||||
for soubor in amcr_viewer/metadata.txt amcr_viewer/__init__.py \
|
||||
amcr_viewer/.flake8; do
|
||||
unzip -l amcr_viewer.zip | grep -qF " $soubor" \
|
||||
|| { echo "::error::v ZIPu chybí $soubor"; exit 1; }
|
||||
done
|
||||
if unzip -l amcr_viewer.zip | grep -qE '\.git'; then
|
||||
echo "::error::v ZIPu jsou git soubory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: amcr_viewer-plugin
|
||||
path: amcr_viewer.zip
|
||||
if-no-files-found: error
|
||||
@@ -1,17 +1,28 @@
|
||||
name: Release QGIS Plugin
|
||||
|
||||
# Spouští se na push tagu, ne na publikaci releasu. Organizace má zapnuté
|
||||
# immutable releases: k už publikovanému releasu nelze nic přiložit, příloha
|
||||
# tedy musí vzniknout dřív, než se release zveřejní. Workflow proto založí
|
||||
# koncept releasu i se ZIPem; text se dopisuje a publikuje ručně.
|
||||
#
|
||||
# Pozor: workflow se čte z commitu, na který tag ukazuje. Tag musí být
|
||||
# založen až na commitu, který tento soubor obsahuje.
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build-and-release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
steps:
|
||||
# 1. Stáhne kód z repozitáře
|
||||
# 1. Stáhne kód z tagu, který běh spustil
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
# 2. Vytvoří ZIP (předpokládá, že kód je ve složce 'amcr_viewer')
|
||||
- name: Zip Plugin
|
||||
@@ -20,11 +31,14 @@ jobs:
|
||||
# -r = rekurzivně, -x = ignorovat skryté git soubory
|
||||
zip -r amcr_viewer.zip amcr_viewer -x "*.git*"
|
||||
|
||||
# 3. Nahraje ZIP k Releasu
|
||||
- name: Upload Release Asset
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
# 3. Založí koncept releasu i s přílohou
|
||||
# Tagy s pomlčkou (v2.0.0-alpha.1) se označí jako pre-release.
|
||||
- name: Create draft release with asset
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||
with:
|
||||
files: amcr_viewer.zip
|
||||
draft: true
|
||||
name: AMCR Viewer ${{ github.ref_name }}
|
||||
prerelease: ${{ contains(github.ref_name, '-') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -211,3 +211,4 @@ __marimo__/
|
||||
README_files/
|
||||
README.html
|
||||
amcr_viewer.zip
|
||||
pyrefly.toml
|
||||
@@ -0,0 +1,232 @@
|
||||
# AGENTS.md
|
||||
|
||||
Pokyny pro AI agenty i lidské přispěvatele pracující v tomto repozitáři.
|
||||
Tento soubor je jediný zdroj pravdy; `CLAUDE.md` na něj pouze odkazuje.
|
||||
|
||||
## O projektu
|
||||
|
||||
**AMČR Viewer** je plugin do QGIS pro stahování a vizualizaci dat z Digitálního
|
||||
archivu Archeologické mapy ČR (AMČR / AIS CR) – akce (*Fieldwork events*),
|
||||
lokality (*Sites*) a jejich komponenty. Podporuje anonymní i přihlášený přístup
|
||||
přes AMČR účet.
|
||||
|
||||
Zdroj dat: https://digiarchiv.aiscr.cz/ · Nápověda: https://amcr-help.aiscr.cz/digiarchiv/qgis-viewer.html
|
||||
|
||||
## Zařazení v 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
|
||||
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,
|
||||
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`.
|
||||
|
||||
## Struktura repozitáře
|
||||
|
||||
```
|
||||
amcr_viewer/ # vlastní kód pluginu (toto se balí do releasu)
|
||||
amcr_viewer.py # vstupní bod pluginu, integrace do QGIS
|
||||
amcr_dialog.py # dialogy a UI (filtry, přihlášení)
|
||||
amcr_tools.py # stahování dat z API, sestavení vrstev a atributů
|
||||
amcr_codelists.py # hesláře / číselníky (codelists)
|
||||
codelists/heslar.csv # lokální kopie číselníků
|
||||
metadata.txt # metadata pluginu + verze + changelog
|
||||
i18n/ # překlady (.ts)
|
||||
*.png # ikony
|
||||
.github/workflows/ # CI – release pluginu
|
||||
README.md # uživatelská dokumentace (anglicky)
|
||||
```
|
||||
|
||||
## Konvence
|
||||
|
||||
### Jazyk
|
||||
- **Kód a identifikátory:** anglicky. Atributová pole vrstev musí být ASCII
|
||||
kompatibilní (bez diakritiky) – viz historie změn v `metadata.txt`.
|
||||
- **README a uživatelská dokumentace:** anglicky.
|
||||
- **Commity, PR a komentáře v issue:** česky.
|
||||
|
||||
### Commity
|
||||
- Styl odpovídá historii: česky, věcně, popisně; jeden commit = jedna logická
|
||||
změna.
|
||||
- První řádek stručně a výstižně (ideálně v imperativu); podrobnosti do těla.
|
||||
- Pokud je commit připraven s pomocí AI, uveď to v těle commitu nebo v popisu PR.
|
||||
|
||||
### Větve
|
||||
Konvence názvů je sladěná s hubem `aiscr-management`:
|
||||
|
||||
- **lidé:** `feat/<téma>` (nová funkce), `fix/<téma>` (oprava), `docs/<téma>`
|
||||
(dokumentace), `chore/<téma>` (údržba).
|
||||
- **AI agenti:** `agents/<jméno-agenta>/<téma>` (např. `agents/claude/oprava-pian`).
|
||||
|
||||
Další pravidla:
|
||||
|
||||
- Cílová větev pro nový vývoj je aktuální `version/v2.x.y` (ne přímo do
|
||||
výchozí větve bez PR).
|
||||
- **Nikdy** nepushuj přímo do chráněných větví; vždy přes Pull Request.
|
||||
- Standardizační / nefunkční změny drž v samostatné větvi, ať se nemíchají do
|
||||
feature PR.
|
||||
|
||||
### Pravidla pro AI agenty (git)
|
||||
- AI ve výchozím stavu zůstává u **lokální práce na aktuální větvi**.
|
||||
- Bez **výslovného pokynu** uživatele AI nestageuje (`git add`), necommituje,
|
||||
nepushuje ani samo nepřepíná/nezakládá větev pro vzdálené doručení.
|
||||
- Při výslovném požadavku na push/PR použij větev `agents/<jméno-agenta>/<téma>`;
|
||||
pokud aktuální větev tomuto vzoru neodpovídá, vyžádej si nejdřív potvrzení.
|
||||
- Vytvoření větve, stage, commit, push ani draft PR AI běžně nenabízí; zmiňuj je
|
||||
jen tehdy, když jsou pro splnění úkolu opravdu nutné.
|
||||
|
||||
### QGIS specifika
|
||||
- Minimální podporovaná verze QGIS je **3.44** (`qgisMinimumVersion` v
|
||||
`metadata.txt`); kód nesmí spoléhat na novější API.
|
||||
- Vrstvy a atributy se sestavují přes `QgsField` / QGIS API v `amcr_tools.py`.
|
||||
Při přidání atributu je potřeba doplnit ho konzistentně na všech místech:
|
||||
definice pole (`QgsField`), naplnění hodnoty z dokumentu, překlad hlavičky
|
||||
sloupce a export atributů.
|
||||
|
||||
### Kompatibilita s Qt6 / QGIS 4
|
||||
|
||||
Plugin cílí na QGIS 3.44 i na QGIS 4 (`qgisMaximumVersion=4.99.0`), tedy na
|
||||
Qt5 i Qt6 zároveň. **Tohle se drží rigorózně** – ne až před releasem, ale při
|
||||
každé změně kódu. Chování obou větví se liší tiše: pod Qt5 projde i to, co
|
||||
QGIS 4 odmítne, takže lokální „funguje mi to“ nic nedokazuje.
|
||||
|
||||
Závazná pravidla:
|
||||
|
||||
- **Nikdy neimportuj přímo z `PyQt5` ani z `PyQt6`.** Vždy přes shim
|
||||
`qgis.PyQt.*`. Ten mimo jiné pod Qt6 přetahuje `QAction`, `QActionGroup`
|
||||
a `QShortcut` z `QtGui`, takže import z `qgis.PyQt.QtWidgets` je správně.
|
||||
- **Enumy vždy plně kvalifikované (scoped).** `Qgis.MessageLevel.Info`, ne
|
||||
`Qgis.Info`; `QgsTask.Flag.CanCancel`, ne `QgsTask.CanCancel`;
|
||||
`QgsWkbTypes.GeometryType.PointGeometry`, ne `QgsWkbTypes.PointGeometry`.
|
||||
Totéž pro Qt: `Qt.CheckState.Checked`, `QDialogButtonBox.StandardButton.Ok`.
|
||||
Zkrácené tvary sice v QGIS 4.2 zatím fungují, ale oficiální kontrola je
|
||||
hlásí a do budoucna mizí.
|
||||
- **Zdrojové `.py` soubory ukládej bez BOM.** Kontrolní skript čte soubor
|
||||
jako UTF-8 bez `utf-8-sig` a na BOM spadne s
|
||||
`SyntaxError: invalid non-printable character U+FEFF`, takže se takový
|
||||
soubor **vůbec nezkontroluje**. (`codelists/heslar.csv` BOM mít smí, tam je
|
||||
kvůli Excelu.)
|
||||
- Nepoužívej API zrušená v Qt6: `exec_()`, `QRegExp`, `QDesktopWidget`,
|
||||
`QApplication.desktop()`, `Qt.MidButton`, `QFontMetrics.width()`,
|
||||
`setResizeMode`, atributy `AA_EnableHighDpiScaling` / `AA_UseHighDpiPixmaps`.
|
||||
- `supportsQt6=True` v `metadata.txt` **nepatří** – bylo zrušeno; o zařazení
|
||||
mezi „QGIS 4 Ready“ rozhoduje rozsah `qgisMinimumVersion` až
|
||||
`qgisMaximumVersion`.
|
||||
|
||||
Ověření před PR, který mění Python kód:
|
||||
|
||||
```sh
|
||||
# oficiální kontrola, kterou pouští i plugins.qgis.org (pyqgis4-checker)
|
||||
docker run --rm --pull always --user $(id -u):$(id -g) \
|
||||
--workdir /workspace/ -v "$(pwd):/workspace/" \
|
||||
ghcr.io/qgis/pyqgis4-checker:main-ubuntu \
|
||||
pyqt5_to_pyqt6.py --dry_run --logfile /workspace/pyqt6_checker.log .
|
||||
```
|
||||
|
||||
Prázdný log = čisté. Kontrola je na plugins.qgis.org informativní
|
||||
(neblokuje schválení), ale nález znamená, že plugin v QGIS 4 dříve nebo
|
||||
později přestane fungovat.
|
||||
|
||||
Když je po ruce QGIS 4 (např. flatpak `org.qgis.qgis`), ověř navíc, že se
|
||||
plugin pod Qt6 opravdu načte:
|
||||
|
||||
```sh
|
||||
flatpak run --command=sh org.qgis.qgis -c \
|
||||
'PYTHONPATH=/app/share/qgis/python python3 -c "import qgis.core"'
|
||||
```
|
||||
|
||||
## Verzování a release
|
||||
|
||||
- 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
|
||||
`changelog=` v `metadata.txt` (formát `vX.Y.Z (RRRR-MM-DD)` + odrážky).
|
||||
- Datum v changelogu ber z **deterministického zdroje**, ne z paměti, např.
|
||||
`python -c "import datetime; print(datetime.date.today().isoformat())"`.
|
||||
- Release se spouští **pushnutím tagu `vX.Y.Z`**, ne publikací releasu
|
||||
v UI. Workflow `.github/workflows/release_plugin.yml` zabalí složku
|
||||
`amcr_viewer/` do `amcr_viewer.zip` a založí **koncept** releasu i s touto
|
||||
přílohou; text se dopíše a release zveřejní ručně. Do ZIPu se nesmí dostat
|
||||
`.git*` soubory.
|
||||
- Organizace má zapnuté **immutable releases** – k publikovanému releasu už
|
||||
nelze nic přiložit. Proto příloha vzniká na konceptu, ještě před
|
||||
zveřejněním; workflow spouštěný na `release: published` by vždy selhal.
|
||||
- Workflow se čte z commitu, na který **tag ukazuje**. Tag proto zakládej až
|
||||
na commitu, který obsahuje aktuální podobu workflow – jinak se nespustí nic.
|
||||
|
||||
## Pull requesty
|
||||
|
||||
- Používej PR šablonu (`.github/pull_request_template.md`): Souhrn / Změny /
|
||||
Testování / Kontrolní seznam.
|
||||
- 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
|
||||
v `metadata.txt`, pokud měníš chování).
|
||||
- V popisu PR uveď **podíl AI** (např. „text navržen AI, ručně zkontrolováno")
|
||||
a odkaz na související issue, pokud existuje.
|
||||
|
||||
## Bezpečnost a soukromí
|
||||
|
||||
- Do promptů, příkladů ani commitů **nevkládej** ostrá produkční data, plné
|
||||
log dumpy ani reálné osobní údaje (PII).
|
||||
- **Rediguj** secrets, tokeny, API klíče a hesla z čehokoli, co posíláš AI;
|
||||
nikdy je necommituj do repozitáře (ani přihlašovací údaje k AMČR účtu).
|
||||
- Pro interní infrastrukturu (URL, hostname, prostředí) používej placeholdery,
|
||||
pokud konkrétní hodnota není nutná a povolená.
|
||||
|
||||
## Lokální ověření
|
||||
|
||||
Plugin se testuje načtením do QGIS (Plugins → Manage and Install Plugins →
|
||||
Install from ZIP, nebo nasazením složky `amcr_viewer/` do adresáře pluginů
|
||||
QGIS). **Ruční test v QGIS nic nenahrazuje** – automatické kontroly ověřují,
|
||||
že se plugin načte a že projde kontrolami kvality, ne že dělá správnou věc.
|
||||
|
||||
### Automatické kontroly
|
||||
|
||||
Workflow `.github/workflows/code_quality.yml` pouští při každém PR do `main`
|
||||
tohle:
|
||||
|
||||
| job | co dělá |
|
||||
|---|---|
|
||||
| **Lint a bezpečnost** | `check_sources.py`, bandit, detect-secrets, flake8, ruff |
|
||||
| **Kompatibilita s Qt6** | `pyqgis4-checker` v dockeru |
|
||||
| **Smoke test** | `smoke_test.py` v `qgis/qgis:ltr` i `qgis/qgis:stable` |
|
||||
| **Balíček pluginu** | sestaví ZIP, ověří obsah, přiloží jako artefakt |
|
||||
|
||||
Smoke test běží v obou podporovaných řadách: `ltr` je QGIS 3.44 na Qt5,
|
||||
`stable` je QGIS 4.x na Qt6.
|
||||
|
||||
Artefakt z posledního jobu se dá stáhnout ze stránky běhu a rovnou
|
||||
nainstalovat přes *Install from ZIP* – recenzent nemusí nic balit ručně.
|
||||
|
||||
Totéž lokálně:
|
||||
|
||||
```sh
|
||||
pip install bandit detect-secrets flake8 ruff
|
||||
|
||||
python3 tests/check_sources.py
|
||||
bandit -r amcr_viewer/
|
||||
detect-secrets scan --all-files amcr_viewer/
|
||||
flake8 --config amcr_viewer/.flake8 amcr_viewer/
|
||||
ruff check .
|
||||
|
||||
# smoke test v obou verzích QGIS (docker, bez instalace čehokoli)
|
||||
for tag in ltr stable; do
|
||||
docker run --rm -v "$PWD:/work:ro" -w /work \
|
||||
--user "$(id -u):$(id -g)" -e HOME=/tmp \
|
||||
"qgis/qgis:$tag" python3 tests/smoke_test.py
|
||||
done
|
||||
```
|
||||
|
||||
Na co si dát pozor:
|
||||
|
||||
- **`pyqgis4-checker` končí kódem 0, i když něco najde** – výsledek je jen
|
||||
v logu. Workflow proto kontroluje, že log obsahuje jen hlavičku.
|
||||
- **`detect-secrets` bez `--all-files` prohledá jen soubory sledované
|
||||
gitem** a o nesledovaném souboru mlčí. Vypadá to jako čistý výsledek.
|
||||
- **Konfigurace lintů je rozdělená schválně.** `amcr_viewer/.flake8` leží
|
||||
vedle `metadata.txt`, protože scanner na plugins.qgis.org hledá config
|
||||
soubory jen v kořeni balíčku uvnitř ZIPu; díky tomu platí stejná pravidla
|
||||
v CI, lokálně i při uploadu. Konfigurace ruffu je naopak v kořenovém
|
||||
`pyproject.toml` – ruff se do balíčku pluginu nedistribuuje.
|
||||
Viz https://plugins.qgis.org/docs/security-scanning/config-files
|
||||
- **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.
|
||||
+6
-7
@@ -20,11 +20,10 @@ identifiers:
|
||||
value: 10.5281/zenodo.18609813
|
||||
repository-code: 'https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer'
|
||||
abstract: >-
|
||||
This QGIS plugin is intended for downloading the data
|
||||
(Fieldwork events data only, at the time) from the
|
||||
Digiarchive of the Archaeological Map of the Czech
|
||||
Republic (AMCR). As of now, only publicly accessible data
|
||||
can be downloaded.
|
||||
This plugin is intended for downloading the data
|
||||
(Fieldwork events, Sites, and their Components) from
|
||||
the Digital archive of the Archaeological Map of the
|
||||
Czech Republic (https://digiarchiv.aiscr.cz/).
|
||||
license: GPL-3.0
|
||||
version: '1.0.1'
|
||||
date-released: '2026-02-11'
|
||||
version: '2.0.0'
|
||||
date-released: '2026-06-05'
|
||||
@@ -0,0 +1,6 @@
|
||||
# CLAUDE.md
|
||||
|
||||
Pokyny pro tento repozitář jsou v [`AGENTS.md`](./AGENTS.md).
|
||||
|
||||
Tento soubor je záměrně jen odkaz, aby existoval jeden zdroj pravdy a obsah se
|
||||
nerozjel. Cokoli platí pro AI agenty v tomto projektu, najdeš v `AGENTS.md`.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
|
||||
**Platform:** QGIS 3.4.x
|
||||
**Platform:** QGIS 3.44.0–4.99.0
|
||||
|
||||
**Module Type:** Data Acquisition & Visualization
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
|
||||
## 1. Overview
|
||||
|
||||
**AMCR Viewer** is a QGIS plugin designed to facilitate direct access to the Digital Archive of the Archaeological Map of the Czech Republic (AMČR). It allows researchers to **query, retrieve, and visualize *Fieldwork events* and *Sites* data (metadata and geometry) directly within the GIS environment**, eliminating the need to manually export data from the web interface. Both *Fieldwork events* and *Sites* layers may be accompanied by a *Components* layer with additional information. **Only publicly accessible data are supported at the time** (accessibility = anonymous).
|
||||
|
||||
**AMCR Viewer** is a QGIS plugin designed to facilitate direct access to the Digital Archive of the Archaeological Map of the Czech Republic (AMČR). It allows researchers to **query, retrieve, and visualize *Fieldwork events* and *Sites* data (metadata and geometry) directly within the GIS environment**, eliminating the need to manually export data from the web interface. Both *Fieldwork events* and *Sites* layers may optionally include component-level data (period and activity area) embedded directly in the attribute table. The plugin supports both **anonymous (public) access** and **authenticated access** for users with an AMČR account.
|
||||
|
||||
### Key Features
|
||||
|
||||
@@ -21,103 +20,163 @@
|
||||
* **Advanced Attribute Filtering:** Supports multi-criteria filtering using controlled vocabularies.
|
||||
* **Dynamic Geometry Retrieval:** Automatically downloads and categorizes spatial data into Point, Line, and Polygon layers.
|
||||
* **Semantic Interoperability:** Automatically translates internal system codes into human-readable labels using the AIS CR API.
|
||||
|
||||
---
|
||||
* **Authenticated Access:** Users with an AMČR account can log in to access non-public records.
|
||||
|
||||
## 2. Installation Guide
|
||||
|
||||
**Install the plugin from QGIS plugin repository.**
|
||||
**Install the latest version of the plugin from the QGIS plugin repository.**
|
||||
|
||||
**OR**
|
||||
**OR** (*in case you need older version*)
|
||||
|
||||
*1. Obtain the [plugin distribution package](https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer/releases) (ZIP archive containing the `amcr_viewer` directory).*
|
||||
*2. Launch QGIS.*
|
||||
*3. Navigate to Plugins → Manage and Install Plugins...*
|
||||
*4. Select the Install from ZIP tab.*
|
||||
*5. Locate the source ZIP file and click Install Plugin.*
|
||||
*6. Upon successful installation, the AMCR download button (load AMCR data) will appear in the interface.*
|
||||
|
||||
---
|
||||
1. *Obtain the [plugin distribution package](https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer/releases) (ZIP archive containing the `amcr_viewer` directory).*
|
||||
2. *Launch QGIS.*
|
||||
3. *Navigate to Plugins → Manage and Install Plugins...*
|
||||
4. *Select the Install from ZIP tab.*
|
||||
5. *Locate the source ZIP file and click Install Plugin.*
|
||||
6. *Upon successful installation, the AMCR Viewer button will appear in the toolbar.*
|
||||
|
||||
## 3. User Manual
|
||||
|
||||
### 3.1 Data Retrieval
|
||||
### 3.1 Authentication (Optional)
|
||||
|
||||
To initiate a search query, click either the **Stáhnout data akcí** or the **Stáhnout data lokalit** icon from the dropdown menu. The filter dialog provides the following options. Shown options vary based on the choosed tool.
|
||||
By default, the plugin accesses only publicly available records (accessibility = anonymous). To access non-public data, log in using your AMČR account:
|
||||
|
||||
* **Spatial Filter:** *Checkbox "Limit search to current map extent":* If checked, the query is restricted to the geographical area currently visible in the QGIS canvas. If unchecked, the query searches the entire database (use with caution regarding data volume).
|
||||
* It is possible to view only those Fieldwork events with positive outcome, if "Positive findings only" is checked. Only *PIANs* marked as (or rather *PIANs* belonging to Documentation units marked as) "Type of evidence" = "positive" are rendered.
|
||||
* Click the dropdown arrow on the AMCR Viewer toolbar button and select **Přihlásit se**.
|
||||
* Enter your e-mail and password. Credentials are encrypted and stored securely in the **QGIS Authentication Manager** (DPAPI on Windows, Keychain on macOS, encrypted SQLite on Linux).
|
||||
* Stored credentials are reused automatically across sessions. To update or remove them, open the login dialog again.
|
||||
|
||||
### 3.2 Data Retrieval
|
||||
|
||||
To initiate a search query, click either the **Stáhnout data akcí** or the **Stáhnout data lokalit** option from the dropdown menu. The filter dialog provides the following options. Shown options vary based on the chosen tool.
|
||||
|
||||
* **Spatial Filter:** *Checkbox "Omezit vyhledávání rozsahem okna":* If checked, the query is restricted to the geographical area currently visible in the QGIS canvas. If unchecked, the query searches the entire database (use with caution regarding data volume).
|
||||
* **Positive findings only:** If checked, only *PIANs* belonging to Documentation units marked as "Type of evidence" = "positive" are included. *(Fieldwork events only.)*
|
||||
|
||||
* **Attribute Filters:**
|
||||
* The dialog utilizes "Picker" widgets for controlled vocabularies (common: Region, District, Cadastral area, Period, Activity Area, *PIAN* accuracy; *events* related: Organisation, Researcher, Event type; *sites* related: Site type and class, Level of confidence, State of preservation).
|
||||
* Click **Select...** to open a searchable selection window. Multiple values can be selected simultaneously (Logic: OR).
|
||||
* The dialog uses "Picker" widgets for controlled vocabularies (common: Region, District, Cadastral area, Period, Activity Area, *PIAN* accuracy, Accessibility; *events* related: Organisation, Researcher, Event type; *sites* related: Site type and class, Level of confidence, State of preservation).
|
||||
* Click **Vybrat...** to open a searchable selection window. Multiple values can be selected simultaneously (Logic: OR).
|
||||
|
||||
* **Codelists (Hesláře):**
|
||||
* Controlled vocabularies are downloaded from the AMČR OAI-PMH API and cached locally in `codelists/heslar.csv`.
|
||||
* To refresh all codelists, click the **Aktualizovat hesláře 🔄** button in the filter dialog. This runs as a background task and may take a few minutes.
|
||||
|
||||
* **Fieldwork Manager (Dynamic List):**
|
||||
* Due to the dynamic nature of the persons database, the list of Fieldwork Managers is retrieved from the AIS CR servers and needs to be updated the first time (and subsequently, if there is need).
|
||||
* To refresh the list from the server, click the **Refresh (🔄)** button next to the selection field. This downloads the latest list of researchers from the API.
|
||||
* **Components:** Check **Načíst komponenty** to include period and activity area data directly in the output layers.
|
||||
> ⚠ When components are loaded, spatial features are duplicated — each feature corresponds to one component. Spatial analyses (areas, counts) may be inaccurate.
|
||||
|
||||
* **Components:** The *components* data are downloaded as well upon checking the corresponding check box. This enriches the main (*Events* and *Sites*) layers with additional information (period and activity area).
|
||||
|
||||
* If no filter is used, all accessible Fieldwork events/PIANs are returned (although the number of Fieldwork events to be loaded is capped at 20000 records; it is advisable to set at least one filter).
|
||||
* If no filter is used, all accessible Fieldwork events/PIANs are returned (the number of records is capped at 20 000; it is advisable to set at least one filter).
|
||||
|
||||
For a more in-depth tutorial refer to the [AMČR Documentation](https://amcr-help.aiscr.cz/digiarchiv/qgis-viewer.html) (only in Czech).
|
||||
|
||||
### 3.3 Layer Structure & Attributes
|
||||
|
||||
Upon successful retrieval, the plugin generates up to three temporary memory layers:
|
||||
|
||||
### 3.2 Layer Structure & Attributes
|
||||
1. **AMCR\_[Akce|Lokalita]\_Polygony**
|
||||
2. **AMCR\_[Akce|Lokalita]\_Linie**
|
||||
3. **AMCR\_[Akce|Lokalita]\_Body**
|
||||
|
||||
Upon successful retrieval, the plugin generates four temporary memory layers:
|
||||
Layers are only created if the query returns features of the corresponding geometry type. All layers share the same attribute schema.
|
||||
|
||||
1. **AMCR Plochy (Polygons)**
|
||||
2. **AMČR Linie (Lines)**
|
||||
3. **AMČR Body (Points)**
|
||||
4. **AMČR Komponenty (*Components*/no geometry)**
|
||||
#### 3.3.1 Common fields
|
||||
|
||||
The Attribute Table includes standardized fields with important metadata. The components layer has no geometry on its own and depend solely on a relation with the other three layers.
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| pian | PIAN (spatial identifier) ID |
|
||||
| presnost | Spatial deviation \[units/tens/hundreds of meters/defined by cadastre\] |
|
||||
| pian\_typ | \[point/line/polygon\] |
|
||||
| dj | Documentation unit ID |
|
||||
| typ\_dj | \[trench/event part/whole event/cadastral territory\] |
|
||||
| definicni\_body | Feature centroid in WGS-84 coordinate system |
|
||||
| akce / lokalita | Fieldwork event / Site ID |
|
||||
| odkaz\_do\_digiarchivu | Link to the record in the Digital Archive |
|
||||
| okres | District |
|
||||
| katastr | Main cadastral area |
|
||||
| dalsi\_katastry | Other cadastral areas, if the event extends beyond the main cadastre |
|
||||
| Přístupnost | Record accessibility \[A/B/C/D\] |
|
||||
|
||||
---
|
||||
#### 3.3.2 Fields related to *Fieldwork events*
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| akce\_lokalizace | Verbal description of the event location |
|
||||
| vedouci | Main fieldwork manager |
|
||||
| organizace | Organisation conducting the research |
|
||||
| specifikace\_data | \[exact date/exact years/sometime in years\] |
|
||||
| zahajeni | Event start date |
|
||||
| ukonceni | Event end date |
|
||||
| hlavni\_typ | Primary research method |
|
||||
| vedlejsi\_typ | Secondary research method |
|
||||
| zjisteni | Did the research reveal archaeological contexts? \[positive/negative\] |
|
||||
| nahrazuje\_NZ | Replaces a fieldwork report? \[yes/no\] |
|
||||
|
||||
#### 3.3.3 Fields related to *Sites*
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| nazev\_lokality | Site name |
|
||||
| popis\_lokality | Site description |
|
||||
| typ\_lokality | Site classification by definition method |
|
||||
| druh\_lokality | Site classification by the nature of identified field relics |
|
||||
| zachovalost | Site preservation state |
|
||||
|
||||
#### 3.3.4 Component fields (only when *Načíst komponenty* is checked)
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| komponenta | Component ID |
|
||||
| komponenta\_areal | Activity area \[settlement/burial area/field/…\] |
|
||||
| komponenta\_obdobi | Period \[Neolithic/High Middle Ages–Modern Period/…\] |
|
||||
|
||||
## 4. Technical Architecture
|
||||
|
||||
The plugin is developed in **Python 3** using the **PyQt5** framework for the GUI and the **Requests** library for HTTP communication.
|
||||
The plugin is developed in **Python 3** using the **PyQt6** framework for the GUI and the **Requests** library for HTTP communication.
|
||||
|
||||
> **Note:** The `requests` library is bundled with the QGIS installers for Windows and macOS. On Linux (distribution packages), it may need to be installed separately (e.g. `python3-requests`).
|
||||
|
||||
### 4.1 File Structure
|
||||
|
||||
* `amcr_viewer.py`: Entry point; handles GUI integration and initialization.
|
||||
* `amcr_dialog.py`: Manages the UI logic, including the custom `FilterableSelectionDialog` for handling large vocabularies.
|
||||
* `amcr_tools.py`: Core logic module. Handles API requests, pagination, data parsing, and vector layer generation.
|
||||
* `amcr_codelists.py`: Manages local caching of controlled vocabularies (`codelists/*.csv`).
|
||||
* `amcr_viewer.py`: Entry point; handles GUI integration, toolbar/menu setup, and login flow.
|
||||
* `amcr_dialog.py`: Manages the UI logic, including `AmcrFilterDialog`, `FilterableSelectionDialog`, and `LoginDialog`.
|
||||
* `amcr_tools.py`: Core logic module. Handles authentication, API requests, pagination, data parsing, and vector layer generation.
|
||||
* `amcr_codelists.py`: Manages local caching of controlled vocabularies (`codelists/heslar.csv`) downloaded via OAI-PMH.
|
||||
|
||||
### 4.2 Data Flow & API Integration
|
||||
|
||||
The plugin interacts with three primary endpoints of the AIS CR infrastructure:
|
||||
The plugin interacts with the following endpoints:
|
||||
|
||||
1. **Search API (Solr):**
|
||||
* Endpoint: `https://digiarchiv.aiscr.cz/api/search/query`
|
||||
* Method: `GET`
|
||||
* Parameters: `entity=akce`, `rows/page` (pagination).
|
||||
* Logic: The plugin implements a `while True` loop to handle pagination, processing data in batches of 500 records to ensure stability.
|
||||
1. **Login API:**
|
||||
* Endpoint: `https://digiarchiv.aiscr.cz/api/user/login`
|
||||
* Method: `POST`
|
||||
* Returns a session cookie used for subsequent authenticated requests.
|
||||
* Credentials are stored in the QGIS Authentication Manager; the session is restored automatically if it expires mid-download.
|
||||
|
||||
2. **Search API (Solr):**
|
||||
* Endpoint: `https://digiarchiv.aiscr.cz/api/search/query`
|
||||
* Method: `GET`
|
||||
* Parameters: `entity=akce|lokalita|pian`, `rows/page` (pagination), `mapa=true`.
|
||||
* Logic: Paginated in batches of 500 records (metadata) and 200 records (geometries). A safety cap of 20 000 records is enforced.
|
||||
|
||||
2. **Translation API:**
|
||||
* Endpoint: `https://digiarchiv.aiscr.cz/api/assets/i18n/cs.json`
|
||||
* Function: Retrieves the mapping between system codes (e.g., `HES-xxxx`) and Czech labels. This dictionary is cached in memory during the session.
|
||||
3. **Translation API:**
|
||||
* Endpoint: `https://digiarchiv.aiscr.cz/api/assets/i18n/cs.json`
|
||||
* Function: Retrieves the mapping between system codes (e.g. `HES-xxxx`) and Czech labels. Cached in memory for the session.
|
||||
|
||||
4. **Codelists API (OAI-PMH):**
|
||||
* Endpoint: `https://api.aiscr.cz/2.2/oai`
|
||||
* Used for downloading controlled vocabularies (periods, regions, organisations, etc.) on demand.
|
||||
|
||||
### 4.3 Data Persistence
|
||||
|
||||
* **Vocabularies:** Static vocabularies (e.g., Periods, Regions) are stored in `codelists/heslar.csv`.
|
||||
* **Dynamic Data:** The list of researchers is downloaded on-demand and cached in `codelists/vedouci.csv`.
|
||||
* **Vocabularies:** Stored in `codelists/heslar.csv`; updated on user request via the background task.
|
||||
* **Layers:** Output layers are created as `memory` layers. They are non-persistent and will be lost if QGIS is closed without saving.
|
||||
|
||||
### 4.4 Constraints
|
||||
|
||||
* **Record Limit:** A safety cap of 20,000 records is enforced.
|
||||
* **Batch Processing:** Geometry fetching is batched (50 IDs per request) to comply with URL length limitations and server load balancing.
|
||||
* **Record Limit:** A safety cap of 20 000 records is enforced.
|
||||
* **Batch Processing:** Geometry fetching is batched (200 IDs per request) to comply with URL length limitations and server load balancing.
|
||||
* **Component duplication:** When components are loaded, each output feature corresponds to one component rather than one documentation unit. A single PIAN may therefore appear multiple times in the layer.
|
||||
|
||||
## 6. Links and resources
|
||||
## 5. Links and resources
|
||||
|
||||
* [AMCR/Digiarchive Documentation](https://amcr-help.aiscr.cz/) (only in Czech).
|
||||
* [AMCR Viewer tutorial](https://amcr-help.aiscr.cz/digiarchiv/qgis-viewer.html) (only in Czech).
|
||||
* [Import/Export. Pluginy propojující QGIS s AMČR \[poster\]](https://zenodo.org/records/20504909) (only in Czech; valid for v1.3.2).
|
||||
@@ -0,0 +1,12 @@
|
||||
# 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
|
||||
@@ -31,6 +31,5 @@ def classFactory(iface): # pylint: disable=invalid-name
|
||||
:param iface: A QGIS interface instance.
|
||||
:type iface: QgsInterface
|
||||
"""
|
||||
#
|
||||
from .amcr_viewer import AmcrViewer
|
||||
return AmcrViewer(iface)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 533 B |
+290
-100
@@ -1,36 +1,76 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
# -*- coding: utf-8 -*-
|
||||
import csv
|
||||
import os
|
||||
import time
|
||||
import xml.etree.ElementTree as ET # nosec
|
||||
|
||||
import requests
|
||||
from qgis.core import Qgis, QgsMessageLog
|
||||
|
||||
# Define paths for the plugin and its codelists directory
|
||||
PLUGIN_DIR = os.path.dirname(__file__)
|
||||
CODELISTS_DIR = os.path.join(PLUGIN_DIR, 'codelists')
|
||||
BASE_URL_AMCR = "https://api.aiscr.cz/2.2/oai"
|
||||
BASE_URL_DA = "https://digiarchiv.aiscr.cz/api/search/query"
|
||||
OUTPUT_FILE = os.path.join(CODELISTS_DIR, 'heslar.csv')
|
||||
|
||||
slovnicek = {
|
||||
'obdobi': (BASE_URL_AMCR, 'heslo:obdobi'),
|
||||
'typ_akce': (BASE_URL_AMCR, 'heslo:akce_typ'),
|
||||
'areal': (BASE_URL_AMCR, 'heslo:areal'),
|
||||
'kraj': (BASE_URL_AMCR, 'ruian_kraj'),
|
||||
'organizace': (BASE_URL_AMCR, 'organizace'),
|
||||
'okres': (BASE_URL_AMCR, 'ruian_okres'),
|
||||
'katastr': (BASE_URL_AMCR, 'ruian_katastr'),
|
||||
'pian_presnost': (BASE_URL_AMCR, 'heslo:pian_presnost'),
|
||||
'typ_lokality': (BASE_URL_AMCR, 'heslo:lokalita_typ'),
|
||||
'druh_lokality': (BASE_URL_AMCR, 'heslo:lokalita_druh'),
|
||||
'jistota': (BASE_URL_AMCR, 'heslo:jistota_urceni'),
|
||||
'lokalita_zachovalost': (BASE_URL_AMCR, 'heslo:stav_dochovani'),
|
||||
'pristupnost': (BASE_URL_AMCR, 'heslo:pristupnost'),
|
||||
'nalez_kategorie': (BASE_URL_AMCR, 'heslo:predmet_druh_kat'),
|
||||
'druh_nalezu': (BASE_URL_AMCR, 'heslo:predmet_druh'),
|
||||
'specifikace': (BASE_URL_AMCR, 'heslo:predmet_specifikace'),
|
||||
'nalezove_okolnosti': (BASE_URL_AMCR, 'heslo:nalezove_okolnosti'),
|
||||
'vedouci': (BASE_URL_DA, 'f_vedouci'),
|
||||
'nalezce': (BASE_URL_DA, 'f_nalezce'),
|
||||
}
|
||||
|
||||
NS = {
|
||||
'oai': 'http://www.openarchives.org/OAI/2.0/',
|
||||
'dc': 'http://purl.org/dc/elements/1.1/',
|
||||
'oai_dc': 'http://www.openarchives.org/OAI/2.0/oai_dc/'
|
||||
}
|
||||
|
||||
|
||||
def ensure_codelists_dir():
|
||||
"""Creates the codelists directory if it does not exist."""
|
||||
if not os.path.exists(CODELISTS_DIR):
|
||||
os.makedirs(CODELISTS_DIR)
|
||||
|
||||
|
||||
def parse_codelist_file(filename, target_dict=None):
|
||||
"""Reads a CSV codelist file and populates the target dictionary grouped by categories."""
|
||||
"""
|
||||
Reads a CSV codelist file and populates
|
||||
the target dictionary grouped by categories.
|
||||
"""
|
||||
if target_dict is None:
|
||||
target_dict = {}
|
||||
|
||||
|
||||
path = os.path.join(CODELISTS_DIR, filename)
|
||||
|
||||
|
||||
# Return early if the file doesn't exist to avoid missing file errors
|
||||
if not os.path.exists(path):
|
||||
if not os.path.exists(path):
|
||||
return target_dict
|
||||
|
||||
|
||||
try:
|
||||
# Open the file using standard UTF-8 encoding
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
with open(path, encoding='utf-8') as f:
|
||||
reader = csv.reader(f, delimiter=';')
|
||||
|
||||
|
||||
# Skip the CSV header row
|
||||
next(reader, None)
|
||||
|
||||
next(reader, None)
|
||||
|
||||
# Iterate through rows and extract label, code, and category
|
||||
for row in reader:
|
||||
if len(row) >= 3:
|
||||
@@ -38,109 +78,259 @@ def parse_codelist_file(filename, target_dict=None):
|
||||
code = row[1].strip()
|
||||
cat = row[2].strip()
|
||||
clean = code if code else None
|
||||
|
||||
# Initialize a new dictionary for a category if encountered for the first time
|
||||
|
||||
# Initialize a new dictionary for a category if encountered
|
||||
# for the first time
|
||||
if cat not in target_dict:
|
||||
target_dict[cat] = {}
|
||||
|
||||
# Assign the extracted code to the corresponding label within the category
|
||||
|
||||
# Assign the extracted code to the corresponding label
|
||||
# within the category
|
||||
target_dict[cat][label] = clean
|
||||
|
||||
except Exception as e:
|
||||
print(f"AMČR Codelist Read Error for {filename}: {e}")
|
||||
|
||||
QgsMessageLog.logMessage(
|
||||
f"AMČR Codelist Read Error for {filename}: {e}",
|
||||
"AMČR", Qgis.MessageLevel.Critical)
|
||||
|
||||
return target_dict
|
||||
|
||||
|
||||
def load_all_data():
|
||||
"""Loads all static and dynamic codelists during plugin startup."""
|
||||
"""Loads the codelist during plugin startup."""
|
||||
ensure_codelists_dir()
|
||||
|
||||
# Initialize the base structure with empty dictionaries for all expected categories
|
||||
categorized_data = {
|
||||
'obdobi': {}, 'typ_akce': {}, 'areal': {},
|
||||
'kraj': {}, 'organizace': {}, 'okres': {}, 'katastr': {},
|
||||
'vedouci': {}, 'pian_presnost': {}, 'typ_lokality': {}, 'druh_lokality': {},
|
||||
'jistota': {}, 'lokalita_zachovalost': {}
|
||||
}
|
||||
|
||||
# Parse the default static codelist and the dynamically generated leaders codelist
|
||||
categorized_data = {k: {} for k in slovnicek}
|
||||
parse_codelist_file('heslar.csv', categorized_data)
|
||||
parse_codelist_file('vedouci.csv', categorized_data)
|
||||
|
||||
return categorized_data
|
||||
|
||||
def download_vedouci():
|
||||
"""Fetches the list of leaders from the AMČR API and saves it to a CSV file."""
|
||||
|
||||
def fetch_set(base_url, internal_name, api_set, task=None):
|
||||
dataset = []
|
||||
params_amcr = {
|
||||
"verb": "ListRecords",
|
||||
"metadataPrefix": "oai_dc",
|
||||
"set": api_set
|
||||
}
|
||||
params_da = {
|
||||
"entity": "samostatny_nalez" if internal_name == "nalezce" else "akce",
|
||||
"rows": 0,
|
||||
"noFacets": "false",
|
||||
"onlyFacets": "true"
|
||||
}
|
||||
|
||||
while True:
|
||||
# Check for cancellation at each iteration
|
||||
if task and task.isCanceled():
|
||||
return None
|
||||
|
||||
try:
|
||||
if "digiarchiv" not in base_url:
|
||||
response = requests.get(
|
||||
base_url, params=params_amcr, timeout=30
|
||||
)
|
||||
response.raise_for_status()
|
||||
root = ET.fromstring(response.content) # nosec
|
||||
|
||||
records = root.findall('.//oai:record', NS)
|
||||
for rec in records:
|
||||
metadata = rec.find('.//oai_dc:dc', NS)
|
||||
if metadata is not None:
|
||||
# Code (identifier)
|
||||
identifier_el = metadata.find('dc:identifier', NS)
|
||||
kod = (
|
||||
identifier_el.text
|
||||
if identifier_el is not None
|
||||
else ""
|
||||
)
|
||||
|
||||
# Title – filter out system labels "AMČR - ..."
|
||||
titles = metadata.findall('dc:title', NS)
|
||||
nazev = ""
|
||||
for t in titles:
|
||||
if (
|
||||
t.text
|
||||
and not t.text.startswith("AMČR -")
|
||||
and not t.text.startswith(" AMČR -")
|
||||
):
|
||||
nazev = t.text
|
||||
break
|
||||
# If no title passed the filter, fall back
|
||||
# to the first available one
|
||||
if not nazev and titles:
|
||||
nazev = titles[0].text
|
||||
|
||||
specialni_pripady = ['okres', 'katastr']
|
||||
|
||||
if internal_name in specialni_pripady:
|
||||
kod = nazev
|
||||
|
||||
if internal_name == 'pristupnost':
|
||||
kod = next(
|
||||
(
|
||||
t.text for t in titles
|
||||
if t.text
|
||||
and len(t.text) == 1
|
||||
and t.text.isalpha()
|
||||
),
|
||||
None
|
||||
)
|
||||
# Skip records without a valid one-letter code –
|
||||
# a None code would end up in the CSV and later
|
||||
# in the API filter as the string "None"
|
||||
if not kod:
|
||||
continue
|
||||
|
||||
dataset.append({
|
||||
'Název': nazev,
|
||||
'Kód': kod,
|
||||
'Kategorie': internal_name
|
||||
})
|
||||
|
||||
# Pagination
|
||||
token = root.find('.//oai:resumptionToken', NS)
|
||||
if token is not None and token.text:
|
||||
params_amcr = {
|
||||
"verb": "ListRecords",
|
||||
"resumptionToken": token.text
|
||||
}
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
break
|
||||
|
||||
else:
|
||||
response = requests.get(base_url, params=params_da, timeout=30)
|
||||
response.raise_for_status()
|
||||
data_json = response.json()
|
||||
|
||||
records = data_json['facet_counts']['facet_fields'][api_set]
|
||||
|
||||
for r in records:
|
||||
|
||||
nazev = r["name"]
|
||||
|
||||
dataset.append({
|
||||
'Název': nazev,
|
||||
'Kód': nazev,
|
||||
'Kategorie': internal_name
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
QgsMessageLog.logMessage(
|
||||
f"Chyba u setu {api_set}: {e}",
|
||||
"AMČR", Qgis.MessageLevel.Warning)
|
||||
break
|
||||
|
||||
return dataset
|
||||
|
||||
|
||||
def download_heslare(task=None):
|
||||
"""Fetches the codelists from the AMČR API and saves it to a CSV file."""
|
||||
ensure_codelists_dir()
|
||||
|
||||
# API endpoint for fetching facet data for leaders
|
||||
url = "https://digiarchiv.aiscr.cz/api/search/query?entity=akce&sort=datestamp%20desc&page=0&onlyFacets=True&rows=0"
|
||||
|
||||
try:
|
||||
# Execute the GET request with a 20-second timeout
|
||||
r = requests.get(url, timeout=20)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
|
||||
# Extract the leaders list from the JSON response using safe dict getters
|
||||
vedouci_list = data.get('facet_counts', {}).get('f_vedouci', [])
|
||||
if not vedouci_list:
|
||||
vedouci_list = data.get('facet_counts', {}).get('facet_fields', {}).get('f_vedouci', [])
|
||||
all_data = []
|
||||
total_sets = len(slovnicek)
|
||||
# index, (interni, api_nazev)
|
||||
for index, (key, value) in enumerate(slovnicek.items()):
|
||||
|
||||
csv_path = os.path.join(CODELISTS_DIR, 'vedouci.csv')
|
||||
|
||||
count = 0
|
||||
|
||||
# Open the target CSV file for writing without extra blank lines
|
||||
with open(csv_path, 'w', encoding='utf-8', newline='') as f:
|
||||
writer = csv.writer(f, delimiter=';')
|
||||
|
||||
# Write the standard header required by the parser function
|
||||
writer.writerow(['Název', 'Kód', 'Kategorie'])
|
||||
|
||||
# Iterate through the API results and format them for the CSV
|
||||
for item in vedouci_list:
|
||||
name = None
|
||||
if isinstance(item, dict):
|
||||
name = item.get('name')
|
||||
elif isinstance(item, str):
|
||||
name = item
|
||||
|
||||
# Ignore pure numbers (which are usually counts) and write valid names
|
||||
if name and not str(name).isdigit():
|
||||
writer.writerow([name, name, 'vedouci'])
|
||||
count += 1
|
||||
|
||||
return True, f"Staženo {count} jmen."
|
||||
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
base_url = value[0]
|
||||
interni = key
|
||||
api_nazev = value[1]
|
||||
|
||||
# Initialize global codelist data when the module is imported
|
||||
_DATA = load_all_data()
|
||||
# Check if the user cancelled the task via the QGIS taskbar
|
||||
if task and task.isCanceled():
|
||||
return False
|
||||
|
||||
# Safely extract individual categories into global variables for easy access across the plugin
|
||||
OBDOBI = _DATA.get('obdobi', {})
|
||||
TYP_AKCE = _DATA.get('typ_akce', {})
|
||||
AREAL = _DATA.get('areal', {})
|
||||
KRAJE = _DATA.get('kraj', {})
|
||||
ORGANIZACE = _DATA.get('organizace', {})
|
||||
OKRESY = _DATA.get('okres', {})
|
||||
KATASTRY = _DATA.get('katastr', {})
|
||||
VEDOUCI = _DATA.get('vedouci', {})
|
||||
PIAN_PRESNOST = _DATA.get('pian_presnost', {})
|
||||
TYP_LOKALITY = _DATA.get('typ_lokality', {})
|
||||
DRUH_LOKALITY = _DATA.get('druh_lokality', {})
|
||||
JISTOTA = _DATA.get('jistota', {})
|
||||
LOKALITA_ZACHOVALOST = _DATA.get('lokalita_zachovalost', {})
|
||||
QgsMessageLog.logMessage(
|
||||
f"Zpracovávám kategorii: {interni}...",
|
||||
"AMČR", Qgis.MessageLevel.Info)
|
||||
|
||||
def refresh_vedouci_cache():
|
||||
"""Reloads only the 'vedouci.csv' file to quickly update the cache without full initialization."""
|
||||
# Parse only the targeted file containing the updated leaders
|
||||
temp_data = parse_codelist_file('vedouci.csv')
|
||||
new_vedouci = temp_data.get('vedouci', {})
|
||||
|
||||
# Clear the existing global dictionary and update it with the fresh data
|
||||
# Pass the task correctly to the updated fetch function
|
||||
data = fetch_set(base_url, interni, api_nazev, task=task)
|
||||
|
||||
if data is None:
|
||||
return False # Cancelled mid-download
|
||||
|
||||
all_data.extend(data)
|
||||
|
||||
# Report progress (0-100)
|
||||
if task:
|
||||
progress = (index + 1) / total_sets * 100
|
||||
task.setProgress(progress)
|
||||
|
||||
# Save to CSV
|
||||
with open(OUTPUT_FILE, 'w', newline='', encoding='utf-8-sig') as f:
|
||||
fieldnames = ['Název', 'Kód', 'Kategorie']
|
||||
writer = csv.DictWriter(f, fieldnames=fieldnames, delimiter=';')
|
||||
writer.writeheader()
|
||||
writer.writerows(all_data)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def refresh_globals():
|
||||
"""Reloads data from files into the global variables."""
|
||||
data = load_all_data()
|
||||
|
||||
OBDOBI.clear()
|
||||
OBDOBI.update(data.get('obdobi', {}))
|
||||
TYP_AKCE.clear()
|
||||
TYP_AKCE.update(data.get('typ_akce', {}))
|
||||
AREAL.clear()
|
||||
AREAL.update(data.get('areal', {}))
|
||||
KRAJE.clear()
|
||||
KRAJE.update(data.get('kraj', {}))
|
||||
ORGANIZACE.clear()
|
||||
ORGANIZACE.update(data.get('organizace', {}))
|
||||
OKRESY.clear()
|
||||
OKRESY.update(data.get('okres', {}))
|
||||
KATASTRY.clear()
|
||||
KATASTRY.update(data.get('katastr', {}))
|
||||
VEDOUCI.clear()
|
||||
VEDOUCI.update(new_vedouci)
|
||||
|
||||
return len(VEDOUCI)
|
||||
VEDOUCI.update(data.get('vedouci', {}))
|
||||
PIAN_PRESNOST.clear()
|
||||
PIAN_PRESNOST.update(data.get('pian_presnost', {}))
|
||||
TYP_LOKALITY.clear()
|
||||
TYP_LOKALITY.update(data.get('typ_lokality', {}))
|
||||
DRUH_LOKALITY.clear()
|
||||
DRUH_LOKALITY.update(data.get('druh_lokality', {}))
|
||||
JISTOTA.clear()
|
||||
JISTOTA.update(data.get('jistota', {}))
|
||||
LOKALITA_ZACHOVALOST.clear()
|
||||
LOKALITA_ZACHOVALOST.update(data.get('lokalita_zachovalost', {}))
|
||||
PRISTUPNOST.clear()
|
||||
PRISTUPNOST.update(data.get('pristupnost', {}))
|
||||
NALEZ_KATEGORIE.clear()
|
||||
NALEZ_KATEGORIE.update(data.get('nalez_kategorie', {}))
|
||||
DRUH_NALEZU.clear()
|
||||
DRUH_NALEZU.update(data.get('druh_nalezu', {}))
|
||||
SPECIFIKACE.clear()
|
||||
SPECIFIKACE.update(data.get('specifikace', {}))
|
||||
NALEZOVE_OKOLNOSTI.clear()
|
||||
NALEZOVE_OKOLNOSTI.update(data.get('nalezove_okolnosti', {}))
|
||||
NALEZCE.clear()
|
||||
NALEZCE.update(data.get('nalezce', {}))
|
||||
|
||||
|
||||
# Initialize empty dicts that will be populated immediately below
|
||||
OBDOBI = {}
|
||||
TYP_AKCE = {}
|
||||
AREAL = {}
|
||||
KRAJE = {}
|
||||
ORGANIZACE = {}
|
||||
OKRESY = {}
|
||||
KATASTRY = {}
|
||||
VEDOUCI = {}
|
||||
PIAN_PRESNOST = {}
|
||||
TYP_LOKALITY = {}
|
||||
DRUH_LOKALITY = {}
|
||||
JISTOTA = {}
|
||||
LOKALITA_ZACHOVALOST = {}
|
||||
PRISTUPNOST = {}
|
||||
NALEZ_KATEGORIE = {}
|
||||
DRUH_NALEZU = {}
|
||||
SPECIFIKACE = {}
|
||||
NALEZOVE_OKOLNOSTI = {}
|
||||
NALEZCE = {}
|
||||
|
||||
refresh_globals()
|
||||
+870
-121
File diff suppressed because it is too large.
Load diff
+1149
-363
File diff suppressed because it is too large.
Load diff
+114
-38
@@ -1,35 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
|
||||
from qgis.PyQt.QtGui import QIcon
|
||||
from qgis.PyQt.QtWidgets import QMenu, QAction, QToolButton, QDialog
|
||||
|
||||
from .amcr_tools import load_amcr_data
|
||||
from .amcr_dialog import AmcrFilterDialog
|
||||
from .resources import *
|
||||
import os.path
|
||||
|
||||
from qgis.core import Qgis
|
||||
from qgis.PyQt.QtCore import QCoreApplication, QSettings, QTranslator, QUrl
|
||||
from qgis.PyQt.QtGui import QDesktopServices, QIcon
|
||||
from qgis.PyQt.QtWidgets import QAction, QDialog, QMenu, QToolButton
|
||||
|
||||
from .amcr_dialog import AmcrFilterDialog, LoginDialog
|
||||
from .amcr_tools import load_amcr_data, login_to_api
|
||||
|
||||
|
||||
class AmcrViewer:
|
||||
"""
|
||||
Main plugin class that manages the GUI elements, menu entries,
|
||||
Main plugin class that manages the GUI elements, menu entries,
|
||||
and coordinates the flow between user input and data processing.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, iface):
|
||||
"""
|
||||
Constructor initializes the connection to QGIS interface and sets up
|
||||
Constructor initializes the connection to QGIS interface and sets up
|
||||
internationalization (i18n).
|
||||
"""
|
||||
self.iface = iface
|
||||
self.plugin_dir = os.path.dirname(__file__)
|
||||
|
||||
# Determine the user's locale to load appropriate translation files
|
||||
locale = QSettings().value('locale/userLocale')[0:2]
|
||||
|
||||
# Determine the user's locale to load appropriate translation files.
|
||||
# The setting may be missing (None) on a fresh QGIS install.
|
||||
locale = str(QSettings().value('locale/userLocale') or 'en')[0:2]
|
||||
locale_path = os.path.join(
|
||||
self.plugin_dir,
|
||||
'i18n',
|
||||
'AmcrViewer_{}.qm'.format(locale))
|
||||
f'AmcrViewer_{locale}.qm'
|
||||
)
|
||||
|
||||
# Install the translator if a translation file for the current locale exists
|
||||
# Install the translator if a translation file
|
||||
# for the current locale exists
|
||||
if os.path.exists(locale_path):
|
||||
self.translator = QTranslator()
|
||||
self.translator.load(locale_path)
|
||||
@@ -37,18 +42,21 @@ class AmcrViewer:
|
||||
|
||||
# Initialize internal state
|
||||
self.actions = []
|
||||
self.menu = self.tr(u'&AMČR Viewer')
|
||||
self.menu = self.tr('&AMČR Viewer')
|
||||
self.first_start = None
|
||||
|
||||
def tr(self, message):
|
||||
"""Helper method for translating strings within the AmcrViewer context."""
|
||||
"""
|
||||
Helper method for translating strings within
|
||||
the AmcrViewer context.
|
||||
"""
|
||||
return QCoreApplication.translate('AmcrViewer', message)
|
||||
|
||||
def add_action(self, icon_path, text, callback, enabled_flag=True,
|
||||
add_to_menu=True, add_to_toolbar=True, status_tip=None,
|
||||
whats_this=None, parent=None):
|
||||
"""
|
||||
Helper method to create QActions and automatically register them
|
||||
Helper method to create QActions and automatically register them
|
||||
into the QGIS Menu and Toolbar.
|
||||
"""
|
||||
icon = QIcon(icon_path)
|
||||
@@ -69,29 +77,33 @@ class AmcrViewer:
|
||||
if add_to_menu:
|
||||
self.iface.addPluginToMenu(self.menu, action)
|
||||
|
||||
# Store only actions that are directly attached to the QGIS UI for later cleanup
|
||||
# Store only actions that are directly attached
|
||||
# to the QGIS UI for later cleanup
|
||||
if add_to_toolbar or add_to_menu:
|
||||
self.actions.append(action)
|
||||
|
||||
|
||||
return action
|
||||
|
||||
def initGui(self):
|
||||
"""
|
||||
Called when the plugin is loaded. Creates the menu structure,
|
||||
Called when the plugin is loaded. Creates the menu structure,
|
||||
sub-actions, and the dropdown tool button in the toolbar.
|
||||
"""
|
||||
# Define paths for action-specific icons
|
||||
icon_akce_path = os.path.join(self.plugin_dir, 'akce.png')
|
||||
icon_pas_path = os.path.join(self.plugin_dir, 'sn.png')
|
||||
icon_lokality_path = os.path.join(self.plugin_dir, 'lokality.png')
|
||||
icon_amcr_help_path = os.path.join(self.plugin_dir, 'amcr-help.png')
|
||||
|
||||
# 1. Create a container menu for the plugin
|
||||
self.plugin_menu = QMenu()
|
||||
|
||||
# 2. Create sub-actions (Download Projects / Download Sites)
|
||||
# add_to_menu/toolbar is False because these go into our custom dropdown menu
|
||||
# add_to_menu/toolbar is False because these go into our
|
||||
# custom dropdown menu
|
||||
self.action_download_akce = self.add_action(
|
||||
icon_path=icon_akce_path,
|
||||
text=self.tr(u'Stáhnout data akcí | AMČR Viewer'),
|
||||
text=self.tr('Stáhnout data akcí | AMČR Viewer'),
|
||||
callback=lambda checked=False: self.run_download('akce'),
|
||||
parent=self.iface.mainWindow(),
|
||||
add_to_menu=False,
|
||||
@@ -99,19 +111,54 @@ class AmcrViewer:
|
||||
)
|
||||
self.plugin_menu.addAction(self.action_download_akce)
|
||||
|
||||
self.action_download_pas = self.add_action(
|
||||
icon_path=icon_pas_path,
|
||||
text=self.tr('Stáhnout data samostatných nálezů | AMČR Viewer'),
|
||||
callback=lambda checked=False: self.run_download(
|
||||
'samostatny_nalez'),
|
||||
parent=self.iface.mainWindow(),
|
||||
add_to_menu=False,
|
||||
add_to_toolbar=False
|
||||
)
|
||||
self.plugin_menu.addAction(self.action_download_pas)
|
||||
|
||||
self.action_download_lokality = self.add_action(
|
||||
icon_path=icon_lokality_path,
|
||||
text=self.tr(u'Stáhnout data lokalit | AMČR Viewer'),
|
||||
callback=lambda checked=False: self.run_download('lokalita'),
|
||||
text=self.tr('Stáhnout data lokalit | AMČR Viewer'),
|
||||
callback=lambda checked=False: self.run_download('lokalita'),
|
||||
parent=self.iface.mainWindow(),
|
||||
add_to_menu=False,
|
||||
add_to_toolbar=False
|
||||
)
|
||||
self.plugin_menu.addAction(self.action_download_lokality)
|
||||
|
||||
self.action_login_dialog = self.add_action(
|
||||
icon_path=icon_pas_path,
|
||||
text=self.tr('Přihlásit se | AMČR Viewer'),
|
||||
callback=lambda checked=False: self.login(),
|
||||
parent=self.iface.mainWindow(),
|
||||
add_to_menu=False,
|
||||
add_to_toolbar=False
|
||||
)
|
||||
self.plugin_menu.addAction(self.action_login_dialog)
|
||||
|
||||
self.action_amcr_help = self.add_action(
|
||||
icon_path=icon_amcr_help_path,
|
||||
text=self.tr('Nápověda AMČR Help | AMČR Viewer'),
|
||||
callback=lambda checked=False: self.open_help(),
|
||||
parent=self.iface.mainWindow(),
|
||||
add_to_menu=False,
|
||||
add_to_toolbar=False
|
||||
)
|
||||
self.plugin_menu.addAction(self.action_amcr_help)
|
||||
|
||||
# 3. Create the main project action and attach the menu to it
|
||||
main_icon = QIcon(icon_akce_path)
|
||||
self.main_action = QAction(main_icon, 'AMČR Viewer', self.iface.mainWindow())
|
||||
self.main_action = QAction(
|
||||
main_icon,
|
||||
'AMČR Viewer',
|
||||
self.iface.mainWindow()
|
||||
)
|
||||
self.main_action.setMenu(self.plugin_menu)
|
||||
self.iface.addPluginToMenu(self.menu, self.main_action)
|
||||
|
||||
@@ -120,16 +167,19 @@ class AmcrViewer:
|
||||
self.tool_button = QToolButton()
|
||||
self.tool_button.setMenu(self.plugin_menu)
|
||||
self.tool_button.setDefaultAction(self.action_download_akce)
|
||||
self.tool_button.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
|
||||
|
||||
# Add the widget directly to the toolbar and store the reference for cleanup
|
||||
self.tool_button.setPopupMode(
|
||||
QToolButton.ToolButtonPopupMode.MenuButtonPopup
|
||||
)
|
||||
|
||||
# Add the widget directly to the toolbar
|
||||
# and store the reference for cleanup
|
||||
self.toolbar_action = self.iface.addToolBarWidget(self.tool_button)
|
||||
|
||||
|
||||
self.first_start = True
|
||||
|
||||
def unload(self):
|
||||
"""
|
||||
Called when the plugin is disabled or removed.
|
||||
Called when the plugin is disabled or removed.
|
||||
Ensures all GUI elements are removed from QGIS to avoid ghost icons.
|
||||
"""
|
||||
# 1. Remove the custom entry from the main 'Plugins' menu
|
||||
@@ -145,7 +195,7 @@ class AmcrViewer:
|
||||
self.iface.removePluginMenu(self.menu, action)
|
||||
self.iface.removeToolBarIcon(action)
|
||||
self.actions.clear()
|
||||
|
||||
|
||||
# 4. Reset map tools if currently active
|
||||
if hasattr(self, 'tool'):
|
||||
self.iface.mapCanvas().unsetMapTool(self.tool)
|
||||
@@ -153,19 +203,45 @@ class AmcrViewer:
|
||||
# --- Data downloading ---
|
||||
def run_download(self, typ_dat):
|
||||
"""
|
||||
Triggered by menu/toolbar actions. Opens the filter dialog and
|
||||
Triggered by menu/toolbar actions. Opens the filter dialog and
|
||||
hands off the parameters to the data loader.
|
||||
"""
|
||||
# Open the specific filter dialog (Projects vs Sites)
|
||||
dlg = AmcrFilterDialog(typ_dat)
|
||||
result = dlg.exec()
|
||||
|
||||
# If user confirmed the dialog (OK button), gather filters and load data
|
||||
|
||||
# If user confirmed the dialog (OK button),
|
||||
# gather filters and load data
|
||||
if result == QDialog.DialogCode.Accepted:
|
||||
filters = dlg.get_filters()
|
||||
bbox = dlg.get_bbox()
|
||||
komponenty = dlg.get_komponenty()
|
||||
|
||||
# Access the map canvas and start the fetch/render process from amcr_tools
|
||||
|
||||
# Access the map canvas and start
|
||||
# the fetch/render process from amcr_tools
|
||||
canvas = self.iface.mapCanvas()
|
||||
load_amcr_data(canvas, bbox, filters, typ_dat, komponenty)
|
||||
load_amcr_data(canvas, bbox, filters, typ_dat, komponenty)
|
||||
|
||||
def login(self):
|
||||
dlg = LoginDialog(parent=self.iface.mainWindow())
|
||||
result = dlg.exec()
|
||||
if result == QDialog.DialogCode.Accepted:
|
||||
username, password = LoginDialog.get_credentials()
|
||||
session = login_to_api(username, password)
|
||||
if session:
|
||||
self.iface.messageBar().pushMessage(
|
||||
"AMČR",
|
||||
"Přihlášení proběhlo úspěšně.",
|
||||
level=Qgis.MessageLevel.Success
|
||||
)
|
||||
else:
|
||||
self.iface.messageBar().pushMessage(
|
||||
"AMČR",
|
||||
"Přihlášení se nezdařilo – viz záložka AMČR login "
|
||||
"v panelu Zprávy.",
|
||||
level=Qgis.MessageLevel.Critical
|
||||
)
|
||||
|
||||
def open_help(self):
|
||||
help_url = "https://amcr-help.aiscr.cz/digiarchiv/qgis-viewer.html"
|
||||
QDesktopServices.openUrl(QUrl(help_url))
|
||||
+16863
-13647
File diff suppressed because it is too large.
Load diff
@@ -5,14 +5,14 @@
|
||||
|
||||
[general]
|
||||
name=AMČR Viewer
|
||||
qgisMinimumVersion=3.4.0
|
||||
qgisMinimumVersion=3.44.0
|
||||
qgisMaximumVersion=4.99.0
|
||||
description=Viewing and downloading the AMČR data.
|
||||
version=1.3.2
|
||||
version=2.1.2
|
||||
author=David Spáčil
|
||||
email=spacil@arub.cz
|
||||
|
||||
about=This plugin is intended for downloading the data (Fieldwork events, Sites and their Components) from the Digiarchive of the Archaeological Map of the Czech Republic (https://digiarchiv.aiscr.cz/). As of now, only publicly accessible data can be downloaded.
|
||||
about=This plugin is intended for downloading the data (Fieldwork events, Sites, and their Components) from the Digital archive of the Archaeological Map of the Czech Republic (https://digiarchiv.aiscr.cz/).
|
||||
|
||||
tracker=https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer/issues
|
||||
repository=https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer
|
||||
@@ -22,14 +22,44 @@ repository=https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer
|
||||
|
||||
hasProcessingProvider=no
|
||||
# Uncomment the following line and add your changelog:
|
||||
# changelog=
|
||||
changelog=
|
||||
Plný seznam změn v češtině je dostupný zde: https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer/releases/tag/v2.1.1
|
||||
v2.1.2 (2026-09-01)
|
||||
* Qt6 compatibility
|
||||
* Code clean-up
|
||||
v2.1.1 (2026-09-01)
|
||||
* Added download of Individual finds (PAS), including a dedicated menu entry
|
||||
* Added filtering by date
|
||||
* Added filtering of project fieldwork events and a Project column in the attribute table
|
||||
* Codelists are now built from the Digiarchiv API as well, not only from OAI-PMH; persons come from its facets by role (finder, fieldwork leader)
|
||||
* Updated the bundled codelist file
|
||||
* Filter dialog is now scrollable
|
||||
* Empty API results are now diagnosable from the Messages panel: the query URL and API errors are logged
|
||||
* Code clean-up
|
||||
v2.0.2 (2026-06-13)
|
||||
* Plugin-wide fixes and optimalizations (details https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer/pull/49)
|
||||
v2.0.1 (2026-06-10)
|
||||
* Fixed QGIS minimum version
|
||||
v2.0.0 (2026-06-05)
|
||||
* Added warning regarding the feature duplication when loading components to the dialog
|
||||
* Added Help button to the plugin menu
|
||||
* Code clean-up
|
||||
v2.0.0-alpha.4 (2026-06-03)
|
||||
* Backend filtering of the results based on the component-related filters improvement (plugin not only loads the results from API, it filters them further)
|
||||
v2.0.0-alpha.2–3 (2026-05-19)
|
||||
* Security vulnerabilities fix
|
||||
v2.0.0-alpha.1 (2026-05-19):
|
||||
* Attribute fields renamed to be ASCII compliant
|
||||
* Codelist update; codelist can be recompiled from AMČR API
|
||||
* Base element changed from Documentation Unit to Component if user asks for components to simplify result filtering
|
||||
* Plugin now supports logging with an AMČR account and enables the downloading of Events and Sites available to the roles Researcher and higher
|
||||
|
||||
# Tags are comma separated with spaces allowed
|
||||
tags=python,AMCR,AIS CR,archaeology,PIAN,AMČR
|
||||
tags=python,AMCR,AIS CR,archaeology,PIAN,AMČR,archeologie
|
||||
|
||||
homepage=https://amcr-help.aiscr.cz/digiarchiv/qgis-viewer.html
|
||||
category=Vector
|
||||
icon=download.png
|
||||
icon=akce.png
|
||||
# experimental flag
|
||||
experimental=False
|
||||
|
||||
@@ -41,9 +71,6 @@ deprecated=False
|
||||
# Check the documentation for more information.
|
||||
# plugin_dependencies=
|
||||
|
||||
# Category of the plugin: Raster, Vector, Database or Web
|
||||
# category=
|
||||
|
||||
# If the plugin can run on QGIS Server.
|
||||
server=False
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 871 B |
@@ -0,0 +1,44 @@
|
||||
# Konfigurace lintů pro tento repozitář.
|
||||
#
|
||||
# Plugin se nedistribuuje jako Python balíček (do QGISu jde ZIP složky
|
||||
# amcr_viewer/), takže tenhle soubor nic nebalí ani neinstaluje – slouží
|
||||
# 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 flake8 je záměrně jinde: v amcr_viewer/.flake8, protože ji
|
||||
# musí najít i scanner na plugins.qgis.org.
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 79
|
||||
# QGIS 3.44 běží na Pythonu 3.9 a novějším
|
||||
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]
|
||||
select = [
|
||||
"E", # pycodestyle – chyby
|
||||
"W", # pycodestyle – varování
|
||||
"F", # pyflakes
|
||||
"I", # pořadí importů
|
||||
"UP", # zastaralé konstrukce
|
||||
"B", # bugbear
|
||||
"C4", # comprehensions
|
||||
"SIM", # zjednodušení
|
||||
"RET", # návratové hodnoty
|
||||
"BLE", # holé except
|
||||
]
|
||||
ignore = [
|
||||
# Hlavička "# -*- coding: utf-8 -*-" je konvence šablony Plugin
|
||||
# Builderu a drží se v celém projektu jednotně.
|
||||
"UP009",
|
||||
# "except Exception" je v pluginu záměr: výjimka nesmí propadnout do
|
||||
# QGISu, chyba se uživateli ukáže v liště zpráv.
|
||||
"BLE001",
|
||||
# Obě dotčená místa mají ke každé větvi vysvětlující komentář,
|
||||
# sloučením do jednoho výrazu by se čitelnost zhoršila.
|
||||
"SIM103",
|
||||
# contextlib.suppress() by kvůli jednomu místu přidal import a odsunul
|
||||
# komentář, který vysvětluje, proč tam ta výjimka je.
|
||||
"SIM105",
|
||||
]
|
||||
+23
-7
@@ -9,7 +9,7 @@
|
||||
id="svg1"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="icon.svg"
|
||||
inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
|
||||
inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -24,12 +24,12 @@
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="22.627417"
|
||||
inkscape:cx="6.8059028"
|
||||
inkscape:cy="14.606174"
|
||||
inkscape:cx="0.57452426"
|
||||
inkscape:cy="15.556349"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1009"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-height="1131"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" /><defs
|
||||
id="defs1"><clipPath
|
||||
@@ -96,4 +96,20 @@
|
||||
d="M 3.3486328 2.4970052 L 3.3486328 3.8323242 L 2.7424683 3.8323242 L 4.2085286 5.738151 L 5.674589 3.8323242 L 5.0684245 3.8323242 L 5.0684245 2.4970052 L 3.3486328 2.4970052 z "
|
||||
inkscape:export-filename="path32.png"
|
||||
inkscape:export-xdpi="3000"
|
||||
inkscape:export-ydpi="3000" /></g></svg>
|
||||
inkscape:export-ydpi="3000" /><rect
|
||||
style="opacity:1;fill:#85140e;fill-opacity:1;stroke:none;stroke-width:0.414999;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;paint-order:stroke fill markers"
|
||||
id="rect1"
|
||||
width="1.672105"
|
||||
height="1.2511554"
|
||||
x="-4.501821"
|
||||
y="4.2814822" /><text
|
||||
xml:space="preserve"
|
||||
style="font-size:0.705556px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;opacity:1;fill:#85140e;fill-opacity:1;stroke:none;stroke-width:0.414999;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;paint-order:stroke fill markers"
|
||||
x="-2.6360023"
|
||||
y="5.1136947"
|
||||
id="text1"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1"
|
||||
style="font-size:0.705556px;fill:#85140e;fill-opacity:1;stroke-width:0.415"
|
||||
x="-2.6360023"
|
||||
y="5.1136947">lokality</tspan></text></g></svg>
|
||||
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 7.2 KiB |
@@ -0,0 +1,85 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Repository hygiene rules that need no QGIS and therefore run first.
|
||||
|
||||
Each rule guards a mistake that has already happened here at least once,
|
||||
or one the plugins.qgis.org file analysis reports:
|
||||
|
||||
* a UTF-8 BOM makes the official pyqgis4-checker skip the file entirely,
|
||||
so a broken file looks clean – it is silent, which is what makes it bad
|
||||
* a direct PyQt5/PyQt6 import breaks the other Qt version
|
||||
* an executable or hidden file in the package is reported on upload
|
||||
|
||||
Run it from the repository root:
|
||||
|
||||
python3 tests/check_sources.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
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
|
||||
PODEZRELE = {".exe", ".dll", ".so", ".dylib", ".sh", ".bat", ".cmd",
|
||||
".pyc", ".pyd", ".jar", ".bin"}
|
||||
|
||||
PRIMY_IMPORT = re.compile(r"^\s*(?:from|import)\s+PyQt[56]\b", re.MULTILINE)
|
||||
|
||||
nalezy = []
|
||||
|
||||
|
||||
def zdrojaky():
|
||||
for adresar, _, soubory in os.walk(BALICEK):
|
||||
for soubor in sorted(soubory):
|
||||
if soubor.endswith(".py"):
|
||||
yield os.path.join(adresar, soubor)
|
||||
|
||||
|
||||
def vsechny_soubory():
|
||||
for adresar, _, soubory in os.walk(BALICEK):
|
||||
for soubor in sorted(soubory):
|
||||
yield os.path.join(adresar, soubor)
|
||||
|
||||
|
||||
def zkratka(cesta):
|
||||
return os.path.relpath(cesta, ROOT)
|
||||
|
||||
|
||||
for cesta in zdrojaky():
|
||||
with open(cesta, "rb") as f:
|
||||
zacatek = f.read(3)
|
||||
if zacatek == b"\xef\xbb\xbf":
|
||||
nalezy.append(f"{zkratka(cesta)}: UTF-8 BOM na začátku souboru")
|
||||
|
||||
with open(cesta, encoding="utf-8-sig") as f:
|
||||
text = f.read()
|
||||
for shoda in PRIMY_IMPORT.finditer(text):
|
||||
radek = text[:shoda.start()].count("\n") + 1
|
||||
nalezy.append(f"{zkratka(cesta)}:{radek}: přímý import z PyQt5/PyQt6, "
|
||||
f"použij shim qgis.PyQt")
|
||||
|
||||
for cesta in vsechny_soubory():
|
||||
jmeno = os.path.basename(cesta)
|
||||
rezim = os.stat(cesta).st_mode
|
||||
if rezim & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
|
||||
nalezy.append(f"{zkratka(cesta)}: spustitelná práva "
|
||||
f"({stat.filemode(rezim)})")
|
||||
if jmeno.startswith(".") and jmeno not in POVOLENE_SKRYTE:
|
||||
nalezy.append(f"{zkratka(cesta)}: skrytý soubor v balíčku pluginu")
|
||||
if os.path.splitext(jmeno)[1].lower() in PODEZRELE:
|
||||
nalezy.append(f"{zkratka(cesta)}: podezřelý typ souboru")
|
||||
|
||||
if nalezy:
|
||||
print("Nálezy:")
|
||||
for nalez in nalezy:
|
||||
print(f" {nalez}")
|
||||
sys.exit(1)
|
||||
print("Kontrola zdrojáků: bez nálezů")
|
||||
@@ -0,0 +1,130 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Smoke test: loads the plugin inside a real QGIS and exercises the parts
|
||||
that differ between Qt5 and Qt6.
|
||||
|
||||
It is deliberately offline – no request ever leaves the machine, so the
|
||||
test says nothing about the AMCR API, only about the plugin loading and
|
||||
its widgets being constructible.
|
||||
|
||||
Run it from the repository root:
|
||||
|
||||
python3 tests/smoke_test.py
|
||||
|
||||
QGIS must be importable (inside the qgis/qgis Docker image it already is).
|
||||
The exit code is 0 when everything passed, 1 otherwise.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
# Offscreen, otherwise the dialogs need an X server
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
selhani = []
|
||||
|
||||
|
||||
def zkouska(nazev, funkce):
|
||||
"""Runs one check and keeps going even when it raises."""
|
||||
try:
|
||||
detail = funkce()
|
||||
except Exception:
|
||||
selhani.append(nazev)
|
||||
print(f" FAIL {nazev}")
|
||||
print(traceback.format_exc().rstrip())
|
||||
else:
|
||||
print(f" OK {nazev}" + (f" – {detail}" if detail else ""))
|
||||
|
||||
|
||||
from qgis.core import ( # noqa: E402
|
||||
Qgis,
|
||||
QgsApplication,
|
||||
QgsTask,
|
||||
QgsWkbTypes,
|
||||
)
|
||||
from qgis.PyQt import QtCore # noqa: E402
|
||||
from qgis.PyQt.QtCore import QDate # noqa: E402
|
||||
|
||||
print(f"QGIS {Qgis.QGIS_VERSION.split('-')[0]} | Qt {QtCore.QT_VERSION_STR} "
|
||||
f"| PyQt {QtCore.PYQT_VERSION_STR}")
|
||||
|
||||
QgsApplication.setPrefixPath(os.environ.get("QGIS_PREFIX_PATH", "/usr"), True)
|
||||
qgs = QgsApplication([], True)
|
||||
qgs.initQgis()
|
||||
|
||||
import amcr_viewer.amcr_codelists # noqa: E402,F401
|
||||
import amcr_viewer.amcr_dialog as dialog # noqa: E402
|
||||
import amcr_viewer.amcr_tools # noqa: E402,F401
|
||||
import amcr_viewer.amcr_viewer # noqa: E402,F401
|
||||
|
||||
print(" OK import všech modulů pluginu")
|
||||
|
||||
|
||||
def enumy():
|
||||
"""
|
||||
The scoped enum forms must exist. Unscoped aliases still resolve in
|
||||
QGIS 4.2, so a plain import proves nothing – these are read explicitly.
|
||||
"""
|
||||
return (f"QgsTask.Flag.CanCancel={int(QgsTask.Flag.CanCancel)}, "
|
||||
f"PointGeometry={int(QgsWkbTypes.GeometryType.PointGeometry)}, "
|
||||
f"MessageLevel.Info={int(Qgis.MessageLevel.Info)}")
|
||||
|
||||
|
||||
def uloha():
|
||||
ukol = dialog.UpdateCodelistsTask("smoke")
|
||||
assert ukol.canCancel() is True
|
||||
return "canCancel=True"
|
||||
|
||||
|
||||
def dialogy():
|
||||
# A modal warning would block the offscreen run forever
|
||||
dialog.QMessageBox.warning = staticmethod(lambda *a, **k: None)
|
||||
popis = []
|
||||
for typ in ("akce", "lokalita", "samostatny_nalez"):
|
||||
okno = dialog.AmcrFilterDialog(typ)
|
||||
okno.show()
|
||||
QgsApplication.processEvents()
|
||||
popis.append(f"{typ}: {len(okno.date_ranges)} rozmezí")
|
||||
okno.close()
|
||||
return ", ".join(popis)
|
||||
|
||||
|
||||
def filtr_datumu():
|
||||
"""
|
||||
A half-filled range must be completed with the sentinel. The API
|
||||
rejects a one-sided range, so this is the part worth guarding.
|
||||
|
||||
The expected value is written out on purpose – comparing against
|
||||
dialog.DATE_OPEN_TO would only prove the module agrees with itself.
|
||||
"""
|
||||
okno = dialog.AmcrFilterDialog("samostatny_nalez")
|
||||
pole, _, od, _do = okno.date_ranges[0]
|
||||
od.setDate(QDate(2016, 1, 1))
|
||||
hodnota = okno.get_filters()[pole]
|
||||
assert hodnota == "2016-01-01,9999-12-31", hodnota
|
||||
|
||||
# A range left completely empty must add no filter at all
|
||||
prazdne = dialog.AmcrFilterDialog("samostatny_nalez")
|
||||
pole_prazdne = prazdne.date_ranges[0][0]
|
||||
assert pole_prazdne not in prazdne.get_filters()
|
||||
prazdne.close()
|
||||
|
||||
okno.close()
|
||||
return hodnota
|
||||
|
||||
|
||||
zkouska("scoped enumy", enumy)
|
||||
zkouska("UpdateCodelistsTask", uloha)
|
||||
zkouska("filtrační dialogy", dialogy)
|
||||
zkouska("filtr podle data", filtr_datumu)
|
||||
|
||||
qgs.exitQgis()
|
||||
|
||||
if selhani:
|
||||
print(f"\nNEPROŠLO: {', '.join(selhani)}")
|
||||
sys.exit(1)
|
||||
print("\nVše prošlo")
|
||||
Reference in new issue
Block a user