← Back to team overview

launchpad-reviewers team mailing list archive

Re: [Merge] lp:~cjwatson/launchpad/git-grantee-widgets into lp:launchpad

 


Diff comments:

> === added file 'lib/lp/code/browser/widgets/gitgrantee.py'
> --- lib/lp/code/browser/widgets/gitgrantee.py	1970-01-01 00:00:00 +0000
> +++ lib/lp/code/browser/widgets/gitgrantee.py	2018-11-09 22:44:37 +0000
> @@ -0,0 +1,253 @@
> +# Copyright 2018 Canonical Ltd.  This software is licensed under the
> +# GNU Affero General Public License version 3 (see the file LICENSE).
> +
> +from __future__ import absolute_import, print_function, unicode_literals
> +
> +__metaclass__ = type
> +__all__ = [
> +    'GitGranteeDisplayWidget',
> +    'GitGranteeField',
> +    'GitGranteeWidget',
> +    ]
> +
> +from lazr.enum import DBItem
> +from lazr.restful.fields import Reference
> +from z3c.ptcompat import ViewPageTemplateFile
> +from zope.formlib.interfaces import (
> +    ConversionError,
> +    IDisplayWidget,
> +    IInputWidget,
> +    InputErrors,
> +    MissingInputError,
> +    WidgetInputError,
> +    )
> +from zope.formlib.utility import setUpWidget
> +from zope.formlib.widget import (
> +    BrowserWidget,
> +    CustomWidgetFactory,
> +    DisplayWidget,
> +    InputWidget,
> +    renderElement,
> +    )
> +from zope.interface import implementer
> +from zope.schema import (
> +    Choice,
> +    Field,
> +    )
> +from zope.schema.interfaces import IField
> +from zope.schema.vocabulary import getVocabularyRegistry
> +from zope.security.proxy import isinstance as zope_isinstance
> +
> +from lp import _
> +from lp.app.errors import UnexpectedFormData
> +from lp.app.validators import LaunchpadValidationError
> +from lp.app.widgets.popup import PersonPickerWidget
> +from lp.code.enums import GitGranteeType
> +from lp.code.interfaces.gitrule import IGitRule
> +from lp.registry.interfaces.person import IPerson
> +from lp.services.webapp.escaping import structured
> +from lp.services.webapp.interfaces import (
> +    IAlwaysSubmittedWidget,
> +    IMultiLineWidgetLayout,
> +    )
> +from lp.services.webapp.publisher import canonical_url
> +
> +
> +class IGitGranteeField(IField):
> +    """An interface for a Git access grantee field."""
> +
> +    rule = Reference(
> +        title=_("Rule"), required=True, readonly=True, schema=IGitRule,
> +        description=_("The rule that this grantee is for."))
> +
> +
> +@implementer(IGitGranteeField)
> +class GitGranteeField(Field):
> +    """A field that holds a Git access grantee."""
> +
> +    def __init__(self, rule, *args, **kwargs):
> +        super(GitGranteeField, self).__init__(*args, **kwargs)
> +        self.rule = rule
> +
> +    def constraint(self, value):
> +        """See `IField`."""
> +        if zope_isinstance(value, DBItem) and value.enum == GitGranteeType:
> +            return value != GitGranteeType.PERSON
> +        else:
> +            return value in getVocabularyRegistry().get(
> +                None, "ValidPersonOrTeam")
> +
> +
> +@implementer(IDisplayWidget)
> +class GitGranteePersonDisplayWidget(BrowserWidget):
> +
> +    def __init__(self, context, vocabulary, request):
> +        super(GitGranteePersonDisplayWidget, self).__init__(context, request)
> +
> +    def __call__(self):
> +        if self._renderedValueSet():
> +            grantee = self._data
> +            person_img = renderElement(
> +                "img", style="padding-bottom: 2px", src="/@@/person", alt="")
> +            return renderElement(
> +                "a", href=canonical_url(grantee),
> +                contents="%s %s" % (
> +                    person_img,
> +                    structured("%s", grantee.display_name).escapedtext))

Good point; there are only very minor differences in positioning, and if anything PersonFormatterAPI is better.  I've recast this in terms of PersonFormatterAPI.

