~ubuntu-branches/ubuntu/quantal/python-django/quantal

« back to all changes in this revision

Viewing changes to django/db/models/fields/files.py

  • Committer: Bazaar Package Importer
  • Author(s): Chris Lamb
  • Date: 2009-07-29 11:26:28 UTC
  • mfrom: (1.1.8 upstream) (4.1.5 sid)
  • Revision ID: james.westby@ubuntu.com-20090729112628-pg09ino8sz0sj21t
Tags: 1.1-1
* New upstream release.
* Merge from experimental:
  - Ship FastCGI initscript and /etc/default file in python-django's examples
    directory (Closes: #538863)
  - Drop "05_10539-sphinx06-compatibility.diff"; it has been applied
    upstream.
  - Bump Standards-Version to 3.8.2.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import copy
1
2
import datetime
2
3
import os
3
4
 
16
17
 
17
18
class FieldFile(File):
18
19
    def __init__(self, instance, field, name):
 
20
        super(FieldFile, self).__init__(None, name)
19
21
        self.instance = instance
20
22
        self.field = field
21
23
        self.storage = field.storage
22
 
        self._name = name or u''
23
 
        self._closed = False
 
24
        self._committed = True
24
25
 
25
26
    def __eq__(self, other):
26
27
        # Older code may be expecting FileField values to be simple strings.
29
30
            return self.name == other.name
30
31
        return self.name == other
31
32
 
 
33
    def __ne__(self, other):
 
34
        return not self.__eq__(other)
 
35
 
 
36
    def __hash__(self):
 
37
        # Required because we defined a custom __eq__.
 
38
        return hash(self.name)
 
39
 
32
40
    # The standard File contains most of the necessary properties, but
33
41
    # FieldFiles can be instantiated without a name, so that needs to
34
42
    # be checked for here.
39
47
 
40
48
    def _get_file(self):
41
49
        self._require_file()
42
 
        if not hasattr(self, '_file'):
 
50
        if not hasattr(self, '_file') or self._file is None:
43
51
            self._file = self.storage.open(self.name, 'rb')
44
52
        return self._file
45
 
    file = property(_get_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)
46
61
 
47
62
    def _get_path(self):
48
63
        self._require_file()
56
71
 
57
72
    def _get_size(self):
58
73
        self._require_file()
 
74
        if not self._committed:
 
75
            return len(self.file)
59
76
        return self.storage.size(self.name)
60
77
    size = property(_get_size)
61
78
 
62
79
    def open(self, mode='rb'):
63
80
        self._require_file()
64
 
        return super(FieldFile, self).open(mode)
 
81
        self.file.open(mode)
65
82
    # open() doesn't alter the file's contents, but it does reset the pointer
66
83
    open.alters_data = True
67
84
 
71
88
 
72
89
    def save(self, name, content, save=True):
73
90
        name = self.field.generate_filename(self.instance, name)
74
 
        self._name = self.storage.save(name, content)
 
91
        self.name = self.storage.save(name, content)
75
92
        setattr(self.instance, self.field.name, self.name)
76
93
 
77
94
        # Update the filesize cache
78
95
        self._size = len(content)
 
96
        self._committed = True
79
97
 
80
98
        # Save the object because it has changed, unless save is False
81
99
        if save:
87
105
        # presence of self._file
88
106
        if hasattr(self, '_file'):
89
107
            self.close()
90
 
            del self._file
91
 
            
 
108
            del self.file
 
109
 
92
110
        self.storage.delete(self.name)
93
111
 
94
 
        self._name = None
 
112
        self.name = None
95
113
        setattr(self.instance, self.field.name, self.name)
96
114
 
97
115
        # Delete the filesize cache
98
116
        if hasattr(self, '_size'):
99
117
            del self._size
 
118
        self._committed = False
100
119
 
101
120
        if save:
102
121
            self.instance.save()
103
122
    delete.alters_data = True
104
123
 
 
124
    def _get_closed(self):
 
125
        file = getattr(self, '_file', None)
 
126
        return file is None or file.closed
 
127
    closed = property(_get_closed)
 
128
 
 
129
    def close(self):
 
130
        file = getattr(self, '_file', None)
 
131
        if file is not None:
 
132
            file.close()
 
133
 
105
134
    def __getstate__(self):
106
135
        # FieldFile needs access to its associated model field and an instance
107
136
        # it's attached to in order to work properly, but the only necessary
108
137
        # data to be pickled is the file's name itself. Everything else will
109
138
        # be restored later, by FileDescriptor below.
110
 
        return {'_name': self.name, '_closed': False}
 
139
        return {'name': self.name, 'closed': False, '_committed': True, '_file': None}
111
140
 
112
141
class FileDescriptor(object):
 
142
    """
 
143
    The descriptor for the file attribute on the model instance. Returns a
 
144
    FieldFile when accessed so you can do stuff like::
 
145
 
 
146
        >>> instance.file.size
 
147
 
 
148
    Assigns a file object on assignment so you can do::
 
149
 
 
150
        >>> instance.file = File(...)
 
151
 
 
152
    """
113
153
    def __init__(self, field):
114
154
        self.field = field
115
155
 
116
156
    def __get__(self, instance=None, owner=None):
117
157
        if instance is None:
118
 
            raise AttributeError, "%s can only be accessed from %s instances." % (self.field.name(self.owner.__name__))
 
158
            raise AttributeError(
 
159
                "The '%s' attribute can only be accessed from %s instances."
 
160
                % (self.field.name, owner.__name__))
 
161
 
 
162
        # This is slightly complicated, so worth an explanation.
 
163
        # instance.file`needs to ultimately return some instance of `File`,
 
164
        # probably a subclass. Additionally, this returned object needs to have
 
165
        # the FieldFile API so that users can easily do things like
 
166
        # instance.file.path and have that delegated to the file storage engine.
 
167
        # Easy enough if we're strict about assignment in __set__, but if you
 
168
        # peek below you can see that we're not. So depending on the current
 
169
        # value of the field we have to dynamically construct some sort of
 
170
        # "thing" to return.
 
171
 
 
172
        # The instance dict contains whatever was originally assigned
 
173
        # in __set__.
119
174
        file = instance.__dict__[self.field.name]
120
 
        if not isinstance(file, FieldFile):
121
 
            # Create a new instance of FieldFile, based on a given file name
122
 
            instance.__dict__[self.field.name] = self.field.attr_class(instance, self.field, file)
123
 
        elif not hasattr(file, 'field'):
124
 
            # The FieldFile was pickled, so some attributes need to be reset.
 
175
 
 
176
        # If this value is a string (instance.file = "path/to/file") or None
 
177
        # then we simply wrap it with the appropriate attribute class according
 
178
        # to the file field. [This is FieldFile for FileFields and
 
179
        # ImageFieldFile for ImageFields; it's also conceivable that user
 
180
        # subclasses might also want to subclass the attribute class]. This
 
181
        # object understands how to convert a path to a file, and also how to
 
182
        # handle None.
 
183
        if isinstance(file, basestring) or file is None:
 
184
            attr = self.field.attr_class(instance, self.field, file)
 
185
            instance.__dict__[self.field.name] = attr
 
186
 
 
187
        # Other types of files may be assigned as well, but they need to have
 
188
        # the FieldFile interface added to the. Thus, we wrap any other type of
 
189
        # File inside a FieldFile (well, the field's attr_class, which is
 
190
        # usually FieldFile).
 
191
        elif isinstance(file, File) and not isinstance(file, FieldFile):
 
192
            file_copy = self.field.attr_class(instance, self.field, file.name)
 
193
            file_copy.file = file
 
194
            file_copy._committed = False
 
195
            instance.__dict__[self.field.name] = file_copy
 
196
 
 
197
        # Finally, because of the (some would say boneheaded) way pickle works,
 
198
        # the underlying FieldFile might not actually itself have an associated
 
199
        # file. So we need to reset the details of the FieldFile in those cases.
 
200
        elif isinstance(file, FieldFile) and not hasattr(file, 'field'):
125
201
            file.instance = instance
126
202
            file.field = self.field
127
203
            file.storage = self.field.storage
 
204
 
 
205
        # That was fun, wasn't it?
128
206
        return instance.__dict__[self.field.name]
129
207
 
130
208
    def __set__(self, instance, value):
131
209
        instance.__dict__[self.field.name] = value
132
210
 
133
211
class FileField(Field):
 
212
    # The class to wrap instance attributes in. Accessing the file object off
 
213
    # the instance will always return an instance of attr_class.
134
214
    attr_class = FieldFile
135
215
 
 
216
    # The descriptor to use for accessing the attribute off of the class.
 
217
    descriptor_class = FileDescriptor
 
218
 
136
219
    def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **kwargs):
