Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/django/db/models/fields/files.py: 31%

234 statements  

« prev     ^ index     » next       coverage.py v7.0.5, created at 2023-01-17 06:13 +0000

1import datetime 

2import posixpath 

3 

4from django import forms 

5from django.core import checks 

6from django.core.files.base import File 

7from django.core.files.images import ImageFile 

8from django.core.files.storage import Storage, default_storage 

9from django.core.files.utils import validate_file_name 

10from django.db.models import signals 

11from django.db.models.fields import Field 

12from django.db.models.query_utils import DeferredAttribute 

13from django.db.models.utils import AltersData 

14from django.utils.translation import gettext_lazy as _ 

15 

16 

17class FieldFile(File, AltersData): 

18 def __init__(self, instance, field, name): 

19 super().__init__(None, name) 

20 self.instance = instance 

21 self.field = field 

22 self.storage = field.storage 

23 self._committed = True 

24 

25 def __eq__(self, other): 

26 # Older code may be expecting FileField values to be simple strings. 

27 # By overriding the == operator, it can remain backwards compatibility. 

28 if hasattr(other, "name"): 

29 return self.name == other.name 

30 return self.name == other 

31 

32 def __hash__(self): 

33 return hash(self.name) 

34 

35 # The standard File contains most of the necessary properties, but 

36 # FieldFiles can be instantiated without a name, so that needs to 

37 # be checked for here. 

38 

39 def _require_file(self): 

40 if not self: 

41 raise ValueError( 

42 "The '%s' attribute has no file associated with it." % self.field.name 

43 ) 

44 

45 def _get_file(self): 

46 self._require_file() 

47 if getattr(self, "_file", None) is None: 

48 self._file = self.storage.open(self.name, "rb") 

49 return self._file 

50 

51 def _set_file(self, file): 

52 self._file = file 

53 

54 def _del_file(self): 

55 del self._file 

56 

57 file = property(_get_file, _set_file, _del_file) 

58 

59 @property 

60 def path(self): 

61 self._require_file() 

62 return self.storage.path(self.name) 

63 

64 @property 

65 def url(self): 

66 self._require_file() 

67 return self.storage.url(self.name) 

68 

69 @property 

70 def size(self): 

71 self._require_file() 

72 if not self._committed: 

73 return self.file.size 

74 return self.storage.size(self.name) 

75 

76 def open(self, mode="rb"): 

77 self._require_file() 

78 if getattr(self, "_file", None) is None: 

79 self.file = self.storage.open(self.name, mode) 

80 else: 

81 self.file.open(mode) 

82 return self 

83 

84 # open() doesn't alter the file's contents, but it does reset the pointer 

85 open.alters_data = True 

86 

87 # In addition to the standard File API, FieldFiles have extra methods 

88 # to further manipulate the underlying file, as well as update the 

89 # associated model instance. 

90 

91 def save(self, name, content, save=True): 

92 name = self.field.generate_filename(self.instance, name) 

93 self.name = self.storage.save(name, content, max_length=self.field.max_length) 

94 setattr(self.instance, self.field.attname, self.name) 

95 self._committed = True 

96 

97 # Save the object because it has changed, unless save is False 

98 if save: 

99 self.instance.save() 

100 

101 save.alters_data = True 

102 

103 def delete(self, save=True): 

104 if not self: 

105 return 

106 # Only close the file if it's already open, which we know by the 

107 # presence of self._file 

108 if hasattr(self, "_file"): 

109 self.close() 

110 del self.file 

111 

112 self.storage.delete(self.name) 

113 

114 self.name = None 

115 setattr(self.instance, self.field.attname, self.name) 

116 self._committed = False 

117 

118 if save: 

119 self.instance.save() 

120 

121 delete.alters_data = True 

122 

123 @property 

124 def closed(self): 

125 file = getattr(self, "_file", None) 

126 return file is None or file.closed 

127 

128 def close(self): 

129 file = getattr(self, "_file", None) 

130 if file is not None: 

131 file.close() 

132 

133 def __getstate__(self): 

134 # FieldFile needs access to its associated model field, an instance and 

135 # the file's name. Everything else will be restored later, by 

136 # FileDescriptor below. 

137 return { 

138 "name": self.name, 

139 "closed": False, 

140 "_committed": True, 

141 "_file": None, 

142 "instance": self.instance, 

143 "field": self.field, 

144 } 

145 

146 def __setstate__(self, state): 

147 self.__dict__.update(state) 

