[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:
|
||||
'''Represents a computation that will be executed on a series of cells
|
||||
within a timeline calendar.'''
|
||||
def __init__(self, initValue):
|
||||
self.value = initValue
|
||||
def __init__(self, initValue): self.value = initValue
|
||||
def __repr__(self): return '<Total=%s>' % str(self.value)
|
||||
|
||||
class TotalRow:
|
||||
'''For a timeline calendar, if you want to add rows representing totals
|
||||
computed from other rows (representing agendas), specify it via TotalRow
|
||||
instances (see field Agenda.totalRows below).'''
|
||||
class Totals:
|
||||
'''For a timeline calendar, if you want to add rows or columns representing
|
||||
totals computed from other rows/columns (representing agendas), specify
|
||||
it via Totals instances (see Agenda fields "totalRows" and "totalCols"
|
||||
below).'''
|
||||
def __init__(self, name, label, onCell, initValue=0):
|
||||
# "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.
|
||||
self.name = name
|
||||
# "label" is a i18n label that will be used to produce a longer name
|
||||
|
@ -131,9 +132,10 @@ class TotalRow:
|
|||
# * last - a boolean that is True if we are walking the last shown
|
||||
# calendar;
|
||||
# * checked - a value "checked" indicating the status of the possible
|
||||
# validation checkbox corresponding to this cell. If there
|
||||
# is a checkbox in this cell, the value will be True or
|
||||
# False; else, the value will be None.
|
||||
# validation checkbox corresponding to this cell. If
|
||||
# there is a checkbox in this cell, the value will be
|
||||
# True or False; else, the value will be None.
|
||||
# * preCompute - the result of Calendar.preCompute (see below)
|
||||
self.onCell = onCell
|
||||
# "initValue" is the initial value given to created Total instances
|
||||
self.initValue = initValue
|
||||
|
@ -179,7 +181,7 @@ class Calendar(Field):
|
|||
Timeslot = Timeslot
|
||||
Validation = Validation
|
||||
Other = Other
|
||||
TotalRow = TotalRow
|
||||
Totals = Totals
|
||||
Event = Event
|
||||
IterSub = sutils.IterSub
|
||||
# Error messages
|
||||
|
@ -190,7 +192,7 @@ class Calendar(Field):
|
|||
"must give another method in param 'eventNameMethod'."
|
||||
TIMESLOT_USED = 'An event is already defined at this timeslot.'
|
||||
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").'
|
||||
|
||||
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
|
||||
pxTotalRows = Px('''
|
||||
<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>
|
||||
<tr for="row in field.totalRows" var2="rowTitle=_(row.label)">
|
||||
<td class="tlLeft">
|
||||
|
@ -254,14 +257,41 @@ class Calendar(Field):
|
|||
others=field.getOthers(zobj, \
|
||||
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
|
||||
pxViewTimeline = Px('''
|
||||
<table cellpadding="0" cellspacing="0" class="list timeline"
|
||||
id=":ajaxHookId + '_cal'"
|
||||
id=":ajaxHookId + '_cal'" style="display: inline-block"
|
||||
var="monthsInfos=field.getTimelineMonths(grid, zobj)">
|
||||
<colgroup> <!-- Column specifiers -->
|
||||
<!-- Names of calendars -->
|
||||
<col/>
|
||||
<col/> <!-- 1st col: Names of calendars -->
|
||||
<col for="date in grid"
|
||||
style=":field.getColumnStyle(zobj, date, render, today)"/>
|
||||
<col/>
|
||||
|
@ -284,9 +314,8 @@ class Calendar(Field):
|
|||
</x>
|
||||
<td class="tlRight">::tlName</td>
|
||||
</tr>
|
||||
<!-- A separator between groups of other calendars -->
|
||||
<tr if="not loop.otherGroup.last" height="5px">
|
||||
<th colspan=":len(grid)+2"></th></tr>
|
||||
<!-- The separator between groups of other calendars -->
|
||||
<x if="not loop.otherGroup.last">::field.getOthersSep(len(grid)+2)</x>
|
||||
</x>
|
||||
</tbody>
|
||||
<!-- Total rows -->
|
||||
|
@ -296,6 +325,8 @@ class Calendar(Field):
|
|||
<x>:field.pxTimeLineMonths</x>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- Total columns, as a separate table -->
|
||||
<x if="field.totalCols">:field.pxTotalCols</x>
|
||||
<x>:field.pxTimelineLegend</x>''')
|
||||
|
||||
# Popup for adding an event in the month view
|
||||
|
@ -544,8 +575,9 @@ class Calendar(Field):
|
|||
others=None, timelineName=None, additionalInfo=None,
|
||||
startDate=None, endDate=None, defaultDate=None, timeslots=None,
|
||||
colors=None, showUncolored=False, preCompute=None,
|
||||
applicableEvents=None, totalRows=None, validation=None,
|
||||
topPx=None, bottomPx=None, view=None, xml=None, delete=True):
|
||||
applicableEvents=None, totalRows=None, totalCols=None,
|
||||
validation=None, topPx=None, bottomPx=None, view=None,
|
||||
xml=None, delete=True):
|
||||
# The "validator" attribute, allowing field-specific validation, behaves
|
||||
# differently for the Calendar field. If specified, it must hold a
|
||||
# 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
|
||||
# (see above).
|
||||
if totalRows and (self.render != 'timeline'):
|
||||
raise Exception(Calendar.TOTALROW_MISUSED)
|
||||
raise Exception(Calendar.TOTALS_MISUSED)
|
||||
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
|
||||
# consists in identifying validators and letting them "convert" event
|
||||
# types being wished to final, validated event types. If you want to
|
||||
|
@ -792,6 +828,11 @@ class Calendar(Field):
|
|||
if res != None: return res
|
||||
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):
|
||||
'''Returns the name of some p_other calendar as must be shown in a
|
||||
timeline.'''
|
||||
|
@ -1546,40 +1587,48 @@ class Calendar(Field):
|
|||
for id in ids.split(','): res[id] = value
|
||||
return res
|
||||
|
||||
def getColumnTotals(self, obj, grid, others):
|
||||
'''If self.totalRows is not empty, this method creates a dict of Total
|
||||
instances ~{s_totalRowName: [Total]}~ that will hold such an instance
|
||||
for every TotalRow and every column in the Calendar.'''
|
||||
if not self.totalRows: return
|
||||
# Initialise totals for every total row
|
||||
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
|
||||
def computeTotals(self, totalType, obj, grid, others, preComputed):
|
||||
'''Compute the totals for every column (p_totalType == 'row') or row
|
||||
(p_totalType == "col").'''
|
||||
allTotals = getattr(self, 'total%ss' % totalType.capitalize())
|
||||
if not allTotals: return
|
||||
# Count other calendars and dates in the grid
|
||||
othersCount = 0
|
||||
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
|
||||
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):
|
||||
i += 1
|
||||
j = -1
|
||||
indexes['i'] += 1
|
||||
indexes['j'] = -1
|
||||
for date in grid:
|
||||
j += 1
|
||||
indexes['j'] += 1
|
||||
# Get the events in this other calendar at this date
|
||||
events = other.field.getEventsAt(other.obj, date)
|
||||
# From info @this date, update the total for every total row
|
||||
last = i == othersCount
|
||||
# From info @this date, update the total for every totals
|
||||
last = indexes[ii] == lastCount - 1
|
||||
# Get the status of the validation checkbox that is possibly
|
||||
# present at this date for this calendar
|
||||
checked = None
|
||||
cbId = '%s_%s_%s' % (other.obj.id, other.field.name,
|
||||
date.strftime('%Y%m%d'))
|
||||
if cbId in status: checked = status[cbId]
|
||||
# Update the Total instance for every total row at this date
|
||||
for row in self.totalRows:
|
||||
total = res[row.name][j]
|
||||
row.onCell(obj, date, other, events, total, last, checked)
|
||||
# Update the Total instance for every totals at this date
|
||||
for totals in allTotals:
|
||||
total = res[totals.name][indexes[jj]]
|
||||
totals.onCell(obj, date, other, events, total, last,
|
||||
checked, preComputed)
|
||||
return res
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -126,11 +126,11 @@ td.search { padding-top: 8px }
|
|||
border-bottom: 5px solid #fdfdfd; padding: 3px 5px 0 5px }
|
||||
.grid td { padding: 0 3px }
|
||||
.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 }
|
||||
.tlLeft { text-align: left !important }
|
||||
.tlRight { text-align: right !important }
|
||||
.tlRight { text-align: right !important }
|
||||
.tlLeft { text-align: left !important; padding-left: 1px !important }
|
||||
.tlRight { text-align: right !important; padding-right: 1px !important }
|
||||
.msgTable { margin: 6px 0; width: 100%; font-size: 93% }
|
||||
.msgTable tr { vertical-align: top }
|
||||
.msgTable td, .msgTable th { border: 1px solid grey; color: #555555;
|
||||
|
|
Loading…
Reference in a new issue