137
220
        for arg in ('primary_key', 'unique'):
138
221
            if arg in kwargs:
161
244
            return None
162
245
        return unicode(value)
163
246
 
 
247
    def pre_save(self, model_instance, add):
 
248
        "Returns field's value just before saving."
 
249
        file = super(FileField, self).pre_save(model_instance, add)
 
250
        if file and not file._committed:
 
251
            # Commit the file to storage prior to saving the model
 
252
            file.save(file.name, file, save=False)
 
253
        return file
 
254
 
164
255
    def contribute_to_class(self, cls, name):
165
256
        super(FileField, self).contribute_to_class(cls, name)
166
 
        setattr(cls, self.name, FileDescriptor(self))
 
257
        setattr(cls, self.name, self.descriptor_class(self))
167
258
        signals.post_delete.connect(self.delete_file, sender=cls)
168
259
 
169
260
    def delete_file(self, instance, sender, **kwargs):
188
279
        return os.path.join(self.get_directory_name(), self.get_filename(filename))
189
280
 
190
281
    def save_form_data(self, instance, data):
191
 
        if data and isinstance(data, UploadedFile):
192
 
            getattr(instance, self.name).save(data.name, data, save=False)
 
282
        if data:
 
283
            setattr(instance, self.name, data)
193
284
 
