Skip to content

*: replace Celery with django-q#13436

Closed
rissson wants to merge 5 commits intomainfrom
celery-2-django-q
Closed

*: replace Celery with django-q#13436
rissson wants to merge 5 commits intomainfrom
celery-2-django-q

Conversation

@rissson
Copy link
Member

@rissson rissson commented Mar 7, 2025

Details

REPLACE ME


Checklist

  • Local tests pass (ak test authentik/)
  • The code has been formatted (make lint-fix)

If an API change has been made

  • The API schema has been updated (make gen-build)

If changes to the frontend have been made

  • The code has been formatted (make web)

If applicable

  • The documentation has been updated
  • The documentation has been formatted (make website)

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
@rissson rissson self-assigned this Mar 7, 2025
@netlify
Copy link

netlify bot commented Mar 7, 2025

Deploy Preview for authentik-storybook canceled.

Name Link
🔨 Latest commit 584ef18
🔍 Latest deploy log https://app.netlify.com/sites/authentik-storybook/deploys/67cc51b469b46600081570d7

@netlify
Copy link

netlify bot commented Mar 7, 2025

Deploy Preview for authentik-docs canceled.

Name Link
🔨 Latest commit 584ef18
🔍 Latest deploy log https://app.netlify.com/sites/authentik-docs/deploys/67cc51b452ee550008c2a7a3

@codecov
Copy link

codecov bot commented Mar 7, 2025

❌ 734 Tests Failed:

Tests completed Failed Passed Skipped
1672 734 938 2
View the top 3 failed test(s) by shortest run time
authentik.enterprise.tests.test_license.TestEnterpriseLicense::test_valid
Stack Traces | 0.008s run time
self = <unittest.case._Outcome object at 0x7efea091cda0>
test_case = <authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_valid>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.12.9............/x64/lib/python3.12/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_valid>
result = <TestCaseFunction test_valid>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.12.9............/x64/lib/python3.12/unittest/case.py:634: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_valid>
method = <bound method TestEnterpriseLicense.test_valid of <authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_valid>>

    def _callTestMethod(self, method):
>       if method() is not None:

.../hostedtoolcache/Python/3.12.9............/x64/lib/python3.12/unittest/case.py:589: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_valid>,)
keywargs = {}
newargs = (<authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_valid>,)
newkeywargs = {}

    @wraps(func)
    def patched(*args, **keywargs):
        with self.decoration_helper(patched,
                                    args,
                                    keywargs) as (newargs, newkeywargs):
>           return func(*newargs, **newkeywargs)

.../hostedtoolcache/Python/3.12.9............/x64/lib/python3.12/unittest/mock.py:1396: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_valid>

    @patch(
        "authentik.enterprise.license.LicenseKey.validate",
        MagicMock(
            return_value=LicenseKey(
                aud="",
                exp=expiry_valid,
                name=generate_id(),
                internal_users=100,
                external_users=100,
            )
        ),
    )
    def test_valid(self):
        """Check license verification"""
>       lic = License.objects.create(key=generate_id())

.../enterprise/tests/test_license.py:51: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.models.manager.Manager object at 0x7efea39a2e10>, args = ()
kwargs = {'key': 'dETuyVdZ69O5Iq6UmA797xxJHG0kHwYNozJsfsWp'}

    @wraps(method)
    def manager_method(self, *args, **kwargs):
>       return getattr(self.get_queryset(), name)(*args, **kwargs)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../db/models/manager.py:87: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <QuerySet []>
kwargs = {'key': 'dETuyVdZ69O5Iq6UmA797xxJHG0kHwYNozJsfsWp'}
reverse_one_to_one_fields = frozenset()
obj = <License: License object (9e86c733-35f3-4182-9320-b6dd1a517ffd)>

    def create(self, **kwargs):
        """
        Create a new object with the given kwargs, saving it to the database
        and returning the created object.
        """
        reverse_one_to_one_fields = frozenset(kwargs).intersection(
            self.model._meta._reverse_one_to_one_field_names
        )
        if reverse_one_to_one_fields:
            raise ValueError(
                "The following fields do not exist in this model: %s"
                % ", ".join(reverse_one_to_one_fields)
            )
    
        obj = self.model(**kwargs)
        self._for_write = True
