~launchpad-pqm/launchpad/devel

11411.7.24 by j.c.sackett
Merged from devel.
1
# Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
8687.15.18 by Karl Fogel
Add the copyright header block to files under lib/canonical/.
2
# GNU Affero General Public License version 3 (see the file LICENSE).
10744.6.3 by Adi Roiban
Add a fueld error message containing a real sentence.
3
# pylint: disable-msg=F0401
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
4
1988 by Canonical.com Patch Queue Manager
[r=salgado] RosettaMultipleFormatExports
5
"""Browser code for PO templates."""
6
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
7
__metaclass__ = type
8
1988 by Canonical.com Patch Queue Manager
[r=salgado] RosettaMultipleFormatExports
9
__all__ = [
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
10
    'POTemplateAdminView',
9483.1.1 by Danilo Šegan
Re-revert 9477 minus branch-links update.
11
    'POTemplateBreadcrumb',
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
12
    'POTemplateEditView',
13
    'POTemplateFacets',
14
    'POTemplateExportView',
8255.12.1 by Henning Eggers
Moved POTemplate pages to NavigationMenu.
15
    'POTemplateMenu',
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
16
    'POTemplateNavigation',
17
    'POTemplateSetNavigation',
18
    'POTemplateSubsetNavigation',
19
    'POTemplateSubsetURL',
20
    'POTemplateSubsetView',
21
    'POTemplateURL',
9264.2.5 by Danilo Šegan
Migrate potemplate-upload.pt and move page_title for pofile-upload.pt into view as well.
22
    'POTemplateUploadView',
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
23
    'POTemplateView',
24
    'POTemplateViewPreferred',
10095.5.1 by Adi Roiban
Initial fix. Pagetests needs to be updated.
25
    'BaseSeriesTemplatesView',
1716.1.190 by Christian Reis
Merge from RF, again, this time for real
26
    ]
1988 by Canonical.com Patch Queue Manager
[r=salgado] RosettaMultipleFormatExports
27
7326.1.3 by Henning Eggers
Improved handling of conflicting file names in translation uploads.
28
import cgi
5548.2.2 by Carlos Perello Marin
Improved test to confirm that translation domain is changed when the form is submitted and also, that we update the potemplate's date_last_changed
29
import datetime
3691.93.33 by Christian Reis
Various random cleanups, including swapping lambdas for operator.attrgetter
30
import operator
3691.457.39 by Carlos Perello Marin
Removed more format dependent code from browser code
31
import os.path
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
32
13931.2.1 by Steve Kowalik
Chip away at canonical.lazr a little more.
33
from lazr.restful.utils import smartquote
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
34
import pytz
13378.3.12 by Benji York
checkpoint
35
from storm.expr import (
36
    And,
37
    Or,
38
    )
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
39
from storm.info import ClassAlias
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
40
from zope.component import getUtility
1955 by Canonical.com Patch Queue Manager
menus. [r=spiv]
41
from zope.interface import implements
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
42
from zope.publisher.browser import FileUpload
10180.3.3 by Adi Roiban
Fix date_last_update permission.
43
from zope.security.proxy import removeSecurityProxy
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
44
14600.1.12 by Curtis Hovey
Move i18n to lp.
45
from lp import _
14612.2.1 by William Grant
format-imports on lib/. So many imports.
46
from lp.app.browser.launchpadform import ReturnToReferrerMixin
47
from lp.app.browser.tales import DateTimeFormatterAPI
48
from lp.app.enums import (
49
    service_uses_launchpad,
50
    ServiceUsage,
51
    )
52
from lp.app.errors import NotFoundError
53
from lp.app.validators.name import valid_name
54
from lp.registry.browser.productseries import ProductSeriesFacets
55
from lp.registry.browser.sourcepackage import SourcePackageFacets
56
from lp.registry.interfaces.productseries import IProductSeries
57
from lp.registry.interfaces.role import IPersonRoles
58
from lp.registry.interfaces.sourcepackage import ISourcePackage
59
from lp.registry.model.packaging import Packaging
60
from lp.registry.model.product import Product
61
from lp.registry.model.productseries import ProductSeries
62
from lp.registry.model.sourcepackagename import SourcePackageName
63
from lp.services.helpers import is_tar_filename
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
64
from lp.services.webapp import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
65
    action,
66
    canonical_url,
67
    enabled_with_permission,
68
    GetitemNavigation,
69
    LaunchpadEditFormView,
70
    LaunchpadView,
71
    Link,
72
    Navigation,
73
    NavigationMenu,
74
    StandardLaunchpadFacets,
75
    )
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
76
from lp.services.webapp.authorization import check_permission
77
from lp.services.webapp.breadcrumb import Breadcrumb
78
from lp.services.webapp.interfaces import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
79
    ICanonicalUrlData,
80
    ILaunchBag,
81
    )
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
82
from lp.services.webapp.menu import structured
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
83
from lp.services.worlddata.interfaces.language import ILanguageSet
84
from lp.translations.browser.poexportrequest import BaseExportView
85
from lp.translations.browser.translations import TranslationsMixin
12428.4.32 by Henning Eggers
Implemented for potemplate but still failing for sourcepackage.
86
from lp.translations.browser.translationsharing import (
87
    TranslationSharingDetailsMixin,
88
    )
8751.1.1 by Danilo Šegan
Store migration changes so far.
89
from lp.translations.interfaces.pofile import IPOFileSet
90
from lp.translations.interfaces.potemplate import (
8751.1.8 by Danilo Šegan
Fix most lint warnings.
91
    IPOTemplate,
92
    IPOTemplateSet,
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
93
    IPOTemplateSubset,
94
    )
12428.4.11 by Henning Eggers
Removed lint.
95
from lp.translations.interfaces.side import TranslationSide
8751.1.8 by Danilo Šegan
Fix most lint warnings.
96
from lp.translations.interfaces.translationimporter import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
97
    ITranslationImporter,
98
    )
8751.1.8 by Danilo Šegan
Fix most lint warnings.
99
from lp.translations.interfaces.translationimportqueue import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
100
    ITranslationImportQueue,
101
    )
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
102
from lp.translations.model.potemplate import POTemplate
1716.1.190 by Christian Reis
Merge from RF, again, this time for real
103
104
105
class POTemplateNavigation(Navigation):
106
107
    usedfor = IPOTemplate
108
109
    def traverse(self, name):
3452.1.1 by Carlos Perello Marin
Restructured the way we create pofiles and fixed the security problem raised by bug #2529
110
        """Return the IPOFile associated with the given name."""
111
112
        assert self.request.method in ['GET', 'HEAD', 'POST'], (
113
            'We only know about GET, HEAD, and POST')
114
1716.1.190 by Christian Reis
Merge from RF, again, this time for real
115
        user = getUtility(ILaunchBag).user
3504.3.5 by Carlos Perello Marin
Improved the way we use DummyPOMsgSet
116
4452.4.1 by Curtis Hovey
Added a pair of rules to not permit users to see the English list of translations, and for the SQL to not return English in the list of translations. Revised the tests.
117
        # We do not want users to see the 'en' potemplate because
118
        # we store the messages we want to translate as English.
119
        if name == 'en':
120
            raise NotFoundError(name)
121
3504.3.5 by Carlos Perello Marin
Improved the way we use DummyPOMsgSet
122
        pofile = self.context.getPOFileByLang(name)
123
124
        if pofile is not None:
125
            # Already have a valid POFile entry, just return it.
3452.1.1 by Carlos Perello Marin
Restructured the way we create pofiles and fixed the security problem raised by bug #2529
126
            return pofile
3504.3.5 by Carlos Perello Marin
Improved the way we use DummyPOMsgSet
127
        elif self.request.method in ['GET', 'HEAD']:
128
            # It's just a query, get a fake one so we don't create new
129
            # POFiles just because someone is browsing the web.
11196.4.1 by Danilo Šegan
Change getDummyPOFile to accept actual language as the parameter instead of language code.
130
            language = getUtility(ILanguageSet).getLanguageByCode(name)
11411.7.24 by j.c.sackett
Merged from devel.
131
            if language is None:
132
                raise NotFoundError(name)
133
            return self.context.getDummyPOFile(
134
                language, requester=user, check_for_existing=False)
1716.1.190 by Christian Reis
Merge from RF, again, this time for real
135
        else:
3452.1.2 by Carlos Perello Marin
Added a migration script and a test
136
            # It's a POST.
9953.1.2 by Jeroen Vermeulen
Lift conditional setting of POFile.owner out of newPOFile.
137
            # XXX CarlosPerelloMarin 2006-04-20 bug=40275: We should
138
            # check the kind of POST we got.  A logout will also be a
139
            # POST and we should not create a POFile in that case.
7675.916.98 by Henning Eggers
Merged db-stable at r10026 (recife roll-back) but without accepting the changes.
140
            return self.context.newPOFile(name, owner=user)
2148 by Canonical.com Patch Queue Manager
[r=SteveA] menus for Rosetta
141
142
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
143
class POTemplateFacets(StandardLaunchpadFacets):
144
    usedfor = IPOTemplate
145
146
    def __init__(self, context):
147
        StandardLaunchpadFacets.__init__(self, context)
148
        target = context.translationtarget
149
        if IProductSeries.providedBy(target):
7847.1.42 by Jonathan Lange
Make all the pagetests pass.
150
            self._is_product_series = True
4055.2.10 by Carlos Perello Marin
Applied review comments
151
            self.target_facets = ProductSeriesFacets(target)
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
152
        elif ISourcePackage.providedBy(target):
7847.1.42 by Jonathan Lange
Make all the pagetests pass.
153
            self._is_product_series = False
4055.2.10 by Carlos Perello Marin
Applied review comments
154
            self.target_facets = SourcePackageFacets(target)
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
155
        else:
156
            # We don't know yet how to handle this target.
157
            raise NotImplementedError
158
159
        # Enable only the menus that the translation target uses.
4055.2.10 by Carlos Perello Marin
Applied review comments
160
        self.enable_only = self.target_facets.enable_only
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
161
162
        # From an IPOTemplate URL, we reach its translationtarget (either
