def get_new_multimedia_between_builds(domain, target_build_id, source_build_id, build_profile_id=None): def _get_mm_map_by_id(multimedia_map): return { media_map_item['multimedia_id']: media_map_item for path, media_map_item in multimedia_map.items() } source_build = get_app_cached(domain, source_build_id) target_build = get_app_cached(domain, target_build_id) assert source_build.copy_of, _("Size calculation available only for builds") assert target_build.copy_of, _("Size calculation available only for builds") build_profile = source_build.build_profiles.get(build_profile_id) if build_profile_id else None source_mm_map = source_build.multimedia_map_for_build(build_profile=build_profile) target_mm_map = target_build.multimedia_map_for_build(build_profile=build_profile) source_mm_map_by_id = _get_mm_map_by_id(source_mm_map) target_mm_map_by_id = _get_mm_map_by_id(target_mm_map) added = set(target_mm_map_by_id.keys()).difference(set(source_mm_map_by_id.keys())) media_objects = { mm.get_id: mm for path, mm in target_build.get_media_objects(multimedia_map=target_mm_map) } total_size = defaultdict(lambda: 0) for multimedia_id in added: media_object = media_objects[multimedia_id] total_size[media_object.doc_type] += media_object.content_length return total_size
def registry_case(request, domain, app_id): request_dict = request.GET if request.method == 'GET' else request.POST case_ids = request_dict.getlist("case_id") case_types = request_dict.getlist("case_type") registry = request_dict.get(CASE_SEARCH_REGISTRY_ID_KEY) missing = [ name for name, value in zip(["case_id", "case_type", CASE_SEARCH_REGISTRY_ID_KEY], [case_ids, case_types, registry]) if not value ] if missing: return HttpResponseBadRequest( ngettext("'{params}' is a required parameter", "'{params}' are required parameters", len(missing)).format(params="', '".join(missing))) helper = DataRegistryHelper(domain, registry_slug=registry) app = get_app_cached(domain, app_id) try: cases = [ helper.get_case(case_id, request.couch_user, app) for case_id in case_ids ] except RegistryNotFound: return HttpResponseNotFound(f"Registry '{registry}' not found") except CaseNotFound as e: return HttpResponseNotFound(f"Case '{str(e)}' not found") except RegistryAccessException as e: return HttpResponseBadRequest(str(e)) for case in cases: if case.type not in case_types: return HttpResponseNotFound(f"Case '{case.case_id}' not found")
def heartbeat(request, domain, app_build_id): """ An endpoint for CommCare mobile to get latest CommCare APK and app version info. (Should serve from cache as it's going to be busy view) 'app_build_id' (that comes from URL) can be id of any version of the app 'app_id' (urlparam) is usually id of an app that is not a copy mobile simply needs it to be resent back in the JSON, and doesn't need any validation on it. This is pulled from @uniqueid from profile.xml """ app_id = request.GET.get('app_id', '') build_profile_id = request.GET.get('build_profile_id', '') master_app_id = app_id try: info = GlobalAppConfig.get_latest_version_info(domain, app_id, build_profile_id) except (Http404, AssertionError): # If it's not a valid master app id, find it by talking to couch app = get_app_cached(domain, app_build_id) notify_exception(request, 'Received an invalid heartbeat request') master_app_id = app.master_id if app else None info = GlobalAppConfig.get_latest_version_info(domain, app.master_id, build_profile_id) info["app_id"] = app_id if master_app_id: error_response = check_authorization(domain, request.couch_user, master_app_id) if error_response: return error_response if not toggles.SKIP_UPDATING_USER_REPORTING_METADATA.enabled(domain): update_user_reporting_data(app_build_id, app_id, build_profile_id, request.couch_user, request) if _should_force_log_submission(request): info['force_logs'] = True return JsonResponse(info)
def heartbeat(request, domain, app_build_id): """ An endpoint for CommCare mobile to get latest CommCare APK and app version info. (Should serve from cache as it's going to be busy view) 'app_build_id' (that comes from URL) can be id of any version of the app 'app_id' (urlparam) is usually id of an app that is not a copy mobile simply needs it to be resent back in the JSON, and doesn't need any validation on it. This is pulled from @uniqueid from profile.xml """ app_id = request.GET.get('app_id', '') build_profile_id = request.GET.get('build_profile_id', '') info = {"app_id": app_id} try: # mobile will send brief_app_id info.update(LatestAppInfo(app_id, domain).get_info()) except (Http404, AssertionError): # If it's not a valid 'brief' app id, find it by talking to couch notify_exception(request, 'Received an invalid heartbeat request') app = get_app_cached(domain, app_build_id) brief_app_id = app.master_id info.update(LatestAppInfo(brief_app_id, domain).get_info()) else: if not toggles.SKIP_UPDATING_USER_REPORTING_METADATA.enabled(domain): update_user_reporting_data(app_build_id, app_id, build_profile_id, request.couch_user, request) if _should_force_log_submission(request): info['force_logs'] = True return JsonResponse(info)
def heartbeat(request, domain, app_build_id): """ An endpoint for CommCare mobile to get latest CommCare APK and app version info. (Should serve from cache as it's going to be busy view) 'app_build_id' (that comes from URL) can be id of any version of the app 'app_id' (urlparam) is usually id of an app that is not a copy mobile simply needs it to be resent back in the JSON, and doesn't need any validation on it. This is pulled from @uniqueid from profile.xml """ app_id = request.GET.get('app_id', '') info = {"app_id": app_id} try: # mobile will send brief_app_id info.update(LatestAppInfo(app_id, domain).get_info()) except (Http404, AssertionError): # If it's not a valid 'brief' app id, find it by talking to couch notify_exception(request, 'Received an invalid heartbeat request') app = get_app_cached(domain, app_build_id) brief_app_id = app.master_id info.update(LatestAppInfo(brief_app_id, domain).get_info()) else: if settings.SERVER_ENVIRONMENT not in settings.ICDS_ENVS: # disable on icds for now since couch still not happy couch_user = request.couch_user try: update_user_reporting_data(app_build_id, app_id, couch_user, request) except ResourceConflict: # https://sentry.io/dimagi/commcarehq/issues/521967014/ couch_user = CouchUser.get(couch_user.user_id) update_user_reporting_data(app_build_id, app_id, couch_user, request) return JsonResponse(info)
def recovery_measures(request, domain, build_id): response = { "app_id": request.GET.get('app_id') } # passed through unchanged app_id = get_app_cached(domain, build_id).master_id measures = get_recovery_measures_cached(domain, app_id) if measures: response["recovery_measures"] = measures return JsonResponse(response)
def recovery_measures(request, domain, build_id): app_id = get_app_cached(domain, build_id).master_id response = { "latest_apk_version": get_default_build_spec().version, "latest_ccz_version": get_latest_released_app_version(domain, app_id), "app_id": request.GET.get('app_id'), # passed through unchanged } measures = get_recovery_measures_cached(domain, app_id) if measures: response["recovery_measures"] = measures return JsonResponse(response)
def recovery_measures(request, domain, build_id): app_id = get_app_cached(domain, build_id).master_id response = { "latest_apk_version": get_default_build_spec().version, "latest_ccz_version": get_latest_released_app_version(domain, app_id), "app_id": request.GET.get('app_id'), # passed through unchanged } measures = get_recovery_measures_cached(domain, app_id) if measures: response["recovery_measures"] = measures return JsonResponse(response)
def get_multimedia_sizes(request, domain, app_id, build_profile_id=None): """ return size for different multimedia types and total for an app, directly presentable to the user """ build = get_app_cached(domain, app_id) if not build.copy_of: return JsonResponse({ "message": _("Multimedia size comparison is only available for app builds") }, status=400) mm_sizes = get_multimedia_sizes_for_build(build, build_profile_id=build_profile_id) if mm_sizes: mm_sizes = _update_mm_sizes(mm_sizes) return JsonResponse(mm_sizes)
def get_related_cases(domain, app_id, case_type, cases): """ Fetch related cases that are necessary to display any related-case properties in the app requesting this case search. Returns list of CommCareCase objects for adding to CaseDBFixture. """ if not cases: return [] app = get_app_cached(domain, app_id) paths = get_related_case_relationships(app, case_type) if not paths: return [] return get_related_case_results(domain, cases, paths)
def toggle_build_profile(request, domain, build_id, build_profile_id): build = get_app_cached(request.domain, build_id) status = request.GET.get('action') == 'enable' try: LatestEnabledBuildProfiles.update_status(build, build_profile_id, status) except ValidationError as e: messages.error(request, e) else: latest_enabled_build_profile = LatestEnabledBuildProfiles.for_app_and_profile( build.copy_of, build_profile_id) if latest_enabled_build_profile: messages.success(request, _("Latest version for profile {} is now {}").format( build.build_profiles[build_profile_id].name, latest_enabled_build_profile.version )) else: messages.success(request, _("Latest release now available for profile {}").format( build.build_profiles[build_profile_id].name )) return HttpResponseRedirect(reverse('download_index', args=[domain, build_id]))
def get_multimedia_sizes_for_build(domain, build_id, build_profile_id=None): build = get_app_cached(domain, build_id) assert build.copy_of, _("Size calculation available only for builds") build_profile = build.build_profiles.get(build_profile_id) if build_profile_id else None multimedia_map_for_build = build.multimedia_map_for_build(build_profile=build_profile) multimedia_map_for_build_by_id = { media_map_item['multimedia_id']: media_map_item for path, media_map_item in multimedia_map_for_build.items() } media_objects = { mm_object.get_id: mm_object for path, mm_object in build.get_media_objects(multimedia_map=multimedia_map_for_build) } total_size = defaultdict(lambda: 0) for multimedia_id, media_item in multimedia_map_for_build_by_id.items(): media_object = media_objects[multimedia_id] total_size[media_object.doc_type] += media_object.content_length return total_size
def get_related_cases(helper, app_id, case_types, cases, custom_related_case_property): """ Fetch related cases that are necessary to display any related-case properties in the app requesting this case search. Returns list of CommCareCase objects for adding to CaseDBFixture. """ if not cases: return [] app = get_app_cached(helper.domain, app_id) paths = [ rel for rels in [ get_related_case_relationships(app, case_type) for case_type in case_types ] for rel in rels ] child_case_types = [ _type for types in [get_child_case_types(app, case_type) for case_type in case_types] for _type in types ] expanded_case_results = [] if custom_related_case_property: expanded_case_results.extend( get_expanded_case_results(helper, custom_related_case_property, cases)) results = expanded_case_results top_level_cases = cases + expanded_case_results if paths: results.extend(get_related_case_results(helper, top_level_cases, paths)) if child_case_types: results.extend( get_child_case_results(helper, top_level_cases, child_case_types)) initial_case_ids = {case.case_id for case in cases}
def heartbeat(request, domain, app_build_id): """ An endpoint for CommCare mobile to get latest CommCare APK and app version info. (Should serve from cache as it's going to be busy view) 'app_build_id' (that comes from URL) can be id of any version of the app 'app_id' (urlparam) is usually id of an app that is not a copy mobile simply needs it to be resent back in the JSON, and doesn't need any validation on it. This is pulled from @uniqueid from profile.xml """ app_id = request.GET.get('app_id', '') info = {"app_id": app_id} try: # mobile will send brief_app_id info.update(LatestAppInfo(app_id, domain).get_info()) except (Http404, AssertionError): # If it's not a valid 'brief' app id, find it by talking to couch notify_exception(request, 'Received an invalid heartbeat request') app = get_app_cached(domain, app_build_id) brief_app_id = app.master_id info.update(LatestAppInfo(brief_app_id, domain).get_info()) else: if settings.SERVER_ENVIRONMENT not in settings.ICDS_ENVS: # disable on icds for now since couch still not happy couch_user = request.couch_user try: update_user_reporting_data(app_build_id, app_id, couch_user, request) except ResourceConflict: # https://sentry.io/dimagi/commcarehq/issues/521967014/ couch_user = CouchUser.get(couch_user.user_id) update_user_reporting_data(app_build_id, app_id, couch_user, request) if _should_force_log_submission(request): info['force_logs'] = True return JsonResponse(info)
def get_restore_response(domain, couch_user, app_id=None, since=None, version='1.0', state=None, items=False, force_cache=False, cache_timeout=None, overwrite_cache=False, as_user=None, device_id=None, user_id=None, openrosa_version=None, case_sync=None): """ :param domain: Domain being restored from :param couch_user: User performing restore :param app_id: App ID of the app making the request :param since: ID of current sync log used to generate incremental sync :param version: Version of the sync response required :param state: Hash value of the current database of cases on the device for consistency checking :param items: Include item count if True :param force_cache: Force response to be cached :param cache_timeout: Override the default cache timeout of 1 hour. :param overwrite_cache: Ignore cached response if True :param as_user: Username of user to generate restore for (if different from current user) :param device_id: ID of device performing restore :param user_id: ID of user performing restore (used in case of deleted user with same username) :param openrosa_version: :param case_sync: Override default case sync algorithm :return: Tuple of (http response, timing context or None) """ if user_id and user_id != couch_user.user_id: # sync with a user that has been deleted but a new # user was created with the same username and password from couchforms.openrosa_response import get_simple_response_xml from couchforms.openrosa_response import ResponseNature response = get_simple_response_xml( 'Attempt to sync with invalid user.', ResponseNature.OTA_RESTORE_ERROR) return HttpResponse(response, content_type="text/xml; charset=utf-8", status=412), None is_demo_restore = couch_user.is_commcare_user() and couch_user.is_demo_user if is_demo_restore: # if user is in demo-mode, return demo restore return demo_user_restore_response(couch_user), None uses_login_as = bool(as_user) as_user_obj = CouchUser.get_by_username(as_user) if uses_login_as else None if uses_login_as and not as_user_obj: msg = _('Invalid restore as user {}').format(as_user) return HttpResponse(msg, status=401), None is_permitted, message = is_permitted_to_restore( domain, couch_user, as_user_obj, ) if not is_permitted: return HttpResponse(message, status=401), None restore_user = get_restore_user(domain, couch_user, as_user_obj) if not restore_user: return HttpResponse('Could not find user', status=404), None project = Domain.get_by_name(domain) async_restore_enabled = (toggles.ASYNC_RESTORE.enabled(domain) and openrosa_version and LooseVersion(openrosa_version) >= LooseVersion( OPENROSA_VERSION_MAP['ASYNC_RESTORE'])) app = get_app_cached(domain, app_id) if app_id else None restore_config = RestoreConfig( project=project, restore_user=restore_user, params=RestoreParams( sync_log_id=since, version=version, state_hash=state, include_item_count=items, app=app, device_id=device_id, openrosa_version=openrosa_version, ), cache_settings=RestoreCacheSettings(force_cache=force_cache or async_restore_enabled, cache_timeout=cache_timeout, overwrite_cache=overwrite_cache), is_async=async_restore_enabled, case_sync=case_sync, ) return restore_config.get_response(), restore_config.timing_context
def get_restore_response(domain, couch_user, app_id=None, since=None, version='1.0', state=None, items=False, force_cache=False, cache_timeout=None, overwrite_cache=False, as_user=None, device_id=None, user_id=None, openrosa_version=None, case_sync=None): if user_id and user_id != couch_user.user_id: # sync with a user that has been deleted but a new # user was created with the same username and password from couchforms.openrosa_response import get_simple_response_xml from couchforms.openrosa_response import ResponseNature response = get_simple_response_xml( 'Attempt to sync with invalid user.', ResponseNature.OTA_RESTORE_ERROR) return HttpResponse(response, content_type="text/xml; charset=utf-8", status=412), None is_demo_restore = couch_user.is_commcare_user() and couch_user.is_demo_user if is_demo_restore: # if user is in demo-mode, return demo restore return demo_user_restore_response(couch_user), None uses_login_as = bool(as_user) as_user_obj = CouchUser.get_by_username(as_user) if uses_login_as else None if uses_login_as and not as_user_obj: msg = _('Invalid restore as user {}').format(as_user) return HttpResponse(msg, status=401), None is_permitted, message = is_permitted_to_restore( domain, couch_user, as_user_obj, ) if not is_permitted: return HttpResponse(message, status=401), None restore_user = get_restore_user(domain, couch_user, as_user_obj) if not restore_user: return HttpResponse('Could not find user', status=404), None project = Domain.get_by_name(domain) async_restore_enabled = (toggles.ASYNC_RESTORE.enabled(domain) and openrosa_version and LooseVersion(openrosa_version) >= LooseVersion( OPENROSA_VERSION_MAP['ASYNC_RESTORE'])) app = get_app_cached(domain, app_id) if app_id else None restore_config = RestoreConfig( project=project, restore_user=restore_user, params=RestoreParams( sync_log_id=since, version=version, state_hash=state, include_item_count=items, app=app, device_id=device_id, openrosa_version=openrosa_version, ), cache_settings=RestoreCacheSettings(force_cache=force_cache or async_restore_enabled, cache_timeout=cache_timeout, overwrite_cache=overwrite_cache), async=async_restore_enabled, case_sync=case_sync, ) return restore_config.get_response(), restore_config.timing_context
def test_get_app_cached(self): app_doc = get_app_cached(self.domain, self.v2_build.get_id) self.assertEqual(app_doc['is_released'], True)
def get_restore_response(domain, couch_user, app_id=None, since=None, version='1.0', state=None, items=False, force_cache=False, cache_timeout=None, overwrite_cache=False, as_user=None, device_id=None, user_id=None, openrosa_version=None, case_sync=None): """ :param domain: Domain being restored from :param couch_user: User performing restore :param app_id: App ID of the app making the request :param since: ID of current sync log used to generate incremental sync :param version: Version of the sync response required :param state: Hash value of the current database of cases on the device for consistency checking :param items: Include item count if True :param force_cache: Force response to be cached :param cache_timeout: Override the default cache timeout of 1 hour. :param overwrite_cache: Ignore cached response if True :param as_user: Username of user to generate restore for (if different from current user) :param device_id: ID of device performing restore :param user_id: ID of user performing restore (used in case of deleted user with same username) :param openrosa_version: :param case_sync: Override default case sync algorithm :return: Tuple of (http response, timing context or None) """ if user_id and user_id != couch_user.user_id: # sync with a user that has been deleted but a new # user was created with the same username and password from couchforms.openrosa_response import get_simple_response_xml from couchforms.openrosa_response import ResponseNature response = get_simple_response_xml( 'Attempt to sync with invalid user.', ResponseNature.OTA_RESTORE_ERROR ) return HttpResponse(response, content_type="text/xml; charset=utf-8", status=412), None is_demo_restore = couch_user.is_commcare_user() and couch_user.is_demo_user if is_demo_restore: # if user is in demo-mode, return demo restore return demo_user_restore_response(couch_user), None uses_login_as = bool(as_user) as_user_obj = CouchUser.get_by_username(as_user) if uses_login_as else None if uses_login_as and not as_user_obj: msg = _('Invalid restore as user {}').format(as_user) return HttpResponse(msg, status=401), None is_permitted, message = is_permitted_to_restore( domain, couch_user, as_user_obj, ) if not is_permitted: return HttpResponse(message, status=401), None restore_user = get_restore_user(domain, couch_user, as_user_obj) if not restore_user: return HttpResponse('Could not find user', status=404), None project = Domain.get_by_name(domain) async_restore_enabled = ( toggles.ASYNC_RESTORE.enabled(domain) and openrosa_version and LooseVersion(openrosa_version) >= LooseVersion(OPENROSA_VERSION_MAP['ASYNC_RESTORE']) ) app = get_app_cached(domain, app_id) if app_id else None restore_config = RestoreConfig( project=project, restore_user=restore_user, params=RestoreParams( sync_log_id=since, version=version, state_hash=state, include_item_count=items, app=app, device_id=device_id, openrosa_version=openrosa_version, ), cache_settings=RestoreCacheSettings( force_cache=force_cache or async_restore_enabled, cache_timeout=cache_timeout, overwrite_cache=overwrite_cache ), is_async=async_restore_enabled, case_sync=case_sync, ) return restore_config.get_response(), restore_config.timing_context