194
285
    def formfield(self, **kwargs):
195
 
        defaults = {'form_class': forms.FileField}
 
286
        defaults = {'form_class': forms.FileField, 'max_length': self.max_length}
196
287
        # If a file has been provided previously, then the form doesn't require
197
288
        # that a new file is provided this time.
198
289
        # The code to mark the form field as not required is used by
203
294
        defaults.update(kwargs)
204
295
        return super(FileField, self).formfield(**defaults)
205
296
 
 
297
class ImageFileDescriptor(FileDescriptor):
 
298
    """
 
299
    Just like the FileDescriptor, but for ImageFields. The only difference is
 
300
    assigning the width/height to the width_field/height_field, if appropriate.
 
301
    """
 
302
    def __set__(self, instance, value):
 
303
        previous_file = instance.__dict__.get(self.field.name)
 
304
        super(ImageFileDescriptor, self).__set__(instance, value)
 
305
 
 
306
        # To prevent recalculating image dimensions when we are instantiating
 
307
        # an object from the database (bug #11084), only update dimensions if
 
308
        # the field had a value before this assignment.  Since the default
 
309
        # value for FileField subclasses is an instance of field.attr_class,
 
310
        # previous_file will only be None when we are called from
 
311
        # Model.__init__().  The ImageField.update_dimension_fields method
 
312
        # hooked up to the post_init signal handles the Model.__init__() cases.
 
313
        # Assignment happening outside of Model.__init__() will trigger the
 
314
        # update right here.
 
315
        if previous_file is not None:
 
316
            self.field.update_dimension_fields(instance, force=True)
 
317
 
206
318
class ImageFieldFile(ImageFile, FieldFile):
207
 
    def save(self, name, content, save=True):
