Add proper time zone support #20
@ -1,6 +1,7 @@
|
|||||||
from flask_babelex import lazy_gettext as _
|
from flask_babelex import lazy_gettext as _
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import PasswordField, StringField, BooleanField
|
import pytz
|
||||||
|
from wtforms import BooleanField, PasswordField, SelectField, StringField
|
||||||
from wtforms.ext.dateutil.fields import DateTimeField
|
from wtforms.ext.dateutil.fields import DateTimeField
|
||||||
from wtforms.validators import DataRequired, Email, ValidationError
|
from wtforms.validators import DataRequired, Email, ValidationError
|
||||||
from wtforms.widgets import TextArea
|
from wtforms.widgets import TextArea
|
||||||
@ -17,14 +18,72 @@ class RegistrationForm(FlaskForm):
|
|||||||
raise ValidationError(_('The two passwords must match!'))
|
raise ValidationError(_('The two passwords must match!'))
|
||||||
|
|
||||||
|
|
||||||
|
class TimezoneField(SelectField):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs.update({
|
||||||
|
'choices': [
|
||||||
|
(pytz.timezone(tz), tz.replace('_', ' '))
|
||||||
|
for tz in pytz.common_timezones
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
SelectField.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def process_formdata(self, valuelist):
|
||||||
|
if not valuelist:
|
||||||
|
self.data = None
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.data = pytz.timezone(valuelist[0])
|
||||||
|
except pytz.exceptions.UnknownTimeZoneError:
|
||||||
|
self.data = None
|
||||||
|
|
||||||
|
raise ValueError('Unknown time zone')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_pytz_instance(value):
|
||||||
|
return value is pytz.UTC or isinstance(value, pytz.tzinfo.BaseTzInfo)
|
||||||
|
|
||||||
|
def process_data(self, value):
|
||||||
|
if value is None:
|
||||||
|
self.data = None
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
if is_pytz_instance(value):
|
||||||
|
self.data = value
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.data = pytz.timezone(value)
|
||||||
|
except Exception as exc:
|
||||||
|
raise ValueError(f'Unknown time zone {value}')
|
||||||
|
|
||||||
|
def iter_choices(self):
|
||||||
|
for value, label in self.choices:
|
||||||
|
yield (value, label, value == self.data)
|
||||||
|
|
||||||
|
|
||||||
class EventForm(FlaskForm):
|
class EventForm(FlaskForm):
|
||||||
title = StringField(_('Title'), validators=[DataRequired()])
|
title = StringField(_('Title'), validators=[DataRequired()])
|
||||||
time_zone = StringField(_('Time zone'), validators=[DataRequired()])
|
time_zone = TimezoneField(_('Time zone'), validators=[DataRequired()])
|
||||||
start_time = DateTimeField(_('Start time'), validators=[DataRequired()])
|
start_time = DateTimeField(_('Start time'), validators=[DataRequired()])
|
||||||
end_time = DateTimeField(_('End time'), validators=[DataRequired()])
|
end_time = DateTimeField(_('End time'), validators=[DataRequired()])
|
||||||
all_day = BooleanField(_('All day'))
|
all_day = BooleanField(_('All day'))
|
||||||
description = StringField(_('Description'), widget=TextArea())
|
description = StringField(_('Description'), widget=TextArea())
|
||||||
|
|
||||||
|
def populate_obj(self, obj):
|
||||||
|
FlaskForm.populate_obj(self, obj)
|
||||||
|
|
||||||
|
tz = self.time_zone.data
|
||||||
|
|
||||||
|
obj.time_zone = str(tz)
|
||||||
|
obj.start_time = tz.localize(self.start_time.data).astimezone(pytz.utc)
|
||||||
|
obj.end_time = tz.localize(self.end_time.data).astimezone(pytz.utc)
|
||||||
|
|
||||||
def validate_end_time(self, field):
|
def validate_end_time(self, field):
|
||||||
if field.data < self.start_time.data:
|
if field.data < self.start_time.data:
|
||||||
raise ValidationError(_('End time must be later than start time!'))
|
raise ValidationError(_('End time must be later than start time!'))
|
||||||
|
@ -83,5 +83,18 @@ class Event(db.Model):
|
|||||||
#: The description of the event
|
#: The description of the event
|
||||||
description = db.Column(db.UnicodeText())
|
description = db.Column(db.UnicodeText())
|
||||||
|
|
||||||
|
def __as_tz(self, timestamp):
|
||||||
|
from pytz import timezone, utc
|
||||||
|
|
||||||
|
return utc.localize(timestamp).astimezone(timezone(self.time_zone))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def start_time_tz(self):
|
||||||
|
return self.__as_tz(self.start_time)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def end_time_tz(self):
|
||||||
|
return self.__as_tz(self.end_time)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'<Event {self.id} ({self.title}) of {self.user}>'
|
return f'<Event {self.id} ({self.title}) of {self.user}>'
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
table.calendar {
|
table.calendar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.month > td {
|
tr.month > td {
|
||||||
@ -46,14 +47,31 @@
|
|||||||
background-color: #d8d8d8;
|
background-color: #d8d8d8;
|
||||||
}
|
}
|
||||||
|
|
||||||
td > div.event {
|
tr.week > td > div.event {
|
||||||
border: 1px solid green;
|
border: 1px solid green;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.sizer > td {
|
||||||
|
width: 14.2857%;
|
||||||
|
height: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<table class="calendar">
|
<table class="calendar">
|
||||||
<thead>
|
<thead>
|
||||||
|
<tr class="sizer">
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
<tr class="month">
|
<tr class="month">
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('hello', date=calendar.prev_year) }}">« {{ calendar.prev_year_year }}</a>
|
<a href="{{ url_for('hello', date=calendar.prev_year) }}">« {{ calendar.prev_year_year }}</a>
|
||||||
@ -101,6 +119,8 @@
|
|||||||
<span class="day-num">{{ day.day }}</span>
|
<span class="day-num">{{ day.day }}</span>
|
||||||
{% for event in calendar.day_events(day, user=current_user) %}
|
{% for event in calendar.day_events(day, user=current_user) %}
|
||||||
<div class="event">
|
<div class="event">
|
||||||
|
{{ event.start_time_tz.strftime('%H:%M') }}–{{ event.end_time_tz.strftime('%H:%M') }}
|
||||||
|
({{ event.time_zone }})
|
||||||
{{ event.title }}
|
{{ event.title }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
Loading…
Reference in New Issue
Block a user