Source code for ls.joyous.widgets
# ------------------------------------------------------------------------------
# Joyous Widgets
# ------------------------------------------------------------------------------
import sys
import json
import datetime as dt
from django.templatetags.static import static
from django.forms import Media
from django.utils.formats import get_format
from django.utils import timezone
from django.utils.translation import gettext as _
from django.forms.widgets import MultiWidget, NumberInput, Select, \
CheckboxSelectMultiple, FileInput
from django.template.loader import render_to_string
from wagtail.admin.widgets import AdminDateInput, AdminTimeInput
from dateutil.parser import parse as dt_parse
from .utils.recurrence import Weekday, Recurrence, DAILY, WEEKLY, MONTHLY, YEARLY
from .utils.manythings import toTheOrdinal
from .utils.names import WEEKDAY_NAMES_DEFINITIVE, WEEKDAY_ABBRS, MONTH_ABBRS
from .utils.weeks import getFirstDayOfWeek
# ------------------------------------------------------------------------------
[docs]class Time12hrInput(AdminTimeInput):
"""
Display and Edit time fields in a 12hr format
"""
template_name = 'joyous/widgets/time12hr_input.html'
def __init__(self, attrs=None):
super().__init__(attrs=attrs, format=None)
def format_value(self, value):
if isinstance(value, (dt.datetime, dt.time)):
return value.strftime("%I:%M%P") # %P for lower case am/pm
else:
return value
@property
def media(self):
return Media(js=[static('joyous/js/vendor/moment-2.22.0.min.js'),
static("joyous/js/time12hr_admin.js")])
# ------------------------------------------------------------------------------
(EVERY_DAY, SAME_DAY, DAY_OF_MONTH) = (100, 101, 200)
[docs]class RecurrenceWidget(MultiWidget):
"""
Widget for entering the rule of a recurrence
"""
template_name = 'joyous/widgets/recurrence_widget.html'
def __init__(self, attrs=None):
freqOptions = [(DAILY, _("Daily")),
(WEEKLY, _("Weekly")),
(MONTHLY, _("Monthly")),
(YEARLY, _("Yearly"))]
ordOptions1 = [(1, toTheOrdinal(1)),
(2, toTheOrdinal(2)),
(3, toTheOrdinal(3)),
(4, toTheOrdinal(4)),
(5, toTheOrdinal(5)),
(-1, toTheOrdinal(-1)),
(EVERY_DAY, _("Every")),
(SAME_DAY, _("The Same"))]
ordOptions2 = [(None, ""),
(1, toTheOrdinal(1)),
(2, toTheOrdinal(2)),
(3, toTheOrdinal(3)),
(4, toTheOrdinal(4)),
(5, toTheOrdinal(5)),
(-1, toTheOrdinal(-1))]
dayOptions1 = enumerate(WEEKDAY_ABBRS)
dayOptions2 = [(None, "")] + list(enumerate(WEEKDAY_NAMES_DEFINITIVE))
dayOptions3 = list(enumerate(WEEKDAY_NAMES_DEFINITIVE)) +\
[(DAY_OF_MONTH, _("Day of the month"))]
monthOptions = enumerate(MONTH_ABBRS[1:], 1)
numAttrs = {'min': 1, 'max': 366}
disableAttrs = {'disabled': True}
if attrs:
numAttrs.update(attrs)
disableAttrs.update(attrs)
widgets = [AdminDateInput(attrs=attrs),
Select(attrs=attrs, choices=freqOptions), #1
NumberInput(attrs=numAttrs),
CheckboxSelectMultiple(attrs=attrs, choices=dayOptions1),
NumberInput(attrs=numAttrs),
AdminDateInput(attrs=attrs), #5
Select(attrs=attrs, choices=ordOptions1),
Select(attrs=attrs, choices=dayOptions3),
Select(attrs=disableAttrs, choices=ordOptions2),
Select(attrs=disableAttrs, choices=dayOptions2),
Select(attrs=disableAttrs, choices=ordOptions2), #10
Select(attrs=disableAttrs, choices=dayOptions2),
CheckboxSelectMultiple(attrs=attrs, choices=monthOptions) ]
super().__init__(widgets, attrs)
def decompress(self, value):
wdayChoices = []
ordChoices = [SAME_DAY, None, None]
dayChoices = [DAY_OF_MONTH, None, None]
monChoices = []
if isinstance(value, Recurrence):
if value.freq == WEEKLY:
if value.byweekday:
wdayChoices = [day.weekday for day in value.byweekday]
elif value.freq in (MONTHLY, YEARLY):
if value.byweekday:
if len(value.byweekday) == 7 and all(not day.n for day in value.byweekday):
ordChoices[0] = EVERY_DAY
dayChoices[0] = DAY_OF_MONTH
else:
for (i, day) in enumerate(value.byweekday[:3]):
dayChoices[i] = day.weekday
ordChoices[i] = day.n or EVERY_DAY
elif value.bymonthday:
ordChoices[0] = value.bymonthday[0]
if value.dtstart.day == ordChoices[0]:
ordChoices[0] = SAME_DAY
dayChoices[0] = DAY_OF_MONTH
if value.bymonth:
monChoices = value.bymonth
else:
value.dtstart.month
return [value.dtstart,
value.freq, #1
value.interval,
wdayChoices,
value.count,
value.until, #5
ordChoices[0],
dayChoices[0],
ordChoices[1],
dayChoices[1],
ordChoices[2], #10
dayChoices[2],
monChoices]
else:
return [None,
None, #1
1,
wdayChoices,
None,
None, #5
ordChoices[0],
dayChoices[0],
ordChoices[1],
dayChoices[1],
ordChoices[2], #10
dayChoices[2],
monChoices]
def get_context(self, name, value, attrs):
"""
Return the context to use with the widget's template, e.g.
{'widget': {
'attrs': {'id': "id_repeat", 'required': True},
'is_hidden': False,
'name': "repeat",
'required': True,
'subwidgets': [... the context of all the component subwigets...],
'template_name': "joyous/widgets/recurrence_widget.html",
'value': "Tuesdays",
'value_s': "Tuesdays",
'value_r': "DTSTART:20181201\nRRULE:FREQ=WEEKLY;WKST=SU;BYDAY=TU",
}}}
"""
context = super().get_context(name, value, attrs)
context['widget']['value_s'] = str(value)
context['widget']['value_r'] = repr(value)
return context
def value_from_datadict(self, data, files, name):
values = [widget.value_from_datadict(data, files, "{}_{}".format(name, i))
for i, widget in enumerate(self.widgets)]
try:
def toIntOrNone(value):
return int(value) if value else None
dtstart = dt_parse(values[0]) if values[0] else None
freq = toIntOrNone(values[1])
interval = toIntOrNone(values[2]) or None
#count = toIntOrNone(values[4]) or None
dtuntil = dt_parse(values[5]) if values[5] else None
ordChoices = [toIntOrNone(values[6]),
toIntOrNone(values[8]),
toIntOrNone(values[10])]
dayChoices = [toIntOrNone(values[7]),
toIntOrNone(values[9]),
toIntOrNone(values[11])]
wdayChoices = None
mdayChoices = None
monChoices = None
if freq == WEEKLY:
if values[3]:
wdayChoices = [int(day) for day in values[3]]
elif freq in (MONTHLY, YEARLY):
if dayChoices[0] == DAY_OF_MONTH: # day of the month
if ordChoices[0] == EVERY_DAY: # every day, == daily
wdayChoices = range(7)
elif ordChoices[0] == SAME_DAY: # the same day of the month
mdayChoices = None
else:
mdayChoices = [ordChoices[0]]
else: # a day of the week
if ordChoices[0] == EVERY_DAY: # every of this weekday
wdayChoices = [Weekday(dayChoices[0])]
elif ordChoices[0] == SAME_DAY: # the same weekday of the month
wdayNum = (dtstart.day - 1) // 7 + 1
wdayChoices = [Weekday(dayChoices[0], wdayNum)]
else:
wdayChoices = [Weekday(dayChoices[0], ordChoices[0])]
if dayChoices[1] != None and ordChoices[1] != None:
wdayChoices.append(Weekday(dayChoices[1], ordChoices[1]))
if dayChoices[2] != None and ordChoices[2] != None:
wdayChoices.append(Weekday(dayChoices[2], ordChoices[2]))
if freq == YEARLY:
if values[12]:
monChoices = [int(month) for month in values[12]]
retval = Recurrence(dtstart = dtstart,
freq = freq,
interval = interval,
byweekday = wdayChoices,
#count = count,
until = dtuntil,
bymonthday = mdayChoices,
bymonth = monChoices)
except (TypeError, ValueError):
retval = None
return retval
@property
def media(self):
media = super()._get_media()
media += Media(css={'all': [static("joyous/css/recurrence_admin.css")]},
js=[static("joyous/js/recurrence_admin.js")])
return media
# ------------------------------------------------------------------------------
[docs]class ExceptionDateInput(AdminDateInput):
"""
Display and Edit the dates which are the exceptions to a recurrence rule
"""
template_name = 'joyous/widgets/exception_date_input.html'
def __init__(self, attrs=None, format=None):
super().__init__(attrs=attrs, format=format)
self.overrides_repeat = None
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
config = {
'dayOfWeekStart': getFirstDayOfWeek(),
'format': self.js_format,
}
context['widget']['valid_dates'] = json.dumps(self.valid_dates())
context['widget']['config_json'] = json.dumps(config)
return context
def valid_dates(self):
valid_dates = None # null in JS
if self.overrides_repeat:
today = timezone.localdate()
past = (today - dt.timedelta(days=200)).replace(day=1)
future = (today + dt.timedelta(days=600)).replace(day=1)
valid_dates = ["{:%Y%m%d}".format(occurence) for occurence in
self.overrides_repeat.between(past, future, inc=True)]
return valid_dates
@property
def media(self):
# TODO: think about changing this to a static definition
return Media(css={'all': [static("joyous/css/recurrence_admin.css")]},
js=[static("joyous/js/recurrence_admin.js")])
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------