Opened 14 years ago
Last modified 9 years ago
#9673 new enhancement
Some TracIni settings should be user configurable
Reported by: | Christian Boos | Owned by: | |
---|---|---|---|
Priority: | normal | Milestone: | next-major-releases |
Component: | general | Version: | 0.13dev |
Severity: | normal | Keywords: | preferences |
Cc: | itamarost@…, leho@… | Branch: | |
Release Notes: | |||
API Changes: | |||
Internal Changes: |
Description (last modified by )
Many aspects of Trac are configurable, via the TracIni settings. A lot of these switches are presentation or slight behavior tweaks that are largely a matter of personal taste and preferences. As those are TracIni settings, this has the effect that the project owners' preferences are enforced, not necessarily the same as those of their users.
It should however not be hard to allow some of these options to be tailored to the end users' needs. As suggested in #3360, we could extend our Option descriptors with an extra user
parameter.
For example:
section = Section('ticket', N_('Ticket Settings')) chronological_order = BoolOption('ticket', 'chronological_order', default=True, user=N_("Show Change History in Chronological Order"), doc=N_(""" Controls in which direction the Change history is displayed. ''(since 0.13)'' """)
We could use this parameter to pass the option summary that the user will see in a new preference panel.
We could either have one user panel, with field sets grouping options from a given section, or we could have one panel per section (but then we need to fix the layout of panel tabs to allow for having lots of panels).
For getting back the values, we could think about having a helper function userpref(env, req, section, option)
, or something smarter that would fetch the value first from the req.session
and only if not found pick the environment's default.
A few tickets are concerned by this new feature:
Attachments (1)
Change History (26)
comment:1 by , 14 years ago
Description: | modified (diff) |
---|
follow-up: 3 comment:2 by , 14 years ago
Cc: | added |
---|
that would be nice indeed.
when I saw the subject, I initially thought it was a little different than what you describe, which might also be useful:
allow a "site manager" (which is not necessarily the sys-admin / TRAC_ADMIN, maybe a new permission - SITE_ADMIN) to control a subset of the INI settings that are "safe" (e.g. header-logo (maybe testing validity), but not components and DB) using the web interface (similar to IniAdminPlugin).
It's also possible to give the TRAC_ADMIN control over who can configure what, using two ini-options:
# A comma separated list of ini-options that are configurable by SITE_ADMIN site_admin_configurable_options = header_logo.*, ... # A comma separated list of ini-options that may be customized by users # values are loaded from user-session-info, with defaults defined in the INI user_customizable_options = ticket.chronological_order, ...
Note that the TRAC_ADMIN can add user_customizable_options
to site_admin_configurable_options
in order to let the SITE_ADMIN to control what options are user-customizable (of course, don't allow the SITE_ADMIN to give the user the ability to customize options that are not configurable by SITE_ADMIN himself…)
comment:3 by , 14 years ago
Interesting, but I think this is targeting a different subset of the options. It doesn't make sense to let an user to configure the header logo, but conversely, it doesn't make much sense for an admin to prevent the user to set his preferences for the [wiki] split_page_names option for example. Maybe we would need another keyword for what you propose (site_admin=True
)?
And by the way, in that case or even for future extensibility, we should probably not overload user=
with the role of specifying the option short summary, but rather have something like user=True, title=N_("This selects the way that information gets presented")
.
comment:4 by , 14 years ago
I don't like the idea of augmenting the config infrastructure to include session-related stuff, as I feel it would introduce too much coupling between barely-related things. If anything, I'd rather augment the session code to allow the automatic generation of preference panels, independently of config options.
comment:5 by , 14 years ago
Well, in this proposal there's nothing session related that will go in the config module. The only addition to the config option descriptors are a few more flags: user (bool), title (string) and eventually site_admin (bool). The userpref
helper is certainly going in trac.util, along with a few other Request related helpers. The preference panel code will merely extract the information from the Config, in much the same way the TracIni macro currently does.
follow-up: 8 comment:6 by , 14 years ago
But why tie the preference functionality to config options (by placing the labels there)? Sure, in a few cases they may be related, but why hardcode this relation? I would prefer an independent user preference "mini-infrastructure".
The Option*
descriptors already have loads of arguments, adding more isn't going to make them easier to read and understand.
class MyComponent(Component): chronological_order = BoolOption('ticket', 'chronological_order', default=True, doc=N_(""" Controls in which direction the Change history is displayed. ''(since 0.13)'' """) chronological_order_pref = BoolPreference('chronological_order', N_("Show Change History in Chronological Order")) def process_request(self, req): chronological = self.chronological_order_pref.get(req, self.chronological_order)
(The section concept is missing, but you get the idea.)
comment:7 by , 14 years ago
Cc: | added |
---|
follow-up: 9 comment:8 by , 14 years ago
Replying to rblank:
But why tie the preference functionality to config options (by placing the labels there)?
The labels can also be useful for a TracIni editor, in the admin. Even the UI and the code could basically be shared, the only difference would be that the admin gets to see all (or most settings) and the user only a handful.
Tying them is also useful for showing the default value to the user, otherwise we would have either to repeat the default value, or have the same (section, key)
information in BoolPreferences
(not to mention having to redo Bool/Int/List/etc.
classes). So I think adding a title and one or two optional flags that would only be used when iterating over the entries of in Option.get_registry(env)
would be of minimal complexity.
The title could eventually be made mandatory, so that we could produce a more compact [[TracIni]] output:
[section] (section title) (section doc) ||= Setting =||= Description =|| || name || title [+] || [+] expands and adds the full doc
In the config view in the about page, the title could also be shown as a title attribute for each table entry.
follow-up: 10 comment:9 by , 14 years ago
Replying to cboos:
The labels can also be useful for a TracIni editor, in the admin. Even the UI and the code could basically be shared, the only difference would be that the admin gets to see all (or most settings) and the user only a handful.
While the labels can be useful for configuration options, I still don't think this justifies tying both concepts together. For one, I only see very few configuration options that should be overridable directly by the user (that is, with the same UI).
Implementing a TracIni editor is nice, but doesn't require configuration options and user preferences to be tightly coupled. Even re-using the code for a user preference editor doesn't require that.
Tying them is also useful for showing the default value to the user, otherwise we would have either to repeat the default value, or have the same
(section, key)
information inBoolPreferences
(not to mention having to redoBool/Int/List/etc.
classes).
The default value of *Preference
could optionally be a function that returns the default value, which could in turn retrieve the value from the configuration option. This keeps both concepts completely decoupled. Despite the apparent repetition of *Preference
vs. *Option
(which really isn't that much), I still think this would be a much cleaner design. Less coupling often means simpler to maintain, and I think this applies quite well here.
So I think adding a title and one or two optional flags that would only be used when iterating over the entries of in
Option.get_registry(env)
would be of minimal complexity.
I don't think the labels should be the same for the configuration option and for the user preference. The former is addressed to a Trac admin, the latter to a Trac user. The communication is not at the same level. Also, the grouping should not necessarily be given by the section of the configuration option.
follow-up: 11 comment:10 by , 14 years ago
Replying to rblank:
The default value of
*Preference
could optionally be a function that returns the default value, which could in turn retrieve the value from the configuration option.
Wow, how simple is that?
This keeps both concepts completely decoupled. Despite the apparent repetition of
*Preference
vs.*Option
(which really isn't that much), I still think this would be a much cleaner design. Less coupling often means simpler to maintain, and I think this applies quite well here.
Well, I think you're making this overly complex, to the point I prefer drop the idea altogether.
So I think adding a title and one or two optional flags that would only be used when iterating over the entries of in
Option.get_registry(env)
would be of minimal complexity.I don't think the labels should be the same for the configuration option and for the user preference. The former is addressed to a Trac admin, the latter to a Trac user. The communication is not at the same level.
Really? Do you have a concrete example in mind. To me, something like Show Change History in Chronological Order is equally valid for both.
Also, the grouping should not necessarily be given by the section of the configuration option.
Maybe, yes.
follow-up: 12 comment:11 by , 14 years ago
Replying to cboos:
Wow, how simple is that?
Quite simple and clear, actually:
class MyComponent(Component): chronological_order = BoolOption('ticket', 'chronological_order', default=True, doc=N_(""" Controls in which direction the Change history is displayed. Users can override this option in their preferences. ''(since 0.13)'' """) chronological_order_pref = BoolPreference('chronological_order', N_("Show Change History in Chronological Order"), default=lambda self: self.chronological_order) def process_request(self, req): chronological = self.chronological_order_pref(req)
Well, I think you're making this overly complex, to the point I prefer drop the idea altogether.
I'm sad that you take it like that. I really like the idea of providing a simple way of using and documenting user preferences (rather than just accessing req.session
). But yes, if the choice is between "tightly couple configuration options and user preferences" and "drop the idea", I'm afraid my vote goes to the latter. I hope, however, that an open discussion will allow finding a better option.
Really? Do you have a concrete example in mind. To me, something like Show Change History in Chronological Order is equally valid for both.
Well, for one, the title for the configuration option would be Default change history chronology mode or something like that, as it only sets a default value.
Come on, Christian, I'm sure we can sort it out. Want to play with two alternative implementations and compare afterward? We could agree on one or two of these preferences, and do a minimal implementation of both variants.
follow-up: 14 comment:12 by , 14 years ago
Replying to rblank:
Replying to cboos:
Well, I think you're making this overly complex, to the point I prefer drop the idea altogether.
I'm sad that you take it like that. I really like the idea of providing a simple way of using and documenting user preferences (rather than just accessing
req.session
).
I suppose we approached the problem differently: my motivation was really just to allow users to override some of the TracIni options (hence the idea of a flag to mark those for which it would be possible); you approached this from the req.session point of view, and how to put some structure and documentation there. This also explains your comment "For one, I only see very few configuration options that should be overridable directly by the user", when my whole proposal was just about those.
But now on your approach (and provided I understand it correctly), I'm not sure it makes sense to repeat the various options we save in the session (for example the timeline options, the diff options, etc.), into a duplicated interface in the user preferences.
But yes, if the choice is between "tightly couple configuration options and user preferences" and "drop the idea", I'm afraid my vote goes to the latter. I hope, however, that an open discussion will allow finding a better option.
Come on, Christian, I'm sure we can sort it out. Want to play with two alternative implementations and compare afterward? We could agree on one or two of these preferences, and do a minimal implementation of both variants.
Yes, we can do that. Let's take my favorite option, "[wiki] split_page_names" ;-)
I do however like the code sample from comment:11, so maybe your approach is not so bad after all.
comment:13 by , 14 years ago
Ok, so my take at this… I'll comment selected portions of patch.
[wiki] split_page_names
was perhaps not the easiest choice, but it was nevertheless useful as it reminded me we shouldn't assume "user ⇔ req", notably in the rendering system.
Here's the change for the [wiki] split_page_names
BoolOption
:
-
trac/wiki/api.py
diff -r df669016cabf trac/wiki/api.py
a b class WikiSystem(Component): 203 204 (''since 0.9'').""") 204 205 205 206 split_page_names = BoolOption('wiki', 'split_page_names', 'false', 206 """Enable/disable splitting the WikiPageNames with space characters207 (''since 0.10'').""")207 N_("Enable/disable splitting the WikiPageNames with space characters"), 208 user=True) # TODO also add support for since="0.10" parameter 208 209 209 210 render_unsafe_content = BoolOption('wiki', 'render_unsafe_content', 'false', 210 211 """Enable/disable the use of unsafe HTML tags such as `<script>` or
If the doc is short enough, no need for a separate title. We should however move away the "since…" information (this metadata could also be useful for better rendering in [[TracIni]]).
A bit later in the same file, we use the new getboolpref
helper function:
-
trac/wiki/api.py
diff -r df669016cabf trac/wiki/api.py
a b class WikiSystem(Component): 254 255 Lu = ''.join(unichr(c) for c in range(0, 0x10000) if unichr(c).isupper()) 255 256 Ll = ''.join(unichr(c) for c in range(0, 0x10000) if unichr(c).islower()) 256 257 258 def split_camelcase(self, page): 259 return self.PAGE_SPLIT_RE.sub(r"\1 \2", page) 260 257 261 def format_page_name(self, page, split=False): 258 262 if split or self.split_page_names: 259 return self. PAGE_SPLIT_RE.sub(r"\1 \2",page)263 return self.split_camelcase(page) 260 264 return page 261 265 262 def make_label_from_target(self, target): 266 def format_page_name_pref(self, page, session): 267 if getboolpref('wiki', 'split_page_names', session, self.config): 268 return self.split_camelcase(page) 269 return page 270 271 def make_label_from_target(self, target, session=None): 263 272 """Create a label from a wiki target.
(here I retain format_page_name()
for backward compatibility)
Why session
and not req
? I'd like to get away from the web Request proper in the Wiki engine, and more generally in the rendering pipeline. One possibility to achieve this would be to integrate the session prefs in the rendering "hints" (or all the session info? that way, when rendering diffs, one could also use the preferences for diffs).
The getboolpref()
helper is defined as:
-
trac/util/__init__.py
diff -r df669016cabf trac/util/__init__.py
a b from urllib import quote, unquote, urlen 34 34 from trac.util.compat import any, md5, sha1, sorted 35 35 from trac.util.text import to_unicode 36 36 37 # -- conversions 38 39 _TRUE_VALUES = ('yes', 'true', 'enabled', 'on', 'aye', '1', 1, True) 40 41 def getbool(value): 42 if isinstance(value, basestring): 43 value = value.lower() in _TRUE_VALUES 44 return bool(value) 45 46 37 47 # -- req/session utils 38 48 39 49 def get_reporter_id(req, arg_name=None): … … def get_reporter_id(req, arg_name=None): 49 59 return '%s <%s>' % (name, email) 50 60 return name or email or req.authname # == 'anonymous' 51 61 62 63 def getboolpref(section, key, session=None, config=None, default=False): 64 """Retrieve a boolean user preference in a session dict. 65 66 The preference is accessed via the prefs.section.key string key. 67 If not found, and `config` is given, the corresponding boolean 68 Configuration entry is taken, otherwise the `default` value is returned. 69 """ 70 pref = 'prefs.%s.%s' % (section, key) 71 if session and pref in session: 72 return getbool(session[pref]) 73 elif config: 74 return config.getbool(section, key, default) 75 return default 76 77 52 78 if os.name == 'nt': 53 79 from getpass import getuser 54 80 else:
Note that we gain a getbool()
helper in the process, in order to not duplicate code from config, and this could be reused in all the places we currently import _TRUE_VALUES
directly (quite a few).
config.py
itself sees minimal changes (use getbool
, add the user
flag):
-
trac/config.py
diff -r df669016cabf trac/config.py
a b class Section(object): 394 393 395 394 Valid default input is a string or a bool. Returns a bool. 396 395 """ 397 value = self.get(key, default) 398 if isinstance(value, basestring): 399 value = value.lower() in _TRUE_VALUES 400 return bool(value) 396 return getbool(self.get(key, default)) 401 397 402 398 def getint(self, key, default=''): 403 399 """Return the value of the specified option as integer. … … class Option(object): 535 531 if each[1] not in components 536 532 or compmgr.is_enabled(components[each[1]])) 537 533 538 def __init__(self, section, name, default=None, doc='' ):534 def __init__(self, section, name, default=None, doc='', user=False): 539 535 """Create the configuration option. 540 536 541 537 @param section: the name of the configuration section this option … … class Option(object): 543 539 @param name: the name of the option 544 540 @param default: the default value for the option 545 541 @param doc: documentation of the option 542 @param user: True if can be overriden by the user, False otherwise 546 543 """ 547 544 self.section = section 548 545 self.name = name 549 546 self.default = default 550 547 self.registry[(self.section, self.name)] = self 551 548 self.__doc__ = doc 549 self.user = user 552 550 553 551 def __get__(self, instance, owner): 554 552 if instance is None:
Finally, we use that metadata in prefs/web_ui.py, for example:
-
trac/prefs/web_ui.py
diff -r df669016cabf trac/prefs/web_ui.py
a b class PreferencesModule(Component): 113 117 data['locales'] = locales 114 118 data['languages'] = languages 115 119 120 if panel == 'config': 121 data['options'] = self._get_user_options() 122 116 123 return 'prefs_%s.html' % (panel or 'general'), data 117 124 118 125 # ITemplateProvider methods … … class PreferencesModule(Component): 143 150 del req.session[field] 144 151 add_notice(req, _('Your preferences have been saved.')) 145 152 153 def _get_user_options(self): 154 options = {} 155 for (section, key), option in Option.get_registry(self.env).items(): 156 if not option.user: 157 continue 158 field = '%s__%s' % (section, key) 159 pref = 'prefs.%s.%s' % (section, key) 160 if isinstance(option, BoolOption): 161 options.setdefault(section, []).append(('bool', field, pref, 162 option.__doc__)) 163 return options 164 165 def _do_save_config(self, req): 166 options = self._get_user_options() 167 for section, entries in options.iteritems(): 168 for kind, field, pref, doc in entries: 169 val = req.args.get(field, '').strip() 170 if val: 171 if kind == 'bool': 172 req.session[pref] = '1' 173 else: 174 req.session[pref] = val 175 elif pref in req.session and \ 176 (field in req.args or field + '_cb' in req.args): 177 del req.session[pref] 178 add_notice(req, _('Your preferences have been saved.')) 179 146 180 def _do_load(self, req): 147 181 if req.authname == 'anonymous': 148 182 oldsid = req.args.get('loadsid')
(support for BoolOption
only, see full patch for the prefs_config.html
template).
The presentation can be improved, but this is a minimal working system.
by , 14 years ago
Attachment: | 9673-userpreferences-cboos-r10224.patch added |
---|
Add a Configuration panel for overriding some TracIni settings.
follow-up: 15 comment:14 by , 14 years ago
Replying to cboos:
I'm not sure it makes sense to repeat the various options we save in the session (for example the timeline options, the diff options, etc.), into a duplicated interface in the user preferences.
Certainly not all, but maybe a few. I would have to go through all the current preferences to find out which ones.
Here's my take on the challenge. I have borrowed quite a lot of code and ideas from your patch, in particular the preference page template and code, and the idea of passing session
instead of req
into the preferences. I have also used the same key for the preference.
The descriptors support getting a value, setting a value, and also resetting a preference, meaning it reverts to the default value (in this case the value configured in trac.ini
). This would allow having "Reset" buttons on the user preference page to reset individual preferences to their default value.
follow-up: 16 comment:15 by , 14 years ago
Replying to rblank:
Replying to cboos:
I'm not sure it makes sense to repeat the various options we save in the session (for example the timeline options, the diff options, etc.), into a duplicated interface in the user preferences.
Certainly not all, but maybe a few. I would have to go through all the current preferences to find out which ones.
Ok, let's put it differently: do you think of any user preference that couldn't also have a corresponding per-environment default? I think the user preferences are a proper subset of per-environment default, therefore I don't see the point in duplicating the Option subsystem just to address this subset.
We can even take this one step further, with the notion of per-project preferences. This concerns the subset of TracIni settings that make sense to configure at the level of a project, in a MultiProject context, for example the default component for new tickets, or most of the [timeline]
options. I don't think it would make sense to introduce a third copy of the Option subsystem ("ProjectPreferences").
Rather, I'd prefer to see the Option system as a registry of "settings" for an environment and its modules, which can be backed by different kind of storage, in layers, from the more general to the particular:
- environment - (tree of) config files (via
[inherit] file
) - project - own config file? secondary
[<project>:...]
sections in main config file? db? - user - req.session
- ad-hoc - RenderingContext hints, req.args
So if anything is wrongly tied together, it's the fact that the Option settings rely on the configuration file exclusively. Of course the details of a configuration file should not need to be aware of the Request. Note that the reverse is maybe not true, as instead of storing most of the user settings as many key/value pairs, always stored and retrieved in batch, we might as well store the user preferences in a in-memory config-like object and serialize/deserialize it into a single "prefs" entry in session_attribute
.
Here's my take on the challenge. I have borrowed quite a lot of code and ideas from your patch, in particular the preference page template and code, and the idea of passing
session
instead ofreq
into the preferences. I have also used the same key for the preference.
Nice clean code as usual…
The descriptors support getting a value, setting a value, and also resetting a preference, meaning it reverts to the default value (in this case the value configured in
trac.ini
).
There's certainly something to take here, the current Option descriptors make it a bit hard to add "extra" setting stores, your way makes it easy to pass a list of stores to consider in order.
This would allow having "Reset" buttons on the user preference page to reset individual preferences to their default value.
That would be nice, yes.
follow-up: 17 comment:16 by , 14 years ago
Replying to cboos:
Ok, let's put it differently: do you think of any user preference that couldn't also have a corresponding per-environment default? I think the user preferences are a proper subset of per-environment default, therefore I don't see the point in duplicating the Option subsystem just to address this subset.
I thought you were talking about duplicating settings in the preferences user interface, but I must have got that wrong.
Yes, preferences are a subset of environment defaults. But to conclude from this that the preference functionality must be therefore added to the config subsystem is about the same as adding a (virtual) method to a base class that is only implemented in some of the derived classes. It feels backwards. Ok, granted, the analogy is not perfect.
We can even take this one step further, with the notion of per-project preferences.
This is very interesting. Note that my implementation could quite easily be modified to support such a "chained" lookup…
There's certainly something to take here, the current Option descriptors make it a bit hard to add "extra" setting stores, your way makes it easy to pass a list of stores to consider in order.
…by implementing a composite store that performs the lookup in the various stores you mentioned. This also means that we can remove the default value for preferences, as it will always be given by the last store (the config subsystem).
The next question is: who manages the store? Who adds e.g. the session store to the chain, or the "per-project defaults" store?
The big issue that bothers me in your reasoning is the following. Preferences are a subset of config options: it doesn't make sense to allow a user to override [trac] repository_dir
or any entries in [extensions]
. Therefore, you actually don't want to add the chained / composite lookup to the config subsystem, but rather to have it as one of several preference stores (the per-instance defaults).
Or in other words, there is no "is a" relationship between preferences and config options, but a "has a" relationship. That means looser coupling.
There's also the issue of backward compatibility: adding the functionality to the config subsystem will require to be careful not to break too many users of the subsystem. Whereas adding new functionality to the prefs
subsystem that relies on the config subsystem won't break anything.
comment:17 by , 14 years ago
Replying to rblank:
Replying to cboos:
Ok, let's put it differently: do you think of any user preference that couldn't also have a corresponding per-environment default? I think the user preferences are a proper subset of per-environment default, therefore I don't see the point in duplicating the Option subsystem just to address this subset.
I thought you were talking about duplicating settings in the preferences user interface
Yeah, I was. Then I introduced a different perspective, concerning the "pure" user settings which don't have a corresponding default, and how would you add a default for them if not by introducing an environment default. But we agree on the subset part.
Yes, preferences are a subset of environment defaults.
But to conclude from this that the preference functionality must be therefore added to the config subsystem
I was trying to make the point that the Option subsystem could be more general than just dealing with the config.
But OK, due to backward compatibility concerns and problems to extend the descriptors, we probably need a new set of classes anyway. So let's drop the idea of evolving the "Option" subsystem into something more general and instead introduce a new "Preference" subsystem.
We can even take this one step further, with the notion of per-project preferences.
This is very interesting. Note that my implementation could quite easily be modified to support such a "chained" lookup…
Right.
There's certainly something to take here, the current Option descriptors make it a bit hard to add "extra" setting stores, your way makes it easy to pass a list of stores to consider in order.
…by implementing a composite store that performs the lookup in the various stores you mentioned. This also means that we can remove the default value for preferences, as it will always be given by the last store (the config subsystem).
That would be one possibility, yes.
The next question is: who manages the store? Who adds e.g. the session store to the chain, or the "per-project defaults" store?
My first idea was to modify the __call__
so it takes a list of stores, trying them in order. In that case, it's clear which stores are involved, for each preference at its point of use.
Another possibility would be to have preference stores that could be easily chained, and which would be light-weight to compose (a kind of chained dict, you would to something like prefs = req.prefs(project.prefs(env.config))
), with req.prefs(a)
itself stacking two stores of preferences on top of a
, the ones from req.session
corresponding to saved preferences and the ones from req.args
corresponding to the per-page pref panel.
The big issue that bothers me in your reasoning is the following. Preferences are a subset of config options: it doesn't make sense to allow a user to override
[trac] repository_dir
or any entries in[extensions]
.
That was never the intention… However, a project could define its default repository like that (if we were still using that way of marking the project as default).
Therefore, you actually don't want to add the chained / composite lookup to the config subsystem, but rather to have it as one of several preference stores (the per-instance defaults).
Agreed. Most of our initial disagreement comes from what we call config subsystem. I tend to see things as being mutable (transmutable even), so I didn't mind making the Option classes evolve into something more general and detach themselves from the notion of config file proper, but for the sake of clarity and backward compatibility, it's perhaps preferable to introduce new classes when the concept evolves.
There's also the issue of backward compatibility: adding the functionality to the config subsystem will require to be careful not to break too many users of the subsystem. Whereas adding new functionality to the
prefs
subsystem that relies on the config subsystem won't break anything.
Fine. Also, sorry for having wanted to drop the gauntlet early on this, the matter was more involved than I originally thought and there was more to it than just "let's add this damn flag and be done with it" ;-) Thanks for insisting that we should continue the discussion.
This also somewhat meshes with my thinking regarding schemas and specialization via different sets of custom fields (e.g. for tickets, per type, per workflow, …). It's not quite the same though, unless we introduce "masking out" of inherited settings. </brainstorming>
comment:18 by , 14 years ago
Please leave the Configuration subsystem alone… :-)
The idea of adding a thin preference layer between options and their usage is also what I'd instinctively thought would be the best approach. Compared to Remy's example above, I'd like something even simpler like this:
class MyComponent(Component): split_page_names = BoolPreference( section='Wiki', label="Split WikiPageNames", doc="Expand 'WikiPageNames' to 'Wiki Page Names' (ugly...)", default=BoolOption('wiki', 'split_page_names', 'false', """Enable/disable splitting the WikiPageNames with space characters (''since 0.10'').""")) def process_request(self, req): # Use without session works just like it does now: if self.split_page_names: .... # Calling with session takes user preference into account if self.split_page_names(req.session):...
A BoolPreference
would naturally know how to deal with the BoolOption
passed in as default, its arguments and so on. Other preference types would know about choices and present drop down in the UI. One registration like example above should provide enough information to Preferences so that the panel can be rendered, values stored, and retrieved when needed without or with session.
A simple pattern for session storing and retrieval is to use the "one-line" config syntax: session['[wiki]split_page_names'] = '1'
.
comment:19 by , 14 years ago
Interesting. However I don't see how you would make the self.split_page_names / self.split_page_names(...)
approach work, in Python.
comment:20 by , 14 years ago
Neither do I actually ;-) My entry was intended to illustrate the passing of the Option
to the Preference
as the means of registering option and providing the default. But to access it you'd need something like self.split_page_names(...)
with or without session argument - but without need to pass in a default when calling.
follow-up: 22 comment:21 by , 14 years ago
self.split_page_names()
… which would defeat your "don't break anything" policy.
follow-up: 23 comment:22 by , 14 years ago
Replying to cboos:
self.split_page_names()
… which would defeat your "don't break anything" policy.
Not break anything? That wasn't a priority. An Option
converted to Option+Preference
would change usage signature slightly, but all regular options in Trac or elsewhere stays the same unless enabled to support preference setting in a request/session-enabled context. Something can be enabled, but nothing really breaks does it?
comment:23 by , 14 years ago
Replying to osimons:
Something can be enabled, but nothing really breaks does it?
Nothing breaks for new options, but if we enable the feature e.g. for [wiki] split_page_names
, then all users of that option have to be modified. That's why I suggested using another name for the preference.
comment:24 by , 10 years ago
Keywords: | preferences added; userpreferences removed |
---|
comment:25 by , 9 years ago
Owner: | removed |
---|
Woops, that proposal started as a TracDev/Proposals, as you can tell by the h1 ;-)