← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~jelmer/launchpad/recipe-format-0.4 into lp:launchpad/db-devel

 

Jelmer Vernooij has proposed merging lp:~jelmer/launchpad/recipe-format-0.4 into lp:launchpad/db-devel with lp:~jelmer/launchpad/optional-deb-version as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #891928 in Launchpad itself: "Unrecognized 0.4 version of recipe"
  https://bugs.launchpad.net/launchpad/+bug/891928

For more details, see:
https://code.launchpad.net/~jelmer/launchpad/recipe-format-0.4/+merge/85245

Add support for version 0.4 of the recipe format. 

This format makes the "deb-version" field optional. 
-- 
https://code.launchpad.net/~jelmer/launchpad/recipe-format-0.4/+merge/85245
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jelmer/launchpad/recipe-format-0.4 into lp:launchpad/db-devel.
=== modified file 'buildout-templates/bin/combine-css.in'
--- buildout-templates/bin/combine-css.in	2011-12-01 20:55:49 +0000
+++ buildout-templates/bin/combine-css.in	2011-12-11 01:49:34 +0000
@@ -24,6 +24,7 @@
 # including lots of styles that we don't need/want, so keeping this hard-coded
 # list seems like the best option for now.
 names = [
+    'ubuntu-webfonts.css',
     'style.css',
     'yui/cssreset/reset.css',
     # Use the old cssgrids instead of the new cssgrids.

=== modified file 'cronscripts/check-teamparticipation.py'
--- cronscripts/check-teamparticipation.py	2011-11-04 21:48:55 +0000
+++ cronscripts/check-teamparticipation.py	2011-12-11 01:49:34 +0000
@@ -23,8 +23,8 @@
 from lp.registry.scripts.teamparticipation import (
     check_teamparticipation_circular,
     check_teamparticipation_consistency,
-    check_teamparticipation_self,
     fetch_team_participation_info,
+    fix_teamparticipation_consistency,
     )
 from lp.services.scripts.base import LaunchpadScript
 from lp.services.utils import (
@@ -53,14 +53,14 @@
         if self.options.load_info:
             participation_info = load_bz2_pickle(self.options.load_info)
         else:
-            check_teamparticipation_self(self.logger)
             check_teamparticipation_circular(self.logger)
             participation_info = fetch_team_participation_info(self.logger)
         if self.options.save_info:
             save_bz2_pickle(participation_info, self.options.save_info)
         else:
-            check_teamparticipation_consistency(
+            errors = check_teamparticipation_consistency(
                 self.logger, participation_info)
+            fix_teamparticipation_consistency(self.logger, errors)
 
 
 if __name__ == '__main__':

=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg	2011-12-09 07:56:42 +0000
+++ database/schema/security.cfg	2011-12-11 01:49:34 +0000
@@ -446,6 +446,8 @@
 public.job                              = SELECT, UPDATE, DELETE
 public.pofilestatsjob                   = SELECT, UPDATE, DELETE
 public.potmsgset                        = SELECT
+public.product                          = SELECT
+public.productseries                    = SELECT
 public.distroseries                     = SELECT
 public.distribution                     = SELECT
 public.sourcepackagename                = SELECT

=== modified file 'lib/canonical/launchpad/browser/logintoken.py'
--- lib/canonical/launchpad/browser/logintoken.py	2011-08-23 13:43:28 +0000
+++ lib/canonical/launchpad/browser/logintoken.py	2011-12-11 01:49:34 +0000
@@ -35,13 +35,6 @@
     EmailAddressStatus,
     IEmailAddressSet,
     )
-from canonical.launchpad.interfaces.gpghandler import (
-    GPGKeyExpired,
-    GPGKeyNotFoundError,
-    GPGKeyRevoked,
-    GPGVerificationError,
-    IGPGHandler,
-    )
 from canonical.launchpad.interfaces.logintoken import (
     IGPGKeyValidationForm,
     ILoginTokenSet,
@@ -70,6 +63,13 @@
     IPersonSet,
     ITeam,
     )
+from lp.services.gpg.interfaces import (
+    GPGKeyExpired,
+    GPGKeyNotFoundError,
+    GPGKeyRevoked,
+    GPGVerificationError,
+    IGPGHandler,
+    )
 
 
 class LoginTokenSetNavigation(GetitemNavigation):

=== modified file 'lib/canonical/launchpad/database/logintoken.py'
--- lib/canonical/launchpad/database/logintoken.py	2011-08-12 14:39:51 +0000
+++ lib/canonical/launchpad/database/logintoken.py	2011-12-11 01:49:34 +0000
@@ -36,7 +36,6 @@
 from canonical.launchpad.helpers import get_email_template
 from canonical.launchpad.interfaces.authtoken import LoginTokenType
 from canonical.launchpad.interfaces.emailaddress import IEmailAddressSet
-from canonical.launchpad.interfaces.gpghandler import IGPGHandler
 from canonical.launchpad.interfaces.logintoken import (
     ILoginToken,
     ILoginTokenSet,
@@ -52,6 +51,7 @@
 from lp.app.validators.email import valid_email
 from lp.registry.interfaces.gpg import IGPGKeySet
 from lp.registry.interfaces.person import IPersonSet
+from lp.services.gpg.interfaces import IGPGHandler
 from lp.services.mail.sendmail import (
     format_address,
     simple_sendmail,

=== removed file 'lib/canonical/launchpad/database/stormsugar.py'
--- lib/canonical/launchpad/database/stormsugar.py	2010-11-05 09:23:08 +0000
+++ lib/canonical/launchpad/database/stormsugar.py	1970-01-01 00:00:00 +0000
@@ -1,135 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-# pylint: disable-msg=C0203
-
-"""Storm is powerful stuff.  This helps it go down more easily.
-
-This module is experimental.  Please give feedback and feel free to adjust it.
-"""
-
-__metaclass__ = type
-
-
-__all__ = [
-    'ForeignKey',
-    'ObjectNotFound',
-    'Sugar',
-    'UnknownProperty',
-    ]
-
-
-from storm.locals import (
-    Int,
-    Reference,
-    Store,
-    Storm,
-    )
-from zope.component import getUtility
-
-from canonical.launchpad.webapp.interfaces import (
-    DEFAULT_FLAVOR,
-    IStoreSelector,
-    MAIN_STORE,
-    MASTER_FLAVOR,
-    )
-from lp.app.errors import NotFoundError
-
-
-class ObjectNotFound(NotFoundError):
-    """Exception raised when a storm object can't be got."""
-
-    def __init__(self, orm_class, id):
-        msg = 'Not found: %s with id %s.' % (orm_class.__name__, id)
-        NotFoundError.__init__(self, msg)
-
-
-class UnknownProperty(Exception):
-    """The property name specified in a kwarg is not pre-defined."""
-
-    def __init__(self, orm_class, name):
-        msg = 'Class %s has no property "%s".' % (orm_class.__name__, name)
-        Exception.__init__(self, msg)
-
-
-class ForeignKey(Reference):
-
-    def __init__(self, remote_key, name=None):
-        self.name = name
-        Reference.__init__(self, None, remote_key)
-
-
-# Use Storm.__metaclass__ because storm.properties.PropertyPublisherMeta isn't
-# in an __all__.
-
-class Sugary(Storm.__metaclass__):
-    """Metaclass that adds support for ForeignKey."""
-
-    def __init__(cls, name, bases, dict):
-        for key in dir(cls):
-            val = getattr(cls, key, None)
-            if not isinstance(val, ForeignKey):
-                continue
-            col_name = val.name
-            if col_name is None:
-                col_name = key
-            val._local_key = Int(col_name)
-            setattr(cls, '_%s_id' % key, val._local_key)
-        # Do this last, because it wants References to have their local_key
-        # properly set up.
-        super(Sugary, cls).__init__(name, bases, dict)
-
-
-class Sugar(Storm):
-    """Base class providing convenient Storm API."""
-
-    __metaclass__ = Sugary
-
-    __store_type__ = MAIN_STORE
-
-    id = Int(primary=True)
-
-    def __init__(self, **kwargs):
-        super(Sugar).__init__(Sugar, self)
-        for key, value in kwargs.items():
-            if getattr(self.__class__, key, None) is None:
-                raise UnknownProperty(self.__class__, key)
-            setattr(self, key, value)
-        self.master_store.add(self)
-
-    @property
-    def master_store(self):
-        selector = getUtility(IStoreSelector)
-        return selector.get(self.__store_type__, MASTER_FLAVOR)
-
-    @classmethod
-    def getDefaultStore(klass):
-        """Return the default store for this class."""
-        selector = getUtility(IStoreSelector)
-        return selector.get(klass.__store_type__, DEFAULT_FLAVOR)
-
-    @classmethod
-    def getById(klass, id):
-        """Return the object of this type with given id."""
-        store = klass.getDefaultStore()
-        obj = store.get(klass, id)
-        if obj is None:
-            raise ObjectNotFound(klass, id)
-        return obj
-
-    @classmethod
-    def find(klass, **kwargs):
-        """Select the instances whose properties match kwargs."""
-        assert len(kwargs) > 0
-        store = klass.getDefaultStore()
-        return store.find(klass, **kwargs)
-
-    def flush(self):
-        """Bi-directionally update this object with the database."""
-        store = Store.of(self)
-        store.flush()
-        store.autoreload(self)
-
-    def remove(self):
-        """Remove this object from the database."""
-        Store.of(self).remove(self)

=== removed file 'lib/canonical/launchpad/database/tests/test_stormsugar.py'
--- lib/canonical/launchpad/database/tests/test_stormsugar.py	2011-08-12 11:37:08 +0000
+++ lib/canonical/launchpad/database/tests/test_stormsugar.py	1970-01-01 00:00:00 +0000
@@ -1,125 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Tests for stormsugar."""
-
-__metaclass__ = type
-
-from psycopg2 import IntegrityError
-from storm.locals import (
-    Int,
-    Store,
-    )
-
-from canonical.launchpad.database.stormsugar import (
-    ForeignKey,
-    ObjectNotFound,
-    Sugar,
-    UnknownProperty,
-    )
-from canonical.testing.layers import DatabaseFunctionalLayer
-from lp.testing import TestCase
-
-
-class SugarDerived(Sugar):
-    """Class for testing.  (Because we can't test Sugar directly.)"""
-
-    __storm_table__ = 'Job'
-
-    status = Int()
-
-    progress = Int()
-
-
-class TestSugar(TestCase):
-
-    layer = DatabaseFunctionalLayer
-
-    def test_init_adds(self):
-        """Default constructor adds to store."""
-        created = SugarDerived(id=500, status=0)
-        self.assertIs(created.master_store, Store.of(created))
-
-    def test_init_handles_kwargs(self):
-        """Default constructor handles kwargs."""
-        created = SugarDerived(id=500, status=0)
-        self.assertEqual(500, created.id)
-        self.assertEqual(0, created.status)
-
-    def test_init_requires_known_kwargs(self):
-        """Default constructor requires pre-defined kwargs.
-
-        (This reduces the potential for typos to cause confusion or bugs.)
-        """
-        e = self.assertRaises(
-            UnknownProperty, SugarDerived, id=500, status=0, foo='bar')
-        self.assertEqual('Class SugarDerived has no property "foo".', str(e))
-
-    def test_getById(self):
-        """Get either returns the desired object or raises."""
-        e = self.assertRaises(ObjectNotFound, SugarDerived.getById, 500)
-        self.assertEqual("'Not found: SugarDerived with id 500.'", str(e))
-        created = SugarDerived(id=500, status=0)
-        gotten = SugarDerived.getById(500)
-        self.assertEqual(created, gotten)
-
-    def test_remove(self):
-        """destroySelf destroys the object in question."""
-        created = SugarDerived(id=500, status=0)
-        created.remove()
-        self.assertRaises(ObjectNotFound, SugarDerived.getById, 500)
-
-    def test_flush_exercises_constraints(self):
-        """Sugar.flush causes constraints to be tested."""
-        created = SugarDerived(id=500)
-        # The IntegrityError is raised because status is not set, and it
-        # has the NOT NULL constraint.
-        self.assertRaises(IntegrityError, created.flush)
-
-    def test_find(self):
-        """Sugar.find works."""
-        obj1 = SugarDerived(id=500, status=5)
-        obj2 = SugarDerived(id=501, status=6)
-        self.assertEqual([obj1], list(SugarDerived.find(status=5)))
-        self.assertEqual([], list(SugarDerived.find(status=4)))
-        self.assertRaises(AssertionError, SugarDerived.find)
-
-    def test_find_with_multiple_clauses(self):
-        """Multiple kwargs are ANDed."""
-        obj1 = SugarDerived(status=5, progress=1)
-        obj2 = SugarDerived(status=5, progress=2)
-        obj3 = SugarDerived(status=6, progress=2)
-        self.assertEqual(
-            [obj1], list(SugarDerived.find(status=5, progress=1)))
-        self.assertEqual(
-            [obj2], list(SugarDerived.find(status=5, progress=2)))
-        self.assertEqual(
-            [obj3], list(SugarDerived.find(status=6, progress=2)))
-
-    def test_ForeignKey(self):
-        """ForeignKey works, and defaults to property name."""
-
-        class ReferencingObject(Sugar):
-
-            __storm_table__ = 'BranchJob'
-
-            job = ForeignKey(SugarDerived.id)
-
-        obj1 = SugarDerived(status=0)
-        obj2 = ReferencingObject(job=obj1)
-        self.assertEqual(obj1, obj2.job)
-        self.assertEqual(obj1.id, obj2._job_id)
-
-    def test_ForeignKey_with_name(self):
-        """ForeignKey name correctly overrides property name."""
-
-        class ReferencingObjectWithName(Sugar):
-
-            __storm_table__ = 'BranchJob'
-
-            foo = ForeignKey(SugarDerived.id, 'job')
-
-        obj1 = SugarDerived(status=0)
-        obj2 = ReferencingObjectWithName(foo=obj1)
-        self.assertEqual(obj1, obj2.foo)
-        self.assertEqual(obj1.id, obj2._foo_id)

=== modified file 'lib/canonical/launchpad/ftests/__init__.py'
--- lib/canonical/launchpad/ftests/__init__.py	2011-10-05 21:46:17 +0000
+++ lib/canonical/launchpad/ftests/__init__.py	2011-12-11 01:49:34 +0000
@@ -5,10 +5,6 @@
 
 __all__ = [
     'ANONYMOUS',
-    'decrypt_content',
-    'import_public_key',
-    'import_public_test_keys',
-    'import_secret_test_key',
     'LaunchpadFormHarness',
     'login',
     'login_person',
@@ -18,15 +14,11 @@
 from canonical.launchpad.ftests._launchpadformharness import (
     LaunchpadFormHarness,
     )
-from canonical.launchpad.ftests.keys_for_tests import (
-    decrypt_content,
-    import_public_key,
-    import_public_test_keys,
-    import_secret_test_key,
-    )
 from lp.testing import (
     ANONYMOUS,
     login,
     login_person,
     logout,
     )
+
+

=== modified file 'lib/canonical/launchpad/icing/css/base.css'
--- lib/canonical/launchpad/icing/css/base.css	2011-11-18 05:32:00 +0000
+++ lib/canonical/launchpad/icing/css/base.css	2011-12-11 01:49:34 +0000
@@ -1,5 +1,5 @@
 body {
-    font-family: 'UbuntuBeta Regular', Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
+    font-family: Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
     font-size: 12px;
     line-height: 18px; /* The same as the sprite height. */
     color: #333;

=== modified file 'lib/canonical/launchpad/icing/css/forms.css'
--- lib/canonical/launchpad/icing/css/forms.css	2011-11-18 05:15:52 +0000
+++ lib/canonical/launchpad/icing/css/forms.css	2011-12-11 01:49:34 +0000
@@ -232,7 +232,7 @@
     }
 .lazr-multiline-edit .clearfix h3 {
     /* Undo the damage done by lazr. */
-    font-family: 'UbuntuBeta Regular', Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
+    font-family: Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
     line-height: 12px;
     }
 .widget-hd.js-action {

=== modified file 'lib/canonical/launchpad/icing/css/modifiers.css'
--- lib/canonical/launchpad/icing/css/modifiers.css	2011-11-18 05:15:52 +0000
+++ lib/canonical/launchpad/icing/css/modifiers.css	2011-12-11 01:49:34 +0000
@@ -119,7 +119,7 @@
     }
 pre.changelog, table.diff,
 .bug-comment, .bug-activity, .codereviewcomment {
-    font-family: 'UbuntuBeta Mono', 'Ubuntu Mono', monospace;
+    font-family: 'Ubuntu Mono', monospace;
     }
 .cloud-size-smallest {
     font-size: 10px;

=== modified file 'lib/canonical/launchpad/icing/css/typography.css'
--- lib/canonical/launchpad/icing/css/typography.css	2011-11-18 05:15:52 +0000
+++ lib/canonical/launchpad/icing/css/typography.css	2011-12-11 01:49:34 +0000
@@ -38,7 +38,7 @@
     width: 60em;
     }
 pre, code, samp, tt, .console {
-    font-family: 'UbuntuBeta Mono', 'Ubuntu Mono', monospace;
+    font-family: 'Ubuntu Mono', monospace;
     margin-bottom: 0.8em;
     }
 pre.wrap {

=== modified file 'lib/canonical/launchpad/icing/shipit.css'
--- lib/canonical/launchpad/icing/shipit.css	2010-08-21 13:28:33 +0000
+++ lib/canonical/launchpad/icing/shipit.css	2011-12-11 01:49:34 +0000
@@ -2,7 +2,7 @@
 
 html {
   position: relative;
-  font-family: UbuntuBeta, Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
+  font-family: Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
   font-size: 12pt;
   color: black;
   background-color: #FFFFFF;
@@ -52,7 +52,7 @@
 input, select, textarea {
   background-color: #fff;
   color: #656565;
-  font-family: UbuntuBeta, Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
+  font-family: Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
   margin: 0;
 }
 input[type="text"], input[type="submit"], input[type="reset"], textarea {

=== modified file 'lib/canonical/launchpad/icing/style.css'
--- lib/canonical/launchpad/icing/style.css	2011-11-30 06:12:55 +0000
+++ lib/canonical/launchpad/icing/style.css	2011-12-11 01:49:34 +0000
@@ -27,7 +27,7 @@
  */
 div#edit-description,
 div#edit-commit_message {
-    font-family: 'UbuntuBeta Mono', 'Ubuntu Mono', monospace;
+    font-family: 'Ubuntu Mono', monospace;
     margin: 1em 0;
     }
 
@@ -461,7 +461,7 @@
     padding: 5px;
     border: solid gray;
     border-width: 1px;
-    font-family: 'UbuntuBeta Mono', 'Ubuntu Mono', monospace;
+    font-family: 'Ubuntu Mono', monospace;
     -moz-border-radius: 5px;
     -o-border-radius: 5px;
     -webkit-border-radius: 5px;

=== added symlink 'lib/canonical/launchpad/icing/ubuntu-webfonts.css'
=== target is u'../../../lp/contrib/css/ubuntu-webfonts.css'
=== modified file 'lib/canonical/launchpad/icing/yui_2.7.0b/build/calendar/assets/skins/sam/calendar-skin.css'
--- lib/canonical/launchpad/icing/yui_2.7.0b/build/calendar/assets/skins/sam/calendar-skin.css	2010-08-21 13:28:33 +0000
+++ lib/canonical/launchpad/icing/yui_2.7.0b/build/calendar/assets/skins/sam/calendar-skin.css	2011-12-11 01:49:34 +0000
@@ -264,7 +264,7 @@
 
 /* NAVIGATOR BOUNDING BOX */
 .yui-skin-sam .yui-calcontainer .yui-cal-nav {
-	font-family: UbuntuBeta, Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
+	font-family: Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
 	font-size:93%;
 	border:1px solid #808080;
 	left:50%;

=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py	2011-10-12 14:44:59 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py	2011-12-11 01:49:34 +0000
@@ -135,7 +135,7 @@
     )
 from lp.registry.interfaces.person import (
     IPerson,
-    IPersonPublic,
+    IPersonViewRestricted,
     ITeam,
     )
 from lp.registry.interfaces.pillar import (
@@ -310,10 +310,10 @@
 
 IPreviewDiff['branch_merge_proposal'].schema = IBranchMergeProposal
 
-patch_reference_property(IPersonPublic, 'archive', IArchive)
-patch_collection_property(IPersonPublic, 'ppas', IArchive)
-patch_entry_return_type(IPersonPublic, 'getPPAByName', IArchive)
-patch_entry_return_type(IPersonPublic, 'createPPA', IArchive)
+patch_reference_property(IPersonViewRestricted, 'archive', IArchive)
+patch_collection_property(IPersonViewRestricted, 'ppas', IArchive)
+patch_entry_return_type(IPersonViewRestricted, 'getPPAByName', IArchive)
+patch_entry_return_type(IPersonViewRestricted, 'createPPA', IArchive)
 
 IHasBuildRecords['getBuildRecords'].queryTaggedValue(
     LAZR_WEBSERVICE_EXPORTED)[

=== modified file 'lib/canonical/launchpad/offline-maintenance-haproxy.html'
--- lib/canonical/launchpad/offline-maintenance-haproxy.html	2010-12-16 22:42:28 +0000
+++ lib/canonical/launchpad/offline-maintenance-haproxy.html	2011-12-11 01:49:34 +0000
@@ -7,7 +7,7 @@
     <title>Launchpad is offline for maintenance</title>
     <style type="text/css" media="screen, print">
       html, body {
-        font-family: UbuntuBeta, Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
+        font-family: Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
         }
       .offline {
         text-align: center;

=== modified file 'lib/canonical/launchpad/offline-maintenance.html'
--- lib/canonical/launchpad/offline-maintenance.html	2010-12-16 17:04:22 +0000
+++ lib/canonical/launchpad/offline-maintenance.html	2011-12-11 01:49:34 +0000
@@ -4,7 +4,7 @@
     <title>Launchpad is offline for maintenance</title>
     <style type="text/css" media="screen, print">
       html, body {
-        font-family: UbuntuBeta, Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
+        font-family: Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
         }
       .offline {
         text-align: center;

=== modified file 'lib/canonical/launchpad/offline-staging-code-update.html'
--- lib/canonical/launchpad/offline-staging-code-update.html	2010-12-16 17:04:22 +0000
+++ lib/canonical/launchpad/offline-staging-code-update.html	2011-12-11 01:49:34 +0000
@@ -4,7 +4,7 @@
     <title>Please try again</title>
     <style type="text/css" media="screen, print">
       html, body {
-        font-family: UbuntuBeta, Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
+        font-family: Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
         }
       .offline {
         text-align: center;

=== modified file 'lib/canonical/launchpad/offline-staging-db-update.html'
--- lib/canonical/launchpad/offline-staging-db-update.html	2010-12-16 17:04:22 +0000
+++ lib/canonical/launchpad/offline-staging-db-update.html	2011-12-11 01:49:34 +0000
@@ -4,7 +4,7 @@
     <title>Please try again</title>
     <style type="text/css" media="screen, print">
       html, body {
-        font-family: UbuntuBeta, Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
+        font-family: Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
         }
       .offline {
         text-align: center;

=== modified file 'lib/canonical/launchpad/offline-unplanned-haproxy.html'
--- lib/canonical/launchpad/offline-unplanned-haproxy.html	2010-12-16 22:42:28 +0000
+++ lib/canonical/launchpad/offline-unplanned-haproxy.html	2011-12-11 01:49:34 +0000
@@ -7,7 +7,7 @@
     <title>Please try again</title>
     <style type="text/css" media="screen, print">
       html, body {
-        font-family: UbuntuBeta, Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
+        font-family: Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
         }
       .offline {
         text-align: center;

=== modified file 'lib/canonical/launchpad/offline-unplanned.html'
--- lib/canonical/launchpad/offline-unplanned.html	2010-12-16 17:04:22 +0000
+++ lib/canonical/launchpad/offline-unplanned.html	2011-12-11 01:49:34 +0000
@@ -4,7 +4,7 @@
     <title>Please try again</title>
     <style type="text/css" media="screen, print">
       html, body {
-        font-family: UbuntuBeta, Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
+        font-family: Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
         }
       .offline {
         text-align: center;

=== modified file 'lib/canonical/launchpad/pagetests/basics/demo-and-lpnet.txt'
--- lib/canonical/launchpad/pagetests/basics/demo-and-lpnet.txt	2011-09-29 10:23:29 +0000
+++ lib/canonical/launchpad/pagetests/basics/demo-and-lpnet.txt	2011-12-11 01:49:34 +0000
@@ -27,15 +27,9 @@
 information and styles.
 
     # Set config to pretend we're on a demo site:
-    >>> from textwrap import dedent
-    >>> site_message = ('This is a demo site mmk. '
-    ...                 '<a href="http://example.com";>File a bug</a>.')
-    >>> test_data = dedent("""
-    ...     [launchpad]
-    ...     is_demo: True
-    ...     site_message: %s
-    ...     """ % site_message)
-    >>> config.push('test_data', test_data)
+    >>> from lp.testing.fixture import DemoMode
+    >>> demo_mode_fixture = DemoMode()
+    >>> demo_mode_fixture.setUp()
     >>> print config.launchpad.is_demo
     True
 
@@ -61,8 +55,7 @@
 
 When you are not on a demo site, the text no longer appears.
 
-    >>> # Restore the previous config:
-    >>> config_data = config.pop('test_data')
+    >>> demo_mode_fixture.cleanUp()
     >>> print config.launchpad.is_demo
     False
 

=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py	2011-12-08 05:13:31 +0000
+++ lib/canonical/launchpad/security.py	2011-12-11 01:49:34 +0000
@@ -63,6 +63,7 @@
     IBranch,
     user_has_special_branch_access,
     )
+from lp.code.interfaces.branchcollection import IBranchCollection
 from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal
 from lp.code.interfaces.branchmergequeue import IBranchMergeQueue
 from lp.code.interfaces.codeimport import ICodeImport
@@ -800,6 +801,35 @@
             if (invitee.is_team and
                 invitee in user.person.getAdministratedTeams()):
                 return True
+        return False
+
+
+class PublicOrPrivateTeamsExistence(AuthorizationBase):
+    """Restrict knowing about private teams' existence.
+
+    Knowing the existence of a private team allow traversing to its URL and
+    displaying basic information like name, displayname.
+    """
+    permission = 'launchpad.LimitedView'
+    usedfor = IPersonLimitedView
+
+    def checkUnauthenticated(self):
+        """Unauthenticated users can only view public teams."""
+        if self.obj.visibility == PersonVisibility.PUBLIC:
+            return True
+        return False
+
+    def checkAuthenticated(self, user):
+        """By default, we simply perform a View permission check.
+
+        We also grant limited viewability to users who can see PPAs and
+        branches owned by the team. In other scenarios, the context in which
+        the permission is required is responsible for pre-caching the
+        launchpad.LimitedView permission on each team which requires it.
+        """
+        if self.forwardCheckAuthenticated(
+            user, self.obj, 'launchpad.View'):
+            return True
 
         if (self.obj.is_team
             and self.obj.visibility == PersonVisibility.PRIVATE):
@@ -813,33 +843,14 @@
                 ppa.id for ppa in self.obj.ppas if ppa.private)
             if len(subscriber_archive_ids.intersection(team_ppa_ids)) > 0:
                 return True
-        return False
-
-
-class PublicOrPrivateTeamsExistence(AuthorizationBase):
-    """Restrict knowing about private teams' existence.
-
-    Knowing the existence of a private team allow traversing to its URL and
-    displaying basic information like name, displayname.
-    """
-    permission = 'launchpad.LimitedView'
-    usedfor = IPersonLimitedView
-
-    def checkUnauthenticated(self):
-        """Unauthenticated users can only view public teams."""
-        if self.obj.visibility == PersonVisibility.PUBLIC:
-            return True
-        return False
-
-    def checkAuthenticated(self, user):
-        """By default, we simply perform a View permission check.
-
-        The context in which the permission is required is
-        responsible for pre-caching the launchpad.LimitedView permission on
-        each team which requires it.
-        """
-        return self.forwardCheckAuthenticated(
-            user, self.obj, 'launchpad.View')
+
+            # Grant visibility to people with subscriptions to branches owned
+            # by the private team.
+            owned_branches = getUtility(IBranchCollection).ownedBy(self.obj)
+            if owned_branches.visibleByUser(user.person).count() > 0:
+                return True
+
+        return False
 
 
 class EditPollByTeamOwnerOrTeamAdminsOrAdmins(
@@ -2410,13 +2421,13 @@
         yield self.obj.archive
 
 
-class ViewSourcePackagePublishingHistory(ViewArchive):
+class ViewSourcePackagePublishingHistory(DelegatedAuthorization):
     """Restrict viewing of source publications."""
     permission = "launchpad.View"
     usedfor = ISourcePackagePublishingHistory
 
-    def __init__(self, obj):
-        super(ViewSourcePackagePublishingHistory, self).__init__(obj.archive)
+    def iter_objects(self):
+        yield self.obj.archive
 
 
 class EditPublishing(DelegatedAuthorization):

=== modified file 'lib/canonical/launchpad/testing/fakepackager.py'
--- lib/canonical/launchpad/testing/fakepackager.py	2011-06-09 10:50:25 +0000
+++ lib/canonical/launchpad/testing/fakepackager.py	2011-12-11 01:49:34 +0000
@@ -20,13 +20,13 @@
 
 from zope.component import getUtility
 
-from canonical.launchpad.ftests.keys_for_tests import import_secret_test_key
-from canonical.launchpad.interfaces.gpghandler import IGPGHandler
 from lp.archiveuploader.nascentupload import NascentUpload
 from lp.archiveuploader.uploadpolicy import findPolicyByName
 from lp.registry.interfaces.distribution import IDistributionSet
+from lp.services.gpg.interfaces import IGPGHandler
 from lp.services.log.logger import BufferLogger
 from lp.soyuz.enums import PackageUploadStatus
+from lp.testing.gpgkeys import import_secret_test_key
 
 
 changelog_entry_template = (

=== modified file 'lib/canonical/launchpad/utilities/__init__.py'
--- lib/canonical/launchpad/utilities/__init__.py	2011-05-27 21:12:25 +0000
+++ lib/canonical/launchpad/utilities/__init__.py	2011-12-11 01:49:34 +0000
@@ -3,7 +3,6 @@
 
 # pylint: disable-msg=W0401
 
-from canonical.launchpad.utilities.gpghandler import *
 from canonical.launchpad.utilities.looptuner import *
 from canonical.launchpad.utilities.orderingcheck import *
 from lp.app.utilities.celebrities import *

=== removed directory 'lib/canonical/launchpad/utilities/ftests'
=== removed file 'lib/canonical/launchpad/utilities/ftests/__init__.py'
=== modified file 'lib/canonical/launchpad/webapp/authorization.py'
--- lib/canonical/launchpad/webapp/authorization.py	2011-12-07 18:03:40 +0000
+++ lib/canonical/launchpad/webapp/authorization.py	2011-12-11 01:49:34 +0000
@@ -192,7 +192,7 @@
                     objecttoauthorize, {})
                 if permission in object_cache:
                     return object_cache[permission]
-            principal = participation.principal
+            principal = removeAllProxies(participation.principal)
 
         if (principal is not None and
             not isinstance(principal, UnauthenticatedPrincipal)):
@@ -343,7 +343,8 @@
             # LaunchpadBrowserRequest provides a ``clearSecurityPolicyCache``
             # method, but it is not in an interface, and not implemented by
             # all classes that implement IApplicationRequest.
-            del p.annotations[LAUNCHPAD_SECURITY_POLICY_CACHE_KEY]
+            if LAUNCHPAD_SECURITY_POLICY_CACHE_KEY in p.annotations:
+                del p.annotations[LAUNCHPAD_SECURITY_POLICY_CACHE_KEY]
 
 
 class LaunchpadPermissiveSecurityPolicy(PermissiveSecurityPolicy):

=== modified file 'lib/canonical/launchpad/webapp/error.py'
--- lib/canonical/launchpad/webapp/error.py	2011-10-03 14:39:59 +0000
+++ lib/canonical/launchpad/webapp/error.py	2011-12-11 01:49:34 +0000
@@ -107,7 +107,7 @@
         """Returns the given HTML inside a div of an appropriate class."""
 
         return ('<div class="highlight" style="'
-                "font-family: 'UbuntuBeta Mono', 'Ubuntu Mono', monospace;"
+                "font-family: 'Ubuntu Mono', monospace;"
                 ' font-size: smaller;">'
                 '%s'
                 '</div>') % html

=== modified file 'lib/canonical/launchpad/webapp/interaction.py'
--- lib/canonical/launchpad/webapp/interaction.py	2011-10-12 00:31:08 +0000
+++ lib/canonical/launchpad/webapp/interaction.py	2011-12-11 01:49:34 +0000
@@ -163,7 +163,8 @@
         return setupInteraction(ANONYMOUS, participation)
     else:
         # Bypass zope's security because IEmailAddress.email is not public.
-        naked_email = removeSecurityProxy(person.preferredemail)
+        naked_person = removeSecurityProxy(person)
+        naked_email = removeSecurityProxy(naked_person.preferredemail)
         return setupInteractionByEmail(naked_email.email, participation)
 
 

=== modified file 'lib/canonical/launchpad/webapp/login.py'
--- lib/canonical/launchpad/webapp/login.py	2011-12-08 04:36:31 +0000
+++ lib/canonical/launchpad/webapp/login.py	2011-12-11 01:49:34 +0000
@@ -41,7 +41,6 @@
 from canonical.config import config
 from canonical.launchpad import _
 from canonical.launchpad.interfaces.account import AccountSuspendedError
-from canonical.launchpad.interfaces.openidconsumer import IOpenIDConsumerStore
 from canonical.launchpad.readonly import is_read_only
 from canonical.launchpad.webapp.dbpolicy import MasterDatabasePolicy
 from canonical.launchpad.webapp.error import SystemErrorView
@@ -60,6 +59,7 @@
     IPersonSet,
     PersonCreationRationale,
     )
+from lp.services.openid.interfaces.openidconsumer import IOpenIDConsumerStore
 from lp.services.propertycache import cachedproperty
 from lp.services.timeline.requesttimeline import get_request_timeline
 

=== modified file 'lib/canonical/launchpad/webapp/templates/oops-veryplain.pt'
--- lib/canonical/launchpad/webapp/templates/oops-veryplain.pt	2011-05-27 21:03:22 +0000
+++ lib/canonical/launchpad/webapp/templates/oops-veryplain.pt	2011-12-11 01:49:34 +0000
@@ -11,7 +11,7 @@
     <title>Oops!</title>
     <style type="text/css">
       html {
-        font-family: UbuntuBeta, Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
+        font-family: Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
         font-size: 8pt;
       }
       h1 {background: none; color: #83ad23; font-size: 3em; font-weight: normal;}

=== modified file 'lib/canonical/launchpad/webapp/tests/test_publisher.py'
--- lib/canonical/launchpad/webapp/tests/test_publisher.py	2011-12-08 20:29:15 +0000
+++ lib/canonical/launchpad/webapp/tests/test_publisher.py	2011-12-11 01:49:34 +0000
@@ -170,7 +170,8 @@
                     u'priority': 0,
                     u'value': u'on',
                     },
-                )))
+                ),
+            override_scope_lookup=lambda scope_name: True))
         request = LaunchpadTestRequest()
         view = LaunchpadView(object(), request)
         view.related_features = ['test_feature']
@@ -207,7 +208,8 @@
         #     but have different values,
         # then the property related_feature_info contains this feature flag.
         self.useFixture(FeatureFixture(
-            {}, self.makeFeatureFlagDictionaries(u'', u'on')))
+            {}, self.makeFeatureFlagDictionaries(u'', u'on'),
+            override_scope_lookup=lambda scope_name: True))
         request = LaunchpadTestRequest()
         view = LaunchpadView(object(), request)
         view.related_features = ['test_feature']
@@ -228,7 +230,8 @@
         #     and have the same values,
         # then is_beta is false.
         self.useFixture(FeatureFixture(
-            {}, self.makeFeatureFlagDictionaries(u'on', u'on')))
+            {}, self.makeFeatureFlagDictionaries(u'on', u'on'),
+            override_scope_lookup=lambda scope_name: True))
         request = LaunchpadTestRequest()
         view = LaunchpadView(object(), request)
         view.related_features = ['test_feature']
@@ -245,7 +248,8 @@
             related_features = ['test_feature']
 
         self.useFixture(FeatureFixture(
-            {}, self.makeFeatureFlagDictionaries(u'', u'on')))
+            {}, self.makeFeatureFlagDictionaries(u'', u'on'),
+            override_scope_lookup=lambda scope_name: True))
         request = LaunchpadTestRequest()
         view = TestView(object(), request)
         with person_logged_in(self.factory.makePerson()):
@@ -269,7 +273,8 @@
             related_features = ['test_feature_2']
 
         self.useFixture(FeatureFixture(
-            {}, self.makeFeatureFlagDictionaries(u'', u'on')))
+            {}, self.makeFeatureFlagDictionaries(u'', u'on'),
+            override_scope_lookup=lambda scope_name: True))
         request = LaunchpadTestRequest()
         view = TestView(object(), request)
         TestView2(object(), request)