>       obj.save(force_insert=True, using=self.db)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../db/models/query.py:679: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <License: License object (9e86c733-35f3-4182-9320-b6dd1a517ffd)>
force_insert = True, force_update = False, using = 'default'
update_fields = None

    def save(
        self, force_insert=False, force_update=False, using=None, update_fields=None
    ):
        """
        Save the current instance. Override this in a subclass if you want to
        control the saving process.
    
        The 'force_insert' and 'force_update' parameters can be used to insist
        that the "save" must be an SQL insert or update (or equivalent for
        non-SQL backends), respectively. Normally, they should not be set.
        """
        self._prepare_related_fields_for_save(operation_name="save")
    
        using = using or router.db_for_write(self.__class__, instance=self)
        if force_insert and (force_update or update_fields):
            raise ValueError("Cannot force both insert and updating in model saving.")
    
        deferred_non_generated_fields = {
            f.attname
            for f in self._meta.concrete_fields
            if f.attname not in self.__dict__ and f.generated is False
        }
        if update_fields is not None:
            # If update_fields is empty, skip the save. We do also check for
            # no-op saves later on for inheritance cases. This bailout is
            # still needed for skipping signal sending.
            if not update_fields:
                return
    
            update_fields = frozenset(update_fields)
            field_names = self._meta._non_pk_concrete_field_names
            non_model_fields = update_fields.difference(field_names)
    
            if non_model_fields:
                raise ValueError(
                    "The following fields do not exist in this model, are m2m "
                    "fields, or are non-concrete fields: %s"
                    % ", ".join(non_model_fields)
                )
    
        # If saving to the same database, and this model is deferred, then
        # automatically do an "update_fields" save on the loaded fields.
        elif (
            not force_insert
            and deferred_non_generated_fields
            and using == self._state.db
        ):
            field_names = set()
            for field in self._meta.concrete_fields:
                if not field.primary_key and not hasattr(field, "through"):
                    field_names.add(field.attname)
            loaded_fields = field_names.difference(deferred_non_generated_fields)
            if loaded_fields:
                update_fields = frozenset(loaded_fields)
    
>       self.save_base(
            using=using,
            force_insert=force_insert,
            force_update=force_update,
            update_fields=update_fields,
        )

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../db/models/base.py:822: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <License: License object (9e86c733-35f3-4182-9320-b6dd1a517ffd)>
raw = False, force_insert = (<class 'authentik.enterprise.models.License'>,)
force_update = False, using = 'default', update_fields = None

    def save_base(
        self,
        raw=False,
        force_insert=False,
        force_update=False,
        using=None,
        update_fields=None,
    ):
        """
        Handle the parts of saving which should be done only once per save,
        yet need to be done in raw saves, too. This includes some sanity
        checks and signal sending.
    
        The 'raw' argument is telling save_base not to save any parent
        models and not to do any changes to the values before save. This
        is used by fixture loading.
        """
        using = using or router.db_for_write(self.__class__, instance=self)
        assert not (force_insert and (force_update or update_fields))
        assert update_fields is None or update_fields
        cls = origin = self.__class__
        # Skip proxies, but keep the origin as the proxy model.
        if cls._meta.proxy:
            cls = cls._meta.concrete_model
        meta = cls._meta
        if not meta.auto_created:
            pre_save.send(
                sender=origin,
                instance=self,
                raw=raw,
                using=using,
                update_fields=update_fields,
            )
        # A transaction isn't needed if one query is issued.
        if meta.parents:
            context_manager = transaction.atomic(using=using, savepoint=False)
        else:
            context_manager = transaction.mark_for_rollback_on_error(using=using)
        with context_manager:
            parent_inserted = False
            if not raw:
                # Validate force insert only when parents are inserted.
                force_insert = self._validate_force_insert(force_insert)
                parent_inserted = self._save_parents(
                    cls, using, update_fields, force_insert
                )
            updated = self._save_table(
                raw,
                cls,
                force_insert or parent_inserted,
                force_update,
                using,
                update_fields,
            )
        # Store the database on which the object was saved
        self._state.db = using
        # Once saved, this is no longer a to-be-added instance.
        self._state.adding = False
    
        # Signal that the save is complete
        if not meta.auto_created:
