Repository: pypi/warehouse

Issue:
Add 'sole owner' badge on list of projects
We've had a lot of positive feedback on the bus factor feature on the account settings page.

It would be nice to also expose this information on `/manage/projects/` so that users don't have to hunt for it :)

---

**Good First Issue**: This issue is good for first time contributors. If you've already contributed to Warehouse, please work on [another issue without this label](https://github.com/pypa/warehouse/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+-label%3A%22good+first+issue%22) instead. If there is not a corresponding pull request for this issue, it is up for grabs. For directions for getting set up, see our [Getting Started Guide](https://warehouse.pypa.io/development/getting-started/). If you are working on this issue and have questions, please feel free to ask them here, [`#pypa-dev` on Freenode](https://webchat.freenode.net/?channels=%23pypa-dev), or the [pypa-dev mailing list](https://groups.google.com/forum/#!forum/pypa-dev).
Add sole owner badge on lists of projects
closes #3127


Discussion:
Something like this using the `<span class="badge>` component would be ideal:

![screenshot from 2018-03-03 15-35-22](https://user-images.githubusercontent.com/3323703/36936109-834c78ca-1ef8-11e8-81dc-e5fbbb5fcddc.png)

How would you recommend fetching the status for this?

On the account settings page, this is done with a separate query, but I'd be reluctant to add another db query to this frequently used page just for the one badge.
@NotAFile You could reuse the same query, and for each project name, test if it's in the result, and display the badge conditionally.
@di yes, but that was what I was trying to avoid. It's basically two queries on the exact same thing. In django, I'd use the annotate functionality, but this doesn't seem to be available in SQAlchemy. I was wondering how you'd recommend doing this for this project. Two seperate queries and a membership check seems like the worst way to do it, but I might be missing something here.
I would be very surprised if what you want can't be done with SQLAlchemy. Possibly using the SubQuery or Aliasing support.
Can I try this one? @nlhkabu 
Yes, sounds great :)


Resolving patch:
diff --git a/warehouse/manage/views.py b/warehouse/manage/views.py
--- a/warehouse/manage/views.py
+++ b/warehouse/manage/views.py
@@ -39,6 +39,32 @@
 from warehouse.utils.project import confirm_project, destroy_docs, remove_project
 
 
+def user_projects(request):
+    """ Return all the projects for which the user is a sole owner """
+    projects_owned = (
+        request.db.query(Project)
+        .join(Role.project)
+        .filter(Role.role_name == "Owner", Role.user == request.user)
+        .subquery()
+    )
+
+    with_sole_owner = (
+        request.db.query(Role.package_name)
+        .join(projects_owned)
+        .filter(Role.role_name == "Owner")
+        .group_by(Role.package_name)
+        .having(func.count(Role.package_name) == 1)
+        .subquery()
+    )
+
+    return {
+        "projects_owned": request.db.query(projects_owned).all(),
+        "projects_sole_owned": (
+            request.db.query(Project).join(with_sole_owner).order_by(Project.name).all()
+        ),
+    }
+
+
 @view_defaults(
     route_name="manage.account",
     renderer="manage/account.html",
@@ -57,29 +83,7 @@ def __init__(self, request):
 
     @property
     def active_projects(self):
-        """ Return all the projects for with the user is a sole owner """
-        projects_owned = (
-            self.request.db.query(Project)
-            .join(Role.project)
-            .filter(Role.role_name == "Owner", Role.user == self.request.user)
-            .subquery()
-        )
-
-        with_sole_owner = (
-            self.request.db.query(Role.package_name)
-            .join(projects_owned)
-            .filter(Role.role_name == "Owner")
-            .group_by(Role.package_name)
-            .having(func.count(Role.package_name) == 1)
-            .subquery()
-        )
-
-        return (
-            self.request.db.query(Project)
-            .join(with_sole_owner)
-            .order_by(Project.name)
-            .all()
-        )
+        return user_projects(request=self.request)["projects_sole_owned"]
 
     @property
     def default_response(self):
@@ -287,19 +291,18 @@ def _key(project):
             return project.releases[0].created
         return project.created
 
+    all_user_projects = user_projects(request)
     projects_owned = set(
-        project.name
-        for project in (
-            request.db.query(Project.name)
-            .join(Role.project)
-            .filter(Role.role_name == "Owner", Role.user == request.user)
-            .all()
-        )
+        project.name for project in all_user_projects["projects_owned"]
+    )
+    projects_sole_owned = set(
+        project.name for project in all_user_projects["projects_sole_owned"]
     )
 
     return {
         "projects": sorted(request.user.projects, key=_key, reverse=True),
         "projects_owned": projects_owned,
+        "projects_sole_owned": projects_sole_owned,
     }
 
 


Test patch:
diff --git a/tests/unit/manage/test_views.py b/tests/unit/manage/test_views.py
--- a/tests/unit/manage/test_views.py
+++ b/tests/unit/manage/test_views.py
@@ -100,7 +100,7 @@ def test_active_projects(self, db_request):
 
         # A project with a sole owner that is not the user
         not_an_owner = ProjectFactory.create()
-        RoleFactory.create(user=user, project=not_an_owner, role_name="Maintatiner")
+        RoleFactory.create(user=user, project=not_an_owner, role_name="Maintainer")
         RoleFactory.create(user=another_user, project=not_an_owner, role_name="Owner")
 
         view = views.ManageAccountViews(db_request)
@@ -683,7 +683,12 @@ def test_manage_projects(self, db_request):
                 older_project_with_no_releases,
             ]
         )
+        user_second_owner = UserFactory(
+            projects=[project_with_older_release, older_project_with_no_releases]
+        )
         RoleFactory.create(user=db_request.user, project=project_with_newer_release)
+        RoleFactory.create(user=db_request.user, project=newer_project_with_no_releases)
+        RoleFactory.create(user=user_second_owner, project=project_with_newer_release)
 
         assert views.manage_projects(db_request) == {
             "projects": [
@@ -692,7 +697,11 @@ def test_manage_projects(self, db_request):
                 older_project_with_no_releases,
                 project_with_older_release,
             ],
-            "projects_owned": {project_with_newer_release.name},
+            "projects_owned": {
+                project_with_newer_release.name,
+                newer_project_with_no_releases.name,
+            },
+            "projects_sole_owned": {newer_project_with_no_releases.name},
         }