=== modified file 'lib/canonical/launchpad/zcml/configure.zcml'
--- lib/canonical/launchpad/zcml/configure.zcml	2011-12-08 05:13:31 +0000
+++ lib/canonical/launchpad/zcml/configure.zcml	2011-12-11 01:49:34 +0000
@@ -19,15 +19,7 @@
     <include file="librarian.zcml" />
     <include file="lifecycle.zcml" />
     <include file="logintoken.zcml" />
-    <include file="openidconsumer.zcml" />
     <include file="temporaryblobstorage.zcml" />
     <include file="webservice.zcml" />
 
-    <!-- System homepages -->
-
-    <!-- Event configuration -->
-
-    <!-- Special Utilities -->
-    <include file="gpghandler.zcml" />
-
 </configure>

=== removed file 'lib/canonical/launchpad/zcml/openidconsumer.zcml'
--- lib/canonical/launchpad/zcml/openidconsumer.zcml	2009-07-13 18:15:02 +0000
+++ lib/canonical/launchpad/zcml/openidconsumer.zcml	1970-01-01 00:00:00 +0000
@@ -1,12 +0,0 @@
-<!-- Copyright 2009 Canonical Ltd.  This software is licensed under the
-     GNU Affero General Public License version 3 (see the file LICENSE).
--->
-
-<configure xmlns="http://namespaces.zope.org/zope";>
-
-  <utility
-      provides="..interfaces.openidconsumer.IOpenIDConsumerStore"
-      factory="..database.openidconsumer.OpenIDConsumerStore">
-  </utility>
-
-</configure>

=== modified file 'lib/lp/app/browser/launchpad.py'
--- lib/lp/app/browser/launchpad.py	2011-12-09 05:08:09 +0000
+++ lib/lp/app/browser/launchpad.py	2011-12-11 01:49:34 +0000
@@ -724,8 +724,8 @@
                 # Check to see if this is a team, and if so, whether the
                 # logged in user is allowed to view the team, by virtue of
                 # team membership or Launchpad administration.
-                if (person.is_team
-                    and not check_permission('launchpad.View', person)):
+                if (person.is_team and
+                    not check_permission('launchpad.LimitedView', person)):
                     raise NotFound(self.context, name)
                 # Only admins are permitted to see suspended users.
                 if person.account_status == AccountStatus.SUSPENDED:

=== modified file 'lib/lp/app/browser/tales.py'
--- lib/lp/app/browser/tales.py	2011-12-08 22:32:41 +0000
+++ lib/lp/app/browser/tales.py	2011-12-11 01:49:34 +0000
@@ -1266,6 +1266,14 @@
                 self.hidden)
         return super(TeamFormatterAPI, self).link(view_name, rootsite)
 
+    def icon(self, view_name):
+        team = self._context
+        if not check_permission('launchpad.LimitedView', team):
+            css_class = ObjectImageDisplayAPI(team).sprite_css()
+            return '<span class="' + css_class + '"></span>'
+        else:
+            return super(TeamFormatterAPI, self).icon(view_name)
+
     def displayname(self, view_name, rootsite=None):
         """See `PersonFormatterAPI`."""
         person = self._context

=== modified file 'lib/lp/app/templates/base-layout.pt'
--- lib/lp/app/templates/base-layout.pt	2011-12-09 05:08:09 +0000
+++ lib/lp/app/templates/base-layout.pt	2011-12-11 01:49:34 +0000
@@ -71,6 +71,14 @@
       ${view/macro:pagetype}
       ${view/context/fmt:public-private-css}
       yui3-skin-sam">
+        <script type="text/javascript"
+          tal:condition="python: is_lpnet">
+          var _gaq = _gaq || [];
+          _gaq.push(['_setAccount', 'UA-12833497-1']);
+          _gaq.push(['_setDomainName', '.launchpad.net']);
+          _gaq.push(['_setAllowHash', false]);
+          _gaq.push(['_trackPageview']);
+        </script>
     <div class="yui-d0">
       <div id="locationbar" class="login-logout">
         <tal:login replace="structure context/@@login_status" />

