Skip to content

Commit 7808bbf

Browse files
authored
don't n+1 project/role queries on admin.user.detail (#17810)
* don't n+1 project/role queries on admin.user.detail * add tests, fix logic to show projects with no releases
1 parent 53bad90 commit 7808bbf

File tree

3 files changed

+56
-9
lines changed

3 files changed

+56
-9
lines changed

tests/unit/admin/views/test_users.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,16 @@ def test_gets_user(self, db_request):
149149
assert result["roles"] == roles
150150
assert result["emails_form"].emails[0].primary.data
151151
assert result["submitted_by_journals"] == journal_entries[:5]
152+
assert result["user_projects"] == [
153+
{
154+
"name": project.name,
155+
"normalized_name": project.normalized_name,
156+
"releases_count": 0,
157+
"total_size": 0,
158+
"lifecycle_status": None,
159+
"role_name": "Owner",
160+
}
161+
]
152162

153163
def test_updates_user(self, db_request):
154164
user = UserFactory.create()

warehouse/admin/templates/admin/users/detail.html

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ <h5 class="widget-user-desc text-center">{{ user.name }}</h5>
9797
</li>
9898
<li class="nav-item">
9999
<a href="#projects" class="nav-link">
100-
Projects - Total Owned Size <span class="float-right badge bg-success">{{ roles | selectattr("role_name", "equalto", "Owner") | sum(attribute="project.total_size") | filesizeformat(binary=True) }}</span>
100+
Projects - Total Owned Size <span class="float-right badge bg-success">{{ user_projects | selectattr("role_name", "equalto", "Owner") | sum(attribute="total_size") | filesizeformat(binary=True) }}</span>
101101
</a>
102102
</li>
103103
</ul>
@@ -192,11 +192,11 @@ <h4 class="modal-title" id="nukeModalLabel">Nuke user {{ user.username }}?</h4>
192192
This will also delete the following projects and their respective releases:
193193
</p>
194194
<ul>
195-
{% for project in user.projects %}
195+
{% for project in user_projects %}
196196
<li>
197197
<a href="{{ request.route_path('admin.project.detail', project_name=project.normalized_name) }}">
198198
{{ project.name }}
199-
</a> ({{ project.releases|length }} releases)
199+
</a> ({{ project.releases_count }} releases)
200200
</li>
201201
{% endfor %}
202202
</ul>
@@ -795,11 +795,11 @@ <h3 class="card-title">Projects</h3>
795795
</tr>
796796
</thead>
797797
<tbody>
798-
{% for role in roles %}
798+
{% for project in user_projects %}
799799
<tr>
800-
<td><a href="{{ request.route_path('admin.project.detail', project_name=role.project.normalized_name) }}">{{ role.project.name }}</a></td>
801-
<td>{{ role.role_name }}</td>
802-
<td>{{ role.project.total_size | filesizeformat(binary=True) }}</td>
800+
<td><a href="{{ request.route_path('admin.project.detail', project_name=project.normalized_name) }}">{{ project.name }}</a></td>
801+
<td>{{ project.role_name }}</td>
802+
<td>{{ project.total_size | filesizeformat(binary=True) }}</td>
803803
</tr>
804804
{% endfor %}
805805
</tbody>

warehouse/admin/views/users.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from paginate_sqlalchemy import SqlalchemyOrmPage as SQLAlchemyORMPage
2424
from pyramid.httpexceptions import HTTPBadRequest, HTTPMovedPermanently, HTTPSeeOther
2525
from pyramid.view import view_config
26-
from sqlalchemy import or_, select
26+
from sqlalchemy import func, or_, select
2727
from sqlalchemy.orm import joinedload
2828

2929
from warehouse.accounts.interfaces import (
@@ -45,7 +45,7 @@
4545
send_password_reset_by_admin_email,
4646
)
4747
from warehouse.observations.models import ObservationKind
48-
from warehouse.packaging.models import JournalEntry, Project, Role
48+
from warehouse.packaging.models import JournalEntry, Project, Release, Role
4949
from warehouse.utils.paginate import paginate_url_factory
5050

5151

@@ -185,8 +185,45 @@ def user_detail(user, request):
185185
.all()
186186
)
187187

188+
stmt = (
189+
select(
190+
Project.name,
191+
Project.normalized_name,
192+
Project.lifecycle_status,
193+
Project.total_size,
194+
Role.role_name,
195+
func.count(Release.id),
196+
)
197+
.join(Role, Project.id == Role.project_id)
198+
.outerjoin(Release, Project.id == Release.project_id)
199+
.where(Role.user_id == user.id)
200+
.group_by(
201+
Project.name,
202+
Project.normalized_name,
203+
Project.lifecycle_status,
204+
Project.total_size,
205+
Role.role_name,
206+
)
207+
.order_by(Project.normalized_name.asc())
208+
)
209+
210+
user_projects = []
211+
212+
for row in request.db.execute(stmt):
213+
project = {
214+
"name": row.name,
215+
"normalized_name": row.normalized_name,
216+
"lifecycle_status": row.lifecycle_status,
217+
"total_size": row.total_size,
218+
"role_name": row.role_name,
219+
"releases_count": row.count,
220+
}
221+
222+
user_projects.append(project)
223+
188224
return {
189225
"user": user,
226+
"user_projects": user_projects,
190227
"form": form,
191228
"emails_form": emails_form,
192229
"roles": roles,

0 commit comments

Comments
 (0)