>           post_save.send(
                sender=origin,
                instance=self,
                created=(not updated),
                update_fields=update_fields,
                raw=raw,
                using=using,
            )

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../db/models/base.py:924: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.models.signals.ModelSignal object at 0x7efeb0598b30>
sender = <class 'authentik.enterprise.models.License'>
named = {'created': True, 'instance': <License: License object (9e86c733-35f3-4182-9320-b6dd1a517ffd)>, 'raw': False, 'update_fields': None, ...}
responses = [(<function invalidate_flow_cache at 0x7efea075b600>, None), (<function post_save_update at 0x7efea075b920>, None)]
sync_receivers = [<function invalidate_flow_cache at 0x7efea075b600>, <function post_save_update at 0x7efea075b920>, <function post_save_license at 0x7efea07589a0>, <function ssf_device_post_save at 0x7efea0758180>]
receiver = <function post_save_license at 0x7efea07589a0>, response = None

    def send(self, sender, **named):
        """
        Send signal from sender to all connected receivers.
    
        If any receiver raises an error, the error propagates back through send,
        terminating the dispatch loop. So it's possible that all receivers
        won't be called if an error is raised.
    
        If any receivers are asynchronous, they are called after all the
        synchronous receivers via a single call to async_to_sync(). They are
        also executed concurrently with asyncio.gather().
    
        Arguments:
    
            sender
                The sender of the signal. Either a specific object or None.
    
            named
                Named arguments which will be passed to receivers.
    
        Return a list of tuple pairs [(receiver, response), ... ].
        """
        if (
            not self.receivers
            or self.sender_receivers_cache.get(sender) is NO_RECEIVERS
        ):
            return []
        responses = []
        sync_receivers, async_receivers = self._live_receivers(sender)
        for receiver in sync_receivers:
>           response = receiver(signal=self, sender=sender, **named)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../django/dispatch/dispatcher.py:189: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = ()
kwargs = {'created': True, 'instance': <License: License object (9e86c733-35f3-4182-9320-b6dd1a517ffd)>, 'raw': False, 'sender': <class 'authentik.enterprise.models.License'>, ...}
signal_name = 'authentik.enterprise.signals.post_save_license'
span = <Span(op='event.django', description:'authentik.enterprise.signals.post_save_license', trace_id='0a3919c8451c4d64bd8d8ea6d3df7e21', span_id='aabacc50b8f98c60', parent_span_id='85e521d0de128bbf', sampled=None, origin='auto.http.django')>

    @wraps(receiver)
    def wrapper(*args, **kwargs):
        # type: (Any, Any) -> Any
        signal_name = _get_receiver_name(receiver)
        with sentry_sdk.start_span(
            op=OP.EVENT_DJANGO,
            name=signal_name,
            origin=DjangoIntegration.origin,
        ) as span:
            span.set_data("signal", signal_name)
>           return receiver(*args, **kwargs)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../integrations/django/signals_handlers.py:73: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

sender = <class 'authentik.enterprise.models.License'>
instance = <License: License object (9e86c733-35f3-4182-9320-b6dd1a517ffd)>
_ = {'created': True, 'raw': False, 'signal': <django.db.models.signals.ModelSignal object at 0x7efeb0598b30>, 'update_fields': None, ...}

    @receiver(post_save, sender=License)
    def post_save_license(sender: type[License], instance: License, **_):
        """Trigger license usage calculation when license is saved"""
        cache.delete(CACHE_KEY_ENTERPRISE_LICENSE)
>       enterprise_update_usage.delay()
E       AttributeError: 'function' object has no attribute 'delay'

authentik/enterprise/signals.py:29: AttributeError
authentik.enterprise.tests.test_license.TestEnterpriseLicense::test_expiry_soon
Stack Traces | 0.009s run time
self = <unittest.case._Outcome object at 0x7efea091dca0>
test_case = <authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_expiry_soon>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.12.9............/x64/lib/python3.12/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_expiry_soon>
result = <TestCaseFunction test_expiry_soon>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.12.9............/x64/lib/python3.12/unittest/case.py:634: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_expiry_soon>
method = <bound method TestEnterpriseLicense.test_expiry_soon of <authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_expiry_soon>>

    def _callTestMethod(self, method):
