Roundup Tracker - Issues

Issue 2550930

classification
add support for local_replace to classic template
Type: rfe Severity: normal
Components: Web interface Versions: devel
process
Status: fixed fixed
:
: : ThomasAH, ber, rouilj
Priority: : patch

Created on 2016-10-26 11:17 by ber, last changed 2026-08-11 03:56 by rouilj.

Files
File name Uploaded Description Edit Remove
local_replace_for_classic.patch ber, 2016-10-26 11:17
local_replace.py ber, 2016-10-26 11:19 more advanced example local_replace.py as mailed to users list in Sept 2016
add_localReplace_to_all_templates.diff rouilj, 2026-08-03 00:41 registerd localReplace is assigned as staticmethod on TemplatingUtils class.
add_localReplace_to_all_templates2.diff rouilj, 2026-08-03 02:16 __getattr__ returns __localReplace if localReplace is requested
add_localReplace_to_all_templates3-__getattribute__.diff rouilj, 2026-08-03 03:05 __getattribute__ based method to override TemplatingUtils defined methods.
Messages
msg5916 Author: [hidden] (ber) Date: 2016-10-26 11:17
Because the classic template is most complete,
I suggest to add support for the local_replace.py
extension to it.

Here is a patch doing so.
(And I am also adding an example for a more advanced local_replace.py)
msg6306 Author: [hidden] (ThomasAH) Date: 2018-12-11 14:17
The patches add functionality to the extensions directory and
additionally makes this extension required by using it in the html
templates.

If it is required by the classic template, I'd rather see it integrated
into the core.

Additionally the configuration for the extension will happen in the
extension itself. Having a configuration mechanism similar to 
schema.py or interfaces.py (in the tracker root directory, in the case
of interfaces.py even an optional file) might be preferable.
msg6307 Author: [hidden] (ThomasAH) Date: 2018-12-11 14:30
One additional thought: Adding it as a default extension might be OK, if
the extension code and the configuration code can be separated and the
extension provides a sane default when running without further
configuration.
msg8481 Author: [hidden] (rouilj) Date: 2026-08-03 00:41
The way I am doing this is to:

  add a @staticmethod called localReplace to templating.py::TemplatingUtils

this defined localReplace(message) and uses static method to keep the same calling
signature (i.e. without self).

In instance.py: change registerUtil(name, func) to see if the name being registered
already exists as an attribute on self.TemplatingUtils. If it does, use setattr to
replace self.TemplatingUtils.name with staticmethod(func).

If the attribute isn't defined, add it to self.templating_utils[name].

Patch attached.

I am a little uncertain about replacing the attribute. registerUtilMethod does the
replacement/overwrite because of the way it's written, but this may be wrong.

I am not sure why the original code is using self.templating_utils[] rather than
just setattr() on the TemplatingUtils class. Is it possible I could contaminate
another thread running against a different tracker (e.g. with wsgi or roundup-server
in thread mode)?

When I added the new definition to self.templating_utils[], the code was still calling the
@staticmethod on TemplatingUtils. If I could invert the resolution order so templating_utils[]
was checked first, then I wouldn't have to setattr on TemplatingUtils.

IIUC __getattr__ is called if __getattribute__ raises an AttributeError and __getattribute__
always looks in self.__dict__ for a match. So maybe I should redefine __getattribute__ to:

    if name in self.client.instance.templating_utils:
        return self.client.instance.templating_utils[name]
    if name in self.__dict__:
        return self.__dict__[name]
    raise AttributeError(name)

Thoughts welcome.
msg8482 Author: [hidden] (rouilj) Date: 2026-08-03 02:16
This is a second try to provide a default implementation of localReplace that can be
overridden using the extension method.

I define __localReplace(message) in TemplatingUtils.

In __getattr__ I look up the name in self.client.instance.templating_utils. If it is not
found, check to see if the name is 'localReplace'. If so, return self.__localReplace.

This allows a fallback for _localReplace so we can add it in the right places in the templates.
But it doesn't support overriding existing TemplatingUtils methods.

See: add_localReplace_to_all_templates2.diff
msg8483 Author: [hidden] (rouilj) Date: 2026-08-03 03:05
Third way add __getattribute__() to TemplatingUtils. Look up name first in
self.client.instance.templating_utils. If not found look up as instance attribute.

This is a problem if internal names starting with '_' are allowed to be registered.
So registerUtil and registerUtilMethod prevent any name starting with underscore '_'.

I am still unsure if registerutilMethod by manipulating the class attributes could
leak changes into trackers running in other threads. If it does, I may have to figure
out how to create a templating_utils_method dict and call the functions in it like
a method would be called.

See: add_localReplace_to_all_templates.diff
msg8489 Author: [hidden] (rouilj) Date: 2026-08-05 16:59
a patch on issue 2551421 will redo the registration methods.

It does away with __getattr__ by calling setattr on the TemplatingUtil object
instance for the tracker. This simplifies lookup.

The instance.registerUtil* methods block registering any util starting with
'_' or the name client. But all other methods on the TemplatingUtil object can
be replaced by registering them.

The instance registerUtil* methods check for duplicate names and give pointers
to the files/lines where the conflict happens.
msg8490 Author: [hidden] (rouilj) Date: 2026-08-11 03:56
Done in changeset:   8747:551a9f8dcae1

There is now an example in customizing.txt.

The extended example attached to this ticket is similar to the default
ones
in the devel or responsive templates. They are now referenced in the
docs.
History
Date User Action Args
2026-08-11 03:56:42rouiljsetstatus: new -> fixed
type: rfe
resolution: remind -> fixed
messages: + msg8490
2026-08-05 16:59:45rouiljsetmessages: + msg8489
2026-08-03 03:05:07rouiljsetfiles: + add_localReplace_to_all_templates3-__getattribute__.diff
messages: + msg8483
2026-08-03 02:16:33rouiljsetfiles: + add_localReplace_to_all_templates2.diff
messages: + msg8482
2026-08-03 00:41:29rouiljsetresolution: remind
2026-08-03 00:41:04rouiljsetfiles: + add_localReplace_to_all_templates.diff
nosy: + rouilj
messages: + msg8481
2018-12-11 14:30:50ThomasAHsetmessages: + msg6307
2018-12-11 14:17:41ThomasAHsetnosy: + ThomasAH
messages: + msg6306
2016-10-26 11:19:40bersetfiles: + local_replace.py
2016-10-26 11:18:15bersetcomponents: + Web interface
2016-10-26 11:17:58bersetversions: + devel
2016-10-26 11:17:54bercreate