diff --git a/kobo/apps/superuser_stats/tasks.py b/kobo/apps/superuser_stats/tasks.py index 1eec8e1318..5ac665139e 100644 --- a/kobo/apps/superuser_stats/tasks.py +++ b/kobo/apps/superuser_stats/tasks.py @@ -13,7 +13,7 @@ def generate_user_report(output_filename): from kpi.deployment_backends.kc_access.shadow_models import ( KobocatUser, KobocatUserProfile, - ReadOnlyKobocatXForm, + KobocatXForm, ) from hub.models import ExtraUserDetail @@ -66,7 +66,7 @@ def get_row_for_user(u: KobocatUser) -> list: else: row_.append('') - row_.append(ReadOnlyKobocatXForm.objects.filter(user=u).count()) + row_.append(KobocatXForm.objects.filter(user=u).count()) if profile: row_.append(profile.num_of_submissions) diff --git a/kpi/db_routers.py b/kpi/db_routers.py index 1e8e9c6d9a..ffb280c19c 100644 --- a/kpi/db_routers.py +++ b/kpi/db_routers.py @@ -1,5 +1,6 @@ # coding: utf-8 from .constants import SHADOW_MODEL_APP_LABEL +from .exceptions import ReadOnlyModelError class DefaultDatabaseRouter: @@ -16,8 +17,12 @@ def db_for_write(self, model, **hints): """ Writes go to `kc` when `model` is a ShadowModel """ + if getattr(model, 'read_only', False): + raise ReadOnlyModelError + if model._meta.app_label == SHADOW_MODEL_APP_LABEL: return "kobocat" + return "default" def allow_relation(self, obj1, obj2, **hints): diff --git a/kpi/deployment_backends/kc_access/shadow_models.py b/kpi/deployment_backends/kc_access/shadow_models.py index 4453851b10..995ed4ff57 100644 --- a/kpi/deployment_backends/kc_access/shadow_models.py +++ b/kpi/deployment_backends/kc_access/shadow_models.py @@ -44,10 +44,6 @@ def update_autofield_sequence(model): cursor.execute(query) -class ReadOnlyModelError(ValueError): - pass - - class ShadowModel(models.Model): """ Allows identification of writeable and read-only shadow models @@ -68,7 +64,7 @@ class Meta: @staticmethod def get_content_type_for_model(model): model_name_mapping = { - 'readonlykobocatxform': ('logger', 'xform'), + 'kobocatxform': ('logger', 'xform'), 'readonlykobocatinstance': ('logger', 'instance'), 'kobocatuserprofile': ('main', 'userprofile'), 'kobocatuserobjectpermission': ('guardian', 'userobjectpermission'), @@ -81,21 +77,17 @@ def get_content_type_for_model(model): app_label=app_label, model=model_name) -class ReadOnlyModel(ShadowModel): +class ReadOnlyShadowModel(ShadowModel): + + read_only = True class Meta(ShadowModel.Meta): abstract = True - def save(self, *args, **kwargs): - raise ReadOnlyModelError('Cannot save read-only-model') - def delete(self, *args, **kwargs): - raise ReadOnlyModelError('Cannot delete read-only-model') +class KobocatXForm(ShadowModel): - -class ReadOnlyKobocatXForm(ReadOnlyModel): - - class Meta(ReadOnlyModel.Meta): + class Meta(ShadowModel.Meta): db_table = 'logger_xform' verbose_name = 'xform' verbose_name_plural = 'xforms' @@ -129,16 +121,16 @@ def prefixed_hash(self): return "md5:%s" % self.hash -class ReadOnlyKobocatInstance(ReadOnlyModel): +class ReadOnlyKobocatInstance(ReadOnlyShadowModel): - class Meta(ReadOnlyModel.Meta): + class Meta(ReadOnlyShadowModel.Meta): db_table = 'logger_instance' verbose_name = 'instance' verbose_name_plural = 'instances' xml = models.TextField() user = models.ForeignKey(User, null=True, on_delete=models.CASCADE) - xform = models.ForeignKey(ReadOnlyKobocatXForm, related_name='instances', + xform = models.ForeignKey(KobocatXForm, related_name='instances', on_delete=models.CASCADE) date_created = models.DateTimeField() date_modified = models.DateTimeField() diff --git a/kpi/deployment_backends/kc_access/utils.py b/kpi/deployment_backends/kc_access/utils.py index 64978ddb09..56feae902e 100644 --- a/kpi/deployment_backends/kc_access/utils.py +++ b/kpi/deployment_backends/kc_access/utils.py @@ -21,7 +21,7 @@ KobocatUserObjectPermission, KobocatUserPermission, KobocatUserProfile, - ReadOnlyKobocatXForm, + KobocatXForm, ) @@ -44,17 +44,17 @@ def _trigger_kc_profile_creation(user): @safe_kc_read def instance_count(xform_id_string, user_id): try: - return ReadOnlyKobocatXForm.objects.only('num_of_submissions').get( + return KobocatXForm.objects.only('num_of_submissions').get( id_string=xform_id_string, user_id=user_id ).num_of_submissions - except ReadOnlyKobocatXForm.DoesNotExist: + except KobocatXForm.DoesNotExist: return 0 @safe_kc_read def last_submission_time(xform_id_string, user_id): - return ReadOnlyKobocatXForm.objects.get( + return KobocatXForm.objects.get( user_id=user_id, id_string=xform_id_string ).last_submission_time @@ -291,7 +291,7 @@ def set_kc_anonymous_permissions_xform_flags(obj, kpi_codenames, xform_id, flags = {flag: not value for flag, value in flags.items()} xform_updates.update(flags) # Write to the KC database - ReadOnlyKobocatXForm.objects.filter(pk=xform_id).update(**xform_updates) + KobocatXForm.objects.filter(pk=xform_id).update(**xform_updates) @transaction.atomic() diff --git a/kpi/deployment_backends/kobocat_backend.py b/kpi/deployment_backends/kobocat_backend.py index aba85a6de1..31cc1902e2 100644 --- a/kpi/deployment_backends/kobocat_backend.py +++ b/kpi/deployment_backends/kobocat_backend.py @@ -28,7 +28,7 @@ from kpi.utils.log import logging from kpi.utils.mongo_helper import MongoHelper from .base_backend import BaseDeploymentBackend -from .kc_access.shadow_models import ReadOnlyKobocatInstance, ReadOnlyKobocatXForm +from .kc_access.shadow_models import ReadOnlyKobocatInstance, KobocatXForm from .kc_access.utils import ( assign_applicable_kc_permissions, instance_count, @@ -192,7 +192,7 @@ def xform_id_string(self): @property def xform_id(self): pk = self.asset._deployment_data.get('backend_response', {}).get('formid') - xform = ReadOnlyKobocatXForm.objects.filter(pk=pk).only( + xform = KobocatXForm.objects.filter(pk=pk).only( 'user__username', 'id_string').first() if not (xform.user.username == self.asset.owner.username and xform.id_string == self.xform_id_string): diff --git a/kpi/exceptions.py b/kpi/exceptions.py index 8a53e0aa5f..9a1a65f7fb 100644 --- a/kpi/exceptions.py +++ b/kpi/exceptions.py @@ -33,6 +33,12 @@ class InvalidSearchException(exceptions.APIException): default_code = 'invalid_search' +class ReadOnlyModelError(Exception): + + def __init__(self, msg='This model is read only', *args, **kwargs): + super().__init__(msg, *args, **kwargs) + + class SearchQueryTooShortException(InvalidSearchException): default_detail = _('Your query is too short') default_code = 'query_too_short' diff --git a/kpi/management/commands/sync_kobocat_xforms.py b/kpi/management/commands/sync_kobocat_xforms.py index 98d5bdd04a..d4d30cd9bf 100644 --- a/kpi/management/commands/sync_kobocat_xforms.py +++ b/kpi/management/commands/sync_kobocat_xforms.py @@ -23,7 +23,7 @@ from kpi.deployment_backends.kc_access.shadow_models import ( KobocatPermission, KobocatUserObjectPermission, - ReadOnlyKobocatXForm, + KobocatXForm, ShadowModel, ) from kpi.deployment_backends.kobocat_backend import KobocatDeploymentBackend @@ -40,7 +40,7 @@ ASSET_CT = ContentType.objects.get_for_model(Asset) FROM_KC_ONLY_PERMISSION = Permission.objects.get( content_type=ASSET_CT, codename=PERM_FROM_KC_ONLY) -XFORM_CT = ShadowModel.get_content_type_for_model(ReadOnlyKobocatXForm) +XFORM_CT = ShadowModel.get_content_type_for_model(KobocatXForm) ANONYMOUS_USER = get_anonymous_user() # Replace codenames with Permission PKs, remembering the codenames permission_map_copy = dict(PERMISSIONS_MAP) @@ -474,9 +474,9 @@ def handle(self, *args, **options): username = options.get('username') populate_xform_kpi_asset_uid = options.get('populate_xform_kpi_asset_uid') users = User.objects.all() - # Do a basic query just to make sure the ReadOnlyKobocatXForm model is + # Do a basic query just to make sure the KobocatXForm model is # loaded - if not ReadOnlyKobocatXForm.objects.exists(): + if not KobocatXForm.objects.exists(): return self._print_str('%d total users' % users.count()) # A specific user or everyone? @@ -506,8 +506,8 @@ def handle(self, *args, **options): xform_uuids_to_asset_pks[backend_response['uuid']] = \ existing_survey.pk - # ReadOnlyKobocatXForm has a foreign key on KobocatUser, not on User - xforms = ReadOnlyKobocatXForm.objects.filter(user_id=user.pk).all() + # KobocatXForm has a foreign key on KobocatUser, not on User + xforms = KobocatXForm.objects.filter(user_id=user.pk).all() for xform in xforms: try: with transaction.atomic():