~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/soyuz/browser/archive.py

  • Committer: Launchpad Patch Queue Manager
  • Date: 2011-05-23 18:43:31 UTC
  • mfrom: (13084.2.6 page-match-rewrite-url)
  • Revision ID: launchpad@pqm.canonical.com-20110523184331-dhd2c7cgfuu49epw
[r=sinzui][bug=784273] Adds facility to the PageMatch to handle bad
        URIs

Show diffs side-by-side

added added

removed removed

Lines of Context:
33
33
    datetime,
34
34
    timedelta,
35
35
    )
 
36
from urlparse import urlparse
36
37
 
37
38
import pytz
38
39
from sqlobject import SQLObjectNotFound
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 (
65
67
    canonical_url,
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 (
76
 
    ICanonicalUrlData,
77
 
    IStructuredString,
78
 
    )
 
77
from canonical.launchpad.webapp.interfaces import ICanonicalUrlData
79
78
from canonical.launchpad.webapp.menu import (
80
79
    NavigationMenu,
81
80
    structured,
92
91
    TextLineEditorWidget,
93
92
    )
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,
134
132
    ArchivePurpose,
135
133
    ArchiveStatus,
136
 
    PackageCopyPolicy,
137
134
    PackagePublishingStatus,
138
135
    )
139
136
from lp.soyuz.interfaces.archive import (
140
 
    ArchiveDependencyError,
141
137
    CannotCopy,
142
138
    IArchive,
143
139
    IArchiveEditDependenciesForm,
144
140
    IArchiveSet,
145
141
    NoSuchPPA,
146
 
    validate_external_dependencies,
147
142
    )
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)
1288
1283
 
1289
1284
 
 
1285
def partition_pubs_by_archive(source_pubs):
 
1286
    """Group `source_pubs` by archive.
 
1287
 
 
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.
 
1291
    """
 
1292
    by_source_archive = {}
 
1293
    for spph in source_pubs:
 
1294
        by_source_archive.setdefault(spph.archive, []).append(spph)
 
1295
    return by_source_archive
 
1296
 
 
1297
 
 
1298
def name_pubs_with_versions(source_pubs):
 
1299
    """Annotate each entry from `source_pubs` with its version.
 
1300
 
 
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`.
 
1304
    """
 
1305
    sprs = [spph.sourcepackagerelease for spph in source_pubs]
 
1306
    return [(spr.sourcepackagename.name, spr.version) for spr in sprs]
 
1307
 
 
1308
 
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)
1307
1326
 
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)
1315
 
 
 
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.
1363
1380
 
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.
1387
1402
 
1388
1403
        :return: True if the copying worked, False otherwise.
1389
1404
        """
1390
1405
        try:
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()
1583
1597
 
1584
1598
 
1585
 
def get_escapedtext(message):
1586
 
    """Return escapedtext if message is an `IStructuredString`."""
1587
 
    if IStructuredString.providedBy(message):
1588
 
        return message.escapedtext
1589
 
    else:
1590
 
        return message
1591
 
 
1592
 
 
1593
1599
class ArchiveEditDependenciesView(ArchiveViewBase, LaunchpadFormView):
1594
1600
    """Archive dependencies view class."""
1595
1601
 
1786
1792
 
1787
1793
    @property
1788
1794
    def messages(self):
1789
 
        return '\n'.join(map(get_escapedtext, self._messages))
 
1795
        return '\n'.join(self._messages)
1790
1796
 
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>')
1808
1813
 
1809
1814
    def _add_ppa_dependencies(self, data):
1816
1821
            dependency_candidate, PackagePublishingPocket.RELEASE,
1817
1822
            getUtility(IComponentSet)['main'])
1818
1823
 
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)
1821
1826
 
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)
 
1872
 
 
1873
    def validate(self, data):
 
1874
        """Validate dependency configuration changes.
 
1875
 
 
1876
        Skip checks if no dependency candidate was sent in the form.
 
1877
 
 
1878
        Validate if the requested PPA dependency is sane (different than
 
1879
        the context PPA and not yet registered).
 
1880
 
 
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).
 
1885
        """
 
1886
        dependency_candidate = data.get('dependency_candidate')
 
1887
 
 
1888
        if dependency_candidate is None:
 
1889
            return
 
1890
 
 
1891
        if dependency_candidate == self.context:
 
1892
            self.setFieldError('dependency_candidate',
 
1893
                               "An archive should not depend on itself.")
 
1894
            return
 
1895
 
 
1896
        if self.context.getArchiveDependency(dependency_candidate):
 
1897
            self.setFieldError('dependency_candidate',
 
1898
                               "This dependency is already registered.")
 
1899
            return
 
1900
 
 
1901
        if not check_permission('launchpad.View', dependency_candidate):
 
1902
            self.setFieldError(
 
1903
                'dependency_candidate',
 
1904
                "You don't have permission to use this dependency.")
 
1905
            return
 
1906
 
 
1907
        if dependency_candidate.private and not self.context.private:
 
1908
            self.setFieldError(
 
1909
                'dependency_candidate',
 
1910
                "Public PPAs cannot depend on private ones.")
1867
1911
 
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
1877
1921
        changes made.
1878
1922
        """
 
1923
        # Redirect after POST.
 
1924
        self.next_url = self.request.URL
 
1925
 
1879
1926
        # Process the form.
1880
1927
        self._add_primary_dependencies(data)
1881
 
        try:
1882
 
            self._add_ppa_dependencies(data)
1883
 
        except ArchiveDependencyError as e:
1884
 
            self.setFieldError('dependency_candidate', str(e))
1885
 
            return
 
1928
        self._add_ppa_dependencies(data)
1886
1929
        self._remove_dependencies(data)
1887
1930
 
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
1894
1935
 
1895
1936
 
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)
2103
2144
                'commercial',
2104
2145
                'Can only set commericial for private archives.')
2105
2146
 
 
2147
    def validate_external_dependencies(self, ext_deps):
 
2148
        """Validate the external_dependencies field.
 
2149
 
 
2150
        :param ext_deps: The dependencies form field to check.
 
2151
        """
 
2152
        errors = []
 
2153
        # The field can consist of multiple entries separated by
 
2154
        # newlines, so process each in turn.
 
2155
        for dep in ext_deps.splitlines():
 
2156
            try:
 
2157
                deb, url, suite, components = dep.split(" ", 3)
 
2158
            except ValueError:
 
2159
                errors.append(
 
2160
                    "'%s' is not a complete and valid sources.list entry"
 
2161
                        % dep)
 
2162
                continue
 
2163
 
 
2164
            if deb != "deb":
 
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)
 
2169
 
 
2170
        return errors
 
2171
 
2106
2172
    @property
2107
2173
    def owner_is_private_team(self):
2108
2174
        """Is the owner a private team?