Europass import: a telephone number typed with its trunk zero keeps the zero after the dialling code #644

Closed
opened 2026-10-01 10:48:37 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the Candidate reader's contact details and running it on files whose number is written the national way.

Checked again on 2026-10-01 against main at 0a5cad587: still present. The code is now at plugins/europass/candidate.py:410-425 (_phone); the CV prints the number at documents/rendering.py:174, and the link is built at core/templatetags/postulo.py:301-307. resume/importing.py and core/phones.py are unchanged.

The Candidate reader builds an international number by putting the file's CountryDialing in front of every digit of AreaDialing and DialNumber. When the number in the file is in its national form, with the trunk prefix, the result is not a valid international number, and it is stored as the profile's telephone number without a check. The telephone field treats the same typing differently: phones.combine removes the leading zero before it puts the country's code in front.

  • Where: plugins/europass/candidate.py:409-424 (_phone: digits = re.sub(r"\D", "", local) then return f"+{country}{digits}"); resume/importing.py:150-159 (apply stores the number through save_only_number, no form and no validator); compare core/phones.py:389-392 (combine).

  • What happens: run against the snapshot:

    CountryDialing AreaDialing DialNumber record.person["phone"] the telephone field stores
    44 07911 123456 +4407911123456 +447911123456
    33 06 12 34 56 78 +330612345678 +33612345678
    49 0176 12345678 +49017612345678 +4917612345678
    44 020 7946 123456 +440207946123456 +44207946123456

    The imported value then becomes the profile's primary number. It is printed on the CV as stored (documents/rendering.py:200), and wherever a page shows the number as a link it is tel:+4407911123456, grouped as +44 079 111 234 56 (core/templatetags/postulo.py:320-326). Its normalised form (core/models.py:372) differs from the correct number's, so the instance-wide uniqueness check in apply (resume/importing.py:155, taken_elsewhere) does not see that it is a number somebody already holds.

    What the editor at europass.europa.eu writes in DialNumber when somebody types the zero has not been confirmed on a real download: both fixtures hold numbers from countries without a trunk prefix (tests/data/europass-candidate.xml:57-58, tests/data/europass-candidate-sample.xml:76-78). The reader accepts any Candidate file, whoever wrote it, so the case is reachable either way.

  • Why it matters: an import fills Your details with a number that cannot be dialled as written, and every CV that prints the telephone number prints it. Nothing on the review page says the number was altered or is doubtful.

  • Fix: do not copy combine's lstrip("0") into the plugin. That rule is wrong for the countries whose leading zero is part of the number: CountryDialing 39 with 06 6982 1234 is read correctly today as +390669821234, and combine would make it +39669821234. Whether a leading zero is a trunk prefix is a fact of each country's numbering plan, which #304 brings in. Once it has, pass the number through that parser where the importer writes it (resume/importing.py:150-159), so every importer gets the same treatment and the plugin keeps importing nothing but postulo.plugins.api: a national prefix after the dialling code is removed where the plan defines one, the E.164 form is stored, and a number the plan calls impossible is reported in Report.skipped instead of being stored. Update _phone's docstring, which today argues only the case with no dialling code.

Related to #304, which decides what a valid number is and whose tests cover the form field only; this path never passes through the field. Related to #318: add "a number typed with its leading zero" to its checklist, so the real download settles what the editor writes.

Done when

  • tests/test_europass_candidate.py reads a Candidate file with CountryDialing 44 and DialNumber 07911 123456, applies it, and finds +447911123456 as the profile's number; the same test with 39 and 06 6982 1234 finds +390669821234.
  • tests/test_importers.py applies a record whose phone is +4407911123456 and finds the corrected number stored, or the number named in report.skipped.
Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the Candidate reader's contact details and running it on files whose number is written the national way. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. The code is now at `plugins/europass/candidate.py:410-425` (`_phone`); the CV prints the number at `documents/rendering.py:174`, and the link is built at `core/templatetags/postulo.py:301-307`. `resume/importing.py` and `core/phones.py` are unchanged. The Candidate reader builds an international number by putting the file's `CountryDialing` in front of every digit of `AreaDialing` and `DialNumber`. When the number in the file is in its national form, with the trunk prefix, the result is not a valid international number, and it is stored as the profile's telephone number without a check. The telephone field treats the same typing differently: `phones.combine` removes the leading zero before it puts the country's code in front. - **Where:** `plugins/europass/candidate.py:409-424` (`_phone`: `digits = re.sub(r"\D", "", local)` then `return f"+{country}{digits}"`); `resume/importing.py:150-159` (`apply` stores the number through `save_only_number`, no form and no validator); compare `core/phones.py:389-392` (`combine`). - **What happens:** run against the snapshot: | `CountryDialing` | `AreaDialing` | `DialNumber` | `record.person["phone"]` | the telephone field stores | | --- | --- | --- | --- | --- | | `44` | | `07911 123456` | `+4407911123456` | `+447911123456` | | `33` | | `06 12 34 56 78` | `+330612345678` | `+33612345678` | | `49` | | `0176 12345678` | `+49017612345678` | `+4917612345678` | | `44` | `020 7946` | `123456` | `+440207946123456` | `+44207946123456` | The imported value then becomes the profile's primary number. It is printed on the CV as stored (`documents/rendering.py:200`), and wherever a page shows the number as a link it is `tel:+4407911123456`, grouped as `+44 079 111 234 56` (`core/templatetags/postulo.py:320-326`). Its `normalised` form (`core/models.py:372`) differs from the correct number's, so the instance-wide uniqueness check in `apply` (`resume/importing.py:155`, `taken_elsewhere`) does not see that it is a number somebody already holds. What the editor at europass.europa.eu writes in `DialNumber` when somebody types the zero has not been confirmed on a real download: both fixtures hold numbers from countries without a trunk prefix (`tests/data/europass-candidate.xml:57-58`, `tests/data/europass-candidate-sample.xml:76-78`). The reader accepts any Candidate file, whoever wrote it, so the case is reachable either way. - **Why it matters:** an import fills *Your details* with a number that cannot be dialled as written, and every CV that prints the telephone number prints it. Nothing on the review page says the number was altered or is doubtful. - **Fix:** do not copy `combine`'s `lstrip("0")` into the plugin. That rule is wrong for the countries whose leading zero is part of the number: `CountryDialing` `39` with `06 6982 1234` is read correctly today as `+390669821234`, and `combine` would make it `+39669821234`. Whether a leading zero is a trunk prefix is a fact of each country's numbering plan, which #304 brings in. Once it has, pass the number through that parser where the importer writes it (`resume/importing.py:150-159`), so every importer gets the same treatment and the plugin keeps importing nothing but `postulo.plugins.api`: a national prefix after the dialling code is removed where the plan defines one, the E.164 form is stored, and a number the plan calls impossible is reported in `Report.skipped` instead of being stored. Update `_phone`'s docstring, which today argues only the case with no dialling code. Related to #304, which decides what a valid number is and whose tests cover the form field only; this path never passes through the field. Related to #318: add "a number typed with its leading zero" to its checklist, so the real download settles what the editor writes. ## Done when - `tests/test_europass_candidate.py` reads a Candidate file with `CountryDialing` `44` and `DialNumber` `07911 123456`, applies it, and finds `+447911123456` as the profile's number; the same test with `39` and `06 6982 1234` finds `+390669821234`. - `tests/test_importers.py` applies a record whose `phone` is `+4407911123456` and finds the corrected number stored, or the number named in `report.skipped`.
tiagoagueda added this to the 0.6.0 milestone 2026-10-01 10:48:37 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#644
No description provided.