Uploads in Lino Welfare

This document describes the lino_xl.lib.uploads plugin as used by Lino Welfare.

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_welfare.projects.gerd.settings.doctests')
>>> from lino.api.doctest import *

Lino Welfare uses the lino_xl.lib.uploads plugin together with the lino_xl.lib.coachings plugin, which changes some aspects.

>>> print(cal.TasksByController.detail_layout.main)

    start_date due_date id workflow_buttons
    summary
    project user delegated
    owner created:20 modified:20
    description #notes.NotesByTask

Configuring upload types

This is the list of upload types:

>>> rt.login('rolf').show(uploads.UploadTypes)
==== ============================ ======== ================ ============= ========================= ====================== ============================
 ID   Bezeichnung                  Wanted   Upload-Bereich   Max. number   Ablaufwarnung (Einheit)   Ablaufwarnung (Wert)   Upload shortcut
---- ---------------------------- -------- ---------------- ------------- ------------------------- ---------------------- ----------------------------
 3    Arbeitserlaubnis             Ja       Allgemein        1             monatlich                 2
 2    Aufenthaltserlaubnis         Ja       Allgemein        1             monatlich                 2
 8    Behindertenausweis           Nein     Allgemein        -1                                      1
 9    Diplom                       Ja       Allgemein        -1                                      1
 4    Führerschein                 Ja       Allgemein        1             monatlich                 1
 5    Identifizierendes Dokument   Ja       Allgemein        1             monatlich                 1                      Identifizierendes Dokument
 10   Personalausweis              Nein     Allgemein        -1                                      1
 1    Source document              Ja       Allgemein        1             monatlich                 2
 6    Vertrag                      Nein     Allgemein        -1                                      1
 7    Ärztliche Bescheinigung      Nein     Allgemein        -1                                      1
                                                             **0**                                   **13**
==== ============================ ======== ================ ============= ========================= ====================== ============================

Two clients and their uploads

The following newcomer has uploaded 2 identifying documents. One of these is no longer valid, and we know it: needed has been unchecked. The other is still valid but will expire in 3 days.

>>> newcomer = pcsw.Client.objects.get(pk=121)
>>> print(newcomer)
DERICUM Daniel (121)

The UploadsByProject summary shows a summary grouped by upload type.

>>> rt.show(uploads.UploadsByProject, newcomer)
Identifizierendes Dokument: `12 <…>`__, `11 <…>`__ / Diplom: `9 <…>`__ / Personalausweis: `10 <…>`__ / `❏ <javascript:Lino.pcsw.Clients.show_uploads(null,false,121,{  })>`__
>>> rt.show(uploads.UploadsByProject, newcomer, nosummary=True)
============================ ============ ======= =============================== ===================
 Upload-Art                   Gültig bis   Nötig   Beschreibung                    Hochgeladen durch
---------------------------- ------------ ------- ------------------------------- -------------------
 Identifizierendes Dokument   25.05.14     Ja      Identifizierendes Dokument 12   Theresia Thelen
 Identifizierendes Dokument   22.04.14     Nein    Identifizierendes Dokument 11   Theresia Thelen
 Personalausweis              26.06.15     Nein    Personalausweis 10              Hubert Huppertz
 Diplom                       26.06.15     Nein    Diplom 9                        Hubert Huppertz
============================ ============ ======= =============================== ===================

Here is another beneficiary with three uploads:

>>> oldclient = pcsw.Client.objects.get(pk=124)
>>> print(str(oldclient))
DOBBELSTEIN Dorothée (124)
>>> rt.show(uploads.UploadsByProject, oldclient)
Aufenthaltserlaubnis: `residence_permit.pdf <…>`__ `⇲ </media/uploads/2014/05/residence_permit.pdf>`__ / Arbeitserlaubnis: `work_permit.pdf <…>`__ `⇲ </media/uploads/2014/05/work_permit.pdf>`__ / Führerschein: `driving_license.pdf <…>`__ `⇲ </media/uploads/2014/05/driving_license.pdf>`__ / `❏ <javascript:Lino.pcsw.Clients.show_uploads(null,false,124,{  })>`__
>>> rt.show(uploads.UploadsByProject, oldclient, nosummary=True)
====================== ============ ======= ============================================================================================= ===================
 Upload-Art             Gültig bis   Nötig   Beschreibung                                                                                  Hochgeladen durch
---------------------- ------------ ------- --------------------------------------------------------------------------------------------- -------------------
 Führerschein           01.06.14     Ja      `Führerschein driving_license.pdf </media/uploads/2014/05/driving_license.pdf>`__             Caroline Carnol
 Arbeitserlaubnis       30.08.14     Ja      `Arbeitserlaubnis work_permit.pdf </media/uploads/2014/05/work_permit.pdf>`__                 Alicia Allmanns
 Aufenthaltserlaubnis   18.03.15     Ja      `Aufenthaltserlaubnis residence_permit.pdf </media/uploads/2014/05/residence_permit.pdf>`__   Theresia Thelen
