summaryrefslogtreecommitdiffstats
path: root/bitbake/lib
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib')
-rw-r--r--bitbake/lib/toaster/orm/models.py9
-rw-r--r--bitbake/lib/toaster/toastergui/static/css/default.css3
-rw-r--r--bitbake/lib/toaster/toastergui/templates/basetable_top.html1
-rw-r--r--bitbake/lib/toaster/toastergui/templates/basetable_top_layers.html5
-rw-r--r--bitbake/lib/toaster/toastergui/templates/layers.html282
-rwxr-xr-xbitbake/lib/toaster/toastergui/views.py28
6 files changed, 209 insertions, 119 deletions
diff --git a/bitbake/lib/toaster/orm/models.py b/bitbake/lib/toaster/orm/models.py
index 1521717482..3c7f6611dc 100644
--- a/bitbake/lib/toaster/orm/models.py
+++ b/bitbake/lib/toaster/orm/models.py
@@ -737,6 +737,15 @@ class Layer_Version(models.Model):
737 dirpath = models.CharField(max_length=255, null = True, default = None) # LayerBranch.vcs_subdir 737 dirpath = models.CharField(max_length=255, null = True, default = None) # LayerBranch.vcs_subdir
738 priority = models.IntegerField(default = 0) # if -1, this is a default layer 738 priority = models.IntegerField(default = 0) # if -1, this is a default layer
739 739
740 def get_vcs_link_url(self, file_path="/"):
741 if self.layer.vcs_web_file_base_url is None:
742 return None
743 return self.layer.vcs_web_file_base_url.replace('%path%', file_path).replace('%branch%', self.up_branch.name)
744
745 def get_vcs_link_url_dirpath(self):
746 return self.get_vcs_link_url(self.dirpath)
747
748
740 def __unicode__(self): 749 def __unicode__(self):
741 return "LV " + str(self.layer) + " " + self.commit 750 return "LV " + str(self.layer) + " " + self.commit
742 751
diff --git a/bitbake/lib/toaster/toastergui/static/css/default.css b/bitbake/lib/toaster/toastergui/static/css/default.css
index 8780c4f23d..9e62c6c8e7 100644
--- a/bitbake/lib/toaster/toastergui/static/css/default.css
+++ b/bitbake/lib/toaster/toastergui/static/css/default.css
@@ -60,7 +60,8 @@ dd p { line-height: 20px; }
60.tooltip { z-index: 2000 !important; } 60.tooltip { z-index: 2000 !important; }
61 61
62/* Override default Twitter Boostrap styles for anchor tags inside tables */ 62/* Override default Twitter Boostrap styles for anchor tags inside tables */
63td a { color: #333333; } 63td a, td a > code { color: #333333; }
64td a > code { white-space: normal; }
64td a:hover { color: #000000; text-decoration: underline; } 65td a:hover { color: #000000; text-decoration: underline; }
65 66
66/* Override default Twitter Bootstrap styles for tr.error */ 67/* Override default Twitter Bootstrap styles for tr.error */
diff --git a/bitbake/lib/toaster/toastergui/templates/basetable_top.html b/bitbake/lib/toaster/toastergui/templates/basetable_top.html
index bffa731e13..e3f6a4ee23 100644
--- a/bitbake/lib/toaster/toastergui/templates/basetable_top.html
+++ b/bitbake/lib/toaster/toastergui/templates/basetable_top.html
@@ -176,6 +176,7 @@
176 </form> 176 </form>
177 <div class="pull-right"> 177 <div class="pull-right">
178{% if tablecols %} 178{% if tablecols %}
179 {% block custombuttons%} {% endblock %}
179 <div class="btn-group"> 180 <div class="btn-group">
180 <button class="btn dropdown-toggle" data-toggle="dropdown">Edit columns 181 <button class="btn dropdown-toggle" data-toggle="dropdown">Edit columns
181 <span class="caret"></span> 182 <span class="caret"></span>
diff --git a/bitbake/lib/toaster/toastergui/templates/basetable_top_layers.html b/bitbake/lib/toaster/toastergui/templates/basetable_top_layers.html
new file mode 100644
index 0000000000..bd6e7dd2de
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/templates/basetable_top_layers.html
@@ -0,0 +1,5 @@
1{% extends "basetable_top.html" %}
2
3{%block custombuttons %}
4 <a class="btn" href="{% url 'importlayer' %}" style="margin-right:5px;">Import layer</a>
5{%endblock%}
diff --git a/bitbake/lib/toaster/toastergui/templates/layers.html b/bitbake/lib/toaster/toastergui/templates/layers.html
index b32a7ed2e2..52eed86657 100644
--- a/bitbake/lib/toaster/toastergui/templates/layers.html
+++ b/bitbake/lib/toaster/toastergui/templates/layers.html
@@ -9,43 +9,63 @@
9{% block projectinfomain %} 9{% block projectinfomain %}
10 <div class="page-header"> 10 <div class="page-header">
11 <h1> 11 <h1>
12 All layers 12 {% if request.GET.search and objects.paginator.count > 0 %}
13 <i class="icon-question-sign get-help heading-help" title="This page lists all the layers compatible with Yocto Project 1.7 'Dxxxx' that Toaster knows about. They include community-created layers suitable for use on top of OpenEmbedded Core and any layers you have imported"></i> 13 {{objects.paginator.count}} layer{{objects.paginator.count|pluralize}} found
14 {%elif request.GET.search and objects.paginator.count == 0%}
15 No layer found
16 {%else%}
17 All layers
18 {%endif%}
19 <i class="icon-question-sign get-help heading-help" title="This page lists all the layers compatible with " + {{project.release.name}} + " that Toaster knows about."></i>
14 </h1> 20 </h1>
15 </div> 21 </div>
16 <!--div class="alert"> 22
17 <div class="input-append" style="margin-bottom:0px;"> 23 <div id="zone1alerts">
18 <input class="input-xxlarge" type="text" placeholder="Search layers" value="browser" /> 24
19 <a class="add-on btn"> 25 </div>
20 <i class="icon-remove"></i> 26
21 </a> 27
22 <button class="btn" type="button">Search</button>
23 <a class="btn btn-link" href="#">Show all layers</a>
24 </div>
25 </div-->
26 <div id="layer-added" class="alert alert-info lead" style="display:none;"></div> 28 <div id="layer-added" class="alert alert-info lead" style="display:none;"></div>
27 <div id="layer-removed" class="alert alert-info lead" style="display:none;">
28 <button type="button" class="close" data-dismiss="alert">&times;</button>
29 <strong>1</strong> layer deleted from <a href="project-with-targets.html">your project</a>: <a href="#">meta-aarch64</a>
30 </div>
31 29
32 30
33{% include "basetable_top.html" %} 31{% include "basetable_top_layers.html" %}
34 {% for lv in objects %} 32 {% for lv in objects %}
35 <tr class="data"> 33 <tr class="data">
36 <td class="layer"><a href="{% url 'layerdetails' lv.id %}">{{lv.layer.name}}</a></td> 34 <td class="layer"><a href="{% url 'layerdetails' lv.id %}">{{lv.layer.name}}</a></td>
37 <td class="description">{{lv.layer.summary}}</td> 35 <td class="description">{{lv.layer.summary}}</td>
38 <td class="source"><a href="{% url 'layerdetails' lv.pk %}">{{lv.layer_source.name}}</a></td> 36 <td class="source"><a href="{% url 'layerdetails' lv.pk %}">{{lv.layer_source.name}}</a></td>
39 <td class="git-repo"><a href="{% url 'layerdetails' lv.pk %}"><code>{{lv.layer.layer_index_url}}</code></a></td> 37 <td class="git-repo"><a href="{% url 'layerdetails' lv.pk %}"><code>{{lv.layer.vcs_url}}</code></a>
40 <td class="git-subdir" style="display: table-cell;"><a href="{% url 'layerdetails' lv.pk %}"><code>{{lv.dirpath}}</code></a></td> 38 {% if lv.get_vcs_link_url %}
39 <a target="_blank" href="{{ lv.get_vcs_link_url }}"><i class="icon-share get-info"></i></a>
40 {% endif %}
41 </td>
42 <td class="git-subdir" style="display: table-cell;"><a href="{% url 'layerdetails' lv.pk %}"><code>{{lv.dirpath}}</code></a>
43 {% if lv.get_vcs_link_url %}
44 <a target="_blank" href="{{ lv.get_vcs_link_url_dirpath }}"><i class="icon-share get-info"></i></a>
45 {% endif %}
46 </td>
41 <td class="branch">{% if lv.branch %}{{lv.branch}}{% else %}{{lv.up_branch.name}}{% endif %}</td> 47 <td class="branch">{% if lv.branch %}{{lv.branch}}{% else %}{{lv.up_branch.name}}{% endif %}</td>
42 <td class="dependencies">{% for lvs in lv.dependencies.all %}{{lvs.layer.name}}<br/>{%endfor%}</td> 48 <td class="dependencies">
43 <td class="add-layers"> 49 {% with lvds=lv.dependencies.all%}
44 <button id="remove-layer-{{lv.pk}}" class="btn btn-danger btn-block remove-layer" title="1 layer deleted" style="display:none;"> 50 {% if lvds.count %}
51 <a class="btn"
52 title="<a href='{% url "layerdetails" lv.pk %}'>{{lv.layer.name}}</a> dependencies"
53 data-content="<ul class='unstyled'>
54 {% for i in lvds%}
55 <li><a href='{% url "layerdetails" i.depends_on.pk %}'>{{i.depends_on.layer.name}}</a></li>
56 {% endfor %}
57 </ul>">
58 {{lvds.count}}
59 </a>
60 {% endif %}
61 {% endwith %}
62 </td>
63 <td class="add-del-layers" value="{{lv.pk}}">
64 <button id="layer-del-{{lv.pk}}" class="btn btn-danger btn-block remove-layer" style="display:none;" onclick="layerDel({{lv.pk}}, '{{lv.layer.name}}', '{%url 'layerdetails' lv.pk%}')">
45 <i class="icon-trash"></i> 65 <i class="icon-trash"></i>
46 Delete layer 66 Delete layer
47 </button> 67 </button>
48 <button id="add-layer-{{lv.pk}}" class="btn btn-block add-layer" title="1 layer added"> 68 <button id="layer-add-{{lv.pk}}" class="btn btn-block" style="display:none;" onclick="layerAdd({{lv.pk}}, '{{lv.layer.name}}', '{%url 'layerdetails' lv.pk%}')" >
49 <i class="icon-plus"></i> 69 <i class="icon-plus"></i>
50 Add layer 70 Add layer
51 </button> 71 </button>
@@ -57,113 +77,145 @@
57 <!-- Modals --> 77 <!-- Modals -->
58 78
59 <!-- 'Layer dependencies modal' --> 79 <!-- 'Layer dependencies modal' -->
60 <div id="dependencies-message" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true"> 80 <div id="dependencies_modal" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true">
81 <form id="dependencies_modal_form">
61 <div class="modal-header"> 82 <div class="modal-header">
62 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> 83 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
63 <h3>meta-acer dependencies</h3> 84 <h3><span class="layer-name"></span> dependencies</h3>
64 </div> 85 </div>
65 <div class="modal-body"> 86 <div class="modal-body">
66 <p><strong>meta-acer</strong> depends on some layers that are not added to your project. Select the ones you want to add:</p> 87 <p><strong class="layer-name"></strong> depends on some layers that are not added to your project. Select the ones you want to add:</p>
67 <ul class="unstyled"> 88 <ul class="unstyled" id="dependencies_list">
68 <li>
69 <label class="checkbox">
70 <input type="checkbox" checked="checked">
71 meta-android
72 </label>
73 </li>
74 <li>
75 <label class="checkbox">
76 <input type="checkbox" checked="checked">
77 meta-oe
78 </label>
79 </li>
80 </ul> 89 </ul>
81 </div> 90 </div>
82 <div class="modal-footer"> 91 <div class="modal-footer">
83 <button id="add-layer-dependencies" type="submit" class="btn btn-primary" data-dismiss="modal" >Add layers</button> 92 <button class="btn btn-primary" type="submit">Add layers</button>
84 <button class="btn" data-dismiss="modal">Cancel</button> 93 <button class="btn" type="reset" data-dismiss="modal">Cancel</button>
85 </div> 94 </div>
95 </form>
86 </div> 96 </div>
87 97
88 <script> 98<script>
89 $(document).ready(function() { 99
90 100function _makeXHREditCall(data, onsuccess, onfail) {
91 //show or hide selected columns on load 101 $.ajax( {
92 $("input:checkbox").each(function(){ 102 type: "POST",
93 var selectedType = $(this).val(); 103 url: "{% url 'xhr_projectedit' project.id %}",
94 if($(this).is(":checked")){ 104 data: data,
95 $("."+selectedType).show(); 105 headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
106 success: function (_data) {
107 if (_data.error != "ok") {
108 alert(_data.error);
109 } else {
110 updateButtons(_data.layers.map(function (e) {return e.id}));
111 if (onsuccess != undefined) onsuccess(_data);
96 } 112 }
97 else{ 113 },
98 $("."+selectedType).hide(); 114 error: function (_data) {
115 alert("Call failed");
116 console.log(_data);
117 }
118 });
119}
120
121
122function layerDel(layerId, layerName, layerURL) {
123 _makeXHREditCall({ 'layerDel': layerId }, function () {
124 show_alert("<strong>1</strong> layer deleted from <a href=\"{% url 'project' project.id%}\">{{project.name}}</a>: <a href=\""+layerURL+"\">" + layerName +"</a>");
125 });
126}
127
128function show_alert(text, cls) {
129 $("#zone1alerts").html("<div class=\"alert alert-info\"><button type=\"button\" class=\"close\" data-dismiss=\"alert\">&times;</button>" + text + "</div>");
130}
131
132function show_dependencies_modal(layerId, layerName, layerURL, dependencies) {
133 // update layer name
134 $('.layer-name').text(layerName);
135 var deplistHtml = "";
136 for (var i = 0; i < dependencies.length; i++) {
137 deplistHtml += "<li><label class=\"checkbox\"><input name=\"dependencies\" value=\""
138 deplistHtml += dependencies[i].id;
139 deplistHtml +="\" type=\"checkbox\" checked=\"checked\"/>";
140 deplistHtml += dependencies[i].name;
141 deplistHtml += "</label></li>";
142 }
143 $('#dependencies_list').html(deplistHtml);
144
145 $("#dependencies_modal_form").submit(function (e) {
146 e.preventDefault();
147 var selected = [layerId];
148 $("input[name='dependencies']:checked").map(function () { selected.push(parseInt($(this).val()))});
149
150 _makeXHREditCall({ 'layerAdd': selected.join(",") }, function () {
151 var layer_link_list = "<a href='"+layerURL+"'>"+layerName+"</a>";
152 for (var i = 0; i < selected.length; i++) {
153 for (var j = 0; j < dependencies.length; i++) {
154 if (dependencies[j].id == selected[i]) {
155 layer_link_list+= ", <a href='"+dependencies[j].layerdetailurl+"'>"+dependencies[j].name+"</a>"
156 break;
157 }
158 }
99 } 159 }
100 });
101 160
102 // enable add layer button 161 $('#dependencies_modal').modal('hide');
103 $('#add-layer-with-deps').removeAttr('disabled'); 162 show_alert("<strong>"+selected.length+"</strong> layers added to <a href=\"{% url 'project' project.id%}\">{{project.name}}</a>:" + layer_link_list);
104 163 });
105 //edit columns functionality (show / hide table columns) 164 });
106 $("input:checkbox").change(); 165 $('#dependencies_modal').modal('show');
107 $("input:checkbox").change(function(){ 166}
108 var selectedType = $(this).val(); 167
109 if($(this).is(":checked")){ 168
110 $("."+selectedType).show(); 169function layerAdd(layerId, layerName, layerURL) {
170 $.ajax({
171 url: '{% url "xhr_datatypeahead" %}',
172 data: {'type': 'layerdeps','value':layerId},
173 success: function(_data) {
174 if (_data.error != "ok") {
175 alert(_data.error);
176 } else {
177 if (_data.list.length > 0) {
178 show_dependencies_modal(layerId, layerName, layerURL, _data.list);
111 } 179 }
112 else{ 180 else {
113 $("."+selectedType).hide(); 181 _makeXHREditCall({ 'layerAdd': layerId }, function () {
182 show_alert("<strong>1</strong> layer added to <a href=\"{% url 'project' project.id%}\">{{project.name}}</a>: <a href=\""+layerURL+"\">" + layerName +"</a>");
183 });
114 } 184 }
115 }); 185 }
116 186 }
117 //turn edit columns dropdown into a multi-select menu 187 })
118 $('.dropdown-menu input, .dropdown-menu label').click(function(e) { 188}
119 e.stopPropagation(); 189
120 }); 190function button_set(id, state) {
121 191 if (state == "add")
122 //show tooltip with applied filter 192 {
123 $('#filtered').tooltip({container:'table', placement:'bottom', delay:{hide:1500}, html:true}); 193 $("#layer-add-" + id).show();
124 194 $("#layer-del-" + id).hide();
125 $('#filtered').click(function() { 195 }
126 $(this).tooltip('hide'); 196 else if (state == "del")
127 }); 197 {
128 198 $("#layer-add-" + id).hide();
129 //show layer added tooltip 199 $("#layer-del-" + id).show();
130 $("#remove-layer, #add-layer, #add-layer-with-deps2").tooltip({ trigger: 'manual' }); 200 }
131 201};
132 // add layer without dependencies 202
133 $("#add-layer").click(function(){ 203function updateButtons(projectLayers) {
134 $('#layer-removed').hide(); 204 var displayedLayers = [];
135 $('#layer-added').html('<button type="button" class="close" data-dismiss="alert">&times;</button><strong>1</strong> layer added to <a href="project-with-targets.html">your project</a>: <a href="#">meta-aarch64</a>').fadeIn(); 205 $(".add-del-layers").map(function () { displayedLayers.push(parseInt($(this).attr('value')))});
136 $('#add-layer').tooltip('show'); 206 for (var i=0; i < displayedLayers.length; i++) {
137 $("#add-layer").hide(); 207 if (projectLayers.indexOf(displayedLayers[i]) > -1) {
138 $(".add-layers .tooltip").delay(2000).fadeOut(function(){ 208 button_set(displayedLayers[i], "del");
139 $("#remove-layer").delay(300).fadeIn(); 209 }
140 }); 210 else {
141 }); 211 button_set(displayedLayers[i], "add");
142 212 }
143 // add layer with dependencies 213 }
144 $(document).on("click", "#add-layer-dependencies", function() { 214}
145 $('#layer-removed').hide(); 215
146 $('#layer-added').html('<button type="button" class="close" data-dismiss="alert">&times;</button><strong>3</strong> layers added to <a href="project-with-targets.html">your project</a>: <a href="#">meta-acer</a> and its dependencies <a href="#">meta-android</a> and <a href="#">meta-oe</a>').delay(400).fadeIn(function(){ 216$(document).ready(function (){
147 $('#add-layer-with-deps').tooltip('show'); 217 updateButtons({{projectlayerset}});
148 $("#add-layer-with-deps, #add-layer-with-deps").hide(); 218});
149 $(".add-layers .tooltip").delay(2000).fadeOut(function(){
150 $("#remove-layer-with-deps").delay(300).fadeIn();
151 });
152 });
153 });
154
155 // delete layer
156 $("#remove-layer").click(function(){
157 $('#layer-added').hide();
158 $('#layer-removed').show();
159 $('#remove-layer').tooltip('show');
160 $("#remove-layer").hide();
161 $(".add-layers .tooltip").delay(2000).fadeOut(function(){
162 $("#add-layer").delay(300).fadeIn();
163 });
164 });
165
166 });
167 219
168</script> 220</script>
169 221
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index 5fe4a9d869..53f46ff532 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -2062,7 +2062,8 @@ if toastermain.settings.MANAGED:
2062 2062
2063 return HttpResponse(json.dumps( { "error":"ok", 2063 return HttpResponse(json.dumps( { "error":"ok",
2064 "list" : map( 2064 "list" : map(
2065 lambda x: {"id": x.pk, "name": x.layer.name, "detail": "(" + x.layer.layer_source.name + (")" if x.up_branch == None else " | "+x.up_branch.name+")")}, 2065 lambda x: {"id": x.pk, "name": x.layer.name, "detail": "(" + x.layer.layer_source.name + (")" if x.up_branch == None else " | "+x.up_branch.name+")"),
2066 "layerdetailurl" : reverse('layerdetails', args=(x.pk,))},
2066 map(lambda x: x.depends_on, queryset_all)) 2067 map(lambda x: x.depends_on, queryset_all))
2067 }), content_type = "application/json") 2068 }), content_type = "application/json")
2068 2069
@@ -2131,8 +2132,18 @@ if toastermain.settings.MANAGED:
2131 (filter_string, search_term, ordering_string) = _search_tuple(request, Layer_Version) 2132 (filter_string, search_term, ordering_string) = _search_tuple(request, Layer_Version)
2132 2133
2133 queryset_all = Layer_Version.objects.all() 2134 queryset_all = Layer_Version.objects.all()
2135 # mock an empty Project if we are outside project context
2136 class _mockProject(object):
2137 id = -1
2138 class _mockManager(object):
2139 def all(self):
2140 return []
2141 projectlayer_set = _mockManager()
2142 prj = _mockProject()
2143
2134 if 'project_id' in request.session: 2144 if 'project_id' in request.session:
2135 queryset_all = queryset_all.filter(up_branch__in = Branch.objects.filter(name = Project.objects.get(pk = request.session['project_id']).release.name)) 2145 prj = Project.objects.get(pk = request.session['project_id'])
2146 queryset_all = queryset_all.filter(up_branch__in = Branch.objects.filter(name = prj.release.name))
2136 2147
2137 queryset_with_search = _get_queryset(Layer_Version, queryset_all, None, search_term, ordering_string, '-layer__name') 2148 queryset_with_search = _get_queryset(Layer_Version, queryset_all, None, search_term, ordering_string, '-layer__name')
2138 queryset = _get_queryset(Layer_Version, queryset_all, filter_string, search_term, ordering_string, '-layer__name') 2149 queryset = _get_queryset(Layer_Version, queryset_all, filter_string, search_term, ordering_string, '-layer__name')
@@ -2142,6 +2153,8 @@ if toastermain.settings.MANAGED:
2142 2153
2143 2154
2144 context = { 2155 context = {
2156 'prj' : prj,
2157 'projectlayerset' : json.dumps(map(lambda x: x.layercommit.id, prj.projectlayer_set.all())),
2145 'objects' : layer_info, 2158 'objects' : layer_info,
2146 'objectname' : "layers", 2159 'objectname' : "layers",
2147 'default_orderby' : 'layer__name:+', 2160 'default_orderby' : 'layer__name:+',
@@ -2164,7 +2177,7 @@ if toastermain.settings.MANAGED:
2164 'filter': { 2177 'filter': {
2165 'class': 'layer', 2178 'class': 'layer',
2166 'label': 'Show:', 2179 'label': 'Show:',
2167 'options': map(lambda x: (x.name, 'layer_source__pk:' + str(x.id), queryset_with_search.filter(layer_source__pk = x.id).count() ), LayerSource.objects.all()), 2180 'options': map(lambda x: (x.name + " layers", 'layer_source__pk:' + str(x.id), queryset_with_search.filter(layer_source__pk = x.id).count() ), LayerSource.objects.all()),
2168 } 2181 }
2169 }, 2182 },
2170 { 'name': 'Git repository URL', 2183 { 'name': 'Git repository URL',
@@ -2188,6 +2201,15 @@ if toastermain.settings.MANAGED:
2188 { 'name': 'Add | Delete', 2201 { 'name': 'Add | Delete',
2189 'dclass': 'span2', 2202 'dclass': 'span2',
2190 'qhelp': "Add or delete layers to / from your project ", 2203 'qhelp': "Add or delete layers to / from your project ",
2204 'filter': {
2205 'class': 'add-del-layers',
2206 'label': 'Show:',
2207 'options': [
2208 ('Layers added to this project', "projectlayer__project:" + str(prj.id), queryset_with_search.filter(projectlayer__project = prj.id).count()),
2209 ('Layers not added to this project', "projectlayer__project:NOT" + str(prj.id), queryset_with_search.exclude(projectlayer__project = prj.id).count()),
2210 ]
2211
2212 }
2191 }, 2213 },
2192 2214
2193 ] 2215 ]