aids
: Aid grantings¶
The lino_welfare.modlib.aids
plugin provides functionality for managing
“social aid” or “aid grantings”.
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 *
>>> translation.activate('de')
Concepts¶
- aid granting¶
The decision that a given client gets a given aid during a given period. See Aid grantings.
German: Hilfebeschluss
- aid type¶
Every aid granting has a type, and the list of aid types is configurable. See Aid types.
German: Hilfeart
Date range validation¶
>>> # print(dd.plugins.aids.no_date_range_veto_until)
>>> obj = rt.models.aids.Granting.objects.get(pk=10)
>>> def f(gs, ge, cs, ce):
... if gs: gs = i2d(gs)
... if ge: ge = i2d(ge)
... if cs: cs = i2d(cs)
... if ce: ce = i2d(ce)
... obj.start_date = gs
... obj.end_date = ge
... p = aids.IncomeConfirmation(start_date=cs, end_date=ce, granting=obj)
... return p.get_date_range_veto()
>>> dd.plugins.aids.no_date_range_veto_until = 0
>>> f(20140522, 20140522, 20140522, 20140522)
A confirmation without an end date means only on the start date: >>> f(20140522, 20140522, 20140522, None)
>>> f(20140522, None, 20140522, None)
>>> f(20140522, None, 20140522, 20150521)
>>> f(20140522, 20140522, 20140522, 20140523)
'Datumsbereich 22.05.14...23.05.14 außerhalb der Laufzeit des Beschlusses 22.05.14...22.05.14.'
>>> f(20140522, 20140522, 20140523, 20140523)
'Datumsbereich 23.05.14...23.05.14 außerhalb der Laufzeit des Beschlusses 22.05.14...22.05.14.'
>>> f(20140522, 20140522, 20140523, 20140523)
'Datumsbereich 23.05.14...23.05.14 außerhalb der Laufzeit des Beschlusses 22.05.14...22.05.14.'
>>> f(None, 20140522, 20140521, None)
>>> f(None, 20140522, 20140522, None)
>>> f(None, 20140522, 20140523, None)
'Datumsbereich 23.05.14...23.05.14 außerhalb der Laufzeit des Beschlusses ...22.05.14.'
>>> dd.plugins.aids.no_date_range_veto_until = -1
>>> f(None, 20140522, 20140523, None)
>>> obj.pk
10
>>> dd.plugins.aids.no_date_range_veto_until = 15
>>> f(None, 20140522, 20140523, None)
>>> dd.plugins.aids.no_date_range_veto_until = 5
>>> f(None, 20140522, 20140523, None)
'Datumsbereich 23.05.14...23.05.14 außerhalb der Laufzeit des Beschlusses ...22.05.14.'
Confirmation types¶
>>> from lino_welfare.modlib.pcsw.roles import SocialUser
>>> ses = rt.login('alicia')
>>> ses.user.user_type.has_required_roles([SocialUser])
True
ConfirmationTypes
is a
choicelist where each subclass of Confirmation
has been registered.
Currently Lino Welfare knows three confirmation types.
>>> ses.show(aids.ConfirmationTypes)
...
====== ========================= =================================================== =============
name Wert Text Vorlage
------ ------------------------- --------------------------------------------------- -------------
aids.SimpleConfirmation Einfache Bescheinigung (aids.SimpleConfirmation) Default.odt
aids.IncomeConfirmation Einkommensbescheinigung (aids.IncomeConfirmation) Default.odt
aids.RefundConfirmation Kostenübernahmeschein (aids.RefundConfirmation) Default.odt
====== ========================= =================================================== =============
Aid types¶
The list of aid type can be modified by end users with the required permissions.
>>> rt.login("robin").show_menu_path(aids.AidTypes)
Configure --> PCSW --> Aid Types
>>> rt.login("rolf").show_menu_path(aids.AidTypes)
Konfigurierung --> ÖSHZ --> Hilfearten
>>> ses.show(aids.AidTypes, column_names="name confirmed_by_primary_coach body_template id")
...
================================================= =========================== =============================== ====
Bezeichnung Primärbegleiter bestätigt Textkörper-Vorlage ID
------------------------------------------------- --------------------------- ------------------------------- ----
Ausländerbeihilfe Ja foreigner_income.body.html 2
Dringende Medizinische Hilfe Ja urgent_medical_care.body.html 7
Eingliederungseinkommen Ja integ_income.body.html 1
Erstattung Ja certificate.body.html 4
Feste Beihilfe Ja fixed_income.body.html 3
Heizkosten Ja heating_refund.body.html 9
Kleiderkammer Ja clothing_bank.body.html 11
Lebensmittelbank Nein food_bank.body.html 10
Möbellager Ja furniture.body.html 8
Übernahme von Arzt- und/oder Medikamentenkosten Ja medical_refund.body.html 6
Übernahmeschein Ja certificate.body.html 5
================================================= =========================== =============================== ====
- class lino_welfare.modlib.aids.AidType¶
The Django model representing an aid type.
Database fields:
- short_name¶
The short name for internal use, e.g. when a user must select an aid type from a combobox.
- confirmation_type¶
The database model to use for issuing an aid confirmation of this type. This is a mandatory pointer to
ConfirmationTypes
.
- name¶
The designation of this aid type as seen by the user e.g. when selecting an aid type.
One field for every
language
.
- excerpt_title¶
The text to print as title in confirmations. See also
lino_xl.lib.excerpts.mixins.ExcerptTitle.excerpt_title
.
- body_template¶
The body template to use when printing a confirmation of this type. If this field is empty, Lino uses the excerpt type’s
body_template
. See also Developer intro to printing.
- is_urgent¶
Whether aid grantings of this type are considered as urgent. This is used by
Confirmation.get_urgent_granting()
- is_integ_duty¶
- board¶
Pointer to the default
lino_xl.lib.boards.models.Board
for aid projects of this type.
- confirmed_by_primary_coach¶
Whether grantings for this aid type are to be signed by the client’s primary coach (see
Client.get_primary_coach
).
- pharmacy_type¶
A pointer to the
ClientContactType
to be used when selecting the pharmacy of a refund confirmation (RefundConfirmation.pharmacy
).
Aid grantings¶
- class lino_welfare.modlib.aids.Granting¶
The Django model representing an aid granting.
Database fields:
- client¶
Pointer to the
lino_welfare.modlib.pcsw.models.Client
.
- signer¶
Pointer to the user who is expected to “sign” this granting (i.e. to confirm that it is real).
The default value is the client’s primary coach for grantings whose
aid_type
hasconfirmed_by_primary_coach
checked.
- category¶
Currently only used for printing an isip.Contract.
- class lino_welfare.modlib.aids.Grantings¶
- class lino_welfare.modlib.aids.AllGrantings¶
- class lino_welfare.modlib.aids.MyPendingGrantings¶
- class lino_welfare.modlib.aids.GrantingsByClient¶
- class lino_welfare.modlib.aids.GrantingsByType¶
Alicia hat 2 Hilfebeschlüsse zu bestätigen. Dies kriegt sie als Willkommensmeldung gesagt:
>>> ses = rt.login('alicia')
>>> translation.activate('de')
>>> for msg in settings.SITE.get_welcome_messages(ses):
... print(tostring(msg))
...
<span>Du bist beschäftigt in <a href="…">Beschwerde (22.05.2014) mit COLLARD Charlotte (118)</a> (<b>☑</b>). </span>
<b>Du hast 2 offene Datenproblemmeldungen.</b>
<span>Du hast <b>6 Einträge in Zu bestätigende Hilfebeschlüsse</b>.</span>
When she clicks the link “Zu bestätigende Hilfebeschlüsse”, then they show up:
>>> ses.show(aids.MyPendingGrantings)
...
======================== ========================= =========== ============== ========== ======= ==============================
Klient Hilfeart Kategorie Laufzeit von bis Autor Workflow
------------------------ ------------------------- ----------- -------------- ---------- ------- ------------------------------
DA VINCI David (165) Ausländerbeihilfe 10.08.14 [Bestätigen] **Unbestätigt**
DA VINCI David (165) Eingliederungseinkommen 10.07.14 [Bestätigen] **Unbestätigt**
EMONTS-GAST Erna (152) Heizkosten 30.05.14 31.05.14 [Bestätigen] **Unbestätigt**
DUBOIS Robin (179) Ausländerbeihilfe 06.03.14 [Bestätigen] **Unbestätigt**
DUBOIS Robin (179) Eingliederungseinkommen 27.04.13 [Bestätigen] **Unbestätigt**
DA VINCI David (165) Ausländerbeihilfe 16.03.13 [Bestätigen] **Unbestätigt**
======================== ========================= =========== ============== ========== ======= ==============================
Aid confirmations (Hilfebestätigungen)¶
- class lino_welfare.modlib.aids.Confirmation¶
Base class for all aid confirmations.
Subclassed by
SimpleConfirmation
,IncomeConfirmation
andRefundConfirmation
.- get_date_range_veto(self)¶
Return an error message if this confirmation lies outside of granted period.
- def get_urgent_granting(self):
Return the one and only one urgent aid granting for the client and period defined for this confirmation. Return None if there is no such granting, or if there is more than one such granting.
Used in
medical_refund.body.html
.
Number of issued confirmations by confirmation type:
>>> translation.activate('de')
>>> for at in aids.AidType.objects.exclude(confirmation_type='').order_by('id'):
... M = at.confirmation_type.model
... qs = M.objects.filter(granting__aid_type=at)
... obj = qs[0]
... txt = obj.confirmation_text()
... txt = ' '.join(txt.split())
... print("%s : %d" % (str(at), qs.count()))
Eingliederungseinkommen : 20
Ausländerbeihilfe : 37
Feste Beihilfe : 3
Erstattung : 3
Übernahmeschein : 3
Übernahme von Arzt- und/oder Medikamentenkosten : 6
Dringende Medizinische Hilfe : 6
Möbellager : 3
Heizkosten : 3
Lebensmittelbank : 3
Kleiderkammer : 4
Grantings by ISIP contract¶
The get_aid_type
method of a contract (called from the .odt document template when
printing a welfare.isip.Contract
in Eupen) works only when
get_granting
returns
exactly one granting. Which is the normal situation.
The demo fixtures generate some exceptions to this general rule. Here we see that most contracts have indeed exactly 1 granting:
>>> isip.Contract.objects.all().count()
34
>>> l = []
>>> for con in isip.Contract.objects.all():
... if con.get_aid_type() is not None:
... l.append(con.id)
>>> print(l)
[1, 3, 4, 7, 9, 10, 11, 13, 15, 18, 19, 22, 24, 27, 29, 31, 33]
>>> rr = aids.IncomeConfirmationsByGranting.insert_action.action.required_roles
>>> print(rt.login("rolf").get_user().user_type.has_required_roles(rr))
True
>>> ct = contenttypes.ContentType.objects.get_for_model(aids.Granting)
>>> mt = ct.pk
>>> mk = 3
>>> ct = contenttypes.ContentType.objects.get(pk=mt)
>>> ct.model_class()
<class 'lino_welfare.modlib.aids.models.Granting'>
>>> obj = aids.Granting.objects.get(pk=mk)
>>> obj
Granting #3 ('EiEi/13.10.12/124')
This granting has been confirmed once:
>>> rt.show(aids.IncomeConfirmationsByGranting, obj)
==== ============================ ================ ============ ============= =====
ID Klient Kategorie Betrag Periode vom bis
---- ---------------------------- ---------------- ------------ ------------- -----
4 DOBBELSTEIN Dorothée (124) Zusammenlebend 456,00 13.10.12
**456,00**
==== ============================ ================ ============ ============= =====
Permissions¶
We test whether Theresia is allowed to create an income confirmation.
>>> theresia = rt.login('theresia').user
>>> headers = dict(HTTP_X_REQUESTED_WITH='XMLHttpRequest')
>>> headers.update(REMOTE_USER='rolf')
>>> url = "/api/aids/IncomeConfirmationsByGranting"
>>> url += "?su={2}&mt={0}&mk={1}&an=insert".format(mt, mk, theresia.pk)
>>> test_client.force_login(rt.login('rolf').user)
>>> res = test_client.get(url, **headers)
>>> print(res.status_code)
200
>>> soup = BeautifulSoup(res.content, 'lxml')
>>> scripts = soup.head.find_all('script', type="text/javascript")
The page header includes a lot of scripts:
>>> len(scripts)
21
Here are the default values for their source URLs:
>>> for s in scripts:
... print(s.get('src', '(inline)'))
/static/ext-3.3.1/adapter/ext/ext-base-debug.js
/static/ext-3.3.1/ext-all-debug.js
/static/ext-3.3.1/src/locale/ext-lang-de.js
/static/ext-3.3.1/examples/ux/statusbar/StatusBar.js
/static/extjs/Ext.ux.form.DateTime.js
/static/extensible-1.0.1/extensible-all-debug.js
/static/extensible-1.0.1/src/locale/extensible-lang-de.js
/static/tinymce-3.5.11/tiny_mce.js
/static/byteforce/Ext.ux.TinyMCE.js
/static/ext-3.3.1/examples/ux/gridfilters/menu/RangeMenu.js
/static/ext-3.3.1/examples/ux/gridfilters/menu/ListMenu.js
/static/ext-3.3.1/examples/ux/gridfilters/GridFilters.js
/static/ext-3.3.1/examples/ux/gridfilters/filter/Filter.js
/static/ext-3.3.1/examples/ux/gridfilters/filter/StringFilter.js
/static/ext-3.3.1/examples/ux/gridfilters/filter/DateFilter.js
/static/ext-3.3.1/examples/ux/gridfilters/filter/ListFilter.js
/static/ext-3.3.1/examples/ux/gridfilters/filter/NumericFilter.js
/static/ext-3.3.1/examples/ux/gridfilters/filter/BooleanFilter.js
/static/ext-3.3.1/examples/ux/fileuploadfield/FileUploadField.js
/media/cache/js/lino_210_de.js
(inline)
We are interested in the last one, which defines the onReady function:
>>> on_ready = str(scripts[-1])
>>> len(on_ready.splitlines())
15
And one of these lines calls the JavaScript version of the insert
action of IncomeConfirmationsByGranting
:
>>> "Lino.aids.IncomeConfirmationsByGranting.insert.run" in on_ready
True
The pharmacy of a RefundConfirmation¶
The demo database has exactly one AidType with a nonempty pharmacy_type field:
>>> at = aids.AidType.objects.get(pharmacy_type__isnull=False)
>>> at
AidType #6 ('\xdcbernahme von Arzt- und/oder Medikamentenkosten')
>>> at.pharmacy_type
ClientContactType #1 ('Apotheke')
There are 4 pharmacies altogether:
>>> rt.login('robin').show('clients.PartnersByClientContactType', at.pharmacy_type)
=================================== ===== ================================================================================
Name ID Ansicht als
----------------------------------- ----- --------------------------------------------------------------------------------
Apotheke Schunck 201 `Organisation <…>`__ [**❌**], **Partner**, Person [**➕**], Haushalt [**➕**]
Bosten-Bocken A 203 `Organisation <…>`__ [**❌**], **Partner**, Person [**➕**], Haushalt [**➕**]
Pharmacies Populaires de Verviers 202 `Organisation <…>`__ [**❌**], **Partner**, Person [**➕**], Haushalt [**➕**]
=================================== ===== ================================================================================
There are two grantings with this aid type:
>>> rt.show(aids.GrantingsByType, at)
========================== ==================== ============== ========== ====
Hilfebeschluss Klient Laufzeit von bis ID
-------------------------- -------------------- -------------- ---------- ----
`AMK/27.05.14/139 <…>`__ JONAS Josef (139) 27.05.14 26.06.14 45
`AMK/27.05.14/141 <…>`__ KAIVERS Karl (141) 27.05.14 27.05.14 46
========================== ==================== ============== ========== ====
We will inspect the first granting from above table:
>>> granting = aids.Granting.objects.get(pk=45)
Usually there is at most one pharmacy among the client’s client contacts:
>>> rt.show(clients.ContactsByClient, granting.client)
==================== =============== =================== =============
Klientenkontaktart Organisation Kontaktperson Bemerkungen
-------------------- --------------- ------------------- -------------
Apotheke Apotheke Reul
Arzt Waltraud WALDMANN
Hausarzt Werner WEHNICHT
Zahnarzt Dr. Carmen CASTOU
==================== =============== =================== =============
There is only one pharmacy in above list, but in a confirmation I can manually choose any other pharmacy:
>>> ContentType = rt.models.contenttypes.ContentType
>>> mt = ContentType.objects.get_for_model(rt.models.aids.Granting).id
>>> url = '/choices/aids/RefundConfirmationsByGranting/pharmacy?mt={0}&mk={1}'.format(mt, granting.id)
>>> response = test_client.get(url, REMOTE_USER="rolf")
>>> result = json.loads(response.content.decode())
>>> for r in result['rows']:
... print(r['text'])
<br/>
Apotheke Reul
Apotheke Schunck
Pharmacies Populaires de Verviers
Bosten-Bocken A
Refund confirmations¶
Some example of how to view refund confirmations.
>>> cn = "id granting"
>>> cn += " granting__client granting__aid_type"
>>> cn += " start_date end_date"
>>> #cn += " pharmacy doctor"
>>> rt.show(aids.RefundConfirmations, column_names=cn)
==== ================== ====================== ================================================= ============= ==========
ID Hilfebeschluss Klient Hilfeart Periode vom bis
---- ------------------ ---------------------- ------------------------------------------------- ------------- ----------
12 DMH/28.05.14/144 LAZARUS Line (144) Dringende Medizinische Hilfe 28.05.14 28.05.15
11 DMH/28.05.14/144 LAZARUS Line (144) Dringende Medizinische Hilfe 28.05.14 28.05.15
10 DMH/28.05.14/144 LAZARUS Line (144) Dringende Medizinische Hilfe 28.05.14 28.05.15
9 DMH/28.05.14/142 LAMBERTZ Guido (142) Dringende Medizinische Hilfe 28.05.14
8 DMH/28.05.14/142 LAMBERTZ Guido (142) Dringende Medizinische Hilfe 28.05.14
7 DMH/28.05.14/142 LAMBERTZ Guido (142) Dringende Medizinische Hilfe 28.05.14
6 AMK/27.05.14/141 KAIVERS Karl (141) Übernahme von Arzt- und/oder Medikamentenkosten 27.05.14 27.05.14
5 AMK/27.05.14/141 KAIVERS Karl (141) Übernahme von Arzt- und/oder Medikamentenkosten 27.05.14 27.05.14
4 AMK/27.05.14/141 KAIVERS Karl (141) Übernahme von Arzt- und/oder Medikamentenkosten 27.05.14 27.05.14
3 AMK/27.05.14/139 JONAS Josef (139) Übernahme von Arzt- und/oder Medikamentenkosten 27.05.14 26.06.14
2 AMK/27.05.14/139 JONAS Josef (139) Übernahme von Arzt- und/oder Medikamentenkosten 27.05.14 26.06.14
1 AMK/27.05.14/139 JONAS Josef (139) Übernahme von Arzt- und/oder Medikamentenkosten 27.05.14 26.06.14
==== ================== ====================== ================================================= ============= ==========
>>> cn = "id client start_date end_date"
>>> pv = dict(client=pcsw.Client.objects.get(pk=144))
>>> rt.show(aids.RefundConfirmations, column_names=cn, param_values=pv)
==== ==================== ============= ==========
ID Klient Periode vom bis
---- -------------------- ------------- ----------
12 LAZARUS Line (144) 28.05.14 28.05.15
11 LAZARUS Line (144) 28.05.14 28.05.15
10 LAZARUS Line (144) 28.05.14 28.05.15
==== ==================== ============= ==========
>>> cn = "id client start_date end_date"
>>> pv = dict(aid_type=aids.AidType.objects.get(pk=7))
>>> rt.show(aids.RefundConfirmations, column_names=cn, param_values=pv)
==== ====================== ============= ==========
ID Klient Periode vom bis
---- ---------------------- ------------- ----------
12 LAZARUS Line (144) 28.05.14 28.05.15
11 LAZARUS Line (144) 28.05.14 28.05.15
10 LAZARUS Line (144) 28.05.14 28.05.15
9 LAMBERTZ Guido (142) 28.05.14
8 LAMBERTZ Guido (142) 28.05.14
7 LAMBERTZ Guido (142) 28.05.14
==== ====================== ============= ==========
Number of children and adults in household¶
>>> cn = "id client start_date end_date num_adults num_children"
>>> #rt.show(aids.RefundConfirmations, column_names=cn)
>>> #rt.show(aids.SimpleConfirmations, column_names=cn)
>>> #rt.show(aids.IncomeConfirmations, column_names=cn)
>>> pv = dict(client=pcsw.Client.objects.get(pk=181))
>>> rt.show(aids.IncomeConfirmations, column_names=cn, param_values=pv)
==== ======================== ============= ===== ============ ========
ID Klient Periode vom bis Erwachsene Kinder
---- ------------------------ ------------- ----- ------------ --------
51 JEANÉMART Jérôme (181) 20.03.14 2 0
50 JEANÉMART Jérôme (181) 20.03.14 2 0
49 JEANÉMART Jérôme (181) 11.05.13 2 0
**6** **0**
==== ======================== ============= ===== ============ ========
Creating a doctor¶
Here we try to insert a RefundConfirmation, specifying a new doctor in the doctor combobox, and leaving the doctor_type empty.
>>> url = "/api/aids/RefundConfirmationsByGranting"
>>> data = dict(
... mt=119, mk=38,
... rp="ext-comp-3054",
... an="submit_insert",
... start_date="27.05.2014",
... end_date="27.05.2014",
... doctor_typeHidden="",
... doctor_type="Select a Client Contact type...",
... doctorHidden="Dr. Bean",
... doctor="Dr. Bean",
... pharmacyHidden=209,
... pharmacy="Apotheke Schunck (209)",
... companyHidden="",
... company="Select a Organisation...",
... contact_personHidden='',
... contact_person="Select a Person...",
... languageHidden='',
... language='',
... remark='')
>>> result = post_json_dict('rolf', url, data)
Error during kernel.run_action() in ActionRequest Erstellen on aids.RefundConfirmationsByGranting: Arzt : Kann keinen neuen Arzt erstellen, wenn Art des Arztes leer ist
{'doctor': ['Kann keinen neuen Arzt erstellen, wenn Art des Arztes leer ist']}
Traceback (most recent call last):
...
django.core.exceptions.ValidationError: {'doctor': ['Kann keinen neuen Arzt erstellen, wenn Art des Arztes leer ist']}
>>> result.success
False
>>> print(result.message)
Arzt : Kann keinen neuen Arzt erstellen, wenn Art des Arztes leer ist
The period covered by a confirmation¶
>>> from lino.utils.format_date import fdl
>>> print(dd.fdl(dd.today()))
22. Mai 2014
We define a utility function:
>>> def f(start_date, end_date):
... if end_date: end_date = i2d(end_date)
... if start_date: start_date = i2d(start_date)
... p = aids.IncomeConfirmation(
... start_date=start_date, end_date=end_date)
... for lang in ('en', 'de', 'fr'):
... with translation.override(lang):
... print(p.get_period_text())
A single day:
>>> f(20140522, 20140522)
on 22 May 2014
am 22. Mai 2014
le 22 mai 2014
A fully defined date range:
>>> f(20140522, 20140621)
between 22 May 2014 and 21 June 2014
vom 22. Mai 2014 bis zum 21. Juni 2014
entre le 22 mai 2014 et le 21 juin 2014
The text of a date range with open end can differ in certain languages depending on whether it is in the future or in the past.
>>> f(20140522, None)
from 22 May 2014
seit dem 22. Mai 2014
depuis le 22 mai 2014
>>> f(20140523, None)
from 23 May 2014
ab dem 23. Mai 2014
à partir du 23 mai 2014
No start date:
>>> f(None, 20140501)
until 1 May 2014
bis zum 1. Mai 2014
jusqu'au 1 mai 2014
Neither start nor end:
>>> f(None, None)
ConfirmationsByGranting¶
The detail of a Granting shows a list of the confirmations which have been issued for this granting.
>>> obj = aids.Granting.objects.get(pk=mk)
>>> rt.show(aids.ConfirmationsByGranting, obj, column_names="detail_link user signer printed")
============================= ================ ================ =============
Bestätigung Autor Bestätiger Ausgedruckt
----------------------------- ---------------- ---------------- -------------
`EiEi/13.10.12/124/4 <…>`__ Judith Jousten Mélanie Mélard
============================= ================ ================ =============
The above was written to reproduce #685.
The board field of a granting has a chooser which takes an argument of type date.
>>> show_choices('rolf', '/choices/aids/GrantingsByClient/board?decision_date=&mk=120')
<br/>
Sozialhilferat (SHR)
Sozialhilfeausschuss (SAS)
Ständiges Präsidium (SP)
Categories¶
- class lino_welfare.modlib.aids.Category¶
- class lino_welfare.modlib.aids.Categories¶
Inspecting a FormLayout and its JavaScript¶
On 2015-02-19 I discovered a layout problem in the insert
window of aids.IncomeConfirmationsByGranting
: all
form fields except remark were hidden. The problem was a side
effect of the new feature “hidden form fields” introduced some days
earlier (2015-02-17).
This section helped me to understand the problem, it also verifies that the problem is now solved, and maybe it has some pedagogical value.
Open the detail window (tab panel “Aids”) on Client 116. We know that this client has four grantings:
Clicking on the “Create confirmation” should show the window on the left, but currently it shows the window on the right:
Some preliminary tests, just to see whether we are at the right place:
>>> obj = pcsw.Client.objects.get(id=116)
>>> obj
Client #116 ('AUSDEMWALD Alfons (116)')
>>> rt.login('robin').show(aids.GrantingsByClient, obj, language="en")
========================================== ============== ======= ================= ======================== =================== =========================
Aid granting Applies from until Signer Workflow Board Actions
------------------------------------------ -------------- ------- ----------------- ------------------------ ------------------- -------------------------
`EiEi/22/05/2014/116 <…>`__ 22/05/2014 Caroline Carnol [Sign] **Unconfirmed** Social Board (SB) **Create confirmation**
`Ausländerbeihilfe/08/08/2013/116 <…>`__ 08/08/2013 Caroline Carnol [Sign] **Unconfirmed** **Create confirmation**
`EiEi/29/09/2012/116 <…>`__ 29/09/2012 Caroline Carnol [Sign] **Unconfirmed** **Create confirmation**
========================================== ============== ======= ================= ======================== =================== =========================
Let’s have a closer look the insert_layout in question.
lino_welfare.modlib.aids.models.IncomeConfirmationsByGranting
>>> layout = aids.IncomeConfirmationsByGranting.insert_layout
Yes it is a lino.core.layouts.InsertLayout
:
>>> layout.__class__
<class 'lino.core.layouts.InsertLayout'>
And its hidden_elements has only those fields which I actually want to be hidden:
>>> list(layout.hidden_elements)
['granting', 'client']
The main panel is as we designed it:
>>> print(layout.main)
client granting:25
start_date end_date
category amount
company contact_person language
remark
Let’s look at the LayoutHandle (i.e. version of the Layout which has
been “compiled” for a given front end, in our case
lino.modlib.extjs
).
>>> lh = layout.get_layout_handle()
>>> print(lh)
LayoutHandle for lino.core.layouts.InsertLayout on lino_welfare.modlib.aids.models.IncomeConfirmationsByGranting
>>> lh.main.__class__
<class 'lino.core.elems.DetailMainPanel'>
The lino.core.elems
module contains “Layout Elements” or
“Widgets”. The only purpose of these objects is to generate
JavaScript. They are ExtJS (version 3) specific.
They all inherit from lino.utils.jsgen.Component
, which is
part of lino.utils.jsgen
, which is a general framework for
generating JavaScript.
It uses functions like
with_user_profile
,
declare_vars
and
py2js
.
>>> from lino.utils.jsgen import py2js, declare_vars
>>> from lino.utils.jsgen import with_user_profile
>>> ses = rt.login("robin")
And we must activate a language:
>>> translation.activate('en')
Now we can call declare_vars
who will yield us a series of lines of javascript code. These lines
declare the variables used to define the main panel.
When rendering any JavaScript, Lino must know the user user_type for which we want it:
Here is the JavaScript generated before the change (the following snippet has +SKIP specified which means that it is not being tested:
>> # lines = with_user_profile(ses.user.user_type, declare_vars, lh.main) >> js = ‘n’.join(lines) >> print(js)
>>> with ses.user.user_type.context():
... for ln in declare_vars(lh.main):
... print(ln)
...
var client91 = new Lino.TwinCombo({ "preferred_width": 20, "autoHeight": true, "fieldLabel": "Client", "allowBlank": false, "pageSize": 20, "onTrigger2Click": function(e){ Lino.show_fk_detail(this,Lino.pcsw.Clients.detail,Lino.pcsw.Clients.insert)}, "emptyText": "Select a Client...", "listeners": { "render": Lino.quicktip_renderer("Client","(aids.IncomeConfirmationsByGranting.client) ") }, "selectOnFocus": true, "hidden": true, "hiddenName": "clientHidden", "anchor": "-20", "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/aids/IncomeConfirmationsByGranting/client", "method": "GET" }) }), "name": "client" });
var granting92 = new Lino.TwinCombo({ "autoHeight": true, "fieldLabel": "Aid granting", "name": "granting", "pageSize": 20, "onTrigger2Click": function(e){ Lino.show_fk_detail(this,Lino.aids.GrantingsByX.detail,Lino.aids.GrantingsByX.insert)}, "emptyText": "Select a Aid granting...", "listeners": { "render": Lino.quicktip_renderer("Aid granting","(aids.IncomeConfirmationsByGranting.granting) ") }, "selectOnFocus": true, "hidden": true, "hiddenName": "grantingHidden", "anchor": "-20", "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/aids/IncomeConfirmationsByGranting/granting", "method": "GET" }) }) });
var main_1_panel93 = new Ext.Panel({ "autoScroll": false, "autoHeight": true, "border": false, "layout": "hbox", "labelWidth": 117, "hideCheckBoxLabels": true, "items": [ { "flex": 44, "autoHeight": true, "layout": "form", "xtype": "panel", "items": client91, "labelAlign": "top" }, { "flex": 55, "autoHeight": true, "layout": "form", "xtype": "panel", "items": granting92, "labelAlign": "top" } ], "frame": false, "layoutConfig": { "align": "stretchmax" }, "labelAlign": "top" });
var start_date96 = new Lino.DateField({ "autoHeight": true, "fieldLabel": "Period from", "name": "start_date", "selectOnFocus": true, "listeners": { "render": Lino.quicktip_renderer("Period from","(aids.IncomeConfirmationsByGranting.start_date) ") }, "anchor": "-20" });
var end_date97 = new Lino.DateField({ "autoHeight": true, "fieldLabel": "until", "name": "end_date", "selectOnFocus": true, "listeners": { "render": Lino.quicktip_renderer("until","(aids.IncomeConfirmationsByGranting.end_date) ") }, "anchor": "-20" });
var main_2_panel98 = new Ext.Panel({ "autoScroll": false, "autoHeight": true, "border": false, "layout": "hbox", "labelWidth": 108, "hideCheckBoxLabels": true, "items": [ { "flex": 50, "autoHeight": true, "layout": "form", "xtype": "panel", "items": start_date96, "labelAlign": "top" }, { "flex": 50, "autoHeight": true, "layout": "form", "xtype": "panel", "items": end_date97, "labelAlign": "top" } ], "frame": false, "layoutConfig": { "align": "stretchmax" }, "labelAlign": "top" });
var category101 = new Lino.TwinCombo({ "autoHeight": true, "fieldLabel": "Category", "name": "category", "pageSize": 20, "onTrigger2Click": function(e){ Lino.show_fk_detail(this,Lino.aids.Categories.detail,Lino.aids.Categories.insert)}, "emptyText": "Select a Category...", "listeners": { "render": Lino.quicktip_renderer("Category","(aids.IncomeConfirmationsByGranting.category) ") }, "selectOnFocus": true, "hiddenName": "categoryHidden", "anchor": "-20", "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/aids/IncomeConfirmationsByGranting/category", "method": "GET" }) }) });
var amount102 = new Ext.form.NumberField({ "autoHeight": true, "decimalPrecision": 2, "fieldLabel": "Amount", "name": "amount", "decimalSeparator": ",", "listeners": { "render": Lino.quicktip_renderer("Amount","(aids.IncomeConfirmationsByGranting.amount) ") }, "selectOnFocus": true, "anchor": "-20", "allowBlank": true });
var main_3_panel103 = new Ext.Panel({ "autoScroll": false, "autoHeight": true, "border": false, "layout": "hbox", "labelWidth": 81, "hideCheckBoxLabels": true, "items": [ { "flex": 62, "autoHeight": true, "layout": "form", "xtype": "panel", "items": category101, "labelAlign": "top" }, { "flex": 37, "autoHeight": true, "layout": "form", "xtype": "panel", "items": amount102, "labelAlign": "top" } ], "frame": false, "layoutConfig": { "align": "stretchmax" }, "labelAlign": "top" });
var company106 = new Lino.TwinCombo({ "preferred_width": 20, "autoHeight": true, "fieldLabel": "Recipient (Organization)", "name": "company", "pageSize": 20, "onTrigger2Click": function(e){ Lino.show_fk_detail(this,Lino.contacts.Companies.detail,Lino.contacts.Companies.insert)}, "hiddenName": "companyHidden", "listeners": { "render": Lino.quicktip_renderer("Recipient (Organization)","(aids.IncomeConfirmationsByGranting.company) ") }, "selectOnFocus": true, "emptyText": "Select a Organisation...", "anchor": "-20", "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/aids/IncomeConfirmationsByGranting/company", "method": "GET" }) }) });
var contact_person107 = new Lino.TwinCombo({ "preferred_width": 20, "autoHeight": true, "fieldLabel": "Recipient (Person)", "name": "contact_person", "pageSize": 20, "onTrigger2Click": function(e){ Lino.show_fk_detail(this,Lino.contacts.Persons.detail,Lino.contacts.Persons.insert)}, "hiddenName": "contact_personHidden", "listeners": { "render": Lino.quicktip_renderer("Recipient (Person)","(aids.IncomeConfirmationsByGranting.contact_person) ") }, "selectOnFocus": true, "emptyText": "Select a Person...", "anchor": "-20", "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/aids/IncomeConfirmationsByGranting/contact_person", "method": "GET" }) }) });
var main_4_panel108 = new Ext.Panel({ "autoScroll": false, "autoHeight": true, "border": false, "layout": "hbox", "labelWidth": 225, "hideCheckBoxLabels": true, "items": [ { "flex": 50, "autoHeight": true, "layout": "form", "xtype": "panel", "items": company106, "labelAlign": "top" }, { "flex": 50, "autoHeight": true, "layout": "form", "xtype": "panel", "items": contact_person107, "labelAlign": "top" } ], "frame": false, "layoutConfig": { "align": "stretchmax" }, "labelAlign": "top" });
var remark111 = new Lino.RichTextPanel({ "selectOnFocus": true, "flex": 55, "containing_panel": this, "name": "remark", "title": "Remark" });
var main_panel112 = new Ext.Panel({ "autoScroll": false, "border": false, "layout": "vbox", "labelWidth": 0, "hideCheckBoxLabels": true, "items": [ main_1_panel93, main_2_panel98, main_3_panel103, main_4_panel108, remark111 ], "frame": true, "layoutConfig": { "align": "stretch" }, "bodyBorder": false, "labelAlign": "top" });
This structure caused the ExtJS layout manager to produce unexpected behaviour because the horizontal panel for the two fields client and granting is not hidden. In that case the panel must also be hidden.
>>> with ses.user.user_type.context():
... for ln in declare_vars(lh.main):
... print(ln)
...
var client228 = new Lino.TwinCombo({ "preferred_width": 20, "autoHeight": true, "fieldLabel": "Client", "allowBlank": false, "pageSize": 20, "onTrigger2Click": function(e){ Lino.show_fk_detail(this,Lino.pcsw.Clients.detail,Lino.pcsw.Clients.insert)}, "emptyText": "Select a Client...", "listeners": { "render": Lino.quicktip_renderer("Client","(aids.IncomeConfirmationsByGranting.client) ") }, "selectOnFocus": true, "hidden": true, "hiddenName": "clientHidden", "anchor": "-20", "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/aids/IncomeConfirmationsByGranting/client", "method": "GET" }) }), "name": "client" });
var granting229 = new Lino.TwinCombo({ "autoHeight": true, "fieldLabel": "Aid granting", "name": "granting", "pageSize": 20, "onTrigger2Click": function(e){ Lino.show_fk_detail(this,Lino.aids.GrantingsByX.detail,Lino.aids.GrantingsByX.insert)}, "emptyText": "Select a Aid granting...", "listeners": { "render": Lino.quicktip_renderer("Aid granting","(aids.IncomeConfirmationsByGranting.granting) ") }, "selectOnFocus": true, "hidden": true, "hiddenName": "grantingHidden", "anchor": "-20", "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/aids/IncomeConfirmationsByGranting/granting", "method": "GET" }) }) });
var main_1_panel230 = new Ext.Panel({ "autoHeight": true, "layout": "hbox", "items": [ { "flex": 44, "autoHeight": true, "layout": "form", "xtype": "panel", "items": client228, "labelAlign": "top" }, { "flex": 55, "autoHeight": true, "layout": "form", "xtype": "panel", "items": granting229, "labelAlign": "top" } ], "frame": false, "layoutConfig": { "align": "stretchmax" }, "hideCheckBoxLabels": true, "autoScroll": false, "labelWidth": 117, "hidden": true, "border": false, "labelAlign": "top" });
var start_date233 = new Lino.DateField({ "autoHeight": true, "fieldLabel": "Period from", "name": "start_date", "selectOnFocus": true, "listeners": { "render": Lino.quicktip_renderer("Period from","(aids.IncomeConfirmationsByGranting.start_date) ") }, "anchor": "-20" });
var end_date234 = new Lino.DateField({ "autoHeight": true, "fieldLabel": "until", "name": "end_date", "selectOnFocus": true, "listeners": { "render": Lino.quicktip_renderer("until","(aids.IncomeConfirmationsByGranting.end_date) ") }, "anchor": "-20" });
var main_2_panel235 = new Ext.Panel({ "autoScroll": false, "autoHeight": true, "border": false, "layout": "hbox", "labelWidth": 108, "hideCheckBoxLabels": true, "items": [ { "flex": 50, "autoHeight": true, "layout": "form", "xtype": "panel", "items": start_date233, "labelAlign": "top" }, { "flex": 50, "autoHeight": true, "layout": "form", "xtype": "panel", "items": end_date234, "labelAlign": "top" } ], "frame": false, "layoutConfig": { "align": "stretchmax" }, "labelAlign": "top" });
var category238 = new Lino.TwinCombo({ "autoHeight": true, "fieldLabel": "Category", "name": "category", "pageSize": 20, "onTrigger2Click": function(e){ Lino.show_fk_detail(this,Lino.aids.Categories.detail,Lino.aids.Categories.insert)}, "emptyText": "Select a Category...", "listeners": { "render": Lino.quicktip_renderer("Category","(aids.IncomeConfirmationsByGranting.category) ") }, "selectOnFocus": true, "hiddenName": "categoryHidden", "anchor": "-20", "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/aids/IncomeConfirmationsByGranting/category", "method": "GET" }) }) });
var amount239 = new Ext.form.NumberField({ "autoHeight": true, "decimalPrecision": 2, "fieldLabel": "Amount", "name": "amount", "decimalSeparator": ",", "listeners": { "render": Lino.quicktip_renderer("Amount","(aids.IncomeConfirmationsByGranting.amount) ") }, "selectOnFocus": true, "anchor": "-20", "allowBlank": true });
var main_3_panel240 = new Ext.Panel({ "autoScroll": false, "autoHeight": true, "border": false, "layout": "hbox", "labelWidth": 81, "hideCheckBoxLabels": true, "items": [ { "flex": 62, "autoHeight": true, "layout": "form", "xtype": "panel", "items": category238, "labelAlign": "top" }, { "flex": 37, "autoHeight": true, "layout": "form", "xtype": "panel", "items": amount239, "labelAlign": "top" } ], "frame": false, "layoutConfig": { "align": "stretchmax" }, "labelAlign": "top" });
var company243 = new Lino.TwinCombo({ "preferred_width": 20, "autoHeight": true, "fieldLabel": "Recipient (Organization)", "name": "company", "pageSize": 20, "onTrigger2Click": function(e){ Lino.show_fk_detail(this,Lino.contacts.Companies.detail,Lino.contacts.Companies.insert)}, "hiddenName": "companyHidden", "listeners": { "render": Lino.quicktip_renderer("Recipient (Organization)","(aids.IncomeConfirmationsByGranting.company) ") }, "selectOnFocus": true, "emptyText": "Select a Organisation...", "anchor": "-20", "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/aids/IncomeConfirmationsByGranting/company", "method": "GET" }) }) });
var contact_person244 = new Lino.TwinCombo({ "preferred_width": 20, "autoHeight": true, "fieldLabel": "Recipient (Person)", "name": "contact_person", "pageSize": 20, "onTrigger2Click": function(e){ Lino.show_fk_detail(this,Lino.contacts.Persons.detail,Lino.contacts.Persons.insert)}, "hiddenName": "contact_personHidden", "listeners": { "render": Lino.quicktip_renderer("Recipient (Person)","(aids.IncomeConfirmationsByGranting.contact_person) ") }, "selectOnFocus": true, "emptyText": "Select a Person...", "anchor": "-20", "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/aids/IncomeConfirmationsByGranting/contact_person", "method": "GET" }) }) });
var language245 = new Lino.ChoicesFieldElement({ "autoHeight": true, "fieldLabel": "Language", "name": "language", "hiddenName": "languageHidden", "listeners": { "render": Lino.quicktip_renderer("Language","(aids.IncomeConfirmationsByGranting.language) ") }, "selectOnFocus": true, "anchor": "-20", "store": [ [ "en", "English" ], [ "fr", "French" ], [ "de", "German" ], [ "nl", "Dutch" ] ] });
var main_4_panel246 = new Ext.Panel({ "autoScroll": false, "autoHeight": true, "border": false, "layout": "hbox", "labelWidth": 225, "hideCheckBoxLabels": true, "items": [ { "flex": 33, "autoHeight": true, "layout": "form", "xtype": "panel", "items": company243, "labelAlign": "top" }, { "flex": 33, "autoHeight": true, "layout": "form", "xtype": "panel", "items": contact_person244, "labelAlign": "top" }, { "flex": 33, "autoHeight": true, "layout": "form", "xtype": "panel", "items": language245, "labelAlign": "top" } ], "frame": false, "layoutConfig": { "align": "stretchmax" }, "labelAlign": "top" });
var remark250 = new Lino.RichTextPanel({ "selectOnFocus": true, "flex": 55, "containing_panel": this, "name": "remark", "title": "Remark" });
var main_panel251 = new Ext.Panel({ "autoScroll": false, "border": false, "layout": "vbox", "labelWidth": 0, "hideCheckBoxLabels": true, "items": [ main_1_panel230, main_2_panel235, main_3_panel240, main_4_panel246, remark250 ], "frame": true, "layoutConfig": { "align": "stretch" }, "bodyBorder": false, "labelAlign": "top" });
That’s a long string, but we can test whether it declares three components with an attribute “hidden”:
>>> with ses.user.user_type.context():
... js = '\n'.join(declare_vars(lh.main))
... js.count('"hidden"')
3
Which corresponds to what we want.
The Confirmable mixin¶
- class lino_welfare.modlib.aids.Confirmable¶
Base class for both
Granting
andConfirmation
.A signed confirmation cannot be modified, even not by a privileged user.
- signer¶
The agent who has signed or is expected to sign this item.
- state¶
The confirmation state of this object. Pointer to
ConfirmationStates
.
Actions:
- sign()¶
Sign this granting or confirmation, making most fields read-only.
This is available if signer is either empty or equals the requesting user. Except for system managers who can sign as somebody else by manually setting the signer field before running this action.
- revoke()¶
Revoke your signature of this granting or confirmation.
Just out of curiosity: the following help texts have been loaded using
lino.sphinxcontrib.help_texts_extractor
but aren’t yet translated:
>>> with translation.override('de'):
... print(aids.Granting.sign.help_text)
Sign this granting or confirmation, making most fields read-only.
>>> with translation.override('de'):
... print(aids.Granting.revoke.help_text)
Revoke your signature of this granting or confirmation.
Templates¶
Here is a list of the templates used by this plugin.
- certificate.body.html¶
See the source code
- clothing_bank.body.html¶
See the source code
- fixed_income.body.html¶
See the source code
- food_bank.body.html¶
See the source code
- foreigner_income.body.html¶
See the source code
- furniture.body.html¶
See the source code
- heating_refund.body.html¶
See the source code
- integ_income.body.html¶
See the source code
- medical_refund.body.html¶
See the source code
- urgent_medical_care.body.html¶
See the source code