Skip to content

feat: migrate billing.view permission to billing ownership#17740

Draft
nijel wants to merge 1 commit intoWeblateOrg:mainfrom
nijel:billing-owners
Draft

feat: migrate billing.view permission to billing ownership#17740
nijel wants to merge 1 commit intoWeblateOrg:mainfrom
nijel:billing-owners

Conversation

@nijel
Copy link
Member

@nijel nijel commented Jan 22, 2026

The permission model duplicated access granted by billing ownership.

TODO:

  • migrate existing users with permissions to ownership model
  • add UI to manage ownership

Fixes #17277

@nijel nijel added this to the 5.16 milestone Jan 22, 2026
@nijel nijel self-assigned this Jan 22, 2026
@nijel nijel force-pushed the billing-owners branch 4 times, most recently from e9925fb to 2055568 Compare January 23, 2026 14:22
@codecov
Copy link

codecov bot commented Jan 23, 2026

❌ 16 Tests Failed:

Tests completed Failed Passed Skipped
5629 16 5613 695
View the top 3 failed test(s) by shortest run time
weblate.billing.tests.BillingTest::test_trial
Stack Traces | 0.106s run time
self = <weblate.billing.tests.BillingTest testMethod=test_trial>

    @override_settings(EMAIL_SUBJECT_PREFIX="")
    def test_trial(self) -> None:
        self.billing.state = Billing.STATE_TRIAL
        self.billing.save(skip_limits=True)
        self.billing.invoice_set.all().delete()
>       self.add_project()

weblate/billing/tests.py:276: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate/billing/tests.py:58: in add_project
    project.add_user(self.user, "Billing")
.../trans/models/project.py:416: in add_user
    group_objs = [self.defined_groups.get(name=group)]
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.14.../db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <GroupQuerySet []>, args = (), kwargs = {'name': 'Billing'}
clone = <GroupQuerySet []>, limit = 21, num = 0

    def get(self, *args, **kwargs):
        """
        Perform the query and return a single object matching the given
        keyword arguments.
        """
        if self.query.combinator and (args or kwargs):
            raise NotSupportedError(
                "Calling QuerySet.get(...) with filters after %s() is not "
                "supported." % self.query.combinator
            )
        clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
        if self.query.can_filter() and not self.query.distinct_fields:
            clone = clone.order_by()
        limit = None
        if (
            not clone.query.select_for_update
            or connections[clone.db].features.supports_select_for_update_with_limit
        ):
            limit = MAX_GET_RESULTS
            clone.query.set_limits(high=limit)
        num = len(clone)
        if num == 1:
            return clone._result_cache[0]
        if not num:
>           raise self.model.DoesNotExist(
                "%s matching query does not exist." % self.model._meta.object_name
            )
E           weblate.auth.models.Group.DoesNotExist: Group matching query does not exist.

.venv/lib/python3.14.../db/models/query.py:635: DoesNotExist
weblate.billing.tests.BillingTest::test_view_billing
Stack Traces | 0.148s run time
self = <weblate.billing.tests.BillingTest testMethod=test_view_billing>

    def test_view_billing(self) -> None:
>       self.add_project()

weblate/billing/tests.py:62: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate/billing/tests.py:58: in add_project
    project.add_user(self.user, "Billing")
.../trans/models/project.py:416: in add_user
    group_objs = [self.defined_groups.get(name=group)]
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.14.../db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <GroupQuerySet []>, args = (), kwargs = {'name': 'Billing'}
clone = <GroupQuerySet []>, limit = 21, num = 0

    def get(self, *args, **kwargs):
        """
        Perform the query and return a single object matching the given
        keyword arguments.
        """
        if self.query.combinator and (args or kwargs):
            raise NotSupportedError(
                "Calling QuerySet.get(...) with filters after %s() is not "
                "supported." % self.query.combinator
            )
        clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
        if self.query.can_filter() and not self.query.distinct_fields:
            clone = clone.order_by()
        limit = None
        if (
            not clone.query.select_for_update
            or connections[clone.db].features.supports_select_for_update_with_limit
        ):
            limit = MAX_GET_RESULTS
            clone.query.set_limits(high=limit)
        num = len(clone)
        if num == 1:
            return clone._result_cache[0]
        if not num:
>           raise self.model.DoesNotExist(
                "%s matching query does not exist." % self.model._meta.object_name
            )
E           weblate.auth.models.Group.DoesNotExist: Group matching query does not exist.

