Repository: WeblateOrg/weblate

Issue:
Site-wide project creation permission
### Describe the problem

With https://github.com/WeblateOrg/weblate/issues/263 there needs to be a way to grant users creating a project. Currently, there are two approaches depending on the server settings – either via billing limits, or by site-wide permission.

### Describe the solution you'd like

Add role with the side-wide permission so that it can be easily assigned to the users.

### Describe alternatives you've considered

_No response_

### Screenshots

_No response_

### Additional context

_No response_


Discussion:

Resolving patch:
diff --git a/weblate/auth/data.py b/weblate/auth/data.py
--- a/weblate/auth/data.py
+++ b/weblate/auth/data.py
@@ -218,6 +218,7 @@ def filter_perms(prefix: str, exclude: set | None = None):
     ),
     (pgettext_noop("Access-control role", "Manage repository"), filter_perms("vcs.")),
     (pgettext_noop("Access-control role", "Billing"), filter_perms("billing.")),
+    (pgettext_noop("Access-control role", "Add new projects"), {"project.add"}),
 )
 
 # Default set of roles for groups
@@ -243,6 +244,11 @@ def filter_perms(prefix: str, exclude: set | None = None):
         ("Administration",),
         SELECTION_ALL,
     ),
+    (
+        pgettext_noop("Access-control group", "Project creators"),
+        ("Add new projects",),
+        SELECTION_ALL,
+    ),
 )
 
 # Per project group definitions


Test patch:
diff --git a/weblate/api/tests.py b/weblate/api/tests.py
--- a/weblate/api/tests.py
+++ b/weblate/api/tests.py
@@ -402,7 +402,7 @@ def test_list(self):
         self.assertEqual(response.data["count"], 2)
         self.authenticate(True)
         response = self.client.get(reverse("api:group-list"))
-        self.assertEqual(response.data["count"], 6)
+        self.assertEqual(response.data["count"], 7)
 
     def test_get(self):
         response = self.do_request(
@@ -424,7 +424,7 @@ def test_create(self):
             format="json",
             request={"name": "Group", "project_selection": 0, "language_selection": 0},
         )
-        self.assertEqual(Group.objects.count(), 7)
+        self.assertEqual(Group.objects.count(), 8)
 
     def test_create_project(self):
         self.do_request(
@@ -442,7 +442,7 @@ def test_create_project(self):
                 ),
             },
         )
-        self.assertEqual(Group.objects.count(), 7)
+        self.assertEqual(Group.objects.count(), 8)
         group = Group.objects.get(name="Group")
         self.assertEqual(group.defining_project, self.component.project)
 
@@ -731,7 +731,7 @@ def test_delete(self):
             superuser=True,
             code=204,
         )
-        self.assertEqual(Group.objects.count(), 5)
+        self.assertEqual(Group.objects.count(), 6)
 
     def test_put(self):
         self.do_request(
@@ -783,7 +783,7 @@ def test_list_roles(self):
         self.assertEqual(response.data["count"], 2)
         self.authenticate(True)
         response = self.client.get(reverse("api:role-list"))
-        self.assertEqual(response.data["count"], 14)
+        self.assertEqual(response.data["count"], 15)
 
     def test_get_role(self):
         role = Role.objects.get(name="Access repository")
@@ -808,7 +808,7 @@ def test_create(self):
             format="json",
             request={"name": "Role", "permissions": ["suggestion.add", "comment.add"]},
         )
-        self.assertEqual(Role.objects.count(), 15)
+        self.assertEqual(Role.objects.count(), 16)
         self.assertEqual(Role.objects.get(name="Role").permissions.count(), 2)
 
     def test_delete(self):
@@ -819,7 +819,7 @@ def test_delete(self):
             superuser=True,
             code=204,
         )
-        self.assertEqual(Role.objects.count(), 13)
+        self.assertEqual(Role.objects.count(), 14)
 
     def test_put(self):
         self.do_request(