Hide keyboard shortcuts

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 

2 

3""" 

4camcops_server/cc_modules/cc_device.py 

5 

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

7 

8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com). 

9 

10 This file is part of CamCOPS. 

11 

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. 

16 

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. 

21 

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

24 

25=============================================================================== 

26 

27**Representation of the client devices.** 

28 

29""" 

30 

31from typing import Optional, TYPE_CHECKING 

32 

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 

38 

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 

48 

49if TYPE_CHECKING: 

50 from camcops_server.cc_modules.cc_request import CamcopsRequest 

51 

52 

53# ============================================================================= 

54# Device class 

55# ============================================================================= 

56 

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 ) 

109 

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 

124 

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 

139 

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 

157 

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 

165 

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

174 

175 def get_id(self) -> int: 

176 """ 

177 Get the device's integer ID. 

178 """ 

179 return self.id 

180 

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 

188 

189 

190# ============================================================================= 

191# Reports 

192# ============================================================================= 

193 

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" 

203 

204 @classmethod 

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

206 _ = req.gettext 

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

208 

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