def _get_error_for_field(self, key, value):  # noqa: C901

        if self.is_address_column(key):
            return

        if Columns.make_key(
                key) in self.recipient_column_headers_as_column_keys:
            if value in [None, ''] or isinstance(value, list):
                if self.duplicate_recipient_column_headers:
                    return None
                else:
                    return Cell.missing_field_error
            try:
                validate_recipient(
                    value,
                    self.template_type,
                    allow_international_sms=self.allow_international_sms)
            except (InvalidEmailError, InvalidPhoneError) as error:
                return str(error)

        if Columns.make_key(key) not in self.placeholders_as_column_keys:
            return

        if value in [None, '']:
            return Cell.missing_field_error
Пример #2
0
def get_placeholder_form_instance(
    placeholder_name,
    dict_to_populate_from,
    template_type,
    optional_placeholder=False,
    allow_international_phone_numbers=False,
):

    if (
        Columns.make_key(placeholder_name) == 'emailaddress'
        and template_type == 'email'
    ):
        field = email_address(label=placeholder_name, gov_user=False)
    elif (
        Columns.make_key(placeholder_name) == 'phonenumber'
        and template_type == 'sms'
    ):
        if allow_international_phone_numbers:
            field = international_phone_number(label=placeholder_name)
        else:
            field = uk_mobile_number(label=placeholder_name)
    elif optional_placeholder:
        field = StringField(placeholder_name)
    else:
        field = StringField(placeholder_name, validators=[
            DataRequired(message='Can’t be empty')
        ])

    PlaceholderForm.placeholder_value = field

    return PlaceholderForm(
        placeholder_value=dict_to_populate_from.get(placeholder_name, '')
    )
def validate_address(address_line, column):
    if Columns.make_key(column) in Columns.from_keys(optional_address_columns).keys():
        return address_line
    if Columns.make_key(column) not in Columns.from_keys(first_column_headings['letter']).keys():
        raise TypeError
    if not address_line or not strip_whitespace(address_line):
        raise InvalidAddressError('Missing')
    return address_line
 def values(self, value):
     if not value:
         self._values = {}
     else:
         placeholders = Columns.from_keys(self.placeholders)
         self._values = Columns(value).as_dict_with_keys(
             self.placeholders
             | set(key for key in value.keys()
                   if Columns.make_key(key) not in placeholders.keys()))
    def values_with_default_optional_address_lines(self):
        keys = Columns.from_keys(
            set(self.values.keys()) | {
                'address line 3',
                'address line 4',
                'address line 5',
                'address line 6',
            }).keys()

        return {key: Columns(self.values).get(key) or '' for key in keys}
Пример #6
0
    def duplicate_recipient_column_headers(self):

        raw_recipient_column_headers = [
            Columns.make_key(column_header)
            for column_header in self._raw_column_headers if Columns.make_key(
                column_header) in self.recipient_column_headers_as_column_keys
        ]

        return OrderedSet((column_header
                           for column_header in self._raw_column_headers
                           if raw_recipient_column_headers.count(
                               Columns.make_key(column_header)) > 1))
Пример #7
0
 def placeholders(self, value):
     try:
         self._placeholders = list(value) + self.recipient_column_headers
     except TypeError:
         self._placeholders = self.recipient_column_headers
     self.placeholders_as_column_keys = [
         Columns.make_key(placeholder) for placeholder in self._placeholders
     ]
     self.recipient_column_headers_as_column_keys = [
         Columns.make_key(placeholder)
         for placeholder in self.recipient_column_headers
     ]
Пример #8
0
 def missing_column_headers(self):
     required = {
         Columns.make_key(key): key
         for key in set([self.recipient_column_header] + self.placeholders)
     }
     return set(
         required[key] for key in set(
             [Columns.make_key(self.recipient_column_header)] + self.placeholders_as_column_keys
         ) - set(
             Columns.make_key(column_header) for column_header in self.column_headers
         )
     )
Пример #9
0
def test_missing_data():
    partial_row = partial(
        Row,
        row_dict={},
        index=1,
        error_fn=None,
        recipient_column_headers=[],
        placeholders=[],
        template=None,
    )
    assert Columns({})['foo'] is None
    assert Columns({}).get('foo') is None
    assert Columns({}).get('foo', 'bar') == 'bar'
    assert partial_row()['foo'] == Cell()
    assert partial_row().get('foo') == Cell()
    assert partial_row().get('foo', 'bar') == 'bar'
