Coverage for cc_modules/cc_device.py : 74%

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