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

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/cc_modules/cc_device.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

10 

11 This file is part of CamCOPS. 

12 

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. 

17 

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. 

22 

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/>. 

25 

26=============================================================================== 

27 

28**Representation of the client devices.** 

29 

30""" 

31 

32from typing import Optional, TYPE_CHECKING 

33 

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 

39 

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 

49 

50if TYPE_CHECKING: 

51 from camcops_server.cc_modules.cc_request import CamcopsRequest 

52 

53 

54# ============================================================================= 

55# Device class 

56# ============================================================================= 

57 

58 

59class Device(Base): 

60 """ 

61 Represents a tablet (client) device. 

62 """ 

63 

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 ) 

125 

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 

139 

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 

153 

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 

171 

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 

179 

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})" 

188 

189 def get_id(self) -> int: 

190 """ 

191 Get the device's integer ID. 

192 """ 

193 return self.id 

194 

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 

202 

203 

204# ============================================================================= 

205# Reports 

206# ============================================================================= 

207 

208 

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 """ 

214 

215 # noinspection PyMethodParameters 

216 @classproperty 

217 def report_id(cls) -> str: 

218 return "devices" 

219 

220 @classmethod 

221 def title(cls, req: "CamcopsRequest") -> str: 

222 _ = req.gettext 

223 return _("(Server) Devices registered with the server") 

224 

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