>       if method() is not None:

.../hostedtoolcache/Python/3.12.9............/x64/lib/python3.12/unittest/case.py:589: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_expiry_soon>,)
keywargs = {}
newargs = (<authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_expiry_soon>,)
newkeywargs = {}

    @wraps(func)
    def patched(*args, **keywargs):
        with self.decoration_helper(patched,
                                    args,
                                    keywargs) as (newargs, newkeywargs):
>           return func(*newargs, **newkeywargs)

.../hostedtoolcache/Python/3.12.9............/x64/lib/python3.12/unittest/mock.py:1396: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_expiry_soon>

    @patch(
        "authentik.enterprise.license.LicenseKey.validate",
        MagicMock(
            return_value=LicenseKey(
                aud="",
                exp=expiry_soon,
                name=generate_id(),
                internal_users=100,
                external_users=100,
            )
        ),
    )
    @patch(
        "authentik.enterprise.license.LicenseKey.record_usage",
        MagicMock(),
    )
    def test_expiry_soon(self):
        """Check license verification"""
>       License.objects.create(key=generate_id())

.../enterprise/tests/test_license.py:256: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.models.manager.Manager object at 0x7efea39a2e10>, args = ()
kwargs = {'key': 'blv582ctxcxPH8hL3is09dGj6iLWFJa1kRdz5GX5'}

    @wraps(method)
    def manager_method(self, *args, **kwargs):
>       return getattr(self.get_queryset(), name)(*args, **kwargs)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../db/models/manager.py:87: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <QuerySet []>
kwargs = {'key': 'blv582ctxcxPH8hL3is09dGj6iLWFJa1kRdz5GX5'}
reverse_one_to_one_fields = frozenset()
obj = <License: License object (db9714c5-6381-4e22-88ae-c480f8c471a9)>

    def create(self, **kwargs):
        """
        Create a new object with the given kwargs, saving it to the database
        and returning the created object.
        """
        reverse_one_to_one_fields = frozenset(kwargs).intersection(
            self.model._meta._reverse_one_to_one_field_names
        )
        if reverse_one_to_one_fields:
            raise ValueError(
                "The following fields do not exist in this model: %s"
                % ", ".join(reverse_one_to_one_fields)
            )
    
        obj = self.model(**kwargs)
        self._for_write = True
>       obj.save(force_insert=True, using=self.db)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../db/models/query.py:679: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <License: License object (db9714c5-6381-4e22-88ae-c480f8c471a9)>
force_insert = True, force_update = False, using = 'default'
update_fields = None

    def save(
        self, force_insert=False, force_update=False, using=None, update_fields=None
    ):
        """
        Save the current instance. Override this in a subclass if you want to
        control the saving process.
    
        The 'force_insert' and 'force_update' parameters can be used to insist
        that the "save" must be an SQL insert or update (or equivalent for
        non-SQL backends), respectively. Normally, they should not be set.
        """
        self._prepare_related_fields_for_save(operation_name="save")
    
        using = using or router.db_for_write(self.__class__, instance=self)
        if force_insert and (force_update or update_fields):
            raise ValueError("Cannot force both insert and updating in model saving.")
    
        deferred_non_generated_fields = {
            f.attname
            for f in self._meta.concrete_fields
            if f.attname not in self.__dict__ and f.generated is False
        }
        if update_fields is not None:
            # If update_fields is empty, skip the save. We do also check for
            # no-op saves later on for inheritance cases. This bailout is
            # still needed for skipping signal sending.
            if not update_fields:
                return
    
            update_fields = frozenset(update_fields)
            field_names = self._meta._non_pk_concrete_field_names
            non_model_fields = update_fields.difference(field_names)
    
            if non_model_fields:
                raise ValueError(
                    "The following fields do not exist in this model, are m2m "
                    "fields, or are non-concrete fields: %s"
                    % ", ".join(non_model_fields)
                )
    
        # If saving to the same database, and this model is deferred, then
        # automatically do an "update_fields" save on the loaded fields.
        elif (
            not force_insert
            and deferred_non_generated_fields
            and using == self._state.db
        ):
            field_names = set()
            for field in self._meta.concrete_fields:
                if not field.primary_key and not hasattr(field, "through"):
                    field_names.add(field.attname)
            loaded_fields = field_names.difference(deferred_non_generated_fields)
            if loaded_fields:
                update_fields = frozenset(loaded_fields)
    