> +        else:
> +            return ""
> +
> +
> +@implementer(IMultiLineWidgetLayout)
> +class GitGranteeWidgetBase(BrowserWidget):
> +
> +    template = ViewPageTemplateFile("templates/gitgrantee.pt")
> +    default_option = "person"
> +    _widgets_set_up = False
> +
> +    def setUpSubWidgets(self):
> +        if self._widgets_set_up:
> +            return
> +        fields = [
> +            Choice(
> +                __name__="person", title=u"Person",
> +                required=False, vocabulary="ValidPersonOrTeam"),
> +            ]
> +        if self._read_only:
> +            self.person_widget = CustomWidgetFactory(
> +                GitGranteePersonDisplayWidget)
> +        else:
> +            self.person_widget = CustomWidgetFactory(
> +                PersonPickerWidget,
> +                # XXX cjwatson 2018-10-18: This is a little unfortunate, but
> +                # otherwise there's no spacing at all between the
> +                # (deliberately unlabelled) radio button and the text box.
> +                style="margin-left: 4px;")
> +        for field in fields:
> +            setUpWidget(
> +                self, field.__name__, field, self._sub_widget_interface,
> +                prefix=self.name)
> +        self._widgets_set_up = True
> +
> +    def setUpOptions(self):
> +        """Set up options to be rendered."""
> +        self.options = {}
> +        for option in ("repository_owner", "person"):
> +            attributes = {
> +                "type": "radio", "name": self.name, "value": option,
> +                "id": "%s.option.%s" % (self.name, option),
> +                # XXX cjwatson 2018-10-18: Ugly, but it's worse without
> +                # this, especially in a permissions table where this widget
> +                # is normally used.
> +                "style": "margin-left: 0;",
> +                }
> +            if self.request.form_ng.getOne(
> +                    self.name, self.default_option) == option:
> +                attributes["checked"] = "checked"
> +            if self._read_only:
> +                attributes["disabled"] = "disabled"
> +            self.options[option] = renderElement("input", **attributes)
> +
> +    @property
> +    def show_options(self):
> +        return {
> +            option: not self._read_only or self.default_option == option
> +            for option in ("repository_owner", "person")}
> +
> +    def setRenderedValue(self, value):
> +        """See `IWidget`."""
> +        self.setUpSubWidgets()
> +        if value == GitGranteeType.REPOSITORY_OWNER:
> +            self.default_option = "repository_owner"
> +            return
> +        elif value is None or IPerson.providedBy(value):
> +            self.default_option = "person"
> +            self.person_widget.setRenderedValue(value)
> +            return
> +        else:
> +            raise AssertionError("Not a valid value: %r" % value)
> +
> +    def __call__(self):
> +        """See `zope.formlib.interfaces.IBrowserWidget`."""
> +        self.setUpSubWidgets()
> +        self.setUpOptions()
> +        return self.template()
> +
> +
> +@implementer(IDisplayWidget)
> +class GitGranteeDisplayWidget(GitGranteeWidgetBase, DisplayWidget):
> +    """Widget for displaying a Git access grantee."""
> +
> +    _sub_widget_interface = IDisplayWidget
> +    _read_only = True
> +
> +
> +@implementer(IAlwaysSubmittedWidget, IInputWidget)
> +class GitGranteeWidget(GitGranteeWidgetBase, InputWidget):
> +    """Widget for selecting a Git access grantee."""
> +
> +    _sub_widget_interface = IInputWidget
> +    _read_only = False
> +    _widgets_set_up = False
> +
> +    @property
> +    def show_options(self):
> +        show_options = super(GitGranteeWidget, self).show_options
> +        # Hide options that indicate unique grantee_types (e.g.
> +        # repository_owner) if they already exist for the context rule.
> +        if (show_options["repository_owner"] and
> +            not self.context.rule.repository.findRuleGrantsByGrantee(
> +                GitGranteeType.REPOSITORY_OWNER,
> +                ref_pattern=self.context.rule.ref_pattern,
> +                exact_grantee=True).is_empty()):
> +            show_options["repository_owner"] = False
> +        return show_options
> +
> +    def hasInput(self):
> +        self.setUpSubWidgets()
> +        form_value = self.request.form_ng.getOne(self.name)
> +        if form_value is None:
> +            return False
> +        return form_value != "person" or self.person_widget.hasInput()
> +
> +    def hasValidInput(self):
> +        """See `zope.formlib.interfaces.IInputWidget`."""
> +        try:
> +            self.getInputValue()
> +            return True
> +        except (InputErrors, UnexpectedFormData):
> +            return False
> +
> +    def getInputValue(self):
> +        """See `zope.formlib.interfaces.IInputWidget`."""
> +        self.setUpSubWidgets()
> +        form_value = self.request.form_ng.getOne(self.name)
> +        if form_value == "repository_owner":
> +            return GitGranteeType.REPOSITORY_OWNER
> +        elif form_value == "person":
> +            try:
> +                return self.person_widget.getInputValue()
> +            except MissingInputError:
> +                raise WidgetInputError(
> +                    self.name, self.label,
> +                    LaunchpadValidationError(
> +                        "Please enter a person or team name"))
> +            except ConversionError:
> +                entered_name = self.request.form_ng.getOne(
> +                    "%s.person" % self.name)
> +                raise WidgetInputError(
> +                    self.name, self.label,
> +                    LaunchpadValidationError(
> +                        "There is no person or team named '%s' registered in "
> +                        "Launchpad" % entered_name))
> +        else:
> +            raise UnexpectedFormData("No valid option was selected.")
> +
> +    def error(self):
> +        """See `zope.formlib.interfaces.IBrowserWidget`."""
> +        try:
> +            if self.hasInput():
> +                self.getInputValue()
> +        except InputErrors as error:
> +            self._error = error
> +        return super(GitGranteeWidget, self).error()


-- 
https://code.launchpad.net/~cjwatson/launchpad/git-grantee-widgets/+merge/357600
Your team Launchpad code reviewers is subscribed to branch lp:launchpad.


References