From 4d63974dd450f88a2304180b53c96898bd053c3a Mon Sep 17 00:00:00 2001 From: thenav56 Date: Wed, 23 Feb 2022 23:41:47 +0545 Subject: [PATCH] Add CORS in nginx - Fix flake8 issues. - Add missing environment variables used. --- .circleci/config.yml | 3 +- .env-sample | 4 + .gitignore | 1 + api/admin.py | 35 +++--- api/esconnection.py | 7 +- api/logger.py | 33 +++--- api/management/commands/index_and_notify.py | 97 +++++++++++----- api/management/commands/ingest_mdb.py | 43 ++++--- api/views.py | 30 ++--- docker-compose.yml | 119 +++++++++----------- main/frontend.py | 9 +- main/nginx.conf | 6 + main/runserver.sh | 1 - main/settings.py | 41 +++++-- notifications/notification.py | 43 +++---- registrations/admin.py | 52 ++++++--- registrations/views.py | 42 +++---- 17 files changed, 324 insertions(+), 242 deletions(-) create mode 100644 .env-sample diff --git a/.circleci/config.yml b/.circleci/config.yml index 19fd5b857..fd2163250 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,8 @@ jobs: name: build docker base image command: | export DOCKER_BUILDKIT=1 - docker-compose build --progress plain base + cp .env-sample .env + docker-compose build --progress plain docker-compose pull db redis - run: name: Validate if there are no pending django migrations. diff --git a/.env-sample b/.env-sample new file mode 100644 index 000000000..03355ccd2 --- /dev/null +++ b/.env-sample @@ -0,0 +1,4 @@ +# Required +DJANGO_SECRET_KEY=RANDOM-STRING-FOR-SECRET-KEYS + +# For other, look at main/settings.py:env for available options. diff --git a/.gitignore b/.gitignore index 3844aa23f..71bd79bac 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ env # secret settings env variables .env* +!.env-sample # python stuff *.pyc diff --git a/api/admin.py b/api/admin.py index 3ae794a78..d21ef1394 100644 --- a/api/admin.py +++ b/api/admin.py @@ -1,4 +1,3 @@ -import os import csv import time from django.contrib.gis import admin as geoadmin @@ -7,24 +6,25 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.html import format_html_join, format_html from django.utils.safestring import mark_safe -from api.event_sources import SOURCES -from api.admin_classes import RegionRestrictedAdmin -from django_admin_listfilter_dropdown.filters import RelatedDropdownFilter -import api.models as models from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User +from django.http import HttpResponse, HttpResponseRedirect +from django.conf import settings +from django_admin_listfilter_dropdown.filters import RelatedDropdownFilter from rest_framework.authtoken.admin import TokenAdmin from rest_framework.authtoken.models import Token -from django.http import HttpResponse, HttpResponseRedirect -from .forms import ActionForm +from reversion_compare.admin import CompareVersionAdmin # from reversion.models import Revision -from reversion_compare.admin import CompareVersionAdmin +from api.event_sources import SOURCES +from api.admin_classes import RegionRestrictedAdmin +import api.models as models from lang.admin import TranslationAdmin, TranslationInlineModelAdmin - from api.management.commands.index_and_notify import Command as Notify from notifications.models import RecordType, SubscriptionType +from .forms import ActionForm + class ProfileInline(admin.StackedInline): model = models.Profile @@ -423,16 +423,18 @@ class AppealFilterAdmin(CompareVersionAdmin): list_display = ('name', 'value') search_fields = ('name', 'value') + class UserCountryAdmin(CompareVersionAdmin): - list_display = ('user','country') - #search_fields = ('user','country') + list_display = ('user', 'country') + # search_fields = ('user','country') model = models.UserCountry + class UserRegionAdmin(CompareVersionAdmin): - list_display = ['user','get_firstname','get_lastname','get_email','region',] + list_display = ['user', 'get_firstname', 'get_lastname', 'get_email', 'region'] def get_firstname(self, obj): - return obj.user.first_name + return obj.user.first_name get_firstname.short_description = 'First name' get_firstname.admin_order_field = 'user__first_name' @@ -446,10 +448,10 @@ def get_email(self, obj): get_email.short_description = 'Email' get_email.admin_order_field = 'user__email' - - #search_fields = ('user','country') + # search_fields = ('user','country') model = models.UserRegion + class GeneralDocumentAdmin(CompareVersionAdmin, RegionRestrictedAdmin, TranslationAdmin): search_fields = ('name',) @@ -783,5 +785,6 @@ def get_actions(self, request): admin.site.register(models.UserCountry, UserCountryAdmin) admin.site.register(models.UserRegion, UserRegionAdmin) # admin.site.register(Revision, RevisionAdmin) -admin.site.site_url = 'https://' + os.environ.get('FRONTEND_URL') + +admin.site.site_url = 'https://' + settings.FRONTEND_URL admin.widgets.RelatedFieldWidgetWrapper.template_name = 'related_widget_wrapper.html' diff --git a/api/esconnection.py b/api/esconnection.py index ee04c3248..c6d6c01ec 100644 --- a/api/esconnection.py +++ b/api/esconnection.py @@ -1,9 +1,8 @@ -import os from elasticsearch import Elasticsearch +from django.conf import settings -host = os.environ.get('ES_HOST') -if host is not None: - ES_CLIENT = Elasticsearch([host], timeout=2, max_retries=3, retry_on_timeout=True) +if settings.ELASTIC_SEARCH_HOST is not None: + ES_CLIENT = Elasticsearch([settings.ELASTIC_SEARCH_HOST], timeout=2, max_retries=3, retry_on_timeout=True) else: print('Warning: No elasticsearch host found, will not index elasticsearch') ES_CLIENT = None diff --git a/api/logger.py b/api/logger.py index 4a27790d0..242cefcc1 100644 --- a/api/logger.py +++ b/api/logger.py @@ -1,10 +1,14 @@ import logging import sys -import os + +from django.conf import settings + from azure_storage_logging.handlers import BlobStorageTimedRotatingFileHandler as storage -formatter = logging.Formatter(fmt='%(asctime)s %(levelname)-8s %(message)s', - datefmt='%Y-%m-%d %H:%M:%S') +formatter = logging.Formatter( + fmt='%(asctime)s %(levelname)-8s %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' +) screen_handler = logging.StreamHandler(stream=sys.stdout) screen_handler.setFormatter(formatter) @@ -13,16 +17,19 @@ logger.setLevel('DEBUG') logger.addHandler(screen_handler) -if (os.environ.get('AZURE_STORAGE_ACCOUNT') is not None and - os.environ.get('AZURE_STORAGE_KEY') is not None): - handler = storage(account_name=os.environ.get('AZURE_STORAGE_ACCOUNT'), - account_key=os.environ.get('AZURE_STORAGE_KEY'), - filename='go.log', - when='M', - interval=90, - container='logs', - encoding='utf-8' - ) +if ( + settings.AZURE_STORAGE_ACCOUNT is not None and + settings.AZURE_STORAGE_KEY is not None +): + handler = storage( + account_name=settings.AZURE_STORAGE_ACCOUNT, + account_key=settings.AZURE_STORAGE_KEY, + filename='go.log', + when='M', + interval=90, + container='logs', + encoding='utf-8' + ) handler.setFormatter(formatter) logger.addHandler(handler) else: diff --git a/api/management/commands/index_and_notify.py b/api/management/commands/index_and_notify.py index 01b073d77..93e745e2d 100644 --- a/api/management/commands/index_and_notify.py +++ b/api/management/commands/index_and_notify.py @@ -1,10 +1,13 @@ +import html from datetime import datetime, timezone, timedelta + from django.db.models import Q, F, ExpressionWrapper, DurationField, Sum from django.db.models.query import QuerySet from django.core.management.base import BaseCommand from django.contrib.auth.models import User from django.conf import settings from django.template.loader import render_to_string + from elasticsearch.helpers import bulk from utils.elasticsearch import construct_es_data from api.esconnection import ES_CLIENT @@ -14,8 +17,6 @@ from notifications.hello import get_hello from notifications.notification import send_notification from deployments.models import PersonnelDeployment, ERU, Personnel -from main.frontend import frontend_url -import html time_5_minutes = timedelta(minutes=5) time_1_day = timedelta(days=1) @@ -123,7 +124,8 @@ def gather_subscribers(self, records, rtype, stype): subscribers = User.objects.filter(subscription__rtype=rtype_of_subscr, subscription__stype=stype, is_active=True).values('email') - # For FOLLOWED_EVENTs and DEPLOYMENTs we do not collect other generic (d*, country, region) subscriptions, just one. This part is not called. + # For FOLLOWED_EVENTs and DEPLOYMENTs we do not collect other generic (d*, country, region) subscriptions, just one. + # This part is not called. if rtype_of_subscr != RecordType.FOLLOWED_EVENT and \ rtype_of_subscr != RecordType.SURGE_ALERT and \ rtype_of_subscr != RecordType.SURGE_DEPLOYMENT_MESSAGES: @@ -136,7 +138,9 @@ def gather_subscribers(self, records, rtype, stype): lookups = dtypes + countries + regions if len(lookups): - subscribers = (subscribers | User.objects.filter(subscription__lookup_id__in=lookups, is_active=True).values('email')).distinct() + subscribers = ( + subscribers | User.objects.filter(subscription__lookup_id__in=lookups, is_active=True).values('email') + ).distinct() emails = list(set([subscriber['email'] for subscriber in subscribers])) return emails @@ -148,21 +152,26 @@ def get_template(self, rtype=99): # Get the front-end url of the resource def get_resource_uri(self, record, rtype): # Determine the front-end URL - resource_uri = frontend_url - if rtype == RecordType.SURGE_ALERT or rtype == RecordType.FIELD_REPORT: # Pointing to event instead of field report %s/%s/%s - Munu asked - ¤ - belonging_event = record.event.id if record.event is not None else 57 # Very rare – giving a non-existent | manually created surge – no event - resource_uri = '%s/emergencies/%s#surge' % (frontend_url, belonging_event) + resource_uri = settings.FRONTEND_URL + if ( + rtype == RecordType.SURGE_ALERT or rtype == RecordType.FIELD_REPORT + ): # Pointing to event instead of field report %s/%s/%s - Munu asked - ¤ + belonging_event = ( + record.event.id if record.event is not None else 57 + ) # Very rare – giving a non-existent | manually created surge – no event + resource_uri = '%s/emergencies/%s#surge' % (settings.FRONTEND_URL, belonging_event) elif rtype == RecordType.SURGE_DEPLOYMENT_MESSAGES: - resource_uri = '%s/%s' % (frontend_url, 'deployments') # can be further sophisticated + resource_uri = '%s/%s' % (settings.FRONTEND_URL, 'deployments') # can be further sophisticated elif rtype == RecordType.APPEAL and ( record.event is not None and not record.needs_confirmation): # Appeals with confirmed emergencies link to that emergency - resource_uri = '%s/emergencies/%s' % (frontend_url, record.event.id) + resource_uri = '%s/emergencies/%s' % (settings.FRONTEND_URL, record.event.id) elif rtype != RecordType.APPEAL: # One-by-one followed or globally subscribed emergencies resource_uri = '%s/%s/%s' % ( - frontend_url, - 'emergencies' if rtype == RecordType.EVENT or rtype == RecordType.FOLLOWED_EVENT else 'reports', # this else never occurs, see ¤ + settings.FRONTEND_URL, + # this else never occurs, see ¤ + 'emergencies' if rtype == RecordType.EVENT or rtype == RecordType.FOLLOWED_EVENT else 'reports', record.id ) return resource_uri @@ -191,13 +200,16 @@ def get_record_title(self, record, rtype): sendMe = sendMe + ' (' + country + ')' return sendMe elif rtype == RecordType.SURGE_ALERT: - duration = (record.end-record.start).days + duration = (record.end - record.start).days if duration > 29: durationMonth = (record.end - record.start).days // 30 duration = f"{durationMonth} month{'s' if durationMonth > 1 else ''}" else: duration = f"{(record.end - record.start).days} days" - return f"{record.operation if record.operation_en else record.event.name}, {duration} starting on {record.start.date()}" + return ( + f'{record.operation if record.operation_en else record.event.name}' + f', {duration} starting on {record.start.date()}' + ) # go-frontend/issues/2041: del ' (' + record.atype.name + ', ' + record.category.name.lower() +')' elif rtype == RecordType.SURGE_DEPLOYMENT_MESSAGES: return '%s, %s' % (record.country_deployed_to, record.region_deployed_to) @@ -296,7 +308,10 @@ def get_weekly_digest_latest_deployments(self): # alert_to_add = { # 'type': 'Alert', # 'operation': alert.operation, - # 'event_url': '{}/emergencies/{}#overview'.format(frontend_url, event.id) if event else frontend_url, + # 'event_url': ( + # '{}/emergencies/{}#overview'.format(settings.FRONTEND_URL, event.id) if event else + # settings.FRONTEND_URL, + # ), # 'society_from': '', # 'deployed_to': '', # 'name': '', @@ -313,7 +328,10 @@ def get_weekly_digest_latest_deployments(self): country_from = Country.objects.get(id=pers.country_from_id) if pers.country_from_id is not None else None dep_to_add = { 'operation': event.name if event else '', - 'event_url': '{}/emergencies/{}#overview'.format(frontend_url, event.id) if event else frontend_url, + 'event_url': ( + '{}/emergencies/{}#overview'.format(settings.FRONTEND_URL, event.id) if event else + settings.FRONTEND_URL + ), 'society_from': country_from.society_name if country_from else '', 'name': pers.name, 'role': pers.role, @@ -329,7 +347,9 @@ def get_weekly_digest_highlights(self): events = Event.objects.filter(is_featured=True, updated_at__gte=dig_time).order_by('-updated_at') ret_highlights = [] for ev in events: - amount_requested = Appeal.objects.filter(event_id=ev.id).aggregate(Sum('amount_requested'))['amount_requested__sum'] or '--' + amount_requested = ( + Appeal.objects.filter(event_id=ev.id).aggregate(Sum('amount_requested'))['amount_requested__sum'] or '--' + ) amount_funded = Appeal.objects.filter(event_id=ev.id).aggregate(Sum('amount_funded'))['amount_funded__sum'] or '--' coverage = '--' @@ -340,7 +360,9 @@ def get_weekly_digest_highlights(self): 'hl_id': ev.id, 'hl_name': ev.name, 'hl_last_update': ev.updated_at, - 'hl_people': Appeal.objects.filter(event_id=ev.id).aggregate(Sum('num_beneficiaries'))['num_beneficiaries__sum'] or '--', + 'hl_people': ( + Appeal.objects.filter(event_id=ev.id).aggregate(Sum('num_beneficiaries'))['num_beneficiaries__sum'] or '--' + ), 'hl_funding': amount_requested, 'hl_deployed_eru': ERU.objects.filter(event_id=ev.id).aggregate(Sum('units'))['units__sum'] or '--', 'hl_deployed_sp': PersonnelDeployment.objects.filter(event_deployed_to_id=ev.id).count(), @@ -470,17 +492,19 @@ def construct_template_record(self, rtype, record): volunteers += int(f.num_volunteers or 0) delegates += int(f.num_expats_delegates or 0) resource_uri, follow_url = self.get_resource_uri(record, rtype), None - if resource_uri != frontend_url: - # instead of '{}/account#notifications'.format(frontend_url): + if resource_uri != settings.FRONTEND_URL: + # instead of '{}/account#notifications'.format(settings.FRONTEND_URL): follow_url = resource_uri + '/follow' resource_uri += '#overview' rec_obj = { - 'frontend_url': frontend_url, + 'frontend_url': settings.FRONTEND_URL, 'resource_uri': resource_uri, 'follow_url': follow_url, 'admin_uri': self.get_admin_uri(record, rtype), 'title': self.get_record_title(record, rtype), - 'situation_overview': Event.objects.values_list('summary', flat=True).get(id=record.event_id) if record.event_id is not None else '', + 'situation_overview': ( + Event.objects.values_list('summary', flat=True).get(id=record.event_id) if record.event_id is not None else '' + ), 'key_figures': { 'people_targeted': float(record.num_beneficiaries), 'funding_req': float(record.amount_requested), @@ -513,7 +537,7 @@ def construct_template_record(self, rtype, record): 'admin_uri': self.get_admin_uri(record, rtype), 'title': self.get_record_title(record, rtype), 'content': shortened, - } + } else: # The default (old) template rec_obj = { 'resource_uri': self.get_resource_uri(record, rtype), @@ -698,13 +722,24 @@ def notify(self, records, rtype, stype, uid=None): if len(recipients): # check if email is not in events_sent_to{event_id: recipients} if not emails: - logger.info('Silent about the one-by-one subscribed %s – user %s has not set email address' % (record_type, uid)) - # Recently we do not allow EDIT (modif.) subscription, so it is irrelevant recently (do not check the 1+ events in loop) : + logger.info( + 'Silent about the one-by-one subscribed %s – user %s has not set email address' % (record_type, uid) + ) + # Recently we do not allow EDIT (modif.) subscription + # , so it is irrelevant recently (do not check the 1+ events in loop) : elif (records[0].id not in events_sent_to) or (emails[0] not in events_sent_to[records[0].id]): - logger.info('Notifying %s subscriber about %s one-by-one subscribed %s' % (len(emails), record_count, record_type)) + logger.info( + 'Notifying %s subscriber about %s one-by-one subscribed %s' % ( + len(emails), + record_count, + record_type, + ) + ) send_notification(subject, recipients, html, RTYPE_NAMES[rtype] + ' notification - ' + subject) else: - logger.info('Silent about a one-by-one subscribed %s – user already notified via generic subscription' % (record_type)) + logger.info( + 'Silent about a one-by-one subscribed %s – user already notified via generic subscription' % (record_type) + ) def index_records(self, records, to_create=True): self.bulk([construct_es_data(record, is_create=to_create) for record in list(records)]) @@ -732,7 +767,9 @@ def filter_just_created(self, queryset): record.updated_at.replace(microsecond=0) == record.created_at.replace(microsecond=0))] def check_ingest_issues(self, having_ingest_issue): - # having_ingest_issue = CronJob.objects.raw('SELECT * FROM api_cronjob WHERE status=' + str(CronJobStatus.ERRONEOUS.value)) + # having_ingest_issue = CronJob.objects.raw( + # f'SELECT * FROM api_cronjob WHERE status={CronJobStatus.ERRONEOUS.value}' + # ) ingest_issue_id = having_ingest_issue[0].id if len(having_ingest_issue) > 0 else -1 ingestor_name = having_ingest_issue[0].name if len(having_ingest_issue) > 0 else '' if len(having_ingest_issue) > 0: @@ -756,7 +793,9 @@ def handle(self, *args, **options): condU = Q(updated_at__gte=time_diff) condR = Q(real_data_update__gte=time_diff) # instead of modified at cond2 = ~Q(previous_update__gte=time_diff_1_day) # negate (~) no previous_update in the last day, so send once a day - condF = Q(auto_generated_source='New field report') # exclude those events that were generated from field reports, to avoid 2x notif. + condF = Q( + auto_generated_source='New field report' + ) # exclude those events that were generated from field reports, to avoid 2x notif. condE = Q(status=CronJobStatus.ERRONEOUS) # ".annotate(diff...)" - To check if a record was newly created, we check if diff --git a/api/management/commands/ingest_mdb.py b/api/management/commands/ingest_mdb.py index 2ef73bcd0..6c6599ece 100644 --- a/api/management/commands/ingest_mdb.py +++ b/api/management/commands/ingest_mdb.py @@ -2,14 +2,17 @@ import csv import subprocess import pytz -from django.utils import timezone from datetime import datetime, timedelta from glob import glob from ftplib import FTP from zipfile import ZipFile + +from django.utils import timezone from django.core.management.base import BaseCommand from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist +from django.conf import settings + from api.models import ( DisasterType, Country, @@ -27,6 +30,7 @@ REPORT_DATE_FORMAT = '%m/%d/%y %H:%M:%S' + def extract_table(dbfile, table): """ Extract a table from the Access database """ cmd = 'mdb-export %s %s' % (dbfile, table) @@ -54,10 +58,10 @@ def contact_is_valid(contact, field): def get_dbfile(): - ftphost = os.environ.get('GO_FTPHOST', None) - ftpuser = os.environ.get('GO_FTPUSER', None) - ftppass = os.environ.get('GO_FTPPASS', None) - dbpass = os.environ.get('GO_DBPASS', None) + ftphost = settings.GO_FTPHOST + ftpuser = settings.GO_FTPUSER + ftppass = settings.GO_FTPPASS + dbpass = settings.GO_DBPASS if ftphost is None or ftpuser is None or ftppass is None: if os.path.exists('URLs.mdb'): logger.info('No credentials in env, using local MDB database file') @@ -131,7 +135,7 @@ def handle(self, *args, **options): if len(set(fids)) != len(fids): raise Exception('More than one InformationManagement record for a field report') - ### many-to-many + # ## many-to-many # actions taken actions_national = extract_table(filename, 'EW_Report_ActionTakenByRedCross') @@ -261,7 +265,7 @@ def handle(self, *args, **options): field_report.regions.add(country.region) event.regions.add(country.region) - ### add items with foreignkeys to report + # ## add items with foreignkeys to report # national red cross actions actions = fetch_relation(actions_national, report['ReportID']) if len(actions) > 0: @@ -293,8 +297,10 @@ def handle(self, *args, **options): sources = fetch_relation(source_table, report['ReportID']) for s in sources: spec = '' if s['Specification'] is None else s['Specification'] - src = Source.objects.create(stype=SourceType.objects.get(pk=s['SourceID']), - spec=spec, field_report=field_report) + Source.objects.create( + stype=SourceType.objects.get(pk=s['SourceID']), + spec=spec, field_report=field_report + ) # disaster response response = fetch_relation(dr_table, report['ReportID']) @@ -308,7 +314,7 @@ def handle(self, *args, **options): fields = ['Originator', 'Primary', 'Federation', 'NationalSociety', 'MediaNationalSociety', 'Media'] for f in fields: if contact_is_valid(contact, f): - ct = FieldReportContact.objects.create( + FieldReportContact.objects.create( ctype=f, name=contact['%sName' % f], title=contact['%sFunction' % f], @@ -351,12 +357,13 @@ def handle(self, *args, **options): name = user_data['RealName'].split() first_name = name[0] last_name = ' '.join(name[1:]) if len(name) > 1 else '' - user = User.objects.create(username=user_data['UserName'], - first_name=first_name if len(first_name) <= 30 else '', - last_name=last_name if len(last_name) <= 30 else '', - email=user_data['EmailAddress'], - last_login=last_login, - ) + user = User.objects.create( + username=user_data['UserName'], + first_name=first_name if len(first_name) <= 30 else '', + last_name=last_name if len(last_name) <= 30 else '', + email=user_data['EmailAddress'], + last_login=last_login, + ) user.set_password(user_data['Password']) user.is_staff = True if user_data['UserIsSysAdm'] == '1' else False @@ -365,9 +372,9 @@ def handle(self, *args, **options): user.profile.org_type = org_types.get(user_data['OrgTypeID']) # print(i, user_data['CountryID']) # - for debug purposes. Check go-api/data/Countries.csv for details. if user_data['CountryID'] in ['275', '281']: - user_data['CountryID'] = '47' #Hong Kong or Taiwan should be handled as China. Macao (279) is other case. + user_data['CountryID'] = '47' # Hong Kong or Taiwan should be handled as China. Macao (279) is other case. elif user_data['CountryID'] in ['284']: - user_data['CountryID'] = '292' #Zone Central and West Africa -> Central Africa Country Cluster + user_data['CountryID'] = '292' # Zone Central and West Africa -> Central Africa Country Cluster user.profile.country = Country.objects.get(pk=user_data['CountryID']) user.profile.city = user_data['City'] if len(user_data['City']) <= 100 else '' user.profile.department = user_data['Department'] if len(user_data['Department']) <= 100 else '' diff --git a/api/views.py b/api/views.py index 257b8d241..b9bc1ad06 100644 --- a/api/views.py +++ b/api/views.py @@ -1,10 +1,6 @@ import json - -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework import authentication, permissions - from datetime import datetime, timedelta + from django.http import JsonResponse, HttpResponse from django.contrib.auth import authenticate from django.contrib.auth.models import User @@ -17,17 +13,19 @@ from django.utils import timezone from django.utils.crypto import get_random_string from django.template.loader import render_to_string - from rest_framework.authtoken.models import Token -from .utils import pretty_request -from .esconnection import ES_CLIENT -from .models import Appeal, AppealType, Event, FieldReport, CronJob, AppealHistory -from .indexes import ES_PAGE_NAME +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import authentication, permissions + from deployments.models import Heop from notifications.models import Subscription from notifications.notification import send_notification from registrations.models import Recovery, Pending -from main.frontend import frontend_url + +from .esconnection import ES_CLIENT +from .models import Appeal, AppealType, Event, FieldReport, CronJob, AppealHistory +from .indexes import ES_PAGE_NAME def bad_request(message): @@ -151,9 +149,13 @@ def get(self, request): now = timezone.now() date = request.GET.get('date', now) - appeal_conditions = (Q(atype=AppealType.APPEAL) | Q(atype=AppealType.INTL)) & Q(end_date__gt=date) & Q(start_date__lt=date) + appeal_conditions = ( + Q(atype=AppealType.APPEAL) | Q(atype=AppealType.INTL) + ) & Q(end_date__gt=date) & Q(start_date__lt=date) - all_appealhistory = AppealHistory.objects.select_related('appeal').filter(valid_from__lt=date, valid_to__gt=date, appeal__code__isnull=False) + all_appealhistory = AppealHistory.objects\ + .select_related('appeal')\ + .filter(valid_from__lt=date, valid_to__gt=date, appeal__code__isnull=False) if iso3: all_appealhistory = all_appealhistory.filter(country__iso3__iexact=iso3) @@ -424,7 +426,7 @@ def post(self, request): Recovery.objects.filter(user=user).delete() Recovery.objects.create(user=user, token=token) email_context = { - 'frontend_url': frontend_url, + 'frontend_url': settings.FRONTEND_URL, 'username': user.username, 'token': token } diff --git a/docker-compose.yml b/docker-compose.yml index 875873c68..fa2d7d093 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,45 +1,35 @@ version: '3' -services: - base_django_setup: &base_django_setup - image: ifrcgo/go-api:latest - environment: - DJANGO_DEBUG: 'true' - # DJANGO_DB_NAME: ${DJANGO_DB_NAME:-test} - # DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY:-test} - # DJANGO_DB_HOST: ${DJANGO_DB_HOST:-db} - # DJANGO_DB_USER: ${DJANGO_DB_USER:-test} - # DJANGO_DB_PASS: ${DJANGO_DB_PASS:-test} - DJANGO_DB_NAME: test - DJANGO_SECRET_KEY: test - DJANGO_DB_HOST: db - DJANGO_DB_USER: test - DJANGO_DB_PASS: test - DEBUG_EMAIL: ${DEBUG_EMAIL:-} - FRONTEND_URL: ${FRONTEND_URL:-} - FDRS_CREDENTIAL: ${FDRS_CREDENTIAL:-} - HPC_CREDENTIAL: ${HPC_CREDENTIAL:-} - APPEALS_USER: ${APPEALS_USER:-} - APPEALS_PASS: ${APPEALS_PASS:-} - AWS_TRANSLATE_ACCESS_KEY: ${AWS_TRANSLATE_ACCESS_KEY:-} - AWS_TRANSLATE_SECRET_KEY: ${AWS_TRANSLATE_SECRET_KEY:-} - AWS_TRANSLATE_REGION: ${AWS_TRANSLATE_REGION:-} - APPLICATION_INSIGHTS_INSTRUMENTATION_KEY: ${APPLICATION_INSIGHTS_INSTRUMENTATION_KEY:-} - ERP_API_ENDPOINT: ${ERP_API_ENDPOINT:-} - ERP_API_SUBSCRIPTION_KEY: ${ERP_API_SUBSCRIPTION_KEY:-} - volumes: - - '.:/home/ifrc/go-api' - depends_on: - - db - - redis - links: - - db - - redis - - base: - build: . - image: ifrcgo/go-api:latest +x-server: &base_server_setup + image: ifrcgo/go-api:latest + build: . + tty: true + environment: + # Overwrite this using .env (for additional configuration, look at main/settings.py:env + # Database (from db.environment) + DJANGO_DB_HOST: ${DJANGO_DB_HOST:-db} + DJANGO_DB_NAME: ${DJANGO_DB_NAME:-test} + DJANGO_DB_USER: ${DJANGO_DB_USER:-test} + DJANGO_DB_PASS: ${DJANGO_DB_PASS:-test} + # Other development defaults configs + DJANGO_DEBUG: ${DJANGO_DEBUG:-true} + GO_ENVIRONMENT: ${GO_ENVIRONMENT:-development} + API_FQDN: ${API_FQDN:-localhost:8000} + FRONTEND_URL: ${FRONTEND_URL:-localhost:3000} + DEBUG_EMAIL: ${DEBUG_EMAIL:-true} + MOLNIX_API_BASE: ${MOLNIX_API_BASE:-https://api.ifrc-staging.rpm.molnix.com/api/} + ERP_API_ENDPOINT: ${ERP_API_ENDPOINT:-https://ifrctintapim001.azure-api.net/GoAPI/ExtractGoEmergency} + ERP_API_SUBSCRIPTION_KEY: ${ERP_API_SUBSCRIPTION_KEY:-abcdef} + env_file: + - .env + volumes: + - '.:/home/ifrc/go-api' + depends_on: + - db + - redis + +services: db: image: postgis/postgis:11-3.1-alpine environment: @@ -47,7 +37,6 @@ services: POSTGRES_USER: test POSTGRES_DB: test volumes: - # Database migration should be done manually, so .tmp -> .db: - './.db/pg:/var/lib/postgresql/data' redis: @@ -56,98 +45,98 @@ services: - redis-data:/data serve: - <<: *base_django_setup + <<: *base_server_setup ports: - 8000:8000 command: python manage.py runserver 0.0.0.0:8000 # For development only - celery: - <<: *base_django_setup - command: python manage.py run_celery_dev + # celery: + # <<: *base_server_setup + # command: python manage.py run_celery_dev bash: - <<: *base_django_setup + <<: *base_server_setup entrypoint: /bin/bash shell: - <<: *base_django_setup + <<: *base_server_setup command: python manage.py shell loaddata: - <<: *base_django_setup + <<: *base_server_setup command: python manage.py loaddata Regions Countries Districts DisasterTypes Actions Groups ingest_databank: - <<: *base_django_setup + <<: *base_server_setup command: python manage.py ingest_databank collectstatic: - <<: *base_django_setup + <<: *base_server_setup command: python manage.py collectstatic --noinput -l createsuperuser: - <<: *base_django_setup + <<: *base_server_setup command: python manage.py createsuperuser ingest_appeals: - <<: *base_django_setup + <<: *base_server_setup command: python manage.py ingest_appeals ingest_appeal_docs: - <<: *base_django_setup + <<: *base_server_setup command: python manage.py ingest_appeal_docs ingest_appeal_docs: - <<: *base_django_setup + <<: *base_server_setup command: python manage.py user_registration_reminder ingest_appeal_docs_fullscan: - <<: *base_django_setup + <<: *base_server_setup command: python manage.py ingest_appeal_docs --fullscan ingest_mdb: - <<: *base_django_setup + <<: *base_server_setup command: python manage.py ingest_mdb migrate: - <<: *base_django_setup + <<: *base_server_setup command: python manage.py migrate makemigrations: - <<: *base_django_setup + <<: *base_server_setup command: python manage.py makemigrations makemigrations_merge: - <<: *base_django_setup + <<: *base_server_setup command: python manage.py makemigrations --merge make_permissions: - <<: *base_django_setup + <<: *base_server_setup command: python manage.py make_permissions test: - <<: *base_django_setup + <<: *base_server_setup command: pytest --durations=10 test_snapshot_update: - <<: *base_django_setup + <<: *base_server_setup command: python manage.py test -k --snapshot-update coverage: - <<: *base_django_setup + <<: *base_server_setup command: coverage run --source='.' manage.py test -k coverage_report: - <<: *base_django_setup + <<: *base_server_setup command: coverage report coverage_html: - <<: *base_django_setup + <<: *base_server_setup command: coverage html triggers_to_db: - <<: *base_django_setup + <<: *base_server_setup command: python manage.py triggers_to_db volumes: diff --git a/main/frontend.py b/main/frontend.py index 9b5696c60..321feab78 100644 --- a/main/frontend.py +++ b/main/frontend.py @@ -1,10 +1,5 @@ -import os - -frontend_url = os.environ.get('FRONTEND_URL') -if frontend_url == 'prddsgoproxyapp.azurewebsites.net': - # We use a nicer frontend URL: - frontend_url = 'go.ifrc.org' +from django.conf import settings def get_project_url(id): - return f'https://{frontend_url}/three-w/{id}/' + return f'https://{settings.FRONTEND_URL}/three-w/{id}/' diff --git a/main/nginx.conf b/main/nginx.conf index f7817f337..69a5b96f6 100644 --- a/main/nginx.conf +++ b/main/nginx.conf @@ -6,7 +6,10 @@ server { location = /favicon.ico { access_log off; log_not_found off; } location /static/ { root /home/ifrc/go-api; + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, HEAD, OPTIONS'; } + location / { include proxy_params; proxy_pass http://unix:/home/ifrc/django_app.sock; @@ -31,7 +34,10 @@ server { location = /favicon.ico { access_log off; log_not_found off; } location /static/ { root /home/ifrc/go-api; + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, HEAD, OPTIONS'; } + location / { include proxy_params; proxy_pass http://unix:/home/ifrc/django_app.sock; diff --git a/main/runserver.sh b/main/runserver.sh index 99bed5c22..4e6d9c436 100755 --- a/main/runserver.sh +++ b/main/runserver.sh @@ -11,7 +11,6 @@ python manage.py make_permissions # Add server name(s) to django settings and nginx - later maybe only nginx would be enough, and ALLOWED_HOSTS could be "*" if [ "$API_FQDN"x = prddsgocdnapi.azureedge.netx ]; then sed -i 's/\$NGINX_SERVER_NAME/'$API_FQDN' api.go.ifrc.org goadmin.ifrc.org/g' /etc/nginx/sites-available/nginx.conf - sed -i "s/ALLOWED_HOSTS.append(PRODUCTION_URL)/ALLOWED_HOSTS.append(PRODUCTION_URL)\n ALLOWED_HOSTS.append('api.go.ifrc.org')\n ALLOWED_HOSTS.append('goadmin.ifrc.org')/" $HOME/go-api/main/settings.py else sed -i 's/\$NGINX_SERVER_NAME/'$API_FQDN'/g' /etc/nginx/sites-available/nginx.conf fi diff --git a/main/settings.py b/main/settings.py index d8dd7dd77..8d745cec8 100644 --- a/main/settings.py +++ b/main/settings.py @@ -20,9 +20,10 @@ DJANGO_STATIC_URL=(str, '/static/'), DJANGO_STATIC_ROOT=(str, os.path.join(BASE_DIR, 'static')), DJANGO_ADDITIONAL_ALLOWED_HOSTS=(list, []), # Eg: api.go.ifrc.org,goadmin.ifrc.org,prddsgocdnapi.azureedge.net - GO_ENVIRONMENT=(str, 'development'), + GO_ENVIRONMENT=(str, 'development'), # staging, production # - API_FQDN=(str, 'localhost:8000'), # sub-domain.domain.domain-extension + API_FQDN=str, # sub-domain.domain.domain-extension + FRONTEND_URL=str, # Database DJANGO_DB_NAME=str, DJANGO_DB_USER=str, @@ -33,10 +34,12 @@ AZURE_STORAGE_ACCOUNT=(str, None), AZURE_STORAGE_KEY=(str, None), # Email + EMAIL_API_ENDPOINT=(str, None), EMAIL_HOST=(str, None), EMAIL_PORT=(str, None), EMAIL_USER=(str, None), EMAIL_PASS=(str, None), + TEST_EMAILS=(list, ['im@ifrc.org']), DEBUG_EMAIL=(bool, False), # This was 0/1 before # AWS Translate NOTE: not used right now AWS_TRANSLATE_ACCESS_KEY=(str, None), @@ -58,6 +61,13 @@ APPLICATION_INSIGHTS_INSTRUMENTATION_KEY=(str, None), # Pytest (Only required when running tests) PYTEST_XDIST_WORKER=(str, None), + # Elastic-Cache + ES_HOST=(str, None), + # FTP + GO_FTPHOST=(str, None), + GO_FTPUSER=(str, None), + GO_FTPPASS=(str, None), + GO_DBPASS=(str, None), ) @@ -318,22 +328,27 @@ ] +AZURE_STORAGE_ACCOUNT = env('AZURE_STORAGE_ACCOUNT') +AZURE_STORAGE_KEY = env('AZURE_STORAGE_KEY') + AZURE_STORAGE = { 'CONTAINER': 'api', - 'ACCOUNT_NAME': env('AZURE_STORAGE_ACCOUNT'), - 'ACCOUNT_KEY': env('AZURE_STORAGE_KEY'), + 'ACCOUNT_NAME': AZURE_STORAGE_ACCOUNT, + 'ACCOUNT_KEY': AZURE_STORAGE_KEY, 'CDN_HOST': None, 'USE_SSL': False, } -if env('AZURE_STORAGE_ACCOUNT'): +if AZURE_STORAGE_ACCOUNT: # FIXME: Use https://django-storages.readthedocs.io/en/latest/backends/azure.html instead. DEFAULT_FILE_STORAGE = 'api.storage.AzureStorage' # Email config +EMAIL_API_ENDPOINT = env('EMAIL_API_ENDPOINT') EMAIL_HOST = env('EMAIL_HOST') EMAIL_PORT = env('EMAIL_PORT') -EMAIL_HOST_USER = env('EMAIL_USER') -EMAIL_HOST_PASSWORD = env('EMAIL_PASS') +EMAIL_USER = env('EMAIL_USER') +EMAIL_PASS = env('EMAIL_PASS') +TEST_EMAILS = env('TEST_EMAILS') DEBUG_EMAIL = env('DEBUG_EMAIL') DATA_UPLOAD_MAX_MEMORY_SIZE = 104857600 # default 2621440, 2.5MB -> 100MB @@ -427,3 +442,15 @@ ERP_API_SUBSCRIPTION_KEY = env('ERP_API_SUBSCRIPTION_KEY') TEST_DIR = os.path.join(BASE_DIR, 'main/test_files') + +# Elastic search host +ELASTIC_SEARCH_HOST = env('ES_HOST') + +# FTP +GO_FTPHOST = env('GO_FTPHOST') +GO_FTPUSER = env('GO_FTPUSER') +GO_FTPPASS = env('GO_FTPPASS') +GO_DBPASS = env('GO_DBPASS') + +# MISC +FRONTEND_URL = env('FRONTEND_URL') diff --git a/notifications/notification.py b/notifications/notification.py index a1a6f2b78..8cc9c226e 100644 --- a/notifications/notification.py +++ b/notifications/notification.py @@ -1,33 +1,20 @@ -import os import requests import base64 import threading import smtplib - +from email.mime.multipart import MIMEMultipart +from email.mime.application import MIMEApplication +from email.mime.text import MIMEText from django.conf import settings +from django.utils.html import strip_tags from api.logger import logger from api.models import CronJob, CronJobStatus -from django.utils.html import strip_tags from notifications.models import NotificationGUID -from email.mime.multipart import MIMEMultipart -from email.mime.application import MIMEApplication -from email.mime.text import MIMEText -EMAIL_USER = os.environ.get('EMAIL_USER') -EMAIL_PASS = os.environ.get('EMAIL_PASS') -EMAIL_HOST = os.environ.get('EMAIL_HOST') -EMAIL_PORT = os.environ.get('EMAIL_PORT') -EMAIL_API_ENDPOINT = os.environ.get('EMAIL_API_ENDPOINT') EMAIL_TO = 'no-reply@ifrc.org' -IS_PROD = os.environ.get('PRODUCTION') - -test_emails = os.environ.get('TEST_EMAILS') -if test_emails: - test_emails = test_emails.split(',') -else: - test_emails = ['im@ifrc.org'] +IS_PROD = settings.GO_ENVIRONMENT == 'production' class SendMail(threading.Thread): @@ -38,18 +25,18 @@ def __init__(self, recipients, msg, **kwargs): def run(self): try: - server = smtplib.SMTP(EMAIL_HOST, EMAIL_PORT) + server = smtplib.SMTP(settings.EMAIL_HOST, settings.EMAIL_PORT) server.ehlo() server.starttls() server.ehlo() - succ = server.login(EMAIL_USER, EMAIL_PASS) + succ = server.login(settings.EMAIL_USER, settings.EMAIL_PASS) if 'successful' not in str(succ[1]): cron_rec = {"name": "notification", - "message": 'Error contacting ' + EMAIL_HOST + ' smtp server for notifications', + "message": 'Error contacting ' + settings.EMAIL_HOST + ' smtp server for notifications', "status": CronJobStatus.ERRONEOUS} CronJob.sync_cron(cron_rec) if len(self.recipients) > 0: - server.sendmail(EMAIL_USER, self.recipients, self.msg.as_string()) + server.sendmail(settings.EMAIL_USER, self.recipients, self.msg.as_string()) server.quit() logger.info('E-mails were sent successfully.') except Exception as exc: @@ -70,7 +57,7 @@ def construct_msg(subject, html, files=None): msg = MIMEMultipart('alternative') msg['Subject'] = subject - msg['From'] = EMAIL_USER.upper() + msg['From'] = settings.EMAIL_USER.upper() msg['To'] = 'no-reply@ifrc.org' text_body = MIMEText(strip_tags(html), 'plain') @@ -89,7 +76,7 @@ def construct_msg(subject, html, files=None): def send_notification(subject, recipients, html, mailtype='', files=None): """ Generic email sending method, handly only HTML emails currently """ - if not EMAIL_USER or not EMAIL_API_ENDPOINT: + if not settings.EMAIL_USER or not settings.EMAIL_API_ENDPOINT: logger.warning( 'Cannot send notifications.\n' 'No username and/or API endpoint set as environment variables.' @@ -107,11 +94,11 @@ def send_notification(subject, recipients, html, mailtype='', files=None): # If it's not PROD only able to use test e-mail addresses which are set in the env var to_addresses = recipients if isinstance(recipients, list) else [recipients] - if int(IS_PROD) != 1: + if not IS_PROD: logger.info('Using test email addresses...') to_addresses = [] logger.info(to_addresses) - for eml in test_emails: + for eml in settings.TEST_EMAILS: # It is possible to filter test addressees to domain name only – not used. is_dom = True if '@' not in eml else False @@ -139,7 +126,7 @@ def send_notification(subject, recipients, html, mailtype='', files=None): # Encode with base64 into bytes, then converting it back to strings for the JSON payload = { - "FromAsBase64": str(base64.b64encode(EMAIL_USER.encode('utf-8')), 'utf-8'), + "FromAsBase64": str(base64.b64encode(settings.EMAIL_USER.encode('utf-8')), 'utf-8'), "ToAsBase64": str(base64.b64encode(EMAIL_TO.encode('utf-8')), 'utf-8'), "CcAsBase64": "", "BccAsBase64": str(base64.b64encode(recipients_as_string.encode('utf-8')), 'utf-8'), @@ -151,7 +138,7 @@ def send_notification(subject, recipients, html, mailtype='', files=None): } # The response contains the GUID (res.text) - res = requests.post(EMAIL_API_ENDPOINT, json=payload) + res = requests.post(settings.EMAIL_API_ENDPOINT, json=payload) res_text = res.text.replace('"', '') if res.status_code == 200: diff --git a/registrations/admin.py b/registrations/admin.py index 545008bbc..d7f4759de 100644 --- a/registrations/admin.py +++ b/registrations/admin.py @@ -1,17 +1,30 @@ from django.contrib import admin +from django.template.loader import render_to_string +from django.http import HttpResponseRedirect +from django.conf import settings +from reversion_compare.admin import CompareVersionAdmin + from api.logger import logger from api.models import User, UserRegion, Country, Profile import registrations.models as models -from reversion_compare.admin import CompareVersionAdmin from notifications.notification import send_notification -from django.template.loader import render_to_string -from main.frontend import frontend_url -from django.http import HttpResponse, HttpResponseRedirect + class PendingAdmin(CompareVersionAdmin): - readonly_fields = ('get_username_and_mail','get_region','get_country','get_org','get_city','get_department','get_position','get_phone','justification','created_at') + readonly_fields = ( + 'get_username_and_mail', + 'get_region', + 'get_country', + 'get_org', + 'get_city', + 'get_department', + 'get_position', + 'get_phone', + 'justification', + 'created_at', + ) search_fields = ('user__username', 'user__email', 'admin_contact_1', 'admin_contact_2') - list_display = ('get_username_and_mail', 'get_region','get_country','created_at', 'email_verified') + list_display = ('get_username_and_mail', 'get_region', 'get_country', 'created_at', 'email_verified') actions = ('activate_users',) list_filter = ['email_verified'] @@ -39,7 +52,7 @@ def get_username_and_mail(self, obj): def get_region(self, obj): if obj.user.profile.country: return obj.user.profile.country.region - else: + else: return obj.user.profile.country get_region.short_description = 'Region' @@ -91,13 +104,15 @@ def activate_users(self, request, queryset): if usr.is_active is False: email_context = { - 'frontend_url': frontend_url + 'frontend_url': settings.FRONTEND_URL } - send_notification('Your account has been approved', - [usr.email], - render_to_string('email/registration/outside-email-success.html', email_context), - 'Approved account successfully - ' + usr.username) + send_notification( + 'Your account has been approved', + [usr.email], + render_to_string('email/registration/outside-email-success.html', email_context), + f'Approved account successfully - {usr.username}' + ) usr.is_active = True usr.save() @@ -106,7 +121,6 @@ def activate_users(self, request, queryset): else: logger.info(f'There is no User record with the ID: {pu.user_id}') - def response_change(self, request, obj): if "_activate-user" in request.POST: usr = User.objects.get(id=obj.user.id) @@ -115,13 +129,15 @@ def response_change(self, request, obj): if usr.is_active is False: email_context = { - 'frontend_url': frontend_url + 'frontend_url': settings.FRONTEND_URL } - send_notification('Your account has been approved', - [usr.email], - render_to_string('email/registration/outside-email-success.html', email_context), - 'Approved account successfully - ' + usr.username) + send_notification( + 'Your account has been approved', + [usr.email], + render_to_string('email/registration/outside-email-success.html', email_context), + f'Approved account successfully - {usr.username}' + ) usr.is_active = True usr.save() diff --git a/registrations/views.py b/registrations/views.py index 55e498a11..6c4a96526 100644 --- a/registrations/views.py +++ b/registrations/views.py @@ -10,14 +10,14 @@ from django.http import JsonResponse, HttpResponse from django.template.loader import render_to_string from rest_framework.views import APIView + from api.views import ( bad_request, bad_http_request, ) -from api.models import Country,Profile, UserRegion -from .models import Pending, DomainWhitelist +from api.models import Country, Profile, UserRegion from notifications.notification import send_notification -from main.frontend import frontend_url +from .models import Pending, DomainWhitelist def is_valid_domain(email): @@ -71,11 +71,12 @@ def set_user_profile(user, country, organization_type, organization, city, depar user.save() return user + def getRegionalAdmins(userId): countryId = Profile.objects.get(user_id=userId).country_id - regionId = Country.objects.get(id = countryId).region_id + regionId = Country.objects.get(id=countryId).region_id - admins = UserRegion.objects.filter(region_id=regionId).values_list('user__email',flat=True) + admins = UserRegion.objects.filter(region_id=regionId).values_list('user__email', flat=True) return admins @@ -136,11 +137,9 @@ def post(self, request): User.objects.filter(username=username).delete() return bad_request('Could not create user profile.') - - pending = Pending.objects.create(user=user, justification=justification, - token=get_random_string(length=32)) + pending = Pending.objects.create(user=user, justification=justification, token=get_random_string(length=32)) if not is_staff: - pending.admin_token_1 = get_random_string(length=32) + pending.admin_token_1 = get_random_string(length=32) pending.save() @@ -200,7 +199,7 @@ def get(self, request): pending_user.user.save() pending_user.delete() email_context = { - 'frontend_url': frontend_url + 'frontend_url': settings.FRONTEND_URL } return HttpResponse(render_to_string('registration/success.html', email_context)) else: @@ -208,7 +207,7 @@ def get(self, request): admins = getRegionalAdmins(pending_user.user_id) for admin in admins: - token = pending_user.admin_token_1 + token = pending_user.admin_token_1 email_context = { 'validation_link': 'https://%s/validate_user/?token=%s&user=%s' % ( settings.BASE_URL, # on PROD it should point to goadmin... @@ -219,7 +218,7 @@ def get(self, request): 'last_name': pending_user.user.last_name, 'username': pending_user.user.username, 'email': pending_user.user.email, - 'region': pending_user.user.profile.country.region , + 'region': pending_user.user.profile.country.region, 'country': pending_user.user.profile.country, 'organization': pending_user.user.profile.org, 'city': pending_user.user.profile.city, @@ -263,20 +262,21 @@ def get(self, request): return bad_http_request('Already confirmed', 'You have already confirmed this user.') - setattr(pending_user, 'admin_1_validated' , True) - setattr(pending_user, 'admin_1_validated_date' , timezone.now()) + setattr(pending_user, 'admin_1_validated', True) + setattr(pending_user, 'admin_1_validated_date', timezone.now()) pending_user.save() - if pending_user.admin_1_validated: # and pending_user.admin_2_validated: + if pending_user.admin_1_validated: # and pending_user.admin_2_validated: pending_user.user.is_active = True pending_user.user.save() email_context = { - 'frontend_url': frontend_url + 'frontend_url': settings.FRONTEND_URL } - send_notification('Your account has been approved', - [pending_user.user.email], - render_to_string('email/registration/outside-email-success.html', email_context), - 'Approved account successfully - ' + pending_user.user.username) + send_notification( + 'Your account has been approved', + [pending_user.user.email], + render_to_string('email/registration/outside-email-success.html', email_context), + f'Approved account successfully - {pending_user.user.username}' + ) pending_user.delete() return HttpResponse(render_to_string('registration/validation-success.html')) -