1 """Fields are used to control what's displayed in forms. It makes the link |
1 # organization: Logilab |
2 between something to edit and its display in the form. Actual display is handled |
2 # copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. |
3 by a widget associated to the field. |
3 # contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
4 |
4 # license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
5 :organization: Logilab |
5 """ |
6 :copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. |
6 The Field class and basic fields |
7 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
7 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
8 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
8 |
|
9 .. Note:: |
|
10 Fields are used to control what's edited in forms. They makes the link between |
|
11 something to edit and its display in the form. Actual display is handled by a |
|
12 widget associated to the field. |
|
13 |
|
14 Let first see the base class for fields: |
|
15 |
|
16 .. autoclass:: cubicweb.web.formfields.Field |
|
17 |
|
18 Now, you usually don't use that class but one of the concret field classes |
|
19 described below, according to what you want to edit. |
|
20 |
|
21 Basic fields |
|
22 '''''''''''' |
|
23 |
|
24 .. autoclass:: cubicweb.web.formfields.StringField() |
|
25 .. autoclass:: cubicweb.web.formfields.PasswordField() |
|
26 .. autoclass:: cubicweb.web.formfields.IntField() |
|
27 .. autoclass:: cubicweb.web.formfields.FloatField() |
|
28 .. autoclass:: cubicweb.web.formfields.BooleanField() |
|
29 .. autoclass:: cubicweb.web.formfields.DateField() |
|
30 .. autoclass:: cubicweb.web.formfields.DateTimeField() |
|
31 .. autoclass:: cubicweb.web.formfields.TimeField() |
|
32 |
|
33 Compound fields |
|
34 '''''''''''''''' |
|
35 |
|
36 .. autoclass:: cubicweb.web.formfields.RichTextField() |
|
37 .. autoclass:: cubicweb.web.formfields.FileField() |
|
38 .. autoclass:: cubicweb.web.formfields.CompoundField() |
|
39 |
|
40 .. autoclass cubicweb.web.formfields.EditableFileField() XXX should be a widget |
|
41 |
|
42 Entity specific fields and function |
|
43 ''''''''''''''''''''''''''''''''''' |
|
44 |
|
45 .. autoclass:: cubicweb.web.formfields.RelationField() |
|
46 .. autofunction:: cubicweb.web.formfields.guess_field |
|
47 |
9 """ |
48 """ |
10 __docformat__ = "restructuredtext en" |
49 __docformat__ = "restructuredtext en" |
11 |
50 |
12 from warnings import warn |
51 from warnings import warn |
13 from datetime import datetime |
52 from datetime import datetime |
51 class Field(object): |
90 class Field(object): |
52 """This class is the abstract base class for all fields. It hold a bunch |
91 """This class is the abstract base class for all fields. It hold a bunch |
53 of attributes which may be used for fine control of the behaviour of a |
92 of attributes which may be used for fine control of the behaviour of a |
54 concret field. |
93 concret field. |
55 |
94 |
|
95 **Attributes** |
|
96 |
56 All the attributes described below have sensible default value which may be |
97 All the attributes described below have sensible default value which may be |
57 overriden by value given to field's constructor. |
98 overriden by named arguments given to field's constructor. |
58 |
99 |
59 :name: |
100 :attr:`name` |
60 name of the field (basestring), should be unique in a form. |
101 base name of the field (basestring). The actual input name is returned by |
61 :id: |
102 the :meth:`input_name` method and may differ from that name (for instance |
62 dom identifier (default to the same value as `name`), should be unique in |
103 if `eidparam` is true). |
|
104 :attr:`id` |
|
105 DOM identifier (default to the same value as `name`), should be unique in |
63 a form. |
106 a form. |
64 :label: |
107 :attr:`label` |
65 label of the field (default to the same value as `name`). |
108 label of the field (default to the same value as `name`). |
66 :help: |
109 :attr:`help` |
67 help message about this field. |
110 help message about this field. |
68 :widget: |
111 :attr:`widget` |
69 widget associated to the field. Each field class has a default widget |
112 widget associated to the field. Each field class has a default widget |
70 class which may be overriden per instance. |
113 class which may be overriden per instance. |
71 :required: |
114 :attr:`value` |
|
115 field value. May be an actual value or a callable which should take the |
|
116 form as argument and return a value. |
|
117 :attr:`choices` |
|
118 static vocabulary for this field. May be a list of values, a list of |
|
119 (label, value) tuples or a callable which should take the form and field |
|
120 as arguments and return a list of values or a list of (label, value). |
|
121 :attr:`required` |
72 bool flag telling if the field is required or not. |
122 bool flag telling if the field is required or not. |
73 :value: |
123 :attr:`sort` |
74 field value (may be an actual value, a default value or nothing) |
|
75 :choices: |
|
76 static vocabulary for this field. May be a list of values or a list of |
|
77 (label, value) tuples if specified. |
|
78 :sort: |
|
79 bool flag telling if the vocabulary (either static vocabulary specified |
124 bool flag telling if the vocabulary (either static vocabulary specified |
80 in `choices` or dynamic vocabulary fetched from the form) should be |
125 in `choices` or dynamic vocabulary fetched from the form) should be |
81 sorted on label. |
126 sorted on label. |
82 :internationalizable: |
127 :attr:`internationalizable` |
83 bool flag telling if the vocabulary labels should be translated using the |
128 bool flag telling if the vocabulary labels should be translated using the |
84 current request language. |
129 current request language. |
85 :eidparam: |
130 :attr:`eidparam` |
86 bool flag telling if this field is linked to a specific entity |
131 bool flag telling if this field is linked to a specific entity |
87 :role: |
132 :attr:`role` |
88 when the field is linked to an entity attribute or relation, tells the |
133 when the field is linked to an entity attribute or relation, tells the |
89 role of the entity in the relation (eg 'subject' or 'object') |
134 role of the entity in the relation (eg 'subject' or 'object') |
90 :fieldset: |
135 :attr:`fieldset` |
91 optional fieldset to which this field belongs to |
136 optional fieldset to which this field belongs to |
92 :order: |
137 :attr:`order` |
93 key used by automatic forms to sort fields |
138 key used by automatic forms to sort fields |
94 :ignore_req_params: |
139 :attr:`ignore_req_params` |
95 when true, this field won't consider value potentialy specified using |
140 when true, this field won't consider value potentialy specified using |
96 request's form parameters (eg you won't be able to specify a value using for |
141 request's form parameters (eg you won't be able to specify a value using for |
97 instance url like http://mywebsite.com/form?field=value) |
142 instance url like http://mywebsite.com/form?field=value) |
|
143 |
|
144 .. currentmodule:: cubicweb.web.formfields |
|
145 |
|
146 **Generic methods** |
|
147 |
|
148 .. automethod:: Field.input_name |
|
149 .. automethod:: Field.dom_id |
|
150 .. automethod:: Field.actual_fields |
|
151 |
|
152 **Form generation methods** |
|
153 |
|
154 .. automethod:: form_init |
|
155 .. automethod:: typed_value |
|
156 |
|
157 **Post handling methods** |
|
158 |
|
159 .. automethod:: process_posted |
|
160 .. automethod:: process_form_value |
|
161 |
98 """ |
162 """ |
99 # default widget associated to this class of fields. May be overriden per |
163 # default widget associated to this class of fields. May be overriden per |
100 # instance |
164 # instance |
101 widget = fw.TextInput |
165 widget = fw.TextInput |
102 # does this field requires a multipart form |
166 # does this field requires a multipart form |
162 def is_visible(self): |
226 def is_visible(self): |
163 """return true if the field is not an hidden field""" |
227 """return true if the field is not an hidden field""" |
164 return not isinstance(self.widget, fw.HiddenInput) |
228 return not isinstance(self.widget, fw.HiddenInput) |
165 |
229 |
166 def actual_fields(self, form): |
230 def actual_fields(self, form): |
167 """return actual fields composing this field in case of a compound |
231 """Fields may be composed of other fields. For instance the |
168 field, usually simply return self |
232 :class:`~cubicweb.web.formfields.RichTextField` is containing a format |
|
233 field to define the text format. This method returns actual fields that |
|
234 should be considered for display / edition. It usually simply return |
|
235 self. |
169 """ |
236 """ |
170 yield self |
237 yield self |
171 |
238 |
172 def format_value(self, req, value): |
239 def format_value(self, req, value): |
173 """return value suitable for display where value may be a list or tuple |
240 """return value suitable for display where value may be a list or tuple |
188 def get_widget(self, form): |
255 def get_widget(self, form): |
189 """return the widget instance associated to this field""" |
256 """return the widget instance associated to this field""" |
190 return self.widget |
257 return self.widget |
191 |
258 |
192 def input_name(self, form, suffix=None): |
259 def input_name(self, form, suffix=None): |
193 """return 'qualified name' for this field""" |
260 """Return the 'qualified name' for this field, e.g. something suitable |
|
261 to use as HTML input name. You can specify a suffix that will be |
|
262 included in the name when widget needs several inputs. |
|
263 """ |
194 # caching is necessary else we get some pb on entity creation : |
264 # caching is necessary else we get some pb on entity creation : |
195 # entity.eid is modified from creation mark (eg 'X') to its actual eid |
265 # entity.eid is modified from creation mark (eg 'X') to its actual eid |
196 # (eg 123), and then `field.input_name()` won't return the right key |
266 # (eg 123), and then `field.input_name()` won't return the right key |
197 # anymore if not cached (first call to input_name done *before* eventual |
267 # anymore if not cached (first call to input_name done *before* eventual |
198 # eid affectation). |
268 # eid affectation). |
217 if self.role is not None: |
287 if self.role is not None: |
218 return role_name(self.name, self.role) |
288 return role_name(self.name, self.role) |
219 return self.name |
289 return self.name |
220 |
290 |
221 def dom_id(self, form, suffix=None): |
291 def dom_id(self, form, suffix=None): |
222 """return an html dom identifier for this field""" |
292 """Return the HTML DOM identifier for this field, e.g. something |
|
293 suitable to use as HTML input id. You can specify a suffix that will be |
|
294 included in the name when widget needs several inputs. |
|
295 """ |
223 id = self.id or self.role_name() |
296 id = self.id or self.role_name() |
224 if suffix is not None: |
297 if suffix is not None: |
225 id += suffix |
298 id += suffix |
226 if self.eidparam: |
299 if self.eidparam: |
227 return eid_param(id, form.edited_entity.eid) |
300 return eid_param(id, form.edited_entity.eid) |
228 return id |
301 return id |
229 |
302 |
230 def typed_value(self, form, load_bytes=False): |
303 def typed_value(self, form, load_bytes=False): |
|
304 """Return the correctly typed value for this field in the form context. |
|
305 """ |
231 if self.eidparam and self.role is not None: |
306 if self.eidparam and self.role is not None: |
232 entity = form.edited_entity |
307 entity = form.edited_entity |
233 if form._cw.vreg.schema.rschema(self.name).final: |
308 if form._cw.vreg.schema.rschema(self.name).final: |
234 if entity.has_eid() or self.name in entity: |
309 if entity.has_eid() or self.name in entity: |
235 value = getattr(entity, self.name) |
310 value = getattr(entity, self.name) |
322 entity.has_eid() or '%s_encoding' % self.name in entity): |
397 entity.has_eid() or '%s_encoding' % self.name in entity): |
323 return form.edited_entity.attr_metadata(self.name, 'encoding') |
398 return form.edited_entity.attr_metadata(self.name, 'encoding') |
324 return form._cw.encoding |
399 return form._cw.encoding |
325 |
400 |
326 def form_init(self, form): |
401 def form_init(self, form): |
327 """method called before by build_context to trigger potential field |
402 """Method called at form initialization to trigger potential field |
328 initialization requiring the form instance |
403 initialization requiring the form instance. Do nothing by default. |
329 """ |
404 """ |
330 pass |
405 pass |
331 |
406 |
332 def has_been_modified(self, form): |
407 def has_been_modified(self, form): |
333 # fields not corresponding to an entity attribute / relations |
408 # fields not corresponding to an entity attribute / relations |
394 for field, value in field.process_posted(form): |
472 for field, value in field.process_posted(form): |
395 yield field, value |
473 yield field, value |
396 |
474 |
397 |
475 |
398 class StringField(Field): |
476 class StringField(Field): |
|
477 """Use this field to edit unicode string (`String` yams type). This field |
|
478 additionaly support a `max_length` attribute that specify a maximum size for |
|
479 the string (`None` meaning no limit). |
|
480 |
|
481 Unless explicitly specified, the widget for this field will be: |
|
482 |
|
483 * :class:`~cubicweb.web.formwidgets.Select` if some vocabulary is specified |
|
484 using `choices` attribute |
|
485 |
|
486 * :class:`~cubicweb.web.formwidgets.TextInput` if maximum size is specified |
|
487 using `max_length` attribute and this length is inferior to 257. |
|
488 |
|
489 * :class:`~cubicweb.web.formwidgets.TextArea` in all other cases |
|
490 """ |
399 widget = fw.TextArea |
491 widget = fw.TextArea |
400 size = 45 |
492 size = 45 |
401 |
493 |
402 def __init__(self, name=None, max_length=None, **kwargs): |
494 def __init__(self, name=None, max_length=None, **kwargs): |
403 self.max_length = max_length # must be set before super call |
495 self.max_length = max_length # must be set before super call |
426 widget.attrs.setdefault('cols', 60) |
518 widget.attrs.setdefault('cols', 60) |
427 widget.attrs.setdefault('rows', 5) |
519 widget.attrs.setdefault('rows', 5) |
428 |
520 |
429 |
521 |
430 class PasswordField(StringField): |
522 class PasswordField(StringField): |
|
523 """Use this field to edit password (`Password` yams type, encoded python |
|
524 string). |
|
525 |
|
526 Unless explicitly specified, the widget for this field will be |
|
527 a :class:`~cubicweb.web.formwidgets.PasswordInput`. |
|
528 """ |
431 widget = fw.PasswordInput |
529 widget = fw.PasswordInput |
432 def form_init(self, form): |
530 def form_init(self, form): |
433 if self.eidparam and form.edited_entity.has_eid(): |
531 if self.eidparam and form.edited_entity.has_eid(): |
434 # see below: value is probably set but we can't retreive it. Ensure |
532 # see below: value is probably set but we can't retreive it. Ensure |
435 # the field isn't show as a required field on modification |
533 # the field isn't show as a required field on modification |
443 return self.initial_typed_value(form, load_bytes) |
541 return self.initial_typed_value(form, load_bytes) |
444 return super(PasswordField, self).typed_value(form, load_bytes) |
542 return super(PasswordField, self).typed_value(form, load_bytes) |
445 |
543 |
446 |
544 |
447 class RichTextField(StringField): |
545 class RichTextField(StringField): |
|
546 """This compound field allow edition of text (unicode string) in |
|
547 a particular format. It has an inner field holding the text format, |
|
548 that can be specified using `format_field` argument. If not specified |
|
549 one will be automaticall generated. |
|
550 |
|
551 Unless explicitly specified, the widget for this field will be a |
|
552 :class:`~cubicweb.web.formwidgets.FCKEditor` or a |
|
553 :class:`~cubicweb.web.formwidgets.TextArea`. according to the field's |
|
554 format and to user's preferences. |
|
555 """ |
|
556 |
448 widget = None |
557 widget = None |
449 def __init__(self, format_field=None, **kwargs): |
558 def __init__(self, format_field=None, **kwargs): |
450 super(RichTextField, self).__init__(**kwargs) |
559 super(RichTextField, self).__init__(**kwargs) |
451 self.format_field = format_field |
560 self.format_field = format_field |
452 |
561 |
514 result = u'' |
623 result = u'' |
515 return result + self.get_widget(form).render(form, self, renderer) |
624 return result + self.get_widget(form).render(form, self, renderer) |
516 |
625 |
517 |
626 |
518 class FileField(StringField): |
627 class FileField(StringField): |
|
628 """This compound field allow edition of binary stream (`Bytes` yams |
|
629 type). Three inner fields may be specified: |
|
630 |
|
631 * `format_field`, holding the file's format. |
|
632 * `encoding_field`, holding the file's content encoding. |
|
633 * `name_field`, holding the file's name. |
|
634 |
|
635 Unless explicitly specified, the widget for this field will be a |
|
636 :class:`~cubicweb.web.formwidgets.FileInput`. Inner fields, if any, |
|
637 will be added to a drop down menu at the right of the file input. |
|
638 """ |
519 widget = fw.FileInput |
639 widget = fw.FileInput |
520 needs_multipart = True |
640 needs_multipart = True |
521 |
641 |
522 def __init__(self, format_field=None, encoding_field=None, name_field=None, |
642 def __init__(self, format_field=None, encoding_field=None, name_field=None, |
523 **kwargs): |
643 **kwargs): |
602 return value |
722 return value |
603 |
723 |
604 |
724 |
605 # XXX turn into a widget |
725 # XXX turn into a widget |
606 class EditableFileField(FileField): |
726 class EditableFileField(FileField): |
|
727 """This compound field allow edition of binary stream as |
|
728 :class:`~cubicweb.web.formfields.FileField` but expect that stream to |
|
729 actually contains some text. |
|
730 |
|
731 If the stream format is one of text/plain, text/html, text/rest, |
|
732 then a :class:`~cubicweb.web.formwidgets.TextArea` will be additionaly |
|
733 displayed, allowing to directly the file's content when desired, instead |
|
734 of choosing a file from user's file system. |
|
735 """ |
607 editable_formats = ('text/plain', 'text/html', 'text/rest') |
736 editable_formats = ('text/plain', 'text/html', 'text/rest') |
608 |
737 |
609 def render(self, form, renderer): |
738 def render(self, form, renderer): |
610 wdgs = [super(EditableFileField, self).render(form, renderer)] |
739 wdgs = [super(EditableFileField, self).render(form, renderer)] |
611 if self.format(form) in self.editable_formats: |
740 if self.format(form) in self.editable_formats: |
639 return Binary(value.encode(self.encoding(form))) |
768 return Binary(value.encode(self.encoding(form))) |
640 return super(EditableFileField, self)._process_form_value(form) |
769 return super(EditableFileField, self)._process_form_value(form) |
641 |
770 |
642 |
771 |
643 class IntField(Field): |
772 class IntField(Field): |
|
773 """Use this field to edit integers (`Int` yams type). This field additionaly |
|
774 support `min` and `max` attributes that specify a minimum and/or maximum |
|
775 value for the integer (`None` meaning no boundary). |
|
776 |
|
777 Unless explicitly specified, the widget for this field will be a |
|
778 :class:`~cubicweb.web.formwidgets.TextInput`. |
|
779 """ |
644 def __init__(self, min=None, max=None, **kwargs): |
780 def __init__(self, min=None, max=None, **kwargs): |
645 super(IntField, self).__init__(**kwargs) |
781 super(IntField, self).__init__(**kwargs) |
646 self.min = min |
782 self.min = min |
647 self.max = max |
783 self.max = max |
648 if isinstance(self.widget, fw.TextInput): |
784 if isinstance(self.widget, fw.TextInput): |
672 def _ensure_correctly_typed(self, form, value): |
814 def _ensure_correctly_typed(self, form, value): |
673 return bool(value) |
815 return bool(value) |
674 |
816 |
675 |
817 |
676 class FloatField(IntField): |
818 class FloatField(IntField): |
|
819 """Use this field to edit floats (`Float` yams type). This field additionaly |
|
820 support `min` and `max` attributes as the |
|
821 :class:`~cubicweb.web.formfields.IntField`. |
|
822 |
|
823 Unless explicitly specified, the widget for this field will be a |
|
824 :class:`~cubicweb.web.formwidgets.TextInput`. |
|
825 """ |
677 def format_single_value(self, req, value): |
826 def format_single_value(self, req, value): |
678 formatstr = req.property_value('ui.float-format') |
827 formatstr = req.property_value('ui.float-format') |
679 if value is None: |
828 if value is None: |
680 return u'' |
829 return u'' |
681 return formatstr % float(value) |
830 return formatstr % float(value) |
719 raise ProcessFormError(unicode(ex)) |
873 raise ProcessFormError(unicode(ex)) |
720 return value |
874 return value |
721 |
875 |
722 |
876 |
723 class DateTimeField(DateField): |
877 class DateTimeField(DateField): |
|
878 """Use this field to edit datetime (`Datetime` yams type). |
|
879 |
|
880 Unless explicitly specified, the widget for this field will be a |
|
881 :class:`~cubicweb.web.formwidgets.JQueryDateTimePicker`. |
|
882 """ |
724 widget = fw.JQueryDateTimePicker |
883 widget = fw.JQueryDateTimePicker |
725 format_prop = 'ui.datetime-format' |
884 format_prop = 'ui.datetime-format' |
726 etype = 'Datetime' |
885 etype = 'Datetime' |
727 |
886 |
728 |
887 |
729 class TimeField(DateField): |
888 class TimeField(DateField): |
|
889 """Use this field to edit time (`Time` yams type). |
|
890 |
|
891 Unless explicitly specified, the widget for this field will be a |
|
892 :class:`~cubicweb.web.formwidgets.JQueryTimePicker`. |
|
893 """ |
730 widget = fw.JQueryTimePicker |
894 widget = fw.JQueryTimePicker |
731 format_prop = 'ui.time-format' |
895 format_prop = 'ui.time-format' |
732 etype = 'Time' |
896 etype = 'Time' |
|
897 |
|
898 |
|
899 # XXX use cases where we don't actually want a better widget? |
|
900 class CompoundField(Field): |
|
901 """This field shouldn't be used directly, it's designed to hold inner |
|
902 fields that should be conceptually groupped together. |
|
903 """ |
|
904 def __init__(self, fields, *args, **kwargs): |
|
905 super(CompoundField, self).__init__(*args, **kwargs) |
|
906 self.fields = fields |
|
907 |
|
908 def subfields(self, form): |
|
909 return self.fields |
|
910 |
|
911 def actual_fields(self, form): |
|
912 # don't add [self] to actual fields, compound field is usually kinda |
|
913 # virtual, all interesting values are in subfield. Skipping it may avoid |
|
914 # error when processed by the editcontroller : it may be marked as required |
|
915 # while it has no value, hence generating a false error. |
|
916 return list(self.fields) |
733 |
917 |
734 |
918 |
735 # relation vocabulary helper functions ######################################### |
919 # relation vocabulary helper functions ######################################### |
736 |
920 |
737 def relvoc_linkedto(entity, rtype, role): |
921 def relvoc_linkedto(entity, rtype, role): |
867 form._cw.data['pendingfields'].add( (form, self) ) |
1055 form._cw.data['pendingfields'].add( (form, self) ) |
868 return None |
1056 return None |
869 eids.add(typed_eid) |
1057 eids.add(typed_eid) |
870 return eids |
1058 return eids |
871 |
1059 |
872 # XXX use cases where we don't actually want a better widget? |
|
873 class CompoundField(Field): |
|
874 def __init__(self, fields, *args, **kwargs): |
|
875 super(CompoundField, self).__init__(*args, **kwargs) |
|
876 self.fields = fields |
|
877 |
|
878 def subfields(self, form): |
|
879 return self.fields |
|
880 |
|
881 def actual_fields(self, form): |
|
882 # don't add [self] to actual fields, compound field is usually kinda |
|
883 # virtual, all interesting values are in subfield. Skipping it may avoid |
|
884 # error when processed by the editcontroller : it may be marked as required |
|
885 # while it has no value, hence generating a false error. |
|
886 return list(self.fields) |
|
887 |
|
888 |
1060 |
889 _AFF_KWARGS = uicfg.autoform_field_kwargs |
1061 _AFF_KWARGS = uicfg.autoform_field_kwargs |
890 |
1062 |
891 def guess_field(eschema, rschema, role='subject', skip_meta_attr=True, **kwargs): |
1063 def guess_field(eschema, rschema, role='subject', skip_meta_attr=True, **kwargs): |
892 """return the most adapated widget to edit the relation |
1064 """This function return the most adapted field to edit the given relation |
893 'subjschema rschema objschema' according to information found in the schema |
1065 (`rschema`) where the given entity type (`eschema`) is the subject or object |
|
1066 (`role`). |
|
1067 |
|
1068 The field is initialized according to information found in the schema, |
|
1069 though any value can be explicitly specified using `kwargs`. |
|
1070 |
|
1071 The `skip_meta_attr` flag is used to specify wether this function should |
|
1072 return a field for attributes considered as a meta-attributes |
|
1073 (e.g. describing an other attribute, such as the format or file name of a |
|
1074 file (`Bytes`) attribute). |
894 """ |
1075 """ |
895 fieldclass = None |
1076 fieldclass = None |
896 rdef = eschema.rdef(rschema, role) |
1077 rdef = eschema.rdef(rschema, role) |
897 if role == 'subject': |
1078 if role == 'subject': |
898 targetschema = rdef.object |
1079 targetschema = rdef.object |