Skip to content

Commit

Permalink
Project Admin page optimizations (mozilla#3160)
Browse files Browse the repository at this point in the history
* Save newly added ProjectLocale objects in bulk
* Reduce notifications.count() DB calls from 3 to 1. This change also benefits other (non-Translate) views.
* Retrieve project locales asynchronously to speed up loading of the page
* Minor ride along enhancements
  • Loading branch information
mathjazz authored Apr 3, 2024
1 parent 5d99187 commit ee29b88
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 44 deletions.
15 changes: 15 additions & 0 deletions pontoon/administration/static/js/admin_project.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ $(function () {
});
});

// Suggest slugified name for tags
$('body').on('blur', '[id^=id_tag_set-][id$=-name]', function () {
const target = $('input#' + $(this).attr('id').replace('-name', '-slug'));
const $this = this;
Expand All @@ -167,6 +168,20 @@ $(function () {
});
});

// Set locales to existing projects to be copied to the current project
$.ajax({
url: '/admin/get-project-locales/',
success: function (data) {
$('#copy-locales option').each(function () {
const project = $(this).text();
const locales = data[project];
if (locales) {
$(this).val(JSON.stringify(locales));
}
});
},
});

// Copy locales from another project
$('#copy-locales option').on('click', function () {
let projectLocales = [];
Expand Down
2 changes: 1 addition & 1 deletion pontoon/administration/templates/admin_project.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ <h3>Locales</h3>
<select id="copy-locales">
<option selected>Copy locales from another project</option>
{% for project in projects %}
<option value="{{ project.locales|to_json() }}">{{ project.name }}</option>
<option>{{ project }}</option>
{% endfor %}
</select>
</section>
Expand Down
14 changes: 10 additions & 4 deletions pontoon/administration/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
path(
"sync/",
views.manually_sync_project,
name="pontoon.project.sync",
name="pontoon.admin.project.sync",
),
# Project strings
path(
Expand All @@ -37,13 +37,19 @@
path(
"pretranslate/",
views.manually_pretranslate_project,
name="pontoon.project.sync",
name="pontoon.admin.project.pretranslate",
),
# Edit project
path("", views.manage_project, name="pontoon.admin.project"),
]
),
),
# Get slug
path("get-slug/", views.get_slug, name="pontoon.admin.get_slug"),
# AJAX view: Get slug
path("get-slug/", views.get_slug, name="pontoon.admin.project.slug"),
# AJAX view: Get project locales
path(
"get-project-locales/",
views.get_project_locales,
name="pontoon.admin.project.locales",
),
]
69 changes: 43 additions & 26 deletions pontoon/administration/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django.core.exceptions import PermissionDenied
from django.db import transaction, IntegrityError
from django.db.models import Max
from django.http import Http404, HttpResponse, HttpResponseForbidden
from django.http import Http404, HttpResponse, HttpResponseForbidden, JsonResponse
from django.shortcuts import render
from django.template.defaultfilters import slugify
from django.utils import timezone
Expand All @@ -20,7 +20,7 @@
TagInlineFormSet,
)
from pontoon.base import utils
from pontoon.base.utils import require_AJAX, is_ajax
from pontoon.base.utils import require_AJAX
from pontoon.base.models import (
Entity,
Locale,
Expand Down Expand Up @@ -54,31 +54,51 @@ def admin(request):
return render(request, "admin.html", {"admin": True, "projects": projects})


@login_required(redirect_field_name="", login_url="/403")
@require_AJAX
def get_slug(request):
"""Convert project name to slug."""
log.debug("Convert project name to slug.")

if not request.user.has_perm("base.can_manage_project"):
log.error("Insufficient privileges.")
return HttpResponse("error")

if not is_ajax(request):
log.error("Non-AJAX request")
return HttpResponse("error")
return JsonResponse(
{
"status": False,
"message": "Forbidden: You don't have permission to retrieve the project slug.",
},
status=403,
)

try:
name = request.GET["name"]
except MultiValueDictKeyError as e:
log.error(str(e))
return HttpResponse("error")

log.debug("Name: " + name)
return JsonResponse(
{"status": False, "message": f"Bad Request: {e}"},
status=400,
)

slug = slugify(name)
log.debug("Slug: " + slug)
return HttpResponse(slug)


@login_required(redirect_field_name="", login_url="/403")
@require_AJAX
def get_project_locales(request):
"""Get a map of project names and corresponding locale codes."""
if not request.user.has_perm("base.can_manage_project"):
return JsonResponse(
{
"status": False,
"message": "Forbidden: You don't have permission to retrieve project locales.",
},
status=403,
)

data = {}
for p in Project.objects.prefetch_related("locales"):
data[p.name] = [locale.pk for locale in p.locales.all()]

return JsonResponse(data, safe=False)


@transaction.atomic
def manage_project(request, slug=None, template="admin_project.html"):
"""Admin project."""
Expand Down Expand Up @@ -161,8 +181,13 @@ def manage_project(request, slug=None, template="admin_project.html"):
.exclude(locale__pk__in=[loc.pk for loc in locales])
.delete()
)
for locale in locales:
ProjectLocale.objects.get_or_create(project=project, locale=locale)

