[gen] Calendar field: added the posssibility to add total columns.
This commit is contained in:
parent
588fa9b54f
commit
5beb7699ac
|
@ -104,16 +104,17 @@ class Other:
|
||||||
class Total:
|
class Total:
|
||||||
'''Represents a computation that will be executed on a series of cells
|
'''Represents a computation that will be executed on a series of cells
|
||||||
within a timeline calendar.'''
|
within a timeline calendar.'''
|
||||||
def __init__(self, initValue):
|
def __init__(self, initValue): self.value = initValue
|
||||||
self.value = initValue
|
def __repr__(self): return '<Total=%s>' % str(self.value)
|
||||||
|
|
||||||
class TotalRow:
|
class Totals:
|
||||||
'''For a timeline calendar, if you want to add rows representing totals
|
'''For a timeline calendar, if you want to add rows or columns representing
|
||||||
computed from other rows (representing agendas), specify it via TotalRow
|
totals computed from other rows/columns (representing agendas), specify
|
||||||
instances (see field Agenda.totalRows below).'''
|
it via Totals instances (see Agenda fields "totalRows" and "totalCols"
|
||||||
|
below).'''
|
||||||
def __init__(self, name, label, onCell, initValue=0):
|
def __init__(self, name, label, onCell, initValue=0):
|
||||||
# "name" must hold a short name or acronym and will directly appear
|
# "name" must hold a short name or acronym and will directly appear
|
||||||
# at the beginning of the row. It must be unique within all TotalRow
|
# at the beginning of the row. It must be unique within all Totals
|
||||||
# instances defined for a given Calendar field.
|
# instances defined for a given Calendar field.
|
||||||
self.name = name
|
self.name = name
|
||||||
# "label" is a i18n label that will be used to produce a longer name
|
# "label" is a i18n label that will be used to produce a longer name
|
||||||
|
@ -121,19 +122,20 @@ class TotalRow:
|
||||||
self.label = label
|
self.label = label
|
||||||
# A method that will be called every time a cell is walked in the
|
# A method that will be called every time a cell is walked in the
|
||||||
# agenda. It will get these args:
|
# agenda. It will get these args:
|
||||||
# * date - the date representing the current day;
|
# * date - the date representing the current day;
|
||||||
# * other - the Other instance representing the currently walked
|
# * other - the Other instance representing the currently walked
|
||||||
# calendar;
|
# calendar;
|
||||||
# * events - the list of events (as Event instances) defined at that
|
# * events - the list of events (as Event instances) defined at that
|
||||||
# day in this calendar. Be careful: this can be None;
|
# day in this calendar. Be careful: this can be None;
|
||||||
# * total - the Total instance (see above) corresponding to the
|
# * total - the Total instance (see above) corresponding to the
|
||||||
# current column;
|
# current column;
|
||||||
# * last - a boolean that is True if we are walking the last shown
|
# * last - a boolean that is True if we are walking the last shown
|
||||||
# calendar;
|
# calendar;
|
||||||
# * checked - a value "checked" indicating the status of the possible
|
# * checked - a value "checked" indicating the status of the possible
|
||||||
# validation checkbox corresponding to this cell. If there
|
# validation checkbox corresponding to this cell. If
|
||||||
# is a checkbox in this cell, the value will be True or
|
# there is a checkbox in this cell, the value will be
|
||||||
# False; else, the value will be None.
|
# True or False; else, the value will be None.
|
||||||
|
# * preCompute - the result of Calendar.preCompute (see below)
|
||||||
self.onCell = onCell
|
self.onCell = onCell
|
||||||
# "initValue" is the initial value given to created Total instances
|
# "initValue" is the initial value given to created Total instances
|
||||||
self.initValue = initValue
|
self.initValue = initValue
|
||||||
|
@ -179,7 +181,7 @@ class Calendar(Field):
|
||||||
Timeslot = Timeslot
|
Timeslot = Timeslot
|
||||||
Validation = Validation
|
Validation = Validation
|
||||||
Other = Other
|
Other = Other
|
||||||
TotalRow = TotalRow
|
Totals = Totals
|
||||||
Event = Event
|
Event = Event
|
||||||
IterSub = sutils.IterSub
|
IterSub = sutils.IterSub
|
||||||
# Error messages
|
# Error messages
|
||||||
|
@ -190,7 +192,7 @@ class Calendar(Field):
|
||||||
"must give another method in param 'eventNameMethod'."
|
"must give another method in param 'eventNameMethod'."
|
||||||
TIMESLOT_USED = 'An event is already defined at this timeslot.'
|
TIMESLOT_USED = 'An event is already defined at this timeslot.'
|
||||||
DAY_FULL = 'No more place for adding this event.'
|
DAY_FULL = 'No more place for adding this event.'
|
||||||
TOTALROW_MISUSED = 'Total rows can only be specified for timelines ' \
|
TOTALS_MISUSED = 'Totals can only be specified for timelines ' \
|
||||||
'(render == "timeline").'
|
'(render == "timeline").'
|
||||||
|
|
||||||
timelineBgColors = {'Fri': '#dedede', 'Sat': '#c0c0c0', 'Sun': '#c0c0c0'}
|
timelineBgColors = {'Fri': '#dedede', 'Sat': '#c0c0c0', 'Sun': '#c0c0c0'}
|
||||||
|
@ -233,7 +235,8 @@ class Calendar(Field):
|
||||||
# Displays the total rows at the bottom of a timeline calendar
|
# Displays the total rows at the bottom of a timeline calendar
|
||||||
pxTotalRows = Px('''
|
pxTotalRows = Px('''
|
||||||
<tbody id=":'%s_trs' % ajaxHookId"
|
<tbody id=":'%s_trs' % ajaxHookId"
|
||||||
var="totals=field.getColumnTotals(zobj, grid, others)">
|
var="totals=field.computeTotals('row', zobj, grid, others, \
|
||||||
|
preComputed)">
|
||||||
<script>:field.getAjaxDataTotalRow(ajaxHookId)</script>
|
<script>:field.getAjaxDataTotalRow(ajaxHookId)</script>
|
||||||
<tr for="row in field.totalRows" var2="rowTitle=_(row.label)">
|
<tr for="row in field.totalRows" var2="rowTitle=_(row.label)">
|
||||||
<td class="tlLeft">
|
<td class="tlLeft">
|
||||||
|
@ -254,14 +257,41 @@ class Calendar(Field):
|
||||||
others=field.getOthers(zobj, \
|
others=field.getOthers(zobj, \
|
||||||
preComputed)">:field.pxTotalRows</x>''')
|
preComputed)">:field.pxTotalRows</x>''')
|
||||||
|
|
||||||
|
# Displays the total columns besides the calendar, as a separate table
|
||||||
|
pxTotalCols = Px('''
|
||||||
|
<table cellpadding="0" cellspacing="0" class="list timeline"
|
||||||
|
style="float:right" id=":'%s_tcs' % ajaxHookId"
|
||||||
|
var="totals=field.computeTotals('col', zobj, grid, others, \
|
||||||
|
preComputed)">
|
||||||
|
<!-- The column headers -->
|
||||||
|
<tr>
|
||||||
|
<th for="col in field.totalCols">
|
||||||
|
<acronym title=":_(col.label)">:col.name</acronym>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<!-- 2 empty rows (correspond to month and day names) -->
|
||||||
|
<tr for="i in range(2)"><td> </td></tr>
|
||||||
|
<!-- Re-create one row for every other calendar -->
|
||||||
|
<x var="i=-1" for="otherGroup in others">
|
||||||
|
<tr for="other in otherGroup" var2="@i=i+1">
|
||||||
|
<td for="col in field.totalCols">::totals[col.name][i].value</td>
|
||||||
|
</tr>
|
||||||
|
<!-- The separator between groups of other calendars -->
|
||||||
|
<x if="not loop.otherGroup.last">::field.getOthersSep(\
|
||||||
|
len(field.totalCols))</x>
|
||||||
|
</x>
|
||||||
|
<!-- Repeat the 2 empty rows and add one for every total row -->
|
||||||
|
<tr for="i in range(2+len(field.totalRows))"><td> </td></tr>
|
||||||
|
<tr><th> </th></tr>
|
||||||
|
</table>''')
|
||||||
|
|
||||||
# Timeline view for a calendar
|
# Timeline view for a calendar
|
||||||
pxViewTimeline = Px('''
|
pxViewTimeline = Px('''
|
||||||
<table cellpadding="0" cellspacing="0" class="list timeline"
|
<table cellpadding="0" cellspacing="0" class="list timeline"
|
||||||
id=":ajaxHookId + '_cal'"
|
id=":ajaxHookId + '_cal'" style="display: inline-block"
|
||||||
var="monthsInfos=field.getTimelineMonths(grid, zobj)">
|
var="monthsInfos=field.getTimelineMonths(grid, zobj)">
|
||||||
<colgroup> <!-- Column specifiers -->
|
<colgroup> <!-- Column specifiers -->
|
||||||
<!-- Names of calendars -->
|
<col/> <!-- 1st col: Names of calendars -->
|
||||||
<col/>
|
|
||||||
<col for="date in grid"
|
<col for="date in grid"
|
||||||
style=":field.getColumnStyle(zobj, date, render, today)"/>
|
style=":field.getColumnStyle(zobj, date, render, today)"/>
|
||||||
<col/>
|
<col/>
|
||||||
|
@ -284,9 +314,8 @@ class Calendar(Field):
|
||||||
</x>
|
</x>
|
||||||
<td class="tlRight">::tlName</td>
|
<td class="tlRight">::tlName</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!-- A separator between groups of other calendars -->
|
<!-- The separator between groups of other calendars -->
|
||||||
<tr if="not loop.otherGroup.last" height="5px">
|
<x if="not loop.otherGroup.last">::field.getOthersSep(len(grid)+2)</x>
|
||||||
<th colspan=":len(grid)+2"></th></tr>
|
|
||||||
</x>
|
</x>
|
||||||
</tbody>
|
</tbody>
|
||||||
<!-- Total rows -->
|
<!-- Total rows -->
|
||||||
|
@ -296,6 +325,8 @@ class Calendar(Field):
|
||||||
<x>:field.pxTimeLineMonths</x>
|
<x>:field.pxTimeLineMonths</x>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<!-- Total columns, as a separate table -->
|
||||||
|
<x if="field.totalCols">:field.pxTotalCols</x>
|
||||||
<x>:field.pxTimelineLegend</x>''')
|
<x>:field.pxTimelineLegend</x>''')
|
||||||
|
|
||||||
# Popup for adding an event in the month view
|
# Popup for adding an event in the month view
|
||||||
|
@ -544,8 +575,9 @@ class Calendar(Field):
|
||||||
others=None, timelineName=None, additionalInfo=None,
|
others=None, timelineName=None, additionalInfo=None,
|
||||||
startDate=None, endDate=None, defaultDate=None, timeslots=None,
|
startDate=None, endDate=None, defaultDate=None, timeslots=None,
|
||||||
colors=None, showUncolored=False, preCompute=None,
|
colors=None, showUncolored=False, preCompute=None,
|
||||||
applicableEvents=None, totalRows=None, validation=None,
|
applicableEvents=None, totalRows=None, totalCols=None,
|
||||||
topPx=None, bottomPx=None, view=None, xml=None, delete=True):
|
validation=None, topPx=None, bottomPx=None, view=None,
|
||||||
|
xml=None, delete=True):
|
||||||
# The "validator" attribute, allowing field-specific validation, behaves
|
# The "validator" attribute, allowing field-specific validation, behaves
|
||||||
# differently for the Calendar field. If specified, it must hold a
|
# differently for the Calendar field. If specified, it must hold a
|
||||||
# method that will be executed every time a user wants to create an
|
# method that will be executed every time a user wants to create an
|
||||||
|
@ -669,8 +701,12 @@ class Calendar(Field):
|
||||||
# representing totals, give in "totalRows" a list of TotalRow instances
|
# representing totals, give in "totalRows" a list of TotalRow instances
|
||||||
# (see above).
|
# (see above).
|
||||||
if totalRows and (self.render != 'timeline'):
|
if totalRows and (self.render != 'timeline'):
|
||||||
raise Exception(Calendar.TOTALROW_MISUSED)
|
raise Exception(Calendar.TOTALS_MISUSED)
|
||||||
self.totalRows = totalRows or []
|
self.totalRows = totalRows or []
|
||||||
|
# Similarly, you can specify additional columns in "totalCols"
|
||||||
|
if totalCols and (self.render != 'timeline'):
|
||||||
|
raise Exception(Calendar.TOTALS_MISUSED)
|
||||||
|
self.totalCols = totalCols or []
|
||||||
# A validation process can be associated to a Calendar event. It
|
# A validation process can be associated to a Calendar event. It
|
||||||
# consists in identifying validators and letting them "convert" event
|
# consists in identifying validators and letting them "convert" event
|
||||||
# types being wished to final, validated event types. If you want to
|
# types being wished to final, validated event types. If you want to
|
||||||
|
@ -792,6 +828,11 @@ class Calendar(Field):
|
||||||
if res != None: return res
|
if res != None: return res
|
||||||
return [[]]
|
return [[]]
|
||||||
|
|
||||||
|
def getOthersSep(self, colspan):
|
||||||
|
'''Produces the separator between groups of other calendars'''
|
||||||
|
return '<tr style="height: 8px"><th colspan="%s" style="background-' \
|
||||||
|
'color: grey"></th></tr>' % colspan
|
||||||
|
|
||||||
def getTimelineName(self, other):
|
def getTimelineName(self, other):
|
||||||
'''Returns the name of some p_other calendar as must be shown in a
|
'''Returns the name of some p_other calendar as must be shown in a
|
||||||
timeline.'''
|
timeline.'''
|
||||||
|
@ -1546,40 +1587,48 @@ class Calendar(Field):
|
||||||
for id in ids.split(','): res[id] = value
|
for id in ids.split(','): res[id] = value
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getColumnTotals(self, obj, grid, others):
|
def computeTotals(self, totalType, obj, grid, others, preComputed):
|
||||||
'''If self.totalRows is not empty, this method creates a dict of Total
|
'''Compute the totals for every column (p_totalType == 'row') or row
|
||||||
instances ~{s_totalRowName: [Total]}~ that will hold such an instance
|
(p_totalType == "col").'''
|
||||||
for every TotalRow and every column in the Calendar.'''
|
allTotals = getattr(self, 'total%ss' % totalType.capitalize())
|
||||||
if not self.totalRows: return
|
if not allTotals: return
|
||||||
# Initialise totals for every total row
|
# Count other calendars and dates in the grid
|
||||||
res = {}
|
|
||||||
for row in self.totalRows:
|
|
||||||
res[row.name] = [Total(row.initValue) for date in grid]
|
|
||||||
# Get the status of validation checkboxes
|
|
||||||
status = self.getValidationCheckboxesStatus(obj.REQUEST)
|
|
||||||
# Compute the number of other calendars
|
|
||||||
othersCount = 0
|
othersCount = 0
|
||||||
for group in others: othersCount += len(group)
|
for group in others: othersCount += len(group)
|
||||||
|
datesCount = len(grid)
|
||||||
|
isRow = totalType == 'row'
|
||||||
|
# Initialise, for every (row or col) totals, Total instances
|
||||||
|
totalCount = isRow and datesCount or othersCount
|
||||||
|
lastCount = isRow and othersCount or datesCount
|
||||||
|
res = {}
|
||||||
|
for totals in allTotals:
|
||||||
|
res[totals.name] = [Total(totals.initValue) \
|
||||||
|
for i in range(totalCount)]
|
||||||
|
# Get the status of validation checkboxes
|
||||||
|
status = self.getValidationCheckboxesStatus(obj.REQUEST)
|
||||||
# Walk every date within every calendar
|
# Walk every date within every calendar
|
||||||
i = 0
|
indexes = {'i': -1, 'j': -1}
|
||||||
|
ii = (totalType == 'row') and 'i' or 'j'
|
||||||
|
jj = (totalType == 'row') and 'j' or 'i'
|
||||||
for other in sutils.IterSub(others):
|
for other in sutils.IterSub(others):
|
||||||
i += 1
|
indexes['i'] += 1
|
||||||
j = -1
|
indexes['j'] = -1
|
||||||
for date in grid:
|
for date in grid:
|
||||||
j += 1
|
indexes['j'] += 1
|
||||||
# Get the events in this other calendar at this date
|
# Get the events in this other calendar at this date
|
||||||
events = other.field.getEventsAt(other.obj, date)
|
events = other.field.getEventsAt(other.obj, date)
|
||||||
# From info @this date, update the total for every total row
|
# From info @this date, update the total for every totals
|
||||||
last = i == othersCount
|
last = indexes[ii] == lastCount - 1
|
||||||
# Get the status of the validation checkbox that is possibly
|
# Get the status of the validation checkbox that is possibly
|
||||||
# present at this date for this calendar
|
# present at this date for this calendar
|
||||||
checked = None
|
checked = None
|
||||||
cbId = '%s_%s_%s' % (other.obj.id, other.field.name,
|
cbId = '%s_%s_%s' % (other.obj.id, other.field.name,
|
||||||
date.strftime('%Y%m%d'))
|
date.strftime('%Y%m%d'))
|
||||||
if cbId in status: checked = status[cbId]
|
if cbId in status: checked = status[cbId]
|
||||||
# Update the Total instance for every total row at this date
|
# Update the Total instance for every totals at this date
|
||||||
for row in self.totalRows:
|
for totals in allTotals:
|
||||||
total = res[row.name][j]
|
total = res[totals.name][indexes[jj]]
|
||||||
row.onCell(obj, date, other, events, total, last, checked)
|
totals.onCell(obj, date, other, events, total, last,
|
||||||
|
checked, preComputed)
|
||||||
return res
|
return res
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -126,11 +126,11 @@ td.search { padding-top: 8px }
|
||||||
border-bottom: 5px solid #fdfdfd; padding: 3px 5px 0 5px }
|
border-bottom: 5px solid #fdfdfd; padding: 3px 5px 0 5px }
|
||||||
.grid td { padding: 0 3px }
|
.grid td { padding: 0 3px }
|
||||||
.timeline { font-size: 85%; color: #555555 }
|
.timeline { font-size: 85%; color: #555555 }
|
||||||
.timeline td { text-align: center; padding: 1px }
|
.timeline tr { height: 18px }
|
||||||
|
.timeline td { text-align: center; padding: 0 1px }
|
||||||
.timeline th { padding: 1px }
|
.timeline th { padding: 1px }
|
||||||
.tlLeft { text-align: left !important }
|
.tlLeft { text-align: left !important; padding-left: 1px !important }
|
||||||
.tlRight { text-align: right !important }
|
.tlRight { text-align: right !important; padding-right: 1px !important }
|
||||||
.tlRight { text-align: right !important }
|
|
||||||
.msgTable { margin: 6px 0; width: 100%; font-size: 93% }
|
.msgTable { margin: 6px 0; width: 100%; font-size: 93% }
|
||||||
.msgTable tr { vertical-align: top }
|
.msgTable tr { vertical-align: top }
|
||||||
.msgTable td, .msgTable th { border: 1px solid grey; color: #555555;
|
.msgTable td, .msgTable th { border: 1px solid grey; color: #555555;
|
||||||
|
|
Loading…
Reference in a new issue