households : handling households and their members

The lino_xl.lib.households plugin adds functionality for managing households (i.e. groups of humans who live together in a same house).

A tested document

This is a tested document. The following instructions are used for initialization:

>>> import lino
>>> lino.startup('lino_book.projects.avanti1.settings.demo')
>>> from lino.api.doctest import *

Overview

A household is a group of humans who live together in a same house.

A household membership is the fact that a given person is (or has been) part of a given household.

Households

A household is a subclass of lino_xl.lib.contacts.Partner.

class lino_xl.lib.households.Household

Django model to represent a household.

type

The type of this household. See household types

create_household(cls, ar, head, partner, type)

Create a household with the given head, partner and type.

Household types

There are different types of households.

class lino_xl.lib.households.Type

Django model to represent the type of a household.

http://www.belgium.be/fr/famille/couple/cohabitation/

>>> rt.show(households.Types)  
==== ==================== ========================= ======================
 ID   Designation          Designation (de)          Designation (fr)
---- -------------------- ------------------------- ----------------------
 1    Married couple       Ehepaar                   Couple marié
 2    Divorced couple      Geschiedenes Paar         Couple divorcé
 3    Factual household    Faktischer Haushalt       Cohabitation de fait
 4    Legal cohabitation   Legale Wohngemeinschaft   Cohabitation légale
 5    Isolated             Getrennt                  Isolé
 6    Other                Sonstige                  Autre
==== ==================== ========================= ======================

Memberships

class lino_xl.lib.households.Member

Django model to represent a household membership.

household
person
role
dependency
primary

Whether this is the primary household of this person. Checking this field will automatically disable any other primary memberships.

start_date

Since when this membership exists. This is usually empty.

end_date

Until when this membership exists.

class lino_xl.lib.households.Members
class lino_xl.lib.households.MembersByHousehold
class lino_xl.lib.households.PopulateMembers

Populate household members from data in human links.

Configuration

class lino_xl.lib.households.MemberRoles

The list of allowed choices for the role of a household member.

See role.

>>> rt.show('households.MemberRoles')
======= ============ ===================
 value   name         text
------- ------------ -------------------
 01      head         Head of household
 02      spouse       Spouse
 03      partner      Partner
 04      cohabitant   Cohabitant
 05      child        Child
 06      relative     Relative
 07      adopted      Adopted child
 08      foster       Foster-child
 10      other        Other
======= ============ ===================

SiblingsByPerson

>>> SiblingsByPerson = rt.models.households.SiblingsByPerson

The SiblingsByPerson table shows the family composition of a person, i.e. all members of the current household of that person.

This works of course only when Lino can determine the "one and only" current household. If the person has only one membership (at a given date), then there is no question.

When there are several memberships, then theoretically one of them should be marked as primary.

But even when a person has multiple household memberships and none of them is primary, Lino can look at the end_date.

The active household is determined as follows:

  • If the person has only one household, use this.

  • Otherwise, if one household is marked as primary, use this.

  • Otherwise, if there is exactly one membership whose end_date is either empty or in the future, take this.

If no active household can be determined, the panel just displays an appropriate message.

Let's get a list of the candidates to inspect:

>>> Person = rt.models.contacts.Person
>>> Member = rt.models.households.Member
>>> MemberRoles = rt.models.households.MemberRoles
>>> heads = Person.objects.filter(household_members__role=MemberRoles.head).distinct()
>>> for m in heads.order_by('id'):
...     qs = Member.objects.filter(role=MemberRoles.head, person=m.person)
...     all = qs.count()
...     primary = qs.filter(primary=True).count()
...     if all > 1 and not primary:
...         print(u"{} ({}) is head of {} households".format(
...             m.person, m.person.pk, all))
Mr Aleksándr Alvang (178) is head of 2 households

The most interesting is 178:

>>> ses = rt.login('robin')
>>> p = Person.objects.get(pk=178)
>>> ses.show('households.MembersByPerson', master_instance=p)
Mr Aleksándr Alvang is
`☐  <javascript:Lino.households.Members.set_primary(null,false,11,{  })>`__Head of household in `Aleksándr & Agápiiá Alvang-Bek-Murzin (Other) <Detail>`__
`☐  <javascript:Lino.households.Members.set_primary(null,false,5,{  })>`__Head of household in `Aleksándr & Cátává Alvang-Maalouf (Factual household) <Detail>`__

**Join an existing household** or **create a new one**.
>>> ses.show('households.MembersByPerson', p, nosummary=True)
======================================================= =================== ========= ============ ============
 Household                                               Role                Primary   Start date   End date
------------------------------------------------------- ------------------- --------- ------------ ------------
 Aleksándr & Agápiiá Alvang-Bek-Murzin (Other)           Head of household   No
 Aleksándr & Cátává Alvang-Maalouf (Factual household)   Head of household   No                     04/03/2002
======================================================= =================== ========= ============ ============
>>> rt.show(SiblingsByPerson, p)
========== =================== ======================== ============ ============ ======== ============ ============= ========
 Age        Role                Person                   First name   Last name    Gender   Birth date   Nationality   School
---------- ------------------- ------------------------ ------------ ------------ -------- ------------ ------------- --------
 43 years   Partner             Mrs Agápiiá Bek-Murzin   Agápiiá      Bek-Murzin   Female   1973-09-04
 23 years   Head of household   Mr Aleksándr Alvang      Aleksándr    Alvang       Male     1993-09-09
========== =================== ======================== ============ ============ ======== ============ ============= ========

Let's use the get_json_soup function to analyze

>>> soup = get_json_soup('rolf', 'avanti/Clients/178', 'households_MembersByPerson')
>>> links = soup.find_all('a')
>>> len(links)
6
>>> print(links[4].string)
Bestehendem Haushalt beitreten
>>> print(links[4].get('href'))
... 
javascript:Lino.households.MembersByPerson.insert.run(null,{ "base_params": { "person": 178 },
"data_record": { "data": { "disabled_fields": { "birth_date": true, "first_name": true,
"gender": true, "last_name": true }, "household": null, "householdHidden": null, "person":
"ALVANG Aleks\u00e1ndr (178)", "personHidden": 178, "primary": false, "role": "Kind",
"roleHidden": "05" }, "phantom": true, "title": "Mitglied erstellen" }, "param_values": {
"aged_from": null, "aged_to": null, "end_date": null, "gender": null, "genderHidden": null,
"start_date": null }, "record_id": null })

Don't read on

The following covers a problem that occurred 20181023 and was detected by welfare but not yet by book.

>>> print(p.id)
178
>>> test_client.force_login(ses.user)
>>> def check(uri, fieldname):
...     url = '/api/%s?fmt=json&an=detail' % uri
...     res = test_client.get(url, REMOTE_USER=ses.user.username)
...     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 = 'avanti/Clients/{}'.format(p.id)
>>> html = check(uri, 'households_MembersByPerson')
>>> soup = BeautifulSoup(html, 'lxml')
>>> links = soup.find_all('a')
>>> len(links)
6
class lino_xl.lib.households.MemberDependencies

The list of allowed choices for the charge of a household member.

Table reference

class lino_xl.lib.households.Households
class lino_xl.lib.households.HouseholdsByType
class lino_xl.lib.households.Types