From 7eb79252356c630f70e22afa44b2a2211ea320be Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Tue, 16 Jul 2024 16:55:58 +0530 Subject: [PATCH 01/27] Initial commit from develop branch --- .env_example | 2 + .flake8 | 2 +- Pipfile | 1 + Pipfile.lock | 23 + azure-pipeline-dev.yml | 12 +- celerybeat.sh | 2 +- config/settings/base.py | 57 +- config/settings/dev.py | 5 +- config/settings/dev_test.py | 63 + config/settings/prod.py | 2 - config/urls.py | 1 - proco/about_us/tests/test_api.py | 5 +- proco/accounts/api.py | 428 ++++-- proco/accounts/api_urls.py | 3 +- proco/accounts/config.py | 6 +- proco/accounts/exceptions.py | 12 +- .../0014_added_data_layer_code_field.py | 23 + .../0015_deleted_unused_historical_models.py | 62 + proco/accounts/models.py | 18 +- proco/accounts/serializers.py | 112 +- proco/accounts/tests/test_api.py | 1243 +++++++++++++++-- proco/accounts/tests/test_utils.py | 94 ++ proco/accounts/utils.py | 19 +- proco/background/api.py | 14 +- ...ded_name_descritpion_soft_delete_fields.py | 48 + .../migrations/0003_added_deleted_bu_field.py | 22 + .../0004_added_unique_constraints.py | 22 + proco/background/models.py | 53 +- proco/background/tests/test_api.py | 15 +- proco/background/utils.py | 35 + proco/connection_statistics/aggregations.py | 6 +- proco/connection_statistics/api.py | 455 ++---- .../0063_added_5g_choice_in_coverage_type.py | 19 + proco/connection_statistics/models.py | 8 +- proco/connection_statistics/serializers.py | 6 - .../tests/test_aggregates.py | 5 +- proco/connection_statistics/tests/test_api.py | 9 +- .../tests/test_models.py | 2 +- proco/connection_statistics/utils.py | 24 +- proco/contact/tests/test_api.py | 3 +- proco/core/config.py | 21 + proco/core/db_utils.py | 8 +- proco/core/filters.py | 11 + .../management/commands/create_admin_user.py | 14 +- .../create_api_key_with_write_access.py | 23 +- .../commands/data_alteration_through_sql.py | 32 +- .../core/management/commands/data_cleanup.py | 138 +- .../commands/index_rebuild_schools.py | 45 +- .../commands/load_about_us_content.py | 7 +- .../commands/load_country_admin_data.py | 65 +- .../load_iso3_format_code_for_countries.py | 9 +- .../commands/load_system_data_layers.py | 23 +- ...opulate_active_data_layer_for_countries.py | 59 +- .../populate_admin_id_fields_to_schools.py | 26 +- .../commands/populate_admin_ui_labels.py | 16 +- .../commands/populate_school_new_fields.py | 32 +- .../populate_school_registration_data.py | 25 +- .../management/commands/redo_aggregations.py | 25 +- .../update_system_role_permissions.py | 7 +- proco/core/resources/filters.json | 259 ++++ proco/core/tests/test_utils.py | 5 - proco/core/utils.py | 105 +- proco/custom_auth/authentication.py | 7 +- .../0014_deleted_unused_historical_models.py | 47 + proco/custom_auth/models.py | 4 +- proco/custom_auth/serializers.py | 13 +- proco/custom_auth/tests/test_api.py | 2 +- proco/custom_auth/tests/test_utils.py | 18 +- proco/custom_auth/utils.py | 7 +- proco/data_sources/api.py | 1 - .../commands/data_loss_recovery_for_pcdc.py | 52 +- .../commands/data_loss_recovery_for_qos.py | 102 +- .../0012_added_deleted_published_status.py | 0 proco/data_sources/serializers.py | 1 - proco/data_sources/tasks.py | 425 +++--- proco/data_sources/tests/test_api.py | 2 +- proco/data_sources/utils.py | 157 ++- proco/locations/api.py | 99 +- proco/locations/serializers.py | 20 +- proco/locations/tests/test_api.py | 2 +- .../management_utils/user_role_permissions.py | 14 +- ...ltime_dailycheckapp_and_realtime_unicef.py | 51 + proco/schools/api.py | 196 +-- proco/schools/api_urls.py | 4 +- proco/schools/constants.py | 3 +- ...nique_constraint_on_country_and_giga_id.py | 0 proco/schools/serializers.py | 155 +- proco/schools/tasks.py | 22 +- proco/schools/tests/factories.py | 11 +- proco/schools/tests/test_api.py | 382 ++++- proco/schools/tests/test_utils.py | 35 + proco/schools/utils.py | 9 +- proco/taskapp/__init__.py | 8 +- proco/utils/log.py | 53 +- proco/utils/tasks.py | 218 +-- proco/utils/tests.py | 6 +- proco/utils/urls.py | 2 +- web-worker.sh | 8 + 98 files changed, 4150 insertions(+), 1882 deletions(-) create mode 100644 config/settings/dev_test.py create mode 100644 proco/accounts/migrations/0014_added_data_layer_code_field.py create mode 100644 proco/accounts/migrations/0015_deleted_unused_historical_models.py create mode 100644 proco/accounts/tests/test_utils.py create mode 100755 proco/background/migrations/0002_added_name_descritpion_soft_delete_fields.py create mode 100755 proco/background/migrations/0003_added_deleted_bu_field.py create mode 100755 proco/background/migrations/0004_added_unique_constraints.py create mode 100644 proco/background/utils.py create mode 100755 proco/connection_statistics/migrations/0063_added_5g_choice_in_coverage_type.py create mode 100644 proco/core/filters.py create mode 100644 proco/core/resources/filters.json create mode 100644 proco/custom_auth/migrations/0014_deleted_unused_historical_models.py mode change 100644 => 100755 proco/data_sources/migrations/0012_added_deleted_published_status.py create mode 100644 proco/proco_data_migrations/migrations/0004_drop_tables_for_realtime_dailycheckapp_and_realtime_unicef.py mode change 100644 => 100755 proco/schools/migrations/0030_added_unique_constraint_on_country_and_giga_id.py create mode 100644 proco/schools/tests/test_utils.py diff --git a/.env_example b/.env_example index f0e3be9..bb8ce58 100644 --- a/.env_example +++ b/.env_example @@ -5,6 +5,8 @@ DJANGO_SECRET_KEY= PROJECT_FULL_NAME=gigamaps PROJECT_SHORT_NAME=gigamaps +GIGAMAPS_LOG_LEVEL = DEBUG + SUPPORT_EMAIL_ID=giga@mail.unicef.org SUPPORT_PHONE_NUMBER=1234567890 diff --git a/.flake8 b/.flake8 index c788e06..245c5bc 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,7 @@ [flake8] max-line-length = 120 max-complexity = 30 -exclude = proco_data_migrations, migrations, config/settings, manage.py, Exceptions.py, venv, dailycheckapp_contact, realtime_dailycheckapp, realtime_unicef +exclude = proco_data_migrations, migrations, config/settings, manage.py, Exceptions.py, venv ignore = ; PyFlakes errors ; F405 name may be undefined, or defined from star imports: module diff --git a/Pipfile b/Pipfile index 4fd4291..218007c 100644 --- a/Pipfile +++ b/Pipfile @@ -79,6 +79,7 @@ djangorestframework-jwt = "==1.11.0" jsonfield = "==2.0.2" azure-search-documents = "==11.3.0" django-prometheus = "==2.2.0" +celery-redbeat = "==2.2.0" [requires] python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock index 8d19a97..176140e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -154,6 +154,13 @@ "index": "pypi", "version": "==5.3.6" }, + "celery-redbeat": { + "hashes": [ + "sha256:71104729380d4eaefd91a9b7d270ba7851bfff9ad55a43ac8365acbb0c608b77" + ], + "index": "pypi", + "version": "==2.2.0" + }, "certifi": { "hashes": [ "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", @@ -1327,6 +1334,14 @@ ], "version": "==0.6.3" }, + "tenacity": { + "hashes": [ + "sha256:9e6f7cf7da729125c7437222f8a522279751cdfbe6b67bfe64f75d3a348661b2", + "sha256:cd80a53a79336edba8489e767f729e4f391c896956b57140b5d7511a64bbd3ef" + ], + "markers": "python_version >= '3.8'", + "version": "==8.4.2" + }, "text-unidecode": { "hashes": [ "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", @@ -2328,6 +2343,14 @@ "markers": "python_full_version >= '3.7.0'", "version": "==13.7.0" }, + "setuptools": { + "hashes": [ + "sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05", + "sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1" + ], + "markers": "python_version >= '3.8'", + "version": "==70.2.0" + }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", diff --git a/azure-pipeline-dev.yml b/azure-pipeline-dev.yml index 9334fd3..257f6bd 100644 --- a/azure-pipeline-dev.yml +++ b/azure-pipeline-dev.yml @@ -12,10 +12,10 @@ name: $(SourceBranchName).$(Build.BuildId).$(Date:yyyyMMdd).$(Rev:r) pool: vmImage: 'ubuntu-22.04' -variables: - dockerRegistryServiceConnection: 'UNICEF_DATA_CONNECT_WEB_ACR' +variables: + dockerRegistryServiceConnection: $(DOCKER_SERVICE_CONNECTION) dockerfilePath: './Dockerfile' - app: 'project-connect-backend-v2' + app: $(DOCKER_REPOSITORY) imageRepositoryDev: 'dev/$(app)' imageRepositoryStg: 'stg/$(app)' imageRepositoryProd: 'prod/$(app)' @@ -25,7 +25,7 @@ variables: steps: # Use a specific Python version - task: UsePythonVersion@0 - displayName: Building Razor with $(pythonVersion) + displayName: Building Razor with $(pythonVersion) inputs: versionSpec: $(pythonVersion) addToPath: true @@ -70,9 +70,9 @@ steps: -Dsonar.sources=proco \ -Dsonar.host.url=$(SONAR_HOST) \ -Dsonar.python.coverage.reportPaths=coverage.xml \ - -Dsonar.coverage.exclusions=**/migrations/**,**/proco_data_migrations/**,**/tests/**,**/proco/**/admin.py,**/dailycheckapp_contact/**,**/realtime_dailycheckapp/**,**/realtime_unicef/**,**/management/commands/** + -Dsonar.exclusions=**/migrations/**,**/proco_data_migrations/**,**/tests/**,**/proco/**/admin.py,**/dailycheckapp_contact/**,**/realtime_dailycheckapp/**,**/realtime_unicef/**,**/management/commands/** -# Docker build and push +# Docker build and push - task: Docker@2 displayName: Dev - Build and Push image inputs: diff --git a/celerybeat.sh b/celerybeat.sh index b5d7867..e4d346e 100644 --- a/celerybeat.sh +++ b/celerybeat.sh @@ -14,4 +14,4 @@ pipenv run python -m flask run --host 0.0.0.0 --port 8000 & # pipenv run celery -A proco.taskapp beat $* # --logfile=/code/celeryd-%n.log --loglevel=DEBUG -pipenv run celery --app=proco.taskapp beat $* +pipenv run celery --app=proco.taskapp beat --scheduler=redbeat.RedBeatScheduler $* diff --git a/config/settings/base.py b/config/settings/base.py index 157c3ec..49c99f1 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -1,3 +1,6 @@ +import json +import os +import sys import warnings import environ @@ -34,7 +37,7 @@ # -------------------------------------------------------------------------- DJANGO_APPS = [ - 'config.apps.CustomAdminConfig', + 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', @@ -73,12 +76,9 @@ 'proco.locations', 'proco.connection_statistics', 'proco.contact', - 'proco.dailycheckapp_contact', 'proco.background', - 'proco.realtime_unicef', - 'proco.realtime_dailycheckapp', 'proco.proco_data_migrations', - 'proco.data_sources' + 'proco.data_sources', ] INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS @@ -342,7 +342,6 @@ 'schools', 'background', 'contact', - 'dailycheckapp_contact', 'accounts', 'data_sources', ) @@ -350,7 +349,6 @@ RANDOM_SCHOOLS_DEFAULT_AMOUNT = env('RANDOM_SCHOOLS_DEFAULT_AMOUNT', default=20000) CONTACT_MANAGERS = env.list('CONTACT_MANAGERS', default=['test@test.test']) -DAILYCHECKAPP_CONTACT_MANAGERS = env.list('DAILYCHECKAPP_CONTACT_MANAGERS', default=['test@test.test']) CONSTANCE_REDIS_CONNECTION = env('REDIS_URL', default='redis://localhost:6379/0') CONSTANCE_ADDITIONAL_FIELDS = { @@ -361,7 +359,6 @@ } CONSTANCE_CONFIG = { 'CONTACT_EMAIL': ('', 'Email to receive contact messages', 'email_input'), - 'DAILYCHECKAPP_CONTACT_EMAIL': ('', 'Email to receive dailycheckapp_contact messages', 'email_input'), } # Cache control headers @@ -436,4 +433,48 @@ INVALIDATE_CACHE_HARD = env('INVALIDATE_CACHE_HARD', default='false') +with open(os.path.join(BASE_DIR, 'proco', 'core', 'resources', 'filters.json')) as filters_json_file: + FILTERS_DATA = json.load(filters_json_file) + # DATABASE_ROUTERS = ["proco.utils.read_db_router.StandbyRouter"] + +GIGAMAPS_LOG_LEVEL = env('GIGAMAPS_LOG_LEVEL', default='INFO') + +# LOGGING +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse', + }, + 'require_debug_true': { + '()': 'django.utils.log.RequireDebugTrue', + }, + 'hostname_filter': { + '()': 'proco.core.filters.HostInfoFilter', + }, + }, + 'formatters': { + 'verbose': { + 'format': '%(hostname)s %(hostip)s %(asctime)s %(levelname)s %(pathname)s %(process)d ' + '%(processName)s %(thread)d: %(message)s' + }, + }, + 'handlers': { + 'console': { + 'level': GIGAMAPS_LOG_LEVEL, + 'class': 'logging.StreamHandler', + 'formatter': 'verbose', + 'stream': sys.stderr, + 'filters': ['hostname_filter'], + }, + }, + 'loggers': { + 'gigamaps': { + 'level': GIGAMAPS_LOG_LEVEL, + 'handlers': ['console'], + 'filters': ['hostname_filter'], + }, + }, +} diff --git a/config/settings/dev.py b/config/settings/dev.py index ec2eb03..e2a4970 100644 --- a/config/settings/dev.py +++ b/config/settings/dev.py @@ -28,11 +28,8 @@ try: DATABASES['read_only_database'] = env.db_url(var='READ_ONLY_DATABASE_URL') - DATABASES['realtime'] = env.db_url(var='REALTIME_DATABASE_URL') - DATABASES['dailycheckapp_realtime'] = env.db_url(var='REALTIME_DAILYCHECKAPP_DATABASE_URL') except ImproperlyConfigured: - DATABASES['realtime'] = DATABASES['default'] - DATABASES['dailycheckapp_realtime'] = DATABASES['default'] + pass # Email settings # -------------------------------------------------------------------------- diff --git a/config/settings/dev_test.py b/config/settings/dev_test.py new file mode 100644 index 0000000..eed8304 --- /dev/null +++ b/config/settings/dev_test.py @@ -0,0 +1,63 @@ +from kombu import Exchange, Queue # NOQA + +from config.settings.base import * # noqa: F403 + +# Pytest speed improvements configuration +# Disable debugging for test case execution +DEBUG = False +TEMPLATES[0]['OPTIONS']['debug'] = DEBUG + +SECRET_KEY = env('SECRET_KEY', default='test_key') + +ALLOWED_HOSTS = ['*'] +INTERNAL_IPS = [] + +ADMINS = ( + ('Dev Email', env('DEV_ADMIN_EMAIL', default='admin@localhost')), +) +MANAGERS = ADMINS + + +# Database +# https://docs.djangoproject.com/en/1.9/ref/settings/#databases +# -------------------------------------------------------------------------- + +DATABASES = { + 'default': env.db(default='postgis://localhost/proco'), +} + +DATABASES['default']['CONN_MAX_AGE'] = 1000 + +# Email settings +# -------------------------------------------------------------------------- + +# DEFAULT_FROM_EMAIL = 'noreply@example.com' +# SERVER_EMAIL = DEFAULT_FROM_EMAIL +# EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + +if CELERY_ENABLED: + MAILING_USE_CELERY = False + +INTERNAL_IPS = ('127.0.0.1',) + +# Sentry config +# ------------- + +SENTRY_ENABLED = False + + +# Mapbox +# -------------- + +MAPBOX_KEY = env('MAPBOX_KEY', default='') + +ANYMAIL['DEBUG_API_REQUESTS'] = False + +ENABLE_AZURE_COGNITIVE_SEARCH = False + +AZURE_CONFIG['COGNITIVE_SEARCH'] = { + 'SEARCH_ENDPOINT': env('SEARCH_ENDPOINT', default='test.endpoint'), + 'SEARCH_API_KEY': env('SEARCH_API_KEY', default='testsearchapikey'), + 'COUNTRY_INDEX_NAME': env('COUNTRY_INDEX_NAME', default='giga_countries'), + 'SCHOOL_INDEX_NAME': env('SCHOOL_INDEX_NAME', default='giga_schools'), +} diff --git a/config/settings/prod.py b/config/settings/prod.py index 5a35961..1591de3 100644 --- a/config/settings/prod.py +++ b/config/settings/prod.py @@ -23,8 +23,6 @@ DATABASES = { 'default': env.db(), 'read_only_database': env.db_url(var='READ_ONLY_DATABASE_URL'), - 'realtime': env.db_url(var='REALTIME_DATABASE_URL'), - 'dailycheckapp_realtime': env.db_url(var='REALTIME_DAILYCHECKAPP_DATABASE_URL'), } # Template diff --git a/config/urls.py b/config/urls.py index 7cef051..f2f02e5 100644 --- a/config/urls.py +++ b/config/urls.py @@ -40,7 +40,6 @@ def trigger_error(request): path('statistics/', include('proco.connection_statistics.api_urls')), path('contact/', include('proco.contact.api_urls')), path('about_us/', include('proco.about_us.api_urls')), - path('dailycheckapp_contact/', include('proco.dailycheckapp_contact.api_urls')), path('accounts/', include('proco.accounts.api_urls')), path('sources/', include('proco.data_sources.api_urls')), ])), diff --git a/proco/about_us/tests/test_api.py b/proco/about_us/tests/test_api.py index 458a35c..cec3e75 100644 --- a/proco/about_us/tests/test_api.py +++ b/proco/about_us/tests/test_api.py @@ -10,11 +10,10 @@ class SlideImageAPITestCase(TestAPIViewSetMixin, TestCase): base_view = 'about_us:' - databases = {'read_only_database', 'default'} + databases = {'default',} @classmethod def setUpTestData(cls): - # self.databases = 'default' cls.email = 'test@test.com' cls.password = 'SomeRandomPass96' cls.user = test_utilities.setup_admin_user_by_role() @@ -70,7 +69,7 @@ def test_slide_destroy(self): class AboutUsAPITestCase(TestAPIViewSetMixin, TestCase): base_view = 'about_us:' - databases = {'default', 'read_only_database'} + databases = {'default',} def setUp(self): self.email = 'test@test.com' diff --git a/proco/accounts/api.py b/proco/accounts/api.py index 288c00b..b1a6aed 100644 --- a/proco/accounts/api.py +++ b/proco/accounts/api.py @@ -1,13 +1,13 @@ import copy import json +import logging from datetime import timedelta +from math import floor, ceil from django.conf import settings from django.contrib.admin.models import LogEntry -from django.db.models import ( - Case, Value, When -) -from django.db.models import IntegerField +from django.db.models import Case, F, IntegerField, Value, When, Min, Max +from django.db.models import Q from django.shortcuts import get_object_or_404 from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_control @@ -31,11 +31,15 @@ from proco.core.viewsets import BaseModelViewSet from proco.custom_auth import models as auth_models from proco.locations.models import Country +from proco.schools.models import School from proco.utils import dates as date_utilities from proco.utils.cache import cache_manager from proco.utils.filters import NullsAlwaysLastOrderingFilter +from proco.utils.mixins import CachedListMixin from proco.utils.tasks import update_all_cached_values +logger = logging.getLogger('gigamaps.' + __name__) + class APIsListAPIView(BaseModelViewSet): """ @@ -143,7 +147,6 @@ def update_serializer_context(self, context): if api_instance is not None: context['api_instance'] = api_instance return context - # raise accounts_exceptions.InvalidAPIError() def perform_destroy(self, instance): """ @@ -195,6 +198,7 @@ def put(self, request, *args, **kwargs): request_user = self.request.user queryset = accounts_models.APIKey.objects.all().filter( + Q(user=request_user) | Q(has_write_access=True), api__deleted__isnull=True, api_id=request.data.get('api_id'), api_key=request.data.get('api_key'), @@ -202,12 +206,9 @@ def put(self, request, *args, **kwargs): valid_to__gte=core_utilities.get_current_datetime_object().date(), ) - if queryset.filter(user=request_user).exists(): - return Response(status=rest_status.HTTP_200_OK) - elif queryset.filter(has_write_access=True).exists(): + if queryset.exists(): return Response(status=rest_status.HTTP_200_OK) - - return Response(status=rest_status.HTTP_404_NOT_FOUND, data={"detail": "Please enter valid api key."}) + return Response(status=rest_status.HTTP_404_NOT_FOUND, data={'detail': 'Please enter valid api key.'}) class NotificationViewSet(BaseModelViewSet): @@ -298,6 +299,111 @@ def get(self, request, *args, **kwargs): return Response(data=static_data) +class AdvancedFiltersViewSet(APIView): + base_auth_permissions = ( + permissions.AllowAny, + ) + + CACHE_KEY = 'cache' + CACHE_KEY_PREFIX = 'ADVANCE_FILTERS_JSON' + + def get_cache_key(self): + params = dict(self.request.query_params) + params.pop(self.CACHE_KEY, None) + return '{0}_{1}'.format(self.CACHE_KEY_PREFIX, + '_'.join(map(lambda x: '{0}_{1}'.format(x[0], x[1]), sorted(params.items()))), ) + + def get(self, request, *args, **kwargs): + use_cached_data = self.request.query_params.get(self.CACHE_KEY, 'on').lower() in ['on', 'true'] + cache_key = self.get_cache_key() + + response_data = None + if use_cached_data: + response_data = cache_manager.get(cache_key) + + if not response_data: + filters = copy.deepcopy(settings.FILTERS_DATA) + + for filter_json in filters: + parameter_table = filter_json['parameter']['table'] + parameter_field = filter_json['parameter']['field'] + + last_weekly_status_field = 'last_weekly_status__{}'.format(parameter_field) + + active_countries_list = [] + + # Populate the active countries list + active_countries_sql_filter = filter_json.get('active_countries_filter', None) + if active_countries_sql_filter: + country_qs = School.objects.all() + if parameter_table == 'school_static': + country_qs = country_qs.select_related('last_weekly_status').annotate(**{ + parameter_table + '_' + parameter_field: F(last_weekly_status_field) + }) + + active_countries_list = list(country_qs.extra( + where=[active_countries_sql_filter], + ).order_by('country_id').values_list('country_id', flat=True).distinct('country_id')) + + if len(active_countries_list) > 0: + filter_json['active_countries_list'] = active_countries_list + + del filter_json['active_countries_filter'] + + if filter_json['type'] == 'range': + select_qs = School.objects.all() + if len(active_countries_list) > 0: + select_qs = select_qs.filter(country_id__in=active_countries_list) + + if parameter_table == 'school_static': + select_qs = select_qs.select_related('last_weekly_status').values('country_id').annotate( + min_value=Min(F(last_weekly_status_field)), + max_value=Max(F(last_weekly_status_field)), + ) + else: + select_qs = select_qs.values('country_id').annotate( + min_value=Min(parameter_field), + max_value=Max(parameter_field), + ) + + min_max_result_country_wise = list( + select_qs.values('country_id', 'min_value', 'max_value').order_by('country_id').distinct()) + + active_countries_range = filter_json['active_countries_range'] + + for min_max_result in min_max_result_country_wise: + country_id = min_max_result.pop('country_id') + country_range_json = active_countries_range.get(country_id, copy.deepcopy( + active_countries_range['default'])) + min_max_result['min_value'] = floor(min_max_result['min_value']) + min_max_result['max_value'] = ceil(min_max_result['max_value']) + + if 'downcast_aggr_str' in filter_json: + downcast_eval = filter_json['downcast_aggr_str'] + min_max_result['min_value'] = floor( + eval(downcast_eval.format(val=min_max_result['min_value']))) + min_max_result['max_value'] = ceil( + eval(downcast_eval.format(val=min_max_result['max_value']))) + + country_range_json.update(**min_max_result) + + country_range_json['min_place_holder'] = 'Min ({})'.format(min_max_result['min_value']) + country_range_json['max_place_holder'] = 'Max ({})'.format(min_max_result['max_value']) + active_countries_range[country_id] = country_range_json + + filter_json['active_countries_range'] = active_countries_range + + response_data = { + 'count': len(settings.FILTERS_DATA), + 'results': filters, + } + request_path = remove_query_param(request.get_full_path(), 'cache') + cache_manager.set(cache_key, response_data, request_path=request_path, + soft_timeout=settings.CACHE_CONTROL_MAX_AGE) + + return Response(data=response_data) + + class DataSourceViewSet(BaseModelViewSet): model = accounts_models.DataSource serializer_class = serializers.DataSourceListSerializer @@ -675,7 +781,15 @@ def get(self, request, *args, **kwargs): return Response(data=response) -class PublishedDataLayersViewSet(BaseModelViewSet): +class PublishedDataLayersViewSet(CachedListMixin, BaseModelViewSet): + """ + PublishedDataLayersViewSet + Cache Attr: + Auto Cache: Not required + Call Cache: Yes + """ + LIST_CACHE_KEY_PREFIX = 'PUBLISHED_LAYERS_LIST' + model = accounts_models.DataLayer serializer_class = serializers.DataLayersListSerializer @@ -786,7 +900,7 @@ def update_kwargs(self, country_ids, layer_instance): self.kwargs['admin1_ids'] = [a_id.strip() for a_id in query_params['admin1_id__in'].split(',')] if 'school_id' in query_param_keys: - self.kwargs['school_ids'] = [query_params['school_id']] + self.kwargs['school_ids'] = [str(query_params['school_id']).strip()] elif 'school_id__in' in query_param_keys: self.kwargs['school_ids'] = [s_id.strip() for s_id in query_params['school_id__in'].split(',')] @@ -796,6 +910,11 @@ def update_kwargs(self, country_ids, layer_instance): self.kwargs['convert_unit'] = layer_instance.global_benchmark.get('convert_unit', 'mbps') self.kwargs['is_reverse'] = layer_instance.is_reverse + self.kwargs['school_filters'] = core_utilities.get_filter_sql( + self.request, 'schools', 'schools_school') + self.kwargs['school_static_filters'] = core_utilities.get_filter_sql( + self.request, 'school_static', 'connection_statistics_schoolweeklystatus') + def get_benchmark_value(self, data_layer_instance): benchmark_val = data_layer_instance.global_benchmark.get('value') benchmark_unit = data_layer_instance.global_benchmark.get('unit') @@ -856,6 +975,7 @@ def get_info_query(self): AND (t."date" BETWEEN '{start_date}' AND '{end_date}') AND t."live_data_source" IN ({live_source_types}) ) + {school_weekly_join} WHERE ( "schools_school"."deleted" IS NULL AND "connection_statistics_schoolrealtimeregistration"."deleted" IS NULL @@ -863,6 +983,7 @@ def get_info_query(self): {country_condition} {admin1_condition} {school_condition} + {school_weekly_condition} AND "connection_statistics_schoolrealtimeregistration"."rt_registered" = True AND "connection_statistics_schoolrealtimeregistration"."rt_registration_date"::date <= '{end_date}') GROUP BY "schools_school"."id" @@ -875,6 +996,8 @@ def get_info_query(self): kwargs['country_condition'] = '' kwargs['admin1_condition'] = '' kwargs['school_condition'] = '' + kwargs['school_weekly_join'] = '' + kwargs['school_weekly_condition'] = '' kwargs['case_conditions'] = """ COUNT(DISTINCT CASE WHEN t.field_avg > {benchmark_value} THEN t.school_id ELSE NULL END) AS "good", @@ -903,69 +1026,74 @@ def get_info_query(self): ','.join([str(admin1_id) for admin1_id in kwargs['admin1_ids']]) ) - if len(kwargs.get('school_ids', [])) > 0: - kwargs['school_condition'] = 'AND "schools_school"."id" IN ({0})'.format( - ','.join([str(school_id) for school_id in kwargs['school_ids']]) - ) + if len(kwargs['school_filters']) > 0: + kwargs['school_condition'] = ' AND ' + kwargs['school_filters'] + + if len(kwargs['school_static_filters']) > 0: + kwargs['school_weekly_join'] = """ + LEFT OUTER JOIN "connection_statistics_schoolweeklystatus" + ON "schools_school"."last_weekly_status_id" = "connection_statistics_schoolweeklystatus"."id" + """ + kwargs['school_weekly_condition'] = ' AND ' + kwargs['school_static_filters'] return query.format(**kwargs) def get_school_view_info_query(self): query = """ - SELECT DISTINCT s."id", - s."name", - s."external_id", - s."giga_id_school", + SELECT DISTINCT schools_school."id", + schools_school."name", + schools_school."external_id", + schools_school."giga_id_school", CASE WHEN srr."rt_registered" = True THEN true ELSE false END AS is_data_synced, - s."admin1_id", + schools_school."admin1_id", adm1_metadata."name" AS admin1_name, adm1_metadata."giga_id_admin" AS admin1_code, adm1_metadata."description_ui_label" AS admin1_description_ui_label, - s."admin2_id", + schools_school."admin2_id", adm2_metadata."name" AS admin2_name, adm2_metadata."giga_id_admin" AS admin2_code, adm2_metadata."description_ui_label" AS admin2_description_ui_label, - s."country_id", + schools_school."country_id", c."name" AS country_name, - ST_AsGeoJSON(ST_Transform(s."geopoint", 4326)) AS geopoint, - s."environment", - s."education_level", + ST_AsGeoJSON(ST_Transform(schools_school."geopoint", 4326)) AS geopoint, + schools_school."environment", + schools_school."education_level", ROUND(AVG(sds."{col_name}"::numeric), 2) AS "live_avg", - CASE WHEN s.connectivity_status IN ('good', 'moderate') THEN 'connected' - WHEN s.connectivity_status = 'no' THEN 'not_connected' ELSE 'unknown' END as connectivity_status, + CASE WHEN schools_school.connectivity_status IN ('good', 'moderate') THEN 'connected' + WHEN schools_school.connectivity_status = 'no' THEN 'not_connected' ELSE 'unknown' END as connectivity_status, CASE WHEN srr."rt_registered" = True AND srr."rt_registration_date"::date <= '{end_date}' THEN true ELSE false END AS is_rt_connected, {case_conditions} - FROM "schools_school" s - INNER JOIN public.locations_country c ON c."id" = s."country_id" + FROM "schools_school" schools_school + INNER JOIN public.locations_country c ON c."id" = schools_school."country_id" AND c."deleted" IS NULL - AND s."deleted" IS NULL + AND schools_school."deleted" IS NULL LEFT JOIN public.locations_countryadminmetadata AS adm1_metadata - ON adm1_metadata."id" = s.admin1_id + ON adm1_metadata."id" = schools_school.admin1_id AND adm1_metadata."layer_name" = 'adm1' AND adm1_metadata."deleted" IS NULL LEFT JOIN public.locations_countryadminmetadata AS adm2_metadata - ON adm2_metadata."id" = s.admin2_id + ON adm2_metadata."id" = schools_school.admin2_id AND adm2_metadata."layer_name" = 'adm2' AND adm2_metadata."deleted" IS NULL LEFT JOIN "connection_statistics_schoolrealtimeregistration" AS srr - ON s."id" = srr."school_id" + ON schools_school."id" = srr."school_id" AND srr."deleted" IS NULL LEFT OUTER JOIN "connection_statistics_schooldailystatus" sds - ON s."id" = sds."school_id" + ON schools_school."id" = sds."school_id" AND sds."deleted" IS NULL AND (sds."date" BETWEEN '{start_date}' AND '{end_date}') AND sds."live_data_source" IN ({live_source_types}) - WHERE {school_condition} - GROUP BY s."id", srr."rt_registered", srr."rt_registration_date", + WHERE "schools_school"."id" IN ({ids}) + GROUP BY schools_school."id", srr."rt_registered", srr."rt_registration_date", adm1_metadata."name", adm1_metadata."description_ui_label", adm2_metadata."name", adm2_metadata."description_ui_label", c."name", adm1_metadata."giga_id_admin", adm2_metadata."giga_id_admin" - ORDER BY s."id" ASC + ORDER BY schools_school."id" ASC """ kwargs = copy.deepcopy(self.kwargs) - kwargs['school_condition'] = '' + kwargs['ids'] = ','.join(kwargs['school_ids']) kwargs['case_conditions'] = """ CASE @@ -986,30 +1114,21 @@ def get_school_view_info_query(self): ELSE 'unknown' END AS live_avg_connectivity """.format(**kwargs) - if len(kwargs.get('school_ids', [])) > 0: - kwargs['school_condition'] = 's."id" IN ({0})'.format( - ','.join([str(school_id) for school_id in kwargs['school_ids']]) - ) - return query.format(**kwargs) def get_school_view_statistics_info_query(self): query = """ SELECT sws.* - FROM "schools_school" s - INNER JOIN public.connection_statistics_schoolweeklystatus sws on sws."id" = s."last_weekly_status_id" - WHERE s."deleted" IS NULL AND sws."deleted" IS NULL {school_condition} - """ - - kwargs = copy.deepcopy(self.kwargs) - kwargs['school_condition'] = '' - - if len(kwargs.get('school_ids', [])) > 0: - kwargs['school_condition'] = 'AND s."id" IN ({0})'.format( - ','.join([str(school_id) for school_id in kwargs['school_ids']]) - ) + FROM "schools_school" + INNER JOIN connection_statistics_schoolweeklystatus sws + ON sws."id" = "schools_school"."last_weekly_status_id" + WHERE + "schools_school"."deleted" IS NULL + AND sws."deleted" IS NULL + AND "schools_school"."id" IN ({ids}) + """.format(ids=','.join(self.kwargs['school_ids'])) - return query.format(**kwargs) + return query def get_avg_query(self, **kwargs): query = """ @@ -1028,10 +1147,12 @@ def get_avg_query(self, **kwargs): AND t."live_data_source" IN ({live_source_types}) AND t."deleted" IS NULL ) + {school_weekly_join} WHERE ( {country_condition} {admin1_condition} {school_condition} + {school_weekly_condition} "connection_statistics_schoolrealtimeregistration"."rt_registered" = True AND "connection_statistics_schoolrealtimeregistration"."rt_registration_date"::date <= '{end_date}' AND t."{col_name}" IS NOT NULL) @@ -1044,6 +1165,8 @@ def get_avg_query(self, **kwargs): kwargs['school_condition'] = '' kwargs['school_selection'] = '' kwargs['school_group_by'] = '' + kwargs['school_weekly_join'] = '' + kwargs['school_weekly_condition'] = '' if len(kwargs.get('country_ids', [])) > 0: kwargs['country_condition'] = '"schools_school"."country_id" IN ({0}) AND'.format( @@ -1056,12 +1179,20 @@ def get_avg_query(self, **kwargs): ) if len(kwargs.get('school_ids', [])) > 0: - kwargs['school_condition'] = '"schools_school"."id" IN ({0}) AND'.format( - ','.join([str(school_id) for school_id in kwargs['school_ids']]) - ) + kwargs['school_condition'] = '"schools_school"."id" IN ({0}) AND '.format(','.join(kwargs['school_ids'])) kwargs['school_selection'] = '"schools_school"."id", ' kwargs['school_group_by'] = ', "schools_school"."id"' + if len(kwargs['school_filters']) > 0: + kwargs['school_condition'] += kwargs['school_filters'] + ' AND ' + + if len(kwargs['school_static_filters']) > 0: + kwargs['school_weekly_join'] = """ + LEFT OUTER JOIN "connection_statistics_schoolweeklystatus" + ON "schools_school"."last_weekly_status_id" = "connection_statistics_schoolweeklystatus"."id" + """ + kwargs['school_weekly_condition'] = kwargs['school_static_filters'] + ' AND ' + return query.format(**kwargs) def generate_graph_data(self): @@ -1133,13 +1264,15 @@ def generate_graph_data(self): def get_static_info_query(self, query_labels): query = """ SELECT {label_case_statements} - COUNT(DISTINCT CASE WHEN sws."{col_name}" IS NOT NULL THEN s."id" ELSE NULL END) AS "total_schools" - FROM schools_school AS s - LEFT JOIN connection_statistics_schoolweeklystatus sws ON s.last_weekly_status_id = sws.id - WHERE s."deleted" IS NULL AND sws."deleted" IS NULL + COUNT(DISTINCT CASE WHEN sws."{col_name}" IS NOT NULL THEN "schools_school"."id" ELSE NULL END) AS "total_schools" + FROM "schools_school" + {school_weekly_join} + LEFT JOIN connection_statistics_schoolweeklystatus sws ON "schools_school"."last_weekly_status_id" = sws."id" + WHERE "schools_school"."deleted" IS NULL AND sws."deleted" IS NULL {country_condition} {admin1_condition} {school_condition} + {school_weekly_condition} """ kwargs = copy.deepcopy(self.kwargs) @@ -1147,21 +1280,28 @@ def get_static_info_query(self, query_labels): kwargs['country_condition'] = '' kwargs['admin1_condition'] = '' kwargs['school_condition'] = '' + kwargs['school_weekly_join'] = '' + kwargs['school_weekly_condition'] = '' if len(kwargs.get('country_ids', [])) > 0: - kwargs['country_condition'] = ' AND s."country_id" IN ({0})'.format( + kwargs['country_condition'] = ' AND "schools_school"."country_id" IN ({0})'.format( ','.join([str(country_id) for country_id in kwargs['country_ids']]) ) if len(kwargs.get('admin1_ids', [])) > 0: - kwargs['admin1_condition'] = ' AND s."admin1_id" IN ({0})'.format( + kwargs['admin1_condition'] = ' AND "schools_school"."admin1_id" IN ({0})'.format( ','.join([str(admin1_id) for admin1_id in kwargs['admin1_ids']]) ) - if len(kwargs.get('school_ids', [])) > 0: - kwargs['school_condition'] = ' AND s."id" IN ({0})'.format( - ','.join([str(school_id) for school_id in kwargs['school_ids']]) - ) + if len(kwargs['school_filters']) > 0: + kwargs['school_condition'] = ' AND ' + kwargs['school_filters'] + + if len(kwargs['school_static_filters']) > 0: + kwargs['school_weekly_join'] = """ + LEFT OUTER JOIN "connection_statistics_schoolweeklystatus" + ON "schools_school"."last_weekly_status_id" = "connection_statistics_schoolweeklystatus"."id" + """ + kwargs['school_weekly_condition'] = ' AND ' + kwargs['school_static_filters'] legend_configs = kwargs['legend_configs'] label_cases = [] @@ -1182,17 +1322,15 @@ def get_static_info_query(self, query_labels): col_name=kwargs['col_name'], ) label_cases.append( - 'COUNT(DISTINCT CASE WHEN {sql} THEN s."id" ELSE NULL END) AS "{label}",'.format( + 'COUNT(DISTINCT CASE WHEN {sql} THEN schools_school."id" ELSE NULL END) AS "{label}",'.format( sql=sql_statement, - col_name=kwargs['col_name'], label=label, - value=','.join(["'" + str(v).lower() + "'" for v in values]) )) else: values_l.extend(values) if parameter_col_type == 'str': label_cases.append( - 'COUNT(DISTINCT CASE WHEN LOWER(sws."{col_name}") IN ({value}) THEN s."id" ELSE NULL END) ' + 'COUNT(DISTINCT CASE WHEN LOWER(sws."{col_name}") IN ({value}) THEN schools_school."id" ELSE NULL END) ' 'AS "{label}",'.format( col_name=kwargs['col_name'], label=label, @@ -1200,7 +1338,7 @@ def get_static_info_query(self, query_labels): )) elif parameter_col_type == 'int': label_cases.append( - 'COUNT(DISTINCT CASE WHEN sws."{col_name}" IN ({value}) THEN s."id" ELSE NULL END) ' + 'COUNT(DISTINCT CASE WHEN sws."{col_name}" IN ({value}) THEN schools_school."id" ELSE NULL END) ' 'AS "{label}",'.format( col_name=kwargs['col_name'], label=label, @@ -1209,7 +1347,7 @@ def get_static_info_query(self, query_labels): else: if is_sql_value: label_cases.append( - 'COUNT(DISTINCT CASE WHEN sws."{col_name}" IS NULL THEN s."id" ELSE NULL END) AS "{label}",'.format( + 'COUNT(DISTINCT CASE WHEN sws."{col_name}" IS NULL THEN schools_school."id" ELSE NULL END) AS "{label}",'.format( col_name=kwargs['col_name'], label=label, )) @@ -1217,7 +1355,7 @@ def get_static_info_query(self, query_labels): values = set(values_l) if parameter_col_type == 'str': label_cases.append( - 'COUNT(DISTINCT CASE WHEN LOWER(sws."{col_name}") NOT IN ({value}) THEN s."id" ELSE NULL END) ' + 'COUNT(DISTINCT CASE WHEN LOWER(sws."{col_name}") NOT IN ({value}) THEN schools_school."id" ELSE NULL END) ' 'AS "{label}",'.format( col_name=kwargs['col_name'], label=label, @@ -1225,7 +1363,7 @@ def get_static_info_query(self, query_labels): )) elif parameter_col_type == 'int': label_cases.append( - 'COUNT(DISTINCT CASE WHEN sws."{col_name}" NOT IN ({value}) THEN s."id" ELSE NULL END) ' + 'COUNT(DISTINCT CASE WHEN sws."{col_name}" NOT IN ({value}) THEN schools_school."id" ELSE NULL END) ' 'AS "{label}",'.format( col_name=kwargs['col_name'], label=label, @@ -1238,49 +1376,44 @@ def get_static_info_query(self, query_labels): def get_static_school_view_info_query(self): query = """ - SELECT s."id", - s."name", - s."external_id", - s."giga_id_school", - s."country_id", + SELECT schools_school."id", + schools_school."name", + schools_school."external_id", + schools_school."giga_id_school", + schools_school."country_id", c."name" AS country_name, - s."admin1_id", + schools_school."admin1_id", adm1_metadata."name" AS admin1_name, adm1_metadata."giga_id_admin" AS admin1_code, adm1_metadata."description_ui_label" AS admin1_description_ui_label, - s."admin2_id", + schools_school."admin2_id", adm2_metadata."name" AS admin2_name, adm2_metadata."giga_id_admin" AS admin2_code, adm2_metadata."description_ui_label" AS admin2_description_ui_label, - s."environment", - s."education_level", + schools_school."environment", + schools_school."education_level", sws."{col_name}" AS field_value, {label_case_statements} - ST_AsGeoJSON(ST_Transform(s."geopoint", 4326)) AS geopoint, - CASE WHEN s.connectivity_status IN ('good', 'moderate') THEN 'connected' - WHEN s.connectivity_status = 'no' THEN 'not_connected' ELSE 'unknown' END as connectivity_status - FROM schools_school AS s - INNER JOIN public.locations_country c ON c.id = s.country_id + ST_AsGeoJSON(ST_Transform(schools_school."geopoint", 4326)) AS geopoint, + CASE WHEN schools_school.connectivity_status IN ('good', 'moderate') THEN 'connected' + WHEN schools_school.connectivity_status = 'no' THEN 'not_connected' ELSE 'unknown' END as connectivity_status + FROM "schools_school" + INNER JOIN locations_country c ON c.id = schools_school.country_id AND c."deleted" IS NULL - LEFT JOIN public.locations_countryadminmetadata AS adm1_metadata - ON adm1_metadata."id" = s.admin1_id + LEFT JOIN locations_countryadminmetadata AS adm1_metadata + ON adm1_metadata."id" = schools_school.admin1_id AND adm1_metadata."layer_name" = 'adm1' AND adm1_metadata."deleted" IS NULL - LEFT JOIN public.locations_countryadminmetadata AS adm2_metadata - ON adm2_metadata."id" = s.admin2_id + LEFT JOIN locations_countryadminmetadata AS adm2_metadata + ON adm2_metadata."id" = schools_school.admin2_id AND adm2_metadata."layer_name" = 'adm2' AND adm2_metadata."deleted" IS NULL - LEFT JOIN connection_statistics_schoolweeklystatus sws ON s.last_weekly_status_id = sws.id - {school_condition} + LEFT JOIN connection_statistics_schoolweeklystatus sws ON schools_school.last_weekly_status_id = sws.id + WHERE "schools_school"."id" IN ({ids}) """ kwargs = copy.deepcopy(self.kwargs) - kwargs['school_condition'] = '' - - if len(kwargs.get('school_ids', [])) > 0: - kwargs['school_condition'] = ' WHERE s."id" IN ({0})'.format( - ','.join([str(school_id) for school_id in kwargs['school_ids']]) - ) + kwargs['ids'] = ','.join(kwargs['school_ids']) legend_configs = kwargs['legend_configs'] label_cases = [] @@ -1414,20 +1547,20 @@ def get(self, request, *args, **kwargs): response = info_panel_school_list else: + is_data_synced_qs = SchoolWeeklyStatus.objects.filter( + school__realtime_registration_status__rt_registered=True, + ) + + if len(self.kwargs['school_filters']) > 0: + is_data_synced_qs = is_data_synced_qs.extra(where=[self.kwargs['school_filters']]) + + if len(self.kwargs['school_static_filters']) > 0: + is_data_synced_qs = is_data_synced_qs.extra(where=[self.kwargs['school_static_filters']]) + if len(self.kwargs.get('admin1_ids', [])) > 0: - is_data_synced = SchoolWeeklyStatus.objects.filter( - school__admin1_id__in=self.kwargs['admin1_ids'], - school__realtime_registration_status__rt_registered=True, - ).exists() + is_data_synced_qs = is_data_synced_qs.filter(school__admin1_id__in=self.kwargs['admin1_ids']) elif len(self.kwargs.get('country_ids', [])) > 0: - is_data_synced = SchoolWeeklyStatus.objects.filter( - school__country_id__in=self.kwargs['country_ids'], - school__realtime_registration_status__rt_registered=True, - ).exists() - else: - is_data_synced = SchoolWeeklyStatus.objects.filter( - school__realtime_registration_status__rt_registered=True, - ).exists() + is_data_synced_qs = is_data_synced_qs.filter(school__country_id__in=self.kwargs['country_ids']) query_response = db_utilities.sql_to_response(self.get_info_query(), label=self.__class__.__name__)[ -1] @@ -1465,7 +1598,7 @@ def get(self, request, *args, **kwargs): 'no_internet': query_response['bad'], 'unknown': query_response['unknown'], }, - 'is_data_synced': is_data_synced, + 'is_data_synced': is_data_synced_qs.exists(), 'live_avg': live_avg, 'live_avg_connectivity': live_avg_connectivity, 'graph_data': graph_data, @@ -1538,6 +1671,7 @@ def get_live_map_query(self, env, request): END as connectivity_status FROM schools_school INNER JOIN bounds ON ST_Intersects("schools_school".geopoint, ST_Transform(bounds.geom, 4326)) + {school_weekly_join} LEFT JOIN ( SELECT "schools_school"."id" AS school_id, AVG(t."{col_name}") AS "field_avg" @@ -1550,6 +1684,7 @@ def get_live_map_query(self, env, request): AND (t."date" BETWEEN '{start_date}' AND '{end_date}') AND t."live_data_source" IN ({live_source_types}) ) + {school_weekly_join} WHERE ( "schools_school"."deleted" IS NULL AND "connection_statistics_schoolrealtimeregistration"."deleted" IS NULL @@ -1557,6 +1692,7 @@ def get_live_map_query(self, env, request): {country_condition} {admin1_condition} {school_condition} + {school_weekly_condition} AND "connection_statistics_schoolrealtimeregistration"."rt_registered" = True AND "connection_statistics_schoolrealtimeregistration"."rt_registration_date"::date <= '{end_date}') GROUP BY "schools_school"."id" @@ -1570,6 +1706,7 @@ def get_live_map_query(self, env, request): {country_outer_condition} {admin1_outer_condition} {school_outer_condition} + {school_weekly_condition} {random_order} {limit_condition} ) @@ -1586,6 +1723,9 @@ def get_live_map_query(self, env, request): kwargs['admin1_outer_condition'] = '' kwargs['school_outer_condition'] = '' + kwargs['school_weekly_join'] = '' + kwargs['school_weekly_condition'] = '' + kwargs['env'] = self.envelope_to_bounds_sql(env) kwargs['limit_condition'] = '' @@ -1638,6 +1778,17 @@ def get_live_map_query(self, env, request): ','.join([str(school_id) for school_id in kwargs['school_ids']]) ) + if len(kwargs['school_filters']) > 0: + kwargs['school_condition'] += ' AND ' + kwargs['school_filters'] + kwargs['school_outer_condition'] += ' AND ' + kwargs['school_filters'] + + if len(kwargs['school_static_filters']) > 0: + kwargs['school_weekly_join'] = """ + LEFT OUTER JOIN "connection_statistics_schoolweeklystatus" + ON "schools_school"."last_weekly_status_id" = "connection_statistics_schoolweeklystatus"."id" + """ + kwargs['school_weekly_condition'] = ' AND ' + kwargs['school_static_filters'] + if add_random_condition: kwargs['limit_condition'] = 'LIMIT ' + request.query_params.get('limit', '50000') kwargs['random_order'] = 'ORDER BY random()' if int(request.query_params.get('z', '0')) == 2 else '' @@ -1657,22 +1808,24 @@ def get_static_map_query(self, env, request): {env}::box2d AS b2d ), mvtgeom AS ( - SELECT DISTINCT ST_AsMVTGeom(ST_Transform(s.geopoint, 3857), bounds.b2d) AS geom, - s.id, + SELECT DISTINCT ST_AsMVTGeom(ST_Transform(schools_school.geopoint, 3857), bounds.b2d) AS geom, + schools_school.id, sws."{col_name}" AS field_value, - CASE WHEN s.connectivity_status IN ('good', 'moderate') THEN 'connected' - WHEN s.connectivity_status = 'no' THEN 'not_connected' + CASE WHEN schools_school.connectivity_status IN ('good', 'moderate') THEN 'connected' + WHEN schools_school.connectivity_status = 'no' THEN 'not_connected' ELSE 'unknown' END as connectivity_status, {label_case_statements} - FROM schools_school s - INNER JOIN bounds ON ST_Intersects(s.geopoint, ST_Transform(bounds.geom, 4326)) - LEFT JOIN connection_statistics_schoolweeklystatus sws ON s.last_weekly_status_id = sws.id - WHERE s."deleted" IS NULL + FROM schools_school + INNER JOIN bounds ON ST_Intersects(schools_school.geopoint, ST_Transform(bounds.geom, 4326)) + {school_weekly_join} + LEFT JOIN connection_statistics_schoolweeklystatus sws ON schools_school.last_weekly_status_id = sws.id + WHERE schools_school."deleted" IS NULL AND sws."deleted" IS NULL {country_condition} {admin1_condition} {school_condition} + {school_weekly_condition} {random_order} {limit_condition} ) @@ -1685,6 +1838,9 @@ def get_static_map_query(self, env, request): kwargs['admin1_condition'] = '' kwargs['school_condition'] = '' + kwargs['school_weekly_join'] = '' + kwargs['school_weekly_condition'] = '' + kwargs['env'] = self.envelope_to_bounds_sql(env) kwargs['limit_condition'] = '' @@ -1694,22 +1850,32 @@ def get_static_map_query(self, env, request): if len(kwargs.get('country_ids', [])) > 0: add_random_condition = False - kwargs['country_condition'] = 'AND s."country_id" IN ({0})'.format( + kwargs['country_condition'] = 'AND schools_school."country_id" IN ({0})'.format( ','.join([str(country_id) for country_id in kwargs['country_ids']]) ) if len(kwargs.get('admin1_ids', [])) > 0: add_random_condition = False - kwargs['admin1_condition'] = 'AND s."admin1_id" IN ({0})'.format( + kwargs['admin1_condition'] = 'AND schools_school."admin1_id" IN ({0})'.format( ','.join([str(admin1_id) for admin1_id in kwargs['admin1_ids']]) ) if len(kwargs.get('school_ids', [])) > 0: add_random_condition = False - kwargs['school_condition'] = 'AND s."id" IN ({0})'.format( + kwargs['school_condition'] = 'AND schools_school."id" IN ({0})'.format( ','.join([str(school_id) for school_id in kwargs['school_ids']]) ) + if len(kwargs['school_filters']) > 0: + kwargs['school_condition'] += ' AND ' + kwargs['school_filters'] + + if len(kwargs['school_static_filters']) > 0: + kwargs['school_weekly_join'] = """ + LEFT OUTER JOIN "connection_statistics_schoolweeklystatus" + ON "schools_school"."last_weekly_status_id" = "connection_statistics_schoolweeklystatus"."id" + """ + kwargs['school_weekly_condition'] = ' AND ' + kwargs['school_static_filters'] + legend_configs = kwargs['legend_configs'] label_cases = [] values_l = [] @@ -1779,7 +1945,7 @@ def get(self, request, *args, **kwargs): base_benchmark = str(parameter_col.get('base_benchmark', 1)) self.update_kwargs(country_ids, data_layer_instance) - benchmark_value, benchmark_unit = self.get_benchmark_value(data_layer_instance) + benchmark_value, _ = self.get_benchmark_value(data_layer_instance) if data_layer_instance.type == accounts_models.DataLayer.LAYER_TYPE_LIVE: self.kwargs.update({ @@ -1803,7 +1969,7 @@ def get(self, request, *args, **kwargs): try: return self.generate_tile(request) except Exception as ex: - print('Exception occurred for school connectivity tiles endpoint: {}'.format(ex)) + logger.error('Exception occurred for school connectivity tiles endpoint: {}'.format(ex)) return Response({'error': 'An error occurred while processing the request'}, status=500) @@ -1960,5 +2126,5 @@ def get(self, request, *args, **kwargs): try: return self.generate_tile(request) except Exception as ex: - print('Exception occurred for school connectivity tiles endpoint: {0}'.format(ex)) + logger.error('Exception occurred for school connectivity tiles endpoint: {0}'.format(ex)) return Response({'error': 'An error occurred while processing the request'}, status=500) diff --git a/proco/accounts/api_urls.py b/proco/accounts/api_urls.py index eafe9cd..8041c8c 100644 --- a/proco/accounts/api_urls.py +++ b/proco/accounts/api_urls.py @@ -30,6 +30,7 @@ path('invalidate-cache/', api.InvalidateCache.as_view(), name='admin-invalidate-cache'), path('app_configs/', api.AppStaticConfigurationsViewSet.as_view(), name='get-app-static-configurations'), + path('advanced_filters/', api.AdvancedFiltersViewSet.as_view(), name='list-advanced-filters'), path('data_sources/', api.DataSourceViewSet.as_view({ 'get': 'list', 'post': 'create', @@ -65,7 +66,7 @@ 'get': 'list', }), name='list-published-data-layers'), - path('recent_action_log/', api.LogActionViewSet.as_view({'get': 'list', }), name='recent_action_log'), + path('recent_action_log/', api.LogActionViewSet.as_view({'get': 'list', }), name='list-recent-action-log'), path('time-players/v2/', api.TimePlayerViewSet.as_view(), name='get-time-player-data-v2'), ] diff --git a/proco/accounts/config.py b/proco/accounts/config.py index c0c85f8..6fd07f0 100644 --- a/proco/accounts/config.py +++ b/proco/accounts/config.py @@ -1,9 +1,9 @@ class AppConfig(object): @property - def active_api_key_count_for_single_api_limit(self): - """API Key count limit""" - return 1 + def valid_name_pattern(self): + """Regex to validate names""" + return r'[a-zA-Z0-9-\' _()]*$' @property def public_api_key_generation_email_subject_format(self): diff --git a/proco/accounts/exceptions.py b/proco/accounts/exceptions.py index b5ed4f2..230eb91 100644 --- a/proco/accounts/exceptions.py +++ b/proco/accounts/exceptions.py @@ -224,9 +224,15 @@ class InvalidDataLayerNameError(BaseInvalidValidationError): code = 'invalid_data_layer_name' -class DuplicateDataLayerNameError(BaseInvalidValidationError): - message = _("Data Layer with name '{name}' already exists.") - code = 'duplicate_data_layer_name' +class InvalidDataLayerCodeError(BaseInvalidValidationError): + message = _('Invalid Data Layer code.') + description = _('Provide valid data layer code') + code = 'invalid_data_layer_code' + + +class DuplicateDataLayerCodeError(BaseInvalidValidationError): + message = _("Data Layer with code '{code}' already exists.") + code = 'duplicate_data_layer_code' class InvalidCountryNameOrCodeError(BaseInvalidValidationError): diff --git a/proco/accounts/migrations/0014_added_data_layer_code_field.py b/proco/accounts/migrations/0014_added_data_layer_code_field.py new file mode 100644 index 0000000..d493375 --- /dev/null +++ b/proco/accounts/migrations/0014_added_data_layer_code_field.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.28 on 2024-06-03 10:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0013_added_indexes_on_fields'), + ] + + operations = [ + migrations.AddField( + model_name='datalayer', + name='code', + field=models.CharField(db_index=True, default='UNKNOWN', max_length=255, verbose_name='Layer Code'), + ), + migrations.AddField( + model_name='historicaldatalayer', + name='code', + field=models.CharField(db_index=True, default='UNKNOWN', max_length=255, verbose_name='Layer Code'), + ), + ] diff --git a/proco/accounts/migrations/0015_deleted_unused_historical_models.py b/proco/accounts/migrations/0015_deleted_unused_historical_models.py new file mode 100644 index 0000000..7843b9d --- /dev/null +++ b/proco/accounts/migrations/0015_deleted_unused_historical_models.py @@ -0,0 +1,62 @@ +# Generated by Django 2.2.28 on 2024-07-09 08:43 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0014_added_data_layer_code_field'), + ] + + operations = [ + migrations.RemoveField( + model_name='historicaldatalayercountryrelationship', + name='country', + ), + migrations.RemoveField( + model_name='historicaldatalayercountryrelationship', + name='created_by', + ), + migrations.RemoveField( + model_name='historicaldatalayercountryrelationship', + name='data_layer', + ), + migrations.RemoveField( + model_name='historicaldatalayercountryrelationship', + name='history_user', + ), + migrations.RemoveField( + model_name='historicaldatalayercountryrelationship', + name='last_modified_by', + ), + migrations.RemoveField( + model_name='historicaldatalayerdatasourcerelationship', + name='created_by', + ), + migrations.RemoveField( + model_name='historicaldatalayerdatasourcerelationship', + name='data_layer', + ), + migrations.RemoveField( + model_name='historicaldatalayerdatasourcerelationship', + name='data_source', + ), + migrations.RemoveField( + model_name='historicaldatalayerdatasourcerelationship', + name='history_user', + ), + migrations.RemoveField( + model_name='historicaldatalayerdatasourcerelationship', + name='last_modified_by', + ), + migrations.DeleteModel( + name='HistoricalAPIKeyCountryRelationship', + ), + migrations.DeleteModel( + name='HistoricalDataLayerCountryRelationship', + ), + migrations.DeleteModel( + name='HistoricalDataLayerDataSourceRelationship', + ), + ] diff --git a/proco/accounts/models.py b/proco/accounts/models.py index 8d01fc4..0adb520 100644 --- a/proco/accounts/models.py +++ b/proco/accounts/models.py @@ -122,7 +122,7 @@ class Meta: ordering = ['last_modified_at'] -class APIKeyCountryRelationship(core_models.BaseModel): +class APIKeyCountryRelationship(core_models.BaseModelMixin): """ APIKeyCountryRelationship This model is used to store the Api Key and Country relationship. @@ -314,6 +314,14 @@ class DataLayer(core_models.BaseModel): icon = models.TextField(null=True, blank=True) # Unique + code = models.CharField( + max_length=255, + null=False, + verbose_name='Layer Code', + default='UNKNOWN', + db_index=True, + ) + name = models.CharField( max_length=255, null=False, @@ -360,8 +368,12 @@ class DataLayer(core_models.BaseModel): class Meta: ordering = ['last_modified_at'] + def save(self, **kwargs): + self.code = str(self.code).upper() + super().save(**kwargs) + -class DataLayerDataSourceRelationship(core_models.BaseModel): +class DataLayerDataSourceRelationship(core_models.BaseModelMixin): """ DataLayerDataSourceRelationship This model is used to store the Data Layer and Data Source relationship. @@ -376,7 +388,7 @@ class Meta: ordering = ['last_modified_at'] -class DataLayerCountryRelationship(core_models.BaseModel): +class DataLayerCountryRelationship(core_models.BaseModelMixin): """ DataLayerCountryRelationship This model is used to store the Data Layer and Country relationship. diff --git a/proco/accounts/serializers.py b/proco/accounts/serializers.py index 0b6629f..55739bb 100644 --- a/proco/accounts/serializers.py +++ b/proco/accounts/serializers.py @@ -128,15 +128,6 @@ def get_date_range_filter_applicable(self, api_instance): return False def apply_api_key_filters(self, filters): - """ - filters = { - 'country_id': '144', - 'start_date': '22-09-2023', - 'end_date': '28-09-2023', - 'is_weekly': 'true', - 'is_export': 'true', - } - """ return filters if isinstance(filters, dict) > 0 else {} def get_download_url(self, api_instance): @@ -157,8 +148,10 @@ def get_download_url(self, api_instance): def get_report_title(self, api_instance): report_file_name = api_instance.report_title - if (api_instance.category == accounts_models.API.API_CATEGORY_PUBLIC and - core_utilities.is_blank_string(report_file_name)): + if ( + api_instance.category == accounts_models.API.API_CATEGORY_PUBLIC and + core_utilities.is_blank_string(report_file_name) + ): report_file_name = str('_'.join([api_instance.name, api_instance.category, '{dt}'])) return report_file_name.format( @@ -361,9 +354,11 @@ def _get_status_by_api_category(self): # If API key is created for a Public API, then update status as APPROVED # If API key is created by Admin/Superuser, then also mark it as APPROVED - if api_instance and api_instance.category == accounts_models.API.API_CATEGORY_PUBLIC: - return accounts_models.APIKey.APPROVED - elif core_utilities.is_superuser(request_user): + if ( + (api_instance and api_instance.category == accounts_models.API.API_CATEGORY_PUBLIC) or + core_utilities.is_superuser(request_user) or + request_user.permissions.get(auth_models.RolePermission.CAN_APPROVE_REJECT_API_KEY, False) + ): return accounts_models.APIKey.APPROVED return accounts_models.APIKey.INITIATED @@ -648,7 +643,7 @@ def _validate_has_active_extension_request(self, api_key_instance): ) if has_active_request: message_kwargs = { - 'msg': f'Invalid API Key Extension Request as an active request already logged' + 'msg': 'Invalid API Key Extension Request as an active request already logged' } raise accounts_exceptions.InvalidAPIKeyExtensionError(message_kwargs=message_kwargs) @@ -665,7 +660,7 @@ def validate_extension_valid_to(self, extension_valid_to): ): return extension_valid_to message_kwargs = { - 'msg': f'Invalid API Key Extension Request Date as only 365 days extension is allowed from the current date' + 'msg': 'Invalid API Key Extension Request Date as only 365 days extension is allowed from the current date' } raise accounts_exceptions.InvalidAPIKeyExtensionError(message_kwargs=message_kwargs) @@ -808,8 +803,6 @@ def validate_recipient(self, recipients): for email in recipients: validate_email(email) return recipients - - print('Invalid Email Id email: {0}'.format(str(recipients))) raise accounts_exceptions.InvalidEmailId() # For SMS notification elif message_type == accounts_models.Message.TYPE_SMS: @@ -821,8 +814,6 @@ def validate_recipient(self, recipients): ): raise accounts_exceptions.InvalidPhoneNumberError() return recipients - - print('Invalid phone nos: {0}'.format(str(recipients))) raise accounts_exceptions.InvalidPhoneNumberError() elif message_type == accounts_models.Message.TYPE_NOTIFICATION: if isinstance(recipients, list): @@ -834,8 +825,6 @@ def validate_recipient(self, recipients): ): raise accounts_exceptions.InvalidUserIdError() return recipients - - print('Invalid user ids: {0}'.format(str(recipients))) raise accounts_exceptions.InvalidPhoneNumberError() return recipients @@ -947,22 +936,12 @@ class BaseDataSourceCRUDSerializer(serializers.ModelSerializer): column_config = serializers.JSONField() def validate_name(self, name): - if re.match(r'[a-zA-Z0-9-\' _()]*$', name): + if re.match(account_config.valid_name_pattern, name): if accounts_models.DataSource.objects.filter(name=name).exists(): raise accounts_exceptions.DuplicateDataSourceNameError(message_kwargs={'name': name}) return name raise accounts_exceptions.InvalidDataSourceNameError() - # def validate_request_config(self, request_config): - # if isinstance(request_config, dict): - # if ( - # not core_utilities.is_blank_string(request_config.get('url', None)) and - # not core_utilities.is_blank_string(request_config.get('method', None)) and - # request_config.get('method').lower() in ['get', 'post'] - # ): - # return request_config - # raise accounts_exceptions.InvalidDataSourceRequestConfigError() - def validate_column_config(self, column_config): if isinstance(column_config, dict) and len(column_config) > 0: column_config = [column_config] @@ -1052,22 +1031,25 @@ class Meta: } def validate_name(self, name): - if re.match(r'[a-zA-Z0-9-\' _()]*$', name): + if re.match(account_config.valid_name_pattern, name): if name != self.instance.name and accounts_models.DataSource.objects.filter(name=name).exists(): raise accounts_exceptions.DuplicateDataSourceNameError(message_kwargs={'name': name}) return name raise accounts_exceptions.DuplicateDataSourceNameError(message_kwargs={'name': name}) def validate_status(self, status): - if status in [accounts_models.DataSource.DATA_SOURCE_STATUS_DRAFT, - accounts_models.DataSource.DATA_SOURCE_STATUS_READY_TO_PUBLISH]: - if self.instance.status in [accounts_models.DataSource.DATA_SOURCE_STATUS_DRAFT, - accounts_models.DataSource.DATA_SOURCE_STATUS_READY_TO_PUBLISH]: - return status - elif status == accounts_models.DataSource.DATA_SOURCE_STATUS_DISABLED: - if self.instance.status == accounts_models.DataSource.DATA_SOURCE_STATUS_PUBLISHED: - return status - + if ( + ( + status in [accounts_models.DataSource.DATA_SOURCE_STATUS_DRAFT, + accounts_models.DataSource.DATA_SOURCE_STATUS_READY_TO_PUBLISH] and + self.instance.status in [accounts_models.DataSource.DATA_SOURCE_STATUS_DRAFT, + accounts_models.DataSource.DATA_SOURCE_STATUS_READY_TO_PUBLISH] + ) or + ( + status == accounts_models.DataSource.DATA_SOURCE_STATUS_DISABLED and + self.instance.status == accounts_models.DataSource.DATA_SOURCE_STATUS_PUBLISHED) + ): + return status raise accounts_exceptions.InvalidDataSourceStatusUpdateError() @@ -1140,6 +1122,7 @@ class Meta: read_only_fields = fields = ( 'id', 'icon', + 'code', 'name', 'description', 'version', @@ -1262,12 +1245,22 @@ def create(self, validated_data): class BaseDataLayerCRUDSerializer(serializers.ModelSerializer): def validate_name(self, name): - if re.match(r'[a-zA-Z0-9-\' _()]*$', name): - if accounts_models.DataLayer.objects.filter(name=name).exists(): - raise accounts_exceptions.DuplicateDataLayerNameError(message_kwargs={'name': name}) + if re.match(account_config.valid_name_pattern, name): return name raise accounts_exceptions.InvalidDataLayerNameError() + def validate_code(self, code): + if re.match(r'[A-Z0-9-\' _]*$', code): + # If its Existing layer, then code should match. Else raise error + # If its new Layer, then code should be unique. Else raise error + if ( + (self.instance and code != self.instance.code) or + (not self.instance and accounts_models.DataLayer.objects.filter(code=code).exists()) + ): + raise accounts_exceptions.DuplicateDataLayerCodeError(message_kwargs={'code': code}) + return code + raise accounts_exceptions.InvalidDataLayerCodeError() + def validate_applicable_countries(self, applicable_countries): """ Validate if the given countries are present in our proco DB @@ -1295,7 +1288,8 @@ def validate_applicable_countries(self, applicable_countries): code_lower=Lower('code'), name_lower=Lower('name') ).filter( - Q(name_lower=country_name_or_code.lower()) | Q(code_lower=country_name_or_code.lower()) + Q(name_lower=str(country_name_or_code).lower()) | Q( + code_lower=str(country_name_or_code).lower()) ).last() else: country_instance = locations_models.Country.objects.filter(id=country_data).last() @@ -1374,10 +1368,6 @@ class CreateDataLayersSerializer(BaseDataLayerCRUDSerializer): legend_configs = serializers.JSONField(required=False) data_sources_list = serializers.JSONField() - # serializers.PrimaryKeyRelatedField( - # many=True, - # queryset=accounts_models.DataSource.objects.all() - # ) data_source_column = serializers.JSONField() class Meta: @@ -1391,6 +1381,7 @@ class Meta: fields = read_only_fields + ( 'icon', + 'code', 'name', 'description', 'version', @@ -1407,6 +1398,7 @@ class Meta: extra_kwargs = { 'icon': {'required': True}, + # 'code': {'required': True}, 'name': {'required': True}, 'type': {'required': True}, 'data_sources_list': {'required': True}, @@ -1419,6 +1411,11 @@ def validate_status(self, status): return status raise accounts_exceptions.InvalidDataLayerStatusError() + def to_internal_value(self, data): + if not data.get('code') and data.get('name'): + data['code'] = core_utilities.normalize_str(str(data.get('name'))).upper() + return super().to_internal_value(data) + def create(self, validated_data): """ create @@ -1484,6 +1481,7 @@ class Meta: ) fields = read_only_fields + ( + 'code', 'icon', 'name', 'description', @@ -1503,13 +1501,6 @@ class Meta: 'status': {'required': True}, } - def validate_name(self, name): - if re.match(r'[a-zA-Z0-9-\' _()]*$', name): - if name != self.instance.name and accounts_models.DataLayer.objects.filter(name=name).exists(): - raise accounts_exceptions.DuplicateDataLayerNameError(message_kwargs={'name': name}) - return name - raise accounts_exceptions.InvalidDataLayerNameError() - def validate_status(self, status): if status in [accounts_models.DataLayer.LAYER_STATUS_DRAFT, accounts_models.DataLayer.LAYER_STATUS_READY_TO_PUBLISH]: @@ -1545,7 +1536,6 @@ def _validate_user(self, instance): if request_user == instance.created_by: return True - user_is_publisher = len(get_user_emails_for_permissions( [auth_models.RolePermission.CAN_PUBLISH_DATA_LAYER], ids_to_filter=[request_user.id] @@ -1623,6 +1613,7 @@ class Meta: 'created', 'last_modified_at', 'icon', + 'code', 'name', 'description', 'version', @@ -1719,18 +1710,15 @@ def get_object_data(self, instance): instance.object_id, ) return self.make_url(request.build_absolute_uri(url_name)) - return def get_section_type(self, instance): if instance.content_type: return apps.get_model(instance.content_type.app_label, instance.content_type.model)._meta.verbose_name.title() - return def get_content_type(self, instance): if instance.content_type: return instance.content_type.app_label - return def get_action_flag(self, instance): if instance.action_flag == 1: diff --git a/proco/accounts/tests/test_api.py b/proco/accounts/tests/test_api.py index 68f3d70..56854fa 100755 --- a/proco/accounts/tests/test_api.py +++ b/proco/accounts/tests/test_api.py @@ -1,5 +1,6 @@ import os from collections import OrderedDict +from datetime import timedelta from django.conf import settings from django.core.cache import cache @@ -9,23 +10,27 @@ from rest_framework import status from proco.accounts import models as accounts_models +from proco.accounts.tests import test_utils as accounts_test_utilities +from proco.core import utils as core_utilities from proco.custom_auth.tests import test_utils as test_utilities from proco.locations.tests.factories import CountryFactory +from proco.schools.tests.factories import SchoolFactory from proco.utils.tests import TestAPIViewSetMixin def accounts_url(url_params, query_param, view_name='list-or-create-api-keys'): url = reverse('accounts:' + view_name, args=url_params) - view_info = resolve(url).func + view = resolve(url) + view_info = view.func if len(query_param) > 0: query_params = '?' + '&'.join([key + '=' + str(val) for key, val in query_param.items()]) url += query_params - return url, view_info + return url, view, view_info class APIsApiTestCase(TestAPIViewSetMixin, TestCase): - databases = ['default'] + databases = ['default', ] @classmethod def setUpTestData(cls): @@ -43,7 +48,7 @@ def setUp(self): super().setUp() def test_list_apis_all(self): - url, view = accounts_url((), {}, view_name='list-apis') + url, _, view = accounts_url((), {}, view_name='list-apis') response = self.forced_auth_req('get', url, user=self.user, view=view) @@ -56,7 +61,7 @@ def test_list_apis_all(self): self.assertEqual(len(response_data['results']), 5) def test_list_apis_filter_on_code(self): - url, view = accounts_url((), { + url, _, view = accounts_url((), { 'code': 'DAILY_CHECK_APP' }, view_name='list-apis') @@ -71,7 +76,7 @@ def test_list_apis_filter_on_code(self): self.assertEqual(len(response_data['results']), 1) def test_list_apis_filter_on_category_public(self): - url, view = accounts_url((), { + url, _, view = accounts_url((), { 'category': 'public' }, view_name='list-apis') @@ -86,7 +91,7 @@ def test_list_apis_filter_on_category_public(self): self.assertEqual(len(response_data['results']), 2) def test_list_apis_filter_on_category_private(self): - url, view = accounts_url((), { + url, _, view = accounts_url((), { 'category': 'private' }, view_name='list-apis') @@ -102,7 +107,7 @@ def test_list_apis_filter_on_category_private(self): class APIKeysApiTestCase(TestAPIViewSetMixin, TestCase): - databases = ['default'] + databases = ['default', ] @classmethod def setUpTestData(cls): @@ -116,14 +121,14 @@ def setUpTestData(cls): cls.admin_user = test_utilities.setup_admin_user_by_role() cls.read_only_user = test_utilities.setup_read_only_user_by_role() - cls.country_one = CountryFactory() + cls.country = CountryFactory() def setUp(self): cache.clear() super().setUp() def test_list_api_keys_all_for_logged_in_user(self): - url, view = accounts_url((), {}) + url, _, view = accounts_url((), {}) response = self.forced_auth_req('get', url, user=self.admin_user, view=view) @@ -136,7 +141,7 @@ def test_list_api_keys_all_for_logged_in_user(self): self.assertEqual(len(response_data['results']), 0) def test_list_api_keys_all_for_read_only_user(self): - url, view = accounts_url((), {}) + url, _, view = accounts_url((), {}) response = self.forced_auth_req('get', url, user=self.read_only_user, view=view) @@ -149,14 +154,14 @@ def test_list_api_keys_all_for_read_only_user(self): self.assertEqual(len(response_data['results']), 0) def test_list_api_keys_all_for_non_logged_in_user(self): - url, view = accounts_url((), {}) + url, _, view = accounts_url((), {}) response = self.forced_auth_req('get', url, user=None, view=view) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test_create_api_keys_for_admin_for_public_api(self): - url, view = accounts_url((), {}) + url, _, view = accounts_url((), {}) response = self.forced_auth_req( 'post', @@ -171,7 +176,7 @@ def test_create_api_keys_for_admin_for_public_api(self): self.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_validate_api_keys_for_admin_for_public_api(self): - url, view = accounts_url((), {}) + url, _, view = accounts_url((), {}) response = self.forced_auth_req( 'post', @@ -188,7 +193,7 @@ def test_validate_api_keys_for_admin_for_public_api(self): response_data = response.data api_key = response_data['api_key'] - url, view = accounts_url((), {}, view_name='validate-an-api-key') + url, _, view = accounts_url((), {}, view_name='validate-an-api-key') get_response = self.forced_auth_req( 'put', @@ -202,8 +207,40 @@ def test_validate_api_keys_for_admin_for_public_api(self): self.assertEqual(get_response.status_code, status.HTTP_200_OK) + def test_validate_invalid_api_key_for_admin_for_public_api(self): + url, _, view = accounts_url((), {}) + + response = self.forced_auth_req( + 'post', + url, + user=self.admin_user, + view=view, + data={ + 'api': accounts_models.API.objects.get(code='COUNTRY').id, + } + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response_data = response.data + api_key = response_data['api_key'] + + url, _, view = accounts_url((), {}, view_name='validate-an-api-key') + + get_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'api_id': accounts_models.API.objects.get(code='COUNTRY').id, + 'api_key': api_key + 'abc', + } + ) + + self.assertEqual(get_response.status_code, status.HTTP_404_NOT_FOUND) + def test_create_api_keys_for_admin_for_private_api(self): - url, view = accounts_url((), {}) + url, _, view = accounts_url((), {}) response = self.forced_auth_req( 'post', @@ -212,14 +249,14 @@ def test_create_api_keys_for_admin_for_private_api(self): view=view, data={ 'api': accounts_models.API.objects.get(code='DAILY_CHECK_APP').id, - 'active_countries_list': [self.country_one.id, ] + 'active_countries_list': [self.country.id, ] } ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_create_api_keys_for_read_only_user_for_public_api(self): - url, view = accounts_url((), {}) + url, _, view = accounts_url((), {}) response = self.forced_auth_req( 'post', @@ -228,14 +265,114 @@ def test_create_api_keys_for_read_only_user_for_public_api(self): view=view, data={ 'api': accounts_models.API.objects.get(code='COUNTRY').id, - 'active_countries_list': [self.country_one.id, ] + 'active_countries_list': [self.country.id, ] } ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_create_api_keys_for_read_only_user_for_private_api(self): - url, view = accounts_url((), {}) + url, _, view = accounts_url((), {}) + + response = self.forced_auth_req( + 'post', + url, + user=self.read_only_user, + view=view, + data={ + 'api': accounts_models.API.objects.get(code='DAILY_CHECK_APP').id, + 'active_countries_list': [self.country.id, ] + } + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + def test_approve_api_key_for_read_only_user_for_private_api_by_admin(self): + url, _, view = accounts_url((), {}) + + response = self.forced_auth_req( + 'post', + url, + user=self.read_only_user, + view=view, + data={ + 'api': accounts_models.API.objects.get(code='DAILY_CHECK_APP').id, + 'active_countries_list': [self.country.id, ] + } + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response_data = response.data + + api_key_id = response_data['id'] + + url, _, view = accounts_url((api_key_id,), {}, + view_name='update-and-delete-api-key') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.APIKey.APPROVED, + 'valid_to': core_utilities.get_current_datetime_object().date() + timedelta(days=30), + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + def test_create_api_key_extension_request_for_private_api(self): + url, _, view = accounts_url((), {}) + + response = self.forced_auth_req( + 'post', + url, + user=self.read_only_user, + view=view, + data={ + 'api': accounts_models.API.objects.get(code='DAILY_CHECK_APP').id, + 'active_countries_list': [self.country.id, ] + } + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response_data = response.data + + api_key_id = response_data['id'] + + url, _, view = accounts_url((api_key_id,), {}, + view_name='update-and-delete-api-key') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.APIKey.APPROVED, + 'valid_to': core_utilities.get_current_datetime_object().date() + timedelta(days=30), + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, _, view = accounts_url((api_key_id,), {}, + view_name='request-api-key-extension') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.read_only_user, + data={ + 'extension_valid_to': core_utilities.get_current_datetime_object().date() + timedelta(days=60), + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + def test_approve_api_key_extension_request_for_private_api_by_admin(self): + url, _, view = accounts_url((), {}) response = self.forced_auth_req( 'post', @@ -244,14 +381,62 @@ def test_create_api_keys_for_read_only_user_for_private_api(self): view=view, data={ 'api': accounts_models.API.objects.get(code='DAILY_CHECK_APP').id, - 'active_countries_list': [self.country_one.id, ] + 'active_countries_list': [self.country.id, ] } ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) + response_data = response.data + + api_key_id = response_data['id'] + + url, _, view = accounts_url((api_key_id,), {}, + view_name='update-and-delete-api-key') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.APIKey.APPROVED, + 'valid_to': core_utilities.get_current_datetime_object().date() + timedelta(days=30), + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, _, view = accounts_url((api_key_id,), {}, + view_name='request-api-key-extension') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.read_only_user, + data={ + 'extension_valid_to': core_utilities.get_current_datetime_object().date() + timedelta(days=60), + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, _, view = accounts_url((api_key_id,), {}, + view_name='update-and-delete-api-key') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'extension_status': accounts_models.APIKey.APPROVED, + 'extension_valid_to': core_utilities.get_current_datetime_object().date() + timedelta(days=100), + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + def test_delete_api_key_by_admin(self): - url, view = accounts_url((), {}) + url, _, view = accounts_url((), {}) response = self.forced_auth_req( 'post', @@ -269,8 +454,8 @@ def test_delete_api_key_by_admin(self): api_key_id = response_data['id'] - url, view = accounts_url((api_key_id,), {}, - view_name='update-and-delete-api-key') + url, _, view = accounts_url((api_key_id,), {}, + view_name='update-and-delete-api-key') delete_response = self.forced_auth_req( 'delete', @@ -281,7 +466,7 @@ def test_delete_api_key_by_admin(self): self.assertEqual(delete_response.status_code, status.HTTP_204_NO_CONTENT) def test_delete_api_key_by_read_only_user(self): - url, view = accounts_url((), {}) + url, _, view = accounts_url((), {}) response = self.forced_auth_req( 'post', @@ -299,8 +484,8 @@ def test_delete_api_key_by_read_only_user(self): api_key_id = response_data['id'] - url, view = accounts_url((api_key_id,), {}, - view_name='update-and-delete-api-key') + url, _, view = accounts_url((api_key_id,), {}, + view_name='update-and-delete-api-key') delete_response = self.forced_auth_req( 'delete', @@ -308,11 +493,11 @@ def test_delete_api_key_by_read_only_user(self): user=self.read_only_user, ) - self.assertEqual(delete_response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(delete_response.status_code, status.HTTP_204_NO_CONTENT) class NotificationsApiTestCase(TestAPIViewSetMixin, TestCase): - databases = ['default'] + databases = ['default', ] @classmethod def setUpTestData(cls): @@ -324,7 +509,7 @@ def setUp(self): super().setUp() def test_list_for_admin_user(self): - url, view = accounts_url((), {}, view_name='list-send-notifications') + url, _, view = accounts_url((), {}, view_name='list-send-notifications') response = self.forced_auth_req('get', url, user=self.admin_user, view=view) @@ -337,21 +522,21 @@ def test_list_for_admin_user(self): self.assertEqual(len(response_data['results']), 0) def test_list_for_real_only_user(self): - url, view = accounts_url((), {}, view_name='list-send-notifications') + url, _, view = accounts_url((), {}, view_name='list-send-notifications') response = self.forced_auth_req('get', url, user=self.read_only_user, view=view) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_list_for_non_logged_in_user(self): - url, view = accounts_url((), {}, view_name='list-send-notifications') + url, _, view = accounts_url((), {}, view_name='list-send-notifications') response = self.forced_auth_req('get', url, user=None, view=view) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test_create_notification_by_admin(self): - url, view = accounts_url((), {}, view_name='list-send-notifications') + url, _, view = accounts_url((), {}, view_name='list-send-notifications') response = self.forced_auth_req( 'post', @@ -369,7 +554,7 @@ def test_create_notification_by_admin(self): self.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_create_notification_by_admin_single_recipient(self): - url, view = accounts_url((), {}, view_name='list-send-notifications') + url, _, view = accounts_url((), {}, view_name='list-send-notifications') response = self.forced_auth_req( 'post', @@ -387,7 +572,7 @@ def test_create_notification_by_admin_single_recipient(self): self.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_create_notification_by_admin_invalid_recipient(self): - url, view = accounts_url((), {}, view_name='list-send-notifications') + url, _, view = accounts_url((), {}, view_name='list-send-notifications') response = self.forced_auth_req( 'post', @@ -406,12 +591,12 @@ def test_create_notification_by_admin_invalid_recipient(self): class AppStaticConfigurationsApiTestCase(TestAPIViewSetMixin, TestCase): - databases = ['default'] + databases = ['default', ] def test_get(self): - url, view = accounts_url((), {}, view_name='get-app-static-configurations') + url, _, view = accounts_url((), {}, view_name='get-app-static-configurations') - response = self.forced_auth_req('get', url, view=view) + response = self.forced_auth_req('get', url, _, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -422,21 +607,122 @@ def test_get(self): class TimePlayerApiTestCase(TestAPIViewSetMixin, TestCase): - databases = ['default'] + databases = ['default', ] + + @classmethod + def setUpTestData(cls): + args = ['--delete_data_sources', '--update_data_sources', '--update_data_layers'] + call_command('load_system_data_layers', *args) + + cls.admin_user = test_utilities.setup_admin_user_by_role() + cls.read_only_user = test_utilities.setup_read_only_user_by_role() def test_get_invalid_layer_id(self): - url, view = accounts_url((), { + url, _, view = accounts_url((), { 'layer_id': 123, 'country_id': 123, }, view_name='get-time-player-data-v2') - response = self.forced_auth_req('get', url, view=view) + response = self.forced_auth_req('get', url, _, view=view) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + def test_for_live_layer(self): + pcdc_data_source = accounts_models.DataSource.objects.filter( + data_source_type=accounts_models.DataSource.DATA_SOURCE_TYPE_DAILY_CHECK_APP, + ).first() + + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') + + response = self.forced_auth_req( + 'post', + url, + user=self.admin_user, + view=view, + data={ + 'icon': '', + 'name': 'Test data layer 3', + 'description': 'Test data layer 3 description', + 'version': '1.0.0', + 'type': accounts_models.DataLayer.LAYER_TYPE_LIVE, + 'data_sources_list': [pcdc_data_source.id, ], + 'data_source_column': pcdc_data_source.column_config[0], + 'global_benchmark': { + 'value': '20000000', + 'unit': 'bps', + 'convert_unit': 'mbps' + }, + 'is_reverse': False, + 'legend_configs': { + 'good': { + 'values': [], + 'labels': 'Good' + }, + 'moderate': { + 'values': [], + 'labels': 'Moderate' + }, + 'bad': { + 'values': [], + 'labels': 'Bad' + }, + 'unknown': { + 'values': [], + 'labels': 'Unknown' + } + } + } + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response_data = response.data + + layer_id = response_data['id'] + + url, _, view = accounts_url((layer_id,), {}, + view_name='update-or-delete-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_READY_TO_PUBLISH, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, _, view = accounts_url((layer_id,), {}, + view_name='publish-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_PUBLISHED, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, _, view = accounts_url((), { + 'layer_id': layer_id, + 'country_id': 123, + 'z': '2', + 'x': '1', + 'y': '2.mvt', + }, view_name='get-time-player-data-v2') + + response = self.forced_auth_req('get', url, _, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + class DataSourceApiTestCase(TestAPIViewSetMixin, TestCase): - databases = ['default'] + databases = ['default', ] @classmethod def setUpTestData(cls): @@ -450,7 +736,7 @@ def setUp(self): super().setUp() def test_list_data_sources_all(self): - url, view = accounts_url((), {}, view_name='list-or-create-data-sources') + url, _, view = accounts_url((), {}, view_name='list-or-create-data-sources') response = self.forced_auth_req('get', url, user=self.user, view=view) @@ -463,7 +749,7 @@ def test_list_data_sources_all(self): self.assertEqual(len(response_data['results']), 3) def test_list_data_sources_filter_on_status_published(self): - url, view = accounts_url((), { + url, _, view = accounts_url((), { 'status': 'PUBLISHED' }, view_name='list-or-create-data-sources') @@ -478,7 +764,7 @@ def test_list_data_sources_filter_on_status_published(self): self.assertEqual(len(response_data['results']), 3) def test_list_data_sources_filter_on_status_draft(self): - url, view = accounts_url((), { + url, _, view = accounts_url((), { 'status': 'DRAFT' }, view_name='list-or-create-data-sources') @@ -493,7 +779,7 @@ def test_list_data_sources_filter_on_status_draft(self): self.assertEqual(len(response_data['results']), 0) def test_list_data_sources_filter_on_status_published_without_auth(self): - url, view = accounts_url((), { + url, _, view = accounts_url((), { 'status': 'PUBLISHED' }, view_name='list-or-create-data-sources') @@ -502,7 +788,7 @@ def test_list_data_sources_filter_on_status_published_without_auth(self): self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test_delete_data_source_by_admin(self): - url, view = accounts_url((), {}, view_name='list-or-create-data-sources') + url, _, view = accounts_url((), {}, view_name='list-or-create-data-sources') response = self.forced_auth_req( 'post', @@ -526,8 +812,8 @@ def test_delete_data_source_by_admin(self): source_id = response_data['id'] - url, view = accounts_url((source_id,), {}, - view_name='update-or-delete-data-source') + url, _, view = accounts_url((source_id,), {}, + view_name='update-or-delete-data-source') delete_response = self.forced_auth_req( 'delete', @@ -538,7 +824,7 @@ def test_delete_data_source_by_admin(self): self.assertEqual(delete_response.status_code, status.HTTP_204_NO_CONTENT) def test_publish_data_source_by_admin(self): - url, view = accounts_url((), {}, view_name='list-or-create-data-sources') + url, _, view = accounts_url((), {}, view_name='list-or-create-data-sources') response = self.forced_auth_req( 'post', @@ -562,8 +848,8 @@ def test_publish_data_source_by_admin(self): source_id = response_data['id'] - url, view = accounts_url((source_id,), {}, - view_name='publish-data-source') + url, _, view = accounts_url((source_id,), {}, + view_name='publish-data-source') put_response = self.forced_auth_req( 'put', @@ -578,7 +864,7 @@ def test_publish_data_source_by_admin(self): class DataLayerApiTestCase(TestAPIViewSetMixin, TestCase): - databases = ['default'] + databases = ['default', ] @classmethod def setUpTestData(cls): @@ -593,7 +879,7 @@ def setUp(self): super().setUp() def test_list_data_layers_all(self): - url, view = accounts_url((), {}, view_name='list-or-create-data-layers') + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') response = self.forced_auth_req('get', url, user=self.admin_user, view=view) @@ -606,7 +892,7 @@ def test_list_data_layers_all(self): self.assertEqual(len(response_data['results']), 2) def test_list_data_layers_filter_on_status_published(self): - url, view = accounts_url((), { + url, _, view = accounts_url((), { 'status': 'PUBLISHED' }, view_name='list-or-create-data-layers') @@ -621,7 +907,7 @@ def test_list_data_layers_filter_on_status_published(self): self.assertEqual(len(response_data['results']), 2) def test_list_published_data_layers_for_admin(self): - url, view = accounts_url(('PUBLISHED',), { + url, _, view = accounts_url(('PUBLISHED',), { }, view_name='list-published-data-layers') response = self.forced_auth_req('get', url, user=self.admin_user, view=view) @@ -635,7 +921,7 @@ def test_list_published_data_layers_for_admin(self): self.assertEqual(len(response_data['results']), 2) def test_list_published_data_layers_without_auth(self): - url, view = accounts_url(('PUBLISHED',), { + url, _, view = accounts_url(('PUBLISHED',), { }, view_name='list-published-data-layers') response = self.forced_auth_req('get', url, user=None, view=view) @@ -649,7 +935,7 @@ def test_list_published_data_layers_without_auth(self): self.assertEqual(len(response_data['results']), 2) def test_list_published_data_layers_for_country(self): - url, view = accounts_url(('PUBLISHED',), { + url, _, view = accounts_url(('PUBLISHED',), { 'country_id': 123456789, }, view_name='list-published-data-layers') @@ -664,7 +950,7 @@ def test_list_published_data_layers_for_country(self): self.assertEqual(len(response_data['results']), 0) def test_list_data_layers_filter_on_status_draft(self): - url, view = accounts_url((), { + url, _, view = accounts_url((), { 'status': 'DRAFT' }, view_name='list-or-create-data-layers') @@ -679,7 +965,7 @@ def test_list_data_layers_filter_on_status_draft(self): self.assertEqual(len(response_data['results']), 0) def test_list_data_layers_filter_on_status_published_without_auth(self): - url, view = accounts_url((), { + url, _, view = accounts_url((), { 'status': 'PUBLISHED' }, view_name='list-or-create-data-layers') @@ -692,7 +978,7 @@ def test_create_data_layer_by_admin(self): data_source_type=accounts_models.DataSource.DATA_SOURCE_TYPE_DAILY_CHECK_APP, ).first() - url, view = accounts_url((), {}, view_name='list-or-create-data-layers') + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') response = self.forced_auth_req( 'post', @@ -707,6 +993,30 @@ def test_create_data_layer_by_admin(self): 'type': accounts_models.DataLayer.LAYER_TYPE_LIVE, 'data_sources_list': [pcdc_data_source.id, ], 'data_source_column': pcdc_data_source.column_config[0], + 'global_benchmark': { + 'value': '20000000', + 'unit': 'bps', + 'convert_unit': 'mbps' + }, + 'is_reverse': False, + 'legend_configs': { + 'good': { + 'values': [], + 'labels': 'Good' + }, + 'moderate': { + 'values': [], + 'labels': 'Moderate' + }, + 'bad': { + 'values': [], + 'labels': 'Bad' + }, + 'unknown': { + 'values': [], + 'labels': 'Unknown' + } + } } ) @@ -717,7 +1027,7 @@ def test_publish_in_draft_data_layer_by_admin(self): data_source_type=accounts_models.DataSource.DATA_SOURCE_TYPE_DAILY_CHECK_APP, ).first() - url, view = accounts_url((), {}, view_name='list-or-create-data-layers') + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') response = self.forced_auth_req( 'post', @@ -732,6 +1042,30 @@ def test_publish_in_draft_data_layer_by_admin(self): 'type': accounts_models.DataLayer.LAYER_TYPE_LIVE, 'data_sources_list': [pcdc_data_source.id, ], 'data_source_column': pcdc_data_source.column_config[0], + 'global_benchmark': { + 'value': '20000000', + 'unit': 'bps', + 'convert_unit': 'mbps' + }, + 'is_reverse': False, + 'legend_configs': { + 'good': { + 'values': [], + 'labels': 'Good' + }, + 'moderate': { + 'values': [], + 'labels': 'Moderate' + }, + 'bad': { + 'values': [], + 'labels': 'Bad' + }, + 'unknown': { + 'values': [], + 'labels': 'Unknown' + } + } } ) @@ -741,8 +1075,8 @@ def test_publish_in_draft_data_layer_by_admin(self): layer_id = response_data['id'] - url, view = accounts_url((layer_id,), {}, - view_name='publish-data-layer') + url, _, view = accounts_url((layer_id,), {}, + view_name='publish-data-layer') put_response = self.forced_auth_req( 'put', @@ -760,7 +1094,7 @@ def test_delete_in_draft_data_layer_by_admin(self): data_source_type=accounts_models.DataSource.DATA_SOURCE_TYPE_DAILY_CHECK_APP, ).first() - url, view = accounts_url((), {}, view_name='list-or-create-data-layers') + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') response = self.forced_auth_req( 'post', @@ -775,6 +1109,30 @@ def test_delete_in_draft_data_layer_by_admin(self): 'type': accounts_models.DataLayer.LAYER_TYPE_LIVE, 'data_sources_list': [pcdc_data_source.id, ], 'data_source_column': pcdc_data_source.column_config[0], + 'global_benchmark': { + 'value': '20000000', + 'unit': 'bps', + 'convert_unit': 'mbps' + }, + 'is_reverse': False, + 'legend_configs': { + 'good': { + 'values': [], + 'labels': 'Good' + }, + 'moderate': { + 'values': [], + 'labels': 'Moderate' + }, + 'bad': { + 'values': [], + 'labels': 'Bad' + }, + 'unknown': { + 'values': [], + 'labels': 'Unknown' + } + } } ) @@ -784,8 +1142,8 @@ def test_delete_in_draft_data_layer_by_admin(self): layer_id = response_data['id'] - url, view = accounts_url((layer_id,), {}, - view_name='update-or-delete-data-layer') + url, _, view = accounts_url((layer_id,), {}, + view_name='update-or-delete-data-layer') put_response = self.forced_auth_req( 'delete', @@ -800,7 +1158,7 @@ def test_publish_in_ready_data_layer_by_admin(self): data_source_type=accounts_models.DataSource.DATA_SOURCE_TYPE_DAILY_CHECK_APP, ).first() - url, view = accounts_url((), {}, view_name='list-or-create-data-layers') + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') response = self.forced_auth_req( 'post', @@ -815,6 +1173,30 @@ def test_publish_in_ready_data_layer_by_admin(self): 'type': accounts_models.DataLayer.LAYER_TYPE_LIVE, 'data_sources_list': [pcdc_data_source.id, ], 'data_source_column': pcdc_data_source.column_config[0], + 'global_benchmark': { + 'value': '20000000', + 'unit': 'bps', + 'convert_unit': 'mbps' + }, + 'is_reverse': False, + 'legend_configs': { + 'good': { + 'values': [], + 'labels': 'Good' + }, + 'moderate': { + 'values': [], + 'labels': 'Moderate' + }, + 'bad': { + 'values': [], + 'labels': 'Bad' + }, + 'unknown': { + 'values': [], + 'labels': 'Unknown' + } + } } ) @@ -824,8 +1206,8 @@ def test_publish_in_ready_data_layer_by_admin(self): layer_id = response_data['id'] - url, view = accounts_url((layer_id,), {}, - view_name='update-or-delete-data-layer') + url, _, view = accounts_url((layer_id,), {}, + view_name='update-or-delete-data-layer') put_response = self.forced_auth_req( 'put', @@ -838,8 +1220,8 @@ def test_publish_in_ready_data_layer_by_admin(self): self.assertEqual(put_response.status_code, status.HTTP_200_OK) - url, view = accounts_url((layer_id,), {}, - view_name='publish-data-layer') + url, _, view = accounts_url((layer_id,), {}, + view_name='publish-data-layer') put_response = self.forced_auth_req( 'put', @@ -857,7 +1239,7 @@ def test_preview_pcdc_data_layer_by_admin(self): data_source_type=accounts_models.DataSource.DATA_SOURCE_TYPE_DAILY_CHECK_APP, ).first() - url, view = accounts_url((), {}, view_name='list-or-create-data-layers') + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') response = self.forced_auth_req( 'post', @@ -872,6 +1254,30 @@ def test_preview_pcdc_data_layer_by_admin(self): 'type': accounts_models.DataLayer.LAYER_TYPE_LIVE, 'data_sources_list': [pcdc_data_source.id, ], 'data_source_column': pcdc_data_source.column_config[0], + 'global_benchmark': { + 'value': '20000000', + 'unit': 'bps', + 'convert_unit': 'mbps' + }, + 'is_reverse': False, + 'legend_configs': { + 'good': { + 'values': [], + 'labels': 'Good' + }, + 'moderate': { + 'values': [], + 'labels': 'Moderate' + }, + 'bad': { + 'values': [], + 'labels': 'Bad' + }, + 'unknown': { + 'values': [], + 'labels': 'Unknown' + } + } } ) @@ -881,8 +1287,8 @@ def test_preview_pcdc_data_layer_by_admin(self): layer_id = response_data['id'] - url, view = accounts_url((layer_id,), {}, - view_name='preview-data-layer') + url, _, view = accounts_url((layer_id,), {}, + view_name='preview-data-layer') put_response = self.forced_auth_req( 'get', @@ -897,7 +1303,7 @@ def test_preview_qos_data_layer_by_admin(self): data_source_type=accounts_models.DataSource.DATA_SOURCE_TYPE_QOS, ).first() - url, view = accounts_url((), {}, view_name='list-or-create-data-layers') + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') response = self.forced_auth_req( 'post', @@ -912,6 +1318,30 @@ def test_preview_qos_data_layer_by_admin(self): 'type': accounts_models.DataLayer.LAYER_TYPE_LIVE, 'data_sources_list': [qos_data_source.id, ], 'data_source_column': qos_data_source.column_config[0], + 'global_benchmark': { + 'value': '20000000', + 'unit': 'bps', + 'convert_unit': 'mbps' + }, + 'is_reverse': False, + 'legend_configs': { + 'good': { + 'values': [], + 'labels': 'Good' + }, + 'moderate': { + 'values': [], + 'labels': 'Moderate' + }, + 'bad': { + 'values': [], + 'labels': 'Bad' + }, + 'unknown': { + 'values': [], + 'labels': 'Unknown' + } + } } ) @@ -921,8 +1351,8 @@ def test_preview_qos_data_layer_by_admin(self): layer_id = response_data['id'] - url, view = accounts_url((layer_id,), {}, - view_name='preview-data-layer') + url, _, view = accounts_url((layer_id,), {}, + view_name='preview-data-layer') put_response = self.forced_auth_req( 'get', @@ -933,27 +1363,14 @@ def test_preview_qos_data_layer_by_admin(self): self.assertEqual(put_response.status_code, status.HTTP_200_OK) def test_preview_static_data_layer_by_admin(self): - master_data_source = accounts_models.DataSource.objects.filter( - data_source_type=accounts_models.DataSource.DATA_SOURCE_TYPE_SCHOOL_MASTER, - ).first() - - url, view = accounts_url((), {}, view_name='list-or-create-data-layers') + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') response = self.forced_auth_req( 'post', url, user=self.admin_user, view=view, - data={ - 'icon': '', - 'name': 'Test data layer', - 'description': 'Test data layer description', - 'version': '1.0.0', - 'type': accounts_models.DataLayer.LAYER_TYPE_STATIC, - 'data_sources_list': [master_data_source.id, ], - 'data_source_column': master_data_source.column_config[0], - 'legend_configs': {}, - } + data=accounts_test_utilities.static_coverage_layer_data() ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -962,8 +1379,8 @@ def test_preview_static_data_layer_by_admin(self): layer_id = response_data['id'] - url, view = accounts_url((layer_id,), {}, - view_name='preview-data-layer') + url, _, view = accounts_url((layer_id,), {}, + view_name='preview-data-layer') put_response = self.forced_auth_req( 'get', @@ -972,3 +1389,639 @@ def test_preview_static_data_layer_by_admin(self): ) self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + +class AdvanceFiltersApiTestCase(TestAPIViewSetMixin, TestCase): + databases = ['default', ] + + def setUp(self): + cache.clear() + super().setUp() + + def test_list_advance_filters(self): + url, _, view = accounts_url((), {}, view_name='list-advanced-filters') + + response = self.forced_auth_req('get', url, _, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + + self.assertEqual(type(response_data), dict) + self.assertTrue(response_data['count'] > 0) + self.assertTrue(len(response_data['results']) > 0) + + +class LogActionApiTestCase(TestAPIViewSetMixin, TestCase): + databases = ['default', ] + + @classmethod + def setUpTestData(cls): + cls.admin_user = test_utilities.setup_admin_user_by_role() + cls.read_only_user = test_utilities.setup_read_only_user_by_role() + + def test_list_for_admin_user(self): + url, _, view = accounts_url((), {}, view_name='list-recent-action-log') + + response = self.forced_auth_req('get', url, user=self.admin_user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + + self.assertEqual(type(response_data), dict) + self.assertEqual(response_data['count'], 0) + self.assertEqual(len(response_data['results']), 0) + + def test_list_for_readonly_user(self): + url, _, view = accounts_url((), {}, view_name='list-recent-action-log') + + response = self.forced_auth_req('get', url, user=self.read_only_user, view=view) + + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + +class DataLayerMapApiTestCase(TestAPIViewSetMixin, TestCase): + databases = ['default', ] + + @classmethod + def setUpTestData(cls): + args = ['--delete_data_sources', '--update_data_sources', '--update_data_layers'] + call_command('load_system_data_layers', *args) + + cls.admin_user = test_utilities.setup_admin_user_by_role() + cls.read_only_user = test_utilities.setup_read_only_user_by_role() + + cls.country = CountryFactory() + + def setUp(self): + cache.clear() + super().setUp() + + def test_static_data_layer_map_country_view(self): + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') + + response = self.forced_auth_req( + 'post', + url, + user=self.admin_user, + view=view, + data=accounts_test_utilities.static_coverage_layer_data() + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response_data = response.data + + layer_id = response_data['id'] + + url, _, view = accounts_url((layer_id,), {}, + view_name='update-or-delete-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_READY_TO_PUBLISH, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, _, view = accounts_url((layer_id,), {}, + view_name='publish-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_PUBLISHED, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, view, view_info = accounts_url( + (layer_id,), + { + 'country_id': self.country.id, + 'z': '8', + 'x': '82', + 'y': '114.mvt' + }, + view_name='map-data-layer' + ) + + response = self.forced_auth_req( + 'get', + url, + view=view, + view_info=view_info, + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_static_data_layer_map_school_view(self): + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') + + response = self.forced_auth_req( + 'post', + url, + user=self.admin_user, + view=view, + data=accounts_test_utilities.static_coverage_layer_data() + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response_data = response.data + + layer_id = response_data['id'] + + url, _, view = accounts_url((layer_id,), {}, + view_name='update-or-delete-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_READY_TO_PUBLISH, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, _, view = accounts_url((layer_id,), {}, + view_name='publish-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_PUBLISHED, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, view, view_info = accounts_url( + (layer_id,), + { + 'country_id': self.country.id, + 'school_id': '1234567', + 'z': '8', + 'x': '82', + 'y': '114.mvt' + }, + view_name='map-data-layer' + ) + + response = self.forced_auth_req( + 'get', + url, + view=view, + view_info=view_info, + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_live_data_layer_map_country_view(self): + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') + + response = self.forced_auth_req( + 'post', + url, + user=self.admin_user, + view=view, + data=accounts_test_utilities.live_download_layer_data_pcdc(), + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response_data = response.data + + layer_id = response_data['id'] + + url, _, view = accounts_url((layer_id,), {}, + view_name='update-or-delete-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_READY_TO_PUBLISH, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, _, view = accounts_url((layer_id,), {}, + view_name='publish-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_PUBLISHED, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, view, view_info = accounts_url( + (layer_id,), + { + 'country_id': self.country.id, + 'benchmark': 'global', + 'start_date': '24-06-2024', + 'end_date': '30-06-2024', + 'is_weekly': 'true', + 'z': '8', + 'x': '82', + 'y': '114.mvt' + }, + view_name='map-data-layer' + ) + + response = self.forced_auth_req( + 'get', + url, + view=view, + view_info=view_info, + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_live_data_layer_map_school_view(self): + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') + + response = self.forced_auth_req( + 'post', + url, + user=self.admin_user, + view=view, + data=accounts_test_utilities.live_download_layer_data_pcdc(), + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response_data = response.data + + layer_id = response_data['id'] + + url, _, view = accounts_url((layer_id,), {}, + view_name='update-or-delete-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_READY_TO_PUBLISH, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, _, view = accounts_url((layer_id,), {}, + view_name='publish-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_PUBLISHED, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, view, view_info = accounts_url( + (layer_id,), + { + 'country_id': self.country.id, + 'school_id': '1234568', + 'benchmark': 'global', + 'start_date': '24-06-2024', + 'end_date': '30-06-2024', + 'is_weekly': 'true', + 'z': '8', + 'x': '82', + 'y': '114.mvt' + }, + view_name='map-data-layer' + ) + + response = self.forced_auth_req( + 'get', + url, + view=view, + view_info=view_info, + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class DataLayerInfoApiTestCase(TestAPIViewSetMixin, TestCase): + databases = ['default', ] + + @classmethod + def setUpTestData(cls): + args = ['--delete_data_sources', '--update_data_sources', '--update_data_layers'] + call_command('load_system_data_layers', *args) + + cls.admin_user = test_utilities.setup_admin_user_by_role() + cls.read_only_user = test_utilities.setup_read_only_user_by_role() + + cls.country = CountryFactory() + cls.school = SchoolFactory() + + def setUp(self): + cache.clear() + super().setUp() + + def test_static_data_layer_map_country_view(self): + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') + + response = self.forced_auth_req( + 'post', + url, + user=self.admin_user, + view=view, + data=accounts_test_utilities.static_coverage_layer_data() + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response_data = response.data + + layer_id = response_data['id'] + + url, _, view = accounts_url((layer_id,), {}, + view_name='update-or-delete-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_READY_TO_PUBLISH, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, _, view = accounts_url((layer_id,), {}, + view_name='publish-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_PUBLISHED, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, view, view_info = accounts_url( + (layer_id,), + { + 'country_id': self.country.id, + }, + view_name='info-data-layer' + ) + + response = self.forced_auth_req( + 'get', + url, + view=view, + view_info=view_info, + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_static_data_layer_map_school_view(self): + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') + + response = self.forced_auth_req( + 'post', + url, + user=self.admin_user, + view=view, + data=accounts_test_utilities.static_coverage_layer_data() + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response_data = response.data + + layer_id = response_data['id'] + + url, _, view = accounts_url((layer_id,), {}, + view_name='update-or-delete-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_READY_TO_PUBLISH, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, _, view = accounts_url((layer_id,), {}, + view_name='publish-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_PUBLISHED, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, view, view_info = accounts_url( + (layer_id,), + { + 'country_id': self.country.id, + 'school_id': '1234567', + }, + view_name='info-data-layer' + ) + + response = self.forced_auth_req( + 'get', + url, + view=view, + view_info=view_info, + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_live_data_layer_map_country_view(self): + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') + + response = self.forced_auth_req( + 'post', + url, + user=self.admin_user, + view=view, + data=accounts_test_utilities.live_download_layer_data_pcdc(), + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response_data = response.data + + layer_id = response_data['id'] + + url, _, view = accounts_url((layer_id,), {}, + view_name='update-or-delete-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_READY_TO_PUBLISH, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, _, view = accounts_url((layer_id,), {}, + view_name='publish-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_PUBLISHED, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, view, view_info = accounts_url( + (layer_id,), + { + 'country_id': self.country.id, + 'benchmark': 'global', + 'start_date': '24-06-2024', + 'end_date': '30-06-2024', + 'is_weekly': 'true', + }, + view_name='info-data-layer' + ) + + response = self.forced_auth_req( + 'get', + url, + view=view, + view_info=view_info, + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_live_data_layer_map_school_view(self): + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') + + response = self.forced_auth_req( + 'post', + url, + user=self.admin_user, + view=view, + data=accounts_test_utilities.live_download_layer_data_pcdc(), + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response_data = response.data + + layer_id = response_data['id'] + + url, _, view = accounts_url((layer_id,), {}, + view_name='update-or-delete-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_READY_TO_PUBLISH, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, _, view = accounts_url((layer_id,), {}, + view_name='publish-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_PUBLISHED, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, view, view_info = accounts_url( + (layer_id,), + { + 'country_id': self.country.id, + 'school_id': '123456', + 'benchmark': 'global', + 'start_date': '24-06-2024', + 'end_date': '30-06-2024', + 'is_weekly': 'true', + }, + view_name='info-data-layer' + ) + + response = self.forced_auth_req( + 'get', + url, + view=view, + view_info=view_info, + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class InvalidateCacheApiTestCase(TestAPIViewSetMixin, TestCase): + @classmethod + def setUpTestData(cls): + cls.admin_user = test_utilities.setup_admin_user_by_role() + cls.read_only_user = test_utilities.setup_read_only_user_by_role() + + def test_hard_cache_clean_for_admin(self): + url, view, view_info = accounts_url((), {'hard': 'true'}, view_name='admin-invalidate-cache') + + response = self.forced_auth_req('get', url, user=self.admin_user, view=view, view_info=view_info) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_soft_cache_clean_for_admin(self): + url, view, view_info = accounts_url((), {'hard': 'false'}, view_name='admin-invalidate-cache') + + response = self.forced_auth_req('get', url, user=self.admin_user, view=view, view_info=view_info) + + self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/proco/accounts/tests/test_utils.py b/proco/accounts/tests/test_utils.py new file mode 100644 index 0000000..6ddadfc --- /dev/null +++ b/proco/accounts/tests/test_utils.py @@ -0,0 +1,94 @@ +from proco.accounts import models as accounts_models + + +def static_coverage_layer_data(): + master_data_source = accounts_models.DataSource.objects.filter( + data_source_type=accounts_models.DataSource.DATA_SOURCE_TYPE_SCHOOL_MASTER, + ).first() + return { + "icon": "icon", + "code": "CELLULAR COVERAGE", + "name": "Cellular Coverage", + "description": "Mobile coverage in the area", + "version": "V 1.0", + "type": "STATIC", + "category": "COVERAGE", + "applicable_countries": [], + "legend_configs": { + "good": { + "values": [ + "5G", + "4G" + ], + "labels": "3G & above" + }, + "moderate": { + "values": [ + "3G", + "2G" + ], + "labels": "2G" + }, + "bad": { + "values": [ + "no" + ], + "labels": "No Coverage" + }, + "unknown": { + "values": [], + "labels": "Unknown" + } + }, + "data_sources_list": [master_data_source.id, ], + "data_source_column": master_data_source.column_config[0], + } + + +def live_download_layer_data_pcdc(): + pcdc_data_source = accounts_models.DataSource.objects.filter( + data_source_type=accounts_models.DataSource.DATA_SOURCE_TYPE_DAILY_CHECK_APP, + ).first() + + return { + "icon": "icon", + "code": "DOWNLOAD PCDC", + "name": "Download - PCDC", + "description": "pcdc download speed", + "type": "LIVE", + "category": "CONNECTIVITY", + "applicable_countries": [], + "global_benchmark": { + "value": "20000000", + "unit": "bps", + "convert_unit": "mbps" + }, + "legend_configs": { + "good": { + "values": [], + "labels": "Good" + }, + "moderate": { + "values": [], + "labels": "Moderate" + }, + "bad": { + "values": [], + "labels": "Bad" + }, + "unknown": { + "values": [], + "labels": "Unknown" + } + }, + "is_reverse": False, + "data_sources_list": [pcdc_data_source.id,], + "data_source_column": pcdc_data_source.column_config[0], + "benchmark_metadata": { + "benchmark_value": "20000000", + "benchmark_unit": "bps", + "base_benchmark": "1000000", + "parameter_column_unit": "bps", + "round_unit_value": "{val} / (1000 * 1000)" + }, + } diff --git a/proco/accounts/utils.py b/proco/accounts/utils.py index 72c9c04..1bacb07 100644 --- a/proco/accounts/utils.py +++ b/proco/accounts/utils.py @@ -1,3 +1,4 @@ +import logging import re from anymail.message import AnymailMessage @@ -10,6 +11,8 @@ from proco.accounts.config import app_config as config from proco.core import utils as core_utilities +logger = logging.getLogger('gigamaps.' + __name__) + def send_standard_email(user, data): """ @@ -22,7 +25,7 @@ def send_standard_email(user, data): core_utilities.is_blank_string(settings.ANYMAIL.get('MAILJET_API_KEY')) or core_utilities.is_blank_string(settings.ANYMAIL.get('MAILJET_SECRET_KEY')) ): - print('ERROR: MailJet creds are not configured to send the email. Hence email notification is disabled.') + logger.error('MailJet creds are not configured to send the email. Hence email notification is disabled.') return data.update({ @@ -40,7 +43,7 @@ def send_standard_email(user, data): to=[user.email], ) mail.content_subtype = 'html' - print('Sending standard message over email') + logger.debug('Sending standard message over email') mail.send() @@ -50,7 +53,7 @@ def send_email_over_mailjet_service(recipient_list, cc=None, bcc=None, fail_sile core_utilities.is_blank_string(settings.ANYMAIL.get('MAILJET_API_KEY')) or core_utilities.is_blank_string(settings.ANYMAIL.get('MAILJET_SECRET_KEY')) ): - print('ERROR: MailJet creds are not configured to send the email. Hence email notification is disabled.') + logger.error('MailJet creds are not configured to send the email. Hence email notification is disabled.') return kwargs.update({ @@ -70,15 +73,12 @@ def send_email_over_mailjet_service(recipient_list, cc=None, bcc=None, fail_sile bcc=bcc ) mail.content_subtype = 'html' - print('Sending message over email') + logger.debug('Sending message over email') response = mail.send(fail_silently=fail_silently) return response class BaseTileGenerator: - # def __init__(self, table_config): - # self.table_config = table_config - def path_to_tile(self, request): path = "/" + request.query_params.get('z') + "/" + request.query_params.get( 'x') + "/" + request.query_params.get('y') @@ -133,11 +133,10 @@ def sql_to_pbf(self, sql): if not cur: return Response({"error": f"sql query failed: {sql}"}, status=404) return cur.fetchone()[0] - except Exception as error: + except Exception: return Response({"error": "An error occurred while executing SQL query"}, status=500) def generate_tile(self, request): - # start_time = time.time() tile = self.path_to_tile(request) if not (tile and self.tile_is_valid(tile)): return Response({"error": "Invalid tile path"}, status=400) @@ -146,7 +145,7 @@ def generate_tile(self, request): sql = self.envelope_to_sql(env, request) - print(sql.replace('\n', '')) + logger.debug(sql.replace('\n', '')) pbf = self.sql_to_pbf(sql) if isinstance(pbf, memoryview): diff --git a/proco/background/api.py b/proco/background/api.py index fefe6eb..f58e8d5 100644 --- a/proco/background/api.py +++ b/proco/background/api.py @@ -11,6 +11,7 @@ from proco.background.models import BackgroundTask from proco.background.serializers import BackgroundTaskSerializer, BackgroundTaskHistorySerializer from proco.core import permissions as core_permissions +from proco.core import utils as core_utilities from proco.core.viewsets import BaseModelViewSet from proco.utils.error_message import delete_succ_mess from proco.utils.filters import NullsAlwaysLastOrderingFilter @@ -45,12 +46,15 @@ def destroy(self, request, *args, **kwargs): try: ids = request.data['task_id'] if isinstance(ids, list) and len(ids) > 0: - queryset = self.model.objects.filter(task_id__in=ids, ) - if queryset.exists(): - action_log(request, queryset, 3, 'Background task deleted', self.model, 'task_id') - queryset.delete() + task_qs = self.model.objects.filter(task_id__in=ids, ) + if task_qs.exists(): + action_log(request, task_qs, 3, 'Background task deleted', self.model, 'name') + task_qs.update( + deleted=core_utilities.get_current_datetime_object(), + deleted_by=core_utilities.get_current_user(request=request), + ) return Response(status=rest_status.HTTP_200_OK, data=delete_succ_mess) - raise ValidationError('{0} value missing in database: {1}'.format('id', ids)) + raise ValidationError('{0} value missing in database: {1}'.format('task_id', ids)) except KeyError as ex: return Response(['Required key {0} missing in the request body'.format(ex)], status=status.HTTP_400_BAD_REQUEST) diff --git a/proco/background/migrations/0002_added_name_descritpion_soft_delete_fields.py b/proco/background/migrations/0002_added_name_descritpion_soft_delete_fields.py new file mode 100755 index 0000000..14e66c1 --- /dev/null +++ b/proco/background/migrations/0002_added_name_descritpion_soft_delete_fields.py @@ -0,0 +1,48 @@ +# Generated by Django 2.2.28 on 2024-06-04 12:39 + +from django.db import migrations, models +import proco.core.models + + +class Migration(migrations.Migration): + dependencies = [ + ('background', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='backgroundtask', + name='deleted', + field=proco.core.models.CustomDateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='backgroundtask', + name='description', + field=models.CharField(blank=True, db_index=True, max_length=255, null=True, + verbose_name='Task Readable Name'), + ), + migrations.AddField( + model_name='backgroundtask', + name='name', + field=models.CharField(db_index=True, max_length=255, null=True, verbose_name='Task Unique Name'), + ), + migrations.AlterField( + model_name='backgroundtask', + name='completed_at', + field=proco.core.models.CustomDateTimeField(blank=True, null=True), + ), + migrations.AlterField( + model_name='backgroundtask', + name='created_at', + field=proco.core.models.CustomDateTimeField(blank=True, null=True), + ), + migrations.RunSQL( + sql="UPDATE background_backgroundtask SET name = task_id", + reverse_sql=migrations.RunSQL.noop + ), + migrations.AlterField( + model_name='backgroundtask', + name='name', + field=models.CharField(db_index=True, max_length=255, verbose_name='Task Unique Name'), + ), + ] diff --git a/proco/background/migrations/0003_added_deleted_bu_field.py b/proco/background/migrations/0003_added_deleted_bu_field.py new file mode 100755 index 0000000..185d276 --- /dev/null +++ b/proco/background/migrations/0003_added_deleted_bu_field.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.28 on 2024-06-04 12:53 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('background', '0002_added_name_descritpion_soft_delete_fields'), + ] + + operations = [ + migrations.AddField( + model_name='backgroundtask', + name='deleted_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, + related_name='deleted_backgroundtasks', to=settings.AUTH_USER_MODEL, + verbose_name='Deleted By'), + ), + ] diff --git a/proco/background/migrations/0004_added_unique_constraints.py b/proco/background/migrations/0004_added_unique_constraints.py new file mode 100755 index 0000000..ac80382 --- /dev/null +++ b/proco/background/migrations/0004_added_unique_constraints.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.28 on 2024-06-05 07:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('background', '0003_added_deleted_bu_field'), + ] + + operations = [ + migrations.AddConstraint( + model_name='backgroundtask', + constraint=models.UniqueConstraint(fields=('name', 'status', 'deleted'), + name='background_task_unique_with_deleted'), + ), + migrations.AddConstraint( + model_name='backgroundtask', + constraint=models.UniqueConstraint(condition=models.Q(deleted=None), fields=('name', 'status'), + name='background_task_unique_without_deleted'), + ), + ] diff --git a/proco/background/models.py b/proco/background/models.py index bdaa6a7..47efb1a 100644 --- a/proco/background/models.py +++ b/proco/background/models.py @@ -1,10 +1,15 @@ from django.conf import settings from django.db import models -from django.utils import timezone +from django.db.models import Q +from django.db.models.constraints import UniqueConstraint from django.utils.translation import ugettext as _ - from model_utils import Choices +from proco.core import models as core_models +from proco.core import utils as core_utilities +from proco.core.managers import BaseManager +from proco.utils import dates as date_utilities + class BackgroundTask(models.Model): STATUSES = Choices( @@ -14,22 +19,56 @@ class BackgroundTask(models.Model): PROCESS_STATUSES = [STATUSES.running] task_id = models.CharField(max_length=50, primary_key=True) + created_at = core_models.CustomDateTimeField(null=True, blank=True) + completed_at = core_models.CustomDateTimeField(null=True, blank=True) + + name = models.CharField( + max_length=255, + null=False, + verbose_name='Task Unique Name', + db_index=True, + ) + description = models.CharField( + max_length=255, + null=True, + blank=True, + verbose_name='Task Readable Name', + db_index=True, + ) + status = models.CharField(default=STATUSES.running, choices=STATUSES, max_length=10) log = models.TextField() - created_at = models.DateTimeField(auto_now_add=True) - completed_at = models.DateTimeField(null=True) - objects = models.Manager() + deleted = core_models.CustomDateTimeField(null=True, blank=True) + deleted_by = models.ForeignKey( + settings.AUTH_USER_MODEL, + blank=True, + null=True, + related_name='deleted_%(class)ss', + on_delete=models.DO_NOTHING, + verbose_name='Deleted By' + ) + + objects = BaseManager() class Meta: verbose_name = _('Background Task') + constraints = [ + UniqueConstraint(fields=['name', 'status', 'deleted'], + name='background_task_unique_with_deleted'), + UniqueConstraint(fields=['name', 'status'], + condition=Q(deleted=None), + name='background_task_unique_without_deleted'), + ] def __str__(self): - return f'{self.task_id} {self.status}' + return f'Task: {self.name}, Status: {self.status}' def info(self, text: str): if self.log: self.log += '\n' - self.log += '{0}: {1}'.format(timezone.now().strftime(settings.DATETIME_FORMAT), text) + self.log += '{0}: {1}'.format( + date_utilities.format_datetime(core_utilities.get_current_datetime_object(), frmt='%d-%m-%Y %H:%M:%S'), + text) self.save() diff --git a/proco/background/tests/test_api.py b/proco/background/tests/test_api.py index cc8301c..92c421d 100644 --- a/proco/background/tests/test_api.py +++ b/proco/background/tests/test_api.py @@ -1,11 +1,11 @@ -import traceback +from datetime import datetime +import pytz +import uuid +from django.core.cache import cache from django.test import TestCase from django.urls import reverse from rest_framework import status -from django.core.cache import cache -from datetime import datetime -import pytz, uuid from proco.background.models import BackgroundTask from proco.custom_auth import models as auth_models @@ -14,11 +14,10 @@ class BackgroundTaskTestCase(TestAPIViewSetMixin, TestCase): base_view = 'background:' - databases = {'read_only_database', 'default'} + databases = {'default', } @classmethod def setUpTestData(cls): - # self.databases = 'default' cls.email = 'test@test.com' cls.password = 'SomeRandomPass96' cls.user = auth_models.ApplicationUser.objects.create_user(username=cls.email, password=cls.password) @@ -36,7 +35,7 @@ def setUp(self): cache.clear() super().setUp() - def test_retrive(self): + def test_retrieve(self): response = self.forced_auth_req( 'get', reverse(self.base_view + "update_or_retrieve_backgroundtask", args=(self.task.task_id,)), @@ -58,7 +57,7 @@ def test_destroy(self): class BackgroundTaskHistoryTestCase(TestAPIViewSetMixin, TestCase): base_view = 'background:' - databases = {'default', 'read_only_database'} + databases = {'default', } def setUp(self): self.email = 'test@test.com' diff --git a/proco/background/utils.py b/proco/background/utils.py new file mode 100644 index 0000000..a237b06 --- /dev/null +++ b/proco/background/utils.py @@ -0,0 +1,35 @@ +from datetime import timedelta + +from proco.background.models import BackgroundTask +from proco.core.utils import get_current_datetime_object + + +def task_on_start(task_id, unique_name, description, check_previous=False): + try: + task = BackgroundTask.objects.filter(name=unique_name).first() + if task: + return + else: + if check_previous and BackgroundTask.objects.filter( + description=description, + created_at__gte=get_current_datetime_object() - timedelta(hours=12), + status=BackgroundTask.STATUSES.running, + ).exists(): + return + else: + task = BackgroundTask.objects.create( + task_id=task_id, + name=unique_name, + description=description, + created_at=get_current_datetime_object(), + status=BackgroundTask.STATUSES.running, + ) + return task + except: + return + + +def task_on_complete(task): + task.status = BackgroundTask.STATUSES.completed + task.completed_at = get_current_datetime_object() + task.save() diff --git a/proco/connection_statistics/aggregations.py b/proco/connection_statistics/aggregations.py index a2e61d4..97fff28 100644 --- a/proco/connection_statistics/aggregations.py +++ b/proco/connection_statistics/aggregations.py @@ -31,9 +31,11 @@ def aggregate_connectivity_by_availability(qs): def aggregate_coverage_by_types(qs): return qs.aggregate(**{ ColorMapSchema.GOOD: Count('school', filter=Q( - coverage_type__in=[SchoolWeeklyStatus.COVERAGE_4G, SchoolWeeklyStatus.COVERAGE_3G], + coverage_type__in=[SchoolWeeklyStatus.COVERAGE_5G, SchoolWeeklyStatus.COVERAGE_4G], + )), + ColorMapSchema.MODERATE: Count('school', filter=Q( + coverage_type__in=[SchoolWeeklyStatus.COVERAGE_3G, SchoolWeeklyStatus.COVERAGE_2G], )), - ColorMapSchema.MODERATE: Count('school', filter=Q(coverage_type=SchoolWeeklyStatus.COVERAGE_2G)), ColorMapSchema.NO: Count('school', filter=Q(coverage_type=SchoolWeeklyStatus.COVERAGE_NO)), ColorMapSchema.UNKNOWN: Count('school', filter=Q(coverage_type=SchoolWeeklyStatus.COVERAGE_UNKNOWN)), }) diff --git a/proco/connection_statistics/api.py b/proco/connection_statistics/api.py index c0c411c..e194494 100644 --- a/proco/connection_statistics/api.py +++ b/proco/connection_statistics/api.py @@ -33,7 +33,7 @@ from proco.connection_statistics.utils import get_benchmark_value_for_default_download_layer from proco.core import db_utils as db_utilities from proco.core import permissions as core_permissions -from proco.core.utils import is_blank_string, get_current_datetime_object +from proco.core import utils as core_utilities from proco.core.viewsets import BaseModelViewSet from proco.locations.models import Country, CountryAdminMetadata from proco.schools.models import School @@ -117,6 +117,20 @@ def calculate_global_statistic(self): 'all_countries', 'schools_with_connectivity_status_mapped', 'countries_with_connectivity_status_mapped').order_by() + school_filters = core_utilities.get_filter_sql(self.request, 'schools', 'schools_school') + if len(school_filters) > 0: + school_connectivity_status_qry = school_connectivity_status_qry.extra(where=[school_filters]) + + school_static_filters = core_utilities.get_filter_sql(self.request, 'school_static', + 'connection_statistics_schoolweeklystatus') + if len(school_static_filters) > 0: + school_connectivity_status_qry = school_connectivity_status_qry.annotate( + total_weekly_schools=Count('last_weekly_status__school_id', distinct=True), + ).values('connected', 'not_connected', 'unknown', 'total_schools', + 'all_countries', 'schools_with_connectivity_status_mapped', + 'countries_with_connectivity_status_mapped', 'total_weekly_schools') + school_connectivity_status_qry = school_connectivity_status_qry.extra(where=[school_static_filters]) + giga_connectivity_benchmark, giga_connectivity_benchmark_unit = get_benchmark_value_for_default_download_layer( 'global', None) @@ -141,338 +155,6 @@ def calculate_global_statistic(self): } -# @method_decorator([cache_control(public=True, max_age=settings.CACHE_CONTROL_MAX_AGE_FOR_FE)], name='dispatch') -# class ConnectivityStatsAPIVIEW(APIView): -# permission_classes = (AllowAny,) -# model = SchoolWeeklyStatus -# queryset = model.objects.all() -# serializer_class = statistics_serializers.SchoolWeeklyStatusSerializer -# schools_daily_status_qs = SchoolDailyStatus.objects.all() -# global_giga_benchmark = 20000000 # this will be a dynamic value from the admin panel -# -# def get(self, request, *args, **kwargs): -# -# request_path = remove_query_param(request.get_full_path(), 'cache') -# is_weekly = request.query_params.get('is_weekly', 'true') == 'true' -# -# start_date = date_utilities.to_date(self.request.query_params.get('start_date'), -# default=datetime.combine(datetime.now(), time.min)) -# end_date = date_utilities.to_date(self.request.query_params.get('end_date'), -# default=datetime.combine(datetime.now(), time.min)) -# -# month_number = date_utilities.get_month_from_date(start_date) -# year_number = date_utilities.get_year_from_date(start_date) -# -# if is_weekly: -# # If is_weekly == True, then pick the week number based on start_date -# week_number = date_utilities.get_week_from_date(start_date) -# else: -# # If is_weekly == False, then: -# # 1. Collect dates on all sundays of the given month and year -# # 2. Get the week numbers for all sundays and look into SchoolWeeklyStatus table for which last week number -# # data was created in the given month of the year. And pick this week number -# dates_on_all_sundays = date_utilities.all_days_of_a_month(year_number, month_number, -# day_name='sunday').keys() -# week_numbers_for_month = [date_utilities.get_week_from_date(date) for date in dates_on_all_sundays] -# week_number = self.queryset.filter(year=year_number, week__in=week_numbers_for_month -# ).order_by('-week').values_list('week', flat=True).first() -# -# if not week_number: -# # If for any week of the month data is not available then pick last week number -# week_number = week_numbers_for_month[-1] -# -# indicator = request.query_params.get('indicator', '').lower() -# -# if indicator == 'download': -# cache_key = f'{week_number}{year_number}WEEKLY_DOWNLOAD_CONNECTIVITY_STATS' \ -# if is_weekly else f'{month_number}{year_number}MONTHLY_DOWNLOAD_CONNECTIVITY_STATS' -# -# data = None # cache_manager.get(cache_key) -# -# if not data: -# data = self.calculate_download_connectivity_statistic(start_date, end_date, week_number, year_number) -# cache_manager.set(cache_key, data, request_path=request_path) -# -# elif indicator == 'uptime': -# cache_key = f'{week_number}{year_number}WEEKLY_UPTIME_CONNECTIVITY_STATS' \ -# if is_weekly else f'{month_number}{year_number}MONTHLY_UPTIME_CONNECTIVITY_STATS' -# -# data = None # cache_manager.get(cache_key) -# -# if not data: -# data = { -# 'live_avg': 28, -# 'no_of_schools_measure': 60000, -# 'school_with_realtime_data': 16000, -# 'real_time_connected_schools': { -# 'good': 4000, -# 'moderate': 4000, -# 'no_internet': 4000, -# 'unknown': 4000 -# }, -# 'graph_data': generate_static_graph_data(start_date, end_date, indicator) -# } -# -# cache_manager.set(cache_key, data, request_path=request_path) -# -# else: -# cache_key = f'{week_number}{year_number}WEEKLY_LATENCY_CONNECTIVITY_STATS' \ -# if is_weekly else f'{month_number}{year_number}MONTHLY_LATENCY_CONNECTIVITY_STATS' -# -# data = None # cache_manager.get(cache_key) -# if not data: -# data = { -# 'live_avg': 28, -# 'no_of_schools_measure': 60000, -# 'school_with_realtime_data': 16000, -# 'real_time_connected_schools': { -# 'good': 4000, -# 'moderate': 4000, -# 'no_internet': 4000, -# 'unknown': 4000 -# }, -# 'graph_data': generate_static_graph_data(start_date, end_date, indicator) -# } -# -# cache_manager.set(cache_key, data, request_path=request_path) -# -# return Response(data=data) -# -# def calculate_download_connectivity_statistic(self, start_date, end_date, week_number, year_number): -# -# benchmark = self.request.query_params.get('benchmark', 'global') -# speed_benchmark = GigaGlobalBenchmark.connectivity_speed.value.get( -# 'value', statuses_schema.CONNECTIVITY_SPEED_FOR_GOOD_CONNECTIVITY_STATUS -# ) -# if benchmark == 'national': -# speed_benchmark = statuses_schema.CONNECTIVITY_SPEED_FOR_GOOD_CONNECTIVITY_STATUS -# -# weekly_queryset = self.queryset.filter(week=week_number, year=year_number).annotate( -# dummy_group_by=Value(1)).values('dummy_group_by').annotate( -# good=Count(Case(When(connectivity_speed__gt=speed_benchmark, then='school')), distinct=True), -# moderate=Count(Case(When(connectivity_speed__lte=speed_benchmark, connectivity_speed__gt=1000000, -# then='school')), distinct=True), -# bad=Count(Case(When(connectivity_speed__lte=1000000, then='school')), distinct=True), -# unknown=Count(Case(When(connectivity_speed__isnull=True, then='school')), distinct=True), -# school_with_realtime_data=Count(Case(When(connectivity_speed__isnull=False, then='school')), distinct=True), -# no_of_schools_measure=Count('school', distinct=True), -# ).values('good', 'moderate', 'bad', 'unknown', 'school_with_realtime_data', 'no_of_schools_measure').order_by() -# -# real_time_connected_schools = { -# 'good': weekly_queryset[0]['good'], -# 'moderate': weekly_queryset[0]['moderate'], -# 'no_internet': weekly_queryset[0]['bad'], -# 'unknown': weekly_queryset[0]['unknown'], -# } -# -# graph_data, positive_speeds = self.generate_graph_data(start_date, end_date) -# live_avg = round(sum(positive_speeds) / len(positive_speeds), 2) if len(positive_speeds) > 0 else 0 -# -# data = { -# 'live_avg': live_avg, -# 'no_of_schools_measure': weekly_queryset[0]['no_of_schools_measure'], -# 'school_with_realtime_data': weekly_queryset[0]['school_with_realtime_data'], -# 'real_time_connected_schools': real_time_connected_schools, -# 'graph_data': graph_data -# } -# -# return data -# -# def generate_graph_data(self, start_date, end_date): -# # Get all the school ids from SchoolWeeklyStatus model for the given start_date and end_date -# # school_ids = self.queryset.filter(date__range=[start_date, end_date]).values('school').distinct() -# # Get the daily connectivity_speed for the given school ids from SchoolDailyStatus model -# avg_daily_connectivity_speed = self.schools_daily_status_qs.filter(date__range=[start_date, end_date]).values( -# 'date').annotate(avg_speed=Avg('connectivity_speed')).order_by('date') -# -# # Generate the graph data in the desired format -# graph_data = [] -# current_date = start_date -# -# while current_date <= end_date: -# graph_data.append({ -# 'group': 'Download speed', -# 'key': date_utilities.format_date(current_date), -# 'value': None # Default value, will be updated later if data exists for the date -# }) -# current_date += timedelta(days=1) -# -# all_positive_speeds = [] -# -# # Update the graph_data with actual values if they exist -# for daily_avg_data in avg_daily_connectivity_speed: -# formatted_date = date_utilities.format_date(daily_avg_data['date']) -# for entry in graph_data: -# if entry['key'] == formatted_date: -# try: -# rounded_speed = 0 -# if daily_avg_data['avg_speed'] is not None: -# rounded_speed = round(daily_avg_data['avg_speed'] / 1000000, 2) -# entry['value'] = rounded_speed -# all_positive_speeds.append(rounded_speed) -# except (KeyError, TypeError): -# pass -# -# return graph_data, all_positive_speeds - - -# def generate_static_graph_data(current_date, end_date, indicator): -# data = [] -# -# while current_date <= end_date: -# data.append({ -# 'group': indicator, -# 'key': date_utilities.format_date(current_date), -# 'value': random.randint(2, 50) -# }) -# -# current_date += timedelta(days=1) -# -# return data - - -# class CountryWeekStatsAPIView(RetrieveAPIView): -# permission_classes = (AllowAny,) -# -# model = SchoolWeeklyStatus -# queryset = model.objects.all() -# -# country_daily_status_qs = CountryDailyStatus.objects.all() -# -# def get(self, *args, **kwargs): -# country_id = self.request.query_params.get('country_id', None) -# get_object_or_404(Country.objects.defer('geometry', 'geometry_simplified', ), id=country_id, ) -# -# is_weekly = self.request.query_params.get('is_weekly', 'true') == 'true' -# start_date = date_utilities.to_date(self.request.query_params.get('start_date'), -# default=datetime.combine(datetime.now(), time.min)) -# end_date = date_utilities.to_date(self.request.query_params.get('end_date'), -# default=datetime.combine(datetime.now(), time.min)) -# -# month_number = date_utilities.get_month_from_date(start_date) -# year_number = date_utilities.get_year_from_date(start_date) -# -# if is_weekly: -# # If is_weekly == True, then pick the week number based on start_date -# week_number = date_utilities.get_week_from_date(start_date) -# else: -# # If is_weekly == False, then: -# # 1. Collect dates on all sundays of the given month and year -# # 2. Get the week numbers for all sundays and look into SchoolWeeklyStatus table for which last week number -# # data was created in the given month of the year. And pick this week number -# dates_on_all_sundays = date_utilities.all_days_of_a_month(year_number, month_number, -# day_name='sunday').keys() -# week_numbers_for_month = [date_utilities.get_week_from_date(date) for date in dates_on_all_sundays] -# week_number = self.queryset.filter(year=year_number, week__in=week_numbers_for_month, -# ).order_by('-week').values_list('week', flat=True).first() -# -# if not week_number: -# # If for any week of the month data is not available then pick last week number -# week_number = week_numbers_for_month[-1] -# -# indicator = self.request.query_params.get('indicator', '').lower() -# -# if indicator == 'download': -# data = self.calculate_country_download_indicator(start_date, end_date, week_number, year_number, country_id) -# else: -# data = { -# 'live_avg': 28, -# 'no_of_schools_measure': 60000, -# 'school_with_realtime_data': 1600000, -# 'real_time_connected_schools': { -# 'good': 400000, -# 'moderate': 400000, -# 'no_internet': 300000, -# 'unknown': 500000 -# }, -# 'graph_data': generate_static_graph_data(start_date, end_date, indicator), -# 'is_data_synced': True, -# } -# -# return Response(data=data) -# -# def calculate_country_download_indicator(self, start_date, end_date, week_number, year_number, country_id): -# benchmark = self.request.query_params.get('benchmark', 'global') -# speed_benchmark = GigaGlobalBenchmark.connectivity_speed.value.get( -# 'value', statuses_schema.CONNECTIVITY_SPEED_FOR_GOOD_CONNECTIVITY_STATUS -# ) -# if benchmark == 'national': -# speed_benchmark = statuses_schema.CONNECTIVITY_SPEED_FOR_GOOD_CONNECTIVITY_STATUS -# -# weekly_queryset = self.queryset.filter( -# school__country_id=country_id, week=week_number, year=year_number).annotate( -# dummy_group_by=Value(1)).values('dummy_group_by').annotate( -# good=Count(Case(When(connectivity_speed__gt=speed_benchmark, then='school')), distinct=True), -# moderate=Count(Case(When(connectivity_speed__lte=speed_benchmark, connectivity_speed__gt=1000000, -# then='school')), distinct=True), -# bad=Count(Case(When(connectivity_speed__lte=1000000, then='school')), distinct=True), -# unknown=Count(Case(When(connectivity_speed__isnull=True, then='school')), distinct=True), -# school_with_realtime_data=Count(Case(When(connectivity_speed__isnull=False, then='school')), distinct=True), -# no_of_schools_measure=Count('school', distinct=True), -# ).values('good', 'moderate', 'bad', 'unknown', 'school_with_realtime_data', 'no_of_schools_measure').order_by() -# -# real_time_connected_schools = { -# 'good': weekly_queryset[0]['good'], -# 'moderate': weekly_queryset[0]['moderate'], -# 'no_internet': weekly_queryset[0]['bad'], -# 'unknown': weekly_queryset[0]['unknown'], -# } -# -# graph_data, positive_speeds = self.generate_country_graph_data(start_date, end_date, country_id) -# -# # live_avg = country_instance.get('connectivity_speed', 0) -# live_avg = round(sum(positive_speeds) / len(positive_speeds), 2) if len(positive_speeds) > 0 else 0 -# -# is_data_synced = self.queryset.filter( -# school__country_id=country_id, -# school__realtime_registration_status__rt_registered=True, -# ).exists() -# -# return { -# 'live_avg': live_avg, -# 'no_of_schools_measure': weekly_queryset[0]['no_of_schools_measure'], -# 'school_with_realtime_data': weekly_queryset[0]['school_with_realtime_data'], -# 'real_time_connected_schools': real_time_connected_schools, -# 'graph_data': graph_data, -# 'is_data_synced': is_data_synced, -# } -# -# def generate_country_graph_data(self, start_date, end_date, country_id): -# # Get the daily connectivity_speed for the given country from CountryDailyStatus model -# daily_connectivity_speed = self.country_daily_status_qs.filter(country_id=country_id, -# date__range=[start_date, end_date]) -# -# # Generate the graph data in the desired format -# graph_data = [] -# current_date = start_date -# -# while current_date <= end_date: -# graph_data.append({ -# 'group': 'Download speed', -# 'key': date_utilities.format_date(current_date), -# 'value': None # Default value, will be updated later if data exists for the date -# }) -# current_date += timedelta(days=1) -# -# all_positive_speeds = [] -# -# # Update the graph_data with actual values if they exist -# for daily_data in daily_connectivity_speed: -# formatted_date = date_utilities.format_date(daily_data.date) -# for entry in graph_data: -# if entry['key'] == formatted_date: -# try: -# rounded_speed = 0 -# if daily_data.connectivity_speed is not None: -# rounded_speed = round(daily_data.connectivity_speed / 1000000, 2) -# entry['value'] = rounded_speed -# all_positive_speeds.append(rounded_speed) -# except (KeyError, TypeError): -# pass -# -# return graph_data, all_positive_speeds - - class CountryDailyStatsListAPIView(ListAPIView): model = CountryDailyStatus queryset = model.objects.all() @@ -600,10 +282,9 @@ def update_kwargs(self): time.min)) school_ids = self.request.query_params.get('school_ids', '') - if not is_blank_string(school_ids): + if not core_utilities.is_blank_string(school_ids): school_ids = [int(school_id.strip()) for school_id in school_ids.split(',')] else: - # TODO: If School_ID not provided, then limit the output rows to 3 schools (P3 - Low priority) school_ids = [34554] self.kwargs.update({ @@ -658,7 +339,7 @@ def get_queryset(self): benchmark = self.request.query_params.get('benchmark', 'global') country_id = self.kwargs['country_id'] - speed_benchmark, speed_benchmark_unit = get_benchmark_value_for_default_download_layer(benchmark, country_id) + speed_benchmark, _ = get_benchmark_value_for_default_download_layer(benchmark, country_id) self.kwargs['speed_benchmark'] = speed_benchmark school_status_in_given_week_qry = SchoolWeeklyStatus.objects.filter( @@ -735,10 +416,9 @@ class SchoolCoverageStatsListAPIView(ListAPIView): def update_kwargs(self): school_ids = self.request.query_params.get('school_ids', '') - if not is_blank_string(school_ids): + if not core_utilities.is_blank_string(school_ids): school_ids = [int(school_id.strip()) for school_id in school_ids.split(',')] else: - # TODO: If School_ID not provided, then limit the output rows to 3 schools (P3 - Low priority) school_ids = [34554] self.kwargs.update({ @@ -825,6 +505,9 @@ class ConnectivityAPIView(APIView): CACHE_KEY = 'cache' CACHE_KEY_PREFIX = 'CONNECTIVITY_STATS' + school_filters = [] + school_static_filters = [] + def get_cache_key(self): params = dict(self.request.query_params) params.pop(self.CACHE_KEY, None) @@ -841,16 +524,16 @@ def get(self, request, *args, **kwargs): data = cache_manager.get(cache_key) if not data: + self.school_filters = core_utilities.get_filter_sql(self.request, 'schools', 'schools_school') + self.school_static_filters = core_utilities.get_filter_sql(self.request, 'school_static', + 'connection_statistics_schoolweeklystatus') + country_id = self.request.query_params.get('country_id', None) if country_id: - get_object_or_404(Country.objects.defer('geometry', 'geometry_simplified', ), id=country_id) self.queryset = self.queryset.filter(country_id=country_id) admin1_id = self.request.query_params.get('admin1_id', None) if admin1_id: - get_object_or_404(CountryAdminMetadata.objects.filter( - layer_name=CountryAdminMetadata.LAYER_NAME_ADMIN1, - ), id=admin1_id) self.queryset = self.queryset.filter(admin1_id=admin1_id) is_weekly = self.request.query_params.get('is_weekly', 'true') == 'true' @@ -890,7 +573,7 @@ def calculate_country_download_data(self, start_date, end_date, week_number, yea benchmark = self.request.query_params.get('benchmark', 'global') country_id = self.request.query_params.get('country_id', None) - speed_benchmark, speed_benchmark_unit = get_benchmark_value_for_default_download_layer(benchmark, country_id) + speed_benchmark, _ = get_benchmark_value_for_default_download_layer(benchmark, country_id) weekly_queryset = self.queryset.annotate( t=FilteredRelation( @@ -913,6 +596,18 @@ def calculate_country_download_data(self, start_date, end_date, week_number, yea ).values('good', 'moderate', 'bad', 'unknown', 'school_with_realtime_data', 'no_of_schools_measure', 'countries_with_realtime_data').order_by() + if len(self.school_filters) > 0: + weekly_queryset = weekly_queryset.extra(where=[self.school_filters]) + + if len(self.school_static_filters) > 0: + school_static_filters = core_utilities.get_filter_sql(self.request, 'school_static', 'T5') + weekly_queryset = weekly_queryset.annotate( + total_weekly_schools=Count('last_weekly_status__school_id', distinct=True), + ).values( + 'good', 'moderate', 'bad', 'unknown', 'school_with_realtime_data', + 'no_of_schools_measure', 'countries_with_realtime_data', 'total_weekly_schools' + ).extra(where=[school_static_filters]) + real_time_connected_schools = { 'good': weekly_queryset[0]['good'], 'moderate': weekly_queryset[0]['moderate'], @@ -922,7 +617,6 @@ def calculate_country_download_data(self, start_date, end_date, week_number, yea graph_data, positive_speeds = self.generate_country_graph_data(start_date, end_date) - # live_avg = country_instance.get('connectivity_speed', 0) live_avg = round(sum(positive_speeds) / len(positive_speeds), 2) if len(positive_speeds) > 0 else 0 live_avg_connectivity = 'unknown' @@ -939,20 +633,20 @@ def calculate_country_download_data(self, start_date, end_date, week_number, yea country_id = self.request.query_params.get('country_id', None) admin1_id = self.request.query_params.get('admin1_id', None) + is_data_synced_qs = SchoolWeeklyStatus.objects.filter( + school__realtime_registration_status__rt_registered=True, + ) + + if len(self.school_filters) > 0: + is_data_synced_qs = is_data_synced_qs.extra(where=[self.school_filters]) + + if len(self.school_static_filters) > 0: + is_data_synced_qs = is_data_synced_qs.extra(where=[self.school_static_filters]) + if admin1_id: - is_data_synced = SchoolWeeklyStatus.objects.filter( - school__admin1_id=admin1_id, - school__realtime_registration_status__rt_registered=True, - ).exists() - elif country_id: - is_data_synced = SchoolWeeklyStatus.objects.filter( - school__country_id=country_id, - school__realtime_registration_status__rt_registered=True, - ).exists() - else: - is_data_synced = SchoolWeeklyStatus.objects.filter( - school__realtime_registration_status__rt_registered=True, - ).exists() + is_data_synced_qs = is_data_synced_qs.filter(school__admin1_id=admin1_id) + if country_id: + is_data_synced_qs = is_data_synced_qs.filter(school__country_id=country_id) return { 'live_avg': live_avg, @@ -962,7 +656,7 @@ def calculate_country_download_data(self, start_date, end_date, week_number, yea 'countries_with_realtime_data': weekly_queryset[0]['countries_with_realtime_data'], 'real_time_connected_schools': real_time_connected_schools, 'graph_data': graph_data, - 'is_data_synced': is_data_synced, + 'is_data_synced': is_data_synced_qs.exists(), 'benchmark_metadata': { 'benchmark_value': str(speed_benchmark), 'benchmark_unit': "bps", @@ -983,6 +677,15 @@ def generate_country_graph_data(self, start_date, end_date): avg_speed=Avg('daily_status__connectivity_speed'), ).order_by('daily_status__date') + if len(self.school_filters) > 0: + avg_daily_connectivity_speed = avg_daily_connectivity_speed.extra(where=[self.school_filters]) + + if len(self.school_static_filters) > 0: + avg_daily_connectivity_speed = avg_daily_connectivity_speed.annotate( + total_weekly_schools=Count('last_weekly_status__school_id', distinct=True), + ) + avg_daily_connectivity_speed = avg_daily_connectivity_speed.extra(where=[self.school_static_filters]) + # Generate the graph data in the desired format graph_data = [] current_date = start_date @@ -1035,6 +738,9 @@ class CoverageAPIView(APIView): 'id': ['exact', 'in'], } + school_filters = [] + school_static_filters = [] + def get_cache_key(self): params = dict(self.request.query_params) params.pop(self.CACHE_KEY, None) @@ -1064,6 +770,10 @@ def get(self, request, *args, **kwargs): data = cache_manager.get(cache_key) if not data: + self.school_filters = core_utilities.get_filter_sql(self.request, 'schools', 'schools_school') + self.school_static_filters = core_utilities.get_filter_sql(self.request, 'school_static', + 'connection_statistics_schoolweeklystatus') + # Query the School table to get the coverage data # Get the total number of schools with coverage data # Get the count of schools falling under different coverage types @@ -1078,6 +788,16 @@ def get(self, request, *args, **kwargs): total_coverage_schools=Count(Case(When(coverage_type__isnull=False, then='id')), distinct=True), ).values('g_4_5', 'g_2_3', 'no_coverage', 'unknown', 'total_coverage_schools').order_by() + if len(self.school_filters) > 0: + school_coverage_type_qry = school_coverage_type_qry.extra(where=[self.school_filters]) + + if len(self.school_static_filters) > 0: + school_coverage_type_qry = school_coverage_type_qry.annotate( + total_weekly_schools=Count('last_weekly_status__school_id', distinct=True), + ).values( + 'g_4_5', 'g_2_3', 'no_coverage', 'unknown', 'total_coverage_schools', 'total_weekly_schools' + ).extra(where=[self.school_static_filters]) + coverage_data = { '5g_4g': school_coverage_type_qry[0]['g_4_5'], '3g_2g': school_coverage_type_qry[0]['g_2_3'], @@ -1142,7 +862,7 @@ def get(self, request, *args, **kwargs): self.queryset = self.queryset.filter(school=school_id) school_ids = self.request.query_params.get('school_ids', '') - if not is_blank_string(school_ids): + if not core_utilities.is_blank_string(school_ids): school_ids = [int(school_id.strip()) for school_id in school_ids.split(',')] self.queryset = self.queryset.filter(school__in=school_ids) @@ -1173,7 +893,7 @@ def get(self, request, *args, **kwargs): live_data_source__in=live_data_sources, ).filter(**{parameter_column_name + '__isnull': False}) - today_date = get_current_datetime_object().date() + today_date = core_utilities.get_current_datetime_object().date() monday_date = today_date - timedelta(days=today_date.weekday()) latest_daily_entry = self.queryset.filter( @@ -1206,7 +926,6 @@ def get(self, request, *args, **kwargs): return Response(data=static_data) -# @method_decorator([cache_control(public=True, max_age=settings.CACHE_CONTROL_MAX_AGE_FOR_FE)], name='dispatch') class CountrySummaryAPIViewSet(BaseModelViewSet): model = CountryWeeklyStatus serializer_class = statistics_serializers.CountryWeeklyStatusSerializer @@ -1423,7 +1142,10 @@ class SchoolSummaryAPIViewSet(BaseModelViewSet): ordering_field_names = ['-year', '-week', 'school__name'] apply_query_pagination = True - search_fields = ('=school__id', 'school__name', 'year', 'week',) + search_fields = ( + '=school__id', 'school__name', '=school__giga_id_school', '=school__external_id', + 'year', 'week', + ) filterset_fields = { 'school_id': ['exact', 'in'], 'year': ['exact', 'in'], @@ -1534,7 +1256,9 @@ class SchoolDailyConnectivitySummaryAPIViewSet(BaseModelViewSet): ordering_field_names = ['-date', 'school__name', ] apply_query_pagination = True - search_fields = ('=school__id', 'school__name',) + search_fields = ( + '=school__id', 'school__name', '=school__giga_id_school', '=school__external_id', + ) filterset_fields = { 'school_id': ['exact', 'in'], } @@ -1561,11 +1285,6 @@ def get_queryset(self): if len(countries_ids) > 0: school_data = School.objects.filter(country_id__in=countries_ids).values_list('id', flat=True).distinct() - # limit = len(school_data) - 1 - # if Country.objects.filter(code='BR').exists(): - # if Country.objects.get(code='BR').id in countries_ids: - # limit = 10000 - # print([school_data[i:i + 2] for i in range(0, len(school_data), 2)]) queryset = queryset.filter(school_id__in=school_data) return queryset diff --git a/proco/connection_statistics/migrations/0063_added_5g_choice_in_coverage_type.py b/proco/connection_statistics/migrations/0063_added_5g_choice_in_coverage_type.py new file mode 100755 index 0000000..fefb143 --- /dev/null +++ b/proco/connection_statistics/migrations/0063_added_5g_choice_in_coverage_type.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.28 on 2024-06-14 09:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('connection_statistics', '0062_updated_max_allowed_values_num_students_field'), + ] + + operations = [ + migrations.AlterField( + model_name='schoolweeklystatus', + name='coverage_type', + field=models.CharField( + choices=[('unknown', 'Unknown'), ('no', 'No'), ('2g', '2G'), ('3g', '3G'), ('4g', '4G'), ('5g', '5G')], + default='unknown', max_length=8), + ), + ] diff --git a/proco/connection_statistics/models.py b/proco/connection_statistics/models.py index 8069e86..7d2b995 100644 --- a/proco/connection_statistics/models.py +++ b/proco/connection_statistics/models.py @@ -1,7 +1,6 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.db.models import Q -# from proco.utils.models import ApproxQuerySet from django.db.models.constraints import UniqueConstraint from django.utils import timezone from django.utils.translation import ugettext as _ @@ -12,7 +11,6 @@ from proco.connection_statistics.config import app_config as statistics_configs from proco.core import models as core_models from proco.core.managers import BaseManager -from proco.core.models import CustomDateTimeField from proco.locations.models import Country from proco.schools.constants import statuses_schema from proco.schools.models import School @@ -41,7 +39,7 @@ class ConnectivityStatistics(models.Model): default=statistics_configs.UNKNOWN_SOURCE, ) - deleted = CustomDateTimeField(db_index=True, null=True, blank=True) + deleted = core_models.CustomDateTimeField(db_index=True, null=True, blank=True) class Meta: abstract = True @@ -167,12 +165,14 @@ class SchoolWeeklyStatus(ConnectivityStatistics, TimeStampedModel, models.Model) COVERAGE_2G = '2g' COVERAGE_3G = '3g' COVERAGE_4G = '4g' + COVERAGE_5G = '5g' COVERAGE_TYPES = Choices( (COVERAGE_UNKNOWN, _('Unknown')), (COVERAGE_NO, _('No')), (COVERAGE_2G, _('2G')), (COVERAGE_3G, _('3G')), (COVERAGE_4G, _('4G')), + (COVERAGE_5G, _('5G')), ) school = models.ForeignKey(School, related_name='weekly_status', on_delete=models.CASCADE) @@ -307,7 +307,6 @@ class SchoolDailyStatus(ConnectivityStatistics, TimeStampedModel, models.Model): school = models.ForeignKey(School, related_name='daily_status', on_delete=models.CASCADE) date = models.DateField() - # objects = ApproxQuerySet.as_manager() objects = BaseManager() class Meta: @@ -339,7 +338,6 @@ def delete(self, *args, **kwargs): class RealTimeConnectivity(ConnectivityStatistics, TimeStampedModel, models.Model): school = models.ForeignKey(School, related_name='realtime_status', on_delete=models.CASCADE) - # objects = ApproxQuerySet.as_manager() objects = BaseManager() class Meta: diff --git a/proco/connection_statistics/serializers.py b/proco/connection_statistics/serializers.py index 537c367..d69703e 100644 --- a/proco/connection_statistics/serializers.py +++ b/proco/connection_statistics/serializers.py @@ -356,9 +356,6 @@ class Meta(CountryDailyStatus.Meta): model = CountryDailyStatus fields = ('id', 'country_name', 'date', 'connectivity_speed', 'connectivity_latency',) - # def get_country_name(self, instance): - # return instance.country.name - class DetailCountryDailyStatusSerializer(CountryDailyStatusSerializer): class Meta(CountryWeeklyStatus.Meta): @@ -417,9 +414,6 @@ class Meta(SchoolDailyStatus.Meta): model = SchoolDailyStatus fields = ('id', 'school_name', 'date', 'connectivity_speed', 'connectivity_latency',) - # def get_school_name(self, instance): - # return instance.school.name - class DetailSchoolDailyStatusSerializer(SchoolDailyStatusSerializer): class Meta(SchoolDailyStatus.Meta): diff --git a/proco/connection_statistics/tests/test_aggregates.py b/proco/connection_statistics/tests/test_aggregates.py index ef7cb24..a2112a2 100644 --- a/proco/connection_statistics/tests/test_aggregates.py +++ b/proco/connection_statistics/tests/test_aggregates.py @@ -28,11 +28,10 @@ class AggregateConnectivityDataTestCase(TestCase): - databases = ['default', 'read_only_database'] + databases = ['default',] + @classmethod def setUpTestData(cls): - # cls.databases = ['default'] - # cls.databases = ['default', 'read_only_database'] cls.country = CountryFactory() cls.school = SchoolFactory(country=cls.country) RealTimeConnectivityFactory(school=cls.school, connectivity_speed=4000000) diff --git a/proco/connection_statistics/tests/test_api.py b/proco/connection_statistics/tests/test_api.py index d803086..8fecc3f 100755 --- a/proco/connection_statistics/tests/test_api.py +++ b/proco/connection_statistics/tests/test_api.py @@ -24,7 +24,7 @@ class GlobalStatisticsApiTestCase(TestAPIViewSetMixin, TestCase): - databases = ['default', 'read_only_database'] + databases = ['default',] @classmethod def setUpTestData(cls): @@ -56,7 +56,7 @@ def test_global_stats(self): ['connected', 'not_connected', 'unknown']) def test_global_stats_queries(self): - with self.assertNumQueries(7): + with self.assertNumQueries(2): self.forced_auth_req( 'get', reverse('connection_statistics:global-stat'), @@ -788,7 +788,6 @@ def test_country_download_connectivity_stat(self): }) response = self.forced_auth_req('get', url, user=self.user, view=view) - # print(response.data) self.assertEqual(response.status_code, status.HTTP_200_OK) response_data = response.data @@ -815,7 +814,6 @@ def test_country_download_connectivity_stat_data(self): }) response = self.forced_auth_req('get', url, user=self.user, view=view) - # print(response.data) self.assertEqual(response.status_code, status.HTTP_200_OK) # self.assertEqual(response.data[0]['schools_total'], self.stat_one.schools_total) # self.assertEqual(response.data[0]['school_with_realtime_data'], self.stat_one.schools_connected) @@ -834,7 +832,6 @@ def test_country_download_connectivity_stat_for_invalid_country_id(self): }) response = self.forced_auth_req('get', url, user=self.user, view=view) - # print(response.data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_country_download_connectivity_stat_for_invalid_date_range(self): @@ -884,7 +881,6 @@ def test_country_download_connectivity_stat_for_national_benchmark(self): }) response = self.forced_auth_req('get', url, user=self.user, view=view) - # print(response.data) self.assertEqual(response.status_code, status.HTTP_200_OK) # self.assertEqual(response.data['schools_total'], self.stat_one.schools_total) @@ -995,7 +991,6 @@ def test_get_country_coverage_stats(self): url = reverse('connection_statistics:country-coverage-stat') query_params = {'country_id': self.country_one.id} response = self.client.get(url, query_params) - # print(response.data) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['total_schools'], self.stat_one.schools_total) # self.assertEqual(response.data['connected_schools']['5g_4g'], self.stat_one.schools_coverage_good) diff --git a/proco/connection_statistics/tests/test_models.py b/proco/connection_statistics/tests/test_models.py index cef208b..6c2d310 100644 --- a/proco/connection_statistics/tests/test_models.py +++ b/proco/connection_statistics/tests/test_models.py @@ -51,7 +51,7 @@ def setUpTestData(cls): cls.school_weekly = SchoolWeeklyStatusFactory( school=cls.school, connectivity=True, connectivity_speed=3 * (10 ** 6), - coverage_availability=True, coverage_type='3g', + coverage_availability=True, coverage_type='4g', ) cls.school.last_weekly_status = cls.school_weekly diff --git a/proco/connection_statistics/utils.py b/proco/connection_statistics/utils.py index 052e703..4a4f497 100644 --- a/proco/connection_statistics/utils.py +++ b/proco/connection_statistics/utils.py @@ -411,7 +411,6 @@ def update_country_data_source_by_csv_filename(imported_file): def get_benchmark_value_for_default_download_layer(benchmark, country_id): data_layer_instance = DataLayer.objects.filter( - # name__icontains='download', type=DataLayer.LAYER_TYPE_LIVE, category=DataLayer.LAYER_CATEGORY_CONNECTIVITY, status=DataLayer.LAYER_STATUS_PUBLISHED, @@ -427,17 +426,16 @@ def get_benchmark_value_for_default_download_layer(benchmark, country_id): benchmark_val = data_layer_instance.global_benchmark.get('value') benchmark_unit = data_layer_instance.global_benchmark.get('unit') - if benchmark == 'national': - if country_id: - benchmark_metadata = Country.objects.all().filter( - id=country_id, - benchmark_metadata__isnull=False, - ).order_by('id').values_list('benchmark_metadata', flat=True).first() - - if benchmark_metadata and len(benchmark_metadata) > 0: - benchmark_metadata = json.loads(benchmark_metadata) - all_live_layers = benchmark_metadata.get('live_layer', {}) - if len(all_live_layers) > 0 and str(data_layer_instance.id) in (all_live_layers.keys()): - benchmark_val = all_live_layers[str(data_layer_instance.id)] + if benchmark == 'national' and country_id: + benchmark_metadata = Country.objects.all().filter( + id=country_id, + benchmark_metadata__isnull=False, + ).order_by('id').values_list('benchmark_metadata', flat=True).first() + + if benchmark_metadata and len(benchmark_metadata) > 0: + benchmark_metadata = json.loads(benchmark_metadata) + all_live_layers = benchmark_metadata.get('live_layer', {}) + if len(all_live_layers) > 0 and str(data_layer_instance.id) in (all_live_layers.keys()): + benchmark_val = all_live_layers[str(data_layer_instance.id)] return convert_to_int(str(benchmark_val), default='20000000'), benchmark_unit diff --git a/proco/contact/tests/test_api.py b/proco/contact/tests/test_api.py index 1c53a56..32344ba 100644 --- a/proco/contact/tests/test_api.py +++ b/proco/contact/tests/test_api.py @@ -10,11 +10,10 @@ class ContactAPITestCase(TestAPIViewSetMixin, TestCase): base_view = 'contact:' - databases = {'read_only_database', 'default'} + databases = {'default', } @classmethod def setUpTestData(cls): - # self.databases = 'default' cls.email = 'test@test.com' cls.password = 'SomeRandomPass96' cls.user = auth_models.ApplicationUser.objects.create_user(username=cls.email, password=cls.password) diff --git a/proco/core/config.py b/proco/core/config.py index 144d207..410330b 100644 --- a/proco/core/config.py +++ b/proco/core/config.py @@ -1,4 +1,9 @@ """ Config file to specify application configurations used in the PROCO app""" +import json +import os +from django.conf import settings + +FILTERS_FIELDS = None class AppConfig(object): @@ -46,5 +51,21 @@ def mobile_number_length(self): """Length of valid mobile number""" return 10 + @property + def get_giga_filter_fields(self): + global FILTERS_FIELDS + if FILTERS_FIELDS is None: + filter_fields = {} + filters_data = settings.FILTERS_DATA + for data in filters_data: + parameter = data['parameter'] + table_filters = filter_fields.get(parameter['table'], []) + table_filters.append(parameter['field'] + '__' + parameter['filter']) + if data.get('include_none_filter', False): + table_filters.append(parameter['field'] + '__none_' + parameter['filter']) + filter_fields[parameter['table']] = table_filters + FILTERS_FIELDS = filter_fields + return FILTERS_FIELDS + app_config = AppConfig() diff --git a/proco/core/db_utils.py b/proco/core/db_utils.py index 70f79f1..44238be 100644 --- a/proco/core/db_utils.py +++ b/proco/core/db_utils.py @@ -1,5 +1,9 @@ +import logging + from django.db import connection +logger = logging.getLogger('gigamaps.' + __name__) + def dictfetchall(cursor): """ @@ -11,7 +15,7 @@ def dictfetchall(cursor): def sql_to_response(sql, label=''): - print('Query to execute for "{0}": {1}'.format(label, sql.replace('\n', ''))) + logger.debug('Query to execute for "{0}": {1}'.format(label, sql.replace('\n', ''))) try: with connection.cursor() as cur: cur.execute(sql) @@ -19,5 +23,5 @@ def sql_to_response(sql, label=''): return return dictfetchall(cur) except Exception as ex: - print('ERROR: Exception on query execution - {0}'.format(str(ex))) + logger.error('Exception on query execution - {0}'.format(str(ex))) return diff --git a/proco/core/filters.py b/proco/core/filters.py new file mode 100644 index 0000000..e4de7b1 --- /dev/null +++ b/proco/core/filters.py @@ -0,0 +1,11 @@ +import logging +import socket + + +class HostInfoFilter(logging.Filter): + + def filter(self, record): + record.hostname = socket.gethostname() + record.hostip = socket.gethostbyname(record.hostname) + + return True diff --git a/proco/core/management/commands/create_admin_user.py b/proco/core/management/commands/create_admin_user.py index d745dde..48bff57 100644 --- a/proco/core/management/commands/create_admin_user.py +++ b/proco/core/management/commands/create_admin_user.py @@ -1,12 +1,16 @@ -# encoding: utf-8 from __future__ import absolute_import, division, print_function, unicode_literals +import logging +# encoding: utf-8 + from django.core.management.base import BaseCommand from django.core.validators import validate_email from django.utils import timezone from proco.custom_auth.models import Role, ApplicationUser, UserRoleRelationship +logger = logging.getLogger('gigamaps.' + __name__) + def create_user_role_relationship(user, role_name): role = Role.objects.get(name=role_name) @@ -15,7 +19,7 @@ def create_user_role_relationship(user, role_name): def valid_email(value): - print('Validating: {0}'.format(value)) + logger.debug('Validating email: {0}'.format(value)) validate_email(value) return value @@ -43,7 +47,7 @@ def add_arguments(self, parser): def handle(self, **options): user_email = options.get('user_email') - print('*** User create/update operation STARTED ({0}) ***'.format(user_email)) + logger.debug('User create/update operation started ({0})'.format(user_email)) user_instance, created = ApplicationUser.objects.update_or_create( username=user_email, @@ -58,6 +62,6 @@ def handle(self, **options): }, ) - print(user_instance.__dict__) + logger.debug(user_instance.__dict__) create_user_role_relationship(user_instance, Role.SYSTEM_ROLE_NAME_ADMIN) - print('*** User create/update operation ENDED ({0}) ***'.format(user_email)) + logger.debug('User create/update operation ended ({0})'.format(user_email)) diff --git a/proco/core/management/commands/create_api_key_with_write_access.py b/proco/core/management/commands/create_api_key_with_write_access.py index 35f9974..cb17e8b 100644 --- a/proco/core/management/commands/create_api_key_with_write_access.py +++ b/proco/core/management/commands/create_api_key_with_write_access.py @@ -1,3 +1,5 @@ +import logging + from django.core.management import call_command from django.core.management.base import BaseCommand from django.core.validators import validate_email @@ -9,16 +11,18 @@ from proco.custom_auth.models import ApplicationUser from proco.utils import dates as date_utilities +logger = logging.getLogger('gigamaps.' + __name__) + def get_user(email, force_user, first_name, last_name, inactive_email): - print('Validating: {0}'.format(email)) + logger.debug('Validating: {0}'.format(email)) validate_email(email) application_user = ApplicationUser.objects.all().annotate(email_lower=Lower('email')).filter( email_lower=str(email).lower()).first() if not application_user and force_user: - print('Creating the superuser as user with given email does not exist.') + logger.info('Creating the superuser as user with given email does not exist.') args = ['-email={0}'.format(email)] if first_name: args.append('-first_name={0}'.format(first_name)) @@ -32,10 +36,10 @@ def get_user(email, force_user, first_name, last_name, inactive_email): application_user = ApplicationUser.objects.all().annotate(email_lower=Lower('email')).filter( email_lower=str(email).lower()).first() elif application_user: - print('User with given email already exists.') + logger.info('User with given email already exists.') else: - print('ERROR: User with give email address is not present in the system. ' - 'To force this user, please pass --force_user argument.') + logger.error('User with give email address is not present in the system. ' + 'To force this user, please pass --force_user argument.') exit(0) return application_user @@ -82,7 +86,7 @@ def add_arguments(self, parser): ) def handle(self, **options): - print('Creating API Key with write access....') + logger.info('Creating API Key with write access.') user_email = options.get('user_email') force_user = options.get('force_user') @@ -109,7 +113,7 @@ def handle(self, **options): api_key_instance.write_access_reason = reason api_key_instance.valid_to = valid_till_date api_key_instance.save(update_fields=('write_access_reason', 'valid_to',)) - print('API Key with write access updated successfully!\n') + logger.info('Api key with write access updated successfully!\n') else: api_key_instance = accounts_models.APIKey.objects.create( api=get_object_or_404(accounts_models.API.objects.all(), code=api_code), @@ -120,7 +124,6 @@ def handle(self, **options): has_write_access=True, write_access_reason=reason, ) - print('API Key with write access created successfully!\n') + logger.info('Api key with write access created successfully!\n') - print('API Key: {0}'.format(api_key_instance.api_key)) - print('\n') + logger.debug('Api key: {0}'.format(api_key_instance.api_key)) diff --git a/proco/core/management/commands/data_alteration_through_sql.py b/proco/core/management/commands/data_alteration_through_sql.py index e71be20..58f6f26 100644 --- a/proco/core/management/commands/data_alteration_through_sql.py +++ b/proco/core/management/commands/data_alteration_through_sql.py @@ -1,3 +1,5 @@ +import logging + from collections import OrderedDict from django.core.management.base import BaseCommand @@ -5,10 +7,12 @@ from django.db import transaction from django.utils import timezone +logger = logging.getLogger('gigamaps.' + __name__) + @transaction.atomic def create_and_execute_update_query(value, data_dict_list): - print('Executing update statement for: {0} records'.format(len(data_dict_list))) + logger.debug('Executing update statement for: {0} records'.format(len(data_dict_list))) # create update query stmt = ("UPDATE public.connection_statistics_schooldailystatus " @@ -16,13 +20,13 @@ def create_and_execute_update_query(value, data_dict_list): with connection.cursor() as cursor: for data_dict in data_dict_list: update_query = stmt.format(value=value, school_id=data_dict['school_id']) - print('Current Update Query: {}'.format(update_query)) + logger.debug('Current update query: {}'.format(update_query)) cursor.execute(update_query) @transaction.atomic def create_and_execute_update_query_v2(stmt): - print('Current Update Query: {}'.format(stmt)) + logger.debug('Current update query: {}'.format(stmt)) with connection.cursor() as cursor: cursor.execute(stmt) @@ -40,7 +44,7 @@ def populate_live_data_source_as_qos(start_school_id, end_school_id): create_and_execute_update_query_v2(query) te = timezone.now() - print('Executed the function in {} seconds'.format((te - ts).seconds)) + logger.debug('Executed the function in {} seconds'.format((te - ts).seconds)) query = """ SELECT DISTINCT s.id AS school_id @@ -63,8 +67,8 @@ def populate_live_data_source_as_qos(start_school_id, end_school_id): query = query.format(where_condition=where_condition) - print('Getting select statement query result from "schools_school" table for live_data_source records.') - print('Query: {}'.format(query)) + logger.info('Getting select statement query result from "schools_school" table for live_data_source records.') + logger.debug('Query: {}'.format(query)) data_list = [] with connection.cursor() as cursor: @@ -79,7 +83,7 @@ def populate_live_data_source_as_qos(start_school_id, end_school_id): create_and_execute_update_query('QOS', data_list) te2 = timezone.now() - print('Executed the function in {} seconds'.format((te2 - te).seconds)) + logger.debug('Executed the function in {} seconds'.format((te2 - te).seconds)) def populate_live_data_source_as_daily_check_app(start_school_id, end_school_id): @@ -95,7 +99,7 @@ def populate_live_data_source_as_daily_check_app(start_school_id, end_school_id) create_and_execute_update_query_v2(query) te = timezone.now() - print('Executed the function in {} seconds'.format((te - ts).seconds)) + logger.debug('Executed the function in {} seconds'.format((te - ts).seconds)) query = """ SELECT DISTINCT s.id AS school_id @@ -118,8 +122,8 @@ def populate_live_data_source_as_daily_check_app(start_school_id, end_school_id) query = query.format(where_condition=where_condition) - print('Getting select statement query result from "schools_school" table for live_data_source records.') - print('Query: {}'.format(query)) + logger.info('Getting select statement query result from "schools_school" table for live_data_source records.') + logger.debug('Query: {}'.format(query)) data_list = [] with connection.cursor() as cursor: @@ -134,7 +138,7 @@ def populate_live_data_source_as_daily_check_app(start_school_id, end_school_id) create_and_execute_update_query('DAILY_CHECK_APP_MLAB', data_list) te2 = timezone.now() - print('Executed the function in {} seconds'.format((te2 - te).seconds)) + logger.debug('Executed the function in {} seconds'.format((te2 - te).seconds)) class Command(BaseCommand): @@ -168,11 +172,11 @@ def handle(self, **options): end_school_id = options.get('end_school_id') if options.get('update_brasil_live_data_source', False): - print('update_brasil_live_data_source - START') + logger.info('Update brasil live data source - start') populate_live_data_source_as_qos(start_school_id, end_school_id) if options.get('update_non_brasil_live_data_source_name', False): - print('update_non_brasil_live_data_source_name - START') + logger.info('Update non brasil live data source name - start') populate_live_data_source_as_daily_check_app(start_school_id, end_school_id) - print('Data updated successfully!\n') + logger.info('Data updated successfully!\n') diff --git a/proco/core/management/commands/data_cleanup.py b/proco/core/management/commands/data_cleanup.py index 006fe0e..42df7e0 100644 --- a/proco/core/management/commands/data_cleanup.py +++ b/proco/core/management/commands/data_cleanup.py @@ -1,3 +1,5 @@ +import logging + from datetime import timedelta from django.core.management.base import BaseCommand @@ -13,6 +15,8 @@ from proco.utils.dates import get_current_year from proco.utils.tasks import redo_aggregations_task, populate_school_new_fields_task +logger = logging.getLogger('gigamaps.' + __name__) + def delete_duplicate_schools_based_on_giga_id(): # updated deleted time if multiple school has same deleted datetime @@ -20,9 +24,9 @@ def delete_duplicate_schools_based_on_giga_id(): 'country_id', 'giga_id_school', 'deleted').annotate( total_records=Count('id', distinct=False), ).order_by('-total_records', 'country_id', 'giga_id_school', 'deleted').filter(total_records__gt=1)[:10000] - print('Queryset to get max 10K records to update the deleted time ' - 'where more than 1 record has same Country, School Giga ID and deleted datetime ' - 'in School table: {0}'.format(rows_with_more_than_1_records.query)) + logger.debug('Queryset to get max 10K records to update the deleted time ' + 'where more than 1 record has same Country, School Giga ID and deleted datetime ' + 'in School table: {0}'.format(rows_with_more_than_1_records.query)) for row in rows_with_more_than_1_records: count = 1 @@ -30,7 +34,7 @@ def delete_duplicate_schools_based_on_giga_id(): country_id=row['country_id'], giga_id_school=row['giga_id_school'], ).order_by('-id')[1:]: - print('Deletion for: Id - {0}, Country ID - {1}, Giga ID - {2}'.format( + logger.debug('Deletion for: Id - {0}, Country ID - {1}, Giga ID - {2}'.format( deleted_row.id, deleted_row.country_id, deleted_row.giga_id_school)) deleted_row.deleted = get_current_datetime_object() + timedelta(minutes=count) deleted_row.save(update_fields=('deleted',)) @@ -41,16 +45,15 @@ def delete_duplicate_schools_based_on_giga_id(): 'country_id', 'giga_id_school').annotate( total_records=Count('id', distinct=False), ).order_by('-total_records', 'country_id', 'giga_id_school').filter(total_records__gt=1)[:10000] - print('Queryset to get max 10K records to delete where more than 1 record has same ' - 'Country and School Giga ID in School table: {0}'.format(rows_with_more_than_1_records.query)) + logger.debug('Queryset to get max 10K records to delete where more than 1 record has same ' + 'Country and School Giga ID in School table: {0}'.format(rows_with_more_than_1_records.query)) for row in rows_with_more_than_1_records: - count = 1 for row_to_delete in School.objects.all().filter( country_id=row['country_id'], giga_id_school=row['giga_id_school'], ).order_by('-id')[1:]: - print('Deletion for: Id - {0}, Country ID - {1}, Giga ID - {2}'.format( + logger.debug('Deletion for: Id - {0}, Country ID - {1}, Giga ID - {2}'.format( row_to_delete.id, row_to_delete.country_id, row_to_delete.giga_id_school)) row_to_delete.delete() @@ -60,8 +63,9 @@ def delete_duplicate_schools_based_on_external_id(): 'country_id', 'external_id').annotate( total_records=Count('id', distinct=False), ).order_by('-total_records', 'country_id', 'external_id').filter(total_records__gt=1)[:10000] - print('Queryset to get max 10K records to delete where more than 1 record has same Country and School External ID ' - 'in School table: {0}'.format(rows_with_more_than_1_records.query)) + logger.debug( + 'Queryset to get max 10K records to delete where more than 1 record has same Country and School External ID ' + 'in School table: {0}'.format(rows_with_more_than_1_records.query)) # for row in rows_with_more_than_1_records: # for row_to_delete in School.objects.filter( @@ -79,8 +83,8 @@ def delete_duplicate_school_weekly_records(): 'school_id', 'week', 'year').annotate( total_records=Count('school_id', distinct=False), ).order_by('-total_records', 'school_id', 'week', 'year').filter(total_records__gt=1)[:10000] - print('Queryset to get max 10K records to delete where more than 1 record has same Year, Week and ' - 'School ID in School Weekly table: {0}'.format(rows_with_more_than_1_records.query)) + logger.debug('Queryset to get max 10K records to delete where more than 1 record has same Year, Week and ' + 'School ID in School Weekly table: {0}'.format(rows_with_more_than_1_records.query)) for row in rows_with_more_than_1_records: school_id = row['school_id'] @@ -97,24 +101,25 @@ def delete_duplicate_school_weekly_records(): ).values_list('id', flat=True).order_by('id')) if last_weekly_id in school_weekly_ids_to_delete: - print('School Last Weekly Status id ({0}) is IN the deletion list. ' - 'Hence skipping the current record and deleting all remaining.'.format(last_weekly_id)) + logger.debug('School Last Weekly Status id ({0}) is IN the deletion list. ' + 'Hence skipping the current record and deleting all remaining.'.format(last_weekly_id)) for row_to_delete in statistics_models.SchoolWeeklyStatus.objects.filter( id__in=school_weekly_ids_to_delete, ).exclude(id=last_weekly_id).order_by('-id'): - print('Deletion for: Id - {0}, Year - {1}, Week - {2}, School Id - {3}'.format( + logger.debug('Deletion for: Id - {0}, Year - {1}, Week - {2}, School Id - {3}'.format( row_to_delete.id, row_to_delete.year, row_to_delete.week, row_to_delete.school_id)) # Hard deletion row_to_delete.delete(force=True) else: - print('School Last Weekly Status id ({0}) is NOT IN the deletion list. ' - 'Hence skipping first record and deleting all remaining based on ID DESC.'.format(last_weekly_id)) + logger.debug('School Last Weekly Status id ({0}) is NOT IN the deletion list. ' + 'Hence skipping first record and deleting all remaining based on ID DESC.'.format( + last_weekly_id)) for row_to_delete in statistics_models.SchoolWeeklyStatus.objects.filter( school_id=row['school_id'], week=row['week'], year=row['year'], ).order_by('-id')[1:]: - print('Deletion for: Id - {0}, Year - {1}, Week - {2}, School Id - {3}'.format( + logger.debug('Deletion for: Id - {0}, Year - {1}, Week - {2}, School Id - {3}'.format( row_to_delete.id, row_to_delete.year, row_to_delete.week, row_to_delete.school_id)) # Hard deletion row_to_delete.delete(force=True) @@ -125,8 +130,9 @@ def delete_duplicate_school_daily_records(): 'school_id', 'date', 'live_data_source').annotate( total_records=Count('school_id', distinct=False), ).order_by('-total_records', 'school_id', 'date', 'live_data_source').filter(total_records__gt=1)[:10000] - print('Queryset to get max 10K records to delete where more than 1 record has same Date, Live Data Source and ' - 'School ID in School Daily table: {0}'.format(rows_with_more_than_1_records.query)) + logger.debug( + 'Queryset to get max 10K records to delete where more than 1 record has same Date, Live Data Source and ' + 'School ID in School Daily table: {0}'.format(rows_with_more_than_1_records.query)) for row in rows_with_more_than_1_records: for row_to_delete in statistics_models.SchoolDailyStatus.objects.filter( @@ -134,7 +140,7 @@ def delete_duplicate_school_daily_records(): date=row['date'], live_data_source=row['live_data_source'], ).order_by('-id')[1:]: - print('Deletion for: Id - {0}, Date - {1}, Data Source - {2}, School Id - {3}'.format( + logger.debug('Deletion for: Id - {0}, Date - {1}, Data Source - {2}, School Id - {3}'.format( row_to_delete.id, row_to_delete.date, row_to_delete.live_data_source, row_to_delete.school_id)) # Hard deletion row_to_delete.delete(force=True) @@ -145,8 +151,9 @@ def delete_duplicate_country_daily_records(): 'country_id', 'date', 'live_data_source').annotate( total_records=Count('country_id', distinct=False), ).order_by('-total_records', 'country_id', 'date', 'live_data_source').filter(total_records__gt=1)[:10000] - print('Queryset to get max 10K records to delete where more than 1 record has same Date, Live Data Source and ' - 'Country ID in Country Daily table: {0}'.format(rows_with_more_than_1_records.query)) + logger.debug( + 'Queryset to get max 10K records to delete where more than 1 record has same Date, Live Data Source and ' + 'Country ID in Country Daily table: {0}'.format(rows_with_more_than_1_records.query)) for row in rows_with_more_than_1_records: for row_to_delete in statistics_models.CountryDailyStatus.objects.filter( @@ -154,7 +161,7 @@ def delete_duplicate_country_daily_records(): date=row['date'], live_data_source=row['live_data_source'], ).order_by('-id')[1:]: - print('Deletion for: Id - {0}, Date - {1}, Data Source - {2}, Country Id - {3}'.format( + logger.debug('Deletion for: Id - {0}, Date - {1}, Data Source - {2}, Country Id - {3}'.format( row_to_delete.id, row_to_delete.date, row_to_delete.live_data_source, row_to_delete.country_id)) # Hard deletion row_to_delete.delete(force=True) @@ -165,15 +172,15 @@ def delete_duplicate_qos_model_records(): 'school_id', 'timestamp').annotate( total_records=Count('school_id'), ).order_by('-total_records', 'school_id', 'timestamp').filter(total_records__gt=1) - print('Queryset to get records to delete where more than 1 record has same School and Timestamp in ' - 'QoS Data table: {0}'.format(rows_with_more_than_1_records.query)) + logger.debug('Queryset to get records to delete where more than 1 record has same school and timestamp in ' + 'QoS Data table: {0}'.format(rows_with_more_than_1_records.query)) for row in rows_with_more_than_1_records: for row_to_delete in QoSData.objects.filter( school_id=row['school_id'], timestamp=row['timestamp'], ).order_by('-version')[1:]: - print('Deletion for: Id - {0}, Timestamp - {1}, School Id - {2}'.format( + logger.debug('Deletion for: Id - {0}, Timestamp - {1}, School Id - {2}'.format( row_to_delete.id, row_to_delete.timestamp, row_to_delete.school_id)) # Hard deletion row_to_delete.delete() @@ -184,8 +191,8 @@ def delete_duplicate_country_weekly_records(): 'country_id', 'week', 'year').annotate( total_records=Count('country_id', distinct=False), ).order_by('-total_records', 'country_id', 'week', 'year').filter(total_records__gt=1)[:10000] - print('Queryset to get max 10K records to delete where more than 1 record has same Year, Week and ' - 'Country ID in Country Weekly table: {0}'.format(rows_with_more_than_1_records.query)) + logger.debug('Queryset to get max 10K records to delete where more than 1 record has same Year, Week and ' + 'Country ID in Country Weekly table: {0}'.format(rows_with_more_than_1_records.query)) for row in rows_with_more_than_1_records: country_id = row['country_id'] @@ -202,24 +209,25 @@ def delete_duplicate_country_weekly_records(): ).values_list('id', flat=True).order_by('id')) if last_weekly_id in country_weekly_ids_to_delete: - print('Country Last Weekly Status id ({0}) is IN the deletion list. ' - 'Hence skipping the current record and deleting all remaining.'.format(last_weekly_id)) + logger.debug('Country Last Weekly Status id ({0}) is IN the deletion list. ' + 'Hence skipping the current record and deleting all remaining.'.format(last_weekly_id)) for row_to_delete in statistics_models.CountryWeeklyStatus.objects.filter( id__in=country_weekly_ids_to_delete, ).exclude(id=last_weekly_id).order_by('-id'): - print('Deletion for: Id - {0}, Year - {1}, Week - {2}, Country Id - {3}'.format( + logger.debug('Deletion for: Id - {0}, Year - {1}, Week - {2}, Country Id - {3}'.format( row_to_delete.id, row_to_delete.year, row_to_delete.week, row_to_delete.country_id)) # Hard deletion row_to_delete.delete(force=True) else: - print('Country Last Weekly Status id ({0}) is NOT IN the deletion list. ' - 'Hence skipping first record and deleting all remaining based on ID DESC.'.format(last_weekly_id)) + logger.debug('Country Last Weekly Status id ({0}) is NOT IN the deletion list. ' + 'Hence skipping first record and deleting all remaining based on ID DESC.'.format( + last_weekly_id)) for row_to_delete in statistics_models.CountryWeeklyStatus.objects.filter( country_id=row['country_id'], week=row['week'], year=row['year'], ).order_by('-id')[1:]: - print('Deletion for: Id - {0}, Year - {1}, Week - {2}, Country Id - {3}'.format( + logger.debug('Deletion for: Id - {0}, Year - {1}, Week - {2}, Country Id - {3}'.format( row_to_delete.id, row_to_delete.year, row_to_delete.week, row_to_delete.country_id)) # Hard deletion row_to_delete.delete(force=True) @@ -235,15 +243,15 @@ def delete_duplicate_school_records(): ).exclude( giga_id_school='', ) - print('Queryset to get records to delete where more than 1 record has same Giga ID and ' - 'Country ID in School table: {0}'.format(rows_with_more_than_1_records.query)) + logger.debug('Queryset to get records to delete where more than 1 record has same Giga ID and ' + 'Country ID in School table: {0}'.format(rows_with_more_than_1_records.query)) for row in rows_with_more_than_1_records: for row_to_delete in School.objects.filter( country_id=row['country_id'], giga_id_school=row['giga_id_school'], ).order_by('-id')[1:]: - print('Deletion for: Id - {0}, Country ID - {1}, Giga ID - {2}'.format( + logger.debug('Deletion for: Id - {0}, Country ID - {1}, Giga ID - {2}'.format( row_to_delete.id, row_to_delete.country_id, row_to_delete.giga_id_school)) # Hard deletion may fail row_to_delete.delete() @@ -340,7 +348,7 @@ def update_school_giga_ids(): for tok in row.split(',') ] file_data.append(dict(zip(headers, row_data))) - print(file_data) + logger.debug(file_data) for data in file_data: School.objects.filter( country_id=data['country_id'], @@ -470,8 +478,8 @@ def add_arguments(self, parser): ) def handle(self, **options): - print('Executing "data_cleanup" utility ....\n') - print('Options: {}\n\n'.format(options)) + logger.info('Executing data cleanup utility.\n') + logger.debug('Options: {}\n\n'.format(options)) country_id = options.get('country_id', None) start_school_id = options.get('start_school_id', None) @@ -479,57 +487,57 @@ def handle(self, **options): week_no = options.get('week_no', None) if options.get('clean_duplicate_school_gigs_ids'): - print('Performing School Duplicate record cleanup base on Giga ID and Country ID.') + logger.info('Performing school duplicate record cleanup base on giga ID and country ID.') delete_duplicate_schools_based_on_giga_id() - print('Completed School Duplicate record cleanup base on Giga ID and Country ID.\n\n') + logger.info('Completed school duplicate record cleanup base on giga ID and country ID.\n\n') if options.get('clean_duplicate_school_external_ids'): - print('Performing School Duplicate record cleanup base on External ID and Country ID.') + logger.info('Performing school duplicate record cleanup base on external ID and country ID.') delete_duplicate_schools_based_on_external_id() - print('Completed School Duplicate record cleanup base on External ID and Country ID.\n\n') + logger.info('Completed school duplicate record cleanup base on external ID and country ID.\n\n') if options.get('clean_duplicate_school_weekly'): - print('Performing School Weekly Duplicate record cleanup.') + logger.info('Performing school weekly duplicate record cleanup.') delete_duplicate_school_weekly_records() - print('Completed School Weekly Duplicate record cleanup.\n\n') + logger.info('Completed school weekly duplicate record cleanup.\n\n') if options.get('clean_duplicate_school_daily'): - print('Performing School Daily Duplicate record cleanup.') + logger.info('Performing school daily duplicate record cleanup.') delete_duplicate_school_daily_records() - print('Completed School Daily Duplicate record cleanup.\n\n') + logger.info('Completed school daily duplicate record cleanup.\n\n') if options.get('clean_duplicate_country_weekly'): - print('Performing Country Weekly Duplicate record cleanup.') + logger.info('Performing country weekly wuplicate record cleanup.') delete_duplicate_country_weekly_records() - print('Completed Country Weekly Duplicate record cleanup.\n\n') + logger.info('Completed country weekly duplicate record cleanup.\n\n') if options.get('clean_duplicate_country_daily'): - print('Performing Country Daily Duplicate record cleanup.') + logger.info('Performing country daily duplicate record cleanup.') delete_duplicate_country_daily_records() - print('Completed Country Daily Duplicate record cleanup.\n\n') + logger.info('Completed country daily duplicate record cleanup.\n\n') if options.get('cleanup_qos_data_rows'): - print('Performing QoS Data Model Duplicate record cleanup.') + logger.info('Performing QoS data model duplicate record cleanup.') delete_duplicate_qos_model_records() - print('Completed QoS Data Model Duplicate record cleanup.\n\n') + logger.info('Completed QoS data model duplicate record cleanup.\n\n') if options.get('cleanup_school_master_rows'): - print('Performing School Master Data Source Duplicate record cleanup.') + logger.debinfoug('Performing school master data source duplicate record cleanup.') sources_tasks.cleanup_school_master_rows() - print('Completed School Master Data Source Duplicate record cleanup.\n\n') + logger.info('Completed school master data source duplicate record cleanup.\n\n') if options.get('clean_duplicate_schools'): - print('Performing School Duplicate record cleanup.') + logger.info('Performing school duplicate record cleanup.') delete_duplicate_school_records() - print('Completed School Duplicate record cleanup.\n\n') + logger.info('Completed school duplicate record cleanup.\n\n') if options.get('update_school_giga_ids'): - print('Performing School Giga ID update.') + logger.info('Performing school giga ID update.') update_school_giga_ids() - print('Completed School Giga ID update.\n\n') + logger.info('Completed school giga ID update.\n\n') if options.get('handle_published_school_master_data_row'): - print('Performing School Master Data Source Publish task handling.') + logger.info('Performing school master data source publish task handling.') if country_id: sources_tasks.handle_published_school_master_data_row(country_ids=[country_id, ]) @@ -540,7 +548,7 @@ def handle(self, **options): for row in new_published_records: sources_tasks.handle_published_school_master_data_row(published_row=row) - print('Completed School Master Data Source Publish task handling.\n\n') + logger.info('Completed school master data source publish task handling.\n\n') if options.get('handle_published_school_master_data_row_with_schedular'): sources_tasks.handle_published_school_master_data_row.delay(country_ids=[country_id, ]) @@ -572,9 +580,9 @@ def handle(self, **options): country_id_vs_year_qs = country_id_vs_year_qs.filter(year=options.get('year')) country_id_vs_year_qs = country_id_vs_year_qs.filter(year__lte=get_current_year(), ) - print('Query to select Country and Year for scheduling: {}\n\n'.format(country_id_vs_year_qs.query)) + logger.debug('Query to select country and year for scheduling: {}\n\n'.format(country_id_vs_year_qs.query)) for country_year in country_id_vs_year_qs: # redo_aggregations_task(country_year[0], country_year[1], None) redo_aggregations_task.delay(country_year[0], country_year[1], week_no) - print('Completed "data_cleanup" successfully ....\n') + logger.info('Completed data cleanup successfully.\n') diff --git a/proco/core/management/commands/index_rebuild_schools.py b/proco/core/management/commands/index_rebuild_schools.py index 1eeb5e3..1c55770 100644 --- a/proco/core/management/commands/index_rebuild_schools.py +++ b/proco/core/management/commands/index_rebuild_schools.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, division, print_function, unicode_literals import time +import logging from azure.core.credentials import AzureKeyCredential from azure.search.documents import SearchClient @@ -16,6 +17,8 @@ from proco.locations.search_indexes import SchoolIndex from proco.schools.models import School +logger = logging.getLogger('gigamaps.' + __name__) + # Create a service client cognitive_search_settings = settings.AZURE_CONFIG.get('COGNITIVE_SEARCH') @@ -27,10 +30,10 @@ def delete_index(): try: result = admin_client.delete_index(SchoolIndex.Meta.index_name) - print('Index', SchoolIndex.Meta.index_name, 'Deleted') - print(result) + logger.debug('Index: ', SchoolIndex.Meta.index_name, 'Deleted') + logger.debug(result) except Exception as ex: - print(ex) + logger.error(ex) def create_index(): @@ -48,7 +51,7 @@ def create_index(): cors_options = CorsOptions(allowed_origins=['*'], max_age_in_seconds=24 * 60 * 60) scoring_profiles = [] - print('Index name: ', SchoolIndex.Meta.index_name) + logger.debug('Index name: ', SchoolIndex.Meta.index_name) index = SearchIndex( name=SchoolIndex.Meta.index_name, @@ -59,9 +62,9 @@ def create_index(): try: result = admin_client.create_index(index) - print('Index', result.name, 'created') + logger.debug('Index: ', result.name, 'created') except Exception as ex: - print(ex) + logger.error(ex) def clear_index(): @@ -69,11 +72,12 @@ def clear_index(): AzureKeyCredential(cognitive_search_settings['SEARCH_API_KEY'])) doc_counts = search_client.get_document_count() - print("There are {0} documents in the {1} search index.".format(doc_counts, repr(SchoolIndex.Meta.index_name))) + logger.debug("There are {0} documents in the {1} search index.".format( + doc_counts, repr(SchoolIndex.Meta.index_name))) if doc_counts > 0: all_docs = search_client.search('*') - print('All documents: {0}'.format(all_docs)) + logger.debug('All documents: {0}'.format(all_docs)) search_client.delete_documents(all_docs) @@ -112,7 +116,7 @@ def collect_data(country_id): del qry_data['admin2_id'] docs.append(qry_data) - print('Total records to upload: {0}'.format(len(docs))) + logger.debug('Total records to upload: {0}'.format(len(docs))) # docs = docs[0:100000] # print('Total records to upload: {0}'.format(len(docs))) return docs @@ -130,13 +134,14 @@ def upload_docs(search_client, headers, data_chunk, failed_data_chunks, count, r while retry_no <= 3 and not uploaded: try: result = search_client.upload_documents(documents=data_chunk, headers=headers) - print("Upload of new document SUCCEEDED for count '{0}' in retry no: '{1}': {2}".format( + logger.debug("Upload of new document succeeded for count '{0}' in retry no: '{1}': {2}".format( count, retry_no, result[0].succeeded) ) uploaded = True break except Exception as ex: - print("Upload of new document FAILED for count '{0}' in retry no: '{1}': {2}".format(count, retry_no, ex)) + logger.error( + "Upload of new document failed for count '{0}' in retry no: '{1}': {2}".format(count, retry_no, ex)) time.sleep(1.0) retry_no += 1 uploaded = upload_docs(search_client, headers, data_chunk, failed_data_chunks, count, retry_no=retry_no) @@ -160,7 +165,7 @@ def load_index(docs, batch_size=1000): for data_chunk in divide_chunks(docs, batch_size=batch_size): uploaded = upload_docs(search_client, headers, data_chunk, failed_data_chunks, count, retry_no=1) if not uploaded: - print('ERROR: Failed to upload the docs even after 3 retries. Please check error file for more details.') + logger.error('Failed to upload the docs even after 3 retries. Please check error file for more details.') failed_data_chunks.append(data_chunk) time.sleep(1.0) @@ -197,29 +202,29 @@ def add_arguments(self, parser): ) def handle(self, **options): - print('*** Index operations STARTED ({0}) ***'.format(SchoolIndex.Meta.index_name)) + logger.debug('Index operations STARTED ({0})'.format(SchoolIndex.Meta.index_name)) if settings.ENABLE_AZURE_COGNITIVE_SEARCH: country_id = options.get('country_id', False) if options.get('delete_index', False): - print('DELETE_INDEX - START') + logger.info('Delete index - Start') delete_index() if options.get('create_index', False): - print('CREATE_INDEX - START') + logger.info('Create index - Start') create_index() if options.get('clean_index', False): - print('CLEAR_INDEX - START') + logger.info('Clear index - Start') clear_index() if options.get('update_index', False): - print('COLLECT_INDEX_DATA - START') + logger.info('Collect index data - Start') if country_id: data_to_load = collect_data(country_id) if len(data_to_load) > 0: - print('LOAD_INDEX - START - {0}'.format(country_id)) + logger.debug('Load index - Start - {0}'.format(country_id)) load_index(data_to_load, batch_size=10000) else: all_countries = list( @@ -229,7 +234,7 @@ def handle(self, **options): data_to_load = collect_data(country_id) if len(data_to_load) > 0: - print('LOAD_INDEX - START - {0}'.format(country_id)) + logger.debug('Load index - Start - {0}'.format(country_id)) load_index(data_to_load, batch_size=10000) - print('*** Index operations ENDED ({0}) ***'.format(SchoolIndex.Meta.index_name)) + logger.debug('Index operations ENDED ({0})'.format(SchoolIndex.Meta.index_name)) diff --git a/proco/core/management/commands/load_about_us_content.py b/proco/core/management/commands/load_about_us_content.py index fd75e10..65dc505 100644 --- a/proco/core/management/commands/load_about_us_content.py +++ b/proco/core/management/commands/load_about_us_content.py @@ -1,11 +1,14 @@ import sys import traceback +import logging from django.core.management.base import BaseCommand from django.db import transaction from proco.about_us.models import AboutUs +logger = logging.getLogger('gigamaps.' + __name__) + about_us_content_json = [ { "text": [ @@ -595,7 +598,7 @@ def load_data_sources_data(): AboutUs.objects.all().delete() sys.stdout.write('\nDelete all old record') except: - print(traceback.format_exc()) + logger.error(traceback.format_exc()) for row_data in about_us_content_json: try: @@ -605,7 +608,7 @@ def load_data_sources_data(): else: sys.stdout.write('\nExisting About Us content updated: {}'.format(instance.__dict__)) except: - print(traceback.format_exc()) + logger.error(traceback.format_exc()) class Command(BaseCommand): diff --git a/proco/core/management/commands/load_country_admin_data.py b/proco/core/management/commands/load_country_admin_data.py index 2bf9fdd..b1a7701 100644 --- a/proco/core/management/commands/load_country_admin_data.py +++ b/proco/core/management/commands/load_country_admin_data.py @@ -1,6 +1,7 @@ import json import os import sys +import logging import numpy as np import pandas as pd @@ -11,6 +12,8 @@ from proco.core import utils as core_utilities from proco.locations.models import Country, CountryAdminMetadata +logger = logging.getLogger('gigamaps.' + __name__) + def is_file(fp): if not os.path.isfile(fp): @@ -43,14 +46,15 @@ def load_admin0_file_data(file_path): csv_required_cols = ['iso31661', 'iso31661alpha3', 'name', 'nameen', 'description', 'centroid', 'bbox', 'mapboxid'] core_utilities.column_normalize(input_df, valid_columns=csv_required_cols) - print('CSV normalized columns: {0}'.format(input_df.columns.tolist())) + logger.debug('Csv normalized columns: {0}.'.format(input_df.columns.tolist())) input_df.drop_duplicates(subset=['iso31661', 'iso31661alpha3'], keep='last', inplace=True) country_codes = dict(Country.objects.all().annotate(code_lower=Lower('code')).values_list('code_lower', 'id')) - country_iso3_codes = dict(Country.objects.all().annotate(iso3_format_lower=Lower('iso3_format')).values_list('iso3_format_lower', 'id')) - print('Country Code mapping: {0}'.format(country_codes)) - print('Country ISO3 Code mapping: {0}'.format(country_iso3_codes)) + country_iso3_codes = dict( + Country.objects.all().annotate(iso3_format_lower=Lower('iso3_format')).values_list('iso3_format_lower', 'id')) + logger.debug('Country code mapping: {0}'.format(country_codes)) + logger.debug('Country ISO3 code mapping: {0}'.format(country_iso3_codes)) input_df['errors'] = None @@ -69,20 +73,20 @@ def load_admin0_file_data(file_path): errors.append('Name field is required') if len(errors) > 0: - print('Errors: ', errors, ', Code: ', row['iso31661'], ', Country Id: ', country_id) + logger.debug('Errors: ', errors, ', Code: ', row['iso31661'], ', Country Id: ', country_id) has_data_errors = True input_df.at[index, 'errors'] = ','.join(errors) - print('Has data errors: {0}'.format(has_data_errors)) + logger.debug('Has data errors: {0}'.format(has_data_errors)) if has_data_errors: error_file = '_errors.'.join(get_file_name_and_extension(file_path)) - print('ERROR: CSV has data errors. Please check the error file, correct it and then start again.' - ' Error file: {0}'.format(error_file)) + logger.error('Csv has data errors. Please check the error file, correct it and then start again.' + ' Error file: {0}'.format(error_file)) input_df.to_csv(error_file, quotechar='"', index=False) return - print('SUCCESS: Validation has passed by the input file.') + logger.info('Success: Validation has passed by the input file.') input_df = input_df.replace(np.nan, None) rows = input_df.to_dict(orient='records') @@ -116,16 +120,16 @@ def load_admin1_file_data(file_path): core_utilities.column_normalize(input_df, valid_columns=csv_required_cols) - print('CSV normalized columns: {0}'.format(input_df.columns.tolist())) + logger.debug('Csv normalized columns: {0}'.format(input_df.columns.tolist())) input_df.drop_duplicates(subset=['iso31661', 'iso31661alpha3', 'admin1idgiga'], keep='last', inplace=True) country_codes = dict(Country.objects.all().annotate(code_lower=Lower('code')).values_list('code_lower', 'id')) - print('Country Code mapping: {0}'.format(country_codes)) + logger.debug('Country code mapping: {0}'.format(country_codes)) country_iso3_codes = dict( Country.objects.all().annotate(iso3_format_lower=Lower('iso3_format')).values_list('iso3_format_lower', 'id')) - print('Country ISO3 Code mapping: {0}'.format(country_iso3_codes)) + logger.debug('Country ISO3 code mapping: {0}'.format(country_iso3_codes)) parent_code_vs_id = dict( CountryAdminMetadata.objects.all().filter( @@ -133,7 +137,7 @@ def load_admin1_file_data(file_path): giga_id_admin_lower=Lower('giga_id_admin')).values_list( 'giga_id_admin_lower', 'id'), ) - print('Country Giga ID Code - ID mapping: {0}'.format(parent_code_vs_id)) + logger.debug('Country giga id code - ID mapping: {0}'.format(parent_code_vs_id)) input_df['errors'] = None @@ -157,20 +161,20 @@ def load_admin1_file_data(file_path): errors.append('Name field is required') if len(errors) > 0: - print('Errors: ', errors, ', Code: ', row['admin1idgiga'], ', Country Id: ', country_id) + logger.debug('Errors: ', errors, ', Code: ', row['admin1idgiga'], ', Country Id: ', country_id) has_data_errors = True input_df.at[index, 'errors'] = ','.join(errors) - print('Has data errors: {0}'.format(has_data_errors)) + logger.debug('Has data errors: {0}'.format(has_data_errors)) if has_data_errors: error_file = '_errors.'.join(get_file_name_and_extension(file_path)) - print('ERROR: CSV has data errors. Please check the error file, correct it and then start again.' - ' Error file: {0}'.format(error_file)) + logger.error('Csv has data errors. Please check the error file, correct it and then start again.' + ' Error file: {0}'.format(error_file)) input_df.to_csv(error_file, quotechar='"', index=False) return - print('SUCCESS: Validation has passed by the input file.') + logger.info('Success: Validation has passed by the input file.') input_df = input_df.replace(np.nan, None) rows = input_df.to_dict(orient='records') @@ -205,14 +209,15 @@ def load_admin2_file_data(file_path): core_utilities.column_normalize(input_df, valid_columns=csv_required_cols) - print('CSV normalized columns: {0}'.format(input_df.columns.tolist())) + logger.debug('Csv normalized columns: {0}'.format(input_df.columns.tolist())) input_df.drop_duplicates(subset=['iso31661', 'iso31661alpha3', 'admin2idgiga'], keep='last', inplace=True) country_codes = dict(Country.objects.all().annotate(code_lower=Lower('code')).values_list('code_lower', 'id')) - country_iso3_codes = dict(Country.objects.all().annotate(iso3_format_lower=Lower('iso3_format')).values_list('iso3_format_lower', 'id')) - print('Country Code mapping: {0}'.format(country_codes)) - print('Country ISO3 Code mapping: {0}'.format(country_iso3_codes)) + country_iso3_codes = dict( + Country.objects.all().annotate(iso3_format_lower=Lower('iso3_format')).values_list('iso3_format_lower', 'id')) + logger.debug('Country code mapping: {0}'.format(country_codes)) + logger.debug('Country ISO3 code mapping: {0}'.format(country_iso3_codes)) parent_code_vs_id = dict( CountryAdminMetadata.objects.all().filter( @@ -220,7 +225,7 @@ def load_admin2_file_data(file_path): giga_id_admin_lower=Lower('giga_id_admin')).values_list( 'giga_id_admin_lower', 'id'), ) - print('Admin1 Giga ID Code - ID mapping: {0}'.format(parent_code_vs_id)) + logger.debug('Admin1 giga id code - ID mapping: {0}'.format(parent_code_vs_id)) input_df['errors'] = None @@ -242,20 +247,20 @@ def load_admin2_file_data(file_path): errors.append('Name field is required') if len(errors) > 0: - print('Errors: ', errors, ', Code: ', row['admin2idgiga'], ', Country Id: ', country_id) + logger.error('Errors: ', errors, ', Code: ', row['admin2idgiga'], ', Country Id: ', country_id) has_data_errors = True input_df.at[index, 'errors'] = ','.join(errors) - print('Has data errors: {0}'.format(has_data_errors)) + logger.debug('Has data errors: {0}'.format(has_data_errors)) if has_data_errors: error_file = '_errors.'.join(get_file_name_and_extension(file_path)) - print('ERROR: CSV has data errors. Please check the error file, correct it and then start again.' - ' Error file: {0}'.format(error_file)) + logger.error('Csv has data errors. Please check the error file, correct it and then start again.' + ' Error file: {0}'.format(error_file)) input_df.to_csv(error_file, quotechar='"', index=False) return - print('SUCCESS: Validation has passed by the input file.') + logger.info('Success: Validation has passed by the input file.') input_df = input_df.replace(np.nan, None) rows = input_df.to_dict(orient='records') @@ -305,11 +310,11 @@ def handle(self, **options): ['admin0', 'admin1', 'admin2'])) with transaction.atomic(): - print('Loading Admins data for {0} ....'.format(admin_type)) + logger.info('Loading admins data for {0} ....'.format(admin_type)) if admin_type == 'admin0': load_admin0_file_data(input_file) elif admin_type == 'admin1': load_admin1_file_data(input_file) elif admin_type == 'admin2': load_admin2_file_data(input_file) - print('Data loaded successfully!\n') + logger.info('Data loaded successfully!\n') diff --git a/proco/core/management/commands/load_iso3_format_code_for_countries.py b/proco/core/management/commands/load_iso3_format_code_for_countries.py index 141cbf2..0518b39 100644 --- a/proco/core/management/commands/load_iso3_format_code_for_countries.py +++ b/proco/core/management/commands/load_iso3_format_code_for_countries.py @@ -1,5 +1,6 @@ import os import sys +import logging import pandas as pd from django.core.management.base import BaseCommand, CommandError @@ -8,6 +9,8 @@ from proco.core.utils import is_blank_string from proco.locations.models import Country +logger = logging.getLogger('gigamaps.' + __name__) + def is_file(fp): if not os.path.isfile(fp): @@ -28,10 +31,10 @@ def load_data(input_file): Country.objects.filter(code=str(country_code).strip()).update( iso3_format=str(country_iso3_code).strip(), ) else: - print('ERROR: Invalid Country Code/ISO3 Format submitted.') - print(country_code, '\t', country_iso3_code) + logger.error('Invalid country code ISO3 format submitted.') + logger.error(country_code, '\t', country_iso3_code) except Exception as ex: - print('Error raised for creation: {0}'.format(ex)) + logger.error('Error raised for creation: {0}'.format(ex)) class Command(BaseCommand): diff --git a/proco/core/management/commands/load_system_data_layers.py b/proco/core/management/commands/load_system_data_layers.py index 728d25f..6aa35fa 100644 --- a/proco/core/management/commands/load_system_data_layers.py +++ b/proco/core/management/commands/load_system_data_layers.py @@ -4,7 +4,7 @@ from django.db import transaction from proco.accounts import models as accounts_models -from proco.core.utils import get_current_datetime_object +from proco.core.utils import get_current_datetime_object, normalize_str data_source_json = [ { @@ -228,6 +228,7 @@ download_and_coverage_data_layer_json = [ { + 'code': 'DEFAULT_DOWNLOAD', 'name': 'Download', 'icon': """""", 'description': 'System Download Layer', @@ -268,6 +269,7 @@ ] }, { + 'code': 'DEFAULT_COVERAGE', 'name': 'Coverage data', 'icon': """""", 'description': 'Mobile coverage in the area', @@ -388,6 +390,17 @@ def load_system_data_layers_data(): pass +def populate_data_layer_codes(): + for data_layer_instance in accounts_models.DataLayer.objects.all_records(): + if data_layer_instance.code == 'UNKNOWN': + possible_code = normalize_str(str(data_layer_instance.name)).upper() + count = 1 + while accounts_models.DataLayer.objects.all_records().filter(code=possible_code).exists(): + possible_code = possible_code + '_' + str(count) + data_layer_instance.code = possible_code + data_layer_instance.save(update_fields=('code',)) + + class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument( @@ -410,6 +423,11 @@ def add_arguments(self, parser): help='If provided, already created Download/Coverage data layers will be updated again.' ) + parser.add_argument( + '--update_data_layers_code', action='store_true', dest='update_data_layers_code', default=False, + help='If provided, already created data layers will be updated with code picked from name field.' + ) + def handle(self, **options): sys.stdout.write('\nLoading APIs data....') @@ -437,4 +455,7 @@ def handle(self, **options): if options.get('update_data_layers', False): load_system_data_layers_data() + if options.get('update_data_layers_code', False): + populate_data_layer_codes() + sys.stdout.write('\nData loaded successfully!\n') diff --git a/proco/core/management/commands/populate_active_data_layer_for_countries.py b/proco/core/management/commands/populate_active_data_layer_for_countries.py index 257df72..1274fa7 100644 --- a/proco/core/management/commands/populate_active_data_layer_for_countries.py +++ b/proco/core/management/commands/populate_active_data_layer_for_countries.py @@ -1,6 +1,7 @@ # encoding: utf-8 from __future__ import absolute_import, division, print_function, unicode_literals +import logging from django.core.management.base import BaseCommand from proco.accounts import models as accounts_models @@ -9,6 +10,8 @@ from proco.core.utils import get_current_datetime_object from proco.locations.models import Country +logger = logging.getLogger('gigamaps.' + __name__) + def delete_relationships(country_id, layer_id): relationships = accounts_models.DataLayerCountryRelationship.objects.all() @@ -42,15 +45,15 @@ def add_arguments(self, parser): ) def handle(self, **options): - print('*** Active Data Layer for Country Mapping operations STARTED ***') + logger.info('Active data layer for country mapping operations started.') country_id = options.get('country_id', None) layer_id = options.get('layer_id', None) if options.get('reset_mapping', False): - print('DELETE_OLD_RECORDS - START') + logger.info('Delete old records - start') delete_relationships(country_id, layer_id) - print('DELETE_OLD_RECORDS - END') + logger.info('Delete old records - end') all_published_layers = accounts_models.DataLayer.objects.all() if layer_id: @@ -62,7 +65,7 @@ def handle(self, **options): all_country_ids = list(Country.objects.all().values_list('id', flat=True).order_by('id')) if all_published_layers.count() > 0 and len(all_country_ids) > 0: - print('RELATIONSHIP_CREATION - START') + logger.info('Relationship creation - start') for data_layer_instance in all_published_layers: data_sources = data_layer_instance.data_sources.all() @@ -101,20 +104,23 @@ def handle(self, **options): label='DataLayerCountryRelationship') for country_id_has_layer_data in all_country_ids_has_layer_data: - relationship_instance, created = accounts_models.DataLayerCountryRelationship.objects.update_or_create( - data_layer=data_layer_instance, - country_id=country_id_has_layer_data['country_id'], - defaults={ - 'is_default': not data_layer_instance.created_by, - 'last_modified_at': get_current_datetime_object(), - }, + relationship_instance, created = ( + accounts_models.DataLayerCountryRelationship.objects.update_or_create( + data_layer=data_layer_instance, + country_id=country_id_has_layer_data['country_id'], + defaults={ + 'is_default': not data_layer_instance.created_by, + 'last_modified_at': get_current_datetime_object(), + }, + ) ) if created: - print('New DataLayers + Country Relationship created for LIVE LAYER: {0}'.format( + logger.debug('New dataLayers + country relationship created for live layer: {0}'.format( relationship_instance.__dict__)) else: - print('Existing DataLayers + Country Relationship updated for LIVE LAYER: {0}'.format( - relationship_instance.__dict__)) + logger.debug( + 'Existing dataLayers + country relationship updated for live layer: {0}'.format( + relationship_instance.__dict__)) elif data_layer_instance.type == accounts_models.DataLayer.LAYER_TYPE_STATIC: unknown_condition = '' if parameter_column_type == 'str': @@ -139,19 +145,22 @@ def handle(self, **options): label='DataLayerCountryRelationship') for country_id_has_layer_data in all_country_ids_has_layer_data: - relationship_instance, created = accounts_models.DataLayerCountryRelationship.objects.update_or_create( - data_layer=data_layer_instance, - country_id=country_id_has_layer_data['country_id'], - defaults={ - 'is_default': False, - 'last_modified_at': get_current_datetime_object(), - }, + relationship_instance, created = ( + accounts_models.DataLayerCountryRelationship.objects.update_or_create( + data_layer=data_layer_instance, + country_id=country_id_has_layer_data['country_id'], + defaults={ + 'is_default': False, + 'last_modified_at': get_current_datetime_object(), + }, + ) ) if created: - print('New DataLayers + Country Relationship created for STATIC LAYER: {0}'.format( + logger.debug('New dataLayers + country relationship created for static layer: {0}'.format( relationship_instance.__dict__)) else: - print('Existing DataLayers + Country Relationship updated for STATIC LAYER: {0}'.format( - relationship_instance.__dict__)) + logger.debug( + 'Existing dataLayers + country relationship updated for static layer: {0}'.format( + relationship_instance.__dict__)) - print('*** Active Data Layer for Country Mapping operations ***') + logger.info('Active data layer for country mapping operations.') diff --git a/proco/core/management/commands/populate_admin_id_fields_to_schools.py b/proco/core/management/commands/populate_admin_id_fields_to_schools.py index 91000c4..a7a5847 100644 --- a/proco/core/management/commands/populate_admin_id_fields_to_schools.py +++ b/proco/core/management/commands/populate_admin_id_fields_to_schools.py @@ -1,15 +1,18 @@ +import sys +import logging from collections import OrderedDict from django.core.management.base import BaseCommand from django.db import connection from django.db import transaction from django.utils import timezone -import sys + +logger = logging.getLogger('gigamaps.' + __name__) @transaction.atomic def create_and_execute_update_query(column, data_dict_list): - print('Executing update statement for: {0} records'.format(len(data_dict_list))) + logger.debug('Executing update statement for: {0} records'.format(len(data_dict_list))) # create update query stmt = "UPDATE schools_school SET {column} = {value} WHERE id = {school_id}" @@ -17,7 +20,7 @@ def create_and_execute_update_query(column, data_dict_list): for data_dict in data_dict_list: update_query = stmt.format(column=column, value=data_dict[column], school_id=data_dict['school_id']) # print('Current record: {}'.format(data_dict)) - print('Current Update Query: {}'.format(update_query)) + logger.debug('Current update query: {}'.format(update_query)) cursor.execute(update_query) @@ -55,8 +58,8 @@ def populate_school_admin1_data(start_school_id, end_school_id): query = query.format(where_condition=where_condition) - print('Getting select statement query result from "schools_with_admin_data" table for Admin1 records.') - print('Query: {}'.format(query)) + logger.info('Getting select statement query result from "schools_with_admin_data" table for Admin1 records.') + logger.debug('Query: {}'.format(query)) data_list = [] with connection.cursor() as cursor: @@ -71,7 +74,7 @@ def populate_school_admin1_data(start_school_id, end_school_id): create_and_execute_update_query('admin1_id', data_list) te = timezone.now() - print('Executed the function in {} seconds'.format((te - ts).seconds)) + logger.debug('Executed the function in {} seconds'.format((te - ts).seconds)) def populate_school_admin2_data(start_school_id, end_school_id): @@ -107,8 +110,8 @@ def populate_school_admin2_data(start_school_id, end_school_id): query = query.format(where_condition=where_condition) - print('Getting select statement query result from "schools_with_admin_data" table for Admin2.') - print('Query: {}'.format(query)) + logger.info('Getting select statement query result from "schools_with_admin_data" table for Admin2.') + logger.debug('Query: {}'.format(query)) data_list = [] with connection.cursor() as cursor: @@ -123,7 +126,7 @@ def populate_school_admin2_data(start_school_id, end_school_id): create_and_execute_update_query('admin2_id', data_list) te = timezone.now() - print('Executed the function in {} seconds'.format((te - ts).seconds)) + logger.debug('Executed the function in {} seconds'.format((te - ts).seconds)) class Command(BaseCommand): @@ -156,7 +159,8 @@ def handle(self, **options): sys.exit("Mandatory argument '--admin-type/-at' is missing. Available options: {0}".format( ['admin1', 'admin2', 'both'])) - print('*** School update operation STARTED ({0} - {1}) ***'.format(start_school_id, end_school_id)) + logger.debug('School update operation started for: Start school ID - {0}, End school ID - {1}'.format( + start_school_id, end_school_id)) if admin_type == 'admin1': populate_school_admin1_data(start_school_id, end_school_id) @@ -166,4 +170,4 @@ def handle(self, **options): populate_school_admin1_data(start_school_id, end_school_id) populate_school_admin2_data(start_school_id, end_school_id) - print('Data loaded successfully!\n') + logger.info('Data loaded successfully!\n') diff --git a/proco/core/management/commands/populate_admin_ui_labels.py b/proco/core/management/commands/populate_admin_ui_labels.py index dfb7065..802522d 100644 --- a/proco/core/management/commands/populate_admin_ui_labels.py +++ b/proco/core/management/commands/populate_admin_ui_labels.py @@ -1,15 +1,17 @@ -from collections import OrderedDict +import logging +import sys from django.core.management.base import BaseCommand from django.db import connection from django.db import transaction from django.utils import timezone -import sys + +logger = logging.getLogger('gigamaps.' + __name__) @transaction.atomic def create_and_execute_update_query(stmt): - print('Current Update Query: {}'.format(stmt)) + logger.debug('Current update query: {}'.format(stmt)) with connection.cursor() as cursor: cursor.execute(stmt) @@ -52,7 +54,7 @@ def populate_ui_label_for_admin1_data(country_id, parent_id): create_and_execute_update_query(query) te = timezone.now() - print('Executed the function in {} seconds'.format((te - ts).seconds)) + logger.debug('Executed the function in {} seconds'.format((te - ts).seconds)) def populate_ui_label_for_admin2_data(country_id, parent_id): @@ -92,7 +94,7 @@ def populate_ui_label_for_admin2_data(country_id, parent_id): create_and_execute_update_query(query) te = timezone.now() - print('Executed the function in {} seconds'.format((te - ts).seconds)) + logger.debug('Executed the function in {} seconds'.format((te - ts).seconds)) class Command(BaseCommand): @@ -133,7 +135,7 @@ def handle(self, **options): country_id = options.get('country_id') parent_id = options.get('parent_id') - print('*** Admin update operation STARTED ({0} - {1}) ***'.format(country_id, parent_id)) + logger.debug('Admin update operation started ({0} - {1})'.format(country_id, parent_id)) if admin_type == 'admin1': populate_ui_label_for_admin1_data(country_id, parent_id) @@ -143,4 +145,4 @@ def handle(self, **options): populate_ui_label_for_admin1_data(country_id, parent_id) populate_ui_label_for_admin2_data(country_id, parent_id) - print('Data loaded successfully!\n') + logger.info('Data loaded successfully!\n') diff --git a/proco/core/management/commands/populate_school_new_fields.py b/proco/core/management/commands/populate_school_new_fields.py index dabf8e5..12b48f1 100644 --- a/proco/core/management/commands/populate_school_new_fields.py +++ b/proco/core/management/commands/populate_school_new_fields.py @@ -1,3 +1,5 @@ +import logging + from django.core.management.base import BaseCommand from django.db import transaction from django.db.models import Prefetch @@ -7,8 +9,10 @@ from proco.schools import utils as school_utilities from proco.schools.models import School +logger = logging.getLogger('gigamaps.' + __name__) + -def populate_school_new_fields(school_id, start_school_id, end_school_id, country_id): +def populate_school_new_fields(school_id, start_school_id, end_school_id, country_id, school_ids): """ """ schools_qry = School.objects.all() @@ -16,18 +20,21 @@ def populate_school_new_fields(school_id, start_school_id, end_school_id, countr Prefetch('country', Country.objects.defer('geometry', 'geometry_simplified')), ) if school_id and isinstance(school_id, int): - schools_qry = schools_qry.filter(id=school_id,) + schools_qry = schools_qry.filter(id=school_id, ) if start_school_id: - schools_qry = schools_qry.filter(id__gte=start_school_id,) + schools_qry = schools_qry.filter(id__gte=start_school_id, ) if end_school_id: - schools_qry = schools_qry.filter(id__lte=end_school_id,) + schools_qry = schools_qry.filter(id__lte=end_school_id, ) if country_id: - schools_qry = schools_qry.filter(country_id=country_id,) + schools_qry = schools_qry.filter(country_id=country_id, ) + + if school_ids and len(school_ids) > 0: + schools_qry = schools_qry.filter(id__in=school_ids.split(',')) - print('Starting the process: ', schools_qry.query) + logger.debug('Starting the process: ', schools_qry.query) count = 1 for data_chunk in core_utilities.queryset_iterator(schools_qry, chunk_size=20000): with transaction.atomic(): @@ -37,9 +44,9 @@ def populate_school_new_fields(school_id, start_school_id, end_school_id, countr school.connectivity_status = school_utilities.get_connectivity_status_by_master_api(school) school.save(update_fields=['coverage_type', 'coverage_status', 'connectivity_status']) - print("Update on school records SUCCEEDED for count '{0}'".format(count)) + logger.debug("Update on school records succeeded for count '{0}'".format(count)) count += 1 - print('Completed the process.') + logger.info('Completed the process.') class Command(BaseCommand): @@ -64,13 +71,18 @@ def add_arguments(self, parser): '-country_id', dest='country_id', required=False, type=int, help='Pass the Country ID in case want to control the update.' ) + parser.add_argument( + '-school_ids', dest='school_ids', required=False, type=str, + help='Pass the School IDs in case want to control the update.' + ) def handle(self, **options): school_id = options.get('school_id') + school_ids = options.get('school_ids') start_school_id = options.get('start_school_id') end_school_id = options.get('end_school_id') country_id = options.get('country_id') - print('*** School update operation STARTED ({0}) ***'.format(school_id)) + logger.debug('School update operation started ({0})'.format(school_id)) - populate_school_new_fields(school_id, start_school_id, end_school_id, country_id) + populate_school_new_fields(school_id, start_school_id, end_school_id, country_id, school_ids) diff --git a/proco/core/management/commands/populate_school_registration_data.py b/proco/core/management/commands/populate_school_registration_data.py index 9635a6d..e492ff8 100644 --- a/proco/core/management/commands/populate_school_registration_data.py +++ b/proco/core/management/commands/populate_school_registration_data.py @@ -1,3 +1,4 @@ +import logging from collections import OrderedDict from django.core.management.base import BaseCommand @@ -7,6 +8,8 @@ from proco.core.utils import get_current_datetime_object from proco.connection_statistics import models as statistics_models +logger = logging.getLogger('gigamaps.' + __name__) + def delete_relationships(country_id, school_id): relationships = statistics_models.SchoolRealTimeRegistration.objects.all() @@ -38,11 +41,11 @@ def create_and_execute_insert_query(table_columns, insert_statement_list): ) insert_ts = timezone.now() - print('Executing bulk insert for: {0} records'.format(len(insert_statement_list))) - print(insert_statement) + logger.debug('Executing bulk insert for total: {} records'.format(len(insert_statement_list))) + logger.debug(insert_statement) cursor.executemany(insert_statement, insert_statement_list) insert_te = timezone.now() - print('bulk insert time is {} second'.format((insert_te - insert_ts).seconds)) + logger.debug('Bulk insert time is "{}" second'.format((insert_te - insert_ts).seconds)) def populate_school_registration_data(country_id, school_id): @@ -72,8 +75,8 @@ def populate_school_registration_data(country_id, school_id): query += f' AND school.country_id = {country_id}' with connection.cursor() as cursor: - print('getting select statement query result from School + SchoolDailyStatus tables') - print('Query: {}'.format(query)) + logger.debug('Getting select statement query result from School + SchoolDailyStatus tables') + logger.debug('Query: {}'.format(query)) cursor.execute(query) description = cursor.description @@ -94,7 +97,7 @@ def populate_school_registration_data(country_id, school_id): create_and_execute_insert_query(table_columns, insert_statement_list) te = timezone.now() - print('Executed the function in {} seconds'.format((te - ts).seconds)) + logger.debug('Executed the function in "{}" seconds'.format((te - ts).seconds)) return current_rows_num, last_processed_id @@ -122,12 +125,10 @@ def handle(self, **options): school_id = options.get('school_id') if options.get('reset_mapping', False): - print('DELETE_OLD_RECORDS - START') + logger.debug('Starting deleting old records.') delete_relationships(country_id, school_id) - print('DELETE_OLD_RECORDS - END') - - print('*** School Registration update operation STARTED ({0}) ***'.format(options)) + logger.debug('Deleted old records.') + logger.info('School Registration update operation STARTED ({0})'.format(options)) populate_school_registration_data(country_id, school_id) - - print('*** School Registration update operation ENDED ({0}) ***'.format(options)) + logger.info('School Registration update operation ENDED ({0})'.format(options)) diff --git a/proco/core/management/commands/redo_aggregations.py b/proco/core/management/commands/redo_aggregations.py index ce07a8f..c208c64 100644 --- a/proco/core/management/commands/redo_aggregations.py +++ b/proco/core/management/commands/redo_aggregations.py @@ -1,4 +1,5 @@ import datetime +import logging from django.core.management.base import BaseCommand @@ -12,6 +13,8 @@ from proco.utils import dates as date_utilities from proco.connection_statistics.models import SchoolWeeklyStatus +logger = logging.getLogger('gigamaps.' + __name__) + def get_date_list(year, week_no): if week_no: @@ -72,8 +75,8 @@ def add_arguments(self, parser): ) def handle(self, **options): - print('Executing "redo_aggregations" utility ....\n') - print('Options: {}\n\n'.format(options)) + logger.info('Executing redo aggregations utility.\n') + logger.debug('Options: {}\n\n'.format(options)) country_id = options.get('country_id', None) country = Country.objects.get(id=country_id) @@ -85,21 +88,21 @@ def handle(self, **options): monday_date_list = list(get_all_monday_dates(dates_list)) if options.get('update_school_weekly'): - print('Performing School Weekly Aggregations for date range: {0} - {1}'.format( + logger.debug('Performing school weekly aggregations for date range: {0} - {1}'.format( monday_date_list[0], monday_date_list[-1])) for monday_date in monday_date_list: aggregate_school_daily_status_to_school_weekly_status(country, monday_date) - print('Completed School Weekly Aggregations.\n\n') + logger.info('Completed school weekly aggregations.\n\n') if options.get('update_country_daily'): - print('Performing Country Daily Aggregations for date range: {0} - {1}'.format( + logger.debug('Performing country daily aggregations for date range: {0} - {1}'.format( dates_list[0], dates_list[-1])) for date in dates_list: aggregate_school_daily_to_country_daily(country, date) - print('Completed Country Daily Aggregations.\n\n') + logger.info('Completed country daily aggregations.\n\n') if options.get('update_country_weekly'): - print('Performing Country Weekly Aggregations for date range: {0} - {1}'.format( + logger.debug('Performing country weekly aggregations for date range: {0} - {1}'.format( monday_date_list[0], monday_date_list[-1])) for monday_date in monday_date_list: monday_week_no = date_utilities.get_week_from_date(monday_date) @@ -108,10 +111,10 @@ def handle(self, **options): ).exists(): update_country_weekly_status(country, monday_date) else: - print('Country Weekly Aggregations skipped as School Weekly has no record for same data:' - ' Year - {0}, Week No - {1}'.format(year, monday_week_no)) - print('Completed Country Weekly Aggregations.\n\n') + logger.debug('Country weekly aggregations skipped as school weekly has no record for same data:' + ' Year - {0}, Week No - {1}'.format(year, monday_week_no)) + logger.info('Completed country weekly aggregations.\n\n') country.invalidate_country_related_cache() - print('Completed "redo_aggregations" successfully ....\n') + logger.info('Completed redo aggregations successfully.\n') diff --git a/proco/core/management/commands/update_system_role_permissions.py b/proco/core/management/commands/update_system_role_permissions.py index 35a88b4..2247a79 100644 --- a/proco/core/management/commands/update_system_role_permissions.py +++ b/proco/core/management/commands/update_system_role_permissions.py @@ -1,4 +1,5 @@ # encoding: utf-8 +import logging from collections import OrderedDict @@ -7,6 +8,8 @@ from proco.custom_auth.models import Role, RolePermission +logger = logging.getLogger('gigamaps.' + __name__) + role_permissions = OrderedDict({ Role.SYSTEM_ROLE_NAME_ADMIN: [perm[0] for perm in RolePermission.PERMISSION_CHOICES], Role.SYSTEM_ROLE_NAME_READ_ONLY: [RolePermission.CAN_DELETE_API_KEY, ], @@ -34,7 +37,7 @@ class Command(BaseCommand): help = "Update the System Role's permissions as it can not be updated from GUI." def handle(self, **options): - print('*** System role update operation STARTED ***') + logger.info('System role update operation started.') with transaction.atomic(): populate_role_permissions() - print('*** System role update operation ENDED ***') + logger.info('System role update operation ended.') diff --git a/proco/core/resources/filters.json b/proco/core/resources/filters.json new file mode 100644 index 0000000..4cf67e6 --- /dev/null +++ b/proco/core/resources/filters.json @@ -0,0 +1,259 @@ +[ + { + "name": "School Region", + "type": "drop-down", + "description": "", + "choices": [ + { + "label": "None", + "value": "none" + }, + { + "label": "Urban", + "value": "urban" + }, + { + "label": "Rural", + "value": "rural" + } + ], + "parameter": { + "label": "Region", + "table": "schools", + "field": "environment", + "filter": "iexact" + }, + "active_countries_filter": "LOWER(environment) IN ('urban', 'rural')", + "active_countries_list": null + }, + { + "name": "School Type", + "type": "drop-down", + "description": "", + "choices": [ + { + "label": "None", + "value": "none" + }, + { + "label": "Private", + "value": "private" + }, + { + "label": "Public", + "value": "public" + } + ], + "parameter": { + "label": "School Type", + "table": "schools", + "field": "school_type", + "filter": "iexact" + }, + "active_countries_filter": "LOWER(school_type) IN ('private', 'public')", + "active_countries_list": null + }, + { + "name": "Education Level", + "type": "drop-down", + "description": "", + "choices": [ + { + "label": "None", + "value": "none" + }, + { + "label": "Primary", + "value": "primary" + }, + { + "label": "Secondary", + "value": "secondary" + } + ], + "parameter": { + "label": "Education Level", + "table": "schools", + "field": "education_level", + "filter": "iexact" + }, + "active_countries_filter": "LOWER(education_level) IN ('primary', 'secondary')", + "active_countries_list": null + }, + { + "name": "No of Computers", + "type": "range", + "description": "", + "include_none_filter": true, + "parameter": { + "label": "# of Computers", + "table": "school_static", + "field": "num_computers", + "filter": "range" + }, + "active_countries_filter": "num_computers IS NOT NULL AND num_computers > 0", + "active_countries_list": null, + "active_countries_range": { + "default": { + "min_place_holder": "Min (0)", + "max_place_holder": "Max (1 Lac)", + "min_value": 0, + "max_value": 2147483647 + } + } + }, + { + "name": "No of Students", + "type": "range", + "description": "", + "include_none_filter": true, + "parameter": { + "label": "# of Students", + "table": "school_static", + "field": "num_students", + "filter": "range" + }, + "active_countries_filter": "num_students IS NOT NULL AND num_students > 0", + "active_countries_list": null, + "active_countries_range": { + "default": { + "min_place_holder": "Min (0)", + "max_place_holder": "Max (1 Lac)", + "min_value": 0, + "max_value": 2147483647 + } + } + }, + { + "name": "No of Teachers", + "type": "range", + "description": "", + "include_none_filter": true, + "parameter": { + "label": "# of Teachers", + "table": "school_static", + "field": "num_teachers", + "filter": "range" + }, + "active_countries_filter": "num_teachers IS NOT NULL AND num_teachers > 0", + "active_countries_list": null, + "active_countries_range": { + "default": { + "min_place_holder": "Min (0)", + "max_place_holder": "Max (10K)", + "min_value": 0, + "max_value": 32767 + } + } + }, + { + "name": "Download Speed", + "type": "range", + "description": "", + + "include_none_filter": true, + "parameter": { + "label": "Connectivity Speed", + "table": "school_static", + "field": "connectivity_speed", + "filter": "range" + }, + "downcast_aggr_str": "{val} / (1000 * 1000)", + "upcast_aggr_str": "{val} * 1000 * 1000", + "active_countries_filter": "connectivity_speed IS NOT NULL", + "active_countries_list": null, + "active_countries_range": { + "default": { + "min_place_holder": "Min (MBs)", + "max_place_holder": "Max (MBs)", + "min_value": 0, + "max_value": 2147 + } + } + }, + { + "name": "Latency", + "type": "range", + "description": "", + + "include_none_filter": true, + "parameter": { + "label": "Latency", + "table": "school_static", + "field": "connectivity_latency", + "filter": "range" + }, + "active_countries_filter": "connectivity_latency IS NOT NULL", + "active_countries_list": null, + "active_countries_range": { + "default": { + "min_place_holder": "Min (ms)", + "max_place_holder": "Max (ms)", + "min_value": 0, + "max_value": 36000000 + } + } + }, + { + "name": "Has Computer Lab", + "type": "drop-down", + "description": "", + "choices": [ + { + "label": "Yes", + "value": "true" + }, + { + "label": "No", + "value": "false" + } + ], + "parameter": { + "label": "Has Computer Lab", + "table": "school_static", + "field": "computer_lab", + "filter": "on" + }, + "active_countries_filter": "computer_lab = true", + "active_countries_list": null + }, + { + "name": "Coverage Type", + "type": "drop-down-multiselect", + "description": "", + "choices": [ + { + "label": "Unknown", + "value": "unknown" + }, + { + "label": "No", + "value": "no" + }, + { + "label": "2G", + "value": "2g" + }, + { + "label": "3G", + "value": "3g" + }, + { + "label": "4G", + "value": "4g" + }, + { + "label": "5G", + "value": "5g" + } + ], + "parameter": { + "label": "Coverage Type", + "table": "school_static", + "field": "coverage_type", + "filter": "in" + }, + "active_countries_filter": "LOWER(\"connection_statistics_schoolweeklystatus\".\"coverage_type\") != 'unknown'", + "active_countries_list": null + } +] \ No newline at end of file diff --git a/proco/core/tests/test_utils.py b/proco/core/tests/test_utils.py index f6bdb67..8947b82 100644 --- a/proco/core/tests/test_utils.py +++ b/proco/core/tests/test_utils.py @@ -85,11 +85,6 @@ def test_get_random_choice_utility(self): self.assertIn(core_utilities.get_random_choice(['aa', 'bb', 'cc', 'dd']), ['aa', 'bb', 'cc', 'dd']) - def test_get_sender_email_utility(self): - self.assertEqual(type(core_utilities.get_sender_email()), str) - - self.assertIsNotNone(core_utilities.get_sender_email()) - def test_get_support_email_utility(self): self.assertEqual(type(core_utilities.get_support_email()), str) diff --git a/proco/core/utils.py b/proco/core/utils.py index 86de8a5..a6ff3f7 100644 --- a/proco/core/utils.py +++ b/proco/core/utils.py @@ -3,6 +3,7 @@ import locale import random import re +import logging from decimal import Decimal import pytz @@ -11,6 +12,7 @@ from proco.core.config import app_config as core_config +logger = logging.getLogger('gigamaps.' + __name__) def get_timezone_converted_value(value, tz=settings.TIME_ZONE): """ @@ -186,24 +188,16 @@ def get_random_choice(choices): return random.choice(choices) -def get_sender_email(): - """ - get email id for sending emails - :return: - """ - emails = settings.DEFAULT_FROM_EMAIL - email_options = emails.split(',') - return get_random_choice(email_options) - - def get_support_email(): """ get email id for sending emails :return: """ emails = settings.SUPPORT_EMAIL_ID - email_options = emails.split(',') - return get_random_choice(email_options) + if len(emails) > 0: + email_options = emails.split(',') + return get_random_choice(email_options) + return '' def get_project_title(): @@ -233,7 +227,7 @@ def queryset_iterator(queryset, chunk_size=1000, print_msg=True): Note that the implementation of the iterator does not support ordered query sets. """ if not queryset: - print('Queryset has not data to iterate over: {0}'.format(queryset.query)) + logger.debug('Queryset has not data to iterate over: {0}'.format(queryset.query)) return list(queryset) pk = 0 @@ -241,7 +235,7 @@ def queryset_iterator(queryset, chunk_size=1000, print_msg=True): queryset = queryset.order_by('pk') while pk < last_pk: if print_msg: - print('Current selection query: {0}'.format(queryset.filter(pk__gt=pk)[:chunk_size].query)) + logger.debug('Current selection query: {0}'.format(queryset.filter(pk__gt=pk)[:chunk_size].query)) row = list(queryset.filter(pk__gt=pk)[:chunk_size]) pk = row[-1].pk yield row @@ -300,7 +294,7 @@ def bulk_create_or_update(records, model, unique_fields, batch_size=1000): ] if len(records_to_create) > 0: - print('Total records to create: {}'.format(len(records_to_create))) + logger.debug('Total records to create: {}'.format(len(records_to_create))) # Remove the 'id' field, as these will all hold a value of None, # since these records do not already exist in the DB [record.pop('id') for record in records_to_create] @@ -310,7 +304,7 @@ def bulk_create_or_update(records, model, unique_fields, batch_size=1000): ) if len(records_to_update) > 0: - print('Total records to update: {}'.format(len(records_to_update))) + logger.debug('Total records to update: {}'.format(len(records_to_update))) for f in unique_fields: [record.pop(f) for record in records_to_update] @@ -323,3 +317,82 @@ def bulk_create_or_update(records, model, unique_fields, batch_size=1000): batch_size=batch_size, ) + +def get_filter_sql(request, filter_key, table_name): + filter_fields = core_config.get_giga_filter_fields[filter_key] + query_params = request.query_params.dict() + + advance_filters = set(filter_fields) & set(query_params.keys()) + + sql_list = [] + for field_filter in advance_filters: + filter_value = str(query_params[field_filter]).lower() + sql_str = None + field_name = None + + if field_filter.endswith('__iexact'): + field_name = field_filter.replace('__iexact', '') + + if filter_value == 'none': + sql_str = """coalesce(TRIM({table_name}."{field_name}"), '') = ''""" + else: + sql_str = """LOWER({table_name}."{field_name}") = '{value}'""" + elif field_filter.endswith('__on'): + field_name = field_filter.replace('__on', '') + + if filter_value == 'none': + sql_str = """{table_name}."{field_name}" IS NULL""" + else: + sql_str = """{table_name}."{field_name}" = {value}""" + elif field_filter.endswith('__range'): + field_name = field_filter.replace('__range', '') + + start, end = filter_value.split(',') + if start != 'null': + sql_list.append("""{table_name}."{field_name}" >= {value}""".format( + table_name=table_name, + field_name=field_name, + value=start, + )) + if end != 'null': + sql_list.append("""{table_name}."{field_name}" <= {value}""".format( + table_name=table_name, + field_name=field_name, + value=end, + )) + elif field_filter.endswith('__none_range'): + field_name = field_filter.replace('__none_range', '') + none_sql_str = """{table_name}."{field_name}" IS NULL""".format( + table_name=table_name, + field_name=field_name, + ) + + start, end = filter_value.split(',') + range_sql_list = [] + if start != 'null': + range_sql_list.append("""{table_name}."{field_name}" >= {value}""".format( + table_name=table_name, + field_name=field_name, + value=start, + )) + if end != 'null': + range_sql_list.append("""{table_name}."{field_name}" <= {value}""".format( + table_name=table_name, + field_name=field_name, + value=end, + )) + if len(range_sql_list) == 0: + sql_list.append(none_sql_str) + elif len(range_sql_list) == 1: + sql_list.append('(' + none_sql_str + ' OR ' + range_sql_list[0] + ')') + elif len(range_sql_list) == 2: + sql_list.append('(' + none_sql_str + ' OR (' + range_sql_list[0] + ' AND ' + range_sql_list[1] + '))') + elif field_filter.endswith('__in'): + field_name = field_filter.replace('__in', '') + filter_value = ','.join(["'" + str(f).lower() + "'" for f in filter_value.split(',')]) + sql_str = """LOWER({table_name}."{field_name}") IN ({value})""" + + if sql_str: + sql_list.append(sql_str.format(table_name=table_name, field_name=field_name, value=filter_value)) + + return ' AND '.join(sql_list) diff --git a/proco/custom_auth/authentication.py b/proco/custom_auth/authentication.py index 4790464..7896ffa 100644 --- a/proco/custom_auth/authentication.py +++ b/proco/custom_auth/authentication.py @@ -1,12 +1,15 @@ +import logging + from django.contrib.auth import get_user_model from django.utils.translation import ugettext as _ from rest_framework import exceptions from rest_framework_jwt import authentication as jwt_authentication from rest_framework_jwt.settings import api_settings -from proco.custom_auth.models import Role from proco.core.utils import is_blank_string +from proco.custom_auth.models import Role +logger = logging.getLogger('gigamaps.' + __name__) jwt_decode_handler = api_settings.JWT_DECODE_HANDLER @@ -59,5 +62,5 @@ def authenticate(self, request): except user_model.DoesNotExist: msg = _('Invalid signatures.') e = exceptions.AuthenticationFailed(msg) - print(str(e)) + logger.debug(str(e)) raise e diff --git a/proco/custom_auth/migrations/0014_deleted_unused_historical_models.py b/proco/custom_auth/migrations/0014_deleted_unused_historical_models.py new file mode 100644 index 0000000..0984c5d --- /dev/null +++ b/proco/custom_auth/migrations/0014_deleted_unused_historical_models.py @@ -0,0 +1,47 @@ +# Generated by Django 2.2.28 on 2024-07-09 08:43 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('custom_auth', '0013_updated_applicationuser_username_max_length_same_as_email'), + ] + + operations = [ + migrations.RemoveField( + model_name='historicaluserrolerelationship', + name='created_by', + ), + migrations.RemoveField( + model_name='historicaluserrolerelationship', + name='history_user', + ), + migrations.RemoveField( + model_name='historicaluserrolerelationship', + name='last_modified_by', + ), + migrations.RemoveField( + model_name='historicaluserrolerelationship', + name='role', + ), + migrations.RemoveField( + model_name='historicaluserrolerelationship', + name='user', + ), + migrations.AlterModelOptions( + name='rolepermission', + options={}, + ), + migrations.AlterModelOptions( + name='userrolerelationship', + options={}, + ), + migrations.DeleteModel( + name='HistoricalRolePermission', + ), + migrations.DeleteModel( + name='HistoricalUserRoleRelationship', + ), + ] diff --git a/proco/custom_auth/models.py b/proco/custom_auth/models.py index 6971615..3f8ed8e 100644 --- a/proco/custom_auth/models.py +++ b/proco/custom_auth/models.py @@ -175,7 +175,7 @@ def permission_slugs(self): return self.permissions.all().values_list('slug', flat=True) -class UserRoleRelationship(core_models.BaseModel): +class UserRoleRelationship(core_models.BaseModelMixin): """ UserRoleRelationship This model is used to store the user roles. @@ -192,7 +192,7 @@ def perm_dict(self): return role_perms -class RolePermission(core_models.BaseModel): +class RolePermission(core_models.BaseModelMixin): """ RolePermission diff --git a/proco/custom_auth/serializers.py b/proco/custom_auth/serializers.py index 6fd239d..e9ad936 100644 --- a/proco/custom_auth/serializers.py +++ b/proco/custom_auth/serializers.py @@ -1,11 +1,9 @@ import re +import logging -from django.contrib.auth import get_user_model from django.db import transaction -from django.utils.translation import ugettext as _ from rest_flex_fields.serializers import FlexFieldsModelSerializer from rest_framework import serializers -from rest_framework_jwt import serializers as jwt_serializers from proco.core import utils as core_utilities from proco.custom_auth import exceptions as auth_exceptions @@ -13,6 +11,7 @@ from proco.custom_auth import utils as auth_utilities from proco.custom_auth.config import app_config as auth_config +logger = logging.getLogger('gigamaps.' + __name__) class RoleSerializer(serializers.ModelSerializer): """ @@ -262,11 +261,11 @@ def validate_email(self, email): email_lower = email.lower() if auth_models.ApplicationUser.objects.filter(email=email_lower).exists(): e = auth_exceptions.EmailAlreadyExistsError() - print(e.message) - print('Details: {0}'.format(email_lower)) + logger.error(e.message) + logger.debug('Details: {0}'.format(email_lower)) raise e - print('Email validated') - print('Details: {0}'.format(email_lower)) + logger.info('Email validated.') + logger.debug('Details: {0}'.format(email_lower)) return email_lower def get_role_fields(self): diff --git a/proco/custom_auth/tests/test_api.py b/proco/custom_auth/tests/test_api.py index d859823..fff1b67 100755 --- a/proco/custom_auth/tests/test_api.py +++ b/proco/custom_auth/tests/test_api.py @@ -19,7 +19,7 @@ def custom_auth_url(url_params, query_param, view_name='create-and-list-users'): class UserApiTestCase(TestAPIViewSetMixin, TestCase): - databases = ['default'] + databases = ['default',] @classmethod def setUpTestData(cls): diff --git a/proco/custom_auth/tests/test_utils.py b/proco/custom_auth/tests/test_utils.py index 69c7a0c..313c95c 100644 --- a/proco/custom_auth/tests/test_utils.py +++ b/proco/custom_auth/tests/test_utils.py @@ -38,7 +38,7 @@ def setup_read_only_role(): role = auth_models.Role.objects.create(name=auth_models.Role.SYSTEM_ROLE_NAME_READ_ONLY, category=auth_models.Role.ROLE_CATEGORY_SYSTEM) - perms = [] + perms = [auth_models.RolePermission.CAN_DELETE_API_KEY, ] for perm in perms: auth_models.RolePermission.objects.get_or_create( @@ -58,7 +58,13 @@ def setup_admin_user_by_role(): password = 'SomeRandomPass96' user = auth_models.ApplicationUser.objects.filter(username=email).first() if not user: - user = auth_models.ApplicationUser.objects.create_user(username=email, password=password) + user = auth_models.ApplicationUser.objects.create_user( + username=email, + email=email, + password=password, + first_name='Admin', + last_name='User', + ) admin_role = setup_admin_role() auth_models.UserRoleRelationship.objects.create(user=user, role=admin_role) @@ -71,7 +77,13 @@ def setup_read_only_user_by_role(): password = 'SomeRandomPass96' user = auth_models.ApplicationUser.objects.filter(username=email).first() if not user: - user = auth_models.ApplicationUser.objects.create_user(username=email, password=password) + user = auth_models.ApplicationUser.objects.create_user( + username=email, + email=email, + password=password, + first_name='Read Only', + last_name='User', + ) read_only_role = setup_read_only_role() auth_models.UserRoleRelationship.objects.create(user=user, role=read_only_role) diff --git a/proco/custom_auth/utils.py b/proco/custom_auth/utils.py index b0add6b..cfd5e0c 100644 --- a/proco/custom_auth/utils.py +++ b/proco/custom_auth/utils.py @@ -1,3 +1,4 @@ +import logging from datetime import datetime, timedelta import jwt @@ -9,6 +10,8 @@ from proco.core import utils as core_utilities from proco.custom_auth import models as auth_models +logger = logging.getLogger('gigamaps.' + __name__) + def jwt_get_username_from_payload_handler(payload): """ @@ -84,7 +87,7 @@ def jwt_decode_handler(token): """ try: token_header = jwt.get_unverified_header(token) - print('Token header: {0}'.format(token_header)) + logger.debug('Token header: {0}'.format(token_header)) payload = jwt.decode( token, @@ -93,7 +96,7 @@ def jwt_decode_handler(token): algorithms=[token_header.get('alg')], options={'verify_signature': False} ) - print('Token as decoded payload: {0}'.format(payload)) + logger.debug('Token as decoded payload: {0}'.format(payload)) validate_azure_ad_b2c_token(payload) except jwt.ExpiredSignature: msg = _('Signature has expired.') diff --git a/proco/data_sources/api.py b/proco/data_sources/api.py index bdbb590..f9a55bc 100644 --- a/proco/data_sources/api.py +++ b/proco/data_sources/api.py @@ -66,7 +66,6 @@ def get(self, request, *args, **kwargs): sources_tasks.load_data_from_qos_apis() date = QoSData.objects.all().values_list('date', flat=True).order_by('-date').first() - print('Latest date from QoSData: {}'.format(date)) countries_ids = QoSData.objects.all().values_list('country_id', flat=True).order_by('country_id').distinct( 'country_id') diff --git a/proco/data_sources/management/commands/data_loss_recovery_for_pcdc.py b/proco/data_sources/management/commands/data_loss_recovery_for_pcdc.py index ffe3b42..a9d4f82 100644 --- a/proco/data_sources/management/commands/data_loss_recovery_for_pcdc.py +++ b/proco/data_sources/management/commands/data_loss_recovery_for_pcdc.py @@ -1,3 +1,5 @@ +import logging + from datetime import timedelta from django.conf import settings @@ -16,6 +18,8 @@ from proco.schools.models import School from proco.utils import dates as date_utilities +logger = logging.getLogger('gigamaps.' + __name__) + ds_settings = settings.DATA_SOURCE_CONFIG today_date = get_current_datetime_object().date() @@ -27,22 +31,22 @@ def check_missing_dates_to_table(date_list): timestamp__date__lte=date_list[-1], ).values_list('timestamp__date', flat=True).distinct('timestamp__date').order_by('timestamp__date') - print('Missing dates are between {0} - {1}: '.format(date_list[0], date_list[-1])) + logger.debug('Missing dates are between {0} - {1}: '.format(date_list[0], date_list[-1])) missing_dates = list(set(date_list) - set(list(pcdc_timestamp_qry))) for missing_date in sorted(missing_dates): # print missing date in string format - print(date_utilities.format_date(missing_date)) + logger.debug(date_utilities.format_date(missing_date)) def delete_dailycheckapp_realtime_data(date): - print('Deleting all the PCDC rows only, from "RealTimeConnectivity" Data Table for date: {0}'.format(date)) + logger.debug('Deleting all the PCDC rows only, from "RealTimeConnectivity" data table for date: {0}'.format(date)) RealTimeConnectivity.objects.filter( created__date=date, live_data_source=statistics_configs.DAILY_CHECK_APP_MLAB_SOURCE, ).delete() - print('Deleting all the rows from "DailyCheckAppMeasurementData" Data Table for date: {0}'.format(date)) + logger.debug('Deleting all the rows from "DailyCheckAppMeasurementData" data table for date: {0}'.format(date)) DailyCheckAppMeasurementData.objects.filter(timestamp__date=date).delete() @@ -98,13 +102,13 @@ def add_arguments(self, parser): ) def handle(self, **options): - print('Executing "data_loss_recovery_for_pcdc" ....') + logger.info('Executing data loss recovery for pcdc.') check_missing_dates = options.get('check_missing_dates') start_date = date_utilities.to_date(options.get('start_date')) end_date = date_utilities.to_date(options.get('end_date')) if start_date > end_date: - print('ERROR: start_date value can not be greater than end_date.') + logger.error('Start date value can not be greater than end_date.') exit(0) date_list = sorted([(start_date + timedelta(days=x)).date() for x in range((end_date - start_date).days)] + [ @@ -119,15 +123,15 @@ def handle(self, **options): if pull_data and pull_data_date: pull_data_date = pull_data_date.date() - print('Deleting PCDC data for date: {}'.format(pull_data_date)) + logger.debug('Deleting PCDC data for date: {}'.format(pull_data_date)) delete_dailycheckapp_realtime_data(pull_data_date) - print('Data deleted successfully.\n\n') + logger.debug('Data deleted successfully.\n\n') - print('Syncing the PCDC API data to Proco PCDC table for date: {}'.format(pull_data_date)) + logger.debug('Syncing the PCDC api data to proco PCDC table for date: {}'.format(pull_data_date)) sync_dailycheckapp_realtime_data(pull_data_date) - print('Data synced successfully.\n\n') + logger.debug('Data synced successfully.\n\n') - print('Aggregating the pulled data by giga_id_school + country_code and ' + logger.debug('Aggregating the pulled data by giga_id_school + country_code and ' 'storing in RealTimeConnectivity table.') dailycheckapp_measurements = DailyCheckAppMeasurementData.objects.filter( timestamp__date=pull_data_date, @@ -144,7 +148,7 @@ def handle(self, **options): ).order_by('country_code', 'giga_id_school', 'school_id', 'source') if not dailycheckapp_measurements.exists(): - print('ERROR: No records to aggregate on provided date: "{0}". ' + logger.error('No records to aggregate on provided date: "{0}". ' 'Hence stopping the execution here.'.format(pull_data_date)) return @@ -154,7 +158,7 @@ def handle(self, **options): 'country_code', flat=True, ).order_by('country_code')) for country_code in countries: - print('Current Country Code: {}'.format(country_code)) + logger.debug('Current country code: {}'.format(country_code)) if country_code: country = Country.objects.filter(code=country_code).first() else: @@ -175,7 +179,7 @@ def handle(self, **options): school.giga_id_school: school for school in schools_qs.filter(giga_id_school__in=dcm_giga_ids) } - print('Total schools in DailyCheckApp: {0}, Successfully mapped schools: {1}'.format( + logger.debug('Total schools in dailycheckapp: {0}, Successfully mapped schools: {1}'.format( len(dcm_giga_ids), len(dcm_schools))) mlab_school_ids = set(dailycheckapp_measurements.filter( @@ -189,7 +193,7 @@ def handle(self, **options): school.external_id: school for school in schools_qs.filter(external_id__in=mlab_school_ids) } - print('Total schools in MLab: {0}, Successfully mapped schools: {1}'.format( + logger.debug('Total schools in MLab: {0}, Successfully mapped schools: {1}'.format( len(mlab_school_ids), len(mlab_schools))) unknown_schools = [] @@ -228,21 +232,21 @@ def handle(self, **options): )) if len(realtime) == 5000: - print('Loading the data to "RealTimeConnectivity" table as it has reached 5000 benchmark.') + logger.debug('Loading the data to "RealTimeConnectivity" table as it has reached 5000 benchmark.') RealTimeConnectivity.objects.bulk_create(realtime) realtime = [] if len(unknown_schools) > 0: - print('Skipped dailycheckapp_measurement for country: "{0}" unknown school: {1}'.format( + logger.debug('Skipped dailycheckapp measurement for country: "{0}" unknown school: {1}'.format( country_code, unknown_schools)) - print('Loading the remaining ({0}) data to "RealTimeConnectivity" table.'.format(len(realtime))) + logger.debug('Loading the remaining ({0}) data to "RealTimeConnectivity" table.'.format(len(realtime))) if len(realtime) > 0: RealTimeConnectivity.objects.bulk_create(realtime) - print('Aggregated successfully to RealTimeConnectivity table.\n\n') + logger.debug('Aggregated successfully to RealTimeConnectivity table.\n\n') - print('Starting finalizing the records to actual proco tables.') + logger.debug('Starting finalizing the records to actual proco tables.') countries_ids = RealTimeConnectivity.objects.filter( created__date=pull_data_date, live_data_source=statistics_configs.DAILY_CHECK_APP_MLAB_SOURCE, @@ -256,12 +260,12 @@ def handle(self, **options): monday_week_no = date_utilities.get_week_from_date(monday_date) monday_year = date_utilities.get_year_from_date(monday_date) - print('Weekly record details. \tWeek No: {0}\tYear: {1}'.format(monday_week_no, monday_year)) + logger.debug('Weekly record details. \tWeek No: {0}\tYear: {1}'.format(monday_week_no, monday_year)) for country_id in countries_ids: - print('Finalizing the records for Country ID: {0}'.format(country_id)) + logger.debug('Finalizing the records for Country ID: {0}'.format(country_id)) finalize_previous_day_data(None, country_id, pull_data_date) - print('Finalized records successfully to actual proco tables.\n\n') + logger.info('Finalized records successfully to actual proco tables.\n\n') - print('Completed "data_loss_recovery_for_pcdc" successfully ....\n') + logger.info('Completed dataloss recovery for pcdc successfully.\n') diff --git a/proco/data_sources/management/commands/data_loss_recovery_for_qos.py b/proco/data_sources/management/commands/data_loss_recovery_for_qos.py index b858d04..4540fbc 100644 --- a/proco/data_sources/management/commands/data_loss_recovery_for_qos.py +++ b/proco/data_sources/management/commands/data_loss_recovery_for_qos.py @@ -1,5 +1,6 @@ import json import os +import logging from datetime import timedelta import delta_sharing @@ -18,6 +19,8 @@ from proco.schools.models import School from proco.utils import dates as date_utilities +logger = logging.getLogger('gigamaps.' + __name__) + ds_settings = settings.DATA_SOURCE_CONFIG today_date = get_current_datetime_object().date() @@ -62,18 +65,18 @@ def load_qos_data_source_response_to_model(version_number, country): if country.iso3_format != table_name: continue - print('#' * 10) + logger.debug('#' * 10) try: if QoSData.objects.all().filter( country=country, version=version_number, ).exists(): - print('WARNING: QoSData table has given version data already in the table. ' + logger.debug('WARNING: QoSData table has given version data already in the table. ' 'To re collect, please clean this version data first then retry again.' - 'Country Code: {0}, \t\tVersion: {1}'.format(table_name, version_number)) + 'Country code: {0}, \t\tVersion: {1}'.format(table_name, version_number)) continue - print('Current version data not available in the table. Hence fetching the data from QoS API.') + logger.info('Current version data not available in the table. Hence fetching the data from QoS api.') # Create an url to access a shared table. # A table path is the profile file path following with `#` and the fully qualified name of a table @@ -85,10 +88,10 @@ def load_qos_data_source_response_to_model(version_number, country): ) api_current_version = delta_sharing.get_table_version(table_url) - print('Current version from API: {0}'.format(api_current_version)) + logger.debug('Current version from api: {0}'.format(api_current_version)) if version_number > api_current_version: - print('ERROR: Given version must not be higher then latest API version. ' + logger.error('Given version must not be higher then latest api version. ' 'Hence skipping current data pull.') exit(0) @@ -99,12 +102,12 @@ def load_qos_data_source_response_to_model(version_number, country): None, None, ) - print('Total count of rows in the {0} version data: {1}'.format( + logger.debug('Total count of rows in the {0} version data: {1}'.format( version_number, len(loaded_data_df))) loaded_data_df = loaded_data_df[loaded_data_df[DeltaSharingReader._change_type_col_name()].isin( ['insert', 'update_postimage'])] - print('Total count of rows after filtering only ["insert", "update_postimage"] in the "{0}" ' + logger.info('Total count of rows after filtering only ["insert", "update_postimage"] in the "{0}" ' 'version data: {1}'.format(version_number, len(loaded_data_df))) if len(loaded_data_df) > 0: @@ -117,8 +120,8 @@ def load_qos_data_source_response_to_model(version_number, country): 'modified', 'school_id', 'country_id', 'modified_by', ] - print('All QoS API response columns: {}'.format(df_columns)) - print('All QoS API response columns to delete: {}'.format( + logger.debug('All QoS api response columns: {}'.format(df_columns)) + logger.debug('All QoS api response columns to delete: {}'.format( list(set(df_columns) - set(qos_model_fields)))) loaded_data_df.drop(columns=cols_to_delete, inplace=True, errors='ignore', ) @@ -134,7 +137,7 @@ def load_qos_data_source_response_to_model(version_number, country): ).first() if not school: - print('ERROR: School with Giga ID ({0}) not found in PROCO DB. ' + logger.error('School with giga ID ({0}) not found in proco db. ' 'Hence skipping the load for current school.'.format(row['school_id_giga'])) continue @@ -147,10 +150,10 @@ def load_qos_data_source_response_to_model(version_number, country): version__gt=version_number, ) if duplicate_higher_version_records.exists(): - print('ERROR: Higher version for same School ID and Timestamp already exists. ' + logger.error('Higher version for same school ID and timestamp already exists. ' 'Hence skipping the update for current row.') qos_instance = duplicate_higher_version_records.first() - print('School ID: {0},\tTimestamp: {1},\tCurrent Version: {2},\t' + logger.debug('School ID: {0},\tTimestamp: {1},\tCurrent Version: {2},\t' 'Higher Version: {3}'.format(qos_instance.school_id, qos_instance.timestamp, version_number, qos_instance.version)) continue @@ -158,24 +161,23 @@ def load_qos_data_source_response_to_model(version_number, country): insert_entries.append(row_as_dict) if len(insert_entries) == 5000: - print('Loading the data to "QoSData" table as it has reached 5000 benchmark.') + logger.info('Loading the data to "QoSData" table as it has reached 5000 benchmark.') bulk_create_or_update(insert_entries, QoSData, ['school', 'timestamp']) insert_entries = [] - print('#' * 10) - print('\n\n') + logger.debug('#\n' * 10) - print('Loading the remaining ({0}) data to "QoSData" table.'.format(len(insert_entries))) + logger.debug('Loading the remaining ({0}) data to "QoSData" table.'.format(len(insert_entries))) if len(insert_entries) > 0: bulk_create_or_update(insert_entries, QoSData, ['school', 'timestamp']) else: - print('INFO: No data to update in current table: {0}.'.format(table_name)) + logger.debug('No data to update in current table: {0}.'.format(table_name)) except Exception as ex: - print('ERROR: Exception caught for "{0}": {1}'.format(schema_table.name, str(ex))) + logger.error('Exception caught for "{0}": {1}'.format(schema_table.name, str(ex))) else: - print('ERROR: QoS schema ({0}) does not exist to use for share ({1}).'.format(schema_name, share_name)) + logger.error('QoS schema ({0}) does not exist to use for share ({1}).'.format(schema_name, share_name)) exit(0) else: - print('ERROR: QoS share ({0}) does not exist to use.'.format(share_name)) + logger.error('QoS share ({0}) does not exist to use.'.format(share_name)) exit(0) @@ -199,10 +201,10 @@ def sync_qos_realtime_data(date, country): ).order_by('school') if not qos_measurements.exists(): - print('ERROR: No records to aggregate on provided date: "{0}". Hence skipping for the given date.'.format(date)) + logger.debug('No records to aggregate on provided date: "{0}". Hence skipping for the given date.'.format(date)) return - print('Migrating the records from "QoSData" to "RealTimeConnectivity" with date: {0} '.format(date)) + logger.debug('Migrating the records from "QoSData" to "RealTimeConnectivity" with date: {0} '.format(date)) realtime = [] @@ -244,11 +246,11 @@ def sync_qos_realtime_data(date, country): )) if len(realtime) == 5000: - print('Loading the data to "RealTimeConnectivity" table as it has reached 5000 benchmark.') + logger.debug('Loading the data to "RealTimeConnectivity" table as it has reached 5000 benchmark.') RealTimeConnectivity.objects.bulk_create(realtime) realtime = [] - print('Loading the remaining ({0}) data to "RealTimeConnectivity" table.'.format(len(realtime))) + logger.debug('Loading the remaining ({0}) data to "RealTimeConnectivity" table.'.format(len(realtime))) if len(realtime) > 0: RealTimeConnectivity.objects.bulk_create(realtime) @@ -265,7 +267,7 @@ def get_latest_api_version(country_code=None): if qos_schema: schema_tables = client.list_tables(qos_schema) - print('\nAll tables ready to access: {0}'.format(schema_tables)) + logger.debug('\nAll tables ready to access: {0}'.format(schema_tables)) for schema_table in schema_tables: table_name = schema_table.name @@ -284,11 +286,11 @@ def get_latest_api_version(country_code=None): ) table_current_version = delta_sharing.get_table_version(table_url) - print('Country "{0}" current version from API: {1}\n'.format(table_name, table_current_version)) + logger.debug('Country "{0}" current version from API: {1}\n'.format(table_name, table_current_version)) version_for_countries[table_name] = table_current_version except Exception as ex: - print('ERROR: Exception caught for "{0}": {1}\n'.format(table_name, str(ex))) + logger.error('Exception caught for "{0}": {1}\n'.format(table_name, str(ex))) return version_for_countries @@ -320,9 +322,9 @@ def check_missing_versions_from_table(country_code=None): missing_version_list = list(set(must_version_list) - set(versions_list)) - print('Missing versions details for country "{0}" are: \n\tStart Version from DB: {1}' - '\n\tEnd Version from API: {2}' - '\n\tmissing versions: {3}\n'.format(country_iso_code, start_version, end_version, missing_version_list)) + logger.debug('Missing versions details for country "{0}" are: \n\tStart version from DB: {1}' + '\n\tEnd version from API: {2}' + '\n\tMissing versions: {3}\n'.format(country_iso_code, start_version, end_version, missing_version_list)) class Command(BaseCommand): @@ -374,7 +376,7 @@ def add_arguments(self, parser): ) def handle(self, **options): - print('Executing "data_loss_recovery_for_QoS" ....\n') + logger.info('Executing data loss recovery for QoS" ....\n') check_missing_versions = options.get('check_missing_versions') country_iso3_format = options.get('country_iso3_format') @@ -382,22 +384,22 @@ def handle(self, **options): country = None if country_iso3_format: country = Country.objects.filter(iso3_format=country_iso3_format).first() - print('Country object: {0}'.format(country)) + logger.debug('Country object: {0}'.format(country)) if not country: - print('ERROR: Country with ISO3 Format ({0}) not found in PROCO DB. ' + logger.error('Country with ISO3 format ({0}) not found in proco db. ' 'Hence stopping the load.'.format(country_iso3_format)) exit(0) if check_missing_versions: - print('\n*** Checking the missing versions ***') + logger.info('\nChecking the missing versions.') check_missing_versions_from_table(country_code=country_iso3_format) - print('*** Checking the missing versions action completed successfully ***\n') + logger.debug('Checking the missing versions action completed successfully.\n') pull_data = options.get('pull_data') if pull_data: if not country: - print('ERROR: Country Code is mandatory to pull the data.' + logger.error('Country code is mandatory to pull the data.' ' Please pass required parameters as: -country_code=\n') exit(0) @@ -405,12 +407,12 @@ def handle(self, **options): pull_end_version = options.get('pull_end_version') if pull_start_version and pull_end_version and pull_start_version <= pull_end_version: - print('\n*** Loading the API data to "data_sources_qosdata" table ***\n') + logger.debug('\nLoading the api data to "data_sources_qosdata" table ***\n') for version_number in range(pull_start_version, pull_end_version + 1): load_qos_data_source_response_to_model(version_number, country) - print('\n*** Data load completed successfully ***\n') + logger.info('\nData load completed successfully.\n') else: - print('ERROR: Please provide valid required parameters as:' + logger.error('Please provide valid required parameters as:' ' -pull_start_version= -pull_end_version=\n') exit(0) @@ -422,7 +424,7 @@ def handle(self, **options): aggregate_data = options.get('aggregate_data') if aggregate_data: if not country: - print('ERROR: Country Code is mandatory to aggregate the data.' + logger.error('Country code is mandatory to aggregate the data.' ' Please pass required parameters as: -country_code=') exit(0) @@ -439,26 +441,26 @@ def handle(self, **options): date_list_from_versions = qos_queryset.order_by('timestamp__date').values_list( 'timestamp__date', flat=True).distinct('timestamp__date') - print('date_list_from_versions: {0}'.format(date_list_from_versions)) + logger.debug('Date list from versions: {0}'.format(date_list_from_versions)) for pull_data_date in date_list_from_versions: - print('\nSyncing the "data_sources_qosdata" data to "connection_statistics_realtimeconnectivity" ' + logger.debug('\nSyncing the "data_sources_qosdata" data to "connection_statistics_realtimeconnectivity" ' 'for date: {0}'.format(pull_data_date)) sync_qos_realtime_data(pull_data_date, country) - print('Data synced successfully.\n\n') + logger.debug('Data synced successfully.\n\n') - print('Starting finalizing the records to actual proco tables.') + logger.debug('Starting finalizing the records to actual proco tables.') monday_date = pull_data_date - timedelta(days=pull_data_date.weekday()) monday_week_no = date_utilities.get_week_from_date(monday_date) monday_year = date_utilities.get_year_from_date(monday_date) - print('Weekly record details. \tWeek No: {0}\tYear: {1}'.format(monday_week_no, monday_year)) + logger.debug('Weekly record details. \tWeek No: {0}\tYear: {1}'.format(monday_week_no, monday_year)) - print('\n\nFinalizing the records for Country ID: {0}'.format(country.id)) + logger.debug('\n\nFinalizing the records for country ID: {0}'.format(country.id)) finalize_previous_day_data(None, country.id, pull_data_date) - print('Finalized records successfully to actual proco tables.\n\n') + logger.debug('Finalized records successfully to actual proco tables.\n\n') else: - print('ERROR: Please pass required parameters as:' + logger.error('Please pass required parameters as:' ' -pull_start_version= -pull_end_version=') - print('Completed "data_loss_recovery_for_qos" successfully ....\n') + logger.info('Completed data loss recovery for qos successfully.\n') exit(0) diff --git a/proco/data_sources/migrations/0012_added_deleted_published_status.py b/proco/data_sources/migrations/0012_added_deleted_published_status.py old mode 100644 new mode 100755 diff --git a/proco/data_sources/serializers.py b/proco/data_sources/serializers.py index 2211897..5ee1419 100644 --- a/proco/data_sources/serializers.py +++ b/proco/data_sources/serializers.py @@ -375,7 +375,6 @@ def _validate_status(self, instance, validated_data): raise source_exceptions.InvalidSchoolMasterDataRowStatusError(message_kwargs=message_kwargs) def delete_all_related_rows(self, instance): - print('Deleting all the row for same school giga id: {0}'.format(instance.school_id_giga)) sources_models.SchoolMasterData.objects.filter( school_id_giga=instance.school_id_giga, ).exclude( diff --git a/proco/data_sources/tasks.py b/proco/data_sources/tasks.py index fb9c34b..b36ac16 100644 --- a/proco/data_sources/tasks.py +++ b/proco/data_sources/tasks.py @@ -1,16 +1,18 @@ import json +import logging import os +import uuid from datetime import timedelta -from celery import chain, chord, group +from celery import chain, chord, group, current_task from django.conf import settings from django.contrib.gis.geos import Point -from django.core.cache import cache from django.db.models import Count from django.db.utils import DataError from requests.exceptions import HTTPError from proco.accounts import utils as account_utilities +from proco.background import utils as background_task_utilities from proco.connection_statistics import models as statistics_models from proco.connection_statistics.utils import ( aggregate_real_time_data_to_school_daily_status, @@ -28,6 +30,9 @@ from proco.schools.models import School from proco.taskapp import app from proco.utils.dates import format_date +from proco.utils.tasks import populate_school_new_fields_task + +logger = logging.getLogger('gigamaps.' + __name__) @app.task @@ -41,7 +46,7 @@ def load_data_from_school_master_apis(*args, country_iso3_format=None): Execution Frequency: Once in a week """ - print('***** Loading the School Master Data to PROCO DB *****') + logger.info('Starting loading the school master data from API to DB.') errors = [] ds_settings = settings.DATA_SOURCE_CONFIG.get('SCHOOL_MASTER') @@ -77,20 +82,20 @@ def load_data_from_school_master_apis(*args, country_iso3_format=None): if school_master_schema: schema_tables = client.list_tables(school_master_schema) - print('All tables ready to access: {0}'.format(schema_tables)) + logger.debug('All tables ready to access: {0}'.format(schema_tables)) school_master_fields = [f.name for f in sources_models.SchoolMasterData._meta.get_fields()] for schema_table in schema_tables: - print('#' * 10) - print('Table: %s', schema_table) + logger.debug('#' * 10) + logger.debug('Table: %s', schema_table) if country_iso3_format and country_iso3_format != schema_table.name: continue if len(country_codes_for_exclusion) > 0 and schema_table.name in country_codes_for_exclusion: - print('WARNING: Country with ISO3 Format ({0}) configured to exclude from School Master data pull. ' - 'Hence skipping the load for this country code.'.format(schema_table.name)) + logger.warning('Country with ISO3 Format ({0}) configured to exclude from School Master data pull. ' + 'Hence skipping the load for this country code.'.format(schema_table.name)) continue try: @@ -98,17 +103,17 @@ def load_data_from_school_master_apis(*args, country_iso3_format=None): profile_file, share_name, schema_name, schema_table.name, changes_for_countries, deleted_schools, school_master_fields) except (HTTPError, DataError, ValueError) as ex: - print('ERROR: Exception caught for "{0}": {1}'.format(schema_table.name, str(ex))) + logger.error('Exception caught for "{0}": {1}'.format(schema_table.name, str(ex))) errors.append('{0} : {1} - {2}'.format(schema_table.name, type(ex).__name__, str(ex))) except Exception as ex: - print('ERROR: Exception caught for "{0}": {1}'.format(schema_table.name, str(ex))) + logger.error('Exception caught for "{0}": {1}'.format(schema_table.name, str(ex))) errors.append('{0} : {1} - {2}'.format(schema_table.name, type(ex).__name__, str(ex))) else: - print('ERROR: School Master schema ({0}) does not exist to use for share ({1}).'.format(schema_name, + logger.error('School Master schema ({0}) does not exist to use for share ({1}).'.format(schema_name, share_name)) else: - print('ERROR: School Master share ({0}) does not exist to use.'.format(share_name)) + logger.error('School Master share ({0}) does not exist to use.'.format(share_name)) try: os.remove(profile_file) @@ -175,7 +180,7 @@ def handle_published_school_master_data_row(published_row=None, country_ids=None Execution Frequency: Every 12 hours """ - print('***** Handling the School Master Data row publish *****') + logger.info('Handling the published school master data rows.') environment_map = { 'urban': 'urban', @@ -187,27 +192,29 @@ def handle_published_school_master_data_row(published_row=None, country_ids=None true_choices = ['true', 'yes', '1'] if country_ids and len(country_ids) > 0: - task_cache_key = 'handle_published_school_master_data_row_status_{current_time}_country_ids_{ids}'.format( + task_key = 'handle_published_school_master_data_row_status_{current_time}_country_ids_{ids}'.format( current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H'), ids='_'.join([str(c_id) for c_id in country_ids]), ) + task_description = 'Handle published school master data rows for countries' elif published_row: - task_cache_key = 'handle_published_school_master_data_row_status_{current_time}_row_id_{ids}'.format( + task_key = 'handle_published_school_master_data_row_status_{current_time}_row_id_{ids}'.format( current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H'), ids=published_row.id, ) + task_description = 'Handle published school master data row for single record' else: - task_cache_key = 'handle_published_school_master_data_row_status_{current_time}'.format( + task_key = 'handle_published_school_master_data_row_status_{current_time}'.format( current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H')) - running_task = cache.get(task_cache_key, None) - - print('***** Before checking the task status in Redis *****') + task_description = 'Handle published school master data rows' - if running_task in [None, b'completed', 'completed'] or published_row: - print('***** Not found running Job *****') - cache.set(task_cache_key, 'running', None) - print('***** After checking the task status in Redis *****') + task_id = current_task.request.id or str(uuid.uuid4()) + task_instance = background_task_utilities.task_on_start( + task_id, task_key, task_description) + if task_instance: + logger.debug('Not found running job: {}'.format(task_key)) + updated_school_ids = [] new_published_records = sources_models.SchoolMasterData.objects.filter( status=sources_models.SchoolMasterData.ROW_STATUS_PUBLISHED, is_read=False, ) @@ -218,11 +225,11 @@ def handle_published_school_master_data_row(published_row=None, country_ids=None if country_ids and len(country_ids) > 0: new_published_records = new_published_records.filter(country_id__in=country_ids) + task_instance.info('Total published records to update: {}'.format(new_published_records.count())) count = 0 - print('***** Before starting the iteration *****') - for data_chunk in core_utilities.queryset_iterator(new_published_records, chunk_size=10, print_msg=False): + + for data_chunk in core_utilities.queryset_iterator(new_published_records, chunk_size=100, print_msg=False): count += 1 - print('***** Iteration No: {} *****'.format(count)) for row in data_chunk: try: environment = row.school_area_type.lower() if not core_utilities.is_blank_string( @@ -388,14 +395,20 @@ def handle_published_school_master_data_row(published_row=None, country_ids=None row.is_read = True row.school = school row.save() + + updated_school_ids.append(school.id) except Exception as ex: - print('Error reported on publishing: {0}'.format(ex)) - print('Record: {0}'.format(row.__dict__)) + logger.error('Error reported on publishing: {0}'.format(ex)) + logger.error('Record: {0}'.format(row.__dict__)) + task_instance.info('Error reported for ID ({0}) on publishing: {1}'.format(row.id, ex)) + + if len(updated_school_ids) > 0: + for i in range(0, len(updated_school_ids), 20): + populate_school_new_fields_task.delay(None, None, None, school_ids=updated_school_ids[i:i + 20]) - cache.set(task_cache_key, 'completed', None) + background_task_utilities.task_on_complete(task_instance) else: - print('***** Found running Job with "{0}" name so skipping current iteration *****'.format(task_cache_key)) - # TODO: Handle cache clean + logger.error('Found running Job with "{0}" name so skipping current iteration'.format(task_key)) @app.task(soft_time_limit=2 * 55 * 60, time_limit=2 * 55 * 60) @@ -405,27 +418,29 @@ def handle_deleted_school_master_data_row(deleted_row=None, country_ids=None): Execution Frequency: Every day """ - print('***** Handling the School Master Data row deletion *****') + logger.info('Handling the deleted school master data rows.') if country_ids and len(country_ids) > 0: - task_cache_key = 'handle_deleted_school_master_data_row_status_{current_time}_country_ids_{ids}'.format( + task_key = 'handle_deleted_school_master_data_row_status_{current_time}_country_ids_{ids}'.format( current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H'), ids='_'.join([str(c_id) for c_id in country_ids]), ) + task_description = 'Handle deleted school master data rows for countries' elif deleted_row: - task_cache_key = 'handle_deleted_school_master_data_row_status_{current_time}_row_id_{ids}'.format( + task_key = 'handle_deleted_school_master_data_row_status_{current_time}_row_id_{ids}'.format( current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H'), ids=deleted_row.id, ) + task_description = 'Handle deleted school master data row for single record' else: - task_cache_key = 'handle_deleted_school_master_data_row_status_{current_time}'.format( + task_key = 'handle_deleted_school_master_data_row_status_{current_time}'.format( current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H')) + task_description = 'Handle deleted school master data rows' - running_task = cache.get(task_cache_key, None) - - if running_task in [None, b'completed', 'completed'] or deleted_row: - print('***** Not found running Job *****') - cache.set(task_cache_key, 'running', None) + task_id = current_task.request.id or str(uuid.uuid4()) + task_instance = background_task_utilities.task_on_start(task_id, task_key, task_description) + if task_instance: + logger.debug('Not found running job: {}'.format(task_key)) new_deleted_records = sources_models.SchoolMasterData.objects.filter( status=sources_models.SchoolMasterData.ROW_STATUS_DELETED_PUBLISHED, is_read=False, @@ -439,6 +454,7 @@ def handle_deleted_school_master_data_row(deleted_row=None, country_ids=None): new_deleted_records = new_deleted_records.filter(country_id__in=country_ids) current_date = core_utilities.get_current_datetime_object() + task_instance.info('Total records to update: {}'.format(new_deleted_records.count())) for data_chunk in core_utilities.queryset_iterator(new_deleted_records, chunk_size=1000): for row in data_chunk: @@ -456,13 +472,14 @@ def handle_deleted_school_master_data_row(deleted_row=None, country_ids=None): row.save() except Exception as ex: - print('Error reported on deletion: {0}'.format(ex)) - print('Record: {0}'.format(row.__dict__)) + logger.error('Error reported on deletion: {0}'.format(ex)) + logger.error('Record: {0}'.format(row.__dict__)) + task_instance.info('Error reported for ID ({0}) on deletion: {1}'.format(row.id, ex)) - cache.set(task_cache_key, 'completed', None) + task_instance.info('Remaining records: {}'.format(new_deleted_records.count())) + background_task_utilities.task_on_complete(task_instance) else: - print('***** Found running Job with "{0}" name so skipping current iteration *****'.format(task_cache_key)) - # TODO: Handle cache clean + logger.error('Found running Job with "{0}" name so skipping current iteration'.format(task_key)) @app.task @@ -474,138 +491,158 @@ def email_reminder_to_editor_and_publisher_for_review_waiting_records(): Execution Frequency: Every day only once """ - task_cache_key = 'email_reminder_to_editor_and_publisher_for_review_waiting_records_status_{current_time}'.format( + task_key = 'email_reminder_to_editor_and_publisher_for_review_waiting_records_status_{current_time}'.format( current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y')) - running_task = cache.get(task_cache_key, None) + task_id = current_task.request.id or str(uuid.uuid4()) + task_instance = background_task_utilities.task_on_start( + task_id, task_key, 'Send reminder email to Editor and Publisher to review the school master rows') + + if task_instance: + logger.debug('Not found running job: {}'.format(task_key)) - if running_task in [None, b'completed', 'completed']: - print('***** Not found running Job *****') - cache.set(task_cache_key, 'running', None) ds_settings = settings.DATA_SOURCE_CONFIG.get('SCHOOL_MASTER') review_grace_period = core_utilities.convert_to_int(ds_settings['REVIEW_GRACE_PERIOD_IN_HRS'], default='48') - print('***** Sending email reminder to Editor/Publisher if records are waiting for more ' - 'than {0} hrs *****'.format(review_grace_period)) - - current_time = core_utilities.get_current_datetime_object() - check_time = current_time - timedelta(hours=review_grace_period) - email_user_list = [] - - # If there are records for all editor to review which collected date is more than 48 hrs - has_records_to_review_for_all_editors = sources_models.SchoolMasterData.objects.filter( - status=sources_models.SchoolMasterData.ROW_STATUS_DRAFT, - modified__lt=check_time, - ).exists() - - # If there are records for all publishers to review which are sent to publishers - # to publish more than 48 hrs back - has_records_to_review_for_all_publishers = sources_models.SchoolMasterData.objects.filter( - status__in=[ - sources_models.SchoolMasterData.ROW_STATUS_DRAFT_LOCKED, - sources_models.SchoolMasterData.ROW_STATUS_DELETED, - ], - is_read=False, - modified__lt=check_time, - ).exists() - - # If it has records for all editors and publishers to review than send the reminder email to all - if has_records_to_review_for_all_editors and has_records_to_review_for_all_publishers: - print('All Editors and Publishers has records to review') - email_user_list.extend(get_user_emails_for_permissions([ - auth_models.RolePermission.CAN_UPDATE_SCHOOL_MASTER_DATA, - auth_models.RolePermission.CAN_PUBLISH_SCHOOL_MASTER_DATA, - ])) + logger.info('Sending email reminder to Editor/Publisher if records are waiting for more ' + 'than {0} hrs'.format(review_grace_period)) + task_instance.info('Sending email reminder to Editor/Publisher if records are waiting for ' + 'more than {0} hrs'.format(review_grace_period)) + + if ( + core_utilities.is_blank_string(settings.ANYMAIL.get('MAILJET_API_KEY')) or + core_utilities.is_blank_string(settings.ANYMAIL.get('MAILJET_SECRET_KEY')) + ): + logger.error('MailJet creds are not configured to send the email. Hence email notification is disabled.') + task_instance.info('ERROR: MailJet creds are not configured to send the email. Hence email notification is ' + 'disabled.') else: - # If all editors have records to review, then send reminder email - if has_records_to_review_for_all_editors: - print('All Editors has records to review') - email_user_list.extend( - get_user_emails_for_permissions([auth_models.RolePermission.CAN_UPDATE_SCHOOL_MASTER_DATA])) + current_time = core_utilities.get_current_datetime_object() + check_time = current_time - timedelta(hours=review_grace_period) + email_user_list = [] + + # If there are records for all editor to review which collected date is more than 48 hrs + has_records_to_review_for_all_editors = sources_models.SchoolMasterData.objects.filter( + status=sources_models.SchoolMasterData.ROW_STATUS_DRAFT, + modified__lt=check_time, + ).exists() + + # If there are records for all publishers to review which are sent to publishers + # to publish more than 48 hrs back + has_records_to_review_for_all_publishers = sources_models.SchoolMasterData.objects.filter( + status__in=[ + sources_models.SchoolMasterData.ROW_STATUS_DRAFT_LOCKED, + sources_models.SchoolMasterData.ROW_STATUS_DELETED, + ], + is_read=False, + modified__lt=check_time, + ).exists() + + # If it has records for all editors and publishers to review than send the reminder email to all + if has_records_to_review_for_all_editors and has_records_to_review_for_all_publishers: + logger.info('All Editors and Publishers has records to review') + task_instance.info('All Editors and Publishers has records to review') + email_user_list.extend(get_user_emails_for_permissions([ + auth_models.RolePermission.CAN_UPDATE_SCHOOL_MASTER_DATA, + auth_models.RolePermission.CAN_PUBLISH_SCHOOL_MASTER_DATA, + ])) else: - # Else send the email to those editors who have updated the DRAFT records but not touched - # it in last 48 hrs - editor_ids_who_has_old_updated_records = list(sources_models.SchoolMasterData.objects.filter( - status=sources_models.SchoolMasterData.ROW_STATUS_UPDATED_IN_DRAFT, - modified__lt=check_time, - ).values_list('modified_by_id', flat=True).order_by('modified_by_id').distinct('modified_by_id')) - - if len(editor_ids_who_has_old_updated_records) > 0: - print('Only few Editors has records to review') + # If all editors have records to review, then send reminder email + if has_records_to_review_for_all_editors: + logger.info('All Editors has records to review') + task_instance.info('All Editors has records to review') email_user_list.extend( - get_user_emails_for_permissions( - [auth_models.RolePermission.CAN_UPDATE_SCHOOL_MASTER_DATA], - ids_to_filter=editor_ids_who_has_old_updated_records) - ) - - # If all publishers have records to review, then send reminder email to all - if has_records_to_review_for_all_publishers: - print('All Publishers has records to review') - email_user_list.extend( - get_user_emails_for_permissions([auth_models.RolePermission.CAN_PUBLISH_SCHOOL_MASTER_DATA])) - else: - # Else send the email to those publishers who have updated the records but not touched it in last 48 hrs - publisher_ids_who_has_old_updated_records = list(sources_models.SchoolMasterData.objects.filter( - status=sources_models.SchoolMasterData.ROW_STATUS_UPDATED_IN_DRAFT_LOCKED, - modified__lt=check_time, - ).values_list('modified_by_id', flat=True).order_by('modified_by_id').distinct('modified_by_id')) - - if len(publisher_ids_who_has_old_updated_records) > 0: - print('Only few Publishers has records to review') + get_user_emails_for_permissions([auth_models.RolePermission.CAN_UPDATE_SCHOOL_MASTER_DATA])) + else: + # Else send the email to those editors who have updated the DRAFT records but not touched + # it in last 48 hrs + editor_ids_who_has_old_updated_records = list(sources_models.SchoolMasterData.objects.filter( + status=sources_models.SchoolMasterData.ROW_STATUS_UPDATED_IN_DRAFT, + modified__lt=check_time, + ).values_list('modified_by_id', flat=True).order_by('modified_by_id').distinct('modified_by_id')) + + if len(editor_ids_who_has_old_updated_records) > 0: + logger.info('Only few Editors has records to review') + task_instance.info('Only few Editors has records to review') + email_user_list.extend( + get_user_emails_for_permissions( + [auth_models.RolePermission.CAN_UPDATE_SCHOOL_MASTER_DATA], + ids_to_filter=editor_ids_who_has_old_updated_records) + ) + + # If all publishers have records to review, then send reminder email to all + if has_records_to_review_for_all_publishers: + logger.info('All Publishers has records to review') + task_instance.info('All Publishers has records to review') email_user_list.extend( - get_user_emails_for_permissions( - [auth_models.RolePermission.CAN_PUBLISH_SCHOOL_MASTER_DATA], - ids_to_filter=publisher_ids_who_has_old_updated_records) - ) - - if len(email_user_list) > 0: - # Get the unique email IDs so it sends only 1 email - unique_email_ids = set(email_user_list) - - email_subject = sources_config.school_master_records_to_review_email_subject_format % ( - core_utilities.get_project_title() - ) - - dashboard_url = ds_settings['DASHBOARD_URL'] - email_message = sources_config.school_master_records_to_review_email_message_format.format( - dashboard_url='Dashboard url: {}'.format(dashboard_url) if dashboard_url else '', - ) - - email_content = {'subject': email_subject, 'message': email_message} - print('Sending the below emails:\n' - 'To: {0}\n' - 'Subject: {1}\n' - 'Body: {2}'.format(unique_email_ids, email_subject, email_message)) - account_utilities.send_email_over_mailjet_service(unique_email_ids, **email_content) - cache.set(task_cache_key, 'completed', None) + get_user_emails_for_permissions([auth_models.RolePermission.CAN_PUBLISH_SCHOOL_MASTER_DATA])) + else: + # Else send the email to those publishers who have updated the records + # but not touched it in last 48 hrs + publisher_ids_who_has_old_updated_records = list(sources_models.SchoolMasterData.objects.filter( + status=sources_models.SchoolMasterData.ROW_STATUS_UPDATED_IN_DRAFT_LOCKED, + modified__lt=check_time, + ).values_list('modified_by_id', flat=True).order_by('modified_by_id').distinct('modified_by_id')) + + if len(publisher_ids_who_has_old_updated_records) > 0: + logger.info('Only few Publishers has records to review') + task_instance.info('Only few Publishers has records to review') + email_user_list.extend( + get_user_emails_for_permissions( + [auth_models.RolePermission.CAN_PUBLISH_SCHOOL_MASTER_DATA], + ids_to_filter=publisher_ids_who_has_old_updated_records) + ) + + if len(email_user_list) > 0: + # Get the unique email IDs so it sends only 1 email + unique_email_ids = set(email_user_list) + + email_subject = sources_config.school_master_records_to_review_email_subject_format % ( + core_utilities.get_project_title() + ) + + dashboard_url = ds_settings['DASHBOARD_URL'] + email_message = sources_config.school_master_records_to_review_email_message_format.format( + dashboard_url='Dashboard url: {}'.format(dashboard_url) if dashboard_url else '', + ) + + email_content = {'subject': email_subject, 'message': email_message} + logger.info('Sending the below emails:\n' + 'To: {0}\n' + 'Subject: {1}\n' + 'Body: {2}'.format(unique_email_ids, email_subject, email_message)) + task_instance.info('Sending the below emails:\tTo: {0}\tSubject: {1}\tBody: {2}'.format( + unique_email_ids, email_subject, email_message)) + account_utilities.send_email_over_mailjet_service(unique_email_ids, **email_content) + + background_task_utilities.task_on_complete(task_instance) else: - print('***** Found running Job with "{0}" name so skipping current iteration *****'.format(task_cache_key)) + logger.error('Found running Job with "{0}" name so skipping current iteration'.format(task_key)) @app.task(soft_time_limit=60 * 60, time_limit=60 * 60) def load_data_from_daily_check_app_api(*args): - print('***** Loading the DailyCheckApp Data to PROCO DB *****') + logger.info('Loading the DailyCheckApp data to DB.') source_utilities.sync_dailycheckapp_realtime_data() - print('***** Loaded the DailyCheckApp Data to PROCO DB - SUCCESS *****') + logger.info('Loaded the DailyCheckApp data to DB successfully.') @app.task(soft_time_limit=4 * 60 * 60, time_limit=4 * 60 * 60) def load_data_from_qos_apis(*args): - print('***** Loading the QoS Data to PROCO DB *****') + logger.info('Loading the QoS data to DB.') source_utilities.load_qos_data_source_response_to_model() source_utilities.sync_qos_realtime_data() - print('***** Loaded the QoS Data to PROCO DB - SUCCESS *****') + logger.info('Loaded the QoS data to DB successfully.') @app.task(soft_time_limit=2 * 60 * 60, time_limit=2 * 60 * 60) def cleanup_school_master_rows(): - task_cache_key = 'cleanup_school_master_rows_status_{current_time}'.format( + task_key = 'cleanup_school_master_rows_status_{current_time}'.format( current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H')) - running_task = cache.get(task_cache_key, None) - - if running_task in [None, b'completed', 'completed']: - print('***** Not found running Job *****') - cache.set(task_cache_key, 'running', None) + task_id = current_task.request.id or str(uuid.uuid4()) + task_instance = background_task_utilities.task_on_start(task_id, task_key, 'Cleanup school master rows') + if task_instance: + logger.debug('Not found running job: {}'.format(task_key)) # Delete all the old records where more than 1 record are in DRAFT/UPDATED_IN_DRAFT or # ROW_STATUS_DRAFT_LOCKED/ROW_STATUS_UPDATED_IN_DRAFT_LOCKED for same School GIGA ID rows_with_more_than_1_record_in_draft = sources_models.SchoolMasterData.objects.filter( @@ -618,9 +655,10 @@ def cleanup_school_master_rows(): ).values('school_id_giga', 'country_id').annotate( total_records=Count('school_id_giga', distinct=False), ).order_by('-total_records', 'school_id_giga', 'country_id').filter(total_records__gt=1) - print('Queryset to get all the old records to delete where more than 1 record are in DRAFT/' - 'UPDATED_IN_DRAFT/ROW_STATUS_DRAFT_LOCKED/ROW_STATUS_UPDATED_IN_DRAFT_LOCKED ' - 'for same School GIGA ID: {0}'.format(rows_with_more_than_1_record_in_draft.query)) + + logger.debug('Queryset to get all the old records to delete where more than 1 record are in DRAFT/' + 'UPDATED_IN_DRAFT/ROW_STATUS_DRAFT_LOCKED/ROW_STATUS_UPDATED_IN_DRAFT_LOCKED ' + 'for same School GIGA ID: {0}'.format(rows_with_more_than_1_record_in_draft.query)) for row in rows_with_more_than_1_record_in_draft: for row_to_delete in sources_models.SchoolMasterData.objects.filter( @@ -628,14 +666,19 @@ def cleanup_school_master_rows(): country_id=row['country_id'], ).order_by('-created')[1:]: row_to_delete.delete() + task_instance.info('Deleted rows where more than 1 record are in DRAFT/' + 'UPDATED_IN_DRAFT/ROW_STATUS_DRAFT_LOCKED/ROW_STATUS_UPDATED_IN_DRAFT_LOCKED ' + 'for same School GIGA ID') + # Delete all the old records where more than 1 record are in is_read=True for same School GIGA ID rows_with_more_than_1_record_in_read = sources_models.SchoolMasterData.objects.filter( is_read=True, ).values('school_id_giga', 'country_id').annotate( total_records=Count('school_id_giga', distinct=False), ).order_by('-total_records').filter(total_records__gt=1) - print('Queryset to get all the old records to delete where more than 1 record are in is_read=True ' - 'for same School GIGA ID: {0}'.format(rows_with_more_than_1_record_in_read.query)) + + logger.debug('Queryset to get all the old records to delete where more than 1 record are in is_read=True ' + 'for same School GIGA ID: {0}'.format(rows_with_more_than_1_record_in_read.query)) for row in rows_with_more_than_1_record_in_read: for row_to_delete in sources_models.SchoolMasterData.objects.filter( @@ -643,10 +686,11 @@ def cleanup_school_master_rows(): country_id=row['country_id'], ).order_by('-published_at')[1:]: row_to_delete.delete() + task_instance.info('Deleted rows where more than 1 record are in is_read=True for same School GIGA ID') - cache.set(task_cache_key, 'completed', None) + background_task_utilities.task_on_complete(task_instance) else: - print('***** Found running Job with "{0}" name so skipping current iteration *****'.format(task_cache_key)) + logger.error('Found running Job with "{0}" name so skipping current iteration'.format(task_key)) @app.task(soft_time_limit=6 * 60 * 60, time_limit=6 * 60 * 60) @@ -658,25 +702,25 @@ def update_static_data(*args, country_iso3_format=None): Execution Frequency: Once in a week/once in 2 weeks """ - task_cache_key = 'update_static_data_status_{current_time}'.format( - current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y')) - running_task = cache.get(task_cache_key, None) - - if running_task in [None, b'completed', 'completed']: - print('***** Not found running Job *****') - cache.set(task_cache_key, 'running', None) + task_key = 'update_static_data_status_{current_time}'.format( + current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H')) + task_id = current_task.request.id or str(uuid.uuid4()) + task_instance = background_task_utilities.task_on_start( + task_id, task_key, 'Sync Static Data from School Master sources', check_previous=True) + if task_instance: + logger.debug('Not found running job: {}'.format(task_key)) load_data_from_school_master_apis(country_iso3_format=country_iso3_format) + task_instance.info('Completed the load data from School Master API call') cleanup_school_master_rows.s() - - cache.set(task_cache_key, 'completed', None) + task_instance.info('Scheduled cleanup school master rows') + background_task_utilities.task_on_complete(task_instance) else: - print('***** Found running Job with "{0}" name so skipping current iteration *****'.format(task_cache_key)) + logger.error('Found Job with "{0}" name so skipping current iteration'.format(task_key)) @app.task(soft_time_limit=60 * 60, time_limit=60 * 60) def finalize_previous_day_data(_prev_result, country_id, date, *args): - print('Inside finalize_previous_day_data() *** ', country_id) country = Country.objects.get(id=country_id) aggregate_real_time_data_to_school_daily_status(country, date) @@ -699,17 +743,15 @@ def update_live_data(*args, today=True): Execution Frequency: 4-5 times a day """ - - task_cache_key = 'update_live_data_status_{current_time}_{today}'.format( + task_key = 'update_live_data_status_{current_time}_{today}'.format( current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H'), today=today, ) - running_task = cache.get(task_cache_key, None) - - if running_task in [None, b'completed', 'completed']: - print('***** Not found running Job *****') - cache.set(task_cache_key, 'running', None) + task_id = current_task.request.id or str(uuid.uuid4()) + task_instance = background_task_utilities.task_on_start(task_id, task_key, 'Sync Realtime Data from Live sources') + if task_instance: + logger.debug('Not found running job: {}'.format(task_key)) countries_ids = Country.objects.values_list('id', flat=True) if today: @@ -743,38 +785,41 @@ def update_live_data(*args, today=True): ).delay() - cache.set(task_cache_key, 'completed', None) + background_task_utilities.task_on_complete(task_instance) else: - print('***** Found running Job with "{0}" name so skipping current iteration *****'.format(task_cache_key)) + logger.error('Found running Job with "{0}" name so skipping current iteration'.format(task_key)) @app.task(soft_time_limit=1 * 60 * 60, time_limit=1 * 60 * 60) def clean_old_live_data(): current_datetime = core_utilities.get_current_datetime_object() - task_cache_key = 'clean_old_live_data_status_{current_time}'.format( - current_time=format_date(current_datetime, frmt='%d%m%Y'), + task_key = 'clean_old_live_data_status_{current_time}'.format( + current_time=format_date(current_datetime, frmt='%d%m%Y_%H'), ) - running_task = cache.get(task_cache_key, None) - - if running_task in [None, b'completed', 'completed']: - print('***** Not found running Job *****') - cache.set(task_cache_key, 'running', None) + task_id = current_task.request.id or str(uuid.uuid4()) + task_instance = background_task_utilities.task_on_start(task_id, task_key, 'Clean live data older than 30 days') + if task_instance: + logger.debug('Not found running job: {}'.format(task_key)) older_then_date = current_datetime - timedelta(days=30) - print('Deleting all the rows from "RealTimeConnectivity" Data Table which is older than: {0}'.format( + logger.debug('Deleting all the rows from "RealTimeConnectivity" Data Table which is older than: {0}'.format( older_then_date)) statistics_models.RealTimeConnectivity.objects.filter(created__lt=older_then_date).delete() + task_instance.info('"RealTimeConnectivity" data table completed') - print('Deleting all the rows from "DailyCheckAppMeasurementData" Data Table which is older than: {0}'.format( - older_then_date)) + logger.debug( + 'Deleting all the rows from "DailyCheckAppMeasurementData" Data Table which is older than: {0}'.format( + older_then_date)) # Delete all entries from DailyCheckApp Data Table which is older than 7 days sources_models.DailyCheckAppMeasurementData.objects.filter(created_at__lt=older_then_date).delete() + task_instance.info('"DailyCheckAppMeasurementData" data table completed') - print('Deleting all the rows from "QoSData" Data Table which is older than: {0}'.format(older_then_date)) + logger.debug('Deleting all the rows from "QoSData" Data Table which is older than: {0}'.format(older_then_date)) # Delete all entries from QoS Data Table which is older than 7 days sources_models.QoSData.objects.filter(timestamp__lt=older_then_date).delete() + task_instance.info('"QoSData" data table completed') - cache.set(task_cache_key, 'completed', None) + background_task_utilities.task_on_complete(task_instance) else: - print('***** Found running Job with "{0}" name so skipping current iteration *****'.format(task_cache_key)) + logger.error('Found running Job with "{0}" name so skipping current iteration'.format(task_key)) diff --git a/proco/data_sources/tests/test_api.py b/proco/data_sources/tests/test_api.py index 4bf0998..c78dd93 100755 --- a/proco/data_sources/tests/test_api.py +++ b/proco/data_sources/tests/test_api.py @@ -21,7 +21,7 @@ def sources_url(url_params, query_param, view_name='list-school-master-rows'): class SchoolMasterApiTestCase(TestAPIViewSetMixin, TestCase): - databases = ['default'] + databases = ['default',] @classmethod def setUpTestData(cls): diff --git a/proco/data_sources/utils.py b/proco/data_sources/utils.py index 015bc68..89c6137 100644 --- a/proco/data_sources/utils.py +++ b/proco/data_sources/utils.py @@ -13,7 +13,6 @@ from django.conf import settings from django.db.models import Q from django.db.models.functions import Lower -from django.utils import timezone from rest_framework import status from proco.accounts.models import APIKey @@ -27,7 +26,7 @@ from proco.utils.dates import format_date from proco.utils.urls import add_url_params -logger = logging.getLogger('django.' + __name__) +logger = logging.getLogger('gigamaps.' + __name__) response_timezone = pytz.timezone(settings.TIME_ZONE) @@ -180,15 +179,15 @@ def parse_row(row): def sync_school_master_data(profile_file, share_name, schema_name, table_name, changes_for_countries, deleted_schools, school_master_fields): country = Country.objects.filter(iso3_format=table_name, ).first() - print('Country object: {0}'.format(country)) + logger.debug('Country object: {0}'.format(country)) if not country: - print('ERROR: Country with ISO3 Format ({0}) not found in PROCO DB. ' - 'Hence skipping the load for current table.'.format(table_name)) + logger.error('Country with ISO3 Format ({0}) not found in DB. ' + 'Hence skipping the load for current table.'.format(table_name)) raise ValueError(f"Invalid 'iso3_format': {table_name}") table_last_data_version = sources_models.SchoolMasterData.get_last_version(table_name) - print('Table last data version present in PROCO DB: {0}'.format(table_last_data_version)) + logger.debug('Table last data version present in DB: {0}'.format(table_last_data_version)) # Create an url to access a shared table. # A table path is the profile file path following with `#` and the fully qualified name of a table @@ -198,21 +197,21 @@ def sync_school_master_data(profile_file, share_name, schema_name, table_name, c schema_name=schema_name, table_name=table_name, ) - print('Table URL: %s', table_url) + logger.debug('Table URL: %s', table_url) table_current_version = delta_sharing.get_table_version(table_url) - print('Table current version from API: {0}'.format(table_current_version)) + logger.debug('Table current version from API: {0}'.format(table_current_version)) if table_last_data_version == table_current_version: - print('Both School Master data version in DB and Table version from API, are same. ' - 'Hence skipping the data update for current country ({0}).'.format(country)) + logger.info('Both School Master data version in DB and Table version from API, are same. ' + 'Hence skipping the data update for current country ({0}).'.format(country)) return table_protocol = delta_sharing.get_table_protocol(table_url) - print('Table Protocol: {0}'.format(table_protocol)) + logger.debug('Table Protocol: {0}'.format(table_protocol)) table_meta_data = delta_sharing.get_table_metadata(table_url) - print('Table Metadata: {0}'.format(table_meta_data.__dict__)) + logger.debug('Table Metadata: {0}'.format(table_meta_data.__dict__)) # loaded_data_df = delta_sharing.load_as_pandas(table_url, None, table_current_version) loaded_data_df = delta_sharing.load_table_changes_as_pandas( @@ -222,7 +221,7 @@ def sync_school_master_data(profile_file, share_name, schema_name, table_name, c None, None, ) - print('Total count of rows in the data: {0}'.format(len(loaded_data_df))) + logger.debug('Total count of rows in the data: {0}'.format(len(loaded_data_df))) if len(loaded_data_df) > 0: # Sort the values based on _commit_timestamp ASC @@ -237,15 +236,15 @@ def sync_school_master_data(profile_file, share_name, schema_name, table_name, c loaded_data_df = loaded_data_df[loaded_data_df[DeltaSharingReader._change_type_col_name()].isin( ['insert', 'update_postimage', 'remove', 'delete'])] - print('Total count of rows in the data after duplicate cleanup: {0}'.format(len(loaded_data_df))) + logger.debug('Total count of rows in the data after duplicate cleanup: {0}'.format(len(loaded_data_df))) df_columns = list(loaded_data_df.columns.tolist()) cols_to_delete = list(set(df_columns) - set(school_master_fields)) + ['id', 'created', 'modified', 'school_id', 'country_id', 'status', 'modified_by', 'published_by', 'published_at', 'is_read', ] - print('All School Master API response columns: {}'.format(df_columns)) - print('All School Master API response columns to delete: {}'.format( + logger.debug('All School Master API response columns: {}'.format(df_columns)) + logger.debug('All School Master API response columns to delete: {}'.format( list(set(df_columns) - set(school_master_fields)))) insert_entries = [] @@ -258,10 +257,6 @@ def sync_school_master_data(profile_file, share_name, schema_name, table_name, c loaded_data_df['version'] = table_current_version loaded_data_df['country'] = country - # print('Table data: ') - # print(loaded_data_df) - # print(loaded_data_df.to_dict(orient='records')) - for _, row in loaded_data_df.iterrows(): change_type = row[DeltaSharingReader._change_type_col_name()] @@ -291,11 +286,11 @@ def sync_school_master_data(profile_file, share_name, schema_name, table_name, c insert_entries.append(sources_models.SchoolMasterData(**row_as_dict)) if len(insert_entries) == 5000: - print('Loading the data to "SchoolMasterData" table as it has reached 5000 benchmark.') + logger.debug('Loading the data to "SchoolMasterData" table as it has reached 5000 benchmark.') sources_models.SchoolMasterData.objects.bulk_create(insert_entries) insert_entries = [] - print('#' * 10) - print('\n\n') + logger.debug('#' * 10) + logger.debug('\n\n') elif change_type in ['remove', 'delete']: school = School.objects.filter( @@ -313,24 +308,24 @@ def sync_school_master_data(profile_file, share_name, schema_name, table_name, c remove_entries.append(sources_models.SchoolMasterData(**row_as_dict)) if len(remove_entries) == 5000: - print('Loading the data to "SchoolMasterData" table as it has reached 5000 benchmark.') + logger.info('Loading the data to "SchoolMasterData" table as it has reached 5000 benchmark.') sources_models.SchoolMasterData.objects.bulk_create(remove_entries) remove_entries = [] - print('#' * 10) - print('\n\n') + logger.debug('#' * 10) + logger.debug('\n\n') - print('Loading the remaining ({0}) data to "SchoolMasterData" table.'.format(len(insert_entries))) + logger.info('Loading the remaining ({0}) data to "SchoolMasterData" table.'.format(len(insert_entries))) if len(insert_entries) > 0: sources_models.SchoolMasterData.objects.bulk_create(insert_entries) - print('Removing ({0}) records from "SchoolMasterData" table.'.format(len(remove_entries))) + logger.info('Removing ({0}) records from "SchoolMasterData" table.'.format(len(remove_entries))) if len(remove_entries) > 0: sources_models.SchoolMasterData.objects.bulk_create(remove_entries) deleted_schools.extend( [country.name + ' : ' + school_master_row.school_name for school_master_row in remove_entries]) else: - print('INFO: No data to update in current table: {0}.'.format(table_name)) + logger.info('No data to update in current table: {0}.'.format(table_name)) def get_request_headers(request_configs): @@ -384,7 +379,7 @@ def load_daily_check_app_data_source_response_to_model(model, request_configs): new_params = {} while has_more_data: - print('#' * 10) + logger.debug('#' * 10) source_url = request_configs.get('url') if request_configs.get('query_params'): @@ -394,19 +389,19 @@ def load_daily_check_app_data_source_response_to_model(model, request_configs): page_no += 1 source_url = add_url_params(request_configs.get('url'), new_params) - print('Executing the request URL: {0}'.format(source_url)) - print('Request header: {0}'.format(source_request_headers)) + logger.debug('Executing the request URL: {0}'.format(source_url)) + logger.debug('Request header: {0}'.format(source_request_headers)) response = requests.get(source_url, headers=source_request_headers) if response.status_code != status.HTTP_200_OK: - print('ERROR: Invalid response received {0}'.format(response)) + logger.error('Invalid response received {0}'.format(response)) return response_data = response.json() if len(response_data) == 0: - print('No records to read further.') + logger.debug('No records to read further.') has_more_data = False else: for data in response_data: @@ -416,13 +411,13 @@ def load_daily_check_app_data_source_response_to_model(model, request_configs): insert_entries.append(model(**data)) if len(insert_entries) >= 5000: - print('Loading the data to "{0}" table as it has reached 5000 benchmark.'.format(model.__name__)) + logger.info('Loading the data to "{0}" table as it has reached 5000 benchmark.'.format(model.__name__)) model.objects.bulk_create(insert_entries) insert_entries = [] - print('#' * 10) - print('\n\n') + logger.debug('#' * 10) + logger.debug('\n\n') - print('Loading the remaining ({0}) data to "{1}" table.'.format(len(insert_entries), model.__name__)) + logger.info('Loading the remaining ({0}) data to "{1}" table.'.format(len(insert_entries), model.__name__)) if len(insert_entries) > 0: model.objects.bulk_create(insert_entries) @@ -431,7 +426,7 @@ def sync_dailycheckapp_realtime_data(): current_datetime = core_utilities.get_current_datetime_object() last_measurement_date = sources_models.DailyCheckAppMeasurementData.get_last_dailycheckapp_measurement_date() - print('Daily Check APP Last Measurement Date: {0}'.format(last_measurement_date)) + logger.info('Daily Check APP last measurement date: {0}'.format(last_measurement_date)) request_configs = { 'url': '{0}/measurements/v2'.format(ds_settings.get('DAILY_CHECK_APP').get('BASE_URL')), @@ -460,8 +455,8 @@ def sync_dailycheckapp_realtime_data(): (Q(upload__isnull=True) | Q(upload__gte=0)) & (Q(latency__isnull=True) | Q(latency__gte=0)), ) - print('Migrating the records from "DailyCheckAppMeasurementData" to "RealTimeConnectivity" ' - 'with date range: {0} - {1}'.format(last_measurement_date, current_datetime)) + logger.debug('Migrating the records from "DailyCheckAppMeasurementData" to "RealTimeConnectivity" ' + 'with date range: {0} - {1}'.format(last_measurement_date, current_datetime)) realtime = [] @@ -470,7 +465,7 @@ def sync_dailycheckapp_realtime_data(): ).order_by('country_code')) for country_code in countries: - print('Current Country Code: {}'.format(country_code)) + logger.debug('Current Country Code: {}'.format(country_code)) if country_code: country = Country.objects.filter(code=country_code).first() else: @@ -491,7 +486,7 @@ def sync_dailycheckapp_realtime_data(): school.giga_id_school: school for school in schools_qs.filter(giga_id_school__in=dcm_giga_ids) } - print('Total schools in DailyCheckApp: {0}, Successfully mapped schools: {1}'.format( + logger.debug('Total schools in DailyCheckApp: {0}, Successfully mapped schools: {1}'.format( len(dcm_giga_ids), len(dcm_schools))) mlab_school_ids = set(dailycheckapp_measurements.filter( @@ -505,7 +500,7 @@ def sync_dailycheckapp_realtime_data(): school.external_id: school for school in schools_qs.filter(external_id__in=mlab_school_ids) } - print('Total schools in MLab: {0}, Successfully mapped schools: {1}'.format( + logger.debug('Total schools in MLab: {0}, Successfully mapped schools: {1}'.format( len(mlab_school_ids), len(mlab_schools))) for dailycheckapp_measurement in dailycheckapp_measurements.filter(country_code=country_code): @@ -544,11 +539,11 @@ def sync_dailycheckapp_realtime_data(): )) if len(realtime) == 5000: - print('Loading the data to "RealTimeConnectivity" table as it has reached 5000 benchmark.') + logger.info('Loading the data to "RealTimeConnectivity" table as it has reached 5000 benchmark.') RealTimeConnectivity.objects.bulk_create(realtime) realtime = [] - print('Loading the remaining ({0}) data to "RealTimeConnectivity" table.'.format(len(realtime))) + logger.info('Loading the remaining ({0}) data to "RealTimeConnectivity" table.'.format(len(realtime))) if len(realtime) > 0: RealTimeConnectivity.objects.bulk_create(realtime) @@ -592,32 +587,32 @@ def load_qos_data_source_response_to_model(): if qos_schema: schema_tables = client.list_tables(qos_schema) - print('All tables ready to access: {0}'.format(schema_tables)) + logger.debug('All tables ready to access: {0}'.format(schema_tables)) qos_model_fields = [f.name for f in sources_models.QoSData._meta.get_fields()] for schema_table in schema_tables: - print('#' * 10) - print('Table: %s', schema_table) + logger.debug('#' * 10) + logger.debug('Table: %s', schema_table) table_name = schema_table.name try: country = Country.objects.filter(iso3_format=table_name).first() - print('Country object: {0}'.format(country)) + logger.debug('Country object: {0}'.format(country)) if not country: - print('ERROR: Country with ISO3 Format ({0}) not found in PROCO DB. ' - 'Hence skipping the load for current table.'.format(table_name)) + logger.error('Country with ISO3 Format ({0}) not found in DB. ' + 'Hence skipping the load for current table.'.format(table_name)) continue if len(country_codes_for_exclusion) > 0 and table_name in country_codes_for_exclusion: - print('WARNING: Country with ISO3 Format ({0}) asked to exclude in PROCO DB. ' - 'Hence skipping the load for current table.'.format(table_name)) + logger.warning('Country with ISO3 Format ({0}) asked to exclude in PROCO DB. ' + 'Hence skipping the load for current table.'.format(table_name)) continue table_last_data_version = sources_models.QoSData.get_last_version(table_name) - print('Table last data version present in PROCO DB: {0}'.format(table_last_data_version)) + logger.debug('Table last data version present in DB: {0}'.format(table_last_data_version)) # Create an url to access a shared table. # A table path is the profile file path following with `#` and the fully qualified name of a table @@ -627,21 +622,21 @@ def load_qos_data_source_response_to_model(): schema_name=schema_name, table_name=table_name, ) - print('Table URL: %s', table_url) + logger.debug('Table URL: %s', table_url) table_current_version = delta_sharing.get_table_version(table_url) - print('Table current version from API: {0}'.format(table_current_version)) + logger.debug('Table current version from API: {0}'.format(table_current_version)) if table_last_data_version == table_current_version: - print('Both QoS data version in DB and Table version from API, are same. ' - 'Hence skipping the data update for current country ({0}).'.format(country)) + logger.info('Both QoS data version in DB and Table version from API, are same. ' + 'Hence skipping the data update for current country ({0}).'.format(country)) continue table_protocol = delta_sharing.get_table_protocol(table_url) - print('Table Protocol: {0}'.format(table_protocol)) + logger.debug('Table Protocol: {0}'.format(table_protocol)) table_meta_data = delta_sharing.get_table_metadata(table_url) - print('Table Metadata: {0}'.format(table_meta_data.__dict__)) + logger.debug('Table Metadata: {0}'.format(table_meta_data.__dict__)) if not table_last_data_version: # In case if its 1st pull, then pull only last 10 version's data at max @@ -658,12 +653,14 @@ def load_qos_data_source_response_to_model(): None, None, ) - print('Total count of rows in the {0} version data: {1}'.format(version, len(loaded_data_df))) + logger.debug( + 'Total count of rows in the {0} version data: {1}'.format(version, len(loaded_data_df))) loaded_data_df = loaded_data_df[loaded_data_df[DeltaSharingReader._change_type_col_name()].isin( ['insert', 'update_postimage'])] - print('Total count of rows after filtering only ["insert", "update_postimage"] in the "{0}" ' - 'version data: {1}'.format(version, len(loaded_data_df))) + logger.debug( + 'Total count of rows after filtering only ["insert", "update_postimage"] in the "{0}" ' + 'version data: {1}'.format(version, len(loaded_data_df))) if len(loaded_data_df) > 0: insert_entries = [] @@ -675,8 +672,8 @@ def load_qos_data_source_response_to_model(): 'modified', 'school_id', 'country_id', 'modified_by', ] - print('All QoS API response columns: {}'.format(df_columns)) - print('All QoS API response columns to delete: {}'.format( + logger.debug('All QoS API response columns: {}'.format(df_columns)) + logger.debug('All QoS API response columns to delete: {}'.format( list(set(df_columns) - set(qos_model_fields)))) loaded_data_df.drop(columns=cols_to_delete, inplace=True, errors='ignore', ) @@ -692,8 +689,9 @@ def load_qos_data_source_response_to_model(): ).first() if not school: - print('ERROR: School with Giga ID ({0}) not found in PROCO DB. ' - 'Hence skipping the load for current school.'.format(row['school_id_giga'])) + logger.warning('School with Giga ID ({0}) not found in PROCO DB. ' + 'Hence skipping the load for current school.'.format( + row['school_id_giga'])) continue row['school'] = school @@ -702,25 +700,26 @@ def load_qos_data_source_response_to_model(): insert_entries.append(row_as_dict) if len(insert_entries) == 5000: - print('Loading the data to "QoSData" table as it has reached 5000 benchmark.') + logger.info('Loading the data to "QoSData" table as it has reached 5000 benchmark.') core_utilities.bulk_create_or_update(insert_entries, sources_models.QoSData, ['school', 'timestamp']) insert_entries = [] - print('#' * 10) - print('\n\n') + logger.debug('#' * 10) + logger.debug('\n\n') - print('Loading the remaining ({0}) data to "QoSData" table.'.format(len(insert_entries))) + logger.info( + 'Loading the remaining ({0}) data to "QoSData" table.'.format(len(insert_entries))) if len(insert_entries) > 0: core_utilities.bulk_create_or_update(insert_entries, sources_models.QoSData, ['school', 'timestamp']) else: - print('INFO: No data to update in current table: {0}.'.format(table_name)) + logger.info('No data to update in current table: {0}.'.format(table_name)) except Exception as ex: - print('ERROR: Exception caught for "{0}": {1}'.format(schema_table.name, str(ex))) + logger.error('Exception caught for "{0}": {1}'.format(schema_table.name, str(ex))) else: - print('ERROR: QoS schema ({0}) does not exist to use for share ({1}).'.format(schema_name, share_name)) + logger.error('QoS schema ({0}) does not exist to use for share ({1}).'.format(schema_name, share_name)) else: - print('ERROR: QoS share ({0}) does not exist to use.'.format(share_name)) + logger.error('QoS share ({0}) does not exist to use.'.format(share_name)) try: os.remove(profile_file) @@ -747,7 +746,7 @@ def sync_qos_realtime_data(): 'speed_download_probe', 'speed_upload_probe', 'latency_probe', ).order_by('timestamp').distinct(*['timestamp', 'school']) - print('Migrating the records from "QoSData" to "RealTimeConnectivity" with date range: {0} - {1}'.format( + logger.debug('Migrating the records from "QoSData" to "RealTimeConnectivity" with date range: {0} - {1}'.format( last_entry_date, current_datetime)) realtime = [] @@ -790,10 +789,10 @@ def sync_qos_realtime_data(): )) if len(realtime) == 5000: - print('Loading the data to "RealTimeConnectivity" table as it has reached 5000 benchmark.') + logger.info('Loading the data to "RealTimeConnectivity" table as it has reached 5000 benchmark.') RealTimeConnectivity.objects.bulk_create(realtime) realtime = [] - print('Loading the remaining ({0}) data to "RealTimeConnectivity" table.'.format(len(realtime))) + logger.info('Loading the remaining ({0}) data to "RealTimeConnectivity" table.'.format(len(realtime))) if len(realtime) > 0: RealTimeConnectivity.objects.bulk_create(realtime) diff --git a/proco/locations/api.py b/proco/locations/api.py index 2294412..5bb19bf 100644 --- a/proco/locations/api.py +++ b/proco/locations/api.py @@ -1,4 +1,5 @@ import copy +import logging import traceback from collections import OrderedDict @@ -32,7 +33,7 @@ from proco.data_sources.models import SchoolMasterData from proco.locations.models import Country, CountryAdminMetadata from proco.locations.search_indexes import SchoolIndex -from proco.locations.serializers import ( # BoundaryListCountrySerializer, +from proco.locations.serializers import ( CountryCSVSerializer, CountrySerializer, CountryStatusSerializer, @@ -50,6 +51,8 @@ from proco.utils.mixins import CachedListMixin, CachedRetrieveMixin from proco.utils.tasks import update_country_related_cache +logger = logging.getLogger('gigamaps.' + __name__) + @method_decorator([cache_control(public=True, max_age=settings.CACHE_CONTROL_MAX_AGE_FOR_FE)], name='dispatch') class CountryViewSet( @@ -126,7 +129,6 @@ def filter_queryset(self, queryset): return queryset -# @method_decorator([cache_control(public=True, max_age=settings.CACHE_CONTROL_MAX_AGE_FOR_FE)], name='dispatch') class CountryDataViewSet(BaseModelViewSet): model = Country serializer_class = CountrySerializer @@ -278,55 +280,6 @@ def validate_ids(data, field='id', unique=True): return [int(data[field])] -# @method_decorator([cache_control(public=True, max_age=settings.CACHE_CONTROL_MAX_AGE_FOR_FE)], name='dispatch') -# class CountryBoundaryListAPIView(CachedListMixin, ListAPIView): -# LIST_CACHE_KEY_PREFIX = 'COUNTRY_BOUNDARY' -# -# queryset = Country.objects.all().annotate( -# geometry_empty=Func(F('geometry'), function='ST_IsEmpty', output_field=BooleanField()), -# ).filter(geometry_empty=False).only('id', 'code', 'geometry_simplified') -# serializer_class = BoundaryListCountrySerializer -# pagination_class = None - - -# class CountryTileGenerator(BaseTileGenerator): -# def __init__(self, table_config): -# super().__init__() -# self.table_config = table_config -# -# def envelope_to_sql(self, env, request): -# tbl = self.table_config.copy() -# tbl['env'] = self.envelope_to_bounds_sql(env) -# tbl['limit'] = int(request.query_params.get('limit', 100000)) -# # tbl['random_order'] = "ORDER BY random()" if int(request.query_params.get('z', 0)) == 2 else "" -# -# """sql with join and connectivity_speed at country level """ -# sql_tmpl = """ -# -# """ -# -# return sql_tmpl.format(**tbl) - -# -# class CountryTileRequestHandler(APIView): -# def __init__(self, *args, **kwargs): -# super().__init__(*args, **kwargs) -# -# table_config = { -# 'table': 'schools_school', -# 'srid': '4326', -# 'geomColumn': 'geopoint', -# 'attrColumns': 'id', -# } -# self.tile_generator = CountryTileGenerator(table_config) -# -# def get(self, request): -# try: -# return self.tile_generator.generate_tile(request) -# except Exception as e: -# return Response({"error": "An error occurred while processing the request"}, status=500) - - class DownloadCountriesViewSet(BaseModelViewSet, core_mixins.DownloadAPIDataToCSVMixin): model = Country queryset = Country.objects.all().select_related('last_weekly_status') @@ -427,10 +380,7 @@ def list(self, request, *args, **kwargs): if page_size: page = self.paginate_queryset(queryset) if page is not None: - # serializer = self.get_serializer(page, many=True) return self.get_paginated_response(page) - - # serializer = self.get_serializer(queryset, many=True) return Response(list(queryset)) def finalize_response(self, request, response, *args, **kwargs): @@ -515,8 +465,6 @@ def finalize_response(self, request, response, *args, **kwargs): response.data = OrderedDict() response.data['results'] = data - # response.data['results'] = response_data - return response @@ -535,18 +483,7 @@ class CountrySearchStatListAPIView(CachedListMixin, ListAPIView): LIST_CACHE_KEY_PREFIX = 'GLOBAL_COUNTRY_SEARCH_MAPPING' def get_queryset(self): - # fields = ('country_id', 'country_name', 'country_code', 'admin1_name', 'admin2_name', 'id', 'name') - # ordering_fields = ('country_name', 'admin1_name', 'admin2_name', 'name') - - queryset = self.model.objects.all() # .filter(country_id=144) - - # qs = queryset.prefetch_related( - # Prefetch('country', - # Country.objects.defer('geometry', 'geometry_simplified')), - # ).annotate( - # country_name=F('country__name'), - # country_code=F('country__code'), - # ).values(*fields).order_by(*ordering_fields).distinct(*fields) + queryset = self.model.objects.all() qs = queryset.values( 'country__id', 'country__name', 'country__code', 'country__last_weekly_status__integration_status', @@ -618,7 +555,6 @@ def _format_result(self, qry_data): admin1_name_data['data'] = admin2_data country_data['data'][admin1_name] = admin1_name_data - # country_data['data'] = admin1_name_data data[country_id] = country_data for country_id, country_data in data.items(): @@ -634,7 +570,6 @@ def _get_raw_list_response(self, request, *args, **kwargs): queryset = self.get_queryset() queryset_data = list(queryset) - # data = self._format_result(queryset_data) data = self._format_result(queryset_data) request_path = remove_query_param(request.get_full_path(), self.CACHE_KEY) @@ -805,7 +740,7 @@ def index_search(self, request, *args, **kwargs): self.params = dict(request.query_params) search_client = self.create_search_client() - print( + logger.debug( 'Search params: \nsearch_text - {search_text}\ninclude_total_count - {include_total_count}' '\norder_by - {order_by}\nsearch_fields - {search_fields}\nselect - {select}' '\nskip - {skip}\ntop - {top}\nfilter - {filter}\nquery_type - {query_type}'.format( @@ -831,10 +766,7 @@ def index_search(self, request, *args, **kwargs): query_type=self.get_query_type, ) - print('Total Documents Matching Query:', results.get_count()) - # for result in results: - # print("{0}".format(result)) - + logger.debug('Total Documents Matching Query:', results.get_count()) return results @@ -869,24 +801,9 @@ class AggregateSearchViewSet(BaseSearchMixin, ListAPIView): def list(self, request, *args, **kwargs): resp_data = OrderedDict() data = self.index_search(request, *args, **kwargs) - counts = data.get_count() - # next_url = None - # previous_url = None - - # page = int(str(self.params.get('page', ['0'])[-1])) - # page_size = int(str(self.params.get('page_size', ['20'])[-1])) - # limit = (page * page_size) + page_size - # if counts > limit: - # next_url = replace_query_param(request.get_full_path(), 'page', page + 1) - # if page > 0: - # previous_url = replace_query_param(request.get_full_path(), 'page', page - 1) - resp_data['count'] = counts - # resp_data['next'] = next_url - # resp_data['previous'] = previous_url resp_data['results'] = list(data) - return Response(resp_data) @@ -960,5 +877,5 @@ def create(self, request, *args, **kwargs): message = 'Countries validation started. Please wait.' return Response({'desc': message, 'task_id': [task.id]}, status=rest_status.HTTP_200_OK) except: - print(traceback.format_exc()) + logger.error(traceback.format_exc()) return Response(data=error_mess, status=rest_status.HTTP_502_BAD_GATEWAY) diff --git a/proco/locations/serializers.py b/proco/locations/serializers.py index 4839d30..f647ee5 100644 --- a/proco/locations/serializers.py +++ b/proco/locations/serializers.py @@ -1,4 +1,5 @@ import re +import logging from collections import OrderedDict from django.db.models.functions.text import Lower @@ -23,6 +24,7 @@ from proco.schools.models import School from proco.schools.serializers import ExpandCountrySerializer +logger = logging.getLogger('gigamaps.' + __name__) class ExpandCountryAdminMetadataSerializer(FlexFieldsModelSerializer): """ @@ -168,26 +170,26 @@ def create(self, validated_data): if deleted_country_with_same_code_iso3_format: validated_data['deleted'] = None country_instance = super().update(deleted_country_with_same_code_iso3_format, validated_data) - print('Country restored') + logger.info('Country restored.') CountryDailyStatus.objects.all_deleted().filter(country=country_instance).update(deleted=None) - print('Country Daily restored') + logger.info('Country daily restored.') CountryWeeklyStatus.objects.all_deleted().filter(country=country_instance).update(deleted=None) - print('Country Weekly restored') + logger.info('Country weekly restored.') School.objects.all_deleted().filter(country=country_instance).update(deleted=None) - print('Schools restored') + logger.info('Schools restored.') SchoolDailyStatus.objects.all_deleted().filter(school__country=country_instance).update(deleted=None) - print('School Daily restored') + logger.info('School daily restored.') SchoolWeeklyStatus.objects.all_deleted().filter(school__country=country_instance).update(deleted=None) - print('School Weekly restored') + logger.info('School weekly restored.') SchoolRealTimeRegistration.objects.all_deleted().filter(school__country=country_instance).update( deleted=None) - print('School Real Time Registration restored') + logger.info('School real time registration restored.') request_user = core_utilities.get_current_user(context=self.context) DataLayerCountryRelationship.objects.filter(country=country_instance).update( @@ -206,10 +208,10 @@ def create(self, validated_data): api_key__valid_to__gte=core_utilities.get_current_datetime_object().date(), deleted__isnull=True, ).exists(): - print('WARNING: API Key for country ({0}) already exists.'.format(country_instance.iso3_format)) + logger.debug('Warning: api key for country ({0}) already exists.'.format(country_instance.iso3_format)) else: country_api_key_relationship_obj.update(deleted=None, last_modified_by=request_user) - print('API Key restored.') + logger.info('Api key restored.') else: country_instance = super().create(validated_data) diff --git a/proco/locations/tests/test_api.py b/proco/locations/tests/test_api.py index 9986d9c..d49b2f0 100644 --- a/proco/locations/tests/test_api.py +++ b/proco/locations/tests/test_api.py @@ -101,7 +101,7 @@ def test_empty_countries_hidden(self): class CountryDataTestCase(TestAPIViewSetMixin, TestCase): base_view = 'locations:' - databases = {'default', 'read_only_database'} + databases = {'default',} def setUp(self): self.email = 'test@test.com' diff --git a/proco/proco_data_migrations/management_utils/user_role_permissions.py b/proco/proco_data_migrations/management_utils/user_role_permissions.py index ac41636..ae447be 100644 --- a/proco/proco_data_migrations/management_utils/user_role_permissions.py +++ b/proco/proco_data_migrations/management_utils/user_role_permissions.py @@ -2,6 +2,7 @@ from django.db import transaction +from proco.core import utils as core_utilities from proco.custom_auth.models import Role, RolePermission, ApplicationUser, UserRoleRelationship @@ -22,10 +23,7 @@ def populate_roles_data(): role_permissions = OrderedDict({ Role.SYSTEM_ROLE_NAME_ADMIN: [perm[0] for perm in RolePermission.PERMISSION_CHOICES], - Role.SYSTEM_ROLE_NAME_READ_ONLY: [ - RolePermission.CAN_VIEW_COUNTRY, - RolePermission.CAN_VIEW_ALL_ROLES, - ], + Role.SYSTEM_ROLE_NAME_READ_ONLY: [RolePermission.CAN_DELETE_API_KEY, ], }) @@ -44,10 +42,10 @@ def populate_role_permissions(): def clean_data(): - UserRoleRelationship.objects.all().delete() - ApplicationUser.objects.all().delete() - RolePermission.objects.all().delete() - Role.objects.all().delete() + UserRoleRelationship.objects.all().update(deleted=core_utilities.get_current_datetime_object()) + # ApplicationUser.objects.all().update(deleted=core_utilities.get_current_datetime_object()) + RolePermission.objects.all().update(deleted=core_utilities.get_current_datetime_object()) + Role.objects.all().update(deleted=core_utilities.get_current_datetime_object()) def create_user_role_relationship(user, role_name): diff --git a/proco/proco_data_migrations/migrations/0004_drop_tables_for_realtime_dailycheckapp_and_realtime_unicef.py b/proco/proco_data_migrations/migrations/0004_drop_tables_for_realtime_dailycheckapp_and_realtime_unicef.py new file mode 100644 index 0000000..2f94133 --- /dev/null +++ b/proco/proco_data_migrations/migrations/0004_drop_tables_for_realtime_dailycheckapp_and_realtime_unicef.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('proco_data_migrations', '0003_setup_user_role_permissions'), + ] + + operations = [ + migrations.RunSQL( + sql='DROP TABLE IF EXISTS "dailycheckapp_measurements"', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='DROP TABLE IF EXISTS "dailycheckapp_measurements_backup"', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='DROP TABLE IF EXISTS "measurements"', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='DROP TABLE IF EXISTS "dailycheckapp_school"', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='DROP TABLE IF EXISTS "dailycheckapp_school_backup"', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='DROP TABLE IF EXISTS "dailycheckapp_flagged_school"', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='DROP TABLE IF EXISTS "dailycheckapp_contact_contactmessage"', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='DROP TABLE IF EXISTS "dailycheckapp_country"', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='DROP TABLE IF EXISTS "dailycheckapp_wrong_country_fix"', + reverse_sql=migrations.RunSQL.noop, + ), + ] diff --git a/proco/schools/api.py b/proco/schools/api.py index 752143b..48ca32d 100644 --- a/proco/schools/api.py +++ b/proco/schools/api.py @@ -1,4 +1,5 @@ import re +import logging from datetime import datetime, time from django.conf import settings @@ -42,10 +43,10 @@ from proco.utils import dates as date_utilities from proco.utils.error_message import id_missing_error_mess, delete_succ_mess, \ error_mess -from proco.utils.filters import NullsAlwaysLastOrderingFilter from proco.utils.log import action_log, changed_fields from proco.utils.mixins import CachedListMixin +logger = logging.getLogger('gigamaps.' + __name__) @method_decorator([cache_control(public=True, max_age=settings.CACHE_CONTROL_MAX_AGE_FOR_FE)], name='dispatch') class SchoolsViewSet( @@ -100,10 +101,6 @@ def get_serializer_class(self): @action(methods=['get'], detail=False, url_path='export-csv-schools', url_name='export_csv_schools') def export_csv_schools(self, request, *args, **kwargs): - # country = get_object_or_404( - # self.get_queryset().annotate(code_lower=Lower('country__code')), - # code_lower=self.kwargs.get('country_code').lower(), - # ) country = self.get_country() serializer = self.get_serializer(self.get_queryset(), many=True) csvwriter = SchoolsCSVWriterBackend(serializer, country) @@ -130,9 +127,6 @@ def get_serializer(self, *args, **kwargs): class BaseTileGenerator: - # def __init__(self, table_config): - # self.table_config = table_config - def path_to_tile(self, request): path = "/" + request.query_params.get('z') + "/" + request.query_params.get( 'x') + "/" + request.query_params.get('y') @@ -191,7 +185,6 @@ def sql_to_pbf(self, sql): return Response({"error": "An error occurred while executing SQL query"}, status=500) def generate_tile(self, request): - # start_time = time.time() tile = self.path_to_tile(request) if not (tile and self.tile_is_valid(tile)): return Response({"error": "Invalid tile path"}, status=400) @@ -200,7 +193,7 @@ def generate_tile(self, request): sql = self.envelope_to_sql(env, request) - print(sql.replace('\n', '')) + logger.debug(sql.replace('\n', '')) pbf = self.sql_to_pbf(sql) if isinstance(pbf, memoryview): @@ -218,7 +211,6 @@ def __init__(self, table_config): def envelope_to_sql(self, env, request): country_id = request.query_params.get('country_id', None) admin1_id = request.query_params.get('admin1_id', None) - # school_ids = request.query_params.get('school_ids', "") tbl = self.table_config.copy() tbl['env'] = self.envelope_to_bounds_sql(env) @@ -230,20 +222,13 @@ def envelope_to_sql(self, env, request): if country_id or admin1_id: if admin1_id: - tbl['admin1_condition'] = f"AND t.admin1_id = {admin1_id}" + tbl['admin1_condition'] = f"AND schools_school.admin1_id = {admin1_id}" if country_id: - tbl['country_condition'] = f"AND t.country_id = {country_id}" + tbl['country_condition'] = f"AND schools_school.country_id = {country_id}" else: tbl['random_order'] = 'ORDER BY random()' if int(request.query_params.get('z', 0)) == 2 else '' - # # Splitting the school_ids string into a list of individual IDs - # school_id_list = school_ids.split(',') - - # school_condition = "" - # if school_id_list: - # school_condition = "AND t._id IN ({0})".format(','.join(school_id_list)) - """In order to cater school requirements, {school_condition} can be added to id before/after country_condition in the query""" @@ -253,27 +238,48 @@ def envelope_to_sql(self, env, request): {env}::box2d AS b2d ), mvtgeom AS ( - SELECT ST_AsMVTGeom(ST_Transform(t.{geomColumn}, 3857), bounds.b2d) AS geom, - {attrColumns}, t.coverage_type, - CASE WHEN LOWER(t."coverage_type") IN ('5g', '4g') THEN 'good' - WHEN LOWER(t."coverage_type") IN ('3g', '2g') THEN 'moderate' - WHEN LOWER(t."coverage_type") = 'no' THEN 'bad' + SELECT ST_AsMVTGeom(ST_Transform(schools_school."geopoint", 3857), bounds.b2d) AS geom, + schools_school."id", + schools_school."coverage_type", + CASE WHEN LOWER(schools_school."coverage_type") IN ('5g', '4g') THEN 'good' + WHEN LOWER(schools_school."coverage_type") IN ('3g', '2g') THEN 'moderate' + WHEN LOWER(schools_school."coverage_type") = 'no' THEN 'bad' ELSE 'unknown' END AS coverage_status, - CASE WHEN t.connectivity_status IN ('good', 'moderate') THEN 'connected' - WHEN t.connectivity_status = 'no' THEN 'not_connected' ELSE 'unknown' + CASE WHEN schools_school."connectivity_status" IN ('good', 'moderate') THEN 'connected' + WHEN schools_school."connectivity_status" = 'no' THEN 'not_connected' ELSE 'unknown' END AS connectivity_status - FROM {table} t, bounds - WHERE ST_Intersects(t.{geomColumn}, ST_Transform(bounds.geom, {srid})) - AND t."deleted" IS NULL + FROM schools_school + INNER JOIN bounds ON ST_Intersects(schools_school."geopoint", ST_Transform(bounds.geom, 4326)) + {school_weekly_join} + WHERE schools_school."deleted" IS NULL {country_condition} {admin1_condition} + {school_condition} + {school_weekly_condition} {random_order} {limit_condition} ) SELECT ST_AsMVT(DISTINCT mvtgeom.*) FROM mvtgeom """ + tbl['school_condition'] = '' + tbl['school_weekly_join'] = '' + tbl['school_weekly_condition'] = '' + + school_filters = core_utilities.get_filter_sql(request, 'schools', 'schools_school') + if len(school_filters) > 0: + tbl['school_condition'] = 'AND ' + school_filters + + school_static_filters = core_utilities.get_filter_sql(request, 'school_static', + 'connection_statistics_schoolweeklystatus') + if len(school_static_filters) > 0: + tbl['school_weekly_join'] = """ + LEFT OUTER JOIN connection_statistics_schoolweeklystatus + ON schools_school."last_weekly_status_id" = connection_statistics_schoolweeklystatus."id" + """ + tbl['school_weekly_condition'] = 'AND ' + school_static_filters + return sql_tmpl.format(**tbl) @@ -294,7 +300,7 @@ def get(self, request): try: return self.tile_generator.generate_tile(request) except Exception as ex: - print('Exception occurred for school tiles endpoint: {}'.format(ex)) + logger.error('Exception occurred for school tiles endpoint: {}'.format(ex)) return Response({"error": "An error occurred while processing the request"}, status=500) @@ -315,22 +321,22 @@ def query_filters(self, request, table_configs): 'school_id__in' in request.query_params ): if 'country_id' in request.query_params: - table_configs['country_condition'] = f" AND t.country_id = {request.query_params['country_id']}" + table_configs['country_condition'] = f" AND schools_school.country_id = {request.query_params['country_id']}" elif 'country_id__in' in request.query_params: country_ids = ','.join([c.strip() for c in request.query_params['country_id__in'].split(',')]) - table_configs['country_condition'] = f" AND t.country_id IN ({country_ids})" + table_configs['country_condition'] = f" AND schools_school.country_id IN ({country_ids})" if 'admin1_id' in request.query_params: - table_configs['admin1_condition'] = f" AND t.admin1_id = {request.query_params['admin1_id']}" + table_configs['admin1_condition'] = f" AND schools_school.admin1_id = {request.query_params['admin1_id']}" elif 'admin1_id__in' in request.query_params: admin1_ids = ','.join([c.strip() for c in request.query_params['admin1_id__in'].split(',')]) - table_configs['admin1_condition'] = f" AND t.admin1_id IN ({admin1_ids})" + table_configs['admin1_condition'] = f" AND schools_school.admin1_id IN ({admin1_ids})" if 'school_id' in request.query_params: - table_configs['school_condition'] = f" AND t.id = {request.query_params['school_id']}" + table_configs['school_condition'] = f" AND schools_school.id = {request.query_params['school_id']}" elif 'school_id__in' in request.query_params: school_ids = ','.join([c.strip() for c in request.query_params['school_id__in'].split(',')]) - table_configs['school_condition'] = f" AND t.id IN ({school_ids})" + table_configs['school_condition'] = f" AND schools_school.id IN ({school_ids})" else: zoom_level = int(request.query_params.get('z', '0')) if zoom_level == 0: @@ -371,7 +377,7 @@ def query_filters(self, request, table_configs): # If for any week of the month data is not available then pick last week number week_number = week_numbers_for_month[-1] - table_configs['weekly_lookup_condition'] = (f'ON t.id = c.school_id AND c.week={week_number} ' + table_configs['weekly_lookup_condition'] = (f'ON schools_school.id = c.school_id AND c.week={week_number} ' f'AND c.year={year_number}') table_configs['benchmark'], table_configs['benchmark_unit'] = get_benchmark_value_for_default_download_layer( @@ -387,97 +393,21 @@ def envelope_to_sql(self, env, request): tbl['country_condition'] = '' tbl['admin1_condition'] = '' tbl['school_condition'] = '' - tbl['weekly_lookup_condition'] = 'ON t.last_weekly_status_id = c.id' + tbl['weekly_lookup_condition'] = 'ON schools_school.last_weekly_status_id = c.id' tbl['random_order'] = '' tbl['rt_date_condition'] = '' self.query_filters(request, tbl) """sql with join and connectivity_speed""" - # sql_tmpl = """ - # WITH bounds AS ( - # SELECT {env} AS geom, - # {env}::box2d AS b2d - # ), - # rt_status AS ( - # SELECT DISTINCT t.id as school_id, - # true rt_registered, - # FIRST_VALUE(dailystat.created) OVER (PARTITION BY dailystat.school_id ORDER BY dailystat.created) - # rt_registration_date - # FROM connection_statistics_schooldailystatus dailystat - # INNER JOIN schools_school t ON t.id = dailystat.school_id - # {country_condition}{school_condition} - # ), - # mvtgeom AS ( - # SELECT ST_AsMVTGeom(ST_Transform(t.{geomColumn}, 3857), bounds.b2d) AS geom, - # t.{attrColumns}, - # c.connectivity_speed, - # t.connectivity_status as connectivity, - # CASE WHEN c.connectivity_speed > {benchmark} THEN 'good' - # WHEN c.connectivity_speed < {benchmark} and c.connectivity_speed >= 1000000 THEN 'moderate' - # WHEN c.connectivity_speed < 1000000 THEN 'bad' - # ELSE 'unknown' - # END as hist_connectivity, - # CASE WHEN t.connectivity_status IN ('good', 'moderate') THEN 'connected' - # WHEN t.connectivity_status = 'no' THEN 'not_connected' - # ELSE 'unknown' - # END as connectivity_status, - # CASE WHEN rt_status.rt_registered = True {rt_date_condition} THEN True - # ELSE False - # END as is_rt_connected - # FROM {table} t - # INNER JOIN bounds ON ST_Intersects(t.{geomColumn}, ST_Transform(bounds.geom, {srid})) - # {country_condition}{school_condition} - # LEFT JOIN connection_statistics_schoolweeklystatus c {weekly_lookup_condition} - # LEFT JOIN rt_status ON rt_status.school_id = t.id - # {random_order} - # {limit_condition} - # ) - # SELECT ST_AsMVT(mvtgeom.*) FROM mvtgeom; - # """ - - # sql_tmpl = """ - # WITH bounds AS ( - # SELECT {env} AS geom, - # {env}::box2d AS b2d - # ), - # mvtgeom AS ( - # SELECT ST_AsMVTGeom(ST_Transform(t.{geomColumn}, 3857), bounds.b2d) AS geom, - # t.{attrColumns}, - # c.connectivity_speed, - # t.connectivity_status as connectivity, - # CASE WHEN c.connectivity_speed > {benchmark} THEN 'good' - # WHEN c.connectivity_speed < {benchmark} and c.connectivity_speed >= 1000000 - # THEN 'moderate' - # WHEN c.connectivity_speed < 1000000 THEN 'bad' - # ELSE 'unknown' - # END as hist_connectivity, - # CASE WHEN t.connectivity_status IN ('good', 'moderate') THEN 'connected' - # WHEN t.connectivity_status = 'no' THEN 'not_connected' - # ELSE 'unknown' - # END as connectivity_status, - # CASE WHEN rt_status.rt_registered = True {rt_date_condition} THEN True - # ELSE False - # END as is_rt_connected - # FROM {table} t - # INNER JOIN bounds ON ST_Intersects(t.{geomColumn}, ST_Transform(bounds.geom, {srid})) - # {country_condition}{school_condition} - # LEFT JOIN connection_statistics_schoolweeklystatus c {weekly_lookup_condition} - # LEFT JOIN school_rt_connectivity_stat rt_status ON rt_status.giga_id_school = t.giga_id_school - # {random_order} - # {limit_condition} - # ) - # SELECT ST_AsMVT(mvtgeom.*) FROM mvtgeom; - # """ - sql_tmpl = """ WITH bounds AS ( SELECT {env} AS geom, {env}::box2d AS b2d ), mvtgeom AS ( - SELECT ST_AsMVTGeom(ST_Transform(t.{geomColumn}, 3857), bounds.b2d) AS geom, - t.{attrColumns}, + SELECT ST_AsMVTGeom(ST_Transform(schools_school.geopoint, 3857), bounds.b2d) AS geom, + schools_school.id, CASE WHEN c.id is NULL AND rt_status.rt_registered = True {rt_date_condition} THEN 'unknown' WHEN c.id is NULL THEN NULL WHEN c.connectivity_speed > {benchmark} THEN 'good' @@ -485,26 +415,44 @@ def envelope_to_sql(self, env, request): WHEN c.connectivity_speed < 1000000 THEN 'bad' ELSE 'unknown' END as connectivity, - CASE WHEN t.connectivity_status IN ('good', 'moderate') THEN 'connected' - WHEN t.connectivity_status = 'no' THEN 'not_connected' + CASE WHEN schools_school.connectivity_status IN ('good', 'moderate') THEN 'connected' + WHEN schools_school.connectivity_status = 'no' THEN 'not_connected' ELSE 'unknown' END as connectivity_status, CASE WHEN rt_status.rt_registered = True {rt_date_condition} THEN True ELSE False END as is_rt_connected - FROM {table} t - INNER JOIN bounds ON ST_Intersects(t.{geomColumn}, ST_Transform(bounds.geom, {srid})) - AND t."deleted" IS NULL {country_condition}{admin1_condition}{school_condition} + FROM schools_school + INNER JOIN bounds ON ST_Intersects(schools_school.geopoint, ST_Transform(bounds.geom, {srid})) + AND schools_school."deleted" IS NULL {country_condition}{admin1_condition}{school_condition} + {school_weekly_join} LEFT JOIN connection_statistics_schoolweeklystatus c {weekly_lookup_condition} AND c."deleted" IS NULL - LEFT JOIN connection_statistics_schoolrealtimeregistration rt_status ON rt_status.school_id = t.id + LEFT JOIN connection_statistics_schoolrealtimeregistration rt_status ON rt_status.school_id = schools_school.id AND rt_status."deleted" IS NULL + {school_weekly_condition} {random_order} {limit_condition} ) SELECT ST_AsMVT(DISTINCT mvtgeom.*) FROM mvtgeom; """ + tbl['school_weekly_join'] = '' + tbl['school_weekly_condition'] = '' + + school_filters = core_utilities.get_filter_sql(request, 'schools', 'schools_school') + if len(school_filters) > 0: + tbl['school_condition'] += ' AND ' + school_filters + + school_static_filters = core_utilities.get_filter_sql(request, 'school_static', + 'connection_statistics_schoolweeklystatus') + if len(school_static_filters) > 0: + tbl['school_weekly_join'] = """ + LEFT OUTER JOIN connection_statistics_schoolweeklystatus + ON schools_school."last_weekly_status_id" = connection_statistics_schoolweeklystatus."id" + """ + tbl['school_weekly_condition'] = 'WHERE ' + school_static_filters + return sql_tmpl.format(**tbl) @@ -525,7 +473,7 @@ def get(self, request): try: return self.tile_generator.generate_tile(request) except Exception as ex: - print('Exception occurred for school connectivity tiles endpoint: {}'.format(ex)) + logger.error('Exception occurred for school connectivity tiles endpoint: {}'.format(ex)) return Response({'error': 'An error occurred while processing the request'}, status=500) diff --git a/proco/schools/api_urls.py b/proco/schools/api_urls.py index 7e5b169..764fd5c 100644 --- a/proco/schools/api_urls.py +++ b/proco/schools/api_urls.py @@ -20,11 +20,11 @@ 'get': 'list', 'post': 'create', 'delete': 'destroy', - }), name='list_or_create_or_destroy_school'), + }), name='list-create-destroy-school'), path('schools/school//', api.AdminViewSchoolAPIViewSet.as_view({ 'put': 'update', 'get': 'retrieve', - }), name='update_or_retrieve_school'), + }), name='update-or-retrieve-school'), path('schools/fileimport/', api.ImportCSVViewSet.as_view({ 'post': 'fileimport', 'get': 'list', diff --git a/proco/schools/constants.py b/proco/schools/constants.py index 1177c25..2448ea6 100644 --- a/proco/schools/constants.py +++ b/proco/schools/constants.py @@ -19,8 +19,9 @@ class ColorMapSchema: 'unknown': 'unknown', 'no': 'no', '2g': 'moderate', - '3g': 'good', + '3g': 'moderate', '4g': 'good', + '5g': 'good', } CONNECTIVITY_SPEED_FOR_GOOD_CONNECTIVITY_STATUS = 5 * (10 ** 6) diff --git a/proco/schools/migrations/0030_added_unique_constraint_on_country_and_giga_id.py b/proco/schools/migrations/0030_added_unique_constraint_on_country_and_giga_id.py old mode 100644 new mode 100755 diff --git a/proco/schools/serializers.py b/proco/schools/serializers.py index 7507f1b..c050627 100644 --- a/proco/schools/serializers.py +++ b/proco/schools/serializers.py @@ -1,3 +1,4 @@ +import logging import re from collections import OrderedDict from datetime import timedelta @@ -21,6 +22,8 @@ from proco.schools.models import School, FileImport from proco.utils import dates as date_utilities +logger = logging.getLogger('gigamaps.' + __name__) + class BaseSchoolSerializer(serializers.ModelSerializer): class Meta: @@ -80,73 +83,6 @@ class Meta(BaseSchoolSerializer.Meta): ) -class SchoolSerializer(CountryToSerializerMixin, BaseSchoolSerializer): - admin1_name = serializers.ReadOnlyField(source='admin1.name', default='') - admin2_name = serializers.ReadOnlyField(source='admin2.name', default='') - - statistics = serializers.SerializerMethodField() - data_status = serializers.SerializerMethodField() - - is_verified = serializers.SerializerMethodField() - - class Meta(BaseSchoolSerializer.Meta): - fields = BaseSchoolSerializer.Meta.fields + ( - 'statistics', - 'data_status', - 'connectivity_status', - 'coverage_status', - 'coverage_type', - 'gps_confidence', - 'address', - 'postal_code', - 'admin1_name', - 'admin2_name', - 'timezone', - 'altitude', - 'email', - 'education_level', - 'environment', - 'school_type', - 'is_verified', - # 'connectivity_dist_status', - ) - - def get_statistics(self, instance): - return SchoolWeeklyStatusSerializer(instance.last_weekly_status).data - - def get_data_status(self, instance): - latest_school_weekly_instance = instance.last_weekly_status - static_data = {} - - if latest_school_weekly_instance: - year = latest_school_weekly_instance.year - week_number = latest_school_weekly_instance.week - - monday_of_week = date_utilities.get_first_date_of_week(year, week_number) - sunday_of_week = monday_of_week + timedelta(days=6) - - static_data = { - 'week': { - 'start_date': date_utilities.format_date(monday_of_week), - 'end_date': date_utilities.format_date(sunday_of_week), - }, - 'month': { - 'start_date': date_utilities.format_date(date_utilities.get_first_date_of_month( - year, monday_of_week.month)), - 'end_date': date_utilities.format_date(date_utilities.get_last_date_of_month( - year, monday_of_week.month)) - } - } - return static_data - - def get_is_verified(self, obj): - if not self.country.last_weekly_status: - return None - return self.country.last_weekly_status.integration_status not in [ - CountryWeeklyStatus.COUNTRY_CREATED, CountryWeeklyStatus.SCHOOL_OSM_MAPPED, - ] - - class ExtendedSchoolSerializer(BaseSchoolSerializer): class Meta(BaseSchoolSerializer.Meta): fields = BaseSchoolSerializer.Meta.fields + ( @@ -216,24 +152,7 @@ class ExpandSchoolWeeklyStatusSerializer(FlexFieldsModelSerializer): class Meta: model = SchoolWeeklyStatus fields = ( - # 'id', - # 'num_students', - # 'num_teachers', - # 'num_classroom', - # 'num_latrines', - # 'running_water', - # 'electricity_availability', - # 'computer_lab', - # 'num_computers', - # 'connectivity', - # 'connectivity_type', - # 'connectivity_speed', - # 'connectivity_latency', - # 'coverage_availability', - # 'coverage_type', 'school_data_source', - # 'created', - # 'modified', ) @@ -241,49 +160,28 @@ class SchoolStatusSerializer(FlexFieldsModelSerializer): class Meta: model = School read_only_fields = fields = ( - # 'id', - # 'created', - # 'modified', 'name', - # 'timezone', 'geopoint', - # 'gps_confidence', - # 'altitude', - # 'address', - # 'postal_code', - # 'email', 'education_level', - # 'environment', - # 'school_type', 'country_id', - # 'location_id', - # 'admin1_id', - # 'admin2_id', 'external_id', 'last_weekly_status_id', - # 'name_lower', 'giga_id_school', 'education_level_regional', - # 'connectivity_status', - # 'coverage_type', ) expandable_fields = { 'country': (ExpandCountrySerializer, {'source': 'country'}), - # 'admin1': (ExpandCountryAdminSerializer, {'source': 'admin1'}), - # 'admin2': (ExpandCountryAdminSerializer, {'source': 'admin2'}), 'last_weekly_status': (ExpandSchoolWeeklyStatusSerializer, {'source': 'last_weekly_status'}), } class SchoolCSVSerializer(SchoolStatusSerializer, DownloadSerializerMixin): - # geopoint = GeoPointCSVField() class Meta(SchoolStatusSerializer.Meta): report_fields = OrderedDict([ ('giga_id_school', 'School Giga ID'), - # ('external_id', 'School Source ID'), ('name', 'School Name'), ('longitude', {'name': 'Longitude', 'is_computed': True}), ('latitude', {'name': 'Latitude', 'is_computed': True}), @@ -291,62 +189,23 @@ class Meta(SchoolStatusSerializer.Meta): ('country_iso3_format', {'name': 'Country ISO3 Code', 'is_computed': True}), ('country_name', {'name': 'Country Name', 'is_computed': True}), ('school_data_source', {'name': 'School Data Source', 'is_computed': True}), - # ('timezone', 'TimeZone'), - # ('geopoint', 'Location GeoPoints'), - # ('gps_confidence', 'GPS Confidence'), - # ('altitude', 'Altitude'), - # ('address', 'Address'), - # ('postal_code', 'Postal Code'), - # ('email', 'Email'), - # ('environment', 'Environment'), - # ('school_type', 'School Type'), - # ('country_code', {'name': 'Country Code', 'is_computed': True}), - # ('admin1_name', {'name': 'Admin 1 Name', 'is_computed': True}), - # ('admin2_name', {'name': 'Admin 2 Name', 'is_computed': True}), - # ('name_lower', 'Name Lower'), - # ('education_level_regional', 'Education Level Regional'), - # ('last_weekly_status', {'name': 'Last Week Status', 'is_computed': True}), ]) def get_country_name(self, data): return data.get('country', {}).get('name') - # def get_admin1_name(self, data): - # admin_data = data.get('admin1', None) - # if admin_data: - # return admin_data.get('name') - # - # def get_admin2_name(self, data): - # admin_data = data.get('admin2', None) - # if admin_data: - # return admin_data.get('name') - def get_country_iso3_format(self, data): return data.get('country', {}).get('iso3_format') - # def get_country_code(self, data): - # return data.get('country', {}).get('code') - def get_longitude(self, data): point_coordinates = data.get('geopoint', {}).get('coordinates', []) if len(point_coordinates) > 0: return point_coordinates[0] - return def get_latitude(self, data): point_coordinates = data.get('geopoint', {}).get('coordinates', []) if len(point_coordinates) > 1: return point_coordinates[1] - return - - # def get_last_weekly_status(self, data): - # last_week_data = data.get('last_weekly_status', {}) - # values = [] - # for key, value in last_week_data.items(): - # if isinstance(value, bool): - # value = self.boolean_flags.get(value) - # values.append('{0}:{1}'.format(key, value)) - # return '\t'.join(values) def get_school_data_source(self, data): return data.get('last_weekly_status', {}).get('school_data_source') @@ -393,16 +252,16 @@ def create(self, validated_data): if deleted_school_with_same_giga_id: validated_data['deleted'] = None school_instance = super().update(deleted_school_with_same_giga_id, validated_data) - print('School restored') + logger.debug('School restored') SchoolDailyStatus.objects.all_deleted().filter(school=school_instance).update(deleted=None) - print('School Daily restored') + logger.debug('School Daily restored') SchoolWeeklyStatus.objects.all_deleted().filter(school=school_instance).update(deleted=None) - print('School Weekly restored') + logger.debug('School Weekly restored') SchoolRealTimeRegistration.objects.all_deleted().filter(school=school_instance).update(deleted=None) - print('School Real Time Registration restored') + logger.debug('School Real Time Registration restored') return school_instance else: diff --git a/proco/schools/tasks.py b/proco/schools/tasks.py index 5179c9d..a6a8827 100644 --- a/proco/schools/tasks.py +++ b/proco/schools/tasks.py @@ -1,14 +1,17 @@ import random import traceback +import uuid +import logging from collections import Counter from copy import copy from random import randint # noqa from typing import List +from celery import current_task from django.contrib.gis.geos import MultiPoint, Point -from django.core.cache import cache from django.db import transaction +from proco.background import utils as background_task_utilities from proco.connection_statistics.utils import update_country_data_source_by_csv_filename, update_country_weekly_status from proco.core import utils as core_utilities from proco.locations.models import Country @@ -20,6 +23,7 @@ from proco.utils.dates import format_date from proco.utils.tasks import update_country_related_cache +logger = logging.getLogger('gigamaps.' + __name__) class FailedImportError(Exception): pass @@ -140,17 +144,17 @@ def update_school_records(): Periodic task executed every day at 01:00 AM and 01:00 PM to update the school fields based on changes in SchoolWeekly or CountryWeekly tables. """ - task_cache_key = 'update_school_records_status_{current_time}'.format( + task_key = 'update_school_records_status_{current_time}'.format( current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H')) - running_task = cache.get(task_cache_key, None) - if running_task in [None, b'completed', 'completed']: - print('***** Not found running Job *****') - cache.set(task_cache_key, 'running', None) + task_id = current_task.request.id or str(uuid.uuid4()) + task_instance = background_task_utilities.task_on_start( + task_id, task_key, 'Update the school fields based on changes in SchoolWeekly or CountryWeekly tables') + if task_instance: + logger.debug('Not found running job.') school_utilities.update_school_from_country_or_school_weekly_update() - - cache.set(task_cache_key, 'completed', None) + background_task_utilities.task_on_complete(task_instance) else: - print('***** Found running Job with "{0}" name so skipping current iteration *****'.format(task_cache_key)) + logger.debug('Found running job with "{0}" name so skipping current iteration.'.format(task_key)) diff --git a/proco/schools/tests/factories.py b/proco/schools/tests/factories.py index 09b9980..4a26b95 100644 --- a/proco/schools/tests/factories.py +++ b/proco/schools/tests/factories.py @@ -5,11 +5,12 @@ from factory import fuzzy from proco.locations.tests.factories import CountryFactory, LocationFactory -from proco.schools.models import School +from proco.schools.models import School, FileImport class SchoolFactory(django_factory.DjangoModelFactory): name = fuzzy.FuzzyText(length=20) + giga_id_school = fuzzy.FuzzyText(length=20) external_id = fuzzy.FuzzyText(length=20) country = SubFactory(CountryFactory) location = SubFactory(LocationFactory) @@ -19,3 +20,11 @@ class SchoolFactory(django_factory.DjangoModelFactory): class Meta: model = School + + +class FileImportFactory(django_factory.DjangoModelFactory): + country = SubFactory(CountryFactory) + uploaded_file = fuzzy.FuzzyText(length=20) + + class Meta: + model = FileImport diff --git a/proco/schools/tests/test_api.py b/proco/schools/tests/test_api.py index dc76194..ebc28e0 100644 --- a/proco/schools/tests/test_api.py +++ b/proco/schools/tests/test_api.py @@ -1,16 +1,27 @@ from django.core.cache import cache from django.test import TestCase -from django.urls import reverse - +from django.urls import resolve, reverse from rest_framework import status from proco.connection_statistics.models import CountryWeeklyStatus from proco.connection_statistics.tests.factories import SchoolWeeklyStatusFactory +from proco.custom_auth.tests import test_utils as test_utilities from proco.locations.tests.factories import CountryFactory -from proco.schools.tests.factories import SchoolFactory +from proco.schools.tests.factories import SchoolFactory, FileImportFactory from proco.utils.tests import TestAPIViewSetMixin +def schools_url(url_params, query_param, view_name='schools-list'): + url = reverse('schools:' + view_name, args=url_params) + view = resolve(url) + view_info = view.func + + if len(query_param) > 0: + query_params = '?' + '&'.join([key + '=' + str(val) for key, val in query_param.items()]) + url += query_params + return url, view, view_info + + class SchoolsApiTestCase(TestAPIViewSetMixin, TestCase): base_view = 'schools:schools' @@ -42,6 +53,13 @@ def setUpTestData(cls): cls.school_three.last_weekly_status = cls.school_weekly_three cls.school_three.save() + cls.admin_user = test_utilities.setup_admin_user_by_role() + cls.read_only_user = test_utilities.setup_read_only_user_by_role() + + cls.imported_file_one = FileImportFactory(country=cls.country) + cls.imported_file_one.uploaded_by = cls.admin_user + cls.imported_file_one.save() + def setUp(self): cache.clear() super().setUp() @@ -118,7 +136,6 @@ def test_schools_detail(self): ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['id'], self.school_one.id) - # self.assertIn('statistics', response.data) def test_update_keys(self): # todo: move me to proper place @@ -135,3 +152,360 @@ def test_update_keys(self): f'SOFT_CACHE_SCHOOLS_{self.country.code.lower()}_', ])), ) + + def test_random_schools_list(self): + with self.assertNumQueries(2): + response = self.forced_auth_req( + 'get', + reverse('schools:random-schools'), + user=None, + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('geopoint', response.data[0]) + self.assertIn('country_integration_status', response.data[0]) + self.assertIn('country_id', response.data[0]) + + def test_default_coverage_layer_school_tiles_country_view(self): + url, _, view = schools_url((), { + 'country_id': self.country.id, + 'z': '2', + 'x': '1', + 'y': '2.mvt', + }, view_name='tiles-view') + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_default_coverage_layer_school_tiles_admin_view(self): + url, _, view = schools_url((), { + 'country_id': self.country.id, + 'admin1_id': '12345678', + 'z': '2', + 'x': '1', + 'y': '2.mvt', + }, view_name='tiles-view') + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_default_coverage_layer_school_tiles_global_view(self): + url, _, view = schools_url((), { + 'z': '2', + 'x': '1', + 'y': '2.mvt', + }, view_name='tiles-view') + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_default_download_layer_school_tiles_country_view(self): + url, _, view = schools_url((), { + 'country_id': self.country.id, + 'indicator': 'download', + 'benchmark': 'global', + 'start_date': '24-06-2024', + 'end_date': '30-06-2024', + 'is_weekly': 'true', + 'z': '2', + 'x': '1', + 'y': '2.mvt', + }, view_name='tiles-connectivity-view') + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_default_download_layer_school_tiles_admin_view(self): + url, _, view = schools_url((), { + 'country_id': self.country.id, + 'admin1_id': '1234543', + 'indicator': 'download', + 'benchmark': 'global', + 'start_date': '24-06-2024', + 'end_date': '30-06-2024', + 'is_weekly': 'true', + 'z': '2', + 'x': '1', + 'y': '2.mvt', + }, view_name='tiles-connectivity-view') + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_default_download_layer_school_tiles_global_view(self): + url, _, view = schools_url((), { + 'indicator': 'download', + 'benchmark': 'global', + 'start_date': '24-06-2024', + 'end_date': '30-06-2024', + 'is_weekly': 'true', + 'z': '2', + 'x': '1', + 'y': '2.mvt', + }, view_name='tiles-connectivity-view') + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_default_download_layer_school_tiles_country_view_month_filter(self): + url, _, view = schools_url((), { + 'country_id': self.country.id, + 'indicator': 'download', + 'benchmark': 'global', + 'start_date': '01-06-2024', + 'end_date': '30-06-2024', + 'is_weekly': 'false', + 'z': '2', + 'x': '1', + 'y': '2.mvt', + }, view_name='tiles-connectivity-view') + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_list_file_imports_on_admin_view(self): + url, _, view = schools_url((), {}, view_name='file-import') + + response = self.forced_auth_req('get', url, view=view, user=self.admin_user) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_update_school(self): + url, _, view = schools_url((self.school_one.id,), {}, + view_name='update-or-retrieve-school') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'name': self.school_one.name + ' Test', + 'timezone': 'UTC', + 'country': self.country.id, + 'giga_id_school': self.school_one.giga_id_school + '-test', + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + def test_update_school_giga_id_to_duplicate_value(self): + url, _, view = schools_url((self.school_one.id,), {}, + view_name='update-or-retrieve-school') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'name': self.school_one.name + ' Test', + 'timezone': 'UTC', + 'country': self.country.id, + 'giga_id_school': self.school_two.giga_id_school, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_update_school_giga_id_to_invalid_regex(self): + url, _, view = schools_url((self.school_one.id,), {}, + view_name='update-or-retrieve-school') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'name': self.school_one.name + ' Test', + 'timezone': 'UTC', + 'country': self.country.id, + 'giga_id_school': self.school_one.giga_id_school + '$!@#', + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_update_school_to_invalid_id(self): + url, _, view = schools_url((12345678,), {}, + view_name='update-or-retrieve-school') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'name': self.school_one.name + ' Test', + 'timezone': 'UTC', + 'country': self.country.id, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_list_schools(self): + url, _, view = schools_url((), {}, view_name='list-create-destroy-school') + + response = self.forced_auth_req('get', url, view=view, user=self.admin_user) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_create_school_by_admin(self): + url, _, view = schools_url((), {}, view_name='list-create-destroy-school') + + response = self.forced_auth_req( + 'post', + url, + user=self.admin_user, + view=view, + data={ + 'name': 'New School', + 'giga_id_school': 'ac65543e-cdba-4f5c-891a-448bzdcfge099', + 'external_id': '25805591031323454', + 'country': self.country.id, + 'geopoint': { + 'type': 'Point', + 'coordinates': [ + 76.92044830322266, + 9.022849082946777 + ] + }, + 'gps_confidence': 1.0, + 'altitude': 0, + 'timezone': 'Africa/Conakry' + } + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_restore_school_by_admin(self): + url, _, view = schools_url((), {}, view_name='list-create-destroy-school') + + response = self.forced_auth_req( + 'post', + url, + user=self.admin_user, + view=view, + data={ + 'name': 'New School 2', + 'giga_id_school': 'ac65543e-cdba-4f5c-891a-448bzdc12e099', + 'external_id': '258055910313231', + 'country': self.country.id, + 'geopoint': { + 'type': 'Point', + 'coordinates': [ + 76.92044830322266, + 9.022849082946777 + ] + }, + 'gps_confidence': 1.0, + 'altitude': 0, + 'timezone': 'Africa/Conakry' + } + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + school_id = response.data['id'] + + put_response = self.forced_auth_req( + 'delete', + url, + user=self.admin_user, + data={ + 'id': [school_id, ] + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + response = self.forced_auth_req( + 'post', + url, + user=self.admin_user, + view=view, + data={ + 'name': 'New School 3', + 'giga_id_school': 'ac65543e-cdba-4f5c-891a-448bzdc12e099', + 'external_id': '258055910313231', + 'country': self.country.id, + 'geopoint': { + 'type': 'Point', + 'coordinates': [ + 76.92044830322266, + 9.022849082946777 + ] + }, + 'gps_confidence': 1.0, + 'altitude': 0, + 'timezone': 'Africa/Conakry' + } + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.assertEqual(school_id, response.data['id']) + + def test_retrieve_school(self): + url, view, view_info = schools_url((self.school_one.id,), {}, view_name='update-or-retrieve-school') + + response = self.forced_auth_req('get', url, view=view, user=self.admin_user, view_info=view_info) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_retrieve_school_to_invalid_id(self): + url, view, view_info = schools_url((1234,), {}, view_name='update-or-retrieve-school') + + response = self.forced_auth_req('get', url, view=view, user=self.admin_user, view_info=view_info) + + self.assertEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_delete_school(self): + url, _, view = schools_url((), {}, view_name='list-create-destroy-school') + + put_response = self.forced_auth_req( + 'delete', + url, + user=self.admin_user, + data={ + 'id': [self.school_one.id, ] + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + def test_delete_school_to_invalid_id(self): + url, _, view = schools_url((), {}, view_name='list-create-destroy-school') + + put_response = self.forced_auth_req( + 'delete', + url, + user=self.admin_user, + data={ + 'id': [54321, ] + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_download_school_data_without_api_key(self): + url, view, view_info = schools_url((), { + 'page': '1', + 'page_size': '10', + 'ordering': 'name', + 'expand': 'country,last_weekly_status,admin1,admin2', + }, view_name='download-schools') + + response = self.forced_auth_req( + 'get', + url, + user=self.admin_user, + view_info=view_info, + view=view, + request_format='text/csv' + ) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) diff --git a/proco/schools/tests/test_utils.py b/proco/schools/tests/test_utils.py new file mode 100644 index 0000000..069544f --- /dev/null +++ b/proco/schools/tests/test_utils.py @@ -0,0 +1,35 @@ +from django.test import TestCase + +from proco.locations.tests.factories import CountryFactory +from proco.schools import utils as schools_utilities +from proco.schools.tests.factories import SchoolFactory +from proco.utils.tests import TestAPIViewSetMixin + + +class UtilsUtilitiesTestCase(TestAPIViewSetMixin, TestCase): + + def test_get_imported_file_path_utility(self): + self.assertEqual(type(schools_utilities.get_imported_file_path(None, 'TestImportFile.csv')), str) + + def test_get_coverage_type_utility(self): + country = CountryFactory() + school_one = SchoolFactory(country=country, location__country=country) + self.assertEqual(type(schools_utilities.get_coverage_type(school_one)), str) + + def test_get_connectivity_status_utility(self): + country = CountryFactory() + school_one = SchoolFactory(country=country, location__country=country) + self.assertEqual(type(schools_utilities.get_connectivity_status(school_one)), str) + + def test_get_connectivity_status_by_master_api_utility(self): + country = CountryFactory() + school_one = SchoolFactory(country=country, location__country=country) + self.assertEqual(type(schools_utilities.get_connectivity_status_by_master_api(school_one)), str) + + def test_get_get_coverage_status_utility(self): + country = CountryFactory() + school_one = SchoolFactory(country=country, location__country=country) + self.assertEqual(type(schools_utilities.get_coverage_status(school_one)), str) + + def test_update_school_from_country_or_school_weekly_update_utility(self): + self.assertEqual(schools_utilities.update_school_from_country_or_school_weekly_update(), None) diff --git a/proco/schools/utils.py b/proco/schools/utils.py index c0c143e..ac606dd 100644 --- a/proco/schools/utils.py +++ b/proco/schools/utils.py @@ -1,5 +1,6 @@ import os import uuid +import logging from datetime import timedelta from django.db import transaction @@ -8,6 +9,8 @@ from proco.core import utils as core_utilities from proco.schools.constants import statuses_schema +logger = logging.getLogger('gigamaps.' + __name__) + def get_imported_file_path(instance, filename): filename_stripped = os.path.splitext(filename)[0].split('/')[-1] @@ -126,7 +129,7 @@ def update_school_from_country_or_school_weekly_update(start_time=None, end_time modified__lt=end_time, ).values_list('id', flat=True).order_by('id').distinct('id') - print('Query to select countries updated between ({0} - {1}): {2}'.format( + logger.debug('Query to select countries updated between ({0} - {1}): {2}'.format( start_time, end_time, country_ids_updated_in_last_12_hours.query)) # CountryWeeklyStatus modified in last 24 hours @@ -136,7 +139,7 @@ def update_school_from_country_or_school_weekly_update(start_time=None, end_time ).exclude(id__in=list(country_ids_updated_in_last_12_hours)).values_list( 'id', flat=True).order_by('id').distinct('id') - print('Query to select countries where CountryWeeklyStatus updated between ({0} - {1}): {2}'.format( + logger.debug('Query to select countries where CountryWeeklyStatus updated between ({0} - {1}): {2}'.format( start_time, end_time, country_status_updated_in_last_12_hours.query)) # SchoolWeeklyStatus updated in last 24 hours @@ -145,7 +148,7 @@ def update_school_from_country_or_school_weekly_update(start_time=None, end_time Q(country_id__in=list(country_ids_updated_in_last_12_hours) + list(country_status_updated_in_last_12_hours)) ) - print('Query to select schools where SchoolWeeklyStatus updated between ({0} - {1}): {2}'.format( + logger.debug('Query to select schools where SchoolWeeklyStatus updated between ({0} - {1}): {2}'.format( start_time, end_time, school_updated_in_last_12_hours.query)) for data_chunk in core_utilities.queryset_iterator(school_updated_in_last_12_hours, chunk_size=20000): diff --git a/proco/taskapp/__init__.py b/proco/taskapp/__init__.py index 997cf0b..a0f43e2 100755 --- a/proco/taskapp/__init__.py +++ b/proco/taskapp/__init__.py @@ -15,6 +15,7 @@ app.conf.timezone = 'UTC' app.conf.broker_transport_options = {"visibility_timeout": 36000} # 10h app.conf.worker_deduplicate_successful_tasks = True +app.conf.redbeat_lock_key = None @app.on_after_finalize.connect @@ -46,7 +47,7 @@ def finalize_setup(sender, **kwargs): 'proco.data_sources.tasks.update_static_data': { 'task': 'proco.data_sources.tasks.update_static_data', # Executes at 4:00 AM every day - 'schedule': crontab(hour=3, minute=0), # crontab(hour=1, minute=0, day_of_week='mon'), + 'schedule': crontab(hour='*/4', minute=47), # crontab(hour=1, minute=0, day_of_week='mon'), 'args': (), }, 'proco.data_sources.tasks.update_live_data': { @@ -89,5 +90,10 @@ def finalize_setup(sender, **kwargs): 'schedule': crontab(hour=5, minute=10), 'args': (), }, + 'proco.utils.tasks.duplicate_task_test': { + 'task': 'proco.utils.tasks.duplicate_task_test', + 'schedule': crontab(minute='*/20'), + 'args': (), + }, # 'drf_secure_token.tasks.delete_old_tokens': DELETE_OLD_TOKENS, }) diff --git a/proco/utils/log.py b/proco/utils/log.py index eec51b9..c51528e 100644 --- a/proco/utils/log.py +++ b/proco/utils/log.py @@ -1,8 +1,10 @@ import traceback +import logging from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION from django.contrib.contenttypes.models import ContentType +logger = logging.getLogger('gigamaps.' + __name__) def action_log(request, queryset, checked, change_message, model, field_name): action_flag = ADDITION @@ -36,7 +38,10 @@ def action_log(request, queryset, checked, change_message, model, field_name): change_message=change_message) -def changed_fields(instance, validated_data, changed_data=[]): +def changed_fields(instance, validated_data, changed_data=None): + if not changed_data: + changed_data = [] + model_instance = ['country', 'school', 'last_weekly_status', 'location'] try: for field, value in validated_data.items(): @@ -44,8 +49,12 @@ def changed_fields(instance, validated_data, changed_data=[]): if field in model_instance and int(value) != int(getattr(instance, field, None).id): changed_data.append(field) elif 'date' in field: - if (getattr(instance, field, None) != None and str(getattr(instance, field, None).strftime( - "%d-%m-%Y")) != value) or getattr(instance, field, None) == None and value != '': + if ( + ( + getattr(instance, field, None) is not None and + str(getattr(instance, field, None).strftime("%d-%m-%Y")) != value + ) or getattr(instance, field, None) is None and value != '' + ): changed_data.append(field) else: try: @@ -53,18 +62,22 @@ def changed_fields(instance, validated_data, changed_data=[]): except: pass elif 'date' not in field and field not in model_instance and value != getattr(instance, field, None): - if value == "" and ( - getattr(instance, field, None) == None or getattr(instance, field, None) == '') or ( - field in ['schools_with_data_percentage'] and getattr(instance, field, None) == float(value)): + if ( + value == "" and + (getattr(instance, field, None) is None or getattr(instance, field, None) == '') or + (field in ['schools_with_data_percentage'] and getattr(instance, field, None) == float(value)) + ): pass - elif (getattr(instance, field, None) != '' and (value != "" or value == "")) or ( - getattr(instance, field, None) == None and value != "") or ( - getattr(instance, field, None) == '' and value != ""): + elif ( + (getattr(instance, field, None) != '' and (value != "" or value == "")) or + (getattr(instance, field, None) is None and value != "") or + (getattr(instance, field, None) == '' and value != "") + ): changed_data.append(field) else: changed_fields(getattr(instance, field), validated_data[field], changed_data) except: - print(traceback.format_exc()) + logger.error(traceback.format_exc()) changed_data = list(set(changed_data)) remove_item = ["created", "modified"] @@ -74,17 +87,22 @@ def changed_fields(instance, validated_data, changed_data=[]): return changed_data -def changed_about_us_content_fields(instance, validated_data, changed_data=[], d=True): +def changed_about_us_content_fields(instance, validated_data, changed_data=None): + if not changed_data: + changed_data = [] + try: for field, value in validated_data.items(): if not isinstance(value, dict) and not isinstance(value, list): if value != getattr(instance, field, None): - if (getattr(instance, field, None) != '' and (value != "" or value == "")) or ( - getattr(instance, field, None) == None and value != "") or ( - getattr(instance, field, None) == '' and value != ""): + if ( + (getattr(instance, field, None) != '' and (value != "" or value == "")) or + (getattr(instance, field, None) is None and value != "") or + (getattr(instance, field, None) == '' and value != "") + ): changed_data.append(field) elif isinstance(value, dict): - changed_about_us_content_fields(getattr(instance, field), validated_data[field], changed_data, 'dict') + changed_about_us_content_fields(getattr(instance, field), validated_data[field], changed_data) elif isinstance(value, list) and len(value) > 0 and isinstance(value[0], str): try: if set(instance[field]) != set(value): @@ -99,8 +117,7 @@ def changed_about_us_content_fields(instance, validated_data, changed_data=[], d for dict_field, dict_value in item.items(): if isinstance(dict_value, dict): changed_about_us_content_fields(getattr(instance, field)[i][dict_field], item[dict_field], - changed_data, - 'dict') + changed_data) elif isinstance(dict_value, list): if set(dict_value) != set(getattr(instance, field)[i][dict_field]): changed_data.append(field + '_' + dict_field) @@ -109,5 +126,5 @@ def changed_about_us_content_fields(instance, validated_data, changed_data=[], d changed_data.append(field + '_' + dict_field) i += 1 except: - print(traceback.format_exc()) + logger.error(traceback.format_exc()) return changed_data diff --git a/proco/utils/tasks.py b/proco/utils/tasks.py index 8233e11..0a975ac 100644 --- a/proco/utils/tasks.py +++ b/proco/utils/tasks.py @@ -1,15 +1,23 @@ +import logging +import time +import uuid + from celery import chain -from django.core.cache import cache +from celery import current_task from django.core.management import call_command +from django.db.models import Q from django.db.models.functions.text import Lower from django.urls import reverse from rest_framework.test import APIClient +from proco.background import utils as background_task_utilities from proco.core import db_utils as db_utilities from proco.core import utils as core_utilities from proco.taskapp import app from proco.utils.dates import format_date +logger = logging.getLogger('gigamaps.' + __name__) + @app.task(soft_time_limit=10 * 60, time_limit=11 * 60) def update_cached_value(*args, url='', query_params=None, **kwargs): @@ -25,59 +33,78 @@ def update_cached_value(*args, url='', query_params=None, **kwargs): def update_all_cached_values(): from proco.locations.models import Country from proco.schools.models import School + from proco.accounts.models import DataLayerCountryRelationship, DataLayer - task_cache_key = 'update_all_cached_values_status_{current_time}'.format( - current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y'), - ) - running_task = cache.get(task_cache_key, None) + task_key = 'update_all_cached_values_status_{current_time}'.format( + current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H%M')) - if running_task in [None, b'completed', 'completed']: - print('***** Not found running Job *****') - cache.set(task_cache_key, 'running', None) + task_id = current_task.request.id or str(uuid.uuid4()) + task_instance = background_task_utilities.task_on_start( + task_id, task_key, 'Update the Redis cache, allowed once in a hour') - update_cached_value.delay(url=reverse('connection_statistics:global-stat')) - update_cached_value.delay(url=reverse('locations:countries-list')) + if task_instance: + logger.debug('Not found running job: {}'.format(task_key)) update_cached_value.delay(url=reverse('locations:search-countries-admin-schools')) - # update_cached_value.delay(url=reverse('locations:countries-boundary')) - # update_cached_value.delay(url=reverse('schools:random-schools')) + update_cached_value.delay(url=reverse('locations:countries-list')) + update_cached_value.delay(url=reverse('accounts:list-advanced-filters')) + update_cached_value.delay(url=reverse('connection_statistics:global-stat')) # Get countries which has at least has 1 school countries = Country.objects.filter(id__in=list( School.objects.all().values_list('country_id', flat=True).order_by('country_id').distinct('country_id') )) - for country in countries: - chain([ - update_cached_value.s( - url=reverse('connection_statistics:global-stat'), - query_params={'country_id': country.id}, + + country_wise_default_layers = { + row['country_id']: row['data_layer_id'] + for row in DataLayerCountryRelationship.objects.filter( + Q(is_default=True) | Q( + is_default=False, + data_layer__category=DataLayer.LAYER_CATEGORY_CONNECTIVITY, + data_layer__created_by__isnull=True, ), + data_layer__type=DataLayer.LAYER_TYPE_LIVE, + data_layer__status=DataLayer.LAYER_STATUS_PUBLISHED, + data_layer__deleted__isnull=True, + country_id__in=list(countries)).values('country_id', 'data_layer_id').order_by('country_id').distinct() + } + + for country in countries: + country_wise_task_list = [ update_cached_value.s( url=reverse('locations:countries-detail', kwargs={'pk': country.code.lower()}) ), - # update_cached_value.s( - # url=reverse('schools:schools-list', kwargs={'country_code': country.code.lower()}) - # ), update_cached_value.s( - url=reverse('connection_statistics:get-latest-week-and-month'), + url=reverse('connection_statistics:global-stat'), query_params={'country_id': country.id}, ), - ]).delay() + ] + + if country_wise_default_layers.get(country.id, None): + country_wise_task_list.append(update_cached_value.s( + url=reverse('connection_statistics:get-latest-week-and-month'), + query_params={ + 'country_id': country.id, + 'layer_id': country_wise_default_layers[country.id], + }, + )) - cache.set(task_cache_key, 'completed', None) + chain(country_wise_task_list).delay() + + background_task_utilities.task_on_complete(task_instance) else: - print('***** Found running Job with "{0}" name so skipping current iteration *****'.format(task_cache_key)) + logger.error('Found running Job with "{0}" name so skipping current iteration'.format(task_key)) @app.task def update_country_related_cache(country_code): from proco.locations.models import Country - update_cached_value.delay(url=reverse('connection_statistics:global-stat')) - update_cached_value.delay(url=reverse('locations:countries-list')) update_cached_value.delay(url=reverse('locations:search-countries-admin-schools')) + update_cached_value.delay(url=reverse('locations:countries-list')) + update_cached_value.delay(url=reverse('accounts:list-advanced-filters')) + update_cached_value.delay(url=reverse('connection_statistics:global-stat')) update_cached_value.delay(url=reverse('locations:countries-detail', kwargs={'pk': country_code.lower()})) - # update_cached_value.delay(url=reverse('schools:random-schools')) - # update_cached_value.delay(url=reverse('schools:schools-list', kwargs={'country_code': country_code.lower()})) + country = Country.objects.annotate( code_lower=Lower('code'), ).filter(code_lower=country_code.lower()) @@ -95,21 +122,23 @@ def rebuild_school_index(): Task which runs to rebuild the Cognitive Search Index for Schools from scratch. Frequency: Once in a day - Limit: 15 mins + Limit: 15 minutes """ - print('Rebuilding the School Index') - task_cache_key = 'rebuild_school_index_status' - running_task = cache.get(task_cache_key, None) - - if running_task in [None, b'completed', 'completed']: - print('***** Not found running Job *****') - cache.set(task_cache_key, 'running', None) - args = ['--delete_index', '--create_index', '--clean_index', '--update_index'] - call_command('index_rebuild_schools', *args) - - cache.set(task_cache_key, 'completed', None) + logger.info('Rebuilding the school indexes.') + task_key = 'rebuild_school_index_status_{current_time}'.format( + current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H')) + + task_id = current_task.request.id or str(uuid.uuid4()) + task_instance = background_task_utilities.task_on_start( + task_id, task_key, 'Update the Cognitive Search Index for Schools') + + if task_instance: + logger.debug('Not found running job: {}'.format(task_key)) + cmd_args = ['--delete_index', '--create_index', '--clean_index', '--update_index'] + call_command('index_rebuild_schools', *cmd_args) + background_task_utilities.task_on_complete(task_instance) else: - print('***** Found running Job with "{0}" name so skipping current iteration *****'.format(task_cache_key)) + logger.error('Found running Job with "{0}" name so skipping current iteration'.format(task_key)) @app.task(soft_time_limit=1 * 60 * 60, time_limit=1 * 60 * 60) @@ -121,13 +150,17 @@ def populate_school_registration_data(): Frequency: Once in a day Limit: 1 hour """ - print('Setting RT status, RT Date for School which start live data from sources.') - task_cache_key = 'populate_school_registration_data_status' - running_task = cache.get(task_cache_key, None) + logger.info('Setting RT status, RT Date for schools which start live data from sources.') + + task_key = 'populate_school_registration_data_status_{current_time}'.format( + current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H')) - if running_task in [None, b'completed', 'completed']: - print('***** Not found running Job *****') - cache.set(task_cache_key, 'running', None) + task_id = current_task.request.id or str(uuid.uuid4()) + task_instance = background_task_utilities.task_on_start( + task_id, task_key, 'Populate the RT table data for new schools') + + if task_instance: + logger.debug('Not found running job: {}'.format(task_key)) sql = """ SELECT DISTINCT sds.school_id FROM public.connection_statistics_schooldailystatus AS sds @@ -141,12 +174,12 @@ def populate_school_registration_data(): school_ids_missing_in_rt_table = db_utilities.sql_to_response(sql, label='SchoolRealtimeRegistration') for missing_school_id in school_ids_missing_in_rt_table: - args = ['--reset', '-school_id={0}'.format(missing_school_id['school_id'])] - call_command('populate_school_registration_data', *args) + cmd_args = ['--reset', '-school_id={0}'.format(missing_school_id['school_id'])] + call_command('populate_school_registration_data', *cmd_args) - cache.set(task_cache_key, 'completed', None) + background_task_utilities.task_on_complete(task_instance) else: - print('***** Found running Job with "{0}" name so skipping current iteration *****'.format(task_cache_key)) + logger.error('Found running Job with "{0}" name so skipping current iteration'.format(task_key)) @app.task(soft_time_limit=10 * 60 * 60, time_limit=10 * 60 * 60) @@ -156,20 +189,22 @@ def redo_aggregations_task(country_id, year, week_no, *args): Task to schedule manually from Console. """ if not country_id or not year: - print('ERROR: Required args not provided: [country_id, year]') + logger.error('Required args not provided: [country_id, year]') return - print('Starting redo_aggregations_task: Country ID "{0}" - Year "{1}" - Week "{2}"'.format( + logger.info('Starting redo aggregations task: Country ID "{0}" - Year "{1}" - Week "{2}"'.format( country_id, year, week_no)) - task_cache_key = 'redo_aggregations_task_country_id_{0}_year_{1}_week_{2}_on_{3}'.format( - country_id, year, week_no, format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y')) - running_task = cache.get(task_cache_key, None) + task_key = 'redo_aggregations_task_country_id_{0}_year_{1}_week_{2}_on_{3}'.format( + country_id, year, week_no, format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H')) + + task_id = current_task.request.id or str(uuid.uuid4()) + task_instance = background_task_utilities.task_on_start( + task_id, task_key, 'Update the SchoolWeekly, CountryDaily and CountryWeekly from SchoolDaily') - if running_task in [None, b'completed', 'completed']: - print('***** Not found running Job "{}" *****'.format(task_cache_key)) - cache.set(task_cache_key, 'running', None) - args = [ + if task_instance: + logger.debug('Not found running job: {}'.format(task_key)) + cmd_args = [ '-country_id={}'.format(country_id), '-year={}'.format(year), '--update_school_weekly', @@ -178,46 +213,59 @@ def redo_aggregations_task(country_id, year, week_no, *args): ] if week_no: - args.append('-week_no={}'.format(week_no)) + cmd_args.append('-week_no={}'.format(week_no)) - call_command('redo_aggregations', *args) + call_command('redo_aggregations', *cmd_args) - cache.set(task_cache_key, 'completed', None) + background_task_utilities.task_on_complete(task_instance) else: - print('***** Found running Job with "{0}" name so skipping current iteration *****'.format(task_cache_key)) + logger.error('Found running Job with "{0}" name so skipping current iteration'.format(task_key)) @app.task(soft_time_limit=10 * 60 * 60, time_limit=10 * 60 * 60) -def populate_school_new_fields_task(start_school_id, end_school_id, country_id, *args): +def populate_school_new_fields_task(start_school_id, end_school_id, country_id, *args, school_ids=None): """ populate_school_new_fields_task Task to schedule manually from Console. """ - print( - 'Starting populate_school_new_fields_task: Country ID "{0}" - start_school_id "{1}" - end_school_id "{2}"'.format( - country_id, start_school_id, end_school_id)) + logger.info('Starting populate school new fields task: Country ID "{0}" - start_school_id "{1}" - ' + 'end_school_id "{2}"'.format(country_id, start_school_id, end_school_id)) + + cmd_args = [] + + if country_id: + cmd_args.append('-country_id={}'.format(country_id)) - task_cache_key = 'populate_school_new_fields_task_country_id_{0}_start_school_id_{1}_end_school_id_{2}_on_{3}'.format( - country_id, start_school_id, end_school_id, - format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y')) - running_task = cache.get(task_cache_key, None) + if start_school_id: + cmd_args.append('-start_school_id={}'.format(start_school_id)) - if running_task in [None, b'completed', 'completed']: - print('***** Not found running Job "{}" *****'.format(task_cache_key)) - cache.set(task_cache_key, 'running', None) - args = [] + if end_school_id: + cmd_args.append('-end_school_id={}'.format(end_school_id)) - if country_id: - args.append('-country_id={}'.format(country_id)) + if school_ids and len(school_ids) > 0: + cmd_args.append('-school_ids={}'.format(','.join([str(school_id) for school_id in school_ids]))) - if start_school_id: - args.append('-start_school_id={}'.format(start_school_id)) + task_key = 'populate_school_new_fields_task{0}_at_{1}'.format( + ''.join(cmd_args), format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H')) - if end_school_id: - args.append('-end_school_id={}'.format(end_school_id)) + task_id = current_task.request.id or str(uuid.uuid4()) + task_instance = background_task_utilities.task_on_start( + task_id, task_key, 'Update the school new fields for provided records') - call_command('populate_school_new_fields', *args) + if task_instance: + logger.debug('Not found running job: {}'.format(task_key)) - cache.set(task_cache_key, 'completed', None) + task_instance.info('Starting the command with args: {}'.format(cmd_args)) + call_command('populate_school_new_fields', *cmd_args) + task_instance.info('Completed the command.') + + background_task_utilities.task_on_complete(task_instance) else: - print('***** Found running Job with "{0}" name so skipping current iteration *****'.format(task_cache_key)) + logger.error('Found running Job with "{0}" name so skipping current iteration'.format(task_key)) + + +@app.task(soft_time_limit=10 * 60 * 60, time_limit=10 * 60 * 60) +def duplicate_task_test(): + logger.info('Inside duplicate_task_test task {}'.format(core_utilities.get_current_datetime_object())) + time.sleep(180.0) + logger.info('Exiting from duplicate_task_test task {}'.format(core_utilities.get_current_datetime_object())) diff --git a/proco/utils/tests.py b/proco/utils/tests.py index 4cb7b03..d0c1c76 100644 --- a/proco/utils/tests.py +++ b/proco/utils/tests.py @@ -26,7 +26,11 @@ def forced_auth_req(self, method, url, user=None, data=None, request_format='jso if 'view' in kwargs: view = kwargs.pop('view') - response = view(request) + if 'view_info' in kwargs: + view_info = kwargs.pop('view_info') + response = view_info(request, *view.args, **view.kwargs) + else: + response = view(request) else: view_info = resolve(url) view = view_info.func diff --git a/proco/utils/urls.py b/proco/utils/urls.py index ab99f3c..0ebecfe 100644 --- a/proco/utils/urls.py +++ b/proco/utils/urls.py @@ -37,7 +37,7 @@ def add_url_params(url, params): # Converting URL argument to proper query string encoded_get_args = urlencode(parsed_get_args, doseq=True) # Creating new parsed result object based on provided with new - # URL arguments. Same thing happens inside of urlparse. + # URL arguments. Same thing happens inside urlparse. new_url = ParseResult( parsed_url.scheme, parsed_url.netloc, parsed_url.path, parsed_url.params, encoded_get_args, parsed_url.fragment, diff --git a/web-worker.sh b/web-worker.sh index 62a5534..04eb750 100644 --- a/web-worker.sh +++ b/web-worker.sh @@ -71,3 +71,11 @@ pipenv run gunicorn config.wsgi:application -b 0.0.0.0:8000 -w 8 --timeout=300 # pipenv run python manage.py data_loss_recovery_for_qos --pull_data -country_code='MNG' -pull_start_version=11 -pull_end_version=20 # Step 3: Update the proco tables with new aggregation # pipenv run python manage.py data_loss_recovery_for_qos --aggregate -country_code='MNG' -aggregate_start_version=11 -aggregate_end_version=20 + + +# pipenv run python manage.py create_api_key_with_write_access -user='pcdc_user_with_write_api_key66@nagarro.com' -api_code='DAILY_CHECK_APP' -reason='API Key to GET the PCDC measurement data, Post/Delete API Control over DailyCheckApp documentation' -valid_till='31-12-2099' --force_user -first_name='PCDC' -last_name='User' --inactive_email +# pipenv run python manage.py load_system_data_layers --update_data_layers_code +# pipenv run python manage.py update_system_role_permissions +# pipenv run python manage.py data_cleanup --clean_duplicate_school_gigs_ids + +# pipenv run python manage.py create_admin_user -email='pcdc_user_with_write_api_key5@nagarro.com' -first_name='PCDC' -last_name='User' --inactive_email From 7553f1cf29d14436c56f067b5e5afbf0bbcc80a8 Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Tue, 16 Jul 2024 18:30:31 +0530 Subject: [PATCH 02/27] Deleted unused files and folders --- config/admin.py | 28 - config/apps.py | 5 - proco/assets/admin/styles/admin.css | 56 - proco/assets/test.txt | 1 - proco/assets/vendor/fontawesome/css/all.css | 4586 --------------- .../fontawesome/webfonts/fa-brands-400.eot | Bin 134622 -> 0 bytes .../fontawesome/webfonts/fa-brands-400.svg | 3637 ------------ .../fontawesome/webfonts/fa-brands-400.ttf | Bin 134316 -> 0 bytes .../fontawesome/webfonts/fa-brands-400.woff | Bin 90672 -> 0 bytes .../fontawesome/webfonts/fa-brands-400.woff2 | Bin 77400 -> 0 bytes .../fontawesome/webfonts/fa-regular-400.eot | Bin 34350 -> 0 bytes .../fontawesome/webfonts/fa-regular-400.svg | 805 --- .../fontawesome/webfonts/fa-regular-400.ttf | Bin 34052 -> 0 bytes .../fontawesome/webfonts/fa-regular-400.woff | Bin 16780 -> 0 bytes .../fontawesome/webfonts/fa-regular-400.woff2 | Bin 13600 -> 0 bytes .../fontawesome/webfonts/fa-solid-900.eot | Bin 204266 -> 0 bytes .../fontawesome/webfonts/fa-solid-900.svg | 5015 ----------------- .../fontawesome/webfonts/fa-solid-900.ttf | Bin 203980 -> 0 bytes .../fontawesome/webfonts/fa-solid-900.woff | Bin 104004 -> 0 bytes .../fontawesome/webfonts/fa-solid-900.woff2 | Bin 80148 -> 0 bytes proco/background/admin.py | 11 - proco/connection_statistics/admin.py | 113 - proco/dailycheckapp_contact/__init__.py | 1 - proco/dailycheckapp_contact/admin.py | 16 - proco/dailycheckapp_contact/api.py | 9 - proco/dailycheckapp_contact/api_urls.py | 9 - proco/dailycheckapp_contact/apps.py | 9 - .../migrations/0001_initial.py | 34 - .../migrations/__init__.py | 0 proco/dailycheckapp_contact/models.py | 17 - proco/dailycheckapp_contact/receivers.py | 18 - proco/dailycheckapp_contact/serializers.py | 9 - proco/locations/admin.py | 129 - proco/realtime_dailycheckapp/__init__.py | 0 .../migrations/0001_initial.py | 38 - .../migrations/__init__.py | 0 proco/realtime_dailycheckapp/models.py | 46 - .../realtime_dailycheckapp/tests/__init__.py | 0 .../tests/db/__init__.py | 0 .../tests/db/initdb.sql | 20 - proco/realtime_dailycheckapp/tests/db/test.py | 9 - .../realtime_dailycheckapp/tests/factories.py | 42 - .../realtime_dailycheckapp/tests/test_sync.py | 130 - proco/realtime_dailycheckapp/utils.py | 54 - proco/realtime_unicef/__init__.py | 0 .../migrations/0001_initial.py | 38 - proco/realtime_unicef/migrations/__init__.py | 0 proco/realtime_unicef/models.py | 46 - proco/realtime_unicef/tests/__init__.py | 0 proco/realtime_unicef/tests/db/__init__.py | 0 proco/realtime_unicef/tests/db/initdb.sql | 20 - proco/realtime_unicef/tests/db/test.py | 9 - proco/realtime_unicef/tests/factories.py | 42 - proco/realtime_unicef/tests/test_sync.py | 130 - proco/realtime_unicef/utils.py | 55 - proco/schools/admin.py | 96 - proco/schools/forms.py | 48 - .../background/backgroud_task_change.html | 11 - proco/templates/admin/index.html | 27 - .../admin/locations/action_confirm.html | 59 - .../templates/admin/schools/change_list.html | 123 - .../schools/file_imports_change_form.html | 21 - .../email/dailycheckapp_contact_email.html | 12 - proco/utils/admin.py | 22 - 64 files changed, 15606 deletions(-) delete mode 100644 config/admin.py delete mode 100644 config/apps.py delete mode 100644 proco/assets/admin/styles/admin.css delete mode 100644 proco/assets/test.txt delete mode 100644 proco/assets/vendor/fontawesome/css/all.css delete mode 100644 proco/assets/vendor/fontawesome/webfonts/fa-brands-400.eot delete mode 100644 proco/assets/vendor/fontawesome/webfonts/fa-brands-400.svg delete mode 100644 proco/assets/vendor/fontawesome/webfonts/fa-brands-400.ttf delete mode 100644 proco/assets/vendor/fontawesome/webfonts/fa-brands-400.woff delete mode 100644 proco/assets/vendor/fontawesome/webfonts/fa-brands-400.woff2 delete mode 100644 proco/assets/vendor/fontawesome/webfonts/fa-regular-400.eot delete mode 100644 proco/assets/vendor/fontawesome/webfonts/fa-regular-400.svg delete mode 100644 proco/assets/vendor/fontawesome/webfonts/fa-regular-400.ttf delete mode 100644 proco/assets/vendor/fontawesome/webfonts/fa-regular-400.woff delete mode 100644 proco/assets/vendor/fontawesome/webfonts/fa-regular-400.woff2 delete mode 100644 proco/assets/vendor/fontawesome/webfonts/fa-solid-900.eot delete mode 100644 proco/assets/vendor/fontawesome/webfonts/fa-solid-900.svg delete mode 100644 proco/assets/vendor/fontawesome/webfonts/fa-solid-900.ttf delete mode 100644 proco/assets/vendor/fontawesome/webfonts/fa-solid-900.woff delete mode 100644 proco/assets/vendor/fontawesome/webfonts/fa-solid-900.woff2 delete mode 100644 proco/background/admin.py delete mode 100644 proco/connection_statistics/admin.py delete mode 100644 proco/dailycheckapp_contact/__init__.py delete mode 100644 proco/dailycheckapp_contact/admin.py delete mode 100644 proco/dailycheckapp_contact/api.py delete mode 100644 proco/dailycheckapp_contact/api_urls.py delete mode 100644 proco/dailycheckapp_contact/apps.py delete mode 100644 proco/dailycheckapp_contact/migrations/0001_initial.py delete mode 100644 proco/dailycheckapp_contact/migrations/__init__.py delete mode 100644 proco/dailycheckapp_contact/models.py delete mode 100644 proco/dailycheckapp_contact/receivers.py delete mode 100644 proco/dailycheckapp_contact/serializers.py delete mode 100644 proco/locations/admin.py delete mode 100644 proco/realtime_dailycheckapp/__init__.py delete mode 100644 proco/realtime_dailycheckapp/migrations/0001_initial.py delete mode 100644 proco/realtime_dailycheckapp/migrations/__init__.py delete mode 100644 proco/realtime_dailycheckapp/models.py delete mode 100644 proco/realtime_dailycheckapp/tests/__init__.py delete mode 100644 proco/realtime_dailycheckapp/tests/db/__init__.py delete mode 100644 proco/realtime_dailycheckapp/tests/db/initdb.sql delete mode 100644 proco/realtime_dailycheckapp/tests/db/test.py delete mode 100644 proco/realtime_dailycheckapp/tests/factories.py delete mode 100644 proco/realtime_dailycheckapp/tests/test_sync.py delete mode 100644 proco/realtime_dailycheckapp/utils.py delete mode 100644 proco/realtime_unicef/__init__.py delete mode 100644 proco/realtime_unicef/migrations/0001_initial.py delete mode 100644 proco/realtime_unicef/migrations/__init__.py delete mode 100644 proco/realtime_unicef/models.py delete mode 100644 proco/realtime_unicef/tests/__init__.py delete mode 100644 proco/realtime_unicef/tests/db/__init__.py delete mode 100644 proco/realtime_unicef/tests/db/initdb.sql delete mode 100644 proco/realtime_unicef/tests/db/test.py delete mode 100644 proco/realtime_unicef/tests/factories.py delete mode 100644 proco/realtime_unicef/tests/test_sync.py delete mode 100644 proco/realtime_unicef/utils.py delete mode 100644 proco/schools/admin.py delete mode 100644 proco/schools/forms.py delete mode 100644 proco/templates/admin/background/backgroud_task_change.html delete mode 100644 proco/templates/admin/index.html delete mode 100644 proco/templates/admin/locations/action_confirm.html delete mode 100644 proco/templates/admin/schools/change_list.html delete mode 100644 proco/templates/admin/schools/file_imports_change_form.html delete mode 100644 proco/templates/email/dailycheckapp_contact_email.html delete mode 100644 proco/utils/admin.py diff --git a/config/admin.py b/config/admin.py deleted file mode 100644 index e71b782..0000000 --- a/config/admin.py +++ /dev/null @@ -1,28 +0,0 @@ -from django.conf.urls import url -from django.contrib import admin, messages -from django.shortcuts import redirect -from django.utils.translation import ugettext as _ - -from proco.utils.cache import cache_manager -from proco.utils.tasks import update_all_cached_values - - -class CustomAdminSite(admin.AdminSite): - site_header = _('Project Connect') - site_title = _('Project Connect') - index_title = _('Welcome to Project Connect') - index_templates = 'admin/index.html' - - def get_urls(self): - urls = super().get_urls() - urls += [ - url(r'^invalidate-cache/$', self.admin_view(self.invalidate_cache), name='admin_invalidate_cache'), - ] - return urls - - def invalidate_cache(self, request): - cache_manager.invalidate() - update_all_cached_values.delay() - - messages.success(request, 'Cache invalidation started. Maps will be updated in a few minutes.') - return redirect('admin:index') diff --git a/config/apps.py b/config/apps.py deleted file mode 100644 index 5cf7c6e..0000000 --- a/config/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.contrib.admin.apps import AdminConfig - - -class CustomAdminConfig(AdminConfig): - default_site = 'config.admin.CustomAdminSite' diff --git a/proco/assets/admin/styles/admin.css b/proco/assets/admin/styles/admin.css deleted file mode 100644 index 0681192..0000000 --- a/proco/assets/admin/styles/admin.css +++ /dev/null @@ -1,56 +0,0 @@ -ul.action-list { - padding-left: 20px; -} - -ul.action-list li { - list-style-type: none; -} -ul.action-list li a { - font-weight: 600; -} -ul.action-list li a i { - padding-right: 5px; -} - -.admin-actions { - background: #f8f8f8; - display: block; - float: left; - position: relative; -} - -#content-related { - margin-top: 150px; -} - -.admin-actions h2 { - padding: 16px; - margin-bottom: 16px; - border-bottom: 1px solid #eaeaea; - font-size: 18px; - color: #333; - font-weight: 400; -} - -@media (min-width: 1025px) { -.admin-actions { - margin-right: -300px; - width: 260px; - margin-left: 40px; -} -} - -@media (max-width: 1024px) and (min-width: 767px) { -.admin-actions { - margin-left: 30px; - margin-right: -300px; - width: 260px; -} -} - -@media (max-width: 767px) { -.admin-actions { - width: 100%; - margin-bottom: 20px; -} -} diff --git a/proco/assets/test.txt b/proco/assets/test.txt deleted file mode 100644 index 84362ca..0000000 --- a/proco/assets/test.txt +++ /dev/null @@ -1 +0,0 @@ -Test file \ No newline at end of file diff --git a/proco/assets/vendor/fontawesome/css/all.css b/proco/assets/vendor/fontawesome/css/all.css deleted file mode 100644 index 934c5ca..0000000 --- a/proco/assets/vendor/fontawesome/css/all.css +++ /dev/null @@ -1,4586 +0,0 @@ -/*! - * Font Awesome Free 5.14.0 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - */ -.fa, -.fas, -.far, -.fal, -.fad, -.fab { - -moz-osx-font-smoothing: grayscale; - -webkit-font-smoothing: antialiased; - display: inline-block; - font-style: normal; - font-variant: normal; - text-rendering: auto; - line-height: 1; } - -.fa-lg { - font-size: 1.33333em; - line-height: 0.75em; - vertical-align: -.0667em; } - -.fa-xs { - font-size: .75em; } - -.fa-sm { - font-size: .875em; } - -.fa-1x { - font-size: 1em; } - -.fa-2x { - font-size: 2em; } - -.fa-3x { - font-size: 3em; } - -.fa-4x { - font-size: 4em; } - -.fa-5x { - font-size: 5em; } - -.fa-6x { - font-size: 6em; } - -.fa-7x { - font-size: 7em; } - -.fa-8x { - font-size: 8em; } - -.fa-9x { - font-size: 9em; } - -.fa-10x { - font-size: 10em; } - -.fa-fw { - text-align: center; - width: 1.25em; } - -.fa-ul { - list-style-type: none; - margin-left: 2.5em; - padding-left: 0; } - .fa-ul > li { - position: relative; } - -.fa-li { - left: -2em; - position: absolute; - text-align: center; - width: 2em; - line-height: inherit; } - -.fa-border { - border: solid 0.08em #eee; - border-radius: .1em; - padding: .2em .25em .15em; } - -.fa-pull-left { - float: left; } - -.fa-pull-right { - float: right; } - -.fa.fa-pull-left, -.fas.fa-pull-left, -.far.fa-pull-left, -.fal.fa-pull-left, -.fab.fa-pull-left { - margin-right: .3em; } - -.fa.fa-pull-right, -.fas.fa-pull-right, -.far.fa-pull-right, -.fal.fa-pull-right, -.fab.fa-pull-right { - margin-left: .3em; } - -.fa-spin { - -webkit-animation: fa-spin 2s infinite linear; - animation: fa-spin 2s infinite linear; } - -.fa-pulse { - -webkit-animation: fa-spin 1s infinite steps(8); - animation: fa-spin 1s infinite steps(8); } - -@-webkit-keyframes fa-spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); } } - -@keyframes fa-spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); } } - -.fa-rotate-90 { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; - -webkit-transform: rotate(90deg); - transform: rotate(90deg); } - -.fa-rotate-180 { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; - -webkit-transform: rotate(180deg); - transform: rotate(180deg); } - -.fa-rotate-270 { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; - -webkit-transform: rotate(270deg); - transform: rotate(270deg); } - -.fa-flip-horizontal { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; - -webkit-transform: scale(-1, 1); - transform: scale(-1, 1); } - -.fa-flip-vertical { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; - -webkit-transform: scale(1, -1); - transform: scale(1, -1); } - -.fa-flip-both, .fa-flip-horizontal.fa-flip-vertical { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; - -webkit-transform: scale(-1, -1); - transform: scale(-1, -1); } - -:root .fa-rotate-90, -:root .fa-rotate-180, -:root .fa-rotate-270, -:root .fa-flip-horizontal, -:root .fa-flip-vertical, -:root .fa-flip-both { - -webkit-filter: none; - filter: none; } - -.fa-stack { - display: inline-block; - height: 2em; - line-height: 2em; - position: relative; - vertical-align: middle; - width: 2.5em; } - -.fa-stack-1x, -.fa-stack-2x { - left: 0; - position: absolute; - text-align: center; - width: 100%; } - -.fa-stack-1x { - line-height: inherit; } - -.fa-stack-2x { - font-size: 2em; } - -.fa-inverse { - color: #fff; } - -/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen -readers do not read off random characters that represent icons */ -.fa-500px:before { - content: "\f26e"; } - -.fa-accessible-icon:before { - content: "\f368"; } - -.fa-accusoft:before { - content: "\f369"; } - -.fa-acquisitions-incorporated:before { - content: "\f6af"; } - -.fa-ad:before { - content: "\f641"; } - -.fa-address-book:before { - content: "\f2b9"; } - -.fa-address-card:before { - content: "\f2bb"; } - -.fa-adjust:before { - content: "\f042"; } - -.fa-adn:before { - content: "\f170"; } - -.fa-adobe:before { - content: "\f778"; } - -.fa-adversal:before { - content: "\f36a"; } - -.fa-affiliatetheme:before { - content: "\f36b"; } - -.fa-air-freshener:before { - content: "\f5d0"; } - -.fa-airbnb:before { - content: "\f834"; } - -.fa-algolia:before { - content: "\f36c"; } - -.fa-align-center:before { - content: "\f037"; } - -.fa-align-justify:before { - content: "\f039"; } - -.fa-align-left:before { - content: "\f036"; } - -.fa-align-right:before { - content: "\f038"; } - -.fa-alipay:before { - content: "\f642"; } - -.fa-allergies:before { - content: "\f461"; } - -.fa-amazon:before { - content: "\f270"; } - -.fa-amazon-pay:before { - content: "\f42c"; } - -.fa-ambulance:before { - content: "\f0f9"; } - -.fa-american-sign-language-interpreting:before { - content: "\f2a3"; } - -.fa-amilia:before { - content: "\f36d"; } - -.fa-anchor:before { - content: "\f13d"; } - -.fa-android:before { - content: "\f17b"; } - -.fa-angellist:before { - content: "\f209"; } - -.fa-angle-double-down:before { - content: "\f103"; } - -.fa-angle-double-left:before { - content: "\f100"; } - -.fa-angle-double-right:before { - content: "\f101"; } - -.fa-angle-double-up:before { - content: "\f102"; } - -.fa-angle-down:before { - content: "\f107"; } - -.fa-angle-left:before { - content: "\f104"; } - -.fa-angle-right:before { - content: "\f105"; } - -.fa-angle-up:before { - content: "\f106"; } - -.fa-angry:before { - content: "\f556"; } - -.fa-angrycreative:before { - content: "\f36e"; } - -.fa-angular:before { - content: "\f420"; } - -.fa-ankh:before { - content: "\f644"; } - -.fa-app-store:before { - content: "\f36f"; } - -.fa-app-store-ios:before { - content: "\f370"; } - -.fa-apper:before { - content: "\f371"; } - -.fa-apple:before { - content: "\f179"; } - -.fa-apple-alt:before { - content: "\f5d1"; } - -.fa-apple-pay:before { - content: "\f415"; } - -.fa-archive:before { - content: "\f187"; } - -.fa-archway:before { - content: "\f557"; } - -.fa-arrow-alt-circle-down:before { - content: "\f358"; } - -.fa-arrow-alt-circle-left:before { - content: "\f359"; } - -.fa-arrow-alt-circle-right:before { - content: "\f35a"; } - -.fa-arrow-alt-circle-up:before { - content: "\f35b"; } - -.fa-arrow-circle-down:before { - content: "\f0ab"; } - -.fa-arrow-circle-left:before { - content: "\f0a8"; } - -.fa-arrow-circle-right:before { - content: "\f0a9"; } - -.fa-arrow-circle-up:before { - content: "\f0aa"; } - -.fa-arrow-down:before { - content: "\f063"; } - -.fa-arrow-left:before { - content: "\f060"; } - -.fa-arrow-right:before { - content: "\f061"; } - -.fa-arrow-up:before { - content: "\f062"; } - -.fa-arrows-alt:before { - content: "\f0b2"; } - -.fa-arrows-alt-h:before { - content: "\f337"; } - -.fa-arrows-alt-v:before { - content: "\f338"; } - -.fa-artstation:before { - content: "\f77a"; } - -.fa-assistive-listening-systems:before { - content: "\f2a2"; } - -.fa-asterisk:before { - content: "\f069"; } - -.fa-asymmetrik:before { - content: "\f372"; } - -.fa-at:before { - content: "\f1fa"; } - -.fa-atlas:before { - content: "\f558"; } - -.fa-atlassian:before { - content: "\f77b"; } - -.fa-atom:before { - content: "\f5d2"; } - -.fa-audible:before { - content: "\f373"; } - -.fa-audio-description:before { - content: "\f29e"; } - -.fa-autoprefixer:before { - content: "\f41c"; } - -.fa-avianex:before { - content: "\f374"; } - -.fa-aviato:before { - content: "\f421"; } - -.fa-award:before { - content: "\f559"; } - -.fa-aws:before { - content: "\f375"; } - -.fa-baby:before { - content: "\f77c"; } - -.fa-baby-carriage:before { - content: "\f77d"; } - -.fa-backspace:before { - content: "\f55a"; } - -.fa-backward:before { - content: "\f04a"; } - -.fa-bacon:before { - content: "\f7e5"; } - -.fa-bacteria:before { - content: "\e059"; } - -.fa-bacterium:before { - content: "\e05a"; } - -.fa-bahai:before { - content: "\f666"; } - -.fa-balance-scale:before { - content: "\f24e"; } - -.fa-balance-scale-left:before { - content: "\f515"; } - -.fa-balance-scale-right:before { - content: "\f516"; } - -.fa-ban:before { - content: "\f05e"; } - -.fa-band-aid:before { - content: "\f462"; } - -.fa-bandcamp:before { - content: "\f2d5"; } - -.fa-barcode:before { - content: "\f02a"; } - -.fa-bars:before { - content: "\f0c9"; } - -.fa-baseball-ball:before { - content: "\f433"; } - -.fa-basketball-ball:before { - content: "\f434"; } - -.fa-bath:before { - content: "\f2cd"; } - -.fa-battery-empty:before { - content: "\f244"; } - -.fa-battery-full:before { - content: "\f240"; } - -.fa-battery-half:before { - content: "\f242"; } - -.fa-battery-quarter:before { - content: "\f243"; } - -.fa-battery-three-quarters:before { - content: "\f241"; } - -.fa-battle-net:before { - content: "\f835"; } - -.fa-bed:before { - content: "\f236"; } - -.fa-beer:before { - content: "\f0fc"; } - -.fa-behance:before { - content: "\f1b4"; } - -.fa-behance-square:before { - content: "\f1b5"; } - -.fa-bell:before { - content: "\f0f3"; } - -.fa-bell-slash:before { - content: "\f1f6"; } - -.fa-bezier-curve:before { - content: "\f55b"; } - -.fa-bible:before { - content: "\f647"; } - -.fa-bicycle:before { - content: "\f206"; } - -.fa-biking:before { - content: "\f84a"; } - -.fa-bimobject:before { - content: "\f378"; } - -.fa-binoculars:before { - content: "\f1e5"; } - -.fa-biohazard:before { - content: "\f780"; } - -.fa-birthday-cake:before { - content: "\f1fd"; } - -.fa-bitbucket:before { - content: "\f171"; } - -.fa-bitcoin:before { - content: "\f379"; } - -.fa-bity:before { - content: "\f37a"; } - -.fa-black-tie:before { - content: "\f27e"; } - -.fa-blackberry:before { - content: "\f37b"; } - -.fa-blender:before { - content: "\f517"; } - -.fa-blender-phone:before { - content: "\f6b6"; } - -.fa-blind:before { - content: "\f29d"; } - -.fa-blog:before { - content: "\f781"; } - -.fa-blogger:before { - content: "\f37c"; } - -.fa-blogger-b:before { - content: "\f37d"; } - -.fa-bluetooth:before { - content: "\f293"; } - -.fa-bluetooth-b:before { - content: "\f294"; } - -.fa-bold:before { - content: "\f032"; } - -.fa-bolt:before { - content: "\f0e7"; } - -.fa-bomb:before { - content: "\f1e2"; } - -.fa-bone:before { - content: "\f5d7"; } - -.fa-bong:before { - content: "\f55c"; } - -.fa-book:before { - content: "\f02d"; } - -.fa-book-dead:before { - content: "\f6b7"; } - -.fa-book-medical:before { - content: "\f7e6"; } - -.fa-book-open:before { - content: "\f518"; } - -.fa-book-reader:before { - content: "\f5da"; } - -.fa-bookmark:before { - content: "\f02e"; } - -.fa-bootstrap:before { - content: "\f836"; } - -.fa-border-all:before { - content: "\f84c"; } - -.fa-border-none:before { - content: "\f850"; } - -.fa-border-style:before { - content: "\f853"; } - -.fa-bowling-ball:before { - content: "\f436"; } - -.fa-box:before { - content: "\f466"; } - -.fa-box-open:before { - content: "\f49e"; } - -.fa-box-tissue:before { - content: "\e05b"; } - -.fa-boxes:before { - content: "\f468"; } - -.fa-braille:before { - content: "\f2a1"; } - -.fa-brain:before { - content: "\f5dc"; } - -.fa-bread-slice:before { - content: "\f7ec"; } - -.fa-briefcase:before { - content: "\f0b1"; } - -.fa-briefcase-medical:before { - content: "\f469"; } - -.fa-broadcast-tower:before { - content: "\f519"; } - -.fa-broom:before { - content: "\f51a"; } - -.fa-brush:before { - content: "\f55d"; } - -.fa-btc:before { - content: "\f15a"; } - -.fa-buffer:before { - content: "\f837"; } - -.fa-bug:before { - content: "\f188"; } - -.fa-building:before { - content: "\f1ad"; } - -.fa-bullhorn:before { - content: "\f0a1"; } - -.fa-bullseye:before { - content: "\f140"; } - -.fa-burn:before { - content: "\f46a"; } - -.fa-buromobelexperte:before { - content: "\f37f"; } - -.fa-bus:before { - content: "\f207"; } - -.fa-bus-alt:before { - content: "\f55e"; } - -.fa-business-time:before { - content: "\f64a"; } - -.fa-buy-n-large:before { - content: "\f8a6"; } - -.fa-buysellads:before { - content: "\f20d"; } - -.fa-calculator:before { - content: "\f1ec"; } - -.fa-calendar:before { - content: "\f133"; } - -.fa-calendar-alt:before { - content: "\f073"; } - -.fa-calendar-check:before { - content: "\f274"; } - -.fa-calendar-day:before { - content: "\f783"; } - -.fa-calendar-minus:before { - content: "\f272"; } - -.fa-calendar-plus:before { - content: "\f271"; } - -.fa-calendar-times:before { - content: "\f273"; } - -.fa-calendar-week:before { - content: "\f784"; } - -.fa-camera:before { - content: "\f030"; } - -.fa-camera-retro:before { - content: "\f083"; } - -.fa-campground:before { - content: "\f6bb"; } - -.fa-canadian-maple-leaf:before { - content: "\f785"; } - -.fa-candy-cane:before { - content: "\f786"; } - -.fa-cannabis:before { - content: "\f55f"; } - -.fa-capsules:before { - content: "\f46b"; } - -.fa-car:before { - content: "\f1b9"; } - -.fa-car-alt:before { - content: "\f5de"; } - -.fa-car-battery:before { - content: "\f5df"; } - -.fa-car-crash:before { - content: "\f5e1"; } - -.fa-car-side:before { - content: "\f5e4"; } - -.fa-caravan:before { - content: "\f8ff"; } - -.fa-caret-down:before { - content: "\f0d7"; } - -.fa-caret-left:before { - content: "\f0d9"; } - -.fa-caret-right:before { - content: "\f0da"; } - -.fa-caret-square-down:before { - content: "\f150"; } - -.fa-caret-square-left:before { - content: "\f191"; } - -.fa-caret-square-right:before { - content: "\f152"; } - -.fa-caret-square-up:before { - content: "\f151"; } - -.fa-caret-up:before { - content: "\f0d8"; } - -.fa-carrot:before { - content: "\f787"; } - -.fa-cart-arrow-down:before { - content: "\f218"; } - -.fa-cart-plus:before { - content: "\f217"; } - -.fa-cash-register:before { - content: "\f788"; } - -.fa-cat:before { - content: "\f6be"; } - -.fa-cc-amazon-pay:before { - content: "\f42d"; } - -.fa-cc-amex:before { - content: "\f1f3"; } - -.fa-cc-apple-pay:before { - content: "\f416"; } - -.fa-cc-diners-club:before { - content: "\f24c"; } - -.fa-cc-discover:before { - content: "\f1f2"; } - -.fa-cc-jcb:before { - content: "\f24b"; } - -.fa-cc-mastercard:before { - content: "\f1f1"; } - -.fa-cc-paypal:before { - content: "\f1f4"; } - -.fa-cc-stripe:before { - content: "\f1f5"; } - -.fa-cc-visa:before { - content: "\f1f0"; } - -.fa-centercode:before { - content: "\f380"; } - -.fa-centos:before { - content: "\f789"; } - -.fa-certificate:before { - content: "\f0a3"; } - -.fa-chair:before { - content: "\f6c0"; } - -.fa-chalkboard:before { - content: "\f51b"; } - -.fa-chalkboard-teacher:before { - content: "\f51c"; } - -.fa-charging-station:before { - content: "\f5e7"; } - -.fa-chart-area:before { - content: "\f1fe"; } - -.fa-chart-bar:before { - content: "\f080"; } - -.fa-chart-line:before { - content: "\f201"; } - -.fa-chart-pie:before { - content: "\f200"; } - -.fa-check:before { - content: "\f00c"; } - -.fa-check-circle:before { - content: "\f058"; } - -.fa-check-double:before { - content: "\f560"; } - -.fa-check-square:before { - content: "\f14a"; } - -.fa-cheese:before { - content: "\f7ef"; } - -.fa-chess:before { - content: "\f439"; } - -.fa-chess-bishop:before { - content: "\f43a"; } - -.fa-chess-board:before { - content: "\f43c"; } - -.fa-chess-king:before { - content: "\f43f"; } - -.fa-chess-knight:before { - content: "\f441"; } - -.fa-chess-pawn:before { - content: "\f443"; } - -.fa-chess-queen:before { - content: "\f445"; } - -.fa-chess-rook:before { - content: "\f447"; } - -.fa-chevron-circle-down:before { - content: "\f13a"; } - -.fa-chevron-circle-left:before { - content: "\f137"; } - -.fa-chevron-circle-right:before { - content: "\f138"; } - -.fa-chevron-circle-up:before { - content: "\f139"; } - -.fa-chevron-down:before { - content: "\f078"; } - -.fa-chevron-left:before { - content: "\f053"; } - -.fa-chevron-right:before { - content: "\f054"; } - -.fa-chevron-up:before { - content: "\f077"; } - -.fa-child:before { - content: "\f1ae"; } - -.fa-chrome:before { - content: "\f268"; } - -.fa-chromecast:before { - content: "\f838"; } - -.fa-church:before { - content: "\f51d"; } - -.fa-circle:before { - content: "\f111"; } - -.fa-circle-notch:before { - content: "\f1ce"; } - -.fa-city:before { - content: "\f64f"; } - -.fa-clinic-medical:before { - content: "\f7f2"; } - -.fa-clipboard:before { - content: "\f328"; } - -.fa-clipboard-check:before { - content: "\f46c"; } - -.fa-clipboard-list:before { - content: "\f46d"; } - -.fa-clock:before { - content: "\f017"; } - -.fa-clone:before { - content: "\f24d"; } - -.fa-closed-captioning:before { - content: "\f20a"; } - -.fa-cloud:before { - content: "\f0c2"; } - -.fa-cloud-download-alt:before { - content: "\f381"; } - -.fa-cloud-meatball:before { - content: "\f73b"; } - -.fa-cloud-moon:before { - content: "\f6c3"; } - -.fa-cloud-moon-rain:before { - content: "\f73c"; } - -.fa-cloud-rain:before { - content: "\f73d"; } - -.fa-cloud-showers-heavy:before { - content: "\f740"; } - -.fa-cloud-sun:before { - content: "\f6c4"; } - -.fa-cloud-sun-rain:before { - content: "\f743"; } - -.fa-cloud-upload-alt:before { - content: "\f382"; } - -.fa-cloudscale:before { - content: "\f383"; } - -.fa-cloudsmith:before { - content: "\f384"; } - -.fa-cloudversify:before { - content: "\f385"; } - -.fa-cocktail:before { - content: "\f561"; } - -.fa-code:before { - content: "\f121"; } - -.fa-code-branch:before { - content: "\f126"; } - -.fa-codepen:before { - content: "\f1cb"; } - -.fa-codiepie:before { - content: "\f284"; } - -.fa-coffee:before { - content: "\f0f4"; } - -.fa-cog:before { - content: "\f013"; } - -.fa-cogs:before { - content: "\f085"; } - -.fa-coins:before { - content: "\f51e"; } - -.fa-columns:before { - content: "\f0db"; } - -.fa-comment:before { - content: "\f075"; } - -.fa-comment-alt:before { - content: "\f27a"; } - -.fa-comment-dollar:before { - content: "\f651"; } - -.fa-comment-dots:before { - content: "\f4ad"; } - -.fa-comment-medical:before { - content: "\f7f5"; } - -.fa-comment-slash:before { - content: "\f4b3"; } - -.fa-comments:before { - content: "\f086"; } - -.fa-comments-dollar:before { - content: "\f653"; } - -.fa-compact-disc:before { - content: "\f51f"; } - -.fa-compass:before { - content: "\f14e"; } - -.fa-compress:before { - content: "\f066"; } - -.fa-compress-alt:before { - content: "\f422"; } - -.fa-compress-arrows-alt:before { - content: "\f78c"; } - -.fa-concierge-bell:before { - content: "\f562"; } - -.fa-confluence:before { - content: "\f78d"; } - -.fa-connectdevelop:before { - content: "\f20e"; } - -.fa-contao:before { - content: "\f26d"; } - -.fa-cookie:before { - content: "\f563"; } - -.fa-cookie-bite:before { - content: "\f564"; } - -.fa-copy:before { - content: "\f0c5"; } - -.fa-copyright:before { - content: "\f1f9"; } - -.fa-cotton-bureau:before { - content: "\f89e"; } - -.fa-couch:before { - content: "\f4b8"; } - -.fa-cpanel:before { - content: "\f388"; } - -.fa-creative-commons:before { - content: "\f25e"; } - -.fa-creative-commons-by:before { - content: "\f4e7"; } - -.fa-creative-commons-nc:before { - content: "\f4e8"; } - -.fa-creative-commons-nc-eu:before { - content: "\f4e9"; } - -.fa-creative-commons-nc-jp:before { - content: "\f4ea"; } - -.fa-creative-commons-nd:before { - content: "\f4eb"; } - -.fa-creative-commons-pd:before { - content: "\f4ec"; } - -.fa-creative-commons-pd-alt:before { - content: "\f4ed"; } - -.fa-creative-commons-remix:before { - content: "\f4ee"; } - -.fa-creative-commons-sa:before { - content: "\f4ef"; } - -.fa-creative-commons-sampling:before { - content: "\f4f0"; } - -.fa-creative-commons-sampling-plus:before { - content: "\f4f1"; } - -.fa-creative-commons-share:before { - content: "\f4f2"; } - -.fa-creative-commons-zero:before { - content: "\f4f3"; } - -.fa-credit-card:before { - content: "\f09d"; } - -.fa-critical-role:before { - content: "\f6c9"; } - -.fa-crop:before { - content: "\f125"; } - -.fa-crop-alt:before { - content: "\f565"; } - -.fa-cross:before { - content: "\f654"; } - -.fa-crosshairs:before { - content: "\f05b"; } - -.fa-crow:before { - content: "\f520"; } - -.fa-crown:before { - content: "\f521"; } - -.fa-crutch:before { - content: "\f7f7"; } - -.fa-css3:before { - content: "\f13c"; } - -.fa-css3-alt:before { - content: "\f38b"; } - -.fa-cube:before { - content: "\f1b2"; } - -.fa-cubes:before { - content: "\f1b3"; } - -.fa-cut:before { - content: "\f0c4"; } - -.fa-cuttlefish:before { - content: "\f38c"; } - -.fa-d-and-d:before { - content: "\f38d"; } - -.fa-d-and-d-beyond:before { - content: "\f6ca"; } - -.fa-dailymotion:before { - content: "\e052"; } - -.fa-dashcube:before { - content: "\f210"; } - -.fa-database:before { - content: "\f1c0"; } - -.fa-deaf:before { - content: "\f2a4"; } - -.fa-deezer:before { - content: "\e077"; } - -.fa-delicious:before { - content: "\f1a5"; } - -.fa-democrat:before { - content: "\f747"; } - -.fa-deploydog:before { - content: "\f38e"; } - -.fa-deskpro:before { - content: "\f38f"; } - -.fa-desktop:before { - content: "\f108"; } - -.fa-dev:before { - content: "\f6cc"; } - -.fa-deviantart:before { - content: "\f1bd"; } - -.fa-dharmachakra:before { - content: "\f655"; } - -.fa-dhl:before { - content: "\f790"; } - -.fa-diagnoses:before { - content: "\f470"; } - -.fa-diaspora:before { - content: "\f791"; } - -.fa-dice:before { - content: "\f522"; } - -.fa-dice-d20:before { - content: "\f6cf"; } - -.fa-dice-d6:before { - content: "\f6d1"; } - -.fa-dice-five:before { - content: "\f523"; } - -.fa-dice-four:before { - content: "\f524"; } - -.fa-dice-one:before { - content: "\f525"; } - -.fa-dice-six:before { - content: "\f526"; } - -.fa-dice-three:before { - content: "\f527"; } - -.fa-dice-two:before { - content: "\f528"; } - -.fa-digg:before { - content: "\f1a6"; } - -.fa-digital-ocean:before { - content: "\f391"; } - -.fa-digital-tachograph:before { - content: "\f566"; } - -.fa-directions:before { - content: "\f5eb"; } - -.fa-discord:before { - content: "\f392"; } - -.fa-discourse:before { - content: "\f393"; } - -.fa-disease:before { - content: "\f7fa"; } - -.fa-divide:before { - content: "\f529"; } - -.fa-dizzy:before { - content: "\f567"; } - -.fa-dna:before { - content: "\f471"; } - -.fa-dochub:before { - content: "\f394"; } - -.fa-docker:before { - content: "\f395"; } - -.fa-dog:before { - content: "\f6d3"; } - -.fa-dollar-sign:before { - content: "\f155"; } - -.fa-dolly:before { - content: "\f472"; } - -.fa-dolly-flatbed:before { - content: "\f474"; } - -.fa-donate:before { - content: "\f4b9"; } - -.fa-door-closed:before { - content: "\f52a"; } - -.fa-door-open:before { - content: "\f52b"; } - -.fa-dot-circle:before { - content: "\f192"; } - -.fa-dove:before { - content: "\f4ba"; } - -.fa-download:before { - content: "\f019"; } - -.fa-draft2digital:before { - content: "\f396"; } - -.fa-drafting-compass:before { - content: "\f568"; } - -.fa-dragon:before { - content: "\f6d5"; } - -.fa-draw-polygon:before { - content: "\f5ee"; } - -.fa-dribbble:before { - content: "\f17d"; } - -.fa-dribbble-square:before { - content: "\f397"; } - -.fa-dropbox:before { - content: "\f16b"; } - -.fa-drum:before { - content: "\f569"; } - -.fa-drum-steelpan:before { - content: "\f56a"; } - -.fa-drumstick-bite:before { - content: "\f6d7"; } - -.fa-drupal:before { - content: "\f1a9"; } - -.fa-dumbbell:before { - content: "\f44b"; } - -.fa-dumpster:before { - content: "\f793"; } - -.fa-dumpster-fire:before { - content: "\f794"; } - -.fa-dungeon:before { - content: "\f6d9"; } - -.fa-dyalog:before { - content: "\f399"; } - -.fa-earlybirds:before { - content: "\f39a"; } - -.fa-ebay:before { - content: "\f4f4"; } - -.fa-edge:before { - content: "\f282"; } - -.fa-edge-legacy:before { - content: "\e078"; } - -.fa-edit:before { - content: "\f044"; } - -.fa-egg:before { - content: "\f7fb"; } - -.fa-eject:before { - content: "\f052"; } - -.fa-elementor:before { - content: "\f430"; } - -.fa-ellipsis-h:before { - content: "\f141"; } - -.fa-ellipsis-v:before { - content: "\f142"; } - -.fa-ello:before { - content: "\f5f1"; } - -.fa-ember:before { - content: "\f423"; } - -.fa-empire:before { - content: "\f1d1"; } - -.fa-envelope:before { - content: "\f0e0"; } - -.fa-envelope-open:before { - content: "\f2b6"; } - -.fa-envelope-open-text:before { - content: "\f658"; } - -.fa-envelope-square:before { - content: "\f199"; } - -.fa-envira:before { - content: "\f299"; } - -.fa-equals:before { - content: "\f52c"; } - -.fa-eraser:before { - content: "\f12d"; } - -.fa-erlang:before { - content: "\f39d"; } - -.fa-ethereum:before { - content: "\f42e"; } - -.fa-ethernet:before { - content: "\f796"; } - -.fa-etsy:before { - content: "\f2d7"; } - -.fa-euro-sign:before { - content: "\f153"; } - -.fa-evernote:before { - content: "\f839"; } - -.fa-exchange-alt:before { - content: "\f362"; } - -.fa-exclamation:before { - content: "\f12a"; } - -.fa-exclamation-circle:before { - content: "\f06a"; } - -.fa-exclamation-triangle:before { - content: "\f071"; } - -.fa-expand:before { - content: "\f065"; } - -.fa-expand-alt:before { - content: "\f424"; } - -.fa-expand-arrows-alt:before { - content: "\f31e"; } - -.fa-expeditedssl:before { - content: "\f23e"; } - -.fa-external-link-alt:before { - content: "\f35d"; } - -.fa-external-link-square-alt:before { - content: "\f360"; } - -.fa-eye:before { - content: "\f06e"; } - -.fa-eye-dropper:before { - content: "\f1fb"; } - -.fa-eye-slash:before { - content: "\f070"; } - -.fa-facebook:before { - content: "\f09a"; } - -.fa-facebook-f:before { - content: "\f39e"; } - -.fa-facebook-messenger:before { - content: "\f39f"; } - -.fa-facebook-square:before { - content: "\f082"; } - -.fa-fan:before { - content: "\f863"; } - -.fa-fantasy-flight-games:before { - content: "\f6dc"; } - -.fa-fast-backward:before { - content: "\f049"; } - -.fa-fast-forward:before { - content: "\f050"; } - -.fa-faucet:before { - content: "\e005"; } - -.fa-fax:before { - content: "\f1ac"; } - -.fa-feather:before { - content: "\f52d"; } - -.fa-feather-alt:before { - content: "\f56b"; } - -.fa-fedex:before { - content: "\f797"; } - -.fa-fedora:before { - content: "\f798"; } - -.fa-female:before { - content: "\f182"; } - -.fa-fighter-jet:before { - content: "\f0fb"; } - -.fa-figma:before { - content: "\f799"; } - -.fa-file:before { - content: "\f15b"; } - -.fa-file-alt:before { - content: "\f15c"; } - -.fa-file-archive:before { - content: "\f1c6"; } - -.fa-file-audio:before { - content: "\f1c7"; } - -.fa-file-code:before { - content: "\f1c9"; } - -.fa-file-contract:before { - content: "\f56c"; } - -.fa-file-csv:before { - content: "\f6dd"; } - -.fa-file-download:before { - content: "\f56d"; } - -.fa-file-excel:before { - content: "\f1c3"; } - -.fa-file-export:before { - content: "\f56e"; } - -.fa-file-image:before { - content: "\f1c5"; } - -.fa-file-import:before { - content: "\f56f"; } - -.fa-file-invoice:before { - content: "\f570"; } - -.fa-file-invoice-dollar:before { - content: "\f571"; } - -.fa-file-medical:before { - content: "\f477"; } - -.fa-file-medical-alt:before { - content: "\f478"; } - -.fa-file-pdf:before { - content: "\f1c1"; } - -.fa-file-powerpoint:before { - content: "\f1c4"; } - -.fa-file-prescription:before { - content: "\f572"; } - -.fa-file-signature:before { - content: "\f573"; } - -.fa-file-upload:before { - content: "\f574"; } - -.fa-file-video:before { - content: "\f1c8"; } - -.fa-file-word:before { - content: "\f1c2"; } - -.fa-fill:before { - content: "\f575"; } - -.fa-fill-drip:before { - content: "\f576"; } - -.fa-film:before { - content: "\f008"; } - -.fa-filter:before { - content: "\f0b0"; } - -.fa-fingerprint:before { - content: "\f577"; } - -.fa-fire:before { - content: "\f06d"; } - -.fa-fire-alt:before { - content: "\f7e4"; } - -.fa-fire-extinguisher:before { - content: "\f134"; } - -.fa-firefox:before { - content: "\f269"; } - -.fa-firefox-browser:before { - content: "\e007"; } - -.fa-first-aid:before { - content: "\f479"; } - -.fa-first-order:before { - content: "\f2b0"; } - -.fa-first-order-alt:before { - content: "\f50a"; } - -.fa-firstdraft:before { - content: "\f3a1"; } - -.fa-fish:before { - content: "\f578"; } - -.fa-fist-raised:before { - content: "\f6de"; } - -.fa-flag:before { - content: "\f024"; } - -.fa-flag-checkered:before { - content: "\f11e"; } - -.fa-flag-usa:before { - content: "\f74d"; } - -.fa-flask:before { - content: "\f0c3"; } - -.fa-flickr:before { - content: "\f16e"; } - -.fa-flipboard:before { - content: "\f44d"; } - -.fa-flushed:before { - content: "\f579"; } - -.fa-fly:before { - content: "\f417"; } - -.fa-folder:before { - content: "\f07b"; } - -.fa-folder-minus:before { - content: "\f65d"; } - -.fa-folder-open:before { - content: "\f07c"; } - -.fa-folder-plus:before { - content: "\f65e"; } - -.fa-font:before { - content: "\f031"; } - -.fa-font-awesome:before { - content: "\f2b4"; } - -.fa-font-awesome-alt:before { - content: "\f35c"; } - -.fa-font-awesome-flag:before { - content: "\f425"; } - -.fa-font-awesome-logo-full:before { - content: "\f4e6"; } - -.fa-fonticons:before { - content: "\f280"; } - -.fa-fonticons-fi:before { - content: "\f3a2"; } - -.fa-football-ball:before { - content: "\f44e"; } - -.fa-fort-awesome:before { - content: "\f286"; } - -.fa-fort-awesome-alt:before { - content: "\f3a3"; } - -.fa-forumbee:before { - content: "\f211"; } - -.fa-forward:before { - content: "\f04e"; } - -.fa-foursquare:before { - content: "\f180"; } - -.fa-free-code-camp:before { - content: "\f2c5"; } - -.fa-freebsd:before { - content: "\f3a4"; } - -.fa-frog:before { - content: "\f52e"; } - -.fa-frown:before { - content: "\f119"; } - -.fa-frown-open:before { - content: "\f57a"; } - -.fa-fulcrum:before { - content: "\f50b"; } - -.fa-funnel-dollar:before { - content: "\f662"; } - -.fa-futbol:before { - content: "\f1e3"; } - -.fa-galactic-republic:before { - content: "\f50c"; } - -.fa-galactic-senate:before { - content: "\f50d"; } - -.fa-gamepad:before { - content: "\f11b"; } - -.fa-gas-pump:before { - content: "\f52f"; } - -.fa-gavel:before { - content: "\f0e3"; } - -.fa-gem:before { - content: "\f3a5"; } - -.fa-genderless:before { - content: "\f22d"; } - -.fa-get-pocket:before { - content: "\f265"; } - -.fa-gg:before { - content: "\f260"; } - -.fa-gg-circle:before { - content: "\f261"; } - -.fa-ghost:before { - content: "\f6e2"; } - -.fa-gift:before { - content: "\f06b"; } - -.fa-gifts:before { - content: "\f79c"; } - -.fa-git:before { - content: "\f1d3"; } - -.fa-git-alt:before { - content: "\f841"; } - -.fa-git-square:before { - content: "\f1d2"; } - -.fa-github:before { - content: "\f09b"; } - -.fa-github-alt:before { - content: "\f113"; } - -.fa-github-square:before { - content: "\f092"; } - -.fa-gitkraken:before { - content: "\f3a6"; } - -.fa-gitlab:before { - content: "\f296"; } - -.fa-gitter:before { - content: "\f426"; } - -.fa-glass-cheers:before { - content: "\f79f"; } - -.fa-glass-martini:before { - content: "\f000"; } - -.fa-glass-martini-alt:before { - content: "\f57b"; } - -.fa-glass-whiskey:before { - content: "\f7a0"; } - -.fa-glasses:before { - content: "\f530"; } - -.fa-glide:before { - content: "\f2a5"; } - -.fa-glide-g:before { - content: "\f2a6"; } - -.fa-globe:before { - content: "\f0ac"; } - -.fa-globe-africa:before { - content: "\f57c"; } - -.fa-globe-americas:before { - content: "\f57d"; } - -.fa-globe-asia:before { - content: "\f57e"; } - -.fa-globe-europe:before { - content: "\f7a2"; } - -.fa-gofore:before { - content: "\f3a7"; } - -.fa-golf-ball:before { - content: "\f450"; } - -.fa-goodreads:before { - content: "\f3a8"; } - -.fa-goodreads-g:before { - content: "\f3a9"; } - -.fa-google:before { - content: "\f1a0"; } - -.fa-google-drive:before { - content: "\f3aa"; } - -.fa-google-pay:before { - content: "\e079"; } - -.fa-google-play:before { - content: "\f3ab"; } - -.fa-google-plus:before { - content: "\f2b3"; } - -.fa-google-plus-g:before { - content: "\f0d5"; } - -.fa-google-plus-square:before { - content: "\f0d4"; } - -.fa-google-wallet:before { - content: "\f1ee"; } - -.fa-gopuram:before { - content: "\f664"; } - -.fa-graduation-cap:before { - content: "\f19d"; } - -.fa-gratipay:before { - content: "\f184"; } - -.fa-grav:before { - content: "\f2d6"; } - -.fa-greater-than:before { - content: "\f531"; } - -.fa-greater-than-equal:before { - content: "\f532"; } - -.fa-grimace:before { - content: "\f57f"; } - -.fa-grin:before { - content: "\f580"; } - -.fa-grin-alt:before { - content: "\f581"; } - -.fa-grin-beam:before { - content: "\f582"; } - -.fa-grin-beam-sweat:before { - content: "\f583"; } - -.fa-grin-hearts:before { - content: "\f584"; } - -.fa-grin-squint:before { - content: "\f585"; } - -.fa-grin-squint-tears:before { - content: "\f586"; } - -.fa-grin-stars:before { - content: "\f587"; } - -.fa-grin-tears:before { - content: "\f588"; } - -.fa-grin-tongue:before { - content: "\f589"; } - -.fa-grin-tongue-squint:before { - content: "\f58a"; } - -.fa-grin-tongue-wink:before { - content: "\f58b"; } - -.fa-grin-wink:before { - content: "\f58c"; } - -.fa-grip-horizontal:before { - content: "\f58d"; } - -.fa-grip-lines:before { - content: "\f7a4"; } - -.fa-grip-lines-vertical:before { - content: "\f7a5"; } - -.fa-grip-vertical:before { - content: "\f58e"; } - -.fa-gripfire:before { - content: "\f3ac"; } - -.fa-grunt:before { - content: "\f3ad"; } - -.fa-guitar:before { - content: "\f7a6"; } - -.fa-gulp:before { - content: "\f3ae"; } - -.fa-h-square:before { - content: "\f0fd"; } - -.fa-hacker-news:before { - content: "\f1d4"; } - -.fa-hacker-news-square:before { - content: "\f3af"; } - -.fa-hackerrank:before { - content: "\f5f7"; } - -.fa-hamburger:before { - content: "\f805"; } - -.fa-hammer:before { - content: "\f6e3"; } - -.fa-hamsa:before { - content: "\f665"; } - -.fa-hand-holding:before { - content: "\f4bd"; } - -.fa-hand-holding-heart:before { - content: "\f4be"; } - -.fa-hand-holding-medical:before { - content: "\e05c"; } - -.fa-hand-holding-usd:before { - content: "\f4c0"; } - -.fa-hand-holding-water:before { - content: "\f4c1"; } - -.fa-hand-lizard:before { - content: "\f258"; } - -.fa-hand-middle-finger:before { - content: "\f806"; } - -.fa-hand-paper:before { - content: "\f256"; } - -.fa-hand-peace:before { - content: "\f25b"; } - -.fa-hand-point-down:before { - content: "\f0a7"; } - -.fa-hand-point-left:before { - content: "\f0a5"; } - -.fa-hand-point-right:before { - content: "\f0a4"; } - -.fa-hand-point-up:before { - content: "\f0a6"; } - -.fa-hand-pointer:before { - content: "\f25a"; } - -.fa-hand-rock:before { - content: "\f255"; } - -.fa-hand-scissors:before { - content: "\f257"; } - -.fa-hand-sparkles:before { - content: "\e05d"; } - -.fa-hand-spock:before { - content: "\f259"; } - -.fa-hands:before { - content: "\f4c2"; } - -.fa-hands-helping:before { - content: "\f4c4"; } - -.fa-hands-wash:before { - content: "\e05e"; } - -.fa-handshake:before { - content: "\f2b5"; } - -.fa-handshake-alt-slash:before { - content: "\e05f"; } - -.fa-handshake-slash:before { - content: "\e060"; } - -.fa-hanukiah:before { - content: "\f6e6"; } - -.fa-hard-hat:before { - content: "\f807"; } - -.fa-hashtag:before { - content: "\f292"; } - -.fa-hat-cowboy:before { - content: "\f8c0"; } - -.fa-hat-cowboy-side:before { - content: "\f8c1"; } - -.fa-hat-wizard:before { - content: "\f6e8"; } - -.fa-hdd:before { - content: "\f0a0"; } - -.fa-head-side-cough:before { - content: "\e061"; } - -.fa-head-side-cough-slash:before { - content: "\e062"; } - -.fa-head-side-mask:before { - content: "\e063"; } - -.fa-head-side-virus:before { - content: "\e064"; } - -.fa-heading:before { - content: "\f1dc"; } - -.fa-headphones:before { - content: "\f025"; } - -.fa-headphones-alt:before { - content: "\f58f"; } - -.fa-headset:before { - content: "\f590"; } - -.fa-heart:before { - content: "\f004"; } - -.fa-heart-broken:before { - content: "\f7a9"; } - -.fa-heartbeat:before { - content: "\f21e"; } - -.fa-helicopter:before { - content: "\f533"; } - -.fa-highlighter:before { - content: "\f591"; } - -.fa-hiking:before { - content: "\f6ec"; } - -.fa-hippo:before { - content: "\f6ed"; } - -.fa-hips:before { - content: "\f452"; } - -.fa-hire-a-helper:before { - content: "\f3b0"; } - -.fa-history:before { - content: "\f1da"; } - -.fa-hockey-puck:before { - content: "\f453"; } - -.fa-holly-berry:before { - content: "\f7aa"; } - -.fa-home:before { - content: "\f015"; } - -.fa-hooli:before { - content: "\f427"; } - -.fa-hornbill:before { - content: "\f592"; } - -.fa-horse:before { - content: "\f6f0"; } - -.fa-horse-head:before { - content: "\f7ab"; } - -.fa-hospital:before { - content: "\f0f8"; } - -.fa-hospital-alt:before { - content: "\f47d"; } - -.fa-hospital-symbol:before { - content: "\f47e"; } - -.fa-hospital-user:before { - content: "\f80d"; } - -.fa-hot-tub:before { - content: "\f593"; } - -.fa-hotdog:before { - content: "\f80f"; } - -.fa-hotel:before { - content: "\f594"; } - -.fa-hotjar:before { - content: "\f3b1"; } - -.fa-hourglass:before { - content: "\f254"; } - -.fa-hourglass-end:before { - content: "\f253"; } - -.fa-hourglass-half:before { - content: "\f252"; } - -.fa-hourglass-start:before { - content: "\f251"; } - -.fa-house-damage:before { - content: "\f6f1"; } - -.fa-house-user:before { - content: "\e065"; } - -.fa-houzz:before { - content: "\f27c"; } - -.fa-hryvnia:before { - content: "\f6f2"; } - -.fa-html5:before { - content: "\f13b"; } - -.fa-hubspot:before { - content: "\f3b2"; } - -.fa-i-cursor:before { - content: "\f246"; } - -.fa-ice-cream:before { - content: "\f810"; } - -.fa-icicles:before { - content: "\f7ad"; } - -.fa-icons:before { - content: "\f86d"; } - -.fa-id-badge:before { - content: "\f2c1"; } - -.fa-id-card:before { - content: "\f2c2"; } - -.fa-id-card-alt:before { - content: "\f47f"; } - -.fa-ideal:before { - content: "\e013"; } - -.fa-igloo:before { - content: "\f7ae"; } - -.fa-image:before { - content: "\f03e"; } - -.fa-images:before { - content: "\f302"; } - -.fa-imdb:before { - content: "\f2d8"; } - -.fa-inbox:before { - content: "\f01c"; } - -.fa-indent:before { - content: "\f03c"; } - -.fa-industry:before { - content: "\f275"; } - -.fa-infinity:before { - content: "\f534"; } - -.fa-info:before { - content: "\f129"; } - -.fa-info-circle:before { - content: "\f05a"; } - -.fa-instagram:before { - content: "\f16d"; } - -.fa-instagram-square:before { - content: "\e055"; } - -.fa-intercom:before { - content: "\f7af"; } - -.fa-internet-explorer:before { - content: "\f26b"; } - -.fa-invision:before { - content: "\f7b0"; } - -.fa-ioxhost:before { - content: "\f208"; } - -.fa-italic:before { - content: "\f033"; } - -.fa-itch-io:before { - content: "\f83a"; } - -.fa-itunes:before { - content: "\f3b4"; } - -.fa-itunes-note:before { - content: "\f3b5"; } - -.fa-java:before { - content: "\f4e4"; } - -.fa-jedi:before { - content: "\f669"; } - -.fa-jedi-order:before { - content: "\f50e"; } - -.fa-jenkins:before { - content: "\f3b6"; } - -.fa-jira:before { - content: "\f7b1"; } - -.fa-joget:before { - content: "\f3b7"; } - -.fa-joint:before { - content: "\f595"; } - -.fa-joomla:before { - content: "\f1aa"; } - -.fa-journal-whills:before { - content: "\f66a"; } - -.fa-js:before { - content: "\f3b8"; } - -.fa-js-square:before { - content: "\f3b9"; } - -.fa-jsfiddle:before { - content: "\f1cc"; } - -.fa-kaaba:before { - content: "\f66b"; } - -.fa-kaggle:before { - content: "\f5fa"; } - -.fa-key:before { - content: "\f084"; } - -.fa-keybase:before { - content: "\f4f5"; } - -.fa-keyboard:before { - content: "\f11c"; } - -.fa-keycdn:before { - content: "\f3ba"; } - -.fa-khanda:before { - content: "\f66d"; } - -.fa-kickstarter:before { - content: "\f3bb"; } - -.fa-kickstarter-k:before { - content: "\f3bc"; } - -.fa-kiss:before { - content: "\f596"; } - -.fa-kiss-beam:before { - content: "\f597"; } - -.fa-kiss-wink-heart:before { - content: "\f598"; } - -.fa-kiwi-bird:before { - content: "\f535"; } - -.fa-korvue:before { - content: "\f42f"; } - -.fa-landmark:before { - content: "\f66f"; } - -.fa-language:before { - content: "\f1ab"; } - -.fa-laptop:before { - content: "\f109"; } - -.fa-laptop-code:before { - content: "\f5fc"; } - -.fa-laptop-house:before { - content: "\e066"; } - -.fa-laptop-medical:before { - content: "\f812"; } - -.fa-laravel:before { - content: "\f3bd"; } - -.fa-lastfm:before { - content: "\f202"; } - -.fa-lastfm-square:before { - content: "\f203"; } - -.fa-laugh:before { - content: "\f599"; } - -.fa-laugh-beam:before { - content: "\f59a"; } - -.fa-laugh-squint:before { - content: "\f59b"; } - -.fa-laugh-wink:before { - content: "\f59c"; } - -.fa-layer-group:before { - content: "\f5fd"; } - -.fa-leaf:before { - content: "\f06c"; } - -.fa-leanpub:before { - content: "\f212"; } - -.fa-lemon:before { - content: "\f094"; } - -.fa-less:before { - content: "\f41d"; } - -.fa-less-than:before { - content: "\f536"; } - -.fa-less-than-equal:before { - content: "\f537"; } - -.fa-level-down-alt:before { - content: "\f3be"; } - -.fa-level-up-alt:before { - content: "\f3bf"; } - -.fa-life-ring:before { - content: "\f1cd"; } - -.fa-lightbulb:before { - content: "\f0eb"; } - -.fa-line:before { - content: "\f3c0"; } - -.fa-link:before { - content: "\f0c1"; } - -.fa-linkedin:before { - content: "\f08c"; } - -.fa-linkedin-in:before { - content: "\f0e1"; } - -.fa-linode:before { - content: "\f2b8"; } - -.fa-linux:before { - content: "\f17c"; } - -.fa-lira-sign:before { - content: "\f195"; } - -.fa-list:before { - content: "\f03a"; } - -.fa-list-alt:before { - content: "\f022"; } - -.fa-list-ol:before { - content: "\f0cb"; } - -.fa-list-ul:before { - content: "\f0ca"; } - -.fa-location-arrow:before { - content: "\f124"; } - -.fa-lock:before { - content: "\f023"; } - -.fa-lock-open:before { - content: "\f3c1"; } - -.fa-long-arrow-alt-down:before { - content: "\f309"; } - -.fa-long-arrow-alt-left:before { - content: "\f30a"; } - -.fa-long-arrow-alt-right:before { - content: "\f30b"; } - -.fa-long-arrow-alt-up:before { - content: "\f30c"; } - -.fa-low-vision:before { - content: "\f2a8"; } - -.fa-luggage-cart:before { - content: "\f59d"; } - -.fa-lungs:before { - content: "\f604"; } - -.fa-lungs-virus:before { - content: "\e067"; } - -.fa-lyft:before { - content: "\f3c3"; } - -.fa-magento:before { - content: "\f3c4"; } - -.fa-magic:before { - content: "\f0d0"; } - -.fa-magnet:before { - content: "\f076"; } - -.fa-mail-bulk:before { - content: "\f674"; } - -.fa-mailchimp:before { - content: "\f59e"; } - -.fa-male:before { - content: "\f183"; } - -.fa-mandalorian:before { - content: "\f50f"; } - -.fa-map:before { - content: "\f279"; } - -.fa-map-marked:before { - content: "\f59f"; } - -.fa-map-marked-alt:before { - content: "\f5a0"; } - -.fa-map-marker:before { - content: "\f041"; } - -.fa-map-marker-alt:before { - content: "\f3c5"; } - -.fa-map-pin:before { - content: "\f276"; } - -.fa-map-signs:before { - content: "\f277"; } - -.fa-markdown:before { - content: "\f60f"; } - -.fa-marker:before { - content: "\f5a1"; } - -.fa-mars:before { - content: "\f222"; } - -.fa-mars-double:before { - content: "\f227"; } - -.fa-mars-stroke:before { - content: "\f229"; } - -.fa-mars-stroke-h:before { - content: "\f22b"; } - -.fa-mars-stroke-v:before { - content: "\f22a"; } - -.fa-mask:before { - content: "\f6fa"; } - -.fa-mastodon:before { - content: "\f4f6"; } - -.fa-maxcdn:before { - content: "\f136"; } - -.fa-mdb:before { - content: "\f8ca"; } - -.fa-medal:before { - content: "\f5a2"; } - -.fa-medapps:before { - content: "\f3c6"; } - -.fa-medium:before { - content: "\f23a"; } - -.fa-medium-m:before { - content: "\f3c7"; } - -.fa-medkit:before { - content: "\f0fa"; } - -.fa-medrt:before { - content: "\f3c8"; } - -.fa-meetup:before { - content: "\f2e0"; } - -.fa-megaport:before { - content: "\f5a3"; } - -.fa-meh:before { - content: "\f11a"; } - -.fa-meh-blank:before { - content: "\f5a4"; } - -.fa-meh-rolling-eyes:before { - content: "\f5a5"; } - -.fa-memory:before { - content: "\f538"; } - -.fa-mendeley:before { - content: "\f7b3"; } - -.fa-menorah:before { - content: "\f676"; } - -.fa-mercury:before { - content: "\f223"; } - -.fa-meteor:before { - content: "\f753"; } - -.fa-microblog:before { - content: "\e01a"; } - -.fa-microchip:before { - content: "\f2db"; } - -.fa-microphone:before { - content: "\f130"; } - -.fa-microphone-alt:before { - content: "\f3c9"; } - -.fa-microphone-alt-slash:before { - content: "\f539"; } - -.fa-microphone-slash:before { - content: "\f131"; } - -.fa-microscope:before { - content: "\f610"; } - -.fa-microsoft:before { - content: "\f3ca"; } - -.fa-minus:before { - content: "\f068"; } - -.fa-minus-circle:before { - content: "\f056"; } - -.fa-minus-square:before { - content: "\f146"; } - -.fa-mitten:before { - content: "\f7b5"; } - -.fa-mix:before { - content: "\f3cb"; } - -.fa-mixcloud:before { - content: "\f289"; } - -.fa-mixer:before { - content: "\e056"; } - -.fa-mizuni:before { - content: "\f3cc"; } - -.fa-mobile:before { - content: "\f10b"; } - -.fa-mobile-alt:before { - content: "\f3cd"; } - -.fa-modx:before { - content: "\f285"; } - -.fa-monero:before { - content: "\f3d0"; } - -.fa-money-bill:before { - content: "\f0d6"; } - -.fa-money-bill-alt:before { - content: "\f3d1"; } - -.fa-money-bill-wave:before { - content: "\f53a"; } - -.fa-money-bill-wave-alt:before { - content: "\f53b"; } - -.fa-money-check:before { - content: "\f53c"; } - -.fa-money-check-alt:before { - content: "\f53d"; } - -.fa-monument:before { - content: "\f5a6"; } - -.fa-moon:before { - content: "\f186"; } - -.fa-mortar-pestle:before { - content: "\f5a7"; } - -.fa-mosque:before { - content: "\f678"; } - -.fa-motorcycle:before { - content: "\f21c"; } - -.fa-mountain:before { - content: "\f6fc"; } - -.fa-mouse:before { - content: "\f8cc"; } - -.fa-mouse-pointer:before { - content: "\f245"; } - -.fa-mug-hot:before { - content: "\f7b6"; } - -.fa-music:before { - content: "\f001"; } - -.fa-napster:before { - content: "\f3d2"; } - -.fa-neos:before { - content: "\f612"; } - -.fa-network-wired:before { - content: "\f6ff"; } - -.fa-neuter:before { - content: "\f22c"; } - -.fa-newspaper:before { - content: "\f1ea"; } - -.fa-nimblr:before { - content: "\f5a8"; } - -.fa-node:before { - content: "\f419"; } - -.fa-node-js:before { - content: "\f3d3"; } - -.fa-not-equal:before { - content: "\f53e"; } - -.fa-notes-medical:before { - content: "\f481"; } - -.fa-npm:before { - content: "\f3d4"; } - -.fa-ns8:before { - content: "\f3d5"; } - -.fa-nutritionix:before { - content: "\f3d6"; } - -.fa-object-group:before { - content: "\f247"; } - -.fa-object-ungroup:before { - content: "\f248"; } - -.fa-odnoklassniki:before { - content: "\f263"; } - -.fa-odnoklassniki-square:before { - content: "\f264"; } - -.fa-oil-can:before { - content: "\f613"; } - -.fa-old-republic:before { - content: "\f510"; } - -.fa-om:before { - content: "\f679"; } - -.fa-opencart:before { - content: "\f23d"; } - -.fa-openid:before { - content: "\f19b"; } - -.fa-opera:before { - content: "\f26a"; } - -.fa-optin-monster:before { - content: "\f23c"; } - -.fa-orcid:before { - content: "\f8d2"; } - -.fa-osi:before { - content: "\f41a"; } - -.fa-otter:before { - content: "\f700"; } - -.fa-outdent:before { - content: "\f03b"; } - -.fa-page4:before { - content: "\f3d7"; } - -.fa-pagelines:before { - content: "\f18c"; } - -.fa-pager:before { - content: "\f815"; } - -.fa-paint-brush:before { - content: "\f1fc"; } - -.fa-paint-roller:before { - content: "\f5aa"; } - -.fa-palette:before { - content: "\f53f"; } - -.fa-palfed:before { - content: "\f3d8"; } - -.fa-pallet:before { - content: "\f482"; } - -.fa-paper-plane:before { - content: "\f1d8"; } - -.fa-paperclip:before { - content: "\f0c6"; } - -.fa-parachute-box:before { - content: "\f4cd"; } - -.fa-paragraph:before { - content: "\f1dd"; } - -.fa-parking:before { - content: "\f540"; } - -.fa-passport:before { - content: "\f5ab"; } - -.fa-pastafarianism:before { - content: "\f67b"; } - -.fa-paste:before { - content: "\f0ea"; } - -.fa-patreon:before { - content: "\f3d9"; } - -.fa-pause:before { - content: "\f04c"; } - -.fa-pause-circle:before { - content: "\f28b"; } - -.fa-paw:before { - content: "\f1b0"; } - -.fa-paypal:before { - content: "\f1ed"; } - -.fa-peace:before { - content: "\f67c"; } - -.fa-pen:before { - content: "\f304"; } - -.fa-pen-alt:before { - content: "\f305"; } - -.fa-pen-fancy:before { - content: "\f5ac"; } - -.fa-pen-nib:before { - content: "\f5ad"; } - -.fa-pen-square:before { - content: "\f14b"; } - -.fa-pencil-alt:before { - content: "\f303"; } - -.fa-pencil-ruler:before { - content: "\f5ae"; } - -.fa-penny-arcade:before { - content: "\f704"; } - -.fa-people-arrows:before { - content: "\e068"; } - -.fa-people-carry:before { - content: "\f4ce"; } - -.fa-pepper-hot:before { - content: "\f816"; } - -.fa-percent:before { - content: "\f295"; } - -.fa-percentage:before { - content: "\f541"; } - -.fa-periscope:before { - content: "\f3da"; } - -.fa-person-booth:before { - content: "\f756"; } - -.fa-phabricator:before { - content: "\f3db"; } - -.fa-phoenix-framework:before { - content: "\f3dc"; } - -.fa-phoenix-squadron:before { - content: "\f511"; } - -.fa-phone:before { - content: "\f095"; } - -.fa-phone-alt:before { - content: "\f879"; } - -.fa-phone-slash:before { - content: "\f3dd"; } - -.fa-phone-square:before { - content: "\f098"; } - -.fa-phone-square-alt:before { - content: "\f87b"; } - -.fa-phone-volume:before { - content: "\f2a0"; } - -.fa-photo-video:before { - content: "\f87c"; } - -.fa-php:before { - content: "\f457"; } - -.fa-pied-piper:before { - content: "\f2ae"; } - -.fa-pied-piper-alt:before { - content: "\f1a8"; } - -.fa-pied-piper-hat:before { - content: "\f4e5"; } - -.fa-pied-piper-pp:before { - content: "\f1a7"; } - -.fa-pied-piper-square:before { - content: "\e01e"; } - -.fa-piggy-bank:before { - content: "\f4d3"; } - -.fa-pills:before { - content: "\f484"; } - -.fa-pinterest:before { - content: "\f0d2"; } - -.fa-pinterest-p:before { - content: "\f231"; } - -.fa-pinterest-square:before { - content: "\f0d3"; } - -.fa-pizza-slice:before { - content: "\f818"; } - -.fa-place-of-worship:before { - content: "\f67f"; } - -.fa-plane:before { - content: "\f072"; } - -.fa-plane-arrival:before { - content: "\f5af"; } - -.fa-plane-departure:before { - content: "\f5b0"; } - -.fa-plane-slash:before { - content: "\e069"; } - -.fa-play:before { - content: "\f04b"; } - -.fa-play-circle:before { - content: "\f144"; } - -.fa-playstation:before { - content: "\f3df"; } - -.fa-plug:before { - content: "\f1e6"; } - -.fa-plus:before { - content: "\f067"; } - -.fa-plus-circle:before { - content: "\f055"; } - -.fa-plus-square:before { - content: "\f0fe"; } - -.fa-podcast:before { - content: "\f2ce"; } - -.fa-poll:before { - content: "\f681"; } - -.fa-poll-h:before { - content: "\f682"; } - -.fa-poo:before { - content: "\f2fe"; } - -.fa-poo-storm:before { - content: "\f75a"; } - -.fa-poop:before { - content: "\f619"; } - -.fa-portrait:before { - content: "\f3e0"; } - -.fa-pound-sign:before { - content: "\f154"; } - -.fa-power-off:before { - content: "\f011"; } - -.fa-pray:before { - content: "\f683"; } - -.fa-praying-hands:before { - content: "\f684"; } - -.fa-prescription:before { - content: "\f5b1"; } - -.fa-prescription-bottle:before { - content: "\f485"; } - -.fa-prescription-bottle-alt:before { - content: "\f486"; } - -.fa-print:before { - content: "\f02f"; } - -.fa-procedures:before { - content: "\f487"; } - -.fa-product-hunt:before { - content: "\f288"; } - -.fa-project-diagram:before { - content: "\f542"; } - -.fa-pump-medical:before { - content: "\e06a"; } - -.fa-pump-soap:before { - content: "\e06b"; } - -.fa-pushed:before { - content: "\f3e1"; } - -.fa-puzzle-piece:before { - content: "\f12e"; } - -.fa-python:before { - content: "\f3e2"; } - -.fa-qq:before { - content: "\f1d6"; } - -.fa-qrcode:before { - content: "\f029"; } - -.fa-question:before { - content: "\f128"; } - -.fa-question-circle:before { - content: "\f059"; } - -.fa-quidditch:before { - content: "\f458"; } - -.fa-quinscape:before { - content: "\f459"; } - -.fa-quora:before { - content: "\f2c4"; } - -.fa-quote-left:before { - content: "\f10d"; } - -.fa-quote-right:before { - content: "\f10e"; } - -.fa-quran:before { - content: "\f687"; } - -.fa-r-project:before { - content: "\f4f7"; } - -.fa-radiation:before { - content: "\f7b9"; } - -.fa-radiation-alt:before { - content: "\f7ba"; } - -.fa-rainbow:before { - content: "\f75b"; } - -.fa-random:before { - content: "\f074"; } - -.fa-raspberry-pi:before { - content: "\f7bb"; } - -.fa-ravelry:before { - content: "\f2d9"; } - -.fa-react:before { - content: "\f41b"; } - -.fa-reacteurope:before { - content: "\f75d"; } - -.fa-readme:before { - content: "\f4d5"; } - -.fa-rebel:before { - content: "\f1d0"; } - -.fa-receipt:before { - content: "\f543"; } - -.fa-record-vinyl:before { - content: "\f8d9"; } - -.fa-recycle:before { - content: "\f1b8"; } - -.fa-red-river:before { - content: "\f3e3"; } - -.fa-reddit:before { - content: "\f1a1"; } - -.fa-reddit-alien:before { - content: "\f281"; } - -.fa-reddit-square:before { - content: "\f1a2"; } - -.fa-redhat:before { - content: "\f7bc"; } - -.fa-redo:before { - content: "\f01e"; } - -.fa-redo-alt:before { - content: "\f2f9"; } - -.fa-registered:before { - content: "\f25d"; } - -.fa-remove-format:before { - content: "\f87d"; } - -.fa-renren:before { - content: "\f18b"; } - -.fa-reply:before { - content: "\f3e5"; } - -.fa-reply-all:before { - content: "\f122"; } - -.fa-replyd:before { - content: "\f3e6"; } - -.fa-republican:before { - content: "\f75e"; } - -.fa-researchgate:before { - content: "\f4f8"; } - -.fa-resolving:before { - content: "\f3e7"; } - -.fa-restroom:before { - content: "\f7bd"; } - -.fa-retweet:before { - content: "\f079"; } - -.fa-rev:before { - content: "\f5b2"; } - -.fa-ribbon:before { - content: "\f4d6"; } - -.fa-ring:before { - content: "\f70b"; } - -.fa-road:before { - content: "\f018"; } - -.fa-robot:before { - content: "\f544"; } - -.fa-rocket:before { - content: "\f135"; } - -.fa-rocketchat:before { - content: "\f3e8"; } - -.fa-rockrms:before { - content: "\f3e9"; } - -.fa-route:before { - content: "\f4d7"; } - -.fa-rss:before { - content: "\f09e"; } - -.fa-rss-square:before { - content: "\f143"; } - -.fa-ruble-sign:before { - content: "\f158"; } - -.fa-ruler:before { - content: "\f545"; } - -.fa-ruler-combined:before { - content: "\f546"; } - -.fa-ruler-horizontal:before { - content: "\f547"; } - -.fa-ruler-vertical:before { - content: "\f548"; } - -.fa-running:before { - content: "\f70c"; } - -.fa-rupee-sign:before { - content: "\f156"; } - -.fa-rust:before { - content: "\e07a"; } - -.fa-sad-cry:before { - content: "\f5b3"; } - -.fa-sad-tear:before { - content: "\f5b4"; } - -.fa-safari:before { - content: "\f267"; } - -.fa-salesforce:before { - content: "\f83b"; } - -.fa-sass:before { - content: "\f41e"; } - -.fa-satellite:before { - content: "\f7bf"; } - -.fa-satellite-dish:before { - content: "\f7c0"; } - -.fa-save:before { - content: "\f0c7"; } - -.fa-schlix:before { - content: "\f3ea"; } - -.fa-school:before { - content: "\f549"; } - -.fa-screwdriver:before { - content: "\f54a"; } - -.fa-scribd:before { - content: "\f28a"; } - -.fa-scroll:before { - content: "\f70e"; } - -.fa-sd-card:before { - content: "\f7c2"; } - -.fa-search:before { - content: "\f002"; } - -.fa-search-dollar:before { - content: "\f688"; } - -.fa-search-location:before { - content: "\f689"; } - -.fa-search-minus:before { - content: "\f010"; } - -.fa-search-plus:before { - content: "\f00e"; } - -.fa-searchengin:before { - content: "\f3eb"; } - -.fa-seedling:before { - content: "\f4d8"; } - -.fa-sellcast:before { - content: "\f2da"; } - -.fa-sellsy:before { - content: "\f213"; } - -.fa-server:before { - content: "\f233"; } - -.fa-servicestack:before { - content: "\f3ec"; } - -.fa-shapes:before { - content: "\f61f"; } - -.fa-share:before { - content: "\f064"; } - -.fa-share-alt:before { - content: "\f1e0"; } - -.fa-share-alt-square:before { - content: "\f1e1"; } - -.fa-share-square:before { - content: "\f14d"; } - -.fa-shekel-sign:before { - content: "\f20b"; } - -.fa-shield-alt:before { - content: "\f3ed"; } - -.fa-shield-virus:before { - content: "\e06c"; } - -.fa-ship:before { - content: "\f21a"; } - -.fa-shipping-fast:before { - content: "\f48b"; } - -.fa-shirtsinbulk:before { - content: "\f214"; } - -.fa-shoe-prints:before { - content: "\f54b"; } - -.fa-shopify:before { - content: "\e057"; } - -.fa-shopping-bag:before { - content: "\f290"; } - -.fa-shopping-basket:before { - content: "\f291"; } - -.fa-shopping-cart:before { - content: "\f07a"; } - -.fa-shopware:before { - content: "\f5b5"; } - -.fa-shower:before { - content: "\f2cc"; } - -.fa-shuttle-van:before { - content: "\f5b6"; } - -.fa-sign:before { - content: "\f4d9"; } - -.fa-sign-in-alt:before { - content: "\f2f6"; } - -.fa-sign-language:before { - content: "\f2a7"; } - -.fa-sign-out-alt:before { - content: "\f2f5"; } - -.fa-signal:before { - content: "\f012"; } - -.fa-signature:before { - content: "\f5b7"; } - -.fa-sim-card:before { - content: "\f7c4"; } - -.fa-simplybuilt:before { - content: "\f215"; } - -.fa-sink:before { - content: "\e06d"; } - -.fa-sistrix:before { - content: "\f3ee"; } - -.fa-sitemap:before { - content: "\f0e8"; } - -.fa-sith:before { - content: "\f512"; } - -.fa-skating:before { - content: "\f7c5"; } - -.fa-sketch:before { - content: "\f7c6"; } - -.fa-skiing:before { - content: "\f7c9"; } - -.fa-skiing-nordic:before { - content: "\f7ca"; } - -.fa-skull:before { - content: "\f54c"; } - -.fa-skull-crossbones:before { - content: "\f714"; } - -.fa-skyatlas:before { - content: "\f216"; } - -.fa-skype:before { - content: "\f17e"; } - -.fa-slack:before { - content: "\f198"; } - -.fa-slack-hash:before { - content: "\f3ef"; } - -.fa-slash:before { - content: "\f715"; } - -.fa-sleigh:before { - content: "\f7cc"; } - -.fa-sliders-h:before { - content: "\f1de"; } - -.fa-slideshare:before { - content: "\f1e7"; } - -.fa-smile:before { - content: "\f118"; } - -.fa-smile-beam:before { - content: "\f5b8"; } - -.fa-smile-wink:before { - content: "\f4da"; } - -.fa-smog:before { - content: "\f75f"; } - -.fa-smoking:before { - content: "\f48d"; } - -.fa-smoking-ban:before { - content: "\f54d"; } - -.fa-sms:before { - content: "\f7cd"; } - -.fa-snapchat:before { - content: "\f2ab"; } - -.fa-snapchat-ghost:before { - content: "\f2ac"; } - -.fa-snapchat-square:before { - content: "\f2ad"; } - -.fa-snowboarding:before { - content: "\f7ce"; } - -.fa-snowflake:before { - content: "\f2dc"; } - -.fa-snowman:before { - content: "\f7d0"; } - -.fa-snowplow:before { - content: "\f7d2"; } - -.fa-soap:before { - content: "\e06e"; } - -.fa-socks:before { - content: "\f696"; } - -.fa-solar-panel:before { - content: "\f5ba"; } - -.fa-sort:before { - content: "\f0dc"; } - -.fa-sort-alpha-down:before { - content: "\f15d"; } - -.fa-sort-alpha-down-alt:before { - content: "\f881"; } - -.fa-sort-alpha-up:before { - content: "\f15e"; } - -.fa-sort-alpha-up-alt:before { - content: "\f882"; } - -.fa-sort-amount-down:before { - content: "\f160"; } - -.fa-sort-amount-down-alt:before { - content: "\f884"; } - -.fa-sort-amount-up:before { - content: "\f161"; } - -.fa-sort-amount-up-alt:before { - content: "\f885"; } - -.fa-sort-down:before { - content: "\f0dd"; } - -.fa-sort-numeric-down:before { - content: "\f162"; } - -.fa-sort-numeric-down-alt:before { - content: "\f886"; } - -.fa-sort-numeric-up:before { - content: "\f163"; } - -.fa-sort-numeric-up-alt:before { - content: "\f887"; } - -.fa-sort-up:before { - content: "\f0de"; } - -.fa-soundcloud:before { - content: "\f1be"; } - -.fa-sourcetree:before { - content: "\f7d3"; } - -.fa-spa:before { - content: "\f5bb"; } - -.fa-space-shuttle:before { - content: "\f197"; } - -.fa-speakap:before { - content: "\f3f3"; } - -.fa-speaker-deck:before { - content: "\f83c"; } - -.fa-spell-check:before { - content: "\f891"; } - -.fa-spider:before { - content: "\f717"; } - -.fa-spinner:before { - content: "\f110"; } - -.fa-splotch:before { - content: "\f5bc"; } - -.fa-spotify:before { - content: "\f1bc"; } - -.fa-spray-can:before { - content: "\f5bd"; } - -.fa-square:before { - content: "\f0c8"; } - -.fa-square-full:before { - content: "\f45c"; } - -.fa-square-root-alt:before { - content: "\f698"; } - -.fa-squarespace:before { - content: "\f5be"; } - -.fa-stack-exchange:before { - content: "\f18d"; } - -.fa-stack-overflow:before { - content: "\f16c"; } - -.fa-stackpath:before { - content: "\f842"; } - -.fa-stamp:before { - content: "\f5bf"; } - -.fa-star:before { - content: "\f005"; } - -.fa-star-and-crescent:before { - content: "\f699"; } - -.fa-star-half:before { - content: "\f089"; } - -.fa-star-half-alt:before { - content: "\f5c0"; } - -.fa-star-of-david:before { - content: "\f69a"; } - -.fa-star-of-life:before { - content: "\f621"; } - -.fa-staylinked:before { - content: "\f3f5"; } - -.fa-steam:before { - content: "\f1b6"; } - -.fa-steam-square:before { - content: "\f1b7"; } - -.fa-steam-symbol:before { - content: "\f3f6"; } - -.fa-step-backward:before { - content: "\f048"; } - -.fa-step-forward:before { - content: "\f051"; } - -.fa-stethoscope:before { - content: "\f0f1"; } - -.fa-sticker-mule:before { - content: "\f3f7"; } - -.fa-sticky-note:before { - content: "\f249"; } - -.fa-stop:before { - content: "\f04d"; } - -.fa-stop-circle:before { - content: "\f28d"; } - -.fa-stopwatch:before { - content: "\f2f2"; } - -.fa-stopwatch-20:before { - content: "\e06f"; } - -.fa-store:before { - content: "\f54e"; } - -.fa-store-alt:before { - content: "\f54f"; } - -.fa-store-alt-slash:before { - content: "\e070"; } - -.fa-store-slash:before { - content: "\e071"; } - -.fa-strava:before { - content: "\f428"; } - -.fa-stream:before { - content: "\f550"; } - -.fa-street-view:before { - content: "\f21d"; } - -.fa-strikethrough:before { - content: "\f0cc"; } - -.fa-stripe:before { - content: "\f429"; } - -.fa-stripe-s:before { - content: "\f42a"; } - -.fa-stroopwafel:before { - content: "\f551"; } - -.fa-studiovinari:before { - content: "\f3f8"; } - -.fa-stumbleupon:before { - content: "\f1a4"; } - -.fa-stumbleupon-circle:before { - content: "\f1a3"; } - -.fa-subscript:before { - content: "\f12c"; } - -.fa-subway:before { - content: "\f239"; } - -.fa-suitcase:before { - content: "\f0f2"; } - -.fa-suitcase-rolling:before { - content: "\f5c1"; } - -.fa-sun:before { - content: "\f185"; } - -.fa-superpowers:before { - content: "\f2dd"; } - -.fa-superscript:before { - content: "\f12b"; } - -.fa-supple:before { - content: "\f3f9"; } - -.fa-surprise:before { - content: "\f5c2"; } - -.fa-suse:before { - content: "\f7d6"; } - -.fa-swatchbook:before { - content: "\f5c3"; } - -.fa-swift:before { - content: "\f8e1"; } - -.fa-swimmer:before { - content: "\f5c4"; } - -.fa-swimming-pool:before { - content: "\f5c5"; } - -.fa-symfony:before { - content: "\f83d"; } - -.fa-synagogue:before { - content: "\f69b"; } - -.fa-sync:before { - content: "\f021"; } - -.fa-sync-alt:before { - content: "\f2f1"; } - -.fa-syringe:before { - content: "\f48e"; } - -.fa-table:before { - content: "\f0ce"; } - -.fa-table-tennis:before { - content: "\f45d"; } - -.fa-tablet:before { - content: "\f10a"; } - -.fa-tablet-alt:before { - content: "\f3fa"; } - -.fa-tablets:before { - content: "\f490"; } - -.fa-tachometer-alt:before { - content: "\f3fd"; } - -.fa-tag:before { - content: "\f02b"; } - -.fa-tags:before { - content: "\f02c"; } - -.fa-tape:before { - content: "\f4db"; } - -.fa-tasks:before { - content: "\f0ae"; } - -.fa-taxi:before { - content: "\f1ba"; } - -.fa-teamspeak:before { - content: "\f4f9"; } - -.fa-teeth:before { - content: "\f62e"; } - -.fa-teeth-open:before { - content: "\f62f"; } - -.fa-telegram:before { - content: "\f2c6"; } - -.fa-telegram-plane:before { - content: "\f3fe"; } - -.fa-temperature-high:before { - content: "\f769"; } - -.fa-temperature-low:before { - content: "\f76b"; } - -.fa-tencent-weibo:before { - content: "\f1d5"; } - -.fa-tenge:before { - content: "\f7d7"; } - -.fa-terminal:before { - content: "\f120"; } - -.fa-text-height:before { - content: "\f034"; } - -.fa-text-width:before { - content: "\f035"; } - -.fa-th:before { - content: "\f00a"; } - -.fa-th-large:before { - content: "\f009"; } - -.fa-th-list:before { - content: "\f00b"; } - -.fa-the-red-yeti:before { - content: "\f69d"; } - -.fa-theater-masks:before { - content: "\f630"; } - -.fa-themeco:before { - content: "\f5c6"; } - -.fa-themeisle:before { - content: "\f2b2"; } - -.fa-thermometer:before { - content: "\f491"; } - -.fa-thermometer-empty:before { - content: "\f2cb"; } - -.fa-thermometer-full:before { - content: "\f2c7"; } - -.fa-thermometer-half:before { - content: "\f2c9"; } - -.fa-thermometer-quarter:before { - content: "\f2ca"; } - -.fa-thermometer-three-quarters:before { - content: "\f2c8"; } - -.fa-think-peaks:before { - content: "\f731"; } - -.fa-thumbs-down:before { - content: "\f165"; } - -.fa-thumbs-up:before { - content: "\f164"; } - -.fa-thumbtack:before { - content: "\f08d"; } - -.fa-ticket-alt:before { - content: "\f3ff"; } - -.fa-tiktok:before { - content: "\e07b"; } - -.fa-times:before { - content: "\f00d"; } - -.fa-times-circle:before { - content: "\f057"; } - -.fa-tint:before { - content: "\f043"; } - -.fa-tint-slash:before { - content: "\f5c7"; } - -.fa-tired:before { - content: "\f5c8"; } - -.fa-toggle-off:before { - content: "\f204"; } - -.fa-toggle-on:before { - content: "\f205"; } - -.fa-toilet:before { - content: "\f7d8"; } - -.fa-toilet-paper:before { - content: "\f71e"; } - -.fa-toilet-paper-slash:before { - content: "\e072"; } - -.fa-toolbox:before { - content: "\f552"; } - -.fa-tools:before { - content: "\f7d9"; } - -.fa-tooth:before { - content: "\f5c9"; } - -.fa-torah:before { - content: "\f6a0"; } - -.fa-torii-gate:before { - content: "\f6a1"; } - -.fa-tractor:before { - content: "\f722"; } - -.fa-trade-federation:before { - content: "\f513"; } - -.fa-trademark:before { - content: "\f25c"; } - -.fa-traffic-light:before { - content: "\f637"; } - -.fa-trailer:before { - content: "\e041"; } - -.fa-train:before { - content: "\f238"; } - -.fa-tram:before { - content: "\f7da"; } - -.fa-transgender:before { - content: "\f224"; } - -.fa-transgender-alt:before { - content: "\f225"; } - -.fa-trash:before { - content: "\f1f8"; } - -.fa-trash-alt:before { - content: "\f2ed"; } - -.fa-trash-restore:before { - content: "\f829"; } - -.fa-trash-restore-alt:before { - content: "\f82a"; } - -.fa-tree:before { - content: "\f1bb"; } - -.fa-trello:before { - content: "\f181"; } - -.fa-tripadvisor:before { - content: "\f262"; } - -.fa-trophy:before { - content: "\f091"; } - -.fa-truck:before { - content: "\f0d1"; } - -.fa-truck-loading:before { - content: "\f4de"; } - -.fa-truck-monster:before { - content: "\f63b"; } - -.fa-truck-moving:before { - content: "\f4df"; } - -.fa-truck-pickup:before { - content: "\f63c"; } - -.fa-tshirt:before { - content: "\f553"; } - -.fa-tty:before { - content: "\f1e4"; } - -.fa-tumblr:before { - content: "\f173"; } - -.fa-tumblr-square:before { - content: "\f174"; } - -.fa-tv:before { - content: "\f26c"; } - -.fa-twitch:before { - content: "\f1e8"; } - -.fa-twitter:before { - content: "\f099"; } - -.fa-twitter-square:before { - content: "\f081"; } - -.fa-typo3:before { - content: "\f42b"; } - -.fa-uber:before { - content: "\f402"; } - -.fa-ubuntu:before { - content: "\f7df"; } - -.fa-uikit:before { - content: "\f403"; } - -.fa-umbraco:before { - content: "\f8e8"; } - -.fa-umbrella:before { - content: "\f0e9"; } - -.fa-umbrella-beach:before { - content: "\f5ca"; } - -.fa-underline:before { - content: "\f0cd"; } - -.fa-undo:before { - content: "\f0e2"; } - -.fa-undo-alt:before { - content: "\f2ea"; } - -.fa-uniregistry:before { - content: "\f404"; } - -.fa-unity:before { - content: "\e049"; } - -.fa-universal-access:before { - content: "\f29a"; } - -.fa-university:before { - content: "\f19c"; } - -.fa-unlink:before { - content: "\f127"; } - -.fa-unlock:before { - content: "\f09c"; } - -.fa-unlock-alt:before { - content: "\f13e"; } - -.fa-unsplash:before { - content: "\e07c"; } - -.fa-untappd:before { - content: "\f405"; } - -.fa-upload:before { - content: "\f093"; } - -.fa-ups:before { - content: "\f7e0"; } - -.fa-usb:before { - content: "\f287"; } - -.fa-user:before { - content: "\f007"; } - -.fa-user-alt:before { - content: "\f406"; } - -.fa-user-alt-slash:before { - content: "\f4fa"; } - -.fa-user-astronaut:before { - content: "\f4fb"; } - -.fa-user-check:before { - content: "\f4fc"; } - -.fa-user-circle:before { - content: "\f2bd"; } - -.fa-user-clock:before { - content: "\f4fd"; } - -.fa-user-cog:before { - content: "\f4fe"; } - -.fa-user-edit:before { - content: "\f4ff"; } - -.fa-user-friends:before { - content: "\f500"; } - -.fa-user-graduate:before { - content: "\f501"; } - -.fa-user-injured:before { - content: "\f728"; } - -.fa-user-lock:before { - content: "\f502"; } - -.fa-user-md:before { - content: "\f0f0"; } - -.fa-user-minus:before { - content: "\f503"; } - -.fa-user-ninja:before { - content: "\f504"; } - -.fa-user-nurse:before { - content: "\f82f"; } - -.fa-user-plus:before { - content: "\f234"; } - -.fa-user-secret:before { - content: "\f21b"; } - -.fa-user-shield:before { - content: "\f505"; } - -.fa-user-slash:before { - content: "\f506"; } - -.fa-user-tag:before { - content: "\f507"; } - -.fa-user-tie:before { - content: "\f508"; } - -.fa-user-times:before { - content: "\f235"; } - -.fa-users:before { - content: "\f0c0"; } - -.fa-users-cog:before { - content: "\f509"; } - -.fa-users-slash:before { - content: "\e073"; } - -.fa-usps:before { - content: "\f7e1"; } - -.fa-ussunnah:before { - content: "\f407"; } - -.fa-utensil-spoon:before { - content: "\f2e5"; } - -.fa-utensils:before { - content: "\f2e7"; } - -.fa-vaadin:before { - content: "\f408"; } - -.fa-vector-square:before { - content: "\f5cb"; } - -.fa-venus:before { - content: "\f221"; } - -.fa-venus-double:before { - content: "\f226"; } - -.fa-venus-mars:before { - content: "\f228"; } - -.fa-viacoin:before { - content: "\f237"; } - -.fa-viadeo:before { - content: "\f2a9"; } - -.fa-viadeo-square:before { - content: "\f2aa"; } - -.fa-vial:before { - content: "\f492"; } - -.fa-vials:before { - content: "\f493"; } - -.fa-viber:before { - content: "\f409"; } - -.fa-video:before { - content: "\f03d"; } - -.fa-video-slash:before { - content: "\f4e2"; } - -.fa-vihara:before { - content: "\f6a7"; } - -.fa-vimeo:before { - content: "\f40a"; } - -.fa-vimeo-square:before { - content: "\f194"; } - -.fa-vimeo-v:before { - content: "\f27d"; } - -.fa-vine:before { - content: "\f1ca"; } - -.fa-virus:before { - content: "\e074"; } - -.fa-virus-slash:before { - content: "\e075"; } - -.fa-viruses:before { - content: "\e076"; } - -.fa-vk:before { - content: "\f189"; } - -.fa-vnv:before { - content: "\f40b"; } - -.fa-voicemail:before { - content: "\f897"; } - -.fa-volleyball-ball:before { - content: "\f45f"; } - -.fa-volume-down:before { - content: "\f027"; } - -.fa-volume-mute:before { - content: "\f6a9"; } - -.fa-volume-off:before { - content: "\f026"; } - -.fa-volume-up:before { - content: "\f028"; } - -.fa-vote-yea:before { - content: "\f772"; } - -.fa-vr-cardboard:before { - content: "\f729"; } - -.fa-vuejs:before { - content: "\f41f"; } - -.fa-walking:before { - content: "\f554"; } - -.fa-wallet:before { - content: "\f555"; } - -.fa-warehouse:before { - content: "\f494"; } - -.fa-water:before { - content: "\f773"; } - -.fa-wave-square:before { - content: "\f83e"; } - -.fa-waze:before { - content: "\f83f"; } - -.fa-weebly:before { - content: "\f5cc"; } - -.fa-weibo:before { - content: "\f18a"; } - -.fa-weight:before { - content: "\f496"; } - -.fa-weight-hanging:before { - content: "\f5cd"; } - -.fa-weixin:before { - content: "\f1d7"; } - -.fa-whatsapp:before { - content: "\f232"; } - -.fa-whatsapp-square:before { - content: "\f40c"; } - -.fa-wheelchair:before { - content: "\f193"; } - -.fa-whmcs:before { - content: "\f40d"; } - -.fa-wifi:before { - content: "\f1eb"; } - -.fa-wikipedia-w:before { - content: "\f266"; } - -.fa-wind:before { - content: "\f72e"; } - -.fa-window-close:before { - content: "\f410"; } - -.fa-window-maximize:before { - content: "\f2d0"; } - -.fa-window-minimize:before { - content: "\f2d1"; } - -.fa-window-restore:before { - content: "\f2d2"; } - -.fa-windows:before { - content: "\f17a"; } - -.fa-wine-bottle:before { - content: "\f72f"; } - -.fa-wine-glass:before { - content: "\f4e3"; } - -.fa-wine-glass-alt:before { - content: "\f5ce"; } - -.fa-wix:before { - content: "\f5cf"; } - -.fa-wizards-of-the-coast:before { - content: "\f730"; } - -.fa-wolf-pack-battalion:before { - content: "\f514"; } - -.fa-won-sign:before { - content: "\f159"; } - -.fa-wordpress:before { - content: "\f19a"; } - -.fa-wordpress-simple:before { - content: "\f411"; } - -.fa-wpbeginner:before { - content: "\f297"; } - -.fa-wpexplorer:before { - content: "\f2de"; } - -.fa-wpforms:before { - content: "\f298"; } - -.fa-wpressr:before { - content: "\f3e4"; } - -.fa-wrench:before { - content: "\f0ad"; } - -.fa-x-ray:before { - content: "\f497"; } - -.fa-xbox:before { - content: "\f412"; } - -.fa-xing:before { - content: "\f168"; } - -.fa-xing-square:before { - content: "\f169"; } - -.fa-y-combinator:before { - content: "\f23b"; } - -.fa-yahoo:before { - content: "\f19e"; } - -.fa-yammer:before { - content: "\f840"; } - -.fa-yandex:before { - content: "\f413"; } - -.fa-yandex-international:before { - content: "\f414"; } - -.fa-yarn:before { - content: "\f7e3"; } - -.fa-yelp:before { - content: "\f1e9"; } - -.fa-yen-sign:before { - content: "\f157"; } - -.fa-yin-yang:before { - content: "\f6ad"; } - -.fa-yoast:before { - content: "\f2b1"; } - -.fa-youtube:before { - content: "\f167"; } - -.fa-youtube-square:before { - content: "\f431"; } - -.fa-zhihu:before { - content: "\f63f"; } - -.sr-only { - border: 0; - clip: rect(0, 0, 0, 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; } - -.sr-only-focusable:active, .sr-only-focusable:focus { - clip: auto; - height: auto; - margin: 0; - overflow: visible; - position: static; - width: auto; } -@font-face { - font-family: 'Font Awesome 5 Brands'; - font-style: normal; - font-weight: 400; - font-display: block; - src: url("../webfonts/fa-brands-400.eot"); - src: url("../webfonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.woff") format("woff"), url("../webfonts/fa-brands-400.ttf") format("truetype"), url("../webfonts/fa-brands-400.svg#fontawesome") format("svg"); } - -.fab { - font-family: 'Font Awesome 5 Brands'; - font-weight: 400; } -@font-face { - font-family: 'Font Awesome 5 Free'; - font-style: normal; - font-weight: 400; - font-display: block; - src: url("../webfonts/fa-regular-400.eot"); - src: url("../webfonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.woff") format("woff"), url("../webfonts/fa-regular-400.ttf") format("truetype"), url("../webfonts/fa-regular-400.svg#fontawesome") format("svg"); } - -.far { - font-family: 'Font Awesome 5 Free'; - font-weight: 400; } -@font-face { - font-family: 'Font Awesome 5 Free'; - font-style: normal; - font-weight: 900; - font-display: block; - src: url("../webfonts/fa-solid-900.eot"); - src: url("../webfonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.woff") format("woff"), url("../webfonts/fa-solid-900.ttf") format("truetype"), url("../webfonts/fa-solid-900.svg#fontawesome") format("svg"); } - -.fa, -.fas { - font-family: 'Font Awesome 5 Free'; - font-weight: 900; } diff --git a/proco/assets/vendor/fontawesome/webfonts/fa-brands-400.eot b/proco/assets/vendor/fontawesome/webfonts/fa-brands-400.eot deleted file mode 100644 index 54ad8d72dc4642d0b1463c9694365d059168eb76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 134622 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 diff --git a/proco/assets/vendor/fontawesome/webfonts/fa-brands-400.svg b/proco/assets/vendor/fontawesome/webfonts/fa-brands-400.svg deleted file mode 100644 index 2c8659c..0000000 --- a/proco/assets/vendor/fontawesome/webfonts/fa-brands-400.svg +++ /dev/null @@ -1,3637 +0,0 @@ - - - - - -Created by FontForge 20200314 at Wed Jul 15 11:59:41 2020 - By Robert Madole -Copyright (c) Font Awesome - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/proco/assets/vendor/fontawesome/webfonts/fa-brands-400.ttf b/proco/assets/vendor/fontawesome/webfonts/fa-brands-400.ttf deleted file mode 100644 index 16852bfd05b7c5f3ef802a741bb41e5d09ffe352..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 134316 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

s#e%;>zJdj&A+$3p&dzh|F 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 CLs}yM diff --git a/proco/assets/vendor/fontawesome/webfonts/fa-regular-400.eot b/proco/assets/vendor/fontawesome/webfonts/fa-regular-400.eot deleted file mode 100644 index 479b32cecc28b95568c26f28fcd2ba464c10b30d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34350 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 r%i!1yDca~iiZ5MB!tdb>i%^-H^Af`Mep32t - - - - -Created by FontForge 20200314 at Wed Jul 15 11:59:40 2020 - By Robert Madole -Copyright (c) Font Awesome - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/proco/assets/vendor/fontawesome/webfonts/fa-regular-400.ttf b/proco/assets/vendor/fontawesome/webfonts/fa-regular-400.ttf deleted file mode 100644 index 42a04fde4e7f0cc410fed06e0596087fadf9b732..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34052 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

}1lV@j@~t%vD3-Qaa-dXN!LOL2s?V|3d{LLTO$0 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 - - - - -Created by FontForge 20200314 at Wed Jul 15 11:59:41 2020 - By Robert Madole -Copyright (c) Font Awesome - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/proco/assets/vendor/fontawesome/webfonts/fa-solid-900.ttf b/proco/assets/vendor/fontawesome/webfonts/fa-solid-900.ttf deleted file mode 100644 index 7c59512f3ca5c18cae924d6897512ff5734c32b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 203980 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

|)x3^jKapWz0B6g%no11O?0LpPnj{2^-|v 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�y28=T)$P<#~T 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≻$pE^>=T!W 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�p|Sgi6PYemzY^a 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�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Ⓢ_?uYH2l3}~)F&-k&vmJ$M~Qm&g=QcB18L)F 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

ufx{c zMMV~oHWA7t*ONIkC9630PPBetcHynE{MLopcU7Il-|yh5l==(j5qqTg)7B@7BcI>} zMgE1O%%QB`&whQP;O9BpTceJ?OsNb~-dg~=GUrQcxMp9j=N;3#T zE>%K(n+=Zn>Xbb^DHeKh?ZMT9m)&EKNsc~WPknIp`sfvg?&|ujdd?`uQ%}*$CJGz( zpnK~F>GjwZI=z0{FoxqPm=lnzR`A9ipzcR$vKU}{i!{EXMA+_{+_9E!s%1R8W9rqi zs=Yd*q02Q@e)ZIjy;|hYe&v>1e&rTqN)nzOAhU5`7E4k|MTR^zB^yXZ+qLoERP1or zR(^9_yEnXc3;jYvGLbTc@svWoS)3*EL?S^1qgk%kh6@PO{oI4I34`#ljf_Balh|8I z4{94@Z#&aV4tOQ>l*hfNoh*?@({Ncne-``_-r70$t=SX=s1%WT7DpR-F`HfKDJUho zK2TqU+^=Js;Lb*KQ7`BN_c6l=1i>~-w_p#Q=Z27SSl15|PEOiKnM<+>V9H+I{xaHT zD2EK=kn)`%^U2~P*q9inehLK$Tgo8p)1Md^*}P;N%}OW#K@I)%#wY$$YTtx@^E6H}NirdlI&*)BX*mS$t=ixZ~U z_tY}Bg;3~y6i2N|y{{d&B1>z_^a1{Fn7c85IH|ZM?4+Or=*XDkXX(fANl0<-PmtR*C78GNcULkdsej3^+J2t@NYAOo)##D={L_6+QUuKcmGi@+JGUrxk*}__DWm0LrgiER};0=!2%IlocU;bJbLK zs=|%ekEaLCkiD}2tiaheZ_QY1}nAmup}rWq9QJye!na#J4I0zzk9>@cx52rPPD&o zg&z!c>vsDwIU>e*et7nJ91(N8aCFA`f2n)(I61EBUc7E?xAv~?>ZSQH=zLLd`J`0*Z2P>f%IB)|Y6fsjQBYw~^x zdP#VY&tq|t7Xk@xy?gGhUS>2lHjm#QKWVz^)~(ysRk!YP&bjA&zk)DZEqQOpte*ZF zy^h=$U?Tx{8jH_mGi0^64oHjAeo)XwTy|NgAlh`4dP5#PO1&J9F87h*&(+#9rIReT z_c<0H8k7mXu}rmp#9_5vD>bE)Ec;1dQd}!*CTTR>!NHnMKVYWN50n90{y=H!8?*-@ zPHVH7E78st>B&|aNj{RL{JKPRj)-K%n-L-yxjh(w;cTsxBK90|S{?jXF6AyyR03_7<)P5Q^cDS(Ca z{fX>I(x!d3-CE-j^0$K}|F_fQ-=c)xxi#3TX&OHUR)x(&#B(}J7uj|I)!<9@MzJ4p z3|=+;%C6R{MgBgWObG48HW0Vd=wb9V@ui=ziv{i@UyShVL>Rh?#;9 zDM`wQ-_q@!r%)tc?slIJ5)e=PBZ>p?T5elElG;K=poydb#59n&!l<2OhMdyO@F4_B zc?Lxxw5>kZ+RpQLVU0+S)>xsY?JiFEap}mTn(5fm4LELXkxe0@n61>7ieru+I_NsC zhy-5Oy{5Kz8v^lN#1aZeV`3(rYHxd;nz4Rhxk^mnYDj(NGnUgrs->X6G9tZCXm8X$tKa6lM(c(j zdqRKwW7;j@efK!G^#lINOUUKWZ}vXHlMd{d0@|e*I5gYsUgDOPR=VAlrE@ghkHx;o z??k^rX6Q+1Il$BeH9RFW<|+NQ=L5N*1jcF#$N}CGd|Q!2)%1yPpGa3jvSL?v>`6`d z{zPiej%x3RP9l*{>{FEe@j@bwI3-kG*bzT|JicS098#oEDOqi|tI1MGx-0F+ox8SS zyzMS0;gL4{zb}1_$c6t8a7j>$yd1y}L8E2tP|zmVvCR(HVfuD=`PuxBgZ;&2DklDG;1$uc>=mcbXk zhvvVDZjWq0&C-S0f+5|S?Hbj($we-46wDBX>52LXSquB?v*`5Y-ajUq0FnOvM&1=J zTXUcU$`A%XkykJ?kmt~92W}Q*zIJW55%$$Ro6-}q6mr8xr_(`PyT$-NK)}Dy5NhqP zfogM(JGn)XCepdocu6zD{doH{spk}p`-e9S6nzv3mJUxazw@NK^Q~zioF#7_J>!_6 z+u}mhQt)eEixn#>#BU3k&f9jXHFanF%6sB^-MiTlQIxM(x4bpviF@{lUg)j2SQWv2 zBocY#myX+_e@pMPM)RKd+BlvfH_a_jT-tT<*`tsXeZ50^7F615zje?E5k>-Mj{tB{ zTLrR5k!a7)FyjZxQ7WlXcrKdp^%3;hrxzJm*Wz<=ne~0k*Rv6g`jqdc>+G+++>`ui zIuE~L5Q96!=Fq5RlKGT!)C3!D)nO>^Hy1F+3Sm0;1rpE*00;FZMITYsFC9W^LVb*C z8ec{d`n~@~<~^ze-9}vn+R$`jL`?k>3LRF}e`O1h0{VTF>%js>jbak_uAzPa|FzRu z4!9PA*!EjVO`PPe1$KdIi|MVI{+IzH6`3l?%OZPeFe>)}7DrYtTWl3>{l4C(y8R?p z_vrRwZT2R|Qwq*aSdQGC^tBl+p0?9*Z3caizP~M%%FF2A7WO{NPAF`LL(ai<`*LrV z_Vw3e=>}WhoYp+CQ1L_$iRL2js;cj+Iof=3^dC(|EFc?TpDyj|LA*4`CiCEzU;b&H zuw0-4dd)R+78M)o8|H!_(I_&3`vpAja!j*qimF8(1wa)>(zd0=R-f~EbE+x-BeVtf zGgMmcu08&yWU5_`8{tfdak+Jz36ujiBe+pqo^MGo;;bb<_t%N7x#wq>_CVs!(1EF5Xh=e3n z9DB_e){*o)JS3O5Z<2M14~GR)+eGnZ{*8=#UqoMENTXo%gSi!8P=VRBK#zdGhb~G} z1TMIF2vuxST?_p|sGN}%Q{lrJHdMYKdMVOu1=|htBv;OmxqYsXE8aweMAb}dXlAT1 zhq~KmYr9WuF6gGL;a@Z(5%U){)6@%_PwcMEZ7b9j)#-?WWGQ3_FX~D?h(&x2`a>G` zn+AQ*p**d=g|H9qd7$^T$Oj@MNYy#(XD^~u-;0Tm|H%I870GPAJ7H>5v1D<}4&G93 zPAX!02Db2*vf!A z9WyVjYt>iOwlHucHV{ue6EZeTN$+=KiHk8B=_W^zr zmC0{rK=Sj~1T6uyW8ekjf)&CQh}it~u&-QD{BUi)BiTe>xhSi))S15rKZygpo%@W_ zttTZ}RPd9n)5d*hh0LMCr5}^ud4#?@cysV{{n{RFK@BR4(9IAx1B&)-z)sPvqn}p| z>kZ5zN*mMC8!SUTJF9E>9+{|R@;zA7XHPMs*u{$vYi@2xpOJI5>mBt~I#`~fy8bHF zS*7N)-YasnK7M@ZQ|J#Vj$wmwn=#sUP~{2%LR6&>kVnMxW))8Mv+7w;=+Jf5xTj#n zC{66NDa@6IvMEc*ij*AKhaB7I)JxwXb^2wB*_k7nP@UTnNY2*|`TANO_8^cEW+jU9 z2ho#c=xZVmqA02|09;Z2c+$fQ7#@kABWGkh(C+CYVz1g{WGbXW7=7RmGObo7eY*5e zNq1`nZE$qD_W@j%KsTKfAQCK#v8TTy7YUPpb8Ls`9Q@BDU+1N=;$fU61w}!>$fRcg z&zRRvokF4;>8G{ccMRl3RIT`UG>Qzfs6ASiF_wG3Nq2|n#Jq!KtB#9)wP+eB8ddvP z@C=LR7+^pK{uLC~Zk{N%`GQ4pHdqq`6h!L%MZ23H&4@%_+%7e?_sKl?(-=G6WF}Ts zWM0M^?tM>EWJ!c4RjRSfr01{#cG{I#2^5ha%m~Ts{1*jV&`Pe7nN0a83;}YyfJIF* zct16nQQVR?C}8@bpoA zG`ibm6YqOS4@Hc}&{oA#Li$5{B~6z1pvNfx?mvsl53nK@WfaoCVc1Afu%UcI4weORls-WfNK=aZAP!_#5T zyO5}iXZ#ArQf3@^$(oY%`AUM93+U0LhsHA!#uYy^UP+J%oljJFKdIEvY9-NoEHh3E zV64{SQ+_xSuc;U(DsHlOfhOto#xv-FWI5?p60$H<=4B7d9Kw-d{%%7nS1HvK8OVo3burpI~Mz<5fqL!D8$DF!FSwvY8u zrUqq|8Yf-qUHX0g5%d|NP2bEt$i0qx7tuw?K!yX01%)Oa7%qSik=if7w!vFSVuF|q zHf1PTrP!$z2)L&k2$AKReKgb{7L?lK%N4NGkkKI{0PJ5yf;nLtf+Inxu8_tpXGGtF zb8j&uz9{K@h}VwFg0xe`b0U_HXb2-ioRIN0a!!|ooE|HqZ<`|N=@u`V(X2@%xgRQm zZ)RQ6%PpRlBtuE7S|lgQMhGjRaM=F&u;p5&fCU@l-Yc=GDP%e!6Td-*94sLrhFlp5 zs`v9Uj#;@U?nsS^h*tzdGI=PB#DAEwo%Ch5v-VcoSeUSI={?h7Llow*WJ`+Zo0;8k zhl=dVcwUks;i-%vqSyqM4NOEzgrvNvTCSUDWQ>S&6TlM^eOwoiz>}wuL~AVG#MqVc zyfZO%Oo>^dER(51K(wT_2E(*S5xwgmGoZN*O~14kA}XhlS!AdO;{{Y5*n)xUn>3`r z_)A(<(w)J1=c`%g&fY`R>ifWv%KJZXY%)#Il>m>Z)eK^3y#-jGug|qcIY)Ug zWMt!Wg>;qjj+T=v$+A+KkD5e($`VzLNI8H!()&jd<1Gfc!X6!wL0wvar7pWQCVQ?@Sv5xw zP=^Vw0Pc|+y<)SxGn5PMRFx3LdkrZ!(1NcY`R*WIE;&6%~I!YHqV4 z=rcVo6-6u2RHwK6dkeb00Hx`-sBa5P=^_ zUAlDX6I2GW$g=^aOuu!})>dVWj55-r*>IK~Gr9B9@6x(I6X3f{)9xK0 zFhHz4tfB>o3*nwOye@LTi*wlJZUxnUttQ{6pf=>8;t?DKahaDiS{UK>?{G z$)Boaw@x1w^LvrO>!PmcCQgs#G4TC?0s}AT2gmN7 z?Y&DOda1HZqhpwHY9zT#vH5W{b?CN3ox6`+(aI?UjOI82T$K5*sAliGrn65Ek?tr6Z)mrNOrD!V)?9CO2K}3- z=@ao%9OcNg7!ic8YK}Q=@DbxWC8VPG%n(C%2FG{qP`1BVo3QQeek2|r!3>1262A9wk7Efg*%)6RINIK(oN(WhzretD>SQXRiA#nT1C^&v&nS>+J= zPu+9BsznrDzFi;J7imlrrM#l&UlS>7c2xanB+~nr;KT~!J_o-A^!F4utIwG`J#1Te z9shGS5MwiZnz=$#PTjA|f{+tT`7zqQ9+ORSB*>3FHj+)mW?^vR9?_KAWS%+p9(X73 zIYwrmHc`%w9v#USh+I896RdBs@i$E|1NL(-;%?;b<{sXF4{^lli^V?-9l-qm56*#1 z)UNe9uw}$NRG>-GzvfmDyC>-8R z`#{^z=ihRMZ2U9NlJ|-Yz6)(l+n_#Ab1&iUM<(0t$!s20U0DCu?2bw-#I*bF$JhzM$)%Df_GweEEeL-{w2t6QC_MSa2|a3O{7tbr zy_iF#J(Z8RRc*5@`bd=AS%fCEsKpCTSRxZ@NX}w6Wj-}_Ag}N2j(x<<2=U)yC!}yv zb}ob_t0{lHc1)J^s6~F%CMT8Abx`mg+72FvC~67Vtw$-+K>>_6je29a87K-+z?Ii6 zHV(kFmWgT@)jQ#=qwc$QpPDImQYHAK1cDtq?Yy1#)zLr18L?>C6f15yDNkj`|xfrOs8rUpLfi=v(AZwz`^iH{WzL|SwzJW*-;m5QDZ)Ox)vNuq6A-kt^yC! z*R?`DQ+@^)x=6F5DZ7x#6bhMs%v;xVVz)YGr`zdWgqiFGUi-2Fi?prGgJX;$URIDK zUf`8z-(hxv=781bxg#f@wb{A#YJbsOT=P=r_+8iT;`h+)Tp&oU&{ulr47}3!9{CFE zJ6qX1VfQXg?bHR$;+%uhJQlpQN8eQ#nd#fWokLhNC?Tsh%T7vYV1JqM@V7kAY zE_nc_#jvyLKA=nWCmgWON5L#Wr?B@%B5$Xw{Ety4^aW}o3oM(~Tqu=U%fpIwvb#zQIbn0;5eI!MH#J)A2ToqlDw?5bTeVJWs&`-7Sxh@CPI}Qvs~=r{9d>$u z>!epzeDNd{-eqV_O*^cu=E(UOC!Ka?(#|aE=5iNfvGGdUIT!H88-sfWulc>)aqh+3 z&vEw=DeO($ySU%ve&_jFk~g*~KO6V7|1&D%JWCk>nP-d%mR6&kqQkb9b`H>FjheCU zZ~5}94N9%a0-A{452=xx{2YrjX4?bMxIg%1TI!CG?NgWKoV+Zj_nnagu8-#r*alG| zsWHEZB4suuqvCcR6hsMdt{7tU0?z=WE7Tz+=m8e*XAs4V?n6d<6rhBGWCJhcZSDHb z!wN5j((+~$^V0$!3(v_aQDr4X4vUC_VUhX^#SA_;C9ge$RvvufL8q<=f=Kkk%?D3C z#g}8@xD-NI;YCu?5LBKo}R<^yQgBWL4rQX77DtiJ0k5N_Bg5m78%x)`E-kjv`KBj4r#ko4y=b&bOcN=-!tz$q*BgCBIsjK8jGC+Q1#(zm945)W&ea@7bdY4<0>9co!VCy;`?8#=|y!)3-7Wqq*ju3&U8?mia z^eDn8W%-KYTPcz}8ckW>9vZ$D`w8`)&^x{YvdCB7@qH_%?fdx0_h~W9)Fz`TBq3MT zb=4(_RCH1w;dTmBMvb{O62JmA2(1mUyD$`!Rysucri&`8uo^;LLzx8B`QYGYxHZ4g zc=q2I6)D)Sjfx#>fA@?gcQhHM9BTd`za(oNS-bom2ImJ!j`BwTLEsmkq9}YYp80fD za~tX-5cgfAeHaTX&qf%)7Ov?~$Y{IQQU?6ajw=_GqP=K;G!%BxMK>IJ#Up3WK63W_ zhNJ+htz*dM1&LqF7Lc%S*?oMot?FBi=&$}ss9FuZ;VfL}ts9JO)Lv`YD$D03ksmoT zv_pbD^d)5UdBunX+9<+*w@&_#G6C1g|DsF(U#=&tS%BMMKiE{=dT`C5l?YLIl11w6 z#dxlRbf6*HY(1_s@*!`M{QZzwe3^qLX@$#JU0LpSmsgmGz|u5(i7ISMBQDG%+a(HK zvUHxV&3%-$kdgYC<#vFLzRXaf88mWh_{@P^bXmGSCj#HV6b~+o8PwNP&@+O1bNk8m z&8ncdomeiWMYX}UJ-b?Zx26U)v8I~KDa)wJJ+{cmg9 zqp-iQoFfn8mgshC--mt>(>%xR2`s;4cAiFlbm+|9XyL-Zr=8)Sw^&fM7vifeU=hbv zANv@%#H;9OQ!;Kiq*~@3qOK_No;|Xn=;9qbZ)nP)8w|;O>9`a%&KAUkSU77$rSa*~ zbR)XSwl_r^)1`SnpXdJ#CFRBmEhfb7R&;#TRajT2hMYw9t!5Z+z1=i#--^TL zt*@-bmAQ1io}N?UHKRE`-ef*XHhHcY@?5VWPX%5vMF>459de$$ntC;`s6jY8>C(ZT zOm)m7twD(b=msK3vdEdQ&(66{|4SjZ^u@jh)#QWGtw93~hI?wV!*y?#6y;<2M~}<+ z$ivy%f{4)cn0E?zl?0@1!SdMV2|mg5BG04fIL6~qr0{&kAhniVlS>bhe7T|UV=~^N zAwg6n@su|;$mprMf&755#aT411JKPIE!U8`{JW{M?BKT{SIB4w-Fj&4(9yg(jXC`+y& zD16e+YB4+R@Fqf{8WDtuO5U4A-kE{GGYxVSBp`%z-ig~WEo&!vB5AtPzIDoK@2QYZ z^ipIal;cH(cPq2A6_*DLcRoih8S&tIiCj1o$v9yzniY#G`2`db$c>8@(CsdX;$8IT zMUo|i$d3vL=f!N)3p<%eD4a{cEqf7WL;MuwKe4DB0{F3K7z0kZf+Y&jKIh}6FR}H} zLa>C|pj7BEo$jE9y5J~->aC_v*Hi;sasZziIpW8?-oN{P@87-eSUGFbvQWg*|3&k( zf6=XFSzMIib50h!GMTHo@_Bhtl;Lwu?ve!gpgXWXyNL1PtdEaL(w(As;4#8G!eC{Q6F5-Wwg?sv$bqP(HGOxl4i*|78NXx*$_7{hD;UIRcSiC zs4Hz(i%)OW$t)AE-0_oN9Og5ni^5o${B~C_w7bO+bl+(*;$+B<~ zy()9>j%sO}CXYw+$kMPb3o?-(GHL{ri-?*@sRppx=A+}XwlfyryG&+AO{-1U_Ib@{ z39I2yJ)5bAbQL!mSk*)IOtu~htGE=Vak)t zSnv9aKCIypk)aWM`lP%IXq|sV9sNLK%hB5Zk__pKoi6zgte+sCv53Ne8T2MmFGHfN zi&yjfCs*JfbD-&t5QIu2{zMRwxef5awg4~pK>Q{nKiq@G;{siCsZQ|_NixKqY=9$T zgGNsk-7<}oVrXqd2>r$JiCo}m*U7D#4)!O<_=1YVQ8YH`V_<2fE3iT-4z!lJPVZuu zIksO+RC3E~bF6Wm#%E53|-GQgZ14e8oARd4oLaz+Sqe(I#OM#? z7*n*{{M4y0oeM(bJHrOqY1de;>Cn47@AP@A*!zrey8+e64cEg=Q!MA{fI{g2NgWS@ zGlC`7BGo%Ao~1^R1?xgaWG}hJ;xzf)yq6MN4vw&#S?i4ifASvSJBe`5^ zM(g_(!MN}_GAG^2JwnC>W`_v;iV9xPNa#omk(yp$K4zR3UVzR^&-fXk&HzHFL&5^% zL@dy8dLDv%crs8EIe@1wCzeRQC;>utMrfj+^8~@e7`B3fuUuFtC##NzbfGw{yKY8{ zr^md=p~m>mQdk#}pxgP`95_tnRnZnzl`i>)O-)P4QmyTk6OtO94rxR}jR;c6Eqiv> zb4~J1+0jLdXnyFmurD`c1qp35h5Uh^Tt$*K92Ac(RX(hWE3IS~O+qBMu7z>4Ppfi3U& zcEV6B%7xBS*dLFFGfymbJ6eVoEi8=uG z{+;e_CHJV{kgHlzQ#WN_FSf~4n%Y!$5OT_!QV;`L)P30uh2xUshOnYanu38`MUkWs zPNXeK2-}wFl9G1FaP#xw0$+}U&w*o?6{T!DkdBx6LU=y!nii}Y!7eOFRyu)0lB5Wz z%J6CWPKl#96BZYt()b9oQKjnoP29j<%WbvL42Lmfg4LAGl@g(R=n6e(oN%fOaVLqm$EPxAf-u5!xZ>VK#^l4UGEIi(AE}Y=Q775% z_abWgUQ0b>7(}PMH;8*?KB;|-<>&PL>3gF**D*Rrtwt-bT^f$m-3^myrC$_vqp9Bc zWyoJ1U=Gnur@z_we#>igB+r%VYw=nApKy#;U%)Nc{c_La*#?+8=rwfx4AxbHz+qg) z0ge^S*whh>`tbwm26eSWNeZZYU99+0$4y)a`+oS^O}S`fHd3j@s#}b_E0|}Dd;pjG z7wMfZb!Pp7UySb0Uz-=j%+$0d#Pj+AO<-6K4A-Xy$ORmmBEW?(5GJ8f&D6IeX8s;{ zV@bhOG95U6Gby7_s?-Ej2hu6@pl92n_>syq znG(sdbp*?X7RTs58*yZctM1UIk*jxooH?G3o!2E9C-m3&i6hjcWWELaUEM{~+Kwv4 z$fA~}BMJXCdP2ssc0QIq-uGEyy7Me|lsomTE2|;CKw8|@7_4Q)xe}nE553p7 zp%L`d`kXH^)@xXJZ!xXPWgxvQf`pMEgQG9ZJKMo{NZnc)xi1Zm2XhYk5ymMT#W&&h zfm07Y_{4*gwuXC`9ynRuRo!~>LsldnQEW1{E7%}PC{9UO-DE`!$5@{$k&C1-xsGN} zlFK}J>OdQ-C%0C2S57{#SBixq3AS*gOIQ~LQIRykB-uvPT%SF_YZ%nS42#-)9yQS% zfnD_b5MUYf+)sJ;_KDp@?AU!!1D^%ic&?W0WgFn606HVXJN%D8pR>6n zR|dSpmIa~TuxysWIU zNZr-?z2wv;IQ9C>*|D$__wxt5o#$6BLI4(T*(?&P_w@M-oyuu;c7Xc@JkLp1kAds` z5Ynp8^XU&z=mm=+I}bIGEOJO2;unadK1Cs^rlu%v)yRx`kaKo6a%K_^jyu|x z7x5AOa~l;8j=pJWGn&`OlOoT;V*s})&BV@Kj0Us_1hERy8 zvM$<>fBA7+G7a?5Z$E^7j0~}Pn3O7&kSrZ;iU!j6%JN=#{|dZ+^i^`NX)ecAxG^%{ zPZK?}LGi;#h0;aF{(UVln{XX)FpEB}0uYdZ>)3DMdev_gTlFe>yrFyU?QMNrZ{O~E zdSmV3u39wLSZXwu+WyqXqS0C{`X+zsTrIkKl~Xz!iJUDtR}E4q-#T;V46-WieExj? zVX_IHK7uk4Ah8h~v zKwiW?(GV-kn7;=vS4i@IE*srV89&oCmZJw8hZlru9LXqN6$F0m;eSffMDOXphw$US zNA`pyeez~4)O)m>u1aDh_G0v4NW0nob~ejPHJ-0Y{2;~Y1AUMu@?DsVQXWk33pq(w zgt>CH-on)qlY8iF;k0}*LrCF#7!53<3qf!Mnm9S3>vrHD(Zza!F4p1pfFD}f1JmP1usnlQl}>UEsvI}y0851%m##Iq zWpb-hzLDo&p&+ER=r68}Y+I0hRZCUxPs#%R=tr?2C+}C)R&RCn5(k?6G*Lyw=x7uu z+eSr2lV&>+F)Ge=Y}po1of65eGeym-!L|*L^3xwJ#% zQo#S+LzxDcw-YX|+vmf1Oyj@6{kTP=WtqRMPbQ`HCeQv2qsJ8Gy^%L6$|I3`6y@i_ zy^Cl$+*^{2DiJ{U@%xmxUNxkIWNb<%HyIMT2GCalytSMR5RriLqSODNZm0LpCXUI1 za^x?L5_u+u%^e1k4jz<{u~benm*ix*+Xt|a2XZMUTI39QK8i@x*8%*EWmK7pU4vzK zGRR;#fDK*46c3#873>G1X{1l?;Zt39Wu2h<;a$@?s=(wvE8zY6NqZKQ8*Wg@4)5DH zIQkDJo!(C`f!b6j{?=upYNnFNIVLoB)5#@Xtq7TnK$Lx9VuJ31qw#S;`98?3!F5LP zMgN%0fW^xqQ5TRi+R#JDDdYMmf@l2u4b_aB+Sr&zc537My?=Vyw?{O|g9lC3QO$On z?hf{TcV&ee_>!!Up4MV3%LvR~Ly8(a806?Xs4($?WNjCyPYLxX>3dcWnpRQt^W#34 z@&VI}ym}R%PgE!fZLRIp>!}ZQNOSLmXjNXtDwOjo$&mQx)LhwhOdBE)DVKszHcr27(GmV@Id6q zGm=PDz9M6>aWY&cIZhaB{N2aG5t)~YV|QN1Pi6Dd?OJwLBT84To%AD>M7T<{gq>2! z2Ad+r*fbWG1Y`=)ePjEs3FbM5&6pvh&GGedY4er&HMcC6Jzr)BgTeas=vqC3r=h{z z>^l%nGKKgZ=~BDaBhN8kM!Tj8P0NlXDyVm5$PKB7RZ~cxP?QsC!Bo$TNfBx649Uub zP1VX@yDK$YjE`vxieaX!4{EbnXbF|3fJ6aJ?J8w7(pW^#RzgmE&DRH4yo#5jW`5VT zQ^nb&U)n=drD=}S0E?K$qDh=Z+E0$iax>f(BK;f!#F3U?h4vu_(7w3RtOGMsRbWMrZVNCYJpgdt94wx{f-Mni8wMauhF4AAQO_VRulB?t@f>@)&#XtHZQT#wI_mrsQ-l^D6E8-v8$~$xD zN?zmdzn^Sg5{JPKTb#P6&6`lqGJqNA%pNT(Lx)tt_pBsiH;Q)n}0Lx zs9zk8xxkRNj^|-DmNlB3`)uJN{#Xb5Im8)!SCyk4Rwhk|tOh8l>k!bg19Rxgc z;dS@>0@eALlMdzeDS|B&+;CeFLsjn!k4GK|eS`euHR$17krxik^h`yw_P^*4uHIDN zDvDdT3!?O0TRS8RA=SxRYO0g81WR|MRU+lwXYt1AiSe;0-n+}*TAc1^iY3_LxRAN7 zd1suwg7}=MnK!DMf>ROIFQ={FHo^!w)SvWIynsFp);4k>%Fqh2U!Y<6bbSqAZ>xch z2(~ZMz|mjS`Rsv224&Jj9XwSFsbX<1;wuF&Qj8nA7pm=;dn0;t{fe*}&z#9?W0t2I z@nXa)D1KzNB&wm>sbf3l2EOM^&(D#b*5MYpF1N%jbLY5U;oi?ZL28*|Hc=us2&YCq zgEXCBMEB%wgr7iA=gum>KJ)U55>w&5Ti zlZ=t%1B&9ds*+S~xg?cMB(hIoOAO-3VtK}W?Qs8GiTvq6N9QXY^K2GZCvt+ISZmgj;SYdDw1-7H`3#2)%ryTjc zD_<7HaYLKX^)d2B$8>!{GvYXU2c{a#+n64+%HMsw_cR%dBdR`Q7*mooWf(KM8c`G- zDT^=vyEXhIJmt-1_P{E$2L_6$%)E7}k2|KS7kK5-c(Aeuw!f>X*-lTF60^?L(~nGV zN~md4+r1yoiuikx`Er!IB=vMs3?k=@luC*voK4fDTq$knx-u(Dm!jqQ$oDX*8+#=7 zh+*4SxoPfB?g8$#WHfy@_d)I>+$Xs&a9<_0R-dE7a530ZOZLd#ciBNcIG_J%y(;K{ zIpXCZ4wK~qoziLALc@H2n6%K`@W9(Jxu!Jfg}FBQ!2SUFbO9Nn2PxiuJ{}n=PDCw3 zmSfL6$@WOWx(o^H0?VjrbSLY(+3{Q1Zf@OKlv5rceSL!u=`-scGMJWNI?PMmG0Abx z*5#z8RW02fq;i-H&OzqP<>wUxZ}6oJQlucahJzGPnJ#CG07-U70BTa)fu_lv+rn7{ zDdrK){GiU;=k5xZ{-7<&r!Lr?PA=D3&gE>Id`6xz_a7Da_Wz=3rtv2^QlgVvr6uef zwP}1R826IgLGDV9^IJakgR4&QE%0||apj=-_{?J1x2J)@XO^huC#?Y!>D;OAaN&M9^GZYl{HiPKxG^s2p3F z=G_^01WvENN=s*Giu&1T$!laMmplf06wJ7Z`j63x=JgoSv6-dg@;3~sbPFmzWpzNzcqG&CXrBhJVeFJXe#kxwK3<5+EVUVne* z3Kf5xM+bBxX6S#SX@5c!f3Im*&%-T0MrHJi2-?lnWOgYI4KRV3eJ%jV zHD|Fx7nn?tBuPV7$=R3^m9?1e+f&TOz47@(EI7@4xBi(lykADkl#2)$uIBpqt-(ZJ zq`X#5nTUE6(bX3RDYWxz2j{ZM9qH_jO2Vib@k-~|^^Skf!DDChs+xxlnjI#W@4Vvl zfsEO%gu|8gV~gF%)c+>3!Pv-WXpdXvBEV`UvE{RTff{F8;9?p=eyh&^ydTBS4Udk6M~6M%tSUb~bqm}m1FQuogQ&okd}D0L6$%x4gXE>7v!{$FN!((r(p8 z)3j@@&3K^GTc#@!@D6`OpYPLj%sV;~vxxn{<5Wj%iZX4{9(ziF;_Wxy1)lUbd4$ii zNF*x03nU8uwXGCP-iwERxdZ!)!G!9VcVuQHoT)7>w%-*BM^VBtH`77G4#lM-_ggmu z{aCKno_i@ZFP>YZJ7D`rIOX(srZUAZE}c?CPw`R`9c;8jM1 z5&-3G%u7TEsTAJ!H*b6V%a5bQl_mX(uV10}KDPP2o45QbTE24o4GSkuEZnd?GMzWg z@p044U+w$xer&@EmE&B9A0K$ZcwpG>d%={0S^|%9su9DKi2|4;8sjyE0tPywq?hPK z_UA@IQ_S2EsGHp=O;JWOW;D0mGNdeyNs?g*sTa8+tsF9qnwGEyy;`2!KORk>E}g}G zlnTjinpDp+`6S1Vmefq>W{($Q=IJ?e$~!FyT1FAvq>@RIv9Hz``erXvE`$V;+F5S> z6;Z0D(Ne-|*Di5nWSQF4t_8ee=rc4{Hn3$&eRQeIL8S&zQbWH&84t+w*73=rkIJH8 z38wV>zb~02AbuPIYi5RglGeO(J9NgL`WqRB{tg?{gEMONT)cQ^5=neC*ZnVK-GW~Da+Se+l4vc z*{ct*2t$B6AIv;KGbw>{x>v&u)wE1?Zd*y?aVS+vg)py`w$0Jp2G;f-4Quztcv-eZ1)?;B zWCBby2vAEU=_vV0iTw0|nk*-N%Tg89JaE0D9)%I)iu*HWZQHh*mA;=ET#l;B^#@E+ z#5!ihM0JstJ{+BwZ9f$9ZFxR=n4Vo!#hCTP0omXsgkc+^I8*t_Dg z?FT73bg=rk>TEeU_C$O(k?>4KO_+`zGA!kZ#iffQ%PXUQUt3yPT3R{BDCh^+7oVp3 z=k46RMDrYAU-a*gLC1vv-~=;y7kXK=A4a|g&Swxtz6Q{T`b-SqU#LA2)N3MVa(%qm zdbQ}H?;Mz)KQMok;-=h6riFSuxllSzBxMOpvYf>cS+YW=I;ol=OOhivE6d~zCJnD& z#0sUNzmPNAH)q6<?me3W_*p-BjzwjQs2uyZrY|<89GY z`n9#Gk}SnEE$r0-_MyM*(l~rPSPzxS$QkA06d8UiW%{HH4#k$ox?>%pADTpEg9vW7 zPzk1n>cIUdaJ{IL2?@DZUm5DulGom{E8e{A5dUMo_q1i-u)H`EO(719VdnVVtz%?x zwWss@#&Pj*5k0l1G`TqE{@J01V46bXek}am%=osk)zls`Mt7HwwZ@|v4O!Oe*oia?;#lCY78+MUX~BKEbAg7~QMW*9lBaCOv(^1KuB zv1sHZMMM($m71(>*)-GKW8&%T!~sjtl!7K&DR>Jn)6=WdrOBO>1*~hH7Ez7Rh~P{* zrtfKzD#oi}y_THXpF7#gOv&0rRWX$qc`KE~fH(AyiDsPz>pobyu_q7Fdx=`zV$(*e ziqU^kSF$5^DD_^a;au%F?@b*o9;T+J-j5%=`ayI*O&=~EB?ZX&1}PA-k1+DZv!nnw zTt8+us7)Zx_Q$CHNKgeqhI;$U6o&(N4CY%E#$@0%p`DQWEfOi7)ITl`K`@6)jd%;1 zoeMzE{($a?2CGDju%sAs2A0D{L?Ica7&(d;f301ckX1M1ZY3X8o~X6UX@1vKu3^Q> z`Dq@ZT6YA@DGc8Un}Y8PW`tdUUhCb9Z9{X(Khnu0RVo`lvx1B$tXH0U(o#yK0g0AU7R+m&-@)HWlj;2Q zbUr;vrnXwUrj(ON5gQ0O#WOLa{muPc~!*`bo)=b^}O|3xEYi zwSZ9&)HUy40uCb7kN;`~M~s4Ccudcdx>=#%n5&n)`QFEPgO>~B#TPz6Q>JG|`)QJE zxd{9c*1zx{$g(>ZcGGFsuDdjC`R4EdPMC3^AmL;>-@|j6q!*46Idm^~Bk2>P0{eC} zdep3}IVwHJxz=;m(36HBbl}aAn(hd9t~vV1nj`e4HHRnG94$Oc)g6~jc}d}k)Vd@K zFEITt8`LStM$EtI(?mUoHve_{DjBY&A)lz*ja zCQVgPNXryON$9OKE1H>g)R3YdLXsjFf-2#OaY5q^F?9SpmECUdlROf9jhB(`2|RC; zZ$+bmBrd<8?B3^5cf_gSQ>GeGEzy(!8%h+8-ceM6hvP;>${R%@@sV3VVt71syp#*^ zvfv8jPv&i35P41GMZvdUK<;;0Tqp$=qkO3>p3x+8`YiV%(o0^#-3bUOk)d^*2v)El z*dpVAg{FvD-Pn%27R|8`ytN>wQguCleRc+jGH5_D$yf(VI-olqgaQDOIP)i)++L|~ zi_D(bvGa6&;@GA#9Kyabb#jvIrwsCL8u{jde%|cSsoC1jS~!=TFO7M>Gon~9)r47M zh%!v{60=ABMbQPb_YZcgauS~0^rkR`RmZoJ<7Wsax1mim>2VQU-qQ+{l_rVO!038`g)Er}u zqJu6U)KRt1SL*heOVOotE+p#w;a!N|{JwiO6a90~TLsY&k$BDZyns~(k=? zq3f?9=XFth>mE{m^F8m|%%fe0d0nt`X{5 z;%)|x(t6O3oPp)Y_50zk;HffGX}G}wALv*8HI!DP&V03pgg`qG#qn~B<%GS21 zbLSSva>uY^lKx5BiAnCR0&HcQuA-vW`Bi$`W(3Iz%N4Ukq|bE#u8cW9pNEwR1iaFg zdwiQ|w@rwLYEK~a5}I1+CMwP*Cb{j&^#WG5h>`I_@BW7AtPUH^(}TFORq7LXgrW&C z^YIQ!-PfQrjwGuU_~d~VxlhsouG1q@=msF{XdYwn44DZq@)yV$VDs7@l8!u-otVyN z6w@^0wk#*SJ$78MXY$h%S*?-)fOb^|fc8XX$&lYEtMc`v1wNpu^3J=cyPoBFUfeBs z_5=KMK0C$R;Y4MC*%`6Pq=|Rmar!khv1@koNt(FsXr__tQ&Q6X1o}1k(Cd%WLF}Q~|GwFVux?|GR8;hF*ACkYLYiKH0AadeK zZW&gi^1(t3nSB6TK%~E>d6-NrMB^yvWJ06XUO=`V;0#l)fQ?q?$jsygOWSkgB{&H0lLoZPf&ykP9Qc2_Pp;7#f4r0^6wX;kMLyA&RQVSy2kr@cG~B_GOiAC=-ENQdImm*8pse|ql@yLVx*-kqg7-TIaTa|iX=$}^{>2F{1) z8K=7|G5`I6-#)VnJwunKI-aJX?`WEr>n%&FA%o?o_a%%ED)`lk5?7S##uU695OqSx z45J6>bWleoLWpC&LBWR<*S96oxnLy{DgFvwoumD>K_gMv7 z)Eyz%0`+WbePQ`$QJ8N~3+1R8w)gd_nb#D%qbK#v_4;PA>)48x zH>$Bt;<``v z1x>z|dEgvs%M&S3BMnnkO#@PqyeGB~brO}{Ir z5rPm%n_n&{3YCPGn+ zw>9I@XuP>ymfg@qVy-(8vP6S-o3X4mMiO}6T)vgVL^T}KvVX~phl`pW4~OHn*86yJ zaaxdekH_QVyCq?IF&X)vxot9*Y&DTQb3;NEk%f$0C>{^x43d!}1#6ITm+aWy1k5 zmNKDw3iTO4*W?vg<`Y}?nCjNCy|>Y5U-Em3rG~>Z4}MstuU>w`^|Gqr6EA0n(uvIg zSn{{}P20w{s@CZ}KLmfDAHvA3MJz$K=M3wA$RB<$=TV7f6AbXlgW?a1ActhW7=QeYh`j{QU0=F_GlrPq?!I=EX*LhAGyiLAi*> zh*mjEG_wV6H(2FKBV!|5vsK~Y9%DUKo}4=N9>aEREBc}Aw(JM9TeBY`pW~LgNi#}V z*oJr+-L(1s{9lN6(@4mFg%i3hhQ&}zPhi`hZDX`uz}tGi7pX?#i>724t>lSEGJ9g#GkK*TR?;>qdN)iF4R*6Z`o1{3OO#brLwHV-biJK(?2w(V zhy^8oCL7xWSk~002>lKukVD)x)U*0w?iaata~}#2WGsFF69+)zL#LWir&k}ZYu0#( z;6htewYWB;T5lBnwa6)Tf9)mQQGolO2orM&fv^K1$tZ~;#*1iAKpgE4|7L{wKOKs| zxq&=9GZ2m6vbtIuWaoxuzpk8D$X{%PootxgD@oU29b~3!Bx%J6jr7t^$Vd+^zJCPb zd6;}>ZUp+dGx+gkDvmuc`10uy9A=n5KTg|_OWs%qut|bN%xN@O5C-_;0O2|?^)f_M zxrqC;fFlDdMnj!9uo1&oKCf9BJ$s{MN^wcLF|Ql;#mjQ+8zm{O8#iY4jHNBvhTiM- zQ+YT=ir%=g?wFMAT%Pq9y{uu-%PzqWA-gmGdP=!VK7D%tKih?$z`|L zs{6%;RABBFcpy`3L}@%+Hm~L}KXZNJMnjAtwfFT`CBB__RqvvrqL^shm_VO``X{q3 z_2OR**km9tD)O9&a?3_nF_!5+L+KjJ%*3-Yid4+vz^S@LeH=jU|8=6hs9cIjayz)A z+*xWxD>mvy@(|KAb<>@bXJ^1=1VSAL&qSFoiW@yec|?yPs-zF2WtE5}G$OJ-hu)u# zZ@cM{S0zxMRJ6&*5d3pptoyxc|6%{JG5eQmpAeVl&pvW?&wMTG@NvTur0NbS>D4P~ zbS9o9k9#LE=>@i#$KF$DntpQu*fFKFN)1xqM^vqHYYjEcoD3$C3If>_&N zp(QYq73&ZO4_L>oAfC0}{AWF4V)eg(ik8k0nN+oM1~19BZ1B-2^#zi6BPW}pD)MqB z9aYDot1G5+aTTn5t09C!Bo!5FxmKP?bv5zF4L4>Os_;eCFk)^)>KDTy#{`?A zr<$gz`dM;$nfy$%NiHvwpUL{<@}^{pUL@W7aCg~pmPu)pBZ}iPqzof9dFaq&N;hXh zs;&rvqN|}9o)gGi_X+;BL`DV#h@Jg466#R~cExGLeAvM?kGe(}S>%=9%oJNN7ydti4kvoo^`U>C#1 zf;bi-aZ@}*4oDG#3={>600oH@Jqk^ekr|0DGcrj<<~qfcWJNB)j^oI&OIqEERI-qi zd?->`Teekk9H%5zIWkicd-IO&nb`v*7*~MVKHj{3)35uz|NZCpOUeZ>U0GSdeB%X0 z%9^-XKv7*%&L{7xepHpkT1}MIk5=zWo>wr#z#Xb|rl&gwFD z0Ox2e5@%?db*U2gL8nP7R_br+1gLatS5E*zq-HvWj_x0s~VlMN7(yq%h3h*UYvQxUUG z!IHiG)bF+Kg<>?xL_H(PW5*)UC`N3<24bRk@sw6flp_gIG7D|nIi?G6Mj2K7zh;u* zOZa#O3i>g}ZWl~ROhn2f1?|+uVnPH~#EujV7&%tLQ(IB|-)2G{$z%EhJa=B@5|sN5 z{A^lm$|JARu3)AY{I@YN{~gJM-^q((LgB%3y8C>sTPU9^fX7Pjs(uf`?^WMb5{f8w zSLgX&=fWr|Qodkg=OwNM4s6kzB-;@*-%Er$mJG zR)gWuQ5l>F>MS_6Tq#sqm2z_*2oO){)5VRvN}mdgWT~=akPu|dZ)y0!DFQ<6x?*`e zkD%*?Mf0+?<6=uUi0rlfoBuuYo zXqH(wj3B1wtZDMDh;Z!$4!P=@cLSpXkJrWK}z3~-MZKXsv zZ_iOjLgLf=a~hAm#wGEIAQdVCWqZR7CBj%R0ULLL2w0ltb-qA0Q)75@muN-7nrVTT z{V7QTmp&~?Pk%*{{`dnBg1DLV2(DIOFIvUifQCnsP4Sy++#I!{5HBR;6iIZVSHw%Z z>!8^Rvl?1Xko>WUSAGWb+X@ghK*#?{<;kPwx4Rjzbb0@0dP|vA@m0&{Z|kq;utABjdz!gYe|PDm3l8 zZtQt@w6mu-VsqTE&zi+m1D##D()aPBa|jQLHYp2JO@)OXGiMB$KOl1nbbd*U7bT#g zScDhdEIWFHl!>x9T=ojFYeeU&Yt=c-s$3?Dm?)SYv5LEq7RkEVDZC?R1c`v15>-8M z%2_k4RWfCm?sNmMu|EMH#!^F)E6|*MvlXUFCh0T~r2|Y~>efR-QNm<-kuf8Z0}hu@ z2oY(jB`WB1Cj>!iKhc&1;e@?Qpi|TTTf9KErlg2)qP#Yh=t+W@>&_u{xhEL}#m10& zdX7$CTSar-oG3`W#8kj{vh`o5{{JjfYIuJxNhVuFZoS>V#dd|o_0ZdLgL@5Od!-P$ zXD%}#n9?i)5W(M#jzuT%Ef)?K@GT!eDnPT6DNJNOL6`MUzj*1=i>gvzXDSXh`o<(b5EF1`k-p*XYU&vX|&(eSdpMaVnk)Xz)1Z{LT(&uo&zu zSK6G9i#W=-BvN~mnH&!k6sAet3K52@zdTzeNX+-p-G0H;+f=#N)=hp|N+jkI3CZ{S zLDo;`woU+q)#ECG9Uid~bD&eE*V2#wnjXJE)O;7>`X9PNG%C2iJ-p054diOTe%2L!HX#;aNDFtO~qHH)Y@@n^um#YM_)O3 z3fhR}=7h@eLM;^jQ6JY8JD%raOhvk9xwNc-V*TWinUDE zIqSh^=MgVm8KoUFpejoO7{v^-A|kP`j+H+1ep~`b1A9(FrsaKXI2W-&x%ZJR*u-su zHrELLK!?DKZ*a+SFIVf~|6r}0^n0Wbbb6~@`j4#_gEkYGzbkSRcm_8)j`DL}*m|3` zODJEOr*YrxXJ|Zl#mP4bgd7$E%gMDK``Pu*DudTtr3W3rZEziY3h+Jj{55hTYaLS1 z=@EE5c1RHJavh8CL;t6b6_&tMyH3z~Lc3_Z9%TJ1Gk1;Ie2dy6C^trILv0#JRUjiZ zsDt@|!scLN%(j6iDB&BAC@DKsd-*>lI?6~TvaEaBGBg$>l(|nyT1t()ux}+D>m&dR z5&ZV=uzAM;EKjU|pMvZZb?c#e%SdEd^Ymr$vxFw@=3p2L7I!IjXQjmKU3l8HgXkSBWRnO`q8f7@B4B3u42^jrLMdi=D&Whs zTk!}=mh6~+O_3B!`ReqF2QO9)LEZiAh>iT0lX!FrMa@|}nXaMOtgh%I`kuIia*xhf zi69!xe4z}VZdYUO6RAIe5`3PcIfnnmqFup1H@^tCpqkjFyez&mJ9 zf76akWc(bVZj&uu7St+>X`3=EJ9)br`TLPDiuKaruyKtf(04MVP&we?s8uwlgBIJ$*vENT&zKd$~N^XD?6owm6 zR1?B*mYTtc%m`kF4^zvzEKEF96-@*F;WW=rWnTX|^_jpdfw=$4ok9LmJ@S}fwneLCxOi(U39)-JIFAEYSa!${^P4SJ&B!fZREl$KNB`qn5 z5h=NU#Cf+S%4$@5XBX3UQ@0VMIcPCyq3$ zF-MZ(d$gn`>$8p=JtdE-#;#pQ1I+_yjFj8CMiK{vjg=H5n{&z@hrZ@SvA~Kr6WsD z9TtHN9!k#uF)u5{Qq*}UZsf~TB6vn55qp9zrAWebn!C?%=Z??02Fc=w&r_1Y`|H%v za(r7nT$Dr!%VL_r5>zE!XagwnMzpYN`oxGXI|r)!r=o`5#-l`x5dCIagpgOzGytBd zkLC$eUr6ev2f#F$*J+{=ouG~T|DEpIL)$w&g;WxwQk~%#pl$ds_i|klg%U>K+-wiM zAt>;o+xNA{qQRzI>&!sP%qMQ3odb^7s8|&0lp$+ALgn83w`~C$gV2QrER9cUOVb#CT`bqHMJ>^-&g7`k0T6D)|E^q<@uzj=0PvIu~|bu2_{C5l#l?AYRfCo;1zq+8nLo3 zApHreS+ZWdO5f$ImeHCTzPM&z>~*?}7d1K75>75mj@k+z5m%PAqfkI&M@QqDl|7lw zJ#Lkn)=PiMlqOO@Yst6|ZR-mIid!cXX9wIOl2?*QEu|Kqd9@l%V(H~W@p2cH7C$FQ z=x?r*1=Obi2iL#P^SZzz39L*0=emIF_PX7N0`yz@cnzK3cb{dj%*uv!YOgNopvU{4 zU@SGr_2tQdPjZBgzs=Syt8d-nnRRP<_V82x>8S^f%ukG}V-_aCd+tM{srbs=B>0!L z=O!O`=9vdttIN$jIsa?PyDN%z;HsRfR;{^%OozG=;I|O}He&Ok?~(Z8)QIXd@Z`Y) zH;Ocq-I%AxaWzTta(O$}EM7L^Iuh-Ck#JZJ@Q!No zSfY~3YN{$JrBocL$x_*|;#U4I!Ww40Rhcn^??tF-mS(Xkc~_C_{3gE;m-=PhEaL^8 zguz5?1a67UMqe!4G_8eyL!QGEaZV0)|LbO1D?U6_NuKl9S&j9N?cIl~#k6Dk&zg>zh$_DY*0S}z=|pyeW_SafV%pl`MQ)HU{-Y0sKXx;3yX`FWP+O$BIZ!6 zoaIf=@dbz^n1JFpMEt=A5`uq4n2_)}tkQQL@~peVZ1Rk2eWPwrW)rkAw<+@?;Wa^b z^W6?o-V2y_+;imYBb#oF3w0Og;~4HnWirlckxl<)+2omvAWVs8aMW2@JH zfY8HIBj4Wjh&6icJX`h7k0R@lU2Q{MRwdp#t^f&F@gS~Dzy3#38I7K2Eu6nLN-952 zDl5k=7U#0@4;z2Z3*Zx60n7AkR#2-2gUBc|EZZ3C zwv2c)ZnZP6`<YA*!^J!4p8MG598oL=?t^r_&*Eotlsn74 zhkKIy5XX&$QWlmKY9YFIjX5I_&od;EVy}-iy4lAbX)a05JTDmbF{>d%8!Jf9p)3kGrKq2mgbGlfbXbGXbN4LfngE=P>d5;$7>n%X_wM>27rOrt=SS-;h!|7n~93W8r z5*Ytv*}uUY4WrcM@EJCC@g}gxppDYryo*rQ?P^H+UKIkn5UBsaAC9ra_ zWUc(dM~->f_p#c>wNPf0X^(|iT962wdlgGsYh{}3)5 zuiW$UgBp-Dd0ssEk+++eg>1a-S5^f7X@Qqi@E*D-9-!OD13Vt(ftT)N~H==pM{vd=K?uRIe$>e2<9<8aU(jP5icYy1#*c_FKBY9O$u$dGD>l zYkOdG1mAzrbh?iD9D5+ClIR_gLkefQRMa#Jvn3YPG zLG&Lg&*!Om)Bn|>lR%C6uk!U# zHAV&Ycem7-sdQyDPXeoHl=W3=6n%Nf{os&SdY3+kPH z59lEr^fx>oIIiy;q4}oQx8LL(y~+1m0r{=B`1|mU@BdBj?@gXp2(mbBpcyJ6-(7o598Fw|ddq8-Ly1vF6T){?Lzt-Yx&^+ME4acc%sd-=!Vq z&M$A9A&uf&9N}-4^1<5q`eJ_Ap<^(t25)@*7RSw&jsJYGD&=nZsoo@CaBgd{PowF< z^K8B2`#v5aQX3X`XBe6~#ncJl(z1ZTD!C?T^;N#-7VeROCO>YJZmY;T(2Emc!HIqJ ze3|hvo#1q$dT4M=j-aR45V8(i zl6>z}CNq@*zab;>KGBf*#G)jF7|@PMGVfL*cDY$9jmq-nU~g(ae8^q2%=!TPDd3T}|<|_$795QIAtm8w5P^ z7pP}ksBYu>3oR{iJ?fGJeqw@zXs!7_wl45ORI2gHzPb19mNh7zm7)TF!2;1WOH2Us zvIsSK_xtAdDSW6$4CdSP_SF!~Qd=zvS1VUoibs4>41zk_!)dL-apiVFKrp*fEJKhH zRUX`?f9jepY)NPREx@Z{2EcN0B@2-t+^(2{e(h6wDD(Gdo|qI@Jm>clyuvx);jKf0Dduw=2KdfXF`~h9IAO3uDzzX~`HtM-< zFV~^iHCO-woepm1owVxL==K+fwl!Rd8Vmq&a-nnKTU~N_ZZm$aMLn@XPspLM?;#kD zfeIL~Tg|P^KoM2iMF$)o5Twxq3TR4#jR&6i`o9kj9>oMAT#O#ueIWPP5EVH-o0`CN z;D{uEuLTEpPX*pFxW)^!-875k{sA334%ftV`Xr^%p-1#y5ITd$+98PSCQ5CB&|c%Y zp~9=kppTn@dzptCmFt=;AIBX3aaq<_OeVf-ZDB6xUT~BJ3L4P`ct{kr5lj;I(ri4l zxoFo$U5F>64XF3Ie*>>!@rZhR(Aoas|0D=Knqg=P68Ke3d$_h!|13&pXq?t0=&uXj zKc#=sZT(w+EBynxu!H^$O7v@;TkasG#jgiC({Q~#LwmU?X$;zOzE#WD-12>X$5HU( z{;JvO56#|y86$sd>~z}`0XxSN46Teq**3Pm@c%!3EbTZFZncko_n+3swXH8b#8y}b zzY9J|$LrCX&quhGwls@_F^Rtt*b2(pZ9;Z7^-vUY`1msTfVR7$H5M(BA>M>g78lBP`aYdBag)AU z-lkWr0KG*nUwo;2LF(C$fTQ$Gsvid4a=MVm8eAx{MFXP=V2Ze)t-$YJJAHbezq;@A>1(GGmlOUv#rgnUiWLGnxb*aU&z#viH8wW2Z~F_JD8b+Gy#Eo=^~je+1v5^W<>usMb2)=qBBY2svSN;@ zT8XS7ia~r7xvvjW#9glBWrRk)Ks`4ab(j$trkg<7GlUBns(=UJgZW+a`^vBXr5lMr z^PW9T;Epe(BPlz*du%c@@?fhYMkOI-e_ld~qWJMMseD#)^E@$1?F}+LmLP-*Wz^XBQUEE-;6qJE`mcm#B6KR2RrM2N+y8&$2921yG>hT@Q3w z#1guPTOQz+_h~(~FBbcr0=P6BQd=YSz5Y3PiQ4<95a?f@J3g1A%J1&d{r)AY;JQ4> zo3Tc<-#?}{zjsn1Kogo$Rr&o(tCy)_>pOid`J+^;PM*{M1v>c`KLB`~V_;-pVDxsL zYUT2*I-cL=D+4zZ0|?wUc~$|XYyQ9azlCWk<5?h=gMoi zLr3YlrKaUy*H9(9tlK6S#qcj_m-*=)7KQ>u}M3RgX%{FIi z$~mmb{(lnT`FqthEp)fZ_wV_+Yxaq5H>tdv zZ*|^%==1eIiI2(N8wPbe0S+?$JSAU0imhABuIjjL{`P$-=v<=hIzKJth3!065nr!K z*XufNs*HL?_k-<8<)Sh7e3e(0;y#ojI=a40pEMSG8cv)q-rMq&alY0?IU^Q^Ns`=> zdOxg@Nasi=}=D~5&y5;A-#>TP>`jYTRd}bVr+6kiZ_FUXj zk72C4uJ1U+t8va;2dVk;U|f@&v)xOTUJw0$AB+D)H<}xk$EzI9j$7k@_8d5ep?-R8 z_8QOSUDy0i#NHNg+VS2waNg&f`;b?wk;q}+J3~KB6#8)BkLxSS*=ZJE^?4Zbt#b!? z&XEsTud6^aee>T7PJV*i0TkIoEPkpDha(RZcCU6`d@vc)^Aq*9WhqUPqlxi zb~bjrpP)b0`pV|VF7{lD;Y~Sh{e-;d7$0=3`9E6R@qQq@wRgBreN-LlkJifQU-Gr% z-f-to+zw(lgI`%Nw>yR8A%QK&>moICY3HK%!LI)ZIu+lUdkOaXLGZo*@4l?|5c;y7 zbNi8>SI;}N33=!^*4QA91o(r0t2l`7Aq(Ws=AGLUK`e$h%h$?Dyw7#}R#!*hbM`!r z_pWXS`?uQ)cLdd|ehy=-$H32e#u*RF33;)Smw_`!;0DS<@eH41uCM7^o$cyh<(JL_ zF0nsZeU>Q?x@xV4I|JuM_wBh)B91xkVLto;4!zgf=Ukm@|7H1!HEib%)R_4>)(6IM zP~Ur91-_G>1Ob$us`=?8qnXv|s71AH0%b72D@x&Zl^g-Dl{HN=nz8a&>`d$B}I`6;r#mhxUS{> z-sf5CUiW>kwZFZK_`mu$d5k=HrQ#<0?{9WtcY9Bh1%Pi2|Y^US4w^lXhN5xD5qY{0U^{7%osIQt=sH?gsq8j{au&WOh zxYoeyj*>!MO+9L5M1YFge5oVu91~I3SzYz@@Tw=j9#8Absqel%tOo8I@UH(2=x9uDV_r7qK@+u2aBYH5Q}<2xMKp8XTuyVmntQ*ihQOyqE@3_yyv*Qb z2F)$ap(XBjZ;ohXPOZ$WH9uO@(5916e~-Mjc(yf@c06dOS9>|_dD%hF4)k>7OGjRH z;$LUH@AK@!+b%SBfqy^z2l&_(udZsko{8v2e>a#9()ghF?&jBn{+{ODQ~g8yeYm*5 zv6sE*Wlws^>&=fou>10$FJ1lYK|h-Ns~KF+9|dQW_tAKaaW;mI$Mkp{ z_E<9+YkuS0jg$L?+$ZhZc>a#Ze*#?-ah_<-lRPKU@RYrI3coD8vv{0E!(_OV@t@4^ zDe9;2VTztl>yxc68+JC&pW){-o>S#abwADf^mBpJ3|eOLb0%Fgz0a~gvuU5h*E#2& z&gRmV<8zLg&*Q;!e4X!pK3)s#$^!FSXcrdBTZGdh{TDl5Y&VwBxJ2y|UM=-Aywuq; zKP$`BK2Ov0crBN^oS!T3SV7N9yS&o-D!#6=^Q+D61-`A(a}B<0?bbRoTc`F#KD@-2 zm+jbkeKxd<*yw&Ek2d1FiHDnbx*6^kxLa`Bg6BUyUy=6;JzM#`4gNN{uhRb--0kM` zI$m$k^M<{8)6VV?|1zt$?8IC6zpX}k?DW5P@Ol^iyLi7Re-|FR^naf>AK>x<4jhh!uP@neSV?)7Z@k;J!uBNx;tf_r}R9n{xq!9u+P}JGtPdK z_q+N2j_V)x{14w3XVw2n)1R>aGM~Tf+TRl*iDF`TBuN2rGLm#wB-jz*HFwt$o8Y$O*syJ$xw7cYqjih*9;aNg{3EXa#f14h+ZH}a54Y4kgQt(UD zUwVBcWpFKnUm53Rjz?0qihx}Xw{i;vjN4la{L0HM?{j&W73i;^zM}k!v{!Oo$+I%f zm3xc5kyLS4HMhXGT0Mb(b$QkCtD%1lp5DQinr2W_O-;RO>0QgSw!1p`*5O%Q7u%?*aB1z?#$4LKy+>Z#q5{u$X4@YB_PBQ#8c9dJI?~oj-%e)S8Si`fd!PI+dUla_ zKR@p`(+A{qA3df?v^mxp=^m=BvvFS)&W+s7RH@UyR7?8oo^ z_H%%l46uWKuOtKIJp%I)&q4ectp8wohUhm$|3~#2YBz@RahSOd=fMc|Bk;?lZzTMY z?nc2H1!FWFqj7mm?c?@oto(7j8fWfL@bgL9pXAMW&k1@>ux}H2Hc`zaXHVJJESOn# zCQHA`FsJZpie6K^KaJzldS|Q2hUxc3G8La^^?z2*H1n9YJ(B6PPdCRI<~9TNOn0;3 z&vriB4$tvgdWo(tSwGOw5MU+=k|whi`U1CKW1 zwMoq;-fV`k#oYW3NM5lAzABTg`fgLV&2GGEH(%r9Yd*hj?_T$OgTHUOe-q{oy5Hi> zTYP*Qzqj$(N#8rp-Zk6zVDEyx3!nGR{R4aAyFb}&zjpKKBfI&rnor>F!R6oLQ@isS z&3k$HADn&XC!h1^3;6rIe@VkvYQE;derMm%_YF?I8tWrsrpTe}Q$f zi}3ta?^9-TO5JI_&-j`6%?|xW-|uk#@Uwf?{aH9?asAWY{^kDfPLW1gu{qLYLZs=? zNYASf>G}D^)<`ed9qEOQBF)_{(mc-d?1}WE?U7!b8EIbcdFA9&pKouZmllsS|AI&_ zI~wWb>mx1bz2M16uhG+Scyc z(Biu^y{C4hZF%0-4BGV+cy}lw=_s^*>Gv`&>tSp#zYVl*#CM~9zU$IWeAwLof3ybA zJOFr{V_;-pV1C6I%b>sj0!%>62!sp_4q!e504GWTu>g3St&+`d(?Ar3kL{#V(@lS9 zP*4}M%8%4IjgwR&E2>Hn3pS_~V(TPsV>QlL_B2r+0Pz?sSh4_~fd^p0f`?$q3vldQ z(FV~9q}Y<@o0;o#?wz>+4y;$Ouza52SlB`ZZ-i|W@j=*u!cXA>D%Nx1BC6IW;SH4S zqHqa^_FQ-ikL_>5+t_pVgm+MMI>NiCIFax^V?GL(8S|CyR$-H~Zk|cT7CgKZw(0gx z*ugV=7B0ZE9tjum!Ws&1;L!RmT*7PnKzIwT{a$z*Rr`nV4oXfBjTkpb5aJRO7@k@kBQ!V;xHrpJPOw0Ll=FxqPIY{fgqIVe>Z67l=$TO`iy+kz z_wM^ip_fZ0Zw5z1nNvrLvG!Pl9m70Z*s>)n^)oD=mEE8&uw?z zqji_i;oGLQ$Bymhr+dHtoZp1=Fpucxj<=~dDAsAa-f>seR}AMtl7@QLSIY_-wSdWXCG&IkLzY!0F+`0riZYsGmV2Cc&NW4RiYiKta#O|qAdm1l8*?;V z&&;kEV^|5!CxKc7!&<5$of;ME_xHRw)mp#rHyL z^H*wfbNh(l#>8pA)w*C+{<11}q@#bm*1uildiwC8CRuB4zX17;>uY$NZCVAG)D%*@P8|0~Ho)3f>K z`^H@*+p;B9y?Ryhnq0Gg|NZ9LNo)VlAN>Lcu_lESB$Q~0w&;ki=!w1Eepw3~?oKWpNd8rnsuOnz*{ShPbAm$yIpVdCN95#o{JQR30!Eb$m|wm3(eD;_HzCmt`JAf71B6HgLP7EcjR6;BgS z7tavS6weaR7S9pS70(mT7cUSm6fY7l7B3Mm6)zJn7q1Yn6t5Dm7OxSn6|WPo7jF=6 z6mJr57H<)66>k&gi?@q+hwboiI0mG~h~J9eiQkJqteqjO&FE6hkPnTDeXUHqbE6c0MGv!s~)#TOX zHRLtrwdA$sb>wyB_2l*C4de~wjpU8xP2^4G&E(D3&XTu~x0JV%x0bh&x0Sb(x0iR2 zca(RMcb0cqJ7?`Qc~^Nid3Sjac~5ySd2e|id0%-y>C3SUWGJaKQ2EZKPf*YKP^8aKPx{cKQF%^zbL;XzbwBZ zzbd~bzb?NazbU^Zzb(Hbzbn5dzb}6ve<*(>e=L6@e=2__e=dI^e<^<@e=UC_e=C0{ ze=q+a|0w??|1AF^|0@3`|1SR_|0(|^|1JL`|4VBmND`5v7PYBEUFuPv1~jA*t$ zE>BmW)9H$I23?7+Ojn^Z>8f-!x;kBhu1VLTYtwb;x^z9dKHY$BNH?Mz(@p56bThg+ z-GXjOx1w9qZRoaiJGwpHf$m6mqC3-F=&p1(x;x#2?n(Eed((aBzH~qGX-olyq}R@; z2~8=Ym=4j55=tqfeC<3c=rEOJsG^#7Xii6HK}YHS^Z|GBArK1q9@Z+=&AHHdOAIWo=MN5XVY`&x%51GKD~fm zNH3xn(@W^3^fG!my@FmzucBAeYv{G~I(j|5f!;`OqBql9=&kfNI-lN7@1S?myXf8Y z9(pgmkKRuopbyfA=)?38`Y3&jK2D#YPtvF8)ASkoEPakXPhX%f(wFGV^cDIleT}|O z-=J^Ox9HpS9r`YPkG@YopdZqY=*RRE`YHX4eonujU(&DW*Yq3uE&YyuPk*34(x2$h z^cVUo{f+)k|Db=;zv$oeANsFaQ$k5aN~xA=tB&fbp6aWC8mf_6R~u?mZK(rlTOCx# zspHiN>O^&tI$52fE}$-`E~GB3E}|}~PF1I=i>ZsNOQ=h#OQ}n%%c#q$%PCJ?UR^<* zuCA!gP*+k{R##DHs;jE2sjI7NsB5ZgscWn2sOzfhsq3p7s2i#qsT-@CsGF*rshg`? zs9UOAsavbtsN1UBsoSeNs5`1VsXME?sJp7Wsk^ItsC%k=se7yYsQaq>DPN7(zO4ck zD!q0i9vt0$-@s`J#7)RWay)Kk^d)YH{7)HBty)U(xd z)N|GI)brH~)C<*%)QiK*Ex>RsyH>OJbc>V4|{>I3S7>O<Lco->SOBT>J#dd>Qm~|>ND!I>T~Mz z>I>?N>Pza&>MQE2>TBxj>KpBeUkAGCRHYv$y6leqpweaR_Zp#2`{VqGSH;HE`e5W| zp%>*z7-s=s6rbx;FV$fj_{qpNn!+z7N$BoYCeCKcHKY5vUj>nO#_1bS%9YU#oNna< zRgMX*yuvSZ*_`G#G)`{Weh2pGS~^zNW;BInFwnA!v$&PkCJx$0`(+TdFz~Wcrt(Y0 zeW^*Dq}?j=62F{kSw$U|V^g()NC&f46{p$^+`V3r)MnVoX`I!jSLAcNT0WVyO+3x~ zgn}GapQpMNBzZ7XW$uUFFrQ}$gjSrv5LBtdT$x1{bon_vbY-udB>uF|>rzB{1~b^n zlRDMh`JfTGqh2G|Mf-3WF@Rr%!FT>Hq$E+;1 z?ov>&w?(V)v1p17s58k+ZXepqmx}GFQCj+t4yqy6ai`4PU`9*Dj$pv+=>>5aBzkDq zbE()>Un}P%JC-$)eC)$U0om>~qd0x{N z?kaU#A1UD4-5^hkQX6wX9|;ma^;aIJrtt*UqWuZ%w$!8-x`%&f{RdaCuK+;VG|?^V zrbGCp&O*OrYipNyN}hJ`oRk*}%aiGuC@%^ItpE41yP~96Dq3aA%p*OfM9?!gQPGnI1cz7x~GgS4DL?uCT&wTvjalMIj8OPLe1uvq7NY_7gx+r8h0G7C9`< z&HlzpgEfd1y)IVI^4PcB61lmz*B{u|;=0_|R2TbivdcS7^098uOPv9LD!{L419sp~ zy>S`qN#Gmptika5v@FKKp|TmHNFBC-3}!|_U11`NMbF}4ki>;D{*G?DsZp4C)3JzB zC+;^-;8|rn(^XV*BqvQ!M1T!*k%JA;JOPJ~tS*?wUHTc&^kKx3mKQqt;!VEEvmreu^9YEYNKPqFG!G zCfpf%Iivx38i6@#tjPJjleesw;H~TN?h>!IG)uhN#7n%Y^N9Pd#W}kb8)vsNJ9~2n zCMVQp1^{cjINP>M0WZuk_qu<$&a0*$C%SpY7_$t}>8z?{Qe4}bz+y5=b<~-{;R-|= z_}Vy!Q>=3=@hos@xJnn(0DSWk#et$%>LOXd(vo#JlYTWg3=aanTOy5 zbg5$#VVotrTvyvmAX@RjgU7Vl`Pn+Au`Z6im1n-qW6I0E0}*yL3mOIp+J{BOg^7(9 z?TI^mV9TD(_>29q!kuWsgH!sz!DYL}^hRNAK84 z<`?}UubC)99E3UCrgY6EmP1>yyWkgaZETyiZJxU}<~q8|2Oj%|by7bmBHzBrXhpA! zO{b-Jp!4bV)dskLRWmS=G7j8G_O;Z719t;y&`5R-Ch&U%GCq*0GnM9f)?r2!?`Rs= z)BRJ#E8Y+DY8hhZk=9ATF&0>X5W~Y7fj9>kYE)MhBJ>h-mP=A+=(z;oEY_!`AJ$Dg z1^56hrZu<;P5dLUgxGR&@IDAs_;81M3?7GrK@bcUcs}9fz_E_Rsw#m!p%3SU&AT}* z!b#5x^SliER)WvyWW$|hrf5TpJ7rnX&@F}=y=(XDFwUK5>67cy`fUN#`7x^M1E-ZFCJ>9{?qDFWE|NK=sA;-5MGXp@m?H9$6(~qXdIVS6tV{) zZ|OUxfJgfcX#iF6u14;=6WrpO@w9xVM=P?ovrmiKnXZA`eb+nkLW2~h zgemvz0C;?TD_OEiV;%Au#914FJogvFv0vf#g*O4j9lun-1b`I0>`=|_HB81fmP&JW zwA6K4REtq+6GUsk2&J)yser=|^D&cbZwi7@tTzf$MpiMSaWGr3mD+SD)9BJhXwNY8 zZy>`gU8cs2uv*{K`F%!vDrTU!tMx2N;{efjbd4>u$)E$|*g2-yYDs()p|IcN*0_GS zNAEDNPSJo4S?!E=2{O;?`mSb?eowViy{B5}?;_bjIs>QU6bEs-$G>cdh9C_Z+H1|j zrZ3CXfwP!pMA;?RV}H7_q8LQMP6W?FVz=2KnV)!m5WpTI=ZVcZ+C9!hTn}tj5VC1nNwKvD>pc95jYZmk zI9yf^?A6PKOR5ob6hLp{@nOTE%C0^f=kvs&O_Ffc%b1}(;>8(>293fi-golMau6qG zwshnakfyA;{BW&x)-;z~j|xEnnJ$bbdA|4X-ZW1pRvJW1`4?V+WVCm!q8`_l{)Y$(k97Bla~$R0iTJa|IrcwE!>=Bi>%2l)*b`r>f7%vLP{ z-&M>a1@{^>;AfYNgQi(_(k^&(RjYs{nQdO;CDdbCnDn0T|Fl^nD1c8otZ{b4+{Z>0 zE|_&pn)51l%z_D}?l`M6gB6@qJF%a%_+WZ-EP^cvx97H2*gE2s{-VouZ42|uAP1^$ zMoYyJ!yvDbNrrjmS6YSnjvlU-zz^)Jhg;sYxv!}<;r?rYl)7x$cr$X9@<=BwU88l##;$tEr zw9*7d%Y(E^le%bk5#Q&!umr`4i0Fs>hTU2?am@~I98>hCjS#zz3_th4K2~Nid3%48 zt3iwt%S3w|%I&IejJdqcfzzuvmbRehDHHi*X1(XYo(Q+a+HeIUs<{jwfQ1{*%Lwygmr ziRny0>mx4wm-)0i^?_P-T6FC4X)~N6DRCUb&kn3Ap1r*fTZ@Cd;M(0Rp2xt*GVC!q zw-;^fO78M@s#Bz62lt1FX5VHdI?VkYy?u;sLxyX!CbO|3TT}r=xTkag4;oy(!y8C2 z&OVn1;ecQX8vp>y$r-{*KjlPL114bpK#5if^eflx5tfi5@zdd^Q=`0$kLKtkY0mF( zS(U-aASeN?fH!&`mP|>JedWjvc%#9z%nVpPXz+zyuI0$m?nY0WGc7;mgezy_Gto(> zLXU9Tb_L5VtIix|gS*>vhkJl8G>$ zTGY1D8IxK>tuQ`%bg>zh{-m-Ma#!t9VOggmJ|Mx-04YBkG}dP^zRBz}5P_i4mK0^s zln+z?xdUr(dHJ=zO~PINEZwDaY|q@GSWH{;UA-lkf(M@lV!Xghw|0)g<8UaHY4 z0DZQ}QVBz0E7c2-C4frJ!5aB}$=fmoQXAx>u6Dy$BH3qKX5;H|iZMZ4SlDDEi^szz z2eAk-ay!C^xABgjY&b2{0427&;i^q`3`loSF1q~7%Qa}KcixVl^>%Hw3CLrM7CDzY z;X*ngcznmefbfIG9w#2?XMK+tSbz6p4(b{n zl_7liMd3)A@~b>$$kGEFS^Z!sNLMdTBTDH@SE=wHbY7 z>zfFRKzf$U5U@1IkdnnT&j!urKGzc=FD$dj>!M}%`CYZ%ZnkuCEGuSJVJ-^f%AiAL zXy-PccwC5V0nw_cZ;uukM!+>2E&2Nb1McKcCUM|dpBOE|GV9v}FrVoA43lGZQWuGe5^{ zBlz&aixeC7h!2(-Gt+Gy#>-`+<_m;oJ{oPgmD4f^X=Y=&fdHFj``NNf$22-%H)Mxj zE@tS1%l6D7^QSq$5N3`S@8cN9-V_Vk-ia}{{_-1ylxZ_w$y>n4M-3v))v^`)zDv2e;NNqisWV5~!ZS|qIN?kR^JyC<3u9g9&}oZ^HUf7-*r?7k zS8Wf>4&oK4C)@-)uQf$BRkg8hDev_8Sv`v}jL7FN8n_PFj$)=1EzW{+AajX)*?sg9 z`dm-xMCD>9i+#oB+qK&q@V`-}K`6@^I$;RN*#SExOorJB;0#N!X$g;K&5)hgw2{?? zY~?FouXJpT&r8SbLDYe6;c%z437;j%iZl6vMGTj}tvAyJr?P4L-t6&^vt~9~+-_#m z7PL4W@^=>NyEAMr>4qSpz>KPEcLrihd3-Lyt%^3l1Jvh6^JPkb2=Z{t9{Zbbo zN?aRMpmfvCN~gHPqO->#Z)3F~$>-f2-n3k3zh(0@HTK7g5g%+K1`-M@iSx+kB~jxf zpYDB{5W?JdmATe4SWy;QY}akRJFjrwAb{y9S1`-%y?V!P?TPCQ1)m%(1&?!i!%HZ3y?qT({2# zku402HK(1{%y>beU@{TF zD!sldanclZjH?-RO`KYxV`hG3jdK=rPV9a>pi#)*KqVZY$fUsXET3D$vsC~nsh@TE z7jbnCaEGh4odt8?ON@dAr(L^K%ikz~c<0&BI($>47>rB%O&N>@%Y~UWY{DRk!SaeF zbnlimg0e;ohUWvrj3zyu_9EWK2MsS>(x1j*m~a-z#1w9gL=rAN+O3?hJGC)`0F55w zN5GlIdK2WD$FvGqU5TWKccpl`2Lak5lcWa+!D=03tuCa~-o0T;mqKqw? z^v9T2%jPkBCuaQ3N+X+Uk?`(f)d_92anyZwVBHPEn99n?tivzZO!9D+l5jTZVa(gQ&NH zBf)J0pwC>mnEQYN&WXL;E!9IM0<2BF3Tm{w+MPrLuI<>~Eh*x1}*!$qsOcmK2?`@gMa zsL>&)t3U50wbo1O|I0F&-doGiEO?<>t+6Nd|3&K>^EC7s-J?rprL~r!bSPXa^pD@f z{@Z8QZEtHR<8}qnp~haeXFlQo|M&afT>D)6?l=AP!IK|U8pIbV@i02^XEZ^g+r=gM zt-OR-+ryJ?{=3^rF3BaEFmfaiBA^5kAS`o`FjWYU0E#t1AVkzp6a>_^iIbpKaf2&3 zc7MgOO8dc1JEcqQpwfO^-pO`X!>+WX1f0EGs&2)(heb8o_s<#a@D3VQ>>^brdC5giTByRC=pDX1Wd;zmJt z@>z}rsGu8Mpk_H{HlQiCv`o{MHkNkTw9-l&>+-Iyv29$}bt~IfH=S6kRZ6EdQecQ$ zUtm?_&M`4*&hL!W1x#)VfIcH%7;vW68bpRhC&W+0NX~?2zH(-cc>es~qy_`!GKwN2F?^`E5LBdtUyv zwhP5pj2j{IB~dAE%m>5I=hXhCcBh+sG&D`i0Ye&WI{`xx0vFO|L!IZ(eX!VATWfpI zJ(3j|ux!~53y(l9?s*<@0707A$XcUX%~ zu}W&1()TQ5>D!J-u!)#DoT=lN`7fQ-6-?vCJ&nWU=$;Mv-tEcpxFH`-Rlih{s){dE zC4NC63L|7;1eUFm5SEZNrX+C7GWHf_foa*j>kgAV$?4GXy(-DV5*W7xjR)DyEZFUy z6gH*q>Kd!VT$n&Us-I-dXiy~(A4 z1SI68BINFwlX7$Ba^`Irue6l*vUL9jC}07O#e=1RaV!cE$p2reer*8-cZy-{FLzH` z&3+cUUaau$ef3K9UcD+-6@q{QL?Mtg2$B+#WD=xQD1daK2+9=X-$U{z%ATOFPIBmf z+*t%j$tES+lsd$)o0Wc6ds1Ac)uxxTSkG!U_rB5%Xs&>`fE1vX6|#I=lF#4I-x<4O zFG*Q`?%u3n0lXI!0*g>!X>b7n1w)g0PT}ud(h9~@jrnf<_f_iRcF(_%hy9bnxrm4p zG9es75UI`T9kN+;I4F*t2~@C~}96o07qtsQuUfqFWCH^~UzD$7xSdN-1Nu(3Ljp zC;1sT#5VbV7;}*l2a`yFO>7)dN8WLO@5ciGb?~8^D-Zd&xj317lj@J^k;*1M!#G;1B!KdfW8yE}<4ha<-kD8wU z$OJ^Bv~0ls&4;$_b8!;}28Tw)s&R(5{Om6(NQ!3F?Qr-dSWZxMZ!_rMvcGi8wEQa& z%j6gGUVB{sQ@o_xVLESz(_K)!yli^^*XeEfT>i|j&${(qZoL=A-{^9~G%x#6-hVAF zSyrP7d6EeeC5D^=0z+W%L<*B95X*IDtBaQ}1c4?}*eZkF*&QNKxkBj*= zka1IX8#jCZu>k<8L*L>T3=xH$cSiwn#jW|eToRK>xN(m(5rM)*dhnEs!q%<0Niwx>AtS}* zIUl|r+6q6i@mY6T^H_`ilg;%o1^<}6&kVe|G~_GA2{ z$1oWGAaEQF1)h^$Md#h zRi|#ags616t=Wvu-BEx!=M&!c8K_?^tl(F!~ z6=KB*GLYZMkg4DWQW$MnT%g3k}@GEaS$CQ+a& z63bvP_{W8lnN}kSys`Xunu|abI!3v7@>btfo^DGGi-?1%IMuDft{k9BUQz;~YV+ag^z`3($ zP98sY^o`f1CQAqTr(||AOIeA%Q2(aLlE`x%OOqvm=O~iE5Imm`O`gTJp{X)S;24U4 zpPrt#%RCKP8u+FrvJh}~`q-;S%Egx^q|bl$e9m+3lgA2|AK$)wcsCpSu5HR9&mvQi z1dgSj@Ahq(V_(Ej2>3YLY|76|PZiBI3OVJ=5%9AmJif3L00gRQ!Zu>RUV0oXd~3_; z?7_L7Dc=VJD*%258hz}sy|4X_QIQh}7#RgdK}A7E*60`H*!N+Ekq5i%kI5V%?Bl$L zEPD|#t>oAfgF|7IopxE!@QrI~F^;CDU3hl7mZKGxF;^d4gXhZfoDD)z#QN5JG)+(m zfVv_La;P|;>-_4*6!jlrZqW@3UVKQZiM+4LkxKN#?nkqFax}+!ZI+hw45BM%2jboF zjMP{WXIW*J8UZB}QiU!}3kX2jQ%-yrE$}BUueM4Q#RXJ)o}uPuk&9rn7&cRx3W%y+ zx&(aukr(+~;#Ck)?h3G~09Yz)IgW`S87!k@jij&6D?raF8Z_ZvJ&6_g#x~6q z*@_`|KmNJkmAJ-G#GTbN=)=gl=nvknvDAoyDcF~PMj=kMRLDRePK`p+vkhq>CT6N9 zXuJv(dF8b@d{+&3x@cfQ416j|(XrAPPDenZS%IuZqFYmA8wK4FgdN^sihFfNt#;b6 z8QbOTCQ0J2>mnVP3Tnl=@tmoMky3gf2##V)59&nS*Hx}tct_Izm8Q=rj}Z==e^-9+pA`>2$;)_sF(*28`YAkS_4#LC~`Cg zdCzfJuoAa&5YLHPjAsSx*6!4=bE2ckcUSs}tbNPSPp8^wBzOMyzxj)QaFxF>rg^cS zd>j(RTkpeDMecNd$)*g{N`Fs+62?rR`SOUnn<{(pC0sD0T}(HV7n4*>XuidKumtRW zpX+SvoW6=%DCl$dBfPlRUy^L3c|%zft}HB&ml}(g4XeIwunl{Ep|3ID5ZBJf2!U7k zdjTwuNSt5!2gQk3Pv5G-JswHc>bzH)DICu_=r7u1%L295IFcA4Ph?(3ZWb4OVpJ`n zq^fAKD38zsX?G4;)}5>8nT(Y$kYLaH2qa~C8Ex5Kom{;Kn4R!h=)_k(pgN8PmOFuE*O{WwJQbQ ze+Q$C$u=I>T8p*qIV}Shlc&FGw6)Sp8>?!fQx(>$7AhGf2($C~=npJ$(;!ceKuWS_4zjqLM~Z__?e%8qx>q<^s?EB0*jm6(@E#}7o+CG3K_R)6`~Ana z?_ciq`&wa?*WQi}9T&|R5+n)$S8~^F5AFhpTQp0xd1}h60OjQ|0OO^Tgl4+H%w$OKVf8`*DqaPl(mI zRGr0GUQtCB?-3-W+3c>rD_`SQsrI8H&S+h`qUMWwbs%GN)u^q)iXC1V^$)j?OQ&EflZ<&b}_ewF}tXO zlAtVqvbu&bNccGAc?C%2&v|2I7HrDR`#ZZ?uQ$^N$Uo8VQt0KD5t8=`$`PJP$sRK1 z%#)c5Ef53=Ji~#CoIn`wsS=1&N<}#!CF8BDh|e%tyF)sQXUEbw5usOp?90k`XUV$| zs4uiOIE@szM~-7;BAQGnK0-A_X5KLNh^${huGPDv(WI7b=6yc8w`wwxTIuW`7V}x& zKF5efF!F5L?xIAOMyph^i9JO%N}-)b(0?!eVJi`iZ-#&5f}uZ|y<* zsCCgAy;Q3wEoSITzehjmfw|I<|Btvi+k=LYA8mSdD zi@OA_)z^H*qk=Oerru6^kfe$W3qP%TvwSV}KV}p#eF50P0=o9v+gWA zgBD{#cbe^B)!t~&9+Y~Ew7vyLTMJCQT+6cy18Ff^%r~P< zv6vaNIon{HeYIMyc-?*ne0NT*Gw7?rd zNb|>uL%Y2(F>8%VVvNM89|0z&%e+EbE?6Mr<6FN1J6A6#YhpK-`PgnQ5QgDgJ*QK) zH4@{Z0v#}c=Bx_f&ZRS}&+jNq5p$ort}RA^?->JY zXvX@i*|&T{mk5=GvJTNtX34-~p%juZPZFC1!EoHF0ul=y14~^fJX}l%a-f+;8Z)6? zorjGET}kKgxO}RVzs^e$j^Q>!03_f^VM``PAXWCq$ub8-e6}Wfr|DXbF_%uu$^m%@ z+U^lq5H(eh<9H#`&OdXB;o*K8=8?-;)8qOE(}UMPj=Q$$H6(#WQI#ZGZMB-lWBl*rXD*zcm2{E5(kTU`ol#__Ao*B8Sj7E%%gXjm2Z0sja3sF*P zw`+u*h0qWc3meX{!&dE=78`t^IIn#;gf<r5FL5?QtI;kvjqC*JcsE=E?L7Y3%Zm z=D4wU|Cs$6ReQHO4j=@*QPnG&^aAPK$F!j(3zTM!m#Uo9hK%!=`G2*e1uY{&n&Zha zJ{})Wjz4vN4D}fTq`nbgP6O$6@CPgjWxMk}8QM$$wO@ z;E+EQIy*II=(Do(f824Fz_%J$fO&Ya!Y_$q5bNp?j-O!QAKx+d79uQ8`LCwQxh7x* zM$Y?89CKnZpYpoM!}iy;NI-5t{JIWEy3Im!1`Wc(T(Rs5F>MH&Qd@_#uszT=A#Hmm zR4w6|mdXy8Ac}?Na0cQrejaO)jRNh5m;2vR_l!MzWU7`2$7902FTbtuF@TSC;cdo&<2QuvA1P%7| z&|{>LY*Q?xwm{mj3Zm?gfczsPP+Ve(EyoGioSx~Vb>f&UNC)H<5Mt_R3n0gf zT`+z$**VJGDV6|R2!(~LDzlI1cvPUq+DM`2do^hNC-uM_*?UIhH$Hwiw;>{uW)HSb zK(h{^|Fz{xV+g3JUgh!5WL&v$F~qJlf;S^*vDf%c2(CGEzlhG5Q1~2OMoE0axWj#i zFg`igG!A&YA4V+p4WZIHod#uDz^|AOa}^gy*b7YUH6}X2M%g?MqH1fvyv|}N18C_V zVgVSm;*7N?F*62=E`9cIgtrltin2%zX^2iWC!5qy>K+i8es`;nJoai#46W5{sQ2H6 zSpc^X_ux#BTH&d(UJ!kpd|Cx>Af?JLgVsQ)Cz%W1&my)bNQgln$3(afUA#L`XJTmt zcG#z!iiSS+Kda6ASl%0`Mid~&+*&4~tq=$X&G(4rsP-`uiH4O;CvSn3U;FWJ9BmqH z%dbt1#j@+Q;z*@jgu_w0su%p_R2C3eWS`LZ8#(2%jkWbbMGCR>hOYA8J!Xz)!iWoA zhru$+ZzSN69gg+lnv5Z`U)$KWsl{XYUe@h#g86gjNv}nzSnJ5@6`Sb=h9K9XWajKw z?G}f;8C>%2q$3&s${ugvVl_f}^aUu11|&)~^hI;kS~j%8;zG^}%gZUF%c*^>BlG`M zwz7X0j5Z>TLP1Em4`rcBgp%r36QYd0fV>#0 zBMT2#$Aq+N_Apx#4#5%~8Z0LmrDZqnSH+PqLL5)PkLh1!gpt><8rGfm&$%RQD z0d3H2wuC(cr~deMCzRotoi>HW403fN+I7N!%$uSu{w1p;6v=vZn{__9H;i!BRmoj1 zmbRqw^Xj|alIqRsTcsuCd)0R$$iKmi6Kyq%y+*Y*vm?@eCSVwj1G}x%nvyN)HPc4* zkJPB{VCycW-u4ejby8NYTA57EpU3@9`gC&3^uq@^+P0Se(UF$~8%766Zg;~z#B5aioO&kRpnNpBkw zCNM9Kuq-^Gy`vSw({V+l7bV06JGK2!lXE6rDXkLM9217y(4TydAIAo;W!W~LX;|Y zKuK-Jb9UIP^GhqnvMf^1l+2vER_SGbt@>;5&60>~@4w%nBFikV7megJrq-3#mdwS~ z6St;&%GvG>ks$zMWT1!yyW-KBC{cp{*!0lkUslFF{vT~yWDp8z%H$!L`X0+>qMH9h z(u}B@C4SO*dX`9ovc<|%oXI{*>}N;56x4jCdIv>_vo_v3T^C-tTNRaZO94f!hL3bGG9`(aIb#!0Bl zHx9f-HDeEk_caH-iMSW0oW`lZ~3=5lzhk1i0tP0lBWQCP7c|ebLe6M{uq3ma}j{6F= z6O~~;SkZeoV@p4))UI|vRE@A0@c8%~>2=hnwH&=+(Q3CzSKW5-##@Z*1sA7woOwzs zh9RMoh&)crUU;(SKvRnvQeUXQbIwGimBb6aQ4$)LNqH!KOmHFvG-h0{YhSeN=|j!? z0C9~0p;Wpq7Xld|PX`>XR48lOv^ShkPPAmCd8S(+=SV(%q*ox zmw2S@i0(SM+*jeiNw#cc@F%flDLQ+QM%A4ncL2rl`nQ5ojsK;W_hyiH}Snh(PBfxoZy#;_H7c^1@ zKeu{oC#D9lYstUjLA9#e5@t{Zh}48=Z)8$HH@9Vl!V9(y`ox5KIS+KTH<55$aGn6%aImK^FCb7T&}!!_*g|%R@q+0lcJGP805XkIlZ7)y$fisoa zXEy#_-DdRI0phv#brRraf@xX7)B4d>w#!*bLl2+K z$%1+E)U{cwBQ?tS)C3`LIZtyll#NfeRv33CV~1JhL4NGBX`9X0%1>;y207sYzxgDTys=(iey$$b^mLA3 zrZ=G!%8bp>Sfa8e4=O~E6+MnneiPb zJtQE1XCKoY=@;vd^-eq!dsblp)E-}wMVz`Gm|}AQDS+^WA?Ka@RxKb|ktrTlEPZu8 zgP1!Wd=m3WgN=k=+oM1NK#5T&B@qn>{7Fk$vXFe;+gwT)Z!ah#Udlcrb1`4ml0!C+ zD;MH&DN3n`HpBXJ?+$qdl^}oNfRO~L%HOY~O9i_Uox^uhWFRW~D z&Q(Udpx>jr56G(%hfor|cBu#s(&?{=j^my+=4R|`Kx)TO#3teD-u6m^kwsOrmQkuJ z-ALXq3Ij?zG&`UF-Pd1z@1(}IeIzp3Kg=#>9J<5w7b8Eo+p)mGlS|pE3`_Ye`=H<;&|g zIV$M6F8R!KWkrTk3macaY98>hkN&O9=LOPZ1viB1u_FfGS($Gven|bEh`$~s++JMGi(mjmA>4f z1j|V>1%>1|bpPM?Ecmx#yg_IH9EN4i(ewvUICAKh_#34O#MS_D*$p)UrYVvi=%*rDeiVnF(DLxU)|2eu2cvp}5s zZKM{`@qqu}t;&~@b{E@9N)lqhto@WdHK)02xT`5RF`n3C|)@4nW|ozv2&w!Y=S!R zIqC<;+OO((FC$Ik87l8^;^iy4h@!h2sK+%Za{{W0YDzMZ*niQv4qf!*Fq+Hpq84%*Irru8Gjg(Q-*7)Zz`w=r~p|8M6(Bt{pJMOs}h;^o5Vkrs_lYfF*mM;Yo{L$fTFlSnm5gCLZ#B*n+A z&O5~!3MA$GB}S*vR--9KG$>>xwO`&~7-$Cw6MCGt77%VY~zJ@I$VHNwFBb}eVvwt`Wzx zCMxU1N&wZ-*AiDvxNB|4Z8t{1ZVL>EZyd%pj(aOGNAAZ1vjk^*r8B~SXs6P_6uz;kn_jvRz8@bnot)2%3DUla?s*A1D0P2o9$@VQd$LncB#Cgy< z1d$KVzci0X{hEL>M%tuwMCi+(I82X^%^e4u8sSws0xV}WNMESkygfK=B`WukAR<|X zuU($h)!p9K*+B;%JRBOhe{T-7eoq(vFYK=uXV9ipOwyOvL1Rey^!3Q@(w^38IbEl>EY?+zaS*~l%bY%^PD&3OqsSt=x2V2jZ^F7{Vxvtws9|kZb09_b2WjWtzYr6UL zy9wO{Z4xbEF8!XOX*rFe56aZ>hbEJUQ#2TrsqcxPNCh&Oi_s1+CJv0)nU+<}8|u0Q z)cr^^458I4@I=P(H6GKR9W;S6Z$;<}gcb3snbu*IpS`p$ST9tUbwYB!YiE0hc22#f8Sb90qm)FsRY# zC*I_;cR?<^K45|Yf&0&WA~g{`8sC$Yk$Ya4fqU0FmiD<=C}p*ye!qDw>F4y8dUoFO zxOQ}Kf4{U7cog;CE1k%px|`fNqk{3ipTGwxPHnZ9Ymf??_)eq zv7DG?dK(0wyZxlIa)(-i@r?<01d>eEDp@lDjK24#Wk>TZe6n>@J%F4v0CPGVCN>LU zg2je}Z9L-T2Y|$<8$lag1UwZj1hwfgDG8WgTQh(3w2Sj$;2-dmEz3DAN5Pzp$=zPq z-Zb}viUmNbgTs5YEU$GS5OBy)h*7R6gXmYYarVT_W1Y`{MSt9U{a1t7aCSI2v*|N> z*>G$IB4i{-$63{k_u>6Wdj5Fg88EBp`PvNZ*t9HD`HQlQj+raL!(F6!V$ik?sR`70 zpxjd1_(&S4>%rn8``xyVp>c;f%0M`%a!_F#1|7lh1Y#o?8XHp5p3k!d9riGecEv7= zlXu3k+yN1U-c+rm-f0&%+PO-rRxjT9Dom!t>C~Y^dOpy|IGq|w(^=l5M|Xfmv@b%~ zBL$c(eU!eIwY6ZjwG8~y?? z!6bzLoK_iEK8K$KM(xKKB>KZ()lt!OU5C83&RMUWW6+OvuERplW1px|+34`292Usf z$TAVBfw$WYKS+q4Gu}e-#dXy8bYrnZpLJu!{}iFzmfH5WYBui^qhG^SxYMW&?=}r( zpx~RfG`wq3Q}maPYOm`0Yv&j3OUL!$x3yP}>PNq+y_wfuJ*Kta(qBHIy?R<-J*uzh zy&-;1p#yw}(D}O$DL#YrK0F-|6{p*@K$?VpRYT?^OR0jOdXS))F}UoHL2kehh6&!Z zLK`!bw_0};^rb9IYRQ!LQqy+3>l+wfUCMhV)%$WCLl{bb_NXh7kaubn>2a-DFZWz5 z$gQ^gS%W0r3O-uf3c0?#3UxXS5YK2U-zZtfF;na}6XZvVf&7Kcl)sKo=s*%$9MrNZ z;TwB)*|T^`bB=7|jkC#@R?VSRn4OUp`%U9T-h4kww?(gJ#6Jx@S^;})^26tCeyS1kIY2xC z0+F6~uVi<(^ieqc>u4fG2L{$J0tHLJF9T1dw~9$xN-C7nQoW8 z$Tmpo@X~^Pzq{|yxv+|F-8ajAlUm6we1Dp+H>tFkvaAUh*N%A^|H|qIw_M24%E#Us zG3%J4n0AgZKCl8bK&Mdnh;N@2_vWweKy$g!-^elro{BElp9JY0S5~`%W zjw5Y`Ylkc7TBV-`Qb0Y=wBO`HW-VSM>X>l2e}iwncGOLqZ;ud-)zkigci8s63wY^!fMOQR*I+g zYcWO+Oo(-n^7cA&=B9@TJU2&Z9_ra4nxPYp*ImWbkbZ`pbd@VeoUlqBa`vKe=4oPH zOKUeyBz&A=#(=Px3N!@dc_(WkU0j7ha$o&X=-!R0$@gd@;Z%1dg%nV=HjZs_4_G|5 zDN`(D9P`he@V>609UiGBj0{6h%K*&MD(HDSEm9=H(acxa=nmH9|ctl)AF#A_NisB6JmQn z!I)o2d9Ly)dHeP*QQ3ZCAFyiDj1%u->vXRC@nC=b!@r>JV!fSvWtpkf)%1JbPBba4 zwch#6_6%G}Id#a|pVH26ZvYGJwy2OZF3SBqwTp_FBU5$Z3FeoH%T zoPO46eQ~uss~w*+S5Qn5FG~tkY7If)r99v(hbp!)LF^w*4;Q@*GHmBS2+YRY$%#6Y z(9elT$&rxEkL`WO_k)@qIKGRP#|EkGiWpPt5&-1=x83(@5E1`WoFq z2=}pJya{5UY)-#OK%0$HxgwNj=|id^ALXbkqM!m9Jjv#mr8n%u`6}NbR3^QLYVtAW z7|UCTma52m*c$6qd<ZDPOtUZF6cfbA>a4(@e#Oe&3n`h3si@tw-hjFK8UzoFb(L*9UU z=OWC;o6Th2d>IA0E{cegECAB8~5B%Tj?E+|9T_~QmsWLFKQwzV<7t=dGj*Re& zfPk;!<_;Q)n*>SZ;li!peT9SwO!%61or0mwGG(LSk~%W?-?Imu_Iv9t884FlS;yKQ zd|RNz5eiGCH9f{@L_3SwKQhmpX1aZRms!dJlskl@{77teGND;fh{7I}zb(~DX}nyl z>96%K>XR@0!bG)9%f+cmiB-#!ej#J_Xz_#mrCDJnT?uUYvqwLQm@X+7VbcXo+g)Mre@Cu@)tr+N=08kOwk4MdRwJ z&@od-V(vJbV0QbVNo~v~fAXP;m`pYJ#Ia)&K%^%AK#%8H5O0-z z9zR=o8dKqQVENyI8e8OM&5>|qB3%hLDbV^WSQW z1Q96+7K?s@S0IvX;&`^hO0n@BleBko0p#~pe_eO~tY6fMRWu*P(}+6x2AO}1l2Qb; z9p7nuO8u1|%P}+o4xI9M#3FjPRL1c@-xrz@+*qrpZ7-g;j?Z0+_71bULdbZ((mW2`dU9)JZ{0w;-Q`=7%L!mKhp^ zI%&-;)-v6ZbJz;B%s$db^uR?!y1e-8d;AYFK`#_(FjLi-DN1`Aw^Jxv;pE#=Md&$f zG7rUDZyAQ8-Tg>O&+S-SaT19mQ{jAQtC*9|h8%6z-i9Z!9K5@c^pc^i7(Sk>7%AE= zX@^pGg0dEj-K8kMv;?VL7jZ5+{m=98CTwBg@Pd30YgROzL;?0iu<3W5y5&GU=5x|I~k-2==TTwa3ERpjksYJB523Dk&?Za*u zUTwYSB$9t!X|{(fD8wy-ZqYm8Ei_?VvUiYeJ;Eh}lIc^7uDp{4PonJ!l119eE2$SnCAw8}MZow4=VTYOY%1)=pF|&Ov zdODh$D=aTaP~aB#zMD*!vh8_vN92u%$wjy8FQwZVTU=S1EqI&tmG_3101vXIkzUt8 zY?eT^mp(YvW}3TO*1Itq31*MHOq%7c=t%#aS(s&udD5PYs`3W>SF9ff;F-XwGgSAi+d^_s-dq4#y8f{4tC<&QuTz+sjcHv zNGE{IW*@Wga+KWK(^=+l6x20w_ZD80j~j=}2gQ>~kIsxIgC=7KUs<*Mwbrj3*YpEw zEi!&`%u{F4GpW9{)_KCc-@nRiJ1>*_RZrE2PCB(eon*QYcAbWS#Cvr9;*}#%BQV7^ z-ZTZU=Xrh#tlZcynhc6ryGurgt^1_jc3YShm&@I1`BAj^atjp5NRc6;?jiHSWH4Hu>RoxXMfu}$P>N>01~gZ*B#XD7T6`y_ji;>G%+~&gblV?Y z;X+DM=A$CSN=Mp#uGEOvr08#wl%h630LlvEMR3jnkYoQo0jDHP>q;MC!(O};uG34p zw0g6_1a$)2(`xZ#5=r||X^dLKu-WA9W|rxWmOYT<4-1HNZhfR$v)U16%M9UDIoyuD zrDqOEGroAAke7x7wH-FgWZ#j^cslH z=A>CbPoH!0g_jRz;iVX?TpE!EetbeAMX-fr=<;;3Wq=P?S|$UkVnZ3#jBbSIHE|Ogo!0p{o7H`f@^kR%m&A}sUL11+7skx@ z%1#JSl1z^`wr6Aph+cM(=rQI{1wl3C!JwCp|YYVd3WK_JG_Ud1v^0V2hLk6f& z2k<~b%k35l1Id~(e2I2+YpTQ=>}0VIOSB9Y@gW# zPiKlpt=FKAtYHM&4vvs2DE?IN0rqdyiJ#g(fgK8tLkjW^>%J)4N?tnYpoYMfLAc>$XLFb**$te)5`N$!1m_!l6L4(??mo()xuh09kh#mL_ujTCD9Uf*8G|2?;_c# zq!g+N`XUON0>oyj%0)@({Z*cQDmPo=B6PGI10elK6s{>T17=~3yh4M*%n_C@6xF!Q zrwwM|zjbF+*y{jv4vSsCmSKFT=LPv$0cq|Aw^OVk@|mW*5f;aFJJ@1&M1d^I(VbI= zk9T%yYW`{=V{jZLA8WP222&aJ8LVCZ2d8Z&S|6o1SML(_9ysR($u8e1+xFv0mY|9m?#BrA2*?y<0_4I|1 z(_)GrG`YqaLA5o>WKHGZdlRs-jU!@*LjHuV#lxo-_v}&U_WucNqx7uYyxdy4u;-Zj z@(CWk97M*`x0JZ}`RA=1>n3LZ@zL?e{rE$+3Z{z`4Lx+Y0woiwAVo))O^?n`^l-3* ztr&iDhx7u7!1#&kWR-O+??xHf#pwPi125_l-1VtgE5NSiunKO0#Reh)M9wO92ql>k z59W2KC^SZSC{I(pQE7{}gNB+kyGxh5nf(OI&Ed{n4oPbsz?B3J`-W{q8=a1-Tvy4} z$P$Vx1)V1nzxWC;>6xG;@?e@7D&s1ETFNUH-+YMxcJ1ZV2DT0|C#TtH8{IbdL>n`e zW5!N~ykKt^r}Rf${XgyU_F)Mylw+Ec1%Cxw?I{hakH7jv2!1(AzYWwSd3|=(rHsgAMxMyqNhI(wJ&VXvM|M)r90Z=DNLVpyPg7G~4#bS%OevR#4bc>SK$wd1 zE-Ykb<9A6PRvL!aftqd)`Dz~5UDewIU4p!f@)b3XdE8(lC}}hfMj@m)Wb`&FUvq&& zod7F9)W19Wts4+uwhV$P+ZtE#KS?RD6%8vfk_u!@8j=hxD#}!~FR1Ku(FD`)*e8_g zbVl^9-gvi&%QJ=1;!{yQlMH37?RK8an^MF`sUBMgJ&swx*xN?QKw0xRst-MdVa6fK z$PXwLI1ISTcZZkcKH;(N{`fP|EH(3 z3aj&UWkR%fEUZ~6^)B-#-7r7=NEUUch3ja?b7rt$O)AY2I?oCy6CVB62ozM5?irlb zG0$q~RqN+K79;!S#uSp(`xNs4B|~mAHB8TViF<9a-{1IIrqE<-&~Sp1?JMMNHE)VX z9=e72z9X@jLa1Bq3uDbZVUZmiLficMTX zuvt}m&eFAwGy2-8dhu}8sjk&Rqb|z_yVpxi&Hc&FmGZX!CRL8iH$_LCi5l;2$eG$G z|BdR>-4H(V}o5hAExRFg=A8S&{m=UPtMIY~;Y3D!NUPWHZ}Hu&Ry zANZz5_yr>LJRo!o6!oar0@UgSku98*WF-k&cF0f;#IC6ngu|zjDbq1M0Wv&77Lb(p zpObej0-Oe`fON{LRx9vrN(gPCwRg2|d2ITMJbY43tBO%_1YzZ>hb4&T$f~O#&dj9Q z)cIoQ@Tjef=u9!ZT(TP;HlBs3ufvkd{|F^RXExPrM;YFY%oz)>jF7-HsFEhC}B<)6odf$g$=ATb_ zZ@u#h`=VT|G@WEM!)78e#>QY8jZk61iAQh4W5YURA*GQj-SkUttv_4b*Rxlb2eR>o zP+Ks4zA?w|N%bf|z0KHKy7r8ihH1KO_Iq&4HRXySS!ZEknfOupj$k6=l6yF6aa#4u zEGV4*J63_vBsk<2A3~%P%K2a5AN_o^ej^&C@b*W!o}jdhub2%QD}%?=+y&r@2S_EoKawln02iT@4e~#tVx$`YitQs+rV4wvM1& zqYlO{#13t7Ae^b)-wQ5|>QhhH#j>hPJI!f2Vg{t2vlJ~U;79B6-%YI+x%LT z^O1g;D>j`ddr^DRxl*oT`_k+gQm7)&Gq46PNGnF7*Z6Tym3UPFOcx1#PlOsN8LV}N z$|j#PQS>niob7KhPJJ-u%lGg$HPv3(%y?O77`XiaeX~d-{A@;cpPwi>PR~l~<0DnD zxRkhJMM50V`e-l;w6&{V4kq@(^>@t7?nyFa)$&1n8OYVBOtF>$5enVMe%V;lT3SlF zi>*Uu40PThZ+OTa@`uQ`EINc%e!t>klqn9=@0^!$OH0qT%HfotIv^?p|MblxdOzB@ zvPcwLz_+I5owOU7fSLPlv>>__`?V!>kPS8Jmzb1-B*8ZJDpWOkOzGiKM(VKU^GQg^ zusv_Gwq=|$7A)QWJ~{ewj@xzW+Bni$W*D|cB~5!0n%0*|JjKAsto324x9zfj;IwreROJhRwpPRbqzx=Ze~W{oalkP9y?RQ1fb7; z`bg6$FP>B76Rjnh9SrF!W_EMh*E$b9G6Fs}$qzZ34mh9Wmo+NJpi{t(#ee^nV|7+h)gFO`yRaly?4UBnDT-#RS{=4h0uTE+#^ zKk7Pf#P8}Z$9ZR6A>RlrLCVb;--~}5{N>x;He%_wAU_o55RrW%=LzakJ9*9a>&X76 zG(r(Tz>*>Ei*nJyomk1Y9sq7+qG(=?=s~yG)QPpFX8u zQhswP(^&P30!uoyJ|@T&n)&`=TS9qU6iW>(&ZN_I;5)_$Gd}MuA+%r*dy>dDvS?bB z7ijA|4E2vn)$piDiZf zH}^X%!iB;(_#KwC6#(wC&(#2rgJrH=PGf83W`ZeuRd9f**&3VO0CK1hCW8=; z`V`P3eob~%H|e577j=TBfaC*jy^<&kqp7^!7^KS3)&<2{!>*GPif3NB;&^5kIA(w3QbC&;Rbcm%^{6gKvQN<^Jr%yM`2JrXQ6h5q#62pB?xwz*r9=P_L zh<$+5T?JW7mLQ1yt#cicRnm%g=erblQ?@!wfXHxvn`}4IvIL~V+1D(a7qM1LRHBu< zmcYpYrwtmEG$8Br7agv{#>22pFLag$0_P+hYwWhMZprHcwAcg}6!qKO2lBsqcrcyF zpGM+2=Ss>eAqz%pGt=xjaRVj@CJaKI&Te&vYb`*KCCa?N?xKRi*{$ofv|$?FqvsMT zb<@5&%K{nMA5v>U`*!8~{^?lyZsxAsJpBsVVGP4fT=V~|#sOc;`Cw4`Xl4nxzC75H zh2ua|pQAfTFFz9h1eT452Z&RkUHARB;{{P_*1Yv-aPH2Sam=J_SF07N<$0#-)-hV8 zqX=YX+XJWCH{#jiguMbYd(eEh=0{u%k}FtYlOK`xaW%)t`VWevat;Y8q{KgvBTP%w zI9>(Z;kNL7p^09TLWoiO;Af9fF&i>!u19z%Bkl-We_OQm$G4J?)gW^mrR$9_)+eFn zAy7iSsF2{l*~^F8aJ-!}FE)6q4q=@Bm}88?%4Rdn1sP)De9qM*^x^IjzR2ur)0@-? zHwN^wA9RHgC0jy%T~u5#O9IVyoloGVYVN1Yn~a8#Wew3CZKidZV|i1d%Ce19(160+ z)i}o1a6d5Lm|C2F-*|QH%P!wYjvP6dQm0tdF;e3+cgDGYEo#YW-!N$3=!4^r7&KzAp&=wS| z@aGa1SNp7D6^3QNmd!0{%5SiK9R&0bNDD2(%qx3f~_$5=2_^YnJ4#C}nL{ zr=#K^W!v~v&bNzwhMkOVVN0m}y=Eu!QoeoBJC+}!ZATd&CP)30r|HW%XJu~r@$5PV z*&ZG7HAkV+6wOO)j4N`ThIYur$iDQIprAojO!nDTCBQ^nGMOXg(LnHNdIh@oXvR#> zF19P<+n!aQEJzv7b%g?(Qps0!A`3e1J>%Y^PWY|6AED&{kJqlg)P``abeJ1!&H=GV86 z-_dQ;%kPUY`F$nz#YJ}m;r*|k_!3_HN{_sP#K3VJhAoTA>T=-<2YD3K*-+O6S1D3c zj@DZ1cX3;hSVjr@4;3Q<-!%y z5=o_j1O@>eyd8;DTmdD#9m&K%$@c9?CkJhtq4BL$NOD$a+9p)J&SNC+B~h*uc*|;@ z8}~8@48V;Kl+zm0UBztZ^?{sgpk424rcrORE(W8dN?{~aLuKM(ha`CA<*<7Kdp|Mv z#P*=GjSj)tQ(SbX9SNU$lRUnBxgEa=^}&xhxg{=2eU(ZTS39O;5I$ji(wP{SMio=W!ozBf$6ilBqTeNr z14+@qbAr)Ufy9!C#@Cp)PWx%&^|;e`;rj@Y7sHv)Ke3AR$U2my=xQvxR7|^F6B9hW zM++s^fXUU$?qusT(_K1&^I&%zi+WfQnoW`pW0{f_Hz*XGs_9s5p3-zVbQOHAx@f5d zgM394x&1@(NKTjSqzxUhf(FOQ9K*h?OkO>)K0eT7E~(lmtjBrt%SkSV)iO$~T!|6z zo!o4G9mL28H8j_F5642 z)d3BO=QqA`WCzsyCh6))x_g{&mnJJ3=KqFd#Kvi{e~fJvC&~x^y_M5tnjhTTF-`Hv z-wBA)m5cB0ksAI~dS5cJw_@*lS}bc_e+*mG7G7-*>|pa~HRHzOC?>TdG1~tH(THpa zUN;h-fWb}omX6QSsIzk=YX{%gRwMFRkKU7(>%z0dt0MqF^u(gb3pdiG*z2R*v`h=S z_I0-e(oPUMzX7#6N>X|Y=%dNM)t{8Bws6MLJ$IAZ*92mtoH)YT`0D;fj!G-n2+Xy? zcL3sGchkL?k$zL9J9;p0eZ!9kec6%)TP^C&y}NvD#^oMca%we3$y|z(?^xjj@awa42BfLhQxyVBA3GfIh>4jHf3mv&Ft zd}bA^6EP6eU5M5hlpdWmvE_r_NmR&yxBp;nTV00(%^*YS=nF*KnDPf%17Qsw>T37f z-&j8Vh08*oZ@zk5gx;rHWY=A|Wh6C!Rg!;~3{nG$6@B zU9|RYxRnu!U%RQdgMZ9M+T=}PYFkfiC#4yt+%JWlT+Scc?e6Vv%lmfwZnApt?U%A| z-uc~E-uVh@b>LaxrT^`5xvqyWPr!mN^}7uyD`P0kRG{uUwza>rv4OT${KPrPLJH=A zTe8Wm4`1J}S@Gw`{-aNRxRC1`7&bTUQFfF7qr=xfFx3f)VpPlHTuU+Wq`pNZ%?m1c zOgmTH7JjC_)NH_Rd?=b!T5fGkU#^%Q<^!Y!jtt2}$xAj3{%#Nexh!45=18QV_oWH_ zV7@^ro$RauHS9Zy6T!!YwVfq^6i}-Q=XXltCT5?!d zjjd6Ll*xL<6z+~z!|9QuqF?leAA5#;58<1>WXZg4vKzui^SMIDa1qEP2l@P?BqaP zZMpOXuGe1Au#+IVWYapX9EUEA|F%{8z>)+ucPEz9%2Hx|{Bri8f2n(^tl;ggEw$yC zkV^c$RI};2^F10}dZErh`3l#?X3-;sbGv$*&NVS(* zt7D5Pw>!1aC(=)*<{~uP%snLMrKal=noLMx(L2OhKwT~8@@BImgnIuiOORSaC+D1M zZ6{wh-a@z;w=ZwX4VQXur|h6YGg;7NcP^-|0!;`koNT9d`*wY}xN4KtC@1UGv+wOf z#i7wtvb=5a*qJ!G?5cNpzT3j4gH(D{JD_qZ9h;L4L38%CqH7qd8Q7kD4gfoD1HWybgrYjWnnEmT- z^#7}s=;{&D>B%COjtuFMG+$JI-KX`J^e`xS*T1?>{xT!}Bb(Bth9e6h#$1py!=NWI@(h!K#1!JO-LGk#uOvyqQSQn@}0a-YMU)>}g6YMOq zF2oU`_nt+ZgW5hZlU`{%ceP8bIAdC5_bY^Qf$KE0TsnM36p>jf9)LjK5ozT)-7QMb zq#N??%ra`*QGUY33SJK3$4iv@nnq`!yp2NQrjnZbwi0;c7z6&xH?)J3T}8dJ&@=3SQbxC_2}H z2N#AV$ud%ME#ZdMKDyHu_z4l{^9HRRF9kFh#*R`)AKq>Kn3D=UGmiS>H6yvuWS<`I zaP6%0fV)s8y*me`MJwq=UBqql?U*(b-V*V}4UziGimaXBg}_{@cbP4zWx7gCOA_2% ztuLEf>(-vslyq&QX1J>wTq~gskpyCJ%VoKEmm)PkpP~py(%isxUx@Ndw;xSu0iQm* zfMVgBWO{r?wZvc69LX4U*%7-t$Q9b_X)<1|el~QQF}gtGxXIzy1>&Eq(fudN?(`~) z^xTpTO`em2kBxxGE)>)7n7)WOujce22=0=>?bB8P+}l#WrR*FPhANkuLybOo%DW<> zt4q;LS%RCh+%11&Sui0@2pA5SulSzpF(D{zX{mI-AuNxawhs)GebOscEvCubLd9L; z49XY9+zE29%3PJxeh$J%xvh&lE%#nF_V4QnNoYtTw`}4+@lQ3q0+cas9AO{etz!A= z(aq!Yip>ffBQZu3g9+|qjggnCwr)(Wo}B&6s?Gq9O2G-^x`yCzGM-{WJvkq)6>Yo7 zC5)8ZYu5CHf72ca4~~sEKtSIY$=>-^9CwWLStwst7WoJv$kEmDBvAv0a@K`Y^C@YT zAiZ%?(+TqKM1=&Br#s`I!Iq0jjcBRzS2bqGzXto{k#x8HKlIb;hxae1Xwwarf-45G z8v@&RFd;1cODboeO0`zbDW85!!uXWFeh%d*qMuX#Zash*cS@=#+484!W+@3y7$vam zNua0-x7L3ZfQ75?cr_&|?;SY+1>5?E5IT76!}HbT<;^XXQf0a+k=FTc!PmFq+`U5T z++q}dQjzKA)Ak{^bffY<8S)j%>1y10-pOhZG3+#df_IWriZ(vJ|3Ep**A3K-vlkD_P z($y+cyc?0LS=l-bn7$ozwq|oK-1@S- z#zLd)H04DMh(t)%Ewlv}fQFkjP3Fz1IfoI;u6KvT%5+E}_`Y3(v@X})zObs)x=x|3 zX|6BX)L{sfj@4yn-l;U=pY*QmXG^D20%}4U`O#}_hpksG|J3o_DB0PeI*0DXOnkpy zY}R4CDe?flGRFsLX53cZ_fWa-CGX{6RIb37L)p_?Q;JZK{Bhu%+^9W5n!_IEp^f7) zDaM7MZKP$$f0rg432wDInp0)&wNeVo(UAdD9{tUpF@T$w50?)v9jFa`L^P)?Qb79U z!^-l+N_pc%>DX4bbYka-lqEV&W$qVa9`UikEV0kYH>!bN z+SEPlH*g}KR%}&sGdUM2k%uu+QBPbp|Mxmk)Vtc;3B{M}u!RoP$)sl&4=&P#(@ z9YfC|(q_}0k^|D60pKZOKcIdsk0vXw_6RWGQmY}RUw{kqvT$*?r;=EH5l)<7qXKIP zH7NCHy^0bMcZghR6yU)mUx^l`EBh^0Sj_rvD6!u^Tmu#K%r~MHEE{9Vnoz_lCX_yocsK134igk}R zgMap&LP5?+(i12Q=1$o}KFP>jm-HFuz$?OT1}OSsmd~7Z^T^{=x!B}+6y>K-PjaO5 zZE)I`)M+bAAt+2EUYXbYBIE< zps#$J6?{SX>XwYvJf)X))hbpb8+fWC$c@z^YZfTEOQ8hG?A7I@O#Fc@i$7$p#r^)1Fa4&~-%|`E zo3f!awUk~;d;x2%3mlC@$ z@A@&g;O=;2QA3VRmqtA%=|89RY<(2kMB^Z0wZ-$R1+qL^%vT;%aL>z<7JOOz33{lu zxa@@;@Mgg?*2llw#prY~dQmBCW{P*vbRIC0cUn9$GD)#FF*@5+7hMOg|(V>Kt&Rh-*twyrqOuCtFZHVW=<}qwW;mw#c1q@u3MH z#1Iy?Z4NkmD`%|XQAow=UTyE0taTDB`hSD^cK3J`RJFSTg=0$(eAl_sS~}m)-BD1m z035_6@JTsf^9FvMIzjgKHyybVDF*Zqt3H@jpzvyDq_d@hXg%S&ojSv*icP zM{`8+ytmE-mDQRo7JB{FwgQzp`HO&lCc=lWbmXszbKgB5@=s$Tc=U~vr)qNa^WkvN zKfjcNm-#uMH!GFR!gb`v>VezH=~D5jVyt?Vx>jaU{3o1X?{7 z=KSZHiuIuAF(2e>55!>(0VR)oi%xbjL)NYkboW+iJ(&yjth8?4T!z~;UXy9b{Yc*S zG7I_A2~xY~{77~uTdt79^vf#FoqBc){6T#$^=wE2*=FrPg3{K%z1Ja9A9#3<*?IN` zhA&&BKdDt_zD!s0x+}v@ug~@eeV2z-|Dn1?0ub!u$}3(&&N0ri4&$QY!sv4SIGrnh zn4z=zNboyEQ)73AhA*5O9k?};xO*<0yzsJSh@vek$c}}y35Ao}n#?#@L)QjO_Se1! z;Kg!iP1p!;yKRmxdH~ce95nRE5`oujs=8rF4g3ZSkgYn ze-P_DLFQpfjW3j%*^-nBabYnb23Po<(^T{!ypnFX#Ylme0@*Lm^xG6~FW!W5D<4u)pZk!2cLTUCw*g6L z5q}y^&wmV#idj0bcraM4WQ*aILnr-Oe@F1`YO{!WL~Jb{23nsr`*igO)q?YNNOZXS zz#p8m-e#RJm!}*qVC|YwO9Q>Hq5wNwi)k?nWR#`|o%l`s>o%ogzPqZQk zu1qVg@l33O$MSq4BCXg*@D|>?bFip&B!2!g#xRp((0=TE5`CYq9HBIe(zeexUZ?`upQCuTGSy! zgUl!~8W5@?CIPavy13C;Spe`d!-1Qto8MeNm5Dh={ zH88dPUVTDemTC7HFN+2JJV=}M)#?cyIfq71NNo80PAi|wPR0?gi9oN|@NLc`6eO0V z?Tc*KVl&ZlJGM(7yCeVZVi{Wk&PWJKYK3QQEc@#gBke&c(cz3WdZs$j??!5rMst+q zXU$#CQuvp;%J>)p2%+spv53#KyJ$U$2%P~b^A44j8OG54QS!SqiT;vS(%A{2YMmTh zD35E2f6kc1=(Sq)okKR&Ar~D3B9XXgAHIU8fgs}yn(lfoCY8rUb8Rm$ra+aOv(nrk z{c57A>R=X0{33|0q^NOeKR28w6PFNoX&)t*u|6Z7FyGMRF+$;g+=5Uh%8PTRsx&c_ z3(KQSNsXOOK^#}dP{8v7f%#Ifd0&ohh>>_f>_5*7ygf4$kCJM$eV*0r9vF0>zu7#+ zR{|Cvf^fpD!6EJ8T|`$Ygi87ieV+DjtwfC*A~AO3Cb{#DjP)JOy4PxCrm2@KezoA| z)B&T{pM72;$vqK_I;nbS$P^bd&6N3h2lE^Tf^=wTMy#K}6fn91^AMl=tz1p|@}bc* z3_Xe|eae68tAer#@|3XXZS$AFy+;qmdeyUphzs2 zHlQdN$;X0J)wuEKH4_mYpLs?I}l*z?q6l_WC;989zL$rOoF27^pnLsT5FF`n(G zTp&e0AEtAwMV8zy&;|1_#<`!b0&8Ei!TX|2Xb06S9-5DZ;Pz;g9~2CRaGBO*GoO>! z6}nnF*d@r!dGBhRBO`Q-H!aFnV|91WO)l-k+y2U82BfTz4eDhwKeI~Uk*%=; zQbe~%3DxLKsWP#-EOWHVLXW3)`N^+ZUY2-Y>++Q;^eF36u?_g6qC8%*FNQNC%DIul z(6VtF1!XyQ8IyU#+Cgwoz>}lTArQaiWK{~CO>y?JVKM*r3(F-*>llPY0J*LZFviMq zq3WTnk13=@;rsl63u*vKILhC%XTj!2G>&ztyj1(T8RN<(MmCTN2`$Hq4k0q5@ZqeJ z^(O-?@v(4KnO~CLpKEIG^&)B^hwXIb!#M7i+fccI-9d6si!1AoLn6waN^~MD)hF^(AD)rVlG`}-^oHdU?}7jM z7tz)6IwzVII5yniIQHOiaX%j>^yLL*;)#at6Y?nTiy0?Bd@$I5Pjqy)wRd-&WMYC6 zsAEi7)}+Jk=szAV+(&5;gduC!u^5%`*8Tw%D_Bq>oeXh`$t`8q{NP-+e92nXug%6k zQ;Qx^vRXYr7q4Jj4`N}p`Bltzr*$n%#@d>t)>&G3=@+s>ZnzVLl9%;djc-qznj@6iul3?@ z6cnbYIN4xzjok)WFS?lSrH?xWE>h-Ac1>|HBSMP&C^TDqOcgMDc;KHp+1DL(yj{j!&#r@*b+U$Dd`7L(^=#RMvOwq z>G-0O`jY5X*+fw{=XDz_bpdtf|B4kBGV!d@i3`o__ZWaw-I|)VQ+!Ap>$vRruX; z6TK&T%|vE5Yhumf(V*O95+r48bG?o3@QrLMOvoxn85qMS_O|1GpHJ^!Tbu3<%ZJK# zFbmWM1bl#et~p^^Pzxku$e^7hZKyJ9H-Ir_o18{MWhY^jV1OwcYWqE=%- zW#7BTqyv5`TVHK?oMJC%MBd~tu983s{~z4|{1b?7^la6!Gp!>^ET-Kl!HPYTsmn|k zKB3TsWHH%35gLXE8N#dCKC_{cG|ZQt`f$W+%U@)OnJ|~gQgr6-e!QE`BRbh#MWjBV z8WpWRl(fXAS`%zfZ6Aw2P#tH?2-E~4E$v3HEa6+njJ+} z-_U%K(55CC4c{M7S=#kj71kSC=hZpa>S<0LeuBx#km0NIC^7lG!icX zS|DG6mwsm9wrukqYx~J^{5xWYYBT=R-#t2FS8PXeYk)(v{Fk2VW4~sg=2a--|5*}-o!u&6|DCW!) z7V$5?Zxz_JoLo%r+o$Yj@#3p-Ku9*d)Yg8KlRu`#C|A+Go(B$7>{VnS4_PqCXN!s@ zU;e7BOkU#qiM2VIk}m)7JEeRc%2$w3*ilUF(N!#I<RH7J!c=tZGm?0mOWMDe}BqDz83^c76L;fI3BfZfP=j~mF~Z|rZHW1)t||6p7j$ZpOhjUs!(No{+Z zF1N4dD0v>n>b@ua@d+8fU?3AvtU?zux})VlVT#2fl2q=v7x}Mhq)xp&aO`u?@`J-w zwR$bBSfbIiy6khWm#5zgzdCU6RStbq9JHM}K-opf1ZsCekLsvT3OX?4LRQ8d#=c;L zn6!KGy$T%3(u6csxCs<*H?B5`sI0-ya4zDYUE5f8l615dD5y4<#PkAA7w;2g)SVAv zVM8c3ws?iCrFg5av6mPPW8K)fB=|KZZa5e$lWFOe@*D)(yp?wDxLTLBZe z#}hlzps5a#l3S?ne)M%+JREM52*LMFMOFQaAje3Z_~hb8o%x{<#kEM}*_SS16tpy= z+u#T#!v&C(jlx2f6=-#scZw3$jRTcbOKkHF)REpkBW3B6fBp(M`>3g&z>vFw&SF-a zMRXljp0-`b(e1-WNuE*01>cW{udPbhzmvYzvayx7K$C@nEbIP&@9&V$W@H!2>&o-+ zN*-GwfLD9?)5R#%0e|o8vcD;JYHpdqCd)B5y$;1>p+~=-0zvE))zsA;LZrIvM#2AO zgr8jCJVK8R)@^M)Jl&of{tc zc~B)?0LxSYt^d6|>OBJ;@*LNhC$#x33FUCV@jvb#F3^^6IC0R;;?-(irS=P)V)-D~ zo+}_J^^>W3mQ3k(B)iehTM=rRX2)7__qgmlI0R<}{fnGuHxF;!rJM_oBvf5(H|mj+ z_I)G*GOf)&fHHTC8lH1`bNMVeAN<1}mOmQhk|vw5U;BdI(R;&FdC%?^(!BLPg`VHF zod&c|3!_ros62pL#U(t0%449}%;`8+9`_SR4^#V*TCOO|l}UnuaZ8TTx!P=Qw@KDS z*<2&8Q=vK2S)?qo@NRp8ZgaW>8{7nDf)}M*uc}|#P4TXe9-kC}>4u*!;w>Hvg?UO7 zAX7CRpn$}hQ~yQ(Ir~e!ozg|G#j3?KKh;q%h+tKO;h%DFbW0+|P;|I2xly1w2adJW z%*pj@_-rXI9I~pSBtxbf8B~~A4?(6$oX>Vr5?NknUzQKp1CsjmsCg(JIr?|{ zb}U_A4+6>AyF=gQ0Q81{ai`Q8SAS(}sb;xC0QRmKdV9^NR;~n_4?V68&#$ueMsfPM zoT8F2;6XtE#W_2_eaM&Y61}3BB@^rPSoaDsIAwa^zL#aqM+vF&BtVtdBuw!PID}?} z0gwaojmVWsU?{Tryn@2Ha<;B@ei&ZW-vr^oj#tpFZc+gH&@>x{+4E7*|E#R!FQXG( z%V{p`x2EWa3^O0s{=PTKx#Ts$ojrs|^LCF(h1n}v8JAdFFmXQJmKl}sLDEmPF%JEz zhzdGaYeG0`v(J=o2$oWpxzgC?VsC`zTXk($J;W>mqY^D#BL6Weiy3Z&olNs9>&3|& z;qRRH%b(Mb{hQ=iMX@E(39I1Q@#XeW>10d!=js$GQyyQyw{(ICj6}e{PqD7{y$4jo ze;%vO>?d)>Xrsv|gCcQWlYYAZ;zOZTIQ!pGs;KeST5G%aj1TUFjVAq*h4$898S81+ zmmRxgcE6cpDAd)j5293|=ks64RP{7^yM^8Ldy!X5ObO?B{-G1~X6D00!#w9Y z+Oo!}>t@BpIorSGe7BZtpn20cK3_o};!PnoxD?!5J(@+~W%zS*UCU9D#n%)%@N+P~ z8vJkfBxcVdf#=RvUcWS0YH@Ce56^C(xH9a*1zFe7^4S;n{lW_!sZ6G-?#qjalnaBD z?f>S5LjnqBlcS6q4r^vZm47P>cUfr`ETccAS20y^TfnbzZ44AF4G_T&P_mw?EmL>wT+$9q2V<)Ta8Qgnxn2CZP#4d0P} z-Ac8@28-k0iI}SRL|Np@Nb=E>{WBNGm{)M=@~YA~zrqOfLA78P#H~{(1x39>bL&2blk$Hx4c~uDw6AcO3nR zDEFt=01#d|ESHed=2m z2Q#!fMZO-SqYubee(ioF-V{@ijm^xk=w zNy&Sgu9dg0yLU$Aif=isA~Crij|&{#sm{O* zBqo!H6NIF({K`%^Ob!r&*HR#Y;|k}_qXhCefiPQpE;cn_aZ}OKurB!!R3}4mnhGK< zYDq1RbV4$Q@^kct?PfFt7t@Ay6eCqD*8Z6V|MjCVL~+)Ui6M3ogsuN?n!PB3hMhG>M;kn~<%i~!~YwMEfNbWdJ_{=m|EJz!@qFfyZ1aSQ`a?A{{ zf^(71UgxhHvD|TnW$>^&42JSj>2OA}q;Ot%=ARNcg{}y_jX*hssrmv^M$ z-X0=%9LsSWP3UgbV5l9{7|Ofs$dhO|N(`l4(Z=G%SQsv5TW7%;-^UhzsgUrGZ2%oohC#t-_ z>(||twU;ObGh87$M#3U!u@jn#de&65Q^}4oO}bs?yj(EUN)&mhj6=5f)2pU`h!e75 zxX1S=Rg~P&Aqa-v%!Pq>QvpaYB!a9JR3!c787e~b>SGF0C8_GyIjc2=^nSUrsdV4Chz7{R|dGbW33nN#e38J>p+$ zbltfq6i(_5&kGvc%CPRzAv&DiNED8ppU8R}t&?(r*_l-A7i>7p9mfswZ1MZb0}EYa zj=7&JptwdKb7_HAl5EJy(O86bjdnwbcd1eOT)`x;g=t(4o*bfK2z7Fp5vXFI+ zHOEgL+z96BhbbyrXjcbos@LRH6PG}y67DNP^EG^iF%N=ccB>3rNbvmdUj|)uwa(Y$ zb6DbSS?>kUIeVFZ<{n+iRr5m7Kc%!_&TpG5%CQVx!u9i(R)A7d?ZJEsL&Z!CJyjz-P1M} zA$l+Wbj$sEroZ%yW(?5psSJh1)v9zl<$t#p27Ei6kN;CbW>uC9McBgMoN_x=nj>2c zI)iVbO-`K+`ql5ycZnf{J8mG-z@co@IaC#b#H}(Z61ROa+RN+BO7(uM8_j)Cjlztz z?L3BPX~7kpLm{LS>sINf#j)7t9mxryc1;Uu=q_2!3-N?5W2^`KM@*UUBeu1 znXiRpG-zFkkSry=b!JI1vE(T;!u~k>HPV2P4l@{4Pyd1UB%ORQ(q*|T`pT}l{nCT< z!!X-?kb_#Vi~PLT6TbP$1<~omK+8s4DvpAsg1D$v9W*RolLXCVnw4^^pLjiqSAlMz z;Wq}d)`&Cy(h}l4SX{sp>EUV;H8ObsgPdSgV*6Xs5RWau`w18_oV4!OU<7x7Fsz;* zd2N~$^6Gle#W0xY#|t>c-V=3h3a^mPjawx63<-6D!#wFNI3nVYQ73pBE*z2G;z*n~ zAK)RoFui^^;VRsI02YaNQmBXntxiX%($00C)I^&TwVhB2?2Q+!Dk0NG=V100Z*1!X zHdHsVwD2!|^r7M+ReRvHMh%a!JzRHJQ2;9C{qp}3ppGvEp^PG7vOg5~ zw!EHE-kFg zVA0J$BU;za*YFMOY0tgP#kiD^a+RSMIAIi$?4doKn`HpyWN4pt0JqphD{~$WU9)(? z7vSc8>URxu$e_*h9x>2r_#)((`7psvWVq(E!DhjEP}cl;MleRmCNKvyt*JTzuVJc& z0sOaEM$>Y%MMboFWJJ^P^JBptwp;yMsIwv>Vh|)5K@ymPBC`deRUBWG5`ypn9DtD= zQckXnnC&|55+SdYhexWHPjyJ8iTE$N{{Ub z^|2MNhGs&%_lZOeA0RgT$lN)BAGg~231a@6#Wi-U;FB>J?mq01ZCze9dVdHn(udYh zxd2B%xWDY4%*c+lR6BKTjJ3@p=aLScICRLDcs5wAyG`N*e8o!u;$+ajF;-T_c78m& zEfGlnreBK-9hdb?{l=QP#lz(^zces0uW=MpzK-HfMzgMHV-!8jK$7XC5kF77*N z61OR7d05i3%7q!B^D3TS{&X5yFQH#UAm6S+-~C7c@_g@6JuC}f!3T#&$xP=|DVI;J zirjoPKcR(F$wp1ArZ#lfqy6cZmkXc!)MdxQhJb%y;*I^cgjo!9_~8!q@h8F?UAoaV zQ#xj#jnNEaQjIGG^NRR1hK|-m%QKg~N4pMaoNk0zW$O z92-X@P7voGiBk|;AgLuNh>c)p9_O(625>p>_+RMO!zfP(fQmXq+!v0)%+2*kRrx{N z5y)zai}VRs64jbQ8cJ(nWMr5M>@@PFwqgJ7={Zf7egV;F=ZPb%k{R=uYVujI?LMGX;sxKl(lLf=j5qBm#Y*02~REk zXEkhxOjA@GrztLq_x6bUQW_loPlCGezBq9NALAxaLSGgk!T_kk^{x*z07@Bobc6;# zr(6%2&;UfJast390Pj~X^+D9t)bHEF3&yROpSKQ|8o8%;sWBU&U)?M46!cy@ciF}z zvljf+_(!JBz&*IX|_&}Kr=cJzC4G%lt@viW}^&=X}&*4 zhwVoQ5x8lizVYuHkA&amBqky~m>C7G2YYXR-`RPI$k;1Ik&c1R=D7DrepgB&?+xi- zKybAS^&+kT?;&hxVD6UhIXiAu)&z)y<{X9gs#NT5vtyg680ap2#?b z=*UF6@E&}M+{1-sb|63r8*QP->WKZpjT;S=KdWaNM=!DN7;Zecx#mSNE(4r-E2s&a z20QG==T}h1bd-#@b%5so`Q#PSF~)Pbkj3QX^S0gL5>%i1`DWVY&-lX2uRzc+j`Uzx zp*%$sfHd^!kvg|Ti)DRJLZXrvg20R-Aren3l$%v`3Pr8jvTmJ46vybPgjYf!z3tW& ze32B2Dn!2-ZJ+KF5L=l1h=WTFUKa$>HIBO6h@t@TY27SS++rY-Ma3+_1q^UtktMM+ z-72)`s;mUt{$-nBX(Y^nI6ML9B_?o!A;4ao1bKG$qRxjrL6_jx7t`mK0H#cqsQEwt zK>J9&xlitC6>?+43UT8&8qLp2pDTw_qkDsZVQv<$xJ3wd(L3GUl@bQ$o(mKkz`>2< zhBiUU6c}9LtJ%hCtrL`zP~>DN*5w|DA*lo}vIieibd<+~+1Zvvs!zTQ4|tIk^U3@P zE!iP)BsYZGmCKR?lb0#&p&7mMSBW&g`G= zSne605VG9w!ta5EM_;P{WED)HX39|sQ z3!r9@1PHm#cB~FeDo&yz9flV%NQ`PO8O;Ey847-NM@-M&2~p1OYBz#V?+ zjkk1^byIPWL5I$*yJidC9>2ji-)u_Sbn!xP0cijmM_8LE^KHZT7%X`;KqYuymW2!y z{%Hv_zDVl)dq$n&`jSEb;l;&M0wlRPUJV$3s=BrY9V85|pjfvdK=~5DyLy=={NT8Q zXjyCqN&HsgTEpmoDE`@?$koiq1$a8yha$dXK9SnWE>#S6z*M$AB+#*KcWOzF=IV0QSmf4*3 zqC1^{^Dgi)r?v(lJ<(;UkOvh9M{$&}2-I{r2YVM|mWcOoP=1Ks5Fr2*8tMvsd#6?_ zBboMoHPzz{6OteY3Yb;eFOBZ5;oO!>>1&|?pzxlBR#d1+)@`$f7o?Wi5|jc5jCEl; z1aYi}g+pq4EHzdOg`roQ_wBl*-h%XCdIaxd74RXH3W!qrM5Ee}4*0o@#lyDsheiUe z_AMlUR4o>1MIm%+I3r}Ss>LF$DC7odqfI6;U>k?mY80Po^>1qB-Gp~SiNCFPUB}Uk zm=?A>+O&Zk(vqoXL`Rtt{Y{YqSC8N3By9u56O+Ww{P;kiYi0gyugS`4Zf7J16-*}cq_0*cOl44rda zkl>YMv92!}le`4+S7Zer%K}*A4KI7_Lw6mo)8+=-XXWHrERP=LVeN-=k(~P8)3T3z zk{JrISX{G*3<<`TrXth2G_NrG9$o+*uN!8LquB#_7lJ^;J_2Q+sG--7{h?sk8HztH z936Jpfy1=MfY5((iz~BCqaZ)~(B`Fp-uNZ}`%}0~G~WL)NZxPzSc20Z7KSY}-I#-m z8gvt<0)1EW`tc7HUXcA#89+}*$u>Mle8_yk?j=Qb?B7U+Ao3rsYitVPJK~1NpZPvh z-Z%#zsEW^>voI_M3*577iq9yg_28X5*TevwFS*N5zpLA}B!WQAo=u%_(^P_IAZ+tZWi`R)C`K~xI8jSr%sdWK$M+w9`tQ=>RxeBroX?df|JbA zVhncjE0=-8vN|Ynn4H5gOQqgQaH?+oVVL-b;MCwMPb86IX%KbH0>ajhET5C18UZ!o z+i;jFGjcGxT#TMLF;BH0FY-L&sS#SJ1pJp}!1KDRB5i5U$p*}lSpmC}1Cm$H+n2oT z|NnGB3*6)HxgEzlGF(yYqltAvX@M>AbWn2I#T^f6rUxPJlC<)n$5aFHewMsU9yO@% zx_eH_hHcJZz#}h;mydR%IWK0ezaj0I%{)TMhv}G%j2OtYfRdx%ikr19T8B3==Msf6 z#-OY^3Xq!kT12}jZ==~qyVBKtSXHj`tol$GN$ykE-kz@qNgL;u3%r3MEz3}07%u|DJ!}=*|nLD`LH;6};%&4!An!k3W z)yE@KKXI7VG?^Yl50D`}pf={gah9Y~Qf5u=QKb6=7G;)}5Na`l7N(_j^f^$oq(!{lA9-gbE7f{}&mfG(8@NG{(4QYt|O> zUH0mB!biTqfN%pQv?uEfh&hljpD*ep-I%S14~0|zS+-W)$O;0H8YN&Tq6OC?SwN4^ zM!EoiMG)!$2&#Mw0LxbhB^=AohV!hZrcHbh#>vI;j%JzF;tHy4Dd-=#_^XvDJdTQ^ zM5C!Xqn(21W;{hAS5H^vW5YIH8 z4m=`1P#EO$iG(7gF%t)HE!Qp0jyTLgQmrSHhwXANu{fm`@r(zympHsklW8 z6GPW0Ec~$5HE+oU_{Y<}|o-b+n_gqO-C1L=eZSJDW0)co4iyKhcgKJlIq3`Or=8e%q1N3GXSY ztJQ1)NN+AKNZs$B=ag6GPP`(P;;Rr73Ww@wM#%s*CcW>&{YK%fb*L0FgNvNvw{Dn5 zPiE9Ci=yy_xD(0@3!5+~WSOcJc=|WofaL-gTU{{QJX>ElpzO@kZ=8R+kFK4l^8dRJ zQ(FeE)slUjNe#cnkG7t#lF>v-#Nl@R>jrOaN(%JZLqi zu`jAm|DpD;JMF?9-I%<(o3_87I+009o#&m(CPfE(b+1m|ILdW7U8nZ{<23&O_7xv3 z&ch=mZGB_<{bTFgaPODW_EV|cLSGr^yz}h@9C?hoX*+rhyl_rlC<+tHm_6JbId22yRRObH*}us-u~+EuM6*?WA_Ju^Ja(^COf|v zDBJY>Hqo~%zU{2Y&{VuUkHC<)6M4;iagZeozjT?319@e<-%(}t-_@WzE*W5h1pKn4 zDgd&IV820-79PS`Q!=6`IdB0A8V!Ou75!DD`dDpx5;HfLSic~>9b@Iz4uPPfP7o{j zS03YDXOFel3A`7rgt`*LAoD_SQ-W{lOX&$iPqfthJ9mVk>QLdGyBDdt$-zW=M3KKb zF$rh2;>t?Z=P%Tf^K>^JAZt0=lK8hFxHOpG_)v<|HvjLzMGgR!ov}FHjyqYb(VNzI z+3JTLEN$1t&+#}ka;&PhEUhS%8yFeo%;3qBDB+%q+VG8p4+oA8=m4aXOmxyG=>rgT zPEcIfP4{e#I*_PAiI27f64(U*yQQUB7#3w2I!%$5l}RbrN0-hWn;hEYSfB`UB5@Qv5?ftko;ekEv8+ z#P@<0jTtQ7SZ=T0@|_xID~wSqm1^D5)T$B;H0)?MRSf&r^rn**bf~;|d$(I?Qc`G5 zMcF4PG;tde;Z-igKhaNRa}_s>Y2aE=x_5$ah_5{94W~C^t~)rUA0N5%eEET()GXW} zJAkVNZfy5%M_Wr=OMRWgOyefOM*(O%5E88|4K%akGVbeYbChcezgpUT&=USo1EtfV z5J!&Bga)BFnjPHyZl5^ww(ZRBz!H)F~ zN3(V0)LY-_r{d`!?ruuXDQDa~Wy551ZcsvvU>%ze;DMjCuEp#ikm1ct`t1f;>B}vxN&J(tV z2s~8uyWd|3l7kz2%9?ttx43xSw5dL7qexByKEDkf}wNXMmFh zchzBmJ~T7GLag=b-E*+hMyn#8GM zpa^sbW7uXMF!%SXysh6{1tdH(l^ihbN=n)Vk@}c1YSEwVT*vI$4sLH|=D(AGF&I6odBQ=mj3wUd6)C?oH7SkR0n|IKUAh z7dixgI(=7dZ)&nIEOHEYq4i%!4BQ51WzSeCqc-ZcDc^%8)gCet)OUY zr_~fE?{!d{+jvS^tlKZq%U&EZhXM0|)~^grU>l;Jk$ zbj_LoY<1AB3+79d5jxa0HR_oF{9yfV%;BKfBMB(mF2B|xXpmpeD76t4^;=s<7H z{MMm|uNhc%XRsF`V_nItp{7%4>D;yR@~~nCoLStkN$T7DhD!Nw368v{^Ook66OP%* zQFjv$AlCQG1vA{0>Lk8i4=4$;K|pr-?CSru{3^FXxq%9Xtg96jtjCeBumEhS8}z~z zbM}g2scW3$P5k(h^5i6@_vnX{-U4t00;Yt+!|U+!Fpx|y=K?}s{@sk;wQc&u)X)HW zAKe1O1A^sAU-XCtlBV}Oy5uv`5AQe?bq!_!q1U2a{Yf9atU+0bMH-&q>sPlZk6N?^ z_dPjBOcZqg!v!8_X*UVt`1iFMOL1bnr4VLqO~{VijD2y@@9X~Nq&N>Rel=g*Cib4^ z-TG?Xu(KSw^sM=h+5_0#SqWLQxl=x_fr=QvS{TMLNwgeRB7vP(UTEjIO<)&{Yr#S) zF-sU@{VJ7Ve4hf)CO2jB?7W_ub^>242ypH%!ce`yxdtsM@Q;c&v@$QpWMoB~1qv^J zx)S}61{e}Ii+?$>O!~=SLj`u$#!nw`PfnU?1D>qyG6h~D%$VL=2Urkef;#&sX(I3G3A)A>~<%Wl_6-uMReoV03vX{JghLit`IsMIwkf+vG*o)g6B?wHnS;dhNZ?{lPomDl7pZ`K zdaK@Yb8+H+m&%ups7a+3p($%fODe#06dkTwwN0t)0o}t+?ub!+NLv06i^L@rx3v{V zlxfJOM4ZQmq4tne{cuJwUENbg19)to@wPz4H0l#S0A34(WHOHN@O&X|h^B z@S6dK$8roWsEa)Cfq@9r$8SL7-EUO?{yT(Z#8?li6})O}zPwSZ1Y0dAlXYO|2yO7` zWJiw_U$c6h%xA8TY~3;=-uUL~$X@N2t#obtJ?)o_W+%@x@`wwMIXVtd1Q>NJ&<@)` z1QJH;JgT?(Z&eHbtRW78GMU(L(P!u_x!4mjCKw`+zie!gx^gG}p1G}EmK|A99eshi z;{FQmOF=zs1^uHMHa|W4C$H5nQts{?8|rg&;m<^>Or8&}2YG<~S7fy|mGMBJ3Gxrh z6z@%q=H*1_Gq8>^cB8F{)$Bqi45Akl1aUz}by5`XX@Yxq$EL^5_SgJ(w}N))kn^rB zmB5I%hrm?><2;dD*4LYV**>XvKcU-$Lt}=q(ZP>^sJRcDv8C6}e?H9kvLU|wq;mrz zjP|tq)72Hf@+@QWeqGVoy1cgTQTYKhy8RK&1&ISu1tjk@s-J{)0qTG@$7Iu-n{?2q zm!DL}^u?$rcZK|Av%B;~p@m^Nc~Z4lj4{B!OgSe95_Bf1HsJAcAD2tz%CG$9=X)@d zEFX5JPli0t5;hJr3bSH!H4RZMkuC720*z}Y12aq-%6*nkEj9f*o4$8r;70@nVWOWB zYaH%t+#GlwzL5JBu3#iELE$8z3$&Y}Nl3DlVU2 zTn1bu;y9%x`{9tErQa-nMig0?&i*{=b*xw%8-|ryA*lyF6|`03(l|e{RdZap{#=Hf zMpX#AHgk9G1oCKFr4Kd+lwe)6c%Y($RZec*p$!U*#SlhgMz^(rgL;Hn17T{sYOysu z(Wpp;uc=OnKxCvjwwE_VY(Ck^0n8>o(jFjg7IRN+<_?f5w(jLhqQ4e~#L#40lG8z( zXa4@jcJoaZ^Suz*^D_hrEu)+WY9xu-O~qMk64^U41#|+bo!;e>x@Ket)@9$zTSpP| zUT*h3NcmbJx(S}TuYS!##Ndmnm=%*tlEvPvx(L_gvxA^(XP4F}$~rqW>+7~2Foz_l zN=C|wzwaVvf$R39d29A{wh7yD-Oc6b!?VzMoS``EF^cpqC{ZRz|6JF!4X@hI+Rnw( zq2&Zu?FvKN3M<1p(r`gk4VMiybvJR&RdrDWplrZC|GUeb!6vdBpHy zge5N+2Z^O6+;U`7(J}1R8zt0wLX$rx?L%(898^G3EV8@JgGJ&70xP zJl^^Gpv_*Co=2#!8m=5!NE#zOcVQiyp~ZpJ^st)EJ}=_&dEl5~Y=|hzG26mmn8_Ru zXlu)c>*m7O^t4H(R+R-aUck<$RAy00EfxmQIBx10yiQ37`1Q}Y=o8T~D_;Ai`S_l> zq+upvQc5V-2RpI>JAN}qTp@&{&Q|uIW~P0FdD#4pry42J$U>l`=rWEF#vu-HtVXBI zwmVBY6uXiX9eL8@MvMl7S4sc;`OVFNXN%Y231$wE`>Cm^Qr%xt{wm)c(A#qrcgKBx zYPEr;am#$tD2kFHGPzk6Q(~-Zt~SAWmqf*K8cy76t^%P>893Xakm;M~bf~M7`u!YRvV9ComdTZ*u&ES1KVI7%#l!BbGx4en zxS&Br+U$|CY$<1UMYm`+I-k^K)NtdS&V8FQi1(1Xj{;zDI}P8#9hvN+1bk#Mt4KXQ!hMgG`9AqJ5S z;l&fVO4=}oZs1^(xyl#oT;kdTHwE{iV!I@QyJ!1xuiZrL)5S}Ow%hckC0vd#j}Vz? zUv%92xUV-J0JrG1)%t26X>jAXUH0 z6M5OjQo#%$!-Sv1E}L2qBxnyLye3l@oXXJ@em|Pm8io1L0`%w!59pS%t$koKj25Z- zXtZfoEcU>%o8EYBtm29O60l>eU&J!If*8()I_6u`hpRn&tiqz{LlJ~{E*yO8Lo$%Bbf;`tJ|EFfFZ zdN}7yZ^p!|8ki=AEp#qJ{p%d@a;+MqF{)1BNQ~bP1he$3>SddsXlT}}Q_7ROy2XsW5Mzy*juCjH^^f@K9L8ZTj6@(S8Yssh9 zF48P~n8*Si9uwk)_Rw?8ote;B^@xkHO&DcZ=mLJ2n99$j1y8QGmq(qwMw|3g4cPsw z)Nkx{NZPMI5m3T|y9hLVbIu#3=4W^5o9GClCYeT&+)3c`z|u5YStEO(^YLx%$!2dF z_*fK^4hfF;=FM8ZnxIzkpCiQ4&nb2f4E@#O_t288GWODnWsJoUtxEmTdKkGNGMfXB^`}0*;s?plA+=cZ}ijNg^y; zzf&P>{(gCdn?_bmzIc&Lk}O#{nP~AV7{B8#?rsASDmB@ho8Qxn@Ut=Y5*hfm{Oe1~ z%dd@i5Z_a`)+Y#<5CuajDW`%^OG%P6mVYk`SO{uVuw@|xk{t8E&9=-((8Q16NuW@| z84{>CFx!_YWU?}?)(g=AtiP!AL+3iwdeTg;$6Me-3hgTv{nu9m`=ry4=!>lO9AE>F z=$o=(!JenPy>{2AZ`pPvM9I!{(FBTxV8F05y~V3F?XBg@OG@@syn=eF54v*}=I-GV z|Bl{~yD+Ew%C!$W@KnY86)BE3o1=)_flzSt8)Mdru?l>iEMlKGmf<7-339=bPYSZ} ze`)oTVq~s>S)ZuKJ`{h8@3JmP;xP=`ZSs_j8(0IbLIOoFhga_tyKv0kIJEWF{ie={CGd(m-YhlfLV2fTn zH6c0EOPjf=%nMw^$Dpxg1-EWHyDV=B(e+d46`^9|Oc+)aqHAB~xnmATKZ06bvet90 zOU_fWZ(xPX>B`eSik9UrKAbtMJ^DGE9iGeP9oYPHXzO*Jxm8l#=%}pkrc?TdDtZ{* z6`+A!1b(MSAgE&YZICe^CR%<>OebC>V4b zYA5~VFduZ{fN&E1Vei9kj zM>3e1mD_oEVeSB4-tAWc#N40&?{Cb+0@{Rs*&;&~1rt!7K~?5W`$F z-)bJ^zTB*Fl90H$wl4s1ov~O{CtUA*n0DCXN}3_+kDRkDnS$+j#O_9M;KpGmuiQQh z1teuT8}60yzcY-_BSqxhc1nw4tn2+h?U4mHevJyVQ9DpI%oQ<%!^)am+<0{yY&2Jy z#j{))Mh?YFM0A zWOiAQ9-SXu;Np(*24217viTzl`tPPO+OLJgG2U}xWZtdayQ`jIPd1&*-+jKj-YH+h zaAFcuYIxWzNJ=&Q<~!5^NK8`dZ9zv#s30daWp%x;i~DcaG&i?dtj*Il3CJG1t`FqC z1nSSL(Y%z%AUv_5^ZU(Oz04v&HokUy;MhW5MNo~-I5`7$PBX`&;r?9?%pHx?r4-YT zGHPC-cl9=<oJiUtO2WLQk91w>N9mP}-x7SnPFk}K+4ulV&B1Bri%$xx0g9)@)T+6Qb*cK~TzuR-H|vLTIVSZzh7oW?rU;8m}&~dT6pj8lezJ8F?#t zH#O3;0K<@gf8^4cz=Yk|{|FZaOy3w#_!yI&K^rV z21I~~XqFE?qFKH{XlW1&H@qQdycZ$SET0%yjNp#nS^ulVERtOI|5=I_f%OV&3#0I< zL3_0dH#4Q&iX(tBr1u3OU@=(s+ayiFQ_7#3zh;wEe$hg01KFU=By8{~mwAX`v4 z_XyXzEhu<$7?9*xJWcbL?q4Yxw6a@IlYCIf(_3Xia`kuJ%8c4fT-@{@Zr}c)YGmRT zReym;*QbUXX5PSJoK-a0qA0)#s9w^tx`QQxt|nm;aZoV8RWeaB@-BxlX?G^+z4SPI zvIidIW5(WGL+;Fw#A-Ti;X!BsAZmuBHkB}Fbq49dL?&ccmOv$(78XIaL2dphr06<+ zJvbk*fr61vk!pBC1qJgv<@b;1<_goV#d{aYWV&3-cUtsVC{3`$TSc;o1_$Sq_!7$? zs+wf2(Y=fS`k&cc6L8(O!1n$7U+sG({KH|ZN&W@9N{V(uvo30nc(t)0L83MGq4`^V zEB6?s6?mdy@?D+P(4;_!YgxP@Na^ z(C%JYMP4jiu+r&_G~Ld9I$f6hNxyt&{fkg_%>#joS9l!q0MY;4H{i8x0{2#-=SJyv zk8~Ow(*o7$J)F=k;?zq0EFTYrQsg6OCIf!6a?Siwd16Ki`El7(zlm;!e?+iSXNdG= z;;G5h7?0iJuRXN>J-@8Nkao|^7&h5JGj1-IEx(dv-E2X+AZ7xe&ZM#<1aukbK7Zjt zn=SGRc>%VWrvZ_BP0hNol$6Pvn{oiOeY^42n@mX=1E)4*B?2ombD=y6l4q%?C$B!( z4d-P=s45~^|F9&FGPh~Xfw(ijE@5E#{^eo8#c8h}p-ot0Fe zgd`wDBHWxX2NF>*vl4kpo9YZ#Tksc)>(DXk=FOl%o}&yI?k#09J(KQWM+0Gxo`7kHL3dLB!J7!%6D?X^_{D8@a|)n zVqD&hhuy>nRA&htKI3xra(fdJr!d(zmD+*C(A)Tk;gs!ly#O|X{GonP75scb` zhkL4(aux_mktqnYtQ3+YCCMU|zc#F_B$|$^L;d5Y4U;7&$=Kn~f^p-xu84GPP8S!y zmNJ*LSbpn_1zG-yjeen#jx%q$o^*4*e?l#m8ajguro?7@v)uHriOLtcPrKtbC&!EY z)32tVuMu(KB*8E+BIKeQd4%^h0k3Equo*_k50W9;4VuLPr`ar&EaBy8wy(3#g%1luRL_Wh_T14)kf?C?8Ov?(K~Q=sPtebYF429<&mJ()~ZaRbe}-_F@`)4M=0zyo0&j%WZKTBvzK*TEVJ~9wD@_Np8$}9S zzShqMWaBx14^h6iu4n2GtHQTpvSZx;rAsx0vF+uV8_wLjcViRadrUND zZ^G+e`OVGz_HmzaeskhscopxwDda9LyCo6{b~QbQM8_n_n!e98G+nV&&nvYRIc=Iw zPZxdpVqouSH0L4Ck!ACQC8Z$Y;$o^!bo8shJi;dd=EB#T@a27z9E`aFmlJD}kE`M*ru{ zIeyAOE>zw^*Z7D<5sZlI1QX&Vx-QIbu%sw*aRM{a!(c%pUXy#)T^3i6H`Bx=)rJJpUB<15nYK=f41c}u9FKvSwd zD&G*HDXssdbliwEQe#mGDJ=rm7t{LXE~&fkGxv?1pMmr$qeyy@(MIlQtnJtqPh;o( z)B9baZ~Ba}r}=c7Lns6oNV+1{2Vsr_boXj&o@{QB&WIcbxhYRyGJ?m^{C@6AJZKe) zg!l-p!wGXk_61Lu1EaQ{=ovMEc*N;>!eubIrBWpaGsoFHU1uo3C?nif zsam{lt>iNeD*kS3>x%;^mCy5A8P)b(I^uV2E*|(t+a1k^ju?hB$Mnl_{b#E7&|#oL z(QIBdx8jmZ701 zPr_{Jn}25;V%e;fQ87CS9NA7bubrUsBno5N*vFoC)r5|xBzEpZ~N$m=D$#SN{OC>J|K=^#(IV1p0evBgu zrmV4$V!+IE0nLkVAFxL%t9~*-W#lKr^RnS5gZ+3x$l}WIHm`zWWWyU?GP^OmC5P}5 zsu z_=c<9SjZoW@8~>1mP>beg!-+Qn7GTfJIR0l-X`}IcaVimskWg6Qwtr+)3+(DqjM3s zvfJbhGFdgK-D<5=C<3ai@pduk&Jf7f#LmS9nwS z4ag)za=<<5*x>Nqj9#fIP^=@)c$2tBS{qbfONSG)4x=9{M@8!Q83wP%(@!lU;xr>uv2M-Xxv29s4NGt(AM7Nm{$=;^k_2 zH2WP1`?!rSLE1%tz*(dtZE+){t=A9U?kQpcvp!wikGo4~n3rFl!WRH}ta8NBLq!l#3Q;)$8P^*B!sa6}7`1W7_r+Up36$eGu36ivLbv6317 zkc}Y~@h3P3(Hy+;_HRM%yI{b^8ka@22IxqMgdzbmn30IO$GT>WF>V{)TZVB6_Ny4Z zelIab$#F1)h8Z^rzSgA*V4cw&>f%r5G5f5%x+>-5FG**LKc>lR)rU!^!E{<0oNu@F%`;!68LlQ-`rxExWJTAF91Gc}>HPZ{5#*xQlGGWmQ%xW$#16k8n zB;a`YoP@+hpJ1Ch>KS`u^Lbp*xz;txErQK_{0L(<3AigU4OW|$QyGtafV+=-fK?O^ zqp1ASt4K=04WJdHPx($Tl#>wi&bx)|VgQo83ONmJo&>*MoQYM%>utHYpD|{eG96pa zh!1n=b5q6(rb6Lh+gOWZChyrY*4Ep$Dbslq>Re+jZ50(yo7=`5ZTgJ1=Cx}apuE$G zHKcB{ue{jt$T-yF%Q)H{Q>JPTJMw+@>kK8KU zIQsH2Wf(_xcg9pT)^dSolK{7DBe)fETQag6?c#9l0%z?;$BXhS=aQWq?xeXOsGO#4 zgO)Jumrt{7B^&_`Nu9b;UO@$Cdy6l)Pd;E(frm7DrUL!Y9X?Z2leo|0m7U9`u)QlESw2*XxwWEI@GByyGD4AG)X zcM4b=(=lwy1+a7w#R-!<`(0h!!W*zY-sAJJeaZXgtxWdFg6G8bNyj($d=|a2EZACR zhruWcgZHx($D-#uPNalxSp6|0Ve97y@5)UpCoq&394lVKUBh&C-s{YAaDszKbKfY$ z!Qgr@?w5$8Y-}8v@KUrBrY(R+bM(*27K*Xas6#iB;nsTDEwcEcj=>paO;LfQelz|j z0tVmqo%pg0hltg>C;|j6QRiTWjWCGHR_mM)ZpTw4g6tdY5O#PDs0@biFiKeX>QD_7 z8rqM{c3!WhtvRJ3rMXShYuu5&V_vVJ$r`DQv^E)f0V%JeBk%EdgjnpTzO_k}w%YxP zq{WDVXz5ZID7i}rE5n3F8exKKjg#!|ni|ggEm8F&cJjjjuW;5l%ei!i!8xRF9wx*o zCRQy=165TSwwdog{zd5oK_Dt|?&qBwd>JiE4=HrqvQ=P=x#t{JtEB>!S=fHo;;OGC z!S7XjhPVZo0;c4S$S-@*FP-8-%MRwTgQp>Epj{w=IwW*hM}x%qdu;3PhKw?z+nz(-)jzPCwFUDsxm%IWVPeuwgAuHg0103JBzYo7 zPXKzACWlHsUZ=pari~?L22`XnS5)rN0;;NrQ=S4js&Y{-hl`fkG)zd#Q-`T}qW8bj znmpgV2MjqX_V4WYPcw<`x|pmUj)28JOr)aXDLk*j=P5J^PP`NE2sI-%@+Y?r(t8c) zkrM^&Xop@(6qb_l$|Ls)m|pfIjsI3kG9KTrQp0d`=ggGIhkZb4fmXfFezBu&iYXzb z%%g8BFL1^Xyb<7GqY}S=eAx9+Zn`G@bz_j+4JY)T^stK~QtENR?>CKbh`i-SfoGzn z2HF^Q7})vOBpJ(z4u!wnpPhdYI}WNd9?lKdJc^xV%sjVPufss>Uz+f_4>Q!0VxN7B z6>0_-0l&O*Nr6%`yVX4do{bbIr3Sh(@TV$W5-pa50H4%wcQ|ZDs$*BGXDbUXx}2LB z&y}D%IPS{5sel2qZkhqtH~1~?B!CpQE&+ZZR24*^ZWAce-;{1yXIqf-+EWQQE@lUw zXz)du+tOp_zrlkV+;ey_*V6R`H#5@xJ^!w{2cId+)xXq_AjL&5)6^&5$CM^U_v7b` z)u-90m+$m5cBZ|g#WDBjU+VIZ^N{ORe=oRC>nX}*#v+e2XlS24MW&)X$f>g2q8{4) z1xfalcxxmtCA9|lh|QEG(L6L1@~m-*K21h4z3f6e(yT&Wc%VL)ne)t4UC9q5I*;yn zC#@yeM((w++k|i?_r}$PS|d3q0fs{I9F14pKZFO^hd7q|5Zelh{{ySikmtns^Vh-z z5Em&*gsDFTG`?nJ-q^FX_#bgM(2{)=%blA#4m0l-meCAeqtR5Svi96j!bV4PbJJh) zB6AUl7Gl$i)gVIn);D;DvLf^$*V2y%rEzM&69c+)&XXmr?{O_D7s`^1WgpvoJTSi> zp&&aq?mh>Xg?rIgEd-RrtEGdF*VcMwP~4(j?LI<*P9^xdEJ_n7&~vSHHKvv}B;hqI z3Z{ENMZMsP8Eh(!F$tUtih2<(m>z3E`$uk2dvNdG)mgJ+Gn5;$LHEI7#~WA};rj`c zL;tH=q#!CuaK>%w;K*{VKJ05UUbQeRDdIIW+Omdqb19zcV2hWNQ9cyYFatEi=1CJJKFdSFb zV7L-(fq`Oq4zPAlpk(wq`|-Jbjh9sW`Kgs8#&t-<#xi;FaBo&xIi;j;hNXp%hxV8f z=mJD*E)0Rr=w8O=(J{u3OY6;k@g)otey(>9tfl}tZo94aDbSt*JdfE@wNHUSE?lLx zerZEIW{#t-uDLm`JhKo}+(DpNs{JOrbiMz%cQiQLhXgXxKwp5Y-DY3bveyEF4iW(Y zG-FP;^|1sHw$?JH1E&go_}H?V`}pA$#^7AzvOJD*(9CP+9c|Msj;xtE_BNp8{@dKp zLvA-oId1&*8qUCI-u-@(THwlZOFcf{M=rUxefjuyPesMx;A*HRH6#}D_`%>E%jqvu z_e)sDvf6b^0cC=+)_-6HP|?4f;4Byl739y_Wsm-c6@b1PYjrcDS)rRwQy@wvQrmSr z={HDxJ}l)l=o{w-=mIrw^pnpzXo=Yrz7b4g9a1ZGXW8lahg|e5>mlZ1JdcA=|MT>> zC3ibO%iBrUw}iS{UnK*cq)DEW0Rz<|;;?sQNAz$*uy%lHTAX1iDV<4yqnhWkJGaBM z?=sK=lB^TnLc!(rdd8lq$S0U9tBbI3U6Fm{U0?027GpQc(=^*0Di>Mg=2Iz`YJ+*M znyD8M2v85i%J!fSl-lwjTd_aeQDcoqA&cFh#Y|IjTowMS$hG>ilv__SDcP%|gU2)P zd?8z+>jKhal#Wq0yWh%Bt*FFO>k9q2ww_T>J(c8Hyd5_GR3dH@`o<}%C@Cw+afVLs z+t-}trCdupOtSLD1b)BY!r7vPkDLHYhTvp22e0RdV@@pg zbf&JIyO!!&oV;ioH5=f(Q`#=uMVEA$|}D3JLS)Q_-Sf`qpbK z9r9Dv_)qxPc;}-Lh&FHrIXf)^!jS(Ix`yH_ZR7eL%Y(qyYJA?-$?8cAE&a@w76PN(L4z3cR*0U{3<=R%Z)Q8r z6MRhO;@7mTqNZ9m0g)l)lE~Eu+(L3U_^ye4bxe;UDi5~(9ONzinR|h0Qo%u<-=uNU zk9V-ZSz{@U*_Rz^)gfu=z+2BwWx`$CXkbuQjcZRmMl_MAr1_SFzeGBm({$~`mRlSr zh}p|Pc3}y;h?Lz_eAXD(8J{41bK==fEUNr?%L$;P=pZo^uAxd*madr@dezMINrMyC zvMW{J%agRU|7 zKC36o#ta5hX(FGuFH<0GRI;n(3GdG4(1It|W%=QGlJe2TB#q!sQl6X)Ym5)vc1hV& zaiNh-(pplKCrq@K2<$+r=B)k-wJIx_4*LkGg>!fs9NE9NXmVE`08fB5%~N)H0_!3 zN!)JVZk|3eJ6$9^!prm4#kW@;X zsaC~Tc%)4g7Oy4Qi#pVzz#k^d{&re1GygwPs&wVj%w+F$kehSpfrhcp`4K*?s8PR? zu8zu!+qv!9ns-SmpTf{2&#+e~!+>S9ZVA|~?d^Y%TvbKQ$6*`zMhbwgyFhCYS;MQB z1QnHfUSC30cG92(O)x`dmX*&i%!If*Uk-urnAiQ&S5~Udne#Cl@B;;`OcQJHim!bh z2`1j6K=(aqiz&s=Ky3_WAUOs{15yC?Wp{u2WDb%`pl-3FfQP>?= z1||eP4`ez5{^N~a(6pmB`l9wD)Cm3ckx>3C9_1vj{17Rs7+D-+Ev$W-A3EAjH&?Ot z$-L@*>rplF)r$`+twn{i1Mz-{pn3ePqTNMB%HKHi-g3*6PwaNCFXtQhNik|>!iOL4 zo}P0hx}00Nq1&}M?|EJwar?F~w=;|mA)QfubTxzu4)uP@S1A%ECj;9~(o;{=E`kw6 zE1V;o*{&pp*oBLBKJ87BW6np}t_Q!{(G`({o@DG6^06dyS`bh(pWsMO*iY=SqBe~@ z8UHs3^Iv;B4t&na`r$;4;kKao0%w0wpwr8oa?Y{x^aC>Ue`0xV`kgquHS@~Fq12`$ zd5oN0If_qFR^l^p^lqH~I5S~yy)7bKEe8R8fthgX6v41{d$k^YK38HA-V*3)cVvyx z;(hJxVs+|EQts{JE=mH}kuKA*BB6$klbCDsa^XNmu``^GeBrWj=KId^z2(2v4CcBL zR5{4Dpb2PV#(xuObH;3%0}$z}sl^sf)dm^}CKImiza^PayJ|AR^SC$xm&_)ujR3HV z-RbUL+pAx^+eLewI~&L)senkzR{T266J|~S3d5_qoWkspmpgQiVQs zY`pvF>by$9kVD6v9U^lTWPn5at`t}!x>Ubk0yR#Iu8`Eop#jKFH-iWoY#0FQ-x_3` zj^AB>xMeB*u@O^Ugxfmt#o&T{JqW7!J;ImDXV4%pak&m`)eqif?aIkZ!T zdR#I^GjYr8LeiFtK_Mqf;o86k>R+Doc*54>|Ith@V3N$>;s{2>h~=8CTUIYY$uk$H z4>(zC>@gEF#wLEeF|2FN7;F29ZQ7QF!#BpjE*C?tn`{gQ;;YC(;Qwrt7$mFayE*=l~S1yOz+KwL^%KTHI%v6u5HA+Qw z=-{SiOjOMF~#wInCsRpUMdsa%jlM^u@ zg-Gv|Vm4$}N{m}+*P)aTF{U-++=DAZ)M#$`3*tExRGt^4kNx*=X@9USj46gC9cACq zGM!W!r~vGkp8M2L3KCRV7TTzZu^aUH^ORrE%VA{Y`1^N*8Vxlw6a7G=PD|_R0&%HE zW{kBdA6Is>?<77YVSmbVK(&_apv6QnK6djq(ZZ23;lppPd@RT+vxomq)CiszX!yS` z-L=ePvM(QR2aNO;AR4%KAab`z`{(MEZYh~r-MJL3JNqN4R!Z}-tmKoiY0Ttf$3)-* zij2?&Ij?rD$Mb5g5m`oq8zkW?6bu<;%6%O4=Ke^Djd3hZ26hHw_&P(WDIwHC+B0=v zz*&Z_40;{GkF9?Y`tsxb(DgU*XM$dObkucuCGy>STOC$Vdnx>AZr;Yf_(SMa%!d*i z2Q&BRrSRHBRdLf@|5cnWai55dB%ix zK&2xJ1tf9_vJ3*$Z{nIfKg{cJ=_2R=MG%+%?AhmoAv7HU#=yosrM|jor*oM#q92ct z$N@sf{lhS0p`v>U9x>nq&^sse);;iqb2w?(K_G>;XCi+iwJ(9AAOmxKpBzWh zBzFNjIrh>$0F|laGSFxhIb&@XtCMPG5aR17Q=#yUgjQs7aU%OzJ3LHabYl6eo(N{7 zt9n6JDk3*_+S!zF&C)aL6!FrI7FSl;`6kBb;!oB^^I{3*fmPw13gYb2HV$z9v#QqW zRJ^_9qGo8waI9qaZtgmHg&0VY#kMm#s-?XG?@xRPi|p5_bcmhu(letv}2abHN% zx`>o{T_zvhQF|xVr1Y7yHxPl5W9|j%h^f*l1Gn80y}PGUyE3<8w>CiH`6J}jN^sjA z;pZv$b72F;t?ZlEe-Z5$#2>U4(_;fJ#|H>ba3|9#z&Hjp-P!?NH;te?>{PKK0z zYi6mcT1TV1No2mF+k0e0Ys!BP@KI8r;3_Z1GziM*Yw zrmev?wq+}mv9pN{*D(-s zGLX6hLgvcJmuFOZXQtQ{wO)`)I%Wvp=4J4nHIH}WEA7K0yvhWcUqW6tbM~w5yfzNL zt%#Xsj0%O#f&e93J6Qv1Kc8jBEw%^T5CA_;kaqrjvnpB;4psQJ1JutM`YdyD#Od)4 zfN()2x)GK`_la-_(CEf`TPEi3gTJ=B4ggcM!TWrC3>4^lQ_G)2&%p#}U3D0Dnuo~! z&es@ngqzNl7Q18a8G845ot}$oS=hXE^TP6H>Oy(rloy!Mb+>{-rQ)JIQ+_JC#Ys-l zy$!)BbWYwgPJRjl?kWu zdbqtokUUG58uN)U%sBF;eNGaoVjUS6(vW|f!@Wj*u!9nD&=eJ8ImM09a$_9%G?w0o zm_YRqeQlB47~SZeF|hAkQGPZhrhS(VF9=yb5+eCBzR2}JTxNM1KvKAHa|NmjnS?Wy zaC;d^VKy^_>zu_AH;W~-K8HHO=wytZ?XGq=+LA^8 z*N_5T0@wV@ZD1tm;Z%IAGHlIlt5-KWbow^OXp3XA+}hRxu1b(_?;8pNm#fPNoHI}7 zmF03Dk&_dWz4IK{3l)Eb=^Ot$`9X5RHYGCDEmf=~X2hWE_$*IM2611$w7j30zE&n% zYf$FBz_zlD5iR}5f_<{~pv$fptaonT3H~@tev$0$PduuV4}#+1+<}pLIMQ*UH)iDQ z7!|j=UAfn{AbL$|N;7T&CtI={V^(LoBQDO-ez|54t(?GI?TC+8X_glcd}YLyEC>QO zE-35T#nq|9A;iQ9S*-_M5iN;UFldE7DJ2YdqI2Rh%2M=&g+FL-ac`XokV+%)w(~6$ z>#=JM=8wDUV+A?=0+BlU0FD`vM6^WuW-oz%A?FbG!e2Q+UF+=mNH4D-DtNq{Pxk$A z0@xV)Uqsa9cU45{%;49Uw z=X~i05a_RzojhYP?Sc-NUC5$&^3R4cN!|siwRLGUR-J#pnYbTF*)t+dqDPd2+Mhag zSk;lGxHG#)7z+W*8_Q>XZIEb@dwGn$Pmm%X`(Ng&=PS3V!iVDpi$^+my?OZ#o+CGx z%F`0Xf{I95o*H$emDqY1ReRLuZkES(uE&-n*q@}i;<3IT*}thm)g1G6vlU6Hq5EzQ zoq`lWXZdYk%wP074w7g1aipmaNx6Q_mE;J76b0OrocF+huy74dG+cfTMmL|sac73F zzojZ=kW>p}G-Rs2>K_PzXJ`-j-(kNEk^C~k)j?L-T{|~pO%VJgWSc0j$mWZN!%at^ zwb4s)OSgLE7BBT1_|IG2(Gtp?H|OffISWbpZWR1B_MtAbqD@pn(3y%Y8#k~6EVhiw z_us%$zv5i4t<2;4MlWmNZ%ph&_!EorMBCs^izI7;GK#Zy$Lbof4%=;)c85Pwd!k{3 zn^cWM?2KCdp4+c(SS#v!+=KZ+@YW4b#8dN-w^7W-Qde)p7QZ?tP`6JF?zLK77cfr< zY;a4FlArz-~_YE)ARKOVE%T6b=3j?A4e#$Bw^*&u$H0i`8 zdWibdPQJQ0D>z`|$<-%cA1*|H@N&^%nHv)`)DmG)@90Zyfy~;RWJa2ufPeu9L5d(c zY=@?QcSA5m;y6LXU91)@eT5mrJk9vL{9y`YhaiDDJy{D{6`Pd4fv5)b7djyDPmr5{DU) zNM1Y4_MSWo+74%nC+tKJJWHAmmsRY$XHTwvts6_xz+kzJY`Hxn@xGBbDbVubG(blL zwmGQwZl{ZmYcLNy6+b6Fs}5SThJU)mMW)wAKxqAf-n6OD&}!NN)w(*~ zq~7mshj{CjjiIieaV%j9-^apAySpUBC)uhRJ5&VH0n4;XDcvV{_DfXz9j-&Zr zV4h(J7-yJksvI*JUsqqi4An4BGqkVIzFbOqhDl4h5dYEo<$o~ZeVk4OsNQ{5|Huzc z^~rV%))^2?$EMG~blC=R1YKGv=DjQOO`W2VIrId3z6{wmS+ zt@4SkNUDhTSvA~}uRk+y$4bSgG4u4FVhTLFH6(MghbTtGe8Lnl9)+?fiHCoL(-#cI z48ZKKY^IRG30ZsGk^Cgjar{x)!?KHxyeBF41qXVMD&CkxkiD8bbTOdW!ltk-{>^uW zlFy`ExiGAncvcp>F;FAQ7DHXXoH)DsO{ldZ&E~eILDcX=!wVM9^#~aQ(T>%8ZP8Mz zh~InyyufkA1EC*_i`+2qSENwZ}>JXPpFeTy?_njmF>4mZUX)0A}SCc zoDoQF%aIZ=O_(*|1gM6rJvw&K`{R*V7SMcgY4Vke7=#N~Qch9IJ1WHq$w>wjot&SF z5h_0|v6tN8xNXoQ-VF$a?2vte7W&7{zW*BGp?U?qcw}H6v!{xf4Y7u)!@uVWVdu%E zi>lAmVrH%PX3fHGyobRmUkr}@JZq|4BU?#|>X z+>EG@Mc~efDoQr!)wsUlYjyTHa`XOO0cZIcF7n-jG;=D4VE%5a5iWtr)_?^Z=+#b%v*OO>3{0sghcyHL$Yn0e@!UNXi{i) zLy9d6<1w&+cz4`Dj~?+-&7D4OAC<~j43r26Ietc3u&4#_jw_JJt387fY-Uxp5Y5y? zApKq=2RDIJF3HbV;tH4;^4f?J6Xx;JjJa6}poDu{q*7?8I!!^?0&8lSczo{D?8RiI zq@-F_0GasAAnRIr^LW1<>6HMMDj|3?c;|e$J5ReJH+w|w6k+?203*OzpMRMu1YEt=pJRuwTZ!t14y|pe~EiK^S$a+_%-o zM57tN6&$S~1Hfje(;P}2OL@R)m8Bia7BDvB^2es9SI!(!p4jmbL+oIzp`l^WZBR|y z0fXsUr@;P<%gso@^BYer3l8*(SENv`N!=&=?Jk4A_|5#lNueB%@5Se(GJ+yz{O%9B z*!w5lJxW+Va7zk=EU5b;PDAFya#Jz9{FF^pj$Vh8`5N3vYL2X6`RNQGdL`n|1YkXc z4ia*gsR^+ z0#n7{=~po+Ra>kG7v%vH;x~$NUYXI!qc^i}u0tjQQ0_aToQ+ppI@$)>S`^I*xR+cy z8=z6cEIrSs(5(U?SZdty|)@BUQ z{9zdc0f)fx9L$@RU9_7C$D~%t!`{7nv*@O*SH+)c3T9}!x!5Biu4ubHmj!qFBv!CS z`L^7)NfGT7xHLpsD8KX->5_=k!H`t885zVhaB| z>S>O5jepO@8gEXrnE0JVt%xVxE?WfK?^gDZ3b(;BtW5DR}! z(+9?m#yMqG@tH*m)(MIQD)rv0I%}F$%w4sg>kSY&6BL*S)pLbC-R<0V`3bqM|4mKx zY-F37dXP&}0Uhlpc>Jdcr1~0C+Np@NUY+>tuOeJvwB_^_z{0n_zHqz#9V zYU)WP&Z5tB;d5pXf}VI$HcMsF?30^(ZynwncjF)99T5hjlqI*#R({?9__yOW1} z4Lf|D;6p7P21h%ji?P5Qq%+l73&Ln&vX0alN)ro*I8}P*?O?lcn>oHad<}=Lf>w%$ zVe$5EkA*W-GfmAM0GrvEp9kK$>guu4Nj1^t+1Mi^+TI@>yKzHC0)FCn?lcze+c$KE zI+uE8sEG^5Z{TC=6njd=j2k95_tYiXZ;3Jh2k3DNpUa%cRI?r@X-o~>;8N&oWTFS! zFCg$^qQc8VElO8=6!)ayw|{MUlZ&4gfoHx{;nPkh@hVqWA6b75fuG|!BB46ezIS>L zytFhlG?MhHpO#c4qWSoSc>nbA6RYn8s~V4Ust^5-OS3)x z4XOQKxplP21%ATFlCbFom(OOz{Cf2CWw;chbBP=XjOSZ&?9Q&Khs*f~c~8eS7w9SG z2M)OXdsk1mLXWJN6BmNyWNh%zDQNorqH&%l4`*%bZcI@kIK><2NV2IUa2>}uPsF7y zD5P}gXh{m42sHyJWioxiZ=2_-L@c&Ov|oEN=En>UleAtEvtTvxzuF&5R2}@+WMGwX z170Y%iso(pZ9z8R4D)RAoC=x#!RPgovd$#*w=V?_c*h6(h-NJ_uB-@L68@s;uih5WJxyd=|g_ipcC_ihp3}Q2d4n=jSJ#+x&N*P$>7w`;9&g54i0jd zI9ph`Cw>;|uXdk6x*V7ou{v0D@-xCLb}_W%4QY{eNB|y3Id3c#f&5G2K{01LG_#G#FxPELpu?bIQ3a z@__?`gAN{*=NQ!KpQu@Y6HC5FJotL0%zQyTzwPgprfZBT9JDBAj)F!$q;xHZwQ}XI zfycgJsC^k?zF}WH?u;t}aSFcK`56-ob)2TeX-_#53qici$r9&3Xj$ip{F6wDsgu@& z5%_8yk;!1z3^3RZ$#9^@2Rd&!XJK%Axt;cLG1fY`xPdw^4^43C+^?|-Ma~23B@N$moF)DISKA@{k(BClLV6j!rVk8#~Yr*}bpnMk=BdwV1j^xZ2 z3PZ8eXt7lhCE*Hv!pZ6Ey|?5#rtFFveSwP*^WGAYN#CZ~=fPC&p%m*5DGzHkA@TRSOgxez%#NTaB3e?j zh)Q1m`tTDeA{`OyJ&?T3$CV_DUJp$x5>Vi1T9Ocn#@<_stE?&U&eWfjZjP*}1t|LZ z?qW1&@pl3z+}cBh<5;F-8kkv#2B-)QBI+_2qz&m)4-QTqI*flg@%27G&W#_peD(ZZ z@Wn5#9N|yzMlG0W2RnGMvg#-wPB^W}I<=j<= zgRz?UU-=mg4H;x+D%z$I*3hdWa5xbpIE2k+Fs$!eH0M+^2W=gu!TBhPBXAy!pfEIB zilh+QM-^1-QCuW;Scc}pu#s%}lTc@51<# z_6^GSam@w3{;udVah=D#F4;ZLqoO|((+_*&E!qG0XAV0*J}i&T11gYsjLz<%VZ=Mw z^nb7iEK)1GE4C&*s3pZdNJcY#dZG(>C_2Qb*(XYlTiWA~8fq<@u8GC{bgL$YU37a( zIf9RmFMrD~kJBO^(-s%8V`_f*fSW4OuxnY&!r4EaR8fN6kPlRXX!*K9 z82cz6EoE)dsel#U ztXyPoLk?>ly;G*brp|u5x<;^a5E+fo>Erp>MCF)E;wa&>D=SCpsSRl{|8V;SQAlR> z!rpXvbN=(^!Hw{$exVrhd`rhk9%=CPtk+$LkMpFjQj}c;q_aBLjg;d;Y-3R;8;@HA z^D4W5lh9hHa#ZpaIA!50Z{iba`#J3F4U0_j*^%nf{Bzrb$C8RfsP>vTq$ZngIieAV zvud*!#&ef40ykyE-@4Vi0vSu=x!DV~)=kFI{VllD00fB)CBYCclSga?dt??R>`<96 zS}u?i@)IwBcRXo`t7n&}xC@N|T=c|#$%o5$Ff1KqSj$5^c*Y_1acvznabH_d%6bTd z4SN!^*4rLNQO^75MdQ9-%qNqp3q84AkTbXHr+bU%<5_xZCZxj|W~O5}zjYSb@P?6j z9?+5oA6j&iC}IauT>hNkAw@mn>dePoJx*RHvk?}ENHIQMUI;gvx@SzxUq7?1cyTKz$qf`nx+nnI5+-n|D|0Rb!=9L?(d(@`Z638;KtK3<}VM^{ePy;zAm z0V52!nIt-jjt<7E?3a?ao^*kQMy#$w=oJLX>T?7d0~&zqNai{*E8Wk+KMXr#5!Zg2 z9>mG3VYg*bVYWd00n%0to68G5?+Iq3XTvaillQ*U#S|}}I+eR2`AY9T?|qK4`(ev| z@CUR!%)P|H3Q>~qf5YwZWj^gn1;j4A*&ZMMvKu)ae=U07yPi&1L~p0di8l)Up>%25 zo}xGxB@%s7C`r40vu!KhMBES3*prS>E+SIfN0b)l@Vu^id66w%BJ~OF6LLOB2^O^} z9rP9_U`#>`igF=7hm;Dv+%pol1bk-YiHWk<+T|Kjp^iINwq{LJP}zfD+hc=kGr|ym zvN%1-&(YhSjLSzhpr@S2FuUN-#Dc9*Rl$e~hvP+pZLcYZl9Ft;Fd^@m+gIz<$%DIg z*=$8c9!BHnpk;8d+&bhwrcw_AX~3_)&E1w|$F+(3Gs)aQ?G*mouj;yf+O_`m!^HvX zF9C#fTZ%b+krzq#e7HG9ow*cssp^+s(odpR-6kfVBG~B_Ker^rH^j2yOP9S)=d*{~6R|pl&icQk z#KCjqO6d-j{50aVl(GzZDp-DPg%wb+KG9$4x%g(my8#22Ty-D(V9ENPrbe0&M9YGQ_?l= z_4UD+)M5{-bW4#jxE>M&Bk;5@^u@uucigI3QF2_^b&s-y41#fLYEGX@{UnO{v23>4 z=+O@a7F6FQ@_2rhRcGkdMI9!w28rpyP9;=hK6bi0-@H|N>ymIa&bgr)rS*7R!+%M{ zRq=7zaXQtdj$(B@6zM~zN%`_g-UNkoX-wkUfh-Sg{G~XGfD#TCemOv`_B*{>CK?Ow zy-vt)ezVr>C|O&g#oo9rss8=#U$a5C*K18mFozSp1cPQYA;#?V<}RhHhhO=s_>`FC zD|(5Ne^QG+&slcAC*!<`yWnc+p$+N6Ngr!cTw*9BP)exiSot7cUb{iz56{@Z*acU2g(;mlFWkpY6=?WKMm~D|bxR#Z<%D0q?@Ky9;17#);*)!_uh0V`aj7%y-pGT(Zn=0#l>*u()yNzl5fe#0@2g6 zyj1si=X+U8{?w4_wEv&H#?DA_6vrc>!mrztNrAsBe*;oiUCYl+GXkCw^j+fT&~j>G zgWP7S&e0Z%K3W~Z*QV6Fv(PA0bHJ`Vd=!$;h%aZ&=p`;vtReshpl?{h^jAe*xj2}w z{XNkWr}PoN(_=5a85ed(U?_=fTy>OS1nE;&t#2*~*AIvf3?F-1oKC&2mb~gnh-Fq@ zStzYG0%&0)$}LfDOGT< zbD1+-YP8!+dnLUqTq&+AUirQJy>MomObzSWsKdHy@$PS^6zUuIq`4^i-gK!9Bh%Tm zcKvO9PVbY?Kka^$alr4)J!;n2zdN6v^m5D-^|}H_I~N9(1sI0qdgqAZ&Y{I3$}JaV zqQwdqn9D=9^psgC$g1K8={E`9^uM-~uOS8KThE^)q><-RgT@1l>%zj68e z%cN9`uA-~3f*XoIYH94R!bW+II zxOI%Zj+-mIy*YLgb$DzZ3qi&O1>)tfD)B=yU(@OJ1 zX=U&EG{6s-_bO+AbG1Cd!;i?zbx{U#^0H)mF6R7t_Bm(Y*Y9pg?X~@=iZk>FOIEZW zBdsG8yO}#-HV(6rbgs9+9E{)MtzDQKRW>m2v&P!;_4rgLqKcWh);^E3r4Kgxqy2ZVAeC%$+4XLOv!9WL*2JO-LKn23Nt|J`BmxrdzK3r|1%5g zQfC1K5_~uNF4jP2A59IRcUM)-=3AXFqjnz{`(H7o*e8RDi5Yg|%x;3&rTHZr&|+*+ zRh%>ukpp$k=VX>MgwY+B2hBn@iuiCN-Oah|xwM6s$z^dfUvc9PF_gl)*O^w+1a5q0nHhbX zuVa{5W5K))4YHtBxVTKrPsFz3x%5Yn4!UbWQUcZda%^%qE z`iz$5uiC>TO^@hIMcI>7kneJfi>y+gLfXv1XJBVczEHO1oMFj( zsq!{Uc0>01!e#$8UzCgdtIogHT<-VHPZ$gZbY|i8rJd}*;|yazm(=|PgvM3@{ATK% zi$0|R{}dH3DJeSFJ&(nak1nAcDOzVXtKpNo85{#jw>q;`phqh|{(C53cG`)yS#a4B z;Zf*3#cC@m55NeDK>pM_5d!x2Xg>LNP0+j|q~to^yQ*zz!ei9t+vZe}Y3H64Q_j(( zN%UINjY118WK;dx*5d9;J3q(Gx$ZQH0Fa>4xeyIk)=l6$dw%6wXXsKZNR(a`NkqD2 zmFVmu&{1` zsUh}gH5~3p7%Ib#glT7FHoxZe_+&1gcb>Y+6N79bPZmn;;Uw9FUJbnyI3Ab{EMzFTO*IEyZl}$Jz&ZW+d7WbXyze^6`dgeN zJwVo_A}HIiK=&+xZ4=5lsBk=*c(5O3lIpb&1qTCi#Pud^Z~$gv2amIh70S)s#e<2X z$@fn1=)cy#>B8|n`TpUW;N0{5X1KP_BT;4GACa+~EfI~LpqsX!5$q#pKP=f-(JAA-J6VgQ@abW$a#afAY~TLrHAF${98RoVBB z=6kS=gg;v~&`uz^j$r$UNnjVOcxL7Jiaf^V$$?Bf7?6ZZQQi&G+YeS@X9bwhvE||( zbKM@b$58!eO+3q?>L*4L4e`w5V@O17{qfu^*hh;ldQ(~uF?KY`VIVBO-2i&nr`@E| z4rHBpqIQh0n;2j89*!<>vaaUobA8!N#}PMX#G|NGEiYeX7I>m^4> z6NbyT>;Gs($ein_n{d=^9KBP8}gfn8w!!m2o+)5Tkx z+cii_JnakDfj|jX+h-|&@+}Nwk7)8e9md6qi|R8+h~a92@2f>z;Vkdvp9?iLP9z;Kk|m|L-#8KOgwWRVt$ z!2PQDO{EyZu{t~;LbVUZ?x#4RA`t?H4E<)3yI%r>NfM?%#j8mYFf~_w+lUZ<^Q8B~ z*dlcUkC?eS6HPYlisx-~LNILx7E5);Fv}c4Kg=SJK1eC9aKIqld&1qhgF zWCvp^JeJ6p$~OLuj=+~s{R%B<@WcXn_26GV`R>F$L3B!I1E~*4kZGDzQcbh zq?(*%Er^zNvJm|+g@UTAU?!p)MYtVw?eL(YDqYTV%qAP03Fz{L!FB)lyiA$5TkCV- zIRxyga70Wa(8bMR$n`N_N5<**yW4uqd)Q{Na|^;>UeNmNo)-?q_j1&Mw(&iV_KLP3 zu}FiQp3XRGiKyB3Bj4@T+T4hyUAqoWf704pZu}p&)rgHp8OfnckwzTU=7Gdb!OVKW z&YDY?^3L1bh3Av+-Zk$8O(k9U_`HETp11Y;oXk#_ch5ir<^h_h=SakDDd|(D+Svgc z1BXA)tjn&qt_}Y-UY$Rtq`BzO8YdZtAT40i<)YI32p`5RtRySM5@)kOVANBMSZxxR zCqnCNTm#S}bdcy{JO8$Z`IaV>X`fYdg+wyEboqZYKJ52G-_(bnU^N11=vZxULAu&x z4EDwNObs1CJ@Dv;nWJFRa-FZr3RnB?nm2D(#d@=fA5&RHcDl>TCNS$qPALD{%}jvg z#*(cm`z?Y7Jz|U|unSyf{sPlh8H5;((_-OHi4cSPs|I~aJ$H(DRl@ZsT!$af`Uc_4 zIZZ-aOrK-YhvkN$WL#g!O%4=Rhtgr%gP%xzri!ntyFGj+d?Pm3P$c!+o}R7@a^aGB z>2Y1^74Q_{KzB`FH^WmNhr}2Co)E{Pyw1~39t?%mGkc}?3rkZ#My6M7G25%~CnmxB zPgjbszi0%v@+sYiW>zd;eMdP#nNZ$YJ)fw9DOszba}{e#Nw7BDLjOGN$5G}IAl*S=z%2>Q346r7ecG!#2=klH(u z^3~cQa*=@O?ML?aN#R1VaGO+si4HtHZS~W`T~8NxSvuAd$yE6L0wFYKju7JBM7LI0 z{`rseB8nuBl2N~K&bCudCCs;3@#z3&yyN?cCrsxY36vn{U-52*F~?Q!`lVqQfEla1 zM{+7t7T@Eh#*tI7^OtTkvd#EkDX8I0V9vC+-RClPoljviRQFOASLPh=SzZAH3k$xu zRwmQuVtw=OrA`P$o8c$~)z`xy6fx5<1YUI*hGrK8sg!0_J$9y-0*MpA93*t5HG9Jl zAedPR@QjYS)O&yK5U38=3=W`GsStU0N{JX$-(i!*y_Cd;4k%w}8I`7Ipreu^0(a&6 zI+uewn@m`4(%;dl*&I8__f)0{F&cGx@)Kpa4ATw55wp&N7x@dB1=-Mgf%AjQ6iWPE zBXnqB;JKh`dmUW)+&H;NK2$9n8oddL>Wn5UuBHP0{%Bt%lOU(t-B7ZAl z5vCy?6lSa*C_vzZHlx(EW~rY!85e645#oinG!2nu@5o@z4ljVxV9S2bW@hY=Wy^yU zz(4FyS#1sRS&ndG^OO_e5{dV)Sh9N2UTjCWSOQ|&Wt3?a#r`9%Ww90at(}r9{f2Sp zjIHbxd70bndihStT~2|ym3-M>CXas|7)bSmC-lH@s3!p)Ct8dT&4}dUo$y4FuW)ila|qPT(HO$4I3%vZfTeQrOZKO9 zYmtlRsQ_2STr^QY5Fm>t%(#PdQ_j*@|Em5m5KP)c*skLamk=)24E4*IjX-v21D0rS z)ZfyeP*`IO7}Bj+J|!7+<>lzu`J0_?vra2(B$aofq81k*2AyO&rsDX2Si*>3a0W5Bd=g6J?33uP(W|rLAoXXI_0U zjp;;Ie|`TiK*d9war+cxB9k>jGU~g;Mt`q>ql2cIwFUUD6*B_u zZ`IbtKpi?{m)Ij_8e;H`JFw}JukB%zrZ)8`p8^PGAFShav zPpNm0cE02qT1bhQH+q@M^+I~;9WPy_Oa^5jkO~a8S`r=Q+2KO4`>M56n$AV(A=dcB zRpo*~IoBk7J&Hp6dW9-s2m&Gfatkx2{-QZi91Qud>}~w?l4Q=3NwG^-y10SXkj`s} zCzoWoh|}X~@xlz(_I1sMy}9%Wz)|;BKH~wrI;`y3iMF1-a&a!0@nQCv8%#u|B+IE1 zIe?_nDFM*W?o9Z5OeLZzwKiKPp&&#%ij)pnOz)h+z0P5BEO4O&Tbs*Dc-qpxwQK3D zBKMvmDuRX*5n1ecBLxvFaVqF|TCtfPj=09;Tr4da#!mt#M+D*ui zF$1LF<2bT|9^>RVeGpO@Hs_?L*h@b^D*h}yplnGh&c>0t9~RU3q~HZemoOO`itgmk#7KMfS7bZ6Oi%oVYJ85ynJu6M~>IU^od>@03?qL0m?pp< zzPwMdHyehanj2InR9nYweXo0}<7=x=*7K<{|3?bPJf)~Ol^!ENJeyAdR4BO3^ssiH zo^mZo7oXfTJT$as=gcqUtv|f!qPxdXH92y)&6QD=hoB!!1wJiAmH*vrS57g7mv|!Aj@_uCr=OS4A5a1;lQM}RD z+>MMp02-a3%13Fw=FJO9pW*SBXXZ4Kcr%2X076Y1smClw+`17o{!FHIhV6nVG5`uWeE#IWk;y~@Nq(5N>ceo znM-e5VWWNnG00(B5QRvZ%4&4Pm4b%Ap^qkjC%F779qP2oyByK1;F-`Z&2@%RCTw99#L*l9gl@aN=Jm(eMQ^U%bApG%ia44! z->I>CmfS0OxNMc7?oh{NzBt`@L06Q<@ha5M^~GB2b(pm9dEHu#GRNLn#2ZJU z#Yyb@o*Y0|8yhk+;5bBZHUm5RzI1c(b@5FVmb3Qf(8pEb-U`oD^%Y_+GIA74y!3qm zoA<6SNo-s$m5_x>9f6bRPW33PI~A^d>Fik=-V`0=e7&s|(NA zNfx|AqBhs4#$Ea~Oaj5G*nH2m-{HuCku6MOs(%#XwoyJApKwMj z&E&#R&*f<%cv<*PnEBY#?(JxSj^6$k`8j5>W~8(-D22^b0CySJDjphI-}|4`D%)af z-3n_Pcg+sDuH*Bss;b~*Mo)Lr-Bwm8e ztlA;VeBQDFXAzI~mr=!NIHY5dO+}g<*ZvL*10U>7>?TU#ErR50+%eK5#*M+MKv#)m zT4Jx~Q#aMAelh4Q$OT>s=?H-f@4tUliy~i@mb|1T{FR!bFzHu|xNb}%??Y`sr@8bc zSj%s|LNw;aaiSe|bu~Eb^;jrlo82!J{clWSnA2b>2Gg~M0^3B#TQ{D)Q0bg4`TuG9 zwzFwDra3@YtGFv4(|;mJADT79ZyV>vC4f~!CBLqMieY){0;m9uw!yxZemO=l(^|&j zwvowt>>PAG)~eqV#-;&5E(eb> zDfFAx80++}O5+M}@!$NZ?=^~}#YQDCX~T*3809KDGcIXLfwoTp-Lh{O zVK9>vq+Sw~mb>3e+gOsAU;@u$Jw5?xk{J!tsB3^tL^3$u) z8mdR$bWpnxs9ciYc{FMXp0xI!@eo?R`=P zS3LT|Whm<0)0&=Up;ek00wLuu1bATn*{x+!0vhEZD)myw2Bl@(1$(dE!W@*;3R)u6 zi4>HP{90K<$RnZ3129=b^WQXu_!-awv82$qfyVlY7f6z41ME)L0rnbSE@nT(_l=NM zbq;-z`cD5aUAmSry}%Spq1s|b|n!R4U?%zw|pCXMpyll`fF>6Cp8%AOUd zCx~IOc;Lc=6IKRIim@6;uye31=!4JRCL69=e9v27S3g(cEvyFbwYbW>PMv-00ZNy^uO!Ijp)RzBw7+E5ybmi$FdG)yFU=7VzJbLgs_m zUD8Vq02Mj56Ng1LS8I>cmw2T9;$3%ylRPQ|Bdb~Kx*A$pHr=?KsQSc#gel4*_T{k~ zz}!PZuMrg|Q<_7|{?~NlGgS&?zHVh9M-1{onq>@HNZG)Qyi%)&GYq{u94c(|^xoE( zugf>+S4ExZ{KeJCdO0L>)~42DKxWSLsNiY^eql5U#3&Oi7yW_SCaH*S0&VLP-y>GA zEkx0OHJ;b9YV}LMN;}?$RTrh<*2?xmHrV!$s5fsQ#hOcs)3sPUv`$ z5dkka{pc*umqym=m}f4Eo2nzx>wPFtc|QJSXewCgE{y!9VIxO ztq8u19fOtteKZGAfzCXmTL)g>`3AS1^rn78pY|#MyhHY}y&j2{`IBMz>1kmHLN^}t z8eyvWPI{Zr>N83SDeEMOrU2J2(!CLRots67FyA{u^U<%-_gQ~*M9s{A1H{_^);jO< zr;kS?H*@Esb4a6$k`72=#}?}kYBO0s<5R#P>b73Ga@y-rlDHW|%Uwy&CVU^)bX?g& zqfxu-9GnGI{>od*mN5t5A6y4_Kk2hMkQZvHY%FNu@`fvsF(ASb)f2Mx)w-xWwN8$? z5P7}H@HwmfkM@lCM?fM#`uauAmXu@#KyV@DzIpLABmPI5;`)1))0}h)ZE-+T*Yj7` zC0A7l#qv;idsZLRFnRSsBSJ*m(eiXlW?$rtrh`hV9M1@z&3-q|lV&;3!^cq`oWuMp z3R#ZCUYXpKXzwG+FY0-p1l2)ij~f}*0Rk~*F~RFf5eDs`YLz~7elhD*f9yhP&_76^ z9!8-KS&oEBJ5NznF(2hg_Lwy!<%Vj=Zsa}t6#6?aPgu^U50}{o$AlB!$Cbfh_Jq}= zQJjT3Cs)|<4~TNfcF=@g>F<9Hrt*)6k1&0>86^zhezD&Op0{orK5VBY6ZE(QG2}ao z<3lNJyDO1~tAi%NBuDecI;37Y-lZ4MHB zB@ACJdRwUoAAKV%sfL1wpf&64*i3!e3El0Rcy1&JIwQO+pj^lM_j?V4KdWA^>t%eb z+^nU0X5s(NzEV$<;Hu$?i`!r0C@RZshRb3Th{FQ1e3C(q{*>Memp zAs>|tPrv8393vSjUu*xRiR_hLGH%b;NEe3NW>kL$Bf7`Ajl3W-pETf59$v(+P(n9X z)J`vf&JzTM^2^~E0|F)UZ&i@-*7uhJU)T%ET{+}|(KJ=C^+#yCJ#Y^4I5t9SPcq6v zx`i7JxO^XEH18{XMSyV10#+|TYU9+s@NkYwU5M)!U*4o~$N9H~)O-&Y(|jRUi!O&Y z&F^Jd^&k4LH-~GtTfD_gxB2mL54H5|yf6bO%2JOm&Hc(sry9U{%g$V-S;hGH zXgAv;g%_sj*kmd_j*rm?WjxC`M<6q#4Xm|z`8!pS^v*o8S(WDWHo#UQ@{hBCa)gcJ zV)x_=62=8aMsy5s-1{&+s(PlZe%jl9BQBO5Y~w=HL%HLpN*&E3pyJ6eKrhsvLsYYP zr!h>^<~mug^p3X^_1SqfiHaI7kSl;~h=Enp%etG^L#sdjJ*n&kUyQWpNLyg^=}P() zW;$c)A)bXs&d?e*|LdOul9v83tX)og>BCl9f5ziS*%~2r3RaS;;-zWU6PLRG{sa=v zydAG$5`^IwrJ(hcDR3}RO~J^!62%zLZv=XhHi0q)l7KZw&Z=B0@2VVA*l_zV_ajvG zzU&n5F-=8|{NqhgDBY+P)CG`M?3V(gBwnEsSr4nL3Fu*Zkr8?LRVKrC!@Sq?IA)WXHX%9ljB)uk0XVU|Z9)g=t z5v&Nk#kom(b?JD{a78HAA6cI*Pli0aItT7Y9{&syU6>W+`4l1Q1gW)%^ zb8?K$H!JC)2VrZ=nnuVKt-26ZU{Q-OD5telO=L2}qdx3FHSV%G{F928(f%plB5{`w#DLHJE!Uu(kut`9M zq#V+MrN%4{Ub4t7c;!LWnV8iUBK%kK1eGO$m)uB%w(<+hw!i|N9E^$N3FycREiiA$ zTKFs25tSnTQowExkRfE^lH z?~jBImlk}B!5ikD`En^Zw(V|nckVCWotT}LK$-6>49>juc5reHUeB#n!uFY&QU*5{ zPs*hubBY!Ig#vTLo4r_fwwXMpFDnE9W{Oj#8{Y&O8-MOT%z+{QD;)90=>Iw!|+0q~XQ(%Y8C`Wzdq$6o(TIg{cw&VK{c@UI;wzMr2>uW&r`ID!8_t z8h?pX1OTZ>)g7YN=yQc0Dc1WhhQ2S(QhPCF-75S77c&sEmRn?cX=#<5|9a*cth4HG zZtV4?q);1Gu_?dI30kx!9Ty{&jknRvBp6krsE4vUJI*v;`IgeP(gxMq1mQA*-wfN_ z;&-U)m5k22n^^$O$!&C#NW#cqj>pF2XH)Vu$@1FqwI+VdtSNU(<>5Gktb4SD_r^*8 zZ4VZ!lJd~Oc?XG$1H^5hjcxm{%c9?KHC5RA+n|v()q9{875Xux5NKIHDRxvxMiI}| zkUIN%VHQ7cm#3hyHMR#{l{u5#vy&Ft^6MeWK;neZCK&>h=tb%2fQm9hCTV(wzv&Ep z-NMpR56-;IHu{E9gw!UP&p$QKdOS+`n}978{h6{n?Frs=TD$qO{c=mn+#uNP6)cM& zu{iD)^r%z%>(d8QEhxc#Yo=R;DBj`@rKtPN@cS|A-s)#i0#DG&=QCJSA+FO$)LA)) z`>i+C#mMMupm5fCqF)^LY~Hk^y@PQvw^sTK<$<>tyNN|#9-^~U(EeM}AGBqFNOj6B z8qvY+2&@xCySQFgs~?j6bhqTSA~ZglaH2_{e?SLN|c+ zWKwt8YD=wJ0(E3_RSm4lJ-HBik?q+B9DkZA-}+3rq^GC#DpFz*PZE4C4z7Zn-KOwr zi`inp0(%^czd>#$lXPowik`MBsbOWnz7{R_O-N1$Y7#=NQUEl(ba?@zDA<#NG4-Lo zGU;}L$3V{wRl?4A_znJ#{@>WdslUNk{-mT1CBK`7%58+oW@KFIX@+V7zDx=V!+{wM zR$VOBy9OYid`r5~rK(uI%S*Np!Prm6%%g^asj&cCQRHas4njK>Z{66_Qo;y2AfU#W zSn0bD%-#3280EcFxQQ0EJsLR~0oqm|Q5qTSCj4NRH1Chzu>*2DsrUGH^6w5}Kna2e zKuCn(f6QLPpl6IBe*vGq+Jf_$tR=~kH1_c2#p@+J*XG$x0IXTaRqvwVfzg_HZLHZc z0g``w=msp3E5^H|B`%LP!(;_?<&38K6?KexYPlg6{z|fN(Q?-pQP%Pbv1$6HCGgL&fZ^cJe^a2 z!W(g#tTM<|^y|+69>*<#2>1HdL|=YK^{bpeW|^$cU3Eu+Pn%Lgh8_Vkx6gnbOtV5f z2#pcrIz{%?&VUYwCs4$D{z&kFbwup~{PR^7fy$yFynSi`De9Y6Rz+;Tt}Yt#dpB#^ z2>@&28RuQi+Zc^)0iGdxd)*m#aC0Q|pP>%xf-Y)8Efic?fSIj&%LV!QWP%83nBr_9 zhy0L9vZv9-zlp@f=y~{}ywZ?TZ4&oZbyP~0Fv!A5h~nHVDTUx#>^#|o5R0SJ<08d( zox8fXDE{Sy0PATgKpWkQ01AF!K{*} ztSnb=R(MIRf)*}Fa7Pu4KDg6>JEb(09F1BLGFK-P2&8EB9-NycfL*#r7l~^z`x1=J zY1Pr5fRoO$#s)%}M2V~xLa=g>cl46=7d71-N0MzQQn{z%*jY_A?G3%SQ zvvX^}n3=O~Hw7=bz6|y5(xLYa#jMWQGhXDIKjdx_Z}l^VDm1-^yvhAtI9QbXFWCPA_&e^&iEwcIcv44doHT{%9=360^xW-ir6jvI@3 zK(x;t>q<=m`=vLQH0Xt9Df3nuolOz+#1BkYrqF}DT||z3f8GJ!KUm*L*{G%d-*~sq z9(Wfl0mue@Vv8XDm_GzU(E|PjVA1`Z&J{AmRWEKSS|eesN9;%?yY5>wHav*lGtvVT zW+`(9v*cJcRB&2&c4I^FwfGMxjn`Y!Wau+&VxcV5vJQHE{4y7YF|-snhdt=x*d0jw zlw`Ob8r~Y!qO_FZfyH{^l{-{yoZWu=2|`PiXm|ji+0U3{LkZyVX*cV7lBTDV0E)ug zHCq`xN(b$kD2LI+vf}#o^`1ka)pkAz_1)ZTH-};ek;l4Es`_5q0AJ{D@MbxnIGx~? z5KGyB4X^83pl|gyZGk|p!FYHf6aP?Cj8i(fwUIzFBD)-H2nq4eNye{$!eC%J`Z}lI zE6x;GNw;5&?kPq5;HB9wKqeq3IzV=#5{aAM!MusOI2wBN0_T?pG@a5rypJ#A8BwH+ zcr%ArMIIlsq`ag&dqAC{9o-g9BYB$#NJz$;PO9%!G@9nm=F zu=h#wTt7NYZmO@(fyYk*YNi(M^!sKt0S0TlA}b!>?X40>_az&eZ5vo;ThqB0G$o3o zQRf=|s5A_c-N%FtbS9J>J@5AH8lO28nm>Jn#d0kbie@3S;<%A)cPvG@6sc->ogH{b z+Atk$P?%4{&dURf~WbI(8Xqm9F29F@te0P9Lz{&wC5U}cpkAy$@UW*L=4BXM^T z*M0}U0kvYPgwZyI!z%LDk|hbTOY)FWlGg%C-gHof9Qq^Au4!Y}9*&r(F0h>ra;582y9^1T;8&4Ff8L(dl=$UJVe4E)oI94IE7}F!u0kr8qt){6|#d^ z$j-@DyV}M@HW5aVU*z^+j8rJ^YKsKMQ!`S(9@(%uL3E-yh0TRy#Py;jt*F#c zh(WwjY|s)TY|zTp;n9^@3JV6nEr8VT*i>0O=2v8E0H6fcT##Zu;4?|4M<@U4q`^B^ zjjfy}+eKRQw@USmi{2RA;u1{NNh*p_P`t4PE7Bn;*W=Ju zgU#KyULwE*zeD4eA4(CjVuu3Tj2$ZK1_uP6W5cRJ(2O?W+U~GMnt$XyRmn+ zz!f6Qv^F2;qy!Q%(VQsFvQkms9U)8F#*>F_OVAk$-ebJ=HQw^el$lHKrR!Ag12M3Q z*63^@Nd$3`9acWIk8m~6Jyn*b!1w_AC@_IZ!cAl_n@!vU{#PzM}x=}q^RM||J$gb0z=x1WtXZd8jMXP|>; z8s&0o&6oJPh``^DzYXH(X9G0g-2@xs79=RWmoI&OH6@Lhila+xT=EHo)%jhj`M=#Qe9pS4Q*%H6#|XodK8C2VWmh;K3zUPbtBNCi4H@Y z44#mf!od`TE$=Z`LESK;1P6C)@KX{m15S=e*lt`XH3ec5nw6R7k@Ap<-Qh79YI)mn z$gd-TY_iz}b>7nasKb-PjV^vsX6FHgxka@p{suji`h4a7NJ_iizkUS!I; z{ME=(+L&T>b;~t!T|I3#%l_m^^=xj6pV>W3Gap>ywnn#f8B(RXSgc~(QtEDFTJ!83 zS9yJBl$t(ASJ(xX$%2E?aD9kvFhWUuCM=b#S z61Dfpf3!qqTYlWB`!+?y+yXRhaximBZrqOX-vVC5#j8r`eEZgq|$N=da!{4rvZ%US(@9bq}Iegz%_&eNQBJ2O%oxK0d{(sFL(>iQ^{l)^!fr_c9I8=%?kfAwL`b@Fi8<)?-mkUq~=ETMm7+@B! z_jVNeuok=tRp7WeEa8c#&(0($uL{ZJZlnga~CGlwuP|!T!AK;NYur|g$K5FjWx?@DiQl=yzFAM zI)Vazt1C3Mv?))NpHzNyeT5{JfioJ72pJ#i`llz>`0Ha7>;U;M=|X3aUs+LCIVFdY20ZIO%Fv!UVI>AiWFz!RF0JgP)rHuBnvNP4Yx)2FD8YDMu!h) zgO>+?A|^1Hk6p)~Md`vhEII^5qNFi2n%Ehm#x*>Kct)hCA(RTaTBXV*oEim3CcqR% zl|2*4H$qu-RaRGc+3Kij06-9w2#k~}43$irm2MQakc_sn6t~ozxAp|m7>)8D9@Si( z(|#54fQ|8@l`yXCVKnv?vPA&tZ~RPJk=60f*rBIzikbMb>tM6p=+h@r%s$!lePQ)^ z>D9Y&b_Vy0BX8|!C|D?<7(rbXpsTaF!OPRt>ErzYB$lBx5~{YLHH)Z+H7pG{64zdq z-+UV6nx5>u8ui+q{Rr$wqJoPPDhzuzFs|p~3zQ;JEoXa&@_aM>A7h)ocIs^J?C|mQ z_WC$v1%d(*&1d2Qi4-yYx89g+k3UhYRLR;EB6;}O$?LP{7FOIOQjJ2BO5Fx5tMww> z8IbO_i8#9W_R9|#$vCLkD(vV##Aa9Pcl{uRrkeyAsJ6ku(AIWvcXt3wG!VkVP`ohW z?Y}obrLaK9I8o`aQA;>tZLnf5ICJ5@g9sKTWb6cn5;|h!>0mzUee`|n3=AYv?G=4>KrfHAiL61lcA`!*B(rZc}F zR1ueSQ#Vyrr*)UV5Px5LcV}-$Pgj7auebky48}&_zfXN#?_=vEk*ZaQ0)5D?VdD~^ zZ1U>v{eOQ+|Nq<@6il5Mna}$_@%*u3(XHm_8a&%SKfeBZcJy_|3M9f5WWpt^!Z!G; zn@Fay(5BN^r}ec$URR9?qV6Lw@*MS zY_jM+Q4hq{83YL$HAS*SoV!WU#TcJ`*zSEoHb?Ppv6Eo84i)UZd^YZjf^g$VjrPb=)$YLa zw{eW+mpz86y;J&{Ch#-{{B|?>af4zQp)Gpj9djNpb6PU2++jU#hvi3w7OESZUN8Eu zV4E7_)f!HOjv@!*s))9r95mb}t}|fn7E|JMF7yEWZ1Oq*Z*|G>Rc0?`j11qmYIc}m z=`vp5w+hCMAkD1w)#WV-b&od6$}xgLl7UF(<}hcYQf4P0#>Ha#D{OyLdFgIzpF8kq z1f!v=SO7+_SO4i%fjor)u`>>PaM@fYyY!TGCL*k|(aYSo>Z5j$e>2CB7Es5Ds-Zt5 zlC>EiGlYKz2r>{vmN1XvLFLHb9p^y31yr)arqx8bi`gujRI5~=qkZ*5-$W7Ru?*wa zC!(6o=9Q3ikEdYDRPP2o%eB@@0iZUWGx^m)+%=KMM&2mA!`yeq-KEiKk}+56aSjWqEWGE69? zD%2s0=rVR2Go34Nv=PzX1}%s$p-Xcm{&`^1o9j8lT?C zPnyU0!-y+4$7)9h4n**@hy;v?IqO&_NYqDhtIck zovwI1e}k(296wc;pE*Qg1O2xCs+6XF65oy{;@lS--2a2AbnnO02vgh*kfIyZ*Jn@5w!r-{OGqK5T+Hk`a2rMLXB@COT30_CALx?{Q8TmoF|L7+Erm>#W6T#dFK#N! z5RMMItM}roQ#L;d)O`phuOD;Fm%E$Uwj)(tCT^}OzA1)@>*a@XTETL->!*{zJ7<(*$NM`kSn&po~Z^H0)YWWu+l9+?EhMBSRUI~`=Qo*sngc=;Ap&hr`E>Vd#DM{OEqOn}s z7FHoxeg*}b%)wOU4!wZNNG$PQ#Sr}q&faFnbnJ+qaBV6VX#Lyo(X@C`ua3b7kq$eklFtNv;^aih$=NOT}orq-)W1P+jLppDZ1Xcz* zn^t|vypmi%Xf!l5^INtI{o!7;vqt`xJFG`p`{obw;EJ*sRzE5@o65J+FV8;`k#*!3 zaR-mM1lkoWL@37tKG5EvnbwLj7X7ul#bk3!;MUt7r=tilDHTnB`^)^*mTufADam(o ze=c8g2U042qI+$ZM~K6M!oNOu8m$ac zA(&X{YLBp(&Oe6T`bG`NG_aU#Qw2CC-uYF)We7dVs%iPJQ9RUjBQml$T-qQ_qPR>C zqZxB`I!bJ5IWX2UQna|p<o2kuJ3eW{Yd|91HGtDhRO zA8ra-^S9&*f1cutNF4&T6YAd|s9W045753a?h2y86uTdox7Az_ED7cJEeB`uapb!a zQEa2&z-?2)rKONr1!?sGKiamwAUx)$g+w`ZgAmQ zFdyB;^Qhrq5(Y_HY_yV6APCgZ_66Ef|I&Sm_c+kDG9^+NQPWl&BZcQV6W^Lp@~W^EJFk_Z4+ zd)BR1qq89ZG$z^ad=0L^w`&gR(mO&=`n zR8a;=fx7L4G{}tAUgcqv*+LXOX-cvf`{hg9%f$D!{g{Dw9h;y+o+&vTt;x@_;VloT zemnEKBOc5ty&8u)N2Zl1N-T1%*@NpGb;_f?3a>0~e`Rxs)D|E)W~^;F7>I&%**vPC zh5sF(-Tod|LYE8O&fT(!QXunpd)QJ>wc!L}+OPDo&SS6m zPs33gkV5UUf~*L0Z5iqZ#LFdg%?Us5T}{EcU)#-ByWVg~bv2JQH#+JF7l z=AKtRMlJM8;%_SNXaz|jWN#iCN>|hllpj%}=;{y(WrJYh+62wcnSm=nkKu2)H|Mbz z2LCR6N~oh?tWin>FDG?0z_OBma+s0qwj}gn%?Vg9$A=K|fy{N*eU zyUd2x@|Fjd*^T7ODbDz@LCCEM>V&3Pe??!={5bi_pmccK4!9N|OuC75Y7{{5abOkf zvwY#WFMPM|Nj?~h;D5@C(D7(ZI8Oe$CgN?~eyN$UN}jO=?Q%5-87m!27MDq&jUGoR z6RPcegF%`)IC$R#Fr1h3-UT1}Xo4z}c;TpfqO;Tm%23|Yo;nU1 zTa77lE9BS>LN5X9DlQL7>d^U9V{x1(0kh0-goJfM~nEN z>@1b>r%4qfj$ipbJ=h4Ra}>cSL*W+&Cb7Puo!hE{Qxmfm{Lf~_rrbZ@+y{CtI4$A@ zik%#V!~erm9J9i}1m_KXtB*3kGz%wd~fnaYrzi1x;DDpWnrv zDfGC{OZ#5hQ1_@gH(r15xf!KCjL37YS8M;0-|1yW9<;<`epo`F<9=f0_$_C%;2ryx z&Dpbqp44$OjJ!uxZ!p#B>wIu1bM+1r@%$ldr#uN_bfnyMCc|8(AK&I;!7um=Huz%> zNp4Kk30xuN%Ji6lb?HOBg;$#}&4u!gygCj>+NE^e0kq>fxmhs-U)@g%28+yK+jW1z zg+nlb_cWT*6FD`F@KDk&_g?5pb^V_N+8B~7q4S+dRqQ%2t*hx)2!cmFgt=MVIOuJq z7bD~~ED@hVGydXtMaBX-O1+aH#wbdQ3-vXK#(^@w4kEn-X$*aWX$^j@`L1;Ot%5j9 zF>O5g(@`sV3EIT+YF8TwO*32VG>nEBE%cetgVJ0n zu8y0fCW5?4OM8TL!i-U(!l#=r3_4pF;%0lwlU_M!-?=CVJK+m3-J^=R8ujo~kKM`u zXYt#FI>o5JPBt1OW;xZ$1C)TjO@*Z7WV(AB7D57remq7X;i%yu z%zBB43IS66bQ+Csk4(sS{_k=}91NEQLtz?+lRL~S_*i@RYRbg{dvUPSW_Oz*lZ(LA zrm6CHOlxNX8d;oIf5`JV=VAFf*Z`}b{kmBN8MV|yH0x>fh3<@ruOrn_zOBdsyT9Hgw`BrneYa_Z%YtjKD$rJw)RJW$5PEe)-LzD5ehC51@FQ zK3s@+n)b@R0>90<*JR!w7#nzAZwTEST9`CD zY;$;nA{IQ9%jh#JwKfDbXY$TGM({AAlwtTQy)M=~7IuhFG@S795k?_in{4!T6Y=y$ zm4kI-kAWFKsNJWQyz~WC>WTH|ySOAw6ORh}svMj;``j4M`W;%N61PkiCq4@s^{G-%D_Z-M<7u)>CQ*ncol)G#AED@5zBF&q(V}E=vI6mNcsI!^CD~*}+=W0-dhJRIW1?D&$<{tkA*vW)J;YFIX-0sSVk?MJ_ zx6;*9gi`LJLTfY)GISCkW0$CQ?>%VDD&dEUuLBfOS6}q_*8y_XgG#0}&AZQmO0#uj z-G}iCN(M1T%l!HhM#T9dGMWjvQOsr>3Hwa}gPJ+H!5%|!{l1EezcyzAD%RnO88Pn5 z%Z6h46<3s+IZ(yB@kB&^?{?Vc-err@`r=vbfr$fzdfZVz$QRD^Y*~XH$CUu@~@#$?QHk%814= z#6jJOsSs=v>`0II$4;fWbDbcV#rAxkesfB4TZgqVcf3= wKyolddaAP&D<*nBE;N%Gvr^lGp94xz>=t2rw;QX9!jlZs17w&G4G8GJ0Cs?my#N3J diff --git a/proco/background/admin.py b/proco/background/admin.py deleted file mode 100644 index 88ce0d6..0000000 --- a/proco/background/admin.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.contrib import admin - -from proco.background.models import BackgroundTask - - -@admin.register(BackgroundTask) -class BackgroundTaskAdmin(admin.ModelAdmin): - readonly_fields = ('task_id', 'status', 'created_at', 'log', 'completed_at') - change_form_template = 'admin/background/backgroud_task_change.html' - list_display = ('task_id', 'status', 'created_at', 'completed_at') - search_fields = ('task_id', 'log') diff --git a/proco/connection_statistics/admin.py b/proco/connection_statistics/admin.py deleted file mode 100644 index e22f0c9..0000000 --- a/proco/connection_statistics/admin.py +++ /dev/null @@ -1,113 +0,0 @@ -from django.contrib import admin - -from proco.connection_statistics.models import ( - CountryDailyStatus, - CountryWeeklyStatus, - RealTimeConnectivity, - SchoolDailyStatus, - SchoolWeeklyStatus, -) -from proco.locations.filters import CountryFilterList, SchoolCountryFilterList -from proco.utils.admin import CountryNameDisplayAdminMixin, SchoolNameDisplayAdminMixin - - -@admin.register(CountryWeeklyStatus) -class CountryWeeklyStatusAdmin(CountryNameDisplayAdminMixin, admin.ModelAdmin): - list_display = ('get_country_name', 'year', 'week', 'integration_status', 'connectivity_speed', 'schools_total', - 'schools_connected', 'schools_connectivity_unknown', 'schools_connectivity_no', - 'schools_connectivity_moderate', 'schools_connectivity_good') - list_filter = ('integration_status', CountryFilterList) - list_select_related = ('country',) - search_fields = ('country__name', 'year', 'week') - ordering = ('-id',) - readonly_fields = ('year', 'week', 'integration_status') - - def get_queryset(self, request): - qs = super().get_queryset(request) - if not request.user.is_superuser: - qs = qs.filter(country__in=request.user.countries_available.all()) - return qs.defer( - 'country__geometry', - 'country__geometry_simplified', - ) - - def has_change_permission(self, request, obj=None): - perm = super().has_change_permission(request, obj) - if not request.user.is_superuser and obj: - perm = obj.country in request.user.countries_available.all() - return perm - - -@admin.register(SchoolWeeklyStatus) -class SchoolWeeklyStatusAdmin(SchoolNameDisplayAdminMixin, admin.ModelAdmin): - list_display = ('get_school_name', 'year', 'week', 'connectivity_speed', - 'connectivity_latency', 'num_students', 'num_teachers') - list_filter = (SchoolCountryFilterList,) - list_select_related = ('school',) - search_fields = ('school__name', 'year', 'week') - ordering = ('-id',) - readonly_fields = ('year', 'week') - raw_id_fields = ('school',) - - def get_queryset(self, request): - qs = super().get_queryset(request) - if not request.user.is_superuser: - qs = qs.filter(school__country__in=request.user.countries_available.all()) - return qs - - -@admin.register(CountryDailyStatus) -class CountryDailyStatusAdmin(CountryNameDisplayAdminMixin, admin.ModelAdmin): - list_display = ('get_country_name', 'date', 'connectivity_speed', 'connectivity_latency') - list_select_related = ('country',) - list_filter = (CountryFilterList,) - search_fields = ('country__name',) - ordering = ('-id',) - date_hierarchy = 'date' - raw_id_fields = ('country',) - - def get_queryset(self, request): - qs = super().get_queryset(request) - if not request.user.is_superuser: - qs = qs.filter(country__in=request.user.countries_available.all()) - return qs.defer( - 'country__geometry', - 'country__geometry_simplified', - ) - - -@admin.register(SchoolDailyStatus) -class SchoolDailyStatusAdmin(SchoolNameDisplayAdminMixin, admin.ModelAdmin): - list_display = ('get_school_name', 'date', 'connectivity_speed', 'connectivity_latency') - list_select_related = ('school',) - search_fields = ('school__name',) - list_filter = (SchoolCountryFilterList,) - ordering = ('-id',) - raw_id_fields = ('school',) - show_full_result_count = False - - def get_queryset(self, request): - qs = super().get_queryset(request) - if not request.user.is_superuser: - qs = qs.filter(school__country__in=request.user.countries_available.all()) - return qs - - -@admin.register(RealTimeConnectivity) -class RealTimeConnectivityAdmin(admin.ModelAdmin): - list_display = ('id', 'created', 'connectivity_speed', 'connectivity_latency') - ordering = ('-id',) - readonly_fields = ('school', 'created', 'modified') - show_full_result_count = False - - def has_add_permission(self, request): - return False - - def has_delete_permission(self, request, obj=None): - return False - - def has_change_permission(self, request, obj=None): - return False - - def has_view_permission(self, request, obj=None): - return True diff --git a/proco/dailycheckapp_contact/__init__.py b/proco/dailycheckapp_contact/__init__.py deleted file mode 100644 index 3ae615e..0000000 --- a/proco/dailycheckapp_contact/__init__.py +++ /dev/null @@ -1 +0,0 @@ -default_app_config = 'proco.dailycheckapp_contact.apps.DailyCheckAppContactConfig' diff --git a/proco/dailycheckapp_contact/admin.py b/proco/dailycheckapp_contact/admin.py deleted file mode 100644 index 0e76926..0000000 --- a/proco/dailycheckapp_contact/admin.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.contrib import admin - -from proco.dailycheckapp_contact.models import ContactMessage - - -class ContactMessageAdmin(admin.ModelAdmin): - list_display = ('firstname', 'lastname', 'school_id', 'email', 'created', 'message') - - def has_add_permission(self, request): - return False - - def has_change_permission(self, request, obj=None): - return False - - -admin.site.register(ContactMessage, ContactMessageAdmin) diff --git a/proco/dailycheckapp_contact/api.py b/proco/dailycheckapp_contact/api.py deleted file mode 100644 index 62d320f..0000000 --- a/proco/dailycheckapp_contact/api.py +++ /dev/null @@ -1,9 +0,0 @@ -from rest_framework import permissions -from rest_framework.generics import CreateAPIView - -from proco.dailycheckapp_contact.serializers import DailyCheckAppContactSerializer - - -class DailyCheckAppContactAPIView(CreateAPIView): - permission_classes = (permissions.AllowAny,) - serializer_class = DailyCheckAppContactSerializer diff --git a/proco/dailycheckapp_contact/api_urls.py b/proco/dailycheckapp_contact/api_urls.py deleted file mode 100644 index 47ec2c9..0000000 --- a/proco/dailycheckapp_contact/api_urls.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.urls import path - -from proco.dailycheckapp_contact import api - -app_name = 'dailycheckapp_contact' - -urlpatterns = [ - path('dailycheckapp_contact/', api.DailyCheckAppContactAPIView.as_view(), name='dailycheckapp_contact'), -] diff --git a/proco/dailycheckapp_contact/apps.py b/proco/dailycheckapp_contact/apps.py deleted file mode 100644 index bfb9840..0000000 --- a/proco/dailycheckapp_contact/apps.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.apps import AppConfig - - -class DailyCheckAppContactConfig(AppConfig): - name = 'proco.dailycheckapp_contact' - verbose_name = 'Daily Check App Contact' - - def ready(self): - from proco.dailycheckapp_contact import receivers # NOQA diff --git a/proco/dailycheckapp_contact/migrations/0001_initial.py b/proco/dailycheckapp_contact/migrations/0001_initial.py deleted file mode 100644 index a85f8e8..0000000 --- a/proco/dailycheckapp_contact/migrations/0001_initial.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 2.2.17 on 2020-12-01 10:29 - -from django.db import migrations, models -import django.utils.timezone -import model_utils.fields - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='ContactMessage', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('firstname', models.CharField(max_length=256)), - ('lastname', models.CharField(max_length=256)), - ('school_id', models.CharField(max_length=256)), - ('email', models.CharField(max_length=256)), - ('message', models.TextField()), - ], - options={ - # 'abstract': False, - 'db_table': 'dailycheckapp_contact_contactmessages', - # 'managed': False, - }, - ), - ] diff --git a/proco/dailycheckapp_contact/migrations/__init__.py b/proco/dailycheckapp_contact/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/proco/dailycheckapp_contact/models.py b/proco/dailycheckapp_contact/models.py deleted file mode 100644 index 71c0ec6..0000000 --- a/proco/dailycheckapp_contact/models.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.db import models - -from model_utils.models import TimeStampedModel - - -class ContactMessage(TimeStampedModel, models.Model): - firstname = models.CharField(max_length=256) - lastname = models.CharField(max_length=256) - school_id = models.CharField(max_length=256) - email = models.CharField(max_length=256) - message = models.TextField() - - class Meta: - db_table = 'dailycheckapp_contact_contactmessages' - - def __str__(self): - return 'Message from: {0} ({1})'.format(self.firstname, self.created) diff --git a/proco/dailycheckapp_contact/receivers.py b/proco/dailycheckapp_contact/receivers.py deleted file mode 100644 index 0f27b76..0000000 --- a/proco/dailycheckapp_contact/receivers.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.conf import settings -from django.db.models.signals import post_save -from django.dispatch import receiver - -from constance import config -from templated_email import send_templated_mail - -from proco.dailycheckapp_contact.models import ContactMessage - - -@receiver(post_save, sender=ContactMessage) -def send_email_notification(instance, created=False, **kwargs): - if created and config.DAILYCHECKAPP_CONTACT_EMAIL: - send_templated_mail( - '/dailycheckapp_contact_email', settings.DEFAULT_FROM_EMAIL, [config.DAILYCHECKAPP_CONTACT_EMAIL], context={ - 'contact_message': instance, - }, - ) diff --git a/proco/dailycheckapp_contact/serializers.py b/proco/dailycheckapp_contact/serializers.py deleted file mode 100644 index ef353fb..0000000 --- a/proco/dailycheckapp_contact/serializers.py +++ /dev/null @@ -1,9 +0,0 @@ -from rest_framework import serializers - -from proco.dailycheckapp_contact.models import ContactMessage - - -class DailyCheckAppContactSerializer(serializers.ModelSerializer): - class Meta: - model = ContactMessage - fields = ('firstname', 'lastname', 'school_id', 'email', 'message') diff --git a/proco/locations/admin.py b/proco/locations/admin.py deleted file mode 100644 index 44341d2..0000000 --- a/proco/locations/admin.py +++ /dev/null @@ -1,129 +0,0 @@ -from django.contrib import admin, messages -from django.contrib.gis.admin import GeoModelAdmin -from django.db import transaction -from django.db.models import Prefetch -from django.http import HttpResponseRedirect -from django.template.response import TemplateResponse -from django.urls import path, reverse -from django.utils.safestring import mark_safe - -from proco.background.models import BackgroundTask -from proco.background.tasks import reset_countries_data, validate_countries -from proco.locations.filters import CountryFilterList -from proco.locations.models import Country, Location -from proco.utils.admin import CountryNameDisplayAdminMixin - - -@admin.register(Country) -class CountryAdmin(GeoModelAdmin): - modifiable = False - - list_display = ('name', 'code', 'flag_preview') - search_fields = ('name',) - exclude = ('geometry', 'geometry_simplified') - raw_id_fields = ('last_weekly_status',) - actions = ('update_country_status_to_joined', 'clearing_all_data') - - def flag_preview(self, obj): - if not obj.flag: - return '' - return mark_safe(f'') # noqa: S703,S308 - - flag_preview.short_description = 'Flag' - - def get_queryset(self, request): - return super().get_queryset(request).defer('geometry', 'geometry_simplified') - - def get_actions(self, request): - actions = super().get_actions(request) - del actions['delete_selected'] - return actions - - def get_urls(self): - urls = super().get_urls() - custom_urls = [ - path('mark-as-joined/', self.update_country_status_to_joined, name='update_country_status_to_joined'), - path('delete-schools-and-statistics/', self.clearing_all_data, name='delete-schools-and-statistics'), - ] - return custom_urls + urls - - @staticmethod - def check_access(request, queryset): - access = True if request.user.is_superuser else False - if not access: - countries_available = request.user.countries_available.values('id') - qs_not_available = queryset.exclude(id__in=countries_available) - result = (False, qs_not_available) if not qs_not_available.exists() else (True, None) - else: - result = True, None - return result - - def update_country_status_to_joined(self, request, queryset=None): - access, qs_not_available = self.check_access(request, queryset) - - if not access: - message = f'You do not have access to change countries: ' \ - f'{", ".join(qs_not_available.values_list("name", flat=True))}' - level = messages.ERROR - self.message_user(request, message, level=level) - return HttpResponseRedirect(reverse('admin:locations_country_changelist')) - - else: - if request.method == 'POST' and 'post' in request.POST: - objects = request.POST.get('post') - task = validate_countries.apply_async((objects.split(','),), countdown=2) - BackgroundTask.objects.get_or_create(task_id=task.id) - message = 'Countries validation started. Please wait.' - level = messages.INFO - self.message_user(request, message, level=level) - return HttpResponseRedirect(reverse('admin:background_backgroundtask_change', args=[task.id])) - else: - objects = ','.join(str(i) for i in queryset.values_list('id', flat=True)) - context = {'opts': self.model._meta, 'objects': objects, 'action': 'mark_as_joined'} - return TemplateResponse(request, 'admin/locations/action_confirm.html', context) - - update_country_status_to_joined.short_description = 'Mark country data source as verified (non-OSM)' - - @transaction.atomic - def clearing_all_data(self, request, queryset=None): - access, qs_not_available = self.check_access(request, queryset) - if not access: - message = f'You do not have access to change countries: ' \ - f'{", ".join(qs_not_available.values_list("name", flat=True))}' - level = messages.ERROR - self.message_user(request, message, level=level) - return HttpResponseRedirect(reverse('admin:locations_country_changelist')) - - else: - if request.method == 'POST' and 'post' in request.POST: - objects = request.POST.get('post') - task = reset_countries_data.apply_async((objects.split(','),), countdown=2) - BackgroundTask.objects.get_or_create(task_id=task.id) - message = 'Country data clearing started. Please wait.' - level = messages.INFO - self.message_user(request, message, level=level) - return HttpResponseRedirect(reverse('admin:background_backgroundtask_change', args=[task.id])) - else: - objects = ','.join(str(i) for i in queryset.values_list('id', flat=True)) - context = {'opts': self.model._meta, 'objects': objects, 'action': 'delete_schools_and_statistics'} - return TemplateResponse(request, 'admin/locations/action_confirm.html', context) - - clearing_all_data.short_description = 'Delete school points & saved statistics' - - -@admin.register(Location) -class LocationAdmin(CountryNameDisplayAdminMixin, GeoModelAdmin): - modifiable = False - show_full_result_count = False - - list_display = ('name', 'get_country_name') - list_filter = (CountryFilterList,) - search_fields = ('name', 'country__name') - exclude = ('geometry_simplified',) - raw_id_fields = ('parent', 'country') - ordering = ('id',) - - def get_queryset(self, request): - return super().get_queryset(request).prefetch_related( - Prefetch('country', Country.objects.defer('geometry', 'geometry_simplified')), - ) diff --git a/proco/realtime_dailycheckapp/__init__.py b/proco/realtime_dailycheckapp/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/proco/realtime_dailycheckapp/migrations/0001_initial.py b/proco/realtime_dailycheckapp/migrations/0001_initial.py deleted file mode 100644 index 6ba52b7..0000000 --- a/proco/realtime_dailycheckapp/migrations/0001_initial.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 2.2.18 on 2021-03-24 13:25 - -import django.contrib.postgres.fields.jsonb -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='DailyCheckApp_Measurement', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('Timestamp', models.DateTimeField()), - ('UUID', models.TextField(blank=True)), - ('BrowserID', models.TextField(blank=True)), - ('school_id', models.TextField()), - ('DeviceType', models.TextField(blank=True)), - ('Notes', models.TextField()), - ('ClientInfo', django.contrib.postgres.fields.jsonb.JSONField(default=dict)), - ('ServerInfo', django.contrib.postgres.fields.jsonb.JSONField(default=dict)), - ('annotation', models.TextField(blank=True)), - ('Download', models.FloatField(blank=True)), - ('Upload', models.FloatField(blank=True)), - ('Latency', models.IntegerField(blank=True)), - ('Results', django.contrib.postgres.fields.jsonb.JSONField(default=dict)), - ], - options={ - 'db_table': 'dailycheckapp_measurements', - 'managed': False, - }, - ), - ] diff --git a/proco/realtime_dailycheckapp/migrations/__init__.py b/proco/realtime_dailycheckapp/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/proco/realtime_dailycheckapp/models.py b/proco/realtime_dailycheckapp/models.py deleted file mode 100644 index f973f9b..0000000 --- a/proco/realtime_dailycheckapp/models.py +++ /dev/null @@ -1,46 +0,0 @@ -from datetime import datetime, timedelta - -from django.contrib.postgres.fields import JSONField -from django.core.cache import cache -from django.db import models -from django.utils import timezone - - -class DailyCheckApp_MeasurementManager(models.Manager): - def get_queryset(self): - return super().get_queryset().using('dailycheckapp_realtime') - - -class DailyCheckApp_Measurement(models.Model): - DAILYCHECKAPP_MEASUREMENT_DATE_CACHE_KEY = 'dailycheckapp_realtime_last_dailycheckapp_measurement_at' - - Timestamp = models.DateTimeField() - UUID = models.TextField(blank=True) - BrowserID = models.TextField(blank=True) - school_id = models.TextField() - DeviceType = models.TextField(blank=True) - Notes = models.TextField(blank=True) - ClientInfo = JSONField(default=dict) - ServerInfo = JSONField(default=dict) - annotation = models.TextField(blank=True) - Download = models.FloatField(blank=True) - Upload = models.FloatField(blank=True) - Latency = models.IntegerField(blank=True) - Results = JSONField(default=dict) - - objects = DailyCheckApp_MeasurementManager() - - class Meta: - managed = False - db_table = 'dailycheckapp_measurements' - - @classmethod - def get_last_dailycheckapp_measurement_date(cls) -> datetime: - last_dailycheckapp_measurement_at = cache.get(cls.DAILYCHECKAPP_MEASUREMENT_DATE_CACHE_KEY) - if not last_dailycheckapp_measurement_at: - return timezone.now() - timedelta(days=1) - return last_dailycheckapp_measurement_at - - @classmethod - def set_last_dailycheckapp_measurement_date(cls, value: datetime): - cache.set(cls.DAILYCHECKAPP_MEASUREMENT_DATE_CACHE_KEY, value) diff --git a/proco/realtime_dailycheckapp/tests/__init__.py b/proco/realtime_dailycheckapp/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/proco/realtime_dailycheckapp/tests/db/__init__.py b/proco/realtime_dailycheckapp/tests/db/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/proco/realtime_dailycheckapp/tests/db/initdb.sql b/proco/realtime_dailycheckapp/tests/db/initdb.sql deleted file mode 100644 index 5652fc8..0000000 --- a/proco/realtime_dailycheckapp/tests/db/initdb.sql +++ /dev/null @@ -1,20 +0,0 @@ --- clean managed tables -drop table if exists public.dailycheckapp_measurements; - --- create tables & relations -CREATE TABLE public.dailycheckapp_measurements ( - id serial, - "Timestamp" timestamp with time zone, - "UUID" text, - "BrowserID" text, - school_id text NOT NULL, - "DeviceType" text, - "Notes" text, - "ClientInfo" jsonb, - "ServerInfo" jsonb, - annotation text, - "Download" double precision, - "Upload" double precision, - "Latency" bigint, - "Results" jsonb -); diff --git a/proco/realtime_dailycheckapp/tests/db/test.py b/proco/realtime_dailycheckapp/tests/db/test.py deleted file mode 100644 index d9f3dc3..0000000 --- a/proco/realtime_dailycheckapp/tests/db/test.py +++ /dev/null @@ -1,9 +0,0 @@ -import os - -from django.db import connections - - -def init_test_db(): - with connections['dailycheckapp_realtime'].cursor() as cursor: - with open(os.path.join('proco', 'realtime_dailycheckapp', 'tests', 'db', 'initdb.sql'), 'r') as initdb_file: - cursor.execute(initdb_file.read()) diff --git a/proco/realtime_dailycheckapp/tests/factories.py b/proco/realtime_dailycheckapp/tests/factories.py deleted file mode 100644 index f66123e..0000000 --- a/proco/realtime_dailycheckapp/tests/factories.py +++ /dev/null @@ -1,42 +0,0 @@ -from django.utils import timezone - -import factory.fuzzy - -from proco.realtime_dailycheckapp.models import DailyCheckApp_Measurement - - -class DailyCheckApp_MeasurementFactory(factory.django.DjangoModelFactory): - Timestamp = factory.LazyFunction(lambda: timezone.now()) - UUID = factory.fuzzy.FuzzyText() - BrowserID = factory.fuzzy.FuzzyText() - school_id = factory.fuzzy.FuzzyText() - DeviceType = factory.fuzzy.FuzzyText() - Notes = factory.fuzzy.FuzzyText() - ClientInfo = { - 'IP': '127.0.0.1', - 'City': 'Neverwinter', - 'Postal': '9999', - 'Region': 'Sword Coast North', - 'Country': 'Faerûn', - 'Latitude': 0.01, - 'Timezone': 'America/New_York', - 'Longitude': 0.01, - } - ServerInfo = { - 'URL': 'http://localhost:7123', - 'City': 'Icewind Dale', - 'IPv4': '127.0.0.1', - 'Label': 'New York', - 'Country': 'Faerûn', - } - annotation = factory.fuzzy.FuzzyText() - Download = factory.fuzzy.FuzzyFloat(0, 10**6) - Upload = factory.fuzzy.FuzzyFloat(0, 10**6) - Latency = factory.fuzzy.FuzzyInteger(1, 1000) - Results = { - 'CurMSS': '1428', - 'Timeouts': '0', - } - - class Meta: - model = DailyCheckApp_Measurement diff --git a/proco/realtime_dailycheckapp/tests/test_sync.py b/proco/realtime_dailycheckapp/tests/test_sync.py deleted file mode 100644 index 43510da..0000000 --- a/proco/realtime_dailycheckapp/tests/test_sync.py +++ /dev/null @@ -1,130 +0,0 @@ -from datetime import timedelta - -from django.core.cache import cache -from django.db.models import Sum -from django.test import TestCase -from django.utils import timezone - -from proco.connection_statistics.models import RealTimeConnectivity -from proco.realtime_dailycheckapp.models import DailyCheckApp_Measurement -from proco.realtime_dailycheckapp.tests.db.test import init_test_db -from proco.realtime_dailycheckapp.tests.factories import DailyCheckApp_MeasurementFactory -from proco.realtime_dailycheckapp.utils import sync_dailycheckapp_realtime_data -from proco.schools.tests.factories import SchoolFactory - - -class SyncTestCase(TestCase): - databases = ['default', 'realtime', 'dailycheckapp_realtime'] - - @classmethod - def setUpClass(cls): - super().setUpClass() - init_test_db() - - def setUp(self) -> None: - super().setUp() - cache.delete(DailyCheckApp_Measurement.DAILYCHECKAPP_MEASUREMENT_DATE_CACHE_KEY) - - def test_empty_cache(self): - school = SchoolFactory(external_id='test_1') - DailyCheckApp_MeasurementFactory(Timestamp=timezone.now() - timedelta(days=1, hours=1), school_id='test_1', Download=1) - DailyCheckApp_MeasurementFactory(Timestamp=timezone.now() - timedelta(hours=23), school_id='test_1', Download=2) - - sync_dailycheckapp_realtime_data() - - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 1) - self.assertEqual(RealTimeConnectivity.objects.first().school, school) - self.assertEqual(RealTimeConnectivity.objects.aggregate(speed=Sum('connectivity_speed'))['speed'], 2048) - - self.assertGreater(DailyCheckApp_Measurement.get_last_dailycheckapp_measurement_date(), timezone.now() - timedelta(hours=23, seconds=5)) - - def test_cached_dailycheckapp_measurement_date(self): - SchoolFactory(external_id='test_1') - DailyCheckApp_MeasurementFactory(Timestamp=timezone.now() - timedelta(days=1, hours=1), school_id='test_1', Download=1) - DailyCheckApp_MeasurementFactory(Timestamp=timezone.now() - timedelta(hours=23), school_id='test_1', Download=2) - - DailyCheckApp_Measurement.set_last_dailycheckapp_measurement_date(timezone.now() - timedelta(days=1, hours=2)) - - sync_dailycheckapp_realtime_data() - - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 2) - self.assertEqual(RealTimeConnectivity.objects.aggregate(speed=Sum('connectivity_speed'))['speed'], 3072) - - def test_idempotency(self): - SchoolFactory(external_id='test_1') - DailyCheckApp_MeasurementFactory(school_id='test_1', Download=1) - DailyCheckApp_MeasurementFactory(school_id='test_1', Download=2) - - # two objects synchronized because they added after default last dailycheckapp_measurement date (day ago) - sync_dailycheckapp_realtime_data() - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 2) - - # no new entries added, because they are already synchronized - RealTimeConnectivity.objects.all().delete() - sync_dailycheckapp_realtime_data() - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 0) - - # two previous entries synchronized again as we moved date back - DailyCheckApp_Measurement.set_last_dailycheckapp_measurement_date(timezone.now() - timedelta(hours=1)) - sync_dailycheckapp_realtime_data() - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 2) - - def test_school_matching(self): - school = SchoolFactory(external_id='test_1') - DailyCheckApp_Measurement.set_last_dailycheckapp_measurement_date(timezone.now() - timedelta(seconds=1)) - DailyCheckApp_MeasurementFactory(school_id='test_1') - sync_dailycheckapp_realtime_data() - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 1) - self.assertEqual(RealTimeConnectivity.objects.first().school, school) - - def test_school_matching_external_id_collision(self): - SchoolFactory(external_id='test_1') - school = SchoolFactory(external_id='test_1', country__code='US', country__name='United States') - SchoolFactory(external_id='test_1') - - DailyCheckApp_Measurement.set_last_dailycheckapp_measurement_date(timezone.now() - timedelta(seconds=1)) - dailycheckapp_measurement = DailyCheckApp_MeasurementFactory(school_id='test_1') - dailycheckapp_measurement.ClientInfo['Country'] = 'US' - dailycheckapp_measurement.save() - - sync_dailycheckapp_realtime_data() - - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 1) - self.assertEqual(RealTimeConnectivity.objects.first().school, school) - - def test_school_matching_without_client_info(self): - # last school will be used cause to mapping logic - SchoolFactory(external_id='test_1') - school = SchoolFactory(external_id='test_1') - - DailyCheckApp_Measurement.set_last_dailycheckapp_measurement_date(timezone.now() - timedelta(seconds=1)) - dailycheckapp_measurement = DailyCheckApp_MeasurementFactory(school_id='test_1') - dailycheckapp_measurement.ClientInfo = {} - dailycheckapp_measurement.save() - - sync_dailycheckapp_realtime_data() - - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 1) - self.assertEqual(RealTimeConnectivity.objects.first().school, school) - - def test_school_matching_unknown(self): - SchoolFactory(external_id='test_1') - DailyCheckApp_Measurement.set_last_dailycheckapp_measurement_date(timezone.now() - timedelta(seconds=1)) - DailyCheckApp_MeasurementFactory() - sync_dailycheckapp_realtime_data() - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 0) - - def test_fields(self): - school = SchoolFactory(external_id='test_1') - DailyCheckApp_Measurement.set_last_dailycheckapp_measurement_date(timezone.now() - timedelta(seconds=1)) - - dailycheckapp_measurement = DailyCheckApp_MeasurementFactory(school_id='test_1') - sync_dailycheckapp_realtime_data() - - connectivity_info = RealTimeConnectivity.objects.first() - self.assertIsNotNone(connectivity_info) - - self.assertEqual(RealTimeConnectivity.objects.first().created, dailycheckapp_measurement.Timestamp) - self.assertEqual(RealTimeConnectivity.objects.first().connectivity_speed, int(dailycheckapp_measurement.Download * 1024)) - self.assertEqual(RealTimeConnectivity.objects.first().connectivity_latency, dailycheckapp_measurement.Latency) - self.assertEqual(RealTimeConnectivity.objects.first().school, school) diff --git a/proco/realtime_dailycheckapp/utils.py b/proco/realtime_dailycheckapp/utils.py deleted file mode 100644 index ece660f..0000000 --- a/proco/realtime_dailycheckapp/utils.py +++ /dev/null @@ -1,54 +0,0 @@ -import logging - -from django.utils import timezone - -from proco.connection_statistics.models import RealTimeConnectivity -from proco.locations.models import Country -from proco.realtime_dailycheckapp.models import DailyCheckApp_Measurement -from proco.schools.models import School - -logger = logging.getLogger('django.' + __name__) - - -def sync_dailycheckapp_realtime_data(): - dailycheckapp_measurements = DailyCheckApp_Measurement.objects.filter(Timestamp__gt=DailyCheckApp_Measurement.get_last_dailycheckapp_measurement_date()) - - realtime = [] - - countries = {m.ClientInfo.get('Country') for m in dailycheckapp_measurements} - for country_code in countries: - if country_code: - country = Country.objects.filter(code=country_code).first() - else: - country = None - - schools_qs = School.objects - if country: - schools_qs = schools_qs.filter(country=country) - - schools_ids = {m.school_id for m in dailycheckapp_measurements if m.ClientInfo.get('Country') == country_code} - schools = { - school.external_id: school - for school in schools_qs.filter(external_id__in=schools_ids) - } - - for dailycheckapp_measurement in dailycheckapp_measurements: - if dailycheckapp_measurement.school_id not in schools: - logger.debug(f'skipping dailycheckapp_measurement {dailycheckapp_measurement.UUID}: unknown school {dailycheckapp_measurement.school_id}') - continue - - realtime.append(RealTimeConnectivity( - created=dailycheckapp_measurement.Timestamp, - connectivity_speed=dailycheckapp_measurement.Download * 1024, # kb/s -> b/s - connectivity_latency=dailycheckapp_measurement.Latency, - school=schools[dailycheckapp_measurement.school_id], - )) - - RealTimeConnectivity.objects.bulk_create(realtime) - - # not using aggregate because there can be new entries between two operations - if dailycheckapp_measurements: - last_update = max((m.Timestamp for m in dailycheckapp_measurements)) - else: - last_update = timezone.now() - DailyCheckApp_Measurement.set_last_dailycheckapp_measurement_date(last_update) diff --git a/proco/realtime_unicef/__init__.py b/proco/realtime_unicef/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/proco/realtime_unicef/migrations/0001_initial.py b/proco/realtime_unicef/migrations/0001_initial.py deleted file mode 100644 index 6a08549..0000000 --- a/proco/realtime_unicef/migrations/0001_initial.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 2.2.18 on 2021-03-24 13:25 - -import django.contrib.postgres.fields.jsonb -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Measurement', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('timestamp', models.DateTimeField()), - ('uuid', models.TextField(blank=True)), - ('browser_id', models.TextField(blank=True)), - ('school_id', models.TextField()), - ('device_type', models.TextField(blank=True)), - ('notes', models.TextField()), - ('client_info', django.contrib.postgres.fields.jsonb.JSONField(default=dict)), - ('server_info', django.contrib.postgres.fields.jsonb.JSONField(default=dict)), - ('annotation', models.TextField(blank=True)), - ('download', models.FloatField(blank=True)), - ('upload', models.FloatField(blank=True)), - ('latency', models.IntegerField(blank=True)), - ('results', django.contrib.postgres.fields.jsonb.JSONField(default=dict)), - ], - options={ - 'db_table': 'measurements', - 'managed': False, - }, - ), - ] diff --git a/proco/realtime_unicef/migrations/__init__.py b/proco/realtime_unicef/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/proco/realtime_unicef/models.py b/proco/realtime_unicef/models.py deleted file mode 100644 index c9ab957..0000000 --- a/proco/realtime_unicef/models.py +++ /dev/null @@ -1,46 +0,0 @@ -from datetime import datetime, timedelta - -from django.contrib.postgres.fields import JSONField -from django.core.cache import cache -from django.db import models -from django.utils import timezone - - -class MeasurementManager(models.Manager): - def get_queryset(self): - return super().get_queryset().using('realtime') - - -class Measurement(models.Model): - MEASUREMENT_DATE_CACHE_KEY = 'realtime_last_measurement_at' - - timestamp = models.DateTimeField() - uuid = models.TextField(blank=True) - browser_id = models.TextField(blank=True) - school_id = models.TextField() - device_type = models.TextField(blank=True) - notes = models.TextField(blank=True) - client_info = JSONField(default=dict) - server_info = JSONField(default=dict) - annotation = models.TextField(blank=True) - download = models.FloatField(blank=True) - upload = models.FloatField(blank=True) - latency = models.IntegerField(blank=True) - results = JSONField(default=dict) - - objects = MeasurementManager() - - class Meta: - managed = False - db_table = 'measurements' - - @classmethod - def get_last_measurement_date(cls) -> datetime: - last_measurement_at = cache.get(cls.MEASUREMENT_DATE_CACHE_KEY) - if not last_measurement_at: - return timezone.now() - timedelta(days=1) - return last_measurement_at - - @classmethod - def set_last_measurement_date(cls, value: datetime): - cache.set(cls.MEASUREMENT_DATE_CACHE_KEY, value) diff --git a/proco/realtime_unicef/tests/__init__.py b/proco/realtime_unicef/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/proco/realtime_unicef/tests/db/__init__.py b/proco/realtime_unicef/tests/db/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/proco/realtime_unicef/tests/db/initdb.sql b/proco/realtime_unicef/tests/db/initdb.sql deleted file mode 100644 index c5793c3..0000000 --- a/proco/realtime_unicef/tests/db/initdb.sql +++ /dev/null @@ -1,20 +0,0 @@ --- clean managed tables -drop table if exists public.measurements; - --- create tables & relations -CREATE TABLE public.measurements ( - id serial, - "timestamp" timestamp with time zone, - uuid text, - browser_id text, - school_id text NOT NULL, - device_type text, - notes text, - client_info jsonb, - server_info jsonb, - annotation text, - download double precision, - upload double precision, - latency bigint, - results jsonb -); diff --git a/proco/realtime_unicef/tests/db/test.py b/proco/realtime_unicef/tests/db/test.py deleted file mode 100644 index 7c73379..0000000 --- a/proco/realtime_unicef/tests/db/test.py +++ /dev/null @@ -1,9 +0,0 @@ -import os - -from django.db import connections - - -def init_test_db(): - with connections['realtime'].cursor() as cursor: - with open(os.path.join('proco', 'realtime_unicef', 'tests', 'db', 'initdb.sql'), 'r') as initdb_file: - cursor.execute(initdb_file.read()) diff --git a/proco/realtime_unicef/tests/factories.py b/proco/realtime_unicef/tests/factories.py deleted file mode 100644 index 05a8ced..0000000 --- a/proco/realtime_unicef/tests/factories.py +++ /dev/null @@ -1,42 +0,0 @@ -from django.utils import timezone - -import factory.fuzzy - -from proco.realtime_unicef.models import Measurement - - -class MeasurementFactory(factory.django.DjangoModelFactory): - timestamp = factory.LazyFunction(lambda: timezone.now()) - uuid = factory.fuzzy.FuzzyText() - browser_id = factory.fuzzy.FuzzyText() - school_id = factory.fuzzy.FuzzyText() - device_type = factory.fuzzy.FuzzyText() - notes = factory.fuzzy.FuzzyText() - client_info = { - 'IP': '127.0.0.1', - 'City': 'Neverwinter', - 'Postal': '9999', - 'Region': 'Sword Coast North', - 'Country': 'Faerûn', - 'Latitude': 0.01, - 'Timezone': 'America/New_York', - 'Longitude': 0.01, - } - server_info = { - 'URL': 'http://localhost:7123', - 'City': 'Icewind Dale', - 'IPv4': '127.0.0.1', - 'Label': 'New York', - 'Country': 'Faerûn', - } - annotation = factory.fuzzy.FuzzyText() - download = factory.fuzzy.FuzzyFloat(0, 10**6) - upload = factory.fuzzy.FuzzyFloat(0, 10**6) - latency = factory.fuzzy.FuzzyInteger(1, 1000) - results = { - 'CurMSS': '1428', - 'Timeouts': '0', - } - - class Meta: - model = Measurement diff --git a/proco/realtime_unicef/tests/test_sync.py b/proco/realtime_unicef/tests/test_sync.py deleted file mode 100644 index 14ab965..0000000 --- a/proco/realtime_unicef/tests/test_sync.py +++ /dev/null @@ -1,130 +0,0 @@ -from datetime import timedelta - -from django.core.cache import cache -from django.db.models import Sum -from django.test import TestCase -from django.utils import timezone - -from proco.connection_statistics.models import RealTimeConnectivity -from proco.realtime_unicef.models import Measurement -from proco.realtime_unicef.tests.db.test import init_test_db -from proco.realtime_unicef.tests.factories import MeasurementFactory -from proco.realtime_unicef.utils import sync_realtime_data -from proco.schools.tests.factories import SchoolFactory - - -class SyncTestCase(TestCase): - databases = ['default', 'realtime'] - - @classmethod - def setUpClass(cls): - super().setUpClass() - init_test_db() - - def setUp(self) -> None: - super().setUp() - cache.delete(Measurement.MEASUREMENT_DATE_CACHE_KEY) - - def test_empty_cache(self): - school = SchoolFactory(external_id='test_1') - MeasurementFactory(timestamp=timezone.now() - timedelta(days=1, hours=1), school_id='test_1', download=1) - MeasurementFactory(timestamp=timezone.now() - timedelta(hours=23), school_id='test_1', download=2) - - sync_realtime_data() - - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 1) - self.assertEqual(RealTimeConnectivity.objects.first().school, school) - self.assertEqual(RealTimeConnectivity.objects.aggregate(speed=Sum('connectivity_speed'))['speed'], 2048) - - self.assertGreater(Measurement.get_last_measurement_date(), timezone.now() - timedelta(hours=23, seconds=5)) - - def test_cached_measurement_date(self): - SchoolFactory(external_id='test_1') - MeasurementFactory(timestamp=timezone.now() - timedelta(days=1, hours=1), school_id='test_1', download=1) - MeasurementFactory(timestamp=timezone.now() - timedelta(hours=23), school_id='test_1', download=2) - - Measurement.set_last_measurement_date(timezone.now() - timedelta(days=1, hours=2)) - - sync_realtime_data() - - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 2) - self.assertEqual(RealTimeConnectivity.objects.aggregate(speed=Sum('connectivity_speed'))['speed'], 3072) - - def test_idempotency(self): - SchoolFactory(external_id='test_1') - MeasurementFactory(school_id='test_1', download=1) - MeasurementFactory(school_id='test_1', download=2) - - # two objects synchronized because they added after default last measurement date (day ago) - sync_realtime_data() - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 2) - - # no new entries added, because they are already synchronized - RealTimeConnectivity.objects.all().delete() - sync_realtime_data() - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 0) - - # two previous entries synchronized again as we moved date back - Measurement.set_last_measurement_date(timezone.now() - timedelta(hours=1)) - sync_realtime_data() - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 2) - - def test_school_matching(self): - school = SchoolFactory(external_id='test_1') - Measurement.set_last_measurement_date(timezone.now() - timedelta(seconds=1)) - MeasurementFactory(school_id='test_1') - sync_realtime_data() - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 1) - self.assertEqual(RealTimeConnectivity.objects.first().school, school) - - def test_school_matching_external_id_collision(self): - SchoolFactory(external_id='test_1') - school = SchoolFactory(external_id='test_1', country__code='US', country__name='United States') - SchoolFactory(external_id='test_1') - - Measurement.set_last_measurement_date(timezone.now() - timedelta(seconds=1)) - measurement = MeasurementFactory(school_id='test_1') - measurement.client_info['Country'] = 'US' - measurement.save() - - sync_realtime_data() - - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 1) - self.assertEqual(RealTimeConnectivity.objects.first().school, school) - - def test_school_matching_without_client_info(self): - # last school will be used cause to mapping logic - SchoolFactory(external_id='test_1') - school = SchoolFactory(external_id='test_1') - - Measurement.set_last_measurement_date(timezone.now() - timedelta(seconds=1)) - measurement = MeasurementFactory(school_id='test_1') - measurement.client_info = {} - measurement.save() - - sync_realtime_data() - - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 1) - self.assertEqual(RealTimeConnectivity.objects.first().school, school) - - def test_school_matching_unknown(self): - SchoolFactory(external_id='test_1') - Measurement.set_last_measurement_date(timezone.now() - timedelta(seconds=1)) - MeasurementFactory() - sync_realtime_data() - self.assertEqual(RealTimeConnectivity.objects.count(approx=False), 0) - - def test_fields(self): - school = SchoolFactory(external_id='test_1') - Measurement.set_last_measurement_date(timezone.now() - timedelta(seconds=1)) - - measurement = MeasurementFactory(school_id='test_1') - sync_realtime_data() - - connectivity_info = RealTimeConnectivity.objects.first() - self.assertIsNotNone(connectivity_info) - - self.assertEqual(RealTimeConnectivity.objects.first().created, measurement.timestamp) - self.assertEqual(RealTimeConnectivity.objects.first().connectivity_speed, int(measurement.download * 1024)) - self.assertEqual(RealTimeConnectivity.objects.first().connectivity_latency, measurement.latency) - self.assertEqual(RealTimeConnectivity.objects.first().school, school) diff --git a/proco/realtime_unicef/utils.py b/proco/realtime_unicef/utils.py deleted file mode 100644 index ed2d0ec..0000000 --- a/proco/realtime_unicef/utils.py +++ /dev/null @@ -1,55 +0,0 @@ -import logging - -from django.utils import timezone - -from proco.connection_statistics.models import RealTimeConnectivity -from proco.locations.models import Country -from proco.realtime_unicef.models import Measurement -from proco.schools.models import School - -logger = logging.getLogger('django.' + __name__) - - -def sync_realtime_data(): - measurements = Measurement.objects.filter(timestamp__gt=Measurement.get_last_measurement_date()) - - realtime = [] - - countries = {m.client_info.get('Country') for m in measurements} - for country_code in countries: - if country_code: - country = Country.objects.filter(code=country_code).first() - else: - country = None - - schools_qs = School.objects - if country: - schools_qs = schools_qs.filter(country=country) - - schools_ids = {m.school_id for m in measurements if m.client_info.get('Country') == country_code} - schools = { - school.external_id: school - for school in schools_qs.filter(external_id__in=schools_ids) - } - - for measurement in measurements: - if measurement.school_id not in schools: - logger.debug(f'skipping measurement {measurement.uuid}: unknown school {measurement.school_id}') - continue - - if(measurement.download > 0 and measurement.latency > 0): - realtime.append(RealTimeConnectivity( - created=measurement.timestamp, - connectivity_speed=measurement.download * 1024, # kb/s -> b/s - connectivity_latency=measurement.latency, - school=schools[measurement.school_id], - )) - - RealTimeConnectivity.objects.bulk_create(realtime) - - # not using aggregate because there can be new entries between two operations - if measurements: - last_update = max((m.timestamp for m in measurements)) - else: - last_update = timezone.now() - Measurement.set_last_measurement_date(last_update) diff --git a/proco/schools/admin.py b/proco/schools/admin.py deleted file mode 100644 index 919c7b1..0000000 --- a/proco/schools/admin.py +++ /dev/null @@ -1,96 +0,0 @@ -from django.contrib import admin, messages -from django.contrib.admin.options import csrf_protect_m -from django.core.exceptions import PermissionDenied -from django.shortcuts import redirect -from django.urls import path, reverse -from django.utils.safestring import mark_safe - -from mapbox_location_field.admin import MapAdmin - -from proco.locations.filters import CountryFilterList -from proco.schools.forms import ImportSchoolsCSVForm, SchoolAdminForm -from proco.schools.models import FileImport, School -from proco.schools.tasks import process_loaded_file -from proco.utils.admin import CountryNameDisplayAdminMixin - - -class ImportFormMixin(object): - @csrf_protect_m - def changelist_view(self, request, extra_context=None): - if extra_context is None: - extra_context = {} - - extra_context['import_form'] = ImportSchoolsCSVForm() - - return super(ImportFormMixin, self).changelist_view(request, extra_context) - - -@admin.register(School) -class SchoolAdmin(ImportFormMixin, CountryNameDisplayAdminMixin, MapAdmin): - form = SchoolAdminForm - list_display = ('name', 'get_country_name', 'address', 'education_level', 'school_type','giga_id_school','education_level_regional') - list_filter = (CountryFilterList, 'education_level', 'environment', 'school_type') - search_fields = ('name', 'country__name', 'location__name') - change_list_template = 'admin/schools/change_list.html' - ordering = ('country', 'name') - readonly_fields = ('get_weekly_stats_url',) - raw_id_fields = ('country', 'location', 'last_weekly_status') - - def get_urls(self): - urls = super().get_urls() - custom_urls = [ - path('import/csv/', self.import_csv, name='schools_school_import_csv'), - ] - return custom_urls + urls - - def get_queryset(self, request): - qs = super().get_queryset(request) - if not request.user.is_superuser: - qs = qs.filter(country__in=request.user.countries_available.all()) - return qs.prefetch_related('country').defer('location') - - def import_csv(self, request): - user = request.user - if user.is_authenticated and user.has_perm('schools.add_fileimport') and request.method == 'POST': - form = ImportSchoolsCSVForm(data=request.POST, files=request.FILES) - if form.is_valid(): - cleaned_data = form.clean() - imported_file = FileImport.objects.create( - uploaded_file=cleaned_data['csv_file'], uploaded_by=request.user, - ) - process_loaded_file.delay(imported_file.id, force=cleaned_data['force']) - - messages.success(request, 'Your file was uploaded and will be processed soon.') - return redirect('admin:schools_fileimport_change', imported_file.id) - - raise PermissionDenied() - - def get_weekly_stats_url(self, obj): - stats_url = reverse('admin:connection_statistics_schoolweeklystatus_changelist') - return mark_safe(f'Here') # noqa: S703,S308 - - get_weekly_stats_url.short_description = 'Weekly Stats' - - -@admin.register(FileImport) -class FileImportAdmin(ImportFormMixin, admin.ModelAdmin): - change_form_template = 'admin/schools/file_imports_change_form.html' - - list_display = ('id', 'country', 'uploaded_file', 'status', 'uploaded_by', 'modified') - list_select_related = ('uploaded_by', 'country') - list_filter = ('status',) - readonly_fields = ('country', 'uploaded_file', 'status', 'statistic', 'errors', 'uploaded_by', 'modified') - ordering = ('-id',) - raw_id_fields = ('country',) - - def has_add_permission(self, request): - return False - - def has_change_permission(self, request, obj=None): - return False - - def get_queryset(self, request): - qs = super().get_queryset(request) - if not request.user.is_superuser: - qs = qs.filter(uploaded_by=request.user) - return qs.defer('country__geometry') diff --git a/proco/schools/forms.py b/proco/schools/forms.py deleted file mode 100644 index e2a5d4a..0000000 --- a/proco/schools/forms.py +++ /dev/null @@ -1,48 +0,0 @@ -from django import forms -from django.contrib.gis.forms import PointField -from django.contrib.gis.geos import Point -from django.urls import reverse -from django.utils.translation import ugettext as _ - -from crispy_forms.helper import FormHelper -from crispy_forms.layout import ButtonHolder, Field, Layout, Submit -from mapbox_location_field.forms import parse_location -from mapbox_location_field.widgets import MapAdminInput - -from proco.schools.models import School - - -class ImportSchoolsCSVForm(forms.Form): - csv_file = forms.FileField() - force = forms.BooleanField(label=_('Skip rows with bad data'), required=False) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.helper = FormHelper() - self.helper.form_class = 'blueForms import-csv-form' - self.helper.form_method = 'post' - self.helper.form_action = reverse('admin:schools_school_import_csv') - self.helper.layout = Layout( - Field('csv_file'), - Field('force'), - ButtonHolder( - Submit('submit', 'Submit', css_class='button'), - ), - ) - - -class MapboxPointField(PointField): - def to_python(self, value): - if isinstance(value, str): - lng, lat = parse_location(value, first_in_order='lat') - point = Point(x=lng, y=lat, srid=4326) - return point - return value - - -class SchoolAdminForm(forms.ModelForm): - geopoint = MapboxPointField(widget=MapAdminInput, required=False) - - class Meta: - model = School - fields = forms.ALL_FIELDS diff --git a/proco/templates/admin/background/backgroud_task_change.html b/proco/templates/admin/background/backgroud_task_change.html deleted file mode 100644 index fa37f0a..0000000 --- a/proco/templates/admin/background/backgroud_task_change.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "admin/change_form.html" %} - -{% block extrahead %} - {{ block.super }} - - {% if original.status in original.PROCESS_STATUSES %} - - {% endif %} -{% endblock %} diff --git a/proco/templates/admin/index.html b/proco/templates/admin/index.html deleted file mode 100644 index c4ead79..0000000 --- a/proco/templates/admin/index.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "admin/index.html" %} -{% load staticfiles %} - -{% block sidebar %} - {% if user.is_superuser %} -

-

Global actions

- -
- {% endif %} - {{ block.super }} -{% endblock %} - -{% block extrastyle %} - {{ block.super }} - {% if user.is_superuser %} - - - {% endif %} -{% endblock %} diff --git a/proco/templates/admin/locations/action_confirm.html b/proco/templates/admin/locations/action_confirm.html deleted file mode 100644 index aa3f157..0000000 --- a/proco/templates/admin/locations/action_confirm.html +++ /dev/null @@ -1,59 +0,0 @@ -{% extends 'admin/delete_confirmation.html' %} -{% load i18n admin_urls static %} - -{% block breadcrumbs %} - -{% endblock %} - -{% block content %} -{% if perms_lacking %} -

- Deleting the {{ object_name }} '{{ escaped_object }}' would result in deleting related objects, - but your account doesn't have permission to delete the following types of objects: -

-
    - {% for obj in perms_lacking %} -
  • {{ obj }}
  • - {% endfor %} -
-{% elif protected %} -

- Deleting the {{ object_name }} '{{ escaped_object }}' would require deleting - the following protected related objects: -

-
    - {% for obj in protected %} -
  • {{ obj }}
  • - {% endfor %} -
-{% else %} -

Are you sure?

- {% if action == 'mark_as_joined' %} -

- Are you sure you want to mark countries data source as verified?
- At the next upload of the csv file with the list of schools, all existing schools will be deleted. -

- {% else %} -

- Are you sure you want to delete all school points and saved statistics?
- The status of the country will return to its original position and it will be necessary to re-mark as joined. -

- {% endif %} - {% if action == 'mark_as_joined' %} -
{% csrf_token %} - {% else %} - {% csrf_token %} - {% endif %} - -
-{% endif %} -{% endblock %} diff --git a/proco/templates/admin/schools/change_list.html b/proco/templates/admin/schools/change_list.html deleted file mode 100644 index 61fdb53..0000000 --- a/proco/templates/admin/schools/change_list.html +++ /dev/null @@ -1,123 +0,0 @@ -{% extends 'admin/change_list.html' %} -{% load i18n crispy_forms_tags %} - - -{% block extrahead %} - {{ block.super }} - - - - -{% endblock %} - - -{% block object-tools-items %} -
  • - Import CSV -
  • - {{ block.super }} -{% endblock %} - -{% block content %} - {{ block.super }} - -
    - -{% endblock %} diff --git a/proco/templates/admin/schools/file_imports_change_form.html b/proco/templates/admin/schools/file_imports_change_form.html deleted file mode 100644 index d58febf..0000000 --- a/proco/templates/admin/schools/file_imports_change_form.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "admin/change_form.html" %} - -{% block extrastyle %} - {{ block.super }} - -{% endblock %} - -{% block extrahead %} - {{ block.super }} - - {% if original.status in original.PROCESS_STATUSES %} - - {% endif %} -{% endblock %} diff --git a/proco/templates/email/dailycheckapp_contact_email.html b/proco/templates/email/dailycheckapp_contact_email.html deleted file mode 100644 index 93f9c85..0000000 --- a/proco/templates/email/dailycheckapp_contact_email.html +++ /dev/null @@ -1,12 +0,0 @@ -{% block subject %}{{dailycheckapp_contact_message.full_name}}: {{dailycheckapp_contact_message.purpose}}{% endblock %} - -{% block html %} -

    - Message from: {{contact_message.full_name}}.
    - School ID: {{contact_message.school_id}}.
    - Email: {{contact_message.email}}.
    -

    -

    - Message text: {{contact_message.message}} -

    -{% endblock %} \ No newline at end of file diff --git a/proco/utils/admin.py b/proco/utils/admin.py deleted file mode 100644 index 210f910..0000000 --- a/proco/utils/admin.py +++ /dev/null @@ -1,22 +0,0 @@ -class SchoolNameDisplayAdminMixin(object): - def get_school_name(self, obj): - return obj.school.name - - get_school_name.short_description = 'School Name' - get_school_name.admin_order_field = 'school__name' - - -class CountryNameDisplayAdminMixin(object): - def get_country_name(self, obj): - return obj.country.name - - get_country_name.short_description = 'Country Name' - get_country_name.admin_order_field = 'country__name' - - -class LocationNameDisplayAdminMixin(object): - def get_location_name(self, obj): - return obj.location.name if obj.location else '' - - get_location_name.short_description = 'Location Name' - get_location_name.admin_order_field = 'location__name' From e93638c1902bb3e43899ee0713e405bbbe263536 Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Wed, 17 Jul 2024 12:20:25 +0530 Subject: [PATCH 03/27] Added test cases and fixed coverage api duplicate queries issue --- config/settings/dev_test.py | 2 + proco/connection_statistics/api.py | 129 +--- proco/connection_statistics/api_urls.py | 17 +- proco/connection_statistics/tests/test_api.py | 649 ++++++++++++------ proco/data_sources/tests/test_utils.py | 2 +- proco/locations/api_urls.py | 4 +- proco/locations/tests/factories.py | 14 +- proco/locations/tests/test_api.py | 102 +-- proco/schools/tests/factories.py | 6 +- proco/schools/tests/test_api.py | 14 +- proco/utils/urls.py | 2 +- 11 files changed, 556 insertions(+), 385 deletions(-) diff --git a/config/settings/dev_test.py b/config/settings/dev_test.py index eed8304..81cdd22 100644 --- a/config/settings/dev_test.py +++ b/config/settings/dev_test.py @@ -61,3 +61,5 @@ 'COUNTRY_INDEX_NAME': env('COUNTRY_INDEX_NAME', default='giga_countries'), 'SCHOOL_INDEX_NAME': env('SCHOOL_INDEX_NAME', default='giga_schools'), } + +DEFAULT_FILE_STORAGE = 'inmemorystorage.InMemoryStorage' diff --git a/proco/connection_statistics/api.py b/proco/connection_statistics/api.py index e194494..822f939 100644 --- a/proco/connection_statistics/api.py +++ b/proco/connection_statistics/api.py @@ -196,58 +196,6 @@ def get_queryset(self): return queryset.filter(school_id=self.kwargs['school_id']) -@method_decorator([cache_control(public=True, max_age=settings.CACHE_CONTROL_MAX_AGE_FOR_FE)], name='dispatch') -class CoverageStatsAPIVIEW(APIView): - permission_classes = (AllowAny,) - - CACHE_KEY = 'cache' - CACHE_KEY_PREFIX = 'COVERAGE_STATS' - - def get_cache_key(self): - params = dict(self.request.query_params) - params.pop(self.CACHE_KEY, None) - return '{0}_{1}'.format(self.CACHE_KEY_PREFIX, - '_'.join(map(lambda x: '{0}_{1}'.format(x[0], x[1]), sorted(params.items()))), ) - - def get(self, request, *args, **kwargs): - use_cached_data = self.request.query_params.get(self.CACHE_KEY, 'on').lower() in ['on', 'true'] - request_path = remove_query_param(request.get_full_path(), 'cache') - cache_key = self.get_cache_key() - - data = None - if use_cached_data: - data = cache_manager.get(cache_key) - - if not data: - # Query the School table to get the coverage data - # Get the total number of schools with coverage data - # Get the count of schools falling under different coverage types - school_coverage_type_qry = School.objects.all().annotate( - dummy_group_by=Value(1)).values('dummy_group_by').annotate( - g_4_5=Count(Case(When(coverage_type__in=['5g', '4g'], then='id')), distinct=True), - g_2_3=Count(Case(When(coverage_type__in=['3g', '2g'], then='id')), distinct=True), - no_coverage=Count(Case(When(coverage_type='no', then='id')), distinct=True), - unknown=Count(Case(When(coverage_type__in=['unknown', None], then='id')), distinct=True), - total_coverage_schools=Count(Case(When(coverage_type__isnull=False, then='id')), distinct=True), - ).values('g_4_5', 'g_2_3', 'no_coverage', 'unknown', 'total_coverage_schools').order_by() - - coverage_data = { - '5g_4g': school_coverage_type_qry[0]['g_4_5'], - '3g_2g': school_coverage_type_qry[0]['g_2_3'], - 'no_coverage': school_coverage_type_qry[0]['no_coverage'], - 'unknown': school_coverage_type_qry[0]['unknown'], - } - - data = { - 'total_coverage_schools': school_coverage_type_qry[0]['total_coverage_schools'], - 'coverage_schools': coverage_data, - } - - cache_manager.set(cache_key, data, request_path=request_path, soft_timeout=settings.CACHE_CONTROL_MAX_AGE) - - return Response(data=data) - - class SchoolConnectivityStatsListAPIView(ListAPIView): model = School queryset = model.objects.all().select_related('last_weekly_status') @@ -444,57 +392,6 @@ def list(self, request, *args, **kwargs): return Response(serializer.data) -@method_decorator([cache_control(public=True, max_age=settings.CACHE_CONTROL_MAX_AGE_FOR_FE)], name='dispatch') -class CountryCoverageStatsAPIView(APIView): - permission_classes = (AllowAny,) - - CACHE_KEY = 'cache' - CACHE_KEY_PREFIX = 'COUNTRY_COVERAGE_STATS' - - def get_cache_key(self): - params = dict(self.request.query_params) - params.pop(self.CACHE_KEY, None) - return '{0}_{1}'.format(self.CACHE_KEY_PREFIX, - '_'.join(map(lambda x: '{0}_{1}'.format(x[0], x[1]), sorted(params.items()))), ) - - def get(self, request, *args, **kwargs): - use_cached_data = self.request.query_params.get(self.CACHE_KEY, 'on').lower() in ['on', 'true'] - request_path = remove_query_param(request.get_full_path(), self.CACHE_KEY) - cache_key = self.get_cache_key() - - data = None - if use_cached_data: - data = cache_manager.get(cache_key) - - if not data: - country_id = self.request.query_params.get('country_id', None) - - school_coverage_type_qry = School.objects.filter(country_id=country_id).annotate( - dummy_group_by=Value(1)).values('dummy_group_by').annotate( - g_4_5=Count(Case(When(coverage_type__in=['5g', '4g'], then='id')), distinct=True), - g_2_3=Count(Case(When(coverage_type__in=['3g', '2g'], then='id')), distinct=True), - no_coverage=Count(Case(When(coverage_type='no', then='id')), distinct=True), - unknown=Count(Case(When(coverage_type__in=['unknown', None], then='id')), distinct=True), - total_coverage_schools=Count(Case(When(coverage_type__isnull=False, then='id')), distinct=True), - ).values('g_4_5', 'g_2_3', 'no_coverage', 'unknown', 'total_coverage_schools').order_by() - - coverage_data = { - '5g_4g': school_coverage_type_qry[0]['g_4_5'], - '3g_2g': school_coverage_type_qry[0]['g_2_3'], - 'no_coverage': school_coverage_type_qry[0]['no_coverage'], - 'unknown': school_coverage_type_qry[0]['unknown'], - } - - data = { - 'total_coverage_schools': school_coverage_type_qry[0]['total_coverage_schools'], - 'coverage_schools': coverage_data, - } - - cache_manager.set(cache_key, data, request_path=request_path, soft_timeout=settings.CACHE_CONTROL_MAX_AGE) - - return Response(data=data) - - @method_decorator([cache_control(public=True, max_age=settings.CACHE_CONTROL_MAX_AGE_FOR_FE)], name='dispatch') class ConnectivityAPIView(APIView): permission_classes = (AllowAny,) @@ -608,11 +505,12 @@ def calculate_country_download_data(self, start_date, end_date, week_number, yea 'no_of_schools_measure', 'countries_with_realtime_data', 'total_weekly_schools' ).extra(where=[school_static_filters]) + weekly_status = list(weekly_queryset)[0] real_time_connected_schools = { - 'good': weekly_queryset[0]['good'], - 'moderate': weekly_queryset[0]['moderate'], - 'no_internet': weekly_queryset[0]['bad'], - 'unknown': weekly_queryset[0]['unknown'], + 'good': weekly_status['good'], + 'moderate': weekly_status['moderate'], + 'no_internet': weekly_status['bad'], + 'unknown': weekly_status['unknown'], } graph_data, positive_speeds = self.generate_country_graph_data(start_date, end_date) @@ -651,9 +549,9 @@ def calculate_country_download_data(self, start_date, end_date, week_number, yea return { 'live_avg': live_avg, 'live_avg_connectivity': live_avg_connectivity, - 'no_of_schools_measure': weekly_queryset[0]['no_of_schools_measure'], - 'school_with_realtime_data': weekly_queryset[0]['school_with_realtime_data'], - 'countries_with_realtime_data': weekly_queryset[0]['countries_with_realtime_data'], + 'no_of_schools_measure': weekly_status['no_of_schools_measure'], + 'school_with_realtime_data': weekly_status['school_with_realtime_data'], + 'countries_with_realtime_data': weekly_status['countries_with_realtime_data'], 'real_time_connected_schools': real_time_connected_schools, 'graph_data': graph_data, 'is_data_synced': is_data_synced_qs.exists(), @@ -798,15 +696,16 @@ def get(self, request, *args, **kwargs): 'g_4_5', 'g_2_3', 'no_coverage', 'unknown', 'total_coverage_schools', 'total_weekly_schools' ).extra(where=[self.school_static_filters]) + school_coverage_status = list(school_coverage_type_qry)[0] coverage_data = { - '5g_4g': school_coverage_type_qry[0]['g_4_5'], - '3g_2g': school_coverage_type_qry[0]['g_2_3'], - 'no_coverage': school_coverage_type_qry[0]['no_coverage'], - 'unknown': school_coverage_type_qry[0]['unknown'], + '5g_4g': school_coverage_status['g_4_5'], + '3g_2g': school_coverage_status['g_2_3'], + 'no_coverage': school_coverage_status['no_coverage'], + 'unknown': school_coverage_status['unknown'], } data = { - 'total_schools': school_coverage_type_qry[0]['total_coverage_schools'], + 'total_schools': school_coverage_status['total_coverage_schools'], 'connected_schools': coverage_data, } diff --git a/proco/connection_statistics/api_urls.py b/proco/connection_statistics/api_urls.py index 71893c2..4f8326a 100644 --- a/proco/connection_statistics/api_urls.py +++ b/proco/connection_statistics/api_urls.py @@ -15,6 +15,7 @@ path('coverage/', api.CoverageAPIView.as_view(), name='global-coverage-stat'), path('countrycoverage/', api.CoverageAPIView.as_view(), name='country-coverage-stat'), path('schoolcoverage/', api.SchoolCoverageStatsListAPIView.as_view(), name='school-coverage-stat'), + path( 'country//daily-stat/', api.CountryDailyStatsListAPIView.as_view(), @@ -26,38 +27,38 @@ 'get': 'list', 'post': 'create', 'delete': 'destroy', - }), name='list_or_create_destroy_countryweeklystatus'), + }), name='list-create-destroy-countryweeklystatus'), path('countryweeklystatus//', api.CountrySummaryAPIViewSet.as_view({ 'put': 'update', 'get': 'retrieve', - }), name='update_or_retrieve_countryweeklystatus'), + }), name='update-retrieve-countryweeklystatus'), path('countrydailystatus/', api.CountryDailyConnectivitySummaryAPIViewSet.as_view({ 'get': 'list', 'post': 'create', 'delete': 'destroy', - }), name='list_or_create_destroy_countrydailystatus'), + }), name='list-create-destroy-countrydailystatus'), path('countrydailystatus//', api.CountryDailyConnectivitySummaryAPIViewSet.as_view({ 'put': 'update', 'get': 'retrieve', - }), name='update_or_retrieve_countrydailystatus'), + }), name='update-retrieve-countrydailystatus'), path('schoolweeklystatus/', api.SchoolSummaryAPIViewSet.as_view({ 'get': 'list', 'post': 'create', 'delete': 'destroy', - }), name='list_or_create_destroy_schoolweeklystatus'), + }), name='list-create-destroy-schoolweeklystatus'), path('schoolweeklystatus//', api.SchoolSummaryAPIViewSet.as_view({ 'put': 'update', 'get': 'retrieve', - }), name='update_or_retrieve_schoolweeklystatus'), + }), name='update-retrieve-schoolweeklystatus'), path('schooldailystatus/', api.SchoolDailyConnectivitySummaryAPIViewSet.as_view({ 'get': 'list', 'post': 'create', 'delete': 'destroy', - }), name='list_or_create_destroy_schooldailystatus'), + }), name='list-create-destroy-schooldailystatus'), path('schooldailystatus//', api.SchoolDailyConnectivitySummaryAPIViewSet.as_view({ 'put': 'update', 'get': 'retrieve', - }), name='update_or_retrieve_schooldailystatus'), + }), name='update-retrieve-schooldailystatus'), ] diff --git a/proco/connection_statistics/tests/test_api.py b/proco/connection_statistics/tests/test_api.py index 8fecc3f..4e76272 100755 --- a/proco/connection_statistics/tests/test_api.py +++ b/proco/connection_statistics/tests/test_api.py @@ -7,7 +7,6 @@ from isoweek import Week from rest_framework import exceptions as rest_exceptions from rest_framework import status -from rest_framework.test import APITestCase from proco.connection_statistics.models import CountryWeeklyStatus from proco.connection_statistics.tests.factories import ( @@ -16,25 +15,39 @@ SchoolDailyStatusFactory, SchoolWeeklyStatusFactory, ) -from proco.custom_auth import models as auth_models -from proco.locations.tests.factories import CountryFactory +from proco.custom_auth.tests import test_utils as test_utilities +from proco.locations.tests.factories import CountryFactory, Admin1Factory from proco.schools.tests.factories import SchoolFactory from proco.utils.dates import format_date, get_first_date_of_month, get_last_date_of_month from proco.utils.tests import TestAPIViewSetMixin +def statistics_url(url_params, query_param, view_name='global-stat'): + url = reverse('connection_statistics:' + view_name, args=url_params) + view = resolve(url) + view_info = view.func + + if len(query_param) > 0: + query_params = '?' + '&'.join([key + '=' + str(val) for key, val in query_param.items()]) + url += query_params + return url, view, view_info + + class GlobalStatisticsApiTestCase(TestAPIViewSetMixin, TestCase): - databases = ['default',] + databases = ['default', ] @classmethod def setUpTestData(cls): cls.country_one = CountryFactory() + cls.school_one = SchoolFactory(country=cls.country_one, location__country=cls.country_one, geopoint=None) cls.school_two = SchoolFactory(country=cls.country_one, location__country=cls.country_one) + SchoolWeeklyStatusFactory(school=cls.school_one, connectivity=True) SchoolWeeklyStatusFactory(school=cls.school_two, connectivity=False) CountryWeeklyStatusFactory(country=cls.country_one, integration_status=CountryWeeklyStatus.REALTIME_MAPPED, year=datetime.now().year + 1, schools_connectivity_no=1) + cls.cws = CountryWeeklyStatusFactory(integration_status=CountryWeeklyStatus.STATIC_MAPPED, schools_connectivity_no=0, year=datetime.now().year + 2) @@ -44,9 +57,11 @@ def setUp(self): super().setUp() def test_global_stats(self): + url, _, view = statistics_url((), {}) + response = self.forced_auth_req( 'get', - reverse('connection_statistics:global-stat'), + url, ) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -56,58 +71,13 @@ def test_global_stats(self): ['connected', 'not_connected', 'unknown']) def test_global_stats_queries(self): + url, _, view = statistics_url((), {}) + with self.assertNumQueries(2): self.forced_auth_req( 'get', - reverse('connection_statistics:global-stat'), + url, ) - # TODO: Test only when caching is enabled - # with self.assertNumQueries(0): - # self.forced_auth_req( - # 'get', - # reverse('connection_statistics:global-stat'), - # ) - - -# -# class CountryWeekStatsApiTestCase(TestAPIViewSetMixin, TestCase): -# @classmethod -# def setUpTestData(cls): -# cls.country_one = CountryFactory() -# cls.country_two = CountryFactory() -# cls.stat_one = CountryWeeklyStatusFactory(country=cls.country_one) -# cls.stat_two = CountryWeeklyStatusFactory(country=cls.country_two) -# -# def test_country_weekly_stats(self): -# response = self.forced_auth_req( -# 'get', -# reverse('connection_statistics:country-weekly-stat', kwargs={ -# 'country_code': self.stat_one.country.code.lower(), -# 'year': self.stat_one.year, -# 'week': self.stat_one.week, -# }), -# ) -# self.assertEqual(response.status_code, status.HTTP_200_OK) -# self.assertEqual(response.data['schools_total'], self.stat_one.schools_total) -# self.assertEqual(response.data['avg_distance_school'], self.stat_one.avg_distance_school) -# self.assertEqual(response.data['schools_connected'], self.stat_one.schools_connected) -# self.assertEqual(response.data['schools_connectivity_unknown'], self.stat_one.schools_connectivity_unknown) -# self.assertEqual(response.data['schools_connectivity_moderate'], self.stat_one.schools_connectivity_moderate) -# self.assertEqual(response.data['schools_connectivity_good'], self.stat_one.schools_connectivity_good) -# self.assertEqual(response.data['schools_connectivity_no'], self.stat_one.schools_connectivity_no) -# self.assertEqual(response.data['integration_status'], self.stat_one.integration_status) -# -# def test_country_weekly_stats_queries(self): -# code = self.stat_one.country.code.lower() -# with self.assertNumQueries(2): -# self.forced_auth_req( -# 'get', -# reverse('connection_statistics:country-weekly-stat', kwargs={ -# 'country_code': code, -# 'year': self.stat_one.year, -# 'week': self.stat_one.week, -# }), -# ) class CountryDailyStatsApiTestCase(TestAPIViewSetMixin, TestCase): @@ -122,34 +92,54 @@ def setUpTestData(cls): CountryDailyStatusFactory(country=cls.country_two) - def test_country_weekly_stats(self): - response = self.forced_auth_req( - 'get', - reverse('connection_statistics:country-daily-stat', kwargs={ - 'country_code': self.country_one.code.lower(), - }), - ) + def test_country_daily_stats(self): + url, _, view = statistics_url((self.country_one.code.lower(),), {}, view_name='country-daily-stat') + response = self.forced_auth_req('get', url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], self.country_one_stats_number) - response = self.forced_auth_req( - 'get', - reverse('connection_statistics:country-daily-stat', kwargs={ - 'country_code': self.country_two.code.lower(), - }), - ) + url, _, view = statistics_url((self.country_two.code.lower(),), {}, view_name='country-daily-stat') + response = self.forced_auth_req('get', url) + self.assertEqual(response.data['count'], 1) - def test_country_weekly_stats_queries(self): - code = self.country_one.code.lower() + def test_country_daily_stats_queries(self): + url, _, view = statistics_url((self.country_one.code.lower(),), {}, view_name='country-daily-stat') with self.assertNumQueries(2): - self.forced_auth_req( - 'get', - reverse('connection_statistics:country-daily-stat', kwargs={ - 'country_code': code, - }), - ) + self.forced_auth_req('get', url) + + +class SchoolDailyStatsApiTestCase(TestAPIViewSetMixin, TestCase): + @classmethod + def setUpTestData(cls): + cls.country = CountryFactory() + + cls.school_one = SchoolFactory() + cls.school_two = SchoolFactory() + + cls.school_one_stats_number = random.SystemRandom().randint(a=5, b=25) + for _i in range(cls.school_one_stats_number): + SchoolDailyStatusFactory(school=cls.school_one) + + SchoolDailyStatusFactory(school=cls.school_two) + + def test_school_daily_stats(self): + url, _, view = statistics_url((self.school_one.id,), {}, view_name='school-daily-stat') + response = self.forced_auth_req('get', url) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['count'], self.school_one_stats_number) + + url, _, view = statistics_url((self.school_two.id,), {}, view_name='school-daily-stat') + response = self.forced_auth_req('get', url) + + self.assertEqual(response.data['count'], 1) + + def test_school_daily_stats_queries(self): + url, _, view = statistics_url((self.school_one.id,), {}, view_name='school-daily-stat') + with self.assertNumQueries(1): + self.forced_auth_req('get', url) class SchoolCoverageStatApiTestCase(TestAPIViewSetMixin, TestCase): @@ -157,9 +147,11 @@ class SchoolCoverageStatApiTestCase(TestAPIViewSetMixin, TestCase): @classmethod def setUpTestData(cls): cls.country = CountryFactory() + cls.school_one = SchoolFactory(country=cls.country, location__country=cls.country) cls.school_two = SchoolFactory(country=cls.country, location__country=cls.country) cls.school_three = SchoolFactory(country=cls.country, location__country=cls.country) + cls.school_weekly_one = SchoolWeeklyStatusFactory( school=cls.school_one, connectivity=True, connectivity_speed=3 * (10 ** 6), @@ -175,6 +167,7 @@ def setUpTestData(cls): connectivity=None, connectivity_speed=None, coverage_availability=None, coverage_type='unknown', ) + cls.school_one.last_weekly_status = cls.school_weekly_one cls.school_one.save() cls.school_two.last_weekly_status = cls.school_weekly_two @@ -186,53 +179,41 @@ def setUp(self): cache.clear() super().setUp() - def list_school_coverage_url(self, url_params, query_param): - view_name = 'connection_statistics:school-coverage-stat' - url = reverse(view_name, args=url_params) - view_info = resolve(url).func - - if len(query_param) > 0: - query_params = '?' + '&'.join([key + '=' + str(val) for key, val in query_param.items()]) - url += query_params - return url, view_info - def test_school_coverage_stat_school_list(self): - url, view = self.list_school_coverage_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'school_ids': ','.join([str(self.school_one.id), str(self.school_two.id), str(self.school_three.id)]), - }) + }, view_name='school-coverage-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 3) def test_school_coverage_stat_without_school_id(self): - url, view = self.list_school_coverage_url((), { - 'country_id': self.country.id, - }) + url, _, view = statistics_url((), {'country_id': self.country.id}, view_name='school-coverage-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) # TODO: Change it once that hard coded school id is removed self.assertEqual(len(response.data), 0) def test_school_coverage_stat_without_country_id(self): - url, view = self.list_school_coverage_url((), {}) + url, _, view = statistics_url((), {}, view_name='school-coverage-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 0) def test_school_coverage_stat_for_one_school(self): - url, view = self.list_school_coverage_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'school_ids': str(self.school_one.id), - }) + }, view_name='school-coverage-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) @@ -247,12 +228,12 @@ def test_school_coverage_stat_for_one_school(self): self.assertEqual(school_data['country_name'], self.country.name) def test_school_coverage_stat_for_one_school_statistics(self): - url, view = self.list_school_coverage_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'school_ids': str(self.school_one.id), - }) + }, view_name='school-coverage-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) @@ -266,12 +247,12 @@ def test_school_coverage_stat_for_one_school_statistics(self): self.assertEqual(school_statistics_data['connectivity_speed'], round(3 * (10 ** 6) / 1000000, 2)) def test_school_coverage_stat_for_coverage_type_choices(self): - url, view = self.list_school_coverage_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'school_ids': ','.join([str(self.school_one.id), str(self.school_two.id), str(self.school_three.id)]), - }) + }, view_name='school-coverage-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -293,12 +274,12 @@ def test_school_coverage_stat_for_one_school_when_school_weekly_status_not_avail """ school_four = SchoolFactory(country=self.country, location__country=self.country) - url, view = self.list_school_coverage_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'school_ids': school_four.id, - }) + }, view_name='school-coverage-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) @@ -314,12 +295,12 @@ def test_school_coverage_stat_for_connectivity_status(self): self.country.last_weekly_status.connectivity_availability = connectivity_availability self.country.last_weekly_status.save() - url, view = self.list_school_coverage_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'school_ids': ','.join([str(self.school_one.id), str(self.school_two.id), str(self.school_three.id)]), - }) + }, view_name='school-coverage-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data[0]['statistics']['connectivity_status'], 'unknown') @@ -331,12 +312,12 @@ def test_school_coverage_stat_for_connectivity_status_when_connectivity_availabi self.country.last_weekly_status.connectivity_availability = connectivity_availability self.country.last_weekly_status.save() - url, view = self.list_school_coverage_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'school_ids': ','.join([str(self.school_one.id), str(self.school_two.id), str(self.school_three.id)]), - }) + }, view_name='school-coverage-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data[0]['statistics']['connectivity_status'], 'unknown') @@ -347,12 +328,12 @@ def test_school_coverage_stat_for_connectivity_status_when_country_weekly_status self.country.last_weekly_status = None self.country.save() - url, view = self.list_school_coverage_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'school_ids': ','.join([str(self.school_one.id), str(self.school_two.id), str(self.school_three.id)]), - }) + }, view_name='school-coverage-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -361,11 +342,12 @@ def test_school_coverage_stat_for_connectivity_status_when_country_weekly_status self.assertEqual(response.data[2]['statistics']['connectivity_status'], 'unknown') -class SchoolConnectivityStatApiTestCase(TestAPIViewSetMixin, TestCase): +class CountryConnectivityStatApiTestCase(TestAPIViewSetMixin, TestCase): @classmethod def setUpTestData(cls): cls.country = CountryFactory() + cls.country_two = CountryFactory() cls.school_one = SchoolFactory(country=cls.country, location__country=cls.country) cls.school_two = SchoolFactory(country=cls.country, location__country=cls.country) @@ -401,15 +383,186 @@ def setUp(self): cache.clear() super().setUp() - def list_school_connectivity_stat_url(self, url_params, query_param): - view_name = 'connection_statistics:school-connectivity-stat' - url = reverse(view_name, args=url_params) - view_info = resolve(url).func + def test_country_download_connectivity_stat(self): + """ + test_school_download_connectivity_stat_school_list + Positive test case for weekly data. + + Expected: HTTP_200_OK - List of data for all 3 schools + """ + today = datetime.now().date() + date_7_days_back = today - timedelta(days=6) + + url, _, view = statistics_url((), { + 'country_id': self.country.id, + 'start_date': format_date(date_7_days_back), + 'end_date': format_date(today), + 'is_weekly': 'true', + }, view_name='country-connectivity-stat') + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertIn('live_avg', response_data) + self.assertIn('no_of_schools_measure', response_data) + self.assertIn('school_with_realtime_data', response_data) + self.assertIn('is_data_synced', response_data) + self.assertIn('graph_data', response_data) + self.assertIn('real_time_connected_schools', response_data) + + def test_country_download_connectivity_stat_without_country_id(self): + """ + test_school_download_connectivity_stat_without_school_id + Negative test case for weekly data without passing the school id in url query parameters. + + Expected: HTTP_200_OK - As of now it will return no data as we have hard coded the school id as 34554 + in API View. But with changes in API it will return the list of schools from the country. + """ + today = datetime.now().date() + date_7_days_back = today - timedelta(days=6) + + url, _, view = statistics_url((), { + 'start_date': format_date(date_7_days_back), + 'end_date': format_date(today), + 'is_weekly': 'true', + }, view_name='country-connectivity-stat') + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertIn('live_avg', response_data) + self.assertIn('no_of_schools_measure', response_data) + self.assertIn('school_with_realtime_data', response_data) + self.assertIn('is_data_synced', response_data) + self.assertIn('graph_data', response_data) + self.assertIn('real_time_connected_schools', response_data) + + def test_country_download_connectivity_stat_for_one_country_without_daily(self): + """ + test_school_download_connectivity_stat_for_one_school_without_daily + Positive test case for weekly data for 1 school and School Daily records are also not available. + + Expected: HTTP_200_OK - 1 school data with graph_data json with value as null. + Connectivity_speed == 0, as for download speed is calculated based on graph data aggregation. + connectivity_status == unknown, as country.last_weekly_status.connectivity_availability == no_connectivity + """ + today = datetime.now().date() + date_7_days_back = today - timedelta(days=6) + + url, _, view = statistics_url((), { + 'country_id': self.country_two.id, + 'start_date': format_date(date_7_days_back), + 'end_date': format_date(today), + 'is_weekly': 'true', + }, view_name='country-connectivity-stat') + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['no_of_schools_measure'], 0) + + def test_country_download_connectivity_stat_for_one_country_statistics(self): + """ + test_school_download_connectivity_stat_for_one_school_statistics + Positive test case to test the statistics JSON for weekly data for 1 school and + School Daily records are also not available. + + Expected: HTTP_200_OK - 1 school data with filled statistics json and graph_data json with value as null. + Connectivity_speed == 0, as for download speed is calculated based on graph data aggregation. + connectivity_status == unknown, as country.last_weekly_status.connectivity_availability == no_connectivity + + statistics.connectivity_speed == round(3 * (10 ** 6) / 1000000, 2), as speed is picked from SchoolWeeklyStatus + """ + today = datetime.now().date() + date_7_days_back = today - timedelta(days=6) + + url, _, view = statistics_url((), { + 'country_id': self.country.id, + 'start_date': format_date(date_7_days_back), + 'end_date': format_date(today), + 'is_weekly': 'true', + }, view_name='country-connectivity-stat') + + with self.assertNumQueries(4): + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_country_download_connectivity_stat_for_one_school_graph_data_when_school_daily_status_not_available(self): + """ + test_school_download_connectivity_stat_for_one_school_graph_data_when_school_daily_status_not_available + Positive test case to test the graph_data JSON for weekly data for 1 school when + School Daily records are not available. + + Expected: HTTP_200_OK - 1 school data with filled statistics json and filled graph_data json with null values. + Connectivity_speed == 0, as for download speed is calculated based on graph data aggregation. + """ + today = datetime.now().date() + date_7_days_back = today - timedelta(days=6) + + url, _, view = statistics_url((), { + 'country_id': self.country.id, + 'start_date': format_date(date_7_days_back), + 'end_date': format_date(today), + 'is_weekly': 'true', + }, view_name='country-connectivity-stat') + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertIn('graph_data', response_data) + self.assertEqual(len(response_data['graph_data']), 7) + + graph_data = response_data['graph_data'] + for data in graph_data: + self.assertIsNone(data['value']) + + +class SchoolConnectivityStatApiTestCase(TestAPIViewSetMixin, TestCase): + + @classmethod + def setUpTestData(cls): + cls.country = CountryFactory() + + cls.school_one = SchoolFactory(country=cls.country, location__country=cls.country) + cls.school_two = SchoolFactory(country=cls.country, location__country=cls.country) + cls.school_three = SchoolFactory(country=cls.country, location__country=cls.country) + + cls.school_weekly_one = SchoolWeeklyStatusFactory( + school=cls.school_one, + connectivity=True, connectivity_speed=3 * (10 ** 6), + coverage_availability=True, coverage_type='3g', + ) + cls.school_weekly_two = SchoolWeeklyStatusFactory( + school=cls.school_one, + connectivity=False, connectivity_speed=None, + coverage_availability=False, coverage_type='no', + ) + cls.school_weekly_three = SchoolWeeklyStatusFactory( + school=cls.school_one, + connectivity=None, connectivity_speed=None, + coverage_availability=None, coverage_type='unknown', + ) + cls.school_one.last_weekly_status = cls.school_weekly_one + cls.school_one.save() + cls.school_two.last_weekly_status = cls.school_weekly_two + cls.school_two.save() + cls.school_three.last_weekly_status = cls.school_weekly_three + cls.school_three.save() - if len(query_param) > 0: - query_params = '?' + '&'.join([key + '=' + str(val) for key, val in query_param.items()]) - url += query_params - return url, view_info + cls.school_daily_two = SchoolDailyStatusFactory(school=cls.school_two, + date=datetime.now().date() - timedelta(days=1), + connectivity_speed=4000000) + + def setUp(self): + cache.clear() + super().setUp() def test_school_download_connectivity_stat_school_list(self): """ @@ -421,15 +574,15 @@ def test_school_download_connectivity_stat_school_list(self): today = datetime.now().date() date_7_days_back = today - timedelta(days=6) - url, view = self.list_school_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'school_ids': ','.join([str(self.school_one.id), str(self.school_two.id), str(self.school_three.id)]), 'start_date': format_date(date_7_days_back), 'end_date': format_date(today), 'is_weekly': 'true', - }) + }, view_name='school-connectivity-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 3) @@ -445,14 +598,14 @@ def test_school_download_connectivity_stat_without_school_id(self): today = datetime.now().date() date_7_days_back = today - timedelta(days=6) - url, view = self.list_school_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'start_date': format_date(date_7_days_back), 'end_date': format_date(today), 'is_weekly': 'true', - }) + }, view_name='school-connectivity-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) # TODO: Change it once that hard coded school id is removed @@ -468,14 +621,14 @@ def test_school_download_connectivity_stat_without_country_id(self): today = datetime.now().date() date_7_days_back = today - timedelta(days=6) - url, view = self.list_school_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'school_ids': ','.join([str(self.school_one.id), str(self.school_two.id), str(self.school_three.id)]), 'start_date': format_date(date_7_days_back), 'end_date': format_date(today), 'is_weekly': 'true', - }) + }, view_name='school-connectivity-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.data.get('detail').code, rest_exceptions.NotFound.default_code) @@ -492,15 +645,15 @@ def test_school_download_connectivity_stat_for_one_school_without_daily(self): today = datetime.now().date() date_7_days_back = today - timedelta(days=6) - url, view = self.list_school_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'school_ids': self.school_one.id, 'start_date': format_date(date_7_days_back), 'end_date': format_date(today), 'is_weekly': 'true', - }) + }, view_name='school-connectivity-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) @@ -533,15 +686,15 @@ def test_school_download_connectivity_stat_for_one_school_statistics(self): today = datetime.now().date() date_7_days_back = today - timedelta(days=6) - url, view = self.list_school_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'school_ids': self.school_one.id, 'start_date': format_date(date_7_days_back), 'end_date': format_date(today), 'is_weekly': 'true', - }) + }, view_name='school-connectivity-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) @@ -577,15 +730,15 @@ def test_school_download_connectivity_stat_for_connectivity_status_choices(self) today = datetime.now().date() date_7_days_back = today - timedelta(days=6) - url, view = self.list_school_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'school_ids': ','.join([str(self.school_one.id), str(self.school_two.id), str(self.school_three.id)]), 'start_date': format_date(date_7_days_back), 'end_date': format_date(today), 'is_weekly': 'true', - }) + }, view_name='school-connectivity-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -609,15 +762,15 @@ def test_school_download_connectivity_stat_for_one_school_graph_data_when_school today = datetime.now().date() date_7_days_back = today - timedelta(days=6) - url, view = self.list_school_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'school_ids': self.school_one.id, 'start_date': format_date(date_7_days_back), 'end_date': format_date(today), 'is_weekly': 'true', - }) + }, view_name='school-connectivity-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) @@ -643,15 +796,15 @@ def test_school_download_connectivity_stat_for_one_school_graph_data_when_school today = datetime.now().date() date_7_days_back = today - timedelta(days=6) - url, view = self.list_school_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'school_ids': self.school_two.id, 'start_date': format_date(date_7_days_back), 'end_date': format_date(today), 'is_weekly': 'true', - }) + }, view_name='school-connectivity-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) @@ -682,15 +835,15 @@ def test_school_download_connectivity_stat_for_one_school_when_school_weekly_sta today = datetime.now().date() date_7_days_back = today - timedelta(days=6) - url, view = self.list_school_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'school_ids': school_four.id, 'start_date': format_date(date_7_days_back), 'end_date': format_date(today), 'is_weekly': 'true', - }) + }, view_name='school-connectivity-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) @@ -716,15 +869,15 @@ def test_school_latency_connectivity_stat_school_list(self): today = datetime.now().date() date_7_days_back = today - timedelta(days=6) - url, view = self.list_school_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': self.country.id, 'school_ids': ','.join([str(self.school_one.id), str(self.school_two.id), str(self.school_three.id)]), 'start_date': format_date(date_7_days_back), 'end_date': format_date(today), 'is_weekly': 'true', - }) + }, view_name='school-connectivity-stat') - response = self.forced_auth_req('get', url, user=None, view=view) + response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 3) @@ -747,27 +900,12 @@ def setUpTestData(cls): cls.country_one_daily = CountryDailyStatusFactory(country=cls.country_one, date=Week(cls.stat_one.year, cls.stat_one.week).monday()) - cls.email = 'test@test.com' - cls.password = 'SomeRandomPass96' - cls.user = auth_models.ApplicationUser.objects.create_user(username=cls.email, password=cls.password) - - cls.role = auth_models.Role.objects.create(name='Admin', category='system') - cls.role_permission = auth_models.UserRoleRelationship.objects.create(user=cls.user, role=cls.role) + cls.user = test_utilities.setup_admin_user_by_role() def setUp(self): cache.clear() super().setUp() - def country_connectivity_stat_url(self, url_params, query_param): - view_name = 'connection_statistics:list_or_create_destroy_countryweeklystatus' - url = reverse(view_name, args=url_params) - view_info = resolve(url).func - - if len(query_param) > 0: - query_params = '?' + '&'.join([key + '=' + str(val) for key, val in query_param.items()]) - url += query_params - return url, view_info - def test_country_download_connectivity_stat(self): """ test_country_download_connectivity_stat @@ -779,13 +917,13 @@ def test_country_download_connectivity_stat(self): start_date = date - timedelta(days=1) end_date = start_date + timedelta(days=6) - url, view = self.country_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': self.country_one.id, 'start_date': format_date(start_date), 'end_date': format_date(end_date), 'is_weekly': 'true', 'benchmark': 'global' - }) + }, view_name='list-create-destroy-countryweeklystatus') response = self.forced_auth_req('get', url, user=self.user, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -805,13 +943,13 @@ def test_country_download_connectivity_stat_data(self): start_date = date - timedelta(days=1) end_date = start_date + timedelta(days=6) - url, view = self.country_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': self.country_one.id, 'start_date': format_date(start_date), 'end_date': format_date(end_date), 'is_weekly': 'true', 'benchmark': 'global' - }) + }, view_name='list-create-destroy-countryweeklystatus') response = self.forced_auth_req('get', url, user=self.user, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -823,13 +961,13 @@ def test_country_download_connectivity_stat_for_invalid_country_id(self): start_date = date - timedelta(days=1) end_date = start_date + timedelta(days=6) - url, view = self.country_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': 123456, 'start_date': format_date(start_date), 'end_date': format_date(end_date), 'is_weekly': 'true', 'benchmark': 'global' - }) + }, view_name='list-create-destroy-countryweeklystatus') response = self.forced_auth_req('get', url, user=self.user, view=view) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -839,13 +977,13 @@ def test_country_download_connectivity_stat_for_invalid_date_range(self): start_date = date - timedelta(days=1) end_date = start_date + timedelta(days=6) - url, view = self.country_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': self.country_one.id, 'start_date': format_date(start_date), 'end_date': format_date(end_date), 'is_weekly': 'true', 'benchmark': 'global' - }) + }, view_name='list-create-destroy-countryweeklystatus') response = self.forced_auth_req('get', url, user=self.user, view=view) @@ -856,12 +994,12 @@ def test_country_download_connectivity_stat_for_missing_country_id(self): start_date = date - timedelta(days=1) end_date = start_date + timedelta(days=6) - url, view = self.country_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'start_date': format_date(start_date), 'end_date': format_date(end_date), 'is_weekly': 'true', 'benchmark': 'global' - }) + }, view_name='list-create-destroy-countryweeklystatus') response = self.forced_auth_req('get', url, user=self.user, view=view) @@ -872,13 +1010,13 @@ def test_country_download_connectivity_stat_for_national_benchmark(self): start_date = date - timedelta(days=1) end_date = start_date + timedelta(days=6) - url, view = self.country_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': self.country_one.id, 'start_date': format_date(start_date), 'end_date': format_date(end_date), 'is_weekly': 'true', 'benchmark': 'national', - }) + }, view_name='list-create-destroy-countryweeklystatus') response = self.forced_auth_req('get', url, user=self.user, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -898,19 +1036,19 @@ def test_country_uptime_connectivity_stat(self): start_date = date - timedelta(days=1) end_date = start_date + timedelta(days=6) - url, view = self.country_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': self.country_one.id, 'start_date': format_date(start_date), 'end_date': format_date(end_date), 'is_weekly': 'true', 'benchmark': 'global' - }) + }, view_name='list-create-destroy-countryweeklystatus') response = self.forced_auth_req('get', url, user=self.user, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) - response_data = response.data + # response_data = response.data # self.assertIn('live_avg', response_data) # self.assertIn('schools_total', response_data['results']) # self.assertIn('school_with_realtime_data', response_data) @@ -929,13 +1067,13 @@ def test_country_download_connectivity_stat_monthly(self): start_date = get_first_date_of_month(date.year, date.month) end_date = get_last_date_of_month(date.year, date.month) - url, view = self.country_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': self.country_one.id, 'start_date': format_date(start_date), 'end_date': format_date(end_date), 'is_weekly': 'false', 'benchmark': 'global' - }) + }, view_name='list-create-destroy-countryweeklystatus') response = self.forced_auth_req('get', url, user=self.user, view=view) @@ -962,24 +1100,25 @@ def test_country_download_connectivity_stat_monthly_invalid_country_id(self): start_date = get_first_date_of_month(date.year, date.month) end_date = get_last_date_of_month(date.year, date.month) - url, view = self.country_connectivity_stat_url((), { + url, _, view = statistics_url((), { 'country_id': 123456, 'start_date': format_date(start_date), 'end_date': format_date(end_date), 'is_weekly': 'false', 'benchmark': 'global' - }) + }, view_name='list-create-destroy-countryweeklystatus') response = self.forced_auth_req('get', url, user=self.user, view=view) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) -class CountryCoverageStatsAPITestCase(APITestCase): +class CountryCoverageStatsAPITestCase(TestAPIViewSetMixin, TestCase): @classmethod def setUpTestData(cls): cls.country_one = CountryFactory() cls.country_two = CountryFactory() + cls.stat_one = CountryWeeklyStatusFactory(country=cls.country_one) cls.stat_two = CountryWeeklyStatusFactory(country=cls.country_two) @@ -988,9 +1127,10 @@ def setUp(self): super().setUp() def test_get_country_coverage_stats(self): - url = reverse('connection_statistics:country-coverage-stat') - query_params = {'country_id': self.country_one.id} - response = self.client.get(url, query_params) + url, _, view = statistics_url((), {'country_id': self.country_one.id}, view_name='country-coverage-stat') + + response = self.forced_auth_req('get', url, view=view) + self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['total_schools'], self.stat_one.schools_total) # self.assertEqual(response.data['connected_schools']['5g_4g'], self.stat_one.schools_coverage_good) @@ -999,25 +1139,132 @@ def test_get_country_coverage_stats(self): # self.assertEqual(response.data['connected_schools']['unknown'], self.stat_one.schools_coverage_unknown) def test_get_country_coverage_stats_no_data(self): - url = reverse('connection_statistics:country-coverage-stat') - query_params = {'country_id': 999} # Assuming this country ID does not exist - response = self.client.get(url, query_params) + url, _, view = statistics_url((), {'country_id': 999}, view_name='country-coverage-stat') + response = self.forced_auth_req('get', url, view=view) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_get_country_coverage_stats_cached(self): - url = reverse('connection_statistics:country-coverage-stat') - query_params = {'country_id': self.country_one.id} + url, _, view = statistics_url((), {'country_id': self.country_one.id}, view_name='country-coverage-stat') + # Call the API to cache the data - with self.assertNumQueries(6): - self.client.get(url, query_params) + with self.assertNumQueries(2): + self.forced_auth_req('get', url, view=view) with self.assertNumQueries(0): - self.client.get(url, query_params) + self.forced_auth_req('get', url, view=view) def test_get_country_coverage_stats_no_cache(self): url = reverse('connection_statistics:country-coverage-stat') query_params = {'country_id': self.country_one.id} # Call the API without caching - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.get(url, query_params, HTTP_CACHE_CONTROL='no-cache') self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class ConnectivityConfigurationsAPITestCase(TestAPIViewSetMixin, TestCase): + @classmethod + def setUpTestData(cls): + cls.country_one = CountryFactory() + cls.country_two = CountryFactory() + + cls.admin1_one = Admin1Factory(country=cls.country_one, layer_name='adm1') + + cls.school_one = SchoolFactory(country=cls.country_one, admin1=cls.admin1_one) + cls.school_two = SchoolFactory(country=cls.country_one) + cls.school_three = SchoolFactory(country=cls.country_one) + + cls.stat_one = SchoolDailyStatusFactory(school=cls.school_one) + cls.stat_two = SchoolDailyStatusFactory(school=cls.school_two) + + def setUp(self): + cache.clear() + super().setUp() + + def test_global_latest_configurations(self): + url, _, view = statistics_url((), {}, view_name='get-latest-week-and-month') + + with self.assertNumQueries(2): + response = self.forced_auth_req('get', url, view=view) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertIn('week', response_data) + self.assertIn('month', response_data) + self.assertIn('years', response_data) + + with self.assertNumQueries(0): + self.forced_auth_req('get', url, view=view) + + def test_country_with_schools_latest_configurations(self): + url, _, view = statistics_url((), {'country_id': self.country_one.id}, view_name='get-latest-week-and-month') + + with self.assertNumQueries(3): + response = self.forced_auth_req('get', url, view=view) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertIn('week', response_data) + self.assertIn('month', response_data) + self.assertIn('years', response_data) + + with self.assertNumQueries(0): + self.forced_auth_req('get', url, view=view) + + def test_country_without_schools_latest_configurations(self): + url, _, view = statistics_url((), {'country_id': self.country_two.id}, view_name='get-latest-week-and-month') + + with self.assertNumQueries(2): + response = self.forced_auth_req('get', url, view=view) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + + with self.assertNumQueries(2): + self.forced_auth_req('get', url, view=view) + + def test_admin1_latest_configurations(self): + url, _, view = statistics_url((), {'admin1_id': self.admin1_one.id}, view_name='get-latest-week-and-month') + + with self.assertNumQueries(3): + response = self.forced_auth_req('get', url, view=view) + self.assertEqual(response.status_code, status.HTTP_200_OK) + response_data = response.data + self.assertIn('week', response_data) + self.assertIn('month', response_data) + self.assertIn('years', response_data) + + with self.assertNumQueries(0): + self.forced_auth_req('get', url, view=view) + + def test_school_latest_configurations(self): + url, _, view = statistics_url((), {'school_id': self.school_one.id}, view_name='get-latest-week-and-month') + + with self.assertNumQueries(3): + response = self.forced_auth_req('get', url, view=view) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertIn('week', response_data) + self.assertIn('month', response_data) + self.assertIn('years', response_data) + + with self.assertNumQueries(0): + self.forced_auth_req('get', url, view=view) + + def test_schools_latest_configurations(self): + url, _, view = statistics_url((), { + 'school_ids': ','.join([str(s) for s in [self.school_one.id, self.school_two.id]]) + }, view_name='get-latest-week-and-month') + + with self.assertNumQueries(2): + response = self.forced_auth_req('get', url, view=view) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertIn('week', response_data) + self.assertIn('month', response_data) + self.assertIn('years', response_data) + + with self.assertNumQueries(0): + self.forced_auth_req('get', url, view=view) diff --git a/proco/data_sources/tests/test_utils.py b/proco/data_sources/tests/test_utils.py index dcac08f..3781718 100644 --- a/proco/data_sources/tests/test_utils.py +++ b/proco/data_sources/tests/test_utils.py @@ -98,7 +98,7 @@ def test_has_changes_for_review(self): self.assertFalse(sources_utilities.has_changes_for_review({ 'school_name': school.name, 'school_id_govt': school.external_id, - 'admin1_id_giga': None, + 'admin1_id_giga': school.admin1.giga_id_admin, 'admin2_id_giga': None, 'latitude': school.geopoint.y, 'longitude': school.geopoint.x, diff --git a/proco/locations/api_urls.py b/proco/locations/api_urls.py index 9d52b91..05ac801 100644 --- a/proco/locations/api_urls.py +++ b/proco/locations/api_urls.py @@ -27,11 +27,11 @@ 'get': 'list', 'post': 'create', 'delete': 'destroy', - }), name='list_or_create_or_destroy_country'), + }), name='list-create-destroy-country'), path('country//', api.CountryDataViewSet.as_view({ 'get': 'retrieve', 'put': 'update', - }), name='update_or_retrieve_country'), + }), name='update-retrieve-country'), path('country-admin-metadata/', api.CountryAdminMetadataViewSet.as_view({ 'get': 'list', diff --git a/proco/locations/tests/factories.py b/proco/locations/tests/factories.py index d0496a9..749774d 100644 --- a/proco/locations/tests/factories.py +++ b/proco/locations/tests/factories.py @@ -4,7 +4,7 @@ from factory import django as django_factory from factory import fuzzy -from proco.locations.models import Country, Location +from proco.locations.models import Country, CountryAdminMetadata, Location class CountryFactory(django_factory.DjangoModelFactory): @@ -27,3 +27,15 @@ class LocationFactory(django_factory.DjangoModelFactory): class Meta: model = Location + + +class Admin1Factory(django_factory.DjangoModelFactory): + name = fuzzy.FuzzyText(length=20) + giga_id_admin = fuzzy.FuzzyText(length=20) + description = fuzzy.FuzzyText(length=40) + layer_name = fuzzy.FuzzyChoice(dict(CountryAdminMetadata.LAYER_NAME_CHOICES).keys()) + + country = SubFactory(CountryFactory) + + class Meta: + model = CountryAdminMetadata diff --git a/proco/locations/tests/test_api.py b/proco/locations/tests/test_api.py index d49b2f0..39ddcac 100644 --- a/proco/locations/tests/test_api.py +++ b/proco/locations/tests/test_api.py @@ -1,16 +1,18 @@ +import random +import string + from django.contrib.gis.geos import GEOSGeometry from django.core.cache import cache from django.test import TestCase from django.urls import reverse - from rest_framework import status -import string, random + from proco.connection_statistics.tests.factories import CountryWeeklyStatusFactory -from proco.locations.tests.factories import CountryFactory +from proco.custom_auth.tests import test_utils as auth_test_utilities +from proco.locations.models import Country +from proco.locations.tests.factories import Admin1Factory, CountryFactory from proco.schools.tests.factories import SchoolFactory from proco.utils.tests import TestAPIViewSetMixin -from proco.custom_auth import models as auth_models -from proco.locations.models import Country class CountryApiTestCase(TestAPIViewSetMixin, TestCase): @@ -23,8 +25,12 @@ def get_detail_args(self, instance): def setUpTestData(cls): cls.country_one = CountryFactory() cls.country_two = CountryFactory() - SchoolFactory(country=cls.country_one, location__country=cls.country_one) - SchoolFactory(country=cls.country_one, location__country=cls.country_one) + + cls.admin1_one = Admin1Factory(country=cls.country_one) + + SchoolFactory(country=cls.country_one, location__country=cls.country_one, admin1=cls.admin1_one) + SchoolFactory(country=cls.country_one, location__country=cls.country_one, admin1=cls.admin1_one) + CountryWeeklyStatusFactory(country=cls.country_one) def setUp(self): @@ -56,12 +62,6 @@ def test_country_list_cached(self): user=None, expected_objects=[self.country_one, self.country_two], ) - # def test_empty_countries_hidden(self): - # CountryFactory(geometry=GEOSGeometry('{"type": "MultiPolygon", "coordinates": []}')) - # self._test_list( - # user=None, expected_objects=[self.country_one, self.country_two], - # ) - class CountryBoundaryApiTestCase(TestAPIViewSetMixin, TestCase): base_view = 'locations:countries-list' @@ -70,8 +70,11 @@ class CountryBoundaryApiTestCase(TestAPIViewSetMixin, TestCase): def setUpTestData(cls): cls.country_one = CountryFactory() cls.country_two = CountryFactory() - SchoolFactory(country=cls.country_one, location__country=cls.country_one) - SchoolFactory(country=cls.country_one, location__country=cls.country_one) + + cls.admin1_one = Admin1Factory(country=cls.country_one) + + SchoolFactory(country=cls.country_one, location__country=cls.country_one, admin1=cls.admin1_one) + SchoolFactory(country=cls.country_one, location__country=cls.country_one, admin1=cls.admin1_one) def setUp(self): cache.clear() @@ -101,67 +104,70 @@ def test_empty_countries_hidden(self): class CountryDataTestCase(TestAPIViewSetMixin, TestCase): base_view = 'locations:' - databases = {'default',} + databases = {'default', } def setUp(self): - self.email = 'test@test.com' - self.password = 'SomeRandomPass96' - self.user = auth_models.ApplicationUser.objects.create_user(username=self.email, password=self.password) - - self.role = auth_models.Role.objects.create(name='Admin', category='system') - self.role_permission = auth_models.UserRoleRelationship.objects.create(user=self.user, role=self.role) + self.user = auth_test_utilities.setup_admin_user_by_role() - self.data = {"name": ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)), - # ===str(uuid.uuid4())[0:10], - "code": ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(2)), - "last_weekly_status_id": 2091, - "flag": "images/7962e7d2-ea1f-4571-a031-bb830fd575c6.png"} + self.data = { + 'name': ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)), + 'code': ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(2)), + 'last_weekly_status_id': 2091, + 'flag': 'images/7962e7d2-ea1f-4571-a031-bb830fd575c6.png' + } self.country_id = Country.objects.create(**self.data).id - self.delete_data = {"id": [self.country_id]} + self.delete_data = {'id': [self.country_id]} self.country_one = CountryFactory() return super().setUp() # def test_create(self): - # self.data = {"name": ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)),#===str(uuid.uuid4())[0:10], - # "code": ''.join(random.choice(string.ascii_uppercase) for _ in range(2)), - # "last_weekly_status_id": 2091,"benchmark_metadata":{}} + # self.data = { + # "name": ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)), + # "code": ''.join(random.choice(string.ascii_uppercase) for _ in range(2)), + # "last_weekly_status_id": 2091, + # "benchmark_metadata": {} + # } # headers = {'Content-Type': 'multipart/form-data'} # # response = self.forced_auth_req( # 'post', - # reverse(self.base_view + "list_or_create_or_destroy_country"), + # reverse(self.base_view + "list-create-destroy-country"), # data=self.data, # headers=headers, # user=self.user) - - # self.assertEqual(response.status_code, status.HTTP_200_OK) - # self.assertNotEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) - + # + # self.assertEqual(response.status_code, status.HTTP_200_OK) + # self.assertNotEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) + # # def test_update(self): - # self.data = {"name": ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)),#===str(uuid.uuid4())[0:10], - # "code": ''.join(random.choice(string.ascii_uppercase) for _ in range(2)), - # "last_weekly_status_id": 2091,"benchmark_metadata":{}} + # self.data = { + # "name": ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)), + # "code": ''.join(random.choice(string.ascii_uppercase) for _ in range(2)), + # "last_weekly_status_id": 2091, + # "benchmark_metadata": {} + # } # self.country_one = CountryFactory() + # import json # from django.core import serializers - # tmpJson = serializers.serialize("json", self.country_one[0]) - # tmpObj = json.loads(tmpJson) - # # import json - # # print(json.dumps(self.country_one.__dict__)) + # + # tmp_json = serializers.serialize("json", self.country_one[0]) + # tmp_obj = json.loads(tmp_json) + # # response = self.forced_auth_req( # 'put', - # reverse(self.base_view + "update_or_retrieve_country", args=(self.country_id,)), - # data=tmpObj, + # reverse(self.base_view + "update-retrieve-country", args=(self.country_id,)), + # data=tmp_obj, # user=self.user, # ) - # self.assertEqual(response.status_code, status.HTTP_200_OK) - # self.assertNotEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) + # self.assertEqual(response.status_code, status.HTTP_200_OK) + # self.assertNotEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) def test_destroy(self): response = self.forced_auth_req( 'delete', - reverse(self.base_view + "list_or_create_or_destroy_country"), + reverse(self.base_view + 'list-create-destroy-country'), data=self.delete_data, user=self.user, ) diff --git a/proco/schools/tests/factories.py b/proco/schools/tests/factories.py index 4a26b95..3d53b87 100644 --- a/proco/schools/tests/factories.py +++ b/proco/schools/tests/factories.py @@ -1,11 +1,10 @@ from django.contrib.gis.geos import GEOSGeometry - from factory import SubFactory from factory import django as django_factory from factory import fuzzy -from proco.locations.tests.factories import CountryFactory, LocationFactory -from proco.schools.models import School, FileImport +from proco.locations.tests.factories import Admin1Factory, CountryFactory, LocationFactory +from proco.schools.models import FileImport, School class SchoolFactory(django_factory.DjangoModelFactory): @@ -14,6 +13,7 @@ class SchoolFactory(django_factory.DjangoModelFactory): external_id = fuzzy.FuzzyText(length=20) country = SubFactory(CountryFactory) location = SubFactory(LocationFactory) + admin1 = SubFactory(Admin1Factory) geopoint = GEOSGeometry('Point(1 1)') gps_confidence = fuzzy.FuzzyDecimal(low=0.0) altitude = fuzzy.FuzzyInteger(0, 10000) diff --git a/proco/schools/tests/test_api.py b/proco/schools/tests/test_api.py index ebc28e0..67965e3 100644 --- a/proco/schools/tests/test_api.py +++ b/proco/schools/tests/test_api.py @@ -6,8 +6,8 @@ from proco.connection_statistics.models import CountryWeeklyStatus from proco.connection_statistics.tests.factories import SchoolWeeklyStatusFactory from proco.custom_auth.tests import test_utils as test_utilities -from proco.locations.tests.factories import CountryFactory -from proco.schools.tests.factories import SchoolFactory, FileImportFactory +from proco.locations.tests.factories import Admin1Factory, CountryFactory +from proco.schools.tests.factories import FileImportFactory, SchoolFactory from proco.utils.tests import TestAPIViewSetMixin @@ -28,9 +28,13 @@ class SchoolsApiTestCase(TestAPIViewSetMixin, TestCase): @classmethod def setUpTestData(cls): cls.country = CountryFactory() - cls.school_one = SchoolFactory(country=cls.country, location__country=cls.country) - cls.school_two = SchoolFactory(country=cls.country, location__country=cls.country) - cls.school_three = SchoolFactory(country=cls.country, location__country=cls.country) + + cls.admin1_one = Admin1Factory(country=cls.country) + + cls.school_one = SchoolFactory(country=cls.country, location__country=cls.country, admin1=cls.admin1_one) + cls.school_two = SchoolFactory(country=cls.country, location__country=cls.country, admin1=cls.admin1_one) + cls.school_three = SchoolFactory(country=cls.country, location__country=cls.country, admin1=cls.admin1_one) + cls.school_weekly_one = SchoolWeeklyStatusFactory( school=cls.school_one, connectivity=True, connectivity_speed=3 * (10 ** 6), diff --git a/proco/utils/urls.py b/proco/utils/urls.py index 0ebecfe..a29b468 100644 --- a/proco/utils/urls.py +++ b/proco/utils/urls.py @@ -14,7 +14,7 @@ def add_url_params(url, params): >> add_url_params(url, new_params) 'http://stackoverflow.com/test?data=some&data=values&answers=false' """ - # Unquoting URL first so we don't loose existing args + # Unquoting URL first so we don't lose existing args url = unquote(url) # Extracting url info parsed_url = urlparse(url) From 0a0ccd13088e1bc04f4380a6b5cce0e1d7743bd0 Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Wed, 17 Jul 2024 13:03:06 +0530 Subject: [PATCH 04/27] Optimized latest week/month API --- proco/connection_statistics/api.py | 5 -- proco/connection_statistics/tests/test_api.py | 73 +++++++++++++++++-- proco/schools/api.py | 7 +- 3 files changed, 70 insertions(+), 15 deletions(-) diff --git a/proco/connection_statistics/api.py b/proco/connection_statistics/api.py index 822f939..a826e4a 100644 --- a/proco/connection_statistics/api.py +++ b/proco/connection_statistics/api.py @@ -745,19 +745,14 @@ def get(self, request, *args, **kwargs): country_id = self.request.query_params.get('country_id', None) if country_id: - get_object_or_404(Country.objects.defer('geometry', 'geometry_simplified', ), id=country_id) self.queryset = self.queryset.filter(school__country_id=country_id) admin1_id = self.request.query_params.get('admin1_id', None) if admin1_id: - get_object_or_404(CountryAdminMetadata.objects.filter( - layer_name=CountryAdminMetadata.LAYER_NAME_ADMIN1, - ), id=admin1_id) self.queryset = self.queryset.filter(school__admin1_id=admin1_id) school_id = self.request.query_params.get('school_id', None) if school_id: - get_object_or_404(School.objects.all(), id=school_id) self.queryset = self.queryset.filter(school=school_id) school_ids = self.request.query_params.get('school_ids', '') diff --git a/proco/connection_statistics/tests/test_api.py b/proco/connection_statistics/tests/test_api.py index 4e76272..b8834d6 100755 --- a/proco/connection_statistics/tests/test_api.py +++ b/proco/connection_statistics/tests/test_api.py @@ -349,9 +349,11 @@ def setUpTestData(cls): cls.country = CountryFactory() cls.country_two = CountryFactory() - cls.school_one = SchoolFactory(country=cls.country, location__country=cls.country) - cls.school_two = SchoolFactory(country=cls.country, location__country=cls.country) - cls.school_three = SchoolFactory(country=cls.country, location__country=cls.country) + cls.admin1_one = Admin1Factory(country=cls.country, layer_name='adm1') + + cls.school_one = SchoolFactory(country=cls.country, location__country=cls.country, admin1=cls.admin1_one) + cls.school_two = SchoolFactory(country=cls.country, location__country=cls.country, admin1=cls.admin1_one) + cls.school_three = SchoolFactory(country=cls.country, location__country=cls.country, admin1=cls.admin1_one) cls.school_weekly_one = SchoolWeeklyStatusFactory( school=cls.school_one, @@ -412,6 +414,37 @@ def test_country_download_connectivity_stat(self): self.assertIn('graph_data', response_data) self.assertIn('real_time_connected_schools', response_data) + def test_admin1_download_connectivity_stat(self): + """ + test_admin1_download_connectivity_stat + Positive test case for weekly data. + + Expected: HTTP_200_OK - List of data for all 3 schools + """ + today = datetime.now().date() + start_date = get_first_date_of_month(today.year, today.month) + end_date = get_last_date_of_month(today.year, today.month) + + url, _, view = statistics_url((), { + 'country_id': self.country.id, + 'admin1_id': self.admin1_one.id, + 'start_date': format_date(start_date), + 'end_date': format_date(end_date), + 'is_weekly': 'false', + }, view_name='global-connectivity-stat') + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertIn('live_avg', response_data) + self.assertIn('no_of_schools_measure', response_data) + self.assertIn('school_with_realtime_data', response_data) + self.assertIn('is_data_synced', response_data) + self.assertIn('graph_data', response_data) + self.assertIn('real_time_connected_schools', response_data) + def test_country_download_connectivity_stat_without_country_id(self): """ test_school_download_connectivity_stat_without_school_id @@ -587,6 +620,30 @@ def test_school_download_connectivity_stat_school_list(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 3) + def test_school_download_connectivity_stat_school_list_for_month(self): + """ + test_school_download_connectivity_stat_school_list + Positive test case for weekly data. + + Expected: HTTP_200_OK - List of data for all 3 schools + """ + today = datetime.now().date() + start_date = get_first_date_of_month(today.year, today.month) + end_date = get_last_date_of_month(today.year, today.month) + + url, _, view = statistics_url((), { + 'country_id': self.country.id, + 'school_ids': ','.join([str(self.school_one.id), str(self.school_two.id), str(self.school_three.id)]), + 'start_date': format_date(start_date), + 'end_date': format_date(end_date), + 'is_weekly': 'false', + }, view_name='school-connectivity-stat') + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 3) + def test_school_download_connectivity_stat_without_school_id(self): """ test_school_download_connectivity_stat_without_school_id @@ -1200,7 +1257,7 @@ def test_global_latest_configurations(self): def test_country_with_schools_latest_configurations(self): url, _, view = statistics_url((), {'country_id': self.country_one.id}, view_name='get-latest-week-and-month') - with self.assertNumQueries(3): + with self.assertNumQueries(2): response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1215,18 +1272,18 @@ def test_country_with_schools_latest_configurations(self): def test_country_without_schools_latest_configurations(self): url, _, view = statistics_url((), {'country_id': self.country_two.id}, view_name='get-latest-week-and-month') - with self.assertNumQueries(2): + with self.assertNumQueries(1): response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 0) - with self.assertNumQueries(2): + with self.assertNumQueries(1): self.forced_auth_req('get', url, view=view) def test_admin1_latest_configurations(self): url, _, view = statistics_url((), {'admin1_id': self.admin1_one.id}, view_name='get-latest-week-and-month') - with self.assertNumQueries(3): + with self.assertNumQueries(2): response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) response_data = response.data @@ -1240,7 +1297,7 @@ def test_admin1_latest_configurations(self): def test_school_latest_configurations(self): url, _, view = statistics_url((), {'school_id': self.school_one.id}, view_name='get-latest-week-and-month') - with self.assertNumQueries(3): + with self.assertNumQueries(2): response = self.forced_auth_req('get', url, view=view) self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/proco/schools/api.py b/proco/schools/api.py index 48ca32d..bae32ce 100644 --- a/proco/schools/api.py +++ b/proco/schools/api.py @@ -48,6 +48,7 @@ logger = logging.getLogger('gigamaps.' + __name__) + @method_decorator([cache_control(public=True, max_age=settings.CACHE_CONTROL_MAX_AGE_FOR_FE)], name='dispatch') class SchoolsViewSet( CachedListMixin, @@ -321,13 +322,15 @@ def query_filters(self, request, table_configs): 'school_id__in' in request.query_params ): if 'country_id' in request.query_params: - table_configs['country_condition'] = f" AND schools_school.country_id = {request.query_params['country_id']}" + table_configs[ + 'country_condition'] = f" AND schools_school.country_id = {request.query_params['country_id']}" elif 'country_id__in' in request.query_params: country_ids = ','.join([c.strip() for c in request.query_params['country_id__in'].split(',')]) table_configs['country_condition'] = f" AND schools_school.country_id IN ({country_ids})" if 'admin1_id' in request.query_params: - table_configs['admin1_condition'] = f" AND schools_school.admin1_id = {request.query_params['admin1_id']}" + table_configs[ + 'admin1_condition'] = f" AND schools_school.admin1_id = {request.query_params['admin1_id']}" elif 'admin1_id__in' in request.query_params: admin1_ids = ','.join([c.strip() for c in request.query_params['admin1_id__in'].split(',')]) table_configs['admin1_condition'] = f" AND schools_school.admin1_id IN ({admin1_ids})" From e23f9ada6a9fdbcfbc986ca845bfafb58d474b61 Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Thu, 18 Jul 2024 11:57:46 +0530 Subject: [PATCH 05/27] added missed docker file --- docker/docker-compose.dev.yml | 96 +++++++++++++++++++ proco/connection_statistics/api.py | 23 +++-- .../commands/data_loss_recovery_for_pcdc.py | 7 +- 3 files changed, 113 insertions(+), 13 deletions(-) create mode 100644 docker/docker-compose.dev.yml diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml new file mode 100644 index 0000000..f394c22 --- /dev/null +++ b/docker/docker-compose.dev.yml @@ -0,0 +1,96 @@ +version: "3" + +services: + db: + container_name: proco_dev_db + build: + context: .. + dockerfile: docker/Dockerfile-db15 + environment: + POSTGRES_USER: test + POSTGRES_PASSWORD: test + POSTGRES_DB: proco + volumes: + - "../postgres_data:/postgres_data" + ports: + - "7432:5432" + + redis: + restart: always + image: redis:latest + expose: + - "6379" + +# redisinsight: +# image: redislabs/redisinsight:latest +# ports: +# - '8001:8001' + +# omnidb: +# image: omnidbteam/omnidb:latest +# container_name: omnidb +# ports: +# - '9001:9001' + + backend: + container_name: proco_dev_backend + build: + context: .. + dockerfile: docker/Dockerfile-dev + command: bash -c "sleep 10 && pipenv run python manage.py migrate && pipenv run python manage.py runserver 0.0.0.0:8000" + ports: + - "8000:8000" + environment: + DJANGO_SETTINGS_MODULE: config.settings.dev + DATABASE_URL: postgis://test:test@db/proco + READ_ONLY_DATABASE_URL: postgis://test:test@db/proco +# CELERY_BROKER_URL: amqp://rabbitmq:rabbitmq@rabbitmq/ + REDIS_URL: redis://redis:6379/0 + depends_on: + - db +# - rabbitmq + - redis + volumes: + - "..:/code" + +# rabbitmq: +# image: rabbitmq:3.8-alpine +# environment: +# RABBITMQ_DEFAULT_USER: rabbitmq +# RABBITMQ_DEFAULT_PASS: rabbitmq + + celery_beat: + container_name: proco_dev_beat + build: + context: .. + dockerfile: docker/Dockerfile-dev + command: bash -c "sleep 10 && pipenv run celery --app=proco.taskapp beat --scheduler=redbeat.RedBeatScheduler --loglevel=DEBUG" + environment: + DJANGO_SETTINGS_MODULE: config.settings.dev + DATABASE_URL: postgis://test:test@db/proco +# CELERY_BROKER_URL: amqp://rabbitmq:rabbitmq@rabbitmq/ + REDIS_URL: redis://redis:6379/0 + volumes: + - "..:/code" + depends_on: + - db +# - rabbitmq + - redis + + celery_worker: + container_name: proco_dev_celeryd + build: + context: .. + dockerfile: docker/Dockerfile-dev + command: bash -c "sleep 10 && pipenv run celery --app=proco.taskapp worker --loglevel=DEBUG --time-limit=300 --concurrency=2 --soft-time-limit=300" + environment: + DJANGO_SETTINGS_MODULE: config.settings.dev + DATABASE_URL: postgis://test:test@db/proco +# CELERY_BROKER_URL: amqp://rabbitmq:rabbitmq@rabbitmq/ + REDIS_URL: redis://redis:6379/0 + volumes: + - "..:/code" + depends_on: + - db +# - rabbitmq + - redis diff --git a/proco/connection_statistics/api.py b/proco/connection_statistics/api.py index a826e4a..e65be54 100644 --- a/proco/connection_statistics/api.py +++ b/proco/connection_statistics/api.py @@ -35,7 +35,7 @@ from proco.core import permissions as core_permissions from proco.core import utils as core_utilities from proco.core.viewsets import BaseModelViewSet -from proco.locations.models import Country, CountryAdminMetadata +from proco.locations.models import Country from proco.schools.models import School from proco.utils import dates as date_utilities from proco.utils.cache import cache_manager @@ -990,10 +990,11 @@ def retrieve(self, request, pk): try: country_daily_status = CountryDailyStatus.objects.get(id=pk) if country_daily_status: - serializer = statistics_serializers.CountryDailyStatusUpdateRetrieveSerializer(country_daily_status, - partial=True, - context={ - 'request': request}, ) + serializer = statistics_serializers.CountryDailyStatusUpdateRetrieveSerializer( + country_daily_status, + partial=True, + context={'request': request}, + ) return Response(serializer.data) return Response(status=rest_status.HTTP_404_NOT_FOUND, data=error_mess) except CountryDailyStatus.DoesNotExist: @@ -1214,10 +1215,11 @@ def retrieve(self, request, pk): try: school_daily_status = SchoolDailyStatus.objects.get(id=pk) if school_daily_status: - serializer = statistics_serializers.SchoolDailyStatusUpdateRetriveSerializer(school_daily_status, - partial=True, - context={ - 'request': request}, ) + serializer = statistics_serializers.SchoolDailyStatusUpdateRetriveSerializer( + school_daily_status, + partial=True, + context={'request': request}, + ) return Response(serializer.data) return Response(status=rest_status.HTTP_404_NOT_FOUND, data=error_mess) except SchoolDailyStatus.DoesNotExist: @@ -1267,7 +1269,8 @@ def get_live_query(self, **kwargs): ELSE 'unknown' END AS field_status, CASE WHEN rt_status.rt_registered = True - AND EXTRACT(YEAR FROM CAST(rt_status.rt_registration_date AS DATE)) <= EXTRACT(YEAR FROM CAST(t.date AS DATE)) + AND EXTRACT(YEAR FROM CAST(rt_status.rt_registration_date AS DATE)) <= + EXTRACT(YEAR FROM CAST(t.date AS DATE)) THEN True ELSE False END as is_rt_connected FROM schools_school AS s INNER JOIN connection_statistics_schooldailystatus t ON s.id = t.school_id diff --git a/proco/data_sources/management/commands/data_loss_recovery_for_pcdc.py b/proco/data_sources/management/commands/data_loss_recovery_for_pcdc.py index a9d4f82..cae118b 100644 --- a/proco/data_sources/management/commands/data_loss_recovery_for_pcdc.py +++ b/proco/data_sources/management/commands/data_loss_recovery_for_pcdc.py @@ -132,7 +132,7 @@ def handle(self, **options): logger.debug('Data synced successfully.\n\n') logger.debug('Aggregating the pulled data by giga_id_school + country_code and ' - 'storing in RealTimeConnectivity table.') + 'storing in RealTimeConnectivity table.') dailycheckapp_measurements = DailyCheckAppMeasurementData.objects.filter( timestamp__date=pull_data_date, ).filter( @@ -149,7 +149,7 @@ def handle(self, **options): if not dailycheckapp_measurements.exists(): logger.error('No records to aggregate on provided date: "{0}". ' - 'Hence stopping the execution here.'.format(pull_data_date)) + 'Hence stopping the execution here.'.format(pull_data_date)) return realtime = [] @@ -232,7 +232,8 @@ def handle(self, **options): )) if len(realtime) == 5000: - logger.debug('Loading the data to "RealTimeConnectivity" table as it has reached 5000 benchmark.') + logger.debug( + 'Loading the data to "RealTimeConnectivity" table as it has reached 5000 benchmark.') RealTimeConnectivity.objects.bulk_create(realtime) realtime = [] From 1250199679f0fd9dda98cf5757e1adf15ecbf1e3 Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Thu, 18 Jul 2024 12:06:27 +0530 Subject: [PATCH 06/27] Added email fixes from old repo to new repo --- .env_example | 6 ++++++ config/settings/base.py | 4 ++-- config/settings/prod.py | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.env_example b/.env_example index bb8ce58..4f5acf1 100644 --- a/.env_example +++ b/.env_example @@ -61,3 +61,9 @@ INVALIDATE_CACHE_HARD=true CACHE_CONTROL_MAX_AGE_FOR_FE=14400 API_KEY_ADMIN_DASHBOARD_URL=http://localhost:9500/admin/api-keys + +EMAIL_URL= +SERVER_EMAIL_SIGNATURE=Gigamaps +SERVER_EMAIL=gigamaps@mail.unicef.org +CONTACT_EMAIL=vikash.kumar05@nagarro.com +MAILING_USE_CELERY=false diff --git a/config/settings/base.py b/config/settings/base.py index 49c99f1..1c2637c 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -251,7 +251,7 @@ if CELERY_ENABLED: TEMPLATED_EMAIL_BACKEND = 'proco.mailing.backends.AsyncTemplateBackend' - MAILING_USE_CELERY = True + MAILING_USE_CELERY = env.bool('MAILING_USE_CELERY', default=False) TEMPLATED_EMAIL_TEMPLATE_DIR = 'email' TEMPLATED_EMAIL_FILE_EXTENSION = 'html' @@ -358,7 +358,7 @@ }], } CONSTANCE_CONFIG = { - 'CONTACT_EMAIL': ('', 'Email to receive contact messages', 'email_input'), + 'CONTACT_EMAIL': (env('CONTACT_EMAIL', default=''), 'Email to receive contact messages', 'email_input'), } # Cache control headers diff --git a/config/settings/prod.py b/config/settings/prod.py index 1591de3..f2d29da 100644 --- a/config/settings/prod.py +++ b/config/settings/prod.py @@ -86,7 +86,7 @@ # Email settings # -------------------------------------------------------------------------- -EMAIL_CONFIG = env.email() +EMAIL_CONFIG = env.email(backend=EMAIL_BACKEND) vars().update(EMAIL_CONFIG) SERVER_EMAIL_SIGNATURE = env('SERVER_EMAIL_SIGNATURE', default='proco'.capitalize()) From 4b8f338a53ddb40d8f3c16a80ad5fc23ba4b7bcb Mon Sep 17 00:00:00 2001 From: Vikash Kumar <140630604+vikashkum05@users.noreply.github.com> Date: Thu, 18 Jul 2024 21:01:41 +0530 Subject: [PATCH 07/27] Update Pipfile versions --- Pipfile | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Pipfile b/Pipfile index 218007c..2623e67 100644 --- a/Pipfile +++ b/Pipfile @@ -6,24 +6,24 @@ name = "pypi" [dev-packages] django-debug-toolbar = "*" isort = "*" -flake8 = "*" +flake8 = "==6.1.0" dlint = "*" -flake8-bandit = "*" -flake8-blind-except = "*" -flake8-broken-line = "*" -flake8-bugbear = "*" -flake8-comprehensions = "*" -flake8-eradicate = "*" -flake8-string-format = "*" -flake8-commas = "*" -flake8-quotes = "*" -flake8-logging-format = "*" +flake8-bandit = "==4.1.1" +flake8-blind-except = "==0.2.1" +flake8-broken-line = "==1.0.0" +flake8-bugbear = "==23.12.2" +flake8-comprehensions = "==3.14.0" +flake8-eradicate = "==1.5.0" +flake8-string-format = "==0.3.0" +flake8-commas = "==2.1.0" +flake8-quotes = "==3.3.2" +flake8-logging-format = "==0.9.0" mccabe = "*" pep8-naming = "*" pycodestyle = "*" -flake8-rst-docstrings = "*" -flake8-pep3101 = "*" -flake8-mutable = "*" +flake8-rst-docstrings = "==0.3.0" +flake8-pep3101 = "==2.1.0" +flake8-mutable = "==1.2.0" coverage = "==7.3.0" django-anymail = "==7.2" delta-sharing = "==1.0.3" @@ -34,7 +34,7 @@ psycopg2 = "==2.8.6" celery = "<6.0" ipython = "*" django-templated-email = "*" -sentry-sdk = "*" +sentry-sdk = "==1.39.1" newrelic = "*" djangorestframework = ">=3.10" drf-secure-token = "*" From 42080c13aa07c6fb1c8c59232abd9a49818c45e2 Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Thu, 18 Jul 2024 21:38:02 +0530 Subject: [PATCH 08/27] Fixed factories and removed unused html files --- config/settings/dev_test.py | 2 - proco/connection_statistics/api.py | 2 - .../connection_statistics/tests/factories.py | 29 +- .../tests/test_aggregates.py | 52 ++- proco/connection_statistics/tests/test_api.py | 418 ++++++++---------- proco/custom_auth/templates/base.html | 11 - .../templates/email/email_base.html | 77 ---- 7 files changed, 246 insertions(+), 345 deletions(-) delete mode 100644 proco/custom_auth/templates/base.html delete mode 100644 proco/custom_auth/templates/email/email_base.html diff --git a/config/settings/dev_test.py b/config/settings/dev_test.py index 81cdd22..eed8304 100644 --- a/config/settings/dev_test.py +++ b/config/settings/dev_test.py @@ -61,5 +61,3 @@ 'COUNTRY_INDEX_NAME': env('COUNTRY_INDEX_NAME', default='giga_countries'), 'SCHOOL_INDEX_NAME': env('SCHOOL_INDEX_NAME', default='giga_schools'), } - -DEFAULT_FILE_STORAGE = 'inmemorystorage.InMemoryStorage' diff --git a/proco/connection_statistics/api.py b/proco/connection_statistics/api.py index e65be54..a173655 100644 --- a/proco/connection_statistics/api.py +++ b/proco/connection_statistics/api.py @@ -597,8 +597,6 @@ def generate_country_graph_data(self, start_date, end_date): current_date += timedelta(days=1) all_positive_speeds = [] - - # Update the graph_data with actual values if they exist # Update the graph_data with actual values if they exist for daily_avg_data in avg_daily_connectivity_speed: formatted_date = date_utilities.format_date(daily_avg_data['daily_status__date']) diff --git a/proco/connection_statistics/tests/factories.py b/proco/connection_statistics/tests/factories.py index 4a98236..2f03cc2 100644 --- a/proco/connection_statistics/tests/factories.py +++ b/proco/connection_statistics/tests/factories.py @@ -1,9 +1,11 @@ +import datetime from datetime import date from factory import SubFactory from factory import django as django_factory from factory import fuzzy +from proco.connection_statistics.config import app_config as statistics_configs from proco.connection_statistics.models import ( CountryDailyStatus, CountryWeeklyStatus, @@ -17,8 +19,14 @@ class RealTimeConnectivityFactory(django_factory.DjangoModelFactory): school = SubFactory(SchoolFactory) + + created = fuzzy.FuzzyDateTime(datetime.datetime(year=1970, month=1, day=1, tzinfo=datetime.timezone.utc)) + connectivity_speed = fuzzy.FuzzyInteger(1, 1000000) - connectivity_latency = fuzzy.FuzzyInteger(1, 100) + connectivity_upload_speed = fuzzy.FuzzyInteger(1, 1000000) + connectivity_latency = fuzzy.FuzzyFloat(0.0, 1000.0) + + live_data_source = fuzzy.FuzzyChoice(dict(statistics_configs.LIVE_DATA_SOURCE_CHOICES).keys()) class Meta: model = RealTimeConnectivity @@ -27,8 +35,12 @@ class Meta: class CountryDailyStatusFactory(django_factory.DjangoModelFactory): country = SubFactory(CountryFactory) date = fuzzy.FuzzyDate(date(year=1970, month=1, day=1)) + connectivity_speed = fuzzy.FuzzyInteger(1, 1000000) - connectivity_latency = fuzzy.FuzzyInteger(1, 100) + connectivity_upload_speed = fuzzy.FuzzyInteger(1, 1000000) + connectivity_latency = fuzzy.FuzzyFloat(0.0, 1000.0) + + live_data_source = fuzzy.FuzzyChoice(dict(statistics_configs.LIVE_DATA_SOURCE_CHOICES).keys()) class Meta: model = CountryDailyStatus @@ -37,8 +49,12 @@ class Meta: class SchoolDailyStatusFactory(django_factory.DjangoModelFactory): school = SubFactory(SchoolFactory) date = fuzzy.FuzzyDate(date(year=1970, month=1, day=1)) + connectivity_speed = fuzzy.FuzzyInteger(1, 1000000) - connectivity_latency = fuzzy.FuzzyInteger(1, 100) + connectivity_upload_speed = fuzzy.FuzzyInteger(1, 1000000) + connectivity_latency = fuzzy.FuzzyFloat(0.0, 1000.0) + + live_data_source = fuzzy.FuzzyChoice(dict(statistics_configs.LIVE_DATA_SOURCE_CHOICES).keys()) class Meta: model = SchoolDailyStatus @@ -53,7 +69,11 @@ class CountryWeeklyStatusFactory(django_factory.DjangoModelFactory): schools_connectivity_no = fuzzy.FuzzyInteger(0, 1000) schools_connectivity_moderate = fuzzy.FuzzyInteger(0, 1000) schools_connectivity_good = fuzzy.FuzzyInteger(0, 1000) + connectivity_speed = fuzzy.FuzzyInteger(1, 1000000) + connectivity_upload_speed = fuzzy.FuzzyInteger(1, 1000000) + connectivity_latency = fuzzy.FuzzyFloat(0.0, 1000.0) + integration_status = fuzzy.FuzzyChoice(dict(CountryWeeklyStatus.INTEGRATION_STATUS_TYPES).keys()) avg_distance_school = fuzzy.FuzzyFloat(0.0, 1000.0) schools_coverage_good = fuzzy.FuzzyInteger(0, 1000) @@ -72,7 +92,8 @@ class SchoolWeeklyStatusFactory(django_factory.DjangoModelFactory): connectivity_type = fuzzy.FuzzyText(length=64) connectivity_speed = fuzzy.FuzzyInteger(1, 1000000) - connectivity_latency = fuzzy.FuzzyInteger(1, 100) + connectivity_upload_speed = fuzzy.FuzzyInteger(1, 1000000) + connectivity_latency = fuzzy.FuzzyFloat(0.0, 1000.0) class Meta: model = SchoolWeeklyStatus diff --git a/proco/connection_statistics/tests/test_aggregates.py b/proco/connection_statistics/tests/test_aggregates.py index a2112a2..85b678b 100644 --- a/proco/connection_statistics/tests/test_aggregates.py +++ b/proco/connection_statistics/tests/test_aggregates.py @@ -28,14 +28,18 @@ class AggregateConnectivityDataTestCase(TestCase): - databases = ['default',] + databases = ['default', ] @classmethod def setUpTestData(cls): cls.country = CountryFactory() cls.school = SchoolFactory(country=cls.country) - RealTimeConnectivityFactory(school=cls.school, connectivity_speed=4000000) - RealTimeConnectivityFactory(school=cls.school, connectivity_speed=6000000) + cls.today_datetime = datetime.now(tz=timezone.utc) + + RealTimeConnectivityFactory(school=cls.school, connectivity_speed=4000000, created=cls.today_datetime, + live_data_source='DAILY_CHECK_APP_MLAB') + RealTimeConnectivityFactory(school=cls.school, connectivity_speed=6000000, created=cls.today_datetime, + live_data_source='DAILY_CHECK_APP_MLAB') def test_aggregate_real_time_data_to_school_daily_status(self): aggregate_real_time_data_to_school_daily_status(self.country, timezone.now().date()) @@ -49,28 +53,33 @@ def test_aggregate_real_time_data_to_country_daily_status(self): self.assertEqual(CountryDailyStatus.objects.first().connectivity_speed, 5000000) def test_aggregate_real_time_yesterday_data(self): - yesterday_status = SchoolDailyStatusFactory(school=self.school, date=timezone.now().date() - timedelta(days=1)) + yesterday = timezone.now() - timedelta(days=1) + yesterday_status = SchoolDailyStatusFactory(school=self.school, date=yesterday.date(), + live_data_source='DAILY_CHECK_APP_MLAB') RealTimeConnectivityFactory( - school=self.school, connectivity_speed=3000000, created=timezone.now() - timedelta(days=1), + school=self.school, connectivity_speed=3000000, created=yesterday, + live_data_source='DAILY_CHECK_APP_MLAB' ) - finalize_previous_day_data(None, self.country.id, timezone.now().date()) + finalize_previous_day_data(None, self.country.id, yesterday.date()) yesterday_status.refresh_from_db() - # self.assertEqual(yesterday_status.connectivity_speed, 3000000) - # self.assertEqual(self.country.daily_status.get(date=yesterday_status.date).connectivity_speed, 3000000) + self.assertEqual(yesterday_status.connectivity_speed, 3000000) + self.assertEqual(self.country.daily_status.get(date=yesterday_status.date).connectivity_speed, 3000000) def test_aggregate_school_daily_to_country_daily(self): today = datetime.now().date() - SchoolDailyStatusFactory(school__country=self.country, connectivity_speed=4000000, date=today) - SchoolDailyStatusFactory(school__country=self.country, connectivity_speed=6000000, date=today) + SchoolDailyStatusFactory(school__country=self.country, connectivity_speed=4000000, date=today, + live_data_source='DAILY_CHECK_APP_MLAB') + SchoolDailyStatusFactory(school__country=self.country, connectivity_speed=6000000, date=today, + live_data_source='DAILY_CHECK_APP_MLAB') aggregate_school_daily_to_country_daily(self.country, timezone.now().date()) self.assertEqual(CountryDailyStatus.objects.get(country=self.country, date=today).connectivity_speed, 5000000) def test_aggregate_country_daily_status_to_country_weekly_status(self): today = datetime.now().date() - CountryDailyStatusFactory(country=self.country, date=today) + CountryDailyStatusFactory(country=self.country, date=today, live_data_source='DAILY_CHECK_APP_MLAB') SchoolWeeklyStatusFactory( school__country=self.country, connectivity=True, connectivity_speed=4000000, @@ -97,23 +106,29 @@ def test_aggregate_country_daily_status_to_country_weekly_status(self): CountryWeeklyStatus.COVERAGE_TYPES_AVAILABILITY.coverage_availability) def test_aggregate_school_daily_status_to_school_weekly_status(self): - today = datetime.now().date() - SchoolDailyStatusFactory(school=self.school, connectivity_speed=4000000, date=today - timedelta(days=1)) - SchoolDailyStatusFactory(school=self.school, connectivity_speed=6000000, date=today) + date = datetime.now().date() - timedelta(days=6) + monday_date = date - timedelta(days=date.weekday()) + tuesday_date = monday_date + timedelta(days=1) + + SchoolDailyStatusFactory(school=self.school, connectivity_speed=4000000, date=monday_date, + live_data_source='DAILY_CHECK_APP_MLAB') + SchoolDailyStatusFactory(school=self.school, connectivity_speed=6000000, date=tuesday_date, + live_data_source='DAILY_CHECK_APP_MLAB') self.school.last_weekly_status = None self.school.save() - aggregate_school_daily_status_to_school_weekly_status(self.country, today) + aggregate_school_daily_status_to_school_weekly_status(self.country, tuesday_date) self.school.refresh_from_db() self.assertNotEqual(self.school.last_weekly_status, None) self.assertEqual(SchoolWeeklyStatus.objects.count(), 1) - # self.assertEqual(SchoolWeeklyStatus.objects.last().connectivity_speed, 6000000) + self.assertEqual(SchoolWeeklyStatus.objects.last().connectivity_speed, 5000000) self.assertEqual(SchoolWeeklyStatus.objects.last().connectivity, True) def test_aggregate_school_daily_status_to_school_weekly_status_connectivity_unknown(self): # daily status is too old, so it wouldn't be involved into country calculations today = datetime.now().date() - SchoolDailyStatusFactory(school=self.school, connectivity_speed=None, date=today - timedelta(days=8)) + SchoolDailyStatusFactory(school=self.school, connectivity_speed=None, date=today - timedelta(days=8), + live_data_source='DAILY_CHECK_APP_MLAB') SchoolWeeklyStatusFactory( school=self.school, week=get_current_week(), year=get_current_year(), connectivity=None, ) @@ -124,7 +139,8 @@ def test_aggregate_school_daily_status_to_school_weekly_status_connectivity_unkn def test_aggregate_school_daily_status_to_school_weekly_status_connectivity_no(self): today = datetime.now().date() - SchoolDailyStatusFactory(school=self.school, connectivity_speed=None, date=today - timedelta(days=8)) + SchoolDailyStatusFactory(school=self.school, connectivity_speed=None, date=today - timedelta(days=8), + live_data_source='DAILY_CHECK_APP_MLAB') SchoolWeeklyStatusFactory( school=self.school, week=get_current_week(), year=get_current_year(), connectivity=False, ) diff --git a/proco/connection_statistics/tests/test_api.py b/proco/connection_statistics/tests/test_api.py index b8834d6..6febc03 100755 --- a/proco/connection_statistics/tests/test_api.py +++ b/proco/connection_statistics/tests/test_api.py @@ -342,7 +342,7 @@ def test_school_coverage_stat_for_connectivity_status_when_country_weekly_status self.assertEqual(response.data[2]['statistics']['connectivity_status'], 'unknown') -class CountryConnectivityStatApiTestCase(TestAPIViewSetMixin, TestCase): +class ConnectivityStatApiTestCase(TestAPIViewSetMixin, TestCase): @classmethod def setUpTestData(cls): @@ -387,7 +387,7 @@ def setUp(self): def test_country_download_connectivity_stat(self): """ - test_school_download_connectivity_stat_school_list + test_country_download_connectivity_stat Positive test case for weekly data. Expected: HTTP_200_OK - List of data for all 3 schools @@ -414,10 +414,10 @@ def test_country_download_connectivity_stat(self): self.assertIn('graph_data', response_data) self.assertIn('real_time_connected_schools', response_data) - def test_admin1_download_connectivity_stat(self): + def test_admin1_download_connectivity_stat_monthly(self): """ - test_admin1_download_connectivity_stat - Positive test case for weekly data. + test_admin1_download_connectivity_stat_monthly + Positive test case for monthly data. Expected: HTTP_200_OK - List of data for all 3 schools """ @@ -556,6 +556,95 @@ def test_country_download_connectivity_stat_for_one_school_graph_data_when_schoo for data in graph_data: self.assertIsNone(data['value']) + def test_country_download_connectivity_stat_for_global_benchmark(self): + """ + test_country_download_connectivity_stat + Positive test case for country weekly data. + + Expected: HTTP_200_OK - List of data for given country id + """ + date = Week(self.school_weekly_one.year, self.school_weekly_one.week).monday() + start_date = date - timedelta(days=1) + end_date = start_date + timedelta(days=6) + + url, _, view = statistics_url((), { + 'country_id': self.country.id, + 'start_date': format_date(start_date), + 'end_date': format_date(end_date), + 'is_weekly': 'true', + 'benchmark': 'global' + }, view_name='country-connectivity-stat') + + response = self.forced_auth_req('get', url, view=view) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(type(response_data), dict) + + self.assertIn('live_avg', response_data) + self.assertIn('school_with_realtime_data', response_data) + self.assertIn('is_data_synced', response_data) + self.assertIn('graph_data', response_data) + self.assertIn('real_time_connected_schools', response_data) + + def test_country_download_connectivity_stat_for_invalid_date_range(self): + date = Week(2023, 56).monday() + start_date = date - timedelta(days=1) + end_date = start_date + timedelta(days=6) + + url, _, view = statistics_url((), { + 'country_id': self.country.id, + 'start_date': format_date(start_date), + 'end_date': format_date(end_date), + 'is_weekly': 'true', + 'benchmark': 'global' + }, view_name='country-connectivity-stat') + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_country_download_connectivity_stat_for_missing_country_id(self): + date = Week(self.school_weekly_one.year, self.school_weekly_one.week).monday() + start_date = date - timedelta(days=1) + end_date = start_date + timedelta(days=6) + + url, _, view = statistics_url((), { + 'start_date': format_date(start_date), + 'end_date': format_date(end_date), + 'is_weekly': 'true', + 'benchmark': 'global' + }, view_name='country-connectivity-stat') + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_country_download_connectivity_stat_for_national_benchmark(self): + date = Week(self.school_weekly_one.year, self.school_weekly_one.week).monday() + start_date = date - timedelta(days=1) + end_date = start_date + timedelta(days=6) + + url, _, view = statistics_url((), { + 'country_id': self.country.id, + 'start_date': format_date(start_date), + 'end_date': format_date(end_date), + 'is_weekly': 'true', + 'benchmark': 'national', + }, view_name='country-connectivity-stat') + + response = self.forced_auth_req('get', url, view=view) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(type(response_data), dict) + + self.assertIn('live_avg', response_data) + self.assertIn('school_with_realtime_data', response_data) + self.assertIn('is_data_synced', response_data) + self.assertIn('graph_data', response_data) + self.assertIn('real_time_connected_schools', response_data) + class SchoolConnectivityStatApiTestCase(TestAPIViewSetMixin, TestCase): @@ -944,232 +1033,6 @@ def test_school_latency_connectivity_stat_school_list(self): # self.assertEqual(response.data[2]['statistics']['connectivity_speed'], 0) -class CountryWeekStatsApiTestCase(TestAPIViewSetMixin, TestCase): - - @classmethod - def setUpTestData(cls): - cls.country_one = CountryFactory() - cls.country_two = CountryFactory() - - cls.stat_one = CountryWeeklyStatusFactory(country=cls.country_one) - cls.stat_two = CountryWeeklyStatusFactory(country=cls.country_two) - - cls.country_one_daily = CountryDailyStatusFactory(country=cls.country_one, - date=Week(cls.stat_one.year, cls.stat_one.week).monday()) - - cls.user = test_utilities.setup_admin_user_by_role() - - def setUp(self): - cache.clear() - super().setUp() - - def test_country_download_connectivity_stat(self): - """ - test_country_download_connectivity_stat - Positive test case for country weekly data. - - Expected: HTTP_200_OK - List of data for given country id - """ - date = Week(self.stat_one.year, self.stat_one.week).monday() - start_date = date - timedelta(days=1) - end_date = start_date + timedelta(days=6) - - url, _, view = statistics_url((), { - 'country_id': self.country_one.id, - 'start_date': format_date(start_date), - 'end_date': format_date(end_date), - 'is_weekly': 'true', - 'benchmark': 'global' - }, view_name='list-create-destroy-countryweeklystatus') - - response = self.forced_auth_req('get', url, user=self.user, view=view) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - response_data = response.data - self.assertEqual(type(response_data), dict) - - # self.assertIn('live_avg', response_data) - # self.assertIn('schools_total', response_data['results']) - # self.assertIn('school_with_realtime_data', response_data) - # self.assertIn('is_data_synced', response_data) - # self.assertIn('graph_data', response_data) - # self.assertIn('real_time_connected_schools', response_data) - - def test_country_download_connectivity_stat_data(self): - date = Week(self.stat_one.year, self.stat_one.week).monday() - start_date = date - timedelta(days=1) - end_date = start_date + timedelta(days=6) - - url, _, view = statistics_url((), { - 'country_id': self.country_one.id, - 'start_date': format_date(start_date), - 'end_date': format_date(end_date), - 'is_weekly': 'true', - 'benchmark': 'global' - }, view_name='list-create-destroy-countryweeklystatus') - - response = self.forced_auth_req('get', url, user=self.user, view=view) - self.assertEqual(response.status_code, status.HTTP_200_OK) - # self.assertEqual(response.data[0]['schools_total'], self.stat_one.schools_total) - # self.assertEqual(response.data[0]['school_with_realtime_data'], self.stat_one.schools_connected) - - def test_country_download_connectivity_stat_for_invalid_country_id(self): - date = Week(self.stat_one.year, self.stat_one.week).monday() - start_date = date - timedelta(days=1) - end_date = start_date + timedelta(days=6) - - url, _, view = statistics_url((), { - 'country_id': 123456, - 'start_date': format_date(start_date), - 'end_date': format_date(end_date), - 'is_weekly': 'true', - 'benchmark': 'global' - }, view_name='list-create-destroy-countryweeklystatus') - - response = self.forced_auth_req('get', url, user=self.user, view=view) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - def test_country_download_connectivity_stat_for_invalid_date_range(self): - date = Week(2023, 56).monday() - start_date = date - timedelta(days=1) - end_date = start_date + timedelta(days=6) - - url, _, view = statistics_url((), { - 'country_id': self.country_one.id, - 'start_date': format_date(start_date), - 'end_date': format_date(end_date), - 'is_weekly': 'true', - 'benchmark': 'global' - }, view_name='list-create-destroy-countryweeklystatus') - - response = self.forced_auth_req('get', url, user=self.user, view=view) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - def test_country_download_connectivity_stat_for_missing_country_id(self): - date = Week(self.stat_one.year, self.stat_one.week).monday() - start_date = date - timedelta(days=1) - end_date = start_date + timedelta(days=6) - - url, _, view = statistics_url((), { - 'start_date': format_date(start_date), - 'end_date': format_date(end_date), - 'is_weekly': 'true', - 'benchmark': 'global' - }, view_name='list-create-destroy-countryweeklystatus') - - response = self.forced_auth_req('get', url, user=self.user, view=view) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - def test_country_download_connectivity_stat_for_national_benchmark(self): - date = Week(self.stat_one.year, self.stat_one.week).monday() - start_date = date - timedelta(days=1) - end_date = start_date + timedelta(days=6) - - url, _, view = statistics_url((), { - 'country_id': self.country_one.id, - 'start_date': format_date(start_date), - 'end_date': format_date(end_date), - 'is_weekly': 'true', - 'benchmark': 'national', - }, view_name='list-create-destroy-countryweeklystatus') - - response = self.forced_auth_req('get', url, user=self.user, view=view) - self.assertEqual(response.status_code, status.HTTP_200_OK) - # self.assertEqual(response.data['schools_total'], self.stat_one.schools_total) - - # self.assertEqual(response.data['real_time_connected_schools']['good'], - # self.stat_one.schools_connectivity_good) - # self.assertEqual(response.data['real_time_connected_schools']['moderate'], - # self.stat_one.schools_connectivity_moderate) - # self.assertEqual(response.data['real_time_connected_schools']['no_internet'], - # self.stat_one.schools_connectivity_no) - # self.assertEqual(response.data['real_time_connected_schools']['unknown'], - # self.stat_one.schools_connectivity_unknown) - - def test_country_uptime_connectivity_stat(self): - date = Week(self.stat_one.year, self.stat_one.week).monday() - start_date = date - timedelta(days=1) - end_date = start_date + timedelta(days=6) - - url, _, view = statistics_url((), { - 'country_id': self.country_one.id, - 'start_date': format_date(start_date), - 'end_date': format_date(end_date), - 'is_weekly': 'true', - 'benchmark': 'global' - }, view_name='list-create-destroy-countryweeklystatus') - - response = self.forced_auth_req('get', url, user=self.user, view=view) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - # response_data = response.data - # self.assertIn('live_avg', response_data) - # self.assertIn('schools_total', response_data['results']) - # self.assertIn('school_with_realtime_data', response_data) - # self.assertIn('is_data_synced', response_data) - # self.assertIn('graph_data', response_data) - # self.assertIn('real_time_connected_schools', response_data) - - def test_country_download_connectivity_stat_monthly(self): - """ - test_country_download_connectivity_stat_monthly - Positive test case for country weekly data. - - Expected: HTTP_200_OK - List of data for given country id - """ - date = Week(self.stat_one.year, self.stat_one.week).monday() - start_date = get_first_date_of_month(date.year, date.month) - end_date = get_last_date_of_month(date.year, date.month) - - url, _, view = statistics_url((), { - 'country_id': self.country_one.id, - 'start_date': format_date(start_date), - 'end_date': format_date(end_date), - 'is_weekly': 'false', - 'benchmark': 'global' - }, view_name='list-create-destroy-countryweeklystatus') - - response = self.forced_auth_req('get', url, user=self.user, view=view) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - response_data = response.data - self.assertEqual(type(response_data), dict) - - # self.assertIn('live_avg', response_data) - # self.assertIn('schools_total', response_data['results']) - # self.assertIn('school_with_realtime_data', response_data) - # self.assertIn('is_data_synced', response_data) - # self.assertIn('graph_data', response_data) - # self.assertIn('real_time_connected_schools', response_data) - - def test_country_download_connectivity_stat_monthly_invalid_country_id(self): - """ - test_country_download_connectivity_stat - Positive test case for country weekly data. - - Expected: HTTP_200_OK - List of data for given country id - """ - date = Week(self.stat_one.year, self.stat_one.week).monday() - start_date = get_first_date_of_month(date.year, date.month) - end_date = get_last_date_of_month(date.year, date.month) - - url, _, view = statistics_url((), { - 'country_id': 123456, - 'start_date': format_date(start_date), - 'end_date': format_date(end_date), - 'is_weekly': 'false', - 'benchmark': 'global' - }, view_name='list-create-destroy-countryweeklystatus') - - response = self.forced_auth_req('get', url, user=self.user, view=view) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - class CountryCoverageStatsAPITestCase(TestAPIViewSetMixin, TestCase): @classmethod def setUpTestData(cls): @@ -1325,3 +1188,96 @@ def test_schools_latest_configurations(self): with self.assertNumQueries(0): self.forced_auth_req('get', url, view=view) + + +class CountrySummaryAPIViewSetAPITestCase(TestAPIViewSetMixin, TestCase): + databases = ['default', ] + base_view = 'connection_statistics:list-create-destroy-countryweeklystatus' + + @classmethod + def setUpTestData(cls): + cls.country_one = CountryFactory() + cls.country_two = CountryFactory() + + cls.stat_one = CountryWeeklyStatusFactory( + country=cls.country_one, + integration_status=CountryWeeklyStatus.REALTIME_MAPPED, + year=datetime.now().year - 1, + week=12, + schools_connectivity_no=1 + ) + cls.stat_two = CountryWeeklyStatusFactory( + country=cls.country_one, + integration_status=CountryWeeklyStatus.REALTIME_MAPPED, + year=datetime.now().year - 1, + week=13, + schools_connectivity_no=1 + ) + + cls.stat_three = CountryWeeklyStatusFactory( + country=cls.country_two, + integration_status=CountryWeeklyStatus.REALTIME_MAPPED, + year=datetime.now().year - 1, + week=12, + schools_connectivity_no=1 + ) + + cls.user = test_utilities.setup_admin_user_by_role() + + def setUp(self): + cache.clear() + super().setUp() + + def test_list(self): + url, _, view = statistics_url((), {}, view_name='list-create-destroy-countryweeklystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(type(response_data), dict) + # 3 records as we created manually in setup, 2 for each country with latest year and latest week + self.assertEqual(response_data['count'], 5) + self.assertEqual(len(response_data['results']), 5) + + def test_country_id_filter(self): + url, _, view = statistics_url((), {'country_id': self.country_one.id}, + view_name='list-create-destroy-countryweeklystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(type(response_data), dict) + # 2 records as we created manually in setup, 1 for country with latest year and latest week + self.assertEqual(response_data['count'], 3) + self.assertEqual(len(response_data['results']), 3) + + def test_year_week_filter(self): + url, _, view = statistics_url((), {'year': datetime.now().year - 1, 'week': 12}, + view_name='list-create-destroy-countryweeklystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(type(response_data), dict) + # 2 records as we created manually in setup + self.assertEqual(response_data['count'], 2) + self.assertEqual(len(response_data['results']), 2) + + # + # def test_search(self): + # pass + # + # def test_retrieve(self): + # pass + # + # def test_update(self): + # pass + # + # def test_delete(self): + # pass diff --git a/proco/custom_auth/templates/base.html b/proco/custom_auth/templates/base.html deleted file mode 100644 index a76450a..0000000 --- a/proco/custom_auth/templates/base.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - {% block title %}test_project{% endblock %} - - - - -{% block content %}{% endblock %} - - diff --git a/proco/custom_auth/templates/email/email_base.html b/proco/custom_auth/templates/email/email_base.html deleted file mode 100644 index 711c408..0000000 --- a/proco/custom_auth/templates/email/email_base.html +++ /dev/null @@ -1,77 +0,0 @@ -{% load static %} - -{% block html %} - - - - - - - - - - - - -
    - - - - - - - - - - - - -
    - - Logo -
    - {% block content %}{% endblock %} -
    -

    - - - -

    -
    - Copyright © {% now 'Y' %},
    - All rights reserved.
    - Terms of Service | - Privacy Policy | -
    -
    -
    - - -{% endblock %} From 360fb8fe2ece331aaaa4ba64f0517f6adb65b8e0 Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Fri, 19 Jul 2024 15:30:55 +0530 Subject: [PATCH 09/27] Added test cases --- proco/connection_statistics/tests/test_api.py | 894 +++++++++++++++++- .../populate_school_registration_data.py | 2 +- 2 files changed, 880 insertions(+), 16 deletions(-) diff --git a/proco/connection_statistics/tests/test_api.py b/proco/connection_statistics/tests/test_api.py index 6febc03..57fb0b2 100755 --- a/proco/connection_statistics/tests/test_api.py +++ b/proco/connection_statistics/tests/test_api.py @@ -2,12 +2,14 @@ from datetime import datetime, timedelta from django.core.cache import cache +from django.core.management import call_command from django.test import TestCase from django.urls import resolve, reverse from isoweek import Week from rest_framework import exceptions as rest_exceptions from rest_framework import status +from proco.accounts import models as accounts_models from proco.connection_statistics.models import CountryWeeklyStatus from proco.connection_statistics.tests.factories import ( CountryDailyStatusFactory, @@ -33,6 +35,17 @@ def statistics_url(url_params, query_param, view_name='global-stat'): return url, view, view_info +def accounts_url(url_params, query_param, view_name='list-or-create-api-keys'): + url = reverse('accounts:' + view_name, args=url_params) + view = resolve(url) + view_info = view.func + + if len(query_param) > 0: + query_params = '?' + '&'.join([key + '=' + str(val) for key, val in query_param.items()]) + url += query_params + return url, view, view_info + + class GlobalStatisticsApiTestCase(TestAPIViewSetMixin, TestCase): databases = ['default', ] @@ -1095,8 +1108,11 @@ def setUpTestData(cls): cls.school_two = SchoolFactory(country=cls.country_one) cls.school_three = SchoolFactory(country=cls.country_one) - cls.stat_one = SchoolDailyStatusFactory(school=cls.school_one) - cls.stat_two = SchoolDailyStatusFactory(school=cls.school_two) + cls.stat_one = SchoolDailyStatusFactory(school=cls.school_one, live_data_source='DAILY_CHECK_APP_MLAB') + cls.stat_two = SchoolDailyStatusFactory(school=cls.school_two, live_data_source='QOS') + + args = ['--delete_data_sources', '--update_data_sources', '--update_data_layers'] + call_command('load_system_data_layers', *args) def setUp(self): cache.clear() @@ -1132,6 +1148,25 @@ def test_country_with_schools_latest_configurations(self): with self.assertNumQueries(0): self.forced_auth_req('get', url, view=view) + def test_country_with_schools_latest_configurations_for_live_layer(self): + layer = accounts_models.DataLayer.objects.filter( + type=accounts_models.DataLayer.LAYER_TYPE_LIVE, + category=accounts_models.DataLayer.LAYER_CATEGORY_CONNECTIVITY, + status=accounts_models.DataLayer.LAYER_STATUS_PUBLISHED, + created_by__isnull=True, + ).first() + url, _, view = statistics_url((), {'country_id': self.country_one.id, 'layer_id': layer.id}, + view_name='get-latest-week-and-month') + + with self.assertNumQueries(6): + response = self.forced_auth_req('get', url, view=view) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertIn('week', response_data) + self.assertIn('month', response_data) + self.assertIn('years', response_data) + def test_country_without_schools_latest_configurations(self): url, _, view = statistics_url((), {'country_id': self.country_two.id}, view_name='get-latest-week-and-month') @@ -1192,7 +1227,6 @@ def test_schools_latest_configurations(self): class CountrySummaryAPIViewSetAPITestCase(TestAPIViewSetMixin, TestCase): databases = ['default', ] - base_view = 'connection_statistics:list-create-destroy-countryweeklystatus' @classmethod def setUpTestData(cls): @@ -1269,15 +1303,845 @@ def test_year_week_filter(self): self.assertEqual(response_data['count'], 2) self.assertEqual(len(response_data['results']), 2) - # - # def test_search(self): - # pass - # - # def test_retrieve(self): - # pass - # - # def test_update(self): - # pass - # - # def test_delete(self): - # pass + def test_search(self): + url, _, view = statistics_url((), {'search': self.country_one.name}, + view_name='list-create-destroy-countryweeklystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(type(response_data), dict) + # 2 records as we created manually in setup, 1 for country with latest year and latest week + self.assertEqual(response_data['count'], 3) + self.assertEqual(len(response_data['results']), 3) + + def test_retrieve(self): + url, view, view_info = statistics_url((self.stat_one.id,), {}, + view_name='update-retrieve-countryweeklystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view, view_info=view_info, ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(response_data['id'], self.stat_one.id) + self.assertEqual(response_data['connectivity_speed'], self.stat_one.connectivity_speed) + self.assertEqual(response_data['year'], self.stat_one.year) + self.assertEqual(response_data['week'], self.stat_one.week) + self.assertEqual(response_data['integration_status'], self.stat_one.integration_status) + + def test_retrieve_wrong_id(self): + url, view, view_info = statistics_url((1234546,), {}, + view_name='update-retrieve-countryweeklystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view, view_info=view_info, ) + + self.assertEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_update(self): + url, _, view = statistics_url((self.stat_two.id,), {}, + view_name='update-retrieve-countryweeklystatus') + put_response = self.forced_auth_req( + 'put', + url, + user=self.user, + data={ + "id": self.stat_two.id, + "created": self.stat_two.created, + "modified": self.stat_two.modified, + "connectivity_speed": self.stat_two.connectivity_speed, + "connectivity_upload_speed": self.stat_two.connectivity_upload_speed, + "year": self.stat_two.year, + "week": self.stat_two.week, + "date": self.stat_two.date, + "integration_status": CountryWeeklyStatus.STATIC_MAPPED, + "country": self.stat_two.country.id + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + def test_update_wrong_id(self): + url, _, view = statistics_url((123434567,), {}, + view_name='update-retrieve-countryweeklystatus') + put_response = self.forced_auth_req( + 'put', + url, + user=self.user, + data={ + "id": self.stat_two.id, + "created": self.stat_two.created, + "modified": self.stat_two.modified, + "connectivity_speed": self.stat_two.connectivity_speed, + "connectivity_upload_speed": self.stat_two.connectivity_upload_speed, + "year": self.stat_two.year, + "week": self.stat_two.week, + "date": self.stat_two.date, + "integration_status": CountryWeeklyStatus.STATIC_MAPPED, + "country": self.stat_two.country.id + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_update_invalid_data(self): + url, _, view = statistics_url((self.stat_two.id,), {}, + view_name='update-retrieve-countryweeklystatus') + put_response = self.forced_auth_req( + 'put', + url, + user=self.user, + data={ + "id": self.stat_two.id, + "created": self.stat_two.created, + "modified": self.stat_two.modified, + "connectivity_speed": self.stat_two.connectivity_speed, + "connectivity_upload_speed": self.stat_two.connectivity_upload_speed, + "year": self.stat_two.year, + "week": self.stat_two.week, + "date": self.stat_two.date, + "integration_status": 8, + "country": self.stat_two.country.id + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_delete(self): + url, _, view = statistics_url((), {}, view_name='list-create-destroy-countryweeklystatus') + + response = self.forced_auth_req( + 'delete', + url, + data={'id': [self.stat_two.id]}, + user=self.user, + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_delete_without_ids(self): + url, _, view = statistics_url((), {}, view_name='list-create-destroy-countryweeklystatus') + + response = self.forced_auth_req( + 'delete', + url, + user=self.user, + ) + self.assertEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_delete_wrong_ids(self): + url, _, view = statistics_url((), {}, view_name='list-create-destroy-countryweeklystatus') + + response = self.forced_auth_req( + 'delete', + url, + data={'id': [12345432]}, + user=self.user, + ) + self.assertEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) + + +class CountryDailyConnectivitySummaryAPIViewSetAPITestCase(TestAPIViewSetMixin, TestCase): + databases = ['default', ] + + @classmethod + def setUpTestData(cls): + cls.country_one = CountryFactory() + cls.country_two = CountryFactory() + + today = datetime.now().date() + + cls.stat_one = CountryDailyStatusFactory( + country=cls.country_one, + date=today, + live_data_source='DAILY_CHECK_APP_MLAB' + ) + cls.stat_two = CountryDailyStatusFactory( + country=cls.country_one, + date=today, + live_data_source='QOS' + ) + + cls.stat_three = CountryDailyStatusFactory( + country=cls.country_two, + date=today, + live_data_source='DAILY_CHECK_APP_MLAB' + ) + + cls.user = test_utilities.setup_admin_user_by_role() + + def setUp(self): + cache.clear() + super().setUp() + + def test_list(self): + url, _, view = statistics_url((), {}, view_name='list-create-destroy-countrydailystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(type(response_data), dict) + # 3 records as we created manually in setup + self.assertEqual(response_data['count'], 3) + self.assertEqual(len(response_data['results']), 3) + + def test_country_id_filter(self): + url, _, view = statistics_url((), {'country_id': self.country_one.id}, + view_name='list-create-destroy-countrydailystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(type(response_data), dict) + # 2 records as we created manually in setup + self.assertEqual(response_data['count'], 2) + self.assertEqual(len(response_data['results']), 2) + + def test_search(self): + url, _, view = statistics_url((), {'search': self.country_one.name}, + view_name='list-create-destroy-countrydailystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(type(response_data), dict) + # 2 records as we created manually in setup + self.assertEqual(response_data['count'], 2) + self.assertEqual(len(response_data['results']), 2) + + def test_retrieve(self): + url, view, view_info = statistics_url((self.stat_one.id,), {}, + view_name='update-retrieve-countrydailystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view, view_info=view_info, ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(response_data['id'], self.stat_one.id) + self.assertEqual(response_data['connectivity_speed'], self.stat_one.connectivity_speed) + self.assertEqual(response_data['date'], format_date(self.stat_one.date)) + + def test_retrieve_wrong_id(self): + url, view, view_info = statistics_url((1234546,), {}, + view_name='update-retrieve-countrydailystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view, view_info=view_info, ) + + self.assertEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_update(self): + url, _, view = statistics_url((self.stat_two.id,), {}, + view_name='update-retrieve-countrydailystatus') + put_response = self.forced_auth_req( + 'put', + url, + user=self.user, + data={ + "id": self.stat_two.id, + "created": self.stat_two.created, + "modified": self.stat_two.modified, + "connectivity_speed": 10000000, + "connectivity_upload_speed": self.stat_two.connectivity_upload_speed, + "date": self.stat_two.date, + "country": self.stat_two.country.id + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + def test_update_wrong_id(self): + url, _, view = statistics_url((123434567,), {}, + view_name='update-retrieve-countrydailystatus') + put_response = self.forced_auth_req( + 'put', + url, + user=self.user, + data={ + "id": self.stat_two.id, + "created": self.stat_two.created, + "modified": self.stat_two.modified, + "connectivity_speed": self.stat_two.connectivity_speed, + "connectivity_upload_speed": self.stat_two.connectivity_upload_speed, + "date": self.stat_two.date, + "country": self.stat_two.country.id + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_update_invalid_data(self): + url, _, view = statistics_url((self.stat_two.id,), {}, + view_name='update-retrieve-countrydailystatus') + put_response = self.forced_auth_req( + 'put', + url, + user=self.user, + data={ + "id": self.stat_two.id, + "created": self.stat_two.created, + "modified": self.stat_two.modified, + "connectivity_speed": 234.123, + "connectivity_upload_speed": self.stat_two.connectivity_upload_speed, + "date": self.stat_two.date, + "country": self.stat_two.country.id + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_delete(self): + url, _, view = statistics_url((), {}, view_name='list-create-destroy-countrydailystatus') + + response = self.forced_auth_req( + 'delete', + url, + data={'id': [self.stat_two.id]}, + user=self.user, + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_delete_without_ids(self): + url, _, view = statistics_url((), {}, view_name='list-create-destroy-countrydailystatus') + + response = self.forced_auth_req( + 'delete', + url, + user=self.user, + ) + self.assertEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_delete_wrong_ids(self): + url, _, view = statistics_url((), {}, view_name='list-create-destroy-countrydailystatus') + + response = self.forced_auth_req( + 'delete', + url, + data={'id': [12345432]}, + user=self.user, + ) + self.assertEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) + + +class SchoolSummaryAPIViewSetAPITestCase(TestAPIViewSetMixin, TestCase): + databases = ['default', ] + + @classmethod + def setUpTestData(cls): + cls.country = CountryFactory() + + cls.school_one = SchoolFactory(country=cls.country, location__country=cls.country, geopoint=None) + cls.school_two = SchoolFactory(country=cls.country, location__country=cls.country) + + cls.stat_one = SchoolWeeklyStatusFactory( + school=cls.school_one, + connectivity=True, + year=datetime.now().year - 1, + week=12, + ) + cls.stat_two = SchoolWeeklyStatusFactory( + school=cls.school_one, + connectivity=False, + year=datetime.now().year - 1, + week=13, + ) + cls.stat_three = SchoolWeeklyStatusFactory( + school=cls.school_two, + connectivity=True, + year=datetime.now().year - 1, + week=12, + ) + + cls.user = test_utilities.setup_admin_user_by_role() + + def setUp(self): + cache.clear() + super().setUp() + + def test_list(self): + url, _, view = statistics_url((), {}, view_name='list-create-destroy-schoolweeklystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(type(response_data), dict) + # 3 records as we created manually in setup + self.assertEqual(response_data['count'], 3) + self.assertEqual(len(response_data['results']), 3) + + def test_school_id_filter(self): + url, _, view = statistics_url((), {'school_id': self.school_one.id}, + view_name='list-create-destroy-schoolweeklystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(type(response_data), dict) + # 2 records as we created manually in setup + self.assertEqual(response_data['count'], 2) + self.assertEqual(len(response_data['results']), 2) + + def test_year_week_filter(self): + url, _, view = statistics_url((), {'year': datetime.now().year - 1, 'week': 12}, + view_name='list-create-destroy-schoolweeklystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(type(response_data), dict) + # 2 records as we created manually in setup + self.assertEqual(response_data['count'], 2) + self.assertEqual(len(response_data['results']), 2) + + def test_search(self): + url, _, view = statistics_url((), {'search': self.school_one.name}, + view_name='list-create-destroy-schoolweeklystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(type(response_data), dict) + # 2 records as we created manually in setup + self.assertEqual(response_data['count'], 2) + self.assertEqual(len(response_data['results']), 2) + + def test_retrieve(self): + url, view, view_info = statistics_url((self.stat_one.id,), {}, + view_name='update-retrieve-schoolweeklystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view, view_info=view_info, ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(response_data['id'], self.stat_one.id) + self.assertEqual(response_data['connectivity_speed'], self.stat_one.connectivity_speed) + self.assertEqual(response_data['year'], self.stat_one.year) + self.assertEqual(response_data['week'], self.stat_one.week) + + def test_retrieve_wrong_id(self): + url, view, view_info = statistics_url((1234546,), {}, + view_name='update-retrieve-schoolweeklystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view, view_info=view_info, ) + + self.assertEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_update(self): + url, _, view = statistics_url((self.stat_two.id,), {}, + view_name='update-retrieve-schoolweeklystatus') + put_response = self.forced_auth_req( + 'put', + url, + user=self.user, + data={ + "id": self.stat_two.id, + "created": self.stat_two.created, + "modified": self.stat_two.modified, + "connectivity_speed": self.stat_two.connectivity_speed, + "connectivity_upload_speed": self.stat_two.connectivity_upload_speed, + "year": self.stat_two.year, + "week": self.stat_two.week, + "date": self.stat_two.date, + "school": self.school_one.id + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + def test_update_wrong_id(self): + url, _, view = statistics_url((123434567,), {}, + view_name='update-retrieve-schoolweeklystatus') + put_response = self.forced_auth_req( + 'put', + url, + user=self.user, + data={ + "id": self.stat_two.id, + "created": self.stat_two.created, + "modified": self.stat_two.modified, + "connectivity_speed": self.stat_two.connectivity_speed, + "connectivity_upload_speed": self.stat_two.connectivity_upload_speed, + "year": self.stat_two.year, + "week": self.stat_two.week, + "date": self.stat_two.date, + "school": self.school_two.id + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_update_invalid_data(self): + url, _, view = statistics_url((self.stat_two.id,), {}, + view_name='update-retrieve-schoolweeklystatus') + put_response = self.forced_auth_req( + 'put', + url, + user=self.user, + data={ + "id": self.stat_two.id, + "created": self.stat_two.created, + "modified": self.stat_two.modified, + "connectivity_speed": self.stat_two.connectivity_speed, + "connectivity_upload_speed": self.stat_two.connectivity_upload_speed, + "year": self.stat_two.year, + "week": self.stat_two.week, + "date": self.stat_two.date, + 'coverage_type': '7g', + "school": self.school_one.id + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_delete(self): + url, _, view = statistics_url((), {}, view_name='list-create-destroy-schoolweeklystatus') + + response = self.forced_auth_req( + 'delete', + url, + data={'id': [self.stat_two.id]}, + user=self.user, + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_delete_without_ids(self): + url, _, view = statistics_url((), {}, view_name='list-create-destroy-schoolweeklystatus') + + response = self.forced_auth_req( + 'delete', + url, + user=self.user, + ) + self.assertEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_delete_wrong_ids(self): + url, _, view = statistics_url((), {}, view_name='list-create-destroy-schoolweeklystatus') + + response = self.forced_auth_req( + 'delete', + url, + data={'id': [12345432]}, + user=self.user, + ) + self.assertEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) + + +class SchoolDailyConnectivitySummaryAPIViewSetAPITestCase(TestAPIViewSetMixin, TestCase): + databases = ['default', ] + + @classmethod + def setUpTestData(cls): + cls.country = CountryFactory() + + cls.school_one = SchoolFactory(country=cls.country, location__country=cls.country, geopoint=None) + cls.school_two = SchoolFactory(country=cls.country, location__country=cls.country) + + today = datetime.now().date() + + cls.stat_one = SchoolDailyStatusFactory( + school=cls.school_one, + date=today, + live_data_source='DAILY_CHECK_APP_MLAB' + ) + cls.stat_two = SchoolDailyStatusFactory( + school=cls.school_one, + date=today, + live_data_source='QOS' + ) + + cls.stat_three = SchoolDailyStatusFactory( + school=cls.school_two, + date=today, + live_data_source='DAILY_CHECK_APP_MLAB' + ) + + cls.user = test_utilities.setup_admin_user_by_role() + + def setUp(self): + cache.clear() + super().setUp() + + def test_list(self): + url, _, view = statistics_url((), {}, view_name='list-create-destroy-schooldailystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(type(response_data), dict) + # 3 records as we created manually in setup + self.assertEqual(response_data['count'], 3) + self.assertEqual(len(response_data['results']), 3) + + def test_school_id_filter(self): + url, _, view = statistics_url((), {'school_id': self.school_one.id}, + view_name='list-create-destroy-schooldailystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(type(response_data), dict) + # 2 records as we created manually in setup + self.assertEqual(response_data['count'], 2) + self.assertEqual(len(response_data['results']), 2) + + def test_search(self): + url, _, view = statistics_url((), {'search': self.school_one.name}, + view_name='list-create-destroy-schooldailystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(type(response_data), dict) + # 2 records as we created manually in setup + self.assertEqual(response_data['count'], 2) + self.assertEqual(len(response_data['results']), 2) + + def test_retrieve(self): + url, view, view_info = statistics_url((self.stat_one.id,), {}, + view_name='update-retrieve-schooldailystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view, view_info=view_info, ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(response_data['id'], self.stat_one.id) + self.assertEqual(response_data['connectivity_speed'], self.stat_one.connectivity_speed) + self.assertEqual(response_data['date'], format_date(self.stat_one.date)) + + def test_retrieve_wrong_id(self): + url, view, view_info = statistics_url((1234546,), {}, + view_name='update-retrieve-schooldailystatus') + + response = self.forced_auth_req('get', url, user=self.user, view=view, view_info=view_info, ) + + self.assertEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_update(self): + url, _, view = statistics_url((self.stat_two.id,), {}, + view_name='update-retrieve-schooldailystatus') + put_response = self.forced_auth_req( + 'put', + url, + user=self.user, + data={ + "id": self.stat_two.id, + "created": self.stat_two.created, + "modified": self.stat_two.modified, + "connectivity_speed": 10000000, + "connectivity_upload_speed": self.stat_two.connectivity_upload_speed, + "date": self.stat_two.date, + "school": self.stat_two.school.id + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + def test_update_wrong_id(self): + url, _, view = statistics_url((123434567,), {}, + view_name='update-retrieve-schooldailystatus') + put_response = self.forced_auth_req( + 'put', + url, + user=self.user, + data={ + "id": self.stat_two.id, + "created": self.stat_two.created, + "modified": self.stat_two.modified, + "connectivity_speed": self.stat_two.connectivity_speed, + "connectivity_upload_speed": self.stat_two.connectivity_upload_speed, + "date": self.stat_two.date, + "school": self.stat_two.school.id + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_update_invalid_data(self): + url, _, view = statistics_url((self.stat_two.id,), {}, + view_name='update-retrieve-schooldailystatus') + put_response = self.forced_auth_req( + 'put', + url, + user=self.user, + data={ + "id": self.stat_two.id, + "created": self.stat_two.created, + "modified": self.stat_two.modified, + "connectivity_speed": 234.123, + "connectivity_upload_speed": self.stat_two.connectivity_upload_speed, + "date": self.stat_two.date, + "school": self.stat_two.school.id + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_delete(self): + url, _, view = statistics_url((), {}, view_name='list-create-destroy-schooldailystatus') + + response = self.forced_auth_req( + 'delete', + url, + data={'id': [self.stat_two.id]}, + user=self.user, + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_delete_without_ids(self): + url, _, view = statistics_url((), {}, view_name='list-create-destroy-schooldailystatus') + + response = self.forced_auth_req( + 'delete', + url, + user=self.user, + ) + self.assertEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_delete_wrong_ids(self): + url, _, view = statistics_url((), {}, view_name='list-create-destroy-schooldailystatus') + + response = self.forced_auth_req( + 'delete', + url, + data={'id': [12345432]}, + user=self.user, + ) + self.assertEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) + + +class TimePlayerApiTestCase(TestAPIViewSetMixin, TestCase): + databases = ['default', ] + + @classmethod + def setUpTestData(cls): + args = ['--delete_data_sources', '--update_data_sources', '--update_data_layers'] + call_command('load_system_data_layers', *args) + + cls.admin_user = test_utilities.setup_admin_user_by_role() + cls.read_only_user = test_utilities.setup_read_only_user_by_role() + + def test_get_invalid_layer_id(self): + url, _, view = statistics_url((), { + 'layer_id': 123, + 'country_id': 123, + }, view_name='get-time-player-data') + + response = self.forced_auth_req('get', url, _, view=view) + + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_for_live_layer(self): + pcdc_data_source = accounts_models.DataSource.objects.filter( + data_source_type=accounts_models.DataSource.DATA_SOURCE_TYPE_DAILY_CHECK_APP, + ).first() + + url, _, view = accounts_url((), {}, view_name='list-or-create-data-layers') + + response = self.forced_auth_req( + 'post', + url, + user=self.admin_user, + view=view, + data={ + 'icon': '', + 'name': 'Test data layer 3', + 'description': 'Test data layer 3 description', + 'version': '1.0.0', + 'type': accounts_models.DataLayer.LAYER_TYPE_LIVE, + 'data_sources_list': [pcdc_data_source.id, ], + 'data_source_column': pcdc_data_source.column_config[0], + 'global_benchmark': { + 'value': '20000000', + 'unit': 'bps', + 'convert_unit': 'mbps' + }, + 'is_reverse': False, + 'legend_configs': { + 'good': { + 'values': [], + 'labels': 'Good' + }, + 'moderate': { + 'values': [], + 'labels': 'Moderate' + }, + 'bad': { + 'values': [], + 'labels': 'Bad' + }, + 'unknown': { + 'values': [], + 'labels': 'Unknown' + } + } + } + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response_data = response.data + + layer_id = response_data['id'] + + url, _, view = accounts_url((layer_id,), {}, + view_name='update-or-delete-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_READY_TO_PUBLISH, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, _, view = accounts_url((layer_id,), {}, + view_name='publish-data-layer') + + put_response = self.forced_auth_req( + 'put', + url, + user=self.admin_user, + data={ + 'status': accounts_models.DataLayer.LAYER_STATUS_PUBLISHED, + } + ) + + self.assertEqual(put_response.status_code, status.HTTP_200_OK) + + url, _, view = statistics_url((), { + 'layer_id': layer_id, + 'country_id': 123, + 'z': '2', + 'x': '1', + 'y': '2.mvt', + }, view_name='get-time-player-data') + + response = self.forced_auth_req('get', url, _, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/proco/core/management/commands/populate_school_registration_data.py b/proco/core/management/commands/populate_school_registration_data.py index e492ff8..c51a6e7 100644 --- a/proco/core/management/commands/populate_school_registration_data.py +++ b/proco/core/management/commands/populate_school_registration_data.py @@ -60,7 +60,7 @@ def populate_school_registration_data(country_id, school_id): query = """ SELECT DISTINCT dailystat.school_id as school_id, true rt_registered, - FIRST_VALUE(dailystat.created) OVER (PARTITION BY dailystat.school_id ORDER BY dailystat.created) + FIRST_VALUE(dailystat.date) OVER (PARTITION BY dailystat.school_id ORDER BY dailystat.date) rt_registration_date, NULL rt_source FROM connection_statistics_schooldailystatus dailystat From c272b6bf9dd5c736c6f38c78acb9cafe1df9f677 Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Fri, 19 Jul 2024 15:31:49 +0530 Subject: [PATCH 10/27] Added test cases --- azure-pipeline-dev.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipeline-dev.yml b/azure-pipeline-dev.yml index 257f6bd..3482435 100644 --- a/azure-pipeline-dev.yml +++ b/azure-pipeline-dev.yml @@ -32,7 +32,7 @@ steps: - task: Bash@3 displayName: Unit Tests - condition: eq(variables['Build.SourceBranch'], 'refs/heads/develop') +# condition: eq(variables['Build.SourceBranch'], 'refs/heads/develop') inputs: targetType: 'inline' script: | @@ -54,7 +54,7 @@ steps: # Sonar Scan - task: Bash@3 displayName: Sonar Scan - condition: eq(variables['Build.SourceBranch'], 'refs/heads/develop') +# condition: eq(variables['Build.SourceBranch'], 'refs/heads/develop') inputs: targetType: 'inline' script: | From 7e5d9b9025ce6706109d0bf19ca3b91c592b8e98 Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Fri, 19 Jul 2024 19:57:58 +0530 Subject: [PATCH 11/27] Added more test cases --- proco/about_us/api.py | 12 +- proco/accounts/api.py | 20 +- proco/accounts/serializers.py | 23 +-- proco/locations/api.py | 169 +-------------- proco/locations/api_urls.py | 5 +- proco/locations/serializers.py | 5 +- proco/locations/tests/factories.py | 1 + proco/locations/tests/test_api.py | 318 ++++++++++++++++++++++------- 8 files changed, 272 insertions(+), 281 deletions(-) diff --git a/proco/about_us/api.py b/proco/about_us/api.py index 88e8882..7b0ec99 100644 --- a/proco/about_us/api.py +++ b/proco/about_us/api.py @@ -2,7 +2,6 @@ from rest_framework import status as rest_status from rest_framework import viewsets -from rest_framework.decorators import permission_classes from rest_framework.filters import SearchFilter from rest_framework.response import Response @@ -73,7 +72,7 @@ def destroy(self, request): if SliderImage.objects.filter(id__in=request.data['id']).exists(): image_data = SliderImage.objects.filter(id__in=request.data['id']) if image_data: - action_log(request, image_data, 3, "image deleted", self.model, + action_log(request, image_data, 3, 'image deleted', self.model, field_name='title') image_data.delete() return Response(status=rest_status.HTTP_200_OK, data=delete_succ_mess) @@ -92,7 +91,6 @@ class AboutUsAPIView(viewsets.ViewSet): ) def list(self, request, *args, **kwargs): - # queryset = super(AboutUsAPIView, self).get_queryset() try: about_us = AboutUs.objects.filter(status=True).values() list_data = [] @@ -146,8 +144,10 @@ def update(self, request, *args, **kwargs): try: list_data = [] change_data = [] + about_us_obj_list = [] for item in request.data: about_us = AboutUs.objects.get(pk=item['id']) + about_us_obj_list.append(about_us) data = AboutUsSerializer(instance=about_us, data=item, partial=True, context={'request': request}) @@ -162,11 +162,11 @@ def update(self, request, *args, **kwargs): if len(change_data) > 0: change_data = list(itertools.chain(*change_data)) change_data = list(set(change_data)) - remove_item = ["created", "modified"] + remove_item = ['created', 'modified'] for field in remove_item: if field in change_data: change_data.remove(field) - action_log(request, [about_us], 2, change_data, self.model, field_name='title') + action_log(request, about_us_obj_list, 2, change_data, self.model, field_name='title') return Response(list_data) except AboutUs.DoesNotExist: return Response(data=error_mess, status=rest_status.HTTP_502_BAD_GATEWAY) @@ -177,7 +177,7 @@ def destroy(self, request, *args, **kwargs): if AboutUs.objects.filter(id__in=request.data['id']).exists(): about_us = AboutUs.objects.filter(id__in=request.data['id']) if about_us: - action_log(request, about_us, 3, "About Us deleted", self.model, + action_log(request, about_us, 3, 'About Us deleted', self.model, field_name='title') about_us.delete() return Response(status=rest_status.HTTP_200_OK, data=delete_succ_mess) diff --git a/proco/accounts/api.py b/proco/accounts/api.py index b1a6aed..6613590 100644 --- a/proco/accounts/api.py +++ b/proco/accounts/api.py @@ -669,7 +669,6 @@ def get_static_map_query(self, kwargs): for title, values_and_label in legend_configs.items(): values = list(filter(lambda val: val if not core_utilities.is_blank_string(val) else None, values_and_label.get('values', []))) - # label = values_and_label.get('labels', title).strip() if len(values) > 0: is_sql_value = 'SQL:' in values[0] @@ -725,7 +724,6 @@ def get(self, request, *args, **kwargs): } global_benchmark = data_layer_instance.global_benchmark.get('value') - # benchmark_unit = data_layer_instance.global_benchmark.get('unit') benchmark_base = str(parameter_col.get('base_benchmark', 1)) data_layer_qs = statistics_models.SchoolDailyStatus.objects.all() @@ -1223,7 +1221,6 @@ def generate_graph_data(self): graph_data_per_school[school_id] = copy.deepcopy(graph_data) all_positive_speeds_per_school[school_id] = [] - # Update the graph_data with actual values if they exist # Update the graph_data with actual values if they exist for daily_avg_data in data: school_id = str(daily_avg_data['id']) @@ -1246,7 +1243,6 @@ def generate_graph_data(self): all_positive_speeds = [] # Update the graph_data with actual values if they exist - # Update the graph_data with actual values if they exist for daily_avg_data in data: formatted_date = date_utilities.format_date(daily_avg_data['date']) for entry in graph_data: @@ -1264,7 +1260,8 @@ def generate_graph_data(self): def get_static_info_query(self, query_labels): query = """ SELECT {label_case_statements} - COUNT(DISTINCT CASE WHEN sws."{col_name}" IS NOT NULL THEN "schools_school"."id" ELSE NULL END) AS "total_schools" + COUNT(DISTINCT CASE WHEN sws."{col_name}" IS NOT NULL THEN "schools_school"."id" ELSE NULL END) + AS "total_schools" FROM "schools_school" {school_weekly_join} LEFT JOIN connection_statistics_schoolweeklystatus sws ON "schools_school"."last_weekly_status_id" = sws."id" @@ -1533,17 +1530,16 @@ def get(self, request, *args, **kwargs): if len(info_panel_school_list) > 0: for info_panel_school in info_panel_school_list: - school_id = info_panel_school['id'] info_panel_school['geopoint'] = json.loads(info_panel_school['geopoint']) info_panel_school['statistics'] = list(filter( - lambda s: s['school_id'] == school_id, statistics))[-1] + lambda s: s['school_id'] == info_panel_school['id'], statistics))[-1] - live_avg = (round(sum(positive_speeds[str(school_id)]) / len( - positive_speeds[str(school_id)]), 2) if len( - positive_speeds[str(school_id)]) > 0 else 0) + live_avg = (round(sum(positive_speeds[str(info_panel_school['id'])]) / len( + positive_speeds[str(info_panel_school['id'])]), 2) if len( + positive_speeds[str(info_panel_school['id'])]) > 0 else 0) info_panel_school['live_avg'] = live_avg - info_panel_school['graph_data'] = graph_data[str(school_id)] + info_panel_school['graph_data'] = graph_data[str(info_panel_school['id'])] response = info_panel_school_list else: @@ -1883,7 +1879,6 @@ def get_static_map_query(self, env, request): for title, values_and_label in legend_configs.items(): values = list(filter(lambda val: val if not core_utilities.is_blank_string(val) else None, values_and_label.get('values', []))) - # label = values_and_label.get('labels', title).strip() if len(values) > 0: is_sql_value = 'SQL:' in values[0] @@ -1941,7 +1936,6 @@ def get(self, request, *args, **kwargs): parameter_col = data_sources.first().data_source_column parameter_column_name = str(parameter_col['name']) - # parameter_column_unit = str(parameter_col.get('unit', '')).lower() base_benchmark = str(parameter_col.get('base_benchmark', 1)) self.update_kwargs(country_ids, data_layer_instance) diff --git a/proco/accounts/serializers.py b/proco/accounts/serializers.py index 55739bb..28d3b3f 100644 --- a/proco/accounts/serializers.py +++ b/proco/accounts/serializers.py @@ -847,7 +847,6 @@ def to_internal_value(self, data): else account_config.standard_email_template_name # For SMS notification elif message_type == accounts_models.Message.TYPE_SMS: - # TODO: Implement SMS notification pass return super().to_internal_value(data) @@ -865,7 +864,6 @@ def create(self, validated_data): # if its SMS notification, send the SMS over <> service and update the status elif message_type == accounts_models.Message.TYPE_SMS: - # TODO: Implement SMS notification pass # if it's just an application level notification, create the message instance @@ -1338,8 +1336,7 @@ def validate_data_source_column(self, data_source_column): applicable_cols = [col['name'] for col in data_source_column_configs if col.get('is_parameter', False)] if isinstance(data_source_column, list): - # TODO: Remove this dependency - # message='Can not have 2 column for 1 data source' + # Remove this dependency raise accounts_exceptions.InvalidDataSourceColumnForDataLayerError() elif isinstance(data_source_column, dict) and len(data_source_column) > 0: name = data_source_column.get('name') @@ -1534,14 +1531,11 @@ def _validate_user(self, instance): # else: check # 3. If necessary, the Publisher can edit the details and then approve the changes to the data layer. - if request_user == instance.created_by: - return True - user_is_publisher = len(get_user_emails_for_permissions( - [auth_models.RolePermission.CAN_PUBLISH_DATA_LAYER], - ids_to_filter=[request_user.id] - )) > 0 - - if user_is_publisher: + if ( + request_user == instance.created_by or + len(get_user_emails_for_permissions([auth_models.RolePermission.CAN_PUBLISH_DATA_LAYER], + ids_to_filter=[request_user.id])) > 0 + ): return True raise accounts_exceptions.InvalidUserOnDataLayerUpdateError() @@ -1637,14 +1631,11 @@ def validate_status(self, status): status == accounts_models.DataLayer.LAYER_STATUS_PUBLISHED and self.instance.status in [accounts_models.DataLayer.LAYER_STATUS_READY_TO_PUBLISH, accounts_models.DataLayer.LAYER_STATUS_DISABLED] - ): - return status - elif ( + ) or ( status == accounts_models.DataLayer.LAYER_STATUS_DISABLED and self.instance.status == accounts_models.DataLayer.LAYER_STATUS_PUBLISHED ): return status - raise accounts_exceptions.InvalidDataLayerStatusUpdateError() def update(self, instance, validated_data): diff --git a/proco/locations/api.py b/proco/locations/api.py index 5bb19bf..70d1fd5 100644 --- a/proco/locations/api.py +++ b/proco/locations/api.py @@ -179,7 +179,7 @@ def get_queryset(self, ids=None): def create(self, request, *args, **kwargs): try: data = CountryUpdateRetriveSerializer(data=request.data) - if data.is_valid(): + if data.is_valid(raise_exception=True): data.save() action_log(request, [data.data], 1, '', self.model, field_name='name') update_country_related_cache.delay(data.data.get('code')) @@ -195,11 +195,9 @@ def update(self, request, *args, **kwargs): copy_request_data = copy.deepcopy(request.data) if copy_request_data.get('flag') is None: copy_request_data['flag'] = country.flag - # if copy_request_data.get('map_preview') is None: - # copy_request_data['map_preview'] = country.map_preview data = CountryUpdateRetriveSerializer(instance=country, data=copy_request_data) - if data.is_valid(): + if data.is_valid(raise_exception=True): change_message = changed_fields(country, copy_request_data) action_log(request, [country], 2, change_message, self.model, field_name='name') data.save() @@ -213,9 +211,8 @@ def update(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs): request_user = core_utilities.get_current_user(request=request) if 'pk' in kwargs: - response = super().destroy(request, *args, **kwargs) - instance = self.get_object() + response = super().destroy(request, *args, **kwargs) accounts_models.DataLayerCountryRelationship.objects.filter(country=instance).update( deleted=core_utilities.get_current_datetime_object(), @@ -301,8 +298,6 @@ class DownloadCountriesViewSet(BaseModelViewSet, core_mixins.DownloadAPIDataToCS 'id': ['exact', 'in'], } - # permit_list_expands = ['last_weekly_status'] - def list(self, request, *args, **kwargs): if core_utilities.is_export(request, self.action): return self.list_export(request, *args, **kwargs) @@ -312,162 +307,6 @@ def list(self, request, *args, **kwargs): return super().list(request, *args, **kwargs) -class SearchListAPIView(BaseModelViewSet): - """ - SearchListAPIView - This class is used to list all Download APIs. - Inherits: ListAPIView - """ - model = School - serializer_class = SearchListSerializer - - base_auth_permissions = ( - permissions.AllowAny, - ) - - filter_backends = ( - DjangoFilterBackend, - # NullsAlwaysLastOrderingFilter, - ) - - fields = ( - 'id', 'name', - 'admin1_id', 'admin1_name', 'admin1_description', 'admin2_id', 'admin2_name', 'admin2_description', - 'country_id', 'country_name', 'country_code', - ) - - ordering_fields = ('country_name', 'admin1_name', 'admin2_name', 'name') - - filterset_fields = { - 'id': ['exact', 'in'], - 'name': ['iexact', 'contains'], - 'country_id': ['exact', 'in'], - 'country__name': ['iexact', 'contains'], - 'admin1_id': ['exact', 'in'], - 'admin2_id': ['exact', 'in'], - } - - def get_queryset(self): - queryset = self.model.objects.all() - - qry_fields = self.fields - query_param_fields = self.request.query_params.get('fields') - # Select only requested fields - if query_param_fields: - qry_fields = query_param_fields.split(',') - - qry_ordering = self.ordering_fields - query_param_ordering = self.request.query_params.get('ordering') - # Apply the ordering as asked - if query_param_ordering: - qry_ordering = query_param_ordering.split(',') - - qs = queryset.annotate( - country_name=F('country__name'), - country_code=F('country__code'), - admin1_name=F('admin1__name'), - admin1_description=F('admin1__description'), - admin2_name=F('admin2__name'), - admin2_description=F('admin2__description'), - ).filter(country_id=222).values(*qry_fields).order_by(*qry_ordering).distinct(*qry_fields) - - return self.apply_queryset_filters(qs) - - def list(self, request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) - - page_size = request.query_params.get('page_size') - if page_size: - page = self.paginate_queryset(queryset) - if page is not None: - return self.get_paginated_response(page) - return Response(list(queryset)) - - def finalize_response(self, request, response, *args, **kwargs): - data = OrderedDict() - response = super().finalize_response(request, response, *args, **kwargs) - if response.status_code == rest_status.HTTP_200_OK: - response_data = [] - if isinstance(response.data, dict): - # If its paginated request then dict - response_data = response.data.get('results', []) - elif isinstance(response.data, list): - # If its normal request then lis - response_data = response.data - - for resp_data in response_data: - country_id = resp_data.get('country_id') - if country_id: - # Country ID exists - country_data = data.get(country_id, { - 'country_id': country_id, - 'country_name': resp_data.get('country_name'), - 'country_code': resp_data.get('country_code'), - 'admin1_data': OrderedDict(), - }) - - if 'admin1_name' in resp_data: - admin1_name = 'Unknown' if core_utilities.is_blank_string(resp_data['admin1_name']) \ - else resp_data['admin1_name'] - # If admin 1 name exist in response - admin1_data = country_data.get('admin1_data') - admin1_name_data = admin1_data.get(admin1_name, { - 'admin1_name': admin1_name, - 'admin1_id': resp_data.get('admin1_id'), - 'admin1_description': resp_data.get('admin1_description'), - 'admin2_data': OrderedDict(), - }) - - if 'admin2_name' in resp_data: - admin2_name = 'Unknown' if core_utilities.is_blank_string(resp_data['admin2_name']) \ - else resp_data['admin2_name'] - # If admin 2 name exist in response - admin2_data = admin1_name_data.get('admin2_data') - admin2_name_data = admin2_data.get(admin2_name, { - 'admin2_name': admin2_name, - 'admin2_id': resp_data.get('admin2_id'), - 'admin2_description': resp_data.get('admin2_description'), - 'school_data': OrderedDict(), - }) - - if 'id' in resp_data: - school_id = resp_data['id'] - # If admin 2 name exist in response - school_data = admin2_name_data.get('school_data') - - school_id_data = school_data.get(school_id, { - 'id': school_id, - 'name': resp_data.get('name', 'Unknown'), - }) - - school_data[school_id] = school_id_data - admin2_name_data['school_data'] = school_data - else: - pass - - admin2_data[admin2_name] = admin2_name_data - else: - # No admin2 name in response - pass - - admin1_data[admin1_name] = admin1_name_data - country_data['admin1_data'] = admin1_data - else: - # No admin1 name in response - pass - - data[country_id] = country_data - else: - # No country ID in response - pass - - if isinstance(response.data, list): - response.data = OrderedDict() - - response.data['results'] = data - return response - - class CountrySearchStatListAPIView(CachedListMixin, ListAPIView): """ CountrySearchStatListAPIView @@ -766,7 +605,7 @@ def index_search(self, request, *args, **kwargs): query_type=self.get_query_type, ) - logger.debug('Total Documents Matching Query:', results.get_count()) + logger.debug('Total Documents Matching Query: {}'.format(results.get_count())) return results diff --git a/proco/locations/api_urls.py b/proco/locations/api_urls.py index 05ac801..5c26ae8 100644 --- a/proco/locations/api_urls.py +++ b/proco/locations/api_urls.py @@ -14,10 +14,6 @@ 'get': 'list', }), name='download-countries'), - # DB Table based searching - path('search/', api.SearchListAPIView.as_view({ - 'get': 'list', - }), name='search-countries-schools'), # DB table based listing only for Country, Admin1 and Admin2 path('search-countries/', api.CountrySearchStatListAPIView.as_view(), name='search-countries-admin-schools'), # Cognitive Search Index based searching for Schools @@ -31,6 +27,7 @@ path('country//', api.CountryDataViewSet.as_view({ 'get': 'retrieve', 'put': 'update', + 'delete': 'destroy', }), name='update-retrieve-country'), path('country-admin-metadata/', api.CountryAdminMetadataViewSet.as_view({ diff --git a/proco/locations/serializers.py b/proco/locations/serializers.py index f647ee5..bee3790 100644 --- a/proco/locations/serializers.py +++ b/proco/locations/serializers.py @@ -26,6 +26,7 @@ logger = logging.getLogger('gigamaps.' + __name__) + class ExpandCountryAdminMetadataSerializer(FlexFieldsModelSerializer): """ ExpandCountryAdminMetadataSerializer @@ -208,7 +209,8 @@ def create(self, validated_data): api_key__valid_to__gte=core_utilities.get_current_datetime_object().date(), deleted__isnull=True, ).exists(): - logger.debug('Warning: api key for country ({0}) already exists.'.format(country_instance.iso3_format)) + logger.debug( + 'Warning: api key for country ({0}) already exists.'.format(country_instance.iso3_format)) else: country_api_key_relationship_obj.update(deleted=None, last_modified_by=request_user) logger.info('Api key restored.') @@ -467,7 +469,6 @@ class Meta: class CountryCSVSerializer(CountryStatusSerializer, DownloadSerializerMixin): class Meta(CountryStatusSerializer.Meta): - report_fields = OrderedDict([ ('id', 'ID'), ('name', 'Name'), diff --git a/proco/locations/tests/factories.py b/proco/locations/tests/factories.py index 749774d..588c5da 100644 --- a/proco/locations/tests/factories.py +++ b/proco/locations/tests/factories.py @@ -10,6 +10,7 @@ class CountryFactory(django_factory.DjangoModelFactory): name = fuzzy.FuzzyText(length=20) code = fuzzy.FuzzyText(length=20) + iso3_format = fuzzy.FuzzyText(length=20) description = fuzzy.FuzzyText(length=40) data_source = fuzzy.FuzzyText(length=40) diff --git a/proco/locations/tests/test_api.py b/proco/locations/tests/test_api.py index 39ddcac..10f917e 100644 --- a/proco/locations/tests/test_api.py +++ b/proco/locations/tests/test_api.py @@ -1,20 +1,27 @@ -import random -import string - from django.contrib.gis.geos import GEOSGeometry from django.core.cache import cache from django.test import TestCase -from django.urls import reverse +from django.urls import resolve, reverse from rest_framework import status from proco.connection_statistics.tests.factories import CountryWeeklyStatusFactory -from proco.custom_auth.tests import test_utils as auth_test_utilities -from proco.locations.models import Country +from proco.custom_auth.tests import test_utils as test_utilities from proco.locations.tests.factories import Admin1Factory, CountryFactory from proco.schools.tests.factories import SchoolFactory from proco.utils.tests import TestAPIViewSetMixin +def locations_url(url_params, query_param, view_name='countries-list'): + url = reverse('locations:' + view_name, args=url_params) + view = resolve(url) + view_info = view.func + + if len(query_param) > 0: + query_params = '?' + '&'.join([key + '=' + str(val) for key, val in query_param.items()]) + url += query_params + return url, view, view_info + + class CountryApiTestCase(TestAPIViewSetMixin, TestCase): base_view = 'locations:countries' @@ -25,6 +32,7 @@ def get_detail_args(self, instance): def setUpTestData(cls): cls.country_one = CountryFactory() cls.country_two = CountryFactory() + cls.country_three = CountryFactory() cls.admin1_one = Admin1Factory(country=cls.country_one) @@ -38,12 +46,47 @@ def setUp(self): super().setUp() def test_countries_list(self): - with self.assertNumQueries(3): + with self.assertNumQueries(4): response = self._test_list( - user=None, expected_objects=[self.country_one, self.country_two], + user=None, expected_objects=[self.country_one, self.country_two, self.country_three], ) self.assertIn('integration_status', response.data[0]) + def test_list_countries_with_schools(self): + url, _, view = locations_url((), {'has_schools': 'true'}) + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + self.assertIn('integration_status', response.data[0]) + + def test_list_countries_without_schools(self): + url, _, view = locations_url((), {'has_schools': 'false'}) + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 2) + self.assertIn('integration_status', response.data[0]) + + def test_list_countries_with_school_master_records(self): + url, _, view = locations_url((), {'has_school_master_records': 'true'}) + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 3) + + def test_list_countries_without_school_master_records(self): + url, _, view = locations_url((), {'has_school_master_records': 'false'}) + + response = self.forced_auth_req('get', url, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 3) + self.assertIn('integration_status', response.data[0]) + def test_country_detail(self): with self.assertNumQueries(4): response = self._test_retrieve( @@ -52,14 +95,14 @@ def test_country_detail(self): self.assertIn('statistics', response.data) def test_country_list_cached(self): - with self.assertNumQueries(3): + with self.assertNumQueries(4): self._test_list( - user=None, expected_objects=[self.country_one, self.country_two], + user=None, expected_objects=[self.country_one, self.country_two, self.country_three], ) with self.assertNumQueries(0): self._test_list( - user=None, expected_objects=[self.country_one, self.country_two], + user=None, expected_objects=[self.country_one, self.country_two, self.country_three], ) @@ -102,74 +145,199 @@ def test_empty_countries_hidden(self): # self.assertCountEqual([r['id'] for r in response.data], [self.country_one.id, self.country_two.id]) -class CountryDataTestCase(TestAPIViewSetMixin, TestCase): - base_view = 'locations:' - databases = {'default', } +class CountryDataViewSetTestCase(TestAPIViewSetMixin, TestCase): + databases = ['default', ] + + @classmethod + def setUpTestData(cls): + cls.country_one = CountryFactory() + cls.country_two = CountryFactory() + cls.country_three = CountryFactory() + + cls.admin1_one = Admin1Factory(country=cls.country_one) + + cls.school_one = SchoolFactory(country=cls.country_one, location__country=cls.country_one, + admin1=cls.admin1_one) + cls.school_two = SchoolFactory(country=cls.country_one, location__country=cls.country_one, + admin1=cls.admin1_one) + + cls.school_three = SchoolFactory(country=cls.country_two, location__country=cls.country_two, admin1=None) + + cls.user = test_utilities.setup_admin_user_by_role() def setUp(self): - self.user = auth_test_utilities.setup_admin_user_by_role() - - self.data = { - 'name': ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)), - 'code': ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(2)), - 'last_weekly_status_id': 2091, - 'flag': 'images/7962e7d2-ea1f-4571-a031-bb830fd575c6.png' - } - - self.country_id = Country.objects.create(**self.data).id - self.delete_data = {'id': [self.country_id]} - - self.country_one = CountryFactory() - return super().setUp() - - # def test_create(self): - # self.data = { - # "name": ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)), - # "code": ''.join(random.choice(string.ascii_uppercase) for _ in range(2)), - # "last_weekly_status_id": 2091, - # "benchmark_metadata": {} - # } - # headers = {'Content-Type': 'multipart/form-data'} - # - # response = self.forced_auth_req( - # 'post', - # reverse(self.base_view + "list-create-destroy-country"), - # data=self.data, - # headers=headers, - # user=self.user) - # - # self.assertEqual(response.status_code, status.HTTP_200_OK) - # self.assertNotEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) - # - # def test_update(self): - # self.data = { - # "name": ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)), - # "code": ''.join(random.choice(string.ascii_uppercase) for _ in range(2)), - # "last_weekly_status_id": 2091, - # "benchmark_metadata": {} - # } - # self.country_one = CountryFactory() - # import json - # from django.core import serializers - # - # tmp_json = serializers.serialize("json", self.country_one[0]) - # tmp_obj = json.loads(tmp_json) - # - # response = self.forced_auth_req( - # 'put', - # reverse(self.base_view + "update-retrieve-country", args=(self.country_id,)), - # data=tmp_obj, - # user=self.user, - # ) - # self.assertEqual(response.status_code, status.HTTP_200_OK) - # self.assertNotEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) - - def test_destroy(self): + cache.clear() + super().setUp() + + def test_list(self): + url, _, view = locations_url((), {}, view_name='list-create-destroy-country') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + # 3 records as we created manually in setup + self.assertEqual(response_data['count'], 3) + self.assertEqual(len(response_data['results']), 3) + + def test_country_id_filter(self): + url, _, view = locations_url((), {'id': self.country_one.id}, + view_name='list-create-destroy-country') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(response_data['count'], 1) + self.assertEqual(len(response_data['results']), 1) + + def test_search(self): + url, _, view = locations_url((), {'search': self.country_one.name}, + view_name='list-create-destroy-country') + + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(response_data['count'], 1) + self.assertEqual(len(response_data['results']), 1) + + def test_retrieve(self): + url, view, view_info = locations_url((self.country_one.id,), {}, + view_name='update-retrieve-country') + + response = self.forced_auth_req('get', url, user=self.user, view=view, view_info=view_info, ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + self.assertEqual(response_data['id'], self.country_one.id) + self.assertEqual(response_data['name'], self.country_one.name) + + def test_retrieve_wrong_id(self): + url, view, view_info = locations_url((1234546,), {}, + view_name='update-retrieve-country') + + response = self.forced_auth_req('get', url, user=self.user, view=view, view_info=view_info, ) + + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_update_wrong_id(self): + url, view, view_info = locations_url((self.country_one.id,), {}, + view_name='update-retrieve-country') + response = self.forced_auth_req('get', url, user=self.user, view=view, view_info=view_info, ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + + url, _, view = locations_url((123434567,), {}, view_name='update-retrieve-country') + put_response = self.forced_auth_req( + 'put', + url, + user=self.user, + data=response_data + ) + + self.assertEqual(put_response.status_code, status.HTTP_502_BAD_GATEWAY) + + def test_update_invalid_data(self): + url, view, view_info = locations_url((self.country_one.id,), {}, + view_name='update-retrieve-country') + response = self.forced_auth_req('get', url, user=self.user, view=view, view_info=view_info, ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response_data = response.data + response_data['date_of_join'] = '2024-13-01' + response_data['flag'] = b'abd' + put_response = self.forced_auth_req( + 'put', + url, + user=self.user, + data=response_data + ) + + self.assertEqual(put_response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_delete(self): + url, _, view = locations_url((), {}, view_name='list-create-destroy-country') + + response = self.forced_auth_req( + 'delete', + url, + data={'id': [self.country_three.id]}, + user=self.user, + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_delete_single_country(self): + url, view, view_info = locations_url((self.country_three.id, ), {}, view_name='update-retrieve-country') + + response = self.forced_auth_req( + 'delete', + url, + user=self.user, + view=view, view_info=view_info, + ) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + def test_delete_without_ids(self): + url, _, view = locations_url((), {}, view_name='list-create-destroy-country') + response = self.forced_auth_req( 'delete', - reverse(self.base_view + 'list-create-destroy-country'), - data=self.delete_data, + url, + user=self.user, + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_delete_wrong_ids(self): + url, _, view = locations_url((), {}, view_name='list-create-destroy-country') + + response = self.forced_auth_req( + 'delete', + url, + data={'id': [12345432]}, + user=self.user, + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_download_country_data_without_api_key(self): + url, view, view_info = locations_url((), { + 'page': '1', + 'page_size': '10', + 'ordering': 'name', + }, view_name='download-countries') + + response = self.forced_auth_req( + 'get', + url, user=self.user, + view_info=view_info, + view=view, + request_format='text/csv' ) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_list_searchable_details_from_db(self): + url, _, view = locations_url((), {}, view_name='search-countries-admin-schools') + + with self.assertNumQueries(1): + response = self.forced_auth_req('get', url, user=self.user, view=view) + self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertNotEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) + + response_data = response.data + # 2 records as we created manually in setup and only 2 countries has schools + self.assertEqual(len(response_data), 2) + + with self.assertNumQueries(0): + response = self.forced_auth_req('get', url, user=self.user, view=view) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + From a9b2babbbe08547cb4652ffccaf4952d8811f65f Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Fri, 19 Jul 2024 20:46:50 +0530 Subject: [PATCH 12/27] Added bare exception class --- proco/accounts/serializers.py | 7 ------- proco/core/exceptions.py | 4 ++++ proco/core/utils.py | 34 +++++++++++++--------------------- proco/schools/tasks.py | 6 +++--- 4 files changed, 20 insertions(+), 31 deletions(-) diff --git a/proco/accounts/serializers.py b/proco/accounts/serializers.py index 28d3b3f..6d8ab22 100644 --- a/proco/accounts/serializers.py +++ b/proco/accounts/serializers.py @@ -845,9 +845,6 @@ def to_internal_value(self, data): data['template'] = data['template'] \ if 'template' in data \ else account_config.standard_email_template_name - # For SMS notification - elif message_type == accounts_models.Message.TYPE_SMS: - pass return super().to_internal_value(data) def create(self, validated_data): @@ -862,10 +859,6 @@ def create(self, validated_data): if isinstance(response, int): validated_data['is_sent'] = True - # if its SMS notification, send the SMS over <> service and update the status - elif message_type == accounts_models.Message.TYPE_SMS: - pass - # if it's just an application level notification, create the message instance else: validated_data['is_sent'] = True diff --git a/proco/core/exceptions.py b/proco/core/exceptions.py index c23a826..5ba99ac 100644 --- a/proco/core/exceptions.py +++ b/proco/core/exceptions.py @@ -169,3 +169,7 @@ class InvalidAPIKeyError(BaseInvalidValidationError): message = _('Invalid API Key provided') code = 'invalid_api_key_provided' + + +class BareException(Exception): + pass diff --git a/proco/core/utils.py b/proco/core/utils.py index a6ff3f7..7c0cdf7 100644 --- a/proco/core/utils.py +++ b/proco/core/utils.py @@ -1,9 +1,8 @@ import gc -import hashlib import locale -import random -import re import logging +import re +import secrets from decimal import Decimal import pytz @@ -11,16 +10,18 @@ from django.utils import timezone from proco.core.config import app_config as core_config +from proco.core.exceptions import BareException logger = logging.getLogger('gigamaps.' + __name__) + def get_timezone_converted_value(value, tz=settings.TIME_ZONE): """ get_timezone_converted_value Method to convert the timezone of the datetime field value :param tz: timezone :param value: DateTime instance - :return: DateTime instance + :return: """ response_timezone = pytz.timezone(tz) return value.astimezone(response_timezone) @@ -90,11 +91,10 @@ def is_blank_string(val): """Check if the given string is empty.""" if val is None: return True - elif type(val) != str: - return False - else: + elif isinstance(val, str): attr = val.strip().lower() return len(attr) == 0 + return False def sanitize_str(val): @@ -116,16 +116,8 @@ def normalize_str(val): def get_random_string(length=264, allowed_chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._*#'): """ Return a securely generated random string. - - The default length of 12 with the a-z, A-Z, 0-9 character set returns - a 71-bit value. log_2((26+26+10)^12) =~ 71 bits """ - random.seed( - hashlib.sha256( - ('%s%s%s' % (random.getstate(), get_current_datetime_object, settings.SECRET_KEY)).encode() - ).digest() - ) - return ''.join(random.choice(allowed_chars) for i in range(length)) + return ''.join(secrets.choice(allowed_chars) for _ in range(length)) def format_decimal_data(value): @@ -158,7 +150,7 @@ def convert_to_int(val, default=0, orig=False): """Parse to integer value from string. In case of failure to do so, assign default value.""" try: return int(val) - except: + except BareException: if orig: return val else: @@ -169,7 +161,7 @@ def convert_to_float(val, default=0, orig=False): """Parse to float value from string. In case of failure to do so, assign default value.""" try: return float(val) - except: + except BareException: if orig: return val else: @@ -185,7 +177,7 @@ def get_footer_copyright(): def get_random_choice(choices): """ Accepts a list of choices and return the randomly chosen choice. """ - return random.choice(choices) + return secrets.choice(choices) def get_support_email(): @@ -250,7 +242,7 @@ def column_normalize(data_df, valid_columns=None): :param data_df: data frame :param valid_columns: list - list of supported normalized column names, if empty/None keep all columns otherwise remove missing columns from data frame - :return: data frame + :return: """ _columns = dict() _to_delete = [] @@ -285,7 +277,7 @@ def bulk_create_or_update(records, model, unique_fields, batch_size=1000): # This is where we delegate our records to our split lists: # - if the record already exists in the DB (the 'id' primary key), add it to the update list. - # - Otherwise, add it to the create list. + # - Otherwise, add it to the creation list. [ records_to_update.append(record) if record['id'] is not None diff --git a/proco/schools/tasks.py b/proco/schools/tasks.py index a6a8827..2738832 100644 --- a/proco/schools/tasks.py +++ b/proco/schools/tasks.py @@ -1,7 +1,7 @@ +import logging import random import traceback import uuid -import logging from collections import Counter from copy import copy from random import randint # noqa @@ -25,6 +25,7 @@ logger = logging.getLogger('gigamaps.' + __name__) + class FailedImportError(Exception): pass @@ -145,7 +146,7 @@ def update_school_records(): SchoolWeekly or CountryWeekly tables. """ task_key = 'update_school_records_status_{current_time}'.format( - current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H')) + current_time=format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H')) task_id = current_task.request.id or str(uuid.uuid4()) task_instance = background_task_utilities.task_on_start( @@ -157,4 +158,3 @@ def update_school_records(): background_task_utilities.task_on_complete(task_instance) else: logger.debug('Found running job with "{0}" name so skipping current iteration.'.format(task_key)) - From 204e3e074022075ca1bc398a7b22cf222fa7857c Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Mon, 22 Jul 2024 12:13:14 +0530 Subject: [PATCH 13/27] Fixed few code smells --- proco/core/mixins.py | 5 --- proco/data_sources/exceptions.py | 1 - proco/data_sources/tasks.py | 20 ++++------ proco/data_sources/utils.py | 11 ++---- proco/locations/api.py | 3 +- proco/locations/filters.py | 19 ---------- proco/locations/serializers.py | 65 +------------------------------- proco/schools/api.py | 2 +- proco/taskapp/__init__.py | 1 - 9 files changed, 14 insertions(+), 113 deletions(-) delete mode 100644 proco/locations/filters.py diff --git a/proco/core/mixins.py b/proco/core/mixins.py index 021207a..c2e2694 100644 --- a/proco/core/mixins.py +++ b/proco/core/mixins.py @@ -61,12 +61,7 @@ def perform_pre_checks(self, request, *args, **kwargs): if core_utilities.is_blank_string(api_key): raise core_exceptions.RequiredAPIKeyFilterError() - # api = headers.get('Api') - # if core_utilities.is_blank_string(api): - # raise core_exceptions.RequiredAPIFilterError() - valid_api_key = accounts_models.APIKey.objects.filter( - # api=api, user=request.user, # Check if API key is created by the current user api_key=api_key, # Check the API key in database table status=accounts_models.APIKey.APPROVED, # API Key must be APPROVED to enable the download/documentation diff --git a/proco/data_sources/exceptions.py b/proco/data_sources/exceptions.py index 9dad954..d614623 100644 --- a/proco/data_sources/exceptions.py +++ b/proco/data_sources/exceptions.py @@ -33,7 +33,6 @@ def __init__(self, **extra): class InvalidSchoolMasterDataRowStatusAtUpdateError(BaseInvalidValidationError): message = _('Invalid School Master Data row status at update: DB status: "{old}", Requested status: "{new}"') - # description = _('"PUBLISHED" school master data row can not be updated.') code = 'invalid_school_master_data_row_status' diff --git a/proco/data_sources/tasks.py b/proco/data_sources/tasks.py index b36ac16..df89c97 100644 --- a/proco/data_sources/tasks.py +++ b/proco/data_sources/tasks.py @@ -40,7 +40,7 @@ def finalize_task(): return 'Done' -def load_data_from_school_master_apis(*args, country_iso3_format=None): +def load_data_from_school_master_apis(country_iso3_format=None): """ Background task which handles School Master Data source changes from APIs to PROCO DB @@ -213,7 +213,7 @@ def handle_published_school_master_data_row(published_row=None, country_ids=None task_id, task_key, task_description) if task_instance: - logger.debug('Not found running job: {}'.format(task_key)) + logger.debug('Not found running job for published rows handler task: {}'.format(task_key)) updated_school_ids = [] new_published_records = sources_models.SchoolMasterData.objects.filter( status=sources_models.SchoolMasterData.ROW_STATUS_PUBLISHED, is_read=False, @@ -226,10 +226,8 @@ def handle_published_school_master_data_row(published_row=None, country_ids=None new_published_records = new_published_records.filter(country_id__in=country_ids) task_instance.info('Total published records to update: {}'.format(new_published_records.count())) - count = 0 for data_chunk in core_utilities.queryset_iterator(new_published_records, chunk_size=100, print_msg=False): - count += 1 for row in data_chunk: try: environment = row.school_area_type.lower() if not core_utilities.is_blank_string( @@ -252,7 +250,7 @@ def handle_published_school_master_data_row(published_row=None, country_ids=None layer_name=CountryAdminMetadata.LAYER_NAME_ADMIN2, ).first() - school, created = School.objects.update_or_create( + school, _ = School.objects.update_or_create( giga_id_school=row.school_id_giga, country=row.country, defaults={ @@ -440,7 +438,7 @@ def handle_deleted_school_master_data_row(deleted_row=None, country_ids=None): task_instance = background_task_utilities.task_on_start(task_id, task_key, task_description) if task_instance: - logger.debug('Not found running job: {}'.format(task_key)) + logger.debug('Not found running job for deleted rows handler: {}'.format(task_key)) new_deleted_records = sources_models.SchoolMasterData.objects.filter( status=sources_models.SchoolMasterData.ROW_STATUS_DELETED_PUBLISHED, is_read=False, @@ -498,7 +496,7 @@ def email_reminder_to_editor_and_publisher_for_review_waiting_records(): task_id, task_key, 'Send reminder email to Editor and Publisher to review the school master rows') if task_instance: - logger.debug('Not found running job: {}'.format(task_key)) + logger.debug('Not found running job for reminder email task: {}'.format(task_key)) ds_settings = settings.DATA_SOURCE_CONFIG.get('SCHOOL_MASTER') review_grace_period = core_utilities.convert_to_int(ds_settings['REVIEW_GRACE_PERIOD_IN_HRS'], default='48') @@ -642,7 +640,7 @@ def cleanup_school_master_rows(): task_instance = background_task_utilities.task_on_start(task_id, task_key, 'Cleanup school master rows') if task_instance: - logger.debug('Not found running job: {}'.format(task_key)) + logger.debug('Not found running job for school master cleanup task: {}'.format(task_key)) # Delete all the old records where more than 1 record are in DRAFT/UPDATED_IN_DRAFT or # ROW_STATUS_DRAFT_LOCKED/ROW_STATUS_UPDATED_IN_DRAFT_LOCKED for same School GIGA ID rows_with_more_than_1_record_in_draft = sources_models.SchoolMasterData.objects.filter( @@ -709,7 +707,7 @@ def update_static_data(*args, country_iso3_format=None): task_id, task_key, 'Sync Static Data from School Master sources', check_previous=True) if task_instance: - logger.debug('Not found running job: {}'.format(task_key)) + logger.debug('Not found running job for static data pull handler: {}'.format(task_key)) load_data_from_school_master_apis(country_iso3_format=country_iso3_format) task_instance.info('Completed the load data from School Master API call') cleanup_school_master_rows.s() @@ -773,8 +771,6 @@ def update_live_data(*args, today=True): chain( load_data_from_daily_check_app_api.s(), load_data_from_qos_apis.s(), - # load_data_from_unicef_db.s(), - Need to check with Brian for deletion - # load_brasil_daily_statistics.s(), - QoS chord( group([ finalize_previous_day_data.s(country_id, yesterday_date) @@ -800,7 +796,7 @@ def clean_old_live_data(): task_instance = background_task_utilities.task_on_start(task_id, task_key, 'Clean live data older than 30 days') if task_instance: - logger.debug('Not found running job: {}'.format(task_key)) + logger.debug('Not found running job for live data cleanup handler: {}'.format(task_key)) older_then_date = current_datetime - timedelta(days=30) logger.debug('Deleting all the rows from "RealTimeConnectivity" Data Table which is older than: {0}'.format( diff --git a/proco/data_sources/utils.py b/proco/data_sources/utils.py index 89c6137..99b6065 100644 --- a/proco/data_sources/utils.py +++ b/proco/data_sources/utils.py @@ -213,7 +213,6 @@ def sync_school_master_data(profile_file, share_name, schema_name, table_name, c table_meta_data = delta_sharing.get_table_metadata(table_url) logger.debug('Table Metadata: {0}'.format(table_meta_data.__dict__)) - # loaded_data_df = delta_sharing.load_as_pandas(table_url, None, table_current_version) loaded_data_df = delta_sharing.load_table_changes_as_pandas( table_url, table_last_data_version, @@ -405,9 +404,6 @@ def load_daily_check_app_data_source_response_to_model(model, request_configs): has_more_data = False else: for data in response_data: - # created_at = data.pop('created_at', None) - # if created_at: - # data['timestamp'] = created_at insert_entries.append(model(**data)) if len(insert_entries) >= 5000: @@ -645,7 +641,6 @@ def load_qos_data_source_response_to_model(): version_list = list(range(table_last_data_version + 1, table_current_version + 1)) for version in version_list: - # loaded_data_df = delta_sharing.load_as_pandas(table_url, None, version, None) loaded_data_df = delta_sharing.load_table_changes_as_pandas( table_url, version, @@ -689,9 +684,9 @@ def load_qos_data_source_response_to_model(): ).first() if not school: - logger.warning('School with Giga ID ({0}) not found in PROCO DB. ' - 'Hence skipping the load for current school.'.format( - row['school_id_giga'])) + logger.warning( + 'School with Giga ID ({0}) not found in PROCO DB. ' + 'Hence skipping the load for current school.'.format(row['school_id_giga'])) continue row['school'] = school diff --git a/proco/locations/api.py b/proco/locations/api.py index 70d1fd5..f0d37ce 100644 --- a/proco/locations/api.py +++ b/proco/locations/api.py @@ -9,7 +9,7 @@ from azure.search.documents.indexes.models import SearchFieldDataType from django.conf import settings from django.core.exceptions import ValidationError -from django.db.models import Case, Count, F, IntegerField, Value, When +from django.db.models import Case, Count, IntegerField, Value, When from django.db.models.functions.text import Lower from django.shortcuts import get_object_or_404 from django.utils.decorators import method_decorator @@ -41,7 +41,6 @@ DetailCountrySerializer, ExpandCountryAdminMetadataSerializer, ListCountrySerializer, - SearchListSerializer, ) from proco.schools.models import School from proco.utils.cache import cache_manager diff --git a/proco/locations/filters.py b/proco/locations/filters.py deleted file mode 100644 index 3ea8a5e..0000000 --- a/proco/locations/filters.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.contrib.admin import SimpleListFilter -from django.utils.translation import ugettext_lazy as _ - -from proco.locations.models import Country - - -class CountryFilterList(SimpleListFilter): - title = _('Country') - parameter_name = 'country_id' - - def lookups(self, request, model_admin): - return Country.objects.defer('geometry', 'geometry_simplified').values_list('id', 'name') - - def queryset(self, request, queryset): - return queryset.filter(**{self.parameter_name: self.value()}) if self.value() else queryset - - -class SchoolCountryFilterList(CountryFilterList): - parameter_name = 'school__country_id' diff --git a/proco/locations/serializers.py b/proco/locations/serializers.py index bee3790..17db5a2 100644 --- a/proco/locations/serializers.py +++ b/proco/locations/serializers.py @@ -1,5 +1,5 @@ -import re import logging +import re from collections import OrderedDict from django.db.models.functions.text import Lower @@ -278,15 +278,6 @@ def to_representation(self, instance): return super().to_representation(instance) -# class BoundaryListCountrySerializer(serializers.ModelSerializer): -# class Meta: -# model = Country -# fields = ( -# 'id', 'code', 'geometry_simplified', -# ) -# read_only_fields = fields - - class ListCountrySerializer(BaseCountrySerializer): integration_status = serializers.SerializerMethodField() schools_with_data_percentage = serializers.SerializerMethodField() @@ -412,8 +403,6 @@ class Meta: 'schools_connectivity_good', 'integration_status', 'avg_distance_school', - # 'created', - # 'modified', 'schools_with_data_percentage', 'connectivity_speed', 'connectivity_latency', @@ -431,79 +420,27 @@ class Meta: class CountryStatusSerializer(FlexFieldsModelSerializer): - # map_preview = serializers.SerializerMethodField() - # - # benchmark_metadata = serializers.JSONField() - class Meta: model = Country read_only_fields = fields = ( 'id', - # 'created', - # 'modified', 'name', - # 'code', 'iso3_format', - # 'flag', - # 'map_preview', - # 'description', - # 'data_source', - # 'date_of_join', - # 'date_schools_mapped', - # 'last_weekly_status_id', - # 'benchmark_metadata', ) expandable_fields = { 'last_weekly_status': (ExpandCountryWeeklyStatusSerializer, {'source': 'last_weekly_status'}), } - # def get_map_preview(self, instance): - # if not instance.map_preview: - # return '' - # - # request = self.context.get('request') - # photo_url = instance.map_preview.url - # return request.build_absolute_uri(photo_url) - class CountryCSVSerializer(CountryStatusSerializer, DownloadSerializerMixin): class Meta(CountryStatusSerializer.Meta): report_fields = OrderedDict([ ('id', 'ID'), ('name', 'Name'), - # ('code', 'Code'), ('iso3_format', 'Country ISO3 Code'), - # ('map_preview', 'Map Preview'), - # ('description', 'Description'), - # ('data_source', 'Data Source'), - # ('date_of_join', 'Date of Joining'), - # ('date_schools_mapped', 'Date School Mapped'), - # ('live_layer_benchmark', {'name': 'Live Layer Benchmarks', 'is_computed': True}), - # ('last_weekly_status', {'name': 'Last Weekly Status', 'is_computed': True}), ]) - # def get_last_weekly_status(self, data): - # last_week_data = data.get('last_weekly_status', None) - # values = [] - # if last_week_data: - # for key, value in last_week_data.items(): - # if isinstance(value, bool): - # value = self.boolean_flags.get(value) - # values.append('{0}:{1}'.format(key, value)) - # return '\t'.join(values) - - # def get_live_layer_benchmark(self, data): - # values = [] - # layers_data = data.get('benchmark_metadata', {}).get('live_layer', {}) - # if len(layers_data) > 0: - # id_names = dict(DataLayer.objects.filter(id__in=list(layers_data.keys())).values_list('id', 'name')) - # for layer_id, benchmark_value in layers_data.items(): - # values.append( - # '{0}:{1}'.format(id_names.get(core_utilities.convert_to_int(layer_id, orig=True), layer_id), - # benchmark_value)) - # return '\t'.join(values) - def to_representation(self, data): data = super().to_representation(data) return self.to_record_representation(data) diff --git a/proco/schools/api.py b/proco/schools/api.py index bae32ce..02298cf 100644 --- a/proco/schools/api.py +++ b/proco/schools/api.py @@ -182,7 +182,7 @@ def sql_to_pbf(self, sql): if not cur: return Response({"error": f"sql query failed: {sql}"}, status=404) return cur.fetchone()[0] - except Exception as error: + except Exception: return Response({"error": "An error occurred while executing SQL query"}, status=500) def generate_tile(self, request): diff --git a/proco/taskapp/__init__.py b/proco/taskapp/__init__.py index a0f43e2..8bb980b 100755 --- a/proco/taskapp/__init__.py +++ b/proco/taskapp/__init__.py @@ -20,7 +20,6 @@ @app.on_after_finalize.connect def finalize_setup(sender, **kwargs): - # from drf_secure_token.tasks import DELETE_OLD_TOKENS app.conf.beat_schedule.update({ 'proco.utils.tasks.update_all_cached_values': { From 3c3f2abc3b7db64f7fd110bcf91ce4a762125d5a Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Mon, 22 Jul 2024 18:58:16 +0530 Subject: [PATCH 14/27] Updated filter listing --- proco/connection_statistics/api.py | 1 + proco/core/exceptions.py | 4 - proco/core/resources/filters.json | 176 ----------------------------- proco/core/utils.py | 7 +- 4 files changed, 4 insertions(+), 184 deletions(-) diff --git a/proco/connection_statistics/api.py b/proco/connection_statistics/api.py index a173655..5fac616 100644 --- a/proco/connection_statistics/api.py +++ b/proco/connection_statistics/api.py @@ -480,6 +480,7 @@ def calculate_country_download_data(self, start_date, end_date, week_number, yea ).filter( realtime_registration_status__rt_registered=True, realtime_registration_status__rt_registration_date__date__lte=end_date, + realtime_registration_status__deleted__isnull=True, ).annotate( dummy_group_by=Value(1)).values('dummy_group_by').annotate( good=Count(Case(When(t__connectivity_speed__gt=speed_benchmark, then='id')), distinct=True), diff --git a/proco/core/exceptions.py b/proco/core/exceptions.py index 5ba99ac..c23a826 100644 --- a/proco/core/exceptions.py +++ b/proco/core/exceptions.py @@ -169,7 +169,3 @@ class InvalidAPIKeyError(BaseInvalidValidationError): message = _('Invalid API Key provided') code = 'invalid_api_key_provided' - - -class BareException(Exception): - pass diff --git a/proco/core/resources/filters.json b/proco/core/resources/filters.json index 4cf67e6..92fc145 100644 --- a/proco/core/resources/filters.json +++ b/proco/core/resources/filters.json @@ -79,181 +79,5 @@ }, "active_countries_filter": "LOWER(education_level) IN ('primary', 'secondary')", "active_countries_list": null - }, - { - "name": "No of Computers", - "type": "range", - "description": "", - "include_none_filter": true, - "parameter": { - "label": "# of Computers", - "table": "school_static", - "field": "num_computers", - "filter": "range" - }, - "active_countries_filter": "num_computers IS NOT NULL AND num_computers > 0", - "active_countries_list": null, - "active_countries_range": { - "default": { - "min_place_holder": "Min (0)", - "max_place_holder": "Max (1 Lac)", - "min_value": 0, - "max_value": 2147483647 - } - } - }, - { - "name": "No of Students", - "type": "range", - "description": "", - "include_none_filter": true, - "parameter": { - "label": "# of Students", - "table": "school_static", - "field": "num_students", - "filter": "range" - }, - "active_countries_filter": "num_students IS NOT NULL AND num_students > 0", - "active_countries_list": null, - "active_countries_range": { - "default": { - "min_place_holder": "Min (0)", - "max_place_holder": "Max (1 Lac)", - "min_value": 0, - "max_value": 2147483647 - } - } - }, - { - "name": "No of Teachers", - "type": "range", - "description": "", - "include_none_filter": true, - "parameter": { - "label": "# of Teachers", - "table": "school_static", - "field": "num_teachers", - "filter": "range" - }, - "active_countries_filter": "num_teachers IS NOT NULL AND num_teachers > 0", - "active_countries_list": null, - "active_countries_range": { - "default": { - "min_place_holder": "Min (0)", - "max_place_holder": "Max (10K)", - "min_value": 0, - "max_value": 32767 - } - } - }, - { - "name": "Download Speed", - "type": "range", - "description": "", - - "include_none_filter": true, - "parameter": { - "label": "Connectivity Speed", - "table": "school_static", - "field": "connectivity_speed", - "filter": "range" - }, - "downcast_aggr_str": "{val} / (1000 * 1000)", - "upcast_aggr_str": "{val} * 1000 * 1000", - "active_countries_filter": "connectivity_speed IS NOT NULL", - "active_countries_list": null, - "active_countries_range": { - "default": { - "min_place_holder": "Min (MBs)", - "max_place_holder": "Max (MBs)", - "min_value": 0, - "max_value": 2147 - } - } - }, - { - "name": "Latency", - "type": "range", - "description": "", - - "include_none_filter": true, - "parameter": { - "label": "Latency", - "table": "school_static", - "field": "connectivity_latency", - "filter": "range" - }, - "active_countries_filter": "connectivity_latency IS NOT NULL", - "active_countries_list": null, - "active_countries_range": { - "default": { - "min_place_holder": "Min (ms)", - "max_place_holder": "Max (ms)", - "min_value": 0, - "max_value": 36000000 - } - } - }, - { - "name": "Has Computer Lab", - "type": "drop-down", - "description": "", - "choices": [ - { - "label": "Yes", - "value": "true" - }, - { - "label": "No", - "value": "false" - } - ], - "parameter": { - "label": "Has Computer Lab", - "table": "school_static", - "field": "computer_lab", - "filter": "on" - }, - "active_countries_filter": "computer_lab = true", - "active_countries_list": null - }, - { - "name": "Coverage Type", - "type": "drop-down-multiselect", - "description": "", - "choices": [ - { - "label": "Unknown", - "value": "unknown" - }, - { - "label": "No", - "value": "no" - }, - { - "label": "2G", - "value": "2g" - }, - { - "label": "3G", - "value": "3g" - }, - { - "label": "4G", - "value": "4g" - }, - { - "label": "5G", - "value": "5g" - } - ], - "parameter": { - "label": "Coverage Type", - "table": "school_static", - "field": "coverage_type", - "filter": "in" - }, - "active_countries_filter": "LOWER(\"connection_statistics_schoolweeklystatus\".\"coverage_type\") != 'unknown'", - "active_countries_list": null } ] \ No newline at end of file diff --git a/proco/core/utils.py b/proco/core/utils.py index 7c0cdf7..82d0d7c 100644 --- a/proco/core/utils.py +++ b/proco/core/utils.py @@ -10,7 +10,6 @@ from django.utils import timezone from proco.core.config import app_config as core_config -from proco.core.exceptions import BareException logger = logging.getLogger('gigamaps.' + __name__) @@ -150,7 +149,7 @@ def convert_to_int(val, default=0, orig=False): """Parse to integer value from string. In case of failure to do so, assign default value.""" try: return int(val) - except BareException: + except: if orig: return val else: @@ -161,7 +160,7 @@ def convert_to_float(val, default=0, orig=False): """Parse to float value from string. In case of failure to do so, assign default value.""" try: return float(val) - except BareException: + except: if orig: return val else: @@ -311,7 +310,7 @@ def bulk_create_or_update(records, model, unique_fields, batch_size=1000): def get_filter_sql(request, filter_key, table_name): - filter_fields = core_config.get_giga_filter_fields[filter_key] + filter_fields = core_config.get_giga_filter_fields.get(filter_key, []) query_params = request.query_params.dict() advance_filters = set(filter_fields) & set(query_params.keys()) From 8cda0ee86a44fc226b56490fe300f4a1439af240 Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Mon, 22 Jul 2024 18:59:00 +0530 Subject: [PATCH 15/27] Removed commented lines --- azure-pipeline-dev.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipeline-dev.yml b/azure-pipeline-dev.yml index 3482435..257f6bd 100644 --- a/azure-pipeline-dev.yml +++ b/azure-pipeline-dev.yml @@ -32,7 +32,7 @@ steps: - task: Bash@3 displayName: Unit Tests -# condition: eq(variables['Build.SourceBranch'], 'refs/heads/develop') + condition: eq(variables['Build.SourceBranch'], 'refs/heads/develop') inputs: targetType: 'inline' script: | @@ -54,7 +54,7 @@ steps: # Sonar Scan - task: Bash@3 displayName: Sonar Scan -# condition: eq(variables['Build.SourceBranch'], 'refs/heads/develop') + condition: eq(variables['Build.SourceBranch'], 'refs/heads/develop') inputs: targetType: 'inline' script: | From e0f09ec3629dffeb4447028dd454036df1417681 Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Mon, 22 Jul 2024 22:48:44 +0530 Subject: [PATCH 16/27] Fixed task timeout, added more searching fields on BT --- proco/background/api.py | 2 +- proco/background/api_urls.py | 9 ++-- proco/background/tests/test_api.py | 43 +++++++++---------- proco/background/tests/test_task.py | 14 +++--- .../commands/populate_school_new_fields.py | 4 +- proco/taskapp/__init__.py | 11 ++--- proco/utils/tasks.py | 9 +--- 7 files changed, 40 insertions(+), 52 deletions(-) diff --git a/proco/background/api.py b/proco/background/api.py index f58e8d5..6414adf 100644 --- a/proco/background/api.py +++ b/proco/background/api.py @@ -34,7 +34,7 @@ class BackgroundTaskViewSet(BaseModelViewSet): ordering_field_names = ['-created_at', '-completed_at'] apply_query_pagination = True - search_fields = ('task_id', 'log',) + search_fields = ('task_id', 'log', 'name', 'description', 'status') filterset_fields = { 'task_id': ['exact', 'in'], 'log': ['exact', 'in'], diff --git a/proco/background/api_urls.py b/proco/background/api_urls.py index 835814e..16cb07c 100644 --- a/proco/background/api_urls.py +++ b/proco/background/api_urls.py @@ -1,4 +1,5 @@ -from django.urls import include, path +from django.urls import path + from proco.background import api app_name = 'background' @@ -7,12 +8,12 @@ path('backgroundtask/', api.BackgroundTaskViewSet.as_view({ 'get': 'list', 'delete': 'destroy', - }), name='list_or_destroy_backgroundtask'), + }), name='list-destroy-backgroundtask'), path('backgroundtask//', api.BackgroundTaskViewSet.as_view({ 'get': 'retrieve', - }), name='update_or_retrieve_backgroundtask'), + }), name='update-retrieve-backgroundtask'), path('backgroundtask//history', api.BackgroundTaskHistoryViewSet.as_view({ 'get': 'list', - }), name='background_task_history'), + }), name='background-task-history'), ] diff --git a/proco/background/tests/test_api.py b/proco/background/tests/test_api.py index 92c421d..4b52d63 100644 --- a/proco/background/tests/test_api.py +++ b/proco/background/tests/test_api.py @@ -1,14 +1,14 @@ +import uuid from datetime import datetime import pytz -import uuid from django.core.cache import cache from django.test import TestCase from django.urls import reverse from rest_framework import status from proco.background.models import BackgroundTask -from proco.custom_auth import models as auth_models +from proco.custom_auth.tests import test_utils as test_utilities from proco.utils.tests import TestAPIViewSetMixin @@ -18,18 +18,17 @@ class BackgroundTaskTestCase(TestAPIViewSetMixin, TestCase): @classmethod def setUpTestData(cls): - cls.email = 'test@test.com' - cls.password = 'SomeRandomPass96' - cls.user = auth_models.ApplicationUser.objects.create_user(username=cls.email, password=cls.password) - - cls.role = auth_models.Role.objects.create(name='Admin', category='system') - cls.role_permission = auth_models.UserRoleRelationship.objects.create(user=cls.user, role=cls.role) + cls.user = test_utilities.setup_admin_user_by_role() - cls.data = {'task_id': str(uuid.uuid4()), 'status': 'running', 'log': "", - 'completed_at': datetime.now(pytz.timezone('Africa/Lagos'))} + cls.data = { + 'task_id': str(uuid.uuid4()), + 'status': 'running', + 'log': '', + 'completed_at': datetime.now(pytz.timezone('Africa/Lagos')) + } cls.task = BackgroundTask.objects.create(**cls.data) - cls.delete_data = {"task_id": [cls.task.task_id]} + cls.delete_data = {'task_id': [cls.task.task_id]} def setUp(self): cache.clear() @@ -38,7 +37,7 @@ def setUp(self): def test_retrieve(self): response = self.forced_auth_req( 'get', - reverse(self.base_view + "update_or_retrieve_backgroundtask", args=(self.task.task_id,)), + reverse(self.base_view + 'update-retrieve-backgroundtask', args=(self.task.task_id,)), user=self.user, ) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -47,12 +46,11 @@ def test_retrieve(self): def test_destroy(self): response = self.forced_auth_req( 'delete', - reverse(self.base_view + "list_or_destroy_backgroundtask"), + reverse(self.base_view + 'list-destroy-backgroundtask'), data=self.delete_data, user=self.user, ) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertNotEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) class BackgroundTaskHistoryTestCase(TestAPIViewSetMixin, TestCase): @@ -60,20 +58,21 @@ class BackgroundTaskHistoryTestCase(TestAPIViewSetMixin, TestCase): databases = {'default', } def setUp(self): - self.email = 'test@test.com' - self.password = 'SomeRandomPass96' - self.user = auth_models.ApplicationUser.objects.create_user(username=self.email, password=self.password) - - self.data = {'task_id': '8303395e-e8c0-4e72-afb8-35f3a53d01d7', 'status': 'running', 'log': "", - 'completed_at': '2024-03-11 06:50:12'} + self.user = test_utilities.setup_admin_user_by_role() + + self.data = { + 'task_id': '8303395e-e8c0-4e72-afb8-35f3a53d01d7', + 'status': 'running', + 'log': '', + 'completed_at': '2024-03-11 06:50:12' + } self.task = BackgroundTask.objects.create(**self.data) return super().setUp() def test_list(self): response = self.forced_auth_req( 'get', - reverse(self.base_view + "background_task_history", args=(self.task.task_id,)), + reverse(self.base_view + 'background-task-history', args=(self.task.task_id,)), user=self.user) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertNotEqual(response.status_code, status.HTTP_502_BAD_GATEWAY) diff --git a/proco/background/tests/test_task.py b/proco/background/tests/test_task.py index d55b260..3e4b825 100644 --- a/proco/background/tests/test_task.py +++ b/proco/background/tests/test_task.py @@ -1,13 +1,13 @@ -from typing import List -from django.utils import timezone -from celery import current_task -from proco.background.models import BackgroundTask -from proco.locations.models import Country +import uuid from datetime import datetime + import pytz -import uuid, random -from django.test import TestCase from django.core.cache import cache +from django.test import TestCase +from django.utils import timezone + +from proco.background.models import BackgroundTask +from proco.locations.models import Country class BackgroundCeleryTaskTestCase(TestCase): diff --git a/proco/core/management/commands/populate_school_new_fields.py b/proco/core/management/commands/populate_school_new_fields.py index 12b48f1..dc8b969 100644 --- a/proco/core/management/commands/populate_school_new_fields.py +++ b/proco/core/management/commands/populate_school_new_fields.py @@ -34,7 +34,7 @@ def populate_school_new_fields(school_id, start_school_id, end_school_id, countr if school_ids and len(school_ids) > 0: schools_qry = schools_qry.filter(id__in=school_ids.split(',')) - logger.debug('Starting the process: ', schools_qry.query) + logger.debug('Starting the process: {}'.format(schools_qry.query)) count = 1 for data_chunk in core_utilities.queryset_iterator(schools_qry, chunk_size=20000): with transaction.atomic(): @@ -83,6 +83,6 @@ def handle(self, **options): end_school_id = options.get('end_school_id') country_id = options.get('country_id') - logger.debug('School update operation started ({0})'.format(school_id)) + logger.debug('School update operation started ({0})'.format(options)) populate_school_new_fields(school_id, start_school_id, end_school_id, country_id, school_ids) diff --git a/proco/taskapp/__init__.py b/proco/taskapp/__init__.py index 8bb980b..854b44c 100755 --- a/proco/taskapp/__init__.py +++ b/proco/taskapp/__init__.py @@ -15,7 +15,8 @@ app.conf.timezone = 'UTC' app.conf.broker_transport_options = {"visibility_timeout": 36000} # 10h app.conf.worker_deduplicate_successful_tasks = True -app.conf.redbeat_lock_key = None +app.conf.redbeat_key_prefix = 'gigamaps:' +app.conf.redbeat_lock_timeout = 36000 @app.on_after_finalize.connect @@ -46,7 +47,7 @@ def finalize_setup(sender, **kwargs): 'proco.data_sources.tasks.update_static_data': { 'task': 'proco.data_sources.tasks.update_static_data', # Executes at 4:00 AM every day - 'schedule': crontab(hour='*/4', minute=47), # crontab(hour=1, minute=0, day_of_week='mon'), + 'schedule': crontab(hour='*/4', minute=47), 'args': (), }, 'proco.data_sources.tasks.update_live_data': { @@ -89,10 +90,4 @@ def finalize_setup(sender, **kwargs): 'schedule': crontab(hour=5, minute=10), 'args': (), }, - 'proco.utils.tasks.duplicate_task_test': { - 'task': 'proco.utils.tasks.duplicate_task_test', - 'schedule': crontab(minute='*/20'), - 'args': (), - }, - # 'drf_secure_token.tasks.delete_old_tokens': DELETE_OLD_TOKENS, }) diff --git a/proco/utils/tasks.py b/proco/utils/tasks.py index 0a975ac..b1b81f6 100644 --- a/proco/utils/tasks.py +++ b/proco/utils/tasks.py @@ -107,7 +107,7 @@ def update_country_related_cache(country_code): country = Country.objects.annotate( code_lower=Lower('code'), - ).filter(code_lower=country_code.lower()) + ).filter(code_lower=country_code.lower()).first() if country: update_cached_value.delay( url=reverse('connection_statistics:global-stat'), @@ -262,10 +262,3 @@ def populate_school_new_fields_task(start_school_id, end_school_id, country_id, background_task_utilities.task_on_complete(task_instance) else: logger.error('Found running Job with "{0}" name so skipping current iteration'.format(task_key)) - - -@app.task(soft_time_limit=10 * 60 * 60, time_limit=10 * 60 * 60) -def duplicate_task_test(): - logger.info('Inside duplicate_task_test task {}'.format(core_utilities.get_current_datetime_object())) - time.sleep(180.0) - logger.info('Exiting from duplicate_task_test task {}'.format(core_utilities.get_current_datetime_object())) From a4e87271134722806be432dd235de84769e6933f Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Tue, 23 Jul 2024 18:50:24 +0530 Subject: [PATCH 17/27] Updated filters, code cleanup --- proco/accounts/models.py | 1 - proco/core/resources/filters.json | 23 +++++++++++++---- proco/custom_auth/authentication.py | 10 -------- proco/custom_auth/models.py | 3 --- proco/custom_auth/serializers.py | 39 ++++++----------------------- proco/data_sources/exceptions.py | 1 - proco/data_sources/models.py | 2 -- proco/data_sources/serializers.py | 4 +-- proco/schools/models.py | 2 ++ proco/schools/tasks.py | 8 +++--- proco/schools/utils.py | 2 +- 11 files changed, 34 insertions(+), 61 deletions(-) diff --git a/proco/accounts/models.py b/proco/accounts/models.py index 0adb520..9e57db9 100644 --- a/proco/accounts/models.py +++ b/proco/accounts/models.py @@ -183,7 +183,6 @@ class Message(core_models.BaseModel): retry_count = models.IntegerField(default=0) is_sent = models.BooleanField(default=False) is_read = models.BooleanField(default=False) - # external_email = models.EmailField(null=True) subject_text = models.TextField(null=True) message_text = models.TextField() diff --git a/proco/core/resources/filters.json b/proco/core/resources/filters.json index 92fc145..011001d 100644 --- a/proco/core/resources/filters.json +++ b/proco/core/resources/filters.json @@ -1,8 +1,8 @@ [ { - "name": "School Region", + "name": "School Area Type", "type": "drop-down", - "description": "", + "description": "School area type description", "choices": [ { "label": "None", @@ -27,9 +27,9 @@ "active_countries_list": null }, { - "name": "School Type", + "name": "School Funding Type", "type": "drop-down", - "description": "", + "description": "School funding type description", "choices": [ { "label": "None", @@ -56,7 +56,7 @@ { "name": "Education Level", "type": "drop-down", - "description": "", + "description": "School education level description", "choices": [ { "label": "None", @@ -79,5 +79,18 @@ }, "active_countries_filter": "LOWER(education_level) IN ('primary', 'secondary')", "active_countries_list": null + }, + { + "name": "Connectivity Type", + "type": "input", + "description": "School connectivity type description", + "parameter": { + "label": "Connectivity Type", + "table": "school_static", + "field": "connectivity_type", + "filter": "in" + }, + "active_countries_filter": "connectivity_type IS NOT NULL", + "active_countries_list": null } ] \ No newline at end of file diff --git a/proco/custom_auth/authentication.py b/proco/custom_auth/authentication.py index 7896ffa..c105b00 100644 --- a/proco/custom_auth/authentication.py +++ b/proco/custom_auth/authentication.py @@ -13,9 +13,6 @@ jwt_decode_handler = api_settings.JWT_DECODE_HANDLER -# jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER - - class JSONWebTokenAuthentication(jwt_authentication.JSONWebTokenAuthentication): """ Token based authentication using the JSON Web Token standard. @@ -23,13 +20,6 @@ class JSONWebTokenAuthentication(jwt_authentication.JSONWebTokenAuthentication): def authenticate(self, request): user_model = get_user_model() - # user = user_model.objects.filter(email='admin@test.com').first() - # payload = jwt_serializers.jwt_payload_handler(user) - # token = jwt_serializers.jwt_encode_handler(payload) - # payload2 = jwt_decode_handler(token) - # header_token_decoded = jwt_decode_handler(jwt_token) - # username_from_token = jwt_get_username_from_payload(header_token_decoded) - try: return super().authenticate(request) except exceptions.AuthenticationFailed as ex: diff --git a/proco/custom_auth/models.py b/proco/custom_auth/models.py index 3f8ed8e..1945648 100644 --- a/proco/custom_auth/models.py +++ b/proco/custom_auth/models.py @@ -95,9 +95,6 @@ def get_user_permissions(self, user): :type user: custom_auth.models.User :returns perms: Dictionary of permissions """ - # attr = USER_PERMISSIONS_KEY.format(user.id) - # perms = cache_utils.get_or_set_cache(attr, lambda: self.calculate_user_permissions(user)) - # return perms return self.calculate_user_permissions(user) def calculate_user_permissions(self, user): diff --git a/proco/custom_auth/serializers.py b/proco/custom_auth/serializers.py index e9ad936..707aeb6 100644 --- a/proco/custom_auth/serializers.py +++ b/proco/custom_auth/serializers.py @@ -13,6 +13,7 @@ logger = logging.getLogger('gigamaps.' + __name__) + class RoleSerializer(serializers.ModelSerializer): """ RoleSerializer @@ -287,7 +288,6 @@ def get_role_fields(self): def to_representation(self, user): user_role = user.get_roles() - # role_fields = self.get_role_fields() role_serializer = RoleSerializer(instance=user_role) setattr(user, 'role', role_serializer.data) return super().to_representation(user) @@ -306,29 +306,16 @@ class Meta: 'role', ) - # def _check_for_custom_role(self, role): - # - # if role.category == auth_models.Role.ROLE_CATEGORY_CUSTOM: - # - # is_super_user = self.instance and self.instance.user.is_superuser - # - # # Cannot assign custom role to a superuser. - # if is_super_user: - # raise auth_exceptions.CannotAssignCustomRoleToSuperuserUser() - def validate_role(self, role): """ Method to validate new role of the user. """ - if role: - # self._check_for_custom_role(role) - # In case of create user role relationship. - if self.instance: - # In case of updating the user role relationship, the new role should not be same - # as that of the existing role. - if self.instance.role.id != role.id: - return role - raise auth_exceptions.InvalidRoleError() + if role and self.instance: + # In case of updating the user role relationship, the new role should not be same + # as that of the existing role. + if self.instance.role.id != role.id: + return role + raise auth_exceptions.InvalidRoleError() return role @@ -436,23 +423,11 @@ class Meta(BaseUserSerializer.Meta): 'role', ) - # def _check_for_custom_role(self, role): - # - # if role.category == auth_models.Role.ROLE_CATEGORY_CUSTOM: - # - # is_super_user = self.instance and self.instance.is_superuser - # - # # Cannot assign custom role to a superuser. - # if is_super_user: - # raise auth_exceptions.CannotAssignCustomRoleToSuperuserUser() - def validate_role(self, new_role): """ Method to validate new role of the user. """ if new_role and self.instance: - # self._check_for_custom_role(role) - # In case of updating the user role relationship, the new role should not be same # as that of the existing role. user_existing_role = self.instance.get_roles() diff --git a/proco/data_sources/exceptions.py b/proco/data_sources/exceptions.py index d614623..01b1607 100644 --- a/proco/data_sources/exceptions.py +++ b/proco/data_sources/exceptions.py @@ -46,4 +46,3 @@ class ZeroSchoolMasterDataRowError(BaseInvalidValidationError): message = _('Zero School Master Data row to update.') description = _('Zero rows to update.') code = 'invalid_school_master_data_row_count' - diff --git a/proco/data_sources/models.py b/proco/data_sources/models.py index 29b77dc..27cb6f5 100644 --- a/proco/data_sources/models.py +++ b/proco/data_sources/models.py @@ -98,8 +98,6 @@ class SchoolMasterData(TimeStampedModel, models.Model): # connectivity_govt_collection_year connectivity_govt_collection_year = models.PositiveSmallIntegerField(blank=True, default=None, null=True) disputed_region = models.CharField(blank=True, null=True, max_length=255) # disputed_region - # nearest_NR_id = models.CharField(blank=True, null=True, max_length=255) - # connectivity_static = models.CharField(blank=True, null=True, max_length=255) # SchoolRealTimeRegistration connectivity_RT = models.CharField(blank=True, null=True, max_length=255) # rt_registered diff --git a/proco/data_sources/serializers.py b/proco/data_sources/serializers.py index 5ee1419..8edca07 100644 --- a/proco/data_sources/serializers.py +++ b/proco/data_sources/serializers.py @@ -171,8 +171,8 @@ def get_modified_fields(self, row): } old_education_level = None \ - if core_utilities.is_blank_string(row.school.education_level) else str( - row.school.education_level).lower() + if core_utilities.is_blank_string(row.school.education_level) \ + else str(row.school.education_level).lower() new_education_level = None \ if core_utilities.is_blank_string(row.education_level) else str(row.education_level).lower() diff --git a/proco/schools/models.py b/proco/schools/models.py index 1aad77f..0d6564f 100644 --- a/proco/schools/models.py +++ b/proco/schools/models.py @@ -122,6 +122,8 @@ class FileImport(TimeStampedModel): errors = models.TextField(blank=True) statistic = models.TextField(blank=True) + objects = models.Manager() + def __str__(self): return self.uploaded_file.name diff --git a/proco/schools/tasks.py b/proco/schools/tasks.py index 2738832..38c19a7 100644 --- a/proco/schools/tasks.py +++ b/proco/schools/tasks.py @@ -60,11 +60,11 @@ def _find_country(loaded: List[dict]) -> [Country]: else: countries_counter = Counter() for country in countries: - instersections = country.geometry.intersection(points) - if isinstance(instersections, Point): + intersections = country.geometry.intersection(points) + if isinstance(intersections, Point): countries_counter[country] = 1 else: - countries_counter[country] = len(instersections) + countries_counter[country] = len(intersections) return countries_counter.most_common()[0][0] @@ -153,7 +153,7 @@ def update_school_records(): task_id, task_key, 'Update the school fields based on changes in SchoolWeekly or CountryWeekly tables') if task_instance: - logger.debug('Not found running job.') + logger.debug('Not found running job for school connectivity status update task.') school_utilities.update_school_from_country_or_school_weekly_update() background_task_utilities.task_on_complete(task_instance) else: diff --git a/proco/schools/utils.py b/proco/schools/utils.py index ac606dd..278ddc5 100644 --- a/proco/schools/utils.py +++ b/proco/schools/utils.py @@ -151,7 +151,7 @@ def update_school_from_country_or_school_weekly_update(start_time=None, end_time logger.debug('Query to select schools where SchoolWeeklyStatus updated between ({0} - {1}): {2}'.format( start_time, end_time, school_updated_in_last_12_hours.query)) - for data_chunk in core_utilities.queryset_iterator(school_updated_in_last_12_hours, chunk_size=20000): + for data_chunk in core_utilities.queryset_iterator(school_updated_in_last_12_hours, chunk_size=100): with transaction.atomic(): for school in data_chunk: school.coverage_type = get_coverage_type(school) From dacc7da5266fcfc253d46a9e83b7961fe2af6cbf Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Thu, 25 Jul 2024 11:47:28 +0530 Subject: [PATCH 18/27] Updated filter descritpion and choice sequence as per feedback --- proco/core/resources/filters.json | 49 ++++++++++++------------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/proco/core/resources/filters.json b/proco/core/resources/filters.json index 011001d..4886229 100644 --- a/proco/core/resources/filters.json +++ b/proco/core/resources/filters.json @@ -1,13 +1,9 @@ [ { - "name": "School Area Type", + "name": "School area type", "type": "drop-down", - "description": "School area type description", + "description": "Urban or rural region in which school is located", "choices": [ - { - "label": "None", - "value": "none" - }, { "label": "Urban", "value": "urban" @@ -15,6 +11,10 @@ { "label": "Rural", "value": "rural" + }, + { + "label": "None", + "value": "none" } ], "parameter": { @@ -27,14 +27,10 @@ "active_countries_list": null }, { - "name": "School Funding Type", + "name": "School funding source", "type": "drop-down", - "description": "School funding type description", + "description": "", "choices": [ - { - "label": "None", - "value": "none" - }, { "label": "Private", "value": "private" @@ -42,6 +38,10 @@ { "label": "Public", "value": "public" + }, + { + "label": "None", + "value": "none" } ], "parameter": { @@ -54,14 +54,10 @@ "active_countries_list": null }, { - "name": "Education Level", + "name": "Education level", "type": "drop-down", - "description": "School education level description", + "description": "Highest level of education taught at the school", "choices": [ - { - "label": "None", - "value": "none" - }, { "label": "Primary", "value": "primary" @@ -69,6 +65,10 @@ { "label": "Secondary", "value": "secondary" + }, + { + "label": "None", + "value": "none" } ], "parameter": { @@ -79,18 +79,5 @@ }, "active_countries_filter": "LOWER(education_level) IN ('primary', 'secondary')", "active_countries_list": null - }, - { - "name": "Connectivity Type", - "type": "input", - "description": "School connectivity type description", - "parameter": { - "label": "Connectivity Type", - "table": "school_static", - "field": "connectivity_type", - "filter": "in" - }, - "active_countries_filter": "connectivity_type IS NOT NULL", - "active_countries_list": null } ] \ No newline at end of file From 84172f97d4d0176fec2205bb0d38ab7d3f5b593a Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Thu, 25 Jul 2024 19:25:47 +0530 Subject: [PATCH 19/27] Integrated flower to monitor the celery worker --- Pipfile | 1 + Pipfile.lock | 33 +++++++++++++++++++++++++++++++++ celery-dev.sh | 21 +++++++++++++++++++++ celery.sh | 12 +++++++++++- docker/docker-compose.dev.yml | 11 +++++++++-- 5 files changed, 75 insertions(+), 3 deletions(-) create mode 100755 celery-dev.sh mode change 100644 => 100755 celery.sh diff --git a/Pipfile b/Pipfile index 2623e67..9b6f67d 100644 --- a/Pipfile +++ b/Pipfile @@ -80,6 +80,7 @@ jsonfield = "==2.0.2" azure-search-documents = "==11.3.0" django-prometheus = "==2.2.0" celery-redbeat = "==2.2.0" +flower = "==2.0.1" [requires] python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock index 176140e..98416a0 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -676,6 +676,14 @@ "index": "pypi", "version": "==3.0.0" }, + "flower": { + "hashes": [ + "sha256:5ab717b979530770c16afb48b50d2a98d23c3e9fe39851dcf6bc4d01845a02a0", + "sha256:9db2c621eeefbc844c8dd88be64aef61e84e2deb29b271e02ab2b5b9f01068e2" + ], + "index": "pypi", + "version": "==2.0.1" + }, "futures": { "hashes": [ "sha256:3a44f286998ae64f0cc083682fcfec16c406134a81a589a5de445d7bb7c2751b", @@ -725,6 +733,14 @@ "index": "pypi", "version": "==21.2.0" }, + "humanize": { + "hashes": [ + "sha256:06b6eb0293e4b85e8d385397c5868926820db32b9b654b932f57fa41c23c9978", + "sha256:39e7ccb96923e732b5c2e27aeaa3b10a8dfeeba3eb965ba7b74a3eb0e30040a6" + ], + "markers": "python_version >= '3.8'", + "version": "==4.10.0" + }, "idna": { "hashes": [ "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", @@ -1357,6 +1373,23 @@ "markers": "python_version >= '3.8'", "version": "==3.2.0" }, + "tornado": { + "hashes": [ + "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8", + "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f", + "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4", + "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3", + "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14", + "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842", + "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9", + "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698", + "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7", + "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d", + "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4" + ], + "markers": "python_version >= '3.8'", + "version": "==6.4.1" + }, "traitlets": { "hashes": [ "sha256:f14949d23829023013c47df20b4a76ccd1a85effb786dc060f34de7948361b33", diff --git a/celery-dev.sh b/celery-dev.sh new file mode 100755 index 0000000..7ef115f --- /dev/null +++ b/celery-dev.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -ex + +# export environment variables to make them available in ssh session +for var in $(compgen -e); do + echo "export $var=${!var}" >> /etc/profile +done + +export FLASK_APP=hello.py +pipenv run python -m flask run --host 0.0.0.0 --port 8000 & + +if $ENABLED_FLOWER_METRICS; then + echo "Starting worker ..." + pipenv run celery --app=proco.taskapp worker --concurrency=2 --time-limit=300 --soft-time-limit=300 $* & + + echo "Starting flower ..." + pipenv run celery --app=proco.taskapp flower +else + echo "Starting worker ..." + pipenv run celery --app=proco.taskapp worker --concurrency=2 --time-limit=300 --soft-time-limit=300 $* +fi diff --git a/celery.sh b/celery.sh old mode 100644 new mode 100755 index bf8bd65..7072c5d --- a/celery.sh +++ b/celery.sh @@ -14,4 +14,14 @@ pipenv run python -m flask run --host 0.0.0.0 --port 8000 & # pipenv run celery -A proco.taskapp worker $* # --logfile=/code/celeryd-%n.log --loglevel=DEBUG -pipenv run celery --app=proco.taskapp worker --concurrency=3 --time-limit=300 --soft-time-limit=60 $* + +if $ENABLED_FLOWER_METRICS; then + echo "Starting worker ..." + pipenv run celery --app=proco.taskapp worker --concurrency=3 --time-limit=300 --soft-time-limit=60 $* & + + echo "Starting flower ..." + pipenv run celery --app=proco.taskapp flower +else + echo "Starting worker ..." + pipenv run celery --app=proco.taskapp worker --concurrency=3 --time-limit=300 --soft-time-limit=60 $* +fi diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index f394c22..d417af9 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -82,15 +82,22 @@ services: build: context: .. dockerfile: docker/Dockerfile-dev - command: bash -c "sleep 10 && pipenv run celery --app=proco.taskapp worker --loglevel=DEBUG --time-limit=300 --concurrency=2 --soft-time-limit=300" + command: bash -c "sleep 10 && /code/celery-dev.sh --loglevel=DEBUG --task-events" environment: DJANGO_SETTINGS_MODULE: config.settings.dev DATABASE_URL: postgis://test:test@db/proco -# CELERY_BROKER_URL: amqp://rabbitmq:rabbitmq@rabbitmq/ REDIS_URL: redis://redis:6379/0 + CELERY_BROKER_URL: redis://redis:6379/1 + CELERY_RESULT_BACKEND_URL: redis://redis:6379/2 + ENABLED_FLOWER_METRICS: true + FLOWER_BASIC_AUTH: hz195KIQ2Kvu8S9knla7lDZXIVX35mvj:TCynWCrvV5pCPpfjArcMtm40P39Od3FJ + FLOWER_PORT: 6543 + FLOWER_DEBUG: false volumes: - "..:/code" depends_on: - db # - rabbitmq - redis + ports: + - "6543:6543" From 3013738e135c94d464f060726bdbbcc8169245c2 Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Thu, 1 Aug 2024 15:51:25 +0530 Subject: [PATCH 20/27] Fixed the script --- .../core/management/commands/data_cleanup.py | 14 +++--- ...opulate_active_data_layer_for_countries.py | 20 +++++--- proco/custom_auth/models.py | 2 +- .../commands/data_loss_recovery_for_qos.py | 47 ++++++++++--------- 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/proco/core/management/commands/data_cleanup.py b/proco/core/management/commands/data_cleanup.py index 42df7e0..ff11998 100644 --- a/proco/core/management/commands/data_cleanup.py +++ b/proco/core/management/commands/data_cleanup.py @@ -111,9 +111,8 @@ def delete_duplicate_school_weekly_records(): # Hard deletion row_to_delete.delete(force=True) else: - logger.debug('School Last Weekly Status id ({0}) is NOT IN the deletion list. ' - 'Hence skipping first record and deleting all remaining based on ID DESC.'.format( - last_weekly_id)) + logger.debug('School Last Weekly Status id ({0}) is NOT IN the deletion list. Hence skipping first ' + 'record and deleting all remaining based on ID DESC.'.format(last_weekly_id)) for row_to_delete in statistics_models.SchoolWeeklyStatus.objects.filter( school_id=row['school_id'], week=row['week'], @@ -219,9 +218,8 @@ def delete_duplicate_country_weekly_records(): # Hard deletion row_to_delete.delete(force=True) else: - logger.debug('Country Last Weekly Status id ({0}) is NOT IN the deletion list. ' - 'Hence skipping first record and deleting all remaining based on ID DESC.'.format( - last_weekly_id)) + logger.debug('Country Last Weekly Status id ({0}) is NOT IN the deletion list. Hence skipping first ' + 'record and deleting all remaining based on ID DESC.'.format(last_weekly_id)) for row_to_delete in statistics_models.CountryWeeklyStatus.objects.filter( country_id=row['country_id'], week=row['week'], @@ -507,7 +505,7 @@ def handle(self, **options): logger.info('Completed school daily duplicate record cleanup.\n\n') if options.get('clean_duplicate_country_weekly'): - logger.info('Performing country weekly wuplicate record cleanup.') + logger.info('Performing country weekly duplicate record cleanup.') delete_duplicate_country_weekly_records() logger.info('Completed country weekly duplicate record cleanup.\n\n') @@ -522,7 +520,7 @@ def handle(self, **options): logger.info('Completed QoS data model duplicate record cleanup.\n\n') if options.get('cleanup_school_master_rows'): - logger.debinfoug('Performing school master data source duplicate record cleanup.') + logger.info('Performing school master data source duplicate record cleanup.') sources_tasks.cleanup_school_master_rows() logger.info('Completed school master data source duplicate record cleanup.\n\n') diff --git a/proco/core/management/commands/populate_active_data_layer_for_countries.py b/proco/core/management/commands/populate_active_data_layer_for_countries.py index 1274fa7..cc33c75 100644 --- a/proco/core/management/commands/populate_active_data_layer_for_countries.py +++ b/proco/core/management/commands/populate_active_data_layer_for_countries.py @@ -13,7 +13,7 @@ logger = logging.getLogger('gigamaps.' + __name__) -def delete_relationships(country_id, layer_id): +def delete_relationships(country_id, layer_id, excluded_ids): relationships = accounts_models.DataLayerCountryRelationship.objects.all() if country_id: @@ -22,6 +22,9 @@ def delete_relationships(country_id, layer_id): if layer_id: relationships = relationships.filter(data_layer_id=layer_id) + if len(excluded_ids) > 0: + relationships = relationships.exclude(id__in=excluded_ids) + relationships.update(deleted=get_current_datetime_object()) @@ -49,11 +52,7 @@ def handle(self, **options): country_id = options.get('country_id', None) layer_id = options.get('layer_id', None) - - if options.get('reset_mapping', False): - logger.info('Delete old records - start') - delete_relationships(country_id, layer_id) - logger.info('Delete old records - end') + ids_to_keep = [] all_published_layers = accounts_models.DataLayer.objects.all() if layer_id: @@ -109,11 +108,12 @@ def handle(self, **options): data_layer=data_layer_instance, country_id=country_id_has_layer_data['country_id'], defaults={ - 'is_default': not data_layer_instance.created_by, + # 'is_default': not data_layer_instance.created_by, 'last_modified_at': get_current_datetime_object(), }, ) ) + ids_to_keep.append(relationship_instance.id) if created: logger.debug('New dataLayers + country relationship created for live layer: {0}'.format( relationship_instance.__dict__)) @@ -155,6 +155,7 @@ def handle(self, **options): }, ) ) + ids_to_keep.append(relationship_instance.id) if created: logger.debug('New dataLayers + country relationship created for static layer: {0}'.format( relationship_instance.__dict__)) @@ -163,4 +164,9 @@ def handle(self, **options): 'Existing dataLayers + country relationship updated for static layer: {0}'.format( relationship_instance.__dict__)) + if options.get('reset_mapping', False): + logger.info('Delete records which are not active now - start') + delete_relationships(country_id, layer_id, ids_to_keep) + logger.info('Delete records which are not active now - end') + logger.info('Active data layer for country mapping operations.') diff --git a/proco/custom_auth/models.py b/proco/custom_auth/models.py index 1945648..9248769 100644 --- a/proco/custom_auth/models.py +++ b/proco/custom_auth/models.py @@ -28,7 +28,7 @@ class ApplicationUser(core_models.BaseModelMixin, AbstractBaseUser): validators=[ validators.RegexValidator( r'^[\w.@+-]+$', - _('Enter a valid username. This value may contain only letters, numbers ' 'and @/./+/-/_ characters.'), + _('Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.'), ), ], error_messages={ diff --git a/proco/data_sources/management/commands/data_loss_recovery_for_qos.py b/proco/data_sources/management/commands/data_loss_recovery_for_qos.py index 4540fbc..5ac8c31 100644 --- a/proco/data_sources/management/commands/data_loss_recovery_for_qos.py +++ b/proco/data_sources/management/commands/data_loss_recovery_for_qos.py @@ -71,12 +71,13 @@ def load_qos_data_source_response_to_model(version_number, country): country=country, version=version_number, ).exists(): - logger.debug('WARNING: QoSData table has given version data already in the table. ' - 'To re collect, please clean this version data first then retry again.' - 'Country code: {0}, \t\tVersion: {1}'.format(table_name, version_number)) + logger.warning('QoSData table has given version data already in the table. ' + 'To re collect, please clean this version data first then retry again.' + 'Country code: {0}, \t\tVersion: {1}'.format(table_name, version_number)) continue - logger.info('Current version data not available in the table. Hence fetching the data from QoS api.') + logger.info( + 'Current version data not available in the table. Hence fetching the data from QoS api.') # Create an url to access a shared table. # A table path is the profile file path following with `#` and the fully qualified name of a table @@ -92,7 +93,7 @@ def load_qos_data_source_response_to_model(version_number, country): if version_number > api_current_version: logger.error('Given version must not be higher then latest api version. ' - 'Hence skipping current data pull.') + 'Hence skipping current data pull.') exit(0) loaded_data_df = delta_sharing.load_table_changes_as_pandas( @@ -108,7 +109,7 @@ def load_qos_data_source_response_to_model(version_number, country): loaded_data_df = loaded_data_df[loaded_data_df[DeltaSharingReader._change_type_col_name()].isin( ['insert', 'update_postimage'])] logger.info('Total count of rows after filtering only ["insert", "update_postimage"] in the "{0}" ' - 'version data: {1}'.format(version_number, len(loaded_data_df))) + 'version data: {1}'.format(version_number, len(loaded_data_df))) if len(loaded_data_df) > 0: insert_entries = [] @@ -137,8 +138,8 @@ def load_qos_data_source_response_to_model(version_number, country): ).first() if not school: - logger.error('School with giga ID ({0}) not found in proco db. ' - 'Hence skipping the load for current school.'.format(row['school_id_giga'])) + logger.error('School with giga ID ({0}) not found in proco db. Hence skipping the ' + 'load for current school.'.format(row['school_id_giga'])) continue row['school'] = school @@ -151,11 +152,12 @@ def load_qos_data_source_response_to_model(version_number, country): ) if duplicate_higher_version_records.exists(): logger.error('Higher version for same school ID and timestamp already exists. ' - 'Hence skipping the update for current row.') + 'Hence skipping the update for current row.') qos_instance = duplicate_higher_version_records.first() logger.debug('School ID: {0},\tTimestamp: {1},\tCurrent Version: {2},\t' - 'Higher Version: {3}'.format(qos_instance.school_id, qos_instance.timestamp, - version_number, qos_instance.version)) + 'Higher Version: {3}'.format(qos_instance.school_id, + qos_instance.timestamp, + version_number, qos_instance.version)) continue insert_entries.append(row_as_dict) @@ -286,7 +288,8 @@ def get_latest_api_version(country_code=None): ) table_current_version = delta_sharing.get_table_version(table_url) - logger.debug('Country "{0}" current version from API: {1}\n'.format(table_name, table_current_version)) + logger.debug( + 'Country "{0}" current version from API: {1}\n'.format(table_name, table_current_version)) version_for_countries[table_name] = table_current_version except Exception as ex: @@ -323,8 +326,9 @@ def check_missing_versions_from_table(country_code=None): missing_version_list = list(set(must_version_list) - set(versions_list)) logger.debug('Missing versions details for country "{0}" are: \n\tStart version from DB: {1}' - '\n\tEnd version from API: {2}' - '\n\tMissing versions: {3}\n'.format(country_iso_code, start_version, end_version, missing_version_list)) + '\n\tEnd version from API: {2}' + '\n\tMissing versions: {3}\n'.format(country_iso_code, start_version, end_version, + missing_version_list)) class Command(BaseCommand): @@ -388,7 +392,7 @@ def handle(self, **options): if not country: logger.error('Country with ISO3 format ({0}) not found in proco db. ' - 'Hence stopping the load.'.format(country_iso3_format)) + 'Hence stopping the load.'.format(country_iso3_format)) exit(0) if check_missing_versions: @@ -400,7 +404,7 @@ def handle(self, **options): if pull_data: if not country: logger.error('Country code is mandatory to pull the data.' - ' Please pass required parameters as: -country_code=\n') + ' Please pass required parameters as: -country_code=\n') exit(0) pull_start_version = options.get('pull_start_version') @@ -413,7 +417,7 @@ def handle(self, **options): logger.info('\nData load completed successfully.\n') else: logger.error('Please provide valid required parameters as:' - ' -pull_start_version= -pull_end_version=\n') + ' -pull_start_version= -pull_end_version=\n') exit(0) try: @@ -425,7 +429,7 @@ def handle(self, **options): if aggregate_data: if not country: logger.error('Country code is mandatory to aggregate the data.' - ' Please pass required parameters as: -country_code=') + ' Please pass required parameters as: -country_code=') exit(0) aggregate_start_version = options.get('aggregate_start_version') @@ -444,8 +448,9 @@ def handle(self, **options): logger.debug('Date list from versions: {0}'.format(date_list_from_versions)) for pull_data_date in date_list_from_versions: - logger.debug('\nSyncing the "data_sources_qosdata" data to "connection_statistics_realtimeconnectivity" ' - 'for date: {0}'.format(pull_data_date)) + logger.debug( + '\nSyncing the "data_sources_qosdata" data to "connection_statistics_realtimeconnectivity" ' + 'for date: {0}'.format(pull_data_date)) sync_qos_realtime_data(pull_data_date, country) logger.debug('Data synced successfully.\n\n') @@ -460,7 +465,7 @@ def handle(self, **options): logger.debug('Finalized records successfully to actual proco tables.\n\n') else: logger.error('Please pass required parameters as:' - ' -pull_start_version= -pull_end_version=') + ' -pull_start_version= -pull_end_version=') logger.info('Completed data loss recovery for qos successfully.\n') exit(0) From ed79fc14dcb03f78c96195a542643dbb1f7040f2 Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Thu, 1 Aug 2024 16:10:26 +0530 Subject: [PATCH 21/27] Code cleanup --- proco/about_us/tests/test_api.py | 4 ++-- proco/connection_statistics/models.py | 5 +++-- proco/connection_statistics/utils.py | 9 ++++++--- proco/custom_auth/serializers.py | 4 ++-- proco/data_sources/serializers.py | 4 ++-- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/proco/about_us/tests/test_api.py b/proco/about_us/tests/test_api.py index cec3e75..1928a74 100644 --- a/proco/about_us/tests/test_api.py +++ b/proco/about_us/tests/test_api.py @@ -10,7 +10,7 @@ class SlideImageAPITestCase(TestAPIViewSetMixin, TestCase): base_view = 'about_us:' - databases = {'default',} + databases = {'default', } @classmethod def setUpTestData(cls): @@ -69,7 +69,7 @@ def test_slide_destroy(self): class AboutUsAPITestCase(TestAPIViewSetMixin, TestCase): base_view = 'about_us:' - databases = {'default',} + databases = {'default', } def setUp(self): self.email = 'test@test.com' diff --git a/proco/connection_statistics/models.py b/proco/connection_statistics/models.py index 7d2b995..ffcfd5b 100644 --- a/proco/connection_statistics/models.py +++ b/proco/connection_statistics/models.py @@ -24,7 +24,8 @@ class ConnectivityStatistics(models.Model): connectivity_latency = models.FloatField(help_text=_('ms'), blank=True, null=True, default=None) connectivity_speed_probe = models.PositiveIntegerField(help_text=_('bps'), blank=True, null=True, default=None) - connectivity_upload_speed_probe = models.PositiveIntegerField(help_text=_('bps'), blank=True, null=True, default=None) + connectivity_upload_speed_probe = models.PositiveIntegerField(help_text=_('bps'), + blank=True, null=True, default=None) connectivity_latency_probe = models.FloatField(help_text=_('ms'), blank=True, null=True, default=None) @@ -159,7 +160,7 @@ def delete(self, *args, **kwargs): class SchoolWeeklyStatus(ConnectivityStatistics, TimeStampedModel, models.Model): - # unable to use choives as should be (COVERAGE_TYPES.4g), because digit goes first + # unable to use choices as should be (COVERAGE_TYPES.4g), because digit goes first COVERAGE_UNKNOWN = 'unknown' COVERAGE_NO = 'no' COVERAGE_2G = '2g' diff --git a/proco/connection_statistics/utils.py b/proco/connection_statistics/utils.py index 4a4f497..e32e94e 100644 --- a/proco/connection_statistics/utils.py +++ b/proco/connection_statistics/utils.py @@ -336,7 +336,8 @@ def update_country_weekly_status(country: Country, date): country_status.schools_coverage_moderate = coverage_stats[ColorMapSchema.MODERATE] country_status.schools_coverage_no = coverage_stats[ColorMapSchema.NO] - schools_coverage_known = country_status.schools_coverage_good + country_status.schools_coverage_moderate + country_status.schools_coverage_no + schools_coverage_known = (country_status.schools_coverage_good + country_status.schools_coverage_moderate + + country_status.schools_coverage_no) country_status.schools_coverage_unknown = country_status.schools_total - schools_coverage_known # calculate speed & latency where available @@ -384,8 +385,10 @@ def update_country_weekly_status(country: Country, date): ]): country_status.integration_status = CountryWeeklyStatus.STATIC_MAPPED - if country_status.integration_status == CountryWeeklyStatus.STATIC_MAPPED \ - and country_status.connectivity_availability == connectivity_types.realtime_speed: + if ( + country_status.integration_status == CountryWeeklyStatus.STATIC_MAPPED and + country_status.connectivity_availability == connectivity_types.realtime_speed + ): country_status.integration_status = CountryWeeklyStatus.REALTIME_MAPPED country_status.avg_distance_school = country.calculate_avg_distance_school() diff --git a/proco/custom_auth/serializers.py b/proco/custom_auth/serializers.py index 707aeb6..013bfcc 100644 --- a/proco/custom_auth/serializers.py +++ b/proco/custom_auth/serializers.py @@ -70,7 +70,7 @@ def validate_name(self, name): return name raise auth_exceptions.InvalidRoleNameError() - def _validate_custom_role_count_error(self, data): + def _validate_custom_role_count_error(self): max_role_count = auth_config.custom_role_count_limit custom_role = auth_models.Role.objects.filter(category='custom') @@ -105,7 +105,7 @@ def validate(self, data): # If reference role is provided then copy all the permissions as well data['permissions'] = reference_role.permissions - self._validate_custom_role_count_error(data) + self._validate_custom_role_count_error() self._validate_unique_role_name(data) return data diff --git a/proco/data_sources/serializers.py b/proco/data_sources/serializers.py index 8edca07..21a184f 100644 --- a/proco/data_sources/serializers.py +++ b/proco/data_sources/serializers.py @@ -361,7 +361,7 @@ class Meta: list_serializer_class = UpdateListSerializer - def _validate_status(self, instance, validated_data): + def _validate_status(self, instance): if instance.status in [ sources_models.SchoolMasterData.ROW_STATUS_UPDATED_IN_DRAFT, sources_models.SchoolMasterData.ROW_STATUS_PUBLISHED, @@ -399,7 +399,7 @@ def update(self, instance, validated_data): validated_data['status'] = sources_models.SchoolMasterData.ROW_STATUS_DELETED_PUBLISHED else: validated_data['status'] = sources_models.SchoolMasterData.ROW_STATUS_PUBLISHED - self._validate_status(instance, validated_data) + self._validate_status(instance) validated_data['published_at'] = core_utilities.get_current_datetime_object() request_user = core_utilities.get_current_user(context=self.context) From 59af77572512350fa72081e079028b74b04ccb19 Mon Sep 17 00:00:00 2001 From: Vikash Kumar <140630604+vikashkum05@users.noreply.github.com> Date: Thu, 8 Aug 2024 11:45:29 +0530 Subject: [PATCH 22/27] Update data_loss_recovery_for_pcdc.py --- .../commands/data_loss_recovery_for_pcdc.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/proco/data_sources/management/commands/data_loss_recovery_for_pcdc.py b/proco/data_sources/management/commands/data_loss_recovery_for_pcdc.py index cae118b..95722ba 100644 --- a/proco/data_sources/management/commands/data_loss_recovery_for_pcdc.py +++ b/proco/data_sources/management/commands/data_loss_recovery_for_pcdc.py @@ -164,10 +164,6 @@ def handle(self, **options): else: country = None - schools_qs = School.objects - if country: - schools_qs = schools_qs.filter(country=country) - dcm_giga_ids = set(dailycheckapp_measurements.filter( country_code=country_code, source__iexact='DailyCheckApp', @@ -177,7 +173,7 @@ def handle(self, **options): dcm_schools = { school.giga_id_school: school - for school in schools_qs.filter(giga_id_school__in=dcm_giga_ids) + for school in School.objects.filter(giga_id_school__in=dcm_giga_ids) } logger.debug('Total schools in dailycheckapp: {0}, Successfully mapped schools: {1}'.format( len(dcm_giga_ids), len(dcm_schools))) @@ -189,6 +185,10 @@ def handle(self, **options): 'school_id', flat=True, ).order_by('school_id')) + schools_qs = School.objects + if country: + schools_qs = schools_qs.filter(country=country) + mlab_schools = { school.external_id: school for school in schools_qs.filter(external_id__in=mlab_school_ids) @@ -269,4 +269,4 @@ def handle(self, **options): logger.info('Finalized records successfully to actual proco tables.\n\n') - logger.info('Completed dataloss recovery for pcdc successfully.\n') + logger.info('Completed data loss recovery utility for pcdc successfully.\n') From 3b90d717a1312151d0405a4ca9104e186d3252a8 Mon Sep 17 00:00:00 2001 From: Vikash Kumar <140630604+vikashkum05@users.noreply.github.com> Date: Thu, 8 Aug 2024 11:46:49 +0530 Subject: [PATCH 23/27] Update utils.py --- proco/data_sources/utils.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/proco/data_sources/utils.py b/proco/data_sources/utils.py index 99b6065..d83861a 100644 --- a/proco/data_sources/utils.py +++ b/proco/data_sources/utils.py @@ -467,20 +467,14 @@ def sync_dailycheckapp_realtime_data(): else: country = None - schools_qs = School.objects - if country: - schools_qs = schools_qs.filter(country=country) - dcm_giga_ids = set(dailycheckapp_measurements.filter( country_code=country_code, source__iexact='DailyCheckApp', - ).values_list( - 'giga_id_school', flat=True, - ).order_by('giga_id_school')) + ).values_list('giga_id_school', flat=True).order_by('giga_id_school')) dcm_schools = { school.giga_id_school: school - for school in schools_qs.filter(giga_id_school__in=dcm_giga_ids) + for school in School.objects.filter(giga_id_school__in=dcm_giga_ids) } logger.debug('Total schools in DailyCheckApp: {0}, Successfully mapped schools: {1}'.format( len(dcm_giga_ids), len(dcm_schools))) @@ -488,9 +482,11 @@ def sync_dailycheckapp_realtime_data(): mlab_school_ids = set(dailycheckapp_measurements.filter( country_code=country_code, source__iexact='MLab', - ).values_list( - 'school_id', flat=True, - ).order_by('school_id')) + ).values_list('school_id', flat=True).order_by('school_id')) + + schools_qs = School.objects + if country: + schools_qs = schools_qs.filter(country=country) mlab_schools = { school.external_id: school From c5320b107ee2d6c82fc0028b2a6fe8e53d034d2c Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Fri, 9 Aug 2024 14:46:15 +0530 Subject: [PATCH 24/27] Added utility --- proco/connection_statistics/api.py | 4 + .../core/management/commands/data_cleanup.py | 31 ++ .../data_loss_recovery_for_pcdc_weekly.py | 324 ++++++++++++++++++ proco/data_sources/tasks.py | 39 +++ proco/data_sources/utils.py | 2 + 5 files changed, 400 insertions(+) create mode 100644 proco/data_sources/management/commands/data_loss_recovery_for_pcdc_weekly.py diff --git a/proco/connection_statistics/api.py b/proco/connection_statistics/api.py index 5fac616..a70b409 100644 --- a/proco/connection_statistics/api.py +++ b/proco/connection_statistics/api.py @@ -481,6 +481,7 @@ def calculate_country_download_data(self, start_date, end_date, week_number, yea realtime_registration_status__rt_registered=True, realtime_registration_status__rt_registration_date__date__lte=end_date, realtime_registration_status__deleted__isnull=True, + t__deleted__isnull=True, ).annotate( dummy_group_by=Value(1)).values('dummy_group_by').annotate( good=Count(Case(When(t__connectivity_speed__gt=speed_benchmark, then='id')), distinct=True), @@ -570,8 +571,11 @@ def generate_country_graph_data(self, start_date, end_date): avg_daily_connectivity_speed = self.queryset.filter( realtime_registration_status__rt_registered=True, realtime_registration_status__rt_registration_date__date__lte=end_date, + realtime_registration_status__deleted__isnull=True, daily_status__date__range=[start_date, end_date], daily_status__connectivity_speed__isnull=False, + + daily_status__deleted__isnull=True, ).values('daily_status__date').annotate( avg_speed=Avg('daily_status__connectivity_speed'), ).order_by('daily_status__date') diff --git a/proco/core/management/commands/data_cleanup.py b/proco/core/management/commands/data_cleanup.py index ff11998..4868408 100644 --- a/proco/core/management/commands/data_cleanup.py +++ b/proco/core/management/commands/data_cleanup.py @@ -475,6 +475,29 @@ def add_arguments(self, parser): help='Pass the School ID in case want to control the update.' ) + parser.add_argument( + '--data_loss_recovery_for_pcdc_weekly_with_schedular', action='store_true', + dest='data_loss_recovery_for_pcdc_weekly_with_schedular', default=False, + help='If provided, run the data_loss_recovery_for_pcdc_weekly utility through Schedular in real time.' + ) + + parser.add_argument( + '-start_week_no', dest='start_week_no', type=int, + required=False, + help='Start week no from which we need to pull the data and then do aggregation.' + ) + + parser.add_argument( + '-end_week_no', dest='end_week_no', type=int, + required=False, + help='End week no from which we need to pull the data and then do aggregation.' + ) + + parser.add_argument( + '--pull_data', action='store_true', dest='pull_data', default=False, + help='Pull the PCDC live data from API for specified date.' + ) + def handle(self, **options): logger.info('Executing data cleanup utility.\n') logger.debug('Options: {}\n\n'.format(options)) @@ -583,4 +606,12 @@ def handle(self, **options): # redo_aggregations_task(country_year[0], country_year[1], None) redo_aggregations_task.delay(country_year[0], country_year[1], week_no) + if options.get('data_loss_recovery_for_pcdc_weekly_with_schedular'): + start_week_no = options.get('start_week_no', None) + end_week_no = options.get('end_week_no', None) + year = options.get('year', None) + pull_data = options.get('pull_data', False) + + sources_tasks.data_loss_recovery_for_pcdc_weekly_task.delay(start_week_no, end_week_no, year, pull_data) + logger.info('Completed data cleanup successfully.\n') diff --git a/proco/data_sources/management/commands/data_loss_recovery_for_pcdc_weekly.py b/proco/data_sources/management/commands/data_loss_recovery_for_pcdc_weekly.py new file mode 100644 index 0000000..f9b70aa --- /dev/null +++ b/proco/data_sources/management/commands/data_loss_recovery_for_pcdc_weekly.py @@ -0,0 +1,324 @@ +import logging +from datetime import timedelta + +from django.conf import settings +from django.core.management import call_command +from django.core.management.base import BaseCommand +from django.db.models import Avg +from django.db.models import F +from django.db.models import Q + +from proco.connection_statistics import models as statistics_models +from proco.connection_statistics.config import app_config as statistics_configs +from proco.core.utils import get_current_datetime_object +from proco.data_sources.models import DailyCheckAppMeasurementData +from proco.data_sources.tasks import finalize_previous_day_data +from proco.data_sources.utils import load_daily_check_app_data_source_response_to_model +from proco.locations.models import Country +from proco.schools.models import School +from proco.utils import dates as date_utilities + +logger = logging.getLogger('gigamaps.' + __name__) + +ds_settings = settings.DATA_SOURCE_CONFIG + + +def delete_dailycheckapp_realtime_data(start_date, end_date, week_no, year): + logger.info('Deleting all the PCDC rows only, from "RealTimeConnectivity" data table for dates: {0} - {1}'.format( + start_date, end_date)) + statistics_models.RealTimeConnectivity.objects.filter( + created__date__gte=start_date, + created__date__lte=end_date, + live_data_source=statistics_configs.DAILY_CHECK_APP_MLAB_SOURCE, + ).delete() + + logger.info('Deleting all the rows from "DailyCheckAppMeasurementData" data table for for dates: {0} - {1}'.format( + start_date, end_date)) + DailyCheckAppMeasurementData.objects.filter( + timestamp__date__gte=start_date, + timestamp__date__lte=end_date, + ).delete() + + logger.info('Updating live PCDC live fields from "SchoolWeeklyStatus" data table for year - week: {0} - {1}'.format( + year, week_no)) + statistics_models.SchoolWeeklyStatus.objects.filter( + week=week_no, + year=year, + school_id__in=list(statistics_models.SchoolDailyStatus.objects.filter( + date__gte=start_date, + date__lte=end_date, + live_data_source=statistics_configs.DAILY_CHECK_APP_MLAB_SOURCE, + ).values_list('school', flat=True).order_by('school_id').distinct('school_id')) + ).update( + connectivity_speed=None, + connectivity_upload_speed=None, + connectivity_latency=None, + connectivity=False + ) + + logger.info('Deleting all the rows from "SchoolDailyStatus" data table for dates: {0} - {1}'.format( + start_date, end_date)) + statistics_models.SchoolDailyStatus.objects.filter( + date__gte=start_date, + date__lte=end_date, + live_data_source=statistics_configs.DAILY_CHECK_APP_MLAB_SOURCE, + ).update( + deleted=get_current_datetime_object() + ) + + impacted_country_ids = (statistics_models.CountryDailyStatus.objects.filter( + date__gte=start_date, + date__lte=end_date, + live_data_source=statistics_configs.DAILY_CHECK_APP_MLAB_SOURCE, + ).values_list('country', flat=True).order_by('country_id').distinct('country_id')) + + logger.info( + 'Updating live PCDC live fields from "CountryWeeklyStatus" data table for year - week: {0} - {1}'.format( + year, week_no)) + statistics_models.CountryWeeklyStatus.objects.filter( + week=week_no, + year=year, + country_id__in=impacted_country_ids, + ).update( + connectivity_availability='no_connectivity', + schools_connectivity_good=0, + schools_connectivity_moderate=0, + schools_connectivity_no=0, + schools_connected=0, + schools_with_data_percentage=0, + connectivity_speed=None, + connectivity_latency=None, + connectivity_upload_speed=None, + ) + + logger.info('Deleting all the rows from "CountryDailyStatus" data table for dates: {0} - {1}'.format( + start_date, end_date)) + statistics_models.CountryDailyStatus.objects.filter( + date__gte=start_date, + date__lte=end_date, + live_data_source=statistics_configs.DAILY_CHECK_APP_MLAB_SOURCE, + ).update( + deleted=get_current_datetime_object() + ) + + return impacted_country_ids + + +def sync_dailycheckapp_realtime_data(date): + request_configs = { + 'url': '{0}/measurements/v2'.format(ds_settings.get('DAILY_CHECK_APP').get('BASE_URL')), + 'method': 'GET', + 'data_limit': 1000, + 'query_params': { + 'page': '{page_no}', + 'size': '{page_size}', + 'orderBy': 'timestamp', + 'filterBy': 'timestamp', + 'filterCondition': 'eq', + 'filterValue': '{0}'.format(date), + }, + 'auth_token_required': True, + 'headers': { + 'Content-Type': 'application/json' + } + } + load_daily_check_app_data_source_response_to_model(DailyCheckAppMeasurementData, request_configs) + + +class Command(BaseCommand): + def add_arguments(self, parser): + + parser.add_argument( + '-start_week_no', dest='start_week_no', type=int, + required=True, + help='Start week no from which we need to pull the data and then do aggregation.' + ) + + parser.add_argument( + '-end_week_no', dest='end_week_no', type=int, + required=True, + help='End week no from which we need to pull the data and then do aggregation.' + ) + + parser.add_argument( + '-year', dest='year', type=int, + required=True, + help='Year for which weeks are provided.' + ) + + parser.add_argument( + '--pull_data', action='store_true', dest='pull_data', default=False, + help='Pull the PCDC live data from API for specified date.' + ) + + def handle(self, **options): + logger.info('Executing data loss recovery for pcdc.') + start_week_no = options.get('start_week_no') + end_week_no = options.get('end_week_no') + year = options.get('year') + pull_data = options.get('pull_data') + + impacted_country_ids = [] + + if start_week_no > end_week_no: + logger.error('Start date value can not be greater than end_date.') + exit(0) + + for week_no in range(start_week_no, end_week_no + 1): + start_date = date_utilities.get_first_date_of_week(year, week_no) + end_date = start_date + timedelta(days=6) + + if pull_data: + country_ids = delete_dailycheckapp_realtime_data(start_date, end_date, week_no, year) + impacted_country_ids.extend(country_ids) + + date_list = sorted( + [(start_date + timedelta(days=x)) for x in range((end_date - start_date).days)] + [end_date]) + logger.info('date_list: {}'.format(date_list)) + + for pull_data_date in date_list: + logger.info('Syncing the PCDC api data to proco PCDC table for date: {}'.format(pull_data_date)) + sync_dailycheckapp_realtime_data(pull_data_date) + logger.info('Data synced successfully.\n\n') + + logger.info('Aggregating the pulled data by giga_id_school + country_code and storing in ' + 'RealTimeConnectivity table.') + dailycheckapp_measurements = DailyCheckAppMeasurementData.objects.filter( + timestamp__date=pull_data_date, + ).filter( + (Q(download__isnull=True) | Q(download__gte=0)) & + (Q(upload__isnull=True) | Q(upload__gte=0)) & + (Q(latency__isnull=True) | Q(latency__gte=0)), + ).values( + 'giga_id_school', 'country_code', 'school_id', 'source', + ).annotate( + download_avg=Avg('download'), + latency_avg=Avg('latency'), + upload_avg=Avg('upload'), + ).order_by('country_code', 'giga_id_school', 'school_id', 'source') + + if not dailycheckapp_measurements.exists(): + logger.error('No records to aggregate on provided date: "{0}". ' + 'Hence stopping the execution here.'.format(pull_data_date)) + return + + realtime = [] + + countries = set(dailycheckapp_measurements.values_list( + 'country_code', flat=True, + ).order_by('country_code')) + for country_code in countries: + logger.info('Current country code: {}'.format(country_code)) + if country_code: + country = Country.objects.filter(code=country_code).first() + else: + country = None + + dcm_giga_ids = set(dailycheckapp_measurements.filter( + country_code=country_code, + source__iexact='DailyCheckApp', + ).values_list( + 'giga_id_school', flat=True, + ).order_by('giga_id_school')) + + dcm_schools = { + school.giga_id_school: school + for school in School.objects.filter(giga_id_school__in=dcm_giga_ids) + } + logger.info('Total schools in dailycheckapp: {0}, Successfully mapped schools: {1}'.format( + len(dcm_giga_ids), len(dcm_schools))) + + mlab_school_ids = set(dailycheckapp_measurements.filter( + country_code=country_code, + source__iexact='MLab', + ).values_list( + 'school_id', flat=True, + ).order_by('school_id')) + + schools_qs = School.objects + if country: + schools_qs = schools_qs.filter(country=country) + + mlab_schools = { + school.external_id: school + for school in schools_qs.filter(external_id__in=mlab_school_ids) + } + logger.info('Total schools in MLab: {0}, Successfully mapped schools: {1}'.format( + len(mlab_school_ids), len(mlab_schools))) + + unknown_schools = [] + + for dailycheckapp_measurement in dailycheckapp_measurements.filter(country_code=country_code): + if str(dailycheckapp_measurement['source']).lower() == 'dailycheckapp': + giga_id_school = dailycheckapp_measurement.get('giga_id_school') + if giga_id_school not in dcm_schools: + unknown_schools.append(giga_id_school) + continue + school = dcm_schools[giga_id_school] + else: + school_id = dailycheckapp_measurement.get('school_id') + if school_id not in mlab_schools: + unknown_schools.append(school_id) + continue + school = mlab_schools[school_id] + + connectivity_speed = dailycheckapp_measurement.get('download_avg') + if connectivity_speed: + # kb/s -> b/s + connectivity_speed = connectivity_speed * 1000 + + connectivity_upload_speed = dailycheckapp_measurement.get('upload_avg') + if connectivity_upload_speed: + # kb/s -> b/s + connectivity_upload_speed = connectivity_upload_speed * 1000 + + realtime.append(statistics_models.RealTimeConnectivity( + created=pull_data_date, + connectivity_speed=connectivity_speed, + connectivity_upload_speed=connectivity_upload_speed, + connectivity_latency=dailycheckapp_measurement.get('latency_avg'), + school=school, + live_data_source=statistics_configs.DAILY_CHECK_APP_MLAB_SOURCE, + )) + + if len(realtime) == 5000: + logger.info('Loading the data to "RealTimeConnectivity" table as it ' + 'has reached 5000 benchmark.') + statistics_models.RealTimeConnectivity.objects.bulk_create(realtime) + realtime = [] + + if len(unknown_schools) > 0: + logger.info('Skipped dailycheckapp measurement for country: "{0}" unknown school:' + ' {1}'.format(country_code, unknown_schools)) + + logger.info( + 'Loading the remaining ({0}) data to "RealTimeConnectivity" table.'.format(len(realtime))) + if len(realtime) > 0: + statistics_models.RealTimeConnectivity.objects.bulk_create(realtime) + + logger.info('Aggregated successfully to RealTimeConnectivity table.\n\n') + + logger.info('Starting finalizing the records to actual proco tables.') + countries_ids = statistics_models.RealTimeConnectivity.objects.filter( + created__date__gte=start_date, + created__date__lte=end_date, + live_data_source=statistics_configs.DAILY_CHECK_APP_MLAB_SOURCE, + school__deleted__isnull=True, + ).annotate( + country_id=F('school__country_id'), + ).order_by('country_id').values_list('country_id', flat=True).distinct('country_id') + + logger.info('Weekly record details. \tWeek No: {0}\tYear: {1}'.format(week_no, year)) + + for country_id in countries_ids: + logger.info('Finalizing the records for Country ID: {0}'.format(country_id)) + finalize_previous_day_data(None, country_id, end_date) + + impacted_country_ids.extend(countries_ids) + logger.info('Finalized records successfully to actual proco tables.\n\n') + + for impacted_country_id in set(impacted_country_ids): + cmd_args = ['--reset', f'-country_id={impacted_country_id}'] + call_command('populate_school_registration_data', *cmd_args) + + logger.info('Completed data loss recovery utility for pcdc successfully.\n') diff --git a/proco/data_sources/tasks.py b/proco/data_sources/tasks.py index df89c97..eee749e 100644 --- a/proco/data_sources/tasks.py +++ b/proco/data_sources/tasks.py @@ -7,6 +7,7 @@ from celery import chain, chord, group, current_task from django.conf import settings from django.contrib.gis.geos import Point +from django.core.management import call_command from django.db.models import Count from django.db.utils import DataError from requests.exceptions import HTTPError @@ -819,3 +820,41 @@ def clean_old_live_data(): background_task_utilities.task_on_complete(task_instance) else: logger.error('Found running Job with "{0}" name so skipping current iteration'.format(task_key)) + + +@app.task(soft_time_limit=10 * 60 * 60, time_limit=10 * 60 * 60) +def data_loss_recovery_for_pcdc_weekly_task(start_week_no, end_week_no, year, pull_data, *args): + """ + data_loss_recovery_for_pcdc_weekly_task + Task to schedule manually from Console. + """ + if not start_week_no or not end_week_no or not year: + logger.error('Required args not provided: [start_week_no, end_week_no, year]') + return + + logger.info('Starting data loss recovery for pcdc task: start_week_no "{0}" - end_week_no "{1}" - ' + 'year "{2}"'.format(start_week_no, end_week_no, year)) + + task_key = 'data_loss_recovery_for_pcdc_weekly_task_start_week_no_{0}_end_week_no_{1}_year_{2}_on_{3}'.format( + start_week_no, end_week_no, year, format_date(core_utilities.get_current_datetime_object(), frmt='%d%m%Y_%H')) + + task_id = current_task.request.id or str(uuid.uuid4()) + task_instance = background_task_utilities.task_on_start( + task_id, task_key, 'Recover the data fro PCDC live source') + + if task_instance: + logger.debug('Not found running job: {}'.format(task_key)) + cmd_args = [ + '-start_week_no={}'.format(start_week_no), + '-end_week_no={}'.format(end_week_no), + '-year={}'.format(year), + ] + + if pull_data: + cmd_args.append('--pull_data') + + call_command('data_loss_recovery_for_pcdc_weekly', *cmd_args) + + background_task_utilities.task_on_complete(task_instance) + else: + logger.error('Found running Job with "{0}" name so skipping current iteration'.format(task_key)) diff --git a/proco/data_sources/utils.py b/proco/data_sources/utils.py index d83861a..d0de63a 100644 --- a/proco/data_sources/utils.py +++ b/proco/data_sources/utils.py @@ -404,6 +404,8 @@ def load_daily_check_app_data_source_response_to_model(model, request_configs): has_more_data = False else: for data in response_data: + if not data.get('created_at', None): + data['created_at'] = data.get('timestamp') insert_entries.append(model(**data)) if len(insert_entries) >= 5000: From 557c8ec947b07b00aba008fe2767e74b9ed3c388 Mon Sep 17 00:00:00 2001 From: Vikash Kumar <140630604+vikashkum05@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:51:52 +0530 Subject: [PATCH 25/27] Update api.py --- proco/connection_statistics/api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/proco/connection_statistics/api.py b/proco/connection_statistics/api.py index a70b409..5cfd173 100644 --- a/proco/connection_statistics/api.py +++ b/proco/connection_statistics/api.py @@ -574,7 +574,6 @@ def generate_country_graph_data(self, start_date, end_date): realtime_registration_status__deleted__isnull=True, daily_status__date__range=[start_date, end_date], daily_status__connectivity_speed__isnull=False, - daily_status__deleted__isnull=True, ).values('daily_status__date').annotate( avg_speed=Avg('daily_status__connectivity_speed'), From a9677b25caf3a1db426b204a5230211360652c30 Mon Sep 17 00:00:00 2001 From: Vikash Kumar <140630604+vikashkum05@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:52:37 +0530 Subject: [PATCH 26/27] Update data_loss_recovery_for_pcdc.py --- .../commands/data_loss_recovery_for_pcdc.py | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/proco/data_sources/management/commands/data_loss_recovery_for_pcdc.py b/proco/data_sources/management/commands/data_loss_recovery_for_pcdc.py index 95722ba..733bea2 100644 --- a/proco/data_sources/management/commands/data_loss_recovery_for_pcdc.py +++ b/proco/data_sources/management/commands/data_loss_recovery_for_pcdc.py @@ -31,22 +31,22 @@ def check_missing_dates_to_table(date_list): timestamp__date__lte=date_list[-1], ).values_list('timestamp__date', flat=True).distinct('timestamp__date').order_by('timestamp__date') - logger.debug('Missing dates are between {0} - {1}: '.format(date_list[0], date_list[-1])) + logger.info('Missing dates are between {0} - {1}: '.format(date_list[0], date_list[-1])) missing_dates = list(set(date_list) - set(list(pcdc_timestamp_qry))) for missing_date in sorted(missing_dates): # print missing date in string format - logger.debug(date_utilities.format_date(missing_date)) + logger.info(date_utilities.format_date(missing_date)) def delete_dailycheckapp_realtime_data(date): - logger.debug('Deleting all the PCDC rows only, from "RealTimeConnectivity" data table for date: {0}'.format(date)) + logger.info('Deleting all the PCDC rows only, from "RealTimeConnectivity" data table for date: {0}'.format(date)) RealTimeConnectivity.objects.filter( created__date=date, live_data_source=statistics_configs.DAILY_CHECK_APP_MLAB_SOURCE, ).delete() - logger.debug('Deleting all the rows from "DailyCheckAppMeasurementData" data table for date: {0}'.format(date)) + logger.info('Deleting all the rows from "DailyCheckAppMeasurementData" data table for date: {0}'.format(date)) DailyCheckAppMeasurementData.objects.filter(timestamp__date=date).delete() @@ -123,16 +123,16 @@ def handle(self, **options): if pull_data and pull_data_date: pull_data_date = pull_data_date.date() - logger.debug('Deleting PCDC data for date: {}'.format(pull_data_date)) + logger.info('Deleting PCDC data for date: {}'.format(pull_data_date)) delete_dailycheckapp_realtime_data(pull_data_date) - logger.debug('Data deleted successfully.\n\n') + logger.info('Data deleted successfully.\n\n') - logger.debug('Syncing the PCDC api data to proco PCDC table for date: {}'.format(pull_data_date)) + logger.info('Syncing the PCDC api data to proco PCDC table for date: {}'.format(pull_data_date)) sync_dailycheckapp_realtime_data(pull_data_date) - logger.debug('Data synced successfully.\n\n') + logger.info('Data synced successfully.\n\n') - logger.debug('Aggregating the pulled data by giga_id_school + country_code and ' - 'storing in RealTimeConnectivity table.') + logger.info('Aggregating the pulled data by giga_id_school + country_code and ' + 'storing in RealTimeConnectivity table.') dailycheckapp_measurements = DailyCheckAppMeasurementData.objects.filter( timestamp__date=pull_data_date, ).filter( @@ -158,7 +158,7 @@ def handle(self, **options): 'country_code', flat=True, ).order_by('country_code')) for country_code in countries: - logger.debug('Current country code: {}'.format(country_code)) + logger.info('Current country code: {}'.format(country_code)) if country_code: country = Country.objects.filter(code=country_code).first() else: @@ -175,7 +175,7 @@ def handle(self, **options): school.giga_id_school: school for school in School.objects.filter(giga_id_school__in=dcm_giga_ids) } - logger.debug('Total schools in dailycheckapp: {0}, Successfully mapped schools: {1}'.format( + logger.info('Total schools in dailycheckapp: {0}, Successfully mapped schools: {1}'.format( len(dcm_giga_ids), len(dcm_schools))) mlab_school_ids = set(dailycheckapp_measurements.filter( @@ -193,7 +193,7 @@ def handle(self, **options): school.external_id: school for school in schools_qs.filter(external_id__in=mlab_school_ids) } - logger.debug('Total schools in MLab: {0}, Successfully mapped schools: {1}'.format( + logger.info('Total schools in MLab: {0}, Successfully mapped schools: {1}'.format( len(mlab_school_ids), len(mlab_schools))) unknown_schools = [] @@ -232,22 +232,22 @@ def handle(self, **options): )) if len(realtime) == 5000: - logger.debug( + logger.info( 'Loading the data to "RealTimeConnectivity" table as it has reached 5000 benchmark.') RealTimeConnectivity.objects.bulk_create(realtime) realtime = [] if len(unknown_schools) > 0: - logger.debug('Skipped dailycheckapp measurement for country: "{0}" unknown school: {1}'.format( + logger.info('Skipped dailycheckapp measurement for country: "{0}" unknown school: {1}'.format( country_code, unknown_schools)) - logger.debug('Loading the remaining ({0}) data to "RealTimeConnectivity" table.'.format(len(realtime))) + logger.info('Loading the remaining ({0}) data to "RealTimeConnectivity" table.'.format(len(realtime))) if len(realtime) > 0: RealTimeConnectivity.objects.bulk_create(realtime) - logger.debug('Aggregated successfully to RealTimeConnectivity table.\n\n') + logger.info('Aggregated successfully to RealTimeConnectivity table.\n\n') - logger.debug('Starting finalizing the records to actual proco tables.') + logger.info('Starting finalizing the records to actual proco tables.') countries_ids = RealTimeConnectivity.objects.filter( created__date=pull_data_date, live_data_source=statistics_configs.DAILY_CHECK_APP_MLAB_SOURCE, @@ -261,10 +261,10 @@ def handle(self, **options): monday_week_no = date_utilities.get_week_from_date(monday_date) monday_year = date_utilities.get_year_from_date(monday_date) - logger.debug('Weekly record details. \tWeek No: {0}\tYear: {1}'.format(monday_week_no, monday_year)) + logger.info('Weekly record details. \tWeek No: {0}\tYear: {1}'.format(monday_week_no, monday_year)) for country_id in countries_ids: - logger.debug('Finalizing the records for Country ID: {0}'.format(country_id)) + logger.info('Finalizing the records for Country ID: {0}'.format(country_id)) finalize_previous_day_data(None, country_id, pull_data_date) logger.info('Finalized records successfully to actual proco tables.\n\n') From 574ecd62bd96a2596d3d6124c5b5ccd95f12c1f3 Mon Sep 17 00:00:00 2001 From: Vikash Kumar <140630604+vikashkum05@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:52:55 +0530 Subject: [PATCH 27/27] Update data_loss_recovery_for_qos.py --- .../commands/data_loss_recovery_for_qos.py | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/proco/data_sources/management/commands/data_loss_recovery_for_qos.py b/proco/data_sources/management/commands/data_loss_recovery_for_qos.py index 5ac8c31..cb95c70 100644 --- a/proco/data_sources/management/commands/data_loss_recovery_for_qos.py +++ b/proco/data_sources/management/commands/data_loss_recovery_for_qos.py @@ -65,7 +65,7 @@ def load_qos_data_source_response_to_model(version_number, country): if country.iso3_format != table_name: continue - logger.debug('#' * 10) + logger.info('#' * 10) try: if QoSData.objects.all().filter( country=country, @@ -89,7 +89,7 @@ def load_qos_data_source_response_to_model(version_number, country): ) api_current_version = delta_sharing.get_table_version(table_url) - logger.debug('Current version from api: {0}'.format(api_current_version)) + logger.info('Current version from api: {0}'.format(api_current_version)) if version_number > api_current_version: logger.error('Given version must not be higher then latest api version. ' @@ -103,7 +103,7 @@ def load_qos_data_source_response_to_model(version_number, country): None, None, ) - logger.debug('Total count of rows in the {0} version data: {1}'.format( + logger.info('Total count of rows in the {0} version data: {1}'.format( version_number, len(loaded_data_df))) loaded_data_df = loaded_data_df[loaded_data_df[DeltaSharingReader._change_type_col_name()].isin( @@ -121,8 +121,8 @@ def load_qos_data_source_response_to_model(version_number, country): 'modified', 'school_id', 'country_id', 'modified_by', ] - logger.debug('All QoS api response columns: {}'.format(df_columns)) - logger.debug('All QoS api response columns to delete: {}'.format( + logger.info('All QoS api response columns: {}'.format(df_columns)) + logger.info('All QoS api response columns to delete: {}'.format( list(set(df_columns) - set(qos_model_fields)))) loaded_data_df.drop(columns=cols_to_delete, inplace=True, errors='ignore', ) @@ -154,10 +154,10 @@ def load_qos_data_source_response_to_model(version_number, country): logger.error('Higher version for same school ID and timestamp already exists. ' 'Hence skipping the update for current row.') qos_instance = duplicate_higher_version_records.first() - logger.debug('School ID: {0},\tTimestamp: {1},\tCurrent Version: {2},\t' - 'Higher Version: {3}'.format(qos_instance.school_id, - qos_instance.timestamp, - version_number, qos_instance.version)) + logger.info('School ID: {0},\tTimestamp: {1},\tCurrent Version: {2},\t' + 'Higher Version: {3}'.format(qos_instance.school_id, + qos_instance.timestamp, + version_number, qos_instance.version)) continue insert_entries.append(row_as_dict) @@ -166,13 +166,13 @@ def load_qos_data_source_response_to_model(version_number, country): logger.info('Loading the data to "QoSData" table as it has reached 5000 benchmark.') bulk_create_or_update(insert_entries, QoSData, ['school', 'timestamp']) insert_entries = [] - logger.debug('#\n' * 10) + logger.info('#\n' * 10) - logger.debug('Loading the remaining ({0}) data to "QoSData" table.'.format(len(insert_entries))) + logger.info('Loading the remaining ({0}) data to "QoSData" table.'.format(len(insert_entries))) if len(insert_entries) > 0: bulk_create_or_update(insert_entries, QoSData, ['school', 'timestamp']) else: - logger.debug('No data to update in current table: {0}.'.format(table_name)) + logger.info('No data to update in current table: {0}.'.format(table_name)) except Exception as ex: logger.error('Exception caught for "{0}": {1}'.format(schema_table.name, str(ex))) else: @@ -203,10 +203,10 @@ def sync_qos_realtime_data(date, country): ).order_by('school') if not qos_measurements.exists(): - logger.debug('No records to aggregate on provided date: "{0}". Hence skipping for the given date.'.format(date)) + logger.info('No records to aggregate on provided date: "{0}". Hence skipping for the given date.'.format(date)) return - logger.debug('Migrating the records from "QoSData" to "RealTimeConnectivity" with date: {0} '.format(date)) + logger.info('Migrating the records from "QoSData" to "RealTimeConnectivity" with date: {0} '.format(date)) realtime = [] @@ -248,11 +248,11 @@ def sync_qos_realtime_data(date, country): )) if len(realtime) == 5000: - logger.debug('Loading the data to "RealTimeConnectivity" table as it has reached 5000 benchmark.') + logger.info('Loading the data to "RealTimeConnectivity" table as it has reached 5000 benchmark.') RealTimeConnectivity.objects.bulk_create(realtime) realtime = [] - logger.debug('Loading the remaining ({0}) data to "RealTimeConnectivity" table.'.format(len(realtime))) + logger.info('Loading the remaining ({0}) data to "RealTimeConnectivity" table.'.format(len(realtime))) if len(realtime) > 0: RealTimeConnectivity.objects.bulk_create(realtime) @@ -269,7 +269,7 @@ def get_latest_api_version(country_code=None): if qos_schema: schema_tables = client.list_tables(qos_schema) - logger.debug('\nAll tables ready to access: {0}'.format(schema_tables)) + logger.info('\nAll tables ready to access: {0}'.format(schema_tables)) for schema_table in schema_tables: table_name = schema_table.name @@ -288,7 +288,7 @@ def get_latest_api_version(country_code=None): ) table_current_version = delta_sharing.get_table_version(table_url) - logger.debug( + logger.info( 'Country "{0}" current version from API: {1}\n'.format(table_name, table_current_version)) version_for_countries[table_name] = table_current_version @@ -325,10 +325,10 @@ def check_missing_versions_from_table(country_code=None): missing_version_list = list(set(must_version_list) - set(versions_list)) - logger.debug('Missing versions details for country "{0}" are: \n\tStart version from DB: {1}' - '\n\tEnd version from API: {2}' - '\n\tMissing versions: {3}\n'.format(country_iso_code, start_version, end_version, - missing_version_list)) + logger.info('Missing versions details for country "{0}" are: \n\tStart version from DB: {1}' + '\n\tEnd version from API: {2}' + '\n\tMissing versions: {3}\n'.format(country_iso_code, start_version, end_version, + missing_version_list)) class Command(BaseCommand): @@ -388,7 +388,7 @@ def handle(self, **options): country = None if country_iso3_format: country = Country.objects.filter(iso3_format=country_iso3_format).first() - logger.debug('Country object: {0}'.format(country)) + logger.info('Country object: {0}'.format(country)) if not country: logger.error('Country with ISO3 format ({0}) not found in proco db. ' @@ -398,7 +398,7 @@ def handle(self, **options): if check_missing_versions: logger.info('\nChecking the missing versions.') check_missing_versions_from_table(country_code=country_iso3_format) - logger.debug('Checking the missing versions action completed successfully.\n') + logger.info('Checking the missing versions action completed successfully.\n') pull_data = options.get('pull_data') if pull_data: @@ -411,7 +411,7 @@ def handle(self, **options): pull_end_version = options.get('pull_end_version') if pull_start_version and pull_end_version and pull_start_version <= pull_end_version: - logger.debug('\nLoading the api data to "data_sources_qosdata" table ***\n') + logger.info('\nLoading the api data to "data_sources_qosdata" table ***\n') for version_number in range(pull_start_version, pull_end_version + 1): load_qos_data_source_response_to_model(version_number, country) logger.info('\nData load completed successfully.\n') @@ -445,24 +445,24 @@ def handle(self, **options): date_list_from_versions = qos_queryset.order_by('timestamp__date').values_list( 'timestamp__date', flat=True).distinct('timestamp__date') - logger.debug('Date list from versions: {0}'.format(date_list_from_versions)) + logger.info('Date list from versions: {0}'.format(date_list_from_versions)) for pull_data_date in date_list_from_versions: - logger.debug( + logger.info( '\nSyncing the "data_sources_qosdata" data to "connection_statistics_realtimeconnectivity" ' 'for date: {0}'.format(pull_data_date)) sync_qos_realtime_data(pull_data_date, country) - logger.debug('Data synced successfully.\n\n') + logger.info('Data synced successfully.\n\n') - logger.debug('Starting finalizing the records to actual proco tables.') + logger.info('Starting finalizing the records to actual proco tables.') monday_date = pull_data_date - timedelta(days=pull_data_date.weekday()) monday_week_no = date_utilities.get_week_from_date(monday_date) monday_year = date_utilities.get_year_from_date(monday_date) - logger.debug('Weekly record details. \tWeek No: {0}\tYear: {1}'.format(monday_week_no, monday_year)) + logger.info('Weekly record details. \tWeek No: {0}\tYear: {1}'.format(monday_week_no, monday_year)) - logger.debug('\n\nFinalizing the records for country ID: {0}'.format(country.id)) + logger.info('\n\nFinalizing the records for country ID: {0}'.format(country.id)) finalize_previous_day_data(None, country.id, pull_data_date) - logger.debug('Finalized records successfully to actual proco tables.\n\n') + logger.info('Finalized records successfully to actual proco tables.\n\n') else: logger.error('Please pass required parameters as:' ' -pull_start_version= -pull_end_version=')

    Pt||wMzx9^w zx#3J&RwSw3V?-mFXsWMHW^$b@DI`mhat6F2`+dJ~U>!D9#rhXXO9QARJ99AM|BO952Kl;+3Fae{Q9+)e7jj7|N5Ej{TgBMfvnB(kR|f_vu+NPi@(6 zE%L@6UG0L^j77A(v_Q>Yq_X@lpJZFOZi)wvQeGbS7cZ?oj{e2P`ivyjVxTC-Lr5n7 z+CmBZU9v8sb|jtm)3X>gUZldt;My!RG>ywj3Rj|~Vmj0mK&v33u?72ZWz@y|!Gj~E zON+2hj)!r_3B>^VfAk>pmoCAINMpurWSFZ9-~cT|M_js3zTcHskvpe=7!h=GWlRe~ zMgR<%`<*G-gUT1kIEus%)x~fEK_ToS0yJL0dBM3^8NX6O%->4*3d{3?D==|eY6d*1 z7cjw731yQHCf>;B5KK96QSkYIo&Z{4gLL z9l(cO*g4zKFuT*?50&YIlamKaqjV%#M0cKl2sYg}01_@)ViX+JDLb%T$>yQ`@`$Gx zMB@VFohZz9*>VX0n?*#}$})!a7o;jUHSl|&dmK-}irDWr?=jltkoQ;K5M%6EZcAFc zp*cDnjc?muTWe^VW~@EQx{NgpN^NayEc_$AU14wVceKpJBjZLBMM}ts!j~l21=;PX z{MK}b-r5q5VP(PpnDKkPJ{~+0@Ou4qJRZbvd;P5|d2@<}buO~rN+q{8gl`Yjnp!wG zZXq81SIhqmwy>{45+qNO+eh5AjD|t#L5CE0??8v3zPV<9lr6GOFWY+BWfP!OG>n!%u{qHjXMs8%OirY6sR3(V<| z(%Nu77JZw?_Wp0tqCaB2xv52O7BhNl(|6B3ZilE*sFUk25a$zRLaN2|2c)Bo?d^^C9IB5;x70t;-g_)nAA3;K_w5tK2Ytpd zpBa^PNqQz`dP0);0n^aF;J0o67pN5pw9!gX3W;PPX{N*N+uMzhXUmqjXUjkWi=e(8 z%P4hX537@W-}Wd)-KtaBun~?}OdOOwmTK12@Sa`iZnqDJ<|hy{4GTNMT?TTnCXnz! zY9pr@=#ZwqScr$cVFIJIyrDiufwL6vgl0D)_M)DNouS2XSw0_Y$~VshzuwdhDKDFg zJYYSkc}qXkL*Bm(MYXsL`D!K*)6aXo$L+u$);B-8KPxuHn)5x~O?s@Usr1CbXyC`t z)4ZX^;ILuqv4F1Rl(>)cDVoy@pm~1^IeiTH%EcpKt4ISvavvA)V6-FgsaR;hFd~*4 z61*5IXa!oE%H4!Kfk(QSiV=ja>?9mJu#JbJgCfk%>=lo=&AhJjezv3Iu2?wH5|?uI z%va-&WNT$xQCe$NUp%6;`{Zasu951*dMTQal2WbQ4UI!?mlASPu8%0qh~Un95~_J; zV;j3a(XO$B!H8~L#iHCJ(PVw3LBG}X)MLes9B7EhiZ>A`#A;HKQ4Zt zI}p(j!%kHPzL+OLFtkEEIvS?aMowbqgPbS>5r{R(y^{6jjvN0=7og~vCY z)r74DkUS;pw3xXlPV{fZx3n_hxx1cUt+|&Y3gO~D$BArlHG>lbT;o142MEJXG;I~@ zluYcisqh(22Xe(+Q!LV1`URG1sb^H7nL4}SlspRb8_`a4Y#)eIPic4Bf+G&xYR z$Tdhp!<-XK_ZxDURg4725<2q1VfRH^YIyH`I~OV@2DW(OiEyS-3mKpW;dIZ++~%Z| zCcNd52h~JK9cF5sr56>?x2boTpxNo@KHf*EW4-N9)Nk=59->8Ev&0^d&Mk2>!C4v2^^utk zl1r6ZQxGLpYS*Nmq^9I#U6&7?Xc4z1m7u?_TB%6`7)ib3FxjqNRkYjtqf%>=>D7mc z);qpLniF-9^!I?$adFe}=(i^ibAt`xHletgw41R=J1byy{=9>`Dqo5!-O3)6w)v%J#nQNCRI(aj-x$Pu0X z`kJ6@&~J|a5ok?*NAN>(Is!Ri7O8TaT?;n0C1B`Y);>}mUiVy%c)C{nPI;J}2&l&Q8u|gge70jsN0837L!$l+OKcDD^|YO5 zosb^VyWKTB^+FRoqOi$1P`bhfw?;ugMyL04U#}8~1z52^peX?@9T0m|GyHy7GR#kX zLXs-drO(ZmUVIA#^2N6>b_uK0o{8!MlC)zd!*cUG%IR&TA9t~a(oecsLq~}E{mL;1(g@- zLr~+_Rn(-#(0b?{I83i5s|m?R0G**#AE+;?Z8lh6EvyM!!xE}0NZ6~DfkU5Lt&`;{ zf{q?qc@t_RAIq{h58Df^k~%1y5YD34`zf|$APvdI#`+>!00^BW%}Hqhay95@OehCC z6dZdgc+O5JCTg0N2gIU$4Jj@CgHI#PzMDp1lZ|b!@((_Ap`}wn=)el@5d& zqM_Je>~;fj=ED#4_+x(;3zTlL>5@IAsFOypv)%}c(cD%hv7xranAK$ZGltO+SC#Xf zJ0$6_&)TJHYD6nq9*@WJo|NUY&f~CKguGre=`*yrKOlw7H(%wdH%Ia~0xujDTpI$f83$s?4oTEFRl6Ym2&@hvTwm33Mj`I}6lfdHQnpalNt0n_< z0whCzpEyc6YFH!t0VX_cb3sRRmzrju4~H%Ii10 zeZE>bk@FfXISl4VU&oxVLheg1(lU(~@kBQNj7JZ}Q@WmthjfpitG_&+lJ}t&qkq}m)AFDSoiDrRG-|rUm5eB zs8tkK>1R@1mhSrjh78au+p>cVUZzFS%ds2&>)O_aRmUve~Lw#ds4a;N@ zLXv46mNLy5X_y`5JIWm8zSO^P306;5G)jj*PlG~N}=hCuq8U`n}pYcZyYts*?zT8$>ani6l` zI($i%7UiIW^g(%KBQzQ1=(K9PpmrgGXkB|?lO{q|Wot)o5j9%#)(3w2Q*;vJ{UpE= z;=w?}f5KIM3?Q63JP?j8!2XnU0%Fmxm3GH^ir#V|*vRe(STX_F$cbF6w=^!67BG$r zi{Zq^HpUlU9A(3>NG=KtdrvK}Z$O6sFy!|J?C=Doom}-uWV^+P>waBf!X&9?AuSCM z+azUbC&U&oPLf>dg6uoRoRbAU=t{Gkal*eM!VJQhZYQ`}dEi9wcc+i>>pjL{&pgEV6&wnwmVGike!K&1NbE}DH^wMfghy!~+7EiNGG7{p0>#t;$vsi`d5))qyO9>O0*>=tA5>X6>7Hjn2w zt*lIA$QJ1lI>EWO3fEz*D@b$5VLI3esLZTf3l)VpfaXBS*7~`C69V6=oWcxJ0_^jT zQ!D)r)xj_r+N~+92Ta(h&jue4iqbvO*Gh{})lf3&uErSQdo+|TbHd|XY>maX*4Nid zpg$v}%RNp(qBle%9eNW7y~%^#T+emQ3R^tZNIO!-4i@6JG|W>l)q!e-q~8evEc9uF zcJz%ju|nB?cwJ4%o9GpC~b#zTBPW-o=voYM9ErNyM5gDp^$`Oj6=WFvI? zxr!OJj7*SH9Pj0eKHpJ(3Ix*8m*+9pD8im>_|@PILJB|**bZN^5enpK=EL4WN`bS2 zfcBy)6Za92tpX36eMoJCApe-x99!0^Ek`U*Sv873@A#1j*W(xXWD|e@ab?d;2 zj%f)5hLZ$}U#k@QKMp}-54Hvx*Y$Bl)%R3O``<)}qR+YK+u&UV&b`!Bvl}VRw@d(u z;bep?<^6p+XPT^=2WEDz);4FozCz6ZQ8f^phF0n}XxCbOg8Sq_ez@u30wY%}2yS^|Nc_A??SR!PVDGnlPut-q+na{aNQR!p-ORT;xn`k7=& z*e^^OM|j;$Tl0HM|C;|={#!fu=Eon}+dnoIcqq>nl#7~H`lKwg+coW?(lg5cE?m0w z^2?VlahVs4l5{BzYeWW!k_c{uEzFheaEbw6fibjGN)%QfXJUeefW}nDPV}<@XkSgB zH;9{$H028{&gH9jfI-`u>cjRRi0ly|)D7#el9%S!#4EjGPhEx_{&g94DZ_ebE0nuV z`1PaMOGLs_YFlDORKTDEm3VmuK3iJ4L=W%-pM#6Aa}$rR+rVZ)YB#iQXSU_8vENv= z6yR*AcTt_(u>*z&-M~qY8gyE{S}P;CYdZgNqq6RkpnWU*8aZ*z?G(>gUT3)W%;i{j zE1S)CJ$f5o7Fl_G?lt#8T5^PxisPTY_Qps=w^wCvl5Mdbdad=cxoUN3v5<9T+aj$u zAc{cPM0+@Kf>7RUu~4Lc1WHk+fb>M0Ke_WjjA*`PE=h^&?C-s8*&guFG-mzSR-stKw_!-=yrp$_gvf z=UuU$$EB=}C#`$VAwH#uT{jVhvvyop*9$m?oAMmX(kP-E5V*>@RylvOwZ2T_p?TZ6 zzL@1~`Y4a+8>qgMe`u*Z_oo9{KSJ5u#zy1+~D!gr_Y3>Zv&l4a2T z0^X{5@2oQ~AhvcFxWx^K0zFLYj@I^j*}f2U=f75#NnIV6$Mr<}iwJLkL5Z%AIr&@4a}y zt5v&*gYyo|JbDulKd(ecCG$C_H&k7Hqe4h?p}+Meg~R?lSYj##=D6ngL*8O{&s?V& zT`6kCy8g_Q)Ug(S*7hfspA5;6URRk2LTqW%$<>T&cJZHTjOiZRr-jlY^J1+<7TZfb ztuZm3t;o1W=N}O@D;mUTHE6OLh(-gdC~6I@tCuV7`X*m5)s9x(fl=a21Q-zsOSB#@ zOUMBQ7|2p6vqNedwFIHl&a6f}qpD>wGkCa9KG)YwT(hbR*mpV_yELuqmD)m1<^$2n zk!rNuHq~w0xJCVPa?dtf>e~iO)idWjwWXg?>rtYrOIR{hleo^~xh_#t`X}s?M}y^+ zJX)&j6OvNd;*LzqN!lqnl?WzDpuerDQF8jDqK7K!m=`pR^t`>$^LW$CI7b!-v>m(% zaYfu(Q6wHq9Rn05cQB6PTD)b)cvH(5IK9u07=v0HCV{x^4iUa`15|DA)VERLb^?QY{|b>#LW8qQ1{xD>2nJG{h3A=H*I!UK#81 z-qIbyw%5jCGN=FnAW4JmLIEc)6&YlJ!q>#zFMGA7Ce7AIRCVO<>0luA2RYtrpli2J zZ)wqeN{?hqJ^X;ZUU?RDj}mkbk=qaIc|7!uh-o?n+JUiweIJwy27DkOhMs!V!iJA;i0trS6nNO^&Uhz1p&1hPSiXnV16PSKbZ@^`2F zq1L)^W1_hK2$&|b) zns?+4NkImzu>3mP6_R~&D7@`(qsp}GFw>M`s98Wnt)A;;5-BKrNJax=e>6vpUESuF*sreR;C z=dl;>v#>$MxaPPAa=__o96}@m=RZXAuwu|gz!Y$&0UP3Fvf7YHtBbQ5Z5RwDiDtWm zQC&wDxz12U{VI@1yonF7MZ-Ah38p9CJedx9YVMu6xAYNDPQC8To6lUQW-ZV3#p%B* z_a7&fp|zCqzi&ho7go{3LE2jhA4ihDzQMshU$S>_@Y75mIx#qSVyD6OkB+|T^eLyC zk5-e-)HP|O1}vheiwYT5fq9jvArHXv!PN%4+`E;$9$W9Nu{|B(c$rni!yTTNE2Wf5S(e(jA3F-J%#;5}`+^R=1cLze)SL}H z_@s&bds_;IK{V7Lf_jmNs!L`6W36Vv^#s?=!F1uiIY(y+APTt&k+!|i($AL+FBduy?pmTS`3hQ!CD%tzK~Gauya@i#{lW|?7ZJkC{Z)zE|S z{5mD)Pxz4hJJF|-wvyi_!)@GTm8$Hff}_1f;m;#cs!TuD?Pij1DVq?ShTp*v`{^qA z;o}v80kdE~FjTT1sJans=D2*gLRxS&epjxok^^W=w^D>?nGeJeumy<@W<$;nDJGU! zKAqtoZ;;kPTh>+ofCKsQbh;Jm{6(e)B9VYB(@}+=#9tTmID_T>QiaEvtbPvb8=|%y zMc9|BO7NdRcJYXapw@L!gS2N@K9=Ew;==zC{tWPQb)J&vI1Skq^At9|7`NiOHXFH& zgXJI4$h<}afxDFz6h02&+mcL+2(8xC{{m__^E0z0`~g?OG)7Gf#3HcCP)@jr-8 zns%vtoWx4EYUF@%6T7TpK^x)ggI3jY+Og5Pe(4AIGj)WwQ_*bu(JF39QUr ztT}grWWoL2oz>U@Qc=!b<~aR1e*CbeGxfSIyX!iLIct85<=jiWD;%BpHbd>)zP(dL zEJrmw6cdHjfaQ5vh6mSG=t;H{u828Ek9~|ch3`T&Hogz&%r!}iYnDI07R0OvsOLxq zz}yDNZ;l$tvu;{#!kRWDz%vk&x3&!-hbj_XAqm_OAW6fx7WAJhYZI`|zg*^f;DZ;h z3AenWPbfb97``hC#;q$*SNA*dxeZ_ruH|_UamW@2YQA}oKUKTJ!(40daY-Fb`^8m1a zgk3oX0a&3S?UgN-nrjqTy|<~&QrzCww!N*GiUmbj7scQ(uG+Tmga6abSRh;F1_sM7 zxdj7ZOO~mOEL-6~H?MAgAQr34zgRY0R*roWtnAgY>`DL*oONUigcXFRBH6MT1e1@p zX2SZSHLSkM1kK*D1q5OXUfOx06UWc_RKs#9WT@5)0y({g=MVAic--N+vTcL}*O6^A z)U4mDsrfxR_qiW-i{sTSKR$+y1Ms6P)##P(!VZ{lHq@gBhB;0URauwW+I>Plvfi%b&0easv5{e_ zx-qp?JBYrATh=NpNzu#-ujV$ljrmP(l{T)Mxz)Dv$_e}#?wN2zC&xx!4cZ_d`#17| z;N85A*AZRDFJYDGa{1lOm3j)CL93)$b!7#Z>S6qG}nnB`wr<)d`sr<2< zmV^{@e#?{m#C7Z*H(wF5?0Gk>3URT?O`Af8c+pM6Dj;5Q(|#c&^}6YRph>s8>7Y<2 z{jQsipv?d7rlTnHYj}2<3`P#DrHBJcGeCTcn-=jax*v1X66}g!anrI;U|Bb<2nX3C zZdw(3*gv~zQ#dMabJHH7OZ=*v_6u3baMJ<7macQtL7^bs?WQ9r^IzO_6lMMqRBj6E zhaMJYVEs4=E7~05!&=GV?wl|CGGo(CH-F0m=&Tv^ z|7i}#Hk7VxLaQM3_xEc+NX{4rChBpbwX>Z`OC^L)lgk@v68pnAK`BOlH z`%z{N{UIDaHIaO9;#h7rc?Q34ot&PUCM-yvK0b9eIXf|z96LLnoST}Oy#MqZN)yV? z;Z?IRVRz&I1b@vOs;@-ZF8=y6C_XngJ-e;D`vlrG=HP7C@hRLW_e>HhaOIQLL%b25 z{y&YM8@*$7F8sd%C#?aYzvaJyb!Vf$r;*775u8tAG6*;f)J$h&%Jwjec@Y@tV}2H3 zK^9_R>~?^CCRv=-uv%8f5?Crnu8<^a!n(jTYi1diWjWTuT3H*`f_1P?xKX-U59?)p zte@rC0M=C(*%r1Hzy3YQwqpa~A-Eof*)F!5?O}V_b!;EIp6zEhup5PUvjg~@@tfGq z><}Aax3F8;ZR{|+ogHDX6W+y+ve&aa*q!Vyb~n3+-OEPV7(2#}vvD@TPOy{g6g$oC zWB0R3c7~m0Q*4^Ofz7a4HpkAf2iSw`A@(qPguRhH%HG7@%xHnlZ?U(sx3Tl=?d&o3 z+w2|eo$Oug-RyDp9`;`LJM02`f<4LJ$1bwp#nK7<+WX9$JUcfwBcGd{m{E@7_ucQ; zCr{7Lb&gHW$tNbqPU^>|rtUv8Hgmsn`pnqL33+1t^qe(!`pm>^=ke1s$0sMOvPkUcgLq5JS!gu!;ojk9+*&P-*9ehW zkWT<0oetFF#@wlMXO7Kwo}2c#DXK=AJ$F_;n~(D8{$A5TtCJvcGL$HKYAF~@Sim^psx^aB%4{@A(k(^F3J zfz#s?Q%;q~r^YAH>nA2KkY`VNrpKlyW;)SvXD8GX=jM)0O&Vt>9-QUbCJl*$TLIwi z?8G<_Vw&&|Pg+yQ?wdG1*LiYg>fE&LUYtA2FFiog+{{T} z>8!yqWd<|BbZ~|MaL{M=`03f%shL?1zcG3G5e%+VXm%RqtyLVGm~kJT7&|`UEssB% zacLs67(wVVok2?uy46W%g%v5(!(2A*PH_f32;@m&sEu$`1+uYG%TiJv;Rv$i@8=#@q~MI)SS4VV^aQPoAFU(;zZ7gQ-Ec+_!QxUY|>-7hPAD zBXnq8_PJ^4_s zZgg^Jl*zGkCr_FD$O~9b(y77opFVqTW_srI?1VCR8bq16Gh1E;$7V>;M*mTK=}HoQA7@yE%G{in5Pta6(qAD@ N_?!Gs9&yi){$ISCfQtYC diff --git a/proco/assets/vendor/fontawesome/webfonts/fa-regular-400.woff b/proco/assets/vendor/fontawesome/webfonts/fa-regular-400.woff deleted file mode 100644 index c390c60e2b0f2540c63cb77a25ebf51104cff8e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16780 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 diff --git a/proco/assets/vendor/fontawesome/webfonts/fa-solid-900.eot b/proco/assets/vendor/fontawesome/webfonts/fa-solid-900.eot deleted file mode 100644 index 52883b93c83a70d6169a1bd0910b9d07f93d90f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 204266 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�m#pFm8%s1LNvoc01S9nxjMeyoT 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