11import logging
2+ from datetime import datetime
23
4+ from cachetools import FIFOCache
35from django .db import connection
46
5- from geonode .base .models import ThesaurusKeywordLabel
7+ from geonode .base .models import ThesaurusKeywordLabel , Thesaurus
8+
69
710logger = logging .getLogger (__name__ )
811
@@ -54,10 +57,6 @@ def get_localized_tkeywords(lang, thesaurus_identifier: str):
5457 return sorted (ret .values (), key = lambda i : i ["label" ].lower ())
5558
5659
57- def get_localized_labels (lang , key = "about" ):
58- return {i [key ]: i ["label" ] for i in get_localized_tkeywords (lang , I18N_THESAURUS_IDENTIFIER )}
59-
60-
6160def get_localized_label (lang , about ):
6261 return (
6362 ThesaurusKeywordLabel .objects .filter (
@@ -66,3 +65,100 @@ def get_localized_label(lang, about):
6665 .values_list ("label" , flat = True )
6766 .first ()
6867 )
68+
69+
70+ class I18nCache :
71+
72+ DATA_KEY_SCHEMA = "schema"
73+ DATA_KEY_LABELS = "labels"
74+
75+ def __init__ (self ):
76+ # the cache has the lang as key, and various info in the dict value:
77+ # - date: the date field of the thesaurus when it was last loaded, it's used for the expiration check
78+ # - labels: the keyword labels from the i18n thesaurus
79+ # - schema: the localized json schema
80+ # FIFO bc we want to renew the data once in a while
81+ self .cache = FIFOCache (16 )
82+
83+ def get_entry (self , lang , data_key ):
84+ """
85+ returns date:str, data
86+ date is needed for checking the entry freshness when setting info
87+ data may be None if not cached or expired
88+ """
89+ cached_entry = self .cache .get (lang , None )
90+
91+ thesaurus_date = ( # may be none if thesaurus does not exist
92+ Thesaurus .objects .filter (identifier = I18N_THESAURUS_IDENTIFIER ).values_list ("date" , flat = True ).first ()
93+ )
94+ if cached_entry :
95+ if thesaurus_date == cached_entry ["date" ]:
96+ # only return cached data if thesaurus has not been modified
97+ return thesaurus_date , cached_entry .get (data_key , None )
98+ else :
99+ logger .info (f"Schema for { lang } :{ data_key } needs to be recreated" )
100+
101+ return thesaurus_date , None
102+
103+ def set (self , lang : str , data_key : str , data : dict , request_date : str ):
104+ cached_entry : dict = self .cache .setdefault (lang , {})
105+
106+ latest_date = (
107+ Thesaurus .objects .filter (identifier = I18N_THESAURUS_IDENTIFIER ).values_list ("date" , flat = True ).first ()
108+ )
109+
110+ if request_date == latest_date :
111+ # no changes after processing, set the info right away
112+ logger .debug (f"Caching lang:{ lang } key:{ data_key } date:{ request_date } " )
113+ cached_entry .update ({"date" : latest_date , data_key : data })
114+ else :
115+ logger .warning (
116+ f"Cache will not be updated for lang:{ lang } key:{ data_key } reqdate:{ request_date } latest:{ latest_date } "
117+ )
118+
119+ def get_labels (self , lang ):
120+ date , labels = self .get_entry (lang , self .DATA_KEY_LABELS )
121+ if labels is None :
122+ labels = {i ["about" ]: i ["label" ] for i in get_localized_tkeywords (lang , I18N_THESAURUS_IDENTIFIER )}
123+ self .set (lang , self .DATA_KEY_LABELS , labels , date )
124+
125+ return labels
126+
127+ def clear_schema_cache (self ):
128+ logger .info ("Clearing schema cache" )
129+ while True :
130+ try :
131+ self .cache .popitem ()
132+ except KeyError :
133+ return
134+
135+
136+ def thesaurus_changed (sender , instance , ** kwargs ):
137+ if instance .identifier == I18N_THESAURUS_IDENTIFIER :
138+ if hasattr (instance , "_signal_handled" ): # avoid signal recursion
139+ return
140+ logger .debug (f"Thesaurus changed: { instance .identifier } " )
141+ _update_thesaurus_date ()
142+
143+
144+ def thesaurusk_changed (sender , instance , ** kwargs ):
145+ if instance .thesaurus .identifier == I18N_THESAURUS_IDENTIFIER :
146+ logger .debug (f"ThesaurusKeyword changed: { instance .about } ALT:{ instance .alt_label } " )
147+ _update_thesaurus_date ()
148+
149+
150+ def thesauruskl_changed (sender , instance , ** kwargs ):
151+ if instance .keyword .thesaurus .identifier == I18N_THESAURUS_IDENTIFIER :
152+ logger .debug (
153+ f"ThesaurusKeywordLabel changed: { instance .keyword .about } ALT:{ instance .keyword .alt_label } L:{ instance .lang } "
154+ )
155+ _update_thesaurus_date ()
156+
157+
158+ def _update_thesaurus_date ():
159+ logger .debug ("Updating label thesaurus date" )
160+ # update timestamp to invalidate other processes also
161+ i18n_thesaurus = Thesaurus .objects .get (identifier = I18N_THESAURUS_IDENTIFIER )
162+ i18n_thesaurus .date = datetime .now ().replace (microsecond = 0 ).isoformat ()
163+ i18n_thesaurus ._signal_handled = True
164+ i18n_thesaurus .save ()
0 commit comments