====================== ============ ======= ============================================================================================= ===================

My uploads

Most users can open two tables which show “their” uploads.

>>> print(str(uploads.MyUploads.label))
Meine Upload-Dateien
>>> print(str(uploads.MyExpiringUploads.label))
Meine ablaufenden Upload-Dateien

This is the MyUploads table for Theresia:

>>> rt.login('theresia').show(uploads.MyUploads)
==== ============================ ============================ ============ ============ ======= ============================================================================================= ======================================
 ID   Klient                       Upload-Art                   Gültig von   Gültig bis   Nötig   Beschreibung                                                                                  Datei
---- ---------------------------- ---------------------------- ------------ ------------ ------- --------------------------------------------------------------------------------------------- --------------------------------------
 13   DOBBELSTEIN Dorothée (124)   Aufenthaltserlaubnis                      18.03.15     Ja      `Aufenthaltserlaubnis residence_permit.pdf </media/uploads/2014/05/residence_permit.pdf>`__   uploads/2014/05/residence_permit.pdf
 12   DERICUM Daniel (121)         Identifizierendes Dokument                25.05.14     Ja      Identifizierendes Dokument 12
 11   DERICUM Daniel (121)         Identifizierendes Dokument                22.04.14     Nein    Identifizierendes Dokument 11
==== ============================ ============================ ============ ============ ======= ============================================================================================= ======================================

And the same for Caroline:

>>> rt.login('caroline').show(uploads.MyUploads)
==== ============================ ============== ============ ============ ======= =================================================================================== =====================================
 ID   Klient                       Upload-Art     Gültig von   Gültig bis   Nötig   Beschreibung                                                                        Datei
---- ---------------------------- -------------- ------------ ------------ ------- ----------------------------------------------------------------------------------- -------------------------------------
 15   DOBBELSTEIN Dorothée (124)   Führerschein                01.06.14     Ja      `Führerschein driving_license.pdf </media/uploads/2014/05/driving_license.pdf>`__   uploads/2014/05/driving_license.pdf
==== ============================ ============== ============ ============ ======= =================================================================================== =====================================

This is the MyExpiringUploads table for Hubert:

>>> rt.login('hubert').show(uploads.MyExpiringUploads)
========================= ====================== ======================== =================== ============ ============ =======
 Klient                    Upload-Art             Beschreibung             Hochgeladen durch   Gültig von   Gültig bis   Nötig
------------------------- ---------------------- ------------------------ ------------------- ------------ ------------ -------
 AUSDEMWALD Alfons (116)   Source document        Source document 1        Hubert Huppertz                  17.05.15     Ja
 AUSDEMWALD Alfons (116)   Aufenthaltserlaubnis   Aufenthaltserlaubnis 2   Hubert Huppertz                  17.05.15     Ja
========================= ====================== ======================== =================== ============ ============ =======

Theresia does not coach anybody, so the MyExpiringUploads table is empty for her:

>>> rt.login('theresia').show(uploads.MyExpiringUploads)
Keine Daten anzuzeigen

Shortcut fields

>>> id_document = uploads.UploadType.objects.get(shortcut=uploads.Shortcuts.id_document)
>>> rt.show(uploads.UploadsByType, id_document)
=================== ========================= ============================ ======= ============ ============ ======= ===============================
 Hochgeladen durch   Klient                    Upload-Art                   Datei   Gültig von   Gültig bis   Nötig   Beschreibung
------------------- ------------------------- ---------------------------- ------- ------------ ------------ ------- -------------------------------
 Theresia Thelen     DERICUM Daniel (121)      Identifizierendes Dokument                        25.05.14     Ja      Identifizierendes Dokument 12
 Theresia Thelen     DERICUM Daniel (121)      Identifizierendes Dokument                        22.04.14     Nein    Identifizierendes Dokument 11
 Hubert Huppertz     COLLARD Charlotte (118)   Identifizierendes Dokument                        06.06.15     Ja      Identifizierendes Dokument 5
=================== ========================= ============================ ======= ============ ============ ======= ===============================

Let’s have a closer look at the id_document shortcut field for some customers.

The response to this AJAX request is in JSON, and we want to inspect the id_document field using BeautifulSoup:

>>> uri = "pcsw/Clients/{0}".format(newcomer.pk)
>>> soup = get_json_soup('romain', uri, 'id_document')

This is an upload shortcut field whose target has more than one row. Which means that it has two buttons.

>>> div = soup.div
>>> len(div.contents)
3

The first button opens a detail window on the last uploaded file:

>>> div.contents[0]  
<a href='javascript:Lino.uploads.Uploads.detail.run(null,{ "base_params": {  },
"param_values": { ... }, "record_id": 12 })'
style="text-decoration:none">Letzte</a>

The second item is just the comma that separates the two buttons:

>>> div.contents[1]
', '

The second button opens the list of uploads. Here is the full HTML definition of that button:

>>> btn = div.contents[2]
>>> btn  
<a href='javascript:Lino.uploads.UploadsByProject.grid.run(null,{ "base_params":
{ "mk": 121, "mt": 58, "type": 5 }, "param_values": {
"coached_by": null, "coached_byHidden": null, "end_date": null,
"observed_event": "Est active", "observed_eventHidden": "20", "start_date":
null, "upload_type": null, "upload_typeHidden": null, "user": null,
"userHidden": null }, "record_id": 12 })' style="text-decoration:none">Alle 2
Dateien</a>

Already its href is quite long:

>>> btn['href']
'javascript:Lino.uploads.UploadsByProject.grid.run(null,{ "base_params": { "mk": 121, "mt": 58, "type": 5 }, "param_values": { "coached_by": null, "coached_byHidden": null, "end_date": null, "observed_event": "Est active", "observed_eventHidden": "20", "start_date": null, "upload_type": null, "upload_typeHidden": null, "user": null, "userHidden": null }, "record_id": 12 })'

This code would call the run() method of the grid view of the uploads.UploadsByProject table with two positional arguments, the first being null and the second being a big JavaScript object. Let’s inspect this second argument of this second button.

>>> arg = btn['href'][55:-1]
>>> print(arg)  
{ ... }
>>> d = AttrDict(json.loads(arg))

It has 3 keys:

>>> keys = list(d.keys())
>>> keys.sort()
>>> print(json.dumps(keys))
["base_params", "param_values", "record_id"]
>>> d.record_id
12
>>> d.base_params 
{'mk': 121, 'mt': 58, 'type': 5}
>>> pprint(d.param_values)
... 
{'coached_by': None,
 'coached_byHidden': None,
 'end_date': None,
 'observed_event': 'Est active',
 'observed_eventHidden': '20',
 'start_date': None,
 'upload_type': None,
 'upload_typeHidden': None,
 'user': None,
 'userHidden': None}

Uploads by client

UploadsByProject shows all the uploads of a given client, but it has a customized get_slave_summary.

The following example is going to use client #121 as master.

>>> obj = oldclient

Here we use lino.api.doctest.get_json_soup() to inspect what the summary view of UploadsByProject returns for this client.

>>> soup = get_json_soup('rolf', 'pcsw/Clients/124', 'uploads.UploadsByProject')
>>> print(soup.get_text())
... 
Source document: Aufenthaltserlaubnis: residence_permit.pdf ⇲Arbeitserlaubnis: work_permit.pdf ⇲Führerschein: driving_license.pdf ⇲Identifizierendes Dokument: Diplom:

The HTML fragment contains five links:

>>> links = soup.find_all('a')
>>> len(links)
9

The first link would run the insert action on UploadsByProject, with the owner set to this client

>>> btn = links[0]
>>> print(btn.string)
None
>>> print(btn.img['src'])
/static/images/mjames/add.png
>>> print(btn)
... 
<a href='javascript:Lino.uploads.UploadsByProject.insert.run(null,{...})'
style="vertical-align:-30%;" title="Neue(n/s) Upload-Datei
erstellen."><img alt="add" src="/static/images/mjames/add.png"/></a>
>>> print(links[2].get('href'))
... 
/media/uploads/2014/05/residence_permit.pdf
>>> print(links[3].get('href'))
... 
javascript:Lino.uploads.Uploads.detail.run(null,{ ..., "record_id": 14 })

Now let’s inspect the javascript of the first button

>>> dots = btn['href'][57:-1]
>>> print(dots)  
{ ... }
>>> d = AttrDict(json.loads(dots))
>>> pprint(d)
... 
{'base_params': {'mk': 124, 'mt': 58, 'type_id': 1},
 'data_record': {'data': {'description': '',
                          'disabled_fields': {'camera_stream': True,
                                              'file_size': True,
                                              'mimetype': True},
                          'end_date': None,
                          'file': '',
                          'needed': True,
                          'project': 'DOBBELSTEIN Dorothée (124)',
                          'projectHidden': 124,
                          'start_date': None,
                          'type': 'Source document',
                          'typeHidden': 1},
                 'phantom': True,
                 'title': 'Upload-Datei erstellen'},
 'param_values': {'coached_by': None,
                  'coached_byHidden': None,
                  'end_date': None,
                  'observed_event': 'Ist aktiv',
                  'observed_eventHidden': '20',
                  'start_date': None,
                  'upload_type': None,
                  'upload_typeHidden': None,
                  'user': None,
                  'userHidden': None},
 'record_id': None}