Merge lp:~stylesen/lava-dashboard/support-test-definitions into lp:lava-dashboard

Proposed by Senthil Kumaran S
Status: Merged
Merged at revision: 401
Proposed branch: lp:~stylesen/lava-dashboard/support-test-definitions
Merge into: lp:lava-dashboard
Diff against target: 690 lines (+557/-0)
10 files modified
dashboard_app/admin.py (+5/-0)
dashboard_app/extension.py (+1/-0)
dashboard_app/helpers.py (+46/-0)
dashboard_app/migrations/0029_auto__add_testdefinition.py (+281/-0)
dashboard_app/models.py (+76/-0)
dashboard_app/templates/dashboard_app/add_test_definition.html (+25/-0)
dashboard_app/templates/dashboard_app/test_definition.html (+23/-0)
dashboard_app/urls.py (+3/-0)
dashboard_app/views/__init__.py (+51/-0)
dashboard_app/xmlrpc.py (+46/-0)
To merge this branch: bzr merge lp:~stylesen/lava-dashboard/support-test-definitions
Reviewer Review Type Date Requested Status
Antonio Terceiro Approve
Zygmunt Krynicki (community) Needs Fixing
Linaro Validation Team Pending
Linaro Validation Team Pending
Senthil Kumaran S Pending
Review via email: mp+145137@code.launchpad.net

Description of the change

This code adds the following:

1) New model for representing test definitions in database.
2) Basic UI to list and add test definition (will be enhanced next month).
3) XML-RPC api to get test definitions list.

To post a comment you must log in.
Revision history for this message
Zygmunt Krynicki (zyga) wrote :

405 + description = models.CharField(
406 + max_length = 256,
407 + verbose_name = _("Description"),
408 + help_text = _help_max_length(256))

Don't you want to make that a TextField so that length limit is no longer imposed?

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

715 + if os == device == environment == None:
716 + for testdef in TestDefinition.objects.all():
717 + testdefs[testdef.testdef_name] = testdef.url

Comparison to None should be done with 'is'

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

this code:

395 + testdef_name = models.CharField(
396 + max_length = 128,
397 + verbose_name = _("Name"),
398 + help_text = _help_max_length(64))

combined with this code:

703 + if os:
704 + for testdef in TestDefinition.objects.filter(
705 + target_os__contains=os):
706 + testdefs[testdef.testdef_name] = testdef.url
707 + if device:
708 + for testdef in TestDefinition.objects.filter(
709 + target_dev_types__contains=device):
710 + testdefs[testdef.testdef_name] = testdef.url
711 + if environment:
712 + for testdef in TestDefinition.objects.filter(
713 + testdef_environment__contains=environment):
714 + testdefs[testdef.testdef_name] = testdef.url

Is broken,

you need either to make testdef_name unique() or use the real key

review: Needs Fixing
Revision history for this message
Andy Doan (doanac) wrote :

On 01/28/2013 03:35 AM, Senthil Kumaran S wrote:
> === modified file 'dashboard_app/models.py'

> +class TestDefinition(models.Model):

A general comment for all fields. Many of the "max_length" fields don't
match their "_help_max_length" value. I suspect that's a copy/paste
error you made?

> + testdef_name = models.CharField(
> + max_length = 128,
> + verbose_name = _("Name"),
> + help_text = _help_max_length(64))
> +
> + version = models.CharField(
> + max_length=256,
> + verbose_name = _("Version"),
> + help_text = _help_max_length(64))

> + testdef_format = models.CharField(
> + max_length = 128,
> + verbose_name = _("Format"),
> + help_text = _help_max_length(64))

> + url = models.CharField(
> + verbose_name = _(u"URL"),
> + max_length = 512,
> + blank = False,
> + help_text = _help_max_length(64))

> + target_os = models.CharField(
> + max_length = 512,
> + verbose_name = _("Operating Systems"),
> + help_text = _help_max_length(64))

Shouldn't this be an array?

> + target_dev_types = models.CharField(
> + max_length = 512,
> + verbose_name = _("Device types"),
> + help_text = _help_max_length(64))

Shouldn't this be an array?

Revision history for this message
Andy Doan (doanac) wrote :

On 01/28/2013 03:35 AM, Senthil Kumaran S wrote:
> === added file 'dashboard_app/templates/dashboard_app/test_definition.html'

> +{% block extrahead %}
> +<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}lava-server/css/demo_table_jui.css"/>
> +<script type="text/javascript" src="{{ STATIC_URL }}lava-server/js/jquery.dataTables.min.js"></script>
> +{% endblock %}

> +{% block content %}
> + <script type="text/javascript" src=".../jquery.min.js">
> + </script>
> + <script type="text/javascript" src=".../jquery.dataTables.min.js">
> + </script>

I don't think you need this here, because its already in the "extrahead"
block.

> + {% load django_tables2 %}

I forget, but this might not be the proper place for this.

> + {% render_table testdefinition_table %}
> +{% endblock %}

Revision history for this message
Andy Doan (doanac) wrote :

On 01/28/2013 03:35 AM, Senthil Kumaran S wrote:
> === modified file 'dashboard_app/views/__init__.py'

> +class TestDefinitionTable(DataTablesTable):
> + testdef_name = Column()
> + testdef_location = Column()
> + description = Column()
> + def get_queryset(self):
> + return TestDefinition.objects.all()
> +
> +

Trying to visualize this in my head... We aren't showing all the test
definition fields like os and device-types. Is this okay - ie should we
just add a drill-down view that shows a single test definition?

Revision history for this message
Andy Doan (doanac) wrote :

On 01/28/2013 03:35 AM, Senthil Kumaran S wrote:
> === modified file 'dashboard_app/xmlrpc.py'

> + testdefs = {}
> + if os:
> + for testdef in TestDefinition.objects.filter(
> + target_os__contains=os):
> + testdefs[testdef.testdef_name] = testdef.url
> + if device:
> + for testdef in TestDefinition.objects.filter(
> + target_dev_types__contains=device):
> + testdefs[testdef.testdef_name] = testdef.url
> + if environment:
> + for testdef in TestDefinition.objects.filter(
> + testdef_environment__contains=environment):
> + testdefs[testdef.testdef_name] = testdef.url
> + if os == device == environment == None:
> + for testdef in TestDefinition.objects.all():
> + testdefs[testdef.testdef_name] = testdef.url
> + return testdefs
> +