=== modified file 'lib/lp/app/templates/launchpad-databaseunavailable.pt'
--- lib/lp/app/templates/launchpad-databaseunavailable.pt	2011-10-03 14:41:27 +0000
+++ lib/lp/app/templates/launchpad-databaseunavailable.pt	2011-12-11 01:49:34 +0000
@@ -16,7 +16,7 @@
 }
 
 body {
-  font-family: 'UbuntuBeta Regular', Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
+  font-family: Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
   font-size: 0.75em;
   line-height: 1.3em;
   color: #000;

=== modified file 'lib/lp/app/tests/test_tales.py'
--- lib/lp/app/tests/test_tales.py	2011-11-16 00:09:49 +0000
+++ lib/lp/app/tests/test_tales.py	2011-12-11 01:49:34 +0000
@@ -149,18 +149,20 @@
         self.assertEqual(expected, result)
 
 
-class TestTalesFormatterAPI(TestCaseWithFactory):
-    """ Test permissions required to access TalesFormatterAPI methods.
+class TestTeamFormatterAPI(TestCaseWithFactory):
+    """ Test permissions required to access TeamFormatterAPI methods.
 
     A user must have launchpad.LimitedView permission to use
-    TestTalesFormatterAPI with private teams.
+    TeamFormatterAPI with private teams.
     """
-    layer = DatabaseFunctionalLayer
+    layer = LaunchpadFunctionalLayer
 
     def setUp(self):
-        super(TestTalesFormatterAPI, self).setUp()
+        super(TestTeamFormatterAPI, self).setUp()
+        icon = self.factory.makeLibraryFileAlias(
+            filename='smurf.png', content_type='image/png')
         self.team = self.factory.makeTeam(
-            name='team', displayname='a team',
+            name='team', displayname='a team', icon=icon,
             visibility=PersonVisibility.PRIVATE)
 
     def _make_formatter(self, cache_permission=False):
@@ -175,10 +177,11 @@
                 request, 'launchpad.LimitedView', [self.team])
         return formatter, request, any_person
 
-    def _tales_value(self, attr, request):
+    def _tales_value(self, attr, request, path='fmt'):
         # Evaluate the given formatted attribute value on team.
-        return test_tales(
-            "team/fmt:%s" % attr, team=self.team, request=request)
+        result = test_tales(
+            "team/%s:%s" % (path, attr), team=self.team, request=request)
+        return result
 
     def _test_can_view_attribute_no_login(self, attr, hidden=None):
         # Test attribute access with no login.
@@ -228,6 +231,10 @@
     def test_can_view_url(self):
         self._test_can_view_attribute('url')
 
+    def test_can_view_icon(self):
+        self._test_can_view_attribute(
+            'icon', '<span class="sprite team"></span>')
+
 
 class TestObjectFormatterAPI(TestCaseWithFactory):
     """Tests for ObjectFormatterAPI"""

=== modified file 'lib/lp/archivepublisher/archivesigningkey.py'
--- lib/lp/archivepublisher/archivesigningkey.py	2011-05-27 19:53:20 +0000
+++ lib/lp/archivepublisher/archivesigningkey.py	2011-12-11 01:49:34 +0000
@@ -17,7 +17,6 @@
 from zope.interface import implements
 
 from canonical.config import config
-from canonical.launchpad.interfaces.gpghandler import IGPGHandler
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.archivepublisher.config import getPubConfig
 from lp.archivepublisher.interfaces.archivesigningkey import (
@@ -27,6 +26,7 @@
     GPGKeyAlgorithm,
     IGPGKeySet,
     )
+from lp.services.gpg.interfaces import IGPGHandler
 
 
 class ArchiveSigningKey:

=== modified file 'lib/lp/archivepublisher/tests/archive-signing.txt'
--- lib/lp/archivepublisher/tests/archive-signing.txt	2011-03-03 00:43:44 +0000
+++ lib/lp/archivepublisher/tests/archive-signing.txt	2011-12-11 01:49:34 +0000
@@ -195,7 +195,7 @@
 exactly the same procedure for setting the signing_key information.
 
     >>> import os
-    >>> from canonical.launchpad.ftests.keys_for_tests import gpgkeysdir
+    >>> from lp.testing.gpgkeys import gpgkeysdir
     >>> key_path = os.path.join(gpgkeysdir, 'ppa-sample@xxxxxxxxxxxxxxxxx')
     >>> archive_signing_key.setSigningKey(key_path)
 
@@ -226,7 +226,7 @@
 The generated key UID follows the "Launchpad PPA for %(person.displayname)s"
 format.
 
-    >>> from canonical.launchpad.interfaces.gpghandler import IGPGHandler
+    >>> from lp.services.gpg.interfaces import IGPGHandler
     >>> gpghandler = getUtility(IGPGHandler)
 
     >>> retrieved_key = gpghandler.retrieveKey(

=== modified file 'lib/lp/archivepublisher/tests/test_publisher.py'
--- lib/lp/archivepublisher/tests/test_publisher.py	2011-10-18 16:10:28 +0000
+++ lib/lp/archivepublisher/tests/test_publisher.py	2011-12-11 01:49:34 +0000
@@ -23,8 +23,6 @@
 
 from canonical.config import config
 from canonical.database.constants import UTC_NOW
-from canonical.launchpad.ftests.keys_for_tests import gpgkeysdir
-from canonical.launchpad.interfaces.gpghandler import IGPGHandler
 from canonical.testing.layers import ZopelessDatabaseLayer
 from lp.archivepublisher.config import getPubConfig
 from lp.archivepublisher.diskpool import DiskPool
@@ -44,6 +42,7 @@
     pocketsuffix,
     )
 from lp.registry.interfaces.series import SeriesStatus
+from lp.services.gpg.interfaces import IGPGHandler
 from lp.services.log.logger import (
     BufferLogger,
     DevNullLogger,
@@ -58,6 +57,7 @@
 from lp.soyuz.interfaces.archive import IArchiveSet
 from lp.soyuz.tests.test_publishing import TestNativePublishingBase
 from lp.testing import TestCaseWithFactory
+from lp.testing.gpgkeys import gpgkeysdir
 from lp.testing.keyserver import KeyServerTac
 
 

=== modified file 'lib/lp/archiveuploader/dscfile.py'
--- lib/lp/archiveuploader/dscfile.py	2011-05-20 13:36:11 +0000
+++ lib/lp/archiveuploader/dscfile.py	2011-12-11 01:49:34 +0000
@@ -29,10 +29,6 @@
 from debian.deb822 import Deb822Dict
 from zope.component import getUtility
 
-from canonical.launchpad.interfaces.gpghandler import (
-    GPGVerificationError,
-    IGPGHandler,
-    )
 from canonical.librarian.utils import copy_and_close
 from lp.app.errors import NotFoundError
 from lp.archiveuploader.nascentuploadfile import (
@@ -65,6 +61,10 @@
 from lp.registry.interfaces.sourcepackage import SourcePackageFileType
 from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
 from lp.services.encoding import guess as guess_encoding
+from lp.services.gpg.interfaces import (
+    GPGVerificationError,
+    IGPGHandler,
+    )
 from lp.soyuz.enums import (
     ArchivePurpose,
     SourcePackageFormat,

=== modified file 'lib/lp/archiveuploader/tests/nascentupload-announcements.txt'
--- lib/lp/archiveuploader/tests/nascentupload-announcements.txt	2011-10-27 06:50:49 +0000
+++ lib/lp/archiveuploader/tests/nascentupload-announcements.txt	2011-12-11 01:49:34 +0000
@@ -51,7 +51,7 @@
 
 Import the test keys to use 'insecure' policy.
 
-    >>> from canonical.launchpad.ftests import import_public_test_keys
+    >>> from lp.testing.gpgkeys import import_public_test_keys
     >>> import_public_test_keys()
 
 For the purpose of this test, hoary needs to be an open (development)

=== modified file 'lib/lp/archiveuploader/tests/nascentupload.txt'
--- lib/lp/archiveuploader/tests/nascentupload.txt	2011-09-29 04:12:59 +0000
+++ lib/lp/archiveuploader/tests/nascentupload.txt	2011-12-11 01:49:34 +0000
@@ -3,7 +3,7 @@
 
 Import the test keys so we have them ready for verification
 
-    >>> from canonical.launchpad.ftests import import_public_test_keys
+    >>> from lp.testing.gpgkeys import import_public_test_keys
     >>> import_public_test_keys()
 
 We need to be logged into the security model in order to get any further
@@ -743,7 +743,7 @@
 
 Import the test keys again since the transaction was aborted before.
 
-    >>> from canonical.launchpad.ftests import import_public_test_keys
+    >>> from lp.testing.gpgkeys import import_public_test_keys
     >>> import_public_test_keys()
 
 When using 'insecure' policy, NascentUpload instace stores the DSC

=== modified file 'lib/lp/archiveuploader/tests/nascentuploadfile.txt'
--- lib/lp/archiveuploader/tests/nascentuploadfile.txt	2011-06-09 10:50:25 +0000
+++ lib/lp/archiveuploader/tests/nascentuploadfile.txt	2011-12-11 01:49:34 +0000
@@ -13,7 +13,7 @@
 
 Import the test keys so we have them ready for verification
 
-    >>> from canonical.launchpad.ftests import import_public_test_keys
+    >>> from lp.testing.gpgkeys import import_public_test_keys
     >>> import_public_test_keys()
 
 We need to be logged into the security model in order to get any further

=== modified file 'lib/lp/archiveuploader/tests/test_buildduploads.py'
--- lib/lp/archiveuploader/tests/test_buildduploads.py	2011-08-17 14:44:07 +0000
+++ lib/lp/archiveuploader/tests/test_buildduploads.py	2011-12-11 01:49:34 +0000
@@ -10,21 +10,19 @@
 from zope.component import getUtility
 
 from canonical.database.constants import UTC_NOW
-from canonical.launchpad.ftests import import_public_test_keys
-from lp.soyuz.enums import (
-    PackagePublishingStatus,
-    PackageUploadStatus,
-    )
 from lp.archiveuploader.tests.test_uploadprocessor import (
     TestUploadProcessorBase,
     )
-from lp.archiveuploader.uploadprocessor import (
-    UploadHandler,
-    )
+from lp.archiveuploader.uploadprocessor import UploadHandler
 from lp.registry.interfaces.distribution import IDistributionSet
 from lp.registry.interfaces.pocket import PackagePublishingPocket
+from lp.soyuz.enums import (
+    PackagePublishingStatus,
+    PackageUploadStatus,
+    )
 from lp.soyuz.interfaces.publishing import IPublishingSet
 from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild
+from lp.testing.gpgkeys import import_public_test_keys
 
 
 class TestStagedBinaryUploadBase(TestUploadProcessorBase):

=== modified file 'lib/lp/archiveuploader/tests/test_changesfile.py'
--- lib/lp/archiveuploader/tests/test_changesfile.py	2011-05-22 23:47:39 +0000
+++ lib/lp/archiveuploader/tests/test_changesfile.py	2011-12-11 01:49:34 +0000
@@ -10,7 +10,6 @@
 from debian.deb822 import Changes
 from zope.component import getUtility
 
-from canonical.launchpad.ftests import import_public_test_keys
 from canonical.testing.layers import (
     LaunchpadZopelessLayer,
     ZopelessDatabaseLayer,
@@ -36,6 +35,7 @@
 from lp.registry.interfaces.person import IPersonSet
 from lp.services.log.logger import BufferLogger
 from lp.testing import TestCase
+from lp.testing.gpgkeys import import_public_test_keys
 from lp.testing.keyserver import KeyServerTac
 
 

=== modified file 'lib/lp/archiveuploader/tests/test_nascentupload_documentation.py'
--- lib/lp/archiveuploader/tests/test_nascentupload_documentation.py	2010-12-22 01:08:48 +0000
+++ lib/lp/archiveuploader/tests/test_nascentupload_documentation.py	2011-12-11 01:49:34 +0000
@@ -12,7 +12,6 @@
 
 from canonical.launchpad.database.librarian import LibraryFileAlias
 from canonical.launchpad.ftests import (
-    import_public_test_keys,
     login,
     logout,
     )
@@ -29,8 +28,9 @@
 from lp.archiveuploader.uploadpolicy import ArchiveUploadType
 from lp.registry.interfaces.distribution import IDistributionSet
 from lp.services.log.logger import DevNullLogger
+from lp.soyuz.interfaces.component import IComponentSet
 from lp.soyuz.model.component import ComponentSelection
-from lp.soyuz.interfaces.component import IComponentSet
+from lp.testing.gpgkeys import import_public_test_keys
 
 
 def getUploadForSource(upload_path):

=== modified file 'lib/lp/archiveuploader/tests/test_uploadprocessor.py'
--- lib/lp/archiveuploader/tests/test_uploadprocessor.py	2011-10-26 02:14:52 +0000
+++ lib/lp/archiveuploader/tests/test_uploadprocessor.py	2011-12-11 01:49:34 +0000
@@ -26,7 +26,6 @@
 
 from canonical.config import config
 from canonical.database.constants import UTC_NOW
-from canonical.launchpad.ftests import import_public_test_keys
 from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
 from canonical.launchpad.testing.fakepackager import FakePackager
 from canonical.testing.layers import LaunchpadZopelessLayer
@@ -104,6 +103,7 @@
     TestCaseWithFactory,
     )
 from lp.testing.fakemethod import FakeMethod
+from lp.testing.gpgkeys import import_public_test_keys
 from lp.testing.mail_helpers import pop_notifications
 
 

=== modified file 'lib/lp/bugs/browser/tests/test_bugsubscription_views.py'
--- lib/lp/bugs/browser/tests/test_bugsubscription_views.py	2011-11-15 13:22:25 +0000
+++ lib/lp/bugs/browser/tests/test_bugsubscription_views.py	2011-12-11 01:49:34 +0000
@@ -629,7 +629,7 @@
         direct_subscriber = self.factory.makeTeam(
             name='team', displayname='Team Name', owner=teamowner,
             visibility=PersonVisibility.PRIVATE)
-        with person_logged_in(direct_subscriber.teamowner):
+        with person_logged_in(teamowner):
             bug.subscribe(direct_subscriber, direct_subscriber.teamowner,
                           level=BugNotificationLevel.LIFECYCLE)
 

=== modified file 'lib/lp/bugs/javascript/buglisting.js'
--- lib/lp/bugs/javascript/buglisting.js	2011-12-06 16:36:16 +0000
+++ lib/lp/bugs/javascript/buglisting.js	2011-12-11 01:49:34 +0000
@@ -205,11 +205,17 @@
     },
 
     /**
+     * Return the batch key of the current batch.
+     */
+    get_current_batch_key: function(){
+        return this.get('model').get('history').get('batch_key');
+    },
+
+    /**
      * Retrieve the current batch for rendering purposes.
      */
     get_current_batch: function(){
-        var batch_key = this.get('model').get('history').get('batch_key');
-        return this.get('batches')[batch_key];
+        return this.get('batches')[this.get_current_batch_key()];
     },
 
     /**
@@ -230,6 +236,33 @@
     },
 
     /**
+     * If the supplied batch is adjacent to the current batch, find an alias
+     * of one of the batches and store it.
+     *
+     * A batch has two major aliases because "forwards" may be true or false.
+     * Either the ajacent batch will have an alias for the current batch, or
+     * the current batch will have an alias for the adjacent batch.
+     */
+    dealias_batches: function(batch){
+        var batch_a_keys = namespace.get_batch_key_list(batch);
+        var batch_b_keys = namespace.get_batch_key_list(
+            this.get_current_batch());
+        var aliases = namespace.find_batch_alias(batch_a_keys, batch_b_keys);
+        if (Y.Lang.isNull(aliases)){
+            return;
+        }
+        var alias_batch = this.get('batches')[aliases[0]];
+        if (Y.Lang.isValue(alias_batch)){
+            this.get('batches')[aliases[1]] = alias_batch;
+        } else {
+            alias_batch = this.get('batches')[aliases[1]];
+            if (Y.Lang.isValue(alias_batch)){
+                this.get('batches')[aliases[0]] = alias_batch;
+            }
+        }
+    },
+
+    /**
      * Render bug listings via Mustache.
      *
      * If model is supplied, it is used as the data for rendering the
@@ -275,6 +308,7 @@
 
     update_from_new_model: function(query, fetch_only, model){
         var batch_key = this.handle_new_batch(model);
+        this.dealias_batches(model);
         if (fetch_only) {
             return;
         }
@@ -390,45 +424,21 @@
         this.update(this.first_batch_config(order_by));
     },
 
-    next_batch_config: function(){
-        var current_batch = this.get_current_batch();
-        if (!this.has_next()){
-            return null;
-        }
-        return {
-            forwards: true,
-            memo: current_batch.next.memo,
-            start: current_batch.next.start,
-            order_by: current_batch.order_by
-        };
-    },
     /**
      * Update the navigator to display the next batch.
      */
     next_batch: function() {
-        var config = this.next_batch_config();
+        var config = namespace.next_batch_config(this.get_current_batch());
         if (config === null){
             return;
         }
         this.update(config);
     },
-    prev_batch_config: function(){
-        var current_batch = this.get_current_batch();
-        if (!this.has_prev()){
-            return null;
-        }
-        return {
-            forwards: false,
-            memo: current_batch.prev.memo,
-            start: current_batch.prev.start,
-            order_by: current_batch.order_by
-        };
-    },
     /**
      * Update the navigator to display the previous batch.
      */
     prev_batch: function() {
-        var config = this.prev_batch_config();
+        var config = namespace.prev_batch_config(this.get_current_batch());
         if (config === null){
             return;
         }
@@ -439,7 +449,8 @@
      */
     get_pre_fetch_configs: function(){
         var configs = [];
-        var next_batch_config = this.next_batch_config();
+        var next_batch_config = namespace.next_batch_config(
+            this.get_current_batch());
         if (next_batch_config !== null){
             configs.push(next_batch_config);
         }
@@ -545,6 +556,85 @@
 };
 
 
+/**
+ * Return a mapping describing the batch previous to the current batch.
+ */
+namespace.prev_batch_config = function(batch){
+    if (Y.Lang.isNull(batch.prev)){
+        return null;
+    }
+    return {
+        forwards: false,
+        memo: batch.prev.memo,
+        start: batch.prev.start,
+        order_by: batch.order_by
+    };
+};
+
+
+/**
+ * Return a mapping describing the batch after the current batch.
+ */
+namespace.next_batch_config = function(batch){
+    if (Y.Lang.isNull(batch.next)) {
+        return null;
+    }
+    return {
+        forwards: true,
+        memo: batch.next.memo,
+        start: batch.next.start,
+        order_by: batch.order_by
+    };
+};
+
+/**
+ * Return a list of the batch keys described in this batch: prev, current and
+ * next.  If next or prev is null, the corresponding batch key will be null.
+ */
+namespace.get_batch_key_list = function(batch){
+    var prev_config = namespace.prev_batch_config(batch);
+    var next_config = namespace.next_batch_config(batch);
+    var keys = [];
+    if (Y.Lang.isNull(prev_config)){
+        keys.push(null);
+    } else {
+        keys.push(namespace.ListingNavigator.get_batch_key(prev_config));
+    }
+    keys.push(namespace.ListingNavigator.get_batch_key(batch));
+    if (Y.Lang.isNull(next_config)){
+        keys.push(null);
+    } else {
+        keys.push(namespace.ListingNavigator.get_batch_key(next_config));
+    }
+    return keys;
+};
+
+
+/**
+ * Find an alias between the two supplied batches, if they are adjacent.
+ * Returns a list of two batch keys that should be considered equivalent.
+ * If the supplied batches are not adjacent, returns null.
+ */
+namespace.find_batch_alias = function(batch_a, batch_b) {
+    var prev_batch;
+    var next_batch;
+    if (batch_a[2] === batch_b[1] || batch_a[1] === batch_b[0]){
+        prev_batch = batch_a;
+        next_batch = batch_b;
+    } else if (batch_b[2] === batch_a[1] || batch_b[1] === batch_a[0]){
+        prev_batch = batch_b;
+        next_batch = batch_a;
+    }
+    else {
+        return null;
+    }
+    if (prev_batch[1] !== next_batch[0]){
+        return [prev_batch[1], next_batch[0]];
+    } else {
+        return [prev_batch[2], next_batch[1]];
+    }
+};
+
 }, "0.1", {
     "requires": [
         "history", "node", 'lp.client', 'lp.app.errors', 'lp.indicator'

=== modified file 'lib/lp/bugs/javascript/buglisting_utils.js'
--- lib/lp/bugs/javascript/buglisting_utils.js	2011-12-08 13:23:00 +0000
+++ lib/lp/bugs/javascript/buglisting_utils.js	2011-12-11 01:49:34 +0000
@@ -250,31 +250,6 @@
         },
 
         /**
-         * Update field_visibility based on fields stored
-         * in cookies.  This is used as a light-weight
-         * page to page persistence mechanism.
-         *
-         * @method updateFromCookie
-         */
-        updateFromCookie: function() {
-            var cookie_name = this.get('cookie_name');
-            var cookie_fields = Y.Cookie.getSubs(cookie_name);
-            if (Y.Lang.isValue(cookie_fields)) {
-                // We get true/false back as strings from Y.Cookie,
-                // so we have to convert them to booleans.
-                Y.each(cookie_fields, function(val, key, obj) {
-                    if (val === 'true') {
-                        val = true;
-                    } else {
-                        val = false;
-                    }
-                    obj[key] = val;
-                });
-                this.updateFieldVisibilty(cookie_fields);
-            }
-        },
-
-        /**
          * Set the given value for the buglisting config cookie.
          * If config is not specified, the cookie will be cleared.
          *
@@ -287,7 +262,7 @@
                     path: '/',
                     expires: new Date('January 19, 2038')});
             } else {
-                Y.Cookie.remove(cookie_name);
+                Y.Cookie.remove(cookie_name, {path: '/'});
             }
         },
 
@@ -298,7 +273,6 @@
          * @method _extraRenderUI
          */
         _extraRenderUI: function() {
-            this.updateFromCookie();
             var form_content = this.buildFormContent();
             var on_submit_callback = Y.bind(this.handleOverlaySubmit, this);
             util_overlay = new Y.lazr.FormOverlay({

=== modified file 'lib/lp/bugs/javascript/tests/test_buglisting.js'
--- lib/lp/bugs/javascript/tests/test_buglisting.js	2011-12-06 16:36:16 +0000
+++ lib/lp/bugs/javascript/tests/test_buglisting.js	2011-12-11 01:49:34 +0000
@@ -359,6 +359,8 @@
             memo: 'memo1',
             forwards: true,
             start: 5,
+            next: null,
+            prev: null,
             mustache_model: {
                 item: [
                     {name: 'first'},
@@ -521,7 +523,10 @@
             memo: 457,
             start: 400
         },
+        forwards: true,
         order_by: 'foo',
+        memo: 457,
+        start: 450,
         last_start: 23,
         field_visibility: {},
         field_visibility_defaults: {}
@@ -840,6 +845,7 @@
                 memo: "pi",
                 start: 314
             },
+            prev: null,
             forwards: true,
             start: 5,
             mustache_model: {
@@ -858,6 +864,7 @@
     }
 }));
 
+
 suite.add(new Y.Test.Case({
     name: "Test indicators",
 
@@ -884,7 +891,9 @@
         var navigator = get_navigator();
         navigator.update({});
         navigator.get('io_provider').last_request.successJSON({
-            mustache_model: {bugtasks: []}
+            mustache_model: {bugtasks: []},
+            next: null,
+            prev: null
         });
         Y.Assert.isFalse(navigator.indicator.get('visible'));
     },
@@ -896,7 +905,9 @@
         navigator.indicator.setBusy();
         navigator.update({fetch_only: true});
         navigator.get('io_provider').last_request.successJSON({
-            mustache_model: {bugtasks: []}
+            mustache_model: {bugtasks: []},
+            next: null,
+            prev: null
         });
         Y.Assert.isTrue(navigator.indicator.get('visible'));
     },
@@ -921,6 +932,132 @@
     }
 }));
 
+
+suite.add(new Y.Test.Case({
+    name: "Find batch aliases",
+
+    test_get_batch_key_list: function(){
+        var keys = module.get_batch_key_list({
+            prev: null,
+            next:null,
+            memo: 'pi',
+            start: -1,
+            forwards: true,
+            order_by: 'ordering'
+        });
+        Y.ArrayAssert.itemsAreSame(
+            [null, '["ordering","pi",true,-1]', null], keys);
+        keys = module.get_batch_key_list({
+            prev: {
+                memo: "pi",
+                start: -2
+            },
+            next: {
+                memo: "e",
+                start: 0
+            },
+            memo: 'pi',
+            start: -1,
+            forwards: true,
+            order_by: 'ordering'
+        });
+        Y.ArrayAssert.itemsAreSame([
+            '["ordering","pi",false,-2]',
+            '["ordering","pi",true,-1]',
+            '["ordering","e",true,0]'], keys);
+    },
+
+    /* Detect batch aliases for forward movement (next). */
+    test_find_batch_alias_moving_forward: function(){
+        var prev_batch = ['a', 'b', 'c'];
+        var next_batch = ["b'", 'c', 'd'];
+        var result = module.find_batch_alias(prev_batch, next_batch);
+        Y.Assert.areSame(result[0], 'b');
+        Y.Assert.areSame(result[1], "b'");
+        result = module.find_batch_alias(next_batch, prev_batch);
+        Y.Assert.areSame(result[0], 'b');
+        Y.Assert.areSame(result[1], "b'");
+    },
+
+    /* Detect batch aliases for backward movement (prev). */
+    test_find_batch_alias_moving_backward: function(){
+        var prev_batch = ['a', 'b', 'c'];
+        var next_batch = ['b', "c'", 'd'];
+        var result = module.find_batch_alias(prev_batch, next_batch);
+        Y.Assert.areSame(result[0], 'c');
+        Y.Assert.areSame(result[1], "c'");
+        result = module.find_batch_alias(next_batch, prev_batch);
+        Y.Assert.areSame(result[0], 'c');
+        Y.Assert.areSame(result[1], "c'");
+    },
+
+    /* Do not detect aliases if batches are unrelated */
+    test_find_batch_alias_unrelated: function(){
+        var prev_batch = ['a', 'b', 'c'];
+        var next_batch = ['d', 'e', 'f'];
+        var result = module.find_batch_alias(next_batch, prev_batch);
+        Y.Assert.isNull(result);
+    },
+
+    /**
+     * When dealias_batches is called on the next batch, the current batch is
+     * re-added to the batches mapping, under its alias from the next batch.
+     */
+    test_dealias_batches_next: function(){
+        var navigator = get_navigator();
+        var next_batch = {
+            memo: 467,
+            start: 500,
+            order_by: 'foo',
+            forwards: true,
+            prev: {
+                memo: 467,
+                start: 450
+            },
+            next: null
+        };
+        var prev_batch_config = module.prev_batch_config(next_batch);
+        var prev_batch_key = navigator.constructor.get_batch_key(
+            prev_batch_config);
+        navigator.dealias_batches(next_batch);
+        Y.Assert.areSame(
+            navigator.get('batches')[prev_batch_key],
+            navigator.get_current_batch()
+        );
+        Y.Assert.areNotSame(
+            prev_batch_key, navigator.get_current_batch_key());
+    },
+    /**
+     * When dealias_batches is called on the previous batch, the current batch
+     * is re-added to the batches mapping, under its alias from the previous
+     * batch.
+     */
+    test_dealias_batches_prev: function(){
+        var navigator = get_navigator();
+        var prev_batch = {
+            memo: 457,
+            start: 400,
+            order_by: 'foo',
+            forwards: false,
+            next: {
+                memo: 467,
+                start: 450
+            },
+            prev: null
+        };
+        var next_batch_config = module.next_batch_config(prev_batch);
+        var next_batch_key = navigator.constructor.get_batch_key(
+            next_batch_config);
+        navigator.dealias_batches(prev_batch);
+        Y.Assert.areSame(
+            navigator.get('batches')[next_batch_key],
+            navigator.get_current_batch()
+        );
+        Y.Assert.areNotSame(
+            next_batch_key, navigator.get_current_batch_key());
+    }
+}));
+
 var handle_complete = function(data) {
     window.status = '::::' + JSON.stringify(data);
     };

=== modified file 'lib/lp/bugs/javascript/tests/test_buglisting_utils.js'
--- lib/lp/bugs/javascript/tests/test_buglisting_utils.js	2011-12-08 11:03:28 +0000
+++ lib/lp/bugs/javascript/tests/test_buglisting_utils.js	2011-12-11 01:49:34 +0000
@@ -10,10 +10,19 @@
 var ArrayAssert = Y.ArrayAssert;
 var ObjectAssert = Y.ObjectAssert;
 
-suite.add(new Y.Test.Case({
+
+var running_in_webkit = function(){
+    return (/AppleWebKit.53[1-5]/).test(navigator.userAgent);
+};
+
+
+var buglisting_display_utils_tests = new Y.Test.Case({
 
     name: 'buglisting_display_utils_tests',
 
+    _should: {ignore: []},
+
+
     setUp: function() {
         // Default values for model config.
         this.defaults = {
@@ -48,7 +57,9 @@
             }
         };
         // _setDoc is required for tests using cookies to pass.
-        Y.Cookie._setDoc({cookie: ""});
+        if (running_in_webkit()){
+            Y.Cookie._setDoc({cookie: ""});
+        }
         // Simulate LP.cache.field_visibility which will be
         // present in the actual page.
         window.LP = {
@@ -69,8 +80,10 @@
             this.list_util.destroy();
         }
         // Cleanup cookies.
-        Y.Cookie.remove(this.cookie_name);
-        Y.Cookie._setDoc(Y.config.doc);
+        Y.Cookie.remove(this.cookie_name, {path: "/"});
+        if (running_in_webkit()){
+            Y.Cookie._setDoc(Y.config.doc);
+        }
     },
 
     /**
@@ -122,32 +135,6 @@
             this.defaults.field_visibility_defaults);
     },
 
-    test_cookie_updates_field_visibility_config: function() {
-        // If the $USER-buglist-fields cookie is present,
-        // the widget will update field_visibility to these values.
-        var expected_config = {
-            show_title: true,
-            show_id: true,
-            show_importance: true,
-            show_status: true,
-            show_heat: false,
-            show_targetname: false,
-            show_datecreated: true,
-            show_date_last_updated: true,
-            show_assignee: false,
-            show_reporter: false,
-            show_milestone_name: false,
-            show_tag: false
-        };
-        Y.Cookie.setSubs(this.cookie_name, expected_config);
-        this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil(
-            this.defaults);
-        this.list_util.render();
-        var model = this.list_util.get('model');
-        var actual_config = model.get_field_visibility();
-        ObjectAssert.areEqual(expected_config, actual_config);
-    },
-
     test_field_visibility_form_reference: function() {
         // The form created from field_visibility defaults is referenced
         // via BugListingConfigUtil.get('form')
@@ -344,6 +331,7 @@
             field_visibility: field_visibility,
             field_visibility_defaults: this.defaults.field_visibility_defaults
         });
+        this.list_util.setCookie(field_visibility);
         this.list_util.render();
         // Poke at the page to reset the form.
         var config = Y.one('.config');
@@ -426,6 +414,9 @@
     test_form_reset_removes_cookie: function() {
         // Clicking "reset to defaults" on the overlay will
         // remove any cookie added.
+
+        Y.Cookie.remove(this.cookie_name, {path: "/"});
+        Assert.isNull(Y.Cookie.get(this.cookie_name));
         this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil(
             this.defaults);
         this.list_util.render();
@@ -438,9 +429,9 @@
         update.simulate('click');
         // Now reset from the form.
         config.simulate('click');
+        Assert.isNotNull(Y.Cookie.get(this.cookie_name));
         Y.one('.reset-buglisting').simulate('click');
-        var cookie = Y.Cookie.get(this.cookie_name);
-        Assert.areSame('', cookie);
+        Assert.isNull(Y.Cookie.get(this.cookie_name));
     },
 
     test_update_sort_button_visibility: function() {
@@ -500,7 +491,19 @@
         Assert.isFalse(importance_button._isHidden());
     }
 
-}));
+});
+
+
+/**
+ * The Chrome workaround breaks Y.Cookie.remove behaviour
+ */
+if (running_in_webkit()){
+    var ignore = buglisting_display_utils_tests._should.ignore;
+    ignore.test_form_reset_removes_cookie = true;
+    ignore.test_update_from_form_updates_cookie = true;
+}
+
+suite.add(buglisting_display_utils_tests);
 
 buglisting_utils.suite = suite;
 

=== modified file 'lib/lp/bugs/mail/tests/test_handler.py'
--- lib/lp/bugs/mail/tests/test_handler.py	2011-11-28 00:35:15 +0000
+++ lib/lp/bugs/mail/tests/test_handler.py	2011-12-11 01:49:34 +0000
@@ -18,7 +18,6 @@
 
 from canonical.config import config
 from canonical.database.sqlbase import commit
-from canonical.launchpad.ftests import import_secret_test_key
 from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus
 from canonical.launchpad.webapp.authorization import LaunchpadSecurityPolicy
 from canonical.testing.layers import (
@@ -46,6 +45,7 @@
     TestCaseWithFactory,
     )
 from lp.testing.factory import GPGSigningContext
+from lp.testing.gpgkeys import import_secret_test_key
 from lp.testing.mail_helpers import pop_notifications
 
 

=== modified file 'lib/lp/code/browser/tests/test_branchlisting.py'
--- lib/lp/code/browser/tests/test_branchlisting.py	2011-11-18 16:25:47 +0000
+++ lib/lp/code/browser/tests/test_branchlisting.py	2011-12-11 01:49:34 +0000
@@ -613,13 +613,14 @@
     layer = DatabaseFunctionalLayer
 
     def _make_branch_for_private_team(self):
+        owner = self.factory.makePerson()
         private_team = self.factory.makeTeam(
-            name='shh', displayname='Shh',
+            name='shh', displayname='Shh', owner=owner,
             visibility=PersonVisibility.PRIVATE)
         member = self.factory.makePerson(
             email='member@xxxxxxxxxxx', password='test')
-        with person_logged_in(private_team.teamowner):
-            private_team.addMember(member, private_team.teamowner)
+        with person_logged_in(owner):
+            private_team.addMember(member, owner)
         branch = self.factory.makeProductBranch(owner=private_team)
         return private_team, member, branch
 

=== modified file 'lib/lp/code/model/sourcepackagerecipedata.py'
--- lib/lp/code/model/sourcepackagerecipedata.py	2011-11-15 11:04:59 +0000
+++ lib/lp/code/model/sourcepackagerecipedata.py	2011-12-11 01:49:34 +0000
@@ -141,7 +141,7 @@
         return branch
 
 
-MAX_RECIPE_FORMAT = 0.3
+MAX_RECIPE_FORMAT = 0.4
 
 
 class SourcePackageRecipeData(Storm):
@@ -160,7 +160,7 @@
     base_branch = Reference(base_branch_id, 'Branch.id')
 
     recipe_format = Unicode(allow_none=False)
-    deb_version_template = Unicode(allow_none=False)
+    deb_version_template = Unicode(allow_none=True)
     revspec = Unicode(allow_none=True)
 
     instructions = ReferenceSet(
@@ -306,7 +306,10 @@
         self._recordInstructions(
             builder_recipe, parent_insn=None, branch_map=branch_map)
         self.base_branch = base_branch
-        self.deb_version_template = unicode(builder_recipe.deb_version)
+        if builder_recipe.deb_version is None:
+            self.deb_version_template = None
+        else:
+            self.deb_version_template = unicode(builder_recipe.deb_version)
         self.recipe_format = unicode(builder_recipe.format)
 
     def __init__(self, recipe, sourcepackage_recipe=None,

=== modified file 'lib/lp/code/model/tests/test_branchmergeproposal.py'
--- lib/lp/code/model/tests/test_branchmergeproposal.py	2011-09-06 12:41:28 +0000
+++ lib/lp/code/model/tests/test_branchmergeproposal.py	2011-12-11 01:49:34 +0000
@@ -24,7 +24,6 @@
 from zope.security.proxy import removeSecurityProxy
 
 from canonical.database.constants import UTC_NOW
-from canonical.launchpad.ftests import import_secret_test_key
 from canonical.launchpad.interfaces.launchpad import IPrivacy
 from canonical.launchpad.webapp import canonical_url
 from canonical.launchpad.webapp.testing import verifyObject
@@ -90,6 +89,7 @@
     GPGSigningContext,
     LaunchpadObjectFactory,
     )
+from lp.testing.gpgkeys import import_secret_test_key
 
 
 class TestBranchMergeProposalInterface(TestCaseWithFactory):

=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipe.py	2011-11-15 16:25:02 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py	2011-12-11 01:49:34 +0000
@@ -921,6 +921,21 @@
         self.check_recipe_branch(
             child_branch, "zam", self.merged_branch.bzr_identity, revspec="2")
 
+    def test_builds_recipe_without_debversion(self):
+        recipe_text = '''\
+        # bzr-builder format 0.4
+        %(base)s
+        nest bar %(nested)s baz
+        ''' % self.branch_identities
+        base_branch = self.get_recipe(recipe_text)
+        self.check_base_recipe_branch(
+            base_branch, self.base_branch.bzr_identity, num_child_branches=1,
+            deb_version=None)
+        child_branch, location = base_branch.child_branches[0].as_tuple()
+        self.assertEqual("baz", location)
+        self.check_recipe_branch(
+            child_branch, "bar", self.nested_branch.bzr_identity)
+
 
 class RecipeDateLastModified(TestCaseWithFactory):
     """Exercises the situations where date_last_modified is updated."""

=== modified file 'lib/lp/code/scripts/tests/test_create_merge_proposals.py'
--- lib/lp/code/scripts/tests/test_create_merge_proposals.py	2011-08-13 04:07:10 +0000
+++ lib/lp/code/scripts/tests/test_create_merge_proposals.py	2011-12-11 01:49:34 +0000
@@ -12,13 +12,13 @@
 import transaction
 from zope.component import getUtility
 
-from canonical.launchpad.ftests import import_secret_test_key
 from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
 from canonical.launchpad.scripts.tests import run_script
 from canonical.testing.layers import ZopelessAppServerLayer
 from lp.code.model.branchmergeproposaljob import CreateMergeProposalJob
 from lp.testing import TestCaseWithFactory
 from lp.testing.factory import GPGSigningContext
+from lp.testing.gpgkeys import import_secret_test_key
 
 
 class TestCreateMergeProposals(TestCaseWithFactory):

=== modified file 'lib/lp/code/templates/sourcepackagerecipe-index.pt'
--- lib/lp/code/templates/sourcepackagerecipe-index.pt	2011-11-26 04:03:29 +0000
+++ lib/lp/code/templates/sourcepackagerecipe-index.pt	2011-12-11 01:49:34 +0000
@@ -13,7 +13,7 @@
       padding-left: 2em;
     }
     div#edit-recipe_text, div#edit-description {
-      font-family: "UbuntuBeta Mono","Ubuntu Mono",monospace;
+      font-family: "Ubuntu Mono",monospace;
       margin: 1em 0;
     }
     div#edit-recipe_text.yui3-editable_text-content,

=== added directory 'lib/lp/contrib/css'
=== added file 'lib/lp/contrib/css/ubuntu-webfonts.css'
--- lib/lp/contrib/css/ubuntu-webfonts.css	1970-01-01 00:00:00 +0000
+++ lib/lp/contrib/css/ubuntu-webfonts.css	2011-12-11 01:49:34 +0000
@@ -0,0 +1,30 @@
+/* Web font defintions for the Ubuntu font.
+ *
+ * Retrieved from https://fonts.googleapis.com/css?family=Ubuntu:400,400italic,700,700italic
+ *
+ * But served from launchpad.net for performance and security reasons.
+ */
+@font-face {
+  font-family: 'Ubuntu';
+  font-style: italic;
+  font-weight: normal;
+  src: local('Ubuntu Italic'), local('Ubuntu-Italic'), url('https://themes.googleusercontent.com/static/fonts/ubuntu/v3/kbP_6ONYVgE-bLa9ZRbvvvesZW2xOQ-xsNqO47m55DA.woff') format('woff');
+}
+@font-face {
+  font-family: 'Ubuntu';
+  font-style: normal;
+  font-weight: bold;
+  src: local('Ubuntu Bold'), local('Ubuntu-Bold'), url('https://themes.googleusercontent.com/static/fonts/ubuntu/v3/0ihfXUL2emPh0ROJezvraD8E0i7KZn-EPnyo3HZu7kw.woff') format('woff');
+}
+@font-face {
+  font-family: 'Ubuntu';
+  font-style: italic;
+  font-weight: bold;
+  src: local('Ubuntu Bold Italic'), local('Ubuntu-BoldItalic'), url('https://themes.googleusercontent.com/static/fonts/ubuntu/v3/OMD20Sg9RTs7sUORCEN-7YbN6UDyHWBl620a-IRfuBk.woff') format('woff');
+}
+@font-face {
+  font-family: 'Ubuntu';
+  font-style: normal;
+  font-weight: normal;
+  src: local('Ubuntu'), url('https://themes.googleusercontent.com/static/fonts/ubuntu/v3/_xyN3apAT_yRRDeqB3sPRg.woff') format('woff');
+}

=== added directory 'lib/lp/contrib/javascript/google-analytics'
=== added file 'lib/lp/contrib/javascript/google-analytics/ga-disable-gaso.diff'
--- lib/lp/contrib/javascript/google-analytics/ga-disable-gaso.diff	1970-01-01 00:00:00 +0000
+++ lib/lp/contrib/javascript/google-analytics/ga-disable-gaso.diff	2011-12-11 01:49:34 +0000
@@ -0,0 +1,23 @@
+Diff that was applied to a copy of http://www.google-analytics.com/ga.js
+passed through http://http://jsbeautifier.org/ to disable
+the additional JS loading from Google Analytics when the Site Overlay
+feature is used.
+
+--- ga.js.orig	2011-12-08 15:37:38.000000000 -0500
++++ ga.js	2011-12-08 15:44:43.000000000 -0500
+@@ -1180,6 +1180,7 @@
+             return i
+         };
+     var zd, Ad = function (a) {
++        /* Disabled for security.
+             var f;
+             var e;
+             if (!zd) {
+@@ -1190,6 +1191,7 @@
+                 if (f = (e = (b = b && b[ma](d) || c && c[ma](d)) ? b[1] : I(W("GASO")), b = e) && b[ma](/^(?:\|([-0-9a-z.]{1,40})\|)?([-.\w]{10,1200})$/i), c = f) if (cd(a, "GASO", "" + b), K._gasoDomain = a.get(Ma), K._gasoCPath = a.get(N), b = "https://"; + ((c[1] || "www") + ".google.com") + "/analytics/reporting/overlay_js?gaso=" + c[2] + "&" + va()) a = J.createElement("script"), a.type = "text/javascript", a.async = h, a.src = b, a.id = "_gasojs", fa(a, g), b = J.getElementsByTagName("script")[0], b.parentNode.insertBefore(a, b);
+                 zd = h
+             }
++        */
+         };
+     var sd = function (a, b, c) {
+             c && (b = H(b));

=== added file 'lib/lp/contrib/javascript/google-analytics/ga.js'
--- lib/lp/contrib/javascript/google-analytics/ga.js	1970-01-01 00:00:00 +0000
+++ lib/lp/contrib/javascript/google-analytics/ga.js	2011-12-11 01:49:34 +0000
@@ -0,0 +1,1819 @@
+// This file is Copyright (c) Google Inc.
+//
+// Google Analytics Tracker code.
+// Downloaded from http://www.google-analytics.com/ga.js
+// on 2011-12-01.
+//
+// Beautified through http://jsbeautifier.org/
+//
+// We are serving it as part of Launchpad for security reasons.
+// Modification from the original: disable the Site Overlay feature
+// which loads additional JS from Google.
+// See ga-disable-gaso.diff
+(function () {
+    var g = void 0,
+        h = true,
+        i = null,
+        j = false,
+        ba = encodeURIComponent,
+        ca = Infinity,
+        da = setTimeout,
+        ea = decodeURIComponent,
+        k = Math;
+
+    function fa(a, b) {
+        return a.onload = b
+    }
+    function ga(a, b) {
+        return a.name = b
+    }
+    var m = "push",
+        ha = "slice",
+        ia = "replace",
+        ja = "load",
+        ka = "floor",
+        n = "charAt",
+        la = "value",
+        p = "indexOf",
+        ma = "match",
+        r = "name",
+        oa = "host",
+        t = "toString",
+        u = "length",
+        v = "prototype",
+        w = "split",
+        pa = "stopPropagation",
+        qa = "scope",
+        x = "location",
+        y = "getString",
+        z = "substring",
+        ra = "navigator",
+        A = "join",
+        C = "toLowerCase",
+        D;
+
+    function sa(a, b) {
+        switch (b) {
+        case 0:
+            return "" + a;
+        case 1:
+            return a * 1;
+        case 2:
+            return !!a;
+        case 3:
+            return a * 1E3
+        }
+        return a
+    }
+    function E(a, b) {
+        return g == a || "-" == a && !b || "" == a
+    }
+    function ta(a) {
+        if (!a || "" == a) return "";
+        for (; a && " \n\r\t" [p](a[n](0)) > -1;) a = a[z](1);
+        for (; a && " \n\r\t" [p](a[n](a[u] - 1)) > -1;) a = a[z](0, a[u] - 1);
+        return a
+    }
+    function ua(a) {
+        var b = 1,
+            c = 0,
+            d;
+        if (!E(a)) {
+            b = 0;
+            for (d = a[u] - 1; d >= 0; d--) c = a.charCodeAt(d), b = (b << 6 & 268435455) + c + (c << 14), c = b & 266338304, b = c != 0 ? b ^ c >> 21 : b
+        }
+        return b
+    }
+
+    function va() {
+        return k.round(k.random() * 2147483647)
+    }
+    function wa() {}
+    function F(a, b) {
+        return ba instanceof Function ? b ? encodeURI(a) : ba(a) : (G(68), escape(a))
+    }
+    function H(a) {
+        a = a[w]("+")[A](" ");
+        if (ea instanceof Function) try {
+            return ea(a)
+        } catch (b) {
+            G(17)
+        } else G(68);
+        return unescape(a)
+    }
+    var xa = function (a, b, c, d) {
+            a.addEventListener ? a.addEventListener(b, c, !! d) : a.attachEvent && a.attachEvent("on" + b, c)
+        },
+        ya = function (a, b, c, d) {
+            a.removeEventListener ? a.removeEventListener(b, c, !! d) : a.detachEvent && a.detachEvent("on" + b, c)
+        };
+
+    function I(a) {
+        return a && a[u] > 0 ? a[0] : ""
+    }
+    function za(a) {
+        var b = a ? a[u] : 0;
+        return b > 0 ? a[b - 1] : ""
+    }
+    var Aa = function () {
+            this.prefix = "ga.";
+            this.I = {}
+        };
+    Aa[v].set = function (a, b) {
+        this.I[this.prefix + a] = b
+    };
+    Aa[v].get = function (a) {
+        return this.I[this.prefix + a]
+    };
+    Aa[v].contains = function (a) {
+        return this.get(a) !== g
+    };
+
+    function Ba(a) {
+        a[p]("www.") == 0 && (a = a[z](4));
+        return a[C]()
+    }
+    function Ca(a, b) {
+        var c, d = {
+            url: a,
+            protocol: "http",
+            host: "",
+            path: "",
+            c: new Aa,
+            anchor: ""
+        };
+        if (!a) return d;
+        c = a[p]("://");
+        if (c >= 0) d.protocol = a[z](0, c), a = a[z](c + 3);
+        c = a.search("/|\\?|#");
+        if (c >= 0) d.host = a[z](0, c)[C](), a = a[z](c);
+        else return d.host = a[C](), d;
+        c = a[p]("#");
+        if (c >= 0) d.anchor = a[z](c + 1), a = a[z](0, c);
+        c = a[p]("?");
+        c >= 0 && (Da(d.c, a[z](c + 1)), a = a[z](0, c));
+        d.anchor && b && Da(d.c, d.anchor);
+        a && a[n](0) == "/" && (a = a[z](1));
+        d.path = a;
+        return d
+    }
+
+    function Da(a, b) {
+        function c(b, c) {
+            a.contains(b) || a.set(b, []);
+            a.get(b)[m](c)
+        }
+        for (var d = ta(b)[w]("&"), e = 0; e < d[u]; e++) if (d[e]) {
+            var f = d[e][p]("=");
+            f < 0 ? c(d[e], "1") : c(d[e][z](0, f), d[e][z](f + 1))
+        }
+    }
+    function Ea(a, b) {
+        if (E(a)) return "-";
+        if ("[" == a[n](0) && "]" == a[n](a[u] - 1)) return "-";
+        var c = J.domain;
+        c += b && b != "/" ? b : "";
+        return a[p](c) == (a[p]("http://";) == 0 ? 7 : a[p]("https://";) == 0 ? 8 : 0) ? "0" : a
+    };
+
+    function Fa(a, b, c) {
+        k.random() * 100 >= 1 || (a = ["utmt=error", "utmerr=" + a, "utmwv=5.2.2", "utmn=" + va(), "utmsp=1"], b && a[m]("api=" + b), c && a[m]("msg=" + F(c[z](0, 100))), K.q && a[m]("aip=1"), Ga(a[A]("&")))
+    };
+    var Ha = 0;
+
+    function L(a) {
+        return (a ? "_" : "") + Ha++
+    }
+    var Ia = L(),
+        Ja = L(),
+        Ka = L(),
+        La = L(),
+        Ma = L(),
+        M = L(),
+        N = L(),
+        Na = L(),
+        Oa = L(),
+        Pa = L(),
+        Qa = L(),
+        Ra = L(),
+        Sa = L(),
+        Ta = L(),
+        Ua = L(),
+        Va = L(),
+        Wa = L(),
+        Xa = L(),
+        Ya = L(),
+        Za = L(),
+        $a = L(),
+        ab = L(),
+        bb = L(),
+        cb = L(),
+        db = L(),
+        eb = L(),
+        fb = L(),
+        gb = L(),
+        hb = L(),
+        ib = L(),
+        jb = L(),
+        kb = L(),
+        lb = L(),
+        mb = L(),
+        nb = L(),
+        O = L(h),
+        ob = L(),
+        pb = L(),
+        qb = L(),
+        rb = L(),
+        sb = L(),
+        tb = L(),
+        ub = L(),
+        vb = L(),
+        wb = L(),
+        xb = L(),
+        P = L(h),
+        yb = L(h),
+        zb = L(h),
+        Bb = L(h),
+        Cb = L(h),
+        Db = L(h),
+        Eb = L(h),
+        Fb = L(h),
+        Gb = L(h),
+        Hb = L(h),
+        Ib = L(h),
+        Q = L(h),
+        Jb = L(h),
+        Kb = L(h),
+        Lb = L(h),
+        Mb = L(h),
+        Nb = L(h),
+        Ob = L(h),
+        Pb = L(h),
+        Qb = L(h),
+        Rb = L(h),
+        Sb = L(h),
+        Tb = L(h),
+        Ub = L(h),
+        Vb = L(h),
+        Wb = L(),
+        Xb = L(),
+        Yb = L();
+    L();
+    var Zb = L(),
+        $b = L(),
+        ac = L(),
+        bc = L(),
+        cc = L(),
+        dc = L(),
+        ec = L(),
+        hc = L(),
+        ic = L(),
+        jc = L();
+    L();
+    var kc = L(),
+        lc = L();
+    var mc = function () {
+            function a(a, c, d) {
+                R(S[v], a, c, d)
+            }
+            T("_getName", Ka, 58);
+            T("_getAccount", Ia, 64);
+            T("_visitCode", P, 54);
+            T("_getClientInfo", Ta, 53, 1);
+            T("_getDetectTitle", Wa, 56, 1);
+            T("_getDetectFlash", Ua, 65, 1);
+            T("_getLocalGifPath", fb, 57);
+            T("_getServiceMode", gb, 59);
+            U("_setClientInfo", Ta, 66, 2);
+            U("_setAccount", Ia, 3);
+            U("_setNamespace", Ja, 48);
+            U("_setAllowLinker", Qa, 11, 2);
+            U("_setDetectFlash", Ua, 61, 2);
+            U("_setDetectTitle", Wa, 62, 2);
+            U("_setLocalGifPath", fb, 46, 0);
+            U("_setLocalServerMode", gb, 92, g, 0);
+            U("_setRemoteServerMode", gb, 63, g, 1);
+            U("_setLocalRemoteServerMode", gb, 47, g, 2);
+            U("_setSampleRate", eb, 45, 1);
+            U("_setCampaignTrack", Va, 36, 2);
+            U("_setAllowAnchor", Ra, 7, 2);
+            U("_setCampNameKey", Ya, 41);
+            U("_setCampContentKey", cb, 38);
+            U("_setCampIdKey", Xa, 39);
+            U("_setCampMediumKey", ab, 40);
+            U("_setCampNOKey", db, 42);
+            U("_setCampSourceKey", $a, 43);
+            U("_setCampTermKey", bb, 44);
+            U("_setCampCIdKey", Za, 37);
+            U("_setCookiePath", N, 9, 0);
+            U("_setMaxCustomVariables", hb, 0, 1);
+            U("_setVisitorCookieTimeout", Na, 28, 1);
+            U("_setSessionCookieTimeout", Oa, 26, 1);
+            U("_setCampaignCookieTimeout", Pa, 29, 1);
+            U("_setReferrerOverride", qb, 49);
+            U("_setSiteSpeedSampleRate", ic, 132);
+            a("_trackPageview", S[v].na, 1);
+            a("_trackEvent", S[v].v, 4);
+            a("_trackPageLoadTime", S[v].ma, 100);
+            a("_trackSocial", S[v].oa, 104);
+            a("_trackTrans", S[v].pa, 18);
+            a("_sendXEvent", S[v].u, 78);
+            a("_createEventTracker", S[v].V, 74);
+            a("_getVersion", S[v].$, 60);
+            a("_setDomainName", S[v].t, 6);
+            a("_setAllowHash", S[v].ea, 8);
+            a("_getLinkerUrl", S[v].Z, 52);
+            a("_link", S[v].link, 101);
+            a("_linkByPost", S[v].da, 102);
+            a("_setTrans", S[v].ha, 20);
+            a("_addTrans", S[v].O, 21);
+            a("_addItem", S[v].M, 19);
+            a("_setTransactionDelim", S[v].ia, 82);
+            a("_setCustomVar", S[v].fa, 10);
+            a("_deleteCustomVar", S[v].X, 35);
+            a("_getVisitorCustomVar", S[v].aa, 50);
+            a("_setXKey", S[v].ka, 83);
+            a("_setXValue", S[v].la, 84);
+            a("_getXKey", S[v].ba, 76);
+            a("_getXValue", S[v].ca, 77);
+            a("_clearXKey", S[v].S, 72);
+            a("_clearXValue", S[v].T, 73);
+            a("_createXObj", S[v].W, 75);
+            a("_addIgnoredOrganic", S[v].K, 15);
+            a("_clearIgnoredOrganic", S[v].P, 97);
+            a("_addIgnoredRef", S[v].L, 31);
+            a("_clearIgnoredRef", S[v].Q, 32);
+            a("_addOrganic", S[v].N, 14);
+            a("_clearOrganic", S[v].R, 70);
+            a("_cookiePathCopy", S[v].U, 30);
+            a("_get", S[v].Y, 106);
+            a("_set", S[v].ga, 107);
+            a("_addEventListener", S[v].addEventListener, 108);
+            a("_removeEventListener", S[v].removeEventListener, 109);
+            a("_initData", S[v].m, 2);
+            a("_setVar", S[v].ja, 22);
+            U("_setSessionTimeout", Oa, 27, 3);
+            U("_setCookieTimeout", Pa, 25, 3);
+            U("_setCookiePersistence", Na, 24, 1);
+            a("_setAutoTrackOutbound", wa, 79);
+            a("_setTrackOutboundSubdomains", wa, 81);
+            a("_setHrefExamineLimit", wa, 80)
+        },
+        R = function (a, b, c, d) {
+            a[b] = function () {
+                try {
+                    return G(d), c.apply(this, arguments)
+                } catch (a) {
+                    throw Fa("exc", b, a && a[r]), a;
+                }
+            }
+        },
+        T = function (a, b, c, d) {
+            S[v][a] = function () {
+                try {
+                    return G(c), sa(this.a.get(b), d)
+                } catch (e) {
+                    throw Fa("exc", a, e && e[r]), e;
+                }
+            }
+        },
+        U = function (a, b, c, d, e) {
+            S[v][a] = function (f) {
+                try {
+                    G(c), e == g ? this.a.set(b, sa(f, d)) : this.a.set(b, e)
+                } catch (l) {
+                    throw Fa("exc", a, l && l[r]), l;
+                }
+            }
+        },
+        nc = function (a, b) {
+            return {
+                type: b,
+                target: a,
+                stopPropagation: function () {
+                    throw "aborted";
+                }
+            }
+        };
+    var oc = function (a, b) {
+            return b !== "/" ? j : (a[p]("www.google.") == 0 || a[p](".google.") == 0 || a[p]("google.") == 0) && !(a[p]("google.org") > -1) ? h : j
+        },
+        pc = function (a) {
+            var b = a.get(Ma),
+                c = a[y](N, "/");
+            oc(b, c) && a[pa]()
+        };
+    var uc = function () {
+            var a = {},
+                b = {},
+                c = new qc;
+            this.g = function (a, b) {
+                c.add(a, b)
+            };
+            var d = new qc;
+            this.d = function (a, b) {
+                d.add(a, b)
+            };
+            var e = j,
+                f = j,
+                l = h;
+            this.J = function () {
+                e = h
+            };
+            this.f = function (a) {
+                this[ja]();
+                this.set(Wb, a, h);
+                a = new rc(this);
+                e = j;
+                d.execute(this);
+                e = h;
+                b = {};
+                this.i();
+                a.qa()
+            };
+            this.load = function () {
+                e && (e = j, this.sa(), sc(this), f || (f = h, c.execute(this), tc(this), sc(this)), e = h)
+            };
+            this.i = function () {
+                if (e) if (f) e = j, tc(this), e = h;
+                else this[ja]()
+            };
+            this.get = function (c) {
+                c && c[n](0) == "_" && this[ja]();
+                return b[c] !== g ? b[c] : a[c]
+            };
+            this.set = function (c, d, e) {
+                c && c[n](0) == "_" && this[ja]();
+                e ? b[c] = d : a[c] = d;
+                c && c[n](0) == "_" && this.i()
+            };
+            this.n = function (b) {
+                a[b] = this.b(b, 0) + 1
+            };
+            this.b = function (a, b) {
+                var c = this.get(a);
+                return c == g || c === "" ? b : c * 1
+            };
+            this.getString = function (a, b) {
+                var c = this.get(a);
+                return c == g ? b : c + ""
+            };
+            this.sa = function () {
+                if (l) {
+                    var b = this[y](Ma, ""),
+                        c = this[y](N, "/");
+                    oc(b, c) || (a[M] = a[Sa] && b != "" ? ua(b) : 1, l = j)
+                }
+            }
+        };
+    uc[v].stopPropagation = function () {
+        throw "aborted";
+    };
+    var rc = function (a) {
+            var b = this;
+            this.j = 0;
+            var c = a.get(Xb);
+            this.Aa = function () {
+                b.j > 0 && c && (b.j--, b.j || c())
+            };
+            this.qa = function () {
+                !b.j && c && da(c, 0)
+            };
+            a.set(Yb, b, h)
+        };
+
+    function vc(a, b) {
+        for (var b = b || [], c = 0; c < b[u]; c++) {
+            var d = b[c];
+            if ("" + a == d || d[p](a + ".") == 0) return d
+        }
+        return "-"
+    }
+    var xc = function (a, b, c) {
+            c = c ? "" : a[y](M, "1");
+            b = b[w](".");
+            if (b[u] !== 6 || wc(b[0], c)) return j;
+            var c = b[1] * 1,
+                d = b[2] * 1,
+                e = b[3] * 1,
+                f = b[4] * 1,
+                b = b[5] * 1;
+            if (!(c >= 0 && d > 0 && e > 0 && f > 0 && b >= 0)) return G(110), j;
+            a.set(P, c);
+            a.set(Cb, d);
+            a.set(Db, e);
+            a.set(Eb, f);
+            a.set(Fb, b);
+            return h
+        },
+        yc = function (a) {
+            var b = a.get(P),
+                c = a.get(Cb),
+                d = a.get(Db),
+                e = a.get(Eb),
+                f = a.b(Fb, 1);
+            b == g ? G(113) : b == NaN && G(114);
+            b >= 0 && c > 0 && d > 0 && e > 0 && f >= 0 || G(115);
+            return [a.b(M, 1), b != g ? b : "-", c || "-", d || "-", e || "-", f][A](".")
+        },
+        zc = function (a) {
+            return [a.b(M, 1), a.b(Ib, 0), a.b(Q, 1), a.b(Jb, 0)][A](".")
+        },
+        Ac = function (a, b, c) {
+            var c = c ? "" : a[y](M, "1"),
+                d = b[w](".");
+            if (d[u] !== 4 || wc(d[0], c)) d = i;
+            a.set(Ib, d ? d[1] * 1 : 0);
+            a.set(Q, d ? d[2] * 1 : 10);
+            a.set(Jb, d ? d[3] * 1 : a.get(La));
+            return d != i || !wc(b, c)
+        },
+        Bc = function (a, b) {
+            var c = F(a[y](zb, "")),
+                d = [],
+                e = a.get(O);
+            if (!b && e) {
+                for (var f = 0; f < e[u]; f++) {
+                    var l = e[f];
+                    l && l[qa] == 1 && d[m](f + "=" + F(l[r]) + "=" + F(l[la]) + "=1")
+                }
+                d[u] > 0 && (c += "|" + d[A](","))
+            }
+            return c ? a.b(M, 1) + "." + c : i
+        },
+        Cc = function (a, b, c) {
+            c = c ? "" : a[y](M, "1");
+            b = b[w](".");
+            if (b[u] < 2 || wc(b[0], c)) return j;
+            b = b[ha](1)[A](".")[w]("|");
+            b[u] > 0 && a.set(zb, H(b[0]));
+            if (b[u] <= 1) return h;
+            for (var c = b[1][w](b[1][p](",") == -1 ? "^" : ","), d = 0; d < c[u]; d++) {
+                var e = c[d][w]("=");
+                if (e[u] == 4) {
+                    var f = {};
+                    ga(f, H(e[1]));
+                    f.value = H(e[2]);
+                    f.scope = 1;
+                    a.get(O)[e[0]] = f
+                }
+            }
+            b[1][p]("^") >= 0 && G(125);
+            return h
+        },
+        Ec = function (a, b) {
+            var c = Dc(a, b);
+            return c ? [a.b(M, 1), a.b(Kb, 0), a.b(Lb, 1), a.b(Mb, 1), c][A](".") : ""
+        },
+        Dc = function (a) {
+            function b(b, e) {
+                if (!E(a.get(b))) {
+                    var f = a[y](b, ""),
+                        f = f[w](" ")[A]("%20"),
+                        f = f[w]("+")[A]("%20");
+                    c[m](e + "=" + f)
+                }
+            }
+            var c = [];
+            b(Ob, "utmcid");
+            b(Sb, "utmcsr");
+            b(Qb, "utmgclid");
+            b(Rb, "utmdclid");
+            b(Pb, "utmccn");
+            b(Tb, "utmcmd");
+            b(Ub, "utmctr");
+            b(Vb, "utmcct");
+            return c[A]("|")
+        },
+        Gc = function (a, b, c) {
+            c = c ? "" : a[y](M, "1");
+            b = b[w](".");
+            if (b[u] < 5 || wc(b[0], c)) return a.set(Kb, g), a.set(Lb, g), a.set(Mb, g), a.set(Ob, g), a.set(Pb, g), a.set(Sb, g), a.set(Tb, g), a.set(Ub, g), a.set(Vb, g), a.set(Qb, g), a.set(Rb, g), j;
+            a.set(Kb, b[1] * 1);
+            a.set(Lb, b[2] * 1);
+            a.set(Mb, b[3] * 1);
+            Fc(a, b[ha](4)[A]("."));
+            return h
+        },
+        Fc = function (a, b) {
+            function c(a) {
+                return (a = b[ma](a + "=(.*?)(?:\\|utm|$)")) && a[u] == 2 ? a[1] : g
+            }
+            function d(b, c) {
+                c && (c = e ? H(c) : c[w]("%20")[A](" "), a.set(b, c))
+            }
+            b[p]("=") == -1 && (b = H(b));
+            var e = c("utmcvr") == "2";
+            d(Ob, c("utmcid"));
+            d(Pb, c("utmccn"));
+            d(Sb, c("utmcsr"));
+            d(Tb, c("utmcmd"));
+            d(Ub, c("utmctr"));
+            d(Vb, c("utmcct"));
+            d(Qb, c("utmgclid"));
+            d(Rb, c("utmdclid"))
+        },
+        wc = function (a, b) {
+            return b ? a != b : !/^\d+$/.test(a)
+        };
+    var qc = function () {
+            this.s = []
+        };
+    qc[v].add = function (a, b) {
+        this.s[m]({
+            name: a,
+            Da: b
+        })
+    };
+    qc[v].execute = function (a) {
+        try {
+            for (var b = 0; b < this.s[u]; b++) this.s[b].Da.call(V, a)
+        } catch (c) {}
+    };
+
+    function Hc(a) {
+        a.get(eb) != 100 && a.get(P) % 1E4 >= a.get(eb) * 100 && a[pa]()
+    }
+    function Ic(a) {
+        Jc() && a[pa]()
+    }
+    function Kc(a) {
+        J[x].protocol == "file:" && a[pa]()
+    }
+    function Lc(a) {
+        a.get(pb) || a.set(pb, J.title, h);
+        a.get(ob) || a.set(ob, J[x].pathname + J[x].search, h)
+    };
+    var Mc = new function () {
+            var a = [];
+            this.set = function (b) {
+                a[b] = h
+            };
+            this.Ea = function () {
+                for (var b = [], c = 0; c < a[u]; c++) a[c] && (b[k[ka](c / 6)] ^= 1 << c % 6);
+                for (c = 0; c < b[u]; c++) b[c] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" [n](b[c] || 0);
+                return b[A]("") + "~"
+            }
+        };
+
+    function G(a) {
+        Mc.set(a)
+    };
+    var V = window,
+        J = document,
+        Jc = function () {
+            var a = V._gaUserPrefs;
+            return a && a.ioo && a.ioo()
+        },
+        Nc = function (a, b) {
+            da(a, b)
+        },
+        W = function (a) {
+            for (var b = [], c = J.cookie[w](";"), a = RegExp("^\\s*" + a + "=\\s*(.*?)\\s*$"), d = 0; d < c[u]; d++) {
+                var e = c[d][ma](a);
+                e && b[m](e[1])
+            }
+            return b
+        },
+        X = function (a, b, c, d, e) {
+            var f;
+            f = Jc() ? j : oc(d, c) ? j : h;
+            if (f) {
+                if (b && V[ra].userAgent[p]("Firefox") >= 0) {
+                    b = b[ia](/\n|\r/g, " ");
+                    f = 0;
+                    for (var l = b[u]; f < l; ++f) {
+                        var o = b.charCodeAt(f) & 255;
+                        if (o == 10 || o == 13) b = b[z](0, f) + "?" + b[z](f + 1)
+                    }
+                }
+                b && b[u] > 2E3 && (b = b[z](0, 2E3), G(69));
+                a = a + "=" + b + "; path=" + c + "; ";
+                e && (a += "expires=" + (new Date((new Date).getTime() + e)).toGMTString() + "; ");
+                d && (a += "domain=" + d + ";");
+                J.cookie = a
+            }
+        };
+    var Oc, Pc, Qc = function () {
+            if (!Oc) {
+                var a = {},
+                    b = V[ra],
+                    c = V.screen;
+                a.H = c ? c.width + "x" + c.height : "-";
+                a.G = c ? c.colorDepth + "-bit" : "-";
+                a.language = (b && (b.language || b.browserLanguage) || "-")[C]();
+                a.javaEnabled = b && b.javaEnabled() ? 1 : 0;
+                a.characterSet = J.characterSet || J.charset || "-";
+                Oc = a
+            }
+        },
+        Rc = function () {
+            Qc();
+            for (var a = Oc, b = V[ra], a = b.appName + b.version + a.language + b.platform + b.userAgent + a.javaEnabled + a.H + a.G + (J.cookie ? J.cookie : "") + (J.referrer ? J.referrer : ""), b = a[u], c = V.history[u]; c > 0;) a += c-- ^ b++;
+            return ua(a)
+        },
+        Sc = function (a) {
+            Qc();
+            var b = Oc;
+            a.set(sb, b.H);
+            a.set(tb, b.G);
+            a.set(wb, b.language);
+            a.set(xb, b.characterSet);
+            a.set(ub, b.javaEnabled);
+            if (a.get(Ta) && a.get(Ua)) {
+                if (!(b = Pc)) {
+                    var c, d, e;
+                    d = "ShockwaveFlash";
+                    if ((b = (b = V[ra]) ? b.plugins : g) && b[u] > 0) for (c = 0; c < b[u] && !e; c++) d = b[c], d[r][p]("Shockwave Flash") > -1 && (e = d.description[w]("Shockwave Flash ")[1]);
+                    else {
+                        d = d + "." + d;
+                        try {
+                            c = new ActiveXObject(d + ".7"), e = c.GetVariable("$version")
+                        } catch (f) {}
+                        if (!e) try {
+                            c = new ActiveXObject(d + ".6"), e = "WIN 6,0,21,0", c.AllowScriptAccess = "always", e = c.GetVariable("$version")
+                        } catch (l) {}
+                        if (!e) try {
+                            c = new ActiveXObject(d), e = c.GetVariable("$version")
+                        } catch (o) {}
+                        e && (e = e[w](" ")[1][w](","), e = e[0] + "." + e[1] + " r" + e[2])
+                    }
+                    b = e ? e : "-"
+                }
+                Pc = b;
+                a.set(vb, Pc)
+            } else a.set(vb, "-")
+        };
+    var Y = function () {
+            R(Y[v], "push", Y[v][m], 5);
+            R(Y[v], "_createAsyncTracker", Y[v].Ba, 33);
+            R(Y[v], "_getAsyncTracker", Y[v].Ca, 34);
+            this.r = 0
+        };
+    Y[v].Ba = function (a, b) {
+        return K.l(a, b || "")
+    };
+    Y[v].Ca = function (a) {
+        return K.p(a)
+    };
+    Y[v].push = function (a) {
+        this.r > 0 && G(105);
+        this.r++;
+        for (var b = arguments, c = 0, d = 0; d < b[u]; d++) try {
+            if (typeof b[d] === "function") b[d]();
+            else {
+                var e = "",
+                    f = b[d][0],
+                    l = f.lastIndexOf(".");
+                l > 0 && (e = f[z](0, l), f = f[z](l + 1));
+                var o = e == "_gat" ? K : e == "_gaq" ? Tc : K.p(e);
+                o[f].apply(o, b[d][ha](1))
+            }
+        } catch (q) {
+            c++
+        }
+        this.r--;
+        return c
+    };
+    var Yc = function () {
+            function a(a, b, c, d) {
+                g == f[a] && (f[a] = {});
+                g == f[a][b] && (f[a][b] = []);
+                f[a][b][c] = d
+            }
+            function b(a, b, c) {
+                if (g != f[a] && g != f[a][b]) return f[a][b][c]
+            }
+            function c(a, b) {
+                if (g != f[a] && g != f[a][b]) {
+                    f[a][b] = g;
+                    var c = h,
+                        d;
+                    for (d = 0; d < l[u]; d++) if (g != f[a][l[d]]) {
+                        c = j;
+                        break
+                    }
+                    c && (f[a] = g)
+                }
+            }
+            function d(a) {
+                var b = "",
+                    c = j,
+                    d, e;
+                for (d = 0; d < l[u]; d++) if (e = a[l[d]], g != e) {
+                    c && (b += l[d]);
+                    for (var c = [], f = g, $ = g, $ = 0; $ < e[u]; $++) if (g != e[$]) {
+                        f = "";
+                        $ != aa && g == e[$ - 1] && (f += $[t]() + na);
+                        for (var Wc = e[$], Xc = "", Ab = g, fc = g, gc = g, Ab = 0; Ab < Wc[u]; Ab++) fc = Wc[n](Ab), gc = B[fc], Xc += g != gc ? gc : fc;
+                        f += Xc;
+                        c[m](f)
+                    }
+                    b += o + c[A](s) + q;
+                    c = j
+                } else c = h;
+                return b
+            }
+            var e = this,
+                f = [],
+                l = ["k", "v"],
+                o = "(",
+                q = ")",
+                s = "*",
+                na = "!",
+                B = {
+                    "'": "'0"
+                };
+            B[q] = "'1";
+            B[s] = "'2";
+            B[na] = "'3";
+            var aa = 1;
+            e.va = function (a) {
+                return g != f[a]
+            };
+            e.o = function () {
+                for (var a = "", b = 0; b < f[u]; b++) g != f[b] && (a += b[t]() + d(f[b]));
+                return a
+            };
+            e.ua = function (a) {
+                if (a == g) return e.o();
+                for (var b = a.o(), c = 0; c < f[u]; c++) g != f[c] && !a.va(c) && (b += c[t]() + d(f[c]));
+                return b
+            };
+            e.e = function (b, c, d) {
+                if (!Uc(d)) return j;
+                a(b, "k", c, d);
+                return h
+            };
+            e.k = function (b, c, d) {
+                if (!Vc(d)) return j;
+                a(b, "v", c, d[t]());
+                return h
+            };
+            e.getKey = function (a, c) {
+                return b(a, "k", c)
+            };
+            e.C = function (a, c) {
+                return b(a, "v", c)
+            };
+            e.A = function (a) {
+                c(a, "k")
+            };
+            e.B = function (a) {
+                c(a, "v")
+            };
+            R(e, "_setKey", e.e, 89);
+            R(e, "_setValue", e.k, 90);
+            R(e, "_getKey", e.getKey, 87);
+            R(e, "_getValue", e.C, 88);
+            R(e, "_clearKey", e.A, 85);
+            R(e, "_clearValue", e.B, 86)
+        };
+
+    function Uc(a) {
+        return typeof a == "string"
+    }
+    function Vc(a) {
+        return typeof a != "number" && (g == Number || !(a instanceof Number)) || k.round(a) != a || a == NaN || a == ca ? j : h
+    };
+    var Zc = function (a) {
+            var b = V.gaGlobal;
+            a && !b && (V.gaGlobal = b = {});
+            return b
+        },
+        $c = function () {
+            var a = Zc(h).hid;
+            if (a == i) a = va(), Zc(h).hid = a;
+            return a
+        },
+        ad = function (a) {
+            a.set(rb, $c());
+            var b = Zc();
+            if (b && b.dh == a.get(M)) {
+                var c = b.sid;
+                c && (c == "0" && G(112), a.set(Eb, c), a.get(yb) && a.set(Db, c));
+                b = b.vid;
+                a.get(yb) && b && (b = b[w]("."), b[1] * 1 || G(112), a.set(P, b[0] * 1), a.set(Cb, b[1] * 1))
+            }
+        };
+    var bd, cd = function (a, b, c) {
+            var d = a[y](Ma, ""),
+                e = a[y](N, "/"),
+                a = a.b(Na, 0);
+            X(b, c, e, d, a)
+        },
+        tc = function (a) {
+            var b = a[y](Ma, "");
+            a.b(M, 1);
+            var c = a[y](N, "/");
+            X("__utma", yc(a), c, b, a.get(Na));
+            X("__utmb", zc(a), c, b, a.get(Oa));
+            X("__utmc", "" + a.b(M, 1), c, b);
+            var d = Ec(a, h);
+            d ? X("__utmz", d, c, b, a.get(Pa)) : X("__utmz", "", c, b, -1);
+            (d = Bc(a, j)) ? X("__utmv", d, c, b, a.get(Na)) : X("__utmv", "", c, b, -1)
+        },
+        sc = function (a) {
+            var b = a.b(M, 1);
+            if (!xc(a, vc(b, W("__utma")))) return a.set(Bb, h), j;
+            var c = !Ac(a, vc(b, W("__utmb")));
+            a.set(Hb, c);
+            Gc(a, vc(b, W("__utmz")));
+            Cc(a, vc(b, W("__utmv")));
+            bd = !c;
+            return h
+        },
+        dd = function (a) {
+            !bd && !(W("__utmb")[u] > 0) && (X("__utmd", "1", a[y](N, "/"), a[y](Ma, ""), 1E4), W("__utmd")[u] == 0 && a[pa]())
+        };
+    var gd = function (a) {
+            a.get(P) == g ? ed(a) : a.get(Bb) && !a.get(kc) ? ed(a) : a.get(Hb) && fd(a)
+        },
+        hd = function (a) {
+            a.get(Nb) && !a.get(Gb) && (fd(a), a.set(Lb, a.get(Fb)))
+        },
+        ed = function (a) {
+            var b = a.get(La);
+            a.set(yb, h);
+            a.set(P, va() ^ Rc(a) & 2147483647);
+            a.set(zb, "");
+            a.set(Cb, b);
+            a.set(Db, b);
+            a.set(Eb, b);
+            a.set(Fb, 1);
+            a.set(Gb, h);
+            a.set(Ib, 0);
+            a.set(Q, 10);
+            a.set(Jb, b);
+            a.set(O, []);
+            a.set(Bb, j);
+            a.set(Hb, j)
+        },
+        fd = function (a) {
+            a.set(Db, a.get(Eb));
+            a.set(Eb, a.get(La));
+            a.n(Fb);
+            a.set(Gb, h);
+            a.set(Ib, 0);
+            a.set(Q, 10);
+            a.set(Jb, a.get(La));
+            a.set(Hb, j)
+        };
+    var id = "daum:q,eniro:search_word,naver:query,pchome:q,images.google:q,google:q,yahoo:p,yahoo:q,msn:q,bing:q,aol:query,aol:q,lycos:query,ask:q,netscape:query,cnn:query,about:terms,mamma:q,voila:rdata,virgilio:qs,live:q,baidu:wd,alice:qs,yandex:text,najdi:q,seznam:q,search:q,wp:szukaj,onet:qt,szukacz:q,yam:k,kvasir:q,ozu:q,terra:query,rambler:query".split(","),
+        od = function (a) {
+            if (a.get(Va) && !a.get(kc)) {
+                for (var b = !E(a.get(Ob)) || !E(a.get(Sb)) || !E(a.get(Qb)) || !E(a.get(Rb)), c = {}, d = 0; d < jd[u]; d++) {
+                    var e = jd[d];
+                    c[e] = a.get(e)
+                }
+                d = Ca(J[x].href, a.get(Ra));
+                if (!(za(d.c.get(a.get(db))) == "1" && b) && (d = kd(a, d) || ld(a), !d && !b && a.get(Gb) && (md(a, g, "(direct)", g, g, "(direct)", "(none)", g, g), d = h), d && (a.set(Nb, nd(a, c)), b = a.get(Sb) == "(direct)" && a.get(Pb) == "(direct)" && a.get(Tb) == "(none)", a.get(Nb) || a.get(Gb) && !b))) a.set(Kb, a.get(La)), a.set(Lb, a.get(Fb)), a.n(Mb)
+            }
+        },
+        kd = function (a, b) {
+            function c(c, d) {
+                var d = d || "-",
+                    e = za(b.c.get(a.get(c)));
+                return e && e != "-" ? H(e) : d
+            }
+            var d = za(b.c.get(a.get(Xa))) || "-",
+                e = za(b.c.get(a.get($a))) || "-",
+                f = za(b.c.get(a.get(Za))) || "-",
+                l = za(b.c.get("dclid")) || "-",
+                o = c(Ya, "(not set)"),
+                q = c(ab, "(not set)"),
+                s = c(bb),
+                na = c(cb);
+            if (E(d) && E(f) && E(l) && E(e)) return j;
+            if (E(s)) {
+                var B = Ea(a.get(qb), a.get(N)),
+                    B = Ca(B, h);
+                (B = pd(a, B)) && !E(B[1] && !B[2]) && (s = B[1])
+            }
+            md(a, d, e, f, l, o, q, s, na);
+            return h
+        },
+        ld = function (a) {
+            var b = Ea(a.get(qb), a.get(N)),
+                c = Ca(b, h);
+            if (!(b != g && b != i && b != "" && b != "0" && b != "-" && b[p]("://") >= 0) || c && c[oa][p]("google") > -1 && c.c.contains("q") && c.path == "cse") return j;
+            if ((b = pd(a, c)) && !b[2]) return md(a, g, b[0], g, g, "(organic)", "organic", b[1], g), h;
+            else if (b) return j;
+            if (a.get(Gb)) a: {
+                for (var b = a.get(kb), d = Ba(c[oa]), e = 0; e < b[u]; ++e) if (d[p](b[e]) > -1) {
+                    a = j;
+                    break a
+                }
+                md(a, g, d, g, g, "(referral)", "referral", g, "/" + c.path);
+                a = h
+            } else a = j;
+            return a
+        },
+        pd = function (a, b) {
+            for (var c = a.get(ib), d = 0; d < c[u]; ++d) {
+                var e = c[d][w](":");
+                if (b[oa][p](e[0][C]()) > -1) {
+                    var f = b.c.get(e[1]);
+                    if (f && (f = I(f), !f && b[oa][p]("google.") > -1 && (f = "(not provided)"), !e[3] || b.url[p](e[3]) > -1)) {
+                        a: {
+                            for (var c = f, d = a.get(jb), c = H(c)[C](), l = 0; l < d[u]; ++l) if (c == d[l]) {
+                                c = h;
+                                break a
+                            }
+                            c = j
+                        }
+                        return [e[2] || e[0], f, c]
+                    }
+                }
+            }
+            return i
+        },
+        md = function (a, b, c, d, e, f, l, o, q) {
+            a.set(Ob, b);
+            a.set(Sb, c);
+            a.set(Qb, d);
+            a.set(Rb, e);
+            a.set(Pb, f);
+            a.set(Tb, l);
+            a.set(Ub, o);
+            a.set(Vb, q)
+        },
+        jd = [Pb, Ob, Qb, Rb, Sb, Tb, Ub, Vb],
+        nd = function (a, b) {
+            function c(a) {
+                a = ("" + a)[w]("+")[A]("%20");
+                return a = a[w](" ")[A]("%20")
+            }
+            function d(c) {
+                var d = "" + (a.get(c) || ""),
+                    c = "" + (b[c] || "");
+                return d[u] > 0 && d == c
+            }
+            if (d(Qb) || d(Rb)) return G(131), j;
+            for (var e = 0; e < jd[u]; e++) {
+                var f = jd[e],
+                    l = b[f] || "-",
+                    f = a.get(f) || "-";
+                if (c(l) != c(f)) return h
+            }
+            return j
+        };
+    var rd = function (a) {
+            qd(a, J[x].href) ? (a.set(kc, h), G(12)) : a.set(kc, j)
+        },
+        qd = function (a, b) {
+            if (!a.get(Qa)) return j;
+            var c = Ca(b, a.get(Ra)),
+                d = I(c.c.get("__utma")),
+                e = I(c.c.get("__utmb")),
+                f = I(c.c.get("__utmc")),
+                l = I(c.c.get("__utmx")),
+                o = I(c.c.get("__utmz")),
+                q = I(c.c.get("__utmv")),
+                c = I(c.c.get("__utmk"));
+            if (ua("" + d + e + f + l + o + q) != c) {
+                d = H(d);
+                e = H(e);
+                f = H(f);
+                l = H(l);
+                a: {
+                    for (var f = d + e + f + l, s = 0; s < 3; s++) {
+                        for (var na = 0; na < 3; na++) {
+                            if (c == ua(f + o + q)) {
+                                G(127);
+                                c = [o, q];
+                                break a
+                            }
+                            var B = o[ia](/ /g, "%20"),
+                                aa = q[ia](/ /g, "%20");
+                            if (c == ua(f + B + aa)) {
+                                G(128);
+                                c = [B, aa];
+                                break a
+                            }
+                            B = B[ia](/\+/g, "%20");
+                            aa = aa[ia](/\+/g, "%20");
+                            if (c == ua(f + B + aa)) {
+                                G(129);
+                                c = [B, aa];
+                                break a
+                            }
+                            o = H(o)
+                        }
+                        q = H(q)
+                    }
+                    c = g
+                }
+                if (!c) return j;
+                o = c[0];
+                q = c[1]
+            }
+            if (!xc(a, d, h)) return j;
+            Ac(a, e, h);
+            Gc(a, o, h);
+            Cc(a, q, h);
+            sd(a, l, h);
+            return h
+        },
+        ud = function (a, b, c) {
+            var d;
+            d = yc(a) || "-";
+            var e = zc(a) || "-",
+                f = "" + a.b(M, 1) || "-",
+                l = td(a) || "-",
+                o = Ec(a, j) || "-",
+                a = Bc(a, j) || "-",
+                q = ua("" + d + e + f + l + o + a),
+                s = [];
+            s[m]("__utma=" + d);
+            s[m]("__utmb=" + e);
+            s[m]("__utmc=" + f);
+            s[m]("__utmx=" + l);
+            s[m]("__utmz=" + o);
+            s[m]("__utmv=" + a);
+            s[m]("__utmk=" + q);
+            d = s[A]("&");
+            if (!d) return b;
+            e = b[p]("#");
+            return c ? e < 0 ? b + "#" + d : b + "&" + d : (c = "", f = b[p]("?"), e > 0 && (c = b[z](e), b = b[z](0, e)), f < 0 ? b + "?" + d + c : b + "&" + d + c)
+        };
+    var vd = "|",
+        xd = function (a, b, c, d, e, f, l, o, q) {
+            var s = wd(a, b);
+            s || (s = {}, a.get(lb)[m](s));
+            s.id_ = b;
+            s.affiliation_ = c;
+            s.total_ = d;
+            s.tax_ = e;
+            s.shipping_ = f;
+            s.city_ = l;
+            s.state_ = o;
+            s.country_ = q;
+            s.items_ = s.items_ || [];
+            return s
+        },
+        yd = function (a, b, c, d, e, f, l) {
+            var a = wd(a, b) || xd(a, b, "", 0, 0, 0, "", "", ""),
+                o;
+            a: {
+                if (a && a.items_) {
+                    o = a.items_;
+                    for (var q = 0; q < o[u]; q++) if (o[q].sku_ == c) {
+                        o = o[q];
+                        break a
+                    }
+                }
+                o = i
+            }
+            q = o || {};
+            q.transId_ = b;
+            q.sku_ = c;
+            q.name_ = d;
+            q.category_ = e;
+            q.price_ = f;
+            q.quantity_ = l;
+            o || a.items_[m](q);
+            return q
+        },
+        wd = function (a, b) {
+            for (var c = a.get(lb), d = 0; d < c[u]; d++) if (c[d].id_ == b) return c[d];
+            return i
+        };
+    var zd, Ad = function (a) {
+        /* Disabled for security.
+            var f;
+            var e;
+            if (!zd) {
+                var b;
+                b = J[x].hash;
+                var c = V[r],
+                    d = /^#?gaso=([^&]*)/;
+                if (f = (e = (b = b && b[ma](d) || c && c[ma](d)) ? b[1] : I(W("GASO")), b = e) && b[ma](/^(?:\|([-0-9a-z.]{1,40})\|)?([-.\w]{10,1200})$/i), c = f) if (cd(a, "GASO", "" + b), K._gasoDomain = a.get(Ma), K._gasoCPath = a.get(N), b = "https://"; + ((c[1] || "www") + ".google.com") + "/analytics/reporting/overlay_js?gaso=" + c[2] + "&" + va()) a = J.createElement("script"), a.type = "text/javascript", a.async = h, a.src = b, a.id = "_gasojs", fa(a, g), b = J.getElementsByTagName("script")[0], b.parentNode.insertBefore(a, b);
+                zd = h
+            }
+        */
+        };
+    var sd = function (a, b, c) {
+            c && (b = H(b));
+            c = a.b(M, 1);
+            b = b[w](".");
+            !(b[u] < 2) && /^\d+$/.test(b[0]) && (b[0] = "" + c, cd(a, "__utmx", b[A](".")))
+        },
+        td = function (a, b) {
+            var c = vc(a.get(M), W("__utmx"));
+            c == "-" && (c = "");
+            return b ? F(c) : c
+        };
+    var Fd = function (a, b) {
+            var c = k.min(a.b(ic, 0), 10);
+            if (a.b(P, 0) % 100 >= c) return j;
+            c = Bd() || Cd();
+            if (c == g) return j;
+            var d = c[0];
+            if (d == g || d == ca || isNaN(d)) return j;
+            d > 0 ? Dd(c) ? b(Ed(c)) : b(Ed(c[ha](0, 1))) : xa(V, "load", function () {
+                Fd(a, b)
+            }, j);
+            return h
+        },
+        Dd = function (a) {
+            for (var b = 1; b < a[u]; b++) if (isNaN(a[b]) || a[b] == ca || a[b] < 0) return j;
+            return h
+        },
+        Ed = function (a) {
+            for (var b = new Yc, c = 0; c < a[u]; c++) b.e(14, c + 1, (isNaN(a[c]) || a[c] < 0 ? 0 : a[c] < 5E3 ? k[ka](a[c] / 10) * 10 : a[c] < 45E4 ? k[ka](a[c] / 100) * 100 : 45E4) + ""), b.k(14, c + 1, a[c]);
+            return b
+        },
+        Bd = function () {
+            var a = V.performance || V.webkitPerformance;
+            if (a = a && a.timing) {
+                var b = a.navigationStart;
+                if (b == 0) G(133);
+                else return [a.loadEventStart - b, a.domainLookupEnd - a.domainLookupStart, a.connectEnd - a.connectStart, a.responseStart - a.requestStart, a.responseEnd - a.responseStart, a.fetchStart - b]
+            }
+        },
+        Cd = function () {
+            if (V.top == V) {
+                var a = V.external,
+                    b = a && a.onloadT;
+                a && !a.isValidLoadTime && (b = g);
+                b > 2147483648 && (b = g);
+                b > 0 && a.setPageReadyTime();
+                return b == g ? g : [b]
+            }
+        };
+    var S = function (a, b, c) {
+            function d(a) {
+                return function (b) {
+                    if ((b = b.get(lc)[a]) && b[u]) for (var c = nc(e, a), d = 0; d < b[u]; d++) b[d].call(e, c)
+                }
+            }
+            var e = this;
+            this.a = new uc;
+            this.get = function (a) {
+                return this.a.get(a)
+            };
+            this.set = function (a, b, c) {
+                this.a.set(a, b, c)
+            };
+            this.set(Ia, b || "UA-XXXXX-X");
+            this.set(Ka, a || "");
+            this.set(Ja, c || "");
+            this.set(La, k.round((new Date).getTime() / 1E3));
+            this.set(N, "/");
+            this.set(Na, 63072E6);
+            this.set(Pa, 15768E6);
+            this.set(Oa, 18E5);
+            this.set(Qa, j);
+            this.set(hb, 50);
+            this.set(Ra, j);
+            this.set(Sa, h);
+            this.set(Ta, h);
+            this.set(Ua, h);
+            this.set(Va, h);
+            this.set(Wa, h);
+            this.set(Ya, "utm_campaign");
+            this.set(Xa, "utm_id");
+            this.set(Za, "gclid");
+            this.set($a, "utm_source");
+            this.set(ab, "utm_medium");
+            this.set(bb, "utm_term");
+            this.set(cb, "utm_content");
+            this.set(db, "utm_nooverride");
+            this.set(eb, 100);
+            this.set(ic, 1);
+            this.set(jc, j);
+            this.set(fb, "/__utm.gif");
+            this.set(gb, 1);
+            this.set(lb, []);
+            this.set(O, []);
+            this.set(ib, id[ha](0));
+            this.set(jb, []);
+            this.set(kb, []);
+            this.t("auto");
+            this.set(qb, this.ra());
+            this.set(lc, {
+                hit: [],
+                load: []
+            });
+            this.a.g("0", rd);
+            this.a.g("1", gd);
+            this.a.g("2", od);
+            this.a.g("3", hd);
+            this.a.g("4", d("load"));
+            this.a.g("5", Ad);
+            this.a.d("A", Ic);
+            this.a.d("B", Kc);
+            this.a.d("C", gd);
+            this.a.d("D", Hc);
+            this.a.d("E", pc);
+            this.a.d("F", Gd);
+            this.a.d("G", dd);
+            this.a.d("H", Lc);
+            this.a.d("I", Sc);
+            this.a.d("J", ad);
+            this.a.d("K", d("hit"));
+            this.a.d("L", Hd);
+            this.a.d("M", Id);
+            this.get(La) === 0 && G(111);
+            this.a.J();
+            this.w = g
+        };
+    D = S[v];
+    D.h = function () {
+        var a = this.get(mb);
+        a || (a = new Yc, this.set(mb, a));
+        return a
+    };
+    D.ta = function (a) {
+        for (var b in a) {
+            var c = a[b];
+            a.hasOwnProperty(b) && typeof c != "function" && this.set(b, c, h)
+        }
+    };
+    D.z = function (a) {
+        if (this.get(jc)) return j;
+        var b = this,
+            c = Fd(this.a, function (c) {
+                b.set(ob, a, h);
+                b.u(c)
+            });
+        this.set(jc, c);
+        return c
+    };
+    D.na = function (a) {
+        a && a != g && (a.constructor + "")[p]("String") > -1 ? (G(13), this.set(ob, a, h)) : typeof a === "object" && a !== i && this.ta(a);
+        this.w = a = this.get(ob);
+        this.a.f("page");
+        this.z(a)
+    };
+    D.v = function (a, b, c, d, e) {
+        if (a == "" || !Uc(a) || b == "" || !Uc(b)) return j;
+        if (c != g && !Uc(c)) return j;
+        if (d != g && !Vc(d)) return j;
+        this.set($b, a, h);
+        this.set(ac, b, h);
+        this.set(bc, c, h);
+        this.set(cc, d, h);
+        this.set(Zb, !! e, h);
+        this.a.f("event");
+        return h
+    };
+    D.oa = function (a, b, c, d) {
+        if (!a || !b) return j;
+        this.set(dc, a, h);
+        this.set(ec, b, h);
+        this.set(hc, c || J[x].href, h);
+        d && this.set(ob, d, h);
+        this.a.f("social");
+        return h
+    };
+    D.ma = function () {
+        this.set(ic, 10);
+        this.z(this.w)
+    };
+    D.pa = function () {
+        this.a.f("trans")
+    };
+    D.u = function (a) {
+        this.set(nb, a, h);
+        this.a.f("event")
+    };
+    D.V = function (a) {
+        this.m();
+        var b = this;
+        return {
+            _trackEvent: function (c, d, e) {
+                G(91);
+                b.v(a, c, d, e)
+            }
+        }
+    };
+    D.Y = function (a) {
+        return this.get(a)
+    };
+    D.ga = function (a, b) {
+        if (a) if (a != g && (a.constructor + "")[p]("String") > -1) this.set(a, b);
+        else if (typeof a == "object") for (var c in a) a.hasOwnProperty(c) && this.set(c, a[c])
+    };
+    D.addEventListener = function (a, b) {
+        var c = this.get(lc)[a];
+        c && c[m](b)
+    };
+    D.removeEventListener = function (a, b) {
+        for (var c = this.get(lc)[a], d = 0; c && d < c[u]; d++) if (c[d] == b) {
+            c.splice(d, 1);
+            break
+        }
+    };
+    D.$ = function () {
+        return "5.2.2"
+    };
+    D.t = function (a) {
+        this.get(Sa);
+        a = a == "auto" ? Ba(J.domain) : !a || a == "-" || a == "none" ? "" : a[C]();
+        this.set(Ma, a)
+    };
+    D.ea = function (a) {
+        this.set(Sa, !! a)
+    };
+    D.Z = function (a, b) {
+        return ud(this.a, a, b)
+    };
+    D.link = function (a, b) {
+        if (this.a.get(Qa) && a) {
+            var c = ud(this.a, a, b);
+            J[x].href = c
+        }
+    };
+    D.da = function (a, b) {
+        this.a.get(Qa) && a && a.action && (a.action = ud(this.a, a.action, b))
+    };
+    D.ha = function () {
+        this.m();
+        var a = this.a,
+            b = J.getElementById ? J.getElementById("utmtrans") : J.utmform && J.utmform.utmtrans ? J.utmform.utmtrans : i;
+        if (b && b[la]) {
+            a.set(lb, []);
+            for (var b = b[la][w]("UTM:"), c = 0; c < b[u]; c++) {
+                b[c] = ta(b[c]);
+                for (var d = b[c][w](vd), e = 0; e < d[u]; e++) d[e] = ta(d[e]);
+                "T" == d[0] ? xd(a, d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8]) : "I" == d[0] && yd(a, d[1], d[2], d[3], d[4], d[5], d[6])
+            }
+        }
+    };
+    D.O = function (a, b, c, d, e, f, l, o) {
+        return xd(this.a, a, b, c, d, e, f, l, o)
+    };
+    D.M = function (a, b, c, d, e, f) {
+        return yd(this.a, a, b, c, d, e, f)
+    };
+    D.ia = function (a) {
+        vd = a || "|"
+    };
+    D.fa = function (a, b, c, d) {
+        var e = this.a;
+        if (a <= 0 || a > e.get(hb)) a = j;
+        else if (!b || !c || F(b)[u] + F(c)[u] > 64) a = j;
+        else {
+            d != 1 && d != 2 && (d = 3);
+            var f = {};
+            ga(f, b);
+            f.value = c;
+            f.scope = d;
+            e.get(O)[a] = f;
+            a = h
+        }
+        a && this.a.i();
+        return a
+    };
+    D.X = function (a) {
+        this.a.get(O)[a] = g;
+        this.a.i()
+    };
+    D.aa = function (a) {
+        return (a = this.a.get(O)[a]) && a[qa] == 1 ? a[la] : g
+    };
+    D.ka = function (a, b, c) {
+        this.h().e(a, b, c)
+    };
+    D.la = function (a, b, c) {
+        this.h().k(a, b, c)
+    };
+    D.ba = function (a, b) {
+        return this.h().getKey(a, b)
+    };
+    D.ca = function (a, b) {
+        return this.h().C(a, b)
+    };
+    D.S = function (a) {
+        this.h().A(a)
+    };
+    D.T = function (a) {
+        this.h().B(a)
+    };
+    D.W = function () {
+        return new Yc
+    };
+    D.K = function (a) {
+        a && this.get(jb)[m](a[C]())
+    };
+    D.P = function () {
+        this.set(jb, [])
+    };
+    D.L = function (a) {
+        a && this.get(kb)[m](a[C]())
+    };
+    D.Q = function () {
+        this.set(kb, [])
+    };
+    D.N = function (a, b, c, d, e) {
+        if (a && b) {
+            a = [a, b[C]()][A](":");
+            if (d || e) a = [a, d, e][A](":");
+            d = this.get(ib);
+            d.splice(c ? 0 : d[u], 0, a)
+        }
+    };
+    D.R = function () {
+        this.set(ib, [])
+    };
+    D.U = function (a) {
+        this.a[ja]();
+        var b = this.get(N),
+            c = td(this.a);
+        this.set(N, a);
+        this.a.i();
+        sd(this.a, c);
+        this.set(N, b)
+    };
+    D.ra = function () {
+        return J.referrer
+    };
+    D.m = function () {
+        this.a[ja]()
+    };
+    D.ja = function (a) {
+        a && a != "" && (this.set(zb, a), this.a.f("var"))
+    };
+    var Gd = function (a) {
+            a.get(Wb) !== "trans" && a.b(Ib, 0) >= 500 && a[pa]();
+            if (a.get(Wb) === "event") {
+                var b = (new Date).getTime(),
+                    c = a.b(Jb, 0),
+                    d = a.b(Eb, 0),
+                    c = k[ka](0.2 * ((b - (c != d ? c : c * 1E3)) / 1E3));
+                c > 0 && (a.set(Jb, b), a.set(Q, k.min(10, a.b(Q, 0) + c)));
+                a.b(Q, 0) <= 0 && a[pa]()
+            }
+        },
+        Id = function (a) {
+            a.get(Wb) === "event" && a.set(Q, k.max(0, a.b(Q, 10) - 1))
+        };
+    var Jd = function () {
+            var a = [];
+            this.add = function (b, c, d) {
+                d && (c = F("" + c));
+                a[m](b + "=" + c)
+            };
+            this.toString = function () {
+                return a[A]("&")
+            }
+        },
+        Kd = function (a, b) {
+            (b || a.get(gb) != 2) && a.n(Ib)
+        },
+        Ld = function (a, b) {
+            b.add("utmwv", "5.2.2");
+            b.add("utms", a.get(Ib));
+            b.add("utmn", va());
+            var c = J[x].hostname;
+            E(c) || b.add("utmhn", c, h);
+            c = a.get(eb);
+            c != 100 && b.add("utmsp", c, h)
+        },
+        Nd = function (a, b) {
+            b.add("utmac", a.get(Ia));
+            a.get(Zb) && b.add("utmni", 1);
+            Md(a, b);
+            K.q && b.add("aip", 1);
+            b.add("utmu", Mc.Ea())
+        },
+        Md = function (a, b) {
+            function c(a, b) {
+                b && d[m](a + "=" + b + ";")
+            }
+            var d = [];
+            c("__utma", yc(a));
+            c("__utmz", Ec(a, j));
+            c("__utmv", Bc(a, h));
+            c("__utmx", td(a));
+            b.add("utmcc", d[A]("+"), h)
+        },
+        Od = function (a, b) {
+            a.get(Ta) && (b.add("utmcs", a.get(xb), h), b.add("utmsr", a.get(sb)), b.add("utmsc", a.get(tb)), b.add("utmul", a.get(wb)), b.add("utmje", a.get(ub)), b.add("utmfl", a.get(vb), h))
+        },
+        Pd = function (a, b) {
+            a.get(Wa) && a.get(pb) && b.add("utmdt", a.get(pb), h);
+            b.add("utmhid", a.get(rb));
+            b.add("utmr", Ea(a.get(qb), a.get(N)), h);
+            b.add("utmp", F(a.get(ob), h), h)
+        },
+        Qd = function (a, b) {
+            for (var c = a.get(mb), d = a.get(nb), e = a.get(O) || [], f = 0; f < e[u]; f++) {
+                var l = e[f];
+                l && (c || (c = new Yc), c.e(8, f, l[r]), c.e(9, f, l[la]), l[qa] != 3 && c.e(11, f, "" + l[qa]))
+            }!E(a.get($b)) && !E(a.get(ac), h) && (c || (c = new Yc), c.e(5, 1, a.get($b)), c.e(5, 2, a.get(ac)), e = a.get(bc), e != g && c.e(5, 3, e), e = a.get(cc), e != g && c.k(5, 1, e));
+            c ? b.add("utme", c.ua(d), h) : d && b.add("utme", d.o(), h)
+        },
+        Rd = function (a, b, c) {
+            var d = new Jd;
+            Kd(a, c);
+            Ld(a, d);
+            d.add("utmt", "tran");
+            d.add("utmtid", b.id_, h);
+            d.add("utmtst", b.affiliation_, h);
+            d.add("utmtto", b.total_, h);
+            d.add("utmttx", b.tax_, h);
+            d.add("utmtsp", b.shipping_, h);
+            d.add("utmtci", b.city_, h);
+            d.add("utmtrg", b.state_, h);
+            d.add("utmtco", b.country_, h);
+            !c && Nd(a, d);
+            return d[t]()
+        },
+        Sd = function (a, b, c) {
+            var d = new Jd;
+            Kd(a, c);
+            Ld(a, d);
+            d.add("utmt", "item");
+            d.add("utmtid", b.transId_, h);
+            d.add("utmipc", b.sku_, h);
+            d.add("utmipn", b.name_, h);
+            d.add("utmiva", b.category_, h);
+            d.add("utmipr", b.price_, h);
+            d.add("utmiqt", b.quantity_, h);
+            !c && Nd(a, d);
+            return d[t]()
+        },
+        Td = function (a, b) {
+            var c = a.get(Wb);
+            if (c == "page") c = new Jd, Kd(a, b), Ld(a, c), Qd(a, c), Od(a, c), Pd(a, c), b || Nd(a, c), c = [c[t]()];
+            else if (c == "event") c = new Jd, Kd(a, b), Ld(a, c), c.add("utmt", "event"), Qd(a, c), Od(a, c), Pd(a, c), !b && Nd(a, c), c = [c[t]()];
+            else if (c == "var") c = new Jd, Kd(a, b), Ld(a, c), c.add("utmt", "var"), !b && Nd(a, c), c = [c[t]()];
+            else if (c == "trans") for (var c = [], d = a.get(lb), e = 0; e < d[u]; ++e) {
+                c[m](Rd(a, d[e], b));
+                for (var f = d[e].items_, l = 0; l < f[u]; ++l) c[m](Sd(a, f[l], b))
+            } else c == "social" ? b ? c = [] : (c = new Jd, Kd(a, b), Ld(a, c), c.add("utmt", "social"), c.add("utmsn", a.get(dc), h), c.add("utmsa", a.get(ec), h), c.add("utmsid", a.get(hc), h), Qd(a, c), Od(a, c), Pd(a, c), Nd(a, c), c = [c[t]()]) : c = [];
+            return c
+        },
+        Hd = function (a) {
+            var b, c = a.get(gb),
+                d = a.get(Yb),
+                e = d && d.Aa,
+                f = 0;
+            if (c == 0 || c == 2) {
+                var l = a.get(fb) + "?";
+                b = Td(a, h);
+                for (var o = 0, q = b[u]; o < q; o++) Ga(b[o], e, l, h), f++
+            }
+            if (c == 1 || c == 2) {
+                b = Td(a);
+                o = 0;
+                for (q = b[u]; o < q; o++) try {
+                    Ga(b[o], e), f++
+                } catch (s) {
+                    s && Fa(s[r], g, s.message)
+                }
+            }
+            if (d) d.j = f
+        };
+    var Ud = "https:" == J[x].protocol ? "https://ssl.google-analytics.com"; : "http://www.google-analytics.com";,
+        Vd = function (a) {
+            ga(this, "len");
+            this.message = a + "-8192"
+        },
+        Wd = function (a) {
+            ga(this, "ff2post");
+            this.message = a + "-2036"
+        },
+        Ga = function (a, b, c, d) {
+            b = b || wa;
+            if (d || a[u] <= 2036) Xd(a, b, c);
+            else if (a[u] <= 8192) {
+                if (V[ra].userAgent[p]("Firefox") >= 0 && ![].reduce) throw new Wd(a[u]);
+                Yd(a, b) || Zd(a, b)
+            } else throw new Vd(a[u]);
+        },
+        Xd = function (a, b, c) {
+            var c = c || Ud + "/__utm.gif?",
+                d = new Image(1, 1);
+            d.src = c + a;
+            fa(d, function () {
+                fa(d, i);
+                d.onerror = i;
+                b()
+            });
+            d.onerror = function () {
+                fa(d, i);
+                d.onerror = i;
+                b()
+            }
+        },
+        Yd = function (a, b) {
+            var c, d = Ud + "/p/__utm.gif",
+                e = V.XDomainRequest;
+            if (e) c = new e, c.open("POST", d);
+            else if (e = V.XMLHttpRequest) e = new e, "withCredentials" in e && (c = e, c.open("POST", d, h), c.setRequestHeader("Content-Type", "text/plain"));
+            if (c) return c.onreadystatechange = function () {
+                c.readyState == 4 && (b(), c = i)
+            }, c.send(a), h
+        },
+        Zd = function (a, b) {
+            if (J.body) {
+                a = ba(a);
+                try {
+                    var c = J.createElement('<iframe name="' + a + '"></iframe>')
+                } catch (d) {
+                    c = J.createElement("iframe"), ga(c, a)
+                }
+                c.height = "0";
+                c.width = "0";
+                c.style.display = "none";
+                c.style.visibility = "hidden";
+                var e = J[x],
+                    e = Ud + "/u/post_iframe.html#" + ba(e.protocol + "//" + e[oa] + "/favicon.ico"),
+                    f = function () {
+                        c.src = "";
+                        c.parentNode && c.parentNode.removeChild(c)
+                    };
+                xa(V, "beforeunload", f);
+                var l = j,
+                    o = 0,
+                    q = function () {
+                        if (!l) {
+                            try {
+                                if (o > 9 || c.contentWindow[x][oa] == J[x][oa]) {
+                                    l = h;
+                                    f();
+                                    ya(V, "beforeunload", f);
+                                    b();
+                                    return
+                                }
+                            } catch (a) {}
+                            o++;
+                            da(q, 200)
+                        }
+                    };
+                xa(c, "load", q);
+                J.body.appendChild(c);
+                c.src = e
+            } else Nc(function () {
+                Zd(a, b)
+            }, 100)
+        };
+    var Z = function () {
+            this.q = j;
+            this.D = {};
+            this.F = [];
+            this.wa = 0;
+            this._gasoCPath = this._gasoDomain = g;
+            R(Z[v], "_createTracker", Z[v].l, 55);
+            R(Z[v], "_getTracker", Z[v].ya, 0);
+            R(Z[v], "_getTrackerByName", Z[v].p, 51);
+            R(Z[v], "_getTrackers", Z[v].za, 130);
+            R(Z[v], "_anonymizeIp", Z[v].xa, 16);
+            mc()
+        };
+    D = Z[v];
+    D.ya = function (a, b) {
+        return this.l(a, g, b)
+    };
+    D.l = function (a, b, c) {
+        b && G(23);
+        c && G(67);
+        b == g && (b = "~" + K.wa++);
+        a = new S(b, a, c);
+        K.D[b] = a;
+        K.F[m](a);
+        return a
+    };
+    D.p = function (a) {
+        a = a || "";
+        return K.D[a] || K.l(g, a)
+    };
+    D.za = function () {
+        return K.F[ha](0)
+    };
+    D.xa = function () {
+        this.q = h
+    };
+    var $d = function (a) {
+            if (J.webkitVisibilityState == "prerender") return j;
+            a();
+            return h
+        };
+    var K = new Z;
+    var ae = V._gat;
+    ae && typeof ae._getTracker == "function" ? K = ae : V._gat = K;
+    var Tc = new Y;
+    (function (a) {
+        if (!$d(a)) {
+            G(123);
+            var b = j,
+                c = function () {
+                    !b && $d(a) && (G(124), b = h, ya(J, "webkitvisibilitychange", c))
+                };
+            xa(J, "webkitvisibilitychange", c)
+        }
+    })(function () {
+        var a = V._gaq,
+            b = j;
+        if (a && typeof a[m] == "function" && (b = Object[v][t].call(Object(a)) == "[object Array]", !b)) {
+            Tc = a;
+            return
+        }
+        V._gaq = Tc;
+        b && Tc[m].apply(Tc, a)
+    });
+})();

=== modified file 'lib/lp/hardwaredb/doc/hwdb-submission.txt'
--- lib/lp/hardwaredb/doc/hwdb-submission.txt	2010-10-18 22:24:59 +0000
+++ lib/lp/hardwaredb/doc/hwdb-submission.txt	2011-12-11 01:49:34 +0000
@@ -201,7 +201,7 @@
     >>> team_form['field.submission_key'] = u'unique-id-68'
     >>> valid_sample_data_path = os.path.join(
     ...     config.root,
-    ...     'lib/canonical/launchpad/scripts/tests/'
+    ...     'lib/lp/hardwaredb/scripts/tests/'
     ...     'simple_valid_hwdb_submission.xml')
     >>> valid_sample_data = StringIO(open(valid_sample_data_path).read())
     >>> valid_sample_data.filename = 'simple_valid_hwdb_submission.xml'

=== renamed file 'lib/canonical/launchpad/scripts/tests/simple_valid_hwdb_submission.xml' => 'lib/lp/hardwaredb/scripts/tests/simple_valid_hwdb_submission.xml'
=== renamed file 'lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py' => 'lib/lp/hardwaredb/scripts/tests/test_hwdb_submission_parser.py'
=== renamed file 'lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py' => 'lib/lp/hardwaredb/scripts/tests/test_hwdb_submission_processing.py'
--- lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py	2011-11-16 06:15:58 +0000
+++ lib/lp/hardwaredb/scripts/tests/test_hwdb_submission_processing.py	2011-12-11 01:49:34 +0000
@@ -5101,7 +5101,7 @@
     def getSampleData(self, filename):
         """Return the submission data of a short valid submission."""
         sample_data_path = os.path.join(
-            config.root, 'lib', 'canonical', 'launchpad', 'scripts',
+            config.root, 'lib', 'lp', 'hardwaredb', 'scripts',
             'tests', 'simple_valid_hwdb_submission.xml')
         return open(sample_data_path).read()
 

=== modified file 'lib/lp/poppy/tests/test_twistedftp.py'
--- lib/lp/poppy/tests/test_twistedftp.py	2011-05-02 12:55:06 +0000
+++ lib/lp/poppy/tests/test_twistedftp.py	2011-12-11 01:49:34 +0000
@@ -7,24 +7,22 @@
 
 import os
 
-from testtools.deferredruntest import (
-    AsynchronousDeferredRunTest,
-    )
+from testtools.deferredruntest import AsynchronousDeferredRunTest
 import transaction
 from twisted.protocols import ftp
 from zope.component import getUtility
 
 from canonical.config import config
-from canonical.launchpad.ftests.keys_for_tests import gpgkeysdir
-from canonical.launchpad.interfaces.gpghandler import IGPGHandler
 from canonical.testing.layers import ZopelessDatabaseLayer
-
 from lp.poppy.twistedftp import PoppyFileWriter
 from lp.registry.interfaces.gpg import (
     GPGKeyAlgorithm,
-    IGPGKeySet)
+    IGPGKeySet,
+    )
 from lp.services.database.isolation import check_no_transaction
+from lp.services.gpg.interfaces import IGPGHandler
 from lp.testing import TestCaseWithFactory
+from lp.testing.gpgkeys import gpgkeysdir
 from lp.testing.keyserver import KeyServerTac
 
 

=== modified file 'lib/lp/poppy/twistedconfigreset.py'
--- lib/lp/poppy/twistedconfigreset.py	2011-05-14 12:17:46 +0000
+++ lib/lp/poppy/twistedconfigreset.py	2011-12-11 01:49:34 +0000
@@ -11,11 +11,10 @@
 from twisted.application.service import Service
 from twisted.internet import task
 from twisted.internet.error import AlreadyCancelled
-
 from zope.component import getUtility
 from zope.component.interfaces import ComponentLookupError
 
-from canonical.launchpad.interfaces.gpghandler import IGPGHandler
+from lp.services.gpg.interfaces import IGPGHandler
 
 
 class GPGHandlerConfigResetJob(Service):

=== modified file 'lib/lp/poppy/twistedftp.py'
--- lib/lp/poppy/twistedftp.py	2011-04-29 06:47:43 +0000
+++ lib/lp/poppy/twistedftp.py	2011-12-11 01:49:34 +0000
@@ -13,20 +13,23 @@
 import os
 import tempfile
 
-from twisted.application import service, strports
-from twisted.cred import checkers, credentials
-from twisted.cred.portal import IRealm, Portal
+from twisted.application import (
+    service,
+    strports,
+    )
+from twisted.cred import (
+    checkers,
+    credentials,
+    )
+from twisted.cred.portal import (
+    IRealm,
+    Portal,
+    )
 from twisted.internet import defer
 from twisted.protocols import ftp
 from twisted.python import filepath
-
+from zope.component import getUtility
 from zope.interface import implements
-from zope.component import getUtility
-
-from canonical.launchpad.interfaces.gpghandler import (
-    GPGVerificationError,
-    IGPGHandler,
-    )
 
 from canonical.config import config
 from lp.poppy import get_poppy_root
@@ -34,6 +37,10 @@
 from lp.poppy.hooks import Hooks
 from lp.registry.interfaces.gpg import IGPGKeySet
 from lp.services.database import read_transaction
+from lp.services.gpg.interfaces import (
+    GPGVerificationError,
+    IGPGHandler,
+    )
 
 
 class PoppyAccessCheck:

=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py	2011-12-09 12:52:34 +0000
+++ lib/lp/registry/browser/person.py	2011-12-11 01:49:34 +0000
@@ -143,10 +143,6 @@
     IEmailAddress,
     IEmailAddressSet,
     )
-from canonical.launchpad.interfaces.gpghandler import (
-    GPGKeyNotFoundError,
-    IGPGHandler,
-    )
 from canonical.launchpad.interfaces.launchpad import (
     INotificationRecipientSet,
     UnknownRecipientError,
@@ -267,6 +263,10 @@
     )
 from lp.services.fields import LocationField
 from lp.services.geoip.interfaces import IRequestPreferredLanguages
+from lp.services.gpg.interfaces import (
+    GPGKeyNotFoundError,
+    IGPGHandler,
+    )
 from lp.services.messages.interfaces.message import (
     IDirectEmailAuthorization,
     QuotaReachedError,

=== modified file 'lib/lp/registry/browser/tests/test_mailinglists.py'
--- lib/lp/registry/browser/tests/test_mailinglists.py	2010-12-15 20:40:26 +0000
+++ lib/lp/registry/browser/tests/test_mailinglists.py	2011-12-11 01:49:34 +0000
@@ -28,11 +28,12 @@
     """Verify the content in +mailing-list-portlet."""
 
     def makeTeamWithMailingList(self, name=None, owner=None, visibility=None):
+        if owner is None:
+            owner = self.factory.makePerson()
         team = self.factory.makeTeam(
             name=name, owner=owner, visibility=visibility)
-        login_person(team.teamowner)
-        team_list = self.factory.makeMailingList(
-            team=team, owner=team.teamowner)
+        login_person(owner)
+        self.factory.makeMailingList(team=team, owner=owner)
         return team
 
 
@@ -129,10 +130,11 @@
 
     def test_private_message_message_without_list(self):
         # Private teams have private archives.
+        owner = self.factory.makePerson()
         team = self.factory.makeTeam(
-            visibility=PersonVisibility.PRIVATE)
-        login_person(team.teamowner)
+            owner=owner, visibility=PersonVisibility.PRIVATE)
+        login_person(owner)
         view = create_initialized_view(
-            team, name='+mailinglist', principal=team.teamowner)
+            team, name='+mailinglist', principal=owner)
         element = find_tag_by_id(view(), 'mailing-list-archive')
         self.assertEqual('private', extract_text(element))

=== modified file 'lib/lp/registry/browser/tests/test_person_view.py'
--- lib/lp/registry/browser/tests/test_person_view.py	2011-11-20 07:23:45 +0000
+++ lib/lp/registry/browser/tests/test_person_view.py	2011-12-11 01:49:34 +0000
@@ -475,9 +475,11 @@
 
     def test_active_participations_with_direct_private_team(self):
         # Users cannot see private teams that they are not members of.
-        team = self.factory.makeTeam(visibility=PersonVisibility.PRIVATE)
-        login_person(team.teamowner)
-        team.addMember(self.user, team.teamowner)
+        owner = self.factory.makePerson()
+        team = self.factory.makeTeam(
+            owner=owner, visibility=PersonVisibility.PRIVATE)
+        login_person(owner)
+        team.addMember(self.user, owner)
         # The team is included in active_participations.
         login_person(self.user)
         view = create_view(
@@ -492,11 +494,13 @@
 
     def test_active_participations_with_indirect_private_team(self):
         # Users cannot see private teams that they are not members of.
-        team = self.factory.makeTeam(visibility=PersonVisibility.PRIVATE)
-        direct_team = self.factory.makeTeam(owner=team.teamowner)
-        login_person(team.teamowner)
-        direct_team.addMember(self.user, team.teamowner)
-        team.addMember(direct_team, team.teamowner)
+        owner = self.factory.makePerson()
+        team = self.factory.makeTeam(
+            owner=owner, visibility=PersonVisibility.PRIVATE)
+        direct_team = self.factory.makeTeam(owner=owner)
+        login_person(owner)
+        direct_team.addMember(self.user, owner)
+        team.addMember(direct_team, owner)
         # The team is included in active_participations.
         login_person(self.user)
         view = create_view(

=== modified file 'lib/lp/registry/browser/tests/test_product.py'
--- lib/lp/registry/browser/tests/test_product.py	2011-11-28 05:14:57 +0000
+++ lib/lp/registry/browser/tests/test_product.py	2011-12-11 01:49:34 +0000
@@ -27,6 +27,9 @@
     person_logged_in,
     TestCaseWithFactory,
     )
+from lp.testing.fixture import (
+    DemoMode,
+    )
 from lp.testing.mail_helpers import pop_notifications
 from lp.testing.service_usage_helpers import set_service_usage
 from lp.testing.views import (
@@ -164,10 +167,7 @@
         self.assertTrue(message is not None)
 
     def test_staging_message_is_demo(self):
-        config.push('staging-test', '''
-            [launchpad]
-            is_demo: true
-            ''')
+        self.useFixture(DemoMode())
         view = create_initialized_view(self.product_set, '+new')
         message = find_tag_by_id(view.render(), 'staging-message')
         self.assertEqual(None, message)

=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml	2011-12-07 21:33:14 +0000
+++ lib/lp/registry/configure.zcml	2011-12-11 01:49:34 +0000
@@ -912,8 +912,6 @@
             <allow
                 interface="lp.registry.interfaces.person.IPersonPublic"/>
             <allow
-                interface="lp.registry.interfaces.person.ITeamPublic"/>
-            <allow
                 interface="lp.registry.interfaces.person.IHasStanding"/>
             <allow
                 interface="lp.registry.interfaces.person.IPersonCommAdminWriteRestricted"/>
@@ -924,6 +922,9 @@
                 permission="launchpad.View"
                 interface="lp.registry.interfaces.person.IPersonViewRestricted"/>
             <require
+                permission="launchpad.View"
+                interface="lp.registry.interfaces.person.ITeamPublic"/>
+            <require
                 permission="launchpad.Edit"
                 interface="lp.registry.interfaces.location.ISetLocation"/>
             <require
@@ -931,9 +932,10 @@
                 interface="lp.registry.interfaces.person.IPersonEditRestricted"/>
             <require
                 permission="launchpad.Edit"
-                set_schema="lp.registry.interfaces.person.IPersonPublic
+                set_schema="lp.registry.interfaces.person.IPersonViewRestricted
+                            lp.registry.interfaces.person.IPersonPublic
                             lp.registry.interfaces.person.ITeamPublic"
-                set_attributes="displayname"/>
+                set_attributes="displayname icon logo"/>
             <require
                 permission="launchpad.Moderate"
                 set_attributes="name"/>

=== modified file 'lib/lp/registry/doc/person.txt'
--- lib/lp/registry/doc/person.txt	2011-12-09 12:52:34 +0000
+++ lib/lp/registry/doc/person.txt	2011-12-11 01:49:34 +0000
@@ -696,6 +696,7 @@
     >>> private_team = factory.makeTeam(salgado, name='private-team',
     ...     displayname='Private Team',
     ...     visibility=PersonVisibility.PRIVATE)
+    >>> private_team_owner = private_team.teamowner
     >>> bug_subscription = BugSubscription(bug=bug, person=private_team,
     ...     subscribed_by=guadamen)
 
@@ -806,7 +807,7 @@
 
 But Owner, a member of that team, will see it in the results.
 
-    >>> login_person(private_team.teamowner)
+    >>> login_person(private_team_owner)
     >>> print_people(personset.find('team'))
     Another a new team (new3): []
     Hoary Gnome Team (name21): []

=== modified file 'lib/lp/registry/doc/private-team-visibility.txt'
--- lib/lp/registry/doc/private-team-visibility.txt	2011-11-16 00:09:49 +0000
+++ lib/lp/registry/doc/private-team-visibility.txt	2011-12-11 01:49:34 +0000
@@ -107,3 +107,64 @@
     ...
     Unauthorized: (<Person at ... priv-team (Priv Team)>,
         'name', 'launchpad.LimitedView')
+
+A person with visibility to any of the branches owned by the private team will
+be granted limited view permission on the team.
+
+For private branches, a user needs to be subscribed to the branch for the
+branch (and hence team) to be visible.
+
+    >>> login_person(priv_owner)
+    >>> private_team_branch = factory.makeBranch(
+    ...     owner=priv_team, private=True)
+    >>> login_person(pub_member)
+    >>> check_permission('launchpad.LimitedView', priv_team)
+    False
+
+    >>> login_person(priv_owner)
+    >>> sub = factory.makeBranchSubscription(
+    ...     branch=private_team_branch, person=pub_member,
+    ...     subscribed_by=priv_owner)
+
+    >>> login_person(pub_member)
+    >>> check_permission('launchpad.LimitedView', priv_team)
+    True
+
+Subscribers to the teams private PPA have limited view permission.
+
+    >>> login_person(priv_owner)
+    >>> archive = factory.makeArchive(private=True, owner=priv_team)
+    >>> archive_subscriber = factory.makePerson()
+    >>> sub = archive.newSubscription(
+    ...     archive_subscriber, registrant=archive.owner)
+
+    >>> login_person(archive_subscriber)
+    >>> check_permission('launchpad.LimitedView', priv_team)
+    True
+
+Users with LimitedView can know identifying information like name,
+displayname, and unique_name, but cannot know other information like
+teamowner.
+
+    >>> print priv_team.name
+    priv-team
+
+    >>> print priv_team.displayname
+    Priv Team
+
+    >>> print priv_team.unique_displayname
+    Priv Team (priv-team)
+
+    >>> print priv_team.icon
+    None
+
+    >>> print priv_team.allmembers
+    Traceback (most recent call last):
+    ...
+    Unauthorized: (<Person at ... priv-team (Priv Team)>,
+        'allmembers', 'launchpad.View')
+
+Anonymous users do not have permission.
+    >>> login(ANONYMOUS)
+    >>> check_permission('launchpad.LimitedView', priv_team)
+    False

=== modified file 'lib/lp/registry/interfaces/person.py'
--- lib/lp/registry/interfaces/person.py	2011-12-09 12:52:34 +0000
+++ lib/lp/registry/interfaces/person.py	2011-12-11 01:49:34 +0000
@@ -15,7 +15,7 @@
     'IObjectReassignment',
     'IPerson',
     'IPersonClaim',
-    'IPersonPublic',  # Required for a monkey patch in interfaces/archive.py
+    'IPersonPublic',
     'IPersonSet',
     'IPersonSettings',
     'ISoftwareCenterAgentAPI',
@@ -657,26 +657,55 @@
         required=False, default=False)
 
 
-class IPersonPublic(IHasBranches, IHasSpecifications,
-                    IHasMergeProposals, IHasLogo, IHasMugshot, IHasIcon,
-                    IHasLocation, IHasRequestedReviews, IObjectWithLocation,
-                    IPrivacy, IHasBugs, IHasRecipes, IHasTranslationImports,
-                    IPersonSettings, IQuestionsPerson):
-    """Public attributes for a Person."""
+class IPersonPublic(IPrivacy):
+    """Public attributes for a Person.
+
+    Very few attributes on a person can be public because private teams
+    are also persons. The public attributes are generally information
+    needed by the system to determine if the principal in the current
+    interaction can work with the object.
+    """
 
     id = Int(title=_('ID'), required=True, readonly=True)
-    account = Object(schema=IAccount)
-    accountID = Int(title=_('Account ID'), required=True, readonly=True)
-    password = PasswordField(
-        title=_('Password'), required=True, readonly=False)
-    karma = exported(
-        Int(title=_('Karma'), readonly=True,
-            description=_('The cached total karma for this person.')))
-    homepage_content = exported(
-        Text(title=_("Homepage Content"), required=False,
-            description=_(
-                "The content of your profile page. Use plain text, "
-                "paragraphs are preserved and URLs are linked in pages.")))
+    # This is redefined from IPrivacy.private because the attribute is
+    # read-only. It is a summary of the team's visibility.
+    private = exported(Bool(
+            title=_("This team is private"),
+            readonly=True, required=False,
+            description=_("Private teams are visible only to "
+                          "their members.")))
+    is_valid_person = Bool(
+        title=_("This is an active user and not a team."), readonly=True)
+    is_valid_person_or_team = exported(
+        Bool(title=_("This is an active user or a team."), readonly=True),
+        exported_as='is_valid')
+    is_merge_pending = exported(Bool(
+        title=_("Is this person due to be merged with another?"),
+        required=False, default=False))
+    is_team = exported(
+        Bool(title=_('Is this object a team?'), readonly=True))
+
+
+class IPersonLimitedView(IHasIcon, IHasLogo):
+    """IPerson attributes that require launchpad.LimitedView permission."""
+
+    name = exported(
+        PersonNameField(
+            title=_('Name'), required=True, readonly=False,
+            constraint=name_validator,
+            description=_(
+                "A short unique name, beginning with a lower-case "
+                "letter or number, and containing only letters, "
+                "numbers, dots, hyphens, or plus signs.")))
+    displayname = exported(
+        StrippedTextLine(
+            title=_('Display Name'), required=True, readonly=False,
+            description=_(
+                "Your name as you would like it displayed throughout "
+                "Launchpad. Most people use their full name here.")),
+        exported_as='display_name')
+    unique_displayname = TextLine(
+        title=_('Return a string of the form $displayname ($name).'))
     # NB at this stage we do not allow individual people to have their own
     # icon, only teams get that. People can however have a logo and mugshot
     # The icon is only used for teams; that's why we use /@@/team as the
@@ -702,6 +731,26 @@
                 "be no bigger than 50kb in size.")))
     logoID = Int(title=_('Logo ID'), required=True, readonly=True)
 
+
+class IPersonViewRestricted(IHasBranches, IHasSpecifications,
+                    IHasMergeProposals, IHasMugshot,
+                    IHasLocation, IHasRequestedReviews, IObjectWithLocation,
+                    IHasBugs, IHasRecipes, IHasTranslationImports,
+                    IPersonSettings, IQuestionsPerson):
+    """IPerson attributes that require launchpad.View permission."""
+    account = Object(schema=IAccount)
+    accountID = Int(title=_('Account ID'), required=True, readonly=True)
+    password = PasswordField(
+        title=_('Password'), required=True, readonly=False)
+    karma = exported(
+        Int(title=_('Karma'), readonly=True,
+            description=_('The cached total karma for this person.')))
+    homepage_content = exported(
+        Text(title=_("Homepage Content"), required=False,
+            description=_(
+                "The content of your profile page. Use plain text, "
+                "paragraphs are preserved and URLs are linked in pages.")))
+
     mugshot = exported(MugshotImageUpload(
         title=_("Mugshot"), required=False,
         default_image_resource='/@@/person-mugshot',
@@ -766,13 +815,6 @@
     # Properties of the Person object.
     karma_category_caches = Attribute(
         'The caches of karma scores, by karma category.')
-    is_team = exported(
-        Bool(title=_('Is this object a team?'), readonly=True))
-    is_valid_person = Bool(
-        title=_("This is an active user and not a team."), readonly=True)
-    is_valid_person_or_team = exported(
-        Bool(title=_("This is an active user or a team."), readonly=True),
-        exported_as='is_valid')
     is_probationary = exported(
         Bool(title=_("Is this a probationary user?"), readonly=True))
     is_ubuntu_coc_signer = exported(
@@ -879,7 +921,6 @@
         exported_as='team_owner')
     teamownerID = Int(title=_("The Team Owner's ID or None"), required=False,
                       readonly=True)
-
     preferredemail = exported(
         Reference(title=_("Preferred email address"),
                description=_("The preferred email address for this person. "
@@ -987,18 +1028,6 @@
             readonly=True, required=False,
             value_type=Reference(schema=Interface)))  # HWSubmission
 
-    # This is redefined from IPrivacy.private because the attribute is
-    # read-only. It is a summary of the team's visibility.
-    private = exported(Bool(
-            title=_("This team is private"),
-            readonly=True, required=False,
-            description=_("Private teams are visible only to "
-                          "their members.")))
-
-    is_merge_pending = exported(Bool(
-        title=_("Is this person due to be merged with another?"),
-        required=False, default=False))
-
     administrated_teams = Attribute(
         u"the teams that this person/team is an administrator of.")
 
@@ -1463,32 +1492,6 @@
         :return: a boolean.
         """
 
-
-class IPersonLimitedView(Interface):
-    """IPerson attributes that require launchpad.LimitedView permission."""
-
-    name = exported(
-        PersonNameField(
-            title=_('Name'), required=True, readonly=False,
-            constraint=name_validator,
-            description=_(
-                "A short unique name, beginning with a lower-case "
-                "letter or number, and containing only letters, "
-                "numbers, dots, hyphens, or plus signs.")))
-    displayname = exported(
-        StrippedTextLine(
-            title=_('Display Name'), required=True, readonly=False,
-            description=_(
-                "Your name as you would like it displayed throughout "
-                "Launchpad. Most people use their full name here.")),
-        exported_as='display_name')
-    unique_displayname = TextLine(
-        title=_('Return a string of the form $displayname ($name).'))
-
-
-class IPersonViewRestricted(Interface):
-    """IPerson attributes that require launchpad.View permission."""
-
     active_member_count = Attribute(
         "The number of real people who are members of this team.")
     # activemembers.value_type.schema will be set to IPerson once
@@ -2565,18 +2568,19 @@
     ]:
     IPersonViewRestricted[name].value_type.schema = IPerson
 
