163 lines
6.9 KiB
Python
163 lines
6.9 KiB
Python
# ------------------------------------------------------------------------------
|
|
# This file is part of Appy, a framework for building applications in the Python
|
|
# language. Copyright (C) 2007 Gaetan Delannay
|
|
|
|
# Appy is free software; you can redistribute it and/or modify it under the
|
|
# terms of the GNU General Public License as published by the Free Software
|
|
# Foundation; either version 3 of the License, or (at your option) any later
|
|
# version.
|
|
|
|
# Appy is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# Appy. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
# ------------------------------------------------------------------------------
|
|
from appy import Object
|
|
from appy.fields import Field
|
|
from appy.px import Px
|
|
from appy.gen.layout import Table
|
|
|
|
# ------------------------------------------------------------------------------
|
|
class List(Field):
|
|
'''A list.'''
|
|
|
|
# PX for rendering a single row.
|
|
pxRow = Px('''
|
|
<tr valign="top" style="(rowIndex==-1) and 'display: none' or ''">
|
|
<td align="center" for="info in field.fields">
|
|
<x var="field=info[1];
|
|
tagCss='noStyle';
|
|
widgetName='%s*%d' % (field.name, rowIndex)">:field.pxView</x>
|
|
</td>
|
|
<!-- Icon for removing the row -->
|
|
<td if="layoutType=='edit'" align=":dright">
|
|
<img style="cursor:pointer" src=":'%s/ui/delete.png' % appUrl"
|
|
title="Delete"
|
|
onclick=":'deleteRow(%s, this)' % q('list_%s' % name)"/>
|
|
</td>
|
|
</tr>''')
|
|
|
|
# PX for rendering the list (shared between pxView and pxEdit).
|
|
pxTable = Px('''
|
|
<table var="isEdit=layoutType == 'edit'" if="isEdit or value"
|
|
id=":'list_%s' % name" class="isEdit and 'grid' or 'list'">
|
|
<!-- Header -->
|
|
<tr valign="bottom">
|
|
<th for="info in field.fields">::_(info[1].labelId)</th>
|
|
<!-- Icon for adding a new row. -->
|
|
<th if="isEdit">
|
|
<img style="cursor:pointer" src=":'%s/ui/plus.png' % appUrl"
|
|
title=":_('add_ref')"
|
|
onclick=":'insertRow(%s)' % q('list_%s' % name)"/>
|
|
</th>
|
|
</tr>
|
|
|
|
<!-- Template row (edit only) -->
|
|
<x var="rowIndex=-1" if="isEdit">:field.pxRow</x>
|
|
<tr height="7px" if="isEdit"><td></td></tr>
|
|
|
|
<!-- Rows of data -->
|
|
<x var="rows=inRequest and requestValue or value" for="row in rows">
|
|
<x var="rowIndex=loop.row.nb">:field.pxRow</x>
|
|
</x>
|
|
</table>''')
|
|
|
|
pxView = pxCell = Px('''<x>:field.pxTable</x>''')
|
|
pxEdit = Px('''
|
|
<x>
|
|
<!-- This input makes Appy aware that this field is in the request -->
|
|
<input type="hidden" name=":name" value=""/><x>:field.pxTable</x>
|
|
</x>''')
|
|
|
|
pxSearch = ''
|
|
|
|
def __init__(self, fields, validator=None, multiplicity=(0,1), default=None,
|
|
show=True, page='main', group=None, layouts=None, move=0,
|
|
indexed=False, searchable=False, specificReadPermission=False,
|
|
specificWritePermission=False, width=None, height=None,
|
|
maxChars=None, colspan=1, master=None, masterValue=None,
|
|
focus=False, historized=False, mapping=None, label=None,
|
|
subLayouts=Table('fv', width=None)):
|
|
Field.__init__(self, validator, multiplicity, default, show, page,
|
|
group, layouts, move, indexed, False,
|
|
specificReadPermission, specificWritePermission, width,
|
|
height, None, colspan, master, masterValue, focus,
|
|
historized, True, mapping, label, None, None, None, None)
|
|
self.validable = True
|
|
# Tuples of (names, Field instances) determining the format of every
|
|
# element in the list.
|
|
self.fields = fields
|
|
self.fieldsd = [(n, f.__dict__) for (n,f) in self.fields]
|
|
# Force some layouting for sub-fields, if subLayouts are given. So the
|
|
# one who wants freedom on tuning layouts at the field level must
|
|
# specify subLayouts=None.
|
|
if subLayouts:
|
|
for name, field in self.fields:
|
|
field.layouts = field.formatLayouts(subLayouts)
|
|
|
|
def getField(self, name):
|
|
'''Gets the field definition whose name is p_name.'''
|
|
for n, field in self.fields:
|
|
if n == name: return field
|
|
|
|
def getRequestValue(self, request, requestName=None):
|
|
'''Concatenates the list from distinct form elements in the request.'''
|
|
name = requestName or self.name # A List may be into another List (?)
|
|
prefix = name + '*' + self.fields[0][0] + '*'
|
|
res = {}
|
|
for key in request.keys():
|
|
if not key.startswith(prefix): continue
|
|
# I have found a row. Gets its index
|
|
row = Object()
|
|
if '_' in key: key = key[:key.index('_')]
|
|
rowIndex = int(key.split('*')[-1])
|
|
if rowIndex == -1: continue # Ignore the template row.
|
|
for subName, subField in self.fields:
|
|
keyName = '%s*%s*%s' % (name, subName, rowIndex)
|
|
v = subField.getRequestValue(request, requestName=keyName)
|
|
setattr(row, subName, v)
|
|
res[rowIndex] = row
|
|
# Produce a sorted list.
|
|
keys = res.keys()
|
|
keys.sort()
|
|
res = [res[key] for key in keys]
|
|
# I store in the request this computed value. This way, when individual
|
|
# subFields will need to get their value, they will take it from here,
|
|
# instead of taking it from the specific request key. Indeed, specific
|
|
# request keys contain row indexes that may be wrong after row deletions
|
|
# by the user.
|
|
request.set(name, res)
|
|
return res
|
|
|
|
def getStorableValue(self, value):
|
|
'''Gets p_value in a form that can be stored in the database.'''
|
|
res = []
|
|
for v in value:
|
|
sv = Object()
|
|
for name, field in self.fields:
|
|
setattr(sv, name, field.getStorableValue(getattr(v, name)))
|
|
res.append(sv)
|
|
return res
|
|
|
|
def getInnerValue(self, outerValue, name, i):
|
|
'''Returns the value of inner field named p_name in row number p_i
|
|
within the whole list of values p_outerValue.'''
|
|
if i == -1: return ''
|
|
if not outerValue: return ''
|
|
if i >= len(outerValue): return ''
|
|
return getattr(outerValue[i], name, '')
|
|
|
|
def getCss(self, layoutType, res):
|
|
'''Gets the CSS required by sub-fields if any.'''
|
|
for name, field in self.fields:
|
|
field.getCss(layoutType, res)
|
|
|
|
def getJs(self, layoutType, res):
|
|
'''Gets the JS required by sub-fields if any.'''
|
|
for name, field in self.fields:
|
|
field.getJs(layoutType, res)
|
|
# ------------------------------------------------------------------------------
|