Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code refactoring for project deployment #5036

Open
wants to merge 32 commits into
base: release/2.024.36
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cf24c2a
Replace shadow models imports with open rosa models
noliveleger Jun 20, 2024
647eee2
Replace MockAttachment with Attachment model in unit tests
noliveleger Jul 4, 2024
fb05fb7
Remove import shadow model aliases
noliveleger Jul 16, 2024
3a8d00f
Remove debug print
noliveleger Jul 18, 2024
4fa415b
WIP - New OpenRosa deployment backend
noliveleger Jul 18, 2024
f1c3f15
WIP - OpenRosa backend
noliveleger Jul 18, 2024
23b9178
WIP openrosa backend - validation status
noliveleger Jul 23, 2024
aa0b0a2
Support bulk validation status update and edit submission
noliveleger Jul 30, 2024
d8e7fb8
dummy commit to create first PR
noliveleger Jul 30, 2024
7b418bf
Merge branch 'release/2.024.25' into kobocat-django-app-part-2
noliveleger Jul 30, 2024
1205888
Merge branch 'kobocat-django-app-part-2' into kobocat-django-app-part…
noliveleger Jul 30, 2024
1cdcc97
Merge branch 'kobocat-django-app-part-2-remove-imports' into kobocat-…
noliveleger Jul 30, 2024
8cede6c
Support bulk edit with OpenRosa backend
noliveleger Jul 30, 2024
02017ed
Remove KobocatDeploymentBackend and shadow models
noliveleger Jul 30, 2024
06db791
Fix error when calling "delete_submissions" with same params as "bulk…
noliveleger Jul 30, 2024
177b62e
Merge branch 'kobocat-django-app-part-2-openrosa-backend' into koboca…
noliveleger Jul 30, 2024
cae6480
Remove installed app
noliveleger Jul 31, 2024
aa7195f
Use HookUtils directly when submission comes in
noliveleger Jul 31, 2024
11a0322
Remove back and forth API calls to activate Rest Services
noliveleger Jul 31, 2024
5943b24
Improve celery retries
noliveleger Aug 1, 2024
c8996f4
Fix tests
noliveleger Aug 1, 2024
3e1d9d1
Fix bug on redeploy
noliveleger Aug 6, 2024
3ee226a
Merge pull request #5037 from kobotoolbox/kobocat-django-app-part-2-r…
jnm Aug 20, 2024
32bbcc5
Apply requested changes
noliveleger Aug 22, 2024
b36a130
Fix bad refactoring
noliveleger Aug 22, 2024
0f7f8b7
Fix sync_kobocat_xforms
noliveleger Aug 22, 2024
f5e1f92
Add TODO comment
noliveleger Aug 22, 2024
26e21fd
Merge pull request #5038 from kobotoolbox/kobocat-django-app-part-2-o…
jnm Aug 22, 2024
ada05fa
Merge pull request #5042 from kobotoolbox/kobocat-django-app-part-2-r…
jnm Aug 22, 2024
1a8da58
Merge branch 'kobocat-django-app-part-2' into kobocat-django-app-part…
noliveleger Aug 23, 2024
7697d68
Use MagicMock instead of MockSSRFProtect
noliveleger Aug 23, 2024
944d24f
Merge pull request #5041 from kobotoolbox/kobocat-django-app-part-2-r…
jnm Aug 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions hub/admin/extend_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@
USERNAME_INVALID_MESSAGE,
username_validators,
)
from kobo.apps.openrosa.apps.logger.models import MonthlyXFormSubmissionCounter
from kobo.apps.organizations.models import OrganizationUser
from kobo.apps.trash_bin.exceptions import TrashIntegrityError
from kobo.apps.trash_bin.models.account import AccountTrash
from kobo.apps.trash_bin.utils import move_to_trash
from kpi.deployment_backends.kc_access.shadow_models import (
KobocatMonthlyXFormSubmissionCounter,
)


