Repository: WeblateOrg/weblate

Issue:
All add-ons have been removed
### Describe the issue

All add-ons have been removed throughout the weblate. All addons are missing. The worst problem is with the automatic translation add-ons, because I am unable to reconstruct in which components it was enabled (it was included in a dozen or several dozen components). 

The big problem is the "Update PO files to match POT (msgmerge)" add-on, here about 400 components had different settings

I haven't tried to reconfigure these addons yet, because I hope it will be possible to reproduce it. 


Five days ago everything was still working.

### I already tried

- [X] I've read and searched [the documentation](https://docs.weblate.org/).
- [X] I've searched for similar filed issues in this repository.

### Steps to reproduce the behavior

1) Go to https://example.com/manage/addons/ there are no add-ons

### Expected behavior

_No response_

### Screenshots

![image](https://github.com/WeblateOrg/weblate/assets/39633242/40373912-2d62-4b5b-bd48-431d93f0aae6)

### Exception traceback

_No response_

### How do you run Weblate?

Docker container

### Weblate versions

<html>
<body>
<!--StartFragment-->
5.5.2 — 498e77fb241e962ff85541eed05f7fca63ffc5fd
--


<!--EndFragment-->
</body>
</html>5.5.2 — [498e77fb241e962ff85541eed05f7fca63ffc5fd](https://github.com/WeblateOrg/weblate/commits/498e77fb241e962ff85541eed05f7fca63ffc5fd)

### Weblate deploy checks

_No response_

### Additional context

_No response_


Discussion:
![image](https://github.com/WeblateOrg/weblate/assets/39633242/28abf921-4e04-4ff9-82ee-cbadd4ad6651)
However, addons are active because the new component was created by the addon itself. But all add-ons and their settings are gone in the weblate interface.
Everything is fine, add-on settings are only in the base component for the project and from it it is copied to the rest of the components.
Something has changed in addition to finding new components, because it does not run immediately after receiving information from the repository, but only after a long time later. This confused me.
The issue you have reported is now resolved. If you don’t feel it’s right, please follow its labels to get a clue for further steps.

* In case you see a similar problem, please open a separate issue.
* If you are happy with the outcome, don’t hesitate to support Weblate by making a [donation](https://weblate.org/donate/).

Resolving patch:
diff --git a/weblate/addons/views.py b/weblate/addons/views.py
--- a/weblate/addons/views.py
+++ b/weblate/addons/views.py
@@ -31,6 +31,8 @@ def get_queryset(self):
             self.kwargs["project_obj"] = self.path_object
             return Addon.objects.filter_project(self.path_object)
 
+        if not self.request.user.has_perm("management.addons"):
+            raise PermissionDenied("Can not manage add-ons")
         return Addon.objects.filter_sitewide()
 
     def get_success_url(self):
@@ -59,6 +61,10 @@ def get_context_data(self, **kwargs):
                 ),
                 key=lambda x: x.name,
             )
+            result["scope"] = "component"
+            result["project_addons"] = Addon.objects.filter_project(
+                target.project
+            ).count()
         else:
             # This covers both project-wide and site-wide
             result["available"] = sorted(
@@ -69,6 +75,10 @@ def get_context_data(self, **kwargs):
                 ),
                 key=lambda x: x.name,
             )
+            result["scope"] = "sitewide" if target is None else "project"
+
+        if target is not None:
+            result["sitewide_addons"] = Addon.objects.filter_sitewide().count()
 
         return result
 
@@ -165,6 +175,12 @@ def get_object(self):
             raise PermissionDenied("Can not edit component")
         if obj.project and not self.request.user.has_perm("project.edit", obj.project):
             raise PermissionDenied("Can not edit project")
+        if (
+            obj.project is None
+            and obj.component is None
+            and not self.request.user.has_perm("management.addons")
+        ):
+            raise PermissionDenied("Can not manage add-ons")
         return obj
 
     def post(self, request, *args, **kwargs):
diff --git a/weblate/auth/data.py b/weblate/auth/data.py
--- a/weblate/auth/data.py
+++ b/weblate/auth/data.py
@@ -139,6 +139,8 @@
     ("componentlist.edit", gettext_noop("Manage component lists")),
     # Translators: Permission name
     ("billing.manage", gettext_noop("Manage billing")),
+    # Translators: Permission name
+    ("management.addons", gettext_noop("Manage site-wide add-ons")),
 )
 
 GLOBAL_PERM_NAMES = {perm[0] for perm in GLOBAL_PERMISSIONS}


