Summary: Load the groups in the group menu with AJAX and delete the

jump to group modal. This cuts page rendering speed in more than half
for simple pages and similarly shrinks them by a factor 3-4, while
keeping the user experience the same. Fallbacks are in place for
non-JS clients.

There's still some overhead in the menu generation compared to just
rendering the page content, but the group menu was definitely a major
culprit.
 - Legacy-Id: 9077
This commit is contained in:
Ole Laursen 2015-02-12 16:01:05 +00:00
parent a92752bbcf
commit 9760cd9c8e
8 changed files with 98 additions and 69 deletions

View file

@ -31,8 +31,8 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from django import template
from django.core.cache import cache
from django.template.loader import render_to_string
from django.db import models
from ietf.group.models import Group
@ -44,27 +44,18 @@ area_short_names = {
}
@register.simple_tag
def wg_menu(flavor=""):
res = cache.get('wgmenu' + flavor)
if res:
return res
def wg_menu():
parents = Group.objects.filter(models.Q(type="area") | models.Q(type="irtf", acronym="irtf"),
state="active").order_by('type_id', 'acronym')
areas = Group.objects.filter(type="area", state="active").order_by('acronym')
wgs = Group.objects.filter(type="wg", state="active", parent__in=areas).order_by("acronym")
rgs = Group.objects.filter(type="rg", state="active").order_by("acronym")
for p in parents:
p.short_name = area_short_names.get(p.acronym) or p.name
if p.short_name.endswith(" Area"):
p.short_name = p.short_name[:-len(" Area")]
for a in areas:
a.short_area_name = area_short_names.get(a.acronym) or a.name
if a.short_area_name.endswith(" Area"):
a.short_area_name = a.short_area_name[:-len(" Area")]
if p.type_id == "area":
p.menu_url = "/wg/#" + p.acronym
elif p.acronym == "irtf":
p.menu_url = "/rg/"
a.active_groups = [g for g in wgs if g.parent_id == a.id]
areas = [a for a in areas if a.active_groups]
if flavor == "modal":
res = render_to_string('base/menu_wg_modal.html', {'areas':areas, 'rgs':rgs})
else:
res = render_to_string('base/menu_wg.html', {'areas':areas, 'rgs':rgs})
cache.set('wgmenu' + flavor, res, 30*60)
return res
return render_to_string('base/menu_wg.html', { 'parents': parents })

View file

@ -1,7 +1,11 @@
import json
from collections import defaultdict
from django.http import HttpResponse
from django.http import HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404
from django.core.urlresolvers import reverse as urlreverse
from django.utils.html import escape
from django.views.decorators.cache import cache_page, cache_control
from ietf.group.models import Group
@ -12,3 +16,14 @@ def group_json(request, acronym):
sort_keys=True, indent=2),
content_type="text/json")
@cache_control(public=True)
@cache_page(30 * 60)
def group_menu_data(request):
groups = Group.objects.filter(state="active", type__in=("wg", "rg"), parent__state="active").order_by("acronym")
groups_by_parent = defaultdict(list)
for g in groups:
url = urlreverse("ietf.group.info.group_home", kwargs={ 'group_type': g.type_id, 'acronym': g.acronym })
groups_by_parent[g.parent_id].append({ 'acronym': g.acronym, 'name': escape(g.name), 'url': url })
return JsonResponse(groups_by_parent)

View file

@ -2,6 +2,7 @@ import os
import shutil
import calendar
import datetime
import json
from pyquery import PyQuery
import debug # pyflakes:ignore
@ -934,3 +935,26 @@ class CustomizeWorkflowTests(TestCase):
self.assertEqual(len(q('form').find('input[name=tag][value="%s"]' % tag.pk).parents("form").find("input[name=active]")), 1)
group = Group.objects.get(acronym=group.acronym)
self.assertTrue(tag in group.unused_tags.all())
class AjaxTests(TestCase):
def test_group_menu_data(self):
make_test_data()
r = self.client.get(urlreverse("group_menu_data"))
self.assertEqual(r.status_code, 200)
parents = json.loads(r.content)
area = Group.objects.get(type="area", acronym="farfut")
self.assertTrue(str(area.id) in parents)
mars_wg_data = None
for g in parents[str(area.id)]:
if g["acronym"] == "mars":
mars_wg_data = g
break
self.assertTrue(mars_wg_data)
mars_wg = Group.objects.get(acronym="mars")
self.assertEqual(mars_wg_data["name"], mars_wg.name)

View file

