11import os
2- from django .db import models , IntegrityError
3- from django .db .models import Case , When , Value , IntegerField
2+
43from django .contrib .auth import get_user_model
4+ from django .db import IntegrityError , models
5+ from django .db .models import Case , IntegerField , Value , When
6+ from django .utils import timezone
57from django .utils .translation import gettext_lazy as _
68from wagtail .admin .panels import FieldPanel
79from wagtail .fields import RichTextField
8- from wagtail .search import index
910from wagtailautocomplete .edit_handlers import AutocompletePanel
1011
1112from . import choices
1213from .utils .utils import language_iso
1314
1415User = get_user_model ()
1516
17+
1618class CommonControlField (models .Model ):
1719 """
1820 Class with common control fields.
@@ -77,11 +79,9 @@ def autocomplete_label(self):
7779 FieldPanel ("gender" ),
7880 ]
7981
80-
8182 class Meta :
8283 unique_together = [("code" , "gender" )]
8384
84-
8585 def __unicode__ (self ):
8686 return self .gender or self .code
8787
@@ -224,17 +224,20 @@ def get_object_in_preferred_language(self, language):
224224 mission = self .filter (language = language )
225225 if mission :
226226 return mission
227-
228- language_order = ['pt' , 'es' , 'en' ]
227+
228+ language_order = ["pt" , "es" , "en" ]
229229 langs = self .all ().values_list ("language" , flat = True )
230230 languages = Language .objects .filter (id__in = langs )
231-
231+
232232 # Define a ordem baseado na lista language_order
233- order = [When (code2 = lang , then = Value (i )) for i , lang in enumerate (language_order )]
233+ order = [
234+ When (code2 = lang , then = Value (i )) for i , lang in enumerate (language_order )
235+ ]
234236 ordered_languages = languages .annotate (
235- language_order = Case (* order , default = Value (len (language_order )), output_field = IntegerField ())
236- ).order_by ('language_order' )
237-
237+ language_order = Case (
238+ * order , default = Value (len (language_order )), output_field = IntegerField ()
239+ )
240+ ).order_by ("language_order" )
238241
239242 for lang in ordered_languages :
240243 mission = self .filter (language = lang )
@@ -257,7 +260,7 @@ class RichTextWithLanguage(models.Model):
257260 AutocompletePanel ("language" ),
258261 FieldPanel ("rich_text" ),
259262 ]
260-
263+
261264 objects = LanguageFallbackManager ()
262265
263266 class Meta :
@@ -297,7 +300,7 @@ def autocomplete_label(self):
297300 ]
298301
299302 class Meta :
300- unique_together = [("license_type" , )]
303+ unique_together = [("license_type" ,)]
301304 verbose_name = _ ("License" )
302305 verbose_name_plural = _ ("Licenses" )
303306 indexes = [
@@ -326,9 +329,7 @@ def get(
326329 ):
327330 if not license_type :
328331 raise ValueError ("License.get requires license_type parameters" )
329- filters = dict (
330- license_type__iexact = license_type
331- )
332+ filters = dict (license_type__iexact = license_type )
332333 try :
333334 return cls .objects .get (** filters )
334335 except cls .MultipleObjectsReturned :
@@ -368,7 +369,8 @@ class LicenseStatement(CommonControlField):
368369 Language , on_delete = models .SET_NULL , null = True , blank = True
369370 )
370371 license = models .ForeignKey (
371- License , on_delete = models .SET_NULL , null = True , blank = True )
372+ License , on_delete = models .SET_NULL , null = True , blank = True
373+ )
372374
373375 panels = [
374376 FieldPanel ("url" ),
@@ -406,7 +408,8 @@ def get(
406408 raise ValueError ("LicenseStatement.get requires url or license_p" )
407409 try :
408410 return cls .objects .get (
409- url__iexact = url , license_p__iexact = license_p , language = language )
411+ url__iexact = url , license_p__iexact = license_p , language = language
412+ )
410413 except cls .MultipleObjectsReturned :
411414 return cls .objects .filter (
412415 url__iexact = url , license_p__iexact = license_p , language = language
@@ -447,9 +450,7 @@ def create_or_update(
447450 ):
448451 try :
449452 data = dict (
450- url = url ,
451- license_p = license_p ,
452- language = language and language .code2
453+ url = url , license_p = license_p , language = language and language .code2
453454 )
454455 try :
455456 obj = cls .get (url , license_p , language )
@@ -464,7 +465,9 @@ def create_or_update(
464465 except cls .DoesNotExist :
465466 return cls .create (user , url , license_p , language , license )
466467 except Exception as e :
467- raise ValueError (f"Unable to create or update LicenseStatement for { data } : { type (e )} { e } " )
468+ raise ValueError (
469+ f"Unable to create or update LicenseStatement for { data } : { type (e )} { e } "
470+ )
468471
469472 @staticmethod
470473 def parse_url (url ):
@@ -513,7 +516,7 @@ class FileWithLang(models.Model):
513516 blank = True ,
514517 on_delete = models .SET_NULL ,
515518 verbose_name = _ ("File" ),
516- help_text = '' ,
519+ help_text = "" ,
517520 related_name = "+" ,
518521 )
519522
@@ -536,3 +539,52 @@ def filename(self):
536539
537540 class Meta :
538541 abstract = True
542+
543+
544+ class CoreSyncState (models .Model ):
545+ """
546+ Guarda o checkpoint da última coleta da API Core por recurso.
547+
548+ A próxima coleta deve sempre retomar a partir de ``last_updated_at``.
549+ """
550+
551+ resource = models .CharField (_ ("Resource" ), max_length = 50 , unique = True )
552+ last_updated_at = models .DateTimeField (_ ("Last updated at" ), null = True , blank = True )
553+ last_success_at = models .DateTimeField (_ ("Last success at" ), null = True , blank = True )
554+
555+ class Meta :
556+ verbose_name = _ ("Core sync state" )
557+ verbose_name_plural = _ ("Core sync states" )
558+
559+ def __unicode__ (self ):
560+ return self .resource
561+
562+ def __str__ (self ):
563+ return self .resource
564+
565+ @classmethod
566+ def get_for_resource (cls , resource ):
567+ obj , _ = cls .objects .get_or_create (resource = resource )
568+ return obj
569+
570+ def get_from_date_updated (self , default ):
571+ """
572+ Retorna a data inicial para o filtro ``from_date_created`` da API.
573+
574+ Usa sempre a última data coletada; se ainda não houver checkpoint,
575+ retorna ``default``.
576+ """
577+ if self .last_updated_at :
578+ return self .last_updated_at .date ().isoformat ()
579+ return default
580+
581+ def update_checkpoint (self , max_updated_at = None ):
582+ """
583+ Atualiza o checkpoint após uma execução bem-sucedida de sync.
584+ """
585+ update_fields = ["last_success_at" ]
586+ if max_updated_at :
587+ self .last_updated_at = max_updated_at
588+ update_fields .append ("last_updated_at" )
589+ self .last_success_at = timezone .now ()
590+ self .save (update_fields = update_fields )
0 commit comments