Skip to content

Commit

Permalink
feat(generic): introduce custom APIS DateIntervalFields
Browse files Browse the repository at this point in the history
This commit introduces a `FuzzyDateParserField` and a
`FuzzyDateRegexField`.
Both fields are based on the `GenericDateIntervalField`, which adds a
`_from`, a `_sort` and a `_to` field based on the field created. Those
three additional fields contain data that is calculated using either a
parser (in the case of the `FuzzyDateParserField`) or a regex (in the
case of the `FuzzyDateRegexField`).
The default parser for the `FuzzyDateParserField` is the one from the
`apis_core.utils.DateParser` module.
  • Loading branch information
b1rger committed Nov 12, 2024
1 parent e14c806 commit a7e36ee
Showing 1 changed file with 102 additions and 0 deletions.
102 changes: 102 additions & 0 deletions apis_core/generic/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from datetime import date
import re
from typing import Callable, List, Tuple
from django.db.models import DateField, CharField
from django.forms import ValidationError

from apis_core.history.models import APISHistoryTableBase
from apis_core.utils import DateParser


class GenericDateIntervalField(CharField):
def contribute_to_class(self, cls, name):
super().contribute_to_class(cls, name)
if not issubclass(cls, APISHistoryTableBase):
DateField(editable=False, blank=True, null=True).contribute_to_class(
cls, f"{name}_date_sort"
)
DateField(editable=False, blank=True, null=True).contribute_to_class(
cls, f"{name}_date_from"
)
DateField(editable=False, blank=True, null=True).contribute_to_class(
cls, f"{name}_date_to"
)


class FuzzyDateParserField(GenericDateIntervalField):
def __init__(
self, parser: Callable[[str], Tuple[date, date, date]] = DateParser.parse_date, *args, **kwargs
):
self.parser = parser
super().__init__(*args, **kwargs)

def pre_save(self, model_instance, add):
name = self.attname
value = getattr(model_instance, name)
if not getattr(model_instance, "skip_date_parsing", False):
try:
date, date_from, date_to = self.parser(value)
print(date)
setattr(model_instance, f"{name}_date_sort", date)
setattr(model_instance, f"{name}_date_from", date_from)
setattr(model_instance, f"{name}_date_to", date_to)
except Exception as e:
raise ValidationError(f"Error parsing date string: {e}")
return super().pre_save(model_instance, add)


DEFAULT_DATE_REGEX = (r"(?P<day>\d{1,2})\.(?P<month>\d{1,2}).(?P<year>\d{1,4})",)


class FuzzyDateRegexField(GenericDateIntervalField):
def __init__(
self,
regex_patterns: List[Tuple[str, str, str]] = [
(DEFAULT_DATE_REGEX, DEFAULT_DATE_REGEX, DEFAULT_DATE_REGEX)
],
*args,
**kwargs,
):
self.regex_patterns = regex_patterns
super().__init__(*args, **kwargs)

def _parse_date_using_regex_match(self, regex_match: re.Match) -> date:
match_dict = regex_match.groupdict()
if (
"year" not in match_dict
or "month" not in match_dict
or "day" not in match_dict
):
raise ValueError(
f"Regex pattern does not contain all needed named groups (year, month, day): {regex_match}"
)
ret_date = f"{match_dict['year']}-{match_dict['month'] if match_dict['month'] is not None else '01'}-{match_dict['day'] if match_dict['day'] is not None else '01'}"

return ret_date

def _parse_date_using_list_of_regexes(self, value: str):
sort_pattern, from_pattern, to_pattern = self.regex_patterns
date_sort = re.search(sort_pattern, value)
date_from = re.search(from_pattern, value)
date_to = re.search(to_pattern, value)
if date_sort and date_from and date_to:
return (
self._parse_date_using_regex_match(date_sort),
self._parse_date_using_regex_match(date_from),
self._parse_date_using_regex_match(date_to),
)
return None, None, None

def pre_save(self, model_instance, add):
name = self.attname
value = getattr(model_instance, name)
try:
date_sort, date_from, date_to = self._parse_date_using_list_of_regexes(
value
)
setattr(model_instance, f"{name}_date_sort", date_sort)
setattr(model_instance, f"{name}_date_from", date_from)
setattr(model_instance, f"{name}_date_to", date_to)
except Exception as e:
raise ValidationError(f"Error parsing date string with regex: {e}")
return super().pre_save(model_instance, add)

0 comments on commit a7e36ee

Please sign in to comment.