summaryrefslogtreecommitdiffstats
path: root/bitbake/lib
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib')
-rw-r--r--bitbake/lib/toaster/orm/migrations/0012_auto__add_field_projectlayer_optional__add_field_projecttarget_task.py252
-rw-r--r--bitbake/lib/toaster/orm/models.py8
-rw-r--r--bitbake/lib/toaster/toastergui/static/css/default.css42
-rw-r--r--bitbake/lib/toaster/toastergui/templates/base.html4
-rw-r--r--bitbake/lib/toaster/toastergui/templates/project.html356
-rw-r--r--bitbake/lib/toaster/toastergui/urls.py3
-rwxr-xr-xbitbake/lib/toaster/toastergui/views.py105
7 files changed, 748 insertions, 22 deletions
diff --git a/bitbake/lib/toaster/orm/migrations/0012_auto__add_field_projectlayer_optional__add_field_projecttarget_task.py b/bitbake/lib/toaster/orm/migrations/0012_auto__add_field_projectlayer_optional__add_field_projecttarget_task.py
new file mode 100644
index 0000000000..9e483f5dac
--- /dev/null
+++ b/bitbake/lib/toaster/orm/migrations/0012_auto__add_field_projectlayer_optional__add_field_projecttarget_task.py
@@ -0,0 +1,252 @@
1# -*- coding: utf-8 -*-
2from south.utils import datetime_utils as datetime
3from south.db import db
4from south.v2 import SchemaMigration
5from django.db import models
6
7
8class Migration(SchemaMigration):
9
10 def forwards(self, orm):
11 # Adding field 'ProjectLayer.optional'
12 db.add_column(u'orm_projectlayer', 'optional',
13 self.gf('django.db.models.fields.BooleanField')(default=True),
14 keep_default=False)
15
16 # Adding field 'ProjectTarget.task'
17 db.add_column(u'orm_projecttarget', 'task',
18 self.gf('django.db.models.fields.CharField')(max_length=100, null=True),
19 keep_default=False)
20
21
22 def backwards(self, orm):
23 # Deleting field 'ProjectLayer.optional'
24 db.delete_column(u'orm_projectlayer', 'optional')
25
26 # Deleting field 'ProjectTarget.task'
27 db.delete_column(u'orm_projecttarget', 'task')
28
29
30 models = {
31 u'orm.build': {
32 'Meta': {'object_name': 'Build'},
33 'bitbake_version': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
34 'build_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
35 'completed_on': ('django.db.models.fields.DateTimeField', [], {}),
36 'cooker_log_path': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
37 'distro': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
38 'distro_version': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
39 'errors_no': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
40 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
41 'machine': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
42 'outcome': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
43 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']", 'null': 'True'}),
44 'started_on': ('django.db.models.fields.DateTimeField', [], {}),
45 'timespent': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
46 'warnings_no': ('django.db.models.fields.IntegerField', [], {'default': '0'})
47 },
48 u'orm.helptext': {
49 'Meta': {'object_name': 'HelpText'},
50 'area': ('django.db.models.fields.IntegerField', [], {}),
51 'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'helptext_build'", 'to': u"orm['orm.Build']"}),
52 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
53 'key': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
54 'text': ('django.db.models.fields.TextField', [], {})
55 },
56 u'orm.layer': {
57 'Meta': {'object_name': 'Layer'},
58 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
59 'layer_index_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
60 'local_path': ('django.db.models.fields.FilePathField', [], {'max_length': '255'}),
61 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
62 },
63 u'orm.layer_version': {
64 'Meta': {'object_name': 'Layer_Version'},
65 'branch': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
66 'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'layer_version_build'", 'to': u"orm['orm.Build']"}),
67 'commit': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
68 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
69 'layer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'layer_version_layer'", 'to': u"orm['orm.Layer']"}),
70 'priority': ('django.db.models.fields.IntegerField', [], {})
71 },
72 u'orm.logmessage': {
73 'Meta': {'object_name': 'LogMessage'},
74 'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
75 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
76 'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
77 'lineno': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
78 'message': ('django.db.models.fields.CharField', [], {'max_length': '240'}),
79 'pathname': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
80 'task': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Task']", 'null': 'True', 'blank': 'True'})
81 },
82 u'orm.package': {
83 'Meta': {'object_name': 'Package'},
84 'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
85 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
86 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
87 'installed_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}),
88 'installed_size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
89 'license': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
90 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
91 'recipe': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Recipe']", 'null': 'True'}),
92 'revision': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
93 'section': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
94 'size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
95 'summary': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
96 'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
97 },
98 u'orm.package_dependency': {
99 'Meta': {'object_name': 'Package_Dependency'},
100 'dep_type': ('django.db.models.fields.IntegerField', [], {}),
101 'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_dependencies_target'", 'to': u"orm['orm.Package']"}),
102 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
103 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_dependencies_source'", 'to': u"orm['orm.Package']"}),
104 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']", 'null': 'True'})
105 },
106 u'orm.package_file': {
107 'Meta': {'object_name': 'Package_File'},
108 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
109 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'buildfilelist_package'", 'to': u"orm['orm.Package']"}),
110 'path': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
111 'size': ('django.db.models.fields.IntegerField', [], {})
112 },
113 u'orm.project': {
114 'Meta': {'object_name': 'Project'},
115 'branch': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
116 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
117 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
118 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
119 'short_description': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
120 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
121 'user_id': ('django.db.models.fields.IntegerField', [], {'null': 'True'})
122 },
123 u'orm.projectlayer': {
124 'Meta': {'object_name': 'ProjectLayer'},
125 'commit': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
126 'dirpath': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
127 'giturl': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
128 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
129 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
130 'optional': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
131 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"})
132 },
133 u'orm.projecttarget': {
134 'Meta': {'object_name': 'ProjectTarget'},
135 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
136 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
137 'target': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
138 'task': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'})
139 },
140 u'orm.projectvariable': {
141 'Meta': {'object_name': 'ProjectVariable'},
142 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
143 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
144 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
145 'value': ('django.db.models.fields.TextField', [], {'blank': 'True'})
146 },
147 u'orm.recipe': {
148 'Meta': {'object_name': 'Recipe'},
149 'bugtracker': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
150 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
151 'file_path': ('django.db.models.fields.FilePathField', [], {'max_length': '255'}),
152 'homepage': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
153 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
154 'layer_version': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'recipe_layer_version'", 'to': u"orm['orm.Layer_Version']"}),
155 'license': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
156 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
157 'section': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
158 'summary': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
159 'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
160 },
161 u'orm.recipe_dependency': {
162 'Meta': {'object_name': 'Recipe_Dependency'},
163 'dep_type': ('django.db.models.fields.IntegerField', [], {}),
164 'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'r_dependencies_depends'", 'to': u"orm['orm.Recipe']"}),
165 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
166 'recipe': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'r_dependencies_recipe'", 'to': u"orm['orm.Recipe']"})
167 },
168 u'orm.target': {
169 'Meta': {'object_name': 'Target'},
170 'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
171 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
172 'image_size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
173 'is_image': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
174 'license_manifest_path': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
175 'target': ('django.db.models.fields.CharField', [], {'max_length': '100'})
176 },
177 u'orm.target_file': {
178 'Meta': {'object_name': 'Target_File'},
179 'directory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'directory_set'", 'null': 'True', 'to': u"orm['orm.Target_File']"}),
180 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
181 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
182 'inodetype': ('django.db.models.fields.IntegerField', [], {}),
183 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
184 'path': ('django.db.models.fields.FilePathField', [], {'max_length': '100'}),
185 'permission': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
186 'size': ('django.db.models.fields.IntegerField', [], {}),
187 'sym_target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'symlink_set'", 'null': 'True', 'to': u"orm['orm.Target_File']"}),
188 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
189 },
190 u'orm.target_image_file': {
191 'Meta': {'object_name': 'Target_Image_File'},
192 'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '254'}),
193 'file_size': ('django.db.models.fields.IntegerField', [], {}),
194 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
195 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
196 },
197 u'orm.target_installed_package': {
198 'Meta': {'object_name': 'Target_Installed_Package'},
199 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
200 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'buildtargetlist_package'", 'to': u"orm['orm.Package']"}),
201 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
202 },
203 u'orm.task': {
204 'Meta': {'ordering': "('order', 'recipe')", 'unique_together': "(('build', 'recipe', 'task_name'),)", 'object_name': 'Task'},
205 'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_build'", 'to': u"orm['orm.Build']"}),
206 'cpu_usage': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '2'}),
207 'disk_io': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
208 'elapsed_time': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '2'}),
209 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
210 'line_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
211 'logfile': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
212 'message': ('django.db.models.fields.CharField', [], {'max_length': '240'}),
213 'order': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
214 'outcome': ('django.db.models.fields.IntegerField', [], {'default': '-1'}),
215 'path_to_sstate_obj': ('django.db.models.fields.FilePathField', [], {'max_length': '500', 'blank': 'True'}),
216 'recipe': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'build_recipe'", 'to': u"orm['orm.Recipe']"}),
217 'script_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
218 'source_url': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
219 'sstate_checksum': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
220 'sstate_result': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
221 'task_executed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
222 'task_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
223 'work_directory': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'})
224 },
225 u'orm.task_dependency': {
226 'Meta': {'object_name': 'Task_Dependency'},
227 'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_dependencies_depends'", 'to': u"orm['orm.Task']"}),
228 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
229 'task': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_dependencies_task'", 'to': u"orm['orm.Task']"})
230 },
231 u'orm.variable': {
232 'Meta': {'object_name': 'Variable'},
233 'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'variable_build'", 'to': u"orm['orm.Build']"}),
234 'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
235 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
236 'human_readable_name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
237 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
238 'variable_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
239 'variable_value': ('django.db.models.fields.TextField', [], {'blank': 'True'})
240 },
241 u'orm.variablehistory': {
242 'Meta': {'object_name': 'VariableHistory'},
243 'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '255'}),
244 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
245 'line_number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
246 'operation': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
247 'value': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
248 'variable': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'vhistory'", 'to': u"orm['orm.Variable']"})
249 }
250 }
251
252 complete_apps = ['orm'] \ No newline at end of file
diff --git a/bitbake/lib/toaster/orm/models.py b/bitbake/lib/toaster/orm/models.py
index 9b7387a8af..f19a4370c8 100644
--- a/bitbake/lib/toaster/orm/models.py
+++ b/bitbake/lib/toaster/orm/models.py
@@ -37,13 +37,15 @@ class ProjectManager(models.Manager):
37 name = "meta", 37 name = "meta",
38 giturl = "git://git.yoctoproject.org/poky", 38 giturl = "git://git.yoctoproject.org/poky",
39 commit = branch, 39 commit = branch,
40 dirpath = "meta") 40 dirpath = "meta",
41 optional = False)
41 42
42 ProjectLayer.objects.create(project = prj, 43 ProjectLayer.objects.create(project = prj,
43 name = "meta-yocto", 44 name = "meta-yocto",
44 giturl = "git://git.yoctoproject.org/poky", 45 giturl = "git://git.yoctoproject.org/poky",
45 commit = branch, 46 commit = branch,
46 dirpath = "meta-yocto") 47 dirpath = "meta-yocto",
48 optional = False)
47 49
48 return prj 50 return prj
49 51
@@ -116,6 +118,7 @@ class Build(models.Model):
116class ProjectTarget(models.Model): 118class ProjectTarget(models.Model):
117 project = models.ForeignKey(Project) 119 project = models.ForeignKey(Project)
118 target = models.CharField(max_length=100) 120 target = models.CharField(max_length=100)
121 task = models.CharField(max_length=100, null=True)
119 122
120@python_2_unicode_compatible 123@python_2_unicode_compatible
121class Target(models.Model): 124class Target(models.Model):
@@ -392,6 +395,7 @@ class ProjectLayer(models.Model):
392 giturl = models.CharField(max_length = 254) 395 giturl = models.CharField(max_length = 254)
393 commit = models.CharField(max_length = 254) 396 commit = models.CharField(max_length = 254)
394 dirpath = models.CharField(max_length = 254) 397 dirpath = models.CharField(max_length = 254)
398 optional = models.BooleanField(default = True)
395 399
396class Layer(models.Model): 400class Layer(models.Model):
397 name = models.CharField(max_length=100) 401 name = models.CharField(max_length=100)
diff --git a/bitbake/lib/toaster/toastergui/static/css/default.css b/bitbake/lib/toaster/toastergui/static/css/default.css
index 2c283feccf..778d8b8456 100644
--- a/bitbake/lib/toaster/toastergui/static/css/default.css
+++ b/bitbake/lib/toaster/toastergui/static/css/default.css
@@ -116,17 +116,31 @@ select { width: auto; }
116/* make tables Chrome-happy (me, not so much) */ 116/* make tables Chrome-happy (me, not so much) */
117#otable { table-layout: fixed; word-wrap: break-word; } 117#otable { table-layout: fixed; word-wrap: break-word; }
118 118
119 119/* Configuration styles */
120 120.icon-trash { color: #B94A48; font-size: 16px; padding-left: 2px; }
121 121.icon-trash:hover { color: #943A38; text-decoration: none; cursor: pointer; }
122 122.icon-pencil, .icon-download-alt { font-size: 16px; color: #0088CC; padding-left: 2px; }
123 123.icon-pencil:hover, .icon-download-alt:hover { color: #005580; text-decoration: none; cursor: pointer; }
124 124.configuration-list li { line-height: 35px; font-size: 21px; font-weight: 200; }
125 125.configuration-list { font-size: 16px; margin-bottom: 1.5em; }
126 126.configuration-list i { font-size: 16px; }
127 127/*.configuration-layers { height: 135px; overflow: scroll; }*/
128 128.counter { font-weight: normal; }
129 129.well-alert { background-color: #FCF8E3; border: 1px solid #FBEED5; border-radius: 4px; }
130 130.well-alert > .lead { color: #C09853; padding-bottom: .75em; }
131 131.configuration-alert { margin-bottom: 0px; padding: 8px 14px; }
132 132.configuration-alert p { margin-bottom: 0px; }
133fieldset { padding-left: 19px; }
134.project-form { margin-top: 10px; }
135.add-layers .btn-block + .btn-block { margin-top: 0px; }
136input.huge { font-size: 17.5px; padding: 11px 19px; }
137.build-form { margin-bottom: 0px; padding-left: 20px; }
138a code { color: #0088CC; }
139a code:hover { color: #005580; }
140.localconf { font-size: 17.5px; margin-top: 40px; }
141.localconf code { font-size: 17.5px; }
142#add-layer-dependencies { margin-top: 5px; }
143.artifact { width: 9em; }
144.control-group { margin-bottom: 0px; }
145#project-details form { margin: 0px; }
146dd form { margin: 10px 0 0 0; }
diff --git a/bitbake/lib/toaster/toastergui/templates/base.html b/bitbake/lib/toaster/toastergui/templates/base.html
index 1407d641d5..9ef249aab3 100644
--- a/bitbake/lib/toaster/toastergui/templates/base.html
+++ b/bitbake/lib/toaster/toastergui/templates/base.html
@@ -65,6 +65,10 @@ function reload_params(params) {
65 <i class="icon-caret-down"></i> 65 <i class="icon-caret-down"></i>
66 </button> 66 </button>
67 <ul class="dropdown-menu"> 67 <ul class="dropdown-menu">
68{% for prj in projects %}
69 <li><a href="{% url 'project' prj.id %}">{{prj.name}}</a></li>
70{% endfor %}
71 <li><hr/></li>
68 <li><a href="#">Clone project</a></li> 72 <li><a href="#">Clone project</a></li>
69 <li><a href="#">Export project</a></li> 73 <li><a href="#">Export project</a></li>
70 <li><a href="#">Import project</a></li> 74 <li><a href="#">Import project</a></li>
diff --git a/bitbake/lib/toaster/toastergui/templates/project.html b/bitbake/lib/toaster/toastergui/templates/project.html
index 71adb54431..c859f6bcd1 100644
--- a/bitbake/lib/toaster/toastergui/templates/project.html
+++ b/bitbake/lib/toaster/toastergui/templates/project.html
@@ -3,4 +3,360 @@
3{% load humanize %} 3{% load humanize %}
4{% block pagecontent %} 4{% block pagecontent %}
5 5
6<script>
7
8var buildrequests = [];
9
10function targetInPage(targetname) {
11 return targetname in $("ul#target-list > li > a").map(function (i, x) {return x.text});
12}
13
14function setEventHandlers() {
15 $("i#del-target-icon").unbind().click(function (evt) {
16 console.log("del target", evt.target.attributes["x-data"].value);
17 postEditAjaxRequest({"targetDel": evt.target.attributes["x-data"].value});
18 });
19 $("button#add-target-button").unbind().click( function (evt) {
20 if ( $("input#target")[0].value.length == 0) {
21 alert("cannot add empty target");
22 return;
23 }
24 postEditAjaxRequest({"targetAdd" : $("input#target")[0].value});
25 });
26}
27
28function onEditPageUpdate(data) {
29 // update targets
30 var i; var orightml = "";
31
32 $("span#target-count").html(data.targets.length);
33 for (i = 0; i < data.targets.length; i++) {
34 if (! targetInPage(data.targets[i].target)) {
35 orightml += '<li><a href="#">'+data.targets[i].target;
36 if (data.targets[i].task != "" && data.targets[i].task !== null) {
37 orightml += " ("+data.targets[i].task+")";
38 }
39 orightml += '</a><i title="" data-original-title="" class="icon-trash" id="del-target-icon" x-data="'+data.targets[i].pk+'"></i></li>';
40 }
41 }
42
43 $("ul#target-list").html(orightml);
44
45 // update recent builds
46
47 setEventHandlers();
48}
49
50function onEditAjaxSuccess(data, textstatus) {
51 console.log("XHR returned:", data, "(" + textstatus + ")");
52 if (data.error != "ok") {
53 alert("error on request:\n" + data.error);
54 return;
55 }
56 onEditPageUpdate(data);
57}
58
59function onEditAjaxError(jqXHR, textstatus, error) {
60 alert("XHR errored:\n" + error + "\n(" + textstatus + ")");
61}
62
63function postEditAjaxRequest(reqdata) {
64 var ajax = $.ajax({
65 type:"POST",
66 data: $.param(reqdata),
67 url:"{% url 'xhr_projectedit' project.id%}",
68 headers: { 'X-CSRFToken': $.cookie("csrftoken")},
69 success: onEditAjaxSuccess,
70 error: onEditAjaxError,
71 })
72}
73
74$(document).ready(function () {
75 setEventHandlers();
76});
77
78</script>
79
80
81 <div class="page-header">
82 <h1>
83 {{project.name}}
84 {% if project.build_set.all.count == 0 %}
85 <small>No builds yet</small>
86 {% else %}
87 <small><a href="#">{{project.build_set.all.count}} builds</a></small>
88 {% endif %}
89 </h1>
90 </div>
91
92
93 <div class="well">
94 <!--div class="control-group error"-->
95 <button id="build-all-button" class="btn btn-primary btn-large">Build all added targets</button>
96 <div class="input-append build-form controls">
97 <input class="huge input-xxlarge" placeholder="Or enter the target you want to build" autocomplete="off" data-minlength="1" data-autocomplete="off" data-provide="typeahead" data-source="" type="text">
98 <button id="build-button" class="btn btn-large" disabled="">Build</button>
99 </div>
100 <script>
101/* Provide XHR calls for the "build" buttons.*/
102$("button#build-all-button").click( function (evt) {
103 var ajax = $.ajax({
104 type:"POST",
105 url:"{% url 'xhr_projectbuild' project.id %}",
106 headers: { 'X-CSRFToken': $.cookie("csrftoken")},
107 success: function (data, textstatus) {
108 if (data.error != "ok") {
109 alert("XHR fail: " + data.error );
110 }
111 },
112 error: function (jqXHR, textstatus, error) { alert("XHR errored:" + error + "(" + textstatus + ")"); },
113 })
114});
115
116 </script>
117 <!--span class="help-inline">This target is not provided <br />by any of your added layers
118 <i class="icon-question-sign get-help get-help-red" title="Review your list of added layers to make sure one of them provides core-image-xyz. Clicking on a layer name will give you all the information Toaster has about the layer"></i>
119 </span>
120 </div-->
121 </div>
122
123 <div id="meta-tizen-alert" class="alert alert-info lead air" style="display:none;">
124 <button type="button" class="close" data-dismiss="alert">?</button>
125 You have added <strong>6</strong> layers: <a href="#">meta-tizen</a> and its dependencies (<a href="#">meta-efl</a>, <a href="#">meta-intel</a>, <a href="#">meta-multimedia</a>, <a href="#">meta-oe</a> and <a href="#">meta-ruby</a>).
126 </div>
127
128
129
130
131
132 {% if builds|length > 0 or buildrequests|length > 0 %}
133 <h2 class="air">Recent Builds</h2>
134
135 <div id="scheduled-builds">
136 {% for br in buildrequests %}
137<div class="alert {% if br.0.state == br.0.REQ_FAILED%}alert-error{%else%}alert-info{%endif%}" id="build-request">
138 <div class="row-fluid">
139 <div class="lead span4">
140 <span>
141 {{br.0.brtarget_set.all.0.target}} {%if br.brtarget_set.all.count > 1%}(+ {{br.brtarget_set.all.count|add:"-1"}}){%endif%} {{br.1.machine.value}} (Created {{br.0.created}})
142 </span>
143 </div>
144 <div class="span2">
145 {{br.0.get_state_display}}
146 </div>
147 <div class="span8">
148{% if br.state == br.REQ_FAILED%}
149 {% for bre in br.0.brerror_set.all %} {{bre.errmsg}} ({{bre.errtype}}) <br/><hr/><code>{{bre.traceback}}</code>{%endfor%}
150{%endif%}
151 </div>
152
153 </div>
154</div>
155
156 {% endfor %}
157
158 </div>
159
160
161
162<!-- Lifted from build.html -->
163 {% for build in builds %}
164<div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%}">
165 <div class="row-fluid">
166 <div class="lead span5">
167 {%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}
168 {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
169 <a href="{%url 'builddashboard' build.pk%}" class="{%if build.outcome == build.SUCCEEDED %}success{%else%}error{%endif%}">
170 {% endif %}
171 <span data-toggle="tooltip" {%if build.target_set.all.count > 1%}title="Targets: {%for target in build.target_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{build.target_set.all.0.target}} {%if build.target_set.all.count > 1%}(+ {{build.target_set.all.count|add:"-1"}}){%endif%} {{build.machine}} ({{build.completed_on|naturaltime}})</span>
172 {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
173 </a>
174 {% endif %}
175 </div>
176 {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
177 <div class="span2 lead">
178 {% if build.errors_no %}
179 <i class="icon-minus-sign red"></i> <a href="{%url 'builddashboard' build.pk%}#errors" class="error">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>
180 {% endif %}
181 </div>
182 <div class="span2 lead">
183 {% if build.warnings_no %}
184 <i class="icon-warning-sign yellow"></i> <a href="{%url 'builddashboard' build.pk%}#warnings" class="warning">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>
185 {% endif %}
186 </div >
187 <div class="lead pull-right">
188 Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent|sectohms }}</a>
189 </div>
190 {%endif%}{%if build.outcome == build.IN_PROGRESS %}
191 <div class="span4">
192 <div class="progress" style="margin-top:5px;" data-toggle="tooltip" title="{{build.completeper}}% of tasks complete">
193 <div style="width: {{build.completeper}}%;" class="bar"></div>
194 </div>
195 </div>
196 <div class="lead pull-right">ETA: in {{build.eta|naturaltime}}</div>
197 {%endif%}
198 </div>
199 </div>
200 {% endfor %}
201<!-- end of lift-->
202 {%endif%}
203
204 <h2 class="air">Project configuration</h2>
205
206 <div class="row-fluid">
207
208 <div id="layer-container" class="well well-transparent span4">
209 <h3>
210 Add layers
211 <i data-original-title="OpenEmbedded organises metadata into modules called 'layers'. Layers allow you to isolate different types of customizations from each other. <a href='http://www.yoctoproject.org/docs/1.6.1/dev-manual/dev-manual.html#understanding-and-creating-layers' target='_blank'>More on layers</a>" class="icon-question-sign get-help heading-help" title=""></i>
212 </h3>
213 <form style="margin-top:20px;">
214 <div class="input-append">
215 <input class="input-xlarge" id="layer" autocomplete="off" placeholder="Type a layer name" data-provide="typeahead" data-source="" data-minlength="1" data-autocomplete="off" type="text">
216 <button id="add-layer" class="btn" disabled="">Add</button>
217 </div>
218 <div id="import-alert" class="alert alert-info" style="display:none;">
219 Toaster does not know about this layer. Please <a href="#">import it</a>
220 </div>
221 <div id="dependency-alert" class="alert alert-info" style="display:none;">
222 <p><strong>meta-tizen</strong> depends on the layers below. Check the ones you want to add: </p>
223 <ul class="unstyled">
224 <li>
225 <label class="checkbox">
226 <input checked="checked" type="checkbox">
227 meta-efl
228 </label>
229 </li>
230 <li>
231 <label class="checkbox">
232 <input checked="checked" type="checkbox">
233 meta-intel
234 </label>
235 </li>
236 <li>
237 <label class="checkbox">
238 <input checked="checked" type="checkbox">
239 meta-multimedia
240 </label>
241 </li>
242 <li>
243 <label class="checkbox">
244 <input checked="checked" type="checkbox">
245 meta-oe
246 </label>
247 </li>
248 <li>
249 <label class="checkbox">
250 <input checked="checked" type="checkbox">
251 meta-ruby
252 </label>
253 </li>
254 </ul>
255 <button id="add-layer-dependencies" class="btn btn-info add-layer">Add layers</button>
256 </div>
257
258 <p><a href="#">Import your layer</a> | <a href="#">View all layers</a></p>
259 </form>
260
261 <h4 class="air">
262 Added layers
263 <span class="muted counter">{{project.projectlayer_set.count}}</span>
264 <i data-original-title="Your added layers will be listed in this same order in your <code>bblayers.conf</code> file" class="icon-question-sign get-help heading-help" title=""></i>
265 </h4>
266 <ul class="unstyled configuration-list">
267 {% for pl in project.projectlayer_set.all %}
268 <li>
269 <a href="#">{{pl.name}} (<span class="layer-version">{{pl.giturl}}</span>)</a>
270 {% if pl.optional %}
271 <i title="" data-original-title="" class="icon-trash" id="del-layer-icon" x-data="{{pl.pk}}"></i>
272 {% endif %}
273 </li>
274 {% endfor %}
275 </ul>
276 </div>
277
278 <div id="target-container" class="well well-transparent span4">
279 <h3>
280 Add targets
281 <i data-original-title="A target is what you want to build, usually an image recipe that produces a root file system" class="icon-question-sign get-help heading-help" title=""></i>
282 </h3>
283 <form style="margin-top:20px;">
284 <div class="input-append">
285 <input id="target" class="input-xlarge" autocomplete="off" placeholder="Type a target name" data-provide="typeahead" data-source="" data-minlength="1" data-autocomplete="off" type="text">
286 <button id="add-target-button" class="btn" type="button">Add</button>
287 </div>
288
289 <p><a href="#" class="link">View all targets</a></p>
290 </form>
291 <h4 class="air">
292 Added targets
293 <span id="target-count" class="muted counter">{{project.projecttarget_set.count}}</span>
294 </h4>
295 <ul class="unstyled configuration-list" id="target-list">
296 {% for target in project.projecttarget_set.all %}
297 {% if target %}
298 <li>
299 <a href="#">{{target.target}}{% if target.task%} (target.task){%endif%}</a>
300 {% if target.notprovided %}
301 <i title="" data-original-title="" id="msg1" class="icon-exclamation-sign get-help-yellow" data-title="<strong>Target may not be provided</strong>" data-content="From the layer information it currently has, Toaster thinks this target is not provided by any of your added layers. If a target is not provided by one of your added layers, the build will fail.<h5>What Toaster suggests</h5><p>The <a href='#'>meta-abc</a> and <a href='#'>meta-efg</a> layers provide core-image-notprovided. You could add one of them to your project.</p><button class='btn btn-block'>Add meta-abc</button><button class='btn btn-block'>Add meta-efg</button><button id='dismiss1' class='btn btn-block btn-info'>Stop showing this message</button>"></i>
302 {% elif target.notknown %}
303 <i title="" data-original-title="" id="msg2" class="icon-exclamation-sign get-help-yellow" data-title="<strong>Target may not be provided</strong>" data-content="From the layer information it currently has, Toaster thinks this target is not provided by any of your added layers. If a target is not provided by one of your added layers, the build will fail.<h5>What Toaster suggests</h5><p>Review your added layers to make sure one of them provides core-image-unknown. Clicking on a layer name will give you all the information Toaster has about the layer. </p> <button class='btn btn-block btn-info'>Stop showing this message</button>"></i>
304 {% endif %}
305 <i title="" data-original-title="" class="icon-trash" id="del-target-icon" x-data="{{target.pk}}"></i>
306 </li>
307 {% endif %}
308 {% endfor %}
309
310
311 </ul>
312 </div>
313
314 <div class="well well-transparent span4">
315 <h3>
316 Set machine
317 <i data-original-title="The machine is the hardware for which you want to build. You can only set one machine per project" class="icon-question-sign get-help heading-help" title=""></i>
318 </h3>
319 <p class="lead">
320 {{machine}}
321 <i title="" data-original-title="" class="icon-pencil"></i>
322 </p>
323 <h3>
324 Set distro
325 <i data-original-title="When you build an image using the Yocto Project and do not alter the distro, you are creating a Poky distribution" class="icon-question-sign get-help heading-help" title=""></i>
326 </h3>
327 <p class="lead">
328 {{distro}}
329 <i title="" data-original-title="" class="icon-pencil"></i>
330 </p>
331 <p class="localconf">
332 <a href="#" class="link">Edit the <code>local.conf</code> file</a>
333 <i data-original-title="The <code>local.conf</code> file is where other project configuration options are set. Pretty much any configuration option can be set in this file. Each option, like everything else in the build system, is a variable - value pair" class="icon-question-sign get-help heading-help" title=""></i>
334 </p>
335 </div>
336 </div>
337
338 <h2>Project details</h2>
339
340 <div class="well well-transparent">
341 <h3>Project name</h3>
342 <p class="lead">
343 {{project.name}}
344 <i title="" data-original-title="" class="icon-pencil"></i>
345 </p>
346 <h3>Project owner</h3>
347 <p class="lead">
348 {{puser.username}}
349 <i title="" data-original-title="" class="icon-pencil"></i>
350 </p>
351 <h3>Owner's email</h3>
352 <p class="lead">
353 {{puser.email}}
354 <i title="" data-original-title="" class="icon-pencil"></i>
355 </p>
356 <h3>Yocto Project version</h3>
357 <p class="lead">
358 {{project.branch}} - {{project.short_description}}
359 <i title="" data-original-title="" class="icon-pencil"></i>
360 </p>
361 </div>
6{% endblock %} 362{% endblock %}
diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py
index 0d7a4c35fa..7c4f894b9c 100644
--- a/bitbake/lib/toaster/toastergui/urls.py
+++ b/bitbake/lib/toaster/toastergui/urls.py
@@ -69,6 +69,9 @@ urlpatterns = patterns('toastergui.views',
69 # project URLs 69 # project URLs
70 url(r'^newproject/$', 'newproject', name='newproject'), 70 url(r'^newproject/$', 'newproject', name='newproject'),
71 url(r'^project/(?P<pid>\d+)/$', 'project', name='project'), 71 url(r'^project/(?P<pid>\d+)/$', 'project', name='project'),
72 url(r'^xhr_projectbuild/(?P<pid>\d+)/$', 'xhr_projectbuild', name='xhr_projectbuild'),
73 url(r'^xhr_projectedit/(?P<pid>\d+)/$', 'xhr_projectedit', name='xhr_projectedit'),
74
72 75
73 # default redirection 76 # default redirection
74 url(r'^$', RedirectView.as_view( url= 'builds/')), 77 url(r'^$', RedirectView.as_view( url= 'builds/')),
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index f9e8e51688..bd65c08b06 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -30,7 +30,7 @@ from orm.models import Target_Installed_Package, Target_File, Target_Image_File
30from django.views.decorators.cache import cache_control 30from django.views.decorators.cache import cache_control
31from django.core.urlresolvers import reverse 31from django.core.urlresolvers import reverse
32from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger 32from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
33from django.http import HttpResponseBadRequest 33from django.http import HttpResponseBadRequest, HttpResponseNotFound
34from django.utils import timezone 34from django.utils import timezone
35from datetime import timedelta 35from datetime import timedelta
36from django.utils import formats 36from django.utils import formats
@@ -1761,8 +1761,6 @@ def image_information_dir(request, build_id, target_id, packagefile_id):
1761 1761
1762 1762
1763import toastermain.settings 1763import toastermain.settings
1764def managedcontextprocessor(request):
1765 return { "MANAGED" : toastermain.settings.MANAGED }
1766 1764
1767 1765
1768# we have a set of functions if we're in managed mode, or 1766# we have a set of functions if we're in managed mode, or
@@ -1773,7 +1771,8 @@ if toastermain.settings.MANAGED:
1773 from django.contrib.auth import authenticate, login 1771 from django.contrib.auth import authenticate, login
1774 from django.contrib.auth.decorators import login_required 1772 from django.contrib.auth.decorators import login_required
1775 1773
1776 from orm.models import Project 1774 from orm.models import Project, ProjectLayer, ProjectTarget, ProjectVariable
1775 from bldcontrol.models import BuildRequest
1777 1776
1778 import traceback 1777 import traceback
1779 1778
@@ -1831,19 +1830,113 @@ if toastermain.settings.MANAGED:
1831 else: 1830 else:
1832 context['alert'] = str(e) 1831 context['alert'] = str(e)
1833 return render(request, template, context) 1832 return render(request, template, context)
1833
1834 raise Exception("Invalid HTTP method for this page") 1834 raise Exception("Invalid HTTP method for this page")
1835 1835
1836 # Shows the edit project page 1836 # Shows the edit project page
1837 def project(request, pid): 1837 def project(request, pid):
1838 template = "project.html" 1838 template = "project.html"
1839 context = {} 1839 try:
1840 prj = Project.objects.get(id = pid)
1841 except Project.DoesNotExist:
1842 return HttpResponseNotFound("<h1>Project id " + pid + " is unavailable</h1>")
1843
1844 try:
1845 puser = User.objects.get(id = prj.user_id)
1846 except User.DoesNotExist:
1847 puser = None
1848
1849 context = {
1850 "project" : prj,
1851 #"buildrequests" : prj.buildrequest_set.filter(state=BuildRequest.REQ_QUEUED),
1852 "buildrequests" : map(lambda x: (x, {"machine" : x.brvariable_set.filter(name="MACHINE")[0]}), prj.buildrequest_set.order_by("-pk")),
1853 "builds" : prj.build_set.all(),
1854 "puser": puser,
1855 }
1856 try:
1857 context["machine"] = prj.projectvariable_set.get(name="MACHINE").value
1858 except ProjectVariable.DoesNotExist:
1859 context["machine"] = "-- not set yet"
1860
1861 try:
1862 context["distro"] = prj.projectvariable_set.get(name="DISTRO").value
1863 except ProjectVariable.DoesNotExist:
1864 context["distro"] = "-- not set yet"
1865
1866
1840 return render(request, template, context) 1867 return render(request, template, context)
1841 1868
1869 import json
1870
1871 def xhr_projectbuild(request, pid):
1872 try:
1873 if request.method != "POST":
1874 raise BadParameterException("invalid method")
1875 prj = Project.objects.get(id = pid)
1876
1877 if prj.projecttarget_set.count() == 0:
1878 raise BadParameterException("no targets selected")
1879
1880 br = prj.schedule_build()
1881 return HttpResponse(json.dumps({"error":"ok",
1882 "brtarget" : map(lambda x: x.target, br.brtarget_set.all()),
1883 "machine" : br.brvariable_set.get(name="MACHINE").value,
1884
1885 }), content_type = "application/json")
1886 except Exception as e:
1887 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
1888
1889 def xhr_projectedit(request, pid):
1890 try:
1891 prj = Project.objects.get(id = pid)
1892 # add targets
1893 if 'targetAdd' in request.POST:
1894 for t in request.POST['targetAdd'].strip().split(" "):
1895 if ":" in t:
1896 target, task = t.split(":")
1897 else:
1898 target = t
1899 task = ""
1900
1901 pt, created = ProjectTarget.objects.get_or_create(project = prj, target = target, task = task)
1902 # remove targets
1903 if 'targetDel' in request.POST:
1904 for t in request.POST['targetDel'].strip().split(" "):
1905 pt = ProjectTarget.objects.get(pk = int(t)).delete()
1906
1907 # add layers
1908
1909 # remove layers
1910
1911 # return all project settings
1912 return HttpResponse(json.dumps( {
1913 "error": "ok",
1914 "layers": map(lambda x: (x.name, x.giturl), prj.projectlayer_set.all()),
1915 "targets" : map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all()),
1916 "variables": map(lambda x: (x.name, x.value), prj.projectvariable_set.all()),
1917 }), content_type = "application/json")
1918
1919 except Exception as e:
1920 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
1921
1842 1922
1843else: 1923else:
1844 # these are pages that are NOT available in interactive mode 1924 # these are pages that are NOT available in interactive mode
1925 def managedcontextprocessor(request):
1926 return {
1927 "projects": [],
1928 "MANAGED" : toastermain.settings.MANAGED
1929 }
1930
1845 def newproject(request): 1931 def newproject(request):
1846 raise Exception("page not available in interactive mode") 1932 raise Exception("page not available in interactive mode")
1847 1933
1848 def project(request): 1934 def project(request, pid):
1935 raise Exception("page not available in interactive mode")
1936
1937 def xhr_projectbuild(request, pid):
1849 raise Exception("page not available in interactive mode") 1938 raise Exception("page not available in interactive mode")
1939
1940 def xhr_projectedit(request, pid):
1941 raise Exception("page not available in interactive mode")
1942