Households

The lino_welfare.modlib.households plugin extends lino.modlib.households by adding some PCSW-specific features.

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.

>>> from lino import startup
>>> startup('lino_welfare.projects.gerd.settings.doctests')
>>> from lino.api.doctest import *
>>> import datetime

Added features

class lino_welfare.modlib.households.RefundsByPerson

Shows the members of the primary household of this person together with an amount which depends on whether that member is adult or not.

This is a special form of lino_xl.lib.households.SiblingsByPerson, used e.g. in aids/Confirmation/clothes_bank.body.html.

It has two configurable attributes:

child_tariff

The amount to refund for children (household members less than lino_xl.lib.households.Plugin.adult_age years old).

adult_tariff

The amount to refund for children (household members who are lino_xl.lib.households.Plugin.adult_age years or older).

The values of these attributes are hard-coded and defaul to 10 and 20:

>>> rt.models.households.RefundsByPerson.child_tariff
Decimal('10')
>>> rt.models.households.RefundsByPerson.adult_tariff
Decimal('20')

You might change them locally e.g. in the lino.core.site.Site.custom_layouts module.

person_info

The full name of the household member.

amount

The amount to pay. This is either child_tariff or adult_tarif depending on the age of the household member.

See the examples below.

lino_welfare.modlib.households.get_household_summary(person, today=None, adult_age=None)

Return a string which expresses the household composition in a few words. See The household summary.

Note that members without birth_date are considered children.

Paul Frisch

Mr. Paul Frisch is a fictive client for which the demo database contains fictive family links.

>>> paul = contacts.Person.objects.get(first_name="Paul", last_name="Frisch")
>>> print(paul.id)
240
>>> print(paul)
Herr Paul FRISCH

His primary household consists of 2 adults and 3 children:

>>> print(rt.models.households.get_household_summary(paul))
2 Erwachsene und 3 Kinder
>>> test_client.force_login(rt.login('rolf').user)
>>> def check(uri, fieldname):
...     url = '/api/%s?fmt=json&an=detail' % uri
...     res = test_client.get(url, REMOTE_USER='rolf')
...     assert res.status_code == 200
...     d = json.loads(res.content)
...     if not fieldname in d['data']:
...         raise Exception("20181023 '{}' not in {}".format(
...             fieldname, d['data'].keys()))
...     return d['data'][fieldname]
>>> uri = 'contacts/Persons/{}'.format(paul.id)
>>> html = check(uri, 'humanlinks.LinksByHuman')
>>> soup = BeautifulSoup(html, 'lxml')
>>> links = soup.find_all('a')
>>> len(links)
23
>>> print(links[1].get('href'))
... 
javascript:Lino.contacts.Persons.detail.run(null,{ "base_params": { "mk": 240,
"mt": 8 }, "record_id": 238 })

These are the family relationships of Paul Frisch:

>>> print(soup.get_text(' ', strip=True))
... 
Paul ist Sohn von Gaby FROGEMUTH (79 Jahre), Hubert (80 Jahre) Vater von Dennis
(13 Jahre), Clara (14 Jahre), Philippe (17 Jahre), Peter (26 Jahre) Ehemann von
Petra ZWEITH (45 Jahre) Beziehung erstellen als Vater / Sohn Adoptivvater /
Adoptivsohn Pflegevater / Pflegesohn Ehemann Partner Stiefvater / Stiefsohn
Bruder Vetter Onkel / Neffe Verwandter Sonstiger

The previous AJAX call caused Django’s translation machine to switch to German. Switch back to English.

>>> translation.activate('en')

Paul’s father Hubert is married with Gaby and they live together. Their children have moved out.

>>> obj = contacts.Person.objects.get(name="Frisch Hubert")
>>> print(obj)
Mr Hubert FRISCH
>>> ses = rt.login('rolf')
>>> ses.show(households.SiblingsByPerson, master_instance=obj)
========== =================== =============== ==================== ============ =========== ============ ========
 Age        Role                Dependency      Person               First name   Last name   Birth date   Gender
---------- ------------------- --------------- -------------------- ------------ ----------- ------------ --------
 80 years   Head of household   Not at charge   Mr Hubert FRISCH     Hubert       Frisch      1933-07-21   Male
 79 years   Partner             Not at charge   Mrs Gaby FROGEMUTH   Gaby         Frogemuth   1934-08-04   Female
========== =================== =============== ==================== ============ =========== ============ ========

Paul Frisch is married with Petra Zweith and has a child from divorced marriage with Paula Einzig.

>>> obj = contacts.Person.objects.get(name="Frisch Paul")
>>> print(obj)
Mr Paul FRISCH
>>> ses.show(households.SiblingsByPerson, master_instance=obj)
========== =================== ================ ==================== ============ =========== ============ ========
 Age        Role                Dependency       Person               First name   Last name   Birth date   Gender
---------- ------------------- ---------------- -------------------- ------------ ----------- ------------ --------
 46 years   Head of household   Not at charge    Mr Paul FRISCH       Paul         Frisch      1967-06-19   Male
 45 years   Partner             Not at charge    Mrs Petra ZWEITH     Petra        Zweith      1968-12-31   Female
 17 years   Child               At full charge   Mr Philippe FRISCH   Philippe     Frisch      1997-01-01   Male
 14 years   Child               At full charge   Mrs Clara FRISCH     Clara        Frisch      1999-06-19   Female
 13 years   Child               At full charge   Mr Dennis FRISCH     Dennis       Frisch      2001-01-03   Male
========== =================== ================ ==================== ============ =========== ============ ========
>>> ses.show(households.RefundsByPerson, master_instance=obj)
========== ======== ==================== ===========
 Age        Gender   Person               Amount
---------- -------- -------------------- -----------
 46 years   Male     Paul FRISCH          20,00
 45 years   Female   Petra ZWEITH         20,00
 17 years   Male     Philippe FRISCH      10,00
 14 years   Female   Clara FRISCH         10,00
 13 years   Male     Dennis FRISCH        10,00
                     **Total (5 rows)**   **70,00**
========== ======== ==================== ===========

Ludwig Frisch is married with Laura Loslever and they live together with their two children.

>>> obj = contacts.Person.objects.get(name="Frisch Ludwig")
>>> print(obj)
Mr Ludwig FRISCH
>>> ses.show(households.SiblingsByPerson, master_instance=obj)
========== =================== ================ ==================== ============ =========== ============ ========
 Age        Role                Dependency       Person               First name   Last name   Birth date   Gender
---------- ------------------- ---------------- -------------------- ------------ ----------- ------------ --------
 46 years   Partner             Not at charge    Mrs Laura LOSLEVER   Laura        Loslever    1968-04-27   Female
 45 years   Head of household   Not at charge    Mr Ludwig FRISCH     Ludwig       Frisch      1968-06-01   Male
 12 years   Child               At full charge   Mrs Melba FRISCH     Melba        Frisch      2002-04-05   Female
 6 years    Child               At full charge   Mrs Irma FRISCH      Irma         Frisch      2008-03-24   Female
========== =================== ================ ==================== ============ =========== ============ ========

Here is what Ludwig’s LinksByHuman panel shows:

>>> ses.show(humanlinks.LinksByHuman, master_instance=obj)
Ludwig is
Son of `Gaby FROGEMUTH <…>`__ (79 years), `Hubert <…>`__ (80 years)
Father of `Irma <…>`__ (6 years), `Melba <…>`__ (12 years)
Husband of `Laura LOSLEVER <…>`__ (46 years)
Create relationship as **Father**/**Son** **Adoptive father**/**Adopted son** **Foster father**/**Foster son** **Husband** **Partner** **Stepfather**/**Stepson** **Brother** **Cousin** **Uncle**/**Nephew** **Relative** **Other**

Here is his RefundsByPerson table:

>>> ses.show(households.RefundsByPerson, master_instance=obj)
========== ======== ==================== ===========
 Age        Gender   Person               Amount
---------- -------- -------------------- -----------
 46 years   Female   Laura LOSLEVER       20,00
 45 years   Male     Ludwig FRISCH        20,00
 12 years   Female   Melba FRISCH         10,00
 6 years    Female   Irma FRISCH          10,00
                     **Total (4 rows)**   **60,00**
========== ======== ==================== ===========

An edge case

The following edge case failed before 20170206:

>>> settings.SITE.strict_master_check = True
>>> ses.show(humanlinks.LinksByHuman)  
Traceback (most recent call last):
...
django.core.exceptions.BadRequest: Request on slave table humanlinks.LinksByHuman has no master instance (...)
>>> settings.SITE.strict_master_check = False

Melba getting adult

The RefundsByPerson table depends on the current date, which is 2014-05-22 for this demo:

>>> dd.today()
datetime.date(2014, 5, 22)

The demo date is stored in a site attribute the_demo_date:

>>> settings.SITE.the_demo_date
datetime.date(2014, 5, 22)

Let’s have a look at Ludwig’s RefundsByPerson table when Melba gets adult. The default configuration says that you get adult at 18:

>>> dd.plugins.households.adult_age
18

In 2019 Melba is 17, still a child:

>>> settings.SITE.the_demo_date = datetime.date(2019, 5, 22)
>>> ses.show(households.RefundsByPerson, master_instance=obj)
========== ======== ==================== ===========
 Age        Gender   Person               Amount
---------- -------- -------------------- -----------
 51 years   Female   Laura LOSLEVER       20,00
 50 years   Male     Ludwig FRISCH        20,00
 17 years   Female   Melba FRISCH         10,00
 11 years   Female   Irma FRISCH          10,00
                     **Total (4 rows)**   **60,00**
========== ======== ==================== ===========

But in 2020 she is 18, now her refunded amount is that of an adult:

>>> settings.SITE.the_demo_date = datetime.date(2020, 5, 22)
>>> ses.show(households.RefundsByPerson, master_instance=obj)
========== ======== ==================== ===========
 Age        Gender   Person               Amount
---------- -------- -------------------- -----------
 52 years   Female   Laura LOSLEVER       20,00
 51 years   Male     Ludwig FRISCH        20,00
 18 years   Female   Melba FRISCH         20,00
 12 years   Female   Irma FRISCH          10,00
                     **Total (4 rows)**   **70,00**
========== ======== ==================== ===========

Note: Before 20190326 above table gave an amount of 10,00 for Melba because of a bug (you got adult only one year after the age specified in lino_xl.lib.households.Plugin.adult_age).

For the following tests we set the_demo_date back to its original value:

>>> settings.SITE.the_demo_date = datetime.date(2014, 5, 22)

Inspecting the MembersByPerson panel

The following code caused an exception “ParameterStore of LayoutHandle for ParamsLayout on pcsw.Clients expects a list of 12 values but got 16” on 2014-04-29.

We need a client who is member of multiple households.

>> ses = rt.login(“hubert”) >> cli = ses.spawn(integ.Clients)[1]

>>> cli = pcsw.Client.objects.get(pk=181)
>>> cli
Client #181 ('JEANÉMART Jérôme (181)')
>>> print(len(integ.Clients.params_layout.get_layout_handle()._data_elems))
16
>>> url = '/api/integ/Clients/{}?'.format(cli.pk)
>>> url += 'pv=30&pv=&pv=&pv=&pv=&pv=&pv=&pv=&pv=&pv=&pv=false&pv=&pv=&pv=&pv=false&pv=false'
>>> url += '&an=detail&rp=ext-comp-1351&fmt=json'
>>> res = test_client.get(url, REMOTE_USER='rolf')
>>> print(res.status_code)
200

The response to this request is in JSON:

>>> d = json.loads(res.content)

We test the MembersByPerson panel. It contains a summary:

>>> print(d['data']['households.MembersByPerson'])
... 
<div class="htmlText">JEANÉMART Jérôme (181) ist<ul><li>...einen neuen erstellen</a>.</div>

<div>JEAN&#201;MART J&#233;r&#244;me (181) ist<ul><li><a …href=”javascript:Lino.households.Members.set_primary(…)…</div>

Since this is not very human-readable, we are going to analyze it with BeautifulSoup.

>>> soup = BeautifulSoup(d['data']['households.MembersByPerson'], 'lxml')
>>> print(soup.get_text(' ', strip=True))
... 
JEANÉMART Jérôme (181) ist
☐ Vorstand in Jérôme & Marie-Louise Jeanémart-Vandenmeulenbos (Sonstige)
☐ Vorstand in Jérôme & Theresia Jeanémart-Thelen (Faktischer Haushalt)
Bestehendem Haushalt beitreten oder einen neuen erstellen .
>>> links = soup.find_all('a')

It contains 6 links:

>>> len(links)
6

The first link is the disabled checkbox for the primary field:

>>> print(links[0].string)
... 

Clicking on this would run the following JavaScript:

>>> print(links[0].get('href'))
javascript:Lino.households.Members.set_primary("ext-comp-1351",false,11,{  })

The next link is the name of the household, and clicking on it would equally execute some JavaScript code:

>>> print(links[1].string)
Jérôme & Marie-Louise Jeanémart-Vandenmeulenbos (Sonstige)
>>> print(links[1].get('href'))
... 
javascript:Lino.households.Households.detail.run("ext-comp-1351",{
"base_params": { "mk": 181, "mt": 58 }, "param_values": { "aged_from": null,
"aged_to": null, "end_date": null, "gender": null, "genderHidden": null,
"start_date": null }, "record_id": 237 })

The second last link is:

>>> print(links[-2].string)
Bestehendem Haushalt beitreten
>>> print(links[-2].get('href'))
... 
javascript:Lino.households.MembersByPerson.insert.run("ext-comp-1351",{...})

The lino.api.doctest.get_json_soup() automates this trick:

>>> url = 'integ/Clients/{}'.format(cli.pk)
>>> soup = get_json_soup('rolf', url, 'households.MembersByPerson',
... pv=[30,'','','','','','','','','','false','','','','false','false'])
>>> links = soup.find_all('a')
>>> len(links)
6

The household summary

The utility function get_household_summary is used for printing certain aid confirmations. Some examples:

>>> for cli in pcsw.Client.objects.order_by('id'):
...     s = households.get_household_summary(cli)
...     if not s.endswith("ist in keinem Haushalt"):
...         print(u"{} : {}".format(cli, s))
VANDENMEULENBOS Marie-Louise (174) : 2 Erwachsene
LAHM Lisa (176) : 2 Erwachsene
DUBOIS Robin (179) : 2 Erwachsene
DENON Denis (180*) : 1 Erwachsener und 1 Kind
JEANÉMART Jérôme (181) : 2 Erwachsene
KASENNOVA Tatjana (213) : 1 Erwachsener und 1 Kind
FRISCH Paul (240) : 2 Erwachsene und 3 Kinder
BRAUN Bruno (259) : BRAUN Bruno (259) ist in mehreren Haushalten zugleich

Member objects

The following code snippet was used to reproduce #844:

>>> translation.activate('en')
>>> Member = households.Member
>>> print(Member())
 (Child)
>>> person = contacts.Person.objects.get(pk=259)
>>> print(Member(person=person))
Mr Bruno BRAUN (Child)
>>> print(Member(person=person, role=households.MemberRoles.head))
Mr Bruno BRAUN (Head of household)