-IPersonPublic['sub_teams'].value_type.schema = ITeam
-IPersonPublic['super_teams'].value_type.schema = ITeam
+IPersonViewRestricted['sub_teams'].value_type.schema = ITeam
+IPersonViewRestricted['super_teams'].value_type.schema = ITeam
 # XXX: salgado, 2008-08-01: Uncomment these when teams_*participated_in are
 # exported again.
-# IPersonPublic['teams_participated_in'].value_type.schema = ITeam
-# IPersonPublic['teams_indirectly_participated_in'].value_type.schema = ITeam
+# IPersonViewRestricted['teams_participated_in'].value_type.schema = ITeam
+# IPersonViewRestricted[
+#   'teams_indirectly_participated_in'].value_type.schema = ITeam
 
 # Fix schema of operation parameters. We need zope.deferredimport!
 params_to_fix = [
     # XXX: salgado, 2008-08-01: Uncomment these when they are exported again.
-    # (IPersonPublic['findPathToTeam'], 'team'),
-    # (IPersonPublic['inTeam'], 'team'),
+    # (IPersonViewRestricted['findPathToTeam'], 'team'),
+    # (IPersonViewRestricted['inTeam'], 'team'),
     (IPersonEditRestricted['join'], 'team'),
     (IPersonEditRestricted['leave'], 'team'),
     (IPersonEditRestricted['addMember'], 'person'),

=== modified file 'lib/lp/registry/interfaces/role.py'
--- lib/lp/registry/interfaces/role.py	2011-11-16 07:25:58 +0000
+++ lib/lp/registry/interfaces/role.py	2011-12-11 01:49:34 +0000
@@ -119,7 +119,8 @@
     def inTeam(team):
         """Is this person a member or the owner of `team`?
 
-        Passed through to the same method in 'IPersonPublic'.
+        Passed through to the *unproxied* same method in
+        `IPersonViewRestricted`.
         """
 
     def isOwner(obj):

=== modified file 'lib/lp/registry/model/codeofconduct.py'
--- lib/lp/registry/model/codeofconduct.py	2010-08-20 20:31:18 +0000
+++ lib/lp/registry/model/codeofconduct.py	2011-12-11 01:49:34 +0000
@@ -31,10 +31,6 @@
     quote,
     SQLBase,
     )
