Metadata-Version: 2.1
Name: edc-device
Version: 0.3.18
Summary: Get info on a data collection device for clinicedc/edc projects
Home-page: https://github.com/clinicedc/edc-device
Author: Erik van Widenfelt
Author-email: ew2789@gmail.com
License: GPL license, see LICENSE
Keywords: django Edc clinicedc,clinical trials
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 5.1
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Requires-Python: >=3.12
Description-Content-Type: text/x-rst
License-File: LICENSE
License-File: AUTHORS

|pypi| |actions| |coverage|


edc-device
----------

``edc-device`` provides device roles unique device IDs for hosts and clients where the hostname may not be reliable. Hosts can be group as servers, clients, node_servers and some of their functionality limited according to this role.

A unique device ID is used to seed unique subject and sample identifiers. Uniqueness is evaluated during deployment.

Device information is set in and read from ``edc_device.apps.AppConfig``.

You should subclass into your projects ``apps.py`` like this, for example:

.. code-block:: python

    from edc_device.apps import AppConfig as EdcDeviceAppConfigParent

    class EdcDeviceAppConfig(EdcDeviceAppConfigParent):
        device_id = '32'
        device_role = CLIENT
        device_permissions = DevicePermissions(
            plot_add, plot_change, ...)

and then in your settings:

.. code-block:: python

    INSTALLED_APPS = [
        ...
        my_app.apps.EdcDeviceAppConfig,
        myapp.apps.AppConfig',
    ]

Include in your ``urls.py``:

.. code-block:: python

    urlpatterns = [
        ...
        path('edc_device/', include('edc_device.urls')),
        ...
    ]

To get to the Edc Device home page, reverse the url like this:

.. code-block:: python

    reverse('edc_device:home_url')


Usage
=====


A ``client`` might look like this:

.. code-block:: python

    class EdcDeviceAppConfig(EdcDeviceAppConfigParent):
        device_id = '18'
        node_server_id_list = [97, 98, 99]
        middleman_id_list = [95, 96]

    >>> from django.apps import apps as django_apps
    >>> app_config = django_apps.get_app_config('edc_device')
    >>> app_config.device_id
    '18'
    >>> app_config.is_client
    True
    >>> app_config.device_role
    'Client'

A node server server might look like this:

.. code-block:: python

    class EdcDeviceAppConfig(EdcDeviceAppConfigParent):
        device_id = '98'
        node_server_id_list = [97, 98, 99]
        middleman_id_list = [95, 96]

    >>> from django.apps import apps as django_apps
    >>> app_config = django_apps.get_app_config('edc_device')
    >>> app_config.device_id
    '98'
    >>> app_config.is_node_server
    True
    >>> app_config.device_role
    'NodeServer'

A middleman server might look like this:

.. code-block:: python

    class EdcDeviceAppConfig(EdcDeviceAppConfigParent):
        device_id = '95'
        node_server_id_list = [97, 98, 99]
        middleman_id_list = [95, 96]

    >>> from django.apps import apps as django_apps
    >>> app_config = django_apps.get_app_config('edc_device')
    >>> app_config.device_id
    '95'
    >>> app_config.is_middleman
    True
    >>> app_config.device_role
    'Middleman'

The central server might look like this:

.. code-block:: python

    class EdcDeviceAppConfig(EdcDeviceAppConfigParent):
        device_id = '99'
        node_server_id_list = [97, 98, 99]
        middleman_id_list = [95, 96]

    >>> from django.apps import apps as django_apps
    >>> app_config = django_apps.get_app_config('edc_device')
    >>> app_config.device_id
    '99'
    >>> app_config.is_middleman
    True
    >>> app_config.device_role
    'CentralServer'


See also ``django-collect-offline``.


Device Permissions by Model
===========================

You can use the device role, or the device ID, to limit ADD/CHANGE permissions on a model.

``edc-device`` AppConfig maintains a collection of ``DeviceAddPermission`` and ``DeviceChangePermission`` instances that are inspected in the ``save`` method of a model using the ``DeviceModelMixin``.

To declare a ``DeviceAddPermission`` object:

.. code-block:: python

    test_model_add = DeviceAddPermission(
        model='my_app.mymodel, device_roles=[NODE_SERVER, CENTRAL_SERVER])

To declare a ``DeviceChangePermission`` object:

.. code-block:: python

    test_model_change = DeviceChangePermission(
        model='my_app.mymodel, device_roles=[CLIENT])

This means that if ``app_config.device_role`` is anything other than ``NODE_SERVER`` or ``CENTRAL_SERVER``, the save method will raise a ``DevicePermissionsAddError``.

To register the instances with ``edc_device.apps.AppConfig.device_permissions``:

.. code-block:: python

    device_permissions = DevicePermissions(test_model_add, test_model_change)

This means that if ``app_config.device_role`` is anything other than ``CLIENT``, the save method will raise a ``DevicePermissionsChangeError``.

On boot up you should see:

.. code-block:: python

    Loading Edc Device ...
      * device id is '10'.
      * device role is 'Client'.
      * device permissions exist for:
        - edc_device.testmodel ADD NodeServer,CentralServer
        - edc_device.testmodel CHANGE Client
    Done loading Edc Device.

Models declared with the ``EdcDeviceModelMixin`` check the device permissions collection on save. Note the model mixin is already declared with ``BaseUuidModel``.

.. code-block:: python

    from edc_model.models import BaseUuidModel

    class TestModel(BaseUuidModel):
        pass


Declaring device permissions directly on model ``Meta`` class:
==============================================================

You can declare device permissions on ``Meta.device_permissions`` in the same way as above.

.. code-block:: python

    [...]
    class Meta(DeviceModelMixin.Meta):
        device_permissions = DevicePermissions(...)

Both ``Meta`` and ``AppConfig`` device permissions will be called, where the ``Meta`` class object will be called first.

Disable device permissions by model instance:
=============================================

You can disable device permissions ``per model instance`` by setting ``check_device_permissions`` to ``False``


Customizing Device Permissions
==============================

The ADD and CHANGE device permission objects by default inspect the model's ``id``. If ``obj.id`` is ``None``, it as an ADD model operation; If ``obj.id`` is not ``None``, it is a CHANGE model operation.

You can change this by overriding the ``model_operation`` method. The ``model_operation`` must return ``None`` or some value, such as ``self.label``.

For example:

.. code-block:: python

    # default for DeviceAddPermission
    label = 'ADD'

    def model_operation(self, model_obj=None, **kwargs):
        if not model_obj.id:
            return self.label
        return None

    # overridden
    def model_operation(self, model_obj=None, **kwargs):
        """Return ADD if both id and plot identifier are None.
        """
        if not model_obj.id and not obj.plot_identifier:
            return self.label
        return None



.. |pypi| image:: https://img.shields.io/pypi/v/edc-device.svg
    :target: https://pypi.python.org/pypi/edc-device

.. |actions| image:: https://github.com/clinicedc/edc-device/actions/workflows/build.yml/badge.svg
  :target: https://github.com/clinicedc/edc-device/actions/workflows/build.yml

.. |coverage| image:: https://coveralls.io/repos/github/clinicedc/edc-device/badge.svg?branch=develop
    :target: https://coveralls.io/github/clinicedc/edc-device?branch=develop
