bitbake: toaster: Clean up and convert to rest api project edit and get calls

Convert the project xhr calls into proper rest api and port the client
side calls to use the new API. Fix all the pyflakes identified issues
and clean up unused fields.

Also remove the api and client side code for changing release on the fly
as this is no longer supported.

[YOCTO #9519]

(Bitbake rev: 8b01767d6e787cdb09789116ebf57dfb70f521bc)

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
Michael Wood 2016-09-26 13:59:28 +03:00 committed by Richard Purdie
parent 0b17e6d32c
commit 4d0bc8d521
7 changed files with 179 additions and 275 deletions

View File

@ -20,11 +20,13 @@
import re
import logging
from collections import Counter
from orm.models import Project, ProjectTarget, Build, Layer_Version
from orm.models import LayerVersionDependency, LayerSource, ProjectLayer
from orm.models import Recipe, CustomImageRecipe, CustomImagePackage
from orm.models import Layer, Target, Package, Package_Dependency
from orm.models import ProjectVariable
from bldcontrol.models import BuildRequest
from bldcontrol import bbcontroller
@ -772,3 +774,162 @@ class XhrCustomRecipePackages(View):
except CustomImageRecipe.DoesNotExist:
return error_response("Tried to remove package that wasn't"
" present")
class XhrProject(View):
""" Create, delete or edit a project
Entry point: /xhr_project/<project_id>
"""
def post(self, request, *args, **kwargs):
"""
Edit project control
Args:
layerAdd = layer_version_id layer_version_id ...
layerDel = layer_version_id layer_version_id ...
projectName = new_project_name
machineName = new_machine_name
Returns:
{"error": "ok"}
or
{"error": <error message>}
"""
try:
prj = Project.objects.get(pk=kwargs['project_id'])
except Project.DoesNotExist:
return error_response("No such project")
# Add layers
if 'layerAdd' in request.POST and len(request.POST['layerAdd']) > 0:
for layer_version_id in request.POST['layerAdd'].split(','):
try:
lv = Layer_Version.objects.get(pk=int(layer_version_id))
ProjectLayer.objects.get_or_create(project=prj,
layercommit=lv)
except Layer_Version.DoesNotExist:
return error_response("Layer version %s asked to add "
"doesn't exist" % layer_version_id)
# Remove layers
if 'layerDel' in request.POST and len(request.POST['layerDel']) > 0:
layer_version_ids = request.POST['layerDel'].split(',')
ProjectLayer.objects.filter(
project=prj,
layercommit_id__in=layer_version_ids).delete()
# Project name change
if 'projectName' in request.POST:
prj.name = request.POST['projectName']
prj.save()
# Machine name change
if 'machineName' in request.POST:
machinevar = prj.projectvariable_set.get(name="MACHINE")
machinevar.value = request.POST['machineName']
machinevar.save()
return JsonResponse({"error": "ok"})
def get(self, request, *args, **kwargs):
"""
Returns:
json object representing the current project
or:
{"error": <error message>}
"""
try:
project = Project.objects.get(pk=kwargs['project_id'])
except Project.DoesNotExist:
return error_response("Project %s does not exist" %
kwargs['project_id'])
# Create the frequently built targets list
freqtargets = Counter(Target.objects.filter(
Q(build__project=project),
~Q(build__outcome=Build.IN_PROGRESS)
).order_by("target").values_list("target", flat=True))
freqtargets = freqtargets.most_common(5)
# We now have the targets in order of frequency but if there are two
# with the same frequency then we need to make sure those are in
# alphabetical order without losing the frequency ordering
tmp = []
switch = None
for i, freqtartget in enumerate(freqtargets):
target, count = freqtartget
try:
target_next, count_next = freqtargets[i+1]
if count == count_next and target > target_next:
switch = target
continue
except IndexError:
pass
tmp.append(target)
if switch:
tmp.append(switch)
switch = None
freqtargets = tmp
layers = []
for layer in project.projectlayer_set.all():
layers.append({
"id": layer.layercommit.pk,
"name": layer.layercommit.layer.name,
"vcs_url": layer.layercommit.layer.vcs_url,
"local_source_dir": layer.layercommit.layer.local_source_dir,
"vcs_reference": layer.layercommit.get_vcs_reference(),
"url": layer.layercommit.layer.layer_index_url,
"layerdetailurl": layer.layercommit.get_detailspage_url(
project.pk),
"layersource": layer.layercommit.layer_source
})
data = {
"name": project.name,
"layers": layers,
"freqtargets": freqtargets,
}
if project.release is not None:
data['release'] = {
"id": project.release.pk,
"name": project.release.name,
"description": project.release.description
}
try:
data["machine"] = {"name":
project.projectvariable_set.get(
name="MACHINE").value}
except ProjectVariable.DoesNotExist:
data["machine"] = None
try:
data["distro"] = project.projectvariable_set.get(
name="DISTRO").value
except ProjectVariable.DoesNotExist:
data["distro"] = "-- not set yet"
data['error'] = "ok"
return JsonResponse(data)
def put(self, request, *args, **kwargs):
# TODO create new project api
return HttpResponse()
def delete(self, request, *args, **kwargs):
try:
Project.objects.get(kwargs['project_id']).delete()
except Project.DoesNotExist:
return error_response("Project %s does not exist" %
kwargs['project_id'])
return JsonResponse({"error": "ok"})

View File

@ -167,7 +167,6 @@ var libtoaster = (function () {
function _getProjectInfo(url, onsuccess, onfail){
$.ajax({
type: "GET",
data : { format: "json" },
url: url,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (_data) {
@ -194,7 +193,7 @@ var libtoaster = (function () {
function _editCurrentProject(data, onSuccess, onFail){
$.ajax({
type: "POST",
url: libtoaster.ctx.projectPageUrl + "?format=json",
url: libtoaster.ctx.xhrProjectUrl,
data: data,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (data) {

View File

@ -27,11 +27,10 @@ function projectPageInit(ctx) {
var urlParams = libtoaster.parseUrlParams();
libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl, function(prjInfo){
libtoaster.getProjectInfo(libtoaster.ctx.xhrProjectUrl, function(prjInfo){
updateProjectLayers(prjInfo.layers);
updateFreqBuildRecipes(prjInfo.freqtargets);
updateProjectRelease(prjInfo.release);
updateProjectReleases(prjInfo.releases, prjInfo.release);
/* If we're receiving a machine set from the url and it's different from
* our current machine then activate set machine sequence.
@ -287,7 +286,9 @@ function projectPageInit(ctx) {
machineNameTitle.text(machineName);
}
libtoaster.makeTypeahead(machineChangeInput, libtoaster.ctx.machinesTypeAheadUrl, { }, function(item){
libtoaster.makeTypeahead(machineChangeInput,
libtoaster.ctx.machinesTypeAheadUrl,
{ }, function(item){
currentMachineAddSelection = item.name;
machineChangeBtn.removeAttr("disabled");
});
@ -324,146 +325,10 @@ function projectPageInit(ctx) {
releaseTitle.text(release.description);
}
function updateProjectReleases(releases, current){
for (var i in releases){
var releaseOption = $("<option></option>");
releaseOption.val(releases[i].id);
releaseOption.text(releases[i].description);
releaseOption.data('release', releases[i]);
if (releases[i].id == current.id)
releaseOption.attr("selected", "selected");
releaseForm.children("select").append(releaseOption);
}
}
releaseChangeFormToggle.click(function(){
releaseForm.slideDown();
releaseTitle.hide();
$(this).hide();
});
cancelReleaseChange.click(function(e){
$("#delete-project-confirmed").click(function(e){
e.preventDefault();
releaseForm.slideUp(function(){
releaseTitle.show();
releaseChangeFormToggle.show();
});
});
function changeProjectRelease(release, layersToRm){
libtoaster.editCurrentProject({ projectVersion : release.id },
function(){
/* Success */
/* Update layers list with new layers */
layersInPrjList.addClass('muted');
libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl,
function(prjInfo){
layersInPrjList.children().remove();
updateProjectLayers(prjInfo.layers);
layersInPrjList.removeClass('muted');
releaseChangedNotification(release, prjInfo.layers, layersToRm);
});
updateProjectRelease(release);
cancelReleaseChange.click();
});
}
/* Create a notification to show the changes to the layer configuration
* caused by changing a release.
*/
function releaseChangedNotification(release, layers, layersToRm){
var message;
if (layers.length === 0 && layersToRm.length === 0){
message = $('<span><span class="lead">You have changed the project release to: <strong><span id="notify-release-name"></span></strong>.');
message.find("#notify-release-name").text(release.description);
libtoaster.showChangeNotification(message);
return;
}
/* Create the whitespace separated list of layers removed */
var layersDelList = "";
layersToRm.map(function(layer, i){
layersDelList += layer.name;
if (layersToRm[i+1] !== undefined)
layersDelList += ', ';
});
message = $('<span><span class="lead">You have changed the project release to: <strong><span id="notify-release-name"></span></strong>. This has caused the following changes in your project layers:</span><ul id="notify-layers-changed-list"></ul></span>');
var changedList = message.find("#notify-layers-changed-list");
message.find("#notify-release-name").text(release.description);
/* Manually construct the list item for changed layers */
var li = '<li><strong>'+layers.length+'</strong> layers changed to the <strong>'+release.name+'</strong> release: ';
for (var i in layers){
li += '<a href='+layers[i].layerdetailurl+'>'+layers[i].name+'</a>';
if (i !== 0)
li += ', ';
}
changedList.append($(li));
/* Layers removed */
if (layersToRm && layersToRm.length > 0){
if (layersToRm.length == 1)
li = '<li><strong>1</strong> layer removed: '+layersToRm[0].name+'</li>';
else
li = '<li><strong>'+layersToRm.length+'</strong> layers deleted: '+layersDelList+'</li>';
changedList.append($(li));
}
libtoaster.showChangeNotification(message);
}
/* Show the modal dialog which gives the option to remove layers which
* aren't compatible with the proposed release
*/
function showReleaseLayerChangeModal(release, layers){
var layersToRmList = releaseModal.find("#layers-to-remove-list");
layersToRmList.text("");
releaseModal.find(".proposed-release-change-name").text(release.description);
releaseModal.data("layers", layers);
releaseModal.data("release", release);
for (var i in layers){
layersToRmList.append($("<li></li>").text(layers[i].name));
}
releaseModal.modal('show');
}
$("#change-release-btn").click(function(e){
e.preventDefault();
var newRelease = releaseForm.find("option:selected").data('release');
$.getJSON(ctx.testReleaseChangeUrl,
{ new_release_id: newRelease.id },
function(layers) {
if (layers.rows.length === 0){
/* No layers to change for this release */
changeProjectRelease(newRelease, []);
} else {
showReleaseLayerChangeModal(newRelease, layers.rows);
}
});
});
/* Release change modal accept */
$("#change-release-and-rm-layers").click(function(){
var layers = releaseModal.data("layers");
var release = releaseModal.data("release");
changeProjectRelease(release, layers);
});
}

View File

@ -42,9 +42,8 @@ QUnit.test("Layer alert notification", function(assert) {
QUnit.test("Project info", function(assert){
var done = assert.async();
libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl, function(prjInfo){
libtoaster.getProjectInfo(libtoaster.ctx.xhrProjectUrl, function(prjInfo){
assert.ok(prjInfo.machine.name);
assert.ok(prjInfo.releases.length > 0);
assert.ok(prjInfo.layers.length > 0);
assert.ok(prjInfo.freqtargets);
assert.ok(prjInfo.release);
@ -82,11 +81,11 @@ QUnit.test("Add layer", function(assert){
}, 200);
/* Compare the number of layers before and after the add in the project */
libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl, function(prjInfo){
libtoaster.getProjectInfo(libtoaster.ctx.xhrProjectUrl, function(prjInfo){
var origNumLayers = prjInfo.layers.length;
libtoaster.addRmLayer(layer, true, function(deps){
libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl,
libtoaster.getProjectInfo(libtoaster.ctx.xhrProjectUrl,
function(prjInfo){
assert.ok(prjInfo.layers.length > origNumLayers,
"Layer not added to project");

View File

@ -44,6 +44,7 @@
{% if project.id %}
projectId : {{project.id}},
projectPageUrl : {% url 'project' project.id as purl %}{{purl|json}},
xhrProjectUrl : {% url 'xhr_project' project.id as pxurl %}{{pxurl|json}},
projectName : {{project.name|json}},
recipesTypeAheadUrl: {% url 'xhr_recipestypeahead' project.id as paturl%}{{paturl|json}},
layersTypeAheadUrl: {% url 'xhr_layerstypeahead' project.id as paturl%}{{paturl|json}},

View File

@ -220,6 +220,10 @@ urlpatterns = patterns('toastergui.views',
api.XhrBuildRequest.as_view(),
name='xhr_buildrequest'),
url(r'xhr_project/(?P<project_id>\d+)$',
api.XhrProject.as_view(),
name='xhr_project'),
url(r'^mostrecentbuilds$', api.MostRecentBuildsView.as_view(),
name='most_recent_builds'),

View File

@ -1361,136 +1361,11 @@ if True:
raise Exception("Invalid HTTP method for this page")
# Shows the edit project page
@_template_renderer('project.html')
def project(request, pid):
prj = Project.objects.get(id = pid)
try:
puser = User.objects.get(id = prj.user_id)
except User.DoesNotExist:
puser = None
# execute POST requests
if request.method == "POST":
# add layers
if 'layerAdd' in request.POST and len(request.POST['layerAdd']) > 0:
for lc in Layer_Version.objects.filter(pk__in=[i for i in request.POST['layerAdd'].split(",") if len(i) > 0]):
ProjectLayer.objects.get_or_create(project = prj, layercommit = lc)
# remove layers
if 'layerDel' in request.POST and len(request.POST['layerDel']) > 0:
for t in request.POST['layerDel'].strip().split(" "):
pt = ProjectLayer.objects.filter(project = prj, layercommit_id = int(t)).delete()
if 'projectName' in request.POST:
prj.name = request.POST['projectName']
prj.save();
if 'projectVersion' in request.POST:
# If the release is the current project then return now
if prj.release.pk == int(request.POST.get('projectVersion',-1)):
return {}
prj.release = Release.objects.get(pk = request.POST['projectVersion'])
# we need to change the bitbake version
prj.bitbake_version = prj.release.bitbake_version
prj.save()
# we need to change the layers
for project in prj.projectlayer_set.all():
# find and add a similarly-named layer on the new branch
try:
layer_versions = prj.get_all_compatible_layer_versions()
layer_versions = layer_versions.filter(layer__name = project.layercommit.layer.name)
ProjectLayer.objects.get_or_create(project = prj, layercommit = layer_versions.first())
except IndexError:
pass
finally:
# get rid of the old entry
project.delete()
if 'machineName' in request.POST:
machinevar = prj.projectvariable_set.get(name="MACHINE")
machinevar.value=request.POST['machineName']
machinevar.save()
# we use implicit knowledge of the current user's project to filter layer information, e.g.
pid = prj.id
from collections import Counter
freqtargets = Counter(Target.objects.filter(
Q(build__project=prj),
~Q(build__outcome=Build.IN_PROGRESS)
).order_by("target").values_list("target", flat=True))
freqtargets = freqtargets.most_common(5)
# We now have the targets in order of frequency but if there are two
# with the same frequency then we need to make sure those are in
# alphabetical order without losing the frequency ordering
tmp = []
switch = None
for i, freqtartget in enumerate(freqtargets):
target, count = freqtartget
try:
target_next, count_next = freqtargets[i+1]
if count == count_next and target > target_next:
switch = target
continue
except IndexError:
pass
tmp.append(target)
if switch:
tmp.append(switch)
switch = None
freqtargets = tmp
layers = [{"id": x.layercommit.pk, "orderid": x.pk, "name" : x.layercommit.layer.name,
"vcs_url": x.layercommit.layer.vcs_url, "local_source_dir": x.layercommit.layer.local_source_dir, "vcs_reference" : x.layercommit.get_vcs_reference(),
"url": x.layercommit.layer.layer_index_url, "layerdetailurl": x.layercommit.get_detailspage_url(prj.pk),
"branch" : {"name" : x.layercommit.get_vcs_reference(),
"layersource" : x.layercommit.layer_source }
} for x in prj.projectlayer_set.all().order_by("id")]
context = {
"project" : prj,
"lvs_nos" : Layer_Version.objects.all().count(),
"completedbuilds": Build.objects.exclude(outcome = Build.IN_PROGRESS).filter(project_id = pid),
"prj" : {"name": prj.name, },
"buildrequests" : prj.build_set.filter(outcome=Build.IN_PROGRESS),
"builds" : Build.get_recent(prj),
"layers" : layers,
"targets" : [{"target" : x.target, "task" : x.task, "pk": x.pk} for x in prj.projecttarget_set.all()],
"variables": [(x.name, x.value) for x in prj.projectvariable_set.all()],
"freqtargets": freqtargets,
"releases": [{"id": x.pk, "name": x.name, "description":x.description} for x in Release.objects.all()],
"project_html": 1,
"recipesTypeAheadUrl": reverse('xhr_recipestypeahead', args=(prj.pk,)),
"projectBuildsUrl": reverse('projectbuilds', args=(prj.pk,)),
}
if prj.release is not None:
context['release'] = { "id": prj.release.pk, "name": prj.release.name, "description": prj.release.description}
try:
context["machine"] = {"name": prj.projectvariable_set.get(name="MACHINE").value}
except ProjectVariable.DoesNotExist:
context["machine"] = None
try:
context["distro"] = prj.projectvariable_set.get(name="DISTRO").value
except ProjectVariable.DoesNotExist:
context["distro"] = "-- not set yet"
return context
project = Project.objects.get(pk=pid)
context = {"project": project}
return render(request, "project.html", context)
def jsunittests(request):
""" Provides a page for the js unit tests """