project_locales = [
ProjectLocale(project=project, locale=locale) for locale in locales
]
ProjectLocale.objects.bulk_create(
project_locales, ignore_conflicts=True
)

project_locales = ProjectLocale.objects.filter(project=project)

Expand Down Expand Up @@ -238,15 +263,7 @@ def manage_project(request, slug=None, template="admin_project.html"):
# Override default label suffix
form.label_suffix = ""

projects = []
for p in Project.objects.prefetch_related("locales").order_by("name"):
projects.append(
{
"name": p.name,
# Cannot use values_list() here, because it hits the DB again
"locales": [loc.pk for loc in p.locales.all()],
}
)
projects = sorted([p.name for p in Project.objects.all()])

locales_available = Locale.objects.exclude(pk__in=locales_readonly).exclude(
pk__in=locales_selected
Expand Down
15 changes: 5 additions & 10 deletions pontoon/base/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,10 +334,8 @@ def notification_list(self):
return notifications


@property
def menu_notifications(self):
def menu_notifications(self, unread_count):
"""A list of notifications to display in the notifications menu."""
unread_count = self.notifications.unread().count()
count = settings.NOTIFICATIONS_MAX_COUNT

if unread_count > count:
Expand All @@ -348,15 +346,12 @@ def menu_notifications(self):
]


@property
def unread_notifications_display(self):
def unread_notifications_display(self, unread_count):
"""Textual representation of the unread notifications count."""
count = self.notifications.unread().count()

if count > 9:
if unread_count > 9:
return "9+"

return count
return unread_count


@property
Expand Down Expand Up @@ -454,7 +449,7 @@ def serialized_notifications(self):
return {
"has_unread": unread_count > 0,
"notifications": notifications,
"unread_count": str(self.unread_notifications_display),
"unread_count": str(self.unread_notifications_display(unread_count)),
}


Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
{% macro menu() %}
{% if user.is_authenticated %}

<div id="notifications" class="notifications select{% if user.unread_notifications_display %} unread{% endif %}">
{% set unread_count = user.notifications.unread().count() %}
<div id="notifications" class="notifications select{% if unread_count %} unread{% endif %}">

<div class="button selector">
<i class="icon far fa-bell fa-fw"></i>
<i class="badge">{{ user.unread_notifications_display }}</i>
<i class="badge">{{ user.unread_notifications_display(unread_count) }}</i>
</div>

<div class="menu">
{{ list(notifications=user.menu_notifications) }}
{{ list(notifications=user.menu_notifications(unread_count)) }}

<ul>
<li class="horizontal-separator"></li>
Expand Down

0 comments on commit ee29b88

Please sign in to comment.