.venv/lib/python3.14.../db/models/query.py:635: DoesNotExist
weblate.billing.tests.BillingTest::test_remove_project
Stack Traces | 0.164s run time
self = <weblate.billing.tests.BillingTest testMethod=test_remove_project>

    def test_remove_project(self) -> None:
        second_user = User.objects.create_user(
            username="bill2", password="kill2", email="noreply2@example.net"
        )
        third_user = User.objects.create_user(
            username="bill3", password="kill3", email="noreply3@example.net"
        )
>       project = self.add_project()
                  ^^^^^^^^^^^^^^^^^^

weblate/billing/tests.py:378: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate/billing/tests.py:58: in add_project
    project.add_user(self.user, "Billing")
.../trans/models/project.py:416: in add_user
    group_objs = [self.defined_groups.get(name=group)]
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.14.../db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <GroupQuerySet []>, args = (), kwargs = {'name': 'Billing'}
clone = <GroupQuerySet []>, limit = 21, num = 0

    def get(self, *args, **kwargs):
        """
        Perform the query and return a single object matching the given
        keyword arguments.
        """
        if self.query.combinator and (args or kwargs):
            raise NotSupportedError(
                "Calling QuerySet.get(...) with filters after %s() is not "
                "supported." % self.query.combinator
            )
        clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
        if self.query.can_filter() and not self.query.distinct_fields:
            clone = clone.order_by()
        limit = None
        if (
            not clone.query.select_for_update
            or connections[clone.db].features.supports_select_for_update_with_limit
        ):
            limit = MAX_GET_RESULTS
            clone.query.set_limits(high=limit)
        num = len(clone)
        if num == 1:
            return clone._result_cache[0]
        if not num:
>           raise self.model.DoesNotExist(
                "%s matching query does not exist." % self.model._meta.object_name
            )
E           weblate.auth.models.Group.DoesNotExist: Group matching query does not exist.

.venv/lib/python3.14.../db/models/query.py:635: DoesNotExist
weblate.billing.tests.BillingTest::test_free_trial
Stack Traces | 0.167s run time
self = <weblate.billing.tests.BillingTest testMethod=test_free_trial>

    def test_free_trial(self) -> None:
        self.plan.price = 0
        self.plan.yearly_price = 0
        self.plan.save()
>       self.test_trial()

weblate/billing/tests.py:369: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.venv/lib/python3.14.../django/test/utils.py:456: in inner
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
weblate/billing/tests.py:276: in test_trial
    self.add_project()
weblate/billing/tests.py:58: in add_project
    project.add_user(self.user, "Billing")
.../trans/models/project.py:416: in add_user
    group_objs = [self.defined_groups.get(name=group)]
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.14.../db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <GroupQuerySet []>, args = (), kwargs = {'name': 'Billing'}
clone = <GroupQuerySet []>, limit = 21, num = 0

    def get(self, *args, **kwargs):
        """
        Perform the query and return a single object matching the given
        keyword arguments.
        """
        if self.query.combinator and (args or kwargs):
            raise NotSupportedError(
                "Calling QuerySet.get(...) with filters after %s() is not "
                "supported." % self.query.combinator
            )
        clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
        if self.query.can_filter() and not self.query.distinct_fields:
            clone = clone.order_by()
        limit = None
        if (
            not clone.query.select_for_update
            or connections[clone.db].features.supports_select_for_update_with_limit
        ):
            limit = MAX_GET_RESULTS
            clone.query.set_limits(high=limit)
        num = len(clone)
        if num == 1:
            return clone._result_cache[0]
        if not num:
>           raise self.model.DoesNotExist(
                "%s matching query does not exist." % self.model._meta.object_name
            )
E           weblate.auth.models.Group.DoesNotExist: Group matching query does not exist.

.venv/lib/python3.14.../db/models/query.py:635: DoesNotExist
weblate.trans.tests.test_models.ProjectTest::test_acl
Stack Traces | 0.174s run time
self = <weblate.trans.tests.test_models.ProjectTest testMethod=test_acl>

    def test_acl(self) -> None:
        """Test for ACL handling."""
        # Create user to verify ACL
        user = create_test_user()
    
        # Create project
        project = self.create_project()
    
        self.assertEqual(
            {"Administration"},
            set(project.defined_groups.values_list("name", flat=True)),
        )
    
        # Enable ACL
        project.access_control = Project.ACCESS_PRIVATE
        project.save()
