Ejemplo n.º 1
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,
    )
Ejemplo n.º 2
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'
Ejemplo n.º 3
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)
Ejemplo n.º 4
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)
Ejemplo n.º 5
0
def test_columns_as_dict():
    assert dict(Columns({
        'date of birth': '01/01/2001',
        'TOWN': 'London'
    })) == {
        'dateofbirth': '01/01/2001',
        'town': 'London'
    }
Ejemplo n.º 6
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 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'
 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}
Ejemplo n.º 10
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')
         }))
 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)
Ejemplo n.º 12
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()
Ejemplo n.º 13
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))
Ejemplo n.º 14
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')))
Ejemplo n.º 15
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')
         }))
Ejemplo n.º 16
0
def get_normalised_placeholders_from_session():
    return Columns(session.get('placeholders', {}))
Ejemplo n.º 17
0
 def _get_personalisation_from_row(self, row):
     return Columns({
         key: value
         for key, value in row.items()
         if key in self.placeholders_as_column_keys
     })
Ejemplo n.º 18
0
def test_lookup(key, should_be_present, in_dictionary):
    assert (key in Columns(in_dictionary)) == should_be_present
        'address_line_4': 'SW1A1AA',
    },
    {
        'address_line_2': '123 Example Street',
        'address_line_5': 'City of Town',
        'address_line_7': 'SW1A1AA',
    },
    {
        'address_line_1': '123 Example Street',
        'address_line_3': 'City of Town',
        'address_line_7': 'SW1A1AA',
        'postcode': 'ignored if address line 7 provided',
    },
    Columns({
        'address line 1': '123 Example Street',
        'ADDRESS_LINE_2': 'City of Town',
        'Address-Line-7': 'Sw1a  1aa',
    }),
))
def test_from_personalisation(personalisation):
    assert PostalAddress.from_personalisation(personalisation).normalised == (
        '123 Example Street\n'
        'City of Town\n'
        'SW1A 1AA'
    )


def test_from_personalisation_handles_int():
    personalisation = {
        'address_line_1': 123,
        'address_line_2': 'Example Street',
Ejemplo n.º 20
0
def get_back_link(service_id, template, step_index, placeholders=None):
    if get_help_argument():
        # if we're on the check page, redirect back to the beginning. anywhere else, don't return the back link
        if request.endpoint == 'main.check_notification':
            return url_for('main.send_test',
                           service_id=service_id,
                           template_id=template.id,
                           help=get_help_argument())
        else:
            if step_index == 0:
                return url_for(
                    'main.start_tour',
                    service_id=service_id,
                    template_id=template.id,
                )
            elif step_index > 0:
                return url_for(
                    'main.send_test_step',
                    service_id=service_id,
                    template_id=template.id,
                    step_index=step_index - 1,
                    help=2,
                )
    elif 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,
            )
    elif is_current_user_the_recipient() and step_index > 1:
        return url_for(
            'main.send_test_step',
            service_id=service_id,
            template_id=template.id,
            step_index=step_index - 1,
        )
    elif is_current_user_the_recipient() and step_index == 1:
        return url_for(
            'main.send_one_off_step',
            service_id=service_id,
            template_id=template.id,
            step_index=0,
        )

    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,
    )
def test_set_item(key_in, lookup_key):
    columns = Columns({})
    columns[key_in] = 'bar'
    assert columns[lookup_key] == 'bar'
Ejemplo n.º 22
0
 def values(self, value):
     self._values = Columns(value) if value else {}
 def postal_address(self):
     return PostalAddress.from_personalisation(Columns(self.values))
Ejemplo n.º 24
0
def send_one_off_step(service_id, template_id, step_index):
    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)

    email_reply_to = None
    sms_sender = None
    if db_template['template_type'] == 'email':
        email_reply_to = get_email_reply_to_address_from_session()
    elif db_template['template_type'] == 'sms':
        sms_sender = get_sms_sender_from_session()

    template_values = get_recipient_and_placeholders_from_session(
        db_template['template_type'])

    template = get_template(db_template,
                            current_service,
                            show_recipient=True,
                            letter_preview_url=url_for(
                                'no_cookie.send_test_preview',
                                service_id=service_id,
                                template_id=template_id,
                                filetype='png',
                            ),
                            page_count=get_page_count_for_letter(
                                db_template, values=template_values),
                            email_reply_to=email_reply_to,
                            sms_sender=sms_sender)

    placeholders = fields_to_fill_in(template)

    try:
        current_placeholder = placeholders[step_index]
    except IndexError:
        if all_placeholders_in_session(placeholders):
            return get_notification_check_endpoint(service_id, template)
        return redirect(
            url_for(
                '.send_one_off',
                service_id=service_id,
                template_id=template_id,
            ))

    # if we're in a letter, we should show address block rather than "address line #" or "postcode"
    if template.template_type == 'letter':
        if step_index < len(address_lines_1_to_7_keys):
            return redirect(
                url_for(
                    '.send_one_off_letter_address',
                    service_id=service_id,
                    template_id=template_id,
                ))
        if current_placeholder in Columns(
                PostalAddress('').as_personalisation):
            return redirect(
                url_for(
                    request.endpoint,
                    service_id=service_id,
                    template_id=template_id,
                    step_index=step_index + 1,
                ))

    form = get_placeholder_form_instance(
        current_placeholder,
        dict_to_populate_from=get_normalised_placeholders_from_session(),
        template_type=template.template_type,
        allow_international_phone_numbers=current_service.has_permission(
            'international_sms'),
    )

    if form.validate_on_submit():
        # if it's the first input (phone/email), we store against `recipient` as well, for easier extraction.
        # Only if it's not a letter.
        # And only if we're not on the test route, since that will already have the user's own number set
        if (step_index == 0 and template.template_type != 'letter'):
            session['recipient'] = form.placeholder_value.data

        session['placeholders'][
            current_placeholder] = form.placeholder_value.data

        if all_placeholders_in_session(placeholders):
            return get_notification_check_endpoint(service_id, template)

        return redirect(
            url_for(
                request.endpoint,
                service_id=service_id,
                template_id=template_id,
                step_index=step_index + 1,
            ))

    back_link = get_back_link(service_id, template, step_index, placeholders)

    template.values = template_values
    template.values[current_placeholder] = None

    return render_template(
        'views/send-test.html',
        page_title=get_send_test_page_title(
            template.template_type,
            entering_recipient=not session['recipient'],
            name=template.name,
        ),
        template=template,
        form=form,
        skip_link=get_skip_link(step_index, template),
        back_link=back_link,
        link_to_upload=(request.endpoint == 'main.send_one_off_step'
                        and step_index == 0),
    )