Merge lp:~linaro-infrastructure/linaro-ci-dashboard/models-design into lp:linaro-ci-dashboard

Proposed by Stevan Radaković
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
Reviewer Review Type Date Requested Status
Данило Шеган (community) Approve
Deepti B. Kalakeri (community) Needs Fixing
Review via email: mp+114819@code.launchpad.net

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.

To post a comment you must log in.
Revision history for this message
Deepti B. Kalakeri (deeptik) wrote :

Overall changes looks good. Some suggestion here:

An important field of the Build record should be a build number.
so we need to make bnum = models.Integer(PrimaryKey=True) as part of the JenkinsBuild Model.

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.

review: Needs Fixing
29. By Stevan Radaković

Add lib/* to .bzrignore.

Revision history for this message
Данило Шеган (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/JenkinsBuild, I believe we do not really need them: having a Loop and LoopBuild classes in the frontend should be enough. They should not really depend on the Jenkins classes, except for the minimal global interface (like "run a build", "get build results", "is build finished", etc.).

review: Needs Fixing
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.

Revision history for this message
Данило Шеган (danilo) wrote :

Thanks for all the improvements: looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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:

Subscribers

People subscribed via source and target branches

to all changes: