Merge lp:~arthur-she/lava-dashboard/fix-for-bug-1175597 into lp:lava-dashboard
- fix-for-bug-1175597
- Merge into trunk
Status: | Approved | ||||
---|---|---|---|---|---|
Approved by: | Stevan Radaković | ||||
Approved revision: | 408 | ||||
Proposed branch: | lp:~arthur-she/lava-dashboard/fix-for-bug-1175597 | ||||
Merge into: | lp:lava-dashboard | ||||
Diff against target: |
458 lines (+318/-13) 7 files modified
dashboard_app/filters.py (+7/-5) dashboard_app/migrations/0030_auto__add_equation_to_testrunfilterattribute.py (+270/-0) dashboard_app/models.py (+13/-2) dashboard_app/templates/dashboard_app/filter_form.html (+9/-0) dashboard_app/templates/dashboard_app/filter_preview.html (+1/-1) dashboard_app/templates/dashboard_app/filter_summary.html (+3/-3) dashboard_app/views/filters/forms.py (+15/-2) |
||||
To merge this branch: | bzr merge lp:~arthur-she/lava-dashboard/fix-for-bug-1175597 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Stevan Radaković | Approve | ||
Review via email: mp+167494@code.launchpad.net |
Commit message
Description of the change
Add "Equation" field (<=, ==, >=) to make the filter more flexible.
Stevan Radaković (stevanr) wrote : | # |
Sorry for the code paste, I hope it's clear what lines I'm referring to.
Stevan Radaković (stevanr) wrote : | # |
One additional comment, we can circumvent the equation map altogether, if the db values for equation field would be defined as 'lte', 'gte'.. to start with. In the frontend we keep the '<=', '>=' of course...
Arthur She (arthur-she) wrote : | # |
Thanks a lot, Stevan. It really helpful.
I'll modify it.
2013/6/5 Stevan Radaković <email address hidden>
> One additional comment, we can circumvent the equation map altogether, if
> the db values for equation field would be defined as 'lte', 'gte'.. to
> start with. In the frontend we keep the '<=', '>=' of course...
> --
>
> https:/
> You are the owner of lp:~arthur-she/lava-dashboard/fix-for-bug-1175597.
>
Arthur She (arthur-she) wrote : | # |
Hi Stevan,
I know the idea, if we can make it more generic it should be better.
As you mentioned, we can use a dictionary to map the equation "equation_map
= {"<=": "lte", ">=": "gte", ""==": ""}"
and use argument packing, but I still don't have idea how to associate them
together. Could you talk more..
And regarding the tests, Tyler suggested me to do such modification. Do you
have any other idea to approach it?
Thanks a lot.
Arthur
2013/6/5 Stevan Radaković <email address hidden>
> Review: Needs Fixing code
>
> Arthur, great work!
>
> Couple of comments:
>
> === modified file 'dashboard_
> --- dashboard_
> +++ dashboard_
> @@ -365,13 +365,24 @@
>
> content_type_id = ContentType.
>
> - for (name, value) in filter_
> + for (name, equation, value) in filter_
> # We punch through the generic relation abstraction here for 100x
> # better performance.
> - conditions.append(
> - models.
> - name=name, value=value, content_
> - ).values(
> + if equation == '>=':
> + conditions.append(
> + models.
> + name=name, value__gte=value,
> content_
> + ).values(
> + elif equation == '<=':
> + conditions.append(
> + models.
> + name=name, value__lte=value,
> content_
> + ).values(
> + else:
> + conditions.append(
> + models.
> + name=name, value=value,
> content_
> + ).values(
>
> I'd really like to see a more generic solution here... If we add more
> filter equations later, this code will have to be subjected to refactoring
> again and again. If we would have some kind of mapping like
> equation_map = {"<=": "lte" ....}
> we could use pyhon's argument packing, something like
> kwargs = {
> '{0}__{
> '{0}__{
> }
>
> NamedAttribute.
>
>
> Second thing are the tests. I know the test coverage in dashboard is
> scarce, but do we want to keep it that way?
> --
>
> https:/
> You are the owner of lp:~arthur-she/lava-dashboard/fix-for-bug-1175597.
>
Arthur She (arthur-she) wrote : | # |
Stevan, do you mean, use
equation = models.CharField(
instead of
equation = models.CharField(
in the class TestRunFilterAt
2013/6/5 Stevan Radaković <email address hidden>
> One additional comment, we can circumvent the equation map altogether, if
> the db values for equation field would be defined as 'lte', 'gte'.. to
> start with. In the frontend we keep the '<=', '>=' of course...
> --
>
> https:/
> You are the owner of lp:~arthur-she/lava-dashboard/fix-for-bug-1175597.
>
Stevan Radaković (stevanr) wrote : | # |
Exactly like that!
On 06/06/2013 11:07 AM, Arthur She wrote:
> Stevan, do you mean, use
> equation = models.CharField(
> choices=(
> ('lte', '<='),
> ('eq', '=='),
> ('gte', '>=')),
> max_length=3, default='==')
> instead of
> equation = models.CharField(
> choices=(
> ('<=', '<='),
> ('==', '=='),
> ('>=', '>=')),
> max_length=3, default='==')
> in the class TestRunFilterAt
>
>
>
>
>
> 2013/6/5 Stevan Radaković <email address hidden>
>
>> One additional comment, we can circumvent the equation map altogether, if
>> the db values for equation field would be defined as 'lte', 'gte'.. to
>> start with. In the frontend we keep the '<=', '>=' of course...
>> --
>>
>> https:/
>> You are the owner of lp:~arthur-she/lava-dashboard/fix-for-bug-1175597.
>>
--
Stevan Radaković | LAVA Engineer
Linaro.org <www.linaro.org> │ Open source software for ARM SoCs
Stevan Radaković (stevanr) wrote : | # |
> Hi Stevan,
> I know the idea, if we can make it more generic it should be better.
> As you mentioned, we can use a dictionary to map the equation "equation_map
> = {"<=": "lte", ">=": "gte", ""==": ""}"
> and use argument packing, but I still don't have idea how to associate them
> together. Could you talk more..
>
> And regarding the tests, Tyler suggested me to do such modification. Do you
> have any other idea to approach it?
>
> Thanks a lot.
> Arthur
>
You can take a look at the lp:linaro-ci-dashboard it's also a django application and has a lot of good example for model tests. If you need more info about it please feel free to ping me on irc
>
> 2013/6/5 Stevan Radaković <email address hidden>
>
> > Review: Needs Fixing code
> >
> > Arthur, great work!
> >
> > Couple of comments:
> >
> > === modified file 'dashboard_
> > --- dashboard_
> > +++ dashboard_
> > @@ -365,13 +365,24 @@
> >
> > content_type_id = ContentType.
> >
> > - for (name, value) in filter_
> > + for (name, equation, value) in filter_
> > # We punch through the generic relation abstraction here for 100x
> > # better performance.
> > - conditions.append(
> > - models.
> > - name=name, value=value, content_
> > - ).values(
> > + if equation == '>=':
> > + conditions.append(
> > + models.
> > + name=name, value__gte=value,
> > content_
> > + ).values(
> > + elif equation == '<=':
> > + conditions.append(
> > + models.
> > + name=name, value__lte=value,
> > content_
> > + ).values(
> > + else:
> > + conditions.append(
> > + models.
> > + name=name, value=value,
> > content_
> > + ).values(
> >
> > I'd really like to see a more generic solution here... If we add more
> > filter equations later, this code will have to be subjected to refactoring
> > again and again. If we would have some kind of mapping like
> > equation_map = {"<=": "lte" ....}
> > we could use pyhon's argument packing, something like
> > kwargs = {
> > '{0}__{
> > '{0}__{
> > }
> >
> > NamedAttribute.
> >
> >
> > Second thing are the tests. I know the test coverage in dashboard is
> > scarce, but do we want to keep it that way?
> > --
> >
> > https:/
> bug-1175597/+merge/167494
> > You are the owner of lp:~arthur-she/lava-dashboard/fix-for-bug-1175597.
> >
Arthur She (arthur-she) wrote : | # |
Thanks a lot, Stevan. :)
2013/6/6 Stevan Radaković <email address hidden>
> > Hi Stevan,
> > I know the idea, if we can make it more generic it should be better.
> > As you mentioned, we can use a dictionary to map the equation
> "equation_map
> > = {"<=": "lte", ">=": "gte", ""==": ""}"
> > and use argument packing, but I still don't have idea how to associate
> them
> > together. Could you talk more..
> >
> > And regarding the tests, Tyler suggested me to do such modification. Do
> you
> > have any other idea to approach it?
> >
> > Thanks a lot.
> > Arthur
> >
>
> You can take a look at the lp:linaro-ci-dashboard it's also a django
> application and has a lot of good example for model tests. If you need more
> info about it please feel free to ping me on irc
> >
> > 2013/6/5 Stevan Radaković <email address hidden>
> >
> > > Review: Needs Fixing code
> > >
> > > Arthur, great work!
> > >
> > > Couple of comments:
> > >
> > > === modified file 'dashboard_
> > > --- dashboard_
> > > +++ dashboard_
> > > @@ -365,13 +365,24 @@
> > >
> > > content_type_id = ContentType.
> > >
> > > - for (name, value) in filter_
> > > + for (name, equation, value) in filter_
> > > # We punch through the generic relation abstraction here for
> 100x
> > > # better performance.
> > > - conditions.append(
> > > - models.
> > > - name=name, value=value,
> content_
> > > - ).values(
> > > + if equation == '>=':
> > > + conditions.append(
> > > + models.
> > > + name=name, value__gte=value,
> > > content_
> > > + ).values(
> > > + elif equation == '<=':
> > > + conditions.append(
> > > + models.
> > > + name=name, value__lte=value,
> > > content_
> > > + ).values(
> > > + else:
> > > + conditions.append(
> > > + models.
> > > + name=name, value=value,
> > > content_
> > > + ).values(
> > >
> > > I'd really like to see a more generic solution here... If we add more
> > > filter equations later, this code will have to be subjected to
> refactoring
> > > again and again. If we would have some kind of mapping like
> > > equation_map = {"<=": "lte" ....}
> > > we could use pyhon's argument packing, something like
> > > kwargs = {
> > > '{0}__{
> > > '{0}__{
> > > }
> > >
> > > NamedAttribute.
> > >
> > >
> > > Second thing are the tests. I know the test coverage in dashboard is
> > > scarce, but do we want t...
Stevan Radaković (stevanr) wrote : | # |
Hey Arthur,
so instead of all the if/else business, code would look something like this (maybe not 100% accurate didn't try it in the codebase:)
kwargs = {
name: name,
'{0}
content_
}
conditions.
I may be missing some quotes but you'll figure it out :)
Arthur She (arthur-she) wrote : | # |
Thanks a lot for your code, Stevan.
I think I might misunderstand your meaning for the "tests".
I'll check the code again...
2013/6/7 Stevan Radaković <email address hidden>
> Hey Arthur,
>
> so instead of all the if/else business, code would look something like
> this (maybe not 100% accurate didn't try it in the codebase:)
>
> kwargs = {
> name: name,
> '{0}__{
> content_
> }
>
>
> conditions.
>
> I may be missing some quotes but you'll figure it out :)
> --
>
> https:/
> You are the owner of lp:~arthur-she/lava-dashboard/fix-for-bug-1175597.
>
- 406. By Arthur She
-
Modify it to make it more generic.
modified:
dashboard_app/filters. py
dashboard_app/migrations/ 0030_auto_ _add_equation_ to_testrunfilte rattribute. py
dashboard_app/models. py
dashboard_app/views/ filters/ forms.py - 407. By Arthur She
-
use display value instead of db value
modified:
dashboard_app/models. py
dashboard_app/templates/ dashboard_ app/filter_ summary. html
Stevan Radaković (stevanr) wrote : | # |
I've also noticed we're missing the "Attributes 'filter_name' <= 'value' " sting on the filter preview page, please add this as well.
- 408. By Arthur She
-
Show attributes on filter preview page.
modified:
dashboard_app/templates/ dashboard_ app/filter_ preview. html
dashboard_app/views/ filters/ forms.py
Arthur She (arthur-she) wrote : | # |
Thanks a lot for your help, I've modified it and pushed to the server.
2013/6/18 Stevan Radaković <email address hidden>
> I've also noticed we're missing the "Attributes 'filter_name' <= 'value' "
> sting on the filter preview page, please add this as well.
> --
>
> https:/
> You are the owner of lp:~arthur-she/lava-dashboard/fix-for-bug-1175597.
>
Stevan Radaković (stevanr) wrote : | # |
Ok this looks like good to go now.
Thanks Arthur.
Approve +1.
Arthur She (arthur-she) wrote : | # |
Thanks, Stevan :)
2013/6/24 Stevan Radaković <email address hidden>
> The proposal to merge lp:~arthur-she/lava-dashboard/fix-for-bug-1175597
> into lp:lava-dashboard has been updated.
>
> Status: Needs review => Approved
>
> For more details, see:
>
> https:/
> --
>
> https:/
> You are the owner of lp:~arthur-she/lava-dashboard/fix-for-bug-1175597.
>
Unmerged revisions
- 408. By Arthur She
-
Show attributes on filter preview page.
modified:
dashboard_app/templates/ dashboard_ app/filter_ preview. html
dashboard_app/views/ filters/ forms.py - 407. By Arthur She
-
use display value instead of db value
modified:
dashboard_app/models. py
dashboard_app/templates/ dashboard_ app/filter_ summary. html - 406. By Arthur She
-
Modify it to make it more generic.
modified:
dashboard_app/filters. py
dashboard_app/migrations/ 0030_auto_ _add_equation_ to_testrunfilte rattribute. py
dashboard_app/models. py
dashboard_app/views/ filters/ forms.py - 405. By Arthur She
-
fix for bug #1175597
Preview Diff
1 | === modified file 'dashboard_app/filters.py' |
2 | --- dashboard_app/filters.py 2013-01-09 00:15:10 +0000 |
3 | +++ dashboard_app/filters.py 2013-06-19 08:00:38 +0000 |
4 | @@ -365,13 +365,15 @@ |
5 | |
6 | content_type_id = ContentType.objects.get_for_model(TestRun).id |
7 | |
8 | - for (name, value) in filter_data['attributes']: |
9 | + for (name, equation, value) in filter_data['attributes']: |
10 | # We punch through the generic relation abstraction here for 100x |
11 | # better performance. |
12 | - conditions.append( |
13 | - models.Q(id__in=NamedAttribute.objects.filter( |
14 | - name=name, value=value, content_type_id=content_type_id |
15 | - ).values('object_id'))) |
16 | + kwargs = { |
17 | + 'name': name, |
18 | + '{0}__{1}'.format('value', equation): value, |
19 | + 'content_type_id': content_type_id |
20 | + } |
21 | + conditions.append(models.Q(id__in=NamedAttribute.objects.filter(**kwargs).values('object_id'))) |
22 | |
23 | test_condition = None |
24 | for test in filter_data.get('tests', []): |
25 | |
26 | === added file 'dashboard_app/migrations/0030_auto__add_equation_to_testrunfilterattribute.py' |
27 | --- dashboard_app/migrations/0030_auto__add_equation_to_testrunfilterattribute.py 1970-01-01 00:00:00 +0000 |
28 | +++ dashboard_app/migrations/0030_auto__add_equation_to_testrunfilterattribute.py 2013-06-19 08:00:38 +0000 |
29 | @@ -0,0 +1,270 @@ |
30 | +# -*- coding: utf-8 -*- |
31 | +import datetime |
32 | +from south.db import db |
33 | +from south.v2 import SchemaMigration |
34 | +from django.db import models |
35 | + |
36 | + |
37 | +class Migration(SchemaMigration): |
38 | + |
39 | + def forwards(self, orm): |
40 | + # Adding field 'TestRunFilterAttribute.equation' |
41 | + db.add_column('dashboard_app_testrunfilterattribute', 'equation', |
42 | + self.gf('django.db.models.fields.CharField')(default='exact', max_length=6), |
43 | + keep_default=False) |
44 | + |
45 | + |
46 | + def backwards(self, orm): |
47 | + # Deleting field 'TestRunFilterAttribute.equation' |
48 | + db.delete_column('dashboard_app_testrunfilterattribute', 'equation') |
49 | + |
50 | + |
51 | + models = { |
52 | + 'auth.group': { |
53 | + 'Meta': {'object_name': 'Group'}, |
54 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
55 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
56 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
57 | + }, |
58 | + 'auth.permission': { |
59 | + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
60 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
61 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
62 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
63 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
64 | + }, |
65 | + 'auth.user': { |
66 | + 'Meta': {'object_name': 'User'}, |
67 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
68 | + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), |
69 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
70 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
71 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
72 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
73 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
74 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
75 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
76 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
77 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
78 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
79 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
80 | + }, |
81 | + 'contenttypes.contenttype': { |
82 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
83 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
84 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
85 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
86 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
87 | + }, |
88 | + 'dashboard_app.attachment': { |
89 | + 'Meta': {'object_name': 'Attachment'}, |
90 | + 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}), |
91 | + 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}), |
92 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
93 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
94 | + 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), |
95 | + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), |
96 | + 'public_url': ('django.db.models.fields.URLField', [], {'max_length': '512', 'blank': 'True'}) |
97 | + }, |
98 | + 'dashboard_app.bundle': { |
99 | + 'Meta': {'ordering': "['-uploaded_on']", 'object_name': 'Bundle'}, |
100 | + '_gz_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'gz_content'"}), |
101 | + '_raw_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'content'"}), |
102 | + 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}), |
103 | + 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}), |
104 | + 'content_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'unique': 'True', 'null': 'True'}), |
105 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
106 | + 'is_deserialized': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
107 | + 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'uploaded_bundles'", 'null': 'True', 'to': "orm['auth.User']"}), |
108 | + 'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}) |
109 | + }, |
110 | + 'dashboard_app.bundledeserializationerror': { |
111 | + 'Meta': {'object_name': 'BundleDeserializationError'}, |
112 | + 'bundle': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'deserialization_error'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.Bundle']"}), |
113 | + 'error_message': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), |
114 | + 'traceback': ('django.db.models.fields.TextField', [], {'max_length': '32768'}) |
115 | + }, |
116 | + 'dashboard_app.bundlestream': { |
117 | + 'Meta': {'object_name': 'BundleStream'}, |
118 | + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), |
119 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
120 | + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
121 | + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
122 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), |
123 | + 'pathname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}), |
124 | + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), |
125 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) |
126 | + }, |
127 | + 'dashboard_app.hardwaredevice': { |
128 | + 'Meta': {'object_name': 'HardwareDevice'}, |
129 | + 'description': ('django.db.models.fields.CharField', [], {'max_length': '256'}), |
130 | + 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
131 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) |
132 | + }, |
133 | + 'dashboard_app.image': { |
134 | + 'Meta': {'object_name': 'Image'}, |
135 | + 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['dashboard_app.TestRunFilter']"}), |
136 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
137 | + 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'}) |
138 | + }, |
139 | + 'dashboard_app.imageset': { |
140 | + 'Meta': {'object_name': 'ImageSet'}, |
141 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
142 | + 'images': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.Image']", 'symmetrical': 'False'}), |
143 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'}) |
144 | + }, |
145 | + 'dashboard_app.launchpadbug': { |
146 | + 'Meta': {'object_name': 'LaunchpadBug'}, |
147 | + 'bug_id': ('django.db.models.fields.PositiveIntegerField', [], {'unique': 'True'}), |
148 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
149 | + 'test_runs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'launchpad_bugs'", 'symmetrical': 'False', 'to': "orm['dashboard_app.TestRun']"}) |
150 | + }, |
151 | + 'dashboard_app.namedattribute': { |
152 | + 'Meta': {'unique_together': "(('object_id', 'name'),)", 'object_name': 'NamedAttribute'}, |
153 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
154 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
155 | + 'name': ('django.db.models.fields.TextField', [], {}), |
156 | + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), |
157 | + 'value': ('django.db.models.fields.TextField', [], {}) |
158 | + }, |
159 | + 'dashboard_app.pmqabundlestream': { |
160 | + 'Meta': {'object_name': 'PMQABundleStream'}, |
161 | + 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.BundleStream']"}), |
162 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) |
163 | + }, |
164 | + 'dashboard_app.softwarepackage': { |
165 | + 'Meta': {'unique_together': "(('name', 'version'),)", 'object_name': 'SoftwarePackage'}, |
166 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
167 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
168 | + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) |
169 | + }, |
170 | + 'dashboard_app.softwarepackagescratch': { |
171 | + 'Meta': {'object_name': 'SoftwarePackageScratch'}, |
172 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
173 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
174 | + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) |
175 | + }, |
176 | + 'dashboard_app.softwaresource': { |
177 | + 'Meta': {'object_name': 'SoftwareSource'}, |
178 | + 'branch_revision': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
179 | + 'branch_url': ('django.db.models.fields.CharField', [], {'max_length': '256'}), |
180 | + 'branch_vcs': ('django.db.models.fields.CharField', [], {'max_length': '10'}), |
181 | + 'commit_timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), |
182 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
183 | + 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '32'}) |
184 | + }, |
185 | + 'dashboard_app.tag': { |
186 | + 'Meta': {'object_name': 'Tag'}, |
187 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
188 | + 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'}) |
189 | + }, |
190 | + 'dashboard_app.test': { |
191 | + 'Meta': {'object_name': 'Test'}, |
192 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
193 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}), |
194 | + 'test_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'}) |
195 | + }, |
196 | + 'dashboard_app.testcase': { |
197 | + 'Meta': {'unique_together': "(('test', 'test_case_id'),)", 'object_name': 'TestCase'}, |
198 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
199 | + 'name': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
200 | + 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_cases'", 'to': "orm['dashboard_app.Test']"}), |
201 | + 'test_case_id': ('django.db.models.fields.TextField', [], {}), |
202 | + 'units': ('django.db.models.fields.TextField', [], {'blank': 'True'}) |
203 | + }, |
204 | + 'dashboard_app.testdefinition': { |
205 | + 'Meta': {'object_name': 'TestDefinition'}, |
206 | + 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), |
207 | + 'description': ('django.db.models.fields.TextField', [], {}), |
208 | + 'environment': ('django.db.models.fields.CharField', [], {'max_length': '256'}), |
209 | + 'format': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
210 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
211 | + 'location': ('django.db.models.fields.CharField', [], {'default': "'LOCAL'", 'max_length': '64'}), |
212 | + 'mime_type': ('django.db.models.fields.CharField', [], {'default': "'text/plain'", 'max_length': '64'}), |
213 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}), |
214 | + 'target_dev_types': ('django.db.models.fields.CharField', [], {'max_length': '512'}), |
215 | + 'target_os': ('django.db.models.fields.CharField', [], {'max_length': '512'}), |
216 | + 'url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), |
217 | + 'version': ('django.db.models.fields.CharField', [], {'max_length': '256'}) |
218 | + }, |
219 | + 'dashboard_app.testresult': { |
220 | + 'Meta': {'ordering': "('_order',)", 'object_name': 'TestResult'}, |
221 | + '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
222 | + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), |
223 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
224 | + 'lineno': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), |
225 | + 'measurement': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '20', 'decimal_places': '10', 'blank': 'True'}), |
226 | + 'message': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), |
227 | + 'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}), |
228 | + 'relative_index': ('django.db.models.fields.PositiveIntegerField', [], {}), |
229 | + 'result': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), |
230 | + 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'test_results'", 'null': 'True', 'to': "orm['dashboard_app.TestCase']"}), |
231 | + 'test_run': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_results'", 'to': "orm['dashboard_app.TestRun']"}), |
232 | + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) |
233 | + }, |
234 | + 'dashboard_app.testrun': { |
235 | + 'Meta': {'ordering': "['-import_assigned_date']", 'object_name': 'TestRun'}, |
236 | + 'analyzer_assigned_date': ('django.db.models.fields.DateTimeField', [], {}), |
237 | + 'analyzer_assigned_uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}), |
238 | + 'bundle': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Bundle']"}), |
239 | + 'devices': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.HardwareDevice']"}), |
240 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
241 | + 'import_assigned_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
242 | + 'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}), |
243 | + 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwarePackage']"}), |
244 | + 'sources': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwareSource']"}), |
245 | + 'sw_image_desc': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), |
246 | + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.Tag']"}), |
247 | + 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Test']"}), |
248 | + 'time_check_performed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) |
249 | + }, |
250 | + 'dashboard_app.testrundenormalization': { |
251 | + 'Meta': {'object_name': 'TestRunDenormalization'}, |
252 | + 'count_fail': ('django.db.models.fields.PositiveIntegerField', [], {}), |
253 | + 'count_pass': ('django.db.models.fields.PositiveIntegerField', [], {}), |
254 | + 'count_skip': ('django.db.models.fields.PositiveIntegerField', [], {}), |
255 | + 'count_unknown': ('django.db.models.fields.PositiveIntegerField', [], {}), |
256 | + 'test_run': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'denormalization'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.TestRun']"}) |
257 | + }, |
258 | + 'dashboard_app.testrunfilter': { |
259 | + 'Meta': {'unique_together': "(('owner', 'name'),)", 'object_name': 'TestRunFilter'}, |
260 | + 'build_number_attribute': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), |
261 | + 'bundle_streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.BundleStream']", 'symmetrical': 'False'}), |
262 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
263 | + 'name': ('django.db.models.fields.SlugField', [], {'max_length': '1024'}), |
264 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), |
265 | + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
266 | + 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['auth.User']"}) |
267 | + }, |
268 | + 'dashboard_app.testrunfilterattribute': { |
269 | + 'Meta': {'object_name': 'TestRunFilterAttribute'}, |
270 | + 'equation': ('django.db.models.fields.CharField', [], {'default': "'exact'", 'max_length': '6'}), |
271 | + 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attributes'", 'to': "orm['dashboard_app.TestRunFilter']"}), |
272 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
273 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), |
274 | + 'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) |
275 | + }, |
276 | + 'dashboard_app.testrunfiltersubscription': { |
277 | + 'Meta': {'unique_together': "(('user', 'filter'),)", 'object_name': 'TestRunFilterSubscription'}, |
278 | + 'filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestRunFilter']"}), |
279 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
280 | + 'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
281 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) |
282 | + }, |
283 | + 'dashboard_app.testrunfiltertest': { |
284 | + 'Meta': {'object_name': 'TestRunFilterTest'}, |
285 | + 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tests'", 'to': "orm['dashboard_app.TestRunFilter']"}), |
286 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
287 | + 'index': ('django.db.models.fields.PositiveIntegerField', [], {}), |
288 | + 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.Test']"}) |
289 | + }, |
290 | + 'dashboard_app.testrunfiltertestcase': { |
291 | + 'Meta': {'object_name': 'TestRunFilterTestCase'}, |
292 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
293 | + 'index': ('django.db.models.fields.PositiveIntegerField', [], {}), |
294 | + 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'cases'", 'to': "orm['dashboard_app.TestRunFilterTest']"}), |
295 | + 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.TestCase']"}) |
296 | + } |
297 | + } |
298 | + |
299 | + complete_apps = ['dashboard_app'] |
300 | \ No newline at end of file |
301 | |
302 | === modified file 'dashboard_app/models.py' |
303 | --- dashboard_app/models.py 2013-04-01 08:50:05 +0000 |
304 | +++ dashboard_app/models.py 2013-06-19 08:00:38 +0000 |
305 | @@ -1566,11 +1566,17 @@ |
306 | |
307 | name = models.CharField(max_length=1024) |
308 | value = models.CharField(max_length=1024) |
309 | + equation = models.CharField( |
310 | + choices=( |
311 | + ('lte', '<='), |
312 | + ('exact', '=='), |
313 | + ('gte', '>=')), |
314 | + max_length=6, default='exact') |
315 | |
316 | filter = models.ForeignKey("TestRunFilter", related_name="attributes") |
317 | |
318 | def __unicode__(self): |
319 | - return '%s = %s' % (self.name, self.value) |
320 | + return '%s %s %s' % (self.name, self.get_equation_display(), self.value) |
321 | |
322 | |
323 | class TestRunFilterTest(models.Model): |
324 | @@ -1633,9 +1639,14 @@ |
325 | 'test': trftest.test, |
326 | 'test_cases': [trftestcase.test_case for trftestcase in trftest.cases.all().select_related('test_case')], |
327 | }) |
328 | + att = [] |
329 | + for a in self.attributes.all(): |
330 | + b = (a.name, a.get_equation_display(), a.value) |
331 | + att.append(b) |
332 | return { |
333 | 'bundle_streams': self.bundle_streams.all(), |
334 | - 'attributes': self.attributes.all().values_list('name', 'value'), |
335 | + 'attributes': self.attributes.all().values_list('name', 'equation', 'value'), |
336 | + 'attr_display': att, |
337 | 'tests': tests, |
338 | 'build_number_attribute': self.build_number_attribute, |
339 | 'uploaded_by': self.uploaded_by, |
340 | |
341 | === modified file 'dashboard_app/templates/dashboard_app/filter_form.html' |
342 | --- dashboard_app/templates/dashboard_app/filter_form.html 2012-09-19 22:56:03 +0000 |
343 | +++ dashboard_app/templates/dashboard_app/filter_form.html 2013-06-19 08:00:38 +0000 |
344 | @@ -42,6 +42,9 @@ |
345 | Name |
346 | </th> |
347 | <th> |
348 | + Equation |
349 | + </th> |
350 | + <th> |
351 | Value |
352 | </th> |
353 | </tr> |
354 | @@ -54,6 +57,9 @@ |
355 | {{ form.name }} |
356 | </td> |
357 | <td class="value"> |
358 | + {{ form.equation }} |
359 | + </td> |
360 | + <td class="value"> |
361 | {{ form.value }} |
362 | </td> |
363 | <td> |
364 | @@ -70,6 +76,9 @@ |
365 | <td class="name"> |
366 | {{ form.name }} |
367 | </td> |
368 | + <td class="equation"> |
369 | + {{ form.equation }} |
370 | + </td> |
371 | <td class="value"> |
372 | {{ form.value }} |
373 | </td> |
374 | |
375 | === modified file 'dashboard_app/templates/dashboard_app/filter_preview.html' |
376 | --- dashboard_app/templates/dashboard_app/filter_preview.html 2013-01-09 00:05:14 +0000 |
377 | +++ dashboard_app/templates/dashboard_app/filter_preview.html 2013-06-19 08:00:38 +0000 |
378 | @@ -14,7 +14,7 @@ |
379 | <h1>Previewing new filter “{{ form.name.value }}”</h1> |
380 | {% endif %} |
381 | |
382 | -{% include "dashboard_app/filter_summary.html" with summary_data=filter.as_data %} |
383 | +{% include "dashboard_app/filter_summary.html" with filter_data=form.as_data %} |
384 | |
385 | <p> |
386 | These are the results matched by your filter. |
387 | |
388 | === modified file 'dashboard_app/templates/dashboard_app/filter_summary.html' |
389 | --- dashboard_app/templates/dashboard_app/filter_summary.html 2012-12-12 23:49:01 +0000 |
390 | +++ dashboard_app/templates/dashboard_app/filter_summary.html 2013-06-19 08:00:38 +0000 |
391 | @@ -9,14 +9,14 @@ |
392 | {% endfor %} |
393 | </td> |
394 | </tr> |
395 | -{% if filter_data.attributes %} |
396 | +{% if filter_data.attr_display %} |
397 | <tr> |
398 | <th> |
399 | Attributes |
400 | </th> |
401 | <td> |
402 | - {% for a in filter_data.attributes %} |
403 | - {{ a.0 }} == {{ a.1 }} <br /> |
404 | + {% for a in filter_data.attr_display %} |
405 | + {{ a.0 }} {{ a.1 }} {{ a.2 }} <br /> |
406 | {% endfor %} |
407 | </td> |
408 | </tr> |
409 | |
410 | === modified file 'dashboard_app/views/filters/forms.py' |
411 | --- dashboard_app/views/filters/forms.py 2013-01-09 00:14:58 +0000 |
412 | +++ dashboard_app/views/filters/forms.py 2013-06-19 08:00:38 +0000 |
413 | @@ -63,6 +63,11 @@ |
414 | class AttributesForm(forms.Form): |
415 | name = forms.CharField(max_length=1024) |
416 | value = forms.CharField(max_length=1024) |
417 | + equation = forms.ChoiceField( |
418 | + choices=[ |
419 | + ('lte', '<='), |
420 | + ('exact', '=='), |
421 | + ('gte', '>=')]) |
422 | |
423 | AttributesFormSet = formset_factory(AttributesForm, extra=0) |
424 | |
425 | @@ -170,7 +175,7 @@ |
426 | if commit: |
427 | instance.attributes.all().delete() |
428 | for a in self.attributes_formset.cleaned_data: |
429 | - instance.attributes.create(name=a['name'], value=a['value']) |
430 | + instance.attributes.create(name=a['name'], equation=a['equation'], value=a['value']) |
431 | instance.tests.all().delete() |
432 | for i, test_form in enumerate(self.tests_formset.forms): |
433 | trf_test = instance.tests.create( |
434 | @@ -201,8 +206,15 @@ |
435 | tc_form.cleaned_data['test_case'] |
436 | for tc_form in form.test_case_formset] |
437 | }) |
438 | + att = [] |
439 | + for form in self.attributes_formset.forms: |
440 | + att.append(( |
441 | + form.cleaned_data['name'], |
442 | + dict(form.fields['equation'].choices)[form.cleaned_data['equation']], |
443 | + form.cleaned_data['value'])) |
444 | + data['attr_display'] = att |
445 | data['attributes'] = [ |
446 | - (d['name'], d['value']) for d in self.attributes_formset.cleaned_data] |
447 | + (d['name'], d['equation'], d['value']) for d in self.attributes_formset.cleaned_data] |
448 | data['tests'] = tests |
449 | data['uploaded_by'] = None |
450 | return data |
451 | @@ -218,6 +230,7 @@ |
452 | for attr in self.instance.attributes.all(): |
453 | initial.append({ |
454 | 'name': attr.name, |
455 | + 'equation': attr.equation, |
456 | 'value': attr.value, |
457 | }) |
458 | attr_set_args['initial'] = initial |
Arthur, great work!
Couple of comments:
=== modified file 'dashboard_ app/filters. py' app/filters. py 2013-01-09 00:15:10 +0000 app/filters. py 2013-06-05 10:06:54 +0000
--- dashboard_
+++ dashboard_
@@ -365,13 +365,24 @@
content_ type_id = ContentType. objects. get_for_ model(TestRun) .id
- for (name, value) in filter_ data['attribute s']: data['attribute s']: Q(id__in= NamedAttribute. objects. filter( type_id= content_ type_id 'object_ id'))) Q(id__in= NamedAttribute. objects. filter( type_id= content_ type_id 'object_ id'))) Q(id__in= NamedAttribute. objects. filter( type_id= content_ type_id 'object_ id'))) Q(id__in= NamedAttribute. objects. filter( type_id= content_ type_id 'object_ id')))
+ for (name, equation, value) in filter_
# We punch through the generic relation abstraction here for 100x
# better performance.
- conditions.append(
- models.
- name=name, value=value, content_
- ).values(
+ if equation == '>=':
+ conditions.append(
+ models.
+ name=name, value__gte=value, content_
+ ).values(
+ elif equation == '<=':
+ conditions.append(
+ models.
+ name=name, value__lte=value, content_
+ ).values(
+ else:
+ conditions.append(
+ models.
+ name=name, value=value, content_
+ ).values(
I'd really like to see a more generic solution here... If we add more filter equations later, this code will have to be subjected to refactoring again and again. If we would have some kind of mapping like __{1}'. format( 'value' , 'lte'): value, __{1}'. format( 'value' , 'gte'): value
equation_map = {"<=": "lte" ....}
we could use pyhon's argument packing, something like
kwargs = {
'{0}
'{0}
}
NamedAttribute. objects. filter( **kwargs)
Second thing are the tests. I know the test coverage in dashboard is scarce, but do we want to keep it that way?