>       self.save_base(
            using=using,
            force_insert=force_insert,
            force_update=force_update,
            update_fields=update_fields,
        )

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../db/models/base.py:822: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <License: License object (db9714c5-6381-4e22-88ae-c480f8c471a9)>
raw = False, force_insert = (<class 'authentik.enterprise.models.License'>,)
force_update = False, using = 'default', update_fields = None

    def save_base(
        self,
        raw=False,
        force_insert=False,
        force_update=False,
        using=None,
        update_fields=None,
    ):
        """
        Handle the parts of saving which should be done only once per save,
        yet need to be done in raw saves, too. This includes some sanity
        checks and signal sending.
    
        The 'raw' argument is telling save_base not to save any parent
        models and not to do any changes to the values before save. This
        is used by fixture loading.
        """
        using = using or router.db_for_write(self.__class__, instance=self)
        assert not (force_insert and (force_update or update_fields))
        assert update_fields is None or update_fields
        cls = origin = self.__class__
        # Skip proxies, but keep the origin as the proxy model.
        if cls._meta.proxy:
            cls = cls._meta.concrete_model
        meta = cls._meta
        if not meta.auto_created:
            pre_save.send(
                sender=origin,
                instance=self,
                raw=raw,
                using=using,
                update_fields=update_fields,
            )
        # A transaction isn't needed if one query is issued.
        if meta.parents:
            context_manager = transaction.atomic(using=using, savepoint=False)
        else:
            context_manager = transaction.mark_for_rollback_on_error(using=using)
        with context_manager:
            parent_inserted = False
            if not raw:
                # Validate force insert only when parents are inserted.
                force_insert = self._validate_force_insert(force_insert)
                parent_inserted = self._save_parents(
                    cls, using, update_fields, force_insert
                )
            updated = self._save_table(
                raw,
                cls,
                force_insert or parent_inserted,
                force_update,
                using,
                update_fields,
            )
        # Store the database on which the object was saved
        self._state.db = using
        # Once saved, this is no longer a to-be-added instance.
        self._state.adding = False
    
        # Signal that the save is complete
        if not meta.auto_created:
>           post_save.send(
                sender=origin,
                instance=self,
                created=(not updated),
                update_fields=update_fields,
                raw=raw,
                using=using,
            )

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../db/models/base.py:924: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.models.signals.ModelSignal object at 0x7efeb0598b30>
sender = <class 'authentik.enterprise.models.License'>
named = {'created': True, 'instance': <License: License object (db9714c5-6381-4e22-88ae-c480f8c471a9)>, 'raw': False, 'update_fields': None, ...}
responses = [(<function invalidate_flow_cache at 0x7efea26f0900>, None), (<function post_save_update at 0x7efea26f3740>, None)]
sync_receivers = [<function invalidate_flow_cache at 0x7efea26f0900>, <function post_save_update at 0x7efea26f3740>, <function post_save_license at 0x7efea26f3f60>, <function ssf_device_post_save at 0x7efea26f36a0>]
receiver = <function post_save_license at 0x7efea26f3f60>, response = None

    def send(self, sender, **named):
        """
        Send signal from sender to all connected receivers.
    
        If any receiver raises an error, the error propagates back through send,
        terminating the dispatch loop. So it's possible that all receivers
        won't be called if an error is raised.
    
        If any receivers are asynchronous, they are called after all the
        synchronous receivers via a single call to async_to_sync(). They are
        also executed concurrently with asyncio.gather().
    
        Arguments:
    
            sender
                The sender of the signal. Either a specific object or None.
    
            named
                Named arguments which will be passed to receivers.
    
        Return a list of tuple pairs [(receiver, response), ... ].
        """
        if (
            not self.receivers
            or self.sender_receivers_cache.get(sender) is NO_RECEIVERS
        ):
            return []
        responses = []
        sync_receivers, async_receivers = self._live_receivers(sender)
        for receiver in sync_receivers:
>           response = receiver(signal=self, sender=sender, **named)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../django/dispatch/dispatcher.py:189: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = ()
kwargs = {'created': True, 'instance': <License: License object (db9714c5-6381-4e22-88ae-c480f8c471a9)>, 'raw': False, 'sender': <class 'authentik.enterprise.models.License'>, ...}
signal_name = 'authentik.enterprise.signals.post_save_license'
span = <Span(op='event.django', description:'authentik.enterprise.signals.post_save_license', trace_id='0a3919c8451c4d64bd8d8ea6d3df7e21', span_id='b1f14a138e3848e7', parent_span_id='85e521d0de128bbf', sampled=None, origin='auto.http.django')>

    @wraps(receiver)
    def wrapper(*args, **kwargs):
        # type: (Any, Any) -> Any
        signal_name = _get_receiver_name(receiver)
        with sentry_sdk.start_span(
            op=OP.EVENT_DJANGO,
            name=signal_name,
            origin=DjangoIntegration.origin,
        ) as span:
            span.set_data("signal", signal_name)
>           return receiver(*args, **kwargs)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../integrations/django/signals_handlers.py:73: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

sender = <class 'authentik.enterprise.models.License'>
instance = <License: License object (db9714c5-6381-4e22-88ae-c480f8c471a9)>
_ = {'created': True, 'raw': False, 'signal': <django.db.models.signals.ModelSignal object at 0x7efeb0598b30>, 'update_fields': None, ...}

    @receiver(post_save, sender=License)
    def post_save_license(sender: type[License], instance: License, **_):
        """Trigger license usage calculation when license is saved"""
        cache.delete(CACHE_KEY_ENTERPRISE_LICENSE)
>       enterprise_update_usage.delay()
E       AttributeError: 'function' object has no attribute 'delay'

authentik/enterprise/signals.py:29: AttributeError
authentik.enterprise.tests.test_license.TestEnterpriseLicense::test_valid_multiple
Stack Traces | 0.009s run time
self = <unittest.case._Outcome object at 0x7efea04664e0>
test_case = <authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_valid_multiple>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.12.9............/x64/lib/python3.12/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_valid_multiple>
result = <TestCaseFunction test_valid_multiple>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.12.9............/x64/lib/python3.12/unittest/case.py:634: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_valid_multiple>
method = <bound method TestEnterpriseLicense.test_valid_multiple of <authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_valid_multiple>>

    def _callTestMethod(self, method):
>       if method() is not None:

.../hostedtoolcache/Python/3.12.9............/x64/lib/python3.12/unittest/case.py:589: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_valid_multiple>,)
keywargs = {}
newargs = (<authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_valid_multiple>,)
newkeywargs = {}

    @wraps(func)
    def patched(*args, **keywargs):
        with self.decoration_helper(patched,
                                    args,
                                    keywargs) as (newargs, newkeywargs):
>           return func(*newargs, **newkeywargs)

.../hostedtoolcache/Python/3.12.9............/x64/lib/python3.12/unittest/mock.py:1396: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.enterprise.tests.test_license.TestEnterpriseLicense testMethod=test_valid_multiple>

    @patch(
        "authentik.enterprise.license.LicenseKey.validate",
        MagicMock(
            return_value=LicenseKey(
                aud="",
                exp=expiry_valid,
                name=generate_id(),
                internal_users=100,
                external_users=100,
            )
        ),
    )
    def test_valid_multiple(self):
        """Check license verification"""
>       lic = License.objects.create(key=generate_id())

.../enterprise/tests/test_license.py:74: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.models.manager.Manager object at 0x7efea39a2e10>, args = ()
kwargs = {'key': 'fhlofqr265WghdQQwO2qhljWoAxvafEs6psWSJog'}

    @wraps(method)
    def manager_method(self, *args, **kwargs):
>       return getattr(self.get_queryset(), name)(*args, **kwargs)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../db/models/manager.py:87: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <QuerySet []>
kwargs = {'key': 'fhlofqr265WghdQQwO2qhljWoAxvafEs6psWSJog'}
reverse_one_to_one_fields = frozenset()
obj = <License: License object (42d5de96-a54d-4a3a-8ca5-195f48594c9c)>

    def create(self, **kwargs):
        """
        Create a new object with the given kwargs, saving it to the database
        and returning the created object.
        """
        reverse_one_to_one_fields = frozenset(kwargs).intersection(
            self.model._meta._reverse_one_to_one_field_names
        )
        if reverse_one_to_one_fields:
            raise ValueError(
                "The following fields do not exist in this model: %s"
                % ", ".join(reverse_one_to_one_fields)
            )
    
        obj = self.model(**kwargs)
        self._for_write = True