163
        # ISourcePackage or IProductSeries using self.target.
4055.2.3 by Carlos Perello Marin
Fixed page title and heading for IProductSeries and ISourcePackage +translations pages and moved to use a shared template. Fixed Structural Object heading for IPOMsgSet
164
        self.target = '../../'
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
165
166
    def overview(self):
4055.2.10 by Carlos Perello Marin
Applied review comments
167
        overview_link = self.target_facets.overview()
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
168
        overview_link.target = self.target
169
        return overview_link
170
171
    def translations(self):
4055.2.10 by Carlos Perello Marin
Applied review comments
172
        translations_link = self.target_facets.translations()
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
173
        translations_link.target = self.target
174
        return translations_link
175
176
    def bugs(self):
4055.2.10 by Carlos Perello Marin
Applied review comments
177
        bugs_link = self.target_facets.bugs()
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
178
        bugs_link.target = self.target
179
        return bugs_link
180
181
    def answers(self):
4055.2.10 by Carlos Perello Marin
Applied review comments
182
        answers_link = self.target_facets.answers()
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
183
        answers_link.target = self.target
184
        return answers_link
185
186
    def specifications(self):
4055.2.10 by Carlos Perello Marin
Applied review comments
187
        specifications_link = self.target_facets.specifications()
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
188
        specifications_link.target = self.target
189
        return specifications_link
190
191
    def branches(self):
4055.2.10 by Carlos Perello Marin
Applied review comments
192
        branches_link = self.target_facets.branches()
7847.1.42 by Jonathan Lange
Make all the pagetests pass.
193
        if not self._is_product_series:
194
            branches_link.target = self.target
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
195
        return branches_link
196
197
8255.12.1 by Henning Eggers
Moved POTemplate pages to NavigationMenu.
198
class POTemplateMenu(NavigationMenu):
199
    """Navigation menus for `IPOTemplate` objects."""
2148 by Canonical.com Patch Queue Manager
[r=SteveA] menus for Rosetta
200
    usedfor = IPOTemplate
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
201
    facet = 'translations'
8255.12.1 by Henning Eggers
Moved POTemplate pages to NavigationMenu.
202
    # XXX: henninge 2009-04-22 bug=365112: The order in this list was
203
    # rearranged so that the last item is public. The desired order is:
204
    # links = ['overview', 'upload', 'download', 'edit', 'administer']
205
    links = ['overview', 'edit', 'administer', 'upload', 'download']
2548 by Canonical.com Patch Queue Manager
r=bjornt, plus some [trivial], lots of menus work, special error page for request timeouts.
206
8255.12.1 by Henning Eggers
Moved POTemplate pages to NavigationMenu.
207
    def overview(self):
208
        text = 'Overview'
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
209
        return Link('', text)
210
6691.3.1 by Jeroen Vermeulen
Prospective fix. Commit so I can merge RF.
211
    @enabled_with_permission('launchpad.Edit')
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
212
    def upload(self):
8255.12.1 by Henning Eggers
Moved POTemplate pages to NavigationMenu.
213
        text = 'Upload'
10095.5.7 by Adi Roiban
Use link formatter for actions
214
        return Link('+upload', text, icon='add')
4055.2.2 by Carlos Perello Marin
Fixed Tabs and Structural Objects info as agreed with mpt. Moved BaseExportView from IPOFile to IPOTemplate to prevent an inline import
215
2548 by Canonical.com Patch Queue Manager
r=bjornt, plus some [trivial], lots of menus work, special error page for request timeouts.
216
    def download(self):
8255.12.1 by Henning Eggers
Moved POTemplate pages to NavigationMenu.
217
        text = 'Download'
10095.5.7 by Adi Roiban
Use link formatter for actions
218
        return Link('+export', text, icon='download')
2548 by Canonical.com Patch Queue Manager
r=bjornt, plus some [trivial], lots of menus work, special error page for request timeouts.
219
6691.3.5 by Jeroen Vermeulen
Made options-printing test helper a bit more flexible.
220
    @enabled_with_permission('launchpad.Edit')
2880.2.6 by Carlos Perelló Marín
Fixes for the translation form
221
    def edit(self):
10095.5.7 by Adi Roiban
Use link formatter for actions
222
        text = 'Edit'
223
        return Link('+edit', text, icon='edit')
2880.2.6 by Carlos Perelló Marín
Fixes for the translation form
224
9962.8.2 by Adi Roiban
Instead of launchpad.Admin, use launchpad.TranslationsAdmin as it is more specific
225
    @enabled_with_permission('launchpad.TranslationsAdmin')
2548 by Canonical.com Patch Queue Manager
r=bjornt, plus some [trivial], lots of menus work, special error page for request timeouts.
226
    def administer(self):
3847.2.15 by Mark Shuttleworth
Consistency in menu text and capitalisation
227
        text = 'Administer'
10095.5.7 by Adi Roiban
Use link formatter for actions
228
        return Link('+admin', text, icon='edit')
2148 by Canonical.com Patch Queue Manager
[r=SteveA] menus for Rosetta
229
1715 by Canonical.com Patch Queue Manager
Moved the translation form from potemplate context into pofile context. r=SteveA
230
231
class POTemplateSubsetView:
1501 by Canonical.com Patch Queue Manager
First merge of POTemplateLinkages
232
233
    def __init__(self, context, request):
234
        self.context = context
235
        self.request = request
1561 by Canonical.com Patch Queue Manager
Rosetta Polish about portlets and URLs
236
237
    def __call__(self):
238
        # We are not using this context directly, only for traversals.
3618.1.43 by Steve Alexander
add note about response.redirect() semantics and fix some trivially failing page tests.
239
        self.request.response.redirect('../+translations')
1528 by Canonical.com Patch Queue Manager
DistroReleaseTranslationList implementation
240
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
241
12428.4.32 by Henning Eggers
Implemented for potemplate but still failing for sourcepackage.
242
class POTemplateView(LaunchpadView,
243
                     TranslationsMixin, TranslationSharingDetailsMixin):
1706 by Canonical.com Patch Queue Manager
fixrosettatemplateviews
244
9483.1.1 by Danilo Šegan
Re-revert 9477 minus branch-links update.
245
    SHOW_RELATED_TEMPLATES = 4
246
9481.2.2 by Henning Eggers
Fixed labels and page_titles on pofile and potemplate pages.
247
    label = "Translation status"
248
2570.1.39 by Carlos Perelló Marín
Fixed many tests
249
    def initialize(self):
5512.2.5 by Matthew Paul Thomas
Reverts accidental changes from the rocketfuel merge.
250
        """Get the requested languages and submit the form."""
3531.1.15 by Carlos Perello Marin
Restored something I shouldn't remove...
251
        self.description = self.context.description
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
252
3348.1.25 by Mark Shuttleworth
Test fixes and productseries page cleanup.
253
    def requestPoFiles(self):
254
        """Yield a POFile or DummyPOFile for each of the languages in the
255
        request, which includes country languages from the request IP,
256
        browser preferences, and/or personal Launchpad language prefs.
257
        """
4346.7.2 by Curtis Hovey
Added a mixin class to provide the Person's translatable_languages.
258
        for language in self._sortLanguages(self.translatable_languages):
3691.93.33 by Christian Reis
Various random cleanups, including swapping lambdas for operator.attrgetter
259
            yield self._getPOFileOrDummy(language)
3348.1.25 by Mark Shuttleworth
Test fixes and productseries page cleanup.
260
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
261
    def num_messages(self):
262
        N = self.context.messageCount()
263
        if N == 0:
264
            return "no messages at all"
265
        elif N == 1:
266
            return "1 message"
267
        else:
268
            return "%s messages" % N
269
3691.95.1 by Danilo Šegan
Fix bug #2237: simplify potemplate views and review responses.
270
    def pofiles(self, preferred_only=False):
1706 by Canonical.com Patch Queue Manager
fixrosettatemplateviews
271
        """Iterate languages shown when viewing this PO template.
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
272
1818 by Canonical.com Patch Queue Manager
[r=spiv] teams of translators to be organised as groups by language
273
        Yields a POFileView object for each language this template has
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
274
        been translated into, and for each of the user's languages.
1706 by Canonical.com Patch Queue Manager
fixrosettatemplateviews
275
        Where the template has no POFile for that language, we use
276
        a DummyPOFile.
277
        """
4055.2.10 by Carlos Perello Marin
Applied review comments
278
        # This inline import is needed to workaround a circular import problem
8751.1.1 by Danilo Šegan
Store migration changes so far.
279
        # because lp.translations.browser.pofile imports
280
        # lp.translations.browser.potemplate.
281
        from lp.translations.browser.pofile import POFileView
3691.95.1 by Danilo Šegan
Fix bug #2237: simplify potemplate views and review responses.
282
4346.7.2 by Curtis Hovey
Added a mixin class to provide the Person's translatable_languages.
283
        languages = self.translatable_languages
3691.93.33 by Christian Reis
Various random cleanups, including swapping lambdas for operator.attrgetter
284
        if not preferred_only:
3691.95.1 by Danilo Šegan
Fix bug #2237: simplify potemplate views and review responses.
285
            # Union the languages the template has been translated into with
286
            # the user's selected languages.
3691.93.33 by Christian Reis
Various random cleanups, including swapping lambdas for operator.attrgetter
287
            languages = set(self.context.languages()) | set(languages)
3691.95.1 by Danilo Šegan
Fix bug #2237: simplify potemplate views and review responses.
288
3691.93.33 by Christian Reis
Various random cleanups, including swapping lambdas for operator.attrgetter
289
        for language in self._sortLanguages(languages):
290
            pofile = self._getPOFileOrDummy(language)
2570.1.39 by Carlos Perelló Marín
Fixed many tests
291
            pofileview = POFileView(pofile, self.request)
292
            # Initialize the view.
293
            pofileview.initialize()
294
            yield pofileview
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
295
4147.1.2 by Danilo Šegan
Improve test, fix remaining cases.
296
    @property
