Skip to content

Commit c0d6c49

Browse files
authored
Merge pull request #237 from nuest/main
2 parents d9ff893 + 65aa6af commit c0d6c49

File tree

211 files changed

+4795
-1970
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

211 files changed

+4795
-1970
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,29 @@ jobs:
5252
python -m pip install -r requirements-dev.txt
5353
5454
- name: Run Django migrations
55+
env:
56+
DATABASE_URL: postgis://optimap:optimap@localhost:5432/optimap
5557
run: |
5658
python manage.py migrate
5759
5860
- name: Load all testdata to see if it is up to date with the Django migrations
61+
env:
62+
DATABASE_URL: postgis://optimap:optimap@localhost:5432/optimap
5963
run: |
6064
python manage.py loaddata fixtures/test_data_optimap.json
6165
python manage.py loaddata fixtures/test_data_partners.json
6266
6367
- name: Run deploy checks
68+
env:
69+
DATABASE_URL: postgis://optimap:optimap@localhost:5432/optimap
6470
run: |
6571
python -Wa manage.py check --deploy
6672
6773
- name: Run Tests
74+
env:
75+
DATABASE_URL: postgis://optimap:optimap@localhost:5432/optimap
6876
run: |
69-
coverage run --source='publications' --omit='*/migrations/**' manage.py test tests
77+
coverage run --source='works' --omit='*/migrations/**' manage.py test tests
7078
7179
- name: Check coverage and save it to files
7280
run: |

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,7 @@ publications/management/commands/marine_regions_iho.geojson
151151
publications/management/commands/world_continents.geojson
152152

153153
.claude/temp.md
154+
155+
works/management/commands/marine_regions_iho.geojson
156+
157+
works/management/commands/world_continents.geojson

CHANGELOG.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,47 @@
11
# Changelog
22

3+
All notable changes to OPTIMAP are documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
38
## [Unreleased]
49

510
### Added
611