>       obj.save(force_insert=True, using=self.db)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../db/models/query.py:679: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <License: License object (42d5de96-a54d-4a3a-8ca5-195f48594c9c)>
force_insert = True, force_update = False, using = 'default'
update_fields = None

    def save(
        self, force_insert=False, force_update=False, using=None, update_fields=None
    ):
        """
        Save the current instance. Override this in a subclass if you want to
        control the saving process.
    
        The 'force_insert' and 'force_update' parameters can be used to insist
        that the "save" must be an SQL insert or update (or equivalent for
        non-SQL backends), respectively. Normally, they should not be set.
        """
        self._prepare_related_fields_for_save(operation_name="save")
    
        using = using or router.db_for_write(self.__class__, instance=self)
        if force_insert and (force_update or update_fields):
            raise ValueError("Cannot force both insert and updating in model saving.")
    
        deferred_non_generated_fields = {
            f.attname
            for f in self._meta.concrete_fields
            if f.attname not in self.__dict__ and f.generated is False
        }
        if update_fields is not None:
            # If update_fields is empty, skip the save. We do also check for
            # no-op saves later on for inheritance cases. This bailout is
            # still needed for skipping signal sending.
            if not update_fields:
                return
    
            update_fields = frozenset(update_fields)
            field_names = self._meta._non_pk_concrete_field_names
            non_model_fields = update_fields.difference(field_names)
    
            if non_model_fields:
                raise ValueError(
                    "The following fields do not exist in this model, are m2m "
                    "fields, or are non-concrete fields: %s"
                    % ", ".join(non_model_fields)
                )
    
        # If saving to the same database, and this model is deferred, then
        # automatically do an "update_fields" save on the loaded fields.
        elif (
            not force_insert
            and deferred_non_generated_fields
            and using == self._state.db
        ):
            field_names = set()
            for field in self._meta.concrete_fields:
                if not field.primary_key and not hasattr(field, "through"):
                    field_names.add(field.attname)
            loaded_fields = field_names.difference(deferred_non_generated_fields)
            if loaded_fields:
                update_fields = frozenset(loaded_fields)
    
>       self.save_base(
            using=using,
            force_insert=force_insert,
            force_update=force_update,
            update_fields=update_fields,
        )

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../db/models/base.py:822: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <License: License object (42d5de96-a54d-4a3a-8ca5-195f48594c9c)>
raw = False, force_insert = (<class 'authentik.enterprise.models.License'>,)
force_update = False, using = 'default', update_fields = None

    def save_base(
        self,
        raw=False,
        force_insert=False,
        force_update=False,
        using=None,
        update_fields=None,
    ):
        """
        Handle the parts of saving which should be done only once per save,
        yet need to be done in raw saves, too. This includes some sanity
        checks and signal sending.
    
        The 'raw' argument is telling save_base not to save any parent
        models and not to do any changes to the values before save. This
        is used by fixture loading.
        """
        using = using or router.db_for_write(self.__class__, instance=self)
        assert not (force_insert and (force_update or update_fields))
        assert update_fields is None or update_fields
        cls = origin = self.__class__
        # Skip proxies, but keep the origin as the proxy model.
        if cls._meta.proxy:
            cls = cls._meta.concrete_model
        meta = cls._meta
        if not meta.auto_created:
            pre_save.send(
                sender=origin,
                instance=self,
                raw=raw,
                using=using,
                update_fields=update_fields,
            )
        # A transaction isn't needed if one query is issued.
        if meta.parents:
            context_manager = transaction.atomic(using=using, savepoint=False)
        else:
            context_manager = transaction.mark_for_rollback_on_error(using=using)
        with context_manager:
            parent_inserted = False
            if not raw:
                # Validate force insert only when parents are inserted.
                force_insert = self._validate_force_insert(force_insert)
                parent_inserted = self._save_parents(
                    cls, using, update_fields, force_insert
                )
            updated = self._save_table(
                raw,
                cls,
                force_insert or parent_inserted,
                force_update,
                using,
                update_fields,
            )
        # Store the database on which the object was saved
        self._state.db = using
        # Once saved, this is no longer a to-be-added instance.
        self._state.adding = False
    
        # Signal that the save is complete
        if not meta.auto_created:
