diff --git a/notifications_utils/recipients.py b/notifications_utils/recipients.py index 9b9a9863..a141a30d 100644 --- a/notifications_utils/recipients.py +++ b/notifications_utils/recipients.py @@ -26,8 +26,38 @@ region_code = os.getenv("PHONE_REGION_CODE", "US") first_column_headings = { - "email": ["email address"], - "sms": ["phone number"], + "en": { + "email": ["email address"], + "sms": ["phone number"], + # For backwards compatibility + "letter": [ + "address line 1", + "address line 2", + "address line 3", + "address line 4", + "address line 5", + "address line 6", + "postcode", + ], + }, + "fr": { + "email": ["adresse courriel"], + "sms": ["numéro de téléphone"], + # For backwards compatibility + "letter": [ + "address line 1", + "address line 2", + "address line 3", + "address line 4", + "address line 5", + "address line 6", + "postcode", + ], + }, + "email": ["email address"], # left this for backwards compatibility + "sms": ["phone number"], # left this for backwards compatibility + "email_both": ["email address", "adresse courriel"], + "sms_both": ["phone number", "numéro de téléphone"], "letter": [ "address line 1", "address line 2", @@ -60,7 +90,9 @@ def __init__( remaining_messages=sys.maxsize, international_sms=False, max_rows=50000, + user_language="en", ): + self.user_language = user_language self.file_data = strip_whitespace(file_data, extra_characters=",") self.template_type = template_type self.placeholders = placeholders @@ -114,7 +146,10 @@ def template_type(self): @template_type.setter def template_type(self, value): self._template_type = value - self.recipient_column_headers = first_column_headings[self.template_type] + self.recipient_column_headers = first_column_headings[self.user_language][self.template_type] # type: ignore + self.recipient_column_headers_lang_check = ( + first_column_headings.get("email_both") if self.template_type == "email" else first_column_headings.get("sms_both") + ) @property def has_errors(self): @@ -263,11 +298,31 @@ def column_headers_as_column_keys(self): @property 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)) - ) + """ + Missing headers must return the following in each case: + + Eg 1: file: [] + placeholders: [phone number, name] + missing: [phone number, name] + + Eg 2: file: [adresse courriel] + placeholders: [email address] + missing: [] + + """ + result = set() + for key in self.placeholders: + # If the key has a different heading due to language, then we need to check that the column_headers + # in the file have either the same or other language. This is only for email address / phone number + if key in self.recipient_column_headers_lang_check: # type: ignore + if not set(map(Columns.make_key, self.recipient_column_headers_lang_check)).intersection( # type: ignore + set(self.column_headers_as_column_keys) + ): + result.add(key) + continue + elif Columns.make_key(key) not in self.column_headers_as_column_keys and not self.is_optional_address_column(key): + result.add(key) + return result @property def duplicate_recipient_column_headers(self): @@ -290,13 +345,12 @@ def is_optional_address_column(self, key): @property def has_recipient_columns(self): - return ( + return set([list(self.column_headers_as_column_keys)[0]]).issubset( set( Columns.make_key(recipient_column) - for recipient_column in self.recipient_column_headers + for recipient_column in self.recipient_column_headers_lang_check # type: ignore if not self.is_optional_address_column(recipient_column) ) - <= self.column_headers_as_column_keys ) def _get_error_for_field(self, key, value): # noqa: C901 diff --git a/tests/test_recipient_csv.py b/tests/test_recipient_csv.py index f9007a80..1b0e9d09 100644 --- a/tests/test_recipient_csv.py +++ b/tests/test_recipient_csv.py @@ -14,18 +14,10 @@ def _index_rows(rows): @pytest.mark.parametrize( - "file_contents,template_type,expected", + "file_contents,template_type,expected,user_language", [ - ( - "", - "sms", - [], - ), - ( - "phone number", - "sms", - [], - ), + ("", "sms", [], "en"), + ("phone number", "sms", [], "fr"), ( """ phone number,name @@ -34,6 +26,7 @@ def _index_rows(rows): """, "sms", [[("phone number", "+1 123"), ("name", "test1")], [("phone number", "+1 456"), ("name", "test2")]], + "en", ), ( """ @@ -43,6 +36,7 @@ def _index_rows(rows): """, "sms", [[("phone number", "+1 123"), ("name", None)], [("phone number", "+1 456"), ("name", None)]], + "en", ), ( """ @@ -55,6 +49,7 @@ def _index_rows(rows): [("email address", "test@example.com"), ("name", "test1")], [("email address", "test2@example.com"), ("name", "test2")], ], + "en", ), ( """ @@ -67,6 +62,7 @@ def _index_rows(rows): [("email address", "test@example.com"), (None, ["test1", "red"])], [("email address", "test2@example.com"), (None, ["test2", "blue"])], ], + "en", ), ( """ @@ -81,6 +77,7 @@ def _index_rows(rows): [("email address", "test2@example.com"), ("name", "test2")], [("email address", "test3@example.com"), ("name", "test3")], ], + "en", ), ( """ @@ -93,53 +90,70 @@ def _index_rows(rows): [("email address", "test@example.com"), ("date", "Nov 28, 2016"), ("name", "test1")], [("email address", "test2@example.com"), ("date", "Nov 29, 2016"), ("name", "test2")], ], + "en", ), ( """ - address_line_1 - Alice - Bob + phone number, list, list, list + 07900900001, cat, rat, gnat + 07900900002, dog, hog, frog + 07900900003, elephant """, - "letter", - [[("address_line_1", "Alice")], [("address_line_1", "Bob")]], + "sms", + [ + [("phone number", "07900900001"), ("list", ["cat", "rat", "gnat"])], + [("phone number", "07900900002"), ("list", ["dog", "hog", "frog"])], + [("phone number", "07900900003"), ("list", ["elephant", None, None])], + ], + "en", ), ( """ - address line 1,address line 2,address line 5,address line 6,postcode,name,thing - A. Name,,,,XM4 5HQ,example,example + address courriel,name + test@example.com,test1 + test2@example.com, test2 """, - "letter", + "email", [ - [ - ("addressline1", "A. Name"), - ("addressline2", None), - # optional address rows 3 and 4 not in file - ("addressline5", None), - ("addressline5", None), - ("postcode", "XM4 5HQ"), - ("name", "example"), - ("thing", "example"), - ] + [("address courriel", "test@example.com"), ("name", "test1")], + [("address courriel", "test2@example.com"), ("name", "test2")], ], + "fr", ), ( """ - phone number, list, list, list + numéro de téléphone, list, list, list 07900900001, cat, rat, gnat 07900900002, dog, hog, frog 07900900003, elephant """, "sms", [ - [("phone number", "07900900001"), ("list", ["cat", "rat", "gnat"])], - [("phone number", "07900900002"), ("list", ["dog", "hog", "frog"])], - [("phone number", "07900900003"), ("list", ["elephant", None, None])], + [("numéro de téléphone", "07900900001"), ("list", ["cat", "rat", "gnat"])], + [("numéro de téléphone", "07900900002"), ("list", ["dog", "hog", "frog"])], + [("numéro de téléphone", "07900900003"), ("list", ["elephant", None, None])], ], + "fr", + ), + ( + """ + numéro de téléphone, list, list, list + 07900900001, cat, rat, gnat + 07900900002, dog, hog, frog + 07900900003, elephant + """, + "sms", + [ + [("numéro de téléphone", "07900900001"), ("list", ["cat", "rat", "gnat"])], + [("numéro de téléphone", "07900900002"), ("list", ["dog", "hog", "frog"])], + [("numéro de téléphone", "07900900003"), ("list", ["elephant", None, None])], + ], + "en", ), ], ) -def test_get_rows(file_contents, template_type, expected): - rows = list(RecipientCSV(file_contents, template_type=template_type).rows) +def test_get_rows(file_contents, template_type, expected, user_language): + rows = list(RecipientCSV(file_contents, template_type=template_type, user_language=user_language).rows) if not expected: assert rows == expected for index, row in enumerate(expected): @@ -399,35 +413,42 @@ def test_get_recipient_respects_order(file_contents, template_type, placeholders @pytest.mark.parametrize( - "file_contents,template_type,expected,expected_missing", + "file_contents,template_type,expected,expected_missing, user_language", [ - ("", "sms", [], set(["phone number", "name"])), + ("", "sms", [], set(["phone number", "name"]), "en"), + ("", "email", [], set(["email address", "name"]), "en"), ( """ - phone number,name + numéro de téléphone, name 6502532222,test1 6502532222,test1 6502532222,test1 """, "sms", - ["phone number", "name"], + ["numéro de téléphone", "name"], set(), + "fr", ), ( """ - email address,name,colour + phone number,name + 6502532222,test1 + 6502532222,test1 + 6502532222,test1 """, - "email", - ["email address", "name", "colour"], + "sms", + ["phone number", "name"], set(), + "en", ), ( """ - address_line_1, address_line_2, postcode, name + email address,name,colour """, - "letter", - ["address_line_1", "address_line_2", "postcode", "name"], + "email", + ["email address", "name", "colour"], set(), + "en", ), ( """ @@ -436,27 +457,42 @@ def test_get_recipient_respects_order(file_contents, template_type, placeholders "email", ["email address", "colour"], set(["name"]), + "en", ), ( """ - address_line_1, address_line_2, name + phone number,list,list,name,list """, - "letter", - ["address_line_1", "address_line_2", "name"], - set(["postcode"]), + "sms", + ["phone number", "list", "name"], + set(), + "en", ), ( """ - phone number,list,list,name,list + numéro de téléphone,list,list,name,list """, "sms", - ["phone number", "list", "name"], + ["numéro de téléphone", "list", "name"], set(), + "en", + ), + ( + """ + list,list,name,list + """, + "sms", + [ + "list", + "name", + ], + set(["numéro de téléphone"]), + "fr", ), ], ) -def test_column_headers(file_contents, template_type, expected, expected_missing): - recipients = RecipientCSV(file_contents, template_type=template_type, placeholders=["name"]) +def test_column_headers(file_contents, template_type, expected, expected_missing, user_language): + recipients = RecipientCSV(file_contents, template_type=template_type, placeholders=["name"], user_language=user_language) assert recipients.column_headers == expected assert recipients.missing_column_headers == expected_missing assert recipients.has_errors == bool(expected_missing) @@ -469,20 +505,14 @@ def test_column_headers(file_contents, template_type, expected, expected_missing pytest.param("", "sms", marks=pytest.mark.xfail), pytest.param("name", "sms", marks=pytest.mark.xfail), pytest.param("email address", "sms", marks=pytest.mark.xfail), - pytest.param( - # missing postcode - "address_line_1, address_line_2, address_line_3, address_line_4, address_line_5", - "letter", - marks=pytest.mark.xfail, - ), ("phone number", "sms"), ("phone number,name", "sms"), ("email address", "email"), ("email address,name", "email"), ("PHONENUMBER", "sms"), ("email_address", "email"), - ("address_line_1, address_line_2, postcode", "letter"), - ("address_line_1, address_line_2, address_line_3, address_line_4, address_line_5, address_line_6, postcode", "letter"), + ("adresse courriel", "email"), + ("numéro de téléphone", "sms"), ], ) def test_recipient_column(placeholders, file_contents, template_type):