from kpi.models.asset import AssetDeploymentStatus
from .filters import UserAdvancedSearchFilter
from .mixins import AdvancedSearchMixin
Expand Down Expand Up @@ -257,7 +257,7 @@ def monthly_submission_count(self, obj):
displayed in the Django admin user changelist page
"""
today = timezone.now().date()
instances = KobocatMonthlyXFormSubmissionCounter.objects.filter(
instances = MonthlyXFormSubmissionCounter.objects.filter(
user_id=obj.id,
year=today.year,
month=today.month,
Expand Down
8 changes: 4 additions & 4 deletions hub/models/extra_user_detail.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.conf import settings
from django.db import models

from kpi.deployment_backends.kc_access.shadow_models import KobocatUserProfile
from kobo.apps.openrosa.apps.main.models import UserProfile
from kpi.fields import KpiUidField
from kpi.mixins import StandardizeSearchableFieldMixin

Expand Down Expand Up @@ -43,9 +43,9 @@ def save(
update_fields=update_fields,
)

# Sync `validated_password` field to `KobocatUserProfile` only when
# Sync `validated_password` field to `UserProfile` only when
# this object is updated to avoid a race condition and an IntegrityError
# when trying to save `KobocatUserProfile` object whereas the related
# when trying to save `UserProfile` object whereas the related
# `KobocatUser` object has not been created yet.
if (
not settings.TESTING
Expand All @@ -55,7 +55,7 @@ def save(
or (update_fields and 'validated_password' in update_fields)
)
):
KobocatUserProfile.set_password_details(
UserProfile.set_password_details(
self.user.id,
self.validated_password,
)
10 changes: 4 additions & 6 deletions kobo/apps/accounts/mfa/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
MFAMethodAdmin as TrenchMFAMethodAdmin,
)

from kpi.deployment_backends.kc_access.shadow_models import (
KobocatUserProfile,
)
from kobo.apps.openrosa.apps.main.models import UserProfile


class MfaAvailableToUser(models.Model):
Expand Down Expand Up @@ -74,7 +72,7 @@ def save(
Update user's profile in KoBoCAT database.
"""
if not settings.TESTING and not created:
KobocatUserProfile.set_mfa_status(
UserProfile.set_mfa_status(
user_id=self.user.pk, is_active=self.is_active
)

Expand All @@ -83,10 +81,10 @@ def delete(self, using=None, keep_parents=False):
super().delete(using, keep_parents)

"""
Update user's profile in KoBoCAT database.
Update user's profile in KoboCAT database.
"""
if not settings.TESTING:
KobocatUserProfile.set_mfa_status(
UserProfile.set_mfa_status(
user_id=user_id, is_active=False
)

Expand Down
2 changes: 1 addition & 1 deletion kobo/apps/form_disclaimer/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.conf import settings
from django.db import models, transaction
from django.db import models
from django.db.models import Q
from django.db.models.constraints import UniqueConstraint
from markdownx.models import MarkdownxField
Expand Down
9 changes: 9 additions & 0 deletions kobo/apps/hook/constants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# coding: utf-8
from enum import Enum
from rest_framework import status


HOOK_LOG_FAILED = 0
Expand All @@ -16,3 +17,11 @@ class HookLogStatus(Enum):
KOBO_INTERNAL_ERROR_STATUS_CODE = None

SUBMISSION_PLACEHOLDER = '%SUBMISSION%'

# Status codes that trigger a retry
RETRIABLE_STATUS_CODES = [
# status.HTTP_429_TOO_MANY_REQUESTS,
status.HTTP_502_BAD_GATEWAY,
status.HTTP_503_SERVICE_UNAVAILABLE,
status.HTTP_504_GATEWAY_TIMEOUT,
]
3 changes: 3 additions & 0 deletions kobo/apps/hook/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

class HookRemoteServerDownError(Exception):
pass
63 changes: 22 additions & 41 deletions kobo/apps/hook/models/hook_log.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# coding: utf-8
from datetime import timedelta

import constance
Expand All @@ -17,38 +16,43 @@

class HookLog(models.Model):

hook = models.ForeignKey("Hook", related_name="logs", on_delete=models.CASCADE)
hook = models.ForeignKey(
"Hook", related_name="logs", on_delete=models.CASCADE
)
uid = KpiUidField(uid_prefix="hl")
submission_id = models.IntegerField(default=0, db_index=True) # `KoBoCAT.logger.Instance.id`
submission_id = models.IntegerField( # `KoboCAT.logger.Instance.id`
default=0, db_index=True
)
tries = models.PositiveSmallIntegerField(default=0)
status = models.PositiveSmallIntegerField(
choices=[[e.value, e.name.title()] for e in HookLogStatus],
default=HookLogStatus.PENDING.value
default=HookLogStatus.PENDING.value,
) # Could use status_code, but will speed-up queries
status_code = models.IntegerField(default=KOBO_INTERNAL_ERROR_STATUS_CODE, null=True, blank=True)
status_code = models.IntegerField(
default=KOBO_INTERNAL_ERROR_STATUS_CODE, null=True, blank=True
)
message = models.TextField(default="")
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now_add=True)

class Meta:
ordering = ["-date_created"]
ordering = ['-date_created']

@property
def can_retry(self) -> bool:
"""
Return whether instance can be resent to external endpoint.
Notice: even if False is returned, `self.retry()` can be triggered.
"""
if self.hook.active:
seconds = HookLog.get_elapsed_seconds(
constance.config.HOOK_MAX_RETRIES
)
threshold = timezone.now() - timedelta(seconds=seconds)
# We can retry only if system has already tried 3 times.
# If log is still pending after 3 times, there was an issue,
# we allow the retry
return (
self.status == HOOK_LOG_FAILED
or (self.date_modified < threshold and self.status == HOOK_LOG_PENDING)
# If log is still pending after `constance.config.HOOK_MAX_RETRIES`
# times, there was an issue, we allow the retry.
threshold = timezone.now() - timedelta(seconds=120)

return self.status == HOOK_LOG_FAILED or (
self.date_modified < threshold
and self.status == HOOK_LOG_PENDING
and self.tries >= constance.config.HOOK_MAX_RETRIES
)

return False
Expand All @@ -66,29 +70,6 @@ def change_status(

self.save(reset_status=True)

@staticmethod
def get_elapsed_seconds(retries_count: int) -> int:
"""
Calculate number of elapsed seconds since first try.
Return the number of seconds.
"""
# We need to sum all seconds between each retry
seconds = 0
for retries_count in range(retries_count):
# Range is zero-indexed
seconds += HookLog.get_remaining_seconds(retries_count)

return seconds

@staticmethod
def get_remaining_seconds(retries_count):
"""
Calculate number of remaining seconds before next retry
:param retries_count: int.
:return: int. Number of seconds
"""
return 60 * (10 ** retries_count)

def retry(self):
"""
Retries to send data to external service
Expand All @@ -100,7 +81,7 @@ def retry(self):
service_definition.send()
self.refresh_from_db()
except Exception as e:
logging.error("HookLog.retry - {}".format(str(e)), exc_info=True)
logging.error('HookLog.retry - {}'.format(str(e)), exc_info=True)
self.change_status(HOOK_LOG_FAILED)
return False

Expand All @@ -110,7 +91,7 @@ def save(self, *args, **kwargs):
# Update date_modified each time object is saved
self.date_modified = timezone.now()
# We don't want to alter tries when we only change the status
if kwargs.pop("reset_status", False) is False:
if kwargs.pop('reset_status', False) is False:
self.tries += 1
self.hook.reset_totals()
super().save(*args, **kwargs)
Expand Down
Loading