>       self.assertEqual(
            {
                "Administration",
                "Automatic translation",
                "Billing",
                "Bulk editing",
                "Glossary",
                "Languages",
                "Memory",
                "Screenshots",
                "Sources",
                "Translate",
                "VCS",
            },
            set(project.defined_groups.values_list("name", flat=True)),
        )
E       AssertionError: Items in the first set but not the second:
E       'Billing'

.../trans/tests/test_models.py:216: AssertionError
weblate.trans.tests.test_acl.ACLLegalTestCase::test_acl_groups
Stack Traces | 0.191s run time
self = <weblate.trans.tests.test_acl.ACLLegalTestCase testMethod=test_acl_groups>

    def test_acl_groups(self) -> None:
        """Test handling ACL groups."""
        billing_group = 1 if "weblate.billing" in settings.INSTALLED_APPS else 0
        self.project.defined_groups.all().delete()
        self.project.access_control = Project.ACCESS_PUBLIC
        self.project.translation_review = False
        self.project.save()
        self.assertEqual(1, self.project.defined_groups.count())
        self.project.access_control = Project.ACCESS_PROTECTED
        self.project.translation_review = True
        self.project.save()
>       self.assertEqual(11 + billing_group, self.project.defined_groups.count())
E       AssertionError: 12 != 11

.../trans/tests/test_acl.py:379: AssertionError
weblate.billing.tests.BillingTest::test_limit_projects
Stack Traces | 0.247s run time
self = <weblate.billing.tests.BillingTest testMethod=test_limit_projects>

    def test_limit_projects(self) -> None:
        self.assertTrue(self.billing.in_limits)
>       self.add_project()

weblate/billing/tests.py:87: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate/billing/tests.py:58: in add_project
    project.add_user(self.user, "Billing")
.../trans/models/project.py:416: in add_user
    group_objs = [self.defined_groups.get(name=group)]
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.14.../db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <GroupQuerySet []>, args = (), kwargs = {'name': 'Billing'}
clone = <GroupQuerySet []>, limit = 21, num = 0

    def get(self, *args, **kwargs):
        """
        Perform the query and return a single object matching the given
        keyword arguments.
        """
        if self.query.combinator and (args or kwargs):
            raise NotSupportedError(
                "Calling QuerySet.get(...) with filters after %s() is not "
                "supported." % self.query.combinator
            )
        clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
        if self.query.can_filter() and not self.query.distinct_fields:
            clone = clone.order_by()
        limit = None
        if (
            not clone.query.select_for_update
            or connections[clone.db].features.supports_select_for_update_with_limit
        ):
            limit = MAX_GET_RESULTS
            clone.query.set_limits(high=limit)
        num = len(clone)
        if num == 1:
            return clone._result_cache[0]
        if not num:
>           raise self.model.DoesNotExist(
                "%s matching query does not exist." % self.model._meta.object_name
            )
E           weblate.auth.models.Group.DoesNotExist: Group matching query does not exist.

.venv/lib/python3.14.../db/models/query.py:635: DoesNotExist
weblate.trans.tests.test_acl.ACLTest::test_acl_groups
Stack Traces | 0.26s run time
self = <weblate.trans.tests.test_acl.ACLTest testMethod=test_acl_groups>

    def test_acl_groups(self) -> None:
        """Test handling ACL groups."""
        billing_group = 1 if "weblate.billing" in settings.INSTALLED_APPS else 0
        self.project.defined_groups.all().delete()
        self.project.access_control = Project.ACCESS_PUBLIC
        self.project.translation_review = False
        self.project.save()
        self.assertEqual(1, self.project.defined_groups.count())
        self.project.access_control = Project.ACCESS_PROTECTED
        self.project.translation_review = True
        self.project.save()
>       self.assertEqual(11 + billing_group, self.project.defined_groups.count())
E       AssertionError: 12 != 11

.../trans/tests/test_acl.py:379: AssertionError
weblate.billing.tests.BillingTest::test_commands
Stack Traces | 0.263s run time
self = <weblate.billing.tests.BillingTest testMethod=test_commands>

    def test_commands(self) -> None:
        out = StringIO()
        call_command("billing_check", stdout=out)
        self.assertEqual(out.getvalue(), "")
>       self.add_project()

weblate/billing/tests.py:101: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate/billing/tests.py:58: in add_project
    project.add_user(self.user, "Billing")