I think this can be simplified like:

         testdefs = {}
         tds = TestDefinition.objects.all()
         if os:
             tds = tds.filter(target_os__contains=os)

         if device:
             tds = tds.filter(target_dev_types__contains=device)

         if environment:
             tds = tds.filter(testdef_environment__contains=environment)

         for testdef in tds:
             testdefs[testdef.testdef_name] = testdef.url
         return testdefs

Revision history for this message
Senthil Kumaran S (stylesen) wrote :

On Monday 28 January 2013 11:03 PM, Andy Doan wrote:
> Shouldn't this be an array?
>
>> + target_dev_types = models.CharField(
>> + max_length = 512,
>> + verbose_name = _("Device types"),
>> + help_text = _help_max_length(64))
>
> Shouldn't this be an array?

Since they are not on separate columns, it gives me better control on
filtering, hence I made those as strings.

Thank You.
--
Senthil Kumaran S
http://www.stylesen.org/
http://www.sasenthilkumaran.com/

Revision history for this message
Senthil Kumaran S (stylesen) wrote :

On Monday 28 January 2013 11:10 PM, Andy Doan wrote:
>> +class TestDefinitionTable(DataTablesTable):
>> + testdef_name = Column()
>> + testdef_location = Column()
>> + description = Column()
>> + def get_queryset(self):
>> + return TestDefinition.objects.all()
>> +
>> +
>
> Trying to visualize this in my head... We aren't showing all the test
> definition fields like os and device-types. Is this okay - ie should we
> just add a drill-down view that shows a single test definition?

Should I take it up along with the test case management UI improvements
BP? ;)

Thank You.
--
Senthil Kumaran S
http://www.stylesen.org/
http://www.sasenthilkumaran.com/

397. By Senthil Kumaran S

Implement all review comments from zyga and doanac.

398. By Senthil Kumaran S

Make the testdef name column bigger in models. Also fix a typo in referring
a dictionary key.

Revision history for this message
Senthil Kumaran S (stylesen) wrote :

Hi zyga and doanac,

I implemented all the review comments made by you and pushed the latest code at r398. I request you to go through the code once again and let me know if I can merge it to trunk.

Revision history for this message
Andy Doan (doanac) wrote :

On 01/29, Senthil Kumaran S wrote:
> Should I take it up along with the test case management UI improvements
> BP? ;)

That's fine with me.

Revision history for this message
Andy Doan (doanac) wrote :

On 01/28, Senthil Kumaran S wrote:
> On Monday 28 January 2013 11:03 PM, Andy Doan wrote:
> > Shouldn't this be an array?
> >
> >> + target_dev_types = models.CharField(
> >> + max_length = 512,
> >> + verbose_name = _("Device types"),
> >> + help_text = _help_max_length(64))
> >
> > Shouldn't this be an array?
>
> Since they are not on separate columns, it gives me better control on
> filtering, hence I made those as strings.

I don't think this approach will play very nice when we start to make
try and query the database. ie - you'll be mixing SQL and some type of
regular expession.

For device types, we have an easy solution - these are defined in the
lava-scheduler. I'm not sure the answer for operating systems. Michael
mentioned postgres has an array type. Not sure if that would work here.
Worst, case we create an OperatingSystem's table and pre-populate it in
our south migration with Android, Ubuntu, OpenEmbedded

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

17 + pass

Remove this, it's useless

60 + testdef_meta = {'testdef_name': c_test_id,
61 + 'version': version_info,
62 + 'description': c_testdef_metadata["description"],
63 + 'testdef_format': c_testdef_metadata["format"],
64 + 'testdef_location': c_testdef_metadata["location"],
65 + 'url': c_testdef_metadata["url"],
66 + 'testdef_environment':
67 + c_testdef_metadata["environment"],
68 + 'target_os': c_testdef_metadata["os"],
69 + 'target_dev_types': c_testdef_metadata["devices"],
70 + }

This is not PEP-8ish.

I don't care much but I found tools like flake8 invaluable for finding actual issues but stuff like this gets just like noise.

+ 'description': c_testdef_metadata["description"],
63 + 'testdef_format': c_testdef_metadata["format"],
64 + 'testdef_location': c_testdef_metadata["location"],
65 + 'url': c_testdef_metadata["url"],
66 + 'testdef_environment':
67 + c_testdef_metadata["environment"],
68 + 'target_os': c_testdef_metadata["os"],
69 + 'target_dev_types': c_testdef_metadata["devices"],

There is zero validation against a schema so I could upload something without any of those to bork the upload.

72 + s_testdef = TestDefinition.objects.filter(testdef_name = c_test_id)

I'd use get_or_create and use the second return argument and use it to update the stuff if needed. This is not critical thought.

429 +
430 + url = models.CharField(
431 + verbose_name = _(u"URL"),
432 + max_length = 1024,
433 + blank = False,
434 + help_text = _help_max_length(1024))

What should be the value of url if for LOCAL test definitions? Blank prevents this from being empty (at django validation level)

683 + `get_test_definitions` ([`os`[, `device`[, `environment`]]])

I'm not entirely sure, correct me if I'm wrong, but XML-RPC does not support optional arguments, does it?

692 + The type of operating system the retrieved test definitions should
693 + apply to.
694 +
695 + `device`: string
696 + The type of device the retrieved test definitions should apply to.
697 +
698 + `environment`: string
699 + The type of test environment the retrieved test definitions should
700 + apply to.

None of the docstrings describe that a substring search is being performed.

710 + if os:
711 + tds = tds.filter(target_os__contains=os)
712 +
713 + if device:
714 + tds = tds.filter(target_dev_types__contains=device)
715 +
716 + if environment:
717 + tds = tds.filter(testdef_environment__contains=environment)

You may want to check django docs on how subsequent .filter() are applied. Basically document if you want OR or AND behavior and make sure you really do what you want.

719 + for testdef in tds:
720 + testdefs[testdef.testdef_name] = testdef.url

This is probably wasting a lot of SQL. You can implement the same with .values() and that just pick testdef_name and url and return those for each record. It's not much but you are really pulling in all of the data to get two fields out.

Thanks for fixing the model testdef_name uniqueness.

399. By Senthil Kumaran S

Evolve bundle format to 1.6

400. By Senthil Kumaran S

Take care of zyga's review comments.

401. By Senthil Kumaran S

Bring up-to-date with trunk.

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

76 + s_testdef, testdef_created = TestDefinition.objects.get_or_create(
77 + **testdef_meta)
78 +
79 + if testdef_created:
80 + s_testdef.save()
81 + else:
82 + # Do not try to update testdef_name since it is unique, hence
83 + # pop it from the dictionary.
84 + testdef_meta.pop('testdef_name', None)
85 + TestDefinition.objects.filter(
86 + testdef_name=c_test_id).update(**testdef_meta)

I think this is wrong. Basically testdef_created will always be false unless you pass in the exact same data twice. The idea behind get_or_create() is to pass the primary key / some unique key only. Otherwise you'll keep trying to add rows that will create uniqueness failures at the db level.

Also, this shows that either I totally don't know what I'm talking about or you have no tests at all.

I see no reaction (discussion, code) to URL + local problem, xml-rpc api docs, implementation or anything else? Did I miss something?

Revision history for this message
Senthil Kumaran S (stylesen) wrote :

On Friday 01 February 2013 03:46 AM, Zygmunt Krynicki wrote:
> 76 + s_testdef, testdef_created = TestDefinition.objects.get_or_create(
> 77 + **testdef_meta)
> 78 +
> 79 + if testdef_created:
> 80 + s_testdef.save()
> 81 + else:
> 82 + # Do not try to update testdef_name since it is unique, hence
> 83 + # pop it from the dictionary.
> 84 + testdef_meta.pop('testdef_name', None)
> 85 + TestDefinition.objects.filter(
> 86 + testdef_name=c_test_id).update(**testdef_meta)
>
> I think this is wrong. Basically testdef_created will always be false unless you pass in the exact same data twice. The idea behind get_or_create() is to pass the primary key / some unique key only. Otherwise you'll keep trying to add rows that will create uniqueness failures at the db level.

I hope I am doing the right thing here. I try to create an object, if it
fails due to primary key constraint, I will update the same object with
new values for different columns.

> Also, this shows that either I totally don't know what I'm talking about or you have no tests at all.

I tested it many times and it works fine for me.

> I see no reaction (discussion, code) to URL + local problem, xml-rpc api docs, implementation or anything else? Did I miss something?

Yesterday I was busy with some other BP, so I did not comment on this. I
shall let you know once I have addressed all your concerns.

Regarding the URL + local problem - local will be added via the web UI
manually by the user, when he/she does that, the local URL will be
calculated based on the server name and the path within the server where
the testdef contents will be stored. This is work in progress, but in
order to give way for this implementation, I made the field not blank.

Thank You.
--
Senthil Kumaran S
http://www.stylesen.org/
http://www.sasenthilkumaran.com/

402. By Senthil Kumaran S

Bring up-to-date with trunk.

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :
Download full text (18.6 KiB)

Senthil Kumaran S <email address hidden> writes:

> You have been requested to review the proposed merge of lp:~stylesen/lava-dashboard/support-test-definitions into lp:lava-dashboard.
>
> For more details, see:
> https://code.launchpad.net/~stylesen/lava-dashboard/support-test-definitions/+merge/145137
>
> This code adds the following:
>
> 1) New model for representing test definitions in database.
> 2) Basic UI to list and add test definition (will be enhanced next month).
> 3) XML-RPC api to get test definitions list.
>
>

> === modified file 'dashboard_app/admin.py'
> --- dashboard_app/admin.py 2013-01-11 16:25:54 +0000
> +++ dashboard_app/admin.py 2013-02-26 04:15:25 +0000
> @@ -47,6 +47,7 @@
> TestRunFilter,
> TestRunFilterAttribute,
> TestRunFilterSubscription,
> + TestDefinition,
> )
>
>
> @@ -203,6 +204,9 @@
> save_as = True
>
>
> +class TestDefinitionAdmin(admin.ModelAdmin):
> + list_display = ('testdef_name', )

I'm fairly sure that ('testdef_name', 'version') would be a more useful
choice here. Although... the whole issue of testdef versions is a best
messed up as I rant about below.

> admin.site.register(Attachment)
> admin.site.register(Bundle, BundleAdmin)
> admin.site.register(BundleDeserializationError, BundleDeserializationErrorAdmin)
> @@ -221,3 +225,4 @@
> admin.site.register(TestRunFilter, TestRunFilterAdmin)
> admin.site.register(TestRunFilterSubscription)
> admin.site.register(Tag)
> +admin.site.register(TestDefinition, TestDefinitionAdmin)
>
> === modified file 'dashboard_app/extension.py'
> --- dashboard_app/extension.py 2013-01-31 02:52:51 +0000
> +++ dashboard_app/extension.py 2013-02-26 04:15:25 +0000
> @@ -49,6 +49,7 @@
> subm.append(Menu("Data Views", reverse("dashboard_app.views.data_view_list")))
> if not settings.DATAREPORTS_HIDE:
> subm.append(Menu("Reports", reverse("dashboard_app.views.report_list")))
> + subm.append(Menu("Test Definition", reverse("dashboard_app.views.test_definition")))

It seems most other menu items use the plural. So this should probably
be "Test Definitions".

> return menu
>
>
> === modified file 'dashboard_app/helpers.py'
> --- dashboard_app/helpers.py 2012-11-16 01:01:22 +0000
> +++ dashboard_app/helpers.py 2013-02-26 04:15:25 +0000
> @@ -742,6 +742,56 @@
> self._import_test_result_attachments(c_test_result, s_test_result)
>
>
> +class BundleFormatImporter_1_6(BundleFormatImporter_1_5):
> + """
> + IFormatImporter subclass capable of loading "Dashboard Bundle Format 1.6"
> + """
> +
> + def _import_testdef(self, c_test_id, c_testdef_metadata):
> + """
> + Import dashboard_app.models.TestDefinition into the database
> + based on a client-side description of a TestRun metadata.
> + """
> + from dashboard_app.models import TestDefinition
> +
> + if c_testdef_metadata.get('repo_rev'):
> + version_info = c_testdef_metadata["repo_rev"]
> + else:
> + version_info = c_testdef_metadata["version"]
> +
> + testdef_meta = {
> + 'testdef_name': c_test_id,
> + 've...

403. By Senthil Kumaran S

Bring up-to-date with trunk.

404. By Senthil Kumaran S

Make simple changes as suggested by mwhudson.

405. By Senthil Kumaran S

Chnage the insertion logic for test definition which was previously wrong.

406. By Senthil Kumaran S

Use @BreadCrumb properly and avoid including title block in template.

407. By Senthil Kumaran S

Use form.as_table to display add test definition form.

408. By Senthil Kumaran S

Remove explicitly refering to feild names in add test definition form.

409. By Senthil Kumaran S

Change to plural naming convention for test definitions.

Revision history for this message
Antonio Terceiro (terceiro) wrote :
Download full text (6.7 KiB)

Hello Senthil,

Follows my commments.

 review needs-fixing

> === modified file 'dashboard_app/admin.py'
> --- dashboard_app/admin.py 2013-01-11 16:25:54 +0000
> +++ dashboard_app/admin.py 2013-03-26 13:53:49 +0000
> @@ -47,6 +47,7 @@
> TestRunFilter,
> TestRunFilterAttribute,
> TestRunFilterSubscription,
> + TestDefinitions,
> )

