Mercurial > p > roundup > code
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 |
