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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

251 statements  

1import datetime 

2import posixpath 

3 

4from django import forms 

5from django.core import checks 

6from django.core.exceptions import FieldError 

7from django.core.files.base import ContentFile, File 

8from django.core.files.images import ImageFile 

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

10from django.core.files.utils import validate_file_name 

11from django.db.models import signals 

12from django.db.models.expressions import DatabaseDefault 

13from django.db.models.fields import Field 

14from django.db.models.query_utils import DeferredAttribute 

15from django.db.models.utils import AltersData 

16from django.utils.translation import gettext_lazy as _ 

17from django.utils.version import PY311 

18 

19 

20class FieldFile(File, AltersData): 

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

22 super().__init__(None, name) 

23 self.instance = instance 

24 self.field = field 

25 self.storage = field.storage 

26 self._committed = True 

27 

28 def __eq__(self, other): 

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

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

31 if hasattr(other, "name"): 

32 return self.name == other.name 

33 return self.name == other 

34 

35 def __hash__(self): 

36 return hash(self.name) 

37 

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

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

40 # be checked for here. 

41 

42 def _require_file(self): 

43 if not self: 

44 raise ValueError( 

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

46 ) 

47 

48 def _get_file(self): 

49 self._require_file() 

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

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

52 return self._file 

53 

54 def _set_file(self, file): 

55 self._file = file 

56 

57 def _del_file(self): 

58 del self._file 

59 

60 file = property(_get_file, _set_file, _del_file) 

61 

62 @property 

63 def path(self): 

64 self._require_file() 

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

66 

67 @property 

68 def url(self): 

69 self._require_file() 

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

71 

72 @property 

73 def size(self): 

74 self._require_file() 

75 if not self._committed: 

76 return self.file.size 

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

78 

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

80 self._require_file() 

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

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

83 else: 

84 self.file.open(mode) 

85 return self 

86 

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

88 open.alters_data = True 

89 

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

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

92 # associated model instance. 

93 

94 def _set_instance_attribute(self, name, content): 

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

96 

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

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

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

100 self._set_instance_attribute(self.name, content) 

101 self._committed = True 

102 

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

104 if save: 

105 self.instance.save() 

106 

107 save.alters_data = True 

108 

109 def delete(self, save=True): 

110 if not self: 

111 return 

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

113 # presence of self._file 

114 if hasattr(self, "_file"): 

115 self.close() 

116 del self.file 

117 

118 self.storage.delete(self.name) 

119 

120 self.name = None 

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

122 self._committed = False 

123 

124 if save: 

125 self.instance.save() 

126 

127 delete.alters_data = True 

128 

129 @property 

130 def closed(self): 

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

132 return file is None or file.closed 

133 

134 def close(self): 

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

136 if file is not None: 

137 file.close() 

138 

139 def __getstate__(self): 

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

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

142 # FileDescriptor below. 

143 return { 

144 "name": self.name, 

145 "closed": False, 

146 "_committed": True, 

147 "_file": None, 

148 "instance": self.instance, 

149 "field": self.field, 

150 } 

151 

152 def __setstate__(self, state): 

153 self.__dict__.update(state) 

154 self.storage = self.field.storage 

155 

156 

157class FileDescriptor(DeferredAttribute): 

158 """ 

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

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

161 

162 >>> from myapp.models import MyModel 

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

164 >>> instance.file.size 

165 

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

167 

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

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

170 """ 

171 

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

173 if instance is None: 

174 return self 

175 

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

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

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

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

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

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

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

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

184 # "thing" to return. 

185 

186 # The instance dict contains whatever was originally assigned 

187 # in __set__. 

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

189 

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

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

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

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

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

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

196 # handle None. 

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

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

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

200 

201 # If this value is a DatabaseDefault, initialize the attribute class 

202 # for this field with its db_default value. 

203 elif isinstance(file, DatabaseDefault): 

204 attr = self.field.attr_class(instance, self.field, self.field.db_default) 

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

206 

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

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

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

210 # usually FieldFile). 

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

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

213 file_copy.file = file 

214 file_copy._committed = False 

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

216 

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

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

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

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

221 file.instance = instance 

222 file.field = self.field 

223 file.storage = self.field.storage 

224 

225 # Make sure that the instance is correct. 

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

227 file.instance = instance 

228 

229 # That was fun, wasn't it? 

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

231 

232 def __set__(self, instance, value): 

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

234 

235 

236class FileField(Field): 

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

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

239 attr_class = FieldFile 

240 

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

242 descriptor_class = FileDescriptor 

243 

244 description = _("File") 

245 

246 def __init__( 

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

248 ): 

249 self._primary_key_set_explicitly = "primary_key" in kwargs 

250 

251 self.storage = storage or default_storage 

252 if callable(self.storage): 

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

254 self._storage_callable = self.storage 

255 self.storage = self.storage() 

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

257 raise TypeError( 

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

259 % ( 

260 self.__class__.__qualname__, 

261 Storage.__module__, 

262 Storage.__qualname__, 

263 ) 

264 ) 

265 self.upload_to = upload_to 

266 

267 kwargs.setdefault("max_length", 100) 

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

269 

270 def check(self, **kwargs): 

271 return [ 

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

273 *self._check_primary_key(), 

274 *self._check_upload_to(), 

275 ] 

276 

277 def _check_primary_key(self): 

278 if self._primary_key_set_explicitly: 

279 return [ 

280 checks.Error( 

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

282 % self.__class__.__name__, 

283 obj=self, 

284 id="fields.E201", 

285 ) 

286 ] 

287 else: 

288 return [] 

289 

290 def _check_upload_to(self): 

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

