1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """A 'Field' determines how data is displayed and stored.
17
18 A 'Field' is a component of a data structure. Every 'Field' has a type.
19 For example, an 'IntegerField' stores a signed integer while a
20 'TextField' stores a string.
21
22 The value of a 'Field' can be represented as HTML (for display in the
23 GUI), or as XML (when written to persistent storage). Every 'Field' can
24 create an HTML form that can be used by the user to update the value of
25 the 'Field'.
26
27 Every 'Extension' class has a set of arguments composed of 'Field'. An
28 instance of that 'Extension' class can be constructed by providing a
29 value for each 'Field' object. The GUI can display the 'Extension'
30 object by rendering each of the 'Field' values as HTML. The user can
31 change the value of a 'Field' in the GUI, and then write the 'Extension'
32 object to persistent storage.
33
34 Additional derived classes of 'Field' can be created for use in
35 domain-specific situations. For example, the QMTest 'Test' class
36 defines a derived class which allows the user to select from among a set
37 of test names."""
38
39
40
41
42
43 import attachment
44 import common
45 import formatter
46 import htmllib
47 import os
48 import re
49 import qm
50 import string
51 import StringIO
52 import structured_text
53 import sys
54 import time
55 import tokenize
56 import types
57 import urllib
58 import web
59 import xml.dom
60 import xmlutil
61
62
63
64
65
67 """A 'Field' is a named, typed component of a data structure."""
68
69 form_field_prefix = "_field_"
70
71 - def __init__(self,
72 name = "",
73 default_value = None,
74 title = "",
75 description = "",
76 hidden = "false",
77 read_only = "false",
78 computed = "false"):
79 """Create a new (generic) field.
80
81 'name' -- The name of the field.
82
83 'default_value' -- The default value for this field.
84
85 'title' -- The name given this field when it is displayed in
86 user interfaces.
87
88 'description' -- A string explaining the purpose of this field.
89 The 'description' must be provided as structured text. The
90 first line of the structured text must be a one-sentence
91 description of the field; that line is extracted by
92 'GetBriefDescription'.
93
94 'hidden' -- If true, this field is for internal puprpose only
95 and is not shown in user interfaces.
96
97 'read_only' -- If true, this field may not be modified by users.
98
99 'computed' -- If true, this field is computed automatically.
100 All computed fields are implicitly hidden and implicitly
101 read-only.
102
103 The boolean parameters (such as 'hidden') use the convention
104 that true is represented by the string '"true"'; any other value
105 is false. This convention is a historical artifact."""
106
107 self.__name = name
108
109 if not title:
110 self.__title = name
111 else:
112 self.__title = title
113 self.__description = description
114 self.__hidden = hidden == "true"
115 self.__read_only = read_only == "true"
116 self.__computed = computed == "true"
117
118
119 if (self.IsComputed()):
120 self.__read_only = 1
121 self.__hidden = 1
122
123 self.__default_value = default_value
124
125
127 """Set the name of the field."""
128
129
130
131
132 if (self.__name == self.__title):
133 self.__title = name
134 self.__name = name
135
136
138 """Return the name of the field."""
139
140 return self.__name
141
142
144 """Return the default value for this field."""
145
146 return common.copy(self.__default_value)
147
148
150 """Return the user-friendly title of the field."""
151
152 return self.__title
153
154
156 """Return a description of this field.
157
158 This description is used when displaying detailed help
159 information about the field."""
160
161 return self.__description
162
163
174
175
177 """Generate help text about this field in structured text format."""
178
179 raise NotImplementedError
180
181
183 """Generate help text about this field in HTML format.
184
185 'edit' -- If true, display information about editing controls
186 for this field."""
187
188 description = structured_text.to_html(self.GetDescription())
189 help = structured_text.to_html(self.GetHelp())
190
191 return '''
192 <h3>%s</h3>
193 <h4>About This Field</h4>
194 %s
195 <hr noshade size="2">
196 <h4>About This Field\'s Values</h4>
197 %s
198 <hr noshade size="2">
199 <p>Refer to this field as <tt>%s</tt> in Python expressions.</p>
200 ''' % (self.GetTitle(), description, help, self.GetName(), )
201
202
204 """Returns the sequence of subfields contained in this field.
205
206 returns -- The sequence of subfields contained in this field.
207 If there are no subfields, an empty sequence is returned."""
208
209 return ()
210
211
213 """Returns true if this field is computed automatically.
214
215 returns -- True if this field is computed automatically. A
216 computed field is never displayed to users and is not stored
217 should not be stored; the class containing the field is
218 responsible for recomputing it as necessary."""
219
220 return self.__computed
221
222
224 """Returns true if this 'Field' should be hidden from users.
225
226 returns -- True if this 'Field' should be hidden from users.
227 The value of a hidden field is not displayed in the GUI."""
228
229 return self.__hidden
230
231
233 """Returns true if this 'Field' cannot be modified by users.
234
235 returns -- True if this 'Field' cannot be modified by users.
236 The GUI does not allow users to modify a read-only field."""
237
238 return self.__read_only
239
240
241
243 """Return a plain text rendering of a 'value' for this field.
244
245 'columns' -- The maximum width of each line of text.
246
247 returns -- A plain-text string representing 'value'."""
248
249
250 text_file = StringIO.StringIO()
251
252 html_file = StringIO.StringIO(self.FormatValueAsHtml(None,
253 value,
254 "brief"))
255
256
257 parser = htmllib.HTMLParser(formatter.AbstractFormatter
258 (formatter.DumbWriter(text_file,
259 maxcol = columns)))
260 parser.feed(html_file)
261 parser.close()
262 text = text_file.getValue()
263
264
265 html_file.close()
266 text_file.close()
267
268 return text
269
270
292
293
295 """Generate a DOM element node for a value of this field.
296
297 'value' -- The value to represent.
298
299 'document' -- The containing DOM document node."""
300
301 raise NotImplementedError
302
303
304
306 """Validate a field value.
307
308 For an acceptable type and value, return the representation of
309 'value' in the underlying field storage.
310
311 'value' -- A value to validate for this field.
312
313 returns -- If the 'value' is valid, returns 'value' or an
314 equivalent "canonical" version of 'value'. (For example, this
315 function may search a hash table and return an equivalent entry
316 from the hash table.)
317
318 This function must raise an exception if the value is not valid.
319 The string representation of the exception will be used as an
320 error message in some situations.
321
322 Implementations of this method must be idempotent."""
323
324 raise NotImplementedError
325
326
327 - def ParseTextValue(self, value):
328 """Parse a value represented as a string.
329
330 'value' -- A string representing the value.
331
332 returns -- The corresponding field value. The value returned
333 should be processed by 'Validate' to ensure that it is valid
334 before it is returned."""
335
336 raise NotImplemented
337
338
361
362
364 """Return a value for this field represented by DOM 'node'.
365
366 This method does not validate the value for this particular
367 instance; it only makes sure the node is well-formed, and
368 returns a value of the correct Python type.
369
370 'node' -- The DOM node that is being evaluated.
371
372 'attachment_store' -- For attachments, the store that should be
373 used.
374
375 If the 'node' is incorrectly formed, this method should raise an
376 exception."""
377
378 raise NotImplementedError
379
380
381
389
390
392
393
394
395 return "<%s %s>" % (self.__class__, self.GetName())
396
397
398
399
401 """An 'IntegerField' stores an 'int' or 'long' object."""
402
403 - def __init__(self, name="", default_value=0, **properties):
404 """Construct a new 'IntegerField'.
405
406 'name' -- As for 'Field.__init__'.
407
408 'default_value' -- As for 'Field.__init__'.
409
410 'properties' -- Other keyword arguments for 'Field.__init__'."""
411
412
413 super(IntegerField, self).__init__(name, default_value, **properties)
414
415
417
418 return """This field stores an integer.
419
420 The default value of this field is %d."""
421
422
423
427
428
447
448
450 return xmlutil.create_dom_text_element(document, "integer",
451 str(value))
452
453
454
455
457
458 if not isinstance(value, (int, long)):
459 raise ValueError, value
460
461 return value
462
463
464 - def ParseTextValue(self, value):
465
466 try:
467 return self.Validate(int(value))
468 except:
469 raise qm.common.QMException, \
470 qm.error("invalid integer field value")
471
472
474
475
476 if node.nodeType != xml.dom.Node.ELEMENT_NODE \
477 or node.tagName != "integer":
478 raise qm.QMException, \
479 qm.error("dom wrong tag for field",
480 name=self.GetName(),
481 right_tag="integer",
482 wrong_tag=node.tagName)
483
484 value = xmlutil.get_dom_text(node)
485
486 return self.ParseTextValue(value)
487
488
489
490
491 -class TextField(Field):
492 """A field that contains text."""
493
494 - def __init__(self,
495 name = "",
496 default_value = "",
497 multiline = "false",
498 structured = "false",
499 verbatim = "false",
500 not_empty_text = "false",
501 **properties):
502 """Construct a new 'TextField'.
503
504 'multiline' -- If false, a value for this field is a single line
505 of text. If true, multi-line text is allowed.
506
507 'structured' -- If true, the field contains structured text.
508
509 'verbatim' -- If true, the contents of the field are treated as
510 preformatted text.
511
512 'not_empty_text' -- The value of this field is considered
513 invalid if it empty or composed only of whitespace.
514
515 'properties' -- A dictionary of other keyword arguments which
516 are provided to the base class constructor."""
517
518
519 super(TextField, self).__init__(name, default_value, **properties)
520
521 self.__multiline = multiline == "true"
522 self.__structured = structured == "true"
523 self.__verbatim = verbatim == "true"
524 self.__not_empty_text = not_empty_text == "true"
525
526
528
529 help = """
530 A text field. """
531 if self.__structured:
532 help = help + '''
533 The text is interpreted as structured text, and formatted
534 appropriately for the output device. See "Structured Text
535 Formatting
536 Rules":http://www.python.org/sigs/doc-sig/stext.html for
537 more information. '''
538 elif self.__verbatim:
539 help = help + """
540 The text is stored verbatim; whitespace and indentation are
541 preserved. """
542 if self.__not_empty_text:
543 help = help + """
544 This field may not be empty. """
545 help = help + """
546 The default value of this field is "%s".
547 """ % self.GetDefaultValue()
548 return help
549
550
551
560
561
563
564
565 if value is None:
566 value = ""
567 else:
568 value = str(value)
569
570 if name is None:
571 name = self.GetHtmlFormFieldName()
572
573 if style == "new" or style == "edit":
574 if self.__multiline:
575 result = '<textarea cols="64" rows="8" name="%s">' \
576 '%s</textarea>' \
577 % (name, web.escape(value))
578 else:
579 result = \
580 '<input type="text" size="40" name="%s" value="%s" />' \
581 % (name, web.escape(value))
582
583
584 if self.__structured:
585 result = result \
586 + '<br><font size="-1">This is a ' \
587 + qm.web.make_help_link_html(
588 qm.structured_text.html_help_text,
589 "structured text") \
590 + 'field.</font>'
591 return result
592
593 elif style == "hidden":
594 return '<input type="hidden" name="%s" value="%s" />' \
595 % (name, web.escape(value))
596
597 elif style == "brief":
598 if self.__structured:
599
600 value = string.split(value, "\n", 1)
601 value = web.format_structured_text(value[0])
602 else:
603
604 value = re.sub(r"\s", " ", value)
605
606
607 if len(value) > 80:
608 value = value[:80] + "..."
609
610 if self.__verbatim:
611
612 return '<tt>%s</tt>' % web.escape(value)
613 elif self.__structured:
614
615 return value
616 else:
617
618 return web.escape(value)
619
620 elif style == "full":
621 if self.__verbatim:
622
623
624
625
626
627 break_delimiter = "#@LINE$BREAK@#"
628 value = common.wrap_lines(value, columns=80,
629 break_delimiter=break_delimiter)
630
631 value = web.escape(value)
632
633
634 value = string.replace(value,
635 break_delimiter, r"<blink>\</blink>")
636
637 return '<pre>%s</pre>' % value
638 elif self.__structured:
639 return web.format_structured_text(value)
640 else:
641 if value == "":
642
643
644 return " "
645 else:
646 return web.escape(value)
647
648 else:
649 raise ValueError, style
650
651
652 - def MakeDomNodeForValue(self, value, document):
653
654 return xmlutil.create_dom_text_element(document, "text", value)
655
656
657
658 - def Validate(self, value):
659
660 if not isinstance(value, types.StringTypes):
661 raise ValueError, value
662
663
664 if not self.__verbatim:
665
666 value = string.lstrip(value)
667
668
669 if self.__not_empty_text and value == "":
670 raise ValueError, \
671 qm.error("empty text field value",
672 field_title=self.GetTitle())
673
674
675 if not self.__multiline:
676 value = re.sub(" *\n+ *", " ", value)
677 return value
678
679
686
687
688 - def ParseTextValue(self, value):
689
690 return self.Validate(value)
691
692
693 - def GetValueFromDomNode(self, node, attachment_store):
694
695
696 if node.nodeType != xml.dom.Node.ELEMENT_NODE \
697 or node.tagName != "text":
698 raise qm.QMException, \
699 qm.error("dom wrong tag for field",
700 name=self.GetName(),
701 right_tag="text",
702 wrong_tag=node.tagName)
703 return self.Validate(xmlutil.get_dom_text(node))
704
705
706
707
709 """A 'TupleField' contains zero or more other 'Field' objects.
710
711 The contained 'Field' objects may have different types. The value
712 of a 'TupleField' is a Python list; the values in the list
713 correspond to the values of the contained 'Field' objects. For
714 example, '["abc", 3]' would be a valid value for a 'TupleField'
715 containing a 'TextField' and an 'IntegerField'."""
716
717 - def __init__(self, name = "", fields = None, **properties):
718 """Construct a new 'TupleField'.
719
720 'name' -- The name of the field.
721
722 'fields' -- A sequence of 'Field' instances.
723
724 The new 'TupleField' stores a list whose elements correspond to
725 the 'fields'."""
726
727 self.__fields = fields == None and [] or fields
728 default_value = map(lambda f: f.GetDefaultValue(), self.__fields)
729 Field.__init__(self, name, default_value, **properties)
730
731
733
734 help = ""
735 need_space = 0
736 for f in self.__fields:
737 if need_space:
738 help += "\n"
739 else:
740 need_space = 1
741 help += "** " + f.GetTitle() + " **\n\n"
742 help += f.GetHelp()
743
744 return help
745
746
748
749 return self.__fields
750
751
752
753
771
772
774
775 element = document.createElement("tuple")
776 for f, v in map(None, self.__fields, value):
777 element.appendChild(f.MakeDomNodeForValue(v, document))
778
779 return element
780
781
782
788
789
806
807
809
810 values = []
811 for f, element in map(None, self.__fields, node.childNodes):
812 values.append(f.GetValueFromDomNode(element, attachment_store))
813
814 return self.Validate(values)
815
816
817
819 """A 'DictionaryField' maps keys to values."""
820
821 - def __init__(self, key_field, value_field, **properties):
822 """Construct a new 'DictionaryField'.
823
824 'key_field' -- The key field.
825
826 'value_field' -- The value field.
827 """
828
829 self.__key_field = key_field
830 self.__value_field = value_field
831 super(DictionaryField, self).__init__(**properties)
832
833
835
836 help = """
837 A dictionary field. A dictionary maps keys to values. The key type:
838 %s
839 The value type:
840 %s"""%(self.__key_field.GetHelp(), self.__value_field.GetHelp())
841 return help
842
843
846
847
848
921
922
924
925 element = document.createElement('dictionary')
926 for k, v in value.iteritems():
927 item = element.appendChild(document.createElement('item'))
928 item.appendChild(self.__key_field.MakeDomNodeForValue(k, document))
929 item.appendChild(self.__value_field.MakeDomNodeForValue(v, document))
930 return element
931
932
933
934
936
937 valid = {}
938 for k, v in value.items():
939 valid[self.__key_field.Validate(k)] = self.__value_field.Validate(v)
940
941 return valid
942
943
944 - def ParseTextValue(self, value):
945
946 raise NotImplementedError
947
948
988
989
991
992 values = {}
993 for item in node.childNodes:
994 if item.nodeType == xml.dom.Node.ELEMENT_NODE:
995
996
997 children = [c for c in item.childNodes
998 if c.nodeType == xml.dom.Node.ELEMENT_NODE]
999 values[self.__key_field.GetValueFromDomNode
1000 (children[0], attachment_store)] =\
1001 self.__value_field.GetValueFromDomNode(children[1],
1002 attachment_store)
1003 return self.Validate(values)
1004
1005
1006
1008 """A field containing zero or more instances of some other field.
1009
1010 All contents must be of the same field type. A set field may not
1011 contain sets.
1012
1013 The default field value is set to an empty set."""
1014
1015 - def __init__(self, contained, not_empty_set = "false", default_value = None,
1016 **properties):
1017 """Create a set field.
1018
1019 The name of the contained field is taken as the name of this
1020 field.
1021
1022 'contained' -- An 'Field' instance describing the
1023 elements of the set.
1024
1025 'not_empty_set' -- If true, this field may not be empty,
1026 i.e. the value of this field must contain at least one element.
1027
1028 raises -- 'ValueError' if 'contained' is a set field.
1029
1030 raises -- 'TypeError' if 'contained' is not a 'Field'."""
1031
1032 if not properties.has_key('description'):
1033 properties['description'] = contained.GetDescription()
1034
1035 super(SetField, self).__init__(
1036 contained.GetName(),
1037 default_value or [],
1038 title = contained.GetTitle(),
1039 **properties)
1040
1041
1042 if isinstance(contained, SetField):
1043 raise ValueError, \
1044 "A set field may not contain a set field."
1045 if not isinstance(contained, Field):
1046 raise TypeError, "A set must contain another field."
1047
1048 self.__contained = contained
1049 self.__not_empty_set = not_empty_set == "true"
1050
1051
1053 return """
1054 A set field. A set contains zero or more elements, all of the
1055 same type. The elements of the set are described below:
1056
1057 """ + self.__contained.GetHelp()
1058
1059
1061
1062 return (self.__contained,)
1063
1064
1066 help = Field.GetHtmlHelp(self)
1067 if edit:
1068
1069
1070 help = help + """
1071 <hr noshade size="2">
1072 <h4>Modifying This Field</h4>
1073
1074 <p>Add a new element to the set by clicking the
1075 <i>Add</i> button. The new element will have a default
1076 value until you change it. To remove elements from the
1077 set, select them by checking the boxes on the left side of
1078 the form. Then, click the <i>Remove</i> button.</p>
1079 """
1080 return help
1081
1082
1083
1097
1098
1173
1174
1176
1177
1178 element = document.createElement("set")
1179
1180 contained_field = self.__contained
1181 for item in value:
1182
1183
1184 item_node = contained_field.MakeDomNodeForValue(item, document)
1185 element.appendChild(item_node)
1186 return element
1187
1188
1189
1191
1192
1193
1194 if self.__not_empty_set and len(value) == 0:
1195 raise ValueError, \
1196 qm.error("empty set field value",
1197 field_title=self.GetTitle())
1198
1199
1200 return map(lambda v: self.__contained.Validate(v),
1201 value)
1202
1203
1204 - def ParseTextValue(self, value):
1205
1206 def invalid(tok):
1207 """Raise an exception indicating a problem with 'value'.
1208
1209 'tok' -- A token indicating the position of the problem.
1210
1211 This function does not return; instead, it raises an
1212 appropriate exception."""
1213
1214 raise qm.QMException, \
1215 qm.error("invalid set value", start = value[tok[2][1]:])
1216
1217
1218 s = StringIO.StringIO(value)
1219 g = tokenize.generate_tokens(s.readline)
1220
1221
1222 tok = g.next()
1223 if tok[0] != tokenize.OP or tok[1] != "[":
1224 invalid(tok)
1225
1226
1227 elements = []
1228
1229
1230 while 1:
1231
1232
1233 tok = g.next()
1234 if tok[0] == tokenize.OP and tok[1] == "]":
1235 break
1236
1237
1238 if elements:
1239 if tok[0] != tokenize.OP or tok[1] != ",":
1240 invalid(tok)
1241 tok = g.next()
1242
1243 if tok[0] != tokenize.STRING:
1244 invalid(tok)
1245
1246 v = eval(tok[1])
1247 elements.append(self.__contained.ParseTextValue(v))
1248
1249
1250 tok = g.next()
1251 if not tokenize.ISEOF(tok[0]):
1252 invalid(tok)
1253
1254 return self.Validate(elements)
1255
1256
1306
1307
1309
1310 if node.nodeType != xml.dom.Node.ELEMENT_NODE \
1311 or node.tagName != "set":
1312 raise qm.QMException, \
1313 qm.error("dom wrong tag for field",
1314 name=self.GetName(),
1315 right_tag="set",
1316 wrong_tag=node.tagName)
1317
1318
1319 contained_field = self.__contained
1320 fn = lambda n, f=contained_field, s=attachment_store: \
1321 f.GetValueFromDomNode(n, s)
1322 values = map(fn,
1323 filter(lambda n: n.nodeType == xml.dom.Node.ELEMENT_NODE,
1324 node.childNodes))
1325 return self.Validate(values)
1326
1327
1328
1329
1330
1331 -class UploadAttachmentPage(web.DtmlPage):
1332 """DTML context for generating upload-attachment.dtml."""
1333
1334 - def __init__(self,
1335 attachment_store,
1336 field_name,
1337 encoding_name,
1338 summary_field_name,
1339 in_set=0):
1340 """Create a new page object.
1341
1342 'attachment_store' -- The AttachmentStore in which the new
1343 attachment will be placed.
1344
1345 'field_name' -- The user-visible name of the field for which an
1346 attachment is being uploaded.
1347
1348 'encoding_name' -- The name of the HTML input that should
1349 contain the encoded attachment.
1350
1351 'summary_field_name' -- The name of the HTML input that should
1352 contain the user-visible summary of the attachment.
1353
1354 'in_set' -- If true, the attachment is being added to an
1355 attachment set field."""
1356
1357 web.DtmlPage.__init__(self, "attachment.dtml")
1358
1359 self.location = attachment.make_temporary_location()
1360
1361 self.attachment_store_id = id(attachment_store)
1362 self.field_name = field_name
1363 self.encoding_name = encoding_name
1364 self.summary_field_name = summary_field_name
1365 self.in_set = in_set
1366
1367
1368 - def MakeSubmitUrl(self):
1369 """Return the URL for submitting this form."""
1370
1371 return self.request.copy(AttachmentField.upload_url).AsUrl()
1372
1373
1374
1376 """A field containing a file attachment.
1377
1378 Note that the 'FormatValueAsHtml' method uses a popup upload form
1379 for uploading new attachment. The web server must be configured to
1380 handle the attachment submission requests. See
1381 'attachment.register_attachment_upload_script'."""
1382
1383 upload_url = "/attachment-upload"
1384 """The URL used to upload data for an attachment.
1385
1386 The upload request will include these query arguments:
1387
1388 'location' -- The location at which to store the attachment data.
1389
1390 'file_data' -- The attachment data.
1391
1392 """
1393
1394 download_url = "/attachment-download"
1395 """The URL used to download an attachment.
1396
1397 The download request will include this query argument:
1398
1399 'location' -- The location in the attachment store from which to
1400 retrieve the attachment data.
1401
1402 """
1403
1404
1405 - def __init__(self, name = "", **properties):
1406 """Create an attachment field.
1407
1408 Sets the default value of the field to 'None'."""
1409
1410
1411 apply(Field.__init__, (self, name, None), properties)
1412
1413
1415 return """
1416 An attachment field. An attachment consists of an uploaded
1417 file, which may be of any file type, plus a short description.
1418 The name of the file, as well as the file's MIME type, are also
1419 stored. The description is a single line of plain text.
1420
1421 An attachment need not be provided. The field may be left
1422 empty."""
1423
1424
1426 help = Field.GetHtmlHelp(self)
1427 if edit:
1428
1429
1430 help = help + """
1431 <hr noshade size="2">
1432 <h4>Modifying This Field</h4>
1433
1434 <p>The text control describes the current value of this
1435 field, displaying the attachment's description, file name,
1436 and MIME type. If the field is empty, the text control
1437 displays "None". The text control cannot be edited.</p>
1438
1439 <p>To upload a new attachment (replacing the previous one,
1440 if any), click on the <i>Change...</i> button. To clear the
1441 current attachment and make the field empty, click on the
1442 <i>Clear</i> button.</p>
1443 """
1444 return help
1445
1446
1447
1451
1452
1585
1586
1589
1590
1604
1605
1606
1607
1615
1616
1638
1639
1641
1642
1643 if node.nodeType != xml.dom.Node.ELEMENT_NODE \
1644 or node.tagName != "attachment":
1645 raise qm.QMException, \
1646 qm.error("dom wrong tag for field",
1647 name=self.GetName(),
1648 right_tag="attachment",
1649 wrong_tag=node.tagName)
1650 return self.Validate(attachment.from_dom_node(node, attachment_store))
1651
1652
1653
1654
1656 """A 'ChoiceField' allows choosing one of several values.
1657
1658 The set of acceptable values can be determined when the field is
1659 created or dynamically. The empty string is used as the "no
1660 choice" value, and cannot therefore be one of the permitted
1661 values."""
1662
1664 """Return the options from which to choose.
1665
1666 returns -- A sequence of strings, each of which will be
1667 presented as a choice for the user."""
1668
1669 raise NotImplementedError
1670
1671
1698
1699
1706
1707
1708
1710 """A field that contains an enumeral value.
1711
1712 The enumeral value is selected from an enumerated set of values.
1713 An enumeral field uses the following properties:
1714
1715 enumeration -- A mapping from enumeral names to enumeral values.
1716 Names are converted to strings, and values are stored as integers.
1717
1718 ordered -- If non-zero, the enumerals are presented to the user
1719 ordered by value."""
1720
1721 - def __init__(self,
1722 name = "",
1723 default_value=None,
1724 enumerals=[],
1725 **properties):
1726 """Create an enumeration field.
1727
1728 'enumerals' -- A sequence of strings of available
1729 enumerals.
1730
1731 'default_value' -- The default value for this enumeration. If
1732 'None', the first enumeral is used."""
1733
1734
1735 if isinstance(enumerals, types.StringType):
1736 enumerals = string.split(enumerals, ",")
1737
1738 if not default_value in enumerals and len(enumerals) > 0:
1739 default_value = enumerals[0]
1740
1741 super(EnumerationField, self).__init__(name, default_value,
1742 **properties)
1743
1744 self.__enumerals = enumerals
1745
1746
1748 """Return a sequence of enumerals.
1749
1750 returns -- A sequence consisting of string enumerals objects, in
1751 the appropriate order."""
1752
1753 return self.__enumerals
1754
1755
1757 enumerals = self.GetItems()
1758 help = """
1759 An enumeration field. The value of this field must be one of a
1760 preselected set of enumerals. The enumerals for this field are,
1761
1762 """
1763 for enumeral in enumerals:
1764 help = help + ' * "%s"\n\n' % enumeral
1765 help = help + '''
1766
1767 The default value of this field is "%s".
1768 ''' % str(self.GetDefaultValue())
1769 return help
1770
1771
1772
1774
1775
1776 return xmlutil.create_dom_text_element(document, "enumeral",
1777 str(value))
1778
1779
1780
1781
1783
1784
1785 if node.nodeType != xml.dom.Node.ELEMENT_NODE \
1786 or node.tagName != "enumeral":
1787 raise qm.QMException, \
1788 qm.error("dom wrong tag for field",
1789 name=self.GetName(),
1790 right_tag="enumeral",
1791 wrong_tag=node.tagName)
1792
1793 return self.Validate(xmlutil.get_dom_text(node))
1794
1795
1796
1798 """A field containing a boolean value.
1799
1800 The enumeration contains two values: true and false."""
1801
1802 - def __init__(self, name = "", default_value = None, **properties):
1807
1808
1816
1817
1818
1819
1821 """A field containing a date and time.
1822
1823 The data and time is stored as seconds since the start of the UNIX
1824 epoch, UTC (the semantics of the standard 'time' function), with
1825 one-second precision. User representations of 'TimeField' fields
1826 show one-minue precision."""
1827
1828 - def __init__(self, name = "", **properties):
1829 """Create a time field.
1830
1831 The field is given a default value for this field is 'None', which
1832 corresponds to the current time when the field value is first
1833 created."""
1834
1835
1836 super(TimeField, self).__init__(name, None, **properties)
1837
1838
1840 if time.daylight:
1841 time_zones = "%s or %s" % time.tzname
1842 else:
1843 time_zones = time.tzname[0]
1844 help = """
1845 This field contains a time and date. The format for the
1846 time and date is 'YYYY-MM-DD HH:MM ZZZ'. The 'ZZZ' field is
1847 the time zone, and may be the local time zone (%s) or
1848 "UTC".
1849
1850 If the date component is omitted, today's date is used. If
1851 the time component is omitted, midnight is used. If the
1852 time zone component is omitted, the local time zone is
1853 used.
1854 """ % time_zones
1855 default_value = self.GetDefaultValue()
1856 if default_value is None:
1857 help = help + """
1858 The default value for this field is the current time.
1859 """
1860 else:
1861 help = help + """
1862 The default value for this field is %s.
1863 """ % self.FormatValueAsText(default_value)
1864 return help
1865
1866
1867
1873
1874
1895
1896
1897
1898 - def ParseTextValue(self, value):
1899
1900 return self.Validate(qm.common.parse_time(value,
1901 default_local_time_zone=1))
1902
1903
1905
1906 default_value = super(TimeField, self).GetDefaultValue()
1907 if default_value is not None:
1908 return default_value
1909
1910 return int(time.time())
1911
1912
1913
1915 """A 'PythonField' stores a Python value.
1916
1917 All 'PythonField's are computed; they are never written out, nor can
1918 they be specified directly by users. They are used in situations
1919 where the value of the field is specified programatically by the
1920 system."""
1921
1925
1926
1927
1928
1929
1930
1931
1932