Skip to content

Commit

Permalink
Added timezone to time and datetime fields
Browse files Browse the repository at this point in the history
  • Loading branch information
alfred82santa committed Oct 13, 2016
1 parent 758d19d commit 29650c7
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 126 deletions.
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ Version 0.8.1
-------------

- Added __contains__ function to models and lists. It allows to use ``in`` operator.
- Added ``default_timezone`` parameter to DateTimeFields and TimeFields. If value entered has no a timezone
defined, default one will be set.
- Added ``force_timezone`` parameter to DateTimeFields in order to convert value to a specific timezone.
- More cleanups.

Version 0.8.0
-------------
Expand Down
94 changes: 64 additions & 30 deletions dirty_models/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
"""

from datetime import datetime, date, time, timedelta
from dateutil.parser import parse as dateutil_parse
from .model_types import ListModel

from collections import Mapping
from dateutil.parser import parse as dateutil_parse

from .model_types import ListModel

__all__ = ['IntegerField', 'FloatField', 'BooleanField', 'StringField', 'StringIdField',
'TimeField', 'DateField', 'DateTimeField', 'TimedeltaField', 'ModelField', 'ArrayField',
'HashMapField', 'BlobField', 'MultiTypeField']


class BaseField:

"""Base field descriptor."""

def __init__(self, name=None, alias=None, getter=None, setter=None, read_only=False, default=None, doc=None):
Expand Down Expand Up @@ -105,7 +105,6 @@ def __delete__(self, obj):


class IntegerField(BaseField):

"""
It allows to use an integer as value in a field.
Expand All @@ -129,7 +128,6 @@ def can_use_value(self, value):


class FloatField(BaseField):

"""
It allows to use a float as value in a field.
Expand All @@ -153,7 +151,6 @@ def can_use_value(self, value):


class BooleanField(BaseField):

"""
It allows to use a boolean as value in a field.
Expand Down Expand Up @@ -181,7 +178,6 @@ def can_use_value(self, value):


class StringField(BaseField):

"""
It allows to use a string as value in a field.
Expand All @@ -204,7 +200,6 @@ def can_use_value(self, value):


class StringIdField(StringField):

"""
It allows to use a string as value in a field, but not allows empty strings. Empty string are like ``None``
and they will remove data of field.
Expand All @@ -225,7 +220,6 @@ def set_value(self, obj, value):


class DateTimeBaseField(BaseField):

"""Base field for time or/and date fields."""

date_parsers = {}
Expand All @@ -242,24 +236,13 @@ def __init__(self, parse_format=None, **kwargs):
:type parse_format: str or dict
"""
super(DateTimeBaseField, self).__init__(**kwargs)
self._parse_format = None
self.parse_format = parse_format

def export_definition(self):
result = super(DateTimeBaseField, self).export_definition()
result['parse_format'] = self.parse_format
return result

@property
def parse_format(self):
"""Parse_format getter: datetime format used on field"""
return self._parse_format

@parse_format.setter
def parse_format(self, value):
"""Parse_format setter: datetime format used on field"""
self._parse_format = value

def get_parsed_value(self, value):
"""
Helper to cast string to datetime using :member:`parse_format`.
Expand Down Expand Up @@ -300,6 +283,7 @@ def get_formatted_value(self, value):
:type value: datetime
:return: str
"""

def get_formatter(parser_desc):
try:
return parser_desc['formatter']
Expand Down Expand Up @@ -330,7 +314,6 @@ def get_formatter(parser_desc):


class TimeField(DateTimeBaseField):

"""
It allows to use a time as value in a field.
Expand All @@ -347,6 +330,24 @@ class TimeField(DateTimeBaseField):
* :class:`~datetime.datetime` will get time part.
"""

def __init__(self, parse_format=None, default_timezone=None, **kwargs):
"""
:param parse_format: String format to cast string to datetime. It could be
an string format or a :class:`dict` with two keys:
* ``parser`` key to set how string must be parsed. It could be a callable.
* ``formatter`` key to set how datetime must be formatted. It could be a callable.
:type parse_format: str or dict
:param default_timezone: Default timezone to use when value does not have one.
:type default_timezone: datetime.tzinfo
"""
super(TimeField, self).__init__(parse_format=parse_format, **kwargs)
self.default_timezone = default_timezone

def convert_value(self, value):
if isinstance(value, list):
return time(*value)
Expand All @@ -372,9 +373,15 @@ def check_value(self, value):
def can_use_value(self, value):
return isinstance(value, (int, str, datetime, list, dict))

def set_value(self, obj, value: time):
if self.default_timezone and value.tzinfo is None:
value = time(hour=value.hour, minute=value.minute, second=value.microsecond,
microsecond=value.microsecond, tzinfo=self.default_timezone)

class DateField(DateTimeBaseField):
super(TimeField, self).set_value(obj, value)


class DateField(DateTimeBaseField):
"""
It allows to use a date as value in a field.
Expand Down Expand Up @@ -418,7 +425,6 @@ def can_use_value(self, value):