Пример #10
0
def get_back_link(service_id, template, step_index, placeholders=None):
    if step_index == 0:
        if should_skip_template_page(template.template_type):
            return url_for(
                '.choose_template',
                service_id=service_id,
            )
        else:
            return url_for(
                '.view_template',
                service_id=service_id,
                template_id=template.id,
            )

    if template.template_type == 'letter' and placeholders:
        # Make sure we’re not redirecting users to a page which will
        # just redirect them forwards again
        back_link_destination_step_index = next(
            (index for index, placeholder in reversed(
                list(enumerate(placeholders[:step_index]))) if placeholder
             not in Columns(PostalAddress('').as_personalisation)), 1)
        return get_back_link(service_id, template,
                             back_link_destination_step_index + 1)

    return url_for(
        'main.send_one_off_step',
        service_id=service_id,
        template_id=template.id,
        step_index=step_index - 1,
    )
Пример #11
0
    def rows(self):

        column_headers = self._raw_column_headers  # this is for caching
        length_of_column_headers = len(column_headers)

        rows_as_lists_of_columns = self._rows

        next(rows_as_lists_of_columns)  # skip the header row

        for row in rows_as_lists_of_columns:

            output_dict = OrderedDict()

            for column_name, column_value in zip(column_headers, row):
                insert_or_append_to_dict(output_dict, column_name, column_value
                                         or None)

            length_of_row = len(row)

            if length_of_column_headers < length_of_row:
                output_dict[None] = row[length_of_column_headers:]
            elif length_of_column_headers > length_of_row:
                for key in column_headers[length_of_row:]:
                    insert_or_append_to_dict(output_dict, key, None)

            yield Columns(output_dict)
Пример #12
0
 def rows_with_missing_data(self):
     return set(
         row['index'] for row in self.annotated_rows if any(
             str(key) not in Columns.make_key(self.recipient_column_header) and value.get('error')
             for key, value in row['columns'].items()
         )
     )
Пример #13
0
def save_letter(
    self,
    service_id,
    notification_id,
    encrypted_notification,
):
    notification = encryption.decrypt(encrypted_notification)

    postal_address = PostalAddress.from_personalisation(
        Columns(notification['personalisation']))

    service = SerialisedService.from_id(service_id)
    template = SerialisedTemplate.from_id_and_service_id(
        notification['template'],
        service_id=service.id,
        version=notification['template_version'],
    )

    try:
        # if we don't want to actually send the letter, then start it off in SENDING so we don't pick it up
        status = NOTIFICATION_CREATED if not service.research_mode else NOTIFICATION_SENDING

        saved_notification = persist_notification(
            template_id=notification['template'],
            template_version=notification['template_version'],
            postage=postal_address.postage
            if postal_address.international else template.postage,
            recipient=postal_address.normalised,
            service=service,
            personalisation=notification['personalisation'],
            notification_type=LETTER_TYPE,
            api_key_id=None,
            key_type=KEY_TYPE_NORMAL,
            created_at=datetime.utcnow(),
            job_id=notification['job'],
            job_row_number=notification['row_number'],
            notification_id=notification_id,
            reference=create_random_identifier(),
            reply_to_text=template.reply_to_text,
            status=status)

        if not service.research_mode:
            letters_pdf_tasks.get_pdf_for_templated_letter.apply_async(
                [str(saved_notification.id)],
                queue=QueueNames.CREATE_LETTERS_PDF)
        elif current_app.config['NOTIFY_ENVIRONMENT'] in [
                'preview', 'development'
        ]:
            research_mode_tasks.create_fake_letter_response_file.apply_async(
                (saved_notification.reference, ),
                queue=QueueNames.RESEARCH_MODE)
        else:
            update_notification_status_by_reference(
                saved_notification.reference, 'delivered')

        current_app.logger.debug("Letter {} created at {}".format(
            saved_notification.id, saved_notification.created_at))
    except SQLAlchemyError as e:
        handle_exception(self, notification, notification_id, e)
Пример #14
0
 def placeholders(self, value):
     try:
         self._placeholders = list(value)
         self.placeholders_as_column_keys = [
             Columns.make_key(placeholder) for placeholder in value
         ]
     except TypeError:
         self._placeholders, self.placeholders_as_column_keys = [], []
