Keeping track of customizations

Hello there,
we all know the various ways for customizing existing views/templates/etc. But I was wondering how do you check for updates of your customized files? Sure, you can do it manually, but it might be time-consuming. I was thinking of a way to alleviate the process:

  1. Override the file
  2. Have some annotation saying the file is overridden against a specific version with an reference to the original.
  3. Whenever updating, an process checks all the annotations if the files have changed.
  4. As bonus, compare the old and latest original file and show the diff. For doing so, both versions must be present in the file system (like in the eggs folder).
  5. The maintainer must then update the overridden file and update the annotation for that file.

For the annotations I was thinking of the declarative way of some kind (using meta.zcml):

<patchwatcher:check
    original="addon:path/to/file.pt"
    override="./path/in/your/add-on/file.pt"
    hash="some_hash"
/>

The process then checks for any changes. When you incorporated the changes into the override, the process can optionally update the check-statements with the latest hashes (either by directly writing it automatically in the zcml or by printing them in the stdout to copy them manually).

Before re-inventing the wheel, I'd know if there is already a solution for this problem? Maybe there are also other/better ways of doing the same thing. What do you think?

best, Paul

Interesting point you bring up here Paul. We are currently thinking about how we could solve this problem in Volto as well. Looking forward to hearing what ppl bring up. cc @sneridagh @tiberiuichim @avoinea

I am using a package I wrote called GitHub - collective/collective.ptpatcher.

I coded it because we had a rapidly evolving base product used by several customers.
At the time (but even now) changes in the templates were quite frequent.
Some customers needed customizations in those templates (simple or more complex).

Then collective.ptpatcher was created to remove the need to check manually the diffs each time which in some cases was a really hard tasks.
For the same exact reason I developed zpretty that helped me diffing pt much faster.

It is not super easy to describe how collective.ptpatcher is used but basically I have an initialize function:

  <five:registerPackage
      package="."
      initialize=".initialize"
    />

that does something like:

from .personal_settings import patcher as personal_settings_patcher

def initialize(context):
    personal_settings_patcher.apply_patch()

and in the personal_settings.py I have:

from collective.ptpatcher.base import BasePatcher

class PersonalSettingsPatcher(BasePatcher):

    def get_patched(self):
        with open(self.source) as f:
            source = f.read()
        # this is just an example, usually something more complex is done here
        # maybe using lxml or whatever
        return source.replace("foo", "bar")

patcher = PersonalSettingsPatcher(
    source=("base.project.browser", "templates/personal-settings.pt"),
    target=(
        "customer.project",
        "jbot/base.project.browser.templates.personal-settings.pt",
    ),
)

When the instance starts, the patcher runs and changes the template if needed.
You still have to manually check the code and commit it if you want to share it.

I also have a test that checks if the patch is obsolete:

import unittest

class PtPatcherUnitTests(unittest.TestCase):

    def _verify_patcher(self, patcher):
        with open(patcher.target) as f:
            self.assertEqual(
                patcher.get_patched(), f.read(), "Please check %r" % patcher,
            )

    def test_personal_settings_patcher(self):
        from my.package.ptpatcher.personal_settings import patcher

        self._verify_patcher(patcher)

I am pretty satisfied by the way it works even if it is not really super friendly to use.
I think it shaved tons of boring development hours and avoided quite a lot of after deploymnet surprises.

2 Likes

Nothing specific for that use case. We could generate a report when we boot Volto, as we can actually detect the customized files (but I guess a find can tell that as well).

Of course, the best type of customization is the one that you don't have to make. So, in case the customization fixes a bug or it is generic enough to be made an option to the original template, it's probably best to push it upstream.

I actually find that we're moving to "less and less" customizations with our Volto projects, we only have 5 customized files from Volto at the site we're currently building. It's a testament to the work done in Volto lately, with fixes polishing and adding new extension points.

And of course, Volto frontend development has different methods compared to Plone/Zope ZPT, though there's an underlying pattern: to avoid customization of a whole template we need plug-in points. Plone has a ton of plugin points and the customization itself is a plug-in point, but there's portlets and viewlets and adaptors and so on. In Volto have a framework for something similar, the pluggables, though we still need to go back to the old code and add them where appropriate.

1 Like

Currently I just comment on the new template and add a dependency to the specific version of the package I'm overriding in setup.py, like:

Products.CMFPlone==x.y.z # pin to specific version because of template overriding

This will break the build if I try to update the package and hopefully will remember me that I have to check the overrides.

1 Like

When I override a template I first commit the original and then make the necessary changes in the second commit. That in combination with a good commit message lets me quickly see what the purpose of the change was all about.
It also lets me easily diff the original file to any updates from upstream.

4 Likes