-from canonical.launchpad.interfaces.gpghandler import (
-    GPGVerificationError,
-    IGPGHandler,
-    )
 from canonical.launchpad.webapp import canonical_url
 from lp.app.errors import NotFoundError
 from lp.registry.interfaces.codeofconduct import (
@@ -45,6 +41,10 @@
     ISignedCodeOfConductSet,
     )
 from lp.registry.interfaces.gpg import IGPGKeySet
+from lp.services.gpg.interfaces import (
+    GPGVerificationError,
+    IGPGHandler,
+    )
 from lp.services.mail.sendmail import (
     format_address,
     simple_sendmail,

=== modified file 'lib/lp/registry/model/gpgkey.py'
--- lib/lp/registry/model/gpgkey.py	2010-08-20 20:31:18 +0000
+++ lib/lp/registry/model/gpgkey.py	2011-12-11 01:49:34 +0000
@@ -21,12 +21,12 @@
     SQLBase,
     sqlvalues,
     )
-from canonical.launchpad.interfaces.gpghandler import IGPGHandler
 from lp.registry.interfaces.gpg import (
     GPGKeyAlgorithm,
     IGPGKey,
     IGPGKeySet,
     )
+from lp.services.gpg.interfaces import IGPGHandler
 
 
 class GPGKey(SQLBase):

=== modified file 'lib/lp/registry/model/personroles.py'
--- lib/lp/registry/model/personroles.py	2011-11-16 07:25:58 +0000
+++ lib/lp/registry/model/personroles.py	2011-12-11 01:49:34 +0000
@@ -11,6 +11,7 @@
     getUtility,
     )
 from zope.interface import implements
+from zope.security.proxy import removeSecurityProxy
 
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
@@ -29,7 +30,8 @@
     def __init__(self, person):
         self.person = person
         self._celebrities = getUtility(ILaunchpadCelebrities)
-        self.inTeam = self.person.inTeam
+        # Use an unproxied inTeam() method for security checks.
+        self.inTeam = removeSecurityProxy(self.person).inTeam
 
     def __getattr__(self, name):
         """Handle all in_* attributes."""
@@ -39,7 +41,7 @@
             raise AttributeError(errortext)
         attribute = name[len(prefix):]
         try:
-            return self.person.inTeam(getattr(self._celebrities, attribute))
+            return self.inTeam(getattr(self._celebrities, attribute))
         except AttributeError:
             raise AttributeError(errortext)
 
@@ -49,28 +51,28 @@
 
     def isOwner(self, obj):
         """See IPersonRoles."""
-        return self.person.inTeam(obj.owner)
+        return self.inTeam(obj.owner)
 
     def isBugSupervisor(self, obj):
         """See IPersonRoles."""
         return (IHasBugSupervisor.providedBy(obj)
-                and self.person.inTeam(obj.bug_supervisor))
+                and self.inTeam(obj.bug_supervisor))
 
     def isSecurityContact(self, obj):
         """See IPersonRoles."""
         return (IHasSecurityContact.providedBy(obj)
-                and self.person.inTeam(obj.security_contact))
+                and self.inTeam(obj.security_contact))
 
     def isDriver(self, obj):
         """See IPersonRoles."""
-        return self.person.inTeam(obj.driver)
+        return self.inTeam(obj.driver)
 
     def isOneOfDrivers(self, obj):
         """See IPersonRoles."""
         if not IHasDrivers.providedBy(obj):
             return self.isDriver(obj)
         for driver in obj.drivers:
-            if self.person.inTeam(driver):
+            if self.inTeam(driver):
                 return True
         return False
 
@@ -78,6 +80,6 @@
         """See IPersonRoles."""
         for attr in attributes:
             role = getattr(obj, attr)
-            if self.person.inTeam(role):
+            if self.inTeam(role):
                 return True
         return False

=== modified file 'lib/lp/registry/scripts/teamparticipation.py'
--- lib/lp/registry/scripts/teamparticipation.py	2011-11-04 22:10:46 +0000
+++ lib/lp/registry/scripts/teamparticipation.py	2011-12-11 01:49:34 +0000
@@ -7,8 +7,8 @@
 __all__ = [
     "check_teamparticipation_circular",
     "check_teamparticipation_consistency",
-    "check_teamparticipation_self",
     "fetch_team_participation_info",
+    "fix_teamparticipation_consistency",
     ]
 
 from collections import (
@@ -26,17 +26,29 @@
 import transaction
 from zope.component import getUtility
 
-from canonical.database.sqlbase import quote
+from canonical.database.sqlbase import (
+    quote,
+    sqlvalues,
+    )
 from canonical.launchpad.webapp.interfaces import (
     IStoreSelector,
     MAIN_STORE,
+    MASTER_FLAVOR,
     SLAVE_FLAVOR,
     )
 from lp.registry.interfaces.teammembership import ACTIVE_STATES
 from lp.services.scripts.base import LaunchpadScriptFailure
 
 
-def get_store():
+def get_master_store():
+    """Return a master store.
+
+    Errors in `TeamPartipation` must be fixed in the master.
+    """
+    return getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)
+
+
+def get_slave_store():
     """Return a slave store.
 
     Errors in `TeamPartipation` can be detected using a replicated copy.
@@ -44,26 +56,6 @@
     return getUtility(IStoreSelector).get(MAIN_STORE, SLAVE_FLAVOR)
 
 
-def check_teamparticipation_self(log):
-    """Check self-participation.
-
-    All people and teams should participate in themselves.
-    """
-    query = """
-        SELECT id, name
-          FROM Person
-         WHERE id NOT IN (
-            SELECT person FROM TeamParticipation
-             WHERE person = team)
-           AND merged IS NULL
-        """
-    non_self_participants = list(get_store().execute(query))
-    if len(non_self_participants) > 0:
-        log.warn(
-            "Some people/teams are not members of themselves: %s",
-            non_self_participants)
-
-
 def check_teamparticipation_circular(log):
     """Check circular references.
 
@@ -77,7 +69,7 @@
            AND tp.person = tp2.team
            AND tp.id != tp2.id;
         """
-    circular_references = list(get_store().execute(query))
+    circular_references = list(get_slave_store().execute(query))
     if len(circular_references) > 0:
         raise LaunchpadScriptFailure(
             "Circular references found: %s" % circular_references)
@@ -117,7 +109,7 @@
 
 def fetch_team_participation_info(log):
     """Fetch people, teams, memberships and participations."""
-    slurp = partial(execute_long_query, get_store(), log, 10000)
+    slurp = partial(execute_long_query, get_slave_store(), log, 10000)
 
     people = dict(
         slurp(
@@ -209,3 +201,47 @@
             get_repr(error.team), error.type, people_repr)
 
     return errors
+
+
+def fix_teamparticipation_consistency(log, errors):
+    """Fix missing or spurious participations.
+
+    This function does not consult `TeamMembership` at all, so it /may/
+    introduce another participation inconsistency if the records that are the
+    subject of the given errors have been modified since being checked.
+
+    :param errors: An iterable of `ConsistencyError` tuples.
+    """
+    sql_missing = (
+        """
+        INSERT INTO TeamParticipation (team, person)
+        SELECT %(team)s, %(person)s
+        EXCEPT
+        SELECT team, person
+          FROM TeamParticipation
+         WHERE team = %(team)s
+           AND person = %(person)s
+        """)
+    sql_spurious = (
+        """
+        DELETE FROM TeamParticipation
+         WHERE team = %(team)s
+           AND person IN %(people)s
+        """)
+    store = get_master_store()
+    for error in errors:
+        if error.type == "missing":
+            for person in error.people:
+                statement = sql_missing % sqlvalues(
+                    team=error.team, person=person)
+                log.debug(statement)
+                store.execute(statement)
+                transaction.commit()
+        elif error.type == "spurious":
+            statement = sql_spurious % sqlvalues(
+                team=error.team, people=error.people)
+            log.debug(statement)
+            store.execute(statement)
+            transaction.commit()
+        else:
+            log.warn("Unrecognized error: %r", error)

=== modified file 'lib/lp/registry/stories/gpg-coc/xx-gpg-coc.txt'
--- lib/lp/registry/stories/gpg-coc/xx-gpg-coc.txt	2011-12-08 19:18:27 +0000
+++ lib/lp/registry/stories/gpg-coc/xx-gpg-coc.txt	2011-12-11 01:49:34 +0000
@@ -87,9 +87,9 @@
 Import the secret keys needed for this test:
 
     >>> from zope.component import getUtility
-    >>> from canonical.launchpad.interfaces.gpghandler import IGPGHandler
+    >>> from lp.services.gpg.interfaces import IGPGHandler
 
-    >>> from canonical.launchpad.ftests import (
+    >>> from lp.testing.gpgkeys import (
     ...     import_secret_test_key, decrypt_content)
 
 
@@ -518,7 +518,6 @@
 Get the token from the body of the email sent.
 
     >>> import email, re
-    >>> from canonical.launchpad.ftests import decrypt_content
     >>> from lp.services.mail import stub
     >>> from_addr, to_addrs, raw_msg = stub.test_emails.pop()
     >>> msg = email.message_from_string(raw_msg)

=== modified file 'lib/lp/registry/stories/webservice/xx-derivedistroseries.txt'
--- lib/lp/registry/stories/webservice/xx-derivedistroseries.txt	2011-11-15 01:01:55 +0000
+++ lib/lp/registry/stories/webservice/xx-derivedistroseries.txt	2011-12-11 01:49:34 +0000
@@ -19,6 +19,7 @@
     >>> soyuz_team = factory.makeTeam(
     ...     name='soyuz-team',
     ...     subscription_policy=TeamSubscriptionPolicy.RESTRICTED)
+    >>> soyuz_team_owner = soyuz_team.teamowner
     >>> parent_series = factory.makeDistroSeries(name="parentseries")
     >>> arch = factory.makeDistroArchSeries(distroseries=parent_series)
     >>> removeSecurityProxy(parent_series).nominatedarchindep = arch
@@ -41,7 +42,7 @@
     >>> logout()
 
     >>> soyuz_team_webservice = webservice_for_person(
-    ...     soyuz_team.teamowner, permission=OAuthPermission.WRITE_PUBLIC)
+    ...     soyuz_team_owner, permission=OAuthPermission.WRITE_PUBLIC)
 
 
 Calling

=== modified file 'lib/lp/registry/tests/test_person_vocabularies.py'
--- lib/lp/registry/tests/test_person_vocabularies.py	2011-11-07 00:38:37 +0000
+++ lib/lp/registry/tests/test_person_vocabularies.py	2011-12-11 01:49:34 +0000
@@ -311,9 +311,10 @@
     def test_private_team_cannot_be_a_member_of_itself(self):
         # A private team should be filtered by the vocab.extra_clause
         # when provided a search term.
+        owner = self.factory.makePerson()
         team = self.factory.makeTeam(
-            visibility=PersonVisibility.PRIVATE)
-        login_person(team.teamowner)
+            owner=owner, visibility=PersonVisibility.PRIVATE)
+        login_person(owner)
         self.assertNotIn(team, self.searchVocabulary(team, team.name))
 
 

=== modified file 'lib/lp/registry/tests/test_team_webservice.py'
--- lib/lp/registry/tests/test_team_webservice.py	2010-10-21 00:07:32 +0000
+++ lib/lp/registry/tests/test_team_webservice.py	2011-12-11 01:49:34 +0000
@@ -5,8 +5,6 @@
 
 import httplib
 
-from zope.security.proxy import removeSecurityProxy
-
 from lazr.restfulclient.errors import HTTPError
 
 from canonical.testing.layers import DatabaseFunctionalLayer
@@ -79,8 +77,9 @@
         # Calling person.join with a team that has an open membership
         # subscription policy should add that that user to the team.
         self.person = self.factory.makePerson(name='test-person')
-        self.team = self.factory.makeTeam(name='test-team')
-        login_person(self.team.teamowner)
+        owner = self.factory.makePerson()
+        self.team = self.factory.makeTeam(name='test-team', owner=owner)
+        login_person(owner)
         self.team.subscriptionpolicy = TeamSubscriptionPolicy.OPEN
         logout()
 
@@ -88,7 +87,7 @@
         test_person = launchpad.people['test-person']
         test_team = launchpad.people['test-team']
         test_person.join(team=test_team.self_link)
-        login_person(self.team.teamowner)
+        login_person(owner)
         self.assertEqual(
             ['test-team'],
             [membership.team.name

=== modified file 'lib/lp/registry/tests/test_teammembership.py'
--- lib/lp/registry/tests/test_teammembership.py	2011-11-21 05:03:25 +0000
+++ lib/lp/registry/tests/test_teammembership.py	2011-12-11 01:49:34 +0000
@@ -59,9 +59,9 @@
 from lp.registry.scripts.teamparticipation import (
     check_teamparticipation_circular,
     check_teamparticipation_consistency,
-    check_teamparticipation_self,
     ConsistencyError,
     fetch_team_participation_info,
+    fix_teamparticipation_consistency,
     )
 from lp.services.log.logger import BufferLogger
 from lp.testing import (
@@ -1120,8 +1120,6 @@
             re.search('missing TeamParticipation entries for zzzzz', err))
         self.failUnless(
             re.search('spurious TeamParticipation entries for zzzzz', err))
-        self.failUnless(
-            re.search('not members of themselves:.*zzzzz.*', err))
 
     def test_report_circular_team_references(self):
         """The script reports circular references between teams.
@@ -1157,32 +1155,58 @@
         self.assertEqual(0, len(out))
         self.failUnless(re.search('Circular references found', err))
 
-    def test_report_spurious_participants_of_people(self):
+    # A script to create two new people, where both participate in the first,
+    # and first is missing a self-participation.
+    script_create_inconsistent_participation = """
+        INSERT INTO
+            Person (id, name, displayname, creation_rationale)
+            VALUES (6969, 'bobby', 'Dazzler', 1);
+        INSERT INTO
+            Person (id, name, displayname, creation_rationale)
+            VALUES (6970, 'nobby', 'Jazzler', 1);
+        INSERT INTO
+            TeamParticipation (person, team)
+            VALUES (6970, 6969);
+        DELETE FROM
+            TeamParticipation
+            WHERE person = 6969
+              AND team = 6969;
+        """
+
+    def test_check_teamparticipation_consistency(self):
         """The script reports spurious participants of people.
 
         Teams can have multiple participants, but only the person should be a
         paricipant of him/herself.
         """
-        # Create two new people and make both participate in the first.
-        cursor().execute("""
-            INSERT INTO
-                Person (id, name, displayname, creation_rationale)
-                VALUES (6969, 'bobby', 'Dazzler', 1);
-            INSERT INTO
-                Person (id, name, displayname, creation_rationale)
-                VALUES (6970, 'nobby', 'Jazzler', 1);
-            INSERT INTO
-                TeamParticipation (person, team)
-                VALUES (6970, 6969);
-            """ % sqlvalues(approved=TeamMembershipStatus.APPROVED))
-        transaction.commit()
-        logger = BufferLogger()
-        self.addDetail("log", logger.content)
-        errors = check_teamparticipation_consistency(
-            logger, fetch_team_participation_info(logger))
-        self.assertEqual(
-            [ConsistencyError("spurious", 6969, [6970])],
-            errors)
+        cursor().execute(self.script_create_inconsistent_participation)
+        transaction.commit()
+        logger = BufferLogger()
+        self.addDetail("log", logger.content)
+        errors = check_teamparticipation_consistency(
+            logger, fetch_team_participation_info(logger))
+        errors_expected = [
+            ConsistencyError("spurious", 6969, [6970]),
+            ConsistencyError("missing", 6969, [6969]),
+            ]
+        self.assertContentEqual(errors_expected, errors)
+
+    def test_fix_teamparticipation_consistency(self):
+        """
+        `fix_teamparticipation_consistency` takes an iterable of
+        `ConsistencyError`s and attempts to repair the data.
+        """
+        cursor().execute(self.script_create_inconsistent_participation)
+        transaction.commit()
+        logger = BufferLogger()
+        self.addDetail("log", logger.content)
+        errors = check_teamparticipation_consistency(
+            logger, fetch_team_participation_info(logger))
+        self.assertNotEqual([], errors)
+        fix_teamparticipation_consistency(logger, errors)
+        errors = check_teamparticipation_consistency(
+            logger, fetch_team_participation_info(logger))
+        self.assertEqual([], errors)
 
     def test_load_and_save_team_participation(self):
         """The script can load and save participation info."""
@@ -1232,11 +1256,10 @@
         logger = BufferLogger()
         self.addDetail("log", logger.content)
         with StormStatementRecorder() as recorder:
-            check_teamparticipation_self(logger)
             check_teamparticipation_circular(logger)
             check_teamparticipation_consistency(
                 logger, fetch_team_participation_info(logger))
-        self.assertThat(recorder, HasQueryCount(Equals(6)))
+        self.assertThat(recorder, HasQueryCount(Equals(5)))
 
 
 def test_suite():

=== modified file 'lib/lp/scripts/garbo.py'
--- lib/lp/scripts/garbo.py	2011-12-08 05:13:31 +0000
+++ lib/lp/scripts/garbo.py	2011-12-11 01:49:34 +0000
@@ -46,7 +46,6 @@
 from canonical.launchpad.database.emailaddress import EmailAddress
 from canonical.launchpad.database.librarian import TimeLimitedToken
 from canonical.launchpad.database.logintoken import LoginToken
-from canonical.launchpad.database.openidconsumer import OpenIDConsumerNonce
 from canonical.launchpad.interfaces.account import AccountStatus
 from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus
 from canonical.launchpad.interfaces.lpstorm import IMasterStore
@@ -78,6 +77,7 @@
 from lp.services.job.model.job import Job
 from lp.services.log.logger import PrefixFilter
 from lp.services.oauth.model import OAuthNonce