Пример #15
0
def test_columns_as_dict_with_keys():
    assert Columns({
        'Date of Birth': '01/01/2001',
        'TOWN': 'London'
    }).as_dict_with_keys({'date_of_birth', 'town'}) == {
        'date_of_birth': '01/01/2001',
        'town': 'London'
    }
 def missing_column_headers(self):
     return set(
         key for key in self.placeholders
         if (
             Columns.make_key(key) not in self.column_headers_as_column_keys and
             not self.is_optional_address_column(key)
         )
     )
Пример #17
0
def test_columns_as_dict():
    assert dict(Columns({
        'date of birth': '01/01/2001',
        'TOWN': 'London'
    })) == {
        'dateofbirth': '01/01/2001',
        'town': 'London'
    }
def test_missing_data():
    partial_row = partial(
        Row,
        row_dict={},
        index=1,
        error_fn=None,
        recipient_column_headers=[],
        placeholders=[],
        template=None,
        allow_international_letters=False,
    )
    with pytest.raises(KeyError):
        Columns({})['foo']
    assert Columns({}).get('foo') is None
    assert Columns({}).get('foo', 'bar') == 'bar'
    assert partial_row()['foo'] == Cell()
    assert partial_row().get('foo') == Cell()
    assert partial_row().get('foo', 'bar') == 'bar'
Пример #19
0
def get_spreadsheet_column_headings_from_template(template):
    column_headings = []

    for column_heading in (first_column_headings[template.template_type] +
                           list(template.placeholders)):
        if column_heading not in Columns.from_keys(column_headings):
            column_headings.append(column_heading)

    return column_headings
Пример #20
0
 def __str__(self):
     return Markup(
         self.jinja_template.render({
             'admin_base_url':
             self.admin_base_url,
             'logo_file_name':
             self.logo_file_name,
             'subject':
             self.subject,
             'message':
             Take(
                 Field(
                     strip_dvla_markup(self.content),
                     self.values,
                     html='escape',
                     markdown_lists=True,
                     redact_missing_personalisation=self.
                     redact_missing_personalisation,
                 )).then(strip_pipes).then(
                     make_markdown_take_notice_of_multiple_newlines).then(
                         notify_letter_preview_markdown).
             then(strip_characters_inserted_to_force_newlines).then(
                 do_nice_typography).then(remove_trailing_linebreak).then(
                     replace_hyphens_with_non_breaking_hyphens).then(
                         tweak_dvla_list_markup),
             'address':
             Take(
                 Field(self.address_block,
                       (self.values_with_default_optional_address_lines
                        if all(
                            Columns(self.values).get(key) for key in {
                                'address line 1',
                                'address line 2',
                                'postcode',
                            }) else self.values),
                       html='escape',
                       with_brackets=False)
             ).then(strip_pipes).then(remove_empty_lines).then(
                 remove_whitespace_before_punctuation).then(nl2li),
             'contact_block':
             Take(
                 Field(
                     '\n'.join(line.strip()
                               for line in self.contact_block.split('\n')),
                     self.values,
                     redact_missing_personalisation=self.
                     redact_missing_personalisation,
                     html='escape',
                 )).then(remove_whitespace_before_punctuation).then(
                     nl2br).then(strip_pipes),
             'date':
             datetime.utcnow().strftime('%-d %B %Y')
         }))
Пример #21
0
    def _get_error_for_field(self, key, value):

        if key == Columns.make_key(self.recipient_column_header):
            try:
                validate_recipient(value, self.template_type)
            except (InvalidEmailError, InvalidPhoneError) as error:
                return str(error)

        if key not in self.placeholders_as_column_keys:
            return

        if value in [None, '']:
            return 'Missing'
 def _address_block(self):
     return Take(
         Field(self.address_block,
               (self.values_with_default_optional_address_lines if all(
                   Columns(self.values).get(key) for key in {
                       'address line 1',
                       'address line 2',
                       'postcode',
                   }) else self.values),
               html='escape',
               with_brackets=False)).then(strip_pipes).then(
                   remove_empty_lines).then(
                       remove_whitespace_before_punctuation).then(nl2li)