.../trans/models/project.py:416: in add_user
    group_objs = [self.defined_groups.get(name=group)]
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.14.../db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <GroupQuerySet []>, args = (), kwargs = {'name': 'Billing'}
clone = <GroupQuerySet []>, limit = 21, num = 0

    def get(self, *args, **kwargs):
        """
        Perform the query and return a single object matching the given
        keyword arguments.
        """
        if self.query.combinator and (args or kwargs):
            raise NotSupportedError(
                "Calling QuerySet.get(...) with filters after %s() is not "
                "supported." % self.query.combinator
            )
        clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
        if self.query.can_filter() and not self.query.distinct_fields:
            clone = clone.order_by()
        limit = None
        if (
            not clone.query.select_for_update
            or connections[clone.db].features.supports_select_for_update_with_limit
        ):
            limit = MAX_GET_RESULTS
            clone.query.set_limits(high=limit)
        num = len(clone)
        if num == 1:
            return clone._result_cache[0]
        if not num:
>           raise self.model.DoesNotExist(
                "%s matching query does not exist." % self.model._meta.object_name
            )
E           weblate.auth.models.Group.DoesNotExist: Group matching query does not exist.

.venv/lib/python3.14.../db/models/query.py:635: DoesNotExist
weblate.billing.tests.BillingTest::test_download
Stack Traces | 0.264s run time
self = <weblate.billing.tests.BillingTest testMethod=test_download>

    @override_settings(INVOICE_PATH_LEGACY=TEST_DATA)
    def test_download(self) -> None:
>       self.add_project()

weblate/billing/tests.py:172: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate/billing/tests.py:58: in add_project
    project.add_user(self.user, "Billing")
.../trans/models/project.py:416: in add_user
    group_objs = [self.defined_groups.get(name=group)]
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.14.../db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <GroupQuerySet []>, args = (), kwargs = {'name': 'Billing'}
clone = <GroupQuerySet []>, limit = 21, num = 0

    def get(self, *args, **kwargs):
        """
        Perform the query and return a single object matching the given
        keyword arguments.
        """
        if self.query.combinator and (args or kwargs):
            raise NotSupportedError(
                "Calling QuerySet.get(...) with filters after %s() is not "
                "supported." % self.query.combinator
            )
        clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
        if self.query.can_filter() and not self.query.distinct_fields:
            clone = clone.order_by()
        limit = None
        if (
            not clone.query.select_for_update
            or connections[clone.db].features.supports_select_for_update_with_limit
        ):
            limit = MAX_GET_RESULTS
            clone.query.set_limits(high=limit)
        num = len(clone)
        if num == 1:
            return clone._result_cache[0]
        if not num:
>           raise self.model.DoesNotExist(
                "%s matching query does not exist." % self.model._meta.object_name
            )
E           weblate.auth.models.Group.DoesNotExist: Group matching query does not exist.

.venv/lib/python3.14.../db/models/query.py:635: DoesNotExist
weblate.billing.tests.BillingTest::test_billing_notify
Stack Traces | 0.266s run time
self = <weblate.billing.tests.BillingTest testMethod=test_billing_notify>

    def test_billing_notify(self) -> None:
        self.assertEqual(len(mail.outbox), 0)
>       self.add_project()

weblate/billing/tests.py:127: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate/billing/tests.py:58: in add_project
    project.add_user(self.user, "Billing")
.../trans/models/project.py:416: in add_user
    group_objs = [self.defined_groups.get(name=group)]
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.14.../db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <GroupQuerySet []>, args = (), kwargs = {'name': 'Billing'}
clone = <GroupQuerySet []>, limit = 21, num = 0

    def get(self, *args, **kwargs):
        """
        Perform the query and return a single object matching the given
        keyword arguments.
        """
        if self.query.combinator and (args or kwargs):
            raise NotSupportedError(
                "Calling QuerySet.get(...) with filters after %s() is not "
                "supported." % self.query.combinator
            )
        clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
        if self.query.can_filter() and not self.query.distinct_fields:
            clone = clone.order_by()
        limit = None
        if (
            not clone.query.select_for_update
            or connections[clone.db].features.supports_select_for_update_with_limit
        ):
            limit = MAX_GET_RESULTS
            clone.query.set_limits(high=limit)
        num = len(clone)
        if num == 1:
            return clone._result_cache[0]
        if not num:
>           raise self.model.DoesNotExist(
                "%s matching query does not exist." % self.model._meta.object_name
            )
E           weblate.auth.models.Group.DoesNotExist: Group matching query does not exist.

