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
272 """Return an HTML rendering of a 'value' for this field.
273
274 'server' -- The 'WebServer' in which the HTML will be
275 displayed.
276
277 'value' -- The value for this field. May be 'None', which
278 renders a default value (useful for blank forms).
279
280 'style' -- The rendering style. Can be "full" or "brief" (both
281 read-only), or "new" or "edit" or "hidden".
282
283 'name' -- The name to use for the primary HTML form element
284 containing the value of this field, if 'style' specifies the
285 generation of form elements. If 'name' is 'None', the value
286 returned by 'GetHtmlFormFieldName()' should be used.
287
288 returns -- A string containing the HTML representation of
289 'value'."""
290
291 raise NotImplementedError
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
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
1000
1001
1002
1004 """A field containing zero or more instances of some other field.
1005
1006 All contents must be of the same field type. A set field may not
1007 contain sets.
1008
1009 The default field value is set to an empty set."""
1010
1011 - def __init__(self, contained, not_empty_set = "false", default_value = None,
1012 **properties):
1013 """Create a set field.
1014
1015 The name of the contained field is taken as the name of this
1016 field.
1017
1018 'contained' -- An 'Field' instance describing the
1019 elements of the set.
1020
1021 'not_empty_set' -- If true, this field may not be empty,
1022 i.e. the value of this field must contain at least one element.
1023
1024 raises -- 'ValueError' if 'contained' is a set field.
1025
1026 raises -- 'TypeError' if 'contained' is not a 'Field'."""
1027
1028 if not properties.has_key('description'):
1029 properties['description'] = contained.GetDescription()
1030
1031 super(SetField, self).__init__(
1032 contained.GetName(),
1033 default_value or [],
1034 title = contained.GetTitle(),
1035 **properties)
1036
1037
1038 if isinstance(contained, SetField):
1039 raise ValueError, \
1040 "A set field may not contain a set field."
1041 if not isinstance(contained, Field):
1042 raise TypeError, "A set must contain another field."
1043
1044 self.__contained = contained
1045 self.__not_empty_set = not_empty_set == "true"
1046
1047
1049 return """
1050 A set field. A set contains zero or more elements, all of the
1051 same type. The elements of the set are described below:
1052
1053 """ + self.__contained.GetHelp()
1054
1055
1057
1058 return (self.__contained,)
1059
1060
1062 help = Field.GetHtmlHelp(self)
1063 if edit:
1064
1065
1066 help = help + """
1067 <hr noshade size="2">
1068 <h4>Modifying This Field</h4>
1069
1070 <p>Add a new element to the set by clicking the
1071 <i>Add</i> button. The new element will have a default
1072 value until you change it. To remove elements from the
1073 set, select them by checking the boxes on the left side of
1074 the form. Then, click the <i>Remove</i> button.</p>
1075 """
1076 return help
1077
1078
1079
1093
1094
1169
1170
1172
1173
1174 element = document.createElement("set")
1175
1176 contained_field = self.__contained
1177 for item in value:
1178
1179
1180 item_node = contained_field.MakeDomNodeForValue(item, document)
1181 element.appendChild(item_node)
1182 return element
1183
1184
1185
1187
1188
1189
1190 if self.__not_empty_set and len(value) == 0:
1191 raise ValueError, \
1192 qm.error("empty set field value",
1193 field_title=self.GetTitle())
1194
1195
1196 return map(lambda v: self.__contained.Validate(v),
1197 value)
1198
1199
1200 - def ParseTextValue(self, value):
1201
1203 """Raise an exception indicating a problem with 'value'.
1204
1205 'tok' -- A token indicating the position of the problem.
1206
1207 This function does not return; instead, it raises an
1208 appropriate exception."""
1209
1210 raise qm.QMException, \
1211 qm.error("invalid set value", start = value[tok[2][1]:])
1212
1213
1214 s = StringIO.StringIO(value)
1215 g = tokenize.generate_tokens(s.readline)
1216
1217
1218 tok = g.next()
1219 if tok[0] != tokenize.OP or tok[1] != "[":
1220 invalid(tok)
1221
1222
1223 elements = []
1224
1225
1226 while 1:
1227
1228
1229 tok = g.next()
1230 if tok[0] == tokenize.OP and tok[1] == "]":
1231 break
1232
1233
1234 if elements:
1235 if tok[0] != tokenize.OP or tok[1] != ",":
1236 invalid(tok)
1237 tok = g.next()
1238
1239 if tok[0] != tokenize.STRING:
1240 invalid(tok)
1241
1242 v = eval(tok[1])
1243 elements.append(self.__contained.ParseTextValue(v))
1244
1245
1246 tok = g.next()
1247 if not tokenize.ISEOF(tok[0]):
1248 invalid(tok)
1249
1250 return self.Validate(elements)
1251
1252
1302
1303
1305
1306 if node.nodeType != xml.dom.Node.ELEMENT_NODE \
1307 or node.tagName != "set":
1308 raise qm.QMException, \
1309 qm.error("dom wrong tag for field",
1310 name=self.GetName(),
1311 right_tag="set",
1312 wrong_tag=node.tagName)
1313
1314
1315 contained_field = self.__contained
1316 fn = lambda n, f=contained_field, s=attachment_store: \
1317 f.GetValueFromDomNode(n, s)
1318 values = map(fn,
1319 filter(lambda n: n.nodeType == xml.dom.Node.ELEMENT_NODE,
1320 node.childNodes))
1321 return self.Validate(values)
1322
1323
1324
1325
1326
1327 -class UploadAttachmentPage(web.DtmlPage):
1328 """DTML context for generating upload-attachment.dtml."""
1329
1330 - def __init__(self,
1331 attachment_store,
1332 field_name,
1333 encoding_name,
1334 summary_field_name,
1335 in_set=0):
1336 """Create a new page object.
1337
1338 'attachment_store' -- The AttachmentStore in which the new
1339 attachment will be placed.
1340
1341 'field_name' -- The user-visible name of the field for which an
1342 attachment is being uploaded.
1343
1344 'encoding_name' -- The name of the HTML input that should
1345 contain the encoded attachment.
1346
1347 'summary_field_name' -- The name of the HTML input that should
1348 contain the user-visible summary of the attachment.
1349
1350 'in_set' -- If true, the attachment is being added to an
1351 attachment set field."""
1352
1353 web.DtmlPage.__init__(self, "attachment.dtml")
1354
1355 self.location = attachment.make_temporary_location()
1356
1357 self.attachment_store_id = id(attachment_store)
1358 self.field_name = field_name
1359 self.encoding_name = encoding_name
1360 self.summary_field_name = summary_field_name
1361 self.in_set = in_set
1362
1363
1364 - def MakeSubmitUrl(self):
1365 """Return the URL for submitting this form."""
1366
1367 return self.request.copy(AttachmentField.upload_url).AsUrl()
1368
1369
1370
1372 """A field containing a file attachment.
1373
1374 Note that the 'FormatValueAsHtml' method uses a popup upload form
1375 for uploading new attachment. The web server must be configured to
1376 handle the attachment submission requests. See
1377 'attachment.register_attachment_upload_script'."""
1378
1379 upload_url = "/attachment-upload"
1380 """The URL used to upload data for an attachment.
1381
1382 The upload request will include these query arguments:
1383
1384 'location' -- The location at which to store the attachment data.
1385
1386 'file_data' -- The attachment data.
1387
1388 """
1389
1390 download_url = "/attachment-download"
1391 """The URL used to download an attachment.
1392
1393 The download request will include this query argument:
1394
1395 'location' -- The location in the attachment store from which to
1396 retrieve the attachment data.
1397
1398 """
1399
1400
1401 - def __init__(self, name = "", **properties):
1402 """Create an attachment field.
1403
1404 Sets the default value of the field to 'None'."""
1405
1406
1407 apply(Field.__init__, (self, name, None), properties)
1408
1409
1411 return """
1412 An attachment field. An attachment consists of an uploaded
1413 file, which may be of any file type, plus a short description.
1414 The name of the file, as well as the file's MIME type, are also
1415 stored. The description is a single line of plain text.
1416
1417 An attachment need not be provided. The field may be left
1418 empty."""
1419
1420
1422 help = Field.GetHtmlHelp(self)
1423 if edit:
1424
1425
1426 help = help + """
1427 <hr noshade size="2">
1428 <h4>Modifying This Field</h4>
1429
1430 <p>The text control describes the current value of this
1431 field, displaying the attachment's description, file name,
1432 and MIME type. If the field is empty, the text control
1433 displays "None". The text control cannot be edited.</p>
1434
1435 <p>To upload a new attachment (replacing the previous one,
1436 if any), click on the <i>Change...</i> button. To clear the
1437 current attachment and make the field empty, click on the
1438 <i>Clear</i> button.</p>
1439 """
1440 return help
1441
1442
1443
1447
1448
1581
1582
1585
1586
1600
1601
1602
1603
1611
1612
1634
1635
1637
1638
1639 if node.nodeType != xml.dom.Node.ELEMENT_NODE \
1640 or node.tagName != "attachment":
1641 raise qm.QMException, \
1642 qm.error("dom wrong tag for field",
1643 name=self.GetName(),
1644 right_tag="attachment",
1645 wrong_tag=node.tagName)
1646 return self.Validate(attachment.from_dom_node(node, attachment_store))
1647
1648
1649
1650
1652 """A 'ChoiceField' allows choosing one of several values.
1653
1654 The set of acceptable values can be determined when the field is
1655 created or dynamically. The empty string is used as the "no
1656 choice" value, and cannot therefore be one of the permitted
1657 values."""
1658
1660 """Return the options from which to choose.
1661
1662 returns -- A sequence of strings, each of which will be
1663 presented as a choice for the user."""
1664
1665 raise NotImplementedError
1666
1667
1694
1695
1702
1703
1704
1706 """A field that contains an enumeral value.
1707
1708 The enumeral value is selected from an enumerated set of values.
1709 An enumeral field uses the following properties:
1710
1711 enumeration -- A mapping from enumeral names to enumeral values.
1712 Names are converted to strings, and values are stored as integers.
1713
1714 ordered -- If non-zero, the enumerals are presented to the user
1715 ordered by value."""
1716
1717 - def __init__(self,
1718 name = "",
1719 default_value=None,
1720 enumerals=[],
1721 **properties):
1722 """Create an enumeration field.
1723
1724 'enumerals' -- A sequence of strings of available
1725 enumerals.
1726
1727 'default_value' -- The default value for this enumeration. If
1728 'None', the first enumeral is used."""
1729
1730
1731 if isinstance(enumerals, types.StringType):
1732 enumerals = string.split(enumerals, ",")
1733
1734 if not default_value in enumerals and len(enumerals) > 0:
1735 default_value = enumerals[0]
1736
1737 super(EnumerationField, self).__init__(name, default_value,
1738 **properties)
1739
1740 self.__enumerals = enumerals
1741
1742
1744 """Return a sequence of enumerals.
1745
1746 returns -- A sequence consisting of string enumerals objects, in
1747 the appropriate order."""
1748
1749 return self.__enumerals
1750
1751
1753 enumerals = self.GetItems()
1754 help = """
1755 An enumeration field. The value of this field must be one of a
1756 preselected set of enumerals. The enumerals for this field are,
1757
1758 """
1759 for enumeral in enumerals:
1760 help = help + ' * "%s"\n\n' % enumeral
1761 help = help + '''
1762
1763 The default value of this field is "%s".
1764 ''' % str(self.GetDefaultValue())
1765 return help
1766
1767
1768
1770
1771
1772 return xmlutil.create_dom_text_element(document, "enumeral",
1773 str(value))
1774
1775
1776
1777
1779
1780
1781 if node.nodeType != xml.dom.Node.ELEMENT_NODE \
1782 or node.tagName != "enumeral":
1783 raise qm.QMException, \
1784 qm.error("dom wrong tag for field",
1785 name=self.GetName(),
1786 right_tag="enumeral",
1787 wrong_tag=node.tagName)
1788
1789 return self.Validate(xmlutil.get_dom_text(node))
1790
1791
1792
1794 """A field containing a boolean value.
1795
1796 The enumeration contains two values: true and false."""
1797
1798 - def __init__(self, name = "", default_value = None, **properties):
1803
1804
1812
1813
1814
1815
1817 """A field containing a date and time.
1818
1819 The data and time is stored as seconds since the start of the UNIX
1820 epoch, UTC (the semantics of the standard 'time' function), with
1821 one-second precision. User representations of 'TimeField' fields
1822 show one-minue precision."""
1823
1824 - def __init__(self, name = "", **properties):
1825 """Create a time field.
1826
1827 The field is given a default value for this field is 'None', which
1828 corresponds to the current time when the field value is first
1829 created."""
1830
1831
1832 super(TimeField, self).__init__(name, None, **properties)
1833
1834
1836 if time.daylight:
1837 time_zones = "%s or %s" % time.tzname
1838 else:
1839 time_zones = time.tzname[0]
1840 help = """
1841 This field contains a time and date. The format for the
1842 time and date is 'YYYY-MM-DD HH:MM ZZZ'. The 'ZZZ' field is
1843 the time zone, and may be the local time zone (%s) or
1844 "UTC".
1845
1846 If the date component is omitted, today's date is used. If
1847 the time component is omitted, midnight is used. If the
1848 time zone component is omitted, the local time zone is
1849 used.
1850 """ % time_zones
1851 default_value = self.GetDefaultValue()
1852 if default_value is None:
1853 help = help + """
1854 The default value for this field is the current time.
1855 """
1856 else:
1857 help = help + """
1858 The default value for this field is %s.
1859 """ % self.FormatValueAsText(default_value)
1860 return help
1861
1862
1863
1869
1870
1891
1892
1893
1894 - def ParseTextValue(self, value):
1895
1896 return self.Validate(qm.common.parse_time(value,
1897 default_local_time_zone=1))
1898
1899
1901
1902 default_value = super(TimeField, self).GetDefaultValue()
1903 if default_value is not None:
1904 return default_value
1905
1906 return int(time.time())
1907
1908
1909
1911 """A 'PythonField' stores a Python value.
1912
1913 All 'PythonField's are computed; they are never written out, nor can
1914 they be specified directly by users. They are used in situations
1915 where the value of the field is specified programatically by the
1916 system."""
1917
1921
1922
1923
1924
1925
1926
1927
1928