diff options
author | Aurélien Bompard <aurelien@bompard.org> | 2013-11-13 12:47:42 +0100 |
---|---|---|
committer | Aurélien Bompard <aurelien@bompard.org> | 2013-11-20 19:15:40 +0100 |
commit | cdf8249b4cb0a05e88c181674aa0a70f25019e00 (patch) | |
tree | dcb444f60b8dfb0be7a8f9840e18c939f608ed68 | |
parent | 85037f95b2b22a812938dc19aa2a9da35a6ea46f (diff) | |
download | hyperkitty-cdf8249b4cb0a05e88c181674aa0a70f25019e00.tar.gz hyperkitty-cdf8249b4cb0a05e88c181674aa0a70f25019e00.tar.xz hyperkitty-cdf8249b4cb0a05e88c181674aa0a70f25019e00.zip |
Front page redesign
-rw-r--r-- | hyperkitty/lib/view_helpers.py | 25 | ||||
-rw-r--r-- | hyperkitty/static/hyperkitty/css/hyperkitty-common.css | 7 | ||||
-rw-r--r-- | hyperkitty/static/hyperkitty/css/hyperkitty-index.css | 81 | ||||
-rw-r--r-- | hyperkitty/static/hyperkitty/js/hyperkitty-common.js | 133 | ||||
-rw-r--r-- | hyperkitty/static/hyperkitty/js/hyperkitty-frontpage.js | 67 | ||||
-rw-r--r-- | hyperkitty/static/hyperkitty/js/hyperkitty-overview.js | 285 | ||||
-rw-r--r-- | hyperkitty/templates/base.html | 2 | ||||
-rw-r--r-- | hyperkitty/templates/index.html | 110 | ||||
-rw-r--r-- | hyperkitty/templates/overview.html | 27 | ||||
-rw-r--r-- | hyperkitty/templates/threads/summary_thread.html | 10 | ||||
-rw-r--r-- | hyperkitty/templates/threads/summary_thread_large.html | 2 | ||||
-rw-r--r-- | hyperkitty/templatetags/hk_generic.py | 11 | ||||
-rw-r--r-- | hyperkitty/urls.py | 4 | ||||
-rw-r--r-- | hyperkitty/views/index.py | 52 | ||||
-rw-r--r-- | hyperkitty/views/list.py | 64 |
15 files changed, 376 insertions, 504 deletions
diff --git a/hyperkitty/lib/view_helpers.py b/hyperkitty/lib/view_helpers.py index 1187335..f7f9a44 100644 --- a/hyperkitty/lib/view_helpers.py +++ b/hyperkitty/lib/view_helpers.py @@ -170,3 +170,28 @@ def is_thread_unread(request, mlist_name, thread): > last_view_obj.view_date: unread = True return unread + + +def get_recent_list_activity(store, mlist): + """Return the number of emails posted in the last 30 days""" + begin_date, end_date = mlist.get_recent_dates() + days = daterange(begin_date, end_date) + + # Use get_messages and not get_threads to count the emails, because + # recently active threads include messages from before the start date + emails_in_month = store.get_messages(list_name=mlist.name, + start=begin_date, end=end_date) + # graph + emails_per_date = {} + # populate with all days before adding data. + for day in days: + emails_per_date[day.strftime("%Y-%m-%d")] = 0 + # now count the emails + for email in emails_in_month: + date_str = email.date.strftime("%Y-%m-%d") + if date_str not in emails_per_date: + continue # outside the range + emails_per_date[date_str] += 1 + # return the proper format for the javascript chart function + return [ {"date": d, "count": emails_per_date[d]} + for d in sorted(emails_per_date) ] diff --git a/hyperkitty/static/hyperkitty/css/hyperkitty-common.css b/hyperkitty/static/hyperkitty/css/hyperkitty-common.css index c262308..310bfb4 100644 --- a/hyperkitty/static/hyperkitty/css/hyperkitty-common.css +++ b/hyperkitty/static/hyperkitty/css/hyperkitty-common.css @@ -78,7 +78,12 @@ form .buttons .submit { } -/* On the thread list and the overview page */ +/* On the thread list, overview page and list directory */ +ul.list-stats { + list-style-type: none; + margin: 0; + padding: 0; +} .participant, .discussion { padding-left: 20px; background: no-repeat scroll left center; diff --git a/hyperkitty/static/hyperkitty/css/hyperkitty-index.css b/hyperkitty/static/hyperkitty/css/hyperkitty-index.css index 0bd8889..9425f49 100644 --- a/hyperkitty/static/hyperkitty/css/hyperkitty-index.css +++ b/hyperkitty/static/hyperkitty/css/hyperkitty-index.css @@ -6,32 +6,79 @@ clear: both; } -h1.lists { + +.all-lists .lists-menu h2 { + font-size: 100%; + line-height: 100%; + font-weight: bold; + color: #666; + text-transform: uppercase; + margin: 0; + margin-bottom: 1em; + padding: 0; +} +.all-lists .lists-menu ul { + list-style-type: none; + margin-left: 1em; +} +.all-lists .lists-menu li { + margin: 0.5em 0; +} + +.all-lists h1.lists { margin-bottom: 0.5em; } -.all-lists .list-name { +.all-lists table.lists th { + color: #999; + text-transform: uppercase; + font-size: small; + font-weight: normal; +} +.all-lists table.lists tbody tr { + border: 1px solid #ccc; + margin: 0.2em 0; +} +.all-lists table.lists td { + vertical-align: middle; + padding: 5px 8px; + margin-bottom: 5px; +} +.all-lists table.lists .list-name { font-size: 120%; color: black; font-weight: bold; } -.all-lists .list-address { - font-size: 90%; +.all-lists table.lists .list-tags { + font-size: 80%; + color: #999; font-style: italic; } -.all-lists a { - display: block; - padding: 2em; - margin: 1em auto; - overflow: hidden; - border: 1px solid #ccc; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; - color: black; +.all-lists table.lists .list-address { + font-size: 90%; +} +.all-lists table.lists td.activity { + width: 260px; +} +.all-lists table.lists ul.list-stats { + margin: 0; + text-align: center; +} +.all-lists table.lists ul.list-stats li { + display: inline; + font-size: 85%; } -.all-lists a:hover { - text-decoration: none; + +.all-lists table.lists tr.private { + background-color: #e6f2f5; +} +.all-lists table.lists tr.private .list-name { + color: #2e6c83; +} + +.all-lists table.lists tr.inactive { background-color: #eee; - color: black; +} +.all-lists table.lists tr.inactive .list-name { + color: #999; } diff --git a/hyperkitty/static/hyperkitty/js/hyperkitty-common.js b/hyperkitty/static/hyperkitty/js/hyperkitty-common.js index 1fb1eb3..41f2c3e 100644 --- a/hyperkitty/static/hyperkitty/js/hyperkitty-common.js +++ b/hyperkitty/static/hyperkitty/js/hyperkitty-common.js @@ -33,6 +33,7 @@ function form_to_json(form) { } + /* * Voting */ @@ -73,6 +74,7 @@ function setup_vote(baseElem) { } + /* * New messages (or replies) */ @@ -103,6 +105,136 @@ function setup_attachments(baseElem) { } + +/* + * Recent activity bar chart + */ +function chart(elem_id, data, default_props) { + /* Function for grid lines, for x-axis */ + function make_x_axis() { + return d3.svg.axis() + .scale(x) + .orient("bottom") + .ticks(d3.time.days, 1) + } + + /* Function for grid lines, for y-axis */ + function make_y_axis() { + return d3.svg.axis() + .scale(y) + .orient("left") + .ticks(5) + } + if (typeof default_props === "undefined") { + default_props = {}; + } + + var props = {width: 250, height: 50}; + $.extend(props, default_props); + var margin = {top: 0, right: 0, bottom: 0, left: 0}, + width = props.width - margin.left - margin.right, + height = props.height - margin.top - margin.bottom; + + var w = Math.floor(width / data.length); + + var format_in = d3.time.format("%Y-%m-%d"); + var format_out = d3.time.format(""); + + var x = d3.time.scale() + .range([0, width]); + + var y = d3.scale.linear() + .range([height, 0]); + + var xAxis = d3.svg.axis() + .scale(x) + .orient("bottom") + .tickSize(0,0) // change to 2,2 for ticks + .tickFormat(format_out) + .ticks(d3.time.days, 1); + + var yAxis = d3.svg.axis() + .scale(y) + .orient("left") + .tickSize(0,0) // change to 4,3 for ticks + .ticks("") // change to 2 for y-axis tick labels + .tickSubdivide(1); + + var area = d3.svg.area() + .x(function(d) { return x(d.date); }) + // .y0(height) + .y(function(d) { return y(d.count); }); + + var svg = d3.select(elem_id).append("svg") + .attr("id", "chart-data") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + data.forEach(function(d) { + d.date = format_in.parse(d.date); + d.count = parseInt(d.count); + }); + + x.domain(d3.extent(data, function(d) { return d.date; })); + y.domain([0, d3.max(data, function(d) { return d.count; })]); + + + /* Draw the grid lines, for x-axis */ + svg.append("g") + .attr("class", "grid") + .attr("Transform", "translate(0, " + height + ")") + .call(make_x_axis() + .tickSize(height, 0, 0) + .tickFormat("") + ) + + /* Draw the grid lines, for y-axis */ + svg.append("g") + .attr("class", "grid") + .call(make_y_axis() + .tickSize(-width, 0, 0) + .tickFormat("") + ) + + svg.append("g").attr("class","bars").selectAll("rect") + .data(data) + .enter().append("rect") + .attr("x", function(d) { return x(d.date); }) + //.attr("y0", height) + .attr("y", function(d) { return y(d.count); }) + .attr("width", w) + .attr("height", function(d) { return height - y(d.count); }); + + /* draw x-axis */ + svg.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + height + ")") + .call(xAxis) + /*.selectAll("text") + .attr("y", -5) + .attr("x", -30) + .attr("transform", function(d) { + return "rotate(-90)" + });*/ + + /* Y-axis label */ + svg.append("g") + .attr("class", "y axis") + .call(yAxis) + /*.append("text") + .attr("transform", "rotate(-90)") + .attr("y", 0) + .attr("x", 0 - height/2) + .attr("dy", "-3em") + .style("text-anchor", "middle") + .style("fill", "#777") + .text("Messages"); */ +} + + + /* * Misc. */ @@ -131,6 +263,7 @@ function setup_flash_messages() { } + /* * Activate */ diff --git a/hyperkitty/static/hyperkitty/js/hyperkitty-frontpage.js b/hyperkitty/static/hyperkitty/js/hyperkitty-frontpage.js deleted file mode 100644 index 2ba7d43..0000000 --- a/hyperkitty/static/hyperkitty/js/hyperkitty-frontpage.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 1998-2012 by the Free Software Foundation, Inc. - * - * This file is part of HyperKitty. - * - * HyperKitty is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 3 of the License, or (at your option) - * any later version. - * - * HyperKitty is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * HyperKitty. If not, see <http://www.gnu.org/licenses/>. - * - * Author: Aurelien Bompard <abompard@fedoraproject.org> - */ - - -/* - * List descriptions on the front page - */ -function update_list_properties(url) { - // Don't try to update them all at once, there may be hundreds of lists - var bulksize = 5; - // If there is still an ajaxloader, then request the properties - var lists = $(".all-lists .mailinglist img.ajaxloader") - .slice(0, bulksize).parents(".mailinglist"); - if (lists.length === 0) { - return; - } - var listnames = $.makeArray(lists.find(".list-address").map( - function() { return $(this).text(); })); - $.ajax({ - dataType: "json", - url: url + "?name=" + listnames.join("&name="), - success: function(data) { - lists.each(function() { - var name = $(this).find(".list-address").text(); - if (!data[name]) { - return; - } - if (data[name]["display_name"]) { - $(this).find(".list-name").html(data[name]["display_name"]); - } - if (data[name]["description"]) { - $(this).find(".list-description") - .html(data[name]["description"]); - } - }); - }, - error: function(jqXHR, textStatus, errorThrown) { - //alert(jqXHR.responseText); - }, - complete: function(jqXHR, textStatus) { - // Request may have failed if mailman's REST server is unavailable, - // keep going anyway. - lists.find("img.ajaxloader").remove(); - // do it again, until all lists have been populated (or at least we - // tried to) - update_list_properties(url); - } - }); -} diff --git a/hyperkitty/static/hyperkitty/js/hyperkitty-overview.js b/hyperkitty/static/hyperkitty/js/hyperkitty-overview.js deleted file mode 100644 index e020554..0000000 --- a/hyperkitty/static/hyperkitty/js/hyperkitty-overview.js +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright (C) 1998-2012 by the Free Software Foundation, Inc. - * - * This file is part of HyperKitty. - * - * HyperKitty is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 3 of the License, or (at your option) - * any later version. - * - * HyperKitty is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * HyperKitty. If not, see <http://www.gnu.org/licenses/>. - * - * Author: Aurelien Bompard <abompard@fedoraproject.org> - */ - - -/* - * Recent activity graph (area graph) - */ - -function activity_graph(elem_id, dates, counts, baseurl) { - function merge(dates, counts) { - result = [] - for(i = 0; i < dates.length; i++) { - result.push({ - "date": dates[i], - "count": counts[i] - }) - } - return result; - } - - /* Function for grid lines, for x-axis */ - function make_x_axis() { - return d3.svg.axis() - .scale(x) - .orient("bottom") - .ticks(d3.time.days, 1) - } - - /* Function for grid lines, for y-axis */ - function make_y_axis() { - return d3.svg.axis() - .scale(y) - .orient("left") - .ticks(5) - } - - var data = merge(dates, counts); - var margin = {top: 20, right: 20, bottom: 50, left: 40}, - width = 350 - margin.left - margin.right, - height = 150 - margin.top - margin.bottom; - - var format_in = d3.time.format("%Y-%m-%d"); - var format_out = d3.time.format(""); - - var x = d3.time.scale() - .range([0, width]); - - var y = d3.scale.linear() - .range([height, 0]); - - var xAxis = d3.svg.axis() - .scale(x) - .orient("bottom") - .tickSize(2,2) - .tickFormat(format_out) - .ticks(d3.time.days, 1); - - var yAxis = d3.svg.axis() - .scale(y) - .orient("left") - .tickSize(4,3) - .ticks(2) - .tickSubdivide(1); - - var area = d3.svg.area() - .interpolate("monotone") - .x(function(d) { return x(d.date); }) - .y0(height) - .y1(function(d) { return y(d.count); }); - - var svg = d3.select(elem_id).append("svg") - .attr("width", width + margin.left + margin.right) - .attr("height", height + margin.top + margin.bottom) - .append("g") - .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - - data.forEach(function(d) { - d.date = format_in.parse(d.date); - d.count = parseInt(d.count); - }); - - x.domain(d3.extent(data, function(d) { return d.date; })); - y.domain([0, d3.max(data, function(d) { return d.count; })]); - - /* Draw the grid lines, for x-axis */ - svg.append("g") - .attr("class", "grid") - .attr("Transform", "translate(0, " + height + ")") - .call(make_x_axis() - .tickSize(height, 0, 0) - .tickFormat("") - ) - - /* Draw the grid lines, for y-axis */ - svg.append("g") - .attr("class", "grid") - .call(make_y_axis() - .tickSize(-width, 0, 0) - .tickFormat("") - ) - - svg.append("path") - .datum(data) - .attr("class", "area") - .attr("d", area); - - svg.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + height + ")") - .call(xAxis) - .selectAll("text") - .attr("y", -5) - .attr("x", -30) - .attr("transform", function(d) { - return "rotate(-90)" - }); - - /* Y-axis label */ - svg.append("g") - .attr("class", "y axis") - .call(yAxis) - .append("text") - .attr("transform", "rotate(-90)") - .attr("y", 0) - .attr("x", 0 - height/2) - .attr("dy", "-3em") - .style("text-anchor", "middle") - .style("fill", "#777") - .text("Messages"); - -} - - -/* - * Recent activity bar chart - */ -function chart(elem_id, dates, counts, baseurl) { - function merge(dates, counts) { - result = [] - for(i = 0; i < dates.length; i++) { - result.push({ - "date": dates[i], - "count": counts[i] - }) - } - return result; - } - - /* Function for grid lines, for x-axis */ - function make_x_axis() { - return d3.svg.axis() - .scale(x) - .orient("bottom") - .ticks(d3.time.days, 1) - } - - /* Function for grid lines, for y-axis */ - function make_y_axis() { - return d3.svg.axis() - .scale(y) - .orient("left") - .ticks(5) - } - - var data = merge(dates, counts); - var margin = {top: 0, right: 0, bottom: 0, left: 0}, - width = 200 - margin.left - margin.right, - height = 50 - margin.top - margin.bottom; - - var w = 5; - - var format_in = d3.time.format("%Y-%m-%d"); - var format_out = d3.time.format(""); - - var x = d3.time.scale() - .range([0, width]); - - var y = d3.scale.linear() - .range([height, 0]); - - var xAxis = d3.svg.axis() - .scale(x) - .orient("bottom") - .tickSize(0,0) // change to 2,2 for ticks - .tickFormat(format_out) - .ticks(d3.time.days, 1); - - var yAxis = d3.svg.axis() - .scale(y) - .orient("left") - .tickSize(0,0) // change to 4,3 for ticks - .ticks("") // change to 2 for y-axis tick labels - .tickSubdivide(1); - - var area = d3.svg.area() - .x(function(d) { return x(d.date); }) - // .y0(height) - .y(function(d) { return y(d.count); }); - - var svg = d3.select(elem_id).append("svg") - .attr("id", "chart-data") - .attr("width", width + margin.left + margin.right) - .attr("height", height + margin.top + margin.bottom) - .append("g") - .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - - data.forEach(function(d) { - d.date = format_in.parse(d.date); - d.count = parseInt(d.count); - }); - - x.domain(d3.extent(data, function(d) { return d.date; })); - y.domain([0, d3.max(data, function(d) { return d.count; })]); - - - /* Draw the grid lines, for x-axis */ - svg.append("g") - .attr("class", "grid") - .attr("Transform", "translate(0, " + height + ")") - .call(make_x_axis() - .tickSize(height, 0, 0) - .tickFormat("") - ) - - /* Draw the grid lines, for y-axis */ - svg.append("g") - .attr("class", "grid") - .call(make_y_axis() - .tickSize(-width, 0, 0) - .tickFormat("") - ) - - svg.append("g").attr("class","bars").selectAll("rect") - .data(data) - .enter().append("rect") - .attr("x", function(d) { return x(d.date); }) - //.attr("y0", height) - .attr("y", function(d) { return y(d.count); }) - .attr("width", w) - .attr("height", function(d) { return height - y(d.count); }); - - /* draw x-axis */ - svg.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + height + ")") - .call(xAxis) - /*.selectAll("text") - .attr("y", -5) - .attr("x", -30) - .attr("transform", function(d) { - return "rotate(-90)" - });*/ - - /* Y-axis label */ - svg.append("g") - .attr("class", "y axis") - .call(yAxis) - /*.append("text") - .attr("transform", "rotate(-90)") - .attr("y", 0) - .attr("x", 0 - height/2) - .attr("dy", "-3em") - .style("text-anchor", "middle") - .style("fill", "#777") - .text("Messages"); */ - -} diff --git a/hyperkitty/templates/base.html b/hyperkitty/templates/base.html index e7d5303..a6feb9a 100644 --- a/hyperkitty/templates/base.html +++ b/hyperkitty/templates/base.html @@ -130,7 +130,7 @@ <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script> <script>window.jQuery || document.write('<script src="{{ STATIC_URL }}hyperkitty/libs/jquery/jquery-1.10.1.min.js"><\/script>')</script> <script src="{{ STATIC_URL }}hyperkitty/libs/jquery/jquery-ui-1.10.3.custom.min.js"></script> - {% assets filters="rjsmin", output="gen/hyperkitty.js", "hyperkitty/libs/bootstrap/js/bootstrap.min.js", "hyperkitty/libs/jquery.expander.js", "hyperkitty/libs/d3.v2.min.js", "hyperkitty/libs/jquery.hotkeys.js", "hyperkitty/js/hyperkitty-common.js", "hyperkitty/js/hyperkitty-frontpage.js", "hyperkitty/js/hyperkitty-overview.js", "hyperkitty/js/hyperkitty-thread.js", "hyperkitty/js/hyperkitty-userprofile.js" %} + {% assets filters="rjsmin", output="gen/hyperkitty.js", "hyperkitty/libs/bootstrap/js/bootstrap.min.js", "hyperkitty/libs/jquery.expander.js", "hyperkitty/libs/d3.v2.min.js", "hyperkitty/libs/jquery.hotkeys.js", "hyperkitty/js/hyperkitty-common.js", "hyperkitty/js/hyperkitty-thread.js", "hyperkitty/js/hyperkitty-userprofile.js" %} <script src="{{ ASSET_URL }}"></script> {% endassets %} {% block additionaljs %} {% endblock %} diff --git a/hyperkitty/templates/index.html b/hyperkitty/templates/index.html index fc711b2..78804e1 100644 --- a/hyperkitty/templates/index.html +++ b/hyperkitty/templates/index.html @@ -1,6 +1,7 @@ {% extends "base.html" %} {% load url from future %} {% load i18n %} +{% load hk_generic %} {% block title %} @@ -9,34 +10,84 @@ {% block content %} -<div class="all-lists"> +<div class="row-fluid all-lists"> -<h1 class="lists">{% trans 'Available lists' %}</h1> +<div class="span2 lists-menu"> + <h2>{% trans 'Lists' %}</h2> + <ul> + <li><a href="{% url 'root' %}">All</a></li> + <li><a href="{% url 'root' %}?sort=active">Most active</a></li> + <li><a href="{% url 'root' %}?sort=popular">Most popular</a></li> + </ul> +</div> + +<div class="span10"> + +<h1>{% trans 'Available lists' %}</h1> -<div class="row-fluid"> +{% if all_lists %} +<table class="lists table"> + <thead> + <tr><th>List</th><th>Description</th><th>Activity in the past 30 days</th></tr> + </thead> + <tbody> {% for mlist in all_lists %} -<div class="span3"> - <a href="{% url 'list_overview' mlist_fqdn=mlist.name %}" class="mailinglist"> - <p class="list-name"> - {% if mlist.display_name %} - {{ mlist.display_name }} - {% else %} - {{ mlist.name }} - {% endif %} - </p> - <p class="list-address">{{ mlist.name }}</p> - <p class="list-description"></p> - <img alt="Loading..." class="ajaxloader" src="{{ STATIC_URL }}hyperkitty/img/ajax-loader.gif" /> - </a> -</div> -{% if forloop.counter|divisibleby:"4" %} -</div> -<div class="row-fluid"> -{% endif %} -{% empty %} -<p>No archived list yet.</p> + <tr + {% if mlist.is_private %} + class="private" + {% elif mlist.recent_threads_count == 0 %} + class="inactive" + {% endif %} + > + <td> + <a href="{% url 'list_overview' mlist_fqdn=mlist.name %}" + class="list-name"> + {% if mlist.display_name %} + {{ mlist.display_name }} + {% else %} + {{ mlist.name|until:"@" }} + {% endif %} + </a> + {% if mlist.is_private %} + <span class="list-tags">private</span> + {% elif mlist.recent_threads_count == 0 %} + <span class="list-tags">inactive</span> + {% endif %} + <br /> + <a href="{% url 'list_overview' mlist_fqdn=mlist.name %}" + class="list-address"> + {{ mlist.name }} + </a> + </td> + <td class="list-description"></td> + <td class="activity"> + <div class="chart" data-chart-values="{{ mlist.evolution|to_json }}"></div> + <ul class="list-stats"> + <li><span class="participant"> + {% if mlist.can_view %} + {{ mlist.recent_participants_count }} + {% else %} + ... + {% endif %} + participants</span></li> + <li><span class="discussion"> + {% if mlist.can_view %} + {{ mlist.recent_threads_count }} + {% else %} + ... + {% endif %} + discussions</span></li> + </ul> + </td> + </tr> {% endfor %} -</div> + </tbody> +</table> +{% else %} +<p>No archived list yet.</p> +{% endif %} + +</div> <!-- right column --> </div> @@ -46,10 +97,13 @@ {% block additionaljs %} -<script type="text/javascript"> - $(document).ready(function() { - // Load the properties - update_list_properties("{% url 'list_properties' %}"); +<script> + $(function() { + $("div.chart").each(function() { + chart($(this).get(0), + $.parseJSON($(this).attr("data-chart-values")), + {height: 30}); + }); }); </script> diff --git a/hyperkitty/templates/overview.html b/hyperkitty/templates/overview.html index 098e609..a5dc32f 100644 --- a/hyperkitty/templates/overview.html +++ b/hyperkitty/templates/overview.html @@ -25,7 +25,7 @@ <section id="statistics"> - <div id="chart"></div> + <div id="chart" data-chart-values="{{ evolution|to_json }}"></div> <p class="caption">Post volume over the past <strong>30</strong> days.</p> <p class="thread-new"> @@ -37,8 +37,8 @@ <h3>Activity Summary</h3> <p>The following statistics are from the past <strong>30</strong> days:</p> <ul class="list-stats"> - <li><span class="participant">{{ num_participants }} participants</span></li> - <li><span class="discussion">{{ num_threads }} discussions</span></li> + <li><span class="participant">{{ mlist.recent_participants_count }} participants</span></li> + <li><span class="discussion">{{ mlist.recent_threads_count }} discussions</span></li> </ul> <section id="discussion-maker" class="widget"> @@ -145,24 +145,11 @@ {% block additionaljs %} <script type="text/javascript" > -/* $(function() { - activity_graph( - "#fig", - ["{{days|join:'","'}}"], - {{evolution}}, - "{{ archives_baseurl }}" - ) - }); -*/ - - $(function() { - chart( - "#chart", - ["{{days|join:'","'}}"], - {{evolution}}, - "{{ archives_baseurl }}" - ) + $("#chart").each(function() { + chart($(this).get(0), + $.parseJSON($(this).attr("data-chart-values"))); + }); }); </script> diff --git a/hyperkitty/templates/threads/summary_thread.html b/hyperkitty/templates/threads/summary_thread.html index e9b2b1f..5d0972c 100644 --- a/hyperkitty/templates/threads/summary_thread.html +++ b/hyperkitty/templates/threads/summary_thread.html @@ -8,9 +8,9 @@ <a name="{{thread.thread_id}}" href="{% url 'thread' threadid=thread.thread_id mlist_fqdn=mlist.name %}" > - {% if thread.category %} - <span class="label category" style="background-color:{{thread.category.color}}"> - {{ thread.category.name|upper }} + {% if thread.category_widget %} + <span class="label category" style="background-color:{{thread.category_widget.color}}"> + {{ thread.category_widget.name|upper }} </span> {% endif %} {% if thread.unread %} @@ -21,10 +21,10 @@ <div class="thread-stats"> <ul class="inline-block"> <li class="participant"> - {{ thread.participants|length }} + {{ thread.participants_count }} </li> <li class="discussion"> - {{ thread.length }} + {{ thread|length }} </li> <span class="likestatus {{ thread.likestatus }}">+{{ thread.likes }}/-{{ thread.dislikes }}</span> </ul> diff --git a/hyperkitty/templates/threads/summary_thread_large.html b/hyperkitty/templates/threads/summary_thread_large.html index 450278b..51e98a8 100644 --- a/hyperkitty/templates/threads/summary_thread_large.html +++ b/hyperkitty/templates/threads/summary_thread_large.html @@ -44,7 +44,7 @@ </ul> {% endif %} </div> - <span class="participant">{{ thread.participants|length }} participants</span> + <span class="participant">{{ thread.participants_count }} participants</span> <span class="discussion">{{ thread|length }} comments</span> {% include "messages/like_form.html" with message_id_hash=thread.starting_email.message_id_hash object=thread %} <a href="{% url 'thread' threadid=thread.thread_id mlist_fqdn=mlist.name %}" diff --git a/hyperkitty/templatetags/hk_generic.py b/hyperkitty/templatetags/hk_generic.py index de50f9d..9e1cb0e 100644 --- a/hyperkitty/templatetags/hk_generic.py +++ b/hyperkitty/templatetags/hk_generic.py @@ -22,6 +22,7 @@ import datetime import re +import django.utils.simplejson as json from dateutil.tz import tzoffset from django import template from django.utils.datastructures import SortedDict @@ -204,3 +205,13 @@ def add_to_query_string(context, *args, **kwargs): for key, value in new_qs_elements.iteritems(): qs[key] = value return qs.urlencode() + + +@register.filter +def until(value, limit): + return value.partition(limit)[0] + + +@register.filter +def to_json(value): + return json.dumps(value) diff --git a/hyperkitty/urls.py b/hyperkitty/urls.py index 9c92aa5..b1eda96 100644 --- a/hyperkitty/urls.py +++ b/hyperkitty/urls.py @@ -38,9 +38,7 @@ from hyperkitty.views import TextTemplateView urlpatterns = patterns('hyperkitty.views', # Index - url(r'^/$', 'index.index', name='index'), - url(r'^$', 'index.index', name='root'), - url(r'^lists-properties$', 'index.list_properties', name='list_properties'), + url(r'^/?$', 'index.index', name='root'), # Account url(r'^accounts/login/$', 'accounts.login_view', {'template_name': 'login.html', 'SSL': True}, name='user_login'), diff --git a/hyperkitty/views/index.py b/hyperkitty/views/index.py index af9abd8..0954ee8 100644 --- a/hyperkitty/views/index.py +++ b/hyperkitty/views/index.py @@ -22,49 +22,47 @@ import urllib2 +import datetime +from collections import defaultdict import django.utils.simplejson as json from django.shortcuts import render from django.conf import settings from django.http import HttpResponse -from mailmanclient import Client +from mailmanclient import Client, MailmanConnectionError +from mailman.interfaces.archiver import ArchivePolicy from hyperkitty.lib import get_store +from hyperkitty.lib.view_helpers import get_recent_list_activity +from hyperkitty.lib.mailman import is_mlist_authorized def index(request): store = get_store(request) lists = store.get_lists() + for mlist in lists: + if mlist.archive_policy != ArchivePolicy.private: + mlist.is_private = False + mlist.can_view = True + else: + mlist.is_private = True + if is_mlist_authorized(request, mlist): + mlist.can_view = True + else: + mlist.can_view = False + if mlist.can_view: + mlist.evolution = get_recent_list_activity(store, mlist) + + # sorting + sort_mode = request.GET.get('sort') + if sort_mode == "active": + lists.sort(key=lambda l: l.recent_threads_count) + elif sort_mode == "popular": + lists.sort(key=lambda l: l.recent_participants_count) context = { 'view_name': 'all_lists', 'all_lists': lists, } return render(request, "index.html", context) - - -def list_properties(request): - """Get JSON encoded list properties""" - store = get_store(request) - lists = store.get_lists() - onlynames = request.GET.getlist("name") - if onlynames: - lists = [ l for l in lists if l.name in onlynames ] - client = Client('%s/3.0' % settings.MAILMAN_REST_SERVER, - settings.MAILMAN_API_USER, settings.MAILMAN_API_PASS) - props = {} - for ml in lists: - try: - mm_list = client.get_list(ml.name) - except urllib2.HTTPError: - continue - props[ml.name] = { - "display_name": mm_list.display_name, - "description": mm_list.settings["description"], - } - # Update KittyStore if necessary - if ml.display_name != mm_list.display_name: - ml.display_name = mm_list.display_name - return HttpResponse(json.dumps(props), - mimetype='application/javascript') diff --git a/hyperkitty/views/list.py b/hyperkitty/views/list.py index cbcc678..8feff11 100644 --- a/hyperkitty/views/list.py +++ b/hyperkitty/views/list.py @@ -35,7 +35,7 @@ from hyperkitty.models import Tag, Favorite from hyperkitty.lib import get_store from hyperkitty.lib.view_helpers import FLASH_MESSAGES, paginate, \ get_category_widget, get_months, get_display_dates, daterange, \ - is_thread_unread + is_thread_unread, get_recent_list_activity from hyperkitty.lib.voting import set_message_votes, set_thread_votes from hyperkitty.lib.mailman import check_mlist_private @@ -44,11 +44,6 @@ if settings.USE_MOCKUPS: from hyperkitty.lib.mockup import generate_top_author, generate_thread_per_category -Thread = namedtuple('Thread', [ - "thread_id", "subject", "participants", "length", "date_active", - "likes", "dislikes", "likestatus", "category", "unread", - ]) - @check_mlist_private def archives(request, mlist_fqdn, year=None, month=None, day=None): @@ -77,6 +72,9 @@ def archives(request, mlist_fqdn, year=None, month=None, day=None): "list_title": list_title.capitalize(), "no_results_text": no_results_text, } + if day is None: + month_activity = mlist.get_month_activity(int(year), int(month)) + extra_context["participants"] = month_activity.participants_count return _thread_list(request, mlist, threads, extra_context=extra_context) @@ -87,7 +85,8 @@ def _thread_list(request, mlist, threads, template_name='thread_list.html', extr participants = set() for thread in threads: - participants.update(thread.participants) + if "participants" not in extra_context: + participants.update(thread.participants) # Votes set_thread_votes(thread, request.user) @@ -143,39 +142,25 @@ def overview(request, mlist_fqdn=None): if not mlist_fqdn: return redirect('/') - # Get stats for last 30 days - today = datetime.datetime.utcnow() - #today -= datetime.timedelta(days=365) #debug - # the upper boundary is excluded in the search, add one day - end_date = today + datetime.timedelta(days=1) - begin_date = end_date - datetime.timedelta(days=32) - store = get_store(request) mlist = store.get_list(mlist_fqdn) if mlist is None: raise Http404("No archived mailing-list by that name.") + begin_date, end_date = mlist.get_recent_dates() threads_result = store.get_threads( list_name=mlist.name, start=begin_date, end=end_date) threads = [] - participants = set() for thread_obj in threads_result: # Votes set_thread_votes(thread_obj, request.user) - thread = Thread(thread_obj.thread_id, thread_obj.subject, - thread_obj.participants, len(thread_obj), - thread_obj.date_active.replace(tzinfo=utc), - thread_obj.likes, thread_obj.dislikes, - thread_obj.likestatus, - get_category_widget(None, thread_obj.category)[0], - is_thread_unread(request, mlist.name, thread_obj), - ) - # Statistics on how many participants and threads this month - participants.update(thread.participants) - threads.append(thread) + thread_obj.category_widget = get_category_widget( + None, thread_obj.category)[0] + thread_obj.unread = is_thread_unread(request, mlist.name, thread_obj) + threads.append(thread_obj) # top threads are the one with the most answers - top_threads = sorted(threads, key=lambda t: t.length, reverse=True) + top_threads = sorted(threads, key=lambda t: len(t), reverse=True) # active threads are the ones that have the most recent posting active_threads = sorted(threads, key=lambda t: t.date_active, reverse=True) @@ -212,25 +197,9 @@ def overview(request, mlist_fqdn=None): continue threads_by_category[thread.category].append(thread) - # List activity - # Use get_messages and not get_threads to count the emails, because - # recently active threads include messages from before the start date - emails_in_month = store.get_messages(list_name=mlist.name, - start=begin_date, end=end_date) - # graph - dates = defaultdict(lambda: 0) # no activity by default - # populate with all days before adding data. - for single_date in daterange(begin_date, end_date): - dates[single_date.strftime("%Y-%m-%d")] = 0 - - for email in emails_in_month: - date_str = email.date.strftime("%Y-%m-%d") - dates[date_str] = dates[date_str] + 1 - days = dates.keys() - days.sort() - evolution = [dates[d] for d in days] - if not evolution: - evolution.append(0) + # List activity graph + evolution = get_recent_list_activity(store, mlist) + archives_baseurl = reverse("archives_latest", kwargs={'mlist_fqdn': mlist.name}) archives_baseurl = archives_baseurl.rpartition("/")[0] @@ -246,9 +215,6 @@ def overview(request, mlist_fqdn=None): 'threads_by_category': threads_by_category, 'months_list': get_months(store, mlist.name), 'evolution': evolution, - 'days': days, 'archives_baseurl': archives_baseurl, - 'num_threads': len(threads), - 'num_participants': len(participants), } return render(request, "overview.html", context) |