.venv/lib/python3.14.../db/models/query.py:635: DoesNotExist
weblate.trans.tests.test_acl.ACLLoginRequiredTestCase::test_acl_groups
Stack Traces | 0.315s run time
self = <weblate.trans.tests.test_acl.ACLLoginRequiredTestCase testMethod=test_acl_groups>

    def test_acl_groups(self) -> None:
        """Test handling ACL groups."""
        billing_group = 1 if "weblate.billing" in settings.INSTALLED_APPS else 0
        self.project.defined_groups.all().delete()
        self.project.access_control = Project.ACCESS_PUBLIC
        self.project.translation_review = False
        self.project.save()
        self.assertEqual(1, self.project.defined_groups.count())
        self.project.access_control = Project.ACCESS_PROTECTED
        self.project.translation_review = True
        self.project.save()
>       self.assertEqual(11 + billing_group, self.project.defined_groups.count())
E       AssertionError: 12 != 11

.../trans/tests/test_acl.py:379: AssertionError
weblate.billing.tests.BillingTest::test_expiry
Stack Traces | 0.37s run time
self = <weblate.billing.tests.BillingTest testMethod=test_expiry>

    @override_settings(EMAIL_SUBJECT_PREFIX="")
    def test_expiry(self) -> None:
>       self.add_project()

weblate/billing/tests.py:212: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate/billing/tests.py:58: in add_project
    project.add_user(self.user, "Billing")
.../trans/models/project.py:416: in add_user
    group_objs = [self.defined_groups.get(name=group)]
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.14.../db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <GroupQuerySet []>, args = (), kwargs = {'name': 'Billing'}
clone = <GroupQuerySet []>, limit = 21, num = 0

    def get(self, *args, **kwargs):
        """
        Perform the query and return a single object matching the given
        keyword arguments.
        """
        if self.query.combinator and (args or kwargs):
            raise NotSupportedError(
                "Calling QuerySet.get(...) with filters after %s() is not "
                "supported." % self.query.combinator
            )
        clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
        if self.query.can_filter() and not self.query.distinct_fields:
            clone = clone.order_by()
        limit = None
        if (
            not clone.query.select_for_update
            or connections[clone.db].features.supports_select_for_update_with_limit
        ):
            limit = MAX_GET_RESULTS
            clone.query.set_limits(high=limit)
        num = len(clone)
        if num == 1:
            return clone._result_cache[0]
        if not num:
>           raise self.model.DoesNotExist(
                "%s matching query does not exist." % self.model._meta.object_name
            )
E           weblate.auth.models.Group.DoesNotExist: Group matching query does not exist.

.venv/lib/python3.14.../db/models/query.py:635: DoesNotExist
weblate.api.tests.RoleAPITest::test_delete
Stack Traces | 1.97s run time
self = <weblate.api.tests.RoleAPITest testMethod=test_delete>

    def test_delete(self) -> None:
        self.do_request(
            "api:role-detail",
            kwargs={"id": Role.objects.all()[0].pk},
            method="delete",
            superuser=True,
            code=204,
        )
>       self.assertEqual(Role.objects.count(), 16)
E       AssertionError: 15 != 16

weblate/api/tests.py:1278: AssertionError
weblate.api.tests.RoleAPITest::test_create
Stack Traces | 2.28s run time
self = <weblate.api.tests.RoleAPITest testMethod=test_create>

    def test_create(self) -> None:
        self.do_request("api:role-list", method="post", code=403)
        self.do_request(
            "api:role-list",
            method="post",
            superuser=True,
            code=400,
            format="json",
            request={"name": "Role", "permissions": ["invalid.codename"]},
        )
        self.do_request(
            "api:role-list",
            method="post",
            superuser=True,
            code=201,
            format="json",
            request={"name": "Role", "permissions": ["suggestion.add", "comment.add"]},
        )
>       self.assertEqual(Role.objects.count(), 18)
E       AssertionError: 17 != 18

weblate/api/tests.py:1267: AssertionError
weblate.api.tests.RoleAPITest::test_list_roles
Stack Traces | 2.72s run time
self = <weblate.api.tests.RoleAPITest testMethod=test_list_roles>

    def test_list_roles(self) -> None:
        response = self.client.get(reverse("api:role-list"))
        self.assertEqual(response.data["count"], 2)
    
        self.authenticate(True)
        response = self.client.get(reverse("api:role-list"))
>       self.assertEqual(response.data["count"], 17)
E       AssertionError: 16 != 17

weblate/api/tests.py:1228: AssertionError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

The permission model duplicated access granted by billing ownership.

TODO:

- migrate existing users with permissions to ownership model
- add UI to manage ownership

Fixes WeblateOrg#17277
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The billing.view permission is confusing

1 participant