12+
- **Geoextent API** - REST API exposing the [geoextent library](https://github.com/nuest/geoextent) for extracting geospatial and temporal extents from various file formats and remote repositories. Features include:
13+
- `/api/v1/geoextent/extract/` - Extract from uploaded files (GeoJSON, GeoTIFF, Shapefile, GeoPackage, KML, CSV, etc.)
14+
- `/api/v1/geoextent/extract-remote/` - Extract from remote repositories (Zenodo, PANGAEA, OSF, Figshare, Dryad, GFZ Data Services, Dataverse)
15+
- `/api/v1/geoextent/extract-batch/` - Batch processing of multiple files with combined extent
16+
- Multiple response formats: GeoJSON (default), WKT, WKB
17+
- Support for bbox, convex hull, temporal extent, and placename geocoding
18+
- Interactive web UI at `/geoextent/` with file upload, remote extraction, and map preview
19+
- Comprehensive documentation and integration tests
20+
- **Geoextent web interface** - Interactive tool at `/geoextent/` for extracting spatial/temporal extents from data files:
21+
- File upload with drag-and-drop support and size validation
22+
- Remote resource extraction via DOI/URL (comma-separated identifiers)
23+
- Interactive Leaflet map preview with clickable features showing properties
24+
- Parameter customization (bbox, tbox, convex hull, placename, gazetteer selection)
25+
- Response format selection (GeoJSON, WKT, WKB)
26+
- Download results in selected format
27+
- Documentation section with supported formats and providers
28+
- Added to main menu and sitemaps
29+
- **Feeds sitemap** - Dynamic `/sitemap-feeds.xml` listing all regional feeds (continents and oceans) for search engine discovery
30+
- **Wikidata export** - Export publication metadata to Wikibase/Wikidata instances:
31+
- Export works with spatial metadata to Wikidata
32+
- Support for complex geometries (points, lines, polygons, multigeometry)
33+
- Export extreme points (northernmost, southernmost, easternmost, westernmost) and geometric center
34+
- Configurable via `WIKIBASE_*` environment variables
35+
- **Geocoding/gazetteer search** - Map search functionality allowing users to search for locations by name:
36+
- Nominatim geocoder integration (default)
37+
- Optional GeoNames support (requires username configuration)
38+
- Search results displayed on map with zoom to location
39+
- Accessible via search box in map interface
40+
- **Works list with pagination** - Browse all works page at `/works/list/` with:
41+
- Configurable pagination (default 50 items per page)
42+
- User-selectable page size with min/max limits
43+
- Cached publication statistics (total works, published works, metadata completeness)
44+
- Direct links to work landing pages
745
- **Regional subscription system** - Users can subscribe to receive notifications for new publications from specific continents and oceans. Features include:
846
- Checkbox-based UI with 8 continents and 7 oceans
947
- "All Regions" checkbox to select/deselect all at once
@@ -26,6 +64,29 @@
2664

2765
### Changed
2866

67+
- **Contribution page pagination** - Added full pagination support to the contribution page (`/contribute/`) with:
68+
- Configurable page size (default 50, min 10, max 200 works per page)
69+
- User-selectable page size dropdown with automatic form submission
70+
- Full pagination controls at top and bottom (First, Previous, page numbers, Next, Last)
71+
- Shows current range (e.g., "Showing 1 to 50 of 150 works")
72+
- Fixed variable name bugs (`publication``work` throughout template)
73+
- Reuses the same pagination layout as works listing page for consistency
74+
- **Model terminology alignment** - Renamed base entity from "publications" to "works" throughout the codebase to align with [OpenAlex terminology](https://docs.openalex.org/api-entities/works):
75+
- Django app renamed from `publications/` to `works/`
76+
- `Publication` model renamed to `Work`
77+
- API endpoint changed from `/api/v1/publications/` to `/api/v1/works/`
78+
- Sitemap updated from `/sitemap-publications.xml` to `/sitemap-works.xml`
79+
- URL patterns updated from `/publication/<id>/` to `/work/<id>/`
80+
- All import statements, templates, and configuration files updated
81+
- Fresh migrations created from scratch
82+
- All test fixtures updated
83+
- **Work type taxonomy** - Added comprehensive `type` field to works using Crossref/OpenAlex controlled vocabulary:
84+
- 39 work types supported (article, book, book-chapter, dataset, preprint, dissertation, etc.)
85+
- Type set from source's `default_work_type` during harvesting
86+
- Overridden by OpenAlex API type when available
87+
- Indexed and filterable in admin interface
88+
- **Removed external CDN dependencies** - All JavaScript and CSS libraries now served locally for improved privacy, security, and offline functionality
89+
- **Improved map accessibility** - Enhanced keyboard navigation and screen reader support for map interactions
2990
- **Regional subscription email notifications** - Notification emails now group publications by region with dedicated sections for each subscribed continent or ocean. Each region section includes:
3091
- Region name and type (Continent/Ocean)
3192
- Count of new publications in that region

CLAUDE.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ Part of the KOMET project (<https://projects.tib.eu/komet>), continuing from OPT
1616
- `settings.py` - All configuration via environment variables prefixed with `OPTIMAP_`
1717
- `.env` file for local config (see `.env.example` for all available parameters)
1818

19-
- **publications/** - Main application containing all models, views, and business logic
20-
- **Models** ([models.py](publications/models.py)):
21-
- `Publication` - Core model with spatial (`GeometryCollectionField`) and temporal metadata
19+
- **works/** - Main application containing all models, views, and business logic
20+
- **Models** ([models.py](works/models.py)):
21+
- `Work` - Core model with spatial (`GeometryCollectionField`) and temporal metadata
2222
- `Source` - OAI-PMH harvesting sources
2323
- `HarvestingEvent` - Tracks harvesting jobs
2424
- `Subscription` - User subscriptions with spatial/temporal filters
2525
- `CustomUser` - Extended Django user model
2626
- `BlockedEmail`/`BlockedDomain` - Anti-spam mechanisms
27-
- **Views** ([views.py](publications/views.py)) - Handles passwordless login, subscriptions, data downloads
28-
- **Tasks** ([tasks.py](publications/tasks.py)) - Django-Q async tasks for harvesting and data export
29-
- **API** ([api.py](publications/api.py), [viewsets.py](publications/viewsets.py), [serializers.py](publications/serializers.py)) - DRF REST API at `/api/v1/`
30-
- **Feeds** ([feeds.py](publications/feeds.py), [feeds_geometry.py](publications/feeds_geometry.py)) - GeoRSS/GeoAtom feed generation
27+
- **Views** ([views.py](works/views.py)) - Handles passwordless login, subscriptions, data downloads
28+
- **Tasks** ([tasks.py](works/tasks.py)) - Django-Q async tasks for harvesting and data export
29+
- **API** ([api.py](works/api.py), [viewsets.py](works/viewsets.py), [serializers.py](works/serializers.py)) - DRF REST API at `/api/v1/`
30+
- **Feeds** ([feeds.py](works/feeds.py), [feeds_geometry.py](works/feeds_geometry.py)) - GeoRSS/GeoAtom feed generation
3131

3232
### Key Technologies
3333

@@ -38,8 +38,8 @@ Part of the KOMET project (<https://projects.tib.eu/komet>), continuing from OPT
3838

3939
### Data Flow
4040

41-
1. **Harvesting**: OAI-PMH sources → `HarvestingEvent` → parse XML → create `Publication` records with spatial/temporal metadata
42-
2. **API**: Publications exposed via REST API at `/api/v1/publications/` with spatial filtering
41+
1. **Harvesting**: OAI-PMH sources → `HarvestingEvent` → parse XML → create `Work` records with spatial/temporal metadata
42+
2. **API**: Publications exposed via REST API at `/api/v1/works/` with spatial filtering
4343
3. **Feeds**: Dynamic GeoRSS/GeoAtom feeds filtered by region or global
4444
4. **Data Export**: Scheduled tasks generate cached GeoJSON/GeoPackage dumps in `/tmp/optimap_cache/`
4545

@@ -147,7 +147,7 @@ python manage.py flush # Clear all data from database (car
147147

148148
# Shell access
149149
python manage.py shell # Django shell with models loaded
150-
python manage.py shell -c "from publications.tasks import regenerate_geojson_cache; regenerate_geojson_cache()"
150+
python manage.py shell -c "from works.tasks import regenerate_geojson_cache; regenerate_geojson_cache()"
151151
python manage.py dbshell # Direct PostgreSQL shell
152152

153153
# Development server
@@ -166,7 +166,7 @@ python -Wa manage.py test # Show deprecation warnings
166166

167167
#### Custom OPTIMAP Commands
168168

169-
Located in [publications/management/commands/](publications/management/commands/)
169+
Located in [works/management/commands/](works/management/commands/)
170170

171171
```bash
172172
# Global regions setup
@@ -222,7 +222,7 @@ python manage.py loaddata fixtures/test_data_partners.json
222222
python manage.py loaddata fixtures/test_data_global_feeds.json
223223

224224
# Manually regenerate GeoJSON/GeoPackage cache (without Django-Q)
225-
python manage.py shell -c "from publications.tasks import regenerate_geojson_cache; regenerate_geojson_cache()"
225+
python manage.py shell -c "from works.tasks import regenerate_geojson_cache; regenerate_geojson_cache()"
226226
```
227227

228228
## Important Patterns
@@ -241,7 +241,7 @@ All deployment-specific config uses `OPTIMAP_*` environment variables loaded fro
241241

242242
1. Create/configure `Source` in admin with OAI-PMH URL
243243
2. Django-Q task creates `HarvestingEvent`
244-
3. Fetch XML → parse → extract DOI, spatial, temporal metadata → save `Publication` records
244+
3. Fetch XML → parse → extract DOI, spatial, temporal metadata → save `Work` records
245245
4. Track status in `HarvestingEvent.status` (pending/in_progress/completed/failed)
246246

247247
### Authentication
@@ -271,7 +271,7 @@ All deployment-specific config uses `OPTIMAP_*` environment variables loaded fro
271271
```
272272
optimap/
273273
├── optimap/ # Django project settings
274-
├── publications/ # Main app (models, views, tasks, API)
274+
├── works/ # Main app (models, views, tasks, API)
275275
│ ├── management/commands/ # Custom Django commands
276276
│ ├── static/ # Frontend assets, logos
277277
│ └── templates/ # Django templates
@@ -366,7 +366,7 @@ GeoJSON, GeoTIFF, Shapefile, GeoPackage, KML, GML, GPX, FlatGeobuf, CSV (with la
366366

367367
### Geoextent Web UI
368368

369-
Interactive web interface at [/geoextent](publications/templates/geoextent.html) for extracting geospatial/temporal extents from data files.
369+
Interactive web interface at [/geoextent](works/templates/geoextent.html) for extracting geospatial/temporal extents from data files.
370370

371371
**Features:**
372372

@@ -383,10 +383,10 @@ Interactive web interface at [/geoextent](publications/templates/geoextent.html)
383383

384384
**Implementation:**
385385

386-
- View: [publications/views.py](publications/views.py) - `geoextent()` function
386+
- View: [works/views.py](works/views.py) - `geoextent()` function
387387
- Uses `geoextent.lib.features.get_supported_features()` to dynamically load supported formats and providers
388388
- No hardcoded format lists - always reflects current geoextent capabilities
389-
- Template: [publications/templates/geoextent.html](publications/templates/geoextent.html)
389+
- Template: [works/templates/geoextent.html](works/templates/geoextent.html)
390390
- Uses Fetch API for AJAX requests (jQuery slim doesn't include $.ajax)
391391
- Interactive file management with add/remove functionality
392392
- Multiple file selection from different locations
@@ -406,8 +406,8 @@ Size limits passed from Django settings:
406406

407407
**Navigation:**
408408

409-
- Footer link added to [publications/templates/footer.html](publications/templates/footer.html)
410-
- URL route: `path("geoextent/", views.geoextent, name="geoextent")` in [publications/urls.py](publications/urls.py)
409+
- Footer link added to [works/templates/footer.html](works/templates/footer.html)
410+
- URL route: `path("geoextent/", views.geoextent, name="geoextent")` in [works/urls.py](works/urls.py)
411411

412412
## Version Management
413413

fixtures/create_global_feeds_fixture.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@
180180
def create_source(pk, name, issn_l=None, is_oa=True):
181181
"""Create a source object."""
182182
return {
183-
"model": "publications.source",
183+
"model": "works.source",
184184
"pk": pk,
185185
"fields": {
186186
"name": name,
@@ -194,6 +194,7 @@ def create_source(pk, name, issn_l=None, is_oa=True):
194194
"is_oa": is_oa,
195195
"cited_by_count": random.randint(500, 50000),
196196
"is_preprint": random.choice([True, False]),
197+
"default_work_type": "article",
197198
}
198199
}
199200

@@ -247,7 +248,7 @@ def create_publication(pk, source_pk, title, abstract, geometry_wkt, region_desc
247248
)
248249

249250
return {
250-
"model": "publications.publication",
251+
"model": "works.work",
251252
"pk": pk,
252253
"fields": {
253254
"status": "p", # all published for UI testing
@@ -272,6 +273,7 @@ def create_publication(pk, source_pk, title, abstract, geometry_wkt, region_desc
272273
"openalex_is_retracted": is_retracted,
273274
"openalex_ids": openalex_ids,
274275
"openalex_open_access_status": openalex_open_access_status,
276+
"type": "article",
275277
}
276278
}
277279

@@ -564,7 +566,7 @@ def main():
564566
json.dump(fixture_data, f, indent=2)
565567

566568
# Calculate statistics
567-
publications = [item for item in fixture_data if item["model"] == "publications.publication"]
569+
publications = [item for item in fixture_data if item["model"] == "works.work"]
568570

569571
with_authors = sum(1 for p in publications if p["fields"]["authors"])
570572
with_keywords = sum(1 for p in publications if p["fields"]["keywords"])

0 commit comments

Comments
 (0)