148 self.storage = self.field.storage 

149 

150 

151class FileDescriptor(DeferredAttribute): 

152 """ 

153 The descriptor for the file attribute on the model instance. Return a 

154 FieldFile when accessed so you can write code like:: 

155 

156 >>> from myapp.models import MyModel 

157 >>> instance = MyModel.objects.get(pk=1) 

158 >>> instance.file.size 

159 

160 Assign a file object on assignment so you can do:: 

161 

162 >>> with open('/path/to/hello.world') as f: 

163 ... instance.file = File(f) 

164 """ 

165 

166 def __get__(self, instance, cls=None): 

167 if instance is None: 

168 return self 

169 

170 # This is slightly complicated, so worth an explanation. 

171 # instance.file needs to ultimately return some instance of `File`, 

172 # probably a subclass. Additionally, this returned object needs to have 

173 # the FieldFile API so that users can easily do things like 

174 # instance.file.path and have that delegated to the file storage engine. 

175 # Easy enough if we're strict about assignment in __set__, but if you 

176 # peek below you can see that we're not. So depending on the current 

177 # value of the field we have to dynamically construct some sort of 

178 # "thing" to return. 

179 

180 # The instance dict contains whatever was originally assigned 

181 # in __set__. 

182 file = super().__get__(instance, cls) 

183 

184 # If this value is a string (instance.file = "path/to/file") or None 

185 # then we simply wrap it with the appropriate attribute class according 

186 # to the file field. [This is FieldFile for FileFields and 

187 # ImageFieldFile for ImageFields; it's also conceivable that user 

188 # subclasses might also want to subclass the attribute class]. This 

189 # object understands how to convert a path to a file, and also how to 

190 # handle None. 

191 if isinstance(file, str) or file is None: 

192 attr = self.field.attr_class(instance, self.field, file) 

193 instance.__dict__[self.field.attname] = attr 

194 

195 # Other types of files may be assigned as well, but they need to have 

196 # the FieldFile interface added to them. Thus, we wrap any other type of 

197 # File inside a FieldFile (well, the field's attr_class, which is 

198 # usually FieldFile). 

199 elif isinstance(file, File) and not isinstance(file, FieldFile): 

200 file_copy = self.field.attr_class(instance, self.field, file.name) 

201 file_copy.file = file 

202 file_copy._committed = False 

203 instance.__dict__[self.field.attname] = file_copy 

204 

205 # Finally, because of the (some would say boneheaded) way pickle works, 

206 # the underlying FieldFile might not actually itself have an associated 

207 # file. So we need to reset the details of the FieldFile in those cases. 

208 elif isinstance(file, FieldFile) and not hasattr(file, "field"): 

209 file.instance = instance 

210 file.field = self.field 

211 file.storage = self.field.storage 

212 

213 # Make sure that the instance is correct. 

214 elif isinstance(file, FieldFile) and instance is not file.instance: 

215 file.instance = instance 

216 

217 # That was fun, wasn't it? 

218 return instance.__dict__[self.field.attname] 

219 

220 def __set__(self, instance, value): 

221 instance.__dict__[self.field.attname] = value 

222 

223 

224class FileField(Field): 

225 

226 # The class to wrap instance attributes in. Accessing the file object off 

227 # the instance will always return an instance of attr_class. 

228 attr_class = FieldFile 

229 

230 # The descriptor to use for accessing the attribute off of the class. 

231 descriptor_class = FileDescriptor 

232 

233 description = _("File") 

234 

235 def __init__( 

236 self, verbose_name=None, name=None, upload_to="", storage=None, **kwargs 

237 ): 

238 self._primary_key_set_explicitly = "primary_key" in kwargs 

239 

240 self.storage = storage or default_storage 

241 if callable(self.storage): 

242 # Hold a reference to the callable for deconstruct(). 

243 self._storage_callable = self.storage 

244 self.storage = self.storage() 

245 if not isinstance(self.storage, Storage): 

246 raise TypeError( 

247 "%s.storage must be a subclass/instance of %s.%s" 

248 % ( 

249 self.__class__.__qualname__, 

250 Storage.__module__, 

251 Storage.__qualname__, 

252 ) 

253 ) 

254 self.upload_to = upload_to 

255 

256 kwargs.setdefault("max_length", 100) 

257 super().__init__(verbose_name, name, **kwargs) 

258 

259 def check(self, **kwargs): 

260 return [ 

261 *super().check(**kwargs), 

262 *self._check_primary_key(), 

263 *self._check_upload_to(), 

264 ] 

