~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""Wrappers for lazr-js widgets."""

__metaclass__ = type
__all__ = [
    'BooleanChoiceWidget',
    'EnumChoiceWidget',
    'InlineEditPickerWidget',
    'InlinePersonEditPickerWidget',
    'InlineMultiCheckboxWidget',
    'standard_text_html_representation',
    'TextAreaEditorWidget',
    'TextLineEditorWidget',
    'vocabulary_to_choice_edit_items',
    ]

import simplejson

from lazr.enum import IEnumeratedType
from lazr.restful.declarations import LAZR_WEBSERVICE_EXPORTED
from lazr.restful.utils import (
    get_current_browser_request,
    safe_hasattr,
    )
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.component import getUtility
from zope.security.checker import canAccess, canWrite
from zope.schema.interfaces import (
    ICollection,
    IVocabulary,
    )
from zope.schema.vocabulary import getVocabularyRegistry

from canonical.launchpad.webapp.interfaces import ILaunchBag
from canonical.launchpad.webapp.publisher import canonical_url
from canonical.launchpad.webapp.vocabulary import IHugeVocabulary
from lp.app.browser.stringformatter import FormattersAPI
from lp.app.browser.vocabulary import get_person_picker_entry_metadata
from lp.services.propertycache import cachedproperty


class WidgetBase:
    """Useful methods for all widgets."""

    def __init__(self, context, exported_field, content_box_id,
                 edit_view, edit_url, edit_title):
        self.context = context
        self.exported_field = exported_field

        self.request = get_current_browser_request()
        self.attribute_name = exported_field.__name__
        self.optional_field = not exported_field.required

        if content_box_id is None:
            content_box_id = "edit-%s" % self.attribute_name
        self.content_box_id = content_box_id

        if edit_url is None:
            edit_url = canonical_url(self.context, view_name=edit_view)
        self.edit_url = edit_url
        if edit_title is None:
            edit_title = ''
        self.edit_title = edit_title

        # The mutator method name is used to determine whether or not the
        # current user has permission to alter the attribute if the attribute
        # is using a mutator function.
        self.mutator_method_name = None
        ws_stack = exported_field.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)
        if ws_stack is None:
            # The field may be a copy, or similarly named to one we care
            # about.
            self.api_attribute = self.attribute_name
        else:
            self.api_attribute = ws_stack['as']
            mutator_info = ws_stack.get('mutator_annotations')
            if mutator_info is not None:
                mutator_method, mutator_extra = mutator_info
                self.mutator_method_name = mutator_method.__name__
        self.json_attribute = simplejson.dumps(self.api_attribute)

    @property
    def resource_uri(self):
        """A local path to the context object.

        The javascript uses the normalize_uri method that adds the appropriate
        prefix to the uri.  Doing it this way avoids needing to adapt the
        current request into a webservice request in order to get an api url.
        """
        return canonical_url(self.context, force_local_path=True)

    @property
    def json_resource_uri(self):
        return simplejson.dumps(self.resource_uri)

    @property
    def can_write(self):
        """Can the current user write to the attribute."""
        if canWrite(self.context, self.attribute_name):
            return True
        elif self.mutator_method_name is not None:
            # The user may not have write access on the attribute itself, but
            # the REST API may have a mutator method configured, such as
            # transitionToAssignee.
            return canAccess(self.context, self.mutator_method_name)
        else:
            return False


class TextWidgetBase(WidgetBase):
    """Abstract base for the single and multiline text editor widgets."""

    def __init__(self, context, exported_field, title, content_box_id,
                 edit_view, edit_url, edit_title):
        super(TextWidgetBase, self).__init__(
            context, exported_field, content_box_id,
            edit_view, edit_url, edit_title)
        self.accept_empty = simplejson.dumps(self.optional_field)
        self.title = title
        self.widget_css_selector = simplejson.dumps('#' + self.content_box_id)

    @property
    def json_attribute_uri(self):
        return simplejson.dumps(self.resource_uri + '/' + self.api_attribute)


class DefinedTagMixin:
    """Mixin class to define open and closing tags."""

    @property
    def open_tag(self):
        return '<%s id="%s">' % (self.tag, self.content_box_id)

    @property
    def close_tag(self):
        return '</%s>' % self.tag


class TextLineEditorWidget(TextWidgetBase, DefinedTagMixin):
    """Wrapper for the lazr-js inlineedit/editor.js widget."""

    __call__ = ViewPageTemplateFile('../templates/text-line-editor.pt')

    def __init__(self, context, exported_field, title, tag,
                 content_box_id=None, edit_view="+edit", edit_url=None,
                 edit_title='',
                 default_text=None, initial_value_override=None, width=None):
        """Create a widget wrapper.

        :param context: The object that is being edited.
        :param exported_field: The attribute being edited. This should be
            a field from an interface of the form ISomeInterface['fieldname']
        :param title: The string to use as the link title.
        :param tag: The HTML tag to use.
        :param content_box_id: The HTML id to use for this widget.
            Defaults to edit-<attribute name>.
        :param edit_view: The view name to use to generate the edit_url if
            one is not specified.
        :param edit_url: The URL to use for editing when the user isn't logged
            in and when JS is off.  Defaults to the edit_view on the context.
        :param edit_title: Used to set the title attribute of the anchor.
        :param default_text: Text to show in the unedited field, if the
            parameter value is missing or None.
        :param initial_value_override: Use this text for the initial edited
            field value instead of the attribute's current value.
        :param width: Initial widget width.
        """
        super(TextLineEditorWidget, self).__init__(
            context, exported_field, title, content_box_id,
            edit_view, edit_url, edit_title)
        self.tag = tag
        self.default_text = default_text
        self.initial_value_override = simplejson.dumps(initial_value_override)
        self.width = simplejson.dumps(width)

    @property
    def value(self):
        text = getattr(self.context, self.attribute_name, self.default_text)
        if text is None:
            return self.default_text
        else:
            return FormattersAPI(text).obfuscate_email()


class TextAreaEditorWidget(TextWidgetBase):
    """Wrapper for the multine-line lazr-js inlineedit/editor.js widget."""

    __call__ = ViewPageTemplateFile('../templates/text-area-editor.pt')

    def __init__(self, context, exported_field, title, content_box_id=None,
                 edit_view="+edit", edit_url=None, edit_title='',
                 hide_empty=True, linkify_text=True):
        """Create the widget wrapper.

        :param context: The object that is being edited.
        :param exported_field: The attribute being edited. This should be
            a field from an interface of the form ISomeInterface['fieldname']
        :param title: The string to use as the link title.
        :param content_box_id: The HTML id to use for this widget.
            Defaults to edit-<attribute name>.
        :param edit_view: The view name to use to generate the edit_url if
            one is not specified.
        :param edit_url: The URL to use for editing when the user isn't logged
            in and when JS is off.  Defaults to the edit_view on the context.
        :param edit_title: Used to set the title attribute of the anchor.
        :param hide_empty: If the attribute has no value, or is empty, then
            hide the editor by adding the "unseen" CSS class.
        :param linkify_text: If True the HTML version of the text will have
            things that look like links made into anchors.
        """
        super(TextAreaEditorWidget, self).__init__(
            context, exported_field, title, content_box_id,
            edit_view, edit_url, edit_title)
        self.hide_empty = hide_empty
        self.linkify_text = linkify_text

    @property
    def tag_class(self):
        """The CSS class for the widget."""
        classes = ['lazr-multiline-edit']
        if self.hide_empty and not self.value:
            classes.append('unseen')
        return ' '.join(classes)

    @cachedproperty
    def value(self):
        text = getattr(self.context, self.attribute_name, None)
        return standard_text_html_representation(text, self.linkify_text)


class InlineEditPickerWidget(WidgetBase):
    """Wrapper for the lazr-js picker widget.

    This widget is not for editing form values like the
    VocabularyPickerWidget.
    """

    __call__ = ViewPageTemplateFile('../templates/inline-picker.pt')

    def __init__(self, context, exported_field, default_html,
                 content_box_id=None, header='Select an item',
                 step_title='Search',
                 null_display_value='None',
                 edit_view="+edit", edit_url=None, edit_title='',
                 help_link=None):
        """Create a widget wrapper.

        :param context: The object that is being edited.
        :param exported_field: The attribute being edited. This should be
            a field from an interface of the form ISomeInterface['fieldname']
        :param default_html: Default display of attribute.
        :param content_box_id: The HTML id to use for this widget.
            Automatically generated if this is not provided.
        :param header: The large text at the top of the picker.
        :param step_title: Smaller line of text below the header.
        :param null_display_value: This will be shown for a missing value
        :param edit_view: The view name to use to generate the edit_url if
            one is not specified.
        :param edit_url: The URL to use for editing when the user isn't logged
            in and when JS is off.  Defaults to the edit_view on the context.
        :param edit_title: Used to set the title attribute of the anchor.
        """
        super(InlineEditPickerWidget, self).__init__(
            context, exported_field, content_box_id,
            edit_view, edit_url, edit_title)
        self.default_html = default_html
        self.header = header
        self.step_title = step_title
        self.null_display_value = null_display_value
        self.help_link = help_link

        # JSON encoded attributes.
        self.json_content_box_id = simplejson.dumps(self.content_box_id)
        self.json_attribute = simplejson.dumps(self.api_attribute + '_link')
        self.json_vocabulary_name = simplejson.dumps(
            self.exported_field.vocabularyName)

    @property
    def picker_type(self):
        return 'default'

    @property
    def selected_value_metadata(self):
        return None

    @property
    def selected_value(self):
        """ String representation of field value associated with the picker.

        Default implementation is to return the 'name' attribute.
        """
        if self.context is None:
            return None
        val = getattr(self.context, self.exported_field.__name__)
        if val is not None and safe_hasattr(val, 'name'):
            return getattr(val, 'name')
        return None

    @property
    def config(self):
        return self.getConfig()

    def getConfig(self):
        return dict(
            picker_type=self.picker_type,
            header=self.header, step_title=self.step_title,
            selected_value=self.selected_value,
            selected_value_metadata=self.selected_value_metadata,
            null_display_value=self.null_display_value,
            show_search_box=self.show_search_box,
            vocabulary_filters=self.vocabulary_filters)

    @property
    def json_config(self):
        return simplejson.dumps(self.config)

    @cachedproperty
    def vocabulary(self):
        registry = getVocabularyRegistry()
        return registry.get(
            IVocabulary, self.exported_field.vocabularyName)

    @cachedproperty
    def vocabulary_filters(self):
        # Only IHugeVocabulary's have filters.
        if not IHugeVocabulary.providedBy(self.vocabulary):
            return []
        supported_filters = self.vocabulary.supportedFilters()
        # If we have no filters or just the ALL filter, then no filtering
        # support is required.
        filters = []
        if (len(supported_filters) == 0 or
           (len(supported_filters) == 1
            and supported_filters[0].name == 'ALL')):
            return filters
        for filter in supported_filters:
            filters.append({
                'name': filter.name,
                'title': filter.title,
                'description': filter.description,
                })
        return filters

    @property
    def show_search_box(self):
        return IHugeVocabulary.providedBy(self.vocabulary)


class InlinePersonEditPickerWidget(InlineEditPickerWidget):
    def __init__(self, context, exported_field, default_html,
                 content_box_id=None, header='Select an item',
                 step_title='Search', assign_me_text='Pick me',
                 remove_person_text='Remove person',
                 remove_team_text='Remove team',
                 null_display_value='None',
                 edit_view="+edit", edit_url=None, edit_title='',
                 help_link=None):
        """Create a widget wrapper.

        :param context: The object that is being edited.
        :param exported_field: The attribute being edited. This should be
            a field from an interface of the form ISomeInterface['fieldname']
        :param default_html: Default display of attribute.
        :param content_box_id: The HTML id to use for this widget.
            Automatically generated if this is not provided.
        :param header: The large text at the top of the picker.
        :param step_title: Smaller line of text below the header.
        :param assign_me_text: Override default button text: "Pick me"
        :param remove_person_text: Override default link text: "Remove person"
        :param remove_team_text: Override default link text: "Remove team"
        :param null_display_value: This will be shown for a missing value
        :param edit_view: The view name to use to generate the edit_url if
            one is not specified.
        :param edit_url: The URL to use for editing when the user isn't logged
            in and when JS is off.  Defaults to the edit_view on the context.
        :param edit_title: Used to set the title attribute of the anchor.
        """
        super(InlinePersonEditPickerWidget, self).__init__(
            context, exported_field, default_html, content_box_id, header,
            step_title, null_display_value,
            edit_view, edit_url, edit_title, help_link)

        self.assign_me_text = assign_me_text
        self.remove_person_text = remove_person_text
        self.remove_team_text = remove_team_text

    @property
    def picker_type(self):
        return 'person'

    @property
    def selected_value_metadata(self):
        val = getattr(self.context, self.exported_field.__name__)
        return get_person_picker_entry_metadata(val)

    @property
    def show_assign_me_button(self):
        # show_assign_me_button is true if user is in the vocabulary.
        vocabulary = self.vocabulary
        user = getUtility(ILaunchBag).user
        return user and user in vocabulary

    def getConfig(self):
        config = super(InlinePersonEditPickerWidget, self).getConfig()
        config.update(dict(
            show_remove_button=self.optional_field,
            show_assign_me_button=self.show_assign_me_button,
            assign_me_text=self.assign_me_text,
            remove_person_text=self.remove_person_text,
            remove_team_text=self.remove_team_text))
        return config


class InlineMultiCheckboxWidget(WidgetBase):
    """Wrapper for the lazr-js multicheckbox widget."""

    __call__ = ViewPageTemplateFile(
                        '../templates/inline-multicheckbox-widget.pt')

    def __init__(self, context, exported_field,
                 label, label_tag="span", attribute_type="default",
                 vocabulary=None, header=None,
                 empty_display_value="None", selected_items=list(),
                 items_tag="span", items_style='',
                 content_box_id=None, edit_view="+edit", edit_url=None,
                 edit_title=''):
        """Create a widget wrapper.

        :param context: The object that is being edited.
        :param exported_field: The attribute being edited. This should be
            a field from an interface of the form ISomeInterface['fieldname']
        :param label: The label text to display above the checkboxes
        :param label_tag: The tag in which to wrap the label text.
        :param attribute_type: The attribute type. Currently only "reference"
            is supported. Used to determine whether to linkify the selected
            checkbox item values. So ubuntu/hoary becomes
            http://launchpad.net/devel/api/ubuntu/hoary
        :param vocabulary: The name of the vocabulary which provides the
            items or a vocabulary instance.
        :param header: The text to display as the title of the popup form.
        :param empty_display_value: The text to display if no items are
            selected.
        :param selected_items: The currently selected items.
        :param items_tag: The tag in which to wrap the items checkboxes.
        :param items_style: The css style to use for each item checkbox.
        :param content_box_id: The HTML id to use for this widget.
            Automatically generated if this is not provided.
        :param edit_view: The view name to use to generate the edit_url if
            one is not specified.
        :param edit_url: The URL to use for editing when the user isn't logged
            in and when JS is off.  Defaults to the edit_view on the context.
        :param edit_title: Used to set the title attribute of the anchor.

        """
        super(InlineMultiCheckboxWidget, self).__init__(
            context, exported_field, content_box_id,
            edit_view, edit_url, edit_title)

        linkify_items = attribute_type == "reference"

        if header is None:
            header = self.exported_field.title + ":"
        self.header = header,
        self.empty_display_value = empty_display_value
        self.label = label
        self.label_open_tag = "<%s>" % label_tag
        self.label_close_tag = "</%s>" % label_tag
        self.items = selected_items
        self.items_open_tag = ("<%s id='%s'>" %
            (items_tag, self.content_box_id + "-items"))
        self.items_close_tag = "</%s>" % items_tag
        self.linkify_items = linkify_items

        if vocabulary is None:
            if ICollection.providedBy(exported_field):
                vocabulary = exported_field.value_type.vocabularyName
            else:
                vocabulary = exported_field.vocabularyName

        if isinstance(vocabulary, basestring):
            vocabulary = getVocabularyRegistry().get(context, vocabulary)

        # Construct checkbox data dict for each item in the vocabulary.
        items = []
        style = ';'.join(['font-weight: normal', items_style])
        for item in vocabulary:
            item_value = item.value if safe_hasattr(item, 'value') else item
            checked = item_value in selected_items
            if linkify_items:
                save_value = canonical_url(item_value, force_local_path=True)
            else:
                save_value = item_value.name
            new_item = {
                'name': item.title,
                'token': item.token,
                'style': style,
                'checked': checked,
                'value': save_value}
            items.append(new_item)
        self.has_choices = len(items)

        # JSON encoded attributes.
        self.json_content_box_id = simplejson.dumps(self.content_box_id)
        self.json_attribute = simplejson.dumps(self.api_attribute)
        self.json_attribute_type = simplejson.dumps(attribute_type)
        self.json_items = simplejson.dumps(items)
        self.json_description = simplejson.dumps(
            self.exported_field.description)

    @property
    def config(self):
        return dict(
            header=self.header,
            )

    @property
    def json_config(self):
        return simplejson.dumps(self.config)


def vocabulary_to_choice_edit_items(
    vocab, css_class_prefix=None, disabled_items=None, as_json=False,
    name_fn=None, value_fn=None):
    """Convert an enumerable to JSON for a ChoiceEdit.

    :vocab: The enumeration to iterate over.
    :css_class_prefix: If present, append this to an item's value to create
        the css_class property for it.
    :disabled_items: A list of items that should be displayed, but disabled.
    :name_fn: A function receiving an item and returning its name.
    :value_fn: A function receiving an item and returning its value.
    """
    if disabled_items is None:
        disabled_items = []
    items = []
    for item in vocab:
        # Introspect to make sure we're dealing with the object itself.
        # SimpleTerm objects have the object itself at item.value.
        if safe_hasattr(item, 'value'):
            item = item.value
        if name_fn is not None:
            name = name_fn(item)
        else:
            name = item.title
        if value_fn is not None:
            value = value_fn(item)
        else:
            value = item.title
        new_item = {
            'name': name,
            'value': value,
            'style': '', 'help': '', 'disabled': False}
        for disabled_item in disabled_items:
            if disabled_item == item:
                new_item['disabled'] = True
                break
        if css_class_prefix is not None:
            new_item['css_class'] = css_class_prefix + item.name
        items.append(new_item)

    if as_json:
        return simplejson.dumps(items)
    else:
        return items


def standard_text_html_representation(value, linkify_text=True):
    """Render a string for html display.

    For this we obfuscate email and render as html.
    """
    if value is None:
        return ''
    nomail = FormattersAPI(value).obfuscate_email()
    return FormattersAPI(nomail).text_to_html(linkify_text=linkify_text)


class BooleanChoiceWidget(WidgetBase, DefinedTagMixin):
    """A ChoiceEdit for a boolean field."""

    __call__ = ViewPageTemplateFile('../templates/boolean-choice-widget.pt')

    def __init__(self, context, exported_field,
                 tag, false_text, true_text, prefix=None,
                 edit_view="+edit", edit_url=None, edit_title='',
                 content_box_id=None, header='Select an item'):
        """Create a widget wrapper.

        :param context: The object that is being edited.
        :param exported_field: The attribute being edited. This should be
            a field from an interface of the form ISomeInterface['fieldname']
        :param tag: The HTML tag to use.
        :param false_text: The string to show for a false value.
        :param true_text: The string to show for a true value.
        :param prefix: Optional text to show before the value.
        :param edit_view: The view name to use to generate the edit_url if
            one is not specified.
        :param edit_url: The URL to use for editing when the user isn't logged
            in and when JS is off.  Defaults to the edit_view on the context.
        :param edit_title: Used to set the title attribute of the anchor.
        :param content_box_id: The HTML id to use for this widget.
            Automatically generated if this is not provided.
        :param header: The large text at the top of the choice popup.
        """
        super(BooleanChoiceWidget, self).__init__(
            context, exported_field, content_box_id,
            edit_view, edit_url, edit_title)
        self.header = header
        self.tag = tag
        self.prefix = prefix
        self.true_text = true_text
        self.false_text = false_text
        self.current_value = getattr(self.context, self.attribute_name)

    @property
    def value(self):
        if self.current_value:
            return self.true_text
        else:
            return self.false_text

    @property
    def config(self):
        return dict(
            contentBox='#' + self.content_box_id,
            value=self.current_value,
            title=self.header,
            items=[
                dict(name=self.true_text, value=True, style='', help='',
                     disabled=False),
                dict(name=self.false_text, value=False, style='', help='',
                     disabled=False)])

    @property
    def json_config(self):
        return simplejson.dumps(self.config)


class EnumChoiceWidget(WidgetBase):
    """A popup choice widget."""

    __call__ = ViewPageTemplateFile('../templates/enum-choice-widget.pt')

    def __init__(self, context, exported_field, header,
                 content_box_id=None, enum=None,
                 edit_view="+edit", edit_url=None, edit_title='',
                 css_class_prefix=''):
        """Create a widget wrapper.

        :param context: The object that is being edited.
        :param exported_field: The attribute being edited. This should be
            a field from an interface of the form ISomeInterface['fieldname']
        :param header: The large text at the top of the picker.
        :param content_box_id: The HTML id to use for this widget.
            Automatically generated if this is not provided.
        :param enum: The enumerated type used to generate the widget items.
        :param edit_view: The view name to use to generate the edit_url if
            one is not specified.
        :param edit_url: The URL to use for editing when the user isn't logged
            in and when JS is off.  Defaults to the edit_view on the context.
        :param edit_title: Used to set the title attribute of the anchor.
        :param css_class_prefix: Added to the start of the enum titles.
        """
        super(EnumChoiceWidget, self).__init__(
            context, exported_field, content_box_id,
            edit_view, edit_url, edit_title)
        self.header = header
        value = getattr(self.context, self.attribute_name)
        self.css_class = "value %s%s" % (css_class_prefix, value.name)
        self.value = value.title
        if enum is None:
            # Get the enum from the exported field.
            enum = exported_field.vocabulary
        if IEnumeratedType(enum, None) is None:
            raise ValueError('%r does not provide IEnumeratedType' % enum)
        self.items = vocabulary_to_choice_edit_items(enum, css_class_prefix)

    @property
    def config(self):
        return dict(
            contentBox='#' + self.content_box_id,
            value=self.value,
            title=self.header,
            items=self.items)

    @property
    def json_config(self):
        return simplejson.dumps(self.config)