I don't understand why you changed the class name to the plural.

I saw Michael's comments about plural, but I think he meant to just use
plural in the manu label, not in the class name. :-)

> @@ -203,6 +204,9 @@
> save_as = True
>
>
> +class TestDefinitionAdmin(admin.ModelAdmin):
> + list_display = ('testdef_name', 'version')
> +
> admin.site.register(Attachment)
> admin.site.register(Bundle, BundleAdmin)
> admin.site.register(BundleDeserializationError, BundleDeserializationErrorAdmin)
> @@ -221,3 +225,4 @@
> admin.site.register(TestRunFilter, TestRunFilterAdmin)
> admin.site.register(TestRunFilterSubscription)
> admin.site.register(Tag)
> +admin.site.register(TestDefinitions, TestDefinitionAdmin)
>
> === modified file 'dashboard_app/extension.py'
> --- dashboard_app/extension.py 2013-01-31 02:52:51 +0000
> +++ dashboard_app/extension.py 2013-03-26 13:53:49 +0000
> @@ -49,6 +49,7 @@
> subm.append(Menu("Data Views", reverse("dashboard_app.views.data_view_list")))
> if not settings.DATAREPORTS_HIDE:
> subm.append(Menu("Reports", reverse("dashboard_app.views.report_list")))
> + subm.append(Menu("Test Definitions", reverse("dashboard_app.views.test_definition")))
>
> return menu
>
>
> === modified file 'dashboard_app/helpers.py'
> --- dashboard_app/helpers.py 2012-11-16 01:01:22 +0000
> +++ dashboard_app/helpers.py 2013-03-26 13:53:49 +0000
> @@ -8,6 +8,7 @@
> import time
>
> from django.core.files.base import ContentFile
> +from django.core.exceptions import ObjectDoesNotExist
> from django.db import connection, transaction, IntegrityError
> from linaro_dashboard_bundle.errors import DocumentFormatError
> from linaro_dashboard_bundle.evolution import DocumentEvolution
> @@ -742,6 +743,51 @@
> self._import_test_result_attachments(c_test_result, s_test_result)
>
>
> +class BundleFormatImporter_1_6(BundleFormatImporter_1_5):
> + """
> + IFormatImporter subclass capable of loading "Dashboard Bundle Format 1.6"
> + """
> +
> + def _import_testdef(self, c_test_id, c_testdef_metadata):
> + """
> + Import dashboard_app.models.TestDefinitions into the database
> + based on a client-side description of a TestRun metadata.
> + """
> + from dashboard_app.models import TestDefinitions
> +
> + testdef_meta = {
> + 'testdef_name': c_test_id,
> + 'version': c_testdef_metadata.get("version"),
> + 'description': c_testdef_metadata.get("description"),
> + 'testdef_format': c_testdef_metadata.get("format"),
> + 'testdef_location': c_testdef_metadata.get("location"),
> + 'url': c_testdef_metadata.get("url"),
> + 'testdef_environment': c_testdef_metadata.get("environment"),
> + ...

Read more...

review: Needs Fixing
410. By Senthil Kumaran S

Remove plural class name for Test Definition.

Suggested by: terceiro

411. By Senthil Kumaran S

Fix testdef_ prefix in model member naming.

412. By Senthil Kumaran S

Change the database updation logic to make minimal queries. Also push the
script for testdefinition table migration which was missed in previous
commits.

Revision history for this message
Senthil Kumaran S (stylesen) wrote :

Hi Antonio,

> Follows my commments.
>
> review needs-fixing

I fixed all the stuff pointed by you in your review comments. Would request you to go through the new difference and approve this branch.

--
Senthil Kumaran
http://www.stylesen.org/
http://www.sasenthilkumaran.com/

413. By Senthil Kumaran S

Get the object first to properly update test definition metadata, else
the logic is flawed.

Revision history for this message
Antonio Terceiro (terceiro) wrote :

Almost there. :)

 review needs-fixing

