Coverage for cc_modules/cc_device.py: 74%
78 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
1#!/usr/bin/env python
3"""
4camcops_server/cc_modules/cc_device.py
6===============================================================================
8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
11 This file is part of CamCOPS.
13 CamCOPS is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
18 CamCOPS is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
26===============================================================================
28**Representation of the client devices.**
30"""
32from typing import Optional, TYPE_CHECKING
34from cardinal_pythonlib.classes import classproperty
35from pendulum import DateTime as Pendulum
36from sqlalchemy.orm import Query, relationship, Session as SqlASession
37from sqlalchemy.sql.schema import Column, ForeignKey
38from sqlalchemy.sql.sqltypes import Boolean, DateTime, Integer, Text
40from camcops_server.cc_modules.cc_constants import DEVICE_NAME_FOR_SERVER
41from camcops_server.cc_modules.cc_report import Report
42from camcops_server.cc_modules.cc_user import User
43from camcops_server.cc_modules.cc_sqla_coltypes import (
44 DeviceNameColType,
45 SemanticVersionColType,
46)
47from camcops_server.cc_modules.cc_sqlalchemy import Base
48from camcops_server.cc_modules.cc_version import CAMCOPS_SERVER_VERSION
50if TYPE_CHECKING:
51 from camcops_server.cc_modules.cc_request import CamcopsRequest
54# =============================================================================
55# Device class
56# =============================================================================
59class Device(Base):
60 """
61 Represents a tablet (client) device.
62 """
64 __tablename__ = "_security_devices"
65 id = Column(
66 "id",
67 Integer,
68 primary_key=True,
69 autoincrement=True,
70 comment="ID of the source tablet device",
71 )
72 name = Column(
73 "name",
74 DeviceNameColType,
75 unique=True,
76 index=True,
77 comment="Short cryptic unique name of the source tablet device",
78 )
79 registered_by_user_id = Column(
80 "registered_by_user_id",
81 Integer,
82 ForeignKey("_security_users.id"),
83 comment="ID of user that registered the device",
84 )
85 registered_by_user = relationship(
86 "User", foreign_keys=[registered_by_user_id]
87 )
88 when_registered_utc = Column(
89 "when_registered_utc",
90 DateTime,
91 comment="Date/time when the device was registered (UTC)",
92 )
93 friendly_name = Column(
94 "friendly_name", Text, comment="Friendly name of the device"
95 )
96 camcops_version = Column(
97 "camcops_version",
98 SemanticVersionColType,
99 comment="CamCOPS version number on the tablet device",
100 )
101 last_upload_batch_utc = Column(
102 "last_upload_batch_utc",
103 DateTime,
104 comment="Date/time when the device's last upload batch started (UTC)",
105 )
106 ongoing_upload_batch_utc = Column(
107 "ongoing_upload_batch_utc",
108 DateTime,
109 comment="Date/time when the device's ongoing upload batch "
110 "started (UTC)",
111 )
112 uploading_user_id = Column(
113 "uploading_user_id",
114 Integer,
115 ForeignKey("_security_users.id", use_alter=True),
116 comment="ID of user in the process of uploading right now",
117 )
118 uploading_user = relationship("User", foreign_keys=[uploading_user_id])
119 currently_preserving = Column(
120 "currently_preserving",
121 Boolean,
122 default=False,
123 comment="Preservation currently in progress",
124 )
126 @classmethod
127 def get_device_by_name(
128 cls, dbsession: SqlASession, device_name: str
129 ) -> Optional["Device"]:
130 """
131 Returns a device by its name.
132 """
133 if not device_name:
134 return None
135 device = (
136 dbsession.query(cls).filter(cls.name == device_name).first()
137 ) # type: Optional[Device]
138 return device
140 @classmethod
141 def get_device_by_id(
142 cls, dbsession: SqlASession, device_id: int
143 ) -> Optional["Device"]:
144 """
145 Returns a device by its integer ID.
146 """
147 if device_id is None:
148 return None
149 device = (
150 dbsession.query(cls).filter(cls.id == device_id).first()
151 ) # type: Optional[Device]
152 return device
154 @classmethod
155 def get_server_device(cls, dbsession: SqlASession) -> "Device":
156 """
157 Return the special device meaning "the server", creating it if it
158 doesn't already exist.
159 """
160 device = cls.get_device_by_name(dbsession, DEVICE_NAME_FOR_SERVER)
161 if device is None:
162 device = Device()
163 device.name = DEVICE_NAME_FOR_SERVER
164 device.friendly_name = "CamCOPS server"
165 device.registered_by_user = User.get_system_user(dbsession)
166 device.when_registered_utc = Pendulum.utcnow()
167 device.camcops_version = CAMCOPS_SERVER_VERSION
168 dbsession.add(device)
169 dbsession.flush() # So that we can use the PK elsewhere
170 return device
172 def get_friendly_name(self) -> str:
173 """
174 Get the device's friendly name (or failing that, its name).
175 """
176 if self.friendly_name is None:
177 return self.name
178 return self.friendly_name
180 def get_friendly_name_and_id(self) -> str:
181 """
182 Get a formatted representation of the device (name, ID,
183 friendly name).
184 """
185 if self.friendly_name is None:
186 return self.name
187 return f"{self.name} (device# {self.id}, {self.friendly_name})"
189 def get_id(self) -> int:
190 """
191 Get the device's integer ID.
192 """
193 return self.id
195 def is_valid(self) -> bool:
196 """
197 Having instantiated an instance with ``Device(device_id)``, this
198 function reports whether it is a valid device, i.e. is it in the
199 database?
200 """
201 return self.id is not None
204# =============================================================================
205# Reports
206# =============================================================================
209class DeviceReport(Report):
210 """
211 Report to show registered devices.
212 This is a superuser-only report, so we do not override superuser_only.
213 """
215 # noinspection PyMethodParameters
216 @classproperty
217 def report_id(cls) -> str:
218 return "devices"
220 @classmethod
221 def title(cls, req: "CamcopsRequest") -> str:
222 _ = req.gettext
223 return _("(Server) Devices registered with the server")
225 def get_query(self, req: "CamcopsRequest") -> Query:
226 dbsession = req.dbsession
227 query = dbsession.query(
228 Device.id,
229 Device.name,
230 Device.registered_by_user_id,
231 Device.when_registered_utc,
232 Device.friendly_name,
233 Device.camcops_version,
234 Device.last_upload_batch_utc,
235 ).order_by(Device.id)
236 return query