diff --git a/bitbake/lib/toaster/toastergui/api.py b/bitbake/lib/toaster/toastergui/api.py index be18090daf..8876409964 100644 --- a/bitbake/lib/toaster/toastergui/api.py +++ b/bitbake/lib/toaster/toastergui/api.py @@ -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/ + """ + 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": } + """ + 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": } + """ + + 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"}) diff --git a/bitbake/lib/toaster/toastergui/static/js/libtoaster.js b/bitbake/lib/toaster/toastergui/static/js/libtoaster.js index f56affd8ea..b2099a6048 100644 --- a/bitbake/lib/toaster/toastergui/static/js/libtoaster.js +++ b/bitbake/lib/toaster/toastergui/static/js/libtoaster.js @@ -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) { diff --git a/bitbake/lib/toaster/toastergui/static/js/projectpage.js b/bitbake/lib/toaster/toastergui/static/js/projectpage.js index b75b3e1869..3bf3cbaf2b 100644 --- a/bitbake/lib/toaster/toastergui/static/js/projectpage.js +++ b/bitbake/lib/toaster/toastergui/static/js/projectpage.js @@ -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 = $(""); - 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 = $('You have changed the project release to: .'); - 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 = $('You have changed the project release to: . This has caused the following changes in your project layers:
    '); - - 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 = '
  • '+layers.length+' layers changed to the '+release.name+' release: '; - for (var i in layers){ - li += ''+layers[i].name+''; - if (i !== 0) - li += ', '; - } - - changedList.append($(li)); - - /* Layers removed */ - if (layersToRm && layersToRm.length > 0){ - if (layersToRm.length == 1) - li = '
  • 1 layer removed: '+layersToRm[0].name+'
  • '; - else - li = '
  • '+layersToRm.length+' layers deleted: '+layersDelList+'
  • '; - - 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($("
  • ").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); + }); } diff --git a/bitbake/lib/toaster/toastergui/static/js/tests/test.js b/bitbake/lib/toaster/toastergui/static/js/tests/test.js index f8d566b3e3..d7953de447 100644 --- a/bitbake/lib/toaster/toastergui/static/js/tests/test.js +++ b/bitbake/lib/toaster/toastergui/static/js/tests/test.js @@ -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"); diff --git a/bitbake/lib/toaster/toastergui/templates/base.html b/bitbake/lib/toaster/toastergui/templates/base.html index 58491eba81..c1b1417a76 100644 --- a/bitbake/lib/toaster/toastergui/templates/base.html +++ b/bitbake/lib/toaster/toastergui/templates/base.html @@ -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}}, diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py index 9509cd5928..1232611e2e 100644 --- a/bitbake/lib/toaster/toastergui/urls.py +++ b/bitbake/lib/toaster/toastergui/urls.py @@ -220,6 +220,10 @@ urlpatterns = patterns('toastergui.views', api.XhrBuildRequest.as_view(), name='xhr_buildrequest'), + url(r'xhr_project/(?P\d+)$', + api.XhrProject.as_view(), + name='xhr_project'), + url(r'^mostrecentbuilds$', api.MostRecentBuildsView.as_view(), name='most_recent_builds'), diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py index 365a1e88ff..2efb0fd56c 100755 --- a/bitbake/lib/toaster/toastergui/views.py +++ b/bitbake/lib/toaster/toastergui/views.py @@ -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 """