9483.1.1 by Danilo Šegan
Re-revert 9477 minus branch-links update.
297
    def group_parent(self):
10089.2.6 by Adi Roiban
Change translations admin for product/project/distro
298
        """Return a parent object implementing `ITranslationPolicy`."""
9483.1.1 by Danilo Šegan
Re-revert 9477 minus branch-links update.
299
        if self.context.productseries is not None:
300
            return self.context.productseries.product
301
        else:
302
            return self.context.distroseries.distribution
303
304
    @property
305
    def has_translation_documentation(self):
306
        """Are there translation instructions for this project."""
307
        translation_group = self.group_parent.translationgroup
308
        return (translation_group is not None and
309
                translation_group.translation_guide_url is not None)
310
311
    @property
312
    def related_templates_by_source(self):
313
        by_source = list(
314
            self.context.relatives_by_source[:self.SHOW_RELATED_TEMPLATES])
315
        return by_source
316
317
    @property
10008.1.7 by Adi Roiban
Fix typos. Rename more_templates_by_source_link.
318
    def more_templates_by_source_link(self):
9483.1.1 by Danilo Šegan
Re-revert 9477 minus branch-links update.
319
        by_source_count = self.context.relatives_by_source.count()
10008.1.1 by Adi Roiban
Initial implementation. New stories required.
320
        if (by_source_count > self.SHOW_RELATED_TEMPLATES):
10008.1.2 by Adi Roiban
Add test stories for covering new functionalities.
321
            other = by_source_count - self.SHOW_RELATED_TEMPLATES
10008.1.4 by Adi Roiban
Fix issues raised during the review.
322
            if (self.context.distroseries):
13715.1.2 by Danilo Segan
Drive-by cleanup to remove an irrelevant XXX.
323
                sourcepackage = self.context.distroseries.getSourcePackage(
324
                    self.context.sourcepackagename)
325
                url = canonical_url(
326
                    sourcepackage, rootsite="translations",
327
                    view_name='+translations')
10008.1.4 by Adi Roiban
Fix issues raised during the review.
328
            else:
329
                url = canonical_url(
330
                    self.context.productseries,
331
                    rootsite="translations",
332
                    view_name="+templates")
10008.1.1 by Adi Roiban
Initial implementation. New stories required.
333
            if other == 1:
10008.1.9 by Adi Roiban
Fix the dot.
334
                return " and <a href=\"%s\">one other template</a>" % url
10008.1.1 by Adi Roiban
Initial implementation. New stories required.
335
            else:
10008.1.9 by Adi Roiban
Fix the dot.
336
                return " and <a href=\"%s\">%d other templates</a>" % (
10008.1.4 by Adi Roiban
Fix issues raised during the review.
337
                    url, other)
10008.1.1 by Adi Roiban
Initial implementation. New stories required.
338
        else:
10008.1.4 by Adi Roiban
Fix issues raised during the review.
339
            return ""
9483.1.1 by Danilo Šegan
Re-revert 9477 minus branch-links update.
340
341
    @property
4147.1.3 by Danilo Šegan
Implement salgado's review suggestions: cleanup test mostly.
342
    def has_pofiles(self):
4346.7.4 by Curtis Hovey
Fixed formatting.
343
        languages = set(
344
            self.context.languages()).union(self.translatable_languages)
4147.1.3 by Danilo Šegan
Implement salgado's review suggestions: cleanup test mostly.
345
        return len(languages) > 0
4147.1.2 by Danilo Šegan
Improve test, fix remaining cases.
346
3691.93.33 by Christian Reis
Various random cleanups, including swapping lambdas for operator.attrgetter
347
    def _sortLanguages(self, languages):
348
        return sorted(languages, key=operator.attrgetter('englishname'))
349
350
    def _getPOFileOrDummy(self, language):
351
        pofile = self.context.getPOFileByLang(language.code)
352
        if pofile is None:
353
            pofileset = getUtility(IPOFileSet)
354
            pofile = pofileset.getDummy(self.context, language)
355
        return pofile
356
12428.4.8 by Henning Eggers
Works for Ubuntu templates.
357
    @property
358
    def is_upstream_template(self):
359
        return self.context.translation_side == TranslationSide.UPSTREAM
360
12428.4.6 by Henning Eggers
Implemented for templates in productseries.
361
    def is_sharing(self):
12622.6.26 by Henning Eggers
Removed lint.
362
        potemplate = self.context.getOtherSidePOTemplate()
12622.6.20 by Henning Eggers
Removed all uses of collections outside of HasTranslationTemplates.
363
        return potemplate is not None
12428.4.6 by Henning Eggers
Implemented for templates in productseries.
364
365
    @property
366
    def sharing_template(self):
12622.6.10 by Henning Eggers
Removed call sites for get_*_sharing_info.
367
        return self.context.getOtherSidePOTemplate()
12428.4.6 by Henning Eggers
Implemented for templates in productseries.
368
12622.9.7 by Henning Eggers
Restrict display of links to sourcepacakge pages.
369
    def getTranslationSourcePackage(self):
12428.4.32 by Henning Eggers
Implemented for potemplate but still failing for sourcepackage.
370
        """See `TranslationSharingDetailsMixin`."""
371
        if self.is_upstream_template:
12622.9.7 by Henning Eggers
Restrict display of links to sourcepacakge pages.
372
            productseries = self.context.productseries
373
            return productseries.getUbuntuTranslationFocusPackage()
12428.4.32 by Henning Eggers
Implemented for potemplate but still failing for sourcepackage.
374
        else:
375
            return self.context.sourcepackage
376
9264.2.10 by Danilo Šegan
Fix up whitespace and most of the review comments.
377
9264.2.5 by Danilo Šegan
Migrate potemplate-upload.pt and move page_title for pofile-upload.pt into view as well.
378
class POTemplateUploadView(LaunchpadView, TranslationsMixin):
379
    """Upload translations and updated template."""
380
9481.2.2 by Henning Eggers
Fixed labels and page_titles on pofile and potemplate pages.
381
    label = "Upload translations"
382
    page_title = "Upload translations"
383
9264.2.5 by Danilo Šegan
Migrate potemplate-upload.pt and move page_title for pofile-upload.pt into view as well.
384
    @property
9264.2.10 by Danilo Šegan
Fix up whitespace and most of the review comments.
385
    def cancel_url(self):
386
        return canonical_url(self.context)
387
9264.2.5 by Danilo Šegan
Migrate potemplate-upload.pt and move page_title for pofile-upload.pt into view as well.
388
    def initialize(self):
389
        """Get the requested languages and submit the form."""
390
        self.submitForm()
9264.2.10 by Danilo Šegan
Fix up whitespace and most of the review comments.
391
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
392
    def submitForm(self):
9264.2.10 by Danilo Šegan
Fix up whitespace and most of the review comments.
393
        """Process any uploaded files."""
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
394
395
        if self.request.method == 'POST':
1789 by Canonical.com Patch Queue Manager
PoTemplateAdmin implementation (finally) r=jamesh,mpt
396
            if 'UPLOAD' in self.request.form:
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
397
                self.upload()
398
399
    def upload(self):
7326.1.6 by Henning Eggers
Added some changes during review.
400
        """Handle a form submission to change the contents of the template.
401
402
        Uploads may fail if there are already entries with the same path name
403
        and uploader (importer) in the queue and the new upload cannot be
404
        safely matched to any of them.  The user will be informed about the
405
        failure with a warning message."""
406
        # XXX henninge 20008-12-03 bug=192925: This code is duplicated for
407
        # productseries and pofile and should be unified.
3691.134.4 by kiko
Fix for OOPS-256B785 and friends: avoid crashing in upload handler when invalid form arguments are provided. Give a decent error message instead by simply making the logic in the potemplate view code more robust.
408
        file = self.request.form.get('file')
1818 by Canonical.com Patch Queue Manager
[r=spiv] teams of translators to be organised as groups by language
409
        if not isinstance(file, FileUpload):
3691.134.4 by kiko
Fix for OOPS-256B785 and friends: avoid crashing in upload handler when invalid form arguments are provided. Give a decent error message instead by simply making the logic in the potemplate view code more robust.
410
            if not file:
2570.1.37 by Carlos Perelló Marín
More review comments applied
411
                self.request.response.addErrorNotification(
3691.134.4 by kiko
Fix for OOPS-256B785 and friends: avoid crashing in upload handler when invalid form arguments are provided. Give a decent error message instead by simply making the logic in the potemplate view code more robust.
412
                    "Your upload was ignored because you didn't select a "
413
                    "file. Please select a file and try again.")
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
414
            else:
6912.5.21 by Curtis Hovey
Updated the XXX comment's metadata using the bug found in the comment text.
415
                # XXX: Carlos Perello Marin 2004-12-30 bug=116:
2570.1.37 by Carlos Perelló Marín
More review comments applied
416
                # Epiphany seems to have an unpredictable bug with upload
417
                # forms (or perhaps it's launchpad because I never had
418
                # problems with bugzilla). The fact is that some uploads don't
419
                # work and we get a unicode object instead of a file-like
420
                # object in "file". We show an error if we see that behaviour.
421
                self.request.response.addErrorNotification(
3691.134.4 by kiko
Fix for OOPS-256B785 and friends: avoid crashing in upload handler when invalid form arguments are provided. Give a decent error message instead by simply making the logic in the potemplate view code more robust.
422
                    "Your upload failed because there was a problem receiving"
423
                    " data. Please try again.")
1789 by Canonical.com Patch Queue Manager
PoTemplateAdmin implementation (finally) r=jamesh,mpt
424
            return
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
425
426
        filename = file.filename
2570.1.7 by Carlos Perello Marin
Lots of fixes + new code + tests updates
427
        content = file.read()
428
429
        if len(content) == 0:
2570.1.37 by Carlos Perelló Marín
More review comments applied
430
            self.request.response.addWarningNotification(
431
                "Ignored your upload because the uploaded file is empty.")
2570.1.7 by Carlos Perello Marin
Lots of fixes + new code + tests updates
432
            return
433
2570.1.37 by Carlos Perelló Marín
More review comments applied
434
        translation_import_queue = getUtility(ITranslationImportQueue)
3691.457.39 by Carlos Perello Marin
Removed more format dependent code from browser code
435
        root, ext = os.path.splitext(filename)
436
        translation_importer = getUtility(ITranslationImporter)
7326.1.3 by Henning Eggers
Improved handling of conflicting file names in translation uploads.
437
        if ext in translation_importer.supported_file_extensions:
2570.1.7 by Carlos Perello Marin
Lots of fixes + new code + tests updates
438
            # Add it to the queue.
7326.1.2 by Henning Eggers
Added support to handle translation file upload conflicts nicely.
439
            entry = translation_import_queue.addOrUpdateEntry(
3691.457.39 by Carlos Perello Marin
Removed more format dependent code from browser code
440
                filename, content, True, self.user,
2570.1.7 by Carlos Perello Marin
Lots of fixes + new code + tests updates
441
                sourcepackagename=self.context.sourcepackagename,
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
442
                distroseries=self.context.distroseries,
3200.1.1 by Carlos Perello Marin
Changed the way the import queue works and removed the old rawimports from POFile and POTemplate. Now we have just one place with all imports + a ton of tests fixes
443
                productseries=self.context.productseries,
3691.457.39 by Carlos Perello Marin
Removed more format dependent code from browser code
444
                potemplate=self.context)
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
445
7326.1.3 by Henning Eggers
Improved handling of conflicting file names in translation uploads.
446
            if entry is None:
7326.1.2 by Henning Eggers
Added support to handle translation file upload conflicts nicely.
447
                self.request.response.addWarningNotification(
7326.1.3 by Henning Eggers
Improved handling of conflicting file names in translation uploads.
448
                    "Upload failed.  The name of the file you "
449
                    "uploaded matched multiple existing "
450
                    "uploads, for different templates.  This makes it "
451
                    "impossible to determine which template the new "
452
                    "upload was for.  Try uploading to a specific "
453
                    "template: visit the page for the template that you "
454
                    "want to upload to, and select the upload option "
455
                    "from there.")
456
            else:
457
                self.request.response.addInfoNotification(
458
                    structured(
459
                    'Thank you for your upload.  It will be automatically '
7326.1.5 by Henning Eggers
Small fixes.
460
                    'reviewed in the next few hours.  If that is not '
7326.1.3 by Henning Eggers
Improved handling of conflicting file names in translation uploads.
461
                    'enough to determine whether and where your file '
462
                    'should be imported, it will be reviewed manually by an '
463
                    'administrator in the coming few days.  You can track '
464
                    'your upload\'s status in the '
12696.1.1 by Henning Eggers
Fixed all structured calls in translations.
465
                    '<a href="%s/+imports">Translation Import Queue</a>',
466
                        canonical_url(self.context.translationtarget)))
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
467
14593.2.15 by Curtis Hovey
Moved helpers to lp.services.
468
        elif is_tar_filename(filename):
2570.1.7 by Carlos Perello Marin
Lots of fixes + new code + tests updates
469
            # Add the whole tarball to the import queue.
7326.1.2 by Henning Eggers
Added support to handle translation file upload conflicts nicely.
470
            (num, conflicts) = (
7326.1.3 by Henning Eggers
Improved handling of conflicting file names in translation uploads.
471
                translation_import_queue.addOrUpdateEntriesFromTarball(
7326.1.2 by Henning Eggers
Added support to handle translation file upload conflicts nicely.
472
                    content, True, self.user,
473
                    sourcepackagename=self.context.sourcepackagename,
474
                    distroseries=self.context.distroseries,
475
                    productseries=self.context.productseries,
476
                    potemplate=self.context))
2570.1.7 by Carlos Perello Marin
Lots of fixes + new code + tests updates
477
478
            if num > 0:
7326.1.7 by Henning Eggers
* Added wording for singular cases.
479
                if num == 1:
480
                    plural_s = ''
481
                    itthey = 'it'
482
                else:
483
                    plural_s = 's'
484
                    itthey = 'they'
5594.1.11 by Maris Fogels
Beautified the addNotification(structured(...)) construct.
485
                self.request.response.addInfoNotification(
486
                    structured(
12696.1.1 by Henning Eggers
Fixed all structured calls in translations.
487
                    'Thank you for your upload. %s file%s from the tarball '
7326.1.3 by Henning Eggers
Improved handling of conflicting file names in translation uploads.
488
                    'will be automatically '
7326.1.5 by Henning Eggers
Small fixes.
489
                    'reviewed in the next few hours.  If that is not enough '
7326.1.7 by Henning Eggers
* Added wording for singular cases.
490
                    'to determine whether and where your file%s should '
491
                    'be imported, %s will be reviewed manually by an '
7326.1.3 by Henning Eggers
Improved handling of conflicting file names in translation uploads.
492
                    'administrator in the coming few days.  You can track '
493
                    'your upload\'s status in the '
13515.3.11 by Henning Eggers
Removed lots of lint.
494
                    '<a href="%s/+imports">Translation Import Queue</a>' % (
7326.1.7 by Henning Eggers
* Added wording for singular cases.
495
                        num, plural_s, plural_s, itthey,
7440.1.1 by Henning Eggers
Corrected URL for translation import queue.
496
                        canonical_url(self.context.translationtarget))))
7326.1.2 by Henning Eggers
Added support to handle translation file upload conflicts nicely.
497
                if len(conflicts) > 0:
7326.1.7 by Henning Eggers
* Added wording for singular cases.
498
                    if len(conflicts) == 1:
499
                        warning = (
500
                            "A file could not be uploaded because its "
501
                            "name matched multiple existing uploads, for "
10008.1.2 by Adi Roiban
Add test stories for covering new functionalities.
502
                            "different templates.")
12696.1.1 by Henning Eggers
Fixed all structured calls in translations.
503
                        ul_conflicts = structured(
7326.1.7 by Henning Eggers
* Added wording for singular cases.
504
                            "The conflicting file name was:<br /> "
12696.1.1 by Henning Eggers
Fixed all structured calls in translations.
505
                            "<ul><li>%s</li></ul>", conflicts[0])
7326.1.7 by Henning Eggers
* Added wording for singular cases.
506
                    else:
12696.1.1 by Henning Eggers
Fixed all structured calls in translations.
507
                        warning = structured(
508
                            "%s files could not be uploaded because their "
7326.1.7 by Henning Eggers
* Added wording for singular cases.
509
                            "names matched multiple existing uploads, for "
12696.1.1 by Henning Eggers
Fixed all structured calls in translations.
510
                            "different templates.", len(conflicts))
511
                        ul_conflicts = structured(
7326.1.7 by Henning Eggers
* Added wording for singular cases.
512
                            "The conflicting file names were:<br /> "
513
                            "<ul><li>%s</li></ul>" % (
514
                            "</li><li>".join(map(cgi.escape, conflicts))))
7326.1.2 by Henning Eggers
Added support to handle translation file upload conflicts nicely.
515
                    self.request.response.addWarningNotification(
516
                        structured(
12696.1.1 by Henning Eggers
Fixed all structured calls in translations.
517
                        "%s  This makes it "
7326.1.3 by Henning Eggers
Improved handling of conflicting file names in translation uploads.
518
                        "impossible to determine which template the new "
519
                        "upload was for.  Try uploading to a specific "
520
                        "template: visit the page for the template that you "
521
                        "want to upload to, and select the upload option "
12696.1.1 by Henning Eggers
Fixed all structured calls in translations.
522
                        "from there.<br />%s", warning, ul_conflicts))
2570.1.7 by Carlos Perello Marin
Lots of fixes + new code + tests updates
523
            else:
7326.1.2 by Henning Eggers
Added support to handle translation file upload conflicts nicely.
524
                if len(conflicts) == 0:
525
                    self.request.response.addWarningNotification(
7326.1.3 by Henning Eggers
Improved handling of conflicting file names in translation uploads.
526
                        "Upload ignored.  The tarball you uploaded did not "
527
                        "contain any files that the system recognized as "
528
                        "translation files.")
7326.1.2 by Henning Eggers
Added support to handle translation file upload conflicts nicely.
529
                else:
530
                    self.request.response.addWarningNotification(
7326.1.3 by Henning Eggers
Improved handling of conflicting file names in translation uploads.
531
                        "Upload failed.  One or more of the files you "
532
                        "uploaded had names that matched multiple existing "
533
                        "uploads, for different templates.  This makes it "
534
                        "impossible to determine which template the new "
535
                        "upload was for.  Try uploading to a specific "
536
                        "template: visit the page for the template that you "
537
                        "want to upload to, and select the upload option "
538
                        "from there.")
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
539
        else:
2570.1.37 by Carlos Perelló Marín
More review comments applied
540
            self.request.response.addWarningNotification(
7326.1.3 by Henning Eggers
Improved handling of conflicting file names in translation uploads.
541
                "Upload failed because the file you uploaded was not"
2570.1.37 by Carlos Perelló Marín
More review comments applied
542
                " recognised as a file that can be imported.")
1646 by Canonical.com Patch Queue Manager
Rosetta source code follows now the Launchpad standard layout rs=SteveA
543
544
3691.95.1 by Danilo Šegan
Fix bug #2237: simplify potemplate views and review responses.
545
class POTemplateViewPreferred(POTemplateView):
10008.1.2 by Adi Roiban
Add test stories for covering new functionalities.
546
    """View class that shows only users preferred templates."""
547
3691.95.1 by Danilo Šegan
Fix bug #2237: simplify potemplate views and review responses.
548
    def pofiles(self):
3691.95.4 by Danilo Šegan
Minor PEP-8 bit.
549
        return POTemplateView.pofiles(self, preferred_only=True)
550
10008.1.2 by Adi Roiban
Add test stories for covering new functionalities.
551
10513.1.1 by Adi Roiban
Implement a more elegant solution for next_url redirection.
552
class POTemplateEditView(ReturnToReferrerMixin, LaunchpadEditFormView):
1789 by Canonical.com Patch Queue Manager
PoTemplateAdmin implementation (finally) r=jamesh,mpt
553
    """View class that lets you edit a POTemplate object."""
2043 by Canonical.com Patch Queue Manager
PoTemplateAdmin changes r=spiv,bjornt
554
7182.6.2 by Guilherme Salgado
Convert POTemplateEditView into a LaunchpadEditFormView
555
    schema = IPOTemplate
9264.3.1 by Danilo Šegan
Replace potemplate edit and admin forms with generic-edit.pt.
556
    label = 'Edit translation template details'
9481.2.2 by Henning Eggers
Fixed labels and page_titles on pofile and potemplate pages.
557
    page_title = 'Edit details'
10744.6.2 by Adi Roiban
Add pagetest.
558
    PRIORITY_MIN_VALUE = 0
559
    PRIORITY_MAX_VALUE = 100000
7182.6.2 by Guilherme Salgado
Convert POTemplateEditView into a LaunchpadEditFormView
560
13715.1.3 by Danilo Segan
Improve the division between POTemplate:+edit and +admin pages.
561
    @property
562
    def field_names(self):
563
        field_names = [
564
            'name', 'translation_domain', 'description', 'priority',
565
            'path', 'iscurrent']
566
        if self.context.distroseries:
567
            field_names.extend([
568
                'sourcepackagename',
569
                'languagepack',
570
                ])
13715.1.13 by Danilo Segan
Fix test failures.
571
        else:
572
            field_names.append('owner')
13715.1.3 by Danilo Segan
Improve the division between POTemplate:+edit and +admin pages.
573
        return field_names
574
13715.1.9 by Danilo Segan
Provide a correct return URL.
575
    @property
576
    def _return_url(self):
13715.1.10 by Danilo Segan
Improve comments for _return_url override.
577
        # We override the ReturnToReferrerMixin _return_url because it might
578
        # change when any of the productseries, distroseries,
579
        # sourcepackagename or name attributes change, and the basic version
580
        # only supports watching changes to a single attribute.
581
582
        # The referer header is a hidden input in the form.
13715.1.9 by Danilo Segan
Provide a correct return URL.
583
        referrer = self.request.form.get('_return_url')
584
        returnChanged = False
585
        if referrer is None:
586
            # "referer" is misspelled in the HTTP specification.
587
            referrer = self.request.getHeader('referer')
13715.1.10 by Danilo Segan
Improve comments for _return_url override.
588
            # If we were looking at the actual template, we want a new
589
            # URL constructed.
13715.1.9 by Danilo Segan
Provide a correct return URL.
590
            if referrer is not None and '/+pots/' in referrer:
591
                returnChanged = True
592
593
        if (referrer is not None
594
            and not returnChanged
595
            and referrer.startswith(self.request.getApplicationURL())
596
            and referrer != self.request.getHeader('location')):
597
            return referrer
598
        else:
599
            return canonical_url(self.context)
600
7182.6.2 by Guilherme Salgado
Convert POTemplateEditView into a LaunchpadEditFormView
601
    @action(_('Change'), name='change')
602
    def change_action(self, action, data):
3691.27.8 by Guilherme Salgado
Update all Person.assignKarma() callsites to pass the product/distribution as the method wants
603
        context = self.context
13515.3.9 by Henning Eggers
Use setActive in browser code.
604
        iscurrent = data.get('iscurrent', context.iscurrent)
605
        context.setActive(iscurrent)
7182.6.2 by Guilherme Salgado
Convert POTemplateEditView into a LaunchpadEditFormView
606
        old_description = context.description
607
        old_translation_domain = context.translation_domain
608
        self.updateContextFromData(data)
609
        if old_description != context.description:
3691.27.8 by Guilherme Salgado
Update all Person.assignKarma() callsites to pass the product/distribution as the method wants
610
            self.user.assignKarma(
611
                'translationtemplatedescriptionchanged',
3691.27.9 by Guilherme Salgado
Add POTemplate.product and POTemplate.distribution, to avoid having to check if productseries/distrorelease is None on Person.assignKarma() callsites
612
                product=context.product, distribution=context.distribution,
3691.27.8 by Guilherme Salgado
Update all Person.assignKarma() callsites to pass the product/distribution as the method wants
613
                sourcepackagename=context.sourcepackagename)
7182.6.2 by Guilherme Salgado
Convert POTemplateEditView into a LaunchpadEditFormView
614
        if old_translation_domain != context.translation_domain:
10180.3.5 by Adi Roiban
Add test for template update date.
615
            # We only change date_last_updated when the translation_domain
616
            # field is changed because it is the only relevant field we
617
            # care about regarding the date of last update.
10180.3.4 by Adi Roiban
Make proper use of removeSecurityProxy.
618
            naked_context = removeSecurityProxy(context)
10180.3.6 by Adi Roiban
Fix syntax formating.
619
            naked_context.date_last_updated = datetime.datetime.now(pytz.UTC)
1789 by Canonical.com Patch Queue Manager
PoTemplateAdmin implementation (finally) r=jamesh,mpt
620
13715.1.4 by Danilo Segan
Rename the method _getTemplateSet.
621
    def _validateTargetAndGetTemplates(self, data):
13715.1.3 by Danilo Segan
Improve the division between POTemplate:+edit and +admin pages.
622
        """Return a POTemplateSubset corresponding to the chosen target."""
623
        sourcepackagename = data.get('sourcepackagename',
624
                                     self.context.sourcepackagename)
625
        return getUtility(IPOTemplateSet).getSubset(
626
            distroseries=self.context.distroseries,
627
            sourcepackagename=sourcepackagename,
628
            productseries=self.context.productseries)
629
10744.6.1 by Adi Roiban
Allow priority values between 0 and 10000.
630
    def validate(self, data):
13715.1.3 by Danilo Segan
Improve the division between POTemplate:+edit and +admin pages.
631
        name = data.get('name', None)
632
        if name is None or not valid_name(name):
633
            self.setFieldError(
634
                'name',
635
                'Template name can only start with lowercase letters a-z '
636
                'or digits 0-9, and other than those characters, can only '
637
                'contain "-", "+" and "." characters.')
638
13715.1.9 by Danilo Segan
Provide a correct return URL.
639
        distroseries = data.get('distroseries', self.context.distroseries)
13715.1.3 by Danilo Segan
Improve the division between POTemplate:+edit and +admin pages.
640
        sourcepackagename = data.get(
641
            'sourcepackagename', self.context.sourcepackagename)
642
        productseries = data.get('productseries', None)
643
        sourcepackage_changed = (
644
            distroseries is not None and
645
            (distroseries != self.context.distroseries or
646
             sourcepackagename != self.context.sourcepackagename))
647
        productseries_changed = (productseries is not None and
648
                                 productseries != self.context.productseries)
13715.1.4 by Danilo Segan
Rename the method _getTemplateSet.
649
        similar_templates = self._validateTargetAndGetTemplates(data)
13715.1.6 by Danilo Segan
Conditionalise use of similar_templates.
650
        if similar_templates is not None:
651
            self.validateName(
652
                name, similar_templates, sourcepackage_changed,
653
                productseries_changed)
654
            self.validateDomain(
655
                data.get('translation_domain'), similar_templates,
656
                sourcepackage_changed, productseries_changed)
13715.1.3 by Danilo Segan
Improve the division between POTemplate:+edit and +admin pages.
657
10744.6.1 by Adi Roiban
Allow priority values between 0 and 10000.
658
        priority = data.get('priority')
10744.6.2 by Adi Roiban
Add pagetest.
659
        if priority is None:
660
            return
661
662
        if (priority < self.PRIORITY_MIN_VALUE or
663
            priority > self.PRIORITY_MAX_VALUE):
10744.6.3 by Adi Roiban
Add a fueld error message containing a real sentence.
664
            self.setFieldError(
665
                'priority',
666
                'The priority value must be between %s and %s.' % (
10744.6.2 by Adi Roiban
Add pagetest.
667
                self.PRIORITY_MIN_VALUE, self.PRIORITY_MAX_VALUE))
3691.102.1 by Danilo Šegan
Fix bug #3986: redirect when potemplate name is changed by admin.
668
13161.1.6 by Henning Eggers
First try at fixing for name.
669
    def validateName(self, name, similar_templates,
670
                     sourcepackage_changed, productseries_changed):
671
        """Check that the name does not clash with an existing template."""
10224.9.1 by Jeroen Vermeulen
Check for name/domain uniqueness on template.
672
        if similar_templates.getPOTemplateByName(name) is not None:
13161.1.6 by Henning Eggers
First try at fixing for name.
673
            if sourcepackage_changed:
674
                self.setFieldError(
675
                    'sourcepackagename',
676
                    "Source package already has a template with "
13161.1.8 by Henning Eggers
Fix the bug.
677
                    "that same name.")
13161.1.6 by Henning Eggers
First try at fixing for name.
678
            elif productseries_changed:
679
                self.setFieldError(
680
                    'productseries',
13161.1.8 by Henning Eggers
Fix the bug.
681
                    "Series already has a template with that same name.")
682
            elif name != self.context.name:
13161.1.6 by Henning Eggers
First try at fixing for name.
683
                self.setFieldError('name', "Name is already in use.")
10224.9.1 by Jeroen Vermeulen
Check for name/domain uniqueness on template.
684
13161.1.8 by Henning Eggers
Fix the bug.
685
    def validateDomain(self, domain, similar_templates,
686
                       sourcepackage_changed, productseries_changed):
13957.3.1 by Jeroen Vermeulen
Register approval conflicts in error_output.
687
        clashes = similar_templates.getPOTemplatesByTranslationDomain(domain)
688
        if not clashes.is_empty():
13161.1.8 by Henning Eggers
Fix the bug.
689
            if sourcepackage_changed:
690
                self.setFieldError(
691
                    'sourcepackagename',
692
                    "Source package already has a template with "
693
                    "that same domain.")
694
            elif productseries_changed:
695
                self.setFieldError(
696
                    'productseries',
697
                    "Series already has a template with that same domain.")
698
            elif domain != self.context.translation_domain:
699
                self.setFieldError(
700
                    'translation_domain', "Domain is already in use.")
10224.9.1 by Jeroen Vermeulen
Check for name/domain uniqueness on template.
701
13715.1.3 by Danilo Segan
Improve the division between POTemplate:+edit and +admin pages.
702
    @property
703
    def _return_attribute_name(self):
704
        """See 'ReturnToReferrerMixin'."""
705
        return "name"
706
707
708
class POTemplateAdminView(POTemplateEditView):
709
    """View class that lets you admin a POTemplate object."""
710
    field_names = [
711
        'name', 'translation_domain', 'description', 'header', 'iscurrent',
712
        'owner', 'productseries', 'distroseries', 'sourcepackagename',
713
        'from_sourcepackagename', 'sourcepackageversion', 'binarypackagename',
714
        'languagepack', 'path', 'source_file_format', 'priority',
715
        'date_last_updated']
716
    label = 'Administer translation template'
717
    page_title = "Administer"
718
13715.1.4 by Danilo Segan
Rename the method _getTemplateSet.
719
    def _validateTargetAndGetTemplates(self, data):
13715.1.3 by Danilo Segan
Improve the division between POTemplate:+edit and +admin pages.
720
        """Return a POTemplateSubset corresponding to the chosen target."""
10224.9.1 by Jeroen Vermeulen
Check for name/domain uniqueness on template.
721
        distroseries = data.get('distroseries')
722
        sourcepackagename = data.get('sourcepackagename')
723
        productseries = data.get('productseries')
724
10212.2.1 by Jeroen Vermeulen
Validate input to POTemplate admin form.
725
        if distroseries is not None and productseries is not None:
726
            message = ("Choose a distribution release series or a project "
727
                "release series, but not both.")
728
        elif distroseries is None and productseries is None:
729
            message = ("Choose either a distribution release series or a "
730
                "project release series.")
731
        else:
732
            message = None
733
734
        if message is not None:
735
            self.addError(message)
13715.1.3 by Danilo Segan
Improve the division between POTemplate:+edit and +admin pages.
736
            return None
737
        return getUtility(IPOTemplateSet).getSubset(
10224.9.1 by Jeroen Vermeulen
Check for name/domain uniqueness on template.
738
            distroseries=distroseries, sourcepackagename=sourcepackagename,
739
            productseries=productseries)
10212.2.1 by Jeroen Vermeulen
Validate input to POTemplate admin form.
740
1789 by Canonical.com Patch Queue Manager
PoTemplateAdmin implementation (finally) r=jamesh,mpt
741
1988 by Canonical.com Patch Queue Manager
[r=salgado] RosettaMultipleFormatExports
742
class POTemplateExportView(BaseExportView):
9264.2.10 by Danilo Šegan
Fix up whitespace and most of the review comments.
743
    """Request downloads of a `POTemplate` and its translations."""
1875 by Canonical.com Patch Queue Manager
TranslationFileDownloading [r=Guilherme]
744
9481.2.2 by Henning Eggers
Fixed labels and page_titles on pofile and potemplate pages.
745
    label = "Download translations"
746
    page_title = "Download translations"
9264.2.10 by Danilo Šegan
Fix up whitespace and most of the review comments.
747
748
    @property
749
    def cancel_url(self):
750
        return canonical_url(self.context)
751
1875 by Canonical.com Patch Queue Manager
TranslationFileDownloading [r=Guilherme]
752
    def processForm(self):
1988 by Canonical.com Patch Queue Manager
[r=salgado] RosettaMultipleFormatExports
753
        """Process a form submission requesting a translation export."""
1875 by Canonical.com Patch Queue Manager
TranslationFileDownloading [r=Guilherme]
754
        what = self.request.form.get('what')
755
        if what == 'all':
756
            export_potemplate = True
757
10008.1.2 by Adi Roiban
Add test stories for covering new functionalities.
758
            pofiles = self.context.pofiles
1875 by Canonical.com Patch Queue Manager
TranslationFileDownloading [r=Guilherme]
759
        elif what == 'some':
3691.134.1 by kiko
Fix OOPS-257C588 and friends, which are crashes when submitting an incomplete potemplate/+export form (via curl, for instance). Reenable and update the pagetest for it, which Carlos and I have no idea about
760
            pofiles = []
1875 by Canonical.com Patch Queue Manager
TranslationFileDownloading [r=Guilherme]
761
            export_potemplate = 'potemplate' in self.request.form
762
11122.3.17 by Danilo Segan
Apply review comments from jtv as well.
763
            for code in self.request.form:
11122.3.4 by Danilo Šegan
Get rid of the remaining variant usage.
764
                pofile = self.context.getPOFileByLang(code)
2570.1.37 by Carlos Perelló Marín
More review comments applied
765
                if pofile is not None:
1875 by Canonical.com Patch Queue Manager
TranslationFileDownloading [r=Guilherme]
766
                    pofiles.append(pofile)
767
        else:
3691.178.2 by Diogo Matsubara
fix bug 1558, 44837 and 63236
768
            self.request.response.addErrorNotification(
1875 by Canonical.com Patch Queue Manager
TranslationFileDownloading [r=Guilherme]
769
                'Please choose whether you would like all files or only some '
770
                'of them.')
771
            return
772
773
        if export_potemplate:
4673.6.6 by Jeroen Vermeulen
Changes based on ongoing review.
774
            requested_templates = [self.context]
1875 by Canonical.com Patch Queue Manager
TranslationFileDownloading [r=Guilherme]
775
        else:
4673.6.6 by Jeroen Vermeulen
Changes based on ongoing review.
776
            requested_templates = None
3691.178.2 by Diogo Matsubara
fix bug 1558, 44837 and 63236
777
4673.6.6 by Jeroen Vermeulen
Changes based on ongoing review.
778
        return (requested_templates, pofiles)
3691.178.2 by Diogo Matsubara
fix bug 1558, 44837 and 63236
779
1875 by Canonical.com Patch Queue Manager
TranslationFileDownloading [r=Guilherme]
780
    def pofiles(self):
1988 by Canonical.com Patch Queue Manager
[r=salgado] RosettaMultipleFormatExports
781
        """Return a list of PO files available for export."""
782
1875 by Canonical.com Patch Queue Manager
TranslationFileDownloading [r=Guilherme]
783
        class BrowserPOFile:
10008.1.2 by Adi Roiban
Add test stories for covering new functionalities.
784
1875 by Canonical.com Patch Queue Manager
TranslationFileDownloading [r=Guilherme]
785
            def __init__(self, value, browsername):
786
                self.value = value
787
                self.browsername = browsername
788
1892 by Canonical.com Patch Queue Manager
workaround for PO export problem [r=JamesH]
789
        def pofile_sort_key(pofile):
790
            return pofile.language.englishname
791
792
        for pofile in sorted(self.context.pofiles, key=pofile_sort_key):
8636.4.2 by Jeroen Vermeulen
Removed other pointless string decode as well.
793
            value = pofile.getFullLanguageCode()
8636.4.1 by Jeroen Vermeulen
Keep non-ascii language names as unicode in POFile descriptions.
794
            browsername = pofile.getFullLanguageName()
1875 by Canonical.com Patch Queue Manager
TranslationFileDownloading [r=Guilherme]
795
796
            yield BrowserPOFile(value, browsername)
797
4673.6.1 by Jeroen Vermeulen
Provide download of all translations for a productseries.
798
    def getDefaultFormat(self):
799
        return self.context.source_file_format
800
1715 by Canonical.com Patch Queue Manager
Moved the translation form from potemplate context into pofile context. r=SteveA
801
1988 by Canonical.com Patch Queue Manager
[r=salgado] RosettaMultipleFormatExports
802
class POTemplateSubsetURL:
1955 by Canonical.com Patch Queue Manager
menus. [r=spiv]
803
    implements(ICanonicalUrlData)
804
3618.1.45 by Steve Alexander
refactor virtual hosting
805
    rootsite = 'mainsite'
3691.3.2 by Steve Alexander
set up new host header based virtual hosting, make canonical_url able to be configured with a rootsite, removal of unused IDefaultViewDirective, fix race condition in request publication factory.
806
1955 by Canonical.com Patch Queue Manager
menus. [r=spiv]
807
    def __init__(self, context):
808
        self.context = context
809
810
    @property
811
    def path(self):
812
        potemplatesubset = self.context
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
813
        if potemplatesubset.distroseries is not None:
2048 by Canonical.com Patch Queue Manager
debbugssync, hct enabling, and ui fixes. r=jamesh
814
            assert potemplatesubset.productseries is None
1955 by Canonical.com Patch Queue Manager
menus. [r=spiv]
815
            assert potemplatesubset.sourcepackagename is not None
2705 by Canonical.com Patch Queue Manager
r=spiv, mark's soyuz loving.
816
            return '+source/%s/+pots' % (
1955 by Canonical.com Patch Queue Manager
menus. [r=spiv]
817
                potemplatesubset.sourcepackagename.name)
818
        else:
2048 by Canonical.com Patch Queue Manager
debbugssync, hct enabling, and ui fixes. r=jamesh
819
            assert potemplatesubset.productseries is not None
1955 by Canonical.com Patch Queue Manager
menus. [r=spiv]
820
            return '+pots'
821
822
    @property
823
    def inside(self):
824
        potemplatesubset = self.context
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
825
        if potemplatesubset.distroseries is not None:
2048 by Canonical.com Patch Queue Manager
debbugssync, hct enabling, and ui fixes. r=jamesh
826
            assert potemplatesubset.productseries is None
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
827
            return potemplatesubset.distroseries
1955 by Canonical.com Patch Queue Manager
menus. [r=spiv]
828
        else:
2048 by Canonical.com Patch Queue Manager
debbugssync, hct enabling, and ui fixes. r=jamesh
829
            assert potemplatesubset.productseries is not None
830
            return potemplatesubset.productseries
