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 )