Merge lp:~linaro-infrastructure/linaro-ci-dashboard/models-design into lp:linaro-ci-dashboard
- models-design
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 11 |
Proposed branch: | lp:~linaro-infrastructure/linaro-ci-dashboard/models-design |
Merge into: | lp:linaro-ci-dashboard |
Diff against target: |
788 lines (+450/-111) 19 files modified
.bzrignore (+1/-0) Makefile (+35/-0) README (+6/-12) dashboard/frontend/management/commands/build.py (+0/-42) dashboard/frontend/migrations/0001_initial.py (+0/-33) dashboard/frontend/models.py (+0/-23) dashboard/frontend/models/__init__.py (+2/-0) dashboard/frontend/models/loop.py (+28/-0) dashboard/frontend/models/loop_build.py (+34/-0) dashboard/frontend/views/index_view.py (+1/-0) dashboard/jenkins/migrations/0001_initial.py (+71/-0) dashboard/jenkins/migrations/0002_auto__add_field_jenkinsjob_server.py (+44/-0) dashboard/jenkins/migrations/0003_auto__add_unique_jenkinsjob_name.py (+44/-0) dashboard/jenkins/models/__init__.py (+1/-0) dashboard/jenkins/models/jenkins_server.py (+80/-0) dashboard/jenkins/tests/__init__.py (+6/-0) dashboard/jenkins/tests/test_jenkins_server.py (+88/-0) dashboard/manage.py (+5/-1) dashboard/settings.py (+4/-0) |
To merge this branch: | bzr merge lp:~linaro-infrastructure/linaro-ci-dashboard/models-design |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Данило Шеган (community) | Approve | ||
Deepti B. Kalakeri (community) | Needs Fixing | ||
Review via email: mp+114819@code.launchpad.net |
Commit message
Description of the change
New app for the models.
Separating models into files.
Create makefile with couple of targets.
Models design and syncjobs method with tests.
- 29. By Stevan Radaković
-
Add lib/* to .bzrignore.
Данило Шеган (danilo) wrote : | # |
It would be good if we could have the "dependencies" not be a .phony target (i.e. build them only when necessary), so we could have tests and run depend on it.
Is the "syncdb" call needed/usable with south? I believe we need to use "migrate" command instead.
And we should definitely rebase before landing this (removal of the jenkinsjob from the frontend is a clear example of what we don't want to keep references of).
For JenkinsJob/
- 30. By Stevan Radaković
-
Fix README typo.
- 31. By Stevan Radaković
-
Remove unnecessary migration files. Fix Makefile a bit and update README.
- 32. By Stevan Radaković
-
Moved Job and JobBuild to Loop and LoopBuild. Moved import_jenkins_job method to JenkinsServer model (including tests).
- 33. By Stevan Radaković
-
Remove running of jenkins job tests, file is removed.
- 34. By Stevan Radaković
-
Add migration file for frontend.
Данило Шеган (danilo) wrote : | # |
Thanks for all the improvements: looks good.
Preview Diff
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2012-07-06 12:51:21 +0000 |
3 | +++ .bzrignore 2012-07-16 12:59:18 +0000 |
4 | @@ -1,1 +1,2 @@ |
5 | *.db |
6 | +lib/* |
7 | |
8 | === added file 'Makefile' |
9 | --- Makefile 1970-01-01 00:00:00 +0000 |
10 | +++ Makefile 2012-07-16 12:59:18 +0000 |
11 | @@ -0,0 +1,35 @@ |
12 | +# Makefile for linaro-ci-dashboard |
13 | + |
14 | +help: |
15 | + @echo "Please use \`make <target>' where <target> is one of" |
16 | + @echo " dependencies to install ci-dashboard dependency libs" |
17 | + @echo " migration to update the database schema with the" \ |
18 | + "latest model changes" |
19 | + @echo " runserver to run the django web server" |
20 | + @echo " test to run the unit tests" |
21 | + |
22 | +create-migration: syncdb |
23 | + python dashboard/manage.py schemamigration frontend --auto || \ |
24 | + echo "No changes to frontend models." |
25 | + python dashboard/manage.py schemamigration jenkins --auto || \ |
26 | + echo "No changes to jenkins models." |
27 | + python dashboard/manage.py migrate |
28 | + |
29 | +dependencies: |
30 | + PYTHONPATH=$(shell pwd)/lib easy_install \ |
31 | + --install-dir=$(shell pwd)/lib jenkinsapi |
32 | + |
33 | +migrate: syncdb |
34 | + python dashboard/manage.py migrate |
35 | + |
36 | +runserver: migrate |
37 | + python dashboard/manage.py runserver |
38 | + |
39 | +syncdb: dependencies |
40 | + python dashboard/manage.py syncdb |
41 | + |
42 | +test: |
43 | + python dashboard/manage.py test frontend \ |
44 | + jenkins |
45 | + |
46 | +.PHONY: help migration test |
47 | |
48 | === modified file 'README' |
49 | --- README 2012-07-11 10:20:55 +0000 |
50 | +++ README 2012-07-16 12:59:18 +0000 |
51 | @@ -19,8 +19,7 @@ |
52 | Running the application |
53 | ----------------------- |
54 | |
55 | - $ python dashboard/manage.py build # only for first time run |
56 | - $ python dashboard/manage.py runserver |
57 | + $ make runserver |
58 | |
59 | |
60 | Database migration with South |
61 | @@ -29,9 +28,9 @@ |
62 | We now provide database migrations with django south. All of the migrations |
63 | files are stored in each app directories (i.e. frontend/migrations). |
64 | Whenever any of the models are changed, it is strongly advised to run |
65 | -South' schemamigration command for the particular application: |
66 | +South' schemamigration command: |
67 | |
68 | - $ python dashboard/manage.py schemamigration frontend --auto |
69 | + $ make migration |
70 | |
71 | |
72 | Tests |
73 | @@ -44,11 +43,6 @@ |
74 | |
75 | python-mock |
76 | |
77 | -To run all tests: |
78 | - |
79 | - $ python dashboard/manage.py test |
80 | - |
81 | -To run only tests for the frontend application within the CI Dashboard |
82 | -tool run: |
83 | - |
84 | - $ python dashboard/manage.py test frontend |
85 | +To run the tests: |
86 | + |
87 | + $ make test |
88 | |
89 | === removed file 'dashboard/frontend/management/commands/build.py' |
90 | --- dashboard/frontend/management/commands/build.py 2012-07-10 12:24:59 +0000 |
91 | +++ dashboard/frontend/management/commands/build.py 1970-01-01 00:00:00 +0000 |
92 | @@ -1,42 +0,0 @@ |
93 | -# Copyright (C) 2012 Linaro |
94 | -# |
95 | -# This file is part of linaro-ci-dashboard. |
96 | -# |
97 | -# linaro-ci-dashboard is free software: you can redistribute it and/or modify |
98 | -# it under the terms of the GNU Affero General Public License as published by |
99 | -# the Free Software Foundation, either version 3 of the License, or |
100 | -# (at your option) any later version. |
101 | -# |
102 | -# linaro-ci-dashboard is distributed in the hope that it will be useful, |
103 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
104 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
105 | -# GNU Affero General Public License for more details. |
106 | -# |
107 | -# You should have received a copy of the GNU Affero General Public License |
108 | -# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>. |
109 | -# Django settings for dashboard project. |
110 | - |
111 | -from django.core import management |
112 | -from django.core.management.base import NoArgsCommand, CommandError |
113 | -from django.conf import settings |
114 | -import os |
115 | -from setuptools.command import easy_install |
116 | - |
117 | - |
118 | -class Command(NoArgsCommand): |
119 | - help = 'Call django syncdb command which takes no arguments and ' + \ |
120 | - 'install jenkinsapi module from pypi.' |
121 | - |
122 | - def handle_noargs(self, **options): |
123 | - |
124 | - try: |
125 | - management.call_command('syncdb', **options) |
126 | - except CommandError, e: |
127 | - print "CommandError: syncdb command failed: " + str(e) |
128 | - |
129 | - self.install_pypi_package("jenkinsapi") |
130 | - |
131 | - def install_pypi_package(self, package): |
132 | - install_path = settings.DEPS_INSTALL_PATH |
133 | - os.environ["PYTHONPATH"] = install_path |
134 | - easy_install.main(["-U", "--install-dir=" + install_path, package]) |
135 | |
136 | === added file 'dashboard/frontend/migrations/0001_initial.py' |
137 | --- dashboard/frontend/migrations/0001_initial.py 1970-01-01 00:00:00 +0000 |
138 | +++ dashboard/frontend/migrations/0001_initial.py 2012-07-16 12:59:18 +0000 |
139 | @@ -0,0 +1,61 @@ |
140 | +# encoding: utf-8 |
141 | +import datetime |
142 | +from south.db import db |
143 | +from south.v2 import SchemaMigration |
144 | +from django.db import models |
145 | + |
146 | +class Migration(SchemaMigration): |
147 | + |
148 | + def forwards(self, orm): |
149 | + |
150 | + # Adding model 'Loop' |
151 | + db.create_table('frontend_loop', ( |
152 | + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
153 | + ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=200)), |
154 | + ('server', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['jenkins.JenkinsServer'])), |
155 | + )) |
156 | + db.send_create_signal('frontend', ['Loop']) |
157 | + |
158 | + # Adding model 'LoopBuild' |
159 | + db.create_table('frontend_loopbuild', ( |
160 | + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
161 | + ('loop', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['frontend.Loop'])), |
162 | + ('status', self.gf('django.db.models.fields.CharField')(max_length=20)), |
163 | + ('duration', self.gf('django.db.models.fields.DecimalField')(max_digits=8, decimal_places=2)), |
164 | + )) |
165 | + db.send_create_signal('frontend', ['LoopBuild']) |
166 | + |
167 | + |
168 | + def backwards(self, orm): |
169 | + |
170 | + # Deleting model 'Loop' |
171 | + db.delete_table('frontend_loop') |
172 | + |
173 | + # Deleting model 'LoopBuild' |
174 | + db.delete_table('frontend_loopbuild') |
175 | + |
176 | + |
177 | + models = { |
178 | + 'frontend.loop': { |
179 | + 'Meta': {'object_name': 'Loop'}, |
180 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
181 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}), |
182 | + 'server': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jenkins.JenkinsServer']"}) |
183 | + }, |
184 | + 'frontend.loopbuild': { |
185 | + 'Meta': {'object_name': 'LoopBuild'}, |
186 | + 'duration': ('django.db.models.fields.DecimalField', [], {'max_digits': '8', 'decimal_places': '2'}), |
187 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
188 | + 'loop': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.Loop']"}), |
189 | + 'status': ('django.db.models.fields.CharField', [], {'max_length': '20'}) |
190 | + }, |
191 | + 'jenkins.jenkinsserver': { |
192 | + 'Meta': {'object_name': 'JenkinsServer'}, |
193 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
194 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
195 | + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
196 | + 'username': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
197 | + } |
198 | + } |
199 | + |
200 | + complete_apps = ['frontend'] |
201 | |
202 | === removed file 'dashboard/frontend/migrations/0001_initial.py' |
203 | --- dashboard/frontend/migrations/0001_initial.py 2012-07-11 10:20:55 +0000 |
204 | +++ dashboard/frontend/migrations/0001_initial.py 1970-01-01 00:00:00 +0000 |
205 | @@ -1,33 +0,0 @@ |
206 | -# encoding: utf-8 |
207 | -import datetime |
208 | -from south.db import db |
209 | -from south.v2 import SchemaMigration |
210 | -from django.db import models |
211 | - |
212 | -class Migration(SchemaMigration): |
213 | - |
214 | - def forwards(self, orm): |
215 | - |
216 | - # Adding model 'JenkinsJob' |
217 | - db.create_table('frontend_jenkinsjob', ( |
218 | - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
219 | - ('name', self.gf('django.db.models.fields.CharField')(max_length=200)), |
220 | - )) |
221 | - db.send_create_signal('frontend', ['JenkinsJob']) |
222 | - |
223 | - |
224 | - def backwards(self, orm): |
225 | - |
226 | - # Deleting model 'JenkinsJob' |
227 | - db.delete_table('frontend_jenkinsjob') |
228 | - |
229 | - |
230 | - models = { |
231 | - 'frontend.jenkinsjob': { |
232 | - 'Meta': {'object_name': 'JenkinsJob'}, |
233 | - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
234 | - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}) |
235 | - } |
236 | - } |
237 | - |
238 | - complete_apps = ['frontend'] |
239 | |
240 | === added directory 'dashboard/frontend/models' |
241 | === removed file 'dashboard/frontend/models.py' |
242 | --- dashboard/frontend/models.py 2012-07-11 10:20:55 +0000 |
243 | +++ dashboard/frontend/models.py 1970-01-01 00:00:00 +0000 |
244 | @@ -1,23 +0,0 @@ |
245 | -#!/usr/bin/env python |
246 | -# Copyright (C) 2012 Linaro |
247 | -# |
248 | -# This file is part of linaro-ci-dashboard. |
249 | -# |
250 | -# linaro-ci-dashboard is free software: you can redistribute it and/or modify |
251 | -# it under the terms of the GNU Affero General Public License as published by |
252 | -# the Free Software Foundation, either version 3 of the License, or |
253 | -# (at your option) any later version. |
254 | -# |
255 | -# linaro-ci-dashboard is distributed in the hope that it will be useful, |
256 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
257 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
258 | -# GNU Affero General Public License for more details. |
259 | -# |
260 | -# You should have received a copy of the GNU Affero General Public License |
261 | -# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>. |
262 | - |
263 | -from django.db import models |
264 | - |
265 | -class JenkinsJob(models.Model): |
266 | - name = models.CharField(max_length=200) |
267 | - |
268 | |
269 | === added file 'dashboard/frontend/models/__init__.py' |
270 | --- dashboard/frontend/models/__init__.py 1970-01-01 00:00:00 +0000 |
271 | +++ dashboard/frontend/models/__init__.py 2012-07-16 12:59:18 +0000 |
272 | @@ -0,0 +1,2 @@ |
273 | +from frontend.models.loop import Loop |
274 | +from frontend.models.loop_build import LoopBuild |
275 | |
276 | === added file 'dashboard/frontend/models/loop.py' |
277 | --- dashboard/frontend/models/loop.py 1970-01-01 00:00:00 +0000 |
278 | +++ dashboard/frontend/models/loop.py 2012-07-16 12:59:18 +0000 |
279 | @@ -0,0 +1,28 @@ |
280 | +#!/usr/bin/env python |
281 | +# Copyright (C) 2012 Linaro |
282 | +# |
283 | +# This file is part of linaro-ci-dashboard. |
284 | +# |
285 | +# linaro-ci-dashboard is free software: you can redistribute it and/or modify |
286 | +# it under the terms of the GNU Affero General Public License as published by |
287 | +# the Free Software Foundation, either version 3 of the License, or |
288 | +# (at your option) any later version. |
289 | +# |
290 | +# linaro-ci-dashboard is distributed in the hope that it will be useful, |
291 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
292 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
293 | +# GNU Affero General Public License for more details. |
294 | +# |
295 | +# You should have received a copy of the GNU Affero General Public License |
296 | +# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>. |
297 | + |
298 | +from django.db import models |
299 | +import jenkins |
300 | + |
301 | + |
302 | +class Loop(models.Model): |
303 | + class Meta: |
304 | + app_label = 'frontend' |
305 | + |
306 | + name = models.CharField(max_length=200, unique=True) |
307 | + server = models.ForeignKey(jenkins.models.jenkins_server.JenkinsServer) |
308 | |
309 | === added file 'dashboard/frontend/models/loop_build.py' |
310 | --- dashboard/frontend/models/loop_build.py 1970-01-01 00:00:00 +0000 |
311 | +++ dashboard/frontend/models/loop_build.py 2012-07-16 12:59:18 +0000 |
312 | @@ -0,0 +1,34 @@ |
313 | +#!/usr/bin/env python |
314 | +# Copyright (C) 2012 Linaro |
315 | +# |
316 | +# This file is part of linaro-ci-dashboard. |
317 | +# |
318 | +# linaro-ci-dashboard is free software: you can redistribute it and/or modify |
319 | +# it under the terms of the GNU Affero General Public License as published by |
320 | +# the Free Software Foundation, either version 3 of the License, or |
321 | +# (at your option) any later version. |
322 | +# |
323 | +# linaro-ci-dashboard is distributed in the hope that it will be useful, |
324 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
325 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
326 | +# GNU Affero General Public License for more details. |
327 | +# |
328 | +# You should have received a copy of the GNU Affero General Public License |
329 | +# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>. |
330 | + |
331 | +from django.db import models |
332 | +from frontend.models.loop import Loop |
333 | + |
334 | + |
335 | +class LoopBuild(models.Model): |
336 | + class Meta: |
337 | + app_label = 'frontend' |
338 | + |
339 | + BUILD_STATUS = ( |
340 | + ('failure','FAILURE'), |
341 | + ('success','SUCCESS'), |
342 | + ('aborted','ABORTED'), |
343 | + ) |
344 | + loop = models.ForeignKey(Loop) |
345 | + status = models.CharField(max_length=20, choices=BUILD_STATUS) |
346 | + duration = models.DecimalField(max_digits=8, decimal_places=2) |
347 | |
348 | === modified file 'dashboard/frontend/views/index_view.py' |
349 | --- dashboard/frontend/views/index_view.py 2012-07-06 12:30:37 +0000 |
350 | +++ dashboard/frontend/views/index_view.py 2012-07-16 12:59:18 +0000 |
351 | @@ -19,6 +19,7 @@ |
352 | from django.views.generic.base import TemplateView |
353 | from django.utils.decorators import method_decorator |
354 | from django.contrib.auth.decorators import login_required |
355 | +from jenkins.models.jenkins_server import JenkinsServer |
356 | |
357 | |
358 | class IndexView(TemplateView): |
359 | |
360 | === added directory 'dashboard/jenkins' |
361 | === added file 'dashboard/jenkins/__init__.py' |
362 | === added directory 'dashboard/jenkins/migrations' |
363 | === added file 'dashboard/jenkins/migrations/0001_initial.py' |
364 | --- dashboard/jenkins/migrations/0001_initial.py 1970-01-01 00:00:00 +0000 |
365 | +++ dashboard/jenkins/migrations/0001_initial.py 2012-07-16 12:59:18 +0000 |
366 | @@ -0,0 +1,71 @@ |
367 | +# encoding: utf-8 |
368 | +import datetime |
369 | +from south.db import db |
370 | +from south.v2 import SchemaMigration |
371 | +from django.db import models |
372 | + |
373 | +class Migration(SchemaMigration): |
374 | + |
375 | + def forwards(self, orm): |
376 | + |
377 | + # Adding model 'JenkinsServer' |
378 | + db.create_table('jenkins_jenkinsserver', ( |
379 | + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
380 | + ('url', self.gf('django.db.models.fields.CharField')(max_length=255)), |
381 | + ('username', self.gf('django.db.models.fields.CharField')(max_length=100)), |
382 | + ('password', self.gf('django.db.models.fields.CharField')(max_length=100)), |
383 | + )) |
384 | + db.send_create_signal('jenkins', ['JenkinsServer']) |
385 | + |
386 | + # Adding model 'JenkinsJob' |
387 | + db.create_table('jenkins_jenkinsjob', ( |
388 | + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
389 | + ('name', self.gf('django.db.models.fields.CharField')(max_length=200)), |
390 | + )) |
391 | + db.send_create_signal('jenkins', ['JenkinsJob']) |
392 | + |
393 | + # Adding model 'JenkinsBuild' |
394 | + db.create_table('jenkins_jenkinsbuild', ( |
395 | + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
396 | + ('job', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['jenkins.JenkinsJob'])), |
397 | + ('status', self.gf('django.db.models.fields.CharField')(max_length=20)), |
398 | + ('duration', self.gf('django.db.models.fields.DecimalField')(max_digits=8, decimal_places=2)), |
399 | + )) |
400 | + db.send_create_signal('jenkins', ['JenkinsBuild']) |
401 | + |
402 | + |
403 | + def backwards(self, orm): |
404 | + |
405 | + # Deleting model 'JenkinsServer' |
406 | + db.delete_table('jenkins_jenkinsserver') |
407 | + |
408 | + # Deleting model 'JenkinsJob' |
409 | + db.delete_table('jenkins_jenkinsjob') |
410 | + |
411 | + # Deleting model 'JenkinsBuild' |
412 | + db.delete_table('jenkins_jenkinsbuild') |
413 | + |
414 | + |
415 | + models = { |
416 | + 'jenkins.jenkinsbuild': { |
417 | + 'Meta': {'object_name': 'JenkinsBuild'}, |
418 | + 'duration': ('django.db.models.fields.DecimalField', [], {'max_digits': '8', 'decimal_places': '2'}), |
419 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
420 | + 'job': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jenkins.JenkinsJob']"}), |
421 | + 'status': ('django.db.models.fields.CharField', [], {'max_length': '20'}) |
422 | + }, |
423 | + 'jenkins.jenkinsjob': { |
424 | + 'Meta': {'object_name': 'JenkinsJob'}, |
425 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
426 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}) |
427 | + }, |
428 | + 'jenkins.jenkinsserver': { |
429 | + 'Meta': {'object_name': 'JenkinsServer'}, |
430 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
431 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
432 | + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
433 | + 'username': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
434 | + } |
435 | + } |
436 | + |
437 | + complete_apps = ['jenkins'] |
438 | |
439 | === added file 'dashboard/jenkins/migrations/0002_auto__add_field_jenkinsjob_server.py' |
440 | --- dashboard/jenkins/migrations/0002_auto__add_field_jenkinsjob_server.py 1970-01-01 00:00:00 +0000 |
441 | +++ dashboard/jenkins/migrations/0002_auto__add_field_jenkinsjob_server.py 2012-07-16 12:59:18 +0000 |
442 | @@ -0,0 +1,44 @@ |
443 | +# encoding: utf-8 |
444 | +import datetime |
445 | +from south.db import db |
446 | +from south.v2 import SchemaMigration |
447 | +from django.db import models |
448 | + |
449 | +class Migration(SchemaMigration): |
450 | + |
451 | + def forwards(self, orm): |
452 | + |
453 | + # Adding field 'JenkinsJob.server' |
454 | + db.add_column('jenkins_jenkinsjob', 'server', self.gf('django.db.models.fields.related.ForeignKey')(default=1, to=orm['jenkins.JenkinsServer']), keep_default=False) |
455 | + |
456 | + |
457 | + def backwards(self, orm): |
458 | + |
459 | + # Deleting field 'JenkinsJob.server' |
460 | + db.delete_column('jenkins_jenkinsjob', 'server_id') |
461 | + |
462 | + |
463 | + models = { |
464 | + 'jenkins.jenkinsbuild': { |
465 | + 'Meta': {'object_name': 'JenkinsBuild'}, |
466 | + 'duration': ('django.db.models.fields.DecimalField', [], {'max_digits': '8', 'decimal_places': '2'}), |
467 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
468 | + 'job': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jenkins.JenkinsJob']"}), |
469 | + 'status': ('django.db.models.fields.CharField', [], {'max_length': '20'}) |
470 | + }, |
471 | + 'jenkins.jenkinsjob': { |
472 | + 'Meta': {'object_name': 'JenkinsJob'}, |
473 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
474 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), |
475 | + 'server': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jenkins.JenkinsServer']"}) |
476 | + }, |
477 | + 'jenkins.jenkinsserver': { |
478 | + 'Meta': {'object_name': 'JenkinsServer'}, |
479 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
480 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
481 | + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
482 | + 'username': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
483 | + } |
484 | + } |
485 | + |
486 | + complete_apps = ['jenkins'] |
487 | |
488 | === added file 'dashboard/jenkins/migrations/0003_auto__add_unique_jenkinsjob_name.py' |
489 | --- dashboard/jenkins/migrations/0003_auto__add_unique_jenkinsjob_name.py 1970-01-01 00:00:00 +0000 |
490 | +++ dashboard/jenkins/migrations/0003_auto__add_unique_jenkinsjob_name.py 2012-07-16 12:59:18 +0000 |
491 | @@ -0,0 +1,44 @@ |
492 | +# encoding: utf-8 |
493 | +import datetime |
494 | +from south.db import db |
495 | +from south.v2 import SchemaMigration |
496 | +from django.db import models |
497 | + |
498 | +class Migration(SchemaMigration): |
499 | + |
500 | + def forwards(self, orm): |
501 | + |
502 | + # Adding unique constraint on 'JenkinsJob', fields ['name'] |
503 | + db.create_unique('jenkins_jenkinsjob', ['name']) |
504 | + |
505 | + |
506 | + def backwards(self, orm): |
507 | + |
508 | + # Removing unique constraint on 'JenkinsJob', fields ['name'] |
509 | + db.delete_unique('jenkins_jenkinsjob', ['name']) |
510 | + |
511 | + |
512 | + models = { |
513 | + 'jenkins.jenkinsbuild': { |
514 | + 'Meta': {'object_name': 'JenkinsBuild'}, |
515 | + 'duration': ('django.db.models.fields.DecimalField', [], {'max_digits': '8', 'decimal_places': '2'}), |
516 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
517 | + 'job': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jenkins.JenkinsJob']"}), |
518 | + 'status': ('django.db.models.fields.CharField', [], {'max_length': '20'}) |
519 | + }, |
520 | + 'jenkins.jenkinsjob': { |
521 | + 'Meta': {'object_name': 'JenkinsJob'}, |
522 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
523 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}), |
524 | + 'server': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jenkins.JenkinsServer']"}) |
525 | + }, |
526 | + 'jenkins.jenkinsserver': { |
527 | + 'Meta': {'object_name': 'JenkinsServer'}, |
528 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
529 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
530 | + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
531 | + 'username': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
532 | + } |
533 | + } |
534 | + |
535 | + complete_apps = ['jenkins'] |
536 | |
537 | === added file 'dashboard/jenkins/migrations/__init__.py' |
538 | === added directory 'dashboard/jenkins/models' |
539 | === added file 'dashboard/jenkins/models/__init__.py' |
540 | --- dashboard/jenkins/models/__init__.py 1970-01-01 00:00:00 +0000 |
541 | +++ dashboard/jenkins/models/__init__.py 2012-07-16 12:59:18 +0000 |
542 | @@ -0,0 +1,1 @@ |
543 | +from jenkins.models.jenkins_server import JenkinsServer |
544 | |
545 | === added file 'dashboard/jenkins/models/jenkins_server.py' |
546 | --- dashboard/jenkins/models/jenkins_server.py 1970-01-01 00:00:00 +0000 |
547 | +++ dashboard/jenkins/models/jenkins_server.py 2012-07-16 12:59:18 +0000 |
548 | @@ -0,0 +1,80 @@ |
549 | +#!/usr/bin/env python |
550 | +# Copyright (C) 2012 Linaro |
551 | +# |
552 | +# This file is part of linaro-ci-dashboard. |
553 | +# |
554 | +# linaro-ci-dashboard is free software: you can redistribute it and/or modify |
555 | +# it under the terms of the GNU Affero General Public License as published by |
556 | +# the Free Software Foundation, either version 3 of the License, or |
557 | +# (at your option) any later version. |
558 | +# |
559 | +# linaro-ci-dashboard is distributed in the hope that it will be useful, |
560 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
561 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
562 | +# GNU Affero General Public License for more details. |
563 | +# |
564 | +# You should have received a copy of the GNU Affero General Public License |
565 | +# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>. |
566 | + |
567 | +from django.db import models |
568 | +from jenkinsapi.jenkins import Jenkins |
569 | + |
570 | + |
571 | +class JenkinsServer(models.Model): |
572 | + class Meta: |
573 | + app_label = 'jenkins' |
574 | + |
575 | + url = models.CharField(max_length=255) |
576 | + username = models.CharField(max_length=100) |
577 | + password = models.CharField(max_length=100) |
578 | + |
579 | + def __init__(self, *args, **kwargs): |
580 | + super(JenkinsServer, self).__init__(*args, **kwargs) |
581 | + self.jenkins = Jenkins(self.url, |
582 | + self.username.encode('utf-8'), |
583 | + self.password.encode('utf-8')) |
584 | + |
585 | + def import_jenkins_job(self, jobname): |
586 | + from frontend.models.loop import Loop |
587 | + job = self.jenkins.get_job(jobname) |
588 | + loop = Loop() |
589 | + loop.name = job.name |
590 | + loop.server = self |
591 | + loop.save() |
592 | + |
593 | + def sync_jobs(self): |
594 | + """Sync jobs in dashboard DB with jobs in jenkins server. |
595 | + |
596 | + Removes all deleted jenkins jobs from dashboard db and |
597 | + adds all newly create jobs in dashboard db.""" |
598 | + |
599 | + jenkins_jobs = self.jenkins.get_jobs() |
600 | + loops = self.loop_set.all() |
601 | + |
602 | + jenkins_jobs_names = [job[0] for job in jenkins_jobs] |
603 | + loop_names = [loop.name for loop in loops] |
604 | + |
605 | + # Remove jobs present in our DB but not in Jenkins |
606 | + self.prune_jobs(self.unique_items(loop_names, |
607 | + jenkins_jobs_names)) |
608 | + |
609 | + # Add jobs present in Jenkins but not our DB |
610 | + self.graft_jobs(self.unique_items(jenkins_jobs_names, |
611 | + loop_names)) |
612 | + |
613 | + def prune_jobs(self, jobs): |
614 | + """Remove all items from 'jobs' list from the database.""" |
615 | + for loop in self.loop_set.all(): |
616 | + if loop.name in jobs: |
617 | + loop.delete() |
618 | + |
619 | + def graft_jobs(self, jobs): |
620 | + """Find all items from 'jobs' list in jenkins and add them |
621 | + to the database.""" |
622 | + |
623 | + for job in jobs: |
624 | + self.import_jenkins_job(job) |
625 | + |
626 | + def unique_items(self, list_1, list_2): |
627 | + """Return a list of items that are present in list_1 but not list_2.""" |
628 | + return [item for item in list_1 if item not in list_2] |
629 | |
630 | === added directory 'dashboard/jenkins/tests' |
631 | === added file 'dashboard/jenkins/tests/__init__.py' |
632 | --- dashboard/jenkins/tests/__init__.py 1970-01-01 00:00:00 +0000 |
633 | +++ dashboard/jenkins/tests/__init__.py 2012-07-16 12:59:18 +0000 |
634 | @@ -0,0 +1,6 @@ |
635 | +from dashboard.jenkins.tests.test_jenkins_server import * |
636 | + |
637 | +#starts the test suite |
638 | +__test__= { |
639 | + 'JenkinsServerTests': JenkinsServerTests, |
640 | + } |
641 | |
642 | === added file 'dashboard/jenkins/tests/test_jenkins_server.py' |
643 | --- dashboard/jenkins/tests/test_jenkins_server.py 1970-01-01 00:00:00 +0000 |
644 | +++ dashboard/jenkins/tests/test_jenkins_server.py 2012-07-16 12:59:18 +0000 |
645 | @@ -0,0 +1,88 @@ |
646 | +#!/usr/bin/env python |
647 | +# Copyright (C) 2012 Linaro |
648 | +# |
649 | +# This file is part of linaro-ci-dashboard. |
650 | +# |
651 | +# linaro-ci-dashboard is free software: you can redistribute it and/or modify |
652 | +# it under the terms of the GNU Affero General Public License as published by |
653 | +# the Free Software Foundation, either version 3 of the License, or |
654 | +# (at your option) any later version. |
655 | +# |
656 | +# linaro-ci-dashboard is distributed in the hope that it will be useful, |
657 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
658 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
659 | +# GNU Affero General Public License for more details. |
660 | +# |
661 | +# You should have received a copy of the GNU Affero General Public License |
662 | +# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>. |
663 | +from django.core.management.base import CommandError |
664 | +from django.contrib.auth.models import User |
665 | +from django.db import IntegrityError |
666 | +from django.test import TestCase |
667 | +from jenkins.models.jenkins_server import JenkinsServer |
668 | +from jenkinsapi.exceptions import UnknownJob |
669 | +from frontend.models.loop import Loop |
670 | +from mock import Mock, patch |
671 | + |
672 | + |
673 | +class JenkinsServerTests(TestCase): |
674 | + |
675 | + def setUp(self): |
676 | + self.server = JenkinsServer.objects.get(id=1) |
677 | + self.test_job_name = "Test job 2" |
678 | + |
679 | + def test_import_jenkins_job_existing(self): |
680 | + loop = Loop() |
681 | + loop.name = self.test_job_name |
682 | + loop.server = self.server |
683 | + loop.save() |
684 | + with self.assertRaises(IntegrityError): |
685 | + self.server.import_jenkins_job(self.test_job_name) |
686 | + |
687 | + def test_import_jenkins_job_non_existing(self): |
688 | + with self.assertRaises(UnknownJob): |
689 | + self.server.import_jenkins_job("non-existing-jenkins-job") |
690 | + |
691 | + def test_import_jenkins_job(self): |
692 | + loop = Loop() |
693 | + self.server.import_jenkins_job(self.test_job_name) |
694 | + loop = Loop.objects.get(name=self.test_job_name) |
695 | + self.assertIsNotNone(loop) |
696 | + |
697 | + def test_sync_jobs(self): |
698 | + self.server.sync_jobs() |
699 | + loops = [loop.name for loop in |
700 | + self.server.loop_set.all()] |
701 | + jenkins_jobs = [job[0] for job in |
702 | + self.server.jenkins.get_jobs()] |
703 | + jenkins_jobs.sort() |
704 | + loops.sort() |
705 | + self.assertEquals(loops, jenkins_jobs) |
706 | + |
707 | + def test_unique_items(self): |
708 | + list_1 = ["1","4","5"] |
709 | + list_2 = ["1","2","5"] |
710 | + self.assertEquals(["4"], self.server.unique_items(list_1, list_2)) |
711 | + self.assertEquals(["2"], self.server.unique_items(list_2, list_1)) |
712 | + |
713 | + def test_graft_jobs(self): |
714 | + # This must be present on Jenkins server |
715 | + jobs = [self.test_job_name] |
716 | + self.server.graft_jobs(jobs) |
717 | + loops = [loop.name.encode("utf8") for loop in |
718 | + self.server.loop_set.all()] |
719 | + loops.sort() |
720 | + self.assertEquals([self.test_job_name], loops) |
721 | + |
722 | + def test_prune_jobs(self): |
723 | + loop = Loop() |
724 | + loop.name = self.test_job_name |
725 | + loop.server = self.server |
726 | + loop.save() |
727 | + |
728 | + jobs = [self.test_job_name] |
729 | + self.server.prune_jobs(jobs) |
730 | + |
731 | + loops = [loop.name.encode("utf8") for loop in |
732 | + self.server.loop_set.all()] |
733 | + self.assertNotIn(self.test_job_name, loops) |
734 | |
735 | === added directory 'dashboard/jenkins/views' |
736 | === modified file 'dashboard/manage.py' |
737 | --- dashboard/manage.py 2012-07-06 12:30:37 +0000 |
738 | +++ dashboard/manage.py 2012-07-16 12:59:18 +0000 |
739 | @@ -18,10 +18,11 @@ |
740 | |
741 | from django.core.management import execute_manager |
742 | import imp |
743 | +import os |
744 | +import sys |
745 | try: |
746 | imp.find_module('settings') # Assumed to be in the same directory. |
747 | except ImportError: |
748 | - import sys |
749 | sys.stderr.write("Error: Can't find the file 'settings.py' in the " + \ |
750 | "directory containing %r. It appears you've " + \ |
751 | "customized things.\nYou'll have to run " + \ |
752 | @@ -32,4 +33,7 @@ |
753 | import settings |
754 | |
755 | if __name__ == "__main__": |
756 | + for d in os.listdir(settings.DEPS_INSTALL_PATH): |
757 | + if os.path.isdir(os.path.join(settings.DEPS_INSTALL_PATH, d)): |
758 | + sys.path.append(os.path.join(settings.DEPS_INSTALL_PATH, d)) |
759 | execute_manager(settings) |
760 | |
761 | === modified file 'dashboard/settings.py' |
762 | --- dashboard/settings.py 2012-07-11 10:20:55 +0000 |
763 | +++ dashboard/settings.py 2012-07-16 12:59:18 +0000 |
764 | @@ -17,6 +17,7 @@ |
765 | # Django settings for dashboard project. |
766 | |
767 | import os |
768 | +import sys |
769 | |
770 | DEBUG = True |
771 | TEMPLATE_DEBUG = DEBUG |
772 | @@ -41,6 +42,8 @@ |
773 | } |
774 | } |
775 | |
776 | +SOUTH_TESTS_MIGRATE = False |
777 | + |
778 | # Local time zone for this installation. Choices can be found here: |
779 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name |
780 | # although not all choices may be available on all operating systems. |
781 | @@ -132,6 +135,7 @@ |
782 | 'django.contrib.messages', |
783 | 'django.contrib.staticfiles', |
784 | 'django_openid_auth', |
785 | + 'jenkins', |
786 | 'frontend', |
787 | 'south', |
788 | # Uncomment the next line to enable the admin: |
Overall changes looks good. Some suggestion here:
An important field of the Build record should be a build number. Integer( PrimaryKey= True) as part of the JenkinsBuild Model.
so we need to make bnum = models.
I would prefer not to expose the password in the initial_data.json fixture. This is not secure.
We should probably have a variable in settings.py which we import and set the values to these fields later.
We should provide dummy values to these variables and merge it in upstream and when we actually deploy this
on production use the actual values to set them.
Also, we should make use of the API token instead of the using the plain passwords.
Thanks!!!
Deepti.