> === modified file 'dashboard_app/xmlrpc.py'
> --- dashboard_app/xmlrpc.py 2013-01-15 00:30:25 +0000
> +++ dashboard_app/xmlrpc.py 2013-04-01 10:18:24 +0000
> @@ -43,6 +43,7 @@
> DataView,
> Test,
> TestRunFilter,
> + TestDefinition,
> )
>
>
> @@ -436,7 +437,7 @@
> """
> test_names = []
> if device_type:
> - for test in Test.objects.filter(
> + for test in Testobjects.filter(

typo

--
Antonio Terceiro
Software Engineer - Linaro
http://www.linaro.org

review: Needs Fixing
414. By Senthil Kumaran S

Fix a typo that was accidental.

Revision history for this message
Senthil Kumaran S (stylesen) wrote :

Hi Antonio,

On Wed, 2013-04-03 at 23:43 +0000, Antonio Terceiro wrote:
> > === modified file 'dashboard_app/xmlrpc.py'
> > --- dashboard_app/xmlrpc.py 2013-01-15 00:30:25 +0000
> > +++ dashboard_app/xmlrpc.py 2013-04-01 10:18:24 +0000
> > @@ -43,6 +43,7 @@
> > DataView,
> > Test,
> > TestRunFilter,
> > + TestDefinition,
> > )
> >
> >
> > @@ -436,7 +437,7 @@
> > """
> > test_names = []
> > if device_type:
> > - for test in Test.objects.filter(
> > + for test in Testobjects.filter(
>
> typo

Thanks for catching it. This is unrelated to my change, but I hope I did
some formatting tweaks which touched this line and by mistake I made
that typo. Anyways, I corrected it in r414.

Thank You.

--
Senthil Kumaran
http://www.stylesen.org/
http://www.sasenthilkumaran.com/

Revision history for this message
Antonio Terceiro (terceiro) wrote :

I think we are done with this one.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'dashboard_app/admin.py'
2--- dashboard_app/admin.py 2013-01-11 16:25:54 +0000
3+++ dashboard_app/admin.py 2013-04-04 08:19:22 +0000
4@@ -47,6 +47,7 @@
5 TestRunFilter,
6 TestRunFilterAttribute,
7 TestRunFilterSubscription,
8+ TestDefinition,
9 )
10
11
12@@ -203,6 +204,9 @@
13 save_as = True
14
15
16+class TestDefinitionAdmin(admin.ModelAdmin):
17+ list_display = ('name', 'version')
18+
19 admin.site.register(Attachment)
20 admin.site.register(Bundle, BundleAdmin)
21 admin.site.register(BundleDeserializationError, BundleDeserializationErrorAdmin)
22@@ -221,3 +225,4 @@
23 admin.site.register(TestRunFilter, TestRunFilterAdmin)
24 admin.site.register(TestRunFilterSubscription)
25 admin.site.register(Tag)
26+admin.site.register(TestDefinition, TestDefinitionAdmin)
27
28=== modified file 'dashboard_app/extension.py'
29--- dashboard_app/extension.py 2013-01-31 02:52:51 +0000
30+++ dashboard_app/extension.py 2013-04-04 08:19:22 +0000
31@@ -49,6 +49,7 @@
32 subm.append(Menu("Data Views", reverse("dashboard_app.views.data_view_list")))
33 if not settings.DATAREPORTS_HIDE:
34 subm.append(Menu("Reports", reverse("dashboard_app.views.report_list")))
35+ subm.append(Menu("Test Definitions", reverse("dashboard_app.views.test_definition")))
36
37 return menu
38
39
40=== modified file 'dashboard_app/helpers.py'
41--- dashboard_app/helpers.py 2012-11-16 01:01:22 +0000
42+++ dashboard_app/helpers.py 2013-04-04 08:19:22 +0000
43@@ -8,6 +8,7 @@
44 import time
45
46 from django.core.files.base import ContentFile
47+from django.core.exceptions import ObjectDoesNotExist
48 from django.db import connection, transaction, IntegrityError
49 from linaro_dashboard_bundle.errors import DocumentFormatError
50 from linaro_dashboard_bundle.evolution import DocumentEvolution
51@@ -742,6 +743,50 @@
52 self._import_test_result_attachments(c_test_result, s_test_result)
53
54
55+class BundleFormatImporter_1_6(BundleFormatImporter_1_5):
56+ """
57+ IFormatImporter subclass capable of loading "Dashboard Bundle Format 1.6"
58+ """
59+
60+ def _import_testdef(self, c_test_id, c_testdef_metadata):
61+ """
62+ Import dashboard_app.models.TestDefinition into the database
63+ based on a client-side description of a TestRun metadata.
64+ """
65+ from dashboard_app.models import TestDefinition
66+
67+ testdef_meta = {
68+ 'name': c_test_id,
69+ 'version': c_testdef_metadata.get("version"),
70+ 'description': c_testdef_metadata.get("description"),
71+ 'format': c_testdef_metadata.get("format"),
72+ 'location': c_testdef_metadata.get("location"),
73+ 'url': c_testdef_metadata.get("url"),
74+ 'environment': c_testdef_metadata.get("environment"),
75+ 'target_os': c_testdef_metadata.get("os"),
76+ 'target_dev_types': c_testdef_metadata.get("devices"),
77+ }
78+
79+ try:
80+ s_testdef = TestDefinition.objects.get(name=c_test_id)
81+ # Do not try to update name since it is unique, hence
82+ # pop it from the dictionary.
83+ testdef_meta.pop('name', None)
84+ TestDefinition.objects.filter(name=c_test_id).update(
85+ **testdef_meta)
86+ except ObjectDoesNotExist:
87+ s_testdef = TestDefinition.objects.create(**testdef_meta)
88+ s_testdef.save()
89+
90+ def _import_test_results(self, c_test_run, s_test_run):
91+ from dashboard_app.models import TestResult
92+ super(BundleFormatImporter_1_6, self)._import_test_results(c_test_run,
93+ s_test_run)
94+ if c_test_run.get("testdef_metadata"):
95+ self._import_testdef(c_test_run["test_id"],
96+ c_test_run["testdef_metadata"])
97+
98+
99 class BundleDeserializer(object):
100 """
101 Helper class for de-serializing JSON bundle content into database models
102@@ -755,6 +800,7 @@
103 "Dashboard Bundle Format 1.3": BundleFormatImporter_1_3,
104 "Dashboard Bundle Format 1.4": BundleFormatImporter_1_4,
105 "Dashboard Bundle Format 1.5": BundleFormatImporter_1_5,
106+ "Dashboard Bundle Format 1.6": BundleFormatImporter_1_6,
107 }
108
109 def deserialize(self, s_bundle, prefer_evolution):
110
111=== added file 'dashboard_app/migrations/0029_auto__add_testdefinition.py'
112--- dashboard_app/migrations/0029_auto__add_testdefinition.py 1970-01-01 00:00:00 +0000
113+++ dashboard_app/migrations/0029_auto__add_testdefinition.py 2013-04-04 08:19:22 +0000
114@@ -0,0 +1,281 @@
115+# -*- coding: utf-8 -*-
116+import datetime
117+from south.db import db
118+from south.v2 import SchemaMigration
119+from django.db import models
120+
121+
122+class Migration(SchemaMigration):
123+
124+ def forwards(self, orm):
125+ # Adding model 'TestDefinition'
126+ db.create_table('dashboard_app_testdefinition', (
127+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
128+ ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=512)),
129+ ('version', self.gf('django.db.models.fields.CharField')(max_length=256)),
130+ ('description', self.gf('django.db.models.fields.TextField')()),
131+ ('format', self.gf('django.db.models.fields.CharField')(max_length=128)),
132+ ('location', self.gf('django.db.models.fields.CharField')(default='LOCAL', max_length=64)),
133+ ('url', self.gf('django.db.models.fields.CharField')(max_length=1024)),
134+ ('environment', self.gf('django.db.models.fields.CharField')(max_length=256)),
135+ ('target_os', self.gf('django.db.models.fields.CharField')(max_length=512)),
136+ ('target_dev_types', self.gf('django.db.models.fields.CharField')(max_length=512)),
137+ ('content', self.gf('django.db.models.fields.files.FileField')(max_length=100, null=True, blank=True)),
138+ ('mime_type', self.gf('django.db.models.fields.CharField')(default='text/plain', max_length=64)),
139+ ))
140+ db.send_create_signal('dashboard_app', ['TestDefinition'])
141+
142+
143+ def backwards(self, orm):
144+ # Deleting model 'TestDefinition'
145+ db.delete_table('dashboard_app_testdefinition')
146+
147+
148+ models = {
149+ 'auth.group': {
150+ 'Meta': {'object_name': 'Group'},
151+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
152+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
153+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
154+ },
155+ 'auth.permission': {
156+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
157+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
158+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
159+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
160+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
161+ },
162+ 'auth.user': {
163+ 'Meta': {'object_name': 'User'},
164+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
165+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
166+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
167+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
168+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
169+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
170+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
171+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
172+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
173+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
174+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
175+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
176+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
177+ },
178+ 'contenttypes.contenttype': {
179+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
180+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
181+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
182+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
183+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
184+ },
185+ 'dashboard_app.attachment': {
186+ 'Meta': {'object_name': 'Attachment'},
187+ 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
188+ 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
189+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
190+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
191+ 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
192+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
193+ 'public_url': ('django.db.models.fields.URLField', [], {'max_length': '512', 'blank': 'True'})
194+ },
195+ 'dashboard_app.bundle': {
196+ 'Meta': {'ordering': "['-uploaded_on']", 'object_name': 'Bundle'},
197+ '_gz_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'gz_content'"}),
198+ '_raw_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'content'"}),
199+ 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}),
200+ 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
201+ 'content_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'unique': 'True', 'null': 'True'}),
202+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
203+ 'is_deserialized': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
204+ 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'uploaded_bundles'", 'null': 'True', 'to': "orm['auth.User']"}),
205+ 'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
206+ },
207+ 'dashboard_app.bundledeserializationerror': {
208+ 'Meta': {'object_name': 'BundleDeserializationError'},
209+ 'bundle': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'deserialization_error'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.Bundle']"}),
210+ 'error_message': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
211+ 'traceback': ('django.db.models.fields.TextField', [], {'max_length': '32768'})
212+ },
213+ 'dashboard_app.bundlestream': {
214+ 'Meta': {'object_name': 'BundleStream'},
215+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
216+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
217+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
218+ 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
219+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
220+ 'pathname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
221+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
222+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
223+ },
224+ 'dashboard_app.hardwaredevice': {
225+ 'Meta': {'object_name': 'HardwareDevice'},
226+ 'description': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
227+ 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
228+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
229+ },
230+ 'dashboard_app.image': {
231+ 'Meta': {'object_name': 'Image'},
232+ 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['dashboard_app.TestRunFilter']"}),
233+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
234+ 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'})
235+ },
236+ 'dashboard_app.imageset': {
237+ 'Meta': {'object_name': 'ImageSet'},
238+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
239+ 'images': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.Image']", 'symmetrical': 'False'}),
240+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'})
241+ },
242+ 'dashboard_app.launchpadbug': {
243+ 'Meta': {'object_name': 'LaunchpadBug'},
244+ 'bug_id': ('django.db.models.fields.PositiveIntegerField', [], {'unique': 'True'}),
245+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
246+ 'test_runs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'launchpad_bugs'", 'symmetrical': 'False', 'to': "orm['dashboard_app.TestRun']"})
247+ },
248+ 'dashboard_app.namedattribute': {
249+ 'Meta': {'unique_together': "(('object_id', 'name'),)", 'object_name': 'NamedAttribute'},
250+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
251+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
252+ 'name': ('django.db.models.fields.TextField', [], {}),
253+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
254+ 'value': ('django.db.models.fields.TextField', [], {})
255+ },
256+ 'dashboard_app.pmqabundlestream': {
257+ 'Meta': {'object_name': 'PMQABundleStream'},
258+ 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.BundleStream']"}),
259+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
260+ },
261+ 'dashboard_app.softwarepackage': {
262+ 'Meta': {'unique_together': "(('name', 'version'),)", 'object_name': 'SoftwarePackage'},
263+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
264+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
265+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'})
266+ },
267+ 'dashboard_app.softwarepackagescratch': {
268+ 'Meta': {'object_name': 'SoftwarePackageScratch'},
269+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
270+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
271+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'})
272+ },
273+ 'dashboard_app.softwaresource': {
274+ 'Meta': {'object_name': 'SoftwareSource'},
275+ 'branch_revision': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
276+ 'branch_url': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
277+ 'branch_vcs': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
278+ 'commit_timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
279+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
280+ 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '32'})
281+ },
282+ 'dashboard_app.tag': {
283+ 'Meta': {'object_name': 'Tag'},
284+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
285+ 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'})
286+ },
287+ 'dashboard_app.test': {
288+ 'Meta': {'object_name': 'Test'},
289+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
290+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
291+ 'test_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'})
292+ },
293+ 'dashboard_app.testcase': {
294+ 'Meta': {'unique_together': "(('test', 'test_case_id'),)", 'object_name': 'TestCase'},
295+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
296+ 'name': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
297+ 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_cases'", 'to': "orm['dashboard_app.Test']"}),
298+ 'test_case_id': ('django.db.models.fields.TextField', [], {}),
299+ 'units': ('django.db.models.fields.TextField', [], {'blank': 'True'})
300+ },
301+ 'dashboard_app.testdefinition': {
302+ 'Meta': {'object_name': 'TestDefinition'},
303+ 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
304+ 'description': ('django.db.models.fields.TextField', [], {}),
305+ 'environment': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
306+ 'format': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
307+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
308+ 'location': ('django.db.models.fields.CharField', [], {'default': "'LOCAL'", 'max_length': '64'}),
309+ 'mime_type': ('django.db.models.fields.CharField', [], {'default': "'text/plain'", 'max_length': '64'}),
310+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
311+ 'target_dev_types': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
312+ 'target_os': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
313+ 'url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
314+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '256'})
315+ },
316+ 'dashboard_app.testresult': {
317+ 'Meta': {'ordering': "('_order',)", 'object_name': 'TestResult'},
318+ '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
319+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
320+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
321+ 'lineno': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
322+ 'measurement': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '20', 'decimal_places': '10', 'blank': 'True'}),
323+ 'message': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
324+ 'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}),
325+ 'relative_index': ('django.db.models.fields.PositiveIntegerField', [], {}),
326+ 'result': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
327+ 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'test_results'", 'null': 'True', 'to': "orm['dashboard_app.TestCase']"}),
328+ 'test_run': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_results'", 'to': "orm['dashboard_app.TestRun']"}),
329+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
330+ },
331+ 'dashboard_app.testrun': {
332+ 'Meta': {'ordering': "['-import_assigned_date']", 'object_name': 'TestRun'},
333+ 'analyzer_assigned_date': ('django.db.models.fields.DateTimeField', [], {}),
334+ 'analyzer_assigned_uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}),
335+ 'bundle': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Bundle']"}),
336+ 'devices': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.HardwareDevice']"}),
337+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
338+ 'import_assigned_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
339+ 'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}),
340+ 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwarePackage']"}),
341+ 'sources': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwareSource']"}),
342+ 'sw_image_desc': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
343+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.Tag']"}),
344+ 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Test']"}),
345+ 'time_check_performed': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
346+ },
347+ 'dashboard_app.testrundenormalization': {
348+ 'Meta': {'object_name': 'TestRunDenormalization'},
349+ 'count_fail': ('django.db.models.fields.PositiveIntegerField', [], {}),
350+ 'count_pass': ('django.db.models.fields.PositiveIntegerField', [], {}),
351+ 'count_skip': ('django.db.models.fields.PositiveIntegerField', [], {}),
352+ 'count_unknown': ('django.db.models.fields.PositiveIntegerField', [], {}),
353+ 'test_run': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'denormalization'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.TestRun']"})
354+ },
355+ 'dashboard_app.testrunfilter': {
356+ 'Meta': {'unique_together': "(('owner', 'name'),)", 'object_name': 'TestRunFilter'},
357+ 'build_number_attribute': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
358+ 'bundle_streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.BundleStream']", 'symmetrical': 'False'}),
359+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
360+ 'name': ('django.db.models.fields.SlugField', [], {'max_length': '1024'}),
361+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
362+ 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
363+ 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['auth.User']"})
364+ },
365+ 'dashboard_app.testrunfilterattribute': {
366+ 'Meta': {'object_name': 'TestRunFilterAttribute'},
367+ 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attributes'", 'to': "orm['dashboard_app.TestRunFilter']"}),
368+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
369+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
370+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
371+ },
372+ 'dashboard_app.testrunfiltersubscription': {
373+ 'Meta': {'unique_together': "(('user', 'filter'),)", 'object_name': 'TestRunFilterSubscription'},
374+ 'filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestRunFilter']"}),
375+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
376+ 'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
377+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
378+ },
379+ 'dashboard_app.testrunfiltertest': {
380+ 'Meta': {'object_name': 'TestRunFilterTest'},
381+ 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tests'", 'to': "orm['dashboard_app.TestRunFilter']"}),
382+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
383+ 'index': ('django.db.models.fields.PositiveIntegerField', [], {}),
384+ 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.Test']"})
385+ },
386+ 'dashboard_app.testrunfiltertestcase': {
387+ 'Meta': {'object_name': 'TestRunFilterTestCase'},
388+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
389+ 'index': ('django.db.models.fields.PositiveIntegerField', [], {}),
390+ 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'cases'", 'to': "orm['dashboard_app.TestRunFilterTest']"}),
391+ 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.TestCase']"})
392+ }
393+ }
394+
395+ complete_apps = ['dashboard_app']
396\ No newline at end of file
397
398=== modified file 'dashboard_app/models.py'
399--- dashboard_app/models.py 2013-02-07 22:30:46 +0000
400+++ dashboard_app/models.py 2013-04-04 08:19:22 +0000
401@@ -701,6 +701,82 @@
402 return self.test_results.filter(result=TestResult.RESULT_FAIL).count()
403
404
405+class TestDefinition(models.Model):
406+ """
407+ Model for representing test definitions.
408+
409+ Test Definition are in YAML format.
410+ """
411+ LOCATION_CHOICES = (
412+ ('LOCAL', 'Local'),
413+ ('URL', 'URL'),
414+ ('GIT', 'GIT Repo'),
415+ ('BZR', 'BZR Repo'),
416+ )
417+
418+ name = models.CharField(
419+ max_length = 512,
420+ verbose_name = _("Name"),
421+ unique = True,
422+ help_text = _help_max_length(512))
423+
424+ version = models.CharField(
425+ max_length=256,
426+ verbose_name = _("Version"),
427+ help_text = _help_max_length(256))
428+
429+ description = models.TextField(
430+ verbose_name = _("Description"))
431+
432+ format = models.CharField(
433+ max_length = 128,
434+ verbose_name = _("Format"),
435+ help_text = _help_max_length(128))
436+
437+ location = models.CharField(
438+ max_length = 64,
439+ verbose_name = _("Location"),
440+ choices = LOCATION_CHOICES,
441+ default = 'LOCAL')
442+
443+ url = models.CharField(
444+ verbose_name = _(u"URL"),
445+ max_length = 1024,
446+ blank = False,
447+ help_text = _help_max_length(1024))
448+
449+ environment = models.CharField(
450+ max_length = 256,
451+ verbose_name = _("Environment"),
452+ help_text = _help_max_length(256))
453+
454+ target_os = models.CharField(
455+ max_length = 512,
456+ verbose_name = _("Operating Systems"),
457+ help_text = _help_max_length(512))
458+
459+ target_dev_types = models.CharField(
460+ max_length = 512,
461+ verbose_name = _("Device types"),
462+ help_text = _help_max_length(512))
463+
464+ content = models.FileField(
465+ verbose_name = _(u"Upload Test Definition"),
466+ help_text = _(u"Test definition file"),
467+ upload_to = 'testdef',
468+ blank = True,
469+ null = True)
470+
471+ mime_type = models.CharField(
472+ verbose_name = _(u"MIME type"),
473+ default = 'text/plain',
474+ max_length = 64,
475+ help_text = _help_max_length(64))
476+
477+ def __unicode__(self):
478+ return self.name
479+
480+
481 class SoftwareSource(models.Model):
482 """
483 Model for representing source reference of a particular project
484
485=== added file 'dashboard_app/templates/dashboard_app/add_test_definition.html'
486--- dashboard_app/templates/dashboard_app/add_test_definition.html 1970-01-01 00:00:00 +0000
487+++ dashboard_app/templates/dashboard_app/add_test_definition.html 2013-04-04 08:19:22 +0000
488@@ -0,0 +1,25 @@
489+{% extends "dashboard_app/_content_with_sidebar.html" %}
490+{% load i18n %}
491+{% load stylize %}
492+
493+{% block extrahead %}
494+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}lava-server/css/demo_table_jui.css"/>
495+{% endblock %}
496+
497+{% block sidebar %}
498+<h3>Actions</h3>
499+<ul>
500+ <li><a href="{% url dashboard_app.views.test_definition %}">
501+ List test definitions</a></li>
502+ <li><a href="{% url dashboard_app.views.add_test_definition %}">
503+ Add test definition</a></li>
504+</ul>
505+{% endblock %}
506+
507+{% block content %}
508+<form method="post" action="">
509+ {% csrf_token %}
510+ <table>{{ form.as_table }}</table>
511+ <input type="submit" value="Save"/>
512+</form>
513+{% endblock %}
514
515=== added file 'dashboard_app/templates/dashboard_app/test_definition.html'
516--- dashboard_app/templates/dashboard_app/test_definition.html 1970-01-01 00:00:00 +0000
517+++ dashboard_app/templates/dashboard_app/test_definition.html 2013-04-04 08:19:22 +0000
518@@ -0,0 +1,23 @@
519+{% extends "dashboard_app/_content_with_sidebar.html" %}
520+{% load i18n %}
521+{% load stylize %}
522+{% load django_tables2 %}
523+
524+{% block extrahead %}
525+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}lava-server/css/demo_table_jui.css"/>
526+<script type="text/javascript" src="{{ STATIC_URL }}lava-server/js/jquery.dataTables.min.js"></script>
527+{% endblock %}
528+
529+{% block sidebar %}
530+<h3>Actions</h3>
531+<ul>
532+ <li><a href="{% url dashboard_app.views.test_definition %}">
533+ List test definitions</a></li>
534+ <li><a href="{% url dashboard_app.views.add_test_definition %}">
535+ Add test definition</a></li>
536+</ul>
537+{% endblock %}
538+
539+{% block content %}
540+ {% render_table testdefinition_table %}
541+{% endblock %}
542
543=== modified file 'dashboard_app/urls.py'
544--- dashboard_app/urls.py 2013-01-11 17:07:32 +0000
545+++ dashboard_app/urls.py 2013-04-04 08:19:22 +0000
546@@ -70,4 +70,7 @@
547 url(r'^image-reports/(?P<name>[A-Za-z0-9_-]+)$', 'images.image_report_detail'),
548 url(r'^api/link-bug-to-testrun', 'images.link_bug_to_testrun'),
549 url(r'^api/unlink-bug-and-testrun', 'images.unlink_bug_and_testrun'),
550+ url(r'^test-definition/add_test_definition', 'add_test_definition'),
551+ url(r'^test-definition/$', 'test_definition'),
552+ url(r'^testdefinition_table_json$', 'testdefinition_table_json'),
553 )
554
555=== modified file 'dashboard_app/views/__init__.py'
556--- dashboard_app/views/__init__.py 2013-01-11 16:25:54 +0000
557+++ dashboard_app/views/__init__.py 2013-04-04 08:19:22 +0000
558@@ -40,6 +40,8 @@
559 from django.template import RequestContext, loader
560 from django.utils.safestring import mark_safe
561 from django.views.generic.list_detail import object_list, object_detail
562+from django.forms import ModelForm
563+from django import forms
564
565 from django_tables2 import Attrs, Column, TemplateColumn
566
567@@ -60,6 +62,7 @@
568 Test,
569 TestResult,
570 TestRun,
571+ TestDefinition,
572 )
573
574
575@@ -673,3 +676,51 @@
576 request.user,
577 content_sha1=content_sha1)
578 return redirect_to(request, bundle, trailing)
579+
580+
581+class TestDefinitionTable(DataTablesTable):
582+ name = Column()
583+ version = Column()
584+ location = Column()
585+ description = Column()
586+ def get_queryset(self):
587+ return TestDefinition.objects.all()
588+
589+
590+def testdefinition_table_json(request):
591+ return TestDefinitionTable.json(request)
592+
593+
594+@BreadCrumb("Test Definitions", parent=index)
595+def test_definition(request):
596+ return render_to_response(
597+ "dashboard_app/test_definition.html", {
598+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(test_definition),
599+ "testdefinition_table": TestDefinitionTable(
600+ 'testdeflist',
601+ reverse(testdefinition_table_json))
602+ }, RequestContext(request))
603+
604+
605+class AddTestDefForm(ModelForm):
606+ class Meta:
607+ model = TestDefinition
608+ fields = ('name', 'version', 'description', 'format', 'location',
609+ 'url', 'environment', 'target_os', 'target_dev_types',
610+ 'content', 'mime_type')
611+
612+@BreadCrumb("Add Test Definition", parent=index)
613+def add_test_definition(request):
614+ if request.method == 'POST':
615+ form = AddTestDefForm(request.POST)
616+ if form.is_valid():
617+ form.save()
618+ return HttpResponseRedirect('/dashboard/test-definition/')
619+ else:
620+ form = AddTestDefForm()
621+ return render_to_response(
622+ "dashboard_app/add_test_definition.html", {
623+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
624+ add_test_definition),
625+ "form": form,
626+ }, RequestContext(request))
627
628=== modified file 'dashboard_app/xmlrpc.py'
629--- dashboard_app/xmlrpc.py 2013-01-15 00:30:25 +0000
630+++ dashboard_app/xmlrpc.py 2013-04-04 08:19:22 +0000
631@@ -43,6 +43,7 @@
632 DataView,
633 Test,
634 TestRunFilter,
635+ TestDefinition,
636 )
637
638
639@@ -882,6 +883,51 @@
640 matches = matches[:100]
641 return [match.serializable() for match in matches]
642
643+ @xml_rpc_signature('str')
644+ def get_test_definitions(self, os=None, device=None, environment=None):
645+ """
646+ Name
647+ ----
648+ `get_test_definitions` ([`os`[, `device`[, `environment`]]])
649+
650+ Description
651+ -----------
652+ Get the name and url of all the test definitions.
653+
654+ Arguments
655+ ---------
656+ `os`: string
657+ The type of operating system the retrieved test definitions should
658+ apply to.
659+
660+ `device`: string
661+ The type of device the retrieved test definitions should apply to.
662+
663+ `environment`: string
664+ The type of test environment the retrieved test definitions should
665+ apply to.
666+
667+ Return value
668+ ------------
669+ This function returns an XML-RPC structure of test definition name and
670+ URL where the test definition exists.
671+ """
672+ testdefs = {}
673+ tds = TestDefinition.objects.all()
674+
675+ if os:
676+ tds = tds.filter(target_os__contains=os)
677+
678+ if device:
679+ tds = tds.filter(target_dev_types__contains=device)
680+
681+ if environment:
682+ tds = tds.filter(environment__contains=environment)
683+
684+ for testdef in tds:
685+ testdefs[testdef.name] = testdef.url
686+ return testdefs
687+
688 # Mapper used by the legacy URL
689 legacy_mapper = Mapper()
690 legacy_mapper.register_introspection_methods()

Subscribers

People subscribed via source and target branches