+from lp.services.openid.model.openidconsumer import OpenIDConsumerNonce
 from lp.services.propertycache import cachedproperty
 from lp.services.scripts.base import (
     LaunchpadCronScript,

=== modified file 'lib/lp/scripts/tests/test_garbo.py'
--- lib/lp/scripts/tests/test_garbo.py	2011-12-08 05:13:31 +0000
+++ lib/lp/scripts/tests/test_garbo.py	2011-12-11 01:49:34 +0000
@@ -44,7 +44,6 @@
     )
 from canonical.launchpad.database.librarian import TimeLimitedToken
 from canonical.launchpad.database.logintoken import LoginToken
-from canonical.launchpad.database.openidconsumer import OpenIDConsumerNonce
 from canonical.launchpad.interfaces.account import AccountStatus
 from canonical.launchpad.interfaces.authtoken import LoginTokenType
 from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus
@@ -100,6 +99,7 @@
     OAuthAccessToken,
     OAuthNonce,
     )
+from lp.services.openid.model.openidconsumer import OpenIDConsumerNonce
 from lp.services.session.model import (
     SessionData,
     SessionPkgData,

=== modified file 'lib/lp/services/configure.zcml'
--- lib/lp/services/configure.zcml	2011-12-08 05:13:31 +0000
+++ lib/lp/services/configure.zcml	2011-12-11 01:49:34 +0000
@@ -9,6 +9,7 @@
   <include package=".fields" />
   <include package=".geoip" />
   <include package=".googlesearch" />
+  <include package=".gpg" />
   <include package=".inlinehelp" file="meta.zcml" />
   <include package=".job" />
   <include package=".longpoll" />

=== modified file 'lib/lp/services/features/testing.py'
--- lib/lp/services/features/testing.py	2011-10-27 15:04:26 +0000
+++ lib/lp/services/features/testing.py	2011-12-11 01:49:34 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# Copyright 2010,2011 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Helpers for writing tests that use feature flags."""
@@ -9,6 +9,8 @@
 
 from fixtures import Fixture
 
+from lazr.restful.utils import get_current_browser_request
+
 from lp.services.features import (
     get_relevant_feature_controller,
     install_feature_controller,
@@ -18,6 +20,9 @@
     Rule,
     StormFeatureRuleSource,
     )
+from lp.services.features.scopes import (
+    ScopesFromRequest,
+    )
 
 
 class FeatureFixture(Fixture):
@@ -37,14 +42,19 @@
     The values are restored when the block exits.
     """
 
-    def __init__(self, features_dict, full_feature_rules=None):
+    def __init__(self, features_dict, full_feature_rules=None,
+            override_scope_lookup=None):
         """Constructor.
 
         :param features_dict: A dictionary-like object with keys and values
             that are flag names and those flags' settings.
+        :param override_scope_lookup: If non-None, an argument that takes
+            a scope name and returns True if it matches.  If not specified, 
+            scopes are looked up from the current request.
         """
         self.desired_features = features_dict
         self.full_feature_rules = full_feature_rules
+        self.override_scope_lookup = override_scope_lookup
 
     def setUp(self):
         """Set the feature flags that this fixture is responsible for."""
@@ -56,8 +66,15 @@
         rule_source.setAllRules(self.makeNewRules())
 
         original_controller = get_relevant_feature_controller()
+
+        def scope_lookup(scope_name):
+            request = get_current_browser_request()
+            return ScopesFromRequest(request).lookup(scope_name)
+
+        if self.override_scope_lookup:
+            scope_lookup = self.override_scope_lookup
         install_feature_controller(
-            FeatureController(lambda _: True, rule_source))
+            FeatureController(scope_lookup, rule_source))
         self.addCleanup(install_feature_controller, original_controller)
 
     def makeNewRules(self):

=== added directory 'lib/lp/services/gpg'
=== added file 'lib/lp/services/gpg/__init__.py'
=== renamed file 'lib/canonical/launchpad/zcml/gpghandler.zcml' => 'lib/lp/services/gpg/configure.zcml'
--- lib/canonical/launchpad/zcml/gpghandler.zcml	2011-05-02 12:55:06 +0000
+++ lib/lp/services/gpg/configure.zcml	2011-12-11 01:49:34 +0000
@@ -9,26 +9,26 @@
     xmlns:zope="http://namespaces.zope.org/zope";
     i18n_domain="launchpad">
 
-    <class class="canonical.launchpad.utilities.GPGHandler">
-        <allow interface="canonical.launchpad.interfaces.gpghandler.IGPGHandler" />
+    <class class="lp.services.gpg.handler.GPGHandler">
+        <allow interface="lp.services.gpg.interfaces.IGPGHandler" />
     </class>
 
     <securedutility
-        class="canonical.launchpad.utilities.GPGHandler"
-        provides="canonical.launchpad.interfaces.gpghandler.IGPGHandler">
-        <allow interface="canonical.launchpad.interfaces.gpghandler.IGPGHandler" />
+        class="lp.services.gpg.handler.GPGHandler"
+        provides="lp.services.gpg.interfaces.IGPGHandler">
+        <allow interface="lp.services.gpg.interfaces.IGPGHandler" />
     </securedutility>
 
-    <class class="canonical.launchpad.utilities.PymeSignature">
-        <allow interface="canonical.launchpad.interfaces.gpghandler.IPymeSignature" />
-    </class>
-
-    <class class="canonical.launchpad.utilities.PymeKey">
-        <allow interface="canonical.launchpad.interfaces.gpghandler.IPymeKey" />
-    </class>
-
-    <class class="canonical.launchpad.utilities.PymeUserId">
-        <allow interface="canonical.launchpad.interfaces.gpghandler.IPymeUserId" />
+    <class class="lp.services.gpg.handler.PymeSignature">
+        <allow interface="lp.services.gpg.interfaces.IPymeSignature" />
+    </class>
+
+    <class class="lp.services.gpg.handler.PymeKey">
+        <allow interface="lp.services.gpg.interfaces.IPymeKey" />
+    </class>
+
+    <class class="lp.services.gpg.handler.PymeUserId">
+        <allow interface="lp.services.gpg.interfaces.IPymeUserId" />
     </class>
 
 </configure>

=== added directory 'lib/lp/services/gpg/doc'
=== renamed file 'lib/canonical/launchpad/doc/gpg-encryption.txt' => 'lib/lp/services/gpg/doc/gpg-encryption.txt'
--- lib/canonical/launchpad/doc/gpg-encryption.txt	2010-10-18 22:24:59 +0000
+++ lib/lp/services/gpg/doc/gpg-encryption.txt	2011-12-11 01:49:34 +0000
@@ -5,7 +5,7 @@
 decrypt contents in Launchpad, and demonstrates that the methods
 can support Unicode contents.
 
-    >>> from canonical.launchpad.ftests import (
+    >>> from lp.testing.gpgkeys import (
     ...     import_public_test_keys, import_secret_test_key, decrypt_content)
     >>> import transaction
     >>> import_public_test_keys()
@@ -28,7 +28,7 @@
     >>> bag.user.name
     u'name12'
 
-    >>> from canonical.launchpad.interfaces.gpghandler import IGPGHandler
+    >>> from lp.services.gpg.interfaces import IGPGHandler
     >>> gpghandler = getUtility(IGPGHandler)
 
 Let's use a unicode content, it can't be directly typed as

=== renamed file 'lib/lp/registry/doc/gpg-signatures.txt' => 'lib/lp/services/gpg/doc/gpg-signatures.txt'
--- lib/lp/registry/doc/gpg-signatures.txt	2010-10-18 22:24:59 +0000
+++ lib/lp/services/gpg/doc/gpg-signatures.txt	2011-12-11 01:49:34 +0000
@@ -1,7 +1,7 @@
 OpenPGP Signature Verification
 ==============================
 
-    >>> from canonical.launchpad.ftests import import_public_test_keys
+    >>> from lp.testing.gpgkeys import import_public_test_keys
     >>> import transaction
     >>> import_public_test_keys()
 
@@ -20,7 +20,7 @@
     u'name12'
 
     >>> from zope.component import getUtility
-    >>> from canonical.launchpad.interfaces.gpghandler import IGPGHandler
+    >>> from lp.services.gpg.interfaces import IGPGHandler
     >>> gpghandler = getUtility(IGPGHandler)
 
 The text below was "clear signed" by 0xDFD20543 master key:

=== renamed file 'lib/canonical/launchpad/doc/gpghandler.txt' => 'lib/lp/services/gpg/doc/gpghandler.txt'
--- lib/canonical/launchpad/doc/gpghandler.txt	2011-05-30 12:45:29 +0000
+++ lib/lp/services/gpg/doc/gpghandler.txt	2011-12-11 01:49:34 +0000
@@ -25,7 +25,7 @@
     >>> from zope.component import getUtility
     >>> from canonical.launchpad.webapp.testing import verifyObject
 
-    >>> from canonical.launchpad.interfaces.gpghandler import (
+    >>> from lp.services.gpg.interfaces import (
     ...     IGPGHandler,
     ...     IPymeKey,
     ...     )
@@ -53,7 +53,7 @@
 Let's recover some coherent data and verify if it works as expected:
 
     >>> import os
-    >>> from canonical.launchpad.ftests.keys_for_tests import gpgkeysdir
+    >>> from lp.testing.gpgkeys import gpgkeysdir
     >>> filepath = os.path.join(gpgkeysdir, 'test@xxxxxxxxxxxxxxxxx')
     >>> pubkey = open(filepath).read()
     >>> key = gpghandler.importPublicKey(pubkey)

=== renamed file 'lib/canonical/launchpad/utilities/gpghandler.py' => 'lib/lp/services/gpg/handler.py'
--- lib/canonical/launchpad/utilities/gpghandler.py	2011-09-13 05:23:16 +0000
+++ lib/lp/services/gpg/handler.py	2011-12-11 01:49:34 +0000
@@ -28,7 +28,17 @@
 from zope.interface import implements
 
 from canonical.config import config
-from canonical.launchpad.interfaces.gpghandler import (
+from canonical.launchpad.webapp import errorlog
+from canonical.lazr.timeout import (
+    TimeoutError,
+    urlfetch,
+    )
+from lp.app.validators.email import valid_email
+from lp.registry.interfaces.gpg import (
+    GPGKeyAlgorithm,
+    valid_fingerprint,
+    )
+from lp.services.gpg.interfaces import (
     GPGKeyDoesNotExistOnServer,
     GPGKeyExpired,
     GPGKeyNotFoundError,
@@ -43,16 +53,6 @@
     MoreThanOneGPGKeyFound,
     SecretGPGKeyImportDetected,
     )
-from canonical.launchpad.webapp import errorlog
-from canonical.lazr.timeout import (
-    TimeoutError,
-    urlfetch,
-    )
-from lp.app.validators.email import valid_email
-from lp.registry.interfaces.gpg import (
-    GPGKeyAlgorithm,
-    valid_fingerprint,
-    )
 from lp.services.timeline.requesttimeline import get_request_timeline
 
 

=== renamed file 'lib/canonical/launchpad/interfaces/gpghandler.py' => 'lib/lp/services/gpg/interfaces.py'
=== added directory 'lib/lp/services/gpg/tests'
=== added file 'lib/lp/services/gpg/tests/__init__.py'
=== added file 'lib/lp/services/gpg/tests/test_doc.py'
--- lib/lp/services/gpg/tests/test_doc.py	1970-01-01 00:00:00 +0000
+++ lib/lp/services/gpg/tests/test_doc.py	2011-12-11 01:49:34 +0000
@@ -0,0 +1,18 @@
+# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""
+Run the doctests and pagetests.
+"""
+
+import os
+
+from canonical.testing.layers import LaunchpadFunctionalLayer
+from lp.services.testing import build_test_suite
+
+
+here = os.path.dirname(os.path.realpath(__file__))
+
+
+def test_suite():
+    return build_test_suite(here, layer=LaunchpadFunctionalLayer)

=== renamed file 'lib/canonical/launchpad/utilities/ftests/test_gpghandler.py' => 'lib/lp/services/gpg/tests/test_gpghandler.py'
--- lib/canonical/launchpad/utilities/ftests/test_gpghandler.py	2011-10-28 06:43:50 +0000
+++ lib/lp/services/gpg/tests/test_gpghandler.py	2011-12-11 01:49:34 +0000
@@ -11,31 +11,35 @@
 from time import time
 
 from pytz import UTC
+from testtools.matchers import (
+    LessThan,
+    Not,
+    )
 from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
 
-from testtools.matchers import (
-    Not,
-    LessThan,
-    )
-
 from canonical.launchpad.ftests import (
     ANONYMOUS,
-    keys_for_tests,
     login,
     logout,
     )
-from canonical.launchpad.interfaces.gpghandler import (
-    GPGKeyDoesNotExistOnServer,
-    GPGKeyTemporarilyNotFoundError,
-    IGPGHandler,
-    )
 from canonical.lazr.timeout import (
     get_default_timeout_function,
     set_default_timeout_function,
     )
 from canonical.testing.layers import LaunchpadFunctionalLayer
+from lp.services.gpg.interfaces import (
+    GPGKeyDoesNotExistOnServer,
+    GPGKeyTemporarilyNotFoundError,
+    IGPGHandler,
+    )
 from lp.testing import TestCase
+from lp.testing.gpgkeys import (
+    import_secret_test_key,
+    iter_test_key_emails,
+    test_keyrings,
+    test_pubkey_from_email,
+    )
 from lp.testing.keyserver import KeyServerTac
 
 
@@ -59,8 +63,8 @@
         super(TestImportKeyRing, self).tearDown()
 
     def populateKeyring(self):
-        for email in keys_for_tests.iter_test_key_emails():
-            pubkey = keys_for_tests.test_pubkey_from_email(email)
+        for email in iter_test_key_emails():
+            pubkey = test_pubkey_from_email(email)
             self.gpg_handler.importPublicKey(pubkey)
 
     # This sequence might fit better as a doctest. Hmm.
@@ -113,7 +117,7 @@
             list(self.gpg_handler.localKeys(secret=True)), [])
 
         # Import a secret key and look it up.
-        keys_for_tests.import_secret_test_key()
+        import_secret_test_key()
         secret_target_fpr = 'A419AE861E88BC9E04B9C26FBA2B9389DFD20543'
 
         filtered_keys = self.gpg_handler.localKeys(secret=True)
@@ -150,7 +154,7 @@
 
     def testTestkeyrings(self):
         """Do we have the expected test keyring files"""
-        self.assertEqual(len(list(keys_for_tests.test_keyrings())), 1)
+        self.assertEqual(len(list(test_keyrings())), 1)
 
     def testHomeDirectoryJob(self):
         """Does the job to touch the home work."""

=== modified file 'lib/lp/services/mail/doc/emailauthentication.txt'
--- lib/lp/services/mail/doc/emailauthentication.txt	2011-08-12 15:57:11 +0000
+++ lib/lp/services/mail/doc/emailauthentication.txt	2011-12-11 01:49:34 +0000
@@ -12,8 +12,8 @@
 
     >>> from canonical.config import config
     >>> from canonical.database.sqlbase import commit
-    >>> from canonical.launchpad.ftests import import_public_test_keys
     >>> from canonical.testing.layers import LaunchpadZopelessLayer
+    >>> from lp.testing.gpgkeys import import_public_test_keys
 
     >>> LaunchpadZopelessLayer.switchDbUser('launchpad')
     >>> import_public_test_keys()
@@ -119,7 +119,7 @@
     ...     '\n'.join(msg_lines), _class=SignedMessage)
     >>> msg.parsed_string = msg.as_string()
 
-    >>> from canonical.launchpad.interfaces.gpghandler import IGPGHandler
+    >>> from lp.services.gpg.interfaces import IGPGHandler
     >>> getUtility(IGPGHandler).getVerifiedSignature(
     ...     msg.signedContent, msg.signature)
     Traceback (most recent call last):

=== modified file 'lib/lp/services/mail/incoming.py'
--- lib/lp/services/mail/incoming.py	2011-10-28 06:43:50 +0000
+++ lib/lp/services/mail/incoming.py	2011-12-11 01:49:34 +0000
@@ -27,10 +27,6 @@
     )
 
 from canonical.launchpad.interfaces.account import AccountStatus
-from canonical.launchpad.interfaces.gpghandler import (
-    GPGVerificationError,
-    IGPGHandler,
-    )
 from canonical.launchpad.mailnotification import (
     send_process_error_notification,
     )
@@ -46,6 +42,10 @@
 from canonical.librarian.interfaces import UploadFailed
 from lp.registry.interfaces.person import IPerson
 from lp.services.features import getFeatureFlag
