Add side-by-side comparison detail and enhanced selection

Specifying the covering recipe in the comparison recipe detail page was
always a bit awkward - you could only type the name, if you wanted to
actually find a recipe or look up the currently selected one's details
then you had to open another browser tab/window. To fix this, replace
the form on the comparison recipe detail page with a side-by-side
display of the covering recipe's information, along with a button that
lets you search and then select the covering recipe and at the same time
enter comments or set any of the other cover fields.

Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
This commit is contained in:
Paul Eggleton 2018-07-19 09:56:02 +02:00
parent 45ebb07da3
commit f38bae1dd9
11 changed files with 650 additions and 199 deletions

View File

@ -240,3 +240,8 @@ class ClassicRecipeSearchForm(forms.Form):
cover_verified = forms.ChoiceField(label='Verified', choices=VERIFIED_CHOICES, required=False)
needs_attention = forms.ChoiceField(label='Needs attention', choices=ATTENTION_CHOICES, required=False)
class ComparisonRecipeSelectForm(forms.Form):
q = forms.CharField(label='Keyword', max_length=255, required=False)
oe_layer = forms.ModelChoiceField(label='OE Layer', queryset=LayerItem.objects.filter(comparison=False).filter(status__in=['P', 'X']).order_by('name'), empty_label="(any)", required=False)

View File

@ -615,6 +615,12 @@ class ClassicRecipe(Recipe):
desc = "%s - %s" % (desc, self.cover_comment)
return desc
def get_cover_recipe(self):
if self.cover_layerbranch and self.cover_pn:
return Recipe.objects.filter(layerbranch=self.cover_layerbranch).filter(pn=self.cover_pn).first()
else:
return None
class ComparisonRecipeUpdate(models.Model):
update = models.ForeignKey(Update)

View File

