rrs: Add initial support of recipes page

templates/rrs/base_toplevel.html: Add support for display statistics by
Milestone.
templates/rrs/recipes.html: Add initial page that display Recipe
status by Milestone also details of every recipe.
rrs/views.py: Add RecipeLitView for support recipes page.
rrs/models.py: Add helper functions.
rrs/static/*: Add css and js resources.

Signed-off-by: Aníbal Limón <anibal.limon@linux.intel.com>
This commit is contained in:
Aníbal Limón 2015-01-22 10:47:46 -06:00
parent 6b2b08456a
commit 02b0f4814d
7 changed files with 1475 additions and 10 deletions

View File

@ -64,6 +64,11 @@ class RecipeMaintainer(models.Model):
recipe = models.ForeignKey(Recipe) recipe = models.ForeignKey(Recipe)
maintainer = models.ForeignKey(Maintainer) maintainer = models.ForeignKey(Maintainer)
@staticmethod
def get_maintainer_by_recipe(recipe):
recipe_maintainer = RecipeMaintainer.objects.filter(recipe = recipe)[0]
return recipe_maintainer.maintainer
def __unicode__(self): def __unicode__(self):
return "%s: %s <%s>" % (self.recipe.pn, self.maintainer.name, return "%s: %s <%s>" % (self.recipe.pn, self.maintainer.name,
self.maintainer.email) self.maintainer.email)
@ -101,11 +106,13 @@ class RecipeUpstream(models.Model):
('D', 'Downgrade'), ('D', 'Downgrade'),
('U', 'Unknown'), ('U', 'Unknown'),
) )
RECIPE_UPSTREAM_STATUS_CHOICES_DICT = dict(RECIPE_UPSTREAM_STATUS_CHOICES)
RECIPE_UPSTREAM_TYPE_CHOICES = ( RECIPE_UPSTREAM_TYPE_CHOICES = (
('A', 'Automatic'), ('A', 'Automatic'),
('M', 'Manual'), ('M', 'Manual'),
) )
RECIPE_UPSTREAM_TYPE_CHOICES_DICT = dict(RECIPE_UPSTREAM_TYPE_CHOICES)
recipe = models.ForeignKey(Recipe) recipe = models.ForeignKey(Recipe)
history = models.ForeignKey(RecipeUpstreamHistory, null=True) history = models.ForeignKey(RecipeUpstreamHistory, null=True)
@ -115,6 +122,12 @@ class RecipeUpstream(models.Model):
no_update_reason = models.CharField(max_length=255, blank=True) no_update_reason = models.CharField(max_length=255, blank=True)
date = models.DateTimeField() date = models.DateTimeField()
@staticmethod
def get_by_recipe_and_history(recipe, history):
ru = RecipeUpstream.objects.filter(recipe = recipe, history = history)
return ru[0] if ru else None
def needs_upgrade(self): def needs_upgrade(self):
if self.status == 'N': if self.status == 'N':
return True return True

View File

@ -0,0 +1,107 @@
.version_column {
max-width: 150px;
word-wrap: break-word;
}
.upstream_version_column {
min-width: 130px;
}
.upstream_status_column {
min-width: 60px;
}
.maintainer_column {
min-width: 110px;
}
.distrolist {
margin-left: -20px;
}
.half-selector {
margin-bottom: -5px;
margin-top: 5px;
}
.selector {
margin-bottom: 5px;
margin-top: 5px;
}
/* Sorting styles */
th > a, th.muted {
font-weight: normal;
}
.sorted {
cursor: pointer;
color: #333;
font-weight: bold;
}
.sorted:hover {
color: #000;
text-decoration: underline;
}
.sorter {
font-size: 10px;
}
/* Table styles */
.navbar .nav > li > p {
color: #777777;
padding: 10px 5px;
margin-bottom: 0;
}
.navbar .badge {
margin-top: 10px;
margin-right: 10px;
}
.navbar-table-controls {
margin-bottom: 5px;
}
.table-controls {
background-color: transparent;
background-image: none;
box-shadow: none;
border:none;
padding-left: 0;
padding-right: 0;
}
.current-wk {
background-color: #fcf8e3;
color: #c09853;
}
th .caret {
vertical-align: middle;
}
/* status styles */
.well{
padding: 8px 14px 8px 14px;
}
.nav > li.lead {
margin: 5px 0 0 5px;
}
/* about recipe styles */
dd {
margin-bottom: 10px;
}
.well-transparent {
background-color: transparent;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,97 @@
/*
* Copyright (c) 2008 Greg Weber greg at gregweber.info
* Dual licensed under the MIT and GPLv2 licenses just as jQuery is:
* http://jquery.org/license
*
* documentation at http://gregweber.info/projects/uitablefilter
*
* allows table rows to be filtered (made invisible)
* <code>
* t = $('table')
* $.uiTableFilter( t, phrase )
* </code>
* arguments:
* jQuery object containing table rows
* phrase to search for
* optional arguments:
* column to limit search too (the column title in the table header)
* showHidden - if true, will search among the hidden elements
* ifHidden - callback to execute if one or more elements was hidden
*/
(function($) {
$.uiTableFilter = function(jq, phrase, column, showHidden, ifHidden){
var new_hidden = false;
if( this.last_phrase === phrase ) return false;
var phrase_length = phrase.length;
var words = phrase.toLowerCase().split(" ");
// these function pointers may change
var matches = function(elem) { elem.show(); }
var noMatch = function(elem) { elem.hide(); new_hidden = true }
var getText = function(elem) { return elem.text() }
if( column ) {
var index = null;
jq.find("thead > tr:last > th").each( function(i){
if( $.trim($(this).text()) == column ){
index = i; return false;
}
});
if( index == null ) throw("given column: " + column + " not found")
getText = function(elem){ return $(elem.find(
("td:eq(" + index + ")") )).text()
}
}
// if added one letter to last time,
// just check newest word and only need to hide
if( (words.size > 1) && (phrase.substr(0, phrase_length - 1) ===
this.last_phrase) ) {
if( phrase[-1] === " " )
{ this.last_phrase = phrase; return false; }
var words = words[-1]; // just search for the newest word
// only hide visible rows
matches = function(elem) {;}
var elems = jq.find("tbody:first > tr:visible")
}
else {
new_hidden = true;
if (showHidden) {
var elems = jq.find("tbody:first > tr");
}
else {
var elems = jq.find("tbody:first > tr:visible");
}
}
elems.each(function(){
var elem = $(this);
$.uiTableFilter.has_words( getText(elem), words, false ) ?
matches(elem) : noMatch(elem);
});
last_phrase = phrase;
if( ifHidden && new_hidden ) ifHidden();
return jq;
};
// caching for speedup
$.uiTableFilter.last_phrase = ""
// not jQuery dependent
// "" [""] -> Boolean
// "" [""] Boolean -> Boolean
$.uiTableFilter.has_words = function( str, words, caseSensitive )
{
var text = caseSensitive ? str : str.toLowerCase();
for (var i=0; i < words.length; i++) {
if (text.indexOf(words[i]) === -1) return false;
}
return true;
}
}) (jQuery);

View File

@ -1,19 +1,128 @@
import urllib
from django.shortcuts import get_object_or_404
from django.views.generic import ListView from django.views.generic import ListView
from django.core.urlresolvers import resolve from django.core.urlresolvers import resolve
from rrs.models import Milestone from layerindex.models import Recipe
from rrs.models import Milestone, Maintainer, RecipeMaintainer, RecipeUpstream, \
RecipeUpstreamHistory
class RecipeList():
name = None
version = None
summary = None
upstream_status = None
upstream_version = None
maintainer_name = None
def __init__(self, name, version, summary, upstream_status,
upstream_version, maintainer_name):
self.name = name
self.version = version
self.summary = summary
self.upstream_status = upstream_status
self.upstream_version = upstream_version
self.maintainer_name = maintainer_name
class RecipeListView(ListView): class RecipeListView(ListView):
context_object_name = 'recipe_list' context_object_name = 'recipe_list'
def get_queryset(self): def get_queryset(self):
pass self.milestone_name = self.kwargs['milestone_name']
milestone = get_object_or_404(Milestone, name=self.milestone_name)
if 'upstream_status' in self.request.GET.keys():
self.upstream_status = self.request.GET['upstream_status']
else:
self.upstream_status = 'All'
if 'maintainer_name' in self.request.GET.keys():
self.maintainer_name = self.request.GET['maintainer_name']
else:
self.maintainer_name = 'All'
recipe_upstream_history = RecipeUpstreamHistory.get_last_by_date_range(
milestone.start_date,
milestone.end_date
)
recipe_list = []
self.recipe_list_count = 0
self.recipes_up_to_date = 0
self.recipes_not_updated = 0
self.recipes_unknown = 0
self.recipes_percentage = '0.00'
if not recipe_upstream_history is None:
recipe_qry = Recipe.objects.filter().order_by('pn')
# get statistics by milestone
recipes_all = RecipeUpstream.objects.filter(history =
recipe_upstream_history).count()
self.recipes_up_to_date = RecipeUpstream.objects.filter(history =
recipe_upstream_history, status = 'Y').count()
self.recipes_not_updated = RecipeUpstream.objects.filter(history =
recipe_upstream_history, status = 'N').count()
self.recipes_unknown = recipes_all - (self.recipes_up_to_date +
self.recipes_not_updated)
self.recipes_percentage = "%.2f" % \
((float(self.recipes_up_to_date) / float(recipes_all)) * 100)
for recipe in recipe_qry:
recipe_upstream = RecipeUpstream.get_by_recipe_and_history(
recipe, recipe_upstream_history)
recipe_upstream_status = \
RecipeUpstream.RECIPE_UPSTREAM_STATUS_CHOICES_DICT[
recipe_upstream.status]
if self.upstream_status != 'All' and self.upstream_status != recipe_upstream_status:
continue
maintainer = RecipeMaintainer.get_maintainer_by_recipe(recipe)
if self.maintainer_name != 'All' and self.maintainer_name != maintainer.name:
continue
recipe_list_item = RecipeList(recipe.pn, recipe.pv, recipe.summary,
recipe_upstream_status, recipe_upstream.version, maintainer.name)
recipe_list.append(recipe_list_item)
self.recipe_list_count = len(recipe_list)
return recipe_list
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(RecipeListView, self).get_context_data(**kwargs) context = super(RecipeListView, self).get_context_data(**kwargs)
context['this_url_name'] = resolve(self.request.path_info).url_name context['this_url_name'] = resolve(self.request.path_info).url_name
context['milestone_name'] = self.kwargs['milestone_name']
context['milestone_name'] = self.milestone_name
context['all_milestones'] = Milestone.objects.filter().order_by('-id') context['all_milestones'] = Milestone.objects.filter().order_by('-id')
context['recipes_percentage'] = self.recipes_percentage
context['recipes_up_to_date'] = self.recipes_up_to_date
context['recipes_not_updated'] = self.recipes_not_updated
context['recipes_unknown'] = self.recipes_unknown
context['recipe_list_count'] = self.recipe_list_count
context['upstream_status'] = self.upstream_status
all_upstream_status = ['All']
for us in RecipeUpstream.RECIPE_UPSTREAM_STATUS_CHOICES:
all_upstream_status.append(us[1])
context['all_upstream_status'] = all_upstream_status
context['maintainer_name'] = self.maintainer_name
all_maintainers = ['All']
for rm in RecipeMaintainer.objects.filter().values(
'maintainer__name').distinct().order_by('maintainer__name'):
all_maintainers.append(rm['maintainer__name'])
context['all_maintainers'] = all_maintainers
extra_url_param = '?' + urllib.urlencode({
'upstream_status': self.upstream_status,
'maintainer_name': self.maintainer_name.encode('utf8')
})
context['extra_url_param'] = extra_url_param
return context return context

View File

@ -1,5 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n %} {% load i18n %}
{% load staticfiles %}
{% load url from future %} {% load url from future %}
@ -13,9 +14,7 @@
{% endcomment %} {% endcomment %}
{% block content %} {% block content %}
<link rel="stylesheet" href="{% static "css/rrs-additional.css" %}" />
<div class="container-fluid">
<div class="row-fluid">
<div class="navbar"> <div class="navbar">
<div class="navbar-inner"> <div class="navbar-inner">
@ -37,6 +36,14 @@
</ul> </ul>
</li> </li>
{% endblock %} {% endblock %}
<li class="divider-vertical"></li>
<li class="lead" id="percentage"><strong>{{ recipes_percentage }}%</strong> done</li>
<li class="divider-vertical"></li>
<li class="lead" id="up-to-date-recipes">Recipes up-to-date: <strong class="text-success">{{ recipes_up_to_date }}</strong></li>
<li class="divider-vertical"></li>
<li class="lead" id="not-updated-recipes">Recipes not updated: <strong class="text-error">{{ recipes_not_updated }}</strong></li>
<li class="divider-vertical"></li>
<li class="lead" id="unknown-recipes">Unknown: <strong class="text-warning">{{ recipes_unknown }}</strong></li>
</ul> </ul>
<ul class="nav"> <ul class="nav">
@ -45,9 +52,25 @@
</div> </div>
</div> </div>
{% block content_top %}
<ul class="nav nav-pills">
{% if this_url_name == 'recipes' %}
<li class="active">
{% else %}
<li>
{% endif %}
<a href="{% url this_url_name milestone_name %}{{ extra_url_param }}">Recipes status</a>
</li>
{% if this_url_name == 'maintainers' %}
<li class="active">
{% else %}
<li>
{% endif %}
<a href="#">Maintainer statistics</a>
</li>
</ul>
{% endblock %}
{% block content_inner %}{% endblock %} {% block content_inner %}{% endblock %}
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -1,5 +1,6 @@
{% extends "rrs/base_toplevel.html" %} {% extends "rrs/base_toplevel.html" %}
{% load i18n %} {% load i18n %}
{% load staticfiles %}
{% load url from future %} {% load url from future %}
@ -16,4 +17,88 @@
{% endblock %} {% endblock %}
{% block content_inner %} {% block content_inner %}
<div class="navbar navbar-table-controls">
<div class="navbar-inner table-controls">
<ul class="nav">
<li class="dropdown">
<span class="badge">{{ recipe_list_count }} recipes</span>
</li>
<li class="dropdown">
<a data-toggle="dropdown" href="#" class="dropdown-toggle" id="selected-status">
Upstream status: <strong>{{ upstream_status }}</strong>
<b class="caret"></b>
</a>
<ul class="dropdown-menu" id="select-status">
{% for us in all_upstream_status %}
{% if us = upstream_status %}
<li class="active">
<a href="#">
{% else %}
<li>
<a href="{% url this_url_name milestone_name %}?upstream_status={{ us|urlencode }}&maintainer_name={{ maintainer_name|urlencode }}">
{% endif %}
{{ us }}
</a>
</li>
{% endfor %}
</ul>
</li>
<li><p>and</p></li>
<li class="dropdown">
<a data-toggle="dropdown" href="#" class="dropdown-toggle" id="select-maintainer">
Maintainer: <strong>{{ maintainer_name }}</strong>
<b class="caret"></b>
</a>
<ul class="dropdown-menu" id="selected-maintainer">
{% for m in all_maintainers %}
{% if m = maintainer_name %}
<li class="active">
<a href="#">
{% else %}
<li>
<a href="{% url this_url_name milestone_name %}?upstream_status={{ upstream_status|urlencode }}&maintainer_name={{ m|urlencode }}">
{% endif %}
{{ m }}</a>
</li>
{% endfor %}
</ul>
</li>
</ul>
<form class="navbar-form pull-right">
<input type="text" class="input-xxlarge" placeholder="Search all recipes" id="filter"/>
</form>
</div>
</div>
<table class="table table-bordered statustable tablesorter table-hover">
<thead>
<tr>
<th class="recipe_column"><a href="#">Recipe</a></th>
<th class="version_column muted">Version</th>
<th class="upstream_status_column"><b class="caret"></b>Upstream status</th>
<th class="upstream_version_column muted">Upstream version</th>
<th class="maintainer_column"><b class="caret"></b>Maintainer</th>
<th class="summary_column muted span5">Summary</th>
</tr>
</thead>
<tbody>
{% for r in recipe_list %}
<tr>
<td>{{ r.name }}</td>
<td>{{ r.version }}</td>
<td>{{ r.upstream_status }}</td>
<td>{{ r.upstream_version }}</td>
<td>{{ r.maintainer_name }}</td>
<td>{{ r.summary }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<script src="{% static "js/uitablefilter.js" %}"></script>
<script src="{% static "js/jquery.tablesorter.js" %}"></script>
{% endblock %} {% endblock %}