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
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
1import datetime
2import posixpath
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 _
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
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
32 def __hash__(self):
33 return hash(self.name)
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.
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 )
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
51 def _set_file(self, file):
52 self._file = file
54 def _del_file(self):
55 del self._file
57 file = property(_get_file, _set_file, _del_file)
59 @property
60 def path(self):
61 self._require_file()
62 return self.storage.path(self.name)
64 @property
65 def url(self):
66 self._require_file()
67 return self.storage.url(self.name)
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)
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
84 # open() doesn't alter the file's contents, but it does reset the pointer
85 open.alters_data = True
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.
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
97 # Save the object because it has changed, unless save is False
98 if save:
99 self.instance.save()
101 save.alters_data = True
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
112 self.storage.delete(self.name)
114 self.name = None
115 setattr(self.instance, self.field.attname, self.name)
116 self._committed = False
118 if save:
119 self.instance.save()
121 delete.alters_data = True
123 @property
124 def closed(self):
125 file = getattr(self, "_file", None)
126 return file is None or file.closed
128 def close(self):
129 file = getattr(self, "_file", None)
130 if file is not None:
131 file.close()
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 }
146 def __setstate__(self, state):
147 self.__dict__.update(state)
148 self.storage = self.field.storage
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::
156 >>> from myapp.models import MyModel
157 >>> instance = MyModel.objects.get(pk=1)
158 >>> instance.file.size
160 Assign a file object on assignment so you can do::
162 >>> with open('/path/to/hello.world') as f:
163 ... instance.file = File(f)
164 """
166 def __get__(self, instance, cls=None):
167 if instance is None:
168 return self
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.
180 # The instance dict contains whatever was originally assigned
181 # in __set__.
182 file = super().__get__(instance, cls)
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
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
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
213 # Make sure that the instance is correct.
214 elif isinstance(file, FieldFile) and instance is not file.instance:
215 file.instance = instance
217 # That was fun, wasn't it?
218 return instance.__dict__[self.field.attname]
220 def __set__(self, instance, value):
221 instance.__dict__[self.field.attname] = value
224class FileField(Field):
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
230 # The descriptor to use for accessing the attribute off of the class.
231 descriptor_class = FileDescriptor
233 description = _("File")
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
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
256 kwargs.setdefault("max_length", 100)
257 super().__init__(verbose_name, name, **kwargs)
259 def check(self, **kwargs):
260 return [
261 *super().check(**kwargs),
262 *self._check_primary_key(),
263 *self._check_upload_to(),
264 ]
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 []
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 []
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
302 def get_internal_type(self):
303 return "FileField"
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)
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
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))
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)
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 "")
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 )
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 """
365 def __set__(self, instance, value):
366 previous_file = instance.__dict__.get(self.field.attname)
367 super().__set__(instance, value)
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)
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)
390class ImageField(FileField):
391 attr_class = ImageFieldFile
392 descriptor_class = ImageFileDescriptor
393 description = _("Image")
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)
406 def check(self, **kwargs):
407 return [
408 *super().check(**kwargs),
409 *self._check_image_library_installed(),
410 ]
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 []
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
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)
447 def update_dimension_fields(self, instance, force=False, *args, **kwargs):
448 """
449 Update field's width and height fields, if defined.
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.
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
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)
471 # Nothing to update if we have no file and not being forced to update.
472 if not file and not force:
473 return
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
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
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)
504 def formfield(self, **kwargs):
505 return super().formfield(
506 **{
507 "form_class": forms.ImageField,
508 **kwargs,
509 }
510 )