diff options
| -rw-r--r-- | bitbake/lib/toaster/toastergui/urls.py | 10 | ||||
| -rwxr-xr-x | bitbake/lib/toaster/toastergui/views.py | 152 |
2 files changed, 159 insertions, 3 deletions
diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py index 55f325d0d6..b47a161687 100644 --- a/bitbake/lib/toaster/toastergui/urls.py +++ b/bitbake/lib/toaster/toastergui/urls.py | |||
| @@ -152,6 +152,14 @@ urlpatterns = patterns('toastergui.views', | |||
| 152 | # JS Unit tests | 152 | # JS Unit tests |
| 153 | url(r'^js-unit-tests/$', 'jsunittests', name='js-unit-tests'), | 153 | url(r'^js-unit-tests/$', 'jsunittests', name='js-unit-tests'), |
| 154 | 154 | ||
| 155 | # default redirection | 155 | # image customisation functionality |
| 156 | url(r'^xhr_customrecipe/(?P<recipe_id>\d+)/packages/(?P<package_id>\d+|)$', | ||
| 157 | 'xhr_customrecipe_packages', name='xhr_customrecipe_packages'), | ||
| 158 | url(r'^xhr_customrecipe/(?P<recipe_id>\d+)$', 'xhr_customrecipe_id', | ||
| 159 | name='xhr_customrecipe_id'), | ||
| 160 | url(r'^xhr_customrecipe/', 'xhr_customrecipe', | ||
| 161 | name='xhr_customrecipe'), | ||
| 162 | |||
| 163 | # default redirection | ||
| 156 | url(r'^$', RedirectView.as_view( url= 'landing')), | 164 | url(r'^$', RedirectView.as_view( url= 'landing')), |
| 157 | ) | 165 | ) |
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py index 5ea6122a02..392e56da2a 100755 --- a/bitbake/lib/toaster/toastergui/views.py +++ b/bitbake/lib/toaster/toastergui/views.py | |||
| @@ -26,12 +26,12 @@ | |||
| 26 | import operator,re | 26 | import operator,re |
| 27 | 27 | ||
| 28 | from django.db.models import F, Q, Sum, Count, Max | 28 | from django.db.models import F, Q, Sum, Count, Max |
| 29 | from django.db import IntegrityError | 29 | from django.db import IntegrityError, Error |
| 30 | from django.shortcuts import render, redirect | 30 | from django.shortcuts import render, redirect |
| 31 | from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable | 31 | from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable |
| 32 | from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency | 32 | from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency |
| 33 | from orm.models import Target_Installed_Package, Target_File, Target_Image_File, BuildArtifact | 33 | from orm.models import Target_Installed_Package, Target_File, Target_Image_File, BuildArtifact |
| 34 | from orm.models import BitbakeVersion | 34 | from orm.models import BitbakeVersion, CustomImageRecipe |
| 35 | from bldcontrol import bbcontroller | 35 | from bldcontrol import bbcontroller |
| 36 | from django.views.decorators.cache import cache_control | 36 | from django.views.decorators.cache import cache_control |
| 37 | from django.core.urlresolvers import reverse, resolve | 37 | from django.core.urlresolvers import reverse, resolve |
| @@ -2596,7 +2596,155 @@ if True: | |||
| 2596 | 2596 | ||
| 2597 | return HttpResponse(jsonfilter({"error": "ok",}), content_type = "application/json") | 2597 | return HttpResponse(jsonfilter({"error": "ok",}), content_type = "application/json") |
| 2598 | 2598 | ||
| 2599 | @xhr_response | ||
| 2600 | def xhr_customrecipe(request): | ||
| 2601 | """ | ||
| 2602 | Custom image recipe REST API | ||
| 2603 | |||
| 2604 | Entry point: /xhr_customrecipe/ | ||
| 2605 | Method: POST | ||
| 2606 | |||
| 2607 | Args: | ||
| 2608 | name: name of custom recipe to create | ||
| 2609 | project: target project id of orm.models.Project | ||
| 2610 | base: base recipe id of orm.models.Recipe | ||
| 2611 | |||
| 2612 | Returns: | ||
| 2613 | {"error": "ok", | ||
| 2614 | "url": <url of the created recipe>} | ||
| 2615 | or | ||
| 2616 | {"error": <error message>} | ||
| 2617 | """ | ||
| 2618 | # check if request has all required parameters | ||
| 2619 | for param in ('name', 'project', 'base'): | ||
| 2620 | if param not in request.POST: | ||
| 2621 | return {"error": "Missing parameter '%s'" % param} | ||
| 2622 | |||
| 2623 | # get project and baserecipe objects | ||
| 2624 | params = {} | ||
| 2625 | for name, model in [("project", Project), | ||
| 2626 | ("base", Recipe)]: | ||
| 2627 | value = request.POST[name] | ||
| 2628 | try: | ||
| 2629 | params[name] = model.objects.get(id=value) | ||
| 2630 | except model.DoesNotExist: | ||
| 2631 | return {"error": "Invalid %s id %s" % (name, value)} | ||
| 2632 | |||
| 2633 | # create custom recipe | ||
| 2634 | try: | ||
| 2635 | recipe = CustomImageRecipe.objects.create( | ||
| 2636 | name=request.POST["name"], | ||
| 2637 | base_recipe=params["base"], | ||
| 2638 | project=params["project"]) | ||
| 2639 | except Error as err: | ||
| 2640 | return {"error": "Can't create custom recipe: %s" % err} | ||
| 2641 | |||
| 2642 | # Find the package list from the last build of this recipe/target | ||
| 2643 | build = Build.objects.filter(target__target=params['base'].name, | ||
| 2644 | project=params['project']).last() | ||
| 2645 | |||
| 2646 | if build: | ||
| 2647 | # Copy in every package | ||
| 2648 | # We don't want these packages to be linked to anything because | ||
| 2649 | # that underlying data may change e.g. delete a build | ||
| 2650 | for package in build.package_set.all(): | ||
| 2651 | # Create the duplicate | ||
| 2652 | package.pk = None | ||
| 2653 | package.save() | ||
| 2654 | # Disassociate the package from the build | ||
| 2655 | package.build = None | ||
| 2656 | package.save() | ||
| 2657 | recipe.packages.add(package) | ||
| 2658 | else: | ||
| 2659 | logger.warn("No packages found for this base recipe") | ||
| 2660 | |||
| 2661 | return {"error": "ok", | ||
| 2662 | "url": reverse('customrecipe', args=(params['project'].pk, | ||
| 2663 | recipe.id))} | ||
| 2664 | |||
| 2665 | @xhr_response | ||
| 2666 | def xhr_customrecipe_id(request, recipe_id): | ||
| 2667 | """ | ||
| 2668 | Set of ReST API processors working with recipe id. | ||
| 2669 | |||
| 2670 | Entry point: /xhr_customrecipe/<recipe_id> | ||
| 2671 | |||
| 2672 | Methods: | ||
| 2673 | GET - Get details of custom image recipe | ||
| 2674 | DELETE - Delete custom image recipe | ||
| 2675 | |||
| 2676 | Returns: | ||
| 2677 | GET: | ||
| 2678 | {"error": "ok", | ||
| 2679 | "info": dictionary of field name -> value pairs | ||
| 2680 | of the CustomImageRecipe model} | ||
| 2681 | DELETE: | ||
| 2682 | {"error": "ok"} | ||
| 2683 | or | ||
| 2684 | {"error": <error message>} | ||
| 2685 | """ | ||
| 2686 | objects = CustomImageRecipe.objects.filter(id=recipe_id) | ||
| 2687 | if not objects: | ||
| 2688 | return {"error": "Custom recipe with id=%s " | ||
| 2689 | "not found" % recipe_id} | ||
| 2690 | if request.method == 'GET': | ||
| 2691 | values = CustomImageRecipe.objects.filter(id=recipe_id).values() | ||
| 2692 | if values: | ||
| 2693 | return {"error": "ok", "info": values[0]} | ||
| 2694 | else: | ||
| 2695 | return {"error": "Custom recipe with id=%s " | ||
| 2696 | "not found" % recipe_id} | ||
| 2697 | return {"error": "ok", "info": objects.values()[0]} | ||
| 2698 | elif request.method == 'DELETE': | ||
| 2699 | objects.delete() | ||
| 2700 | return {"error": "ok"} | ||
| 2701 | else: | ||
| 2702 | return {"error": "Method %s is not supported" % request.method} | ||
| 2703 | |||
| 2704 | @xhr_response | ||
| 2705 | def xhr_customrecipe_packages(request, recipe_id, package_id): | ||
| 2706 | """ | ||
| 2707 | ReST API to add/remove packages to/from custom recipe. | ||
| 2599 | 2708 | ||
| 2709 | Entry point: /xhr_customrecipe/<recipe_id>/packages/ | ||
| 2710 | |||
| 2711 | Methods: | ||
| 2712 | PUT - Add package to the recipe | ||
| 2713 | DELETE - Delete package from the recipe | ||
| 2714 | |||
| 2715 | Returns: | ||
| 2716 | {"error": "ok"} | ||
| 2717 | or | ||
| 2718 | {"error": <error message>} | ||
| 2719 | """ | ||
| 2720 | try: | ||
| 2721 | recipe = CustomImageRecipe.objects.get(id=recipe_id) | ||
| 2722 | except CustomImageRecipe.DoesNotExist: | ||
| 2723 | return {"error": "Custom recipe with id=%s " | ||
| 2724 | "not found" % recipe_id} | ||
| 2725 | |||
| 2726 | if request.method == 'GET' and not package_id: | ||
| 2727 | return {"error": "ok", | ||
| 2728 | "packages": list(recipe.packages.values_list('id'))} | ||
| 2729 | |||
| 2730 | try: | ||
| 2731 | package = Package.objects.get(id=package_id) | ||
| 2732 | except Package.DoesNotExist: | ||
| 2733 | return {"error": "Package with id=%s " | ||
| 2734 | "not found" % package_id} | ||
| 2735 | |||
| 2736 | if request.method == 'PUT': | ||
| 2737 | recipe.packages.add(package) | ||
| 2738 | return {"error": "ok"} | ||
| 2739 | elif request.method == 'DELETE': | ||
| 2740 | if package in recipe.packages.all(): | ||
| 2741 | recipe.packages.remove(package) | ||
| 2742 | return {"error": "ok"} | ||
| 2743 | else: | ||
| 2744 | return {"error": "Package '%s' is not in the recipe '%s'" % \ | ||
| 2745 | (package.name, recipe.name)} | ||
| 2746 | else: | ||
| 2747 | return {"error": "Method %s is not supported" % request.method} | ||
| 2600 | 2748 | ||
| 2601 | def importlayer(request, pid): | 2749 | def importlayer(request, pid): |
| 2602 | template = "importlayer.html" | 2750 | template = "importlayer.html" |
