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 committed by Paul Eggleton
parent 2a651efc9a
commit a7ede0d126
7 changed files with 1475 additions and 10 deletions

View File

@ -64,6 +64,11 @@ class RecipeMaintainer(models.Model):
recipe = models.ForeignKey(Recipe)
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):
return "%s: %s <%s>" % (self.recipe.pn, self.maintainer.name,
self.maintainer.email)
@ -101,11 +106,13 @@ class RecipeUpstream(models.Model):
('D', 'Downgrade'),
('U', 'Unknown'),
)
RECIPE_UPSTREAM_STATUS_CHOICES_DICT = dict(RECIPE_UPSTREAM_STATUS_CHOICES)
RECIPE_UPSTREAM_TYPE_CHOICES = (
('A', 'Automatic'),
('M', 'Manual'),
)
RECIPE_UPSTREAM_TYPE_CHOICES_DICT = dict(RECIPE_UPSTREAM_TYPE_CHOICES)
recipe = models.ForeignKey(Recipe)
history = models.ForeignKey(RecipeUpstreamHistory, null=True)
@ -115,6 +122,12 @@ class RecipeUpstream(models.Model):
no_update_reason = models.CharField(max_length=255, blank=True)
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):
if self.status == 'N':
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.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):
context_object_name = 'recipe_list'
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):
context = super(RecipeListView, self).get_context_data(**kwargs)
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['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

View File

@ -1,5 +1,6 @@
{% extends "base.html" %}
{% load i18n %}
{% load staticfiles %}
{% load url from future %}
@ -13,9 +14,7 @@
{% endcomment %}
{% block content %}
<div class="container-fluid">
<div class="row-fluid">
<link rel="stylesheet" href="{% static "css/rrs-additional.css" %}" />
<div class="navbar">
<div class="navbar-inner">
@ -37,6 +36,14 @@
</ul>
</li>
{% 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 class="nav">
@ -45,9 +52,25 @@
</div>
</div>
{% block content_inner %}{% endblock %}
</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 %}
{% endblock %}

View File

@ -1,5 +1,6 @@
{% extends "rrs/base_toplevel.html" %}
{% load i18n %}
{% load staticfiles %}
{% load url from future %}
@ -16,4 +17,88 @@
{% endblock %}
{% 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 %}