def validate(self, attrs): attrs = super().validate(attrs=attrs) password = attrs["password"] if SystemParameter.get( "REGISTRATION_SOFT_LOCK") and not password.startswith( SystemParameter.get("REGISTRATION_SOFT_LOCK_KEY")): raise ValidationError(_("Registrations are currently locked."), code="registration_locked") return attrs
def test_create_submission_with_notification(self, notifier_client): self.submission_type = SubmissionType.objects.get(name="General") self.submission.type = self.submission_type self.submission.save() payload = { "submission_type": "General", "submission_status_id": 7, "status_context": "received", } notifier_client().send_email_notification.return_value = {} self.client.force_authenticate(user=self.user_1, token=self.user_1.auth_token) response = self.post_form(self.url, payload) self.assertEqual(response.status_code, status.HTTP_200_OK) # Build footer base_footer = SystemParameter.get("NOTIFY_BLOCK_FOOTER") email = f"{self.case.reference}@{SystemParameter.get('TRADE_REMEDIES_EMAIL_DOMAIN')}" footer = "\n".join([base_footer, f"Contact: {email}"]) notify_data = { "company": self.organisation.name, "case_name": self.case.name, "case_title": self.case.name, "case_number": self.case.reference, "case_type": self.case.type.name, "investigation_type": self.case.type.name, "dumped_or_subsidised": self.case.dumped_or_subsidised(), "product": "", "full_name": self.user_1.contact.name.strip(), "country": "N/A", "organisation_name": titlecase(self.organisation.name), "notice_url": self.submission.url or "N/A", # TODO: Remove "notice_of_initiation_url": self.submission.url or "N/A", "login_url": public_login_url(), "submission_type": "General", "company_name": titlecase(self.submission.organisation.name) if self.submission.organisation else "", "deadline": "", "footer": footer, "email": email, "guidance_url": SystemParameter.get("LINK_HELP_BOX_GUIDANCE"), } notifier_client().send_email_notification.assert_called_once_with( email_address=self.user_1.email, personalisation=notify_data, reference=None, template_id="d6fb3018-2338-40c9-aa6d-f1195f5f65de", # /PS-IGNORE ) response_data = response.data["response"] self.assertTrue(response_data["success"]) self.assertEqual(response_data["result"]["submission"]["id"], str(self.submission.id)) self.assertEqual(response_data["result"]["submission"]["type"]["name"], "General")
def post(self, request, template_key, *args, **kwargs): template_id = SystemParameter.get(template_key) values = request.data.get("values") or {} if isinstance(values, str): values = json.loads(values) template = get_preview(template_id, values) return ResponseSuccess({"result": template})
def validate(self, attrs: dict) -> dict: attrs = super().validate(attrs=attrs) password = attrs["password"] if SystemParameter.get("REGISTRATION_SOFT_LOCK") and not password.startswith( SystemParameter.get("REGISTRATION_SOFT_LOCK_KEY") ): raise ValidationError( _("Registrations are currently locked."), code="registration_locked" ) if attrs.get("code") and attrs.get("case_id") and not attrs.get("confirm_invited_org"): raise ValidationError( {"organisation_name": _("Organisation name is required.")}, code="organisation_name_required", ) return attrs
def get_context(extra_context=None): from core.models import SystemParameter extra_context = extra_context or {} email = notify_contact_email(extra_context.get("case_number")) footer = notify_footer(email) context = { "footer": footer, "email": email, "guidance_url": SystemParameter.get("LINK_HELP_BOX_GUIDANCE"), } context.update(extra_context) return context
def notify_footer(email=None): """Build notify footer with specified email. :param (str) email: contact email for footer. :returns (str): NOTIFY_BLOCK_FOOTER system parameter value with email appended, if any. """ from core.models import SystemParameter footer = SystemParameter.get("NOTIFY_BLOCK_FOOTER") if email: return "\n".join([footer, f"Contact: {email}"]) return footer
def notify_contact_email(case_number=None): """Build notify email address. If a case is specified build contact email with it. :param (str) case_number: e.g. 'TD0001' :returns (str): A case contact email if case number specified, otherwise value of TRADE_REMEDIES_EMAIL system parameter. """ from core.models import SystemParameter if case_number: return f"{case_number}@{SystemParameter.get('TRADE_REMEDIES_EMAIL_DOMAIN')}" return SystemParameter.get("TRADE_REMEDIES_EMAIL")
def handle(self, *args, **options): logger.info("Loading system parameters") path = options.get("path") if not path: path = os.path.join( pathlib.Path(__file__).parent.parent.parent, "system", "parameters.json") with open(path) as json_data: objects = json.loads(str(json_data.read())) count_created, count_updated, count_removed = SystemParameter.load_parameters( objects) if count_updated: logger.info(f"Upadted {count_updated} row(s)") if count_created: logger.info(f"Created {count_created} row(s)") if count_removed: logger.info(f"Removed {count_removed} rows(s)")
def is_enabled(name): """ Evaluate if a feature is enabled. Feature flags are expected to be defined as SystemParameter objects of type `int` with a key that is upper case and prefixed with `FEATURE_`. A value of 1 implies the feature is enabled. A value of 0 implies the feature is disabled. """ key = f"FEATURE_{name.upper()}" cache_key = f"FF:{key}" val = cache.get(cache_key) if val is None: sys_param = SystemParameter.get(key) if sys_param is None: raise FeatureFlagNotFound(key) val = sys_param > 0 cache.set(cache_key, val, feature_flag_ttl) return val
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """ Arguments: request: a Django Request object Returns: ResponseSuccess response with the user's token and user data ResponseError response if the user could not be created #todo - raise an error like the other views """ registration_data = json.loads(request.data["registration_data"]) serializer = V2RegistrationSerializer(data=registration_data) if serializer.is_valid(): serializer.save() return ResponseSuccess({"result": serializer.data}, http_status=status.HTTP_201_CREATED) else: if "User already exists." in serializer.errors.get("email", []).detail: # If the email already exists, # notify the original user and pretend registration completed ok. user = User.objects.get( email__iexact=serializer.initial_data["email"]) template_id = SystemParameter.get("NOTIFY_EMAIL_EXISTS") send_mail(user.email, {"full_name": user.name}, template_id) return ResponseSuccess( { "result": { "email": serializer.initial_data["email"], "pk": uuid.uuid4(), # Give them a random UUID } }, http_status=status.HTTP_201_CREATED, ) else: raise ValidationAPIException( serializer_errors=serializer.errors)
def test_dict_value(self): value = SystemParameter.get("DICT") assert isinstance(value, dict) assert value["one"] == 1 assert value["two"] == 2
def test_string_value(self): value = SystemParameter.get("STRING") assert value == "str_value"
def test_list_of_string(self): value = SystemParameter.get("STRING_LIST") assert len(value) == 2 assert isinstance(value[0], str) assert value[1] == "two"
def test_list_with_content_type(self): value = SystemParameter.get("MODELS") assert len(value) == 2 assert isinstance(value[0], Document)
def send(self, sent_by, context=None, direct=False, template_key=None): """Send the invite email via notify Arguments: sent_by {User} -- The user sending the invitation Keyword Arguments: context {dict} -- extra context dict (default: {None}) direct {bool} -- include a direct login link with the invite codes (default: {False}) template_key {str} -- The system param pointing to the template id (default: {None}) Raises: InvitationFailure: raises if the invite is lacking a contact reference """ if not self.contact: raise InvitationFailure("No contact to invite") template_key = template_key or "NOTIFY_INFORM_INTERESTED_PARTIES" notify_template_id = SystemParameter.get(template_key) _context = { "organisation_name": self.organisation.name, "company_name": self.organisation.name, "full_name": self.contact.name, # invited contact "login_url": f"{settings.PUBLIC_ROOT_URL}", "guidance_url": SystemParameter.get("LINK_HELP_BOX_GUIDANCE"), "invited_by": self.created_by.name, } if self.case: product = self.case.product_set.first() export_source = self.case.exportsource_set.first() case_name = self.case.name or (product.sector.name if product else "N/A") _context.update({ "case_name": case_name, "case_number": self.case.reference, "investigation_type": self.case.type.name, "dumped_or_subsidised": self.case.dumped_or_subsidised(), "product": product.name, "country": export_source.country.name if export_source else None, "notice_url": self.submission.url if self.submission else "", # TODO: Remove "notice_of_initiation_url": self.case.latest_notice_of_initiation_url, "invited_by_name": self.submission.contact.name if self.submission else "", "invited_by_organisation": self.submission.organisation.name if self.submission else "", }) # Set email and footer appropriate to case context email = notify_contact_email(_context.get("case_number")) _context.update({"email": email, "footer": notify_footer(email)}) if direct is True: _context[ "login_url"] = f"{settings.PUBLIC_ROOT_URL}/invitation/{self.code}/{self.case.id}/" if context: _context.update(context) audit_kwargs = { "audit_type": AUDIT_TYPE_NOTIFY, "user": sent_by, "case": self.case, "model": self.contact, } send_mail(self.contact.email, _context, notify_template_id, audit_kwargs=audit_kwargs)
def merge_organisation_records(self, organisation, merge_with=None, parameter_map=None, merged_by=None, notify=False): """ Merge two organisations records into one. parameter_map is a map of fields that need to be copied from the merge_with object """ from contacts.models import Contact from invitations.models import Invitation results = [] results.append( Submission.objects.filter(organisation=merge_with).update( organisation=organisation)) results.append( Contact.objects.filter(organisation=merge_with).update( organisation=organisation)) results.append( Invitation.objects.filter(organisation=merge_with).update( organisation=organisation)) results.append( OrganisationName.objects.filter(organisation=merge_with).update( organisation=organisation)) # transfer usercases after finding clashes sql = f"""select uc2.id from security_usercase uc1 join security_usercase uc2 on uc1.organisation_id='{organisation.id}' and uc2.organisation_id = '{merge_with.id}' and uc1.case_id=uc2.case_id and uc1.user_id=uc2.user_id """ clash_list = sql_get_list(sql) try: results.append( UserCase.objects.filter(organisation=merge_with).exclude( id__in=clash_list).update(organisation=organisation)) except Exception as e: raise ValueError( "Same user has access to same case on behalf of both organisations" ) # transfer case_org_contacts after finding clashes sql = f"""select cc2.id from contacts_casecontact cc1 join contacts_casecontact cc2 on cc1.organisation_id='{organisation.id}' and cc2.organisation_id = '{merge_with.id}' and cc1.case_id=cc2.case_id and cc1.contact_id=cc2.contact_id """ clash_list = sql_get_list(sql) try: results.append( CaseContact.objects.filter(organisation=merge_with).exclude( id__in=clash_list).update(organisation=organisation)) except Exception as e: raise ValueError("Same contact is in both organisations") # Migrate cases (caseroles) clash_cases = {} for org_case in OrganisationCaseRole.objects.filter( organisation=organisation): clash_cases[org_case.case.id] = org_case for org_case in OrganisationCaseRole.objects.filter( organisation=merge_with): clash = clash_cases.get(org_case.case.id) if clash: if org_case.role.key not in NOT_IN_CASE_ORG_CASE_ROLES: if (clash.role.key not in NOT_IN_CASE_ORG_CASE_ROLES and org_case.role.key != clash.role.key): # Argh, both orgs are in the same case with different, # non awaiting roles - blow up! raise ValueError( "Cannot merge as organisations have different roles in a case", org_case.case.name, ) # Pick the best possible role for the merged org clash.role = org_case.role clash.save() clash_cases[org_case.case.id] = org_case org_case.delete() results.append( OrganisationCaseRole.objects.filter( organisation=merge_with).update(organisation=organisation)) results.append( OrganisationUser.objects.filter(organisation=merge_with).update( organisation=organisation)) updated = False for parameter, source in parameter_map.items(): if source == "p2": setattr(organisation, parameter, getattr(merge_with, parameter)) updated = True if updated: organisation.merged_from = merge_with organisation.save() # Notify the admins of the organisation of the merge. if notify and merged_by: notify_template_id = SystemParameter.get( "NOTIFY_ORGANISATION_MERGED") # any baseline context can be set here. context = { "footer": notify_footer(notify_contact_email()), "public_cases": SystemParameter.get("LINK_TRA_CASELIST"), } self.notify_owners( organisation=organisation, template_id=notify_template_id, context=context, notified_by=merged_by, ) # soft delete the merged organisation merge_with.delete() return results
return contacts[0] return contact def notify_approval_status(self, action, contact, values, case, sent_by): """ Notify organisation contact about an approval or rejection to a case. """ templates_map = { "approve": "NOTIFY_INTERESTED_PARTY_REQUEST_PERMITTED", "deny": "NOTIFY_INTERESTED_PARTY_REQUEST_DENIED", "change": "NOTIFY_COMPANY_ROLE_CHANGED_V2", "remove": "NOTIFY_COMPANY_ROLE_DENIED_V2", } template_id = templates_map.get(action) if template_id: notify_template_id = SystemParameter.get(template_id) audit_kwargs = { "audit_type": AUDIT_TYPE_NOTIFY, "user": sent_by, "case": case, "model": contact, } send_mail(contact.email, values, notify_template_id, audit_kwargs=audit_kwargs) else: logger.error("Invalid action for organisation notification: %s", action) def has_previous_names(self):
def notify(self, sent_by, contact=None, context=None, template_id=None, new_status=None): """ Notify the contact about this submission using the given template :param core.User sent_by: The user performing an action on the submission. :param contacts.Contact contact: An optional contact to be notified. Defaults to the submission's designated contact. :param dict context: An optional dictionary of parameters to be made available to the template. :param str template_id: An optional string representing the key of a Notify template in the System Parameters. Defaults to NOTIFY_QUESTIONNAIRE :param str new_status: An optional status that the submission will be moved to after sending the notification. This value should correspond to the property of the SubmissionType excluding the `_status` suffix eg: - `sent` -> self.type.sent_status - `received` -> self.type.received_status Defaults to None ie the submission status will not change. """ contact = contact or self.contact template_id = template_id or "NOTIFY_QUESTIONNAIRE" if template_id == "NOTIFY_APPLICATION_SUCCESSFUL": template_id = "NOTIFY_APPLICATION_SUCCESSFUL_V2" notify_template_id = SystemParameter.get(template_id) export_sources = self.case.exportsource_set.filter( deleted_at__isnull=True) export_countries = [src.country.name for src in export_sources] product = self.case.product_set.first() case_name = self.case.name company_name = titlecase(self.organisation.name) values = { "company": self.organisation.name, "investigation_type": self.case.type.name, "product": product.name if product else "", "case_name": case_name, "case_number": self.case.reference, "full_name": contact.name.strip() if contact else "N/A", "country": ", ".join(export_countries) if export_countries else "N/A", "organisation_name": company_name, "company_name": company_name, "login_url": public_login_url(), "submission_type": self.type.name, "deadline": self.due_at.strftime(settings.FRIENDLY_DATE_FORMAT) if self.due_at else "", "dumped_or_subsidised": self.case.dumped_or_subsidised(), "case_title": case_name, # TODO: merge the two identicals "notice_url": self.case.latest_notice_of_initiation_url, # TODO: remove "notice_of_initiation_url": self.case.latest_notice_of_initiation_url, } if context: if template_id == "NOTIFY_QUESTIONNAIRE": context[ "footer"] = "Investigations Team\r\nTrade Remedies\r\nDepartment for International Trade" # /PS-IGNORE values.update(context) if template_id == "NOTIFY_AD_HOC_EMAIL": values[ "footer"] = "Investigations Team\r\nTrade Remedies\r\nDepartment for International Trade\r\nContact: [email protected]" # /PS-IGNORE audit_kwargs = { "audit_type": AUDIT_TYPE_NOTIFY, "user": sent_by, "case": self.case, "model": contact, } send_mail(contact.email, values, notify_template_id, audit_kwargs=audit_kwargs) if new_status: self.status = getattr(self.type, f"{new_status}_status") if new_status == "sent": self.sent_at = timezone.now() self.set_due_date() self.save()
def get(self, request, template_key, *args, **kwargs): template_id = SystemParameter.get(template_key) template = get_template(template_id) return ResponseSuccess({"result": template})
class AssignUserToCaseView(TradeRemediesApiView): def get(self, request, organisation_id, user_id=None): from cases.models import Submission organisation = Organisation.objects.get(id=organisation_id) if user_id: user = User.objects.get(id=user_id) contact_ids = [user.contact.id] else: users = organisation.users contact_ids = users.values_list("user__userprofile__contact", flat=True) submissions = Submission.objects.filter( (Q(status__draft=True) | Q(status__received=True)), contact_id__in=contact_ids, created_by__in=users.values_list("user", flat=True), type__key="assign", ) return ResponseSuccess( {"results": [submission.to_dict() for submission in submissions]}) @transaction.atomic def post( self, request, organisation_id, user_id, case_id, representing_id=None, submission_id=None, invite_id=None, ): from cases.models import get_case primary = request.data.get("primary") remove = request.data.get("remove") try: user_organisation = Organisation.objects.get(id=organisation_id) if representing_id: representing = Organisation.objects.get(id=representing_id) else: representing = user_organisation except Organisation.DoesNotExist: raise NotFoundApiExceptions("Invalid parameters or access denied") if not request.user.is_tra() and user_id and (user_id != request.user.id): if not request.user.groups.filter( name=SECURITY_GROUP_ORGANISATION_OWNER).exists(): raise InvalidAccess( "Only organisation owners can update other members") case = get_case(case_id) user = User.objects.get( id=user_id, organisationuser__organisation=user_organisation) if not remove: user.assign_to_case(case=case, organisation=representing, created_by=request.user) user.contact.add_to_case( case=case, organisation=representing, primary=bool(primary), ) context = { "case_name": case.name, "case_number": case.reference, "company_name": user_organisation.name, "representing_clause": f" representing {representing.name}", "login_url": public_login_url(), } context[ "footer"] = "Investigations Team\r\nTrade Remedies\r\nDepartment for International Trade" # /PS-IGNORE context["full_name"] = user.contact.name or user.name audit_kwargs = { "audit_type": AUDIT_TYPE_NOTIFY, "case": case, "user": user, "model": user.contact, } send_mail( email=user.contact.email, context=context, template_id=SystemParameter.get( "NOTIFY_USER_ASSIGNED_TO_CASE"), audit_kwargs=audit_kwargs, )
def load_system_params(): with open(os.path.join(Path(settings.BASE_DIR).parent.absolute(), "core/system/parameters.json")) as json_data: objects = json.loads(str(json_data.read())) return SystemParameter.load_parameters(objects)
def notify_deficiency(self, sent_by, contact=None, context=None, template_id=None): """ Notify the contact about a deficiency to this submission using the given template. If no template is provided, the type's default is used falling back to the default deficiency template. """ contact = contact or self.contact template_id = "NOTIFY_SUBMISSION_DEFICIENCY" if context.get("submission_type", "") == "Application": template_id = "NOTIFY_APPLICATION_INSUFFICIENT_V2" notify_template_id = SystemParameter.get(template_id) product = self.case.product_set.first() product_name = product.name if product else "" case_name = self.case.name company_name = titlecase(self.organisation.name) # set the due date on this submission self.set_due_date(force=True) values = { "company": company_name, "investigation_type": self.case.type.name, "product": product_name, "full_name": contact.name.strip() if contact else "N/A", "organisation_name": company_name, "case_number": self.case.reference, "case_name": case_name, "tra_contact_name": "us", "submission_type": self.type.name, "login_url": public_login_url(), "deadline": self.due_at.strftime(settings.FRIENDLY_DATE_FORMAT) if self.due_at else "N/A", } if context: values.update(context) audit_kwargs = { "audit_type": AUDIT_TYPE_NOTIFY, "user": sent_by, "case": self.case, "model": contact, } send_mail(contact.email, values, notify_template_id, audit_kwargs=audit_kwargs) self.deficiency_sent_at = timezone.now() self.sent_at = timezone.now() self.save()
else: groups.append(SECURITY_GROUP_ORGANISATION_OWNER) user = serializer.save(groups=groups, **contact_kwargs) invitation.process_invitation( user, accept=accept, register_interest=register_interest) else: user = serializer.save() return ResponseSuccess({"result": user.to_dict()}, http_status=status.HTTP_201_CREATED) else: if serializer.errors.get("email", []) == ["User already exists."]: # If the email already exists, # notify the original user and pretend registration completed ok. user = serializer.get_user(serializer.initial_data["email"]) template_id = SystemParameter.get("NOTIFY_EMAIL_EXISTS") send_mail(user.email, {"full_name": user.name}, template_id) return ResponseSuccess( { "result": { "email": serializer.initial_data["email"], "id": None, } }, http_status=status.HTTP_201_CREATED, ) return ResponseError(serializer.errors) class TwoFactorRequestAPI(TradeRemediesApiView):