Пример #23
0
    def has_recipient_columns(self):

        if self.template_type == 'letter':
            sets_to_check = [
                Columns.from_keys(
                    address_lines_1_to_6_and_postcode_keys).keys(),
                Columns.from_keys(address_lines_1_to_7_keys).keys(),
            ]
        else:
            sets_to_check = [
                self.recipient_column_headers_as_column_keys,
            ]

        for set_to_check in sets_to_check:
            if len(
                    # Work out which columns are shared between the possible
                    # letter address columns and the columns in the user’s
                    # spreadsheet (`&` means set intersection)
                    set_to_check & self.column_headers_as_column_keys
            ) >= self.count_of_required_recipient_columns:
                return True

        return False
Пример #24
0
    def _get_error_for_field(self, key, value):

        if self.is_optional_address_column(key):
            return

        if Columns.make_key(
                key) in self.recipient_column_headers_as_column_keys:
            if value in [None, '']:
                return Cell.missing_field_error
            try:
                validate_recipient(value,
                                   self.template_type,
                                   column=key,
                                   international_sms=self.international_sms)
            except (InvalidEmailError, InvalidPhoneError,
                    InvalidAddressError) as error:
                return str(error)

        if Columns.make_key(key) not in self.placeholders_as_column_keys:
            return

        if value in [None, '']:
            return Cell.missing_field_error
Пример #25
0
    def _address_block(self):
        from notifications_utils.postal_address import PostalAddress

        postal_address = PostalAddress.from_personalisation(Columns(self.values))

        if postal_address.has_enough_lines and not postal_address.has_too_many_lines:
            return postal_address.normalised_lines

        return Field(
            self.address_block,
            self.values,
            html='escape',
            with_brackets=False,
        ).splitlines()
Пример #26
0
def get_spreadsheet_column_headings_from_template(template):
    column_headings = []

    if template.template_type == 'letter':
        # We want to avoid showing `address line 7` for now
        recipient_columns = letter_address_columns
    else:
        recipient_columns = first_column_headings[template.template_type]

    for column_heading in (recipient_columns + list(template.placeholders)):
        if column_heading not in Columns.from_keys(column_headings):
            column_headings.append(column_heading)

    return column_headings
Пример #27
0
    def get_rows(self):

        column_headers = self._raw_column_headers  # this is for caching
        length_of_column_headers = len(column_headers)

        rows_as_lists_of_columns = self._rows

        next(rows_as_lists_of_columns, None)  # skip the header row

        for index, row in enumerate(rows_as_lists_of_columns):

            output_dict = OrderedDict()

            for column_name, column_value in zip(column_headers, row):

                column_value = strip_and_remove_obscure_whitespace(
                    column_value)

                if Columns.make_key(
                        column_name
                ) in self.recipient_column_headers_as_column_keys:
                    output_dict[column_name] = column_value or None
                else:
                    insert_or_append_to_dict(output_dict, column_name,
                                             column_value or None)

            length_of_row = len(row)

            if length_of_column_headers < length_of_row:
                output_dict[None] = row[length_of_column_headers:]
            elif length_of_column_headers > length_of_row:
                for key in column_headers[length_of_row:]:
                    insert_or_append_to_dict(output_dict, key, None)

            if index < self.max_rows:
                yield Row(
                    output_dict,
                    index=index,
                    error_fn=self._get_error_for_field,
                    recipient_column_headers=self.recipient_column_headers,
                    placeholders=self.placeholders_as_column_keys,
                    template=self.template,
                    allow_international_letters=self.
                    allow_international_letters,
                )
            else:
                yield None
Пример #28
0
def get_example_csv_rows(template,
                         use_example_as_example=True,
                         submitted_fields=False):
    return {
        'email': ['*****@*****.**']
        if use_example_as_example else [current_user.email_address],
        'sms': ['6502532222']
        if use_example_as_example else [current_user.mobile_number],
        'letter': [(submitted_fields or {}).get(
            key,
            get_example_letter_address(key) if use_example_as_example else key)
                   for key in first_column_headings['letter']]
    }[template.template_type] + get_example_csv_fields(
        (placeholder
         for placeholder in template.placeholders if placeholder not in
         Columns.from_keys(first_column_headings[template.template_type])),
        use_example_as_example, submitted_fields)