+from lp.services.gpg.interfaces import (
+    GPGVerificationError,
+    IGPGHandler,
+    )
 from lp.services.mail.handlers import mail_handlers
 from lp.services.mail.helpers import (
     ensure_sane_signature_timestamp,

=== modified file 'lib/lp/services/mail/tests/incomingmail.txt'
--- lib/lp/services/mail/tests/incomingmail.txt	2011-10-25 21:24:59 +0000
+++ lib/lp/services/mail/tests/incomingmail.txt	2011-12-11 01:49:34 +0000
@@ -74,7 +74,7 @@
 import the keys before handleMail is called.
 
     >>> from canonical.config import config
-    >>> from canonical.launchpad.ftests import import_public_test_keys
+    >>> from lp.testing.gpgkeys import import_public_test_keys
     >>> import_public_test_keys()
     >>> commit()
     >>> LaunchpadZopelessLayer.switchDbUser(config.processmail.dbuser)

=== modified file 'lib/lp/services/mail/tests/test_incoming.py'
--- lib/lp/services/mail/tests/test_incoming.py	2011-10-17 01:45:44 +0000
+++ lib/lp/services/mail/tests/test_incoming.py	2011-12-11 01:49:34 +0000
@@ -14,7 +14,6 @@
 from zope.security.management import setSecurityPolicy
 
 from canonical.config import config
-from canonical.launchpad.ftests import import_secret_test_key
 from canonical.launchpad.testing.systemdocs import LayeredDocFileSuite
 from canonical.launchpad.webapp.authorization import LaunchpadSecurityPolicy
 from canonical.testing.layers import LaunchpadZopelessLayer
@@ -31,6 +30,7 @@
 from lp.services.mail.tests.helpers import testmails_path
 from lp.testing import TestCaseWithFactory
 from lp.testing.factory import GPGSigningContext
+from lp.testing.gpgkeys import import_secret_test_key
 from lp.testing.mail_helpers import pop_notifications
 
 

=== modified file 'lib/lp/services/mail/tests/test_signedmessage.py'
--- lib/lp/services/mail/tests/test_signedmessage.py	2011-08-13 04:07:10 +0000
+++ lib/lp/services/mail/tests/test_signedmessage.py	2011-12-11 01:49:34 +0000
@@ -17,13 +17,9 @@
 import gpgme
 from zope.component import getUtility
 
-from canonical.launchpad.ftests import (
-    import_public_test_keys,
-    import_secret_test_key,
-    )
-from canonical.launchpad.interfaces.gpghandler import IGPGHandler
 from canonical.testing.layers import DatabaseFunctionalLayer
 from lp.registry.interfaces.person import IPersonSet
+from lp.services.gpg.interfaces import IGPGHandler
 from lp.services.mail.incoming import (
     authenticateEmail,
     canonicalise_line_endings,
@@ -32,6 +28,10 @@
 from lp.services.mail.signedmessage import signed_message_from_string
 from lp.testing import TestCaseWithFactory
 from lp.testing.factory import GPGSigningContext
+from lp.testing.gpgkeys import (
+    import_public_test_keys,
+    import_secret_test_key,
+    )
 
 
 class TestSignedMessage(TestCaseWithFactory):

=== modified file 'lib/lp/services/openid/configure.zcml'
--- lib/lp/services/openid/configure.zcml	2011-09-04 12:46:14 +0000
+++ lib/lp/services/openid/configure.zcml	2011-12-11 01:49:34 +0000
@@ -21,4 +21,8 @@
     <adapter factory=".adapters.openid.OpenIDPersistentIdentity" />
     <adapter factory=".adapters.openid.person_to_openidpersistentidentity" />
 
+    <utility
+        provides=".interfaces.openidconsumer.IOpenIDConsumerStore"
+        factory=".model.openidconsumer.OpenIDConsumerStore" />
+
 </configure>

=== renamed file 'lib/canonical/launchpad/interfaces/openidconsumer.py' => 'lib/lp/services/openid/interfaces/openidconsumer.py'
=== renamed file 'lib/canonical/launchpad/database/baseopenidstore.py' => 'lib/lp/services/openid/model/baseopenidstore.py'
=== renamed file 'lib/canonical/launchpad/database/openidconsumer.py' => 'lib/lp/services/openid/model/openidconsumer.py'
--- lib/canonical/launchpad/database/openidconsumer.py	2010-08-20 20:31:18 +0000
+++ lib/lp/services/openid/model/openidconsumer.py	2011-12-11 01:49:34 +0000
@@ -8,12 +8,12 @@
 
 from zope.interface import implements
 
-from canonical.launchpad.database.baseopenidstore import (
+from lp.services.openid.interfaces.openidconsumer import IOpenIDConsumerStore
+from lp.services.openid.model.baseopenidstore import (
     BaseStormOpenIDAssociation,
     BaseStormOpenIDNonce,
     BaseStormOpenIDStore,
     )
-from canonical.launchpad.interfaces.openidconsumer import IOpenIDConsumerStore
 
 
 class OpenIDConsumerAssociation(BaseStormOpenIDAssociation):

=== renamed file 'lib/canonical/launchpad/database/tests/test_baseopenidstore.py' => 'lib/lp/services/openid/tests/test_baseopenidstore.py'
--- lib/canonical/launchpad/database/tests/test_baseopenidstore.py	2010-08-20 20:31:18 +0000
+++ lib/lp/services/openid/tests/test_baseopenidstore.py	2011-12-11 01:49:34 +0000
@@ -14,8 +14,8 @@
 from openid.association import Association
 from openid.store import nonce
 
-from canonical.launchpad.database.baseopenidstore import BaseStormOpenIDStore
 from canonical.launchpad.interfaces.lpstorm import IMasterStore
+from lp.services.openid.model.baseopenidstore import BaseStormOpenIDStore
 
 
 class BaseStormOpenIDStoreTestsMixin:

=== renamed file 'lib/canonical/launchpad/database/tests/test_openidconsumer.py' => 'lib/lp/services/openid/tests/test_openidconsumer.py'
--- lib/canonical/launchpad/database/tests/test_openidconsumer.py	2011-08-12 11:37:08 +0000
+++ lib/lp/services/openid/tests/test_openidconsumer.py	2011-12-11 01:49:34 +0000
@@ -7,11 +7,11 @@
 
 from zope.component import getUtility
 
-from canonical.launchpad.database.tests.test_baseopenidstore import (
+from canonical.testing.layers import DatabaseFunctionalLayer
+from lp.services.openid.interfaces.openidconsumer import IOpenIDConsumerStore
+from lp.services.openid.tests.test_baseopenidstore import (
     BaseStormOpenIDStoreTestsMixin,
     )
-from canonical.launchpad.interfaces.openidconsumer import IOpenIDConsumerStore
-from canonical.testing.layers import DatabaseFunctionalLayer
 from lp.testing import TestCase
 
 

=== modified file 'lib/lp/soyuz/browser/tests/distroseriesqueue-views.txt'
--- lib/lp/soyuz/browser/tests/distroseriesqueue-views.txt	2011-01-17 21:51:09 +0000
+++ lib/lp/soyuz/browser/tests/distroseriesqueue-views.txt	2011-12-11 01:49:34 +0000
@@ -253,12 +253,12 @@
     ...     sourcename="foo", distroseries=hoary, version="1.0-2",
     ...     status=PackagePublishingStatus.PUBLISHED)
 
-    >>> from canonical.launchpad.ftests import import_public_test_keys
     >>> from lp.archiveuploader.uploadpolicy import ArchiveUploadType
+    >>> from lp.archiveuploader.tests import datadir, getPolicy
+    >>> from lp.archiveuploader.nascentupload import NascentUpload
     >>> from lp.soyuz.interfaces.component import IComponentSet
     >>> from lp.soyuz.model.component import ComponentSelection
-    >>> from lp.archiveuploader.tests import datadir, getPolicy
-    >>> from lp.archiveuploader.nascentupload import NascentUpload
+    >>> from lp.testing.gpgkeys import import_public_test_keys
     >>> import_public_test_keys()
     >>> universe = getUtility(IComponentSet)['universe']
     >>> trash = ComponentSelection(distroseries=hoary, component=universe)

=== modified file 'lib/lp/soyuz/browser/tests/test_archive_packages.py'
--- lib/lp/soyuz/browser/tests/test_archive_packages.py	2011-05-27 21:12:25 +0000
+++ lib/lp/soyuz/browser/tests/test_archive_packages.py	2011-12-11 01:49:34 +0000
@@ -22,13 +22,19 @@
 from canonical.launchpad.testing.pages import get_feedback_messages
 from canonical.launchpad.webapp import canonical_url
 from canonical.launchpad.webapp.authentication import LaunchpadPrincipal
-from canonical.testing.layers import LaunchpadFunctionalLayer
+from canonical.testing.layers import (
+    DatabaseFunctionalLayer,
+    LaunchpadFunctionalLayer,
+    )
 from lp.app.utilities.celebrities import ILaunchpadCelebrities
 from lp.soyuz.browser.archive import ArchiveNavigationMenu
+from lp.soyuz.enums import PackagePublishingStatus
 from lp.testing import (
+    celebrity_logged_in,
     login,
     login_person,
     person_logged_in,
+    record_two_runs,
     TestCaseWithFactory,
     )
 from lp.testing._webservice import QueryCollector
@@ -258,3 +264,40 @@
             url = canonical_url(ppa) + "/+packages"
         browser.open(url)
         self.assertThat(collector, HasQueryCount(Equals(expected_count)))
+
+
+class TestP3APackagesQueryCount(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestP3APackagesQueryCount, self).setUp()
+        self.team = self.factory.makeTeam()
+        login_person(self.team.teamowner)
+        self.person = self.factory.makePerson()
+
+        self.private_ppa = self.factory.makeArchive(
+            owner=self.team, private=True)
+        self.private_ppa.newSubscription(
+            self.person, registrant=self.team.teamowner)
+
+    def createPackage(self):
+        with celebrity_logged_in('admin'):
+            pkg = self.factory.makeBinaryPackagePublishingHistory(
+                status=PackagePublishingStatus.PUBLISHED,
+                archive=self.private_ppa)
+        return pkg
+
+    def test_ppa_index_queries_count(self):
+        def ppa_index_render():
+            with person_logged_in(self.person):
+                view = create_initialized_view(
+                    self.private_ppa, '+index',
+                    principal=self.person)
+                view.page_title = "title"
+                view.render()
+        recorder1, recorder2 = record_two_runs(
+            ppa_index_render, self.createPackage, 2, 3)
+
+        self.assertThat(
+            recorder2, HasQueryCount(LessThan(recorder1.count + 1)))

=== modified file 'lib/lp/soyuz/browser/tests/test_archive_webservice.py'
--- lib/lp/soyuz/browser/tests/test_archive_webservice.py	2011-12-05 16:01:37 +0000
+++ lib/lp/soyuz/browser/tests/test_archive_webservice.py	2011-12-11 01:49:34 +0000
@@ -3,6 +3,8 @@
 
 __metaclass__ = type
 
+from datetime import timedelta
+
 from lazr.restfulclient.errors import (
     BadRequest,
     NotFound,
@@ -377,3 +379,28 @@
         job_source = getUtility(IPlainPackageCopyJobSource)
         copy_job = job_source.getActiveJobs(target_archive).one()
         self.assertEqual(target_archive, copy_job.target_archive)
+
+
+class TestgetPublishedBinaries(WebServiceTestCase):
+    """test getPublishedSources."""
+
+    def test_getPublishedBinaries(self):
+        self.ws_version = 'beta'
+        person = self.factory.makePerson()
+        archive = self.factory.makeArchive()
+        self.factory.makeBinaryPackagePublishingHistory(archive=archive)
+        ws_archive = self.wsObject(archive, user=person)
+        self.assertEqual(1, len(ws_archive.getPublishedBinaries()))
+
+    def test_getPublishedBinaries_created_since_date(self):
+        self.ws_version = 'beta'
+        person = self.factory.makePerson()
+        archive = self.factory.makeArchive()
+        datecreated = self.factory.getUniqueDate()
+        later_date = datecreated + timedelta(minutes=1)
+        self.factory.makeBinaryPackagePublishingHistory(
+                archive=archive, datecreated=datecreated)
+        ws_archive = self.wsObject(archive, user=person)
+        publications = ws_archive.getPublishedBinaries(
+                created_since_date=later_date)
+        self.assertEqual(0, len(publications))

=== modified file 'lib/lp/soyuz/browser/tests/test_builder_views.py'
--- lib/lp/soyuz/browser/tests/test_builder_views.py	2011-12-05 00:45:24 +0000
+++ lib/lp/soyuz/browser/tests/test_builder_views.py	2011-12-11 01:49:34 +0000
@@ -14,10 +14,7 @@
 from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
 
-from canonical.database.sqlbase import (
-    flush_database_caches,
-    flush_database_updates,
-    )
+from canonical.database.sqlbase import flush_database_updates
 from canonical.launchpad.ftests import login
 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
 from canonical.testing.layers import LaunchpadFunctionalLayer
@@ -34,6 +31,7 @@
 from lp.soyuz.browser.builder import BuilderEditView
 from lp.testing import (
     celebrity_logged_in,
+    record_two_runs,
     StormStatementRecorder,
     TestCaseWithFactory,
     )
@@ -216,35 +214,15 @@
         self.addFakeBuildLog(build)
         return build
 
-    def _record_queries_count(self, tested_method, item_creator):
-        # A simple helper that returns the two storm statement recorders
-        # obtained when running tested_method with {nb_objects} items creater
-        # (using item_creator) and then with {nb_objects}*2 items created.
-        for i in range(self.nb_objects):
-            item_creator()
-        # Record how many queries are issued when tested_method is
-        # called with {nb_objects} items created.
-        flush_database_caches()
-        with StormStatementRecorder() as recorder1:
-            tested_method()
-        # Create {nb_objects} more items.
-        for i in range(self.nb_objects):
-            item_creator()
-        # Record again the number of queries issued.
-        flush_database_caches()
-        with StormStatementRecorder() as recorder2:
-            tested_method()
-        return recorder1, recorder2
-
     def test_build_history_queries_count_view_recipe_builds(self):
         # The builder's history view creation (i.e. the call to
         # view.setupBuildList) issues a constant number of queries
         # when recipe builds are displayed.
         def builder_history_render():
             create_initialized_view(self.builder, '+history').render()
-        recorder1, recorder2 = self._record_queries_count(
-            builder_history_render,
-            self.createRecipeBuildWithBuilder)
+        recorder1, recorder2 = record_two_runs(
+            builder_history_render, self.createRecipeBuildWithBuilder,
+            self.nb_objects)
 
         # XXX: rvb 2011-11-14 bug=890326: The only query remaining is the
         # one that results from a call to
@@ -258,9 +236,9 @@
         # when binary builds are displayed.
         def builder_history_render():
             create_initialized_view(self.builder, '+history').render()
-        recorder1, recorder2 = self._record_queries_count(
-            builder_history_render,
-            self.createBinaryPackageBuild)
+        recorder1, recorder2 = record_two_runs(
+            builder_history_render, self.createBinaryPackageBuild,
+            self.nb_objects)
 
         self.assertThat(recorder2, HasQueryCount(Equals(recorder1.count)))
 
@@ -271,8 +249,9 @@
             create_initialized_view(self.builder, '+history').render()
         createBinaryPackageBuildInPPA = partial(
             self.createBinaryPackageBuild, in_ppa=True)
-        recorder1, recorder2 = self._record_queries_count(
-            builder_history_render, createBinaryPackageBuildInPPA)
+        recorder1, recorder2 = record_two_runs(
+            builder_history_render, createBinaryPackageBuildInPPA,
+            self.nb_objects)
 
         self.assertThat(recorder2, HasQueryCount(Equals(recorder1.count)))
 
@@ -281,9 +260,9 @@
         # when translation template builds are displayed.
         def builder_history_render():
             create_initialized_view(self.builder, '+history').render()
-        recorder1, recorder2 = self._record_queries_count(
+        recorder1, recorder2 = record_two_runs(
             builder_history_render,
-            self.createTranslationTemplateBuildWithBuilder)
+            self.createTranslationTemplateBuildWithBuilder, self.nb_objects)
 
         self.assertThat(recorder2, HasQueryCount(Equals(recorder1.count)))
 

=== modified file 'lib/lp/soyuz/doc/distroseriesqueue-ddtp-tarball.txt'
--- lib/lp/soyuz/doc/distroseriesqueue-ddtp-tarball.txt	2011-06-16 08:10:40 +0000
+++ lib/lp/soyuz/doc/distroseriesqueue-ddtp-tarball.txt	2011-12-11 01:49:34 +0000
@@ -29,7 +29,7 @@
     >>> from lp.archiveuploader.nascentupload import NascentUpload
     >>> from lp.archiveuploader.tests import datadir, getPolicy
 
-    >>> from canonical.launchpad.ftests import import_public_test_keys
+    >>> from lp.testing.gpgkeys import import_public_test_keys
     >>> import_public_test_keys()
 
 Login as an admin (or ubuntutest.archive_admin if we have one), since

=== modified file 'lib/lp/soyuz/doc/distroseriesqueue-debian-installer.txt'
--- lib/lp/soyuz/doc/distroseriesqueue-debian-installer.txt	2010-12-22 20:46:21 +0000
+++ lib/lp/soyuz/doc/distroseriesqueue-debian-installer.txt	2011-12-11 01:49:34 +0000
@@ -38,7 +38,7 @@
 debian-installer tarball, despite the fact that it's very unlikely to
 happen in production:
 
-  >>> from canonical.launchpad.ftests import import_public_test_keys
+  >>> from lp.testing.gpgkeys import import_public_test_keys
   >>> import_public_test_keys()
   >>> login('foo.bar@xxxxxxxxxxxxx')
 

=== modified file 'lib/lp/soyuz/doc/distroseriesqueue-dist-upgrader.txt'
--- lib/lp/soyuz/doc/distroseriesqueue-dist-upgrader.txt	2011-10-21 11:14:26 +0000
+++ lib/lp/soyuz/doc/distroseriesqueue-dist-upgrader.txt	2011-12-11 01:49:34 +0000
@@ -11,7 +11,7 @@
     >>> ubuntutest = getUtility(IDistributionSet)['ubuntutest']
     >>> breezy_autotest = ubuntutest['breezy-autotest']
 
-    >>> from canonical.launchpad.ftests import import_public_test_keys
+    >>> from lp.testing.gpgkeys import import_public_test_keys
     >>> import_public_test_keys()
 
 Login as an admin.

=== modified file 'lib/lp/soyuz/doc/distroseriesqueue-translations.txt'
--- lib/lp/soyuz/doc/distroseriesqueue-translations.txt	2011-11-29 05:15:07 +0000
+++ lib/lp/soyuz/doc/distroseriesqueue-translations.txt	2011-12-11 01:49:34 +0000
@@ -25,7 +25,7 @@
     >>> from lp.archiveuploader.nascentupload import NascentUpload
     >>> from lp.archiveuploader.tests import datadir, getPolicy
 
-    >>> from canonical.launchpad.ftests import import_public_test_keys
+    >>> from lp.testing.gpgkeys import import_public_test_keys
     >>> import_public_test_keys()
 
     >>> from canonical.database.constants import UTC_NOW

=== modified file 'lib/lp/soyuz/doc/distroseriesqueue.txt'
--- lib/lp/soyuz/doc/distroseriesqueue.txt	2011-06-28 15:04:29 +0000
+++ lib/lp/soyuz/doc/distroseriesqueue.txt	2011-12-11 01:49:34 +0000
@@ -39,7 +39,7 @@
 First up, we need to actually process an upload to get it into the
 queue. To do this we prepare an OpenPGP key, and then run the upload handler.
 
-    >>> from canonical.launchpad.ftests import import_public_test_keys
+    >>> from lp.testing.gpgkeys import import_public_test_keys
     >>> import_public_test_keys()
 
 We need some setup for the upload handler.

=== modified file 'lib/lp/soyuz/doc/fakepackager.txt'
--- lib/lp/soyuz/doc/fakepackager.txt	2011-06-09 10:50:25 +0000
+++ lib/lp/soyuz/doc/fakepackager.txt	2011-12-11 01:49:34 +0000
@@ -160,7 +160,7 @@
     >>> content = open(changesfile_path).read()
 
     >>> from zope.component import getUtility
-    >>> from canonical.launchpad.interfaces.gpghandler import IGPGHandler
+    >>> from lp.services.gpg.interfaces import IGPGHandler
     >>> gpghandler = getUtility(IGPGHandler)
     >>> sig = gpghandler.verifySignature(content)
 
@@ -225,7 +225,7 @@
 It also requires the public test gpg keys to be imported in the
 database.
 
-    >>> from canonical.launchpad.ftests import import_public_test_keys
+    >>> from lp.testing.gpgkeys import import_public_test_keys
     >>> import_public_test_keys()
 
 The default upload target is ubuntu/hoary and since we will deal with

=== modified file 'lib/lp/soyuz/doc/package-diff.txt'
--- lib/lp/soyuz/doc/package-diff.txt	2010-11-06 12:50:22 +0000
+++ lib/lp/soyuz/doc/package-diff.txt	2011-12-11 01:49:34 +0000
@@ -143,7 +143,7 @@
 
 And setup the test_keys in order to build and upload signed packages.
 
-    >>> from canonical.launchpad.ftests import import_public_test_keys
+    >>> from lp.testing.gpgkeys import import_public_test_keys
     >>> import_public_test_keys()
 
 When the first version of 'biscuit' is uploaded, since there is no

=== modified file 'lib/lp/soyuz/doc/soyuz-set-of-uploads.txt'
--- lib/lp/soyuz/doc/soyuz-set-of-uploads.txt	2011-08-09 06:30:01 +0000
+++ lib/lp/soyuz/doc/soyuz-set-of-uploads.txt	2011-12-11 01:49:34 +0000
@@ -68,7 +68,7 @@
 
 Import public keyring into current LPDB.
 
-    >>> from canonical.launchpad.ftests import import_public_test_keys
+    >>> from lp.testing.gpgkeys import import_public_test_keys
     >>> import_public_test_keys()
 
 Having set up that infrastructure we need to prepare a breezy distroseries

=== modified file 'lib/lp/soyuz/doc/soyuz-upload.txt'
--- lib/lp/soyuz/doc/soyuz-upload.txt	2011-09-29 13:01:04 +0000
+++ lib/lp/soyuz/doc/soyuz-upload.txt	2011-12-11 01:49:34 +0000
@@ -156,8 +156,8 @@
 So, load the GPG key:
 
     >>> from zope.component import getUtility
-    >>> from canonical.launchpad.ftests.keys_for_tests import gpgkeysdir
-    >>> from canonical.launchpad.interfaces.gpghandler import IGPGHandler
+    >>> from lp.services.gpg.interfaces import IGPGHandler
+    >>> from lp.testing.gpgkeys import gpgkeysdir
     >>> gpg_handler = getUtility(IGPGHandler)
     >>> key_path = os.path.join(gpgkeysdir, 'ftpmaster@xxxxxxxxxxxxxxxxx')
     >>> key_data = open(key_path).read()

=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py	2011-12-05 16:01:37 +0000
+++ lib/lp/soyuz/interfaces/archive.py	2011-12-11 01:49:34 +0000
@@ -1004,6 +1004,11 @@
             # Really PackagePublishingPocket, circular import fixed below.
             vocabulary=DBEnumeratedType,
             required=False, readonly=True),
+        created_since_date=Datetime(
+            title=_("Created Since Date"),
+            description=_("Return entries whose `date_created` is greater "
+                          "than or equal to this date."),
+            required=False),
         exact_match=Bool(
             description=_("Whether or not to filter binary names by exact "
                           "matching."),
@@ -1015,7 +1020,7 @@
     @export_read_operation()
     def getAllPublishedBinaries(name=None, version=None, status=None,
                                 distroarchseries=None, pocket=None,
-                                exact_match=False):
+                                exact_match=False, created_since_date=None):
         """All `IBinaryPackagePublishingHistory` target to this archive.
 
         :param: name: binary name filter (exact match or SQL LIKE controlled
@@ -1026,6 +1031,8 @@
         :param: pocket: `PackagePublishingPocket` filter.
         :param: exact_match: either or not filter source names by exact
                              matching.
+        :param: created_since_date: a filter on teh `date_created` of the
+                                    publishing record.
 
         :return: A collection containing `BinaryPackagePublishingHistory`.
         """

=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py	2011-12-08 22:32:41 +0000
+++ lib/lp/soyuz/model/archive.py	2011-12-11 01:49:34 +0000
@@ -714,7 +714,7 @@
 
     def _getBinaryPublishingBaseClauses(
         self, name=None, version=None, status=None, distroarchseries=None,
-        pocket=None, exact_match=False):
+        pocket=None, exact_match=False, created_since_date=None):
         """Base clauses and clauseTables for binary publishing queries.
 
         Returns a list of 'clauses' (to be joined in the callsite) and
@@ -781,15 +781,21 @@
                 BinaryPackagePublishingHistory.pocket = %s
             """ % sqlvalues(pocket))
 
+        if created_since_date is not None:
+            clauses.append(
+                "BinaryPackagePublishingHistory.datecreated >= %s"
+                % sqlvalues(created_since_date))
+
         return clauses, clauseTables, orderBy
 
     def getAllPublishedBinaries(self, name=None, version=None, status=None,
                                 distroarchseries=None, pocket=None,
-                                exact_match=False):
+                                exact_match=False, created_since_date=None):
         """See `IArchive`."""
         clauses, clauseTables, orderBy = self._getBinaryPublishingBaseClauses(
             name=name, version=version, status=status, pocket=pocket,
-            distroarchseries=distroarchseries, exact_match=exact_match)
+            distroarchseries=distroarchseries, exact_match=exact_match,
+            created_since_date=created_since_date)
 
         all_binaries = BinaryPackagePublishingHistory.select(
             ' AND '.join(clauses), clauseTables=clauseTables,
@@ -799,11 +805,13 @@
 
     def getPublishedOnDiskBinaries(self, name=None, version=None, status=None,
                                    distroarchseries=None, pocket=None,
-                                   exact_match=False):
+                                   exact_match=False,
+                                   created_since_date=None):
         """See `IArchive`."""
         clauses, clauseTables, orderBy = self._getBinaryPublishingBaseClauses(
             name=name, version=version, status=status, pocket=pocket,
-            distroarchseries=distroarchseries, exact_match=exact_match)
+            distroarchseries=distroarchseries, exact_match=exact_match,
+            created_since_date=created_since_date)
 
         clauses.append("""
             BinaryPackagePublishingHistory.distroarchseries =

=== modified file 'lib/lp/soyuz/model/publishing.py'
--- lib/lp/soyuz/model/publishing.py	2011-11-04 12:06:11 +0000
+++ lib/lp/soyuz/model/publishing.py	2011-12-11 01:49:34 +0000
@@ -1844,8 +1844,7 @@
 
         return result_set
 
-    def getChangesFilesForSources(
-        self, one_or_more_source_publications):
+    def getChangesFilesForSources(self, one_or_more_source_publications):
         """See `IPublishingSet`."""
         # Import PackageUpload and PackageUploadSource locally
         # to avoid circular imports, since PackageUpload uses

=== modified file 'lib/lp/soyuz/tests/soyuz.py'
--- lib/lp/soyuz/tests/soyuz.py	2011-11-29 05:15:07 +0000
+++ lib/lp/soyuz/tests/soyuz.py	2011-12-11 01:49:34 +0000
@@ -16,7 +16,6 @@
 
 from canonical.config import config
 from canonical.launchpad.database.librarian import LibraryFileAlias
-from canonical.launchpad.ftests import import_public_test_keys
 from canonical.launchpad.testing.fakepackager import FakePackager
 from canonical.testing.layers import LaunchpadZopelessLayer
 from lp.registry.interfaces.distribution import IDistributionSet
@@ -29,6 +28,7 @@
     SourcePackagePublishingHistory,
     )
 from lp.testing.dbuser import dbuser
+from lp.testing.gpgkeys import import_public_test_keys
 from lp.testing.sampledata import (
     BUILDD_ADMIN_USERNAME,
     CHROOT_LIBRARYFILEALIAS,

=== modified file 'lib/lp/soyuz/tests/test_archive.py'
--- lib/lp/soyuz/tests/test_archive.py	2011-12-02 15:07:54 +0000
+++ lib/lp/soyuz/tests/test_archive.py	2011-12-11 01:49:34 +0000
@@ -2303,6 +2303,54 @@
             person=person)
 
 
+class TestgetAllPublishedBinaries(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def test_returns_publication(self):
+        archive = self.factory.makeArchive()
+        publication = self.factory.makeBinaryPackagePublishingHistory(
+            archive=archive)
+        publications = archive.getAllPublishedBinaries()
+        self.assertEqual(1, publications.count())
+        self.assertEqual(publication, publications[0])
+
+    def test_created_since_date_newer(self):
+        archive = self.factory.makeArchive()
+        datecreated = self.factory.getUniqueDate()
+        self.factory.makeBinaryPackagePublishingHistory(
+            archive=archive, datecreated=datecreated)
+        later_date = datecreated + timedelta(minutes=1)
+        publications = archive.getAllPublishedBinaries(
+            created_since_date=later_date)
+        self.assertEqual(0, publications.count())
+
+    def test_created_since_date_older(self):
+        archive = self.factory.makeArchive()
+        datecreated = self.factory.getUniqueDate()
+        publication = self.factory.makeBinaryPackagePublishingHistory(
+            archive=archive, datecreated=datecreated)
+        earlier_date = datecreated - timedelta(minutes=1)
+        publications = archive.getAllPublishedBinaries(
+            created_since_date=earlier_date)
+        self.assertEqual(1, publications.count())
+        self.assertEqual(publication, publications[0])
+
+    def test_created_since_date_middle(self):
+        archive = self.factory.makeArchive()
+        datecreated = self.factory.getUniqueDate()
+        self.factory.makeBinaryPackagePublishingHistory(
+            archive=archive, datecreated=datecreated)
+        middle_date = datecreated + timedelta(minutes=1)
+        later_date = middle_date + timedelta(minutes=1)
+        later_publication = self.factory.makeBinaryPackagePublishingHistory(
+            archive=archive, datecreated=later_date)
+        publications = archive.getAllPublishedBinaries(
+            created_since_date=middle_date)
+        self.assertEqual(1, publications.count())
+        self.assertEqual(later_publication, publications[0])
+
+
 class TestRemovingPermissions(TestCaseWithFactory):
 
     layer = DatabaseFunctionalLayer

=== modified file 'lib/lp/soyuz/tests/test_archive_subscriptions.py'
--- lib/lp/soyuz/tests/test_archive_subscriptions.py	2010-11-12 06:00:51 +0000
+++ lib/lp/soyuz/tests/test_archive_subscriptions.py	2011-12-11 01:49:34 +0000
@@ -26,9 +26,11 @@
     def setUp(self):
         """Create a test archive."""
         super(TestArchiveSubscriptions, self).setUp()
+        self.owner = self.factory.makePerson()
         self.private_team = self.factory.makeTeam(
-            visibility=PersonVisibility.PRIVATE, name="subscribertest")
-        login_person(self.private_team.teamowner)
+            visibility=PersonVisibility.PRIVATE,
+            name="subscribertest", owner=self.owner)
+        login_person(self.owner)
         self.archive = self.factory.makeArchive(
             private=True, owner=self.private_team)
         self.subscriber = self.factory.makePerson()
@@ -45,7 +47,7 @@
         login_person(self.subscriber)
         self.assertRaises(Unauthorized, get_name)
 
-        login_person(self.private_team.teamowner)
+        login_person(self.owner)
         self.archive.newSubscription(
             self.subscriber, registrant=self.archive.owner)
 

=== modified file 'lib/lp/testing/__init__.py'
--- lib/lp/testing/__init__.py	2011-12-02 01:27:41 +0000
+++ lib/lp/testing/__init__.py	2011-12-11 01:49:34 +0000
@@ -110,12 +110,16 @@
 from zope.testing.testrunner.runner import TestResult as ZopeTestResult
 
 from canonical.config import config
+from canonical.database.sqlbase import flush_database_caches
 from canonical.launchpad.webapp import canonical_url
 from canonical.launchpad.webapp.adapter import (
     print_queries,
     start_sql_logging,
     stop_sql_logging,
     )
+from canonical.launchpad.webapp.authorization import (
+    clear_cache as clear_permission_cache,
+    )
 from canonical.launchpad.webapp.interaction import ANONYMOUS
 from canonical.launchpad.webapp.servers import (
     LaunchpadTestRequest,
@@ -311,6 +315,39 @@
     return (ret, recorder.statements)
 
 
+def record_two_runs(tested_method, item_creator, first_round_number,
+                    second_round_number=None):
+    """A helper that returns the two storm statement recorders
+    obtained when running tested_method after having run the
+    method {item_creator} {first_round_number} times and then
+    again after having run the same method {second_round_number}
+    times.
+
+    :return: a tuple containing the two recorders obtained by the successive
+        runs.
+    """
+    for i in range(first_round_number):
+        item_creator()
+    # Record how many queries are issued when {tested_method} is
+    # called after {item_creator} has been run {first_round_number}
+    # times.
+    flush_database_caches()
+    clear_permission_cache()
+    with StormStatementRecorder() as recorder1:
+        tested_method()
+    # Run {item_creator} {second_round_number} more times.
+    if second_round_number is None:
+        second_round_number = first_round_number
+    for i in range(second_round_number):
+        item_creator()
+    # Record again the number of queries issued.
+    flush_database_caches()
+    clear_permission_cache()
+    with StormStatementRecorder() as recorder2:
+        tested_method()
+    return recorder1, recorder2
+
+
 def run_with_storm_debug(function, *args, **kwargs):
     """A helper function to run a function with storm debug tracing on."""
     from storm.tracer import debug

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2011-12-08 05:13:31 +0000
+++ lib/lp/testing/factory.py	2011-12-11 01:49:34 +0000
@@ -80,7 +80,6 @@
     EmailAddressStatus,
     IEmailAddressSet,
     )
-from canonical.launchpad.interfaces.gpghandler import IGPGHandler
 from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
 from canonical.launchpad.interfaces.lpstorm import (
     IMasterStore,
@@ -242,6 +241,7 @@
 from lp.registry.interfaces.ssh import ISSHKeySet
 from lp.registry.model.milestone import Milestone
 from lp.registry.model.suitesourcepackage import SuiteSourcePackage
+from lp.services.gpg.interfaces import IGPGHandler
 from lp.services.job.interfaces.job import SuspendJobException
 from lp.services.log.logger import BufferLogger
 from lp.services.mail.signedmessage import SignedMessage
@@ -772,7 +772,7 @@
             address, person, email_status, account)
 
     def makeTeam(self, owner=None, displayname=None, email=None, name=None,
-                 description=None,
+                 description=None, icon=None, logo=None,
                  subscription_policy=TeamSubscriptionPolicy.OPEN,
                  visibility=None, members=None):
         """Create and return a new, arbitrary Team.
@@ -783,9 +783,11 @@
         :param displayname: The team's display name.  If not given we'll use
             the auto-generated name.
         :param description: Team team's description.
-        :type string:
+        :type description string:
         :param email: The email address to use as the team's contact address.
         :type email: string
+        :param icon: The team's icon.
+        :param logo: The team's logo.
         :param subscription_policy: The subscription policy of the team.
         :type subscription_policy: `TeamSubscriptionPolicy`
         :param visibility: The team's visibility. If it's None, the default
@@ -810,15 +812,19 @@
         team = getUtility(IPersonSet).newTeam(
             owner, name, displayname, teamdescription=description,
             subscriptionpolicy=subscription_policy)
+        naked_team = removeSecurityProxy(team)
         if visibility is not None:
             # Visibility is normally restricted to launchpad.Commercial, so
             # removing the security proxy as we don't care here.
-            removeSecurityProxy(team).visibility = visibility
+            naked_team.visibility = visibility
         if email is not None:
             team.setContactAddress(
                 getUtility(IEmailAddressSet).new(email, team))
+        if icon is not None:
+            naked_team.icon = icon
+        if logo is not None:
+            naked_team.logo = logo
         if members is not None:
-            naked_team = removeSecurityProxy(team)
             for member in members:
                 naked_team.addMember(member, owner)
         return team
@@ -1124,7 +1130,7 @@
 
         if registrant is None:
             if owner.is_team:
-                registrant = owner.teamowner
+                registrant = removeSecurityProxy(owner).teamowner
             else:
                 registrant = owner
 
@@ -3837,6 +3843,7 @@
                                            priority=None, status=None,
                                            scheduleddeletiondate=None,
                                            dateremoved=None,
+                                           datecreated=None,
                                            pocket=None, archive=None,
                                            source_package_release=None,
                                            sourcepackagename=None):
@@ -3878,6 +3885,9 @@
                 section_name=section_name,
                 priority=priority)
 
+        if datecreated is None:
+            datecreated = self.getUniqueDate()
+
         bpph = getUtility(IPublishingSet).newBinaryPublication(
             archive, binarypackagerelease, distroarchseries,
             binarypackagerelease.component, binarypackagerelease.section,
@@ -3885,6 +3895,7 @@
         naked_bpph = removeSecurityProxy(bpph)
         naked_bpph.status = status
         naked_bpph.dateremoved = dateremoved
+        naked_bpph.datecreated = datecreated
         naked_bpph.scheduleddeletiondate = scheduleddeletiondate
         naked_bpph.priority = priority
         if status == PackagePublishingStatus.PUBLISHED:
@@ -4209,7 +4220,7 @@
             system = self.getUniqueString('system-fingerprint')
         if submission_data is None:
             sample_data_path = os.path.join(
-                config.root, 'lib', 'canonical', 'launchpad', 'scripts',
+                config.root, 'lib', 'lp', 'hardwaredb', 'scripts',
                 'tests', 'simple_valid_hwdb_submission.xml')
             submission_data = open(sample_data_path).read()
         filename = self.getUniqueString('submission-file')

=== modified file 'lib/lp/testing/fixture.py'
--- lib/lp/testing/fixture.py	2011-11-23 07:29:09 +0000
+++ lib/lp/testing/fixture.py	2011-12-11 01:49:34 +0000
@@ -6,6 +6,7 @@
 __metaclass__ = type
 __all__ = [
     'CaptureOops',
+    'DemoMode',
     'PGBouncerFixture',
     'Urllib2Fixture',
     'ZopeAdapterFixture',
@@ -344,3 +345,21 @@
         self.timeline = get_request_timeline(
             get_current_browser_request())
         self.addCleanup(webapp.adapter.clear_request_started)
+
+
+class DemoMode(Fixture):
+    """Run with an is_demo configuration.
+
+    This changes the page styling, feature flag permissions, and perhaps
+    other things.
+    """
+
+    def setUp(self):
+        Fixture.setUp(self)
+        config.push('demo-fixture', '''
+[launchpad]
+is_demo: true
+site_message = This is a demo site mmk. \
+<a href="http://example.com";>File a bug</a>.
+            ''')
+        self.addCleanup(lambda: config.pop('demo-fixture'))

=== added directory 'lib/lp/testing/gpgkeys'
=== renamed file 'lib/canonical/launchpad/ftests/keys_for_tests.py' => 'lib/lp/testing/gpgkeys/__init__.py'
--- lib/canonical/launchpad/ftests/keys_for_tests.py	2010-10-21 04:19:36 +0000
+++ lib/lp/testing/gpgkeys/__init__.py	2011-12-11 01:49:34 +0000
@@ -25,15 +25,15 @@
 import gpgme
 from zope.component import getUtility
 
-from canonical.launchpad.interfaces.gpghandler import IGPGHandler
 from lp.registry.interfaces.gpg import (
     GPGKeyAlgorithm,
     IGPGKeySet,
     )
 from lp.registry.interfaces.person import IPersonSet
-
-
-gpgkeysdir = os.path.join(os.path.dirname(__file__), 'gpgkeys')
+from lp.services.gpg.interfaces import IGPGHandler
+
+
+gpgkeysdir = os.path.join(os.path.dirname(__file__), 'data')
 
 
 def import_public_key(email_addr):

=== renamed directory 'lib/canonical/launchpad/ftests/gpgkeys' => 'lib/lp/testing/gpgkeys/data'
=== modified symlink 'lib/lp/testing/keyserver/tests/keys/0x33C0A61893A5DC5EB325B29E415A12CAC2F30234.get'
=== target changed u'../../../../../canonical/launchpad/ftests/gpgkeys/ftpmaster@xxxxxxxxxxxxxxxxx' => u'../../../gpgkeys/data/ftpmaster@xxxxxxxxxxxxxxxxx'
=== modified symlink 'lib/lp/testing/keyserver/tests/keys/0x340CA3BB270E2716C9EE0B768E7EB7086C64A8C5.get'
=== target changed u'../../../../../canonical/launchpad/ftests/gpgkeys/foo.bar@xxxxxxxxxxxxxxxxx' => u'../../../gpgkeys/data/foo.bar@xxxxxxxxxxxxxxxxx'
=== modified symlink 'lib/lp/testing/keyserver/tests/keys/0x447DBF38C4F9C4ED752246B77D88913717B05A8F.get'
=== target changed u'../../../../../canonical/launchpad/ftests/gpgkeys/sign.only@xxxxxxxxxxxxxxxxx' => u'../../../gpgkeys/data/sign.only@xxxxxxxxxxxxxxxxx'
=== modified symlink 'lib/lp/testing/keyserver/tests/keys/0x84D205F03E1E67096CB54E262BE83793AACCD97C.get'
=== target changed u'../../../../../canonical/launchpad/ftests/gpgkeys/revoked.key@xxxxxxxxxxxxxxxxx' => u'../../../gpgkeys/data/revoked.key@xxxxxxxxxxxxxxxxx'
=== modified symlink 'lib/lp/testing/keyserver/tests/keys/0x961F4EB829D7D304A77477822BC8401620687895.get'
=== target changed u'../../../../../canonical/launchpad/ftests/gpgkeys/daniel.silverstone@xxxxxxxxxxxxxxxxx' => u'../../../gpgkeys/data/daniel.silverstone@xxxxxxxxxxxxxxxxx'
=== modified symlink 'lib/lp/testing/keyserver/tests/keys/0xA419AE861E88BC9E04B9C26FBA2B9389DFD20543.get'
=== target changed u'../../../../../canonical/launchpad/ftests/gpgkeys/test@xxxxxxxxxxxxxxxxx' => u'../../../gpgkeys/data/test@xxxxxxxxxxxxxxxxx'
=== modified symlink 'lib/lp/testing/keyserver/tests/keys/0xC85826521A6EF6A6037BB3F79FF2583E681B6469.get'
=== target changed u'../../../../../canonical/launchpad/ftests/gpgkeys/celso.providelo@xxxxxxxxxxxxxxxxx' => u'../../../gpgkeys/data/celso.providelo@xxxxxxxxxxxxxxxxx'
=== modified symlink 'lib/lp/testing/keyserver/tests/keys/0xECA5B797586F2E27381A16CFDE6C9167046C6D63.get'
=== target changed u'../../../../../canonical/launchpad/ftests/gpgkeys/expired.key@xxxxxxxxxxxxxxxxx' => u'../../../gpgkeys/data/expired.key@xxxxxxxxxxxxxxxxx'
=== modified symlink 'lib/lp/testing/keyserver/tests/keys/README'
=== target changed u'../../../../../canonical/launchpad/ftests/gpgkeys/README' => u'../../../gpgkeys/data/README'
=== modified file 'lib/lp/testing/keyserver/web.py'
--- lib/lp/testing/keyserver/web.py	2011-05-30 15:59:19 +0000
+++ lib/lp/testing/keyserver/web.py	2011-12-11 01:49:34 +0000
@@ -38,10 +38,9 @@
 from time import sleep
 
 from twisted.web.resource import Resource
-
 from zope.component import getUtility
 
-from canonical.launchpad.interfaces.gpghandler import (
+from lp.services.gpg.interfaces import (
     GPGKeyNotFoundError,
     IGPGHandler,
     MoreThanOneGPGKeyFound,

=== modified file 'lib/lp/testing/tests/test_factory.py'
--- lib/lp/testing/tests/test_factory.py	2011-08-29 00:05:37 +0000
+++ lib/lp/testing/tests/test_factory.py	2011-12-11 01:49:34 +0000
@@ -176,6 +176,12 @@
         bpph = self.factory.makeBinaryPackagePublishingHistory()
         self.assertNotEqual(None, bpph.datecreated)
 
+    def test_makeBinaryPackagePublishingHistory_uses_datecreated(self):
+        datecreated = self.factory.getUniqueDate()
+        bpph = self.factory.makeBinaryPackagePublishingHistory(
+            datecreated=datecreated)
+        self.assertEqual(datecreated, bpph.datecreated)
+
     def test_makeBinaryPackagePublishingHistory_sets_datepub_PENDING(self):
         bpph = self.factory.makeBinaryPackagePublishingHistory(
             status=PackagePublishingStatus.PENDING)

=== modified file 'lib/lp/translations/tests/test_pofilestatsjob.py'
--- lib/lp/translations/tests/test_pofilestatsjob.py	2011-11-10 15:02:49 +0000
+++ lib/lp/translations/tests/test_pofilestatsjob.py	2011-12-11 01:49:34 +0000
@@ -6,15 +6,18 @@
 __metaclass__ = type
 
 
+from canonical.config import config
 from canonical.launchpad.webapp.testing import verifyObject
 from canonical.testing.layers import (
     LaunchpadZopelessLayer,
     )
+from lp.app.enums import ServiceUsage
 from lp.services.job.interfaces.job import (
     IJobSource,
     IRunnableJob,
     )
 from lp.testing import TestCaseWithFactory
+from lp.testing.dbuser import dbuser
 from lp.translations.interfaces.pofilestatsjob import IPOFileStatsJobSource
 from lp.translations.interfaces.side import TranslationSide
 from lp.translations.model import pofilestatsjob
@@ -45,7 +48,27 @@
         job = pofilestatsjob.schedule(pofile.id)
         # Just scheduling the job doesn't update the statistics.
         self.assertEqual(pofile.potemplate.messageCount(), 0)
-        job.run()
+        with dbuser(config.pofile_stats.dbuser):
+            job.run()
+        # Now that the job ran, the statistics have been updated.
+        self.assertEqual(pofile.potemplate.messageCount(), 1)
+
+    def test_with_product(self):
+        product = self.factory.makeProduct(
+            translations_usage=ServiceUsage.LAUNCHPAD)
+        productseries = self.factory.makeProductSeries(product=product)
+        potemplate = self.factory.makePOTemplate(productseries=productseries)
+        pofile = self.factory.makePOFile('en', potemplate)
+        # Create a message so we have something to have statistics about.
+        singular = self.factory.getUniqueString()
+        self.factory.makePOTMsgSet(pofile.potemplate, singular)
+        # The statistics are still at 0, even though there is a message.
+        self.assertEqual(potemplate.messageCount(), 0)
+        job = pofilestatsjob.schedule(pofile.id)
+        # Just scheduling the job doesn't update the statistics.
+        self.assertEqual(pofile.potemplate.messageCount(), 0)
+        with dbuser(config.pofile_stats.dbuser):
+            job.run()
         # Now that the job ran, the statistics have been updated.
         self.assertEqual(pofile.potemplate.messageCount(), 1)
 

=== modified file 'utilities/apidoc-index.pt'
--- utilities/apidoc-index.pt	2010-08-21 13:28:33 +0000
+++ utilities/apidoc-index.pt	2011-12-11 01:49:34 +0000
@@ -10,7 +10,7 @@
 
     <style type="text/css">
             body {
-                font-family: UbuntuBeta, Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
+                font-family: Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;
                 font-size: 0.85em;
                 margin: 2em 8em;
             }

=== modified file 'utilities/make-lp-user'
--- utilities/make-lp-user	2011-10-18 09:36:12 +0000
+++ utilities/make-lp-user	2011-12-11 01:49:34 +0000
@@ -42,7 +42,7 @@
 
 from zope.component import getUtility
 
-from canonical.launchpad.interfaces.gpghandler import IGPGHandler
+from lp.services.gpg.interfaces import IGPGHandler
 from canonical.launchpad.scripts import execute_zcml_for_scripts
 from canonical.lazr.timeout import set_default_timeout_function
 from lp.registry.interfaces.gpg import GPGKeyAlgorithm, IGPGKeySet