changeset 333:1de0e15f60d6

Get field choices in corner cases: old now-invalid value_id; choices overlays
author Sylvain Beucler <beuc@beuc.net>
date Sun, 22 Aug 2010 21:10:41 +0200
parents 7234fcf49cc0
children 70f5630b1e1e
files doc/scripts/tracker_gendefs.py savane/tracker/defs.py savane/tracker/models.py
diffstat 3 files changed, 142 insertions(+), 88 deletions(-) [+]
line wrap: on
line diff
--- a/doc/scripts/tracker_gendefs.py
+++ b/doc/scripts/tracker_gendefs.py
@@ -18,7 +18,10 @@
     name = row[1]
     field_names.append(name)
     defs[name] = ''
-    defs[name] += "    '"+name+"' : {\n"
+    if name == 'field_name':
+        defs[name] += "    'name' : {\n"
+    else:
+        defs[name] += "    '"+name+"' : {\n"
     for i,val in enumerate(row):
         if i <= 0 \
                 or (complex_defs[name]['display_type'] not in ('TA', 'TF') and tfields[i] == 'display_size'):
--- a/savane/tracker/defs.py
+++ b/savane/tracker/defs.py
@@ -62,7 +62,7 @@
 
 field_defs = {
     'bug_id' : {
-        'field_name': 'bug_id',
+        'name': 'bug_id',
         'display_type': 'TF',
         'scope': 'S',
         'required': 1,
@@ -70,7 +70,7 @@
         'custom': 0,
     },
     'group_id' : {
-        'field_name': 'group_id',
+        'name': 'group_id',
         'display_type': 'TF',
         'scope': 'S',
         'required': 1,
@@ -78,7 +78,7 @@
         'custom': 0,
     },
     'submitted_by' : {
-        'field_name': 'submitted_by',
+        'name': 'submitted_by',
         'display_type': 'SB',
         'scope': 'S',
         'required': 1,
@@ -86,7 +86,7 @@
         'custom': 0,
     },
     'date' : {
-        'field_name': 'date',
+        'name': 'date',
         'display_type': 'DF',
         'scope': 'S',
         'required': 1,
@@ -94,7 +94,7 @@
         'custom': 0,
     },
     'close_date' : {
-        'field_name': 'close_date',
+        'name': 'close_date',
         'display_type': 'DF',
         'scope': 'S',
         'required': 1,
@@ -102,7 +102,7 @@
         'custom': 0,
     },
     'status_id' : {
-        'field_name': 'status_id',
+        'name': 'status_id',
         'display_type': 'SB',
         'scope': 'S',
         'required': 1,
@@ -110,7 +110,7 @@
         'custom': 0,
     },
     'severity' : {
-        'field_name': 'severity',
+        'name': 'severity',
         'display_type': 'SB',
         'scope': 'S',
         'required': 0,
@@ -118,7 +118,7 @@
         'custom': 0,
     },
     'category_id' : {
-        'field_name': 'category_id',
+        'name': 'category_id',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -126,7 +126,7 @@
         'custom': 0,
     },
     'assigned_to' : {
-        'field_name': 'assigned_to',
+        'name': 'assigned_to',
         'display_type': 'SB',
         'scope': 'S',
         'required': 1,
@@ -134,7 +134,7 @@
         'custom': 0,
     },
     'summary' : {
-        'field_name': 'summary',
+        'name': 'summary',
         'display_type': 'TF',
         'scope': 'S',
         'required': 1,
@@ -142,7 +142,7 @@
         'custom': 0,
     },
     'details' : {
-        'field_name': 'details',
+        'name': 'details',
         'display_type': 'TA',
         'scope': 'S',
         'required': 1,
@@ -150,7 +150,7 @@
         'custom': 0,
     },
     'bug_group_id' : {
-        'field_name': 'bug_group_id',
+        'name': 'bug_group_id',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -158,7 +158,7 @@
         'custom': 0,
     },
     'resolution_id' : {
-        'field_name': 'resolution_id',
+        'name': 'resolution_id',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -166,7 +166,7 @@
         'custom': 0,
     },
     'privacy' : {
-        'field_name': 'privacy',
+        'name': 'privacy',
         'display_type': 'SB',
         'scope': 'S',
         'required': 0,
@@ -174,7 +174,7 @@
         'custom': 0,
     },
     'vote' : {
-        'field_name': 'vote',
+        'name': 'vote',
         'display_type': 'TF',
         'scope': 'S',
         'required': 0,
@@ -182,7 +182,7 @@
         'custom': 0,
     },
     'category_version_id' : {
-        'field_name': 'category_version_id',
+        'name': 'category_version_id',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -190,7 +190,7 @@
         'custom': 0,
     },
     'platform_version_id' : {
-        'field_name': 'platform_version_id',
+        'name': 'platform_version_id',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -198,7 +198,7 @@
         'custom': 0,
     },
     'reproducibility_id' : {
-        'field_name': 'reproducibility_id',
+        'name': 'reproducibility_id',
         'display_type': 'SB',
         'scope': 'S',
         'required': 0,
@@ -206,7 +206,7 @@
         'custom': 0,
     },
     'size_id' : {
-        'field_name': 'size_id',
+        'name': 'size_id',
         'display_type': 'SB',
         'scope': 'S',
         'required': 0,
@@ -214,7 +214,7 @@
         'custom': 0,
     },
     'fix_release_id' : {
-        'field_name': 'fix_release_id',
+        'name': 'fix_release_id',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -222,7 +222,7 @@
         'custom': 0,
     },
     'comment_type_id' : {
-        'field_name': 'comment_type_id',
+        'name': 'comment_type_id',
         'display_type': 'SB',
         'scope': 'P',
         'required': 1,
@@ -230,7 +230,7 @@
         'custom': 0,
     },
     'hours' : {
-        'field_name': 'hours',
+        'name': 'hours',
         'display_type': 'TF',
         'scope': 'S',
         'required': 0,
@@ -238,7 +238,7 @@
         'custom': 0,
     },
     'plan_release_id' : {
-        'field_name': 'plan_release_id',
+        'name': 'plan_release_id',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -246,7 +246,7 @@
         'custom': 0,
     },
     'component_version' : {
-        'field_name': 'component_version',
+        'name': 'component_version',
         'display_type': 'TF',
         'scope': 'S',
         'required': 0,
@@ -254,7 +254,7 @@
         'custom': 0,
     },
     'fix_release' : {
-        'field_name': 'fix_release',
+        'name': 'fix_release',
         'display_type': 'TF',
         'scope': 'S',
         'required': 0,
@@ -262,7 +262,7 @@
         'custom': 0,
     },
     'plan_release' : {
-        'field_name': 'plan_release',
+        'name': 'plan_release',
         'display_type': 'TF',
         'scope': 'S',
         'required': 0,
@@ -270,7 +270,7 @@
         'custom': 0,
     },
     'priority' : {
-        'field_name': 'priority',
+        'name': 'priority',
         'display_type': 'SB',
         'scope': 'S',
         'required': 0,
@@ -278,7 +278,7 @@
         'custom': 0,
     },
     'keywords' : {
-        'field_name': 'keywords',
+        'name': 'keywords',
         'display_type': 'TF',
         'scope': 'S',
         'required': 0,
@@ -286,7 +286,7 @@
         'custom': 0,
     },
     'release_id' : {
-        'field_name': 'release_id',
+        'name': 'release_id',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -294,7 +294,7 @@
         'custom': 0,
     },
     'release' : {
-        'field_name': 'release',
+        'name': 'release',
         'display_type': 'TF',
         'scope': 'S',
         'required': 0,
@@ -302,7 +302,7 @@
         'custom': 0,
     },
     'originator_name' : {
-        'field_name': 'originator_name',
+        'name': 'originator_name',
         'display_type': 'TF',
         'scope': 'S',
         'required': 0,
@@ -310,7 +310,7 @@
         'custom': 0,
     },
     'originator_email' : {
-        'field_name': 'originator_email',
+        'name': 'originator_email',
         'display_type': 'TF',
         'scope': 'S',
         'required': 0,
@@ -318,7 +318,7 @@
         'custom': 0,
     },
     'originator_phone' : {
-        'field_name': 'originator_phone',
+        'name': 'originator_phone',
         'display_type': 'TF',
         'scope': 'S',
         'required': 0,
@@ -326,7 +326,7 @@
         'custom': 0,
     },
     'percent_complete' : {
-        'field_name': 'percent_complete',
+        'name': 'percent_complete',
         'display_type': 'SB',
         'scope': 'S',
         'required': 0,
@@ -334,7 +334,7 @@
         'custom': 0,
     },
     'custom_tf1' : {
-        'field_name': 'custom_tf1',
+        'name': 'custom_tf1',
         'display_type': 'TF',
         'scope': 'P',
         'required': 0,
@@ -342,7 +342,7 @@
         'custom': 1,
     },
     'custom_tf2' : {
-        'field_name': 'custom_tf2',
+        'name': 'custom_tf2',
         'display_type': 'TF',
         'scope': 'P',
         'required': 0,
@@ -350,7 +350,7 @@
         'custom': 1,
     },
     'custom_tf3' : {
-        'field_name': 'custom_tf3',
+        'name': 'custom_tf3',
         'display_type': 'TF',
         'scope': 'P',
         'required': 0,
@@ -358,7 +358,7 @@
         'custom': 1,
     },
     'custom_tf4' : {
-        'field_name': 'custom_tf4',
+        'name': 'custom_tf4',
         'display_type': 'TF',
         'scope': 'P',
         'required': 0,
@@ -366,7 +366,7 @@
         'custom': 1,
     },
     'custom_tf5' : {
-        'field_name': 'custom_tf5',
+        'name': 'custom_tf5',
         'display_type': 'TF',
         'scope': 'P',
         'required': 0,
@@ -374,7 +374,7 @@
         'custom': 1,
     },
     'custom_tf6' : {
-        'field_name': 'custom_tf6',
+        'name': 'custom_tf6',
         'display_type': 'TF',
         'scope': 'P',
         'required': 0,
@@ -382,7 +382,7 @@
         'custom': 1,
     },
     'custom_tf7' : {
-        'field_name': 'custom_tf7',
+        'name': 'custom_tf7',
         'display_type': 'TF',
         'scope': 'P',
         'required': 0,
@@ -390,7 +390,7 @@
         'custom': 1,
     },
     'custom_tf8' : {
-        'field_name': 'custom_tf8',
+        'name': 'custom_tf8',
         'display_type': 'TF',
         'scope': 'P',
         'required': 0,
@@ -398,7 +398,7 @@
         'custom': 1,
     },
     'custom_tf9' : {
-        'field_name': 'custom_tf9',
+        'name': 'custom_tf9',
         'display_type': 'TF',
         'scope': 'P',
         'required': 0,
@@ -406,7 +406,7 @@
         'custom': 1,
     },
     'custom_tf10' : {
-        'field_name': 'custom_tf10',
+        'name': 'custom_tf10',
         'display_type': 'TF',
         'scope': 'P',
         'required': 0,
@@ -414,7 +414,7 @@
         'custom': 1,
     },
     'custom_ta1' : {
-        'field_name': 'custom_ta1',
+        'name': 'custom_ta1',
         'display_type': 'TA',
         'scope': 'P',
         'required': 0,
@@ -422,7 +422,7 @@
         'custom': 1,
     },
     'custom_ta2' : {
-        'field_name': 'custom_ta2',
+        'name': 'custom_ta2',
         'display_type': 'TA',
         'scope': 'P',
         'required': 0,
@@ -430,7 +430,7 @@
         'custom': 1,
     },
     'custom_ta3' : {
-        'field_name': 'custom_ta3',
+        'name': 'custom_ta3',
         'display_type': 'TA',
         'scope': 'P',
         'required': 0,
@@ -438,7 +438,7 @@
         'custom': 1,
     },
     'custom_ta4' : {
-        'field_name': 'custom_ta4',
+        'name': 'custom_ta4',
         'display_type': 'TA',
         'scope': 'P',
         'required': 0,
@@ -446,7 +446,7 @@
         'custom': 1,
     },
     'custom_ta5' : {
-        'field_name': 'custom_ta5',
+        'name': 'custom_ta5',
         'display_type': 'TA',
         'scope': 'P',
         'required': 0,
@@ -454,7 +454,7 @@
         'custom': 1,
     },
     'custom_ta6' : {
-        'field_name': 'custom_ta6',
+        'name': 'custom_ta6',
         'display_type': 'TA',
         'scope': 'P',
         'required': 0,
@@ -462,7 +462,7 @@
         'custom': 1,
     },
     'custom_ta7' : {
-        'field_name': 'custom_ta7',
+        'name': 'custom_ta7',
         'display_type': 'TA',
         'scope': 'P',
         'required': 0,
@@ -470,7 +470,7 @@
         'custom': 1,
     },
     'custom_ta8' : {
-        'field_name': 'custom_ta8',
+        'name': 'custom_ta8',
         'display_type': 'TA',
         'scope': 'P',
         'required': 0,
@@ -478,7 +478,7 @@
         'custom': 1,
     },
     'custom_ta9' : {
-        'field_name': 'custom_ta9',
+        'name': 'custom_ta9',
         'display_type': 'TA',
         'scope': 'P',
         'required': 0,
@@ -486,7 +486,7 @@
         'custom': 1,
     },
     'custom_ta10' : {
-        'field_name': 'custom_ta10',
+        'name': 'custom_ta10',
         'display_type': 'TA',
         'scope': 'P',
         'required': 0,
@@ -494,7 +494,7 @@
         'custom': 1,
     },
     'custom_sb1' : {
-        'field_name': 'custom_sb1',
+        'name': 'custom_sb1',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -502,7 +502,7 @@
         'custom': 1,
     },
     'custom_sb2' : {
-        'field_name': 'custom_sb2',
+        'name': 'custom_sb2',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -510,7 +510,7 @@
         'custom': 1,
     },
     'custom_sb3' : {
-        'field_name': 'custom_sb3',
+        'name': 'custom_sb3',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -518,7 +518,7 @@
         'custom': 1,
     },
     'custom_sb4' : {
-        'field_name': 'custom_sb4',
+        'name': 'custom_sb4',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -526,7 +526,7 @@
         'custom': 1,
     },
     'custom_sb5' : {
-        'field_name': 'custom_sb5',
+        'name': 'custom_sb5',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -534,7 +534,7 @@
         'custom': 1,
     },
     'custom_sb6' : {
-        'field_name': 'custom_sb6',
+        'name': 'custom_sb6',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -542,7 +542,7 @@
         'custom': 1,
     },
     'custom_sb7' : {
-        'field_name': 'custom_sb7',
+        'name': 'custom_sb7',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -550,7 +550,7 @@
         'custom': 1,
     },
     'custom_sb8' : {
-        'field_name': 'custom_sb8',
+        'name': 'custom_sb8',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -558,7 +558,7 @@
         'custom': 1,
     },
     'custom_sb9' : {
-        'field_name': 'custom_sb9',
+        'name': 'custom_sb9',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -566,7 +566,7 @@
         'custom': 1,
     },
     'custom_sb10' : {
-        'field_name': 'custom_sb10',
+        'name': 'custom_sb10',
         'display_type': 'SB',
         'scope': 'P',
         'required': 0,
@@ -574,7 +574,7 @@
         'custom': 1,
     },
     'custom_df1' : {
-        'field_name': 'custom_df1',
+        'name': 'custom_df1',
         'display_type': 'DF',
         'scope': 'P',
         'required': 0,
@@ -582,7 +582,7 @@
         'custom': 1,
     },
     'custom_df2' : {
-        'field_name': 'custom_df2',
+        'name': 'custom_df2',
         'display_type': 'DF',
         'scope': 'P',
         'required': 0,
@@ -590,7 +590,7 @@
         'custom': 1,
     },
     'custom_df3' : {
-        'field_name': 'custom_df3',
+        'name': 'custom_df3',
         'display_type': 'DF',
         'scope': 'P',
         'required': 0,
@@ -598,7 +598,7 @@
         'custom': 1,
     },
     'custom_df4' : {
-        'field_name': 'custom_df4',
+        'name': 'custom_df4',
         'display_type': 'DF',
         'scope': 'P',
         'required': 0,
@@ -606,7 +606,7 @@
         'custom': 1,
     },
     'custom_df5' : {
-        'field_name': 'custom_df5',
+        'name': 'custom_df5',
         'display_type': 'DF',
         'scope': 'P',
         'required': 0,
@@ -614,7 +614,7 @@
         'custom': 1,
     },
     'discussion_lock' : {
-        'field_name': 'discussion_lock',
+        'name': 'discussion_lock',
         'display_type': 'SB',
         'scope': 'S',
         'required': 1,
@@ -622,7 +622,7 @@
         'custom': 0,
     },
     'planned_close_date' : {
-        'field_name': 'planned_close_date',
+        'name': 'planned_close_date',
         'display_type': 'DF',
         'scope': 'S',
         'required': 0,
@@ -630,7 +630,7 @@
         'custom': 0,
     },
     'planned_starting_date' : {
-        'field_name': 'planned_starting_date',
+        'name': 'planned_starting_date',
         'display_type': 'DF',
         'scope': 'S',
         'required': 0,
--- a/savane/tracker/models.py
+++ b/savane/tracker/models.py
@@ -17,6 +17,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from django.db import models
+from django.db.models import Q
 from django.utils.translation import ugettext, ugettext_lazy as _
 import django.contrib.auth.models as auth_models
 from django.utils.safestring import mark_safe
@@ -182,7 +183,7 @@
 
     # Can always be changed (expect for special 'summary' and 'details')
     empty_ok = models.CharField(max_length=1, choices=EMPTY_OK_CHOICES,
-                                       default='0', blank=True, null=True)
+                                default='0', blank=True, null=True)
 
     # Can always be changed
     rank = models.IntegerField(help_text=_("display rank"))
@@ -233,7 +234,7 @@
     STATUS_CHOICES = (('A', _('active')),
                       ('H', _('hidden')), # mask previously-active or system fields
                       ('P', _('permanent')),) # status cannot be modified, always visible
-    tracker = models.ForeignKey(Tracker)
+    tracker = models.ForeignKey(Tracker, blank=True, null=True)
     group = models.ForeignKey(auth_models.Group, blank=True, null=True,
                               help_text=_("NULL == default for all groups"))
     field = models.CharField(max_length=32)
@@ -258,23 +259,56 @@
             group_name = self.group.name
         return "%s.%s: %s (%d)" % (group_name, self.field, self.value, self.value_id)
 
-def field_get_values(tracker_id, group, field):
+##
+# Field
+##
+
+def field_get_values(tracker_id, group, field_def, cur_item_value_id=None):
     """
     Return all possible values for this select box field
     """
-    if field == 'assigned_to':
+    name = field_def['name']
+    if name == 'submitted_by':
+        # Not editable
+        return []
+    if name == 'assigned_to':
         # Hard-coded processing: it's a list of project members
-        default_values = [{'value_id' : -1, 'value' : _("None")}]
-        for user in group.user_set.order_by('username'):
-            default_values.append({'value_id' : user.pk, 'value' : user.username})
+        values = [{'value_id' : -1, 'value' : _("None")}]
+        pks = list(group.user_set.order_by('username').values_list('pk', flat=True))
+        # Add the current value if the user was previously part of the
+        # project and assigned this time
+        if cur_item_value_id not in pks:
+            pks.insert(0, cur_item_value_id)
+        for user in auth_models.User.objects.filter(pk__in=pks):
+            values.append({'value_id' : user.pk, 'value' : user.username})
     else:
-        #tracker_id=tracker_id, 
-        default_values = list(FieldChoice.objects \
-            .filter(group=None, field=field).exclude(status='H') \
+        values = list(FieldChoice.objects \
+            .filter(tracker=tracker_id, group=None, field=name) \
+            .filter(~Q(status='H')|Q(value_id=cur_item_value_id)) \
             .values('value_id', 'value', 'rank'))
         # value overlays
-        default_values.sort(key=lambda x: x['rank'])
-    return default_values
+        overlay_values = list(FieldChoice.objects \
+            .filter(tracker=tracker_id, group=group, field=name) \
+            .values('value_id', 'value', 'rank', 'status'))
+        for o in overlay_values:
+            found = False
+            i = 0
+            for v in values:
+                if v['value_id'] == o['value_id']:
+                    found = True
+                    if v['status'] == 'H':
+                        del values[i]
+                        i -= 1
+                    else:
+                        v['value'] = o['value']
+                        v['rank'] = o['rank']
+                    break
+                i += 1
+            if not found and o['status'] != 'H' and field_def['scope'] != 'S':
+                v.append(o)
+        values.sort(key=lambda x: x['rank'])
+
+    return values
 
 # Auto_increment counters
 # We could make this more generic, but we'd have to implement
@@ -468,7 +502,8 @@
                 overlay.apply_on(fields[name])
         for name in fields:
             if fields[name]['display_type'] == 'SB':
-                fields[name]['values'] = field_get_values(self.tracker_id, self.group, name)
+                fields[name]['values'] = field_get_values(self.tracker_id, self.group,
+                                                          fields[name], self.get_value(name))
         return fields
 
     def get_form_fields(self, user=None):
@@ -478,7 +513,6 @@
         fields = self.get_fields().copy()
         ret = []
         for name in fields.keys():
-            print fields[name]
             if (not (fields[name]['required'] or fields[name]['use_it'])
                 or fields[name]['special']):
                 continue
@@ -486,12 +520,29 @@
         ret.sort(key=lambda x: x[1]['rank'])
         return ret
 
+    def get_value(self, key):
+        if key == 'comment_type_id':
+            # not stored in the item, but in the history
+            # TODO: it actually has nothing do in fields definitions
+            # (not part of the generic form, not a stored value); move
+            # it out!
+            return None
+        elif key in ('submitted_by', 'assigned_to'):
+            user = getattr(self, key)
+            if user is None:
+                return None
+            else:
+                return user.pk
+        else:
+            return getattr(self, key)
+
     def get_form(self, user=None):
         # TODO: privacy
+        # TODO: privileges
         form_fields = self.get_form_fields()
         html = '';
         for field_no, (name,field) in enumerate(form_fields):
-            value = getattr(self, name)
+            value = self.get_value(name)
 
             if field_no % 2 == 0:
                 html += '<tr>'