-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(generic): introduce custom APIS DateIntervalFields
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
Showing
1 changed file
with
102 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |