61
62
from canonical.launchpad.browser.librarian import FileNavigationMixin
62
63
from canonical.launchpad.components.tokens import create_token
63
64
from canonical.launchpad.helpers import english_list
65
from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
64
66
from canonical.launchpad.webapp import (
66
68
enabled_with_permission,
72
74
from canonical.launchpad.webapp.authorization import check_permission
73
75
from canonical.launchpad.webapp.badge import HasBadgeBase
74
76
from canonical.launchpad.webapp.batching import BatchNavigator
75
from canonical.launchpad.webapp.interfaces import (
77
from canonical.launchpad.webapp.interfaces import ICanonicalUrlData
79
78
from canonical.launchpad.webapp.menu import (
92
91
TextLineEditorWidget,
94
93
from lp.app.errors import NotFoundError
95
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
96
94
from lp.app.widgets.itemswidgets import (
97
95
LabeledMultiCheckBoxWidget,
98
96
LaunchpadDropdownWidget,
133
131
ArchivePermissionType,
137
134
PackagePublishingStatus,
139
136
from lp.soyuz.interfaces.archive import (
140
ArchiveDependencyError,
143
139
IArchiveEditDependenciesForm,
146
validate_external_dependencies,
148
143
from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet
149
144
from lp.soyuz.interfaces.archivesubscriber import IArchiveSubscriberSet
150
145
from lp.soyuz.interfaces.binarypackagebuild import BuildSetStatus
151
146
from lp.soyuz.interfaces.binarypackagename import IBinaryPackageNameSet
152
147
from lp.soyuz.interfaces.component import IComponentSet
153
from lp.soyuz.interfaces.packagecopyjob import IPlainPackageCopyJobSource
148
from lp.soyuz.interfaces.distributionjob import IPackageCopyJobSource
154
149
from lp.soyuz.interfaces.packagecopyrequest import IPackageCopyRequestSet
155
150
from lp.soyuz.interfaces.packageset import IPackagesetSet
156
151
from lp.soyuz.interfaces.processor import IProcessorFamilySet
1287
1282
dest_display_name)
1285
def partition_pubs_by_archive(source_pubs):
1286
"""Group `source_pubs` by archive.
1288
:param source_pubs: A sequence of `SourcePackagePublishingHistory`.
1289
:return: A dict mapping `Archive`s to the list of entries from
1290
`source_pubs` that are in that archive.
1292
by_source_archive = {}
1293
for spph in source_pubs:
1294
by_source_archive.setdefault(spph.archive, []).append(spph)
1295
return by_source_archive
1298
def name_pubs_with_versions(source_pubs):
1299
"""Annotate each entry from `source_pubs` with its version.
1301
:param source_pubs: A sequence of `SourcePackagePublishingHistory`.
1302
:return: A list of tuples (name, version), one for each respective
1303
entry in `source_pubs`.
1305
sprs = [spph.sourcepackagerelease for spph in source_pubs]
1306
return [(spr.sourcepackagename.name, spr.version) for spr in sprs]
1290
1309
def copy_asynchronously(source_pubs, dest_archive, dest_series, dest_pocket,
1291
1310
include_binaries, dest_url=None,
1292
1311
dest_display_name=None, person=None,
1305
1324
check_copy_permissions(
1306
1325
person, dest_archive, dest_series, dest_pocket, spns)
1308
job_source = getUtility(IPlainPackageCopyJobSource)
1309
for spph in source_pubs:
1327
job_source = getUtility(IPackageCopyJobSource)
1328
archive_pubs = partition_pubs_by_archive(source_pubs)
1329
for source_archive, spphs in archive_pubs.iteritems():
1310
1330
job_source.create(
1311
spph.source_package_name, spph.archive, dest_archive, dest_series,
1312
dest_pocket, include_binaries=include_binaries,
1313
package_version=spph.sourcepackagerelease.version,
1314
copy_policy=PackageCopyPolicy.INSECURE)
1331
name_pubs_with_versions(spphs), source_archive, dest_archive,
1332
dest_series, dest_pocket, include_binaries=include_binaries)
1316
1333
return structured("""
1317
1334
<p>Requested sync of %s packages.</p>
1318
1335
<p>Please allow some time for these to be processed.</p>
1358
1375
def do_copy(self, sources_field_name, source_pubs, dest_archive,
1359
1376
dest_series, dest_pocket, include_binaries,
1360
1377
dest_url=None, dest_display_name=None, person=None,
1361
check_permissions=True, force_async=False):
1378
check_permissions=True):
1362
1379
"""Copy packages and add appropriate feedback to the browser page.
1364
1381
This may either copy synchronously, if there are few enough
1382
1399
:param person: The person requesting the copy.
1383
1400
:param: check_permissions: boolean indicating whether or not the
1384
1401
requester's permissions to copy should be checked.
1385
:param force_async: Force the copy to create package copy jobs and
1386
perform the copy asynchronously.
1388
1403
:return: True if the copying worked, False otherwise.
1391
if (force_async == False and
1392
self.canCopySynchronously(source_pubs)):
1406
if self.canCopySynchronously(source_pubs):
1393
1407
notification = copy_synchronously(
1394
1408
source_pubs, dest_archive, dest_series, dest_pocket,
1395
1409
include_binaries, dest_url=dest_url,
1582
1596
self.setNextURL()
1585
def get_escapedtext(message):
1586
"""Return escapedtext if message is an `IStructuredString`."""
1587
if IStructuredString.providedBy(message):
1588
return message.escapedtext
1593
1599
class ArchiveEditDependenciesView(ArchiveViewBase, LaunchpadFormView):
1594
1600
"""Archive dependencies view class."""
1788
1794
def messages(self):
1789
return '\n'.join(map(get_escapedtext, self._messages))
1795
return '\n'.join(self._messages)
1791
1797
def _remove_dependencies(self, data):
1792
1798
"""Perform the removal of the selected dependencies."""
1802
1808
# Present a page notification describing the action.
1803
1809
self._messages.append('<p>Dependencies removed:')
1804
1810
for dependency in selected_dependencies:
1805
self._messages.append(
1806
structured('<br/>%s', dependency.displayname))
1811
self._messages.append('<br/>%s' % dependency.displayname)
1807
1812
self._messages.append('</p>')
1809
1814
def _add_ppa_dependencies(self, data):
1816
1821
dependency_candidate, PackagePublishingPocket.RELEASE,
1817
1822
getUtility(IComponentSet)['main'])
1819
self._messages.append(structured(
1820
'<p>Dependency added: %s</p>', dependency_candidate.displayname))
1824
self._messages.append(
1825
'<p>Dependency added: %s</p>' % dependency_candidate.displayname)
1822
1827
def _add_primary_dependencies(self, data):
1823
1828
"""Record the selected dependency."""
1862
1867
primary_dependency = self.context.addArchiveDependency(
1863
1868
self.context.distribution.main_archive, dependency_pocket,
1864
1869
dependency_component)
1865
self._messages.append(structured(
1866
'<p>Primary dependency added: %s</p>', primary_dependency.title))
1870
self._messages.append(
1871
'<p>Primary dependency added: %s</p>' % primary_dependency.title)
1873
def validate(self, data):
1874
"""Validate dependency configuration changes.
1876
Skip checks if no dependency candidate was sent in the form.
1878
Validate if the requested PPA dependency is sane (different than
1879
the context PPA and not yet registered).
1881
Also check if the dependency candidate is private, if so, it can
1882
only be set if the user has 'launchpad.View' permission on it and
1883
the context PPA is also private (this way P3A credentials will be
1884
sanitized from buildlogs).
1886
dependency_candidate = data.get('dependency_candidate')
1888
if dependency_candidate is None:
1891
if dependency_candidate == self.context:
1892
self.setFieldError('dependency_candidate',
1893
"An archive should not depend on itself.")
1896
if self.context.getArchiveDependency(dependency_candidate):
1897
self.setFieldError('dependency_candidate',
1898
"This dependency is already registered.")
1901
if not check_permission('launchpad.View', dependency_candidate):
1903
'dependency_candidate',
1904
"You don't have permission to use this dependency.")
1907
if dependency_candidate.private and not self.context.private:
1909
'dependency_candidate',
1910
"Public PPAs cannot depend on private ones.")
1868
1912
@action(_("Save"), name="save")
1869
1913
def save_action(self, action, data):
1876
1920
refreshing. And render a page notification with the summary of the
1923
# Redirect after POST.
1924
self.next_url = self.request.URL
1879
1926
# Process the form.
1880
1927
self._add_primary_dependencies(data)
1882
self._add_ppa_dependencies(data)
1883
except ArchiveDependencyError as e:
1884
self.setFieldError('dependency_candidate', str(e))
1928
self._add_ppa_dependencies(data)
1886
1929
self._remove_dependencies(data)
1888
1931
# Issue a notification if anything was changed.
1889
1932
if len(self.messages) > 0:
1890
1933
self.request.response.addNotification(
1891
1934
structured(self.messages))
1892
# Redirect after POST.
1893
self.next_url = self.request.URL
1896
1937
class ArchiveActivateView(LaunchpadFormView):
2093
2134
# Check the external_dependencies field.
2094
2135
ext_deps = data.get('external_dependencies')
2095
2136
if ext_deps is not None:
2096
errors = validate_external_dependencies(ext_deps)
2137
errors = self.validate_external_dependencies(ext_deps)
2097
2138
if len(errors) != 0:
2098
2139
error_text = "\n".join(errors)
2099
2140
self.setFieldError('external_dependencies', error_text)
2104
2145
'Can only set commericial for private archives.')
2147
def validate_external_dependencies(self, ext_deps):
2148
"""Validate the external_dependencies field.
2150
:param ext_deps: The dependencies form field to check.
2153
# The field can consist of multiple entries separated by
2154
# newlines, so process each in turn.
2155
for dep in ext_deps.splitlines():
2157
deb, url, suite, components = dep.split(" ", 3)
2160
"'%s' is not a complete and valid sources.list entry"
2165
errors.append("%s: Must start with 'deb'" % dep)
2166
url_components = urlparse(url)
2167
if not url_components[0] or not url_components[1]:
2168
errors.append("%s: Invalid URL" % dep)
2107
2173
def owner_is_private_team(self):
2108
2174
"""Is the owner a private team?