comparison roundup/hyperdb.py @ 5112:8901cc4ef0e0

- issue1714899: Feature Request: Optional Change Note. Added a new quiet=True/False option for all property types. When quiet=True changes to the property will not be displayed in the:: confirmation banner (shown in green) when a change is made property change section of change note (nosy emails) web history display for an item. Note that this may confuse users if used on a property that is meant to be changed by a user. It is most useful on administrative properties that are changed by an auditor as part of a user generated change. Original patch by Daniel Diniz (ajaksu2) discussed also at: http://psf.upfronthosting.co.za/roundup/meta/issue249 Support for setting quiet when calling the class specifiers: E.G. prop=String(quiet=True) rather than:: prop=String() prop.quiet=True support for anydb backend, added tests, doc updates, support for ignoring quiet setting using showall=True in call to history() function in templates by John Rouillard. In addition to documenting quiet, I also documented required and default_value additions to the hyperdb property classes. Only place I could find is design.txt. Note tests for history in web interface are not done. It was manually checked but there are no automated tests. The template for setup is in db_test_base.py::testQuietJournal but it has no asserts. I need access to template.py::_HTMLItem::history() and I don't know how to do that. test_templates.py isn't helping me any at all and I want to get this patch in because it handles nicely an issue I have in the design of my own tracker. The issue is: The properties of an issue are displayed in framesets/subframes. The user can roll up the frameset leaving only the title bar. When the user saves the changes, the current state of the framesets (collapsed/uncollapsed) is saved to a property in the user's object. However there is no reason the user should see that this is updated since it's an administrative detail. Similarly, you could count the number of times an issue is reopened or reassigned. Updates to properties that are an indirect result of a user's change should not be displayed to the user as they can be confusing and distracting.
author John Rouillard <rouilj@ieee.org>
date Thu, 30 Jun 2016 20:38:23 -0400
parents d043a09952db
children 7b74a5addfea
comparison
equal deleted inserted replaced
5111:1c94afabb2cb 5112:8901cc4ef0e0
33 # 33 #
34 # Types 34 # Types
35 # 35 #
36 class _Type(object): 36 class _Type(object):
37 """A roundup property type.""" 37 """A roundup property type."""
38 def __init__(self, required=False, default_value = None): 38 def __init__(self, required=False, default_value = None, quiet=False):
39 self.required = required 39 self.required = required
40 self.__default_value = default_value 40 self.__default_value = default_value
41 self.quiet = quiet
41 def __repr__(self): 42 def __repr__(self):
42 ' more useful for dumps ' 43 ' more useful for dumps '
43 return '<%s.%s>'%(self.__class__.__module__, self.__class__.__name__) 44 return '<%s.%s>'%(self.__class__.__module__, self.__class__.__name__)
44 def get_default_value(self): 45 def get_default_value(self):
45 """The default value when creating a new instance of this property.""" 46 """The default value when creating a new instance of this property."""
52 """ 53 """
53 return val 54 return val
54 55
55 class String(_Type): 56 class String(_Type):
56 """An object designating a String property.""" 57 """An object designating a String property."""
57 def __init__(self, indexme='no', required=False, default_value = ""): 58 def __init__(self, indexme='no', required=False, default_value = "", quiet=False):
58 super(String, self).__init__(required, default_value) 59 super(String, self).__init__(required, default_value, quiet)
59 self.indexme = indexme == 'yes' 60 self.indexme = indexme == 'yes'
60 def from_raw(self, value, propname='', **kw): 61 def from_raw(self, value, propname='', **kw):
61 """fix the CRLF/CR -> LF stuff""" 62 """fix the CRLF/CR -> LF stuff"""
62 if propname == 'content': 63 if propname == 'content':
63 # Why oh why wasn't the FileClass content property a File 64 # Why oh why wasn't the FileClass content property a File
71 return int(val) 72 return int(val)
72 return val.lower() 73 return val.lower()
73 74
74 class Password(_Type): 75 class Password(_Type):
75 """An object designating a Password property.""" 76 """An object designating a Password property."""
76 def __init__(self, scheme=None, required=False, default_value = None): 77 def __init__(self, scheme=None, required=False, default_value = None, quiet=False):
77 super(Password, self).__init__(required, default_value) 78 super(Password, self).__init__(required, default_value, quiet)
78 self.scheme = scheme 79 self.scheme = scheme
79 80
80 def from_raw(self, value, **kw): 81 def from_raw(self, value, **kw):
81 if not value: 82 if not value:
82 return None 83 return None
91 return val 92 return val
92 return str(val) 93 return str(val)
93 94
94 class Date(_Type): 95 class Date(_Type):
95 """An object designating a Date property.""" 96 """An object designating a Date property."""
96 def __init__(self, offset=None, required=False, default_value = None): 97 def __init__(self, offset=None, required=False, default_value = None, quiet=False):
97 super(Date, self).__init__(required = required, 98 super(Date, self).__init__(required = required,
98 default_value = default_value) 99 default_value = default_value,
100 quiet=quiet)
99 self._offset = offset 101 self._offset = offset
100 def offset(self, db): 102 def offset(self, db):
101 if self._offset is not None: 103 if self._offset is not None:
102 return self._offset 104 return self._offset
103 return db.getUserTimezone() 105 return db.getUserTimezone()
133 class _Pointer(_Type): 135 class _Pointer(_Type):
134 """An object designating a Pointer property that links or multilinks 136 """An object designating a Pointer property that links or multilinks
135 to a node in a specified class.""" 137 to a node in a specified class."""
136 def __init__(self, classname, do_journal='yes', try_id_parsing='yes', 138 def __init__(self, classname, do_journal='yes', try_id_parsing='yes',
137 required=False, default_value=None, 139 required=False, default_value=None,
138 msg_header_property = None): 140 msg_header_property = None, quiet=False):
139 """ Default is to journal link and unlink events. 141 """ Default is to journal link and unlink events.
140 When try_id_parsing is false, we don't allow IDs in input 142 When try_id_parsing is false, we don't allow IDs in input
141 fields (the key of the Link or Multilink property must be 143 fields (the key of the Link or Multilink property must be
142 given instead). This is useful when the name of a property 144 given instead). This is useful when the name of a property
143 can be numeric. It will only work if the linked item has a 145 can be numeric. It will only work if the linked item has a
152 responsible. In that case setting 154 responsible. In that case setting
153 'msg_header_property="username"' for the assigned_to 155 'msg_header_property="username"' for the assigned_to
154 property will generated message headers of the form: 156 property will generated message headers of the form:
155 'X-Roundup-issue-assigned_to: joe_user'. 157 'X-Roundup-issue-assigned_to: joe_user'.
156 """ 158 """
157 super(_Pointer, self).__init__(required, default_value) 159 super(_Pointer, self).__init__(required, default_value, quiet)
158 self.classname = classname 160 self.classname = classname
159 self.do_journal = do_journal == 'yes' 161 self.do_journal = do_journal == 'yes'
160 self.try_id_parsing = try_id_parsing == 'yes' 162 self.try_id_parsing = try_id_parsing == 'yes'
161 self.msg_header_property = msg_header_property 163 self.msg_header_property = msg_header_property
162 def __repr__(self): 164 def __repr__(self):
194 196
195 "do_journal" indicates whether the linked-to nodes should have 197 "do_journal" indicates whether the linked-to nodes should have
196 'link' and 'unlink' events placed in their journal 198 'link' and 'unlink' events placed in their journal
197 """ 199 """
198 200
199 def __init__(self, classname, do_journal = 'yes', required = False): 201 def __init__(self, classname, do_journal = 'yes', required = False, quiet=False):
200 202
201 super(Multilink, self).__init__(classname, 203 super(Multilink, self).__init__(classname,
202 do_journal, 204 do_journal,
203 required = required, 205 required = required,
204 default_value = []) 206 default_value = [], quiet=quiet)
205 207
206 def from_raw(self, value, db, klass, propname, itemid, **kw): 208 def from_raw(self, value, db, klass, propname, itemid, **kw):
207 if not value: 209 if not value:
208 return [] 210 return []
209 211

Roundup Issue Tracker: http://roundup-tracker.org/