Test patch:
diff --git a/weblate/addons/tests.py b/weblate/addons/tests.py
--- a/weblate/addons/tests.py
+++ b/weblate/addons/tests.py
@@ -695,7 +695,7 @@ def test_add_simple(self) -> None:
             {"name": "weblate.gettext.authors"},
             follow=True,
         )
-        self.assertContains(response, "1 add-on installed")
+        self.assertContains(response, "Installed 1 add-on")
 
     def test_add_simple_project_addon(self) -> None:
         response = self.client.post(
@@ -703,7 +703,7 @@ def test_add_simple_project_addon(self) -> None:
             {"name": "weblate.consistency.languages"},
             follow=True,
         )
-        self.assertContains(response, "1 add-on installed")
+        self.assertContains(response, "Installed 1 add-on")
 
     def test_add_simple_site_wide_addon(self) -> None:
         response = self.client.post(
@@ -711,7 +711,15 @@ def test_add_simple_site_wide_addon(self) -> None:
             {"name": "weblate.consistency.languages"},
             follow=True,
         )
-        self.assertContains(response, "1 add-on installed")
+        self.assertEqual(response.status_code, 403)
+        self.user.is_superuser = True
+        self.user.save()
+        response = self.client.post(
+            reverse("manage-addons"),
+            {"name": "weblate.consistency.languages"},
+            follow=True,
+        )
+        self.assertContains(response, "Installed 1 add-on")
 
     def test_add_invalid(self) -> None:
         response = self.client.post(
@@ -738,9 +746,17 @@ def test_add_config(self) -> None:
             },
             follow=True,
         )
-        self.assertContains(response, "1 add-on installed")
+        self.assertContains(response, "Installed 1 add-on")
 
     def test_add_config_site_wide_addon(self) -> None:
+        response = self.client.post(
+            reverse("manage-addons"),
+            {"name": "weblate.generate.generate"},
+            follow=True,
+        )
+        self.assertEqual(response.status_code, 403)
+        self.user.is_superuser = True
+        self.user.save()
         response = self.client.post(
             reverse("manage-addons"),
             {"name": "weblate.generate.generate"},
@@ -757,7 +773,7 @@ def test_add_config_site_wide_addon(self) -> None:
             },
             follow=True,
         )
-        self.assertContains(response, "1 add-on installed")
+        self.assertContains(response, "Installed 1 add-on")
 
     def test_add_pseudolocale(self) -> None:
         response = self.client.post(
@@ -776,7 +792,7 @@ def test_add_pseudolocale(self) -> None:
             },
             follow=True,
         )
-        self.assertContains(response, "1 add-on installed")
+        self.assertContains(response, "Installed 1 add-on")
 
     def test_edit_config(self) -> None:
         self.test_add_config()
@@ -792,7 +808,7 @@ def test_delete(self) -> None:
         response = self.client.post(
             addon.instance.get_absolute_url(), {"delete": "1"}, follow=True
         )
-        self.assertContains(response, "no add-ons currently installed")
+        self.assertContains(response, "No add-ons currently installed")
 
 
 class PropertiesAddonTest(ViewTestCase):
@@ -1069,7 +1085,7 @@ def test_form(self) -> None:
                 },
                 follow=True,
             )
-        self.assertContains(response, "1 add-on installed")
+        self.assertContains(response, "Installed 1 add-on")
 
 
 class ScriptsTest(TestAddonMixin, ViewTestCase):
@@ -1330,7 +1346,7 @@ def test_create(self) -> None:
             },
             follow=True,
         )
-        self.assertContains(response, "1 add-on installed")
+        self.assertContains(response, "Installed 1 add-on")
 
 
 class CDNJSAddonTest(ViewTestCase):