208
 
        # Repopulate the image dimension cache.
209
 
        self._dimensions_cache = get_image_dimensions(content)
210
 
 
211
 
        # Update width/height fields, if needed
212
 
        if self.field.width_field:
213
 
            setattr(self.instance, self.field.width_field, self.width)
214
 
        if self.field.height_field:
215
 
            setattr(self.instance, self.field.height_field, self.height)
216
 
 
217
 
        super(ImageFieldFile, self).save(name, content, save)
218
 
 
219
319
    def delete(self, save=True):
220
320
        # Clear the image dimensions cache
221
321
        if hasattr(self, '_dimensions_cache'):
224
324
 
225
325
class ImageField(FileField):
226
326
    attr_class = ImageFieldFile
 
327
    descriptor_class = ImageFileDescriptor
227
328
 
228
329
    def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):
229
330
        self.width_field, self.height_field = width_field, height_field
230
331
        FileField.__init__(self, verbose_name, name, **kwargs)
231
332
 
 
333
    def contribute_to_class(self, cls, name):
 
334
        super(ImageField, self).contribute_to_class(cls, name)
 
335
        # Attach update_dimension_fields so that dimension fields declared
 
336
        # after their corresponding image field don't stay cleared by
 
337
        # Model.__init__, see bug #11196.
 
338
        signals.post_init.connect(self.update_dimension_fields, sender=cls)
 
339
 
 
340
    def update_dimension_fields(self, instance, force=False, *args, **kwargs):
 
341
        """
 
342
        Updates field's width and height fields, if defined.
 
343
 
 
344
        This method is hooked up to model's post_init signal to update
 
345
        dimensions after instantiating a model instance.  However, dimensions
 
346
        won't be updated if the dimensions fields are already populated.  This
 
347
        avoids unnecessary recalculation when loading an object from the
 
348
        database.
 
349
 
 
350
        Dimensions can be forced to update with force=True, which is how
 
351
        ImageFileDescriptor.__set__ calls this method.
 
352
        """
 
353
        # Nothing to update if the field doesn't have have dimension fields.
 
354
        has_dimension_fields = self.width_field or self.height_field
 
355
        if not has_dimension_fields:
 
356
            return
 
357
 
 
358
        # getattr will call the ImageFileDescriptor's __get__ method, which
 
359
        # coerces the assigned value into an instance of self.attr_class
 
360
        # (ImageFieldFile in this case).
 
361
        file = getattr(instance, self.attname)
 
362
 
 
363
        # Nothing to update if we have no file and not being forced to update.
 
364
        if not file and not force:
 
365
            return
 
366
 
 
367
        dimension_fields_filled = not(
 
368
            (self.width_field and not getattr(instance, self.width_field))
 
369
            or (self.height_field and not getattr(instance, self.height_field))
 
370
        )
 
371
        # When both dimension fields have values, we are most likely loading
 
372
        # data from the database or updating an image field that already had
 
373
        # an image stored.  In the first case, we don't want to update the
 
374
        # dimension fields because we are already getting their values from the
 
375
        # database.  In the second case, we do want to update the dimensions
 
376
        # fields and will skip this return because force will be True since we
 
377
        # were called from ImageFileDescriptor.__set__.
 
378
        if dimension_fields_filled and not force:
 
379
            return
 
380
 
 
381
        # file should be an instance of ImageFieldFile or should be None.
 
382
        if file:
 
383
            width = file.width
 
384
            height = file.height
 
385
        else:
 
386
            # No file, so clear dimensions fields.
 
387
            width = None
 
388
            height = None
 
389
 
 
390
        # Update the width and height fields.
 
391
        if self.width_field:
 
392
            setattr(instance, self.width_field, width)
 
393
        if self.height_field:
 
394
            setattr(instance, self.height_field, height)
 
395
 
232
396
    def formfield(self, **kwargs):
233
397
        defaults = {'form_class': forms.ImageField}
234
398
        defaults.update(kwargs)