292 return [ 

293 checks.Error( 

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

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

296 obj=self, 

297 id="fields.E202", 

298 hint="Remove the leading slash.", 

299 ) 

300 ] 

301 else: 

302 return [] 

303 

304 def deconstruct(self): 

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

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

307 del kwargs["max_length"] 

308 kwargs["upload_to"] = self.upload_to 

309 storage = getattr(self, "_storage_callable", self.storage) 

310 if storage is not default_storage: 

311 kwargs["storage"] = storage 

312 return name, path, args, kwargs 

313 

314 def get_internal_type(self): 

315 return "FileField" 

316 

317 def get_prep_value(self, value): 

318 value = super().get_prep_value(value) 

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

320 # database insertion. 

321 if value is None: 

322 return None 

323 return str(value) 

324 

325 def pre_save(self, model_instance, add): 

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

327 if file.name is None and file._file is not None: 

328 exc = FieldError( 

329 f"File for {self.name} must have " 

330 "the name attribute specified to be saved." 

331 ) 

332 if PY311 and isinstance(file._file, ContentFile): 

333 exc.add_note("Pass a 'name' argument to ContentFile.") 

334 raise exc 

335 

336 if file and not file._committed: 

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

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

339 return file 

340 

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

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

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

344 

345 def generate_filename(self, instance, filename): 

346 """ 

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

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

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

350 (with forward slashes). 

351 """ 

352 if callable(self.upload_to): 

353 filename = self.upload_to(instance, filename) 

354 else: 

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

356 filename = posixpath.join(dirname, filename) 

357 filename = validate_file_name(filename, allow_relative_path=True) 

358 return self.storage.generate_filename(filename) 

359 

360 def save_form_data(self, instance, data): 

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

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

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

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

365 if data is not None: 

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

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

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

369 

370 def formfield(self, **kwargs): 

371 return super().formfield( 

372 **{ 

373 "form_class": forms.FileField, 

374 "max_length": self.max_length, 

375 **kwargs, 

376 } 

377 ) 

378 

379 

380class ImageFileDescriptor(FileDescriptor): 

381 """ 

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

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

384 """ 

385 

386 def __set__(self, instance, value): 

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

388 super().__set__(instance, value) 

389 

390 # To prevent recalculating image dimensions when we are instantiating 

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

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

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

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

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

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

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

398 # update right here. 

399 if previous_file is not None: 

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

401 

402 

403class ImageFieldFile(ImageFile, FieldFile): 

404 def _set_instance_attribute(self, name, content): 

405 setattr(self.instance, self.field.attname, content) 

406 # Update the name in case generate_filename() or storage.save() changed 

407 # it, but bypass the descriptor to avoid re-reading the file. 

408 self.instance.__dict__[self.field.attname] = self.name 

409 

410 def delete(self, save=True): 

411 # Clear the image dimensions cache 

412 if hasattr(self, "_dimensions_cache"): 

413 del self._dimensions_cache 

414 super().delete(save) 

415 

416 

417class ImageField(FileField): 

418 attr_class = ImageFieldFile 

419 descriptor_class = ImageFileDescriptor 

420 description = _("Image") 

421 

422 def __init__( 

423 self, 

424 verbose_name=None, 

425 name=None, 

426 width_field=None, 

427 height_field=None, 

428 **kwargs, 

429 ): 

430 self.width_field, self.height_field = width_field, height_field 

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

432 

433 def check(self, **kwargs): 

434 return [ 

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

436 *self._check_image_library_installed(), 

437 ] 

438 

439 def _check_image_library_installed(self): 

440 try: 

441 from PIL import Image # NOQA 

442 except ImportError: 

443 return [ 

444 checks.Error( 

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

446 hint=( 

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

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

449 ), 

450 obj=self, 

451 id="fields.E210", 

452 ) 

453 ] 

454 else: 

455 return [] 

456 

457 def deconstruct(self): 

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

459 if self.width_field: 

460 kwargs["width_field"] = self.width_field 

461 if self.height_field: 

462 kwargs["height_field"] = self.height_field 

463 return name, path, args, kwargs 

464 

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

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

467 # Attach update_dimension_fields so that dimension fields declared 

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

469 # Model.__init__, see bug #11196. 

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

471 # with width_field/height_field. 

472 if not cls._meta.abstract and (self.width_field or self.height_field): 

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

474 

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

476 """ 

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

478 

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

480 dimensions after instantiating a model instance. However, dimensions 

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

482 avoids unnecessary recalculation when loading an object from the 

483 database. 

484 

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

486 ImageFileDescriptor.__set__ calls this method. 

487 """ 

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

489 # the field is deferred. 

490 has_dimension_fields = self.width_field or self.height_field 

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

492 return 

493 

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

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

496 # (ImageFieldFile in this case). 

497 file = getattr(instance, self.attname) 

498 

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

500 if not file and not force: 

501 return 

502 

503 dimension_fields_filled = not ( 

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

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

506 ) 

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

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

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

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

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

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

513 # were called from ImageFileDescriptor.__set__. 

514 if dimension_fields_filled and not force: 

515 return 

516 

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

518 if file: 

519 width = file.width 

520 height = file.height 

521 else: 

522 # No file, so clear dimensions fields. 

523 width = None 

524 height = None 

525 

526 # Update the width and height fields. 

527 if self.width_field: 

528 setattr(instance, self.width_field, width) 

529 if self.height_field: 

530 setattr(instance, self.height_field, height) 

531 

532 def formfield(self, **kwargs): 

533 return super().formfield( 

534 **{ 

535 "form_class": forms.ImageField, 

536 **kwargs, 

537 } 

538 )