~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
# Copyright 2010-2012 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""Browser views for DistroSeriesDifferences."""

__metaclass__ = type
__all__ = [
    'CommentXHTMLRepresentation',
    'DistroSeriesDifferenceView',
    ]

from lazr.restful.interfaces import IWebServiceClientRequest
from z3c.ptcompat import ViewPageTemplateFile
from zope.app.form.browser.itemswidgets import RadioWidget
from zope.component import (
    adapts,
    getUtility,
    )
from zope.interface import (
    implements,
    Interface,
    )
from zope.schema import Choice
from zope.schema.vocabulary import (
    SimpleTerm,
    SimpleVocabulary,
    )

from lp.app.browser.launchpadform import LaunchpadFormView
from lp.registry.enum import (
    DistroSeriesDifferenceStatus,
    DistroSeriesDifferenceType,
    )
from lp.registry.interfaces.distroseriesdifference import (
    IDistroSeriesDifference,
    )
from lp.registry.interfaces.distroseriesdifferencecomment import (
    IDistroSeriesDifferenceComment,
    IDistroSeriesDifferenceCommentSource,
    )
from lp.registry.model.distroseriesdifferencecomment import (
    DistroSeriesDifferenceComment,
    )
from lp.services.comments.interfaces.conversation import (
    IComment,
    IConversation,
    )
from lp.services.propertycache import cachedproperty
from lp.services.webapp import (
    LaunchpadView,
    Navigation,
    stepthrough,
    )
from lp.services.webapp.authorization import check_permission
from lp.app.browser.launchpadform import custom_widget


class DistroSeriesDifferenceNavigation(Navigation):
    usedfor = IDistroSeriesDifference

    @stepthrough('comments')
    def traverse_comment(self, id_str):
        try:
            id = int(id_str)
        except ValueError:
            return None

        return getUtility(
            IDistroSeriesDifferenceCommentSource).getForDifference(
                self.context, id)

    @property
    def parent_packagesets_names(self):
        """Return the formatted list of packagesets for the related
        sourcepackagename in the parent.
        """
        return self._formatPackageSets(self.context.parent_packagesets)

    @property
    def packagesets_names(self):
        """Return the formatted list of packagesets for the related
        sourcepackagename in the derived series.
        """
        return self._formatPackageSets(self.context.packagesets)

    def _formatPackageSets(self, packagesets):
        """Format a list of packagesets to display in the UI."""
        if packagesets is not None:
            return ', '.join([packageset.name for packageset in packagesets])
        else:
            return None


class IDistroSeriesDifferenceForm(Interface):
    """An interface used in the browser only for displaying form elements."""
    blacklist_options = Choice(vocabulary=SimpleVocabulary((
        SimpleTerm('NONE', 'NONE', 'No'),
        SimpleTerm(
            DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS,
            DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS.name,
            'All versions'),
        SimpleTerm(
            DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT,
            DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT.name,
            'These versions'),
        )))


class DistroSeriesDifferenceView(LaunchpadFormView):

    implements(IConversation)
    schema = IDistroSeriesDifferenceForm
    custom_widget('blacklist_options', RadioWidget)

    @property
    def initial_values(self):
        """Ensure the correct radio button is checked for blacklisting."""
        blacklisted_statuses = (
            DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT,
            DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS,
            )
        if self.context.status in blacklisted_statuses:
            return dict(blacklist_options=self.context.status)

        return dict(blacklist_options='NONE')

    @property
    def binary_summaries(self):
        """Return the summary of the related binary packages."""
        source_pub = None
        if self.context.source_pub is not None:
            source_pub = self.context.source_pub
        elif self.context.parent_source_pub is not None:
            source_pub = self.context.parent_source_pub

        if source_pub is not None:
            summary = source_pub.meta_sourcepackage.summary
            if summary:
                return summary.split('\n')

        return None

    @property
    def comments(self):
        """See `IConversation`."""
        comments = self.context.getComments().order_by(
            DistroSeriesDifferenceComment.id)
        return [
            DistroSeriesDifferenceDisplayComment(comment) for
                comment in comments]

    @cachedproperty
    def can_request_diffs(self):
        """Does the user have permission to request diff calculation?"""
        return check_permission('launchpad.Edit', self.context)

    @cachedproperty
    def show_add_comment(self):
        """Only show the 'Add comment' if an editor requests via JS."""
        return self.request.is_ajax and self.can_request_diffs

    @cachedproperty
    def enable_blacklist_options(self):
        """Should we enable the blacklisting (ignore) radio widget options.

        Only enable the options if an editor requests via JS and the user
        is an archive admin.
        """
        return self.request.is_ajax and check_permission(
            'launchpad.Admin', self.context)

    @cachedproperty
    def blacklist_options_css_class(self):
        """The css class for the blacklist option slot.
        'blacklist-options' if enabled.
        'blacklist-options-disabled' if not enabled.
        """
        if self.enable_blacklist_options:
            return 'blacklist-options'
        else:
            return 'blacklist-options-disabled'

    @property
    def display_diffs(self):
        """Only show diffs if there's a base version."""
        return self.context.base_version is not None

    @property
    def display_child_diff(self):
        """Only show the child diff if we need to."""
        return self.context.source_version != self.context.base_version

    @property
    def display_parent_diff(self):
        """Only show the parent diff if we need to."""
        return self.context.parent_source_version != self.context.base_version

    @property
    def can_have_packages_diffs(self):
        """Return whether this dsd could have packages diffs."""
        diff_versions = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
        return self.context.difference_type == diff_versions

    @property
    def show_package_diffs_request_link(self):
        """Return whether package diffs can be requested.

        At least one of the package diffs for this dsd must be missing
        and the user must have lp.Edit.

        This method is used in the template to show the package diff
        request link.
        """
        derived_diff_computable = (
            not self.context.package_diff and self.display_child_diff)
        parent_diff_computable = (
            not self.context.parent_package_diff and self.display_parent_diff)
        return (self.display_diffs and
                self.can_request_diffs and
                (derived_diff_computable or
                 parent_diff_computable))

    @property
    def display_package_diffs_info(self):
        """Whether or not to show package differences info.

        Show if:

          There are no diffs yet available AND the base version is set AND
          either the parent or the derived version differs from the base
          version AND the user can request diff calculation,

        Or:

          There are diffs.

        """
        return (
            self.context.package_diff is not None or
            self.context.parent_package_diff is not None or
            self.show_package_diffs_request_link)


class DistroSeriesDifferenceDisplayComment:
    """Used simply to provide `IComment` for rendering."""
    implements(IComment)

    has_body = True
    has_footer = False
    display_attachments = False
    extra_css_class = ''

    def __init__(self, comment):
        """Setup the attributes required by `IComment`."""
        self.comment_author = comment.comment_author
        self.comment_date = comment.comment_date
        self.body_text = comment.body_text


class CommentXHTMLRepresentation(LaunchpadView):
    """Render individual comments when requested via the API."""
    adapts(IDistroSeriesDifferenceComment, IWebServiceClientRequest)
    implements(Interface)

    template = ViewPageTemplateFile(
        '../templates/distroseriesdifferencecomment-fragment.pt')

    @property
    def comment(self):
        return DistroSeriesDifferenceDisplayComment(self.context)