44from django .contrib .auth import login , logout
55from django .shortcuts import render , redirect , get_object_or_404
66from django .core .cache import cache
7- from django .http import HttpResponseRedirect , HttpResponse , FileResponse
7+ from django .http import HttpResponseRedirect , HttpResponse , FileResponse , Http404
88from django .contrib .auth .decorators import login_required
99from django .views .decorators .http import require_GET
1010from django .core .mail import EmailMessage , send_mail , get_connection
2626from publications .models import BlockedEmail , BlockedDomain , Subscription , UserProfile , Publication
2727from django .contrib .auth import get_user_model
2828User = get_user_model ()
29- import tempfile , os
29+ import tempfile , os , glob
30+ from pathlib import Path
3031from publications .tasks import regenerate_geojson_cache , regenerate_geopackage_cache
3132from osgeo import ogr , osr
3233ogr .UseExceptions ()
4546@require_GET
4647def download_geojson (request ):
4748 """
48- Returns the cached GeoJSON file, gzipped if the client accepts it.
49+ Returns the latest GeoJSON dump file, gzipped if the client accepts it.
4950 """
50- cache_dir = os . path . join (tempfile .gettempdir (), "optimap_cache" )
51- os . makedirs ( cache_dir , exist_ok = True )
52- json_path = os . path . join ( cache_dir , 'geojson_cache.json' )
53- gzip_path = os . path . join ( cache_dir , 'geojson_cache.json.gz' )
54- if not os . path . exists ( json_path ):
55- json_path = regenerate_geojson_cache ( )
51+ cache_dir = Path (tempfile .gettempdir ()) / "optimap_cache"
52+ cache_dir . mkdir ( exist_ok = True )
53+
54+ # regenerate and find latest geojson dump
55+ path = regenerate_geojson_cache ()
56+ gzip_path = Path ( str ( path ) + ".gz" )
5657 accept_enc = request .META .get ('HTTP_ACCEPT_ENCODING' , '' )
57- if 'gzip' in accept_enc and os .path .exists (gzip_path ):
58+
59+ if 'gzip' in accept_enc and gzip_path .exists ():
5860 response = FileResponse (
5961 open (gzip_path , 'rb' ),
6062 content_type = "application/json" ,
6163 as_attachment = True ,
62- filename = "publications.geojson"
64+ filename = gzip_path . name
6365 )
6466 response ['Content-Encoding' ] = 'gzip'
6567 else :
6668 response = FileResponse (
67- open (json_path , 'rb' ),
69+ open (path , 'rb' ),
6870 content_type = "application/json" ,
6971 as_attachment = True ,
70- filename = "publications.geojson"
72+ filename = Path ( path ). name
7173 )
7274 return response
7375
76+
7477def generate_geopackage ():
7578 cache_dir = os .path .join (tempfile .gettempdir (), "optimap_cache" )
7679 os .makedirs (cache_dir , exist_ok = True )
@@ -97,7 +100,7 @@ def generate_geopackage():
97100 feat .SetField ("doi" , pub .doi or "" )
98101 feat .SetField ("source" , pub .source or "" )
99102 if pub .geometry :
100- wkb = pub .geometry .wkb # bytes
103+ wkb = pub .geometry .wkb
101104 geom = ogr .CreateGeometryFromWkb (wkb )
102105 geom .AssignSpatialReference (srs )
103106 feat .SetGeometry (geom )
@@ -110,16 +113,16 @@ def generate_geopackage():
110113@require_GET
111114def download_geopackage (request ):
112115 """
113- Returns the generated GeoPackage file as a downloadable file.
116+ Returns the latest GeoPackage dump file.
114117 """
115- filename = generate_geopackage ()
116- if not os .path .exists (filename ):
117- return HttpResponse ( "Error generating GeoPackage." , status = 500 )
118+ path = regenerate_geopackage_cache ()
119+ if not os .path .exists (path ):
120+ raise Http404 ( 'GeoPackage dump not found' )
118121 return FileResponse (
119- open (filename , 'rb' ),
122+ open (path , 'rb' ),
120123 content_type = "application/geopackage+sqlite3" ,
121124 as_attachment = True ,
122- filename = "publications.gpkg"
125+ filename = Path ( path ). name
123126 )
124127
125128
@@ -185,37 +188,54 @@ def loginres(request):
185188def privacy (request ):
186189 return render (request , 'privacy.html' )
187190
188-
189191@never_cache
190192def data (request ):
191- cache_dir = os .path .join (tempfile .gettempdir (), "optimap_cache" )
192- json_path = os .path .join (cache_dir , "geojson_cache.json" )
193- gpkg_path = os .path .join (cache_dir , "publications.gpkg" )
194-
195- # If dumps don’t exist yet, trigger one synchronously
196- if not os .path .exists (json_path ) or not os .path .exists (gpkg_path ):
197- regenerate_geopackage_cache ()
198-
199- if os .path .exists (json_path ):
200- geojson_size = humanize .naturalsize (os .path .getsize (json_path ), binary = True )
201- else :
202- geojson_size = None
203-
204- if os .path .exists (gpkg_path ):
205- geopackage_size = humanize .naturalsize (os .path .getsize (gpkg_path ), binary = True )
193+ """
194+ Renders the data page showing links and sizes for the latest dumps.
195+ """
196+ cache_dir = Path (tempfile .gettempdir ()) / "optimap_cache"
197+ cache_dir .mkdir (exist_ok = True )
198+
199+ # scan for existing dumps
200+ geojson_files = sorted (cache_dir .glob ('optimap_data_dump_*.geojson' ), reverse = True )
201+ gpkg_files = sorted (cache_dir .glob ('optimap_data_dump_*.gpkg' ), reverse = True )
202+
203+ last_geo = geojson_files [0 ] if geojson_files else None
204+ last_gzip = Path (str (last_geo ) + ".gz" ) if last_geo else None
205+ last_gpkg = gpkg_files [0 ] if gpkg_files else None
206+
207+ # — Supervisor check: ensure all dump file times are within 1 hour
208+ mtimes = []
209+ for p in (last_geo , last_gzip , last_gpkg ):
210+ if p and p .exists ():
211+ mtimes .append (p .stat ().st_mtime )
212+ if mtimes and (max (mtimes ) - min (mtimes ) > 3600 ):
213+ ts_map = {
214+ p .name : datetime .fromtimestamp (p .stat ().st_mtime , get_default_timezone ())
215+ for p in (last_geo , last_gzip , last_gpkg ) if p and p .exists ()
216+ }
217+ logger .warning ("Data dump timestamps differ by >1h: %s" , ts_map )
218+
219+ # humanized sizes
220+ geojson_size = humanize .naturalsize (last_geo .stat ().st_size , binary = True ) if last_geo else None
221+ geopackage_size = humanize .naturalsize (last_gpkg .stat ().st_size , binary = True ) if last_gpkg else None
222+
223+ # last updated timestamp (using JSON file)
224+ if last_geo :
225+ ts = last_geo .stat ().st_mtime
226+ last_updated = datetime .fromtimestamp (ts , get_default_timezone ())
206227 else :
207- geopackage_size = None
208- ts = os .path .getmtime (json_path )
209- tz = get_default_timezone ()
210- last_updated = datetime .fromtimestamp (ts , tz )
228+ last_updated = None
211229
212230 return render (request , 'data.html' , {
213- 'geojson_size' : geojson_size ,
231+ 'geojson_size' : geojson_size ,
214232 'geopackage_size' : geopackage_size ,
215- 'interval' : settings .DATA_DUMP_INTERVAL_HOURS ,
216- 'last_updated' : last_updated ,
233+ 'interval' : settings .DATA_DUMP_INTERVAL_HOURS ,
234+ 'last_updated' : last_updated ,
235+ 'last_geojson' : last_geo .name if last_geo else None ,
236+ 'last_gpkg' : last_gpkg .name if last_gpkg else None ,
217237 })
218-
238+
219239def Confirmationlogin (request ):
220240 return render (request , 'confirmation_login.html' )
221241
0 commit comments