1955 by Canonical.com Patch Queue Manager
menus. [r=spiv]
831
832
1988 by Canonical.com Patch Queue Manager
[r=salgado] RosettaMultipleFormatExports
833
class POTemplateURL:
1955 by Canonical.com Patch Queue Manager
menus. [r=spiv]
834
    implements(ICanonicalUrlData)
835
9483.1.1 by Danilo Šegan
Re-revert 9477 minus branch-links update.
836
    rootsite = 'translations'
3691.3.2 by Steve Alexander
set up new host header based virtual hosting, make canonical_url able to be configured with a rootsite, removal of unused IDefaultViewDirective, fix race condition in request publication factory.
837
1955 by Canonical.com Patch Queue Manager
menus. [r=spiv]
838
    def __init__(self, context):
839
        self.context = context
840
        potemplate = self.context
841
        potemplateset = getUtility(IPOTemplateSet)
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
842
        if potemplate.distroseries is not None:
2048 by Canonical.com Patch Queue Manager
debbugssync, hct enabling, and ui fixes. r=jamesh
843
            assert potemplate.productseries is None
1955 by Canonical.com Patch Queue Manager
menus. [r=spiv]
844
            self.potemplatesubset = potemplateset.getSubset(
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
845
                distroseries=potemplate.distroseries,
1955 by Canonical.com Patch Queue Manager
menus. [r=spiv]
846
                sourcepackagename=potemplate.sourcepackagename)
847
        else:
2048 by Canonical.com Patch Queue Manager
debbugssync, hct enabling, and ui fixes. r=jamesh
848
            assert potemplate.productseries is not None
1955 by Canonical.com Patch Queue Manager
menus. [r=spiv]
849
            self.potemplatesubset = potemplateset.getSubset(
2048 by Canonical.com Patch Queue Manager
debbugssync, hct enabling, and ui fixes. r=jamesh
850
                productseries=potemplate.productseries)
1955 by Canonical.com Patch Queue Manager
menus. [r=spiv]
851
852
    @property
853
    def path(self):
854
        potemplate = self.context
855
        return potemplate.name
856
857
    @property
858
    def inside(self):
859
        return self.potemplatesubset
1840 by Canonical.com Patch Queue Manager
restructure translation storage, allowing for wiki mode, non-editor translation, accurate statistics and fewer bugs
860
2630 by Canonical.com Patch Queue Manager
[trivial] lots of tidying up. converting all database classes to use NotFoundError consistently, and to import it from launchpad.interfaces in preparation for the move to a new zope3. Also, introduced a NameNotAvailable error. removed browser:traverse rdirective. commented out shipit test that fails sometimes.
861
862
class POTemplateSetNavigation(GetitemNavigation):
863
864
    usedfor = IPOTemplateSet
865
866
4767.9.1 by Carlos Perello Marin
Improved POTemplate navigation so we only traverse to active templates for normal users and all available templates for admins
867
class POTemplateSubsetNavigation(Navigation):
2630 by Canonical.com Patch Queue Manager
[trivial] lots of tidying up. converting all database classes to use NotFoundError consistently, and to import it from launchpad.interfaces in preparation for the move to a new zope3. Also, introduced a NameNotAvailable error. removed browser:traverse rdirective. commented out shipit test that fails sometimes.
868
869
    usedfor = IPOTemplateSubset
870
4767.9.1 by Carlos Perello Marin
Improved POTemplate navigation so we only traverse to active templates for normal users and all available templates for admins
871
    def traverse(self, name):
872
        """Return the IPOTemplate associated with the given name."""
873
10355.4.14 by Adi Roiban
Make entry names less gettextish. Export translation templates using a method. Allow PATCH-ing POTemplates.
874
        assert self.request.method in ['GET', 'HEAD', 'PATCH', 'POST'], (
875
            'We only know about GET, HEAD, PATCH and POST')
4767.9.1 by Carlos Perello Marin
Improved POTemplate navigation so we only traverse to active templates for normal users and all available templates for admins
876
877
        # Get the requested potemplate.
878
        potemplate = self.context.getPOTemplateByName(name)
879
        if potemplate is None:
880
            # The template doesn't exist.
881
            raise NotFoundError(name)
882
883
        # Get whether the target for the requested template is officially
884
        # using Launchpad Translations.
10355.4.15 by Adi Roiban
Remove shortlist and return the POTemplateSubset as Storm results.
885
        if potemplate.distribution is None:
886
            product_or_distro = potemplate.productseries.product
4767.9.1 by Carlos Perello Marin
Improved POTemplate navigation so we only traverse to active templates for normal users and all available templates for admins
887
        else:
10355.4.15 by Adi Roiban
Remove shortlist and return the POTemplateSubset as Storm results.
888
            product_or_distro = potemplate.distroseries.distribution
11472.3.1 by j.c.sackett
Updated all nontest callsites in the code.
889
        translations_usage = product_or_distro.translations_usage
4767.9.1 by Carlos Perello Marin
Improved POTemplate navigation so we only traverse to active templates for normal users and all available templates for admins
890
11472.3.1 by j.c.sackett
Updated all nontest callsites in the code.
891
        if (service_uses_launchpad(translations_usage) and
892
           potemplate.iscurrent):
10355.4.15 by Adi Roiban
Remove shortlist and return the POTemplateSubset as Storm results.
893
            # This template is available for translation.
894
            return potemplate
13715.1.13 by Danilo Segan
Fix test failures.
895
        elif check_permission('launchpad.TranslationsAdmin', potemplate):
10355.4.15 by Adi Roiban
Remove shortlist and return the POTemplateSubset as Storm results.
896
            # User has Edit privileges for this template and can access it.
4767.9.1 by Carlos Perello Marin
Improved POTemplate navigation so we only traverse to active templates for normal users and all available templates for admins
897
            return potemplate
898
        else:
899
            raise NotFoundError(name)
9483.1.1 by Danilo Šegan
Re-revert 9477 minus branch-links update.
900
901
902
class POTemplateBreadcrumb(Breadcrumb):
903
    """Breadcrumb for `IPOTemplate`."""
10008.1.2 by Adi Roiban
Add test stories for covering new functionalities.
904
9483.1.1 by Danilo Šegan
Re-revert 9477 minus branch-links update.
905
    @property
906
    def text(self):
907
        return smartquote('Template "%s"' % self.context.name)
10095.5.1 by Adi Roiban
Initial fix. Pagetests needs to be updated.
908
909
910
class BaseSeriesTemplatesView(LaunchpadView):
911
    """Show a list of all templates for the Series."""
912
913
    is_distroseries = True
914
    distroseries = None
915
    productseries = None
916
    label = "Translation templates"
917
    page_title = "All templates"
11128.4.3 by Adi Roiban
Fix problems raised during the previous review. Language counts are not added.
918
    can_edit = None
919
    can_admin = None
10095.5.1 by Adi Roiban
Initial fix. Pagetests needs to be updated.
920
921
    def initialize(self, series, is_distroseries=True):
13378.3.11 by Benji York
checkpoint
922
        self._template_name_cache = {}
923
        self._packaging_cache = {}
10095.5.1 by Adi Roiban
Initial fix. Pagetests needs to be updated.
924
        self.is_distroseries = is_distroseries
925
        if is_distroseries:
926
            self.distroseries = series
927
        else:
928
            self.productseries = series
13814.2.1 by Danilo Segan
Fix the wrongly displayed link for project admins.
929
        user = IPersonRoles(self.user, None)
930
        self.can_admin = (user is not None and
931
                          (user.in_admin or user.in_rosetta_experts))
11128.4.3 by Adi Roiban
Fix problems raised during the previous review. Language counts are not added.
932
        self.can_edit = (
13715.1.3 by Danilo Segan
Improve the division between POTemplate:+edit and +admin pages.
933
            self.can_admin or
934
            check_permission('launchpad.TranslationsAdmin', series))
10095.5.1 by Adi Roiban
Initial fix. Pagetests needs to be updated.
935
11532.6.1 by Jeroen Vermeulen
Render template column entirely in the view.
936
        self.user_is_logged_in = (self.user is not None)
937
13378.3.12 by Benji York
checkpoint
938
    def iter_data(self):
13378.3.23 by Benji York
Fix failing tests (and work less) by using the original query when not rendering a distroseries.
939
        # If this is not a distroseries, then the query is much simpler.
940
        if not self.is_distroseries:
941
            potemplateset = getUtility(IPOTemplateSet)
942
            # The "shape" of the data returned by POTemplateSubset isn't quite
943
            # right so we have to run it through zip first.
944
            return zip(potemplateset.getSubset(
945
                productseries=self.productseries,
946
                distroseries=self.distroseries,
947
                ordered_by_names=True))
948
949
        # Otherwise we have to do more work, primarily for the "sharing"
950
        # column.
13378.3.14 by Benji York
checkpoint
951
        OtherTemplate = ClassAlias(POTemplate)
13378.3.20 by Benji York
add joinInner and joinOuter to ITranslationTemplatesCollection
952
        join = (self.context.getTemplatesCollection()
13378.3.12 by Benji York
checkpoint
953
            .joinOuter(Packaging, And(
954
                Packaging.distroseries == self.context.id,
13378.3.18 by Benji York
fix lint and tweak some comments
955
                Packaging.sourcepackagename ==
956
                    POTemplate.sourcepackagenameID))
13378.3.12 by Benji York
checkpoint
957
            .joinOuter(ProductSeries,
13378.3.15 by Benji York
I think it's working.
958
                ProductSeries.id == Packaging.productseriesID)
13378.3.12 by Benji York
checkpoint
959
            .joinOuter(Product, And(
960
                Product.id == ProductSeries.productID,
961
                Or(
14120.2.4 by Danilo Segan
Replace all references to _translation_usage with translation_usage.
962
                    Product.translations_usage == ServiceUsage.LAUNCHPAD,
963
                    Product.translations_usage == ServiceUsage.EXTERNAL)))
13378.3.14 by Benji York
checkpoint
964
            .joinOuter(OtherTemplate, And(
965
                OtherTemplate.productseriesID == ProductSeries.id,
13378.3.15 by Benji York
I think it's working.
966
                OtherTemplate.name == POTemplate.name))
967
            .joinInner(SourcePackageName,
968
                SourcePackageName.id == POTemplate.sourcepackagenameID))
