Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More
Clients in Lino Avanti¶
This document describes the lino_avanti.lib.avanti
plugin.
Side note: Code snippets (lines starting with >>>
) in this document get
tested as part of our development workflow. The following
initialization snippet tells you which demo project is being used in
this document.
>>> import lino
>>> lino.startup('lino_book.projects.avanti1.settings')
>>> from lino.api.doctest import *
Overview¶
A client is a person using our services.
The legacy file number¶
Dossiernummern:
Wenn du “ip6” eintippst, sucht Lino die letzte Dossiernummer, die mit “IP 6” beginnt und zählt +1 hinzu. Also wenn der letzte bestehende Klient “IP 6923” hat, macht Lino aus “ip6” eine “IP 6924”.
Du kannst auch im Schnellsuche-Feld “ip 6900” eintippen, um nach Dossiernummer zu suchen.
Wenn du “ip 1234” eintippst (also die Dossiernummer selber als vierstellige Zahl angibst), dann lässt Lino diese Nummer stehen.
Ob du “ip” oder “IP” eintippst, ist egal, Lino macht daraus immer “IP”.
Auch das Leerzeichen kannst du beim Eintippen sparen, das setzt Lino automatisch rein.
Wenn die Dossiernummer nicht mit “ip” beginnt, lässt Lino sie unverändert
>>> other = avanti.Client.objects.get(pk=116)
>>> other2 = avanti.Client.objects.get(pk=117)
>>> def update_other(ref, ref2):
... other.ref = ref
... other.full_clean()
... other.save()
... other2.ref = ref2
... other2.full_clean()
... other2.save()
>>> update_other(None, None) # tidy up from previous test run
>>> def test(ref):
... obj = avanti.Client(ref=ref, name="x")
... obj.full_clean()
... print(obj.ref)
>>> test("ip")
IP 0001
>>> test("ip 1")
IP 1001
>>> update_other("IP 4010", "IP 5123")
>>> test("ip 4")
IP 4011
>>> test("ip")
IP 5124
>>> update_other("IP 6999", "IP 7000")
>>> test("ip6")
IP 61000
>>> update_other("IP 60999", "IP 61000")
>>> test("ip6")
IP 61001
Damit Lino die Referenzen automatisch verteilen kann, müssen alle bestehenden Dossiernummern die gleiche Länge haben. Ansonsten kann Lino durcheinander kommen. Zum Beispiel:
>>> update_other("IP 6999", "IP 61000")
>>> test("ip6")
Traceback (most recent call last):
...
django.core.exceptions.ValidationError: {'ref': ['Client with this Legacy file number already exists.']}
>>> update_other(None, None) # tidy up for the following tests
Clients¶
- class lino_avanti.lib.avanti.Client(lino.core.model.Model)¶
- translator_type¶
Which type of translator is needed with this client.
See also
TranslatorTypes
- professional_state¶
The professional situation of this client.
See also
ProfessionalStates
- overview¶
A panel with general information about this client.
- client_state¶
The state of this client record.
This is a pointer to
ClientStates
and can have the following values:>>> rt.show('clients.ClientStates') ======= ========== ============ ============= value name text Button text ------- ---------- ------------ ------------- 05 incoming Incoming 07 informed Informed 10 newcomer Newcomer 15 equal Equal 20 coached Registered 25 inactive Inactive 30 former Ended 40 refused Abandoned ======= ========== ============ =============
- unemployed_since¶
The date when this client got unemployed and stopped to have a regular work.
- seeking_since¶
The date when this client registered as unemployed and started to look for a new job.
- work_permit_suspended_until¶
- city¶
The place (village or municipality) where this client lives.
See
lino_xl.lib.contacts.Partner.city
.
- class lino_avanti.lib.avanti.ClientDetail¶
- class lino_avanti.lib.avanti.Clients¶
Base class for most lists of clients.
- client_state¶
If not empty, show only Clients whose client_state equals the specified value.
- class lino_avanti.lib.avanti.AllClients(Clients)¶
This table is visible for Explorer who can also export it.
This table shows only a very limited set of fields because e.g. an auditor may not see all data for privacy reasons. For example the names are hidden. OTOH it includes the
municipality
virtual field.
>>> show_columns(avanti.AllClients, all=True)
...
- State (client_state) : The state of this client record.
- Starting reason (starting_reason) :
- Ending reason (ending_reason) :
- Locality (city) : The locality, i.e. usually a village, city or town.
- Municipality (municipality) : The municipality where this client lives. This is basically
equal to city, except when city is a village
and has a parent which is a municipality (which causes that
place to be returned).
- Country (country) :
- Zip code (zip_code) :
- Nationality (nationality) : The nationality. This is a pointer to
countries.Country which should
contain also entries for refugee statuses.
- Gender (gender) : The sex of this person (male or female).
- Birth country (birth_country) :
- Lives in Belgium since (in_belgium_since) : Uncomplete dates are allowed, e.g.
"00.00.1980" means "some day in 1980",
"00.07.1980" means "in July 1980"
or "23.07.0000" means "on a 23th of July".
- Needs work permit (needs_work_permit) :
- Translator type (translator_type) : Which type of translator is needed with this client.
- Mother tongues (mother_tongues) :
- None (cef_level_de) :
- None (cef_level_fr) :
- None (cef_level_en) :
- Primary coach (user) : The author of this database object.
- Recurrency policy (event_policy) :
>>> rt.show(avanti.AllClients, limit=5)
...
============ ================= =============== ============ ============== ========= ========== ============= ========== ======== =============== ======================== =================== ================= ================ =============== =============== =============== ================= =================== ============== ================
State Starting reason Ending reason Locality Municipality Country Zip code Nationality Age Gender Birth country Lives in Belgium since Needs work permit Translator type Mother tongues cef_level_de cef_level_fr cef_level_en Primary coach Recurrency policy Erstgespräch Bilanzgespräch
------------ ----------------- --------------- ------------ -------------- --------- ---------- ------------- ---------- -------- --------------- ------------------------ ------------------- ----------------- ---------------- --------------- --------------- --------------- ----------------- ------------------- -------------- ----------------
Registered 4700 Eupen 4700 Eupen Belgium 4700 16 years Male No SETIS Dutch Not specified Not specified Not specified nathalie Every month 30/07/2016
Registered 4700 Eupen 4700 Eupen Belgium 4700 20 years Female No Other English Not specified Not specified Not specified Romain Raffault Every 2 weeks
Registered 4700 Eupen 4700 Eupen Belgium 4700 22 years Male No Other French A1+ Not specified Not specified Rolf Rompen Other
Registered 4700 Eupen 4700 Eupen Belgium 4700 24 years Male No Other English Not specified Not specified Not specified Robin Rood Every 2 months
Registered 4700 Eupen 4700 Eupen Belgium 4700 26 years Male No SETIS French Not specified Not specified Not specified nathalie Every 3 months
============ ================= =============== ============ ============== ========= ========== ============= ========== ======== =============== ======================== =================== ================= ================ =============== =============== =============== ================= =================== ============== ================
- class lino_avanti.lib.avanti.MyClients(Clients)¶
Shows all clients having me as primary coach. Shows all client states.
>>> rt.login('robin').show('avanti.MyClients') ... ===================================== ============ =============== ======== ================================= ========== ================ ======= ===== ==================== Name State National ID Mobile Address Age e-mail address Phone ID Legacy file number ------------------------------------- ------------ --------------- -------- --------------------------------- ---------- ---------------- ------- ----- -------------------- ABDALLAH Aáish (127/robin) Registered 920417 001-91 Bellmerin, 4700 Eupen 24 years 127 ABDO Aásim (138/robin) Registered 831201 001-50 Gülcherstraße, 4700 Eupen 33 years 138 ABDULLAH Afááf (155/robin) Ended 760102 002-86 4730 Raeren 41 years 155 ABOUD Ahláám (166/robin) Ended 690627 002-97 4730 Raeren 47 years 166 ARENT Afánásiiá (124/robin) Ended 891219 002-23 Bergkapellstraße, 4700 Eupen 27 years 124 ASTAFUROV Agáfiiá (175/robin) Registered 820120 002-60 Aachen, Germany 35 years 175 BARTOSZEWICZ Agáfokliiá (146/robin) Ended 781018 002-02 Herbesthaler Straße, 4700 Eupen 38 years 146 BERENDT Antoshá (165/robin) Ended 700602 001-93 4730 Raeren 46 years 165 CONTEE Chike (131/robin) Registered 870822 001-58 Edelstraße, 4700 Eupen 29 years 131 DIOP Ashánti (142/robin) Registered 810214 002-32 Habsburgerweg, 4700 Eupen 36 years 142 JALLOH Diállo (158/robin) Registered 740810 001-48 4730 Raeren 42 years 158 ===================================== ============ =============== ======== ================================= ========== ================ ======= ===== ====================
- class lino_avanti.lib.avanti.ClientsByNationality(Clients)¶
- class lino_avanti.lib.avanti.Residence(lino.core.model.Model)¶
- class lino_avanti.lib.avanti.EndingReason(lino.core.model.Model)¶
>>> rt.show('avanti.EndingReasons')
==== ======================== ========================== ========================
ID Designation Designation (de) Designation (fr)
---- ------------------------ -------------------------- ------------------------
1 Successfully ended Erfolgreich beendet Successfully ended
2 Health problems Gesundheitsprobleme Health problems
3 Familiar reasons Familiäre Gründe Familiar reasons
4 Missing motivation Fehlende Motivation Missing motivation
5 Return to home country Rückkehr ins Geburtsland Return to home country
9 Other Sonstige Autre
==== ======================== ========================== ========================
- class lino_avanti.lib.avanti.Category(BabelDesignated)¶
>>> rt.show('avanti.Categories')
==== =============================== =============================== ===============================
ID Designation Designation (de) Designation (fr)
---- ------------------------------- ------------------------------- -------------------------------
1 Language course Sprachkurs Language course
2 Integration course Integrationskurs Integration course
3 Language & integration course Language & integration course Language & integration course
4 External course External course External course
5 Justified interruption Begründete Unterbrechung Justified interruption
6 Successfully terminated Erfolgreich beendet Successfully terminated
==== =============================== =============================== ===============================
- class lino_avanti.lib.avanti.TranslatorTypes¶
List of choices for the
Client.translator_type
field of a client.>>> rt.show(rt.models.avanti.TranslatorTypes, language="de") ====== ====== ========== Wert name Text ------ ------ ---------- 10 SETIS 20 Sonstige 30 Privat ====== ====== ==========
- class lino_avanti.lib.avanti.ProfessionalStates¶
List of choices for the
Client.professional_state
field of a client.>>> rt.show(rt.models.avanti.ProfessionalStates, language="de") ... ====== ====== ================================ Wert name Text ------ ------ -------------------------------- 100 Student 200 Arbeitslos 300 Eingeschrieben beim Arbeitsamt 400 Angestellt 500 Selbstständig 600 Pensioniert 700 Arbeitsunfähig ====== ====== ================================
>>> rt.show(checkdata.Checkers, language="en")
...
================================= =============================================
value text
--------------------------------- ---------------------------------------------
beid.SSINChecker Check for invalid SSINs
cal.ConflictingEventsChecker Check for conflicting calendar entries
cal.EventGuestChecker Entries without participants
cal.LongEntryChecker Too long-lasting calendar entries
cal.ObsoleteEventTypeChecker Obsolete generated calendar entries
comments.CommentChecker Check for missing owner in reply to comment
countries.PlaceChecker Check data of geographical places
dupable.DupableChecker Check for missing phonetic words
dupable.SimilarObjectsChecker Check for similar objects
linod.SystemTaskChecker Check for missing system tasks
memo.PreviewableChecker Check for previewables needing update
printing.CachedPrintableChecker Check for missing target files
system.BleachChecker Find unbleached html content
uploads.UploadChecker Check metadata of upload files
uploads.UploadsFolderChecker Find orphaned files in uploads folder
================================= =============================================
Career¶
Language knowledges¶
Avanti adds an entry date to the language knowledge table of a client. There can be multiple entries per language and client. Because we want to report whether knowledge changed after attending a course.
Some example cases:
>>> client = rt.models.avanti.Client.objects.get(pk=120)
>>> rt.show('cv.LanguageKnowledgesByPerson', client, nosummary=True)
...
========== =============== ============ ============ =========== ============= ============
Language Mother tongue Spoken Written CEF level Certificate Entry date
---------- --------------- ------------ ------------ ----------- ------------- ------------
Dutch No a bit moderate A2+ No 05/02/2017
Dutch No moderate quite well A2 No 12/01/2016
German No quite well very well A1+ No 12/01/2016
French Yes No 12/01/2016
========== =============== ============ ============ =========== ============= ============
>>> client = rt.models.avanti.Client.objects.get(pk=121)
>>> rt.show('cv.LanguageKnowledgesByPerson', client, nosummary=True)
...
========== =============== ======== ========= =========== ============= ============
Language Mother tongue Spoken Written CEF level Certificate Entry date
---------- --------------- -------- --------- ----------- ------------- ------------
Estonian Yes No 12/01/2016
========== =============== ======== ========= =========== ============= ============
>>> client = rt.models.avanti.Client.objects.get(pk=122)
>>> rt.show('cv.LanguageKnowledgesByPerson', client, nosummary=True, language="de")
...
============= =============== ============== ============== =============== ============ =================
Sprache Muttersprache Wort Schrift CEF-Kategorie Zertifikat Erfassungsdatum
------------- --------------- -------------- -------------- --------------- ------------ -----------------
Deutsch Nein gar nicht ein bisschen A1 Nein 05.02.17
Deutsch Nein ein bisschen mittelmäßig A0 Nein 12.01.16
Französisch Ja Nein 12.01.16
============= =============== ============== ============== =============== ============ =================
The end user usually sees the summary of language knowledges , which shows the
CEF level of the languages defined in lino.core.site.Site.languages
,
and only the most recent CEF level. For above client the CEF level for German
is A1 (not A0):
>>> rt.show('cv.LanguageKnowledgesByPerson', client, language="de")
...
en: Ohne Angabe
de: A1
fr: Ohne Angabe
Muttersprachen: Französisch
Creating a new client¶
>>> ses = rt.login("romain")
>>> url = '/api/avanti/MyClients/-99999?an=insert&fmt=json'
>>> test_client.force_login(ses.user)
>>> res = test_client.get(url)
>>> res.status_code
200
>>> d = AttrDict(json.loads(res.content))
>>> sorted(d.keys())
...
['data', 'phantom', 'title']
>>> d.phantom
True
>>> print(d.title)
Insérer Bénéficiaire
The dialog window has 6 data fields:
>>> sorted(d.data.keys())
['disabled_fields', 'email', 'first_name', 'gender', 'genderHidden', 'last_name']
>>> fld = avanti.Clients.parameters['observed_event']
>>> rt.show(fld.choicelist, language="en")
No data to display
Miscellaneous¶
Until 20200818 the help_text of the municipality field wasn’t set at all, and
the help text of Partner.city talked about a client because it had been
overwritten by the help text of lino_avanti.lib.contacts.Person.city
.
On 20210709 (after moving help texts to separate plugin lino.modlib.help
)
there was another subtle problem here.
Compare (a) the specs (i.e. the target of the links) and (b) the help texts of the following fields:
lino_avanti.lib.contacts.Person.city
lino_avanti.lib.contacts.Person.municipality
The lino_xl.lib.countries.CountryCity.municipality
field is defined on
the lino_xl.lib.countries.CountryCity
model mixin. It is documented on
countries : Countries and cities, which causes the help_text_extractor to extract its
help_text:
The locality, i.e. usually a village, city or town.
This help_text
is inherited by all models that use this model mixin:
Person, Partner, Company, Client. But Client overrides it again to be more
specific.
>>> print(contacts.Person._meta.get_field('municipality').help_text)
The municipality, i.e. either the city or a parent of it.
>>> print(contacts.Person._meta.get_field('city').help_text)
The locality, i.e. usually a village, city or town.
>>> print(contacts.Person._meta.get_field('city').help_text)
The locality, i.e. usually a village, city or town.
>>> print(avanti.Client._meta.get_field('municipality').help_text)
The municipality where this client lives. This is basically equal to city, except when city is a village and has a parent which is a municipality (which causes that place to be returned).
Don’t read¶
>>> obj = avanti.Client.objects.get(pk=167)
>>> rt.login("robin").show("changes.ChangesByMaster", obj)
Aucun enregistrement
The following (specifying a wrong mt) caused a server traceback until 20230620
>>> url = "/api/changes/ChangesByMaster?dm=grid&fmt=json&limit=15&mk=167&mt=75&start=0&ul=en&wt=d"
>>> res = test_client.get(url)
>>> d = json.loads(res.content.decode())
>>> print(d['title'])
Changes of MissingRow(Line matching query does not exist. (pk=167))
#5751 ObjectDoesNotExist: Invalid primary key 4968 for avanti.Clients¶
Before 20240910, the following request was returning {'data': 'Oops, Invalid
primary key 115 for avanti.Clients'}
because client 115 has client_state
“former” and therefore is not visible in the default avanti.Clients
table. This was #5751. Until 20241004 Lino didn’t check at all whether
the table (avanti.Clients in this case gives access to a particular row). This
was #5759 (Anonymous can GET private comments).
>>> ses = rt.login("robin")
>>> test_client.force_login(ses.user)
>>> pk = 115 # 166
>>> cli = avanti.Client.objects.get(pk=pk)
>>> cli.client_state
<clients.ClientStates.former:30>
>>> url = f"/api/avanti/Clients/{pk}?fmt=json"
>>> res = test_client.get(url)
Not Found: /api/avanti/Clients/115
>>> res.status_code
404
This 404 Not Found response is because indeed client 115 does not exist in the avanti.Clients table when using its default parameters (i.e. show only coached clients). That’s why the client must also provide the parameter values when asking for a single database row. Another reason for the parameter values is navigation: Lino does not only return data of the row but also navinfo with pointers to next and previous rows.
>>> url += "&fmt=json&pv=&pv=&pv=&pv=&pv=&pv=&pv=&pv=&pv=&pv=&pv="
>>> res = test_client.get(url)
>>> d = json.loads(res.content.decode())
>>> d['data']['first_name']
'Aábid'
>>> d['data']['cal.GuestsByPartner']
'<div><table cellspacing="3px" bgcolor="#eeeeee" width="100%"
name="cal.GuestsByPartner.grid">... title="Insert a new Presence." class="pi
pi-plus-circle" /></p></div>'
The same is true for delayed values:
>>> url = f"/values/avanti/Clients/{pk}/cal.GuestsByPartner"
>>> res = test_client.get(url)
Bad Request: /values/avanti/Clients/115/cal.GuestsByPartner
>>> url += "?pv=&pv=&pv=&pv=&pv=&pv=&pv=&pv=&pv=&pv=&pv="
>>> res = test_client.get(url)
>>> d = json.loads(res.content.decode())
>>> d
{'data': '<div class="htmlText"><ul><li>February 2017: ... <a href="..."
title="Insert a new Presence." class="pi pi-plus-circle"></a></p></div>'}