Repository: CTPUG/wafer

Issue:
Sponsors with multiple packages are listed for each package
When a sponsor takes multiple packages (sponsorship and add-on package, for example), they are listed in the sponsor list and sponsor menu for each package, which is a bit surprising. See Microsoft from PyCon ZA 2018, for example.

![microsoft_multiple](https://user-images.githubusercontent.com/642056/47145177-6b953480-d2c9-11e8-9ed4-c19440cd0cb7.png)


We should list sponsors only once, and add some decent way of marking that sponsors have taken multiple packages in the list.


Discussion:

Resolving patch:
diff --git a/wafer/sponsors/models.py b/wafer/sponsors/models.py
--- a/wafer/sponsors/models.py
+++ b/wafer/sponsors/models.py
@@ -118,10 +118,15 @@ def sponsor_menu(
         packages_item=_("Sponsorship packages")):
     """Add sponsor menu links."""
     root_menu.add_menu(menu, label, items=[])
+    added_to_menu = set()
     for sponsor in (
             Sponsor.objects.all()
             .order_by('packages', 'order', 'id')
             .prefetch_related('packages')):
+        if sponsor in added_to_menu:
+            # We've already added this in a previous packaged
+            continue
+        added_to_menu.add(sponsor)
         symbols = sponsor.symbols()
         if symbols:
             item_name = u"» %s %s" % (sponsor.name, symbols)


Test patch:
diff --git a/wafer/sponsors/tests/test_models.py b/wafer/sponsors/tests/test_models.py
--- a/wafer/sponsors/tests/test_models.py
+++ b/wafer/sponsors/tests/test_models.py
@@ -9,13 +9,32 @@
 from wafer.sponsors.models import SponsorshipPackage, Sponsor
 
 
+def create_package(name, symbol, price, order=None):
+    """Create a sponsor with the correct order if desired"""
+    package = SponsorshipPackage.objects.create(
+        name=name, symbol=symbol, price=price)
+    if order:
+        package.order = order
+    return package
+
+
 def create_sponsor(name, packages):
     """Create a sponsort with the given name and packages."""
     sponsor = Sponsor.objects.create(name=name)
     for name, symbol, price in packages:
-        package = SponsorshipPackage.objects.create(
-            name=name, symbol=symbol, price=price)
+        package = create_package(name, symbol, price)
         sponsor.packages.add(package)
+    # We need to save here to trigger the menu rebuild with the right package info
+    sponsor.save()
+    return sponsor
+
+
+def create_sponsor_in_existing_packages(name, packages):
+    """Create a sponsor and add it to an existing package."""
+    sponsor = Sponsor.objects.create(name=name)
+    for package in packages:
+        sponsor.packages.add(package)
+    sponsor.save()
     return sponsor
 
 
@@ -79,7 +98,85 @@ def test_cached_menu(self):
         menu = get_cached_menus()
         self.assertEqual(menu.items, [
             Menu.mk_menu(u"sponsors", u"Sponsors", items=[
-                Menu.mk_item(u"» Awesome Co", sponsor.get_absolute_url()),
+                Menu.mk_item(u"» Awesome Co *", sponsor.get_absolute_url()),
+                Menu.mk_item(u"Our sponsors", reverse('wafer_sponsors')),
+                Menu.mk_item(u"Sponsorship packages", reverse(
+                    'wafer_sponsorship_packages')),
+            ])
+        ])
+
+    def test_multiple_packages(self):
+        """Test that multiple sponsors get ordered correctly."""
+        gold_package = create_package(u"Gold", u"*", 500, 1)
+        silver_package = create_package(u"Silver", u"+", 400, 2)
+        bronze_package = create_package(u"Bronze", u"-", 300, 3)
+        # We create 2 sponsors for each package, interleaving creation order""""
+        sponsor1 = create_sponsor_in_existing_packages(u"Awesome1 Co", [gold_package])
+        sponsor2 = create_sponsor_in_existing_packages(u"Awesome2 Co", [bronze_package])
+        sponsor3 = create_sponsor_in_existing_packages(u"Awesome3 Co", [silver_package])
+        sponsor4 = create_sponsor_in_existing_packages(u"Awesome4 Co", [bronze_package])
+        sponsor5 = create_sponsor_in_existing_packages(u"Awesome5 Co", [silver_package])
+        sponsor6 = create_sponsor_in_existing_packages(u"Awesome6 Co", [gold_package])
+        # Sponsors in the menu should be sorted by package
+        menu = get_cached_menus()
+        self.assertEqual(menu.items, [
+            Menu.mk_menu(u"sponsors", u"Sponsors", items=[
+                Menu.mk_item(u"» Awesome1 Co *", sponsor1.get_absolute_url()),
+                Menu.mk_item(u"» Awesome6 Co *", sponsor6.get_absolute_url()),
+                Menu.mk_item(u"» Awesome3 Co +", sponsor3.get_absolute_url()),
+                Menu.mk_item(u"» Awesome5 Co +", sponsor5.get_absolute_url()),
+                Menu.mk_item(u"» Awesome2 Co -", sponsor2.get_absolute_url()),
+                Menu.mk_item(u"» Awesome4 Co -", sponsor4.get_absolute_url()),
+                Menu.mk_item(u"Our sponsors", reverse('wafer_sponsors')),
+                Menu.mk_item(u"Sponsorship packages", reverse(
+                    'wafer_sponsorship_packages')),
+            ])
+        ])
+
+        # Test that changing the order field changes the ordering within the package
+        sponsor1.order = 2
+        sponsor3.order = 2
+        # Ensure menus are regenerated
+        sponsor1.save()
+        sponsor3.save()
+
+        menu = get_cached_menus()
+        self.assertEqual(menu.items, [
+            Menu.mk_menu(u"sponsors", u"Sponsors", items=[
+                Menu.mk_item(u"» Awesome6 Co *", sponsor6.get_absolute_url()),
+                Menu.mk_item(u"» Awesome1 Co *", sponsor1.get_absolute_url()),
+                Menu.mk_item(u"» Awesome5 Co +", sponsor5.get_absolute_url()),
+                Menu.mk_item(u"» Awesome3 Co +", sponsor3.get_absolute_url()),
+                Menu.mk_item(u"» Awesome2 Co -", sponsor2.get_absolute_url()),
+                Menu.mk_item(u"» Awesome4 Co -", sponsor4.get_absolute_url()),
+                Menu.mk_item(u"Our sponsors", reverse('wafer_sponsors')),
+                Menu.mk_item(u"Sponsorship packages", reverse(
+                    'wafer_sponsorship_packages')),
+            ])
+        ])
+
+    def test_single_sponsor_multiple_packages(self):
+        """Test that a sponsor in multiple packages only gets shown once, but with all the symbols"""
+        gold_package = create_package(u"Gold", u"*", 500, 1)
+        silver_package = create_package(u"Silver", u"+", 400, 2)
+        bronze_package = create_package(u"Bronze", u"-", 300, 3)
+        # We create 2 sponsors for each package, interleaving creation order""""
+        sponsor1 = create_sponsor_in_existing_packages(u"Awesome1 Co", [gold_package, bronze_package])
+        sponsor2 = create_sponsor_in_existing_packages(u"Awesome2 Co", [bronze_package])
+        sponsor3 = create_sponsor_in_existing_packages(u"Awesome3 Co", [silver_package, gold_package])
+        sponsor4 = create_sponsor_in_existing_packages(u"Awesome4 Co", [bronze_package])
+        sponsor5 = create_sponsor_in_existing_packages(u"Awesome5 Co", [silver_package])
+        sponsor6 = create_sponsor_in_existing_packages(u"Awesome6 Co", [gold_package])
+        # Sponsors in the menu should be sorted by package
+        menu = get_cached_menus()
+        self.assertEqual(menu.items, [
+            Menu.mk_menu(u"sponsors", u"Sponsors", items=[
+                Menu.mk_item(u"» Awesome1 Co *-", sponsor1.get_absolute_url()),
+                Menu.mk_item(u"» Awesome3 Co *+", sponsor3.get_absolute_url()),
+                Menu.mk_item(u"» Awesome6 Co *", sponsor6.get_absolute_url()),
+                Menu.mk_item(u"» Awesome5 Co +", sponsor5.get_absolute_url()),
+                Menu.mk_item(u"» Awesome2 Co -", sponsor2.get_absolute_url()),
+                Menu.mk_item(u"» Awesome4 Co -", sponsor4.get_absolute_url()),
                 Menu.mk_item(u"Our sponsors", reverse('wafer_sponsors')),
                 Menu.mk_item(u"Sponsorship packages", reverse(
                     'wafer_sponsorship_packages')),