Edgewall Software
Modify

Opened 9 years ago

Last modified 4 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:

Description (last modified by Christian Boos)

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)

9673-userpreferences-cboos-r10224.patch (12.3 KB ) - added by Christian Boos 9 years ago.
Add a Configuration panel for overriding some TracIni settings.

Download all attachments as: .zip

Change History (26)

comment:1 by Christian Boos, 9 years ago

Description: modified (diff)

Woops, that proposal started as a TracDev/Proposals, as you can tell by the h1 ;-)

comment:2 by Itamar Ostricher, 9 years ago

Cc: itamarost@… 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…)

in reply to:  2 comment:3 by Christian Boos, 9 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 Remy Blank, 9 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 Christian Boos, 9 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.

comment:6 by Remy Blank, 9 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 lkraav <leho@…>, 9 years ago

Cc: leho@… added

in reply to:  6 ; comment:8 by Christian Boos, 9 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.

in reply to:  8 ; comment:9 by Remy Blank, 9 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 in BoolPreferences (not to mention having to redo Bool/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.

in reply to:  9 ; comment:10 by Christian Boos, 9 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.

in reply to:  10 ; comment:11 by Remy Blank, 9 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.

in reply to:  11 ; comment:12 by Christian Boos, 9 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 Christian Boos, 9 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):  
    203204        (''since 0.9'').""")
    204205
    205206    split_page_names = BoolOption('wiki', 'split_page_names', 'false',
    206         """Enable/disable splitting the WikiPageNames with space characters
    207         (''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
    208209
    209210    render_unsafe_content = BoolOption('wiki', 'render_unsafe_content', 'false',
    210211        """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):  
    254255    Lu = ''.join(unichr(c) for c in range(0, 0x10000) if unichr(c).isupper())
    255256    Ll = ''.join(unichr(c) for c in range(0, 0x10000) if unichr(c).islower())
    256257
     258    def split_camelcase(self, page):
     259        return self.PAGE_SPLIT_RE.sub(r"\1 \2", page)
     260
    257261    def format_page_name(self, page, split=False):
    258262        if split or self.split_page_names:
    259             return self.PAGE_SPLIT_RE.sub(r"\1 \2", page)
     263            return self.split_camelcase(page)
    260264        return page
    261265
    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):
    263272        """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  
    3434from trac.util.compat import any, md5, sha1, sorted
    3535from trac.util.text import to_unicode
    3636
     37# -- conversions
     38
     39_TRUE_VALUES = ('yes', 'true', 'enabled', 'on', 'aye', '1', 1, True)
     40
     41def getbool(value):
     42    if isinstance(value, basestring):
     43        value = value.lower() in _TRUE_VALUES
     44    return bool(value)
     45
     46
    3747# -- req/session utils
    3848
    3949def get_reporter_id(req, arg_name=None):
    def get_reporter_id(req, arg_name=None):  
    4959        return '%s <%s>' % (name, email)
    5060    return name or email or req.authname # == 'anonymous'
    5161
     62
     63def 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
    5278if os.name == 'nt':
    5379    from getpass import getuser
    5480else:

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):  
    394393
    395394        Valid default input is a string or a bool. Returns a bool.
    396395        """
    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))
    401397
    402398    def getint(self, key, default=''):
    403399        """Return the value of the specified option as integer.
    class Option(object):  
    535531                    if each[1] not in components
    536532                       or compmgr.is_enabled(components[each[1]]))
    537533
    538     def __init__(self, section, name, default=None, doc=''):
     534    def __init__(self, section, name, default=None, doc='', user=False):
    539535        """Create the configuration option.
    540536
    541537        @param section: the name of the configuration section this option
    class Option(object):  
    543539        @param name: the name of the option
    544540        @param default: the default value for the option
    545541        @param doc: documentation of the option
     542        @param user: True if can be overriden by the user, False otherwise
    546543        """
    547544        self.section = section
    548545        self.name = name
    549546        self.default = default
    550547        self.registry[(self.section, self.name)] = self
    551548        self.__doc__ = doc
     549        self.user = user
    552550
    553551    def __get__(self, instance, owner):
    554552        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):  
    113117            data['locales'] = locales
    114118            data['languages'] = languages
    115119
     120        if panel == 'config':
     121            data['options'] = self._get_user_options()
     122
    116123        return 'prefs_%s.html' % (panel or 'general'), data
    117124
    118125    # ITemplateProvider methods
    class PreferencesModule(Component):  
    143150                del req.session[field]
    144151        add_notice(req, _('Your preferences have been saved.'))
    145152
     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
    146180    def _do_load(self, req):
    147181        if req.authname == 'anonymous':
    148182            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 Christian Boos, 9 years ago

Add a Configuration panel for overriding some TracIni settings.

in reply to:  12 ; comment:14 by Remy Blank, 9 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.

in reply to:  14 ; comment:15 by Christian Boos, 9 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 of req 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.

in reply to:  15 ; comment:16 by Remy Blank, 9 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.

in reply to:  16 comment:17 by Christian Boos, 9 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 osimons, 9 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 Christian Boos, 9 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 osimons, 9 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.

comment:21 by Christian Boos, 9 years ago

self.split_page_names() … which would defeat your "don't break anything" policy.

in reply to:  21 ; comment:22 by osimons, 9 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?

in reply to:  22 comment:23 by Remy Blank, 9 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 Ryan J Ollos, 5 years ago

Keywords: preferences added; userpreferences removed

comment:25 by Ryan J Ollos, 4 years ago

Owner: Christian Boos removed

Modify Ticket

Change Properties
Set your email in Preferences
Action
as new The ticket will remain with no owner.
The ticket will be disowned. Next status will be 'new'.
as The resolution will be set. Next status will be 'closed'.
The owner will be changed from (none) to anonymous. Next status will be 'assigned'.

Add Comment


E-mail address and name can be saved in the Preferences .
 
Note: See TracTickets for help on using tickets.