@ -3,6 +3,7 @@
from django.conf.urls import patterns
urlpatterns = patterns('',
(r'^groupmenu.json', 'ietf.group.ajax.group_menu_data', None, "group_menu_data"),
(r'^(?P<acronym>[a-z0-9]+).json$', 'ietf.group.ajax.group_json'),
(r'^chartering/$', 'ietf.group.info.chartering_groups'),
(r'^chartering/create/(?P<group_type>(wg|rg))/$', 'ietf.group.edit.edit', {'action': "charter"}, "group_create"),

View file

@ -70,7 +70,7 @@
{% if flavor == "top" %}<li class="divider visible-lg-block"></li>{% endif %}
<li {%if flavor == "top" %}class="dropdown-header visible-lg-block"{% else %}class="nav-header hidden-nojs"{% endif %}>By area/parent</li>
{% wg_menu %}
<li class="hidden-lg hidden-nojs"><a href="#" data-toggle="modal" data-target="#navmodal">Jump to group</a></li>
{# <li class="hidden-lg hidden-nojs"><a href="#" data-toggle="modal" data-target="#navmodal">Jump to group</a></li> #}
{% if flavor == "top" %}<li class="divider hidden-xs"></li>{% endif %}
<li {%if flavor == "top" %}class="dropdown-header hidden-xs"{% else %}class="nav-header"{% endif %}>New work</li>

View file

@ -1,19 +1,5 @@
{% for area in areas %}
<li class="hidden-nojs dropdown-submenu visible-lg-block">
<a href="/wg/#{{ area.acronym }}">{{ area.short_area_name }}</a>
<ul class="dropdown-menu" role="menu">
{% for g in area.active_groups %}
<li><a href="{% url "ietf.group.info.group_home" group_type=g.type_id acronym=g.acronym %}">{{ g.acronym }} &mdash; {{ g.name}}</a></li>
{% endfor %}
</ul>
</li>
{% for p in parents %}
<li class="hidden-nojs dropdown-submenu visible-lg-block group-menu group-parent-{{ p.id }}">
<a href="{{ p.menu_url }}">{{ p.short_name }}</a>
</li>
{% endfor %}
<li class="hidden-nojs dropdown-submenu visible-lg-block">
<a href="">IRTF</a>
<ul class="dropdown-menu" role="menu">
{% for group in rgs %}
<li><a href="{% url "ietf.group.info.group_home" group_type=group.type_id acronym=group.acronym %}">{{ group.acronym }} &mdash; {{ group.name}}</a></li>
{% endfor %}
</ul>
</li>

View file

@ -24,33 +24,9 @@
<link rel="apple-touch-icon-precomposed" href="/images/ietficon.png">
</head>
<body {% block bodyAttrs %}{%endblock%}>
<body {% block bodyAttrs %}{%endblock%} data-group-menu-data-url="{% url "group_menu_data" %}">
{% filter amp|smartypants %}
<div class="modal fade" id="navmodal" tabindex="-1" role="dialog" {#aria-labelledby="navmodallabel"#} aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
{% comment %}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="navmodallabel">Area &amp; WG navigation</h4>
</div>
{% endcomment %}
<div class="modal-body">
{% wg_menu "modal" %}
</div>
{% comment %}
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
{% endcomment %}
</div>
</div>
</div>
<nav class="navbar {% if server_mode and server_mode != "production" %}navbar-dev{% else %}navbar-default{% endif %} navbar-fixed-top" role="navigation">
<nav class="navbar {% if server_mode and server_mode != "production" %}navbar-dev{% else %}navbar-default{% endif %} navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar-collapse">
@ -178,7 +154,6 @@
</script>
{% endfilter %}
{% include "debug.html" %}
</body>
</html>

View file

@ -201,3 +201,40 @@ $(document).ready(function () {
// explicit requirement asterisks
$("form.show-required").find("input[required],select[required],textarea[required]").closest(".form-group").find("label").addClass("required");
});
$(document).ready(function () {
// load data for the menu
$.ajax({
url: $(document.body).data("group-menu-data-url"),
type: 'GET',
dataType: "json",
success: function (data) {
for (var parentId in data) {
var attachTo = $(".group-menu.group-parent-" + parentId);
if (attachTo.length == 0)
continue;
attachTo.find(".dropdown-menu").remove();
var menu = ['<ul class="dropdown-menu" role="menu">'];
var groups = data[parentId];
for (var i = 0; i < groups.length; ++i) {
var g = groups[i];
menu.push('<li><a href="' + g.url + '">' + g.acronym +' &mdash; ' + g.name + '</a></li>');
}
menu.push('</ul>');
attachTo.append(menu.join(""));
}
},
error: function (err) {
$(".group-menu").removeClass("dropdown-submenu");
if (console.log)
console.log("Could not load menu data");
}
});
});