Add proper time zone support #20
@ -1,6 +1,7 @@
|
||||
from flask_babelex import lazy_gettext as _
|
||||
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.validators import DataRequired, Email, ValidationError
|
||||
from wtforms.widgets import TextArea
|
||||
@ -17,14 +18,72 @@ class RegistrationForm(FlaskForm):
|
||||
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):
|
||||
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()])
|
||||
end_time = DateTimeField(_('End time'), validators=[DataRequired()])
|
||||
all_day = BooleanField(_('All day'))
|
||||
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):
|
||||
if field.data < self.start_time.data:
|
||||
raise ValidationError(_('End time must be later than start time!'))
|
||||
|
@ -83,5 +83,18 @@ class Event(db.Model):
|
||||
#: The description of the event
|
||||
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):
|
||||
return f'<Event {self.id} ({self.title}) of {self.user}>'
|
||||
|
@ -6,6 +6,7 @@
|
||||
table.calendar {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
tr.month > td {
|
||||
@ -46,14 +47,31 @@
|
||||
background-color: #d8d8d8;
|
||||
}
|
||||
|
||||
td > div.event {
|
||||
tr.week > td > div.event {
|
||||
border: 1px solid green;
|
||||
background-color: white;
|
||||
border-radius: 2px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
tr.sizer > td {
|
||||
width: 14.2857%;
|
||||
height: 0;
|
||||
}
|
||||
</style>
|
||||
<table class="calendar">
|
||||
<thead>
|
||||
<tr class="sizer">
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="month">
|
||||
<td>
|
||||
<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>
|
||||
{% for event in calendar.day_events(day, user=current_user) %}
|
||||
<div class="event">
|
||||
{{ event.start_time_tz.strftime('%H:%M') }}–{{ event.end_time_tz.strftime('%H:%M') }}
|
||||
({{ event.time_zone }})
|
||||
{{ event.title }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
Loading…
Reference in New Issue
Block a user