@ -254,3 +254,7 @@ td.warning {
td.info {
background-color: #d9edf7 !important;
}
.hidden-select {
display: none !important;
}

View File

@ -1,6 +1,6 @@
# layerindex-web - URL definitions
#
# Copyright (C) 2013 Intel Corporation
# Copyright (C) 2013, 2018 Intel Corporation
#
# Licensed under the MIT license, see COPYING.MIT for details
@ -8,7 +8,7 @@ from django.conf.urls import *
from django.views.generic import TemplateView, DetailView, ListView, RedirectView
from django.views.defaults import page_not_found
from django.core.urlresolvers import reverse_lazy
from layerindex.views import LayerListView, LayerReviewListView, LayerReviewDetailView, RecipeSearchView, MachineSearchView, LayerDetailView, edit_layer_view, delete_layer_view, edit_layernote_view, delete_layernote_view, HistoryListView, EditProfileFormView, AdvancedRecipeSearchView, BulkChangeView, BulkChangeSearchView, bulk_change_edit_view, bulk_change_patch_view, BulkChangeDeleteView, RecipeDetailView, RedirectParamsView, ClassicRecipeSearchView, ClassicRecipeDetailView, ClassicRecipeStatsView, LayerUpdateDetailView, UpdateListView, UpdateDetailView, StatsView, publish_view, LayerCheckListView, BBClassCheckListView, TaskStatusView
from layerindex.views import LayerListView, LayerReviewListView, LayerReviewDetailView, RecipeSearchView, MachineSearchView, LayerDetailView, edit_layer_view, delete_layer_view, edit_layernote_view, delete_layernote_view, HistoryListView, EditProfileFormView, AdvancedRecipeSearchView, BulkChangeView, BulkChangeSearchView, bulk_change_edit_view, bulk_change_patch_view, BulkChangeDeleteView, RecipeDetailView, RedirectParamsView, ClassicRecipeSearchView, ClassicRecipeDetailView, ClassicRecipeStatsView, LayerUpdateDetailView, UpdateListView, UpdateDetailView, StatsView, publish_view, LayerCheckListView, BBClassCheckListView, TaskStatusView, ComparisonRecipeSelectView, ComparisonRecipeSelectDetailView
from layerindex.models import LayerItem, Recipe, RecipeChangeset
from rest_framework import routers
from . import restviews
@ -156,6 +156,14 @@ urlpatterns = [
ClassicRecipeDetailView.as_view(
template_name='layerindex/classicrecipedetail.html'),
name='comparison_recipe'),
url(r'^comparison/select/(?P<pk>[-\w]+)/$',
ComparisonRecipeSelectView.as_view(
template_name='layerindex/comparisonrecipeselect.html'),
name='comparison_select'),
url(r'^comparison/selectdetail/(?P<selectfor>[-\w]+)/(?P<pk>[-\w]+)/$',
ComparisonRecipeSelectDetailView.as_view(
template_name='layerindex/comparisonrecipeselectdetail.html'),
name='comparison_select_detail'),
url(r'^task/(?P<task_id>[-\w]+)/$',
TaskStatusView.as_view(
template_name='layerindex/task.html'),

View File

@ -18,7 +18,7 @@ from django.views.generic import TemplateView, DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.views.generic.base import RedirectView
from django.contrib.messages.views import SuccessMessageMixin
from layerindex.forms import EditLayerForm, LayerMaintainerFormSet, EditNoteForm, EditProfileForm, RecipeChangesetForm, AdvancedRecipeSearchForm, BulkChangeEditFormSet, ClassicRecipeForm, ClassicRecipeSearchForm
from layerindex.forms import EditLayerForm, LayerMaintainerFormSet, EditNoteForm, EditProfileForm, RecipeChangesetForm, AdvancedRecipeSearchForm, BulkChangeEditFormSet, ClassicRecipeForm, ClassicRecipeSearchForm, ComparisonRecipeSelectForm
from django.db import transaction
from django.contrib.auth.models import User, Permission
from django.db.models import Q, Count, Sum
@ -28,6 +28,7 @@ from django.template.loader import get_template
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django import forms
from reversion.models import Revision
from . import utils
from . import simplesearch
@ -1202,25 +1203,11 @@ class ClassicRecipeDetailView(SuccessMessageMixin, UpdateView):
def _can_edit(self):
if self.request.user.is_authenticated():
if not self.request.user.has_perm('layerindex.edit_classic'):
user_email = self.request.user.email.strip().lower()
if not LayerMaintainer.objects.filter(email__iexact=user_email):
return False
else:
return False
return True
def post(self, request, *args, **kwargs):
if not self._can_edit():
raise PermissionDenied
return super(ClassicRecipeDetailView, self).post(request, *args, **kwargs)
def get_success_message(self, cleaned_data):
return "Comparison saved successfully"
def get_success_url(self):
return reverse_lazy('comparison_recipe', args=(self.object.id,))
def get_context_data(self, **kwargs):
context = super(ClassicRecipeDetailView, self).get_context_data(**kwargs)
context['can_edit'] = self._can_edit()
@ -1233,6 +1220,9 @@ class ClassicRecipeDetailView(SuccessMessageMixin, UpdateView):
if rq:
cover_recipe = rq.first()
context['cover_recipe'] = cover_recipe
context['layerbranch_desc'] = str(recipe.layerbranch.branch)
context['to_desc'] = 'OpenEmbedded'
context['recipes'] = [recipe, cover_recipe]
return context
@ -1375,3 +1365,116 @@ class TaskStatusView(TemplateView):
context['result'] = AsyncResult(task_id)
context['update'] = get_object_or_404(Update, task_id=task_id)
return context
class ComparisonRecipeSelectView(ClassicRecipeSearchView):
def _can_edit(self):
if self.request.user.is_authenticated():
if not self.request.user.has_perm('layerindex.edit_classic'):
return False
else:
return False
return True
def get_context_data(self, **kwargs):
self.kwargs['branch'] = 'master'
context = super(ComparisonRecipeSelectView, self).get_context_data(**kwargs)
recipe = get_object_or_404(ClassicRecipe, pk=self.kwargs['pk'])
context['select_for'] = recipe
context['existing_cover_recipe'] = recipe.get_cover_recipe()
comparison_form = ClassicRecipeForm(prefix='selectrecipedialog', instance=recipe)
comparison_form.fields['cover_pn'].widget = forms.HiddenInput()
comparison_form.fields['cover_layerbranch'].widget = forms.HiddenInput()
context['comparison_form'] = comparison_form
if 'q' in self.request.GET:
search_form = ComparisonRecipeSelectForm(self.request.GET)
else:
search_form = ComparisonRecipeSelectForm()
context['search_form'] = search_form
context['can_edit'] = self._can_edit()
return context
def get_queryset(self):
query_string = self.request.GET.get('q', '')
selectedlayers_param = self.request.GET.get('selectedlayers', '')
if selectedlayers_param:
layer_ids = [int(i) for i in selectedlayers_param.split(',')]
else:
layer_ids = []
init_qs = Recipe.objects.filter(layerbranch__branch__name='master')
if layer_ids:
init_qs = init_qs.filter(layerbranch__layer__in=layer_ids)
if query_string.strip():
order_by = (Lower('pn'), 'layerbranch__layer')
qs0 = init_qs.filter(pn=query_string).order_by(*order_by)
entry_query = simplesearch.get_query(query_string, ['pn'])
qs1 = init_qs.filter(entry_query).order_by(*order_by)
entry_query = simplesearch.get_query(query_string, ['summary', 'description'])
qs2 = init_qs.filter(entry_query).order_by(*order_by)
qs = list(utils.chain_unique(qs0, qs1, qs2))
else:
if 'q' in self.request.GET:
qs = init_qs.order_by(Lower('pn'), 'layerbranch__layer')
else:
# It's a bit too slow to return all records by default, and most people
# won't actually want that (if they do they can just hit the search button
# with no query string)
return Recipe.objects.none()
return qs
def post(self, request, *args, **kwargs):
if not self._can_edit():
raise PermissionDenied
recipe = get_object_or_404(ClassicRecipe, pk=self.kwargs['pk'])
form = ClassicRecipeForm(request.POST, prefix='selectrecipedialog', instance=recipe)
if form.is_valid():
form.save()
messages.success(request, 'Changes to comparison recipe %s saved successfully.' % recipe.pn)
return HttpResponseRedirect(reverse('comparison_recipe', args=(recipe.id,)))
else:
# FIXME this is ugly because HTML gets escaped
messages.error(request, 'Failed to save changes: %s' % form.errors)
return self.get(request, *args, **kwargs)
class ComparisonRecipeSelectDetailView(DetailView):
model = Recipe
def get_context_data(self, **kwargs):
context = super(ComparisonRecipeSelectDetailView, self).get_context_data(**kwargs)
recipe = get_object_or_404(ClassicRecipe, pk=self.kwargs['selectfor'])
context['select_for'] = recipe
context['existing_cover_recipe'] = recipe.get_cover_recipe()
comparison_form = ClassicRecipeForm(prefix='selectrecipedialog', instance=recipe)
comparison_form.fields['cover_pn'].widget = forms.HiddenInput()
comparison_form.fields['cover_layerbranch'].widget = forms.HiddenInput()
context['comparison_form'] = comparison_form
context['can_edit'] = False
return context
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated():
raise PermissionDenied
recipe = get_object_or_404(ClassicRecipe, pk=self.kwargs['selectfor'])
form = ClassicRecipeForm(request.POST, prefix='selectrecipedialog', instance=recipe)
if form.is_valid():
form.save()
messages.success(request, 'Changes to comparison recipe %s saved successfully.' % recipe.pn)
return HttpResponseRedirect(reverse('comparison_recipe', args=(recipe.id,)))
else:
# FIXME this is ugly because HTML gets escaped
messages.error(request, 'Failed to save changes: %s' % form.errors)
return self.get(request, *args, **kwargs)

View File

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "layerindex/comparisonrecipebase.html" %}
{% load i18n %}
{% comment %}
@ -17,74 +17,43 @@
{% endautoescape %}
-->
{% block content %}
{% autoescape on %}
{% block breadcrumbs %}
<ul class="breadcrumb">
<li><a href="{% url 'comparison_recipe_search' branch.name %}">{{ branch.short_description }}</a> <span class="divider">&rarr;</span></li>
<li class="active">{{ recipe.name }}</li>
</ul>
{% endblock %}
<div class="container-fluid">
<div class="row-fluid">
{% block page_heading %}
<div class="page-header">
<h1>{{ recipe.name }} {{ recipe.pv }}</h1>
</div>
{% if branch.name == 'oe-classic' %}
<div class="alert alert-warning">
<b>NOTE:</b> This recipe is for OE-Classic, the older monolithic version of OpenEmbedded which is no longer actively developed. See below for migration information. If no replacement is available in current OpenEmbedded layers, you may be able to <a href="http://www.openembedded.org/wiki/Migrating_metadata_to_OE-Core">migrate the recipe</a> yourself.
</div>
{% endif %}
{% endblock %}
<table class="table table-striped table-bordered">
<tbody>
<tr>
<th>Name</th>
<td>{{ recipe.name }}</td>
</tr>
<tr>
<th>Version</th>
<td>{{ recipe.pv }}</td>
</tr>
<tr>
<th>Summary</th>
<td>{{ recipe.summary }}</td>
</tr>
<tr>
<th>Description</th>
<td>{{ recipe.description }}</td>
</tr>
<tr>
<th>Section</th>
<td>{{ recipe.section }}</td>
</tr>
<tr>
<th>License</th>
<td>{{ recipe.license }}{% if branch.name == 'oe-classic' %}*{% endif %}</td>
</tr>
<tr>
<th>Homepage</th>
<td>{% if recipe.homepage %}<a href="{{ recipe.homepage }}">{{ recipe.homepage }}</a>{% endif %}</td>
</tr>
{% if recipe.bugtracker %}
<tr>
<th>Bug tracker</th>
<td><a href="{{ recipe.bugtracker }}">{{ recipe.bugtracker }}</a></td>
</tr>
{% block to_recipe_extra %}{% if recipe.cover_verified %} <span class="label label-info">verified</span>{% endif %}{% if recipe.needs_attention %} <span class="label label-warning">needs attention</span>{% endif %}{% endblock %}
{% block selectbuttons %}
{% if can_edit %}
<a href="{% url 'comparison_select' recipe.id %}?q={{recipe.pn}}" class="btn btn-info">Select...</a>
{% endif %}
<tr>
<th>{% if branch.name == 'oe-classic' %}Recipe file{% else %}Package file{% endif %}</th>
<td>
{% if recipe.vcs_web_url %}
<a href="{{ recipe.vcs_web_url }}">{{ recipe.full_path }}</a>
{% endblock %}
{% block origin_row %}
{% if branch.name == 'oe-classic' %}
<th>Origin</th>
{% else %}
{{ recipe.full_path }}
<th>Distro / Layer</th>
{% endif %}
</td>
</tr>
<td><a href="{% url 'comparison_recipe_search' branch.name %}">{{ branch.short_description }}</a></td>
<td>{% if cover_recipe %}<a href="{% url 'layer_item' cover_recipe.layerbranch.branch.name cover_recipe.layerbranch.layer.name %}">{{ cover_recipe.layerbranch.layer }} ({{ cover_recipe.layerbranch.branch.name }} branch)</a>{% endif %}</td>
{% endblock %}
{% block table_extra %}
{% if recipe.extra_urls %}
<tr>
<th>Extra links</th>
@ -95,123 +64,8 @@
{% endfor %}
</ul>
</td>
<td>
</td>
</tr>
{% endif %}
</tbody>
</table>
{% if branch.name == 'oe-classic' %}
<p>* - in OE-Classic, some of the license values were not accurate. Please refer to current recipes (if available) for this information.</p>
{% endif %}
<h2>Patches</h2>
{% if recipe.patch_set.exists %}
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Patch</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for patch in recipe.patch_set.all %}
<tr>
<td><a href="{{ patch.vcs_web_url }}">{{ patch.src_path }}</a></td>
<td>{{ patch.get_status_display }} {{ patch.status_extra }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>None</p>
{% endif %}
{% if branch.name == 'oe-classic' %}
<h2>Migration information</h2>
{% else %}
<h2>Comparison information</h2>
{% endif %}
{% if can_edit %}
<form id="migration_form" class="form-inline" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
Coverage {{ form.cover_status }} <span id="id_span_cover_opts">by {{ form.cover_pn }} in {{ form.cover_layerbranch }}</span>
<label class="checkbox" id="id_label_cover_verified">
{{ form.cover_verified }} verified
</label>
<p>
<label>
Comment
{{ form.cover_comment }}
</label>
</p>
<p>
<label>
Category
{{ form.classic_category }}
</label>
</p>
<p>
<label class="checkbox" id="id_label_needs_attention">
{{ form.needs_attention }}
Needs attention
</label>
</p>
<input type="submit" value="Save" class='btn' />
</form>
{% else %}
<table class="table table-striped table-bordered">
<tbody>
<tr>
<th class="span2">Coverage</th>
<td>{{ recipe.get_cover_desc }}</td>
</tr>
{% if recipe.cover_pn %}
<th>Covering recipe</th>
<td>{% if cover_recipe %}<a href="{% url 'recipe' cover_recipe.id %}">{% endif %}{{ recipe.cover_pn }}{% if cover_recipe %}</a>{% endif %}{% if recipe.cover_layerbranch %} (in <a href="{% url 'layer_item' 'master' recipe.cover_layerbranch.layer.name %}">{{ recipe.cover_layerbranch.layer.name }}</a>){% endif %}</td>
{% endif %}
<tr>
<th>Categories</th>
<td>{{ recipe.classic_category }}</td>
</tr>
</tbody>
</table>
{% endif %}
</div>
</div>
{% endautoescape %}
{% endblock %}
{% block scripts %}
<script>
enable_value_field = function() {
cover_status = $('#id_cover_status').val()
if( cover_status == 'U' || cover_status == 'N' || cover_status == 'S' ) {
$('#id_cover_pn').prop('readonly', true);
$('#id_cover_layerbranch').prop('readonly', true);
$('#id_cover_verified').prop('readonly', true);
$('#id_label_cover_verified').addClass('muted');
$('#id_span_cover_opts').addClass('muted');
}
else {
$('#id_cover_pn').prop('readonly', false);
$('#id_cover_layerbranch').prop('readonly', false);
$('#id_cover_verified').prop('readonly', false);
$('#id_label_cover_verified').removeClass('muted');
$('#id_span_cover_opts').removeClass('muted');
}
}
$(document).ready(function() {
$('#id_cover_status').change(enable_value_field)
enable_value_field()
});
</script>
{% endblock %}

View File

@ -51,6 +51,7 @@
<div class="span12">
{% block page_heading %}
{% if branch.name == 'oe-classic' %}
<h2>OE-Classic recipes</h2>
@ -60,6 +61,7 @@
{% else %}
<h2>{{ branch.short_description }} packages</h2>
{% endif %}
{% endblock %}
<div class="row-fluid">
<form id="search-form" class="form-inline" method="GET">
@ -108,6 +110,8 @@
{% endif %}
</tr>
{% endfor %}
{% if branch.comparison %}
<tr><td><label class="checkbox"><input type="checkbox" name="compare" {% if compare %}checked{% endif %}></input>Show comparison</label></td></tr>
<tr><td><label class="checkbox"><input type="checkbox" name="reversed" id="id_reversed" {% if reversed %}checked{% endif %}></input>Reversed</label></td></tr>
@ -141,11 +145,14 @@
</div>
</td>
</tr>
{% endif %}
</tbody>
</table>
<button class="btn" type="submit">Search</button>
{% block export_button %}
<button class="btn" type="submit" formaction="{% url 'comparison_recipe_search_csv' branch.name %}">Export CSV</button>
{% endblock %}
</form>
</div>
@ -153,7 +160,13 @@
<table class="table table-striped table-bordered recipestable">
<thead>
<tr>
{% if reversed %}
{% if not branch.comparison %}
<th>Recipe name</th>
<th>Layer</th>
<th>Version</th>
<th class="span7">Description</th>
<th>Section</th>
{% elif reversed %}
<th>Recipe name</th>
<th>OE Layer</th>
<th>Version</th>
@ -190,13 +203,20 @@
<th>Covering recipe</th>
<th>Categories</th>
{% endif %}
{% block table_head_extra %}{% endblock %}
</tr>
</thead>
<tbody>
{% for recipe in recipe_list %}
<tr {% if recipe.preferred_count > 0 %}class="muted"{% endif %}>
{% if reversed %}
{% if not branch.comparison %}
<td><a href="{% block no_comparison_recipe_url %}{% url 'recipe' recipe.id %}{% endblock %}">{{ recipe.name }}</a></td>
<td>{{ recipe.layerbranch.layer.name }}</td>
<td>{{ recipe.pv }}</td>
<td>{{ recipe.short_desc }}</td>
<td>{{ recipe.section }}</td>
{% elif reversed %}
<td><a href="{% url 'recipe' recipe.id %}">{{ recipe.name }}{% if recipe.needs_attention %} <i class="icon-exclamation-sign" data-toggle="tooltip" title="Needs attention"></i>{% endif %}</a></td>
<td><a href="{% url 'layer_item' 'master' recipe.layerbranch.layer.name %}">{{ recipe.layerbranch.layer.name }}</a></td>
<td>{{ recipe.pv }}</td>
@ -220,13 +240,14 @@
<td></td>
{% endif %}
{% else %}
<td><a href="{% url 'comparison_recipe' recipe.id %}">{{ recipe.name }}{% if recipe.needs_attention %} <i class="icon-exclamation-sign" data-toggle="tooltip" title="Needs attention"></i>{% endif %}</a></td>
<td><a href="{% block comparison_recipe_url %}{% url 'comparison_recipe' recipe.id %}{% endblock %}">{{ recipe.name }}{% if recipe.needs_attention %} <i class="icon-exclamation-sign" data-toggle="tooltip" title="Needs attention"></i>{% endif %}</a></td>
<td>{{ recipe.pv }}</td>
<td>{{ recipe.short_desc }}</td>
<td>{{ recipe.get_cover_desc }}</td>
<td>{% if recipe.cover_pn %}{% if recipe.cover_recipe %}<a href="{% url 'recipe' recipe.cover_recipe.id %}">{% endif %}{{ recipe.cover_pn }}{% if recipe.cover_recipe %}</a>{% endif %}{% endif %}</td>
<td>{{ recipe.classic_category }}</td>
{% endif %}
{% block table_row_extra %}{% endblock %}
</tr>
{% endfor %}
</tbody>
@ -240,6 +261,8 @@
{% if searched %}
{% if branch.name == 'oe-classic' %}
<p>No matching OE-Classic recipes in database.</p>
{% elif not branch.comparison %}
<p>No matching recipes in {{ branch }} branch.</p>
{% else %}
<p>No matching {{ branch }} packages in database.</p>
{% endif %}
@ -384,5 +407,7 @@
$('#id_reversed').click(function (e) {
update_filters_enabled()
});
{% block scripts_extra %}
{% endblock %}
</script>
{% endblock %}

View File

@ -0,0 +1,222 @@
{% extends "base.html" %}
{% load i18n %}
{% comment %}
layerindex-web - image comparison recipe detail page template
Copyright (C) 2018 Intel Corporation
Licensed under the MIT license, see COPYING.MIT for details
{% endcomment %}
<!--
{% autoescape on %}
{% block title_append %} - {{ recipe.pn }}{% endblock %}
{% endautoescape %}
-->
{% block contenttag %}<div id="content" class="container-fluid top-padded">{% endblock %}
{% block content %}
{% autoescape on %}
{% block breadcrumbs %}
<ul class="breadcrumb">
<li><a href="#">{{ layerbranch_desc }}</a> <span class="divider">&rarr;</span></li>
<li class="active">{{ recipe.name }}</li>
</ul>
{% endblock %}
<div class="container-fluid">
<div class="row-fluid">
{% block page_heading %}
<div class="page-header">
<h1>{{ recipe.name }} {{ recipe.pv }}</h1>
</div>
{% endblock %}
<table class="table table-striped table-bordered">
<thead>
<tr>
<th></th>
<th width="50%">{{ layerbranch_desc }}{{ layerbranch_addtext }}</th>
<th width="50%">{{ to_desc }}</th>
</tr>
</thead>
<tbody>
<tr>
<th>Name</th>
<td>{{ recipe.name }}</td>
<td>{% if cover_recipe %}{{ cover_recipe.name }} ({{ recipe.get_cover_status_display }}{% if recipe.cover_comment %} - {{ recipe.cover_comment }}{% endif %}){% else %}{{ recipe.get_cover_status_display }}{% if recipe.cover_comment %} - {{ recipe.cover_comment }}{% endif %}{% endif %}{% block to_recipe_extra %}{% endblock %}
<div class="pull-right">
{% block selectbuttons %}
<a href="#" class="btn btn-info">Select...</a>
{% endblock %}
</div>
</td>
</tr>
<tr>
<th>Version</th>
<td>{{ recipe.pv }}</td>
<td>{{ cover_recipe.pv }}</td>
</tr>
<tr>
<th>Summary</th>
<td>{{ recipe.summary }}</td>
<td>{{ cover_recipe.summary }}</td>
</tr>
<tr>
<th>Description</th>
<td>{{ recipe.description }}</td>
<td>{{ cover_recipe.description }}</td>
</tr>
<tr>
<th>Section</th>
<td>{{ recipe.section }}</td>
<td>{{ cover_recipe.section }}</td>
</tr>
<tr>
<th>License</th>
<td>{{ recipe.license }}</td>
<td>{{ cover_recipe.license }}</td>
</tr>
<tr>
<th>Homepage</th>
<td>
{% if recipe.homepage_url_only %}
<a href="{{ recipe.homepage }}">{{ recipe.homepage }}</a>
{% elif recipe.homepage %}
{{ recipe.homepage }}
{% endif %}
</td>
<td>
{% if cover_recipe.homepage_url_only %}
<a href="{{ cover_recipe.homepage }}">{{ cover_recipe.homepage }}</a>
{% elif cover_recipe.homepage %}
{{ cover_recipe.homepage }}
{% endif %}
</td>
</tr>
{% if recipe.bugtracker or cover_recipe.bugtracker %}
<tr>
<th>Bug tracker</th>
<td><a href="{{ recipe.bugtracker }}">{{ recipe.bugtracker }}</a></td>
<td><a href="{{ cover_recipe.bugtracker }}">{{ cover_recipe.bugtracker }}</a></td>
</tr>
{% endif %}
<tr>
<th>Package/recipe file</th>
{% for rcp in recipes %}
<td>
{% if rcp.vcs_web_url %}
<a href="{{ rcp.vcs_web_url }}">{{ rcp.full_path }}</a>
{% else %}
{{ rcp.full_path }}
{% endif %}
</td>
{% endfor %}
</tr>
<tr>
{% block origin_row %}
<th>Layer</th>
<td><a href="{% url 'layer_item' recipe.layerbranch.branch.name recipe.layerbranch.layer.name %}">{{ layerbranch_desc }}</a>{{ layerbranch_addtext }}</td>
<td></td>
{% endblock %}
</tr>
{% block table_extra %}
{% endblock %}
</tbody>
</table>
<h2>Sources</h2>
<table width="100%" class="table table-bordered">
<thead>
<th width="50%">{{ layerbranch_desc }}{{ layerbranch_addtext }}</th>
<th width="50%">{{ to_desc }}</th>
</thead>
<tbody><tr>
{% for rcp in recipes %}
{% if rcp.source_set.exists %}
<td width="50%" valign="top">
<table class="table table-bordered">
<tbody>
{% for source in rcp.source_set.all %}
<tr>
<td>{% if source.web_url %}<a href="{{ source.web_url }}">{% endif %}{{ source.url }}{% if source.web_url %}</a>{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</td>
{% else %}
<td valign="top">None</td>
{% endif %}
{% endfor %}
</tr></tbody>
</table>
<h2>Patches</h2>
<table width="100%" class="table table-bordered">
<thead>
<th width="50%">{{ layerbranch_desc }}{{ layerbranch_addtext }}</th>
<th width="50%">{{ to_desc }}</th>
</thead>
<tbody><tr>
{% for rcp in recipes %}
{% if rcp.patch_set.exists %}
<td width="50%" valign="top">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Patch</th>
<th class="span3">Status</th>
</tr>
</thead>
<tbody>
{% for patch in rcp.patch_set.all %}
<tr>
<td><a href="{{ patch.vcs_web_url }}">{{ patch.src_path }}</a></td>
<td>{{ patch.get_status_display }} {{ patch.status_extra | urlize }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</td>
{% else %}
<td valign="top">None</td>
{% endif %}
{% endfor %}
</tr></tbody>
</table>
</div>
</div>
{% endautoescape %}
{% endblock %}
{% block scripts %}
<script>
$(document).ready(function() {
$('[data-toggle="tooltip"]').tooltip();
$('#id_cover_status').change(enable_value_field)
enable_value_field()
});
enable_value_field = function() {
cover_status = $('#id_cover_status').val()
if( cover_status == 'U' || cover_status == 'N' || cover_status == 'S' ) {
$('#id_cover_pn').prop('readonly', true);
$('#id_cover_layerbranch').prop('readonly', true);
$('#id_span_cover_opts').addClass('muted');
}
else {
$('#id_cover_pn').prop('readonly', false);
$('#id_cover_layerbranch').prop('readonly', false);
$('#id_span_cover_opts').removeClass('muted');
}
}
</script>
{% endblock %}

View File

@ -0,0 +1,119 @@
{% extends "layerindex/classicrecipes.html" %}
{% load i18n %}
{% comment %}
layerindex-web - comparison recipe selection page template
Copyright (C) 2018 Intel Corporation
Licensed under the MIT license, see COPYING.MIT for details
{% endcomment %}
<!--
{% block title_append %} - comparison - select match for {{ select_for.name }}{% endblock %}
-->
{% block page_heading %}
<h2>Select match for {{ select_for.pn }} in {% if branch.comparison %}{{ branch }}{% else %}OpenEmbedded{% endif %}</h2>
<div class="pull-right">
<a href="#selectRecipeDialog" role="button" data-toggle="modal" class="select_recipe_button btn {% if recipe.id == existing_cover_recipe.id %}btn-primary{% else %}btn-default{% endif %}">No match</a>
<a href="{% url 'comparison_recipe' select_for.id %}" class="btn btn-default">Cancel</a>
</div>
<form id="comparison_form" class="form-inline" method="post">
<div id="selectRecipeDialog" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="selectRecipeDialogLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="selectRecipeDialogLabel">Select <span id="id_span_select_recipe">recipe</span> to match {{ select_for.pn }}</h3>
</div>
<div class="modal-body">
{% csrf_token %}
{% for hidden in comparison_form.hidden_fields %}
{{ hidden }}
{% endfor %}
<select class="hidden-select" id="id_hidden_selectbox">
</select>
Coverage {{ comparison_form.cover_status }}
{% if comparison_form.cover_verified %}
<label class="checkbox" id="id_label_cover_verified">
{{ comparison_form.cover_verified }} verified
</label>
{% endif %}
{% if comparison_form.classic_category %}
<p>
<label>
Category
{{ comparison_form.classic_category }}
</label>
</p>
{% endif %}
<p>
<label>
Comment
{{ comparison_form.cover_comment }}
</label>
</p>
{% if comparison_form.needs_attention %}
<p>
<label class="checkbox" id="id_label_needs_attention">
{{ comparison_form.needs_attention }}
Needs attention
</label>
</p>
{% endif %}
</div>
<div class="modal-footer">
<button class="btn btn-primary" id="id_selectrecipedialog_save" data-dismiss="modal" aria-hidden="true">Save</button>
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
</div>
</div>
</form>
{% endblock %}
{% block table_head_extra %}<th></th>{% endblock %}
{% block table_row_extra %}<td><a href="#selectRecipeDialog" role="button" data-toggle="modal" class="select_recipe_button btn {% if recipe.id == existing_cover_recipe.id %}btn-primary{% else %}btn-default{% endif %}" recipe-pn="{{ recipe.pn }}" recipe-layerbranch="{{ recipe.layerbranch.id }}">Select</a></td>{% endblock %}
{% block no_comparison_recipe_url %}{% url 'comparison_select_detail' select_for.id recipe.id %}{% endblock %}
{% block export_button %}
{% endblock %}
{% block scripts_extra %}
$('.select_recipe_button').click(function (e) {
pn = $(this).attr('recipe-pn');
// FIXME this does tend to cause items to re-sort
$('#id_selectrecipedialog-cover_status').append($('#id_hidden_selectbox').children());
if( !pn ) {
$('#id_hidden_selectbox').append($('#id_selectrecipedialog-cover_status option[value="D"]'));
$('#id_hidden_selectbox').append($('#id_selectrecipedialog-cover_status option[value="E"]'));
}
else {
$('#id_hidden_selectbox').append($('#id_selectrecipedialog-cover_status option[value="N"]'));
$('#id_hidden_selectbox').append($('#id_selectrecipedialog-cover_status option[value="S"]'));
$('#id_hidden_selectbox').append($('#id_selectrecipedialog-cover_status option[value="U"]'));
}
$('#id_span_select_recipe').text(pn);
$('#id_selectrecipedialog-cover_pn').val(pn);
$('#id_selectrecipedialog-cover_layerbranch').val($(this).attr('recipe-layerbranch'));
if( pn ) {
{% if existing_cover_recipe %}
$('#id_selectrecipedialog-cover_status').val('{{ select_for.cover_status }}');
{% else %}
$('#id_selectrecipedialog-cover_status').val('D');
{% endif %}
}
else {
$('#id_selectrecipedialog-cover_status').val('N');
}
});
$('#id_selectrecipedialog_save').click(function (e) {
$('#comparison_form').submit()
$('#selectRecipeDialog').modal('hide')
});
{% endblock %}

View File

@ -0,0 +1,98 @@
{% extends "layerindex/recipedetail.html" %}
{% load i18n %}
{% comment %}
layerindex-web - comparison recipe selection detail page template
Copyright (C) 2018 Intel Corporation
Licensed under the MIT license, see COPYING.MIT for details
{% endcomment %}
<!--
{% block title_append %} - comparison - select match for {{ select_for.name }}{% endblock %}
-->
{% block page_heading %}
<div class="page-header">
<h1>{{ recipe.name }} {{ recipe.pv }}</h1>
<div class="pull-right"><a href="#selectRecipeDialog" role="button" data-toggle="modal" class="select_recipe_button btn {% if recipe.id == existing_cover_recipe.id %}btn-primary{% else %}btn-default{% endif %}" recipe-pn="{{ recipe.pn }}" recipe-layerbranch="{{ recipe.layerbranch.id }}">Select</a> <a href="javascript:history.back()" class="btn btn-default">Back</a></div>
</div>
<form id="comparison_form" class="form-inline" method="post">
<div id="selectRecipeDialog" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="selectRecipeDialogLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="selectRecipeDialogLabel">Select <span id="id_span_select_recipe">recipe</span> to match {{ select_for.pn }}</h3>
</div>
<div class="modal-body">
{% csrf_token %}
{% for hidden in comparison_form.hidden_fields %}
{{ hidden }}
{% endfor %}
<select class="hidden-select" id="id_hidden_selectbox">
</select>
Coverage {{ comparison_form.cover_status }}
{% if comparison_form.cover_verified %}
<label class="checkbox" id="id_label_cover_verified">
{{ comparison_form.cover_verified }} verified
</label>
{% endif %}
{% if comparison_form.classic_category %}
<p>
<label>
Category
{{ comparison_form.classic_category }}
</label>
</p>
{% endif %}
<p>
<label>
Comment
{{ comparison_form.cover_comment }}
</label>
</p>
{% if comparison_form.needs_attention %}
<p>
<label class="checkbox" id="id_label_needs_attention">
{{ comparison_form.needs_attention }}
Needs attention
</label>
</p>
{% endif %}
</div>
<div class="modal-footer">
<button class="btn btn-primary" id="id_selectrecipedialog_save" data-dismiss="modal" aria-hidden="true">Save</button>
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
</div>
</div>
</form>
{% endblock %}
{% block scripts %}
<script>
$('.select_recipe_button').click(function (e) {
pn = "{{ recipe.pn }}";
$('#id_span_select_recipe').text(pn);
$('#id_selectrecipedialog-cover_pn').val(pn);
$('#id_selectrecipedialog-cover_layerbranch').val("{{ recipe.layerbranch.id }}");
{% if existing_cover_recipe %}
$('#id_selectrecipedialog-cover_status').val('{{ select_for.cover_status }}');
{% else %}
$('#id_selectrecipedialog-cover_status').val('D');
{% endif %}
});
$('#id_selectrecipedialog_save').click(function (e) {
$('#comparison_form').submit()
$('#selectRecipeDialog').modal('hide')
});
$(document).ready(function() {
$('#id_selectrecipedialog-cover_status option[value="N"]').remove();
$('#id_selectrecipedialog-cover_status option[value="S"]').remove();
$('#id_selectrecipedialog-cover_status option[value="U"]').remove();
});
</script>
{% endblock %}

View File

@ -29,9 +29,11 @@
<div class="container-fluid">
<div class="row-fluid">
{% block page_heading %}
<div class="page-header">
<h1>{{ recipe.name }} {{ recipe.pv }} {% if recipe.blacklisted %} <span class="label label-inverse valign-middle">blacklisted</span> {% endif %}</h1>
</div>
{% endblock %}
{% if recipe.blacklisted %}
<div class="alert">
@ -216,6 +218,9 @@
</div>
</div>
{% block content_extra %}
{% endblock %}
{% endautoescape %}
{% endblock %}
@ -226,4 +231,6 @@
$('[data-toggle="tooltip"]').tooltip();
});
</script>
{% block scripts_extra %}
{% endblock %}
{% endblock %}