265 

266 def _check_primary_key(self): 

267 if self._primary_key_set_explicitly: 

268 return [ 

269 checks.Error( 

270 "'primary_key' is not a valid argument for a %s." 

271 % self.__class__.__name__, 

272 obj=self, 

273 id="fields.E201", 

274 ) 

275 ] 

276 else: 

277 return [] 

278 

279 def _check_upload_to(self): 

280 if isinstance(self.upload_to, str) and self.upload_to.startswith("/"): 

281 return [ 

282 checks.Error( 

283 "%s's 'upload_to' argument must be a relative path, not an " 

284 "absolute path." % self.__class__.__name__, 

285 obj=self, 

286 id="fields.E202", 

287 hint="Remove the leading slash.", 

288 ) 

289 ] 

290 else: 

291 return [] 

292 

293 def deconstruct(self): 

294 name, path, args, kwargs = super().deconstruct() 

295 if kwargs.get("max_length") == 100: 

296 del kwargs["max_length"] 

297 kwargs["upload_to"] = self.upload_to 

298 if self.storage is not default_storage: 

299 kwargs["storage"] = getattr(self, "_storage_callable", self.storage) 

300 return name, path, args, kwargs 

301 

302 def get_internal_type(self): 

303 return "FileField" 

304 

305 def get_prep_value(self, value): 

306 value = super().get_prep_value(value) 

307 # Need to convert File objects provided via a form to string for 

308 # database insertion. 

309 if value is None: 

310 return None 

311 return str(value) 

312 

313 def pre_save(self, model_instance, add): 

314 file = super().pre_save(model_instance, add) 

315 if file and not file._committed: 

316 # Commit the file to storage prior to saving the model 

317 file.save(file.name, file.file, save=False) 

318 return file 

319 

320 def contribute_to_class(self, cls, name, **kwargs): 

321 super().contribute_to_class(cls, name, **kwargs) 

322 setattr(cls, self.attname, self.descriptor_class(self)) 

323 

324 def generate_filename(self, instance, filename): 

325 """ 

326 Apply (if callable) or prepend (if a string) upload_to to the filename, 

327 then delegate further processing of the name to the storage backend. 

328 Until the storage layer, all file paths are expected to be Unix style 

329 (with forward slashes). 

330 """ 

331 if callable(self.upload_to): 

332 filename = self.upload_to(instance, filename) 

333 else: 

334 dirname = datetime.datetime.now().strftime(str(self.upload_to)) 

335 filename = posixpath.join(dirname, filename) 

336 filename = validate_file_name(filename, allow_relative_path=True) 

337 return self.storage.generate_filename(filename) 

338 

339 def save_form_data(self, instance, data): 

340 # Important: None means "no change", other false value means "clear" 

341 # This subtle distinction (rather than a more explicit marker) is 

342 # needed because we need to consume values that are also sane for a 

343 # regular (non Model-) Form to find in its cleaned_data dictionary. 

344 if data is not None: 

345 # This value will be converted to str and stored in the 

346 # database, so leaving False as-is is not acceptable. 

347 setattr(instance, self.name, data or "") 

348 

349 def formfield(self, **kwargs): 

350 return super().formfield( 

351 **{ 

352 "form_class": forms.FileField, 

353 "max_length": self.max_length, 

354 **kwargs, 

355 } 

356 ) 

357 

358 

359class ImageFileDescriptor(FileDescriptor): 

360 """ 

361 Just like the FileDescriptor, but for ImageFields. The only difference is 

362 assigning the width/height to the width_field/height_field, if appropriate. 

363 """ 

364 

365 def __set__(self, instance, value): 

366 previous_file = instance.__dict__.get(self.field.attname) 

367 super().__set__(instance, value) 

368 

369 # To prevent recalculating image dimensions when we are instantiating 

370 # an object from the database (bug #11084), only update dimensions if 

371 # the field had a value before this assignment. Since the default 

372 # value for FileField subclasses is an instance of field.attr_class, 

373 # previous_file will only be None when we are called from 

374 # Model.__init__(). The ImageField.update_dimension_fields method 

375 # hooked up to the post_init signal handles the Model.__init__() cases. 

376 # Assignment happening outside of Model.__init__() will trigger the 

377 # update right here. 

378 if previous_file is not None: 

379 self.field.update_dimension_fields(instance, force=True) 

380 

381 

382class ImageFieldFile(ImageFile, FieldFile): 