class DateTimeField(DateTimeBaseField):

"""
It allows to use a datetime as value in a field.
Expand All @@ -435,6 +441,30 @@ class DateTimeField(DateTimeBaseField):
* :class:`~datetime.date` will set date part.
"""

def __init__(self, parse_format=None, default_timezone=None, force_timezone=False, **kwargs):
"""
:param parse_format: String format to cast string to datetime. It could be
an string format or a :class:`dict` with two keys:
* ``parser`` key to set how string must be parsed. It could be a callable.
* ``formatter`` key to set how datetime must be formatted. It could be a callable.
:type parse_format: str or dict
:param default_timezone: Default timezone to use when value does not have one.
:type default_timezone: datetime.tzinfo
:param force_timezone: If it is True value will be converted to timezone defined on ``default_timezone``
parameter. It ``default_timezone`` is not defined it is ignored.
:type: bool
"""
super(DateTimeField, self).__init__(parse_format=parse_format, **kwargs)
self.default_timezone = default_timezone
self.force_timezone = force_timezone

def convert_value(self, value):
if isinstance(value, list):
return datetime(*value)
Expand All @@ -460,6 +490,15 @@ def check_value(self, value):
def can_use_value(self, value):
return isinstance(value, (int, str, date, dict, list))

def set_value(self, obj, value):
if self.default_timezone:
if value.tzinfo is None:
value = value.replace(tzinfo=self.default_timezone)
elif self.force_timezone and value.tzinfo != self.default_timezone:
value = value.astimezone(tz=self.default_timezone)

super(DateTimeField, self).set_value(obj, value)


class TimedeltaField(BaseField):
"""
Expand All @@ -485,7 +524,6 @@ def can_use_value(self, value):


class ModelField(BaseField):

"""
It allows to use a model as value in a field. Model type must be
defined on constructor using param model_class. If it is not defined
Expand All @@ -505,7 +543,7 @@ def __init__(self, model_class=None, **kwargs):
self._model_setter = None
if 'setter' in kwargs:
self._model_setter = kwargs['setter']
del(kwargs['setter'])
del (kwargs['setter'])
super(ModelField, self).__init__(**kwargs)

def export_definition(self):
Expand Down Expand Up @@ -581,7 +619,6 @@ def field_type(self, value):


class ArrayField(InnerFieldTypeMixin, BaseField):

"""
It allows to create a ListModel (iterable in :mod:`dirty_models.types`) of different elements according
to the specified field_type. So it is possible to have a list of Integers, Strings, Models, etc.
Expand Down Expand Up @@ -652,7 +689,6 @@ def autolist(self, value):


class HashMapField(InnerFieldTypeMixin, ModelField):

"""
It allows to create a field which contains a hash map.
Expand All @@ -675,15 +711,13 @@ def convert_value(self, value):


class BlobField(BaseField):

"""
It allows any type of data.
"""
pass


class MultiTypeField(BaseField):

"""
It allows to define multiple type for a field. So, it is possible to define a field as
a integer and as a model field, for example.
Expand All @@ -703,7 +737,7 @@ def __init__(self, field_types=None, **kwargs):
def get_field_docstring(self):
if len(self._field_types):
return 'Multiple type values are allowed:\n\n{0}'.format(
"\n\n".join(["* {0}".format(field.get_field_docstring())for field in self._field_types]))
"\n\n".join(["* {0}".format(field.get_field_docstring()) for field in self._field_types]))

def export_definition(self):
result = super(MultiTypeField, self).export_definition()
Expand Down

0 comments on commit 29650c7

Please sign in to comment.