13378.3.14 by Benji York
checkpoint
969
13378.3.15 by Benji York
I think it's working.
970
        return join.select(POTemplate, Packaging, ProductSeries, Product,
971
            OtherTemplate, SourcePackageName)
10095.5.1 by Adi Roiban
Initial fix. Pagetests needs to be updated.
972
973
    def rowCSSClass(self, template):
11128.4.2 by Adi Roiban
Reduce the usage of URL formaters.
974
        if template.iscurrent:
10095.5.1 by Adi Roiban
Initial fix. Pagetests needs to be updated.
975
            return "active-template"
976
        else:
977
            return "inactive-template"
11532.6.1 by Jeroen Vermeulen
Render template column entirely in the view.
978
979
    def _renderSourcePackage(self, template):
980
        """Render the `SourcePackageName` for `template`."""
981
        if self.is_distroseries:
982
            return cgi.escape(template.sourcepackagename.name)
983
        else:
984
            return None
985
986
    def _renderTemplateLink(self, template, url):
987
        """Render a link to `template`.
988
989
        :param template: The target `POTemplate`.
990
        :param url: The cached URL for `template`.
991
        :return: HTML for a link to `template`.
992
        """
993
        text = '<a href="%s">%s</a>' % (url, cgi.escape(template.name))
994
        if not template.iscurrent:
995
            text += ' (inactive)'
996
        return text
997
13378.3.14 by Benji York
checkpoint
998
    def _renderSharing(self, template, packaging, productseries, upstream,
13378.3.15 by Benji York
I think it's working.
999
            other_template, sourcepackagename):
13378.3.3 by Benji York
checkpoint
1000
        """Render a link to `template`.
1001
1002
        :param template: The target `POTemplate`.
1003
        :return: HTML for the "sharing" status of `template`.
1004
        """
13378.3.16 by Benji York
checkpoint
1005
        # Testing is easier if we are willing to extract the sourcepackagename
1006
        # from the template.
1007
        if sourcepackagename is None:
1008
            sourcepackagename = template.sourcepackagename
13378.3.8 by Benji York
add edit link for non-shared templates
1009
        # Build the edit link.
13378.3.15 by Benji York
I think it's working.
1010
        escaped_source = cgi.escape(sourcepackagename.name)
13378.3.8 by Benji York
add edit link for non-shared templates
1011
        source_url = '+source/%s' % escaped_source
1012
        details_url = source_url + '/+sharing-details'
1013
        edit_link = '<a class="sprite edit" href="%s"></a>' % details_url
13378.3.11 by Benji York
checkpoint
1014
13378.3.7 by Benji York
made changes suggested by Danilo
1015
        # If all the conditions are met for sharing...
13378.3.14 by Benji York
checkpoint
1016
        if packaging and upstream and other_template is not None:
13378.3.15 by Benji York
I think it's working.
1017
            escaped_series = cgi.escape(productseries.name)
13378.3.7 by Benji York
made changes suggested by Danilo
1018
            escaped_template = cgi.escape(template.name)
1019
            pot_url = ('/%s/%s/+pots/%s' %
1020
                (escaped_source, escaped_series, escaped_template))
13378.3.8 by Benji York
add edit link for non-shared templates
1021
            return (edit_link + '<a href="%s">%s/%s</a>'
1022
                % (pot_url, escaped_source, escaped_series))
13378.3.3 by Benji York
checkpoint
1023
        else:
13378.3.8 by Benji York
add edit link for non-shared templates
1024
            # Otherwise just say that the template isn't shared and give them
1025
            # a link to change the sharing.
1026
            return edit_link + 'not shared'
13378.3.3 by Benji York
checkpoint
1027
11532.6.1 by Jeroen Vermeulen
Render template column entirely in the view.
1028
    def _renderLastUpdateDate(self, template):
1029
        """Render a template's "last updated" column."""
1030
        formatter = DateTimeFormatterAPI(template.date_last_updated)
1031
        full_time = formatter.datetime()
1032
        date = formatter.approximatedate()
1033
        return ''.join([
1034
            '<span class="sortkey">%s</span>' % full_time,
1035
            '<span class="lastupdate_column" title="%s">%s</span>' % (
1036
                full_time, date),
1037
            ])
1038
1039
    def _renderAction(self, base_url, name, path, sprite, enabled):
1040
        """Render an action for the "actions" column.
1041
1042
        :param base_url: The cached URL for `template`.
1043
        :param name: Action name for display in the UI.
1044
        :param path: Path suffix for the action (relative to `base_url`).
1045
        :param sprite: Sprite class for the action.
1046
        :param enabled: Show this action?  If not, return empty string.
1047
        :return: HTML for the contents of the "actions" column.
1048
        """
1049
        if not enabled:
1050
            return ''
1051
1052
        parameters = {
1053
            'base_url': base_url,
1054
            'name': name,
1055
            'path': path,
1056
            'sprite': sprite,
1057
        }
1058
        return (
1059
            '<a class="sprite %(sprite)s" href="%(base_url)s/%(path)s">'
1060
            '%(name)s'
1061
            '</a>') % parameters
1062
1063
    def _renderActionsColumn(self, template, base_url):
1064
        """Render a template's "actions" column."""
1065
        if not self.user_is_logged_in:
1066
            return None
1067
1068
        actions = [
1069
            ('Edit', '+edit', 'edit', self.can_edit),
1070
            ('Upload', '+upload', 'add', self.can_edit),
1071
            ('Download', '+export', 'download', self.user_is_logged_in),
1072
            ('Administer', '+admin', 'edit', self.can_admin),
1073
        ]
1074
        links = [
1075
            self._renderAction(base_url, *action) for action in actions]
1076
        html = '<div class="template_links">\n%s</div>'
1077
        return html % '\n'.join(links)
1078
1079
    def _renderField(self, column_class, content, tag='td'):
1080
        """Create a table field of the given class and contents.
1081
1082
        :param column_class: CSS class for this column.
1083
        :param content: HTML to go into the column.  If None, the field
1084
            will be omitted entirely.  (To produce an empty column, pass
1085
            the empty string instead.)
1086
        :param tag: The HTML tag to surround the field in.
1087
        :return: HTML for the entire table field, or the empty string if
1088
            `content` was None.
1089
        """
1090
        if content is None:
1091
            return ''
1092
        else:
1093
            return '<%s class="%s">%s</%s>' % (
1094
                tag, column_class, content, tag)
1095
1096
    def constructTemplateURL(self, template):
1097
        """Build the URL for `template`.
1098
1099
        Since this is performance-critical, views are allowed to
1100
        override it with optimized implementations.
1101
        """
1102
        return canonical_url(template)
1103
1104
    def renderTemplatesHeader(self):
1105
        """Render HTML for the templates table header."""
1106
        if self.is_distroseries:
1107
            sourcepackage_header = "Source package"
1108
        else:
1109
            sourcepackage_header = None
1110
        if self.user_is_logged_in:
1111
            actions_header = "Actions"
1112
        else:
1113
            actions_header = None
13378.3.3 by Benji York
checkpoint
1114
11532.6.1 by Jeroen Vermeulen
Render template column entirely in the view.
1115
        columns = [
1116
            ('priority_column', "Priority"),
1117
            ('sourcepackage_column', sourcepackage_header),
1118
            ('template_column', "Template name"),
1119
            ('length_column', "Length"),
1120
            ('lastupdate_column', "Updated"),
1121
            ('actions_column', actions_header),
1122
            ]
13378.3.3 by Benji York
checkpoint
1123
13378.3.6 by Benji York
checkpoint
1124
        if self.is_distroseries:
13378.3.7 by Benji York
made changes suggested by Danilo
1125
            columns[3:3] = [('sharing', "Shared with")]
13378.3.3 by Benji York
checkpoint
1126
11532.6.1 by Jeroen Vermeulen
Render template column entirely in the view.
1127
        return '\n'.join([
11542.2.1 by Jeroen Vermeulen
Python 2.6-ism.
1128
            self._renderField(css, text, tag='th')
1129
            for (css, text) in columns])
11532.6.1 by Jeroen Vermeulen
Render template column entirely in the view.
1130
13378.3.12 by Benji York
checkpoint
1131
    def renderTemplateRow(self, template, packaging=None, productseries=None,
13378.3.15 by Benji York
I think it's working.
1132
            upstream=None, other_template=None, sourcepackagename=None):
11532.6.1 by Jeroen Vermeulen
Render template column entirely in the view.
1133
        """Render HTML for an entire template row."""
1134
        if not self.can_edit and not template.iscurrent:
1135
            return ""
1136
1137
        # Cached URL for template.
1138
        base_url = self.constructTemplateURL(template)
1139
1140
        fields = [
1141
            ('priority_column', template.priority),
1142
            ('sourcepackage_column', self._renderSourcePackage(template)),
1143
            ('template_column', self._renderTemplateLink(template, base_url)),
1144
            ('length_column', template.messagecount),
1145
            ('lastupdate_column', self._renderLastUpdateDate(template)),
1146
            ('actions_column', self._renderActionsColumn(template, base_url)),
1147
        ]
1148
13378.3.6 by Benji York
checkpoint
1149
        if self.is_distroseries:
13378.3.15 by Benji York
I think it's working.
1150
            fields[3:3] = [(
1151
                'sharing', self._renderSharing(template, packaging,
1152
                    productseries, upstream, other_template,
1153
                    sourcepackagename)
1154
                )]
13378.3.3 by Benji York
checkpoint
1155
11532.6.1 by Jeroen Vermeulen
Render template column entirely in the view.
1156
        tds = [self._renderField(*field) for field in fields]
1157
1158
        css = self.rowCSSClass(template)
1159
        return '<tr class="template_row %s">\n%s</tr>\n' % (
1160
            css, '\n'.join(tds))