383 def delete(self, save=True): 

384 # Clear the image dimensions cache 

385 if hasattr(self, "_dimensions_cache"): 

386 del self._dimensions_cache 

387 super().delete(save) 

388 

389 

390class ImageField(FileField): 

391 attr_class = ImageFieldFile 

392 descriptor_class = ImageFileDescriptor 

393 description = _("Image") 

394 

395 def __init__( 

396 self, 

397 verbose_name=None, 

398 name=None, 

399 width_field=None, 

400 height_field=None, 

401 **kwargs, 

402 ): 

403 self.width_field, self.height_field = width_field, height_field 

404 super().__init__(verbose_name, name, **kwargs) 

405 

406 def check(self, **kwargs): 

407 return [ 

408 *super().check(**kwargs), 

409 *self._check_image_library_installed(), 

410 ] 

411 

412 def _check_image_library_installed(self): 

413 try: 

414 from PIL import Image # NOQA 

415 except ImportError: 

416 return [ 

417 checks.Error( 

418 "Cannot use ImageField because Pillow is not installed.", 

419 hint=( 

420 "Get Pillow at https://pypi.org/project/Pillow/ " 

421 'or run command "python -m pip install Pillow".' 

422 ), 

423 obj=self, 

424 id="fields.E210", 

425 ) 

426 ] 

427 else: 

428 return [] 

429 

430 def deconstruct(self): 

431 name, path, args, kwargs = super().deconstruct() 

432 if self.width_field: 

433 kwargs["width_field"] = self.width_field 

434 if self.height_field: 

435 kwargs["height_field"] = self.height_field 

436 return name, path, args, kwargs 

437 

438 def contribute_to_class(self, cls, name, **kwargs): 

439 super().contribute_to_class(cls, name, **kwargs) 

440 # Attach update_dimension_fields so that dimension fields declared 

441 # after their corresponding image field don't stay cleared by 

442 # Model.__init__, see bug #11196. 

443 # Only run post-initialization dimension update on non-abstract models 

444 if not cls._meta.abstract: 

445 signals.post_init.connect(self.update_dimension_fields, sender=cls) 

446 

447 def update_dimension_fields(self, instance, force=False, *args, **kwargs): 

448 """ 

449 Update field's width and height fields, if defined. 

450 

451 This method is hooked up to model's post_init signal to update 

452 dimensions after instantiating a model instance. However, dimensions 

453 won't be updated if the dimensions fields are already populated. This 

454 avoids unnecessary recalculation when loading an object from the 

455 database. 

456 

457 Dimensions can be forced to update with force=True, which is how 

458 ImageFileDescriptor.__set__ calls this method. 

459 """ 

460 # Nothing to update if the field doesn't have dimension fields or if 

461 # the field is deferred. 

462 has_dimension_fields = self.width_field or self.height_field 

463 if not has_dimension_fields or self.attname not in instance.__dict__: 

464 return 

465 

466 # getattr will call the ImageFileDescriptor's __get__ method, which 

467 # coerces the assigned value into an instance of self.attr_class 

468 # (ImageFieldFile in this case). 

469 file = getattr(instance, self.attname) 

470 

471 # Nothing to update if we have no file and not being forced to update. 

472 if not file and not force: 

473 return 

474 

475 dimension_fields_filled = not ( 

476 (self.width_field and not getattr(instance, self.width_field)) 

477 or (self.height_field and not getattr(instance, self.height_field)) 

478 ) 

479 # When both dimension fields have values, we are most likely loading 

480 # data from the database or updating an image field that already had 

481 # an image stored. In the first case, we don't want to update the 

482 # dimension fields because we are already getting their values from the 

483 # database. In the second case, we do want to update the dimensions 

484 # fields and will skip this return because force will be True since we 

485 # were called from ImageFileDescriptor.__set__. 

486 if dimension_fields_filled and not force: 

487 return 

488 

489 # file should be an instance of ImageFieldFile or should be None. 

490 if file: 

491 width = file.width 

492 height = file.height 

493 else: 

494 # No file, so clear dimensions fields. 

495 width = None 

496 height = None 

497 

498 # Update the width and height fields. 

499 if self.width_field: 

500 setattr(instance, self.width_field, width) 

501 if self.height_field: 

502 setattr(instance, self.height_field, height) 

503 

504 def formfield(self, **kwargs): 

505 return super().formfield( 

506 **{ 

507 "form_class": forms.ImageField, 

508 **kwargs, 

509 } 

510 )