>           post_save.send(
                sender=origin,
                instance=self,
                created=(not updated),
                update_fields=update_fields,
                raw=raw,
                using=using,
            )

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../db/models/base.py:924: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.models.signals.ModelSignal object at 0x7efeb0598b30>
sender = <class 'authentik.enterprise.models.License'>
named = {'created': True, 'instance': <License: License object (42d5de96-a54d-4a3a-8ca5-195f48594c9c)>, 'raw': False, 'update_fields': None, ...}
responses = [(<function invalidate_flow_cache at 0x7efea2f07560>, None), (<function post_save_update at 0x7efea26f0040>, None)]
sync_receivers = [<function invalidate_flow_cache at 0x7efea2f07560>, <function post_save_update at 0x7efea26f0040>, <function post_save_license at 0x7efea26f0a40>, <function ssf_device_post_save at 0x7efea26f31a0>]
receiver = <function post_save_license at 0x7efea26f0a40>, response = None

    def send(self, sender, **named):
        """
        Send signal from sender to all connected receivers.
    
        If any receiver raises an error, the error propagates back through send,
        terminating the dispatch loop. So it's possible that all receivers
        won't be called if an error is raised.
    
        If any receivers are asynchronous, they are called after all the
        synchronous receivers via a single call to async_to_sync(). They are
        also executed concurrently with asyncio.gather().
    
        Arguments:
    
            sender
                The sender of the signal. Either a specific object or None.
    
            named
                Named arguments which will be passed to receivers.
    
        Return a list of tuple pairs [(receiver, response), ... ].
        """
        if (
            not self.receivers
            or self.sender_receivers_cache.get(sender) is NO_RECEIVERS
        ):
            return []
        responses = []
        sync_receivers, async_receivers = self._live_receivers(sender)
        for receiver in sync_receivers:
>           response = receiver(signal=self, sender=sender, **named)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../django/dispatch/dispatcher.py:189: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = ()
kwargs = {'created': True, 'instance': <License: License object (42d5de96-a54d-4a3a-8ca5-195f48594c9c)>, 'raw': False, 'sender': <class 'authentik.enterprise.models.License'>, ...}
signal_name = 'authentik.enterprise.signals.post_save_license'
span = <Span(op='event.django', description:'authentik.enterprise.signals.post_save_license', trace_id='0a3919c8451c4d64bd8d8ea6d3df7e21', span_id='bb3e442d957fea38', parent_span_id='85e521d0de128bbf', sampled=None, origin='auto.http.django')>

    @wraps(receiver)
    def wrapper(*args, **kwargs):
        # type: (Any, Any) -> Any
        signal_name = _get_receiver_name(receiver)
        with sentry_sdk.start_span(
            op=OP.EVENT_DJANGO,
            name=signal_name,
            origin=DjangoIntegration.origin,
        ) as span:
            span.set_data("signal", signal_name)
>           return receiver(*args, **kwargs)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../integrations/django/signals_handlers.py:73: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

sender = <class 'authentik.enterprise.models.License'>
instance = <License: License object (42d5de96-a54d-4a3a-8ca5-195f48594c9c)>
_ = {'created': True, 'raw': False, 'signal': <django.db.models.signals.ModelSignal object at 0x7efeb0598b30>, 'update_fields': None, ...}

    @receiver(post_save, sender=License)
    def post_save_license(sender: type[License], instance: License, **_):
        """Trigger license usage calculation when license is saved"""
        cache.delete(CACHE_KEY_ENTERPRISE_LICENSE)
>       enterprise_update_usage.delay()
E       AttributeError: 'function' object has no attribute 'delay'

authentik/enterprise/signals.py:29: AttributeError

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

rissson added 4 commits March 7, 2025 23:33
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
@rissson rissson mentioned this pull request Mar 12, 2025
59 tasks
@rissson rissson closed this Mar 12, 2025
@rissson rissson deleted the celery-2-django-q branch March 12, 2025 17:09
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.

1 participant