Пример #29
0
 def get_annotated_rows(self):
     if self.too_many_rows:
         return []
     for row_index, row in enumerate(self.rows):
         if self.template:
             self.template.values = dict(row.items())
         yield dict(
             columns=Columns({key: {
                 'data': value,
                 'error': self._get_error_for_field(key, value),
                 'ignore': (
                     key != Columns.make_key(self.recipient_column_header) and
                     key not in self.placeholders_as_column_keys
                 )
             } for key, value in row.items()}),
             index=row_index,
             message_too_long=bool(self.template and self.template.content_too_long)
         )
Пример #30
0
 def get_annotated_rows(self):
     if self.too_many_rows:
         return []
     for row_index, row in enumerate(self.rows):
         if self.template:
             self.template.values = dict(row.items())
         yield dict(
             columns=Columns({
                 key: {
                     'data': value,
                     'error': self._get_error_for_field(key, value),
                     'ignore': (key not in self.placeholders_as_column_keys)
                 }
                 for key, value in row.items()
             }),
             index=row_index,
             message_too_long=bool(
                 self.template
                 and self.template.content_count > self.character_limit))
Пример #31
0
def send_notification(service_id, template_id):
    if {'recipient', 'placeholders'} - set(session.keys()):
        return redirect(
            url_for(
                '.send_one_off',
                service_id=service_id,
                template_id=template_id,
            ))

    db_template = current_service.get_template_with_user_permission_or_403(
        template_id, current_user)

    try:
        noti = notification_api_client.send_notification(
            service_id,
            template_id=db_template['id'],
            recipient=session['recipient']
            or Columns(session['placeholders'])['address line 1'],
            personalisation=session['placeholders'],
            sender_id=session['sender_id'] if 'sender_id' in session else None)
    except HTTPError as exception:
        current_app.logger.info(
            'Service {} could not send notification: "{}"'.format(
                current_service.id, exception.message))
        return render_template(
            'views/notifications/check.html',
            **_check_notification(service_id, template_id, exception),
        )

    session.pop('placeholders')
    session.pop('recipient')
    session.pop('sender_id', None)

    return redirect(
        url_for(
            '.view_notification',
            service_id=service_id,
            notification_id=noti['id'],
            # used to show the final step of the tour (help=3) or not show
            # a back link on a just sent one off notification (help=0)
            help=request.args.get('help')))
Пример #32
0
 def __str__(self):
     return Markup(
         self.jinja_template.render({
             'admin_base_url':
             self.admin_base_url,
             'logo_file_name':
             self.logo_file_name,
             'subject':
             self.subject,
             'message':
             Take.as_field(
                 strip_dvla_markup(self.content),
                 self.values,
                 html='escape',
                 markdown_lists=True).then(strip_pipes).then(
                     prepare_newlines_for_markdown).then(
                         notify_letter_preview_markdown).as_string,
             'address':
             Take.as_field(
                 self.address_block,
                 (self.values_with_default_optional_address_lines if all(
                     Columns(self.values).get(key) for key in {
                         'address line 1',
                         'address line 2',
                         'postcode',
                     }) else self.values),
                 html='escape',
                 with_brackets=False).then(strip_pipes).then(
                     remove_empty_lines).then(nl2br).as_string,
             'contact_block':
             Take.as_field(
                 '\n'.join(line.strip()
                           for line in self.contact_block.split('\n')),
                 self.values,
                 html='escape',
             ).then(nl2br).then(strip_pipes).as_string,
             'date':
             datetime.utcnow().strftime('%-d %B %Y')
         }))
Пример #33
0
 def __init__(self, old_template, new_template):
     self.old_placeholders = Columns.from_keys(old_template.placeholders)
     self.new_placeholders = Columns.from_keys(new_template.placeholders)
Пример #34
0
 def has_recipient_column(self):
     return Columns.make_key(self.recipient_column_header) in set(
         Columns.make_key(column_header) for column_header in self.column_headers
     )
Пример #35
0
def test_lookup(key, should_be_present, in_dictionary):
    assert (key in Columns(in_dictionary)) == should_be_present
Пример #36
0
 def has_recipient_columns(self):
     return set(
         Columns.make_key(recipient_column)
         for recipient_column in self.recipient_column_headers
         if not self.is_optional_address_column(recipient_column)
     ) <= self.column_headers_as_column_keys
Пример #37
0
 def is_optional_address_column(self, key):
     return (self.template_type == 'letter' and Columns.make_key(key)
             in Columns.from_keys(optional_address_columns).keys())