Code

Ticket #2070: 2070_revision7339_formhandling.diff

File 2070_revision7339_formhandling.diff, 11.4 KB (added by axiak, 6 years ago)

NEW Form handling for uploaded files for revision 7339

Line 
1Index: django/oldforms/__init__.py
2===================================================================
3--- django/oldforms/__init__.py (revision 7340)
4+++ django/oldforms/__init__.py (working copy)
5@@ -680,18 +680,23 @@
6         self.field_name, self.is_required = field_name, is_required
7         self.validator_list = [self.isNonEmptyFile] + validator_list
8 
9-    def isNonEmptyFile(self, field_data, all_data):
10-        try:
11-            content = field_data['content']
12-        except TypeError:
13-            raise validators.CriticalValidationError, ugettext("No file was submitted. Check the encoding type on the form.")
14-        if not content:
15+    def isNonEmptyFile(self, new_data, all_data):
16+        if hasattr(new_data, 'upload_errors'):
17+            upload_errors = new_data.upload_errors()
18+            if upload_errors:
19+                raise validators.CriticalValidationError, upload_errors
20+        if not new_data.file_size:
21             raise validators.CriticalValidationError, ugettext("The submitted file is empty.")
22 
23     def render(self, data):
24         return mark_safe(u'<input type="file" id="%s" class="v%s" name="%s" />' % \
25             (self.get_id(), self.__class__.__name__, self.field_name))
26 
27+    def prepare(self, new_data):
28+        if hasattr(new_data, 'upload_errors'):
29+            upload_errors = new_data.upload_errors()
30+            new_data[self.field_name] = { '_file_upload_error': upload_errors }
31+
32     def html2python(data):
33         if data is None:
34             raise EmptyValue
35Index: django/db/models/base.py
36===================================================================
37--- django/db/models/base.py    (revision 7340)
38+++ django/db/models/base.py    (working copy)
39@@ -13,6 +13,7 @@
40 from django.utils.datastructures import SortedDict
41 from django.utils.functional import curry
42 from django.utils.encoding import smart_str, force_unicode, smart_unicode
43+from django.core.files.filemove import file_move_safe
44 from django.conf import settings
45 from itertools import izip
46 import types
47@@ -384,12 +385,16 @@
48     def _get_FIELD_size(self, field):
49         return os.path.getsize(self._get_FIELD_filename(field))
50 
51-    def _save_FIELD_file(self, field, filename, raw_contents, save=True):
52+    def _save_FIELD_file(self, field, filename, raw_field, save=True):
53         directory = field.get_directory_name()
54         try: # Create the date-based directory if it doesn't exist.
55             os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
56         except OSError: # Directory probably already exists.
57             pass
58+
59+        if filename is None:
60+            filename = raw_field.file_name
61+
62         filename = field.get_filename(filename)
63 
64         # If the filename already exists, keep adding an underscore to the name of
65@@ -406,9 +411,18 @@
66         setattr(self, field.attname, filename)
67 
68         full_filename = self._get_FIELD_filename(field)
69-        fp = open(full_filename, 'wb')
70-        fp.write(raw_contents)
71-        fp.close()
72+        if hasattr(raw_field, 'temporary_file_path'):
73+            raw_field.close()
74+            file_move_safe(raw_field.temporary_file_path(), full_filename)
75+        else:
76+            from django.utils import file_locks
77+            fp = open(full_filename, 'wb')
78+            # exclusive lock
79+            file_locks.lock(fp, file_locks.LOCK_EX)
80+            # Stream it into the file, from where it is.
81+            for chunk in raw_field.chunk(65535):
82+                fp.write(chunk)
83+            fp.close()
84 
85         # Save the width and/or height, if applicable.
86         if isinstance(field, ImageField) and (field.width_field or field.height_field):
87Index: django/db/models/fields/__init__.py
88===================================================================
89--- django/db/models/fields/__init__.py (revision 7340)
90+++ django/db/models/fields/__init__.py (working copy)
91@@ -785,7 +785,8 @@
92         setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self))
93         setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self))
94         setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self))
95-        setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save))
96+        setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_field, save=True: instance._save_FIELD_file(self, filename, raw_field, save))
97+        setattr(cls, 'move_%s_file' % self.name, lambda instance, raw_field, save=True: instance._save_FIELD_file(self, None, raw_field, save))
98         dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls)
99 
100     def delete_file(self, instance):
101@@ -808,9 +809,9 @@
102         if new_data.get(upload_field_name, False):
103             func = getattr(new_object, 'save_%s_file' % self.name)
104             if rel:
105-                func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save)
106+                func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0], save)
107             else:
108-                func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save)
109+                func(new_data[upload_field_name]["filename"], new_data[upload_field_name], save)
110 
111     def get_directory_name(self):
112         return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to))))
113@@ -823,7 +824,7 @@
114     def save_form_data(self, instance, data):
115         from django.newforms.fields import UploadedFile
116         if data and isinstance(data, UploadedFile):
117-            getattr(instance, "save_%s_file" % self.name)(data.filename, data.content, save=False)
118+            getattr(instance, "save_%s_file" % self.name)(data.filename, data.data, save=False)
119 
120     def formfield(self, **kwargs):
121         defaults = {'form_class': forms.FileField}
122Index: django/core/files/filelocks.py
123===================================================================
124--- django/core/files/filelocks.py      (revision 0)
125+++ django/core/files/filelocks.py      (revision 0)
126@@ -0,0 +1,50 @@
127+"""
128+Locking portability by Jonathan Feignberg <jdf@pobox.com> in python cookbook
129+
130+Example Usage::
131+
132+    from django.utils import file_locks
133+
134+    f = open('./file', 'wb')
135+
136+    file_locks.lock(f, file_locks.LOCK_EX)
137+    f.write('Django')
138+    f.close()
139+"""
140+
141+
142+import os
143+
144+__all__ = ['LOCK_EX','LOCK_SH','LOCK_NB','lock','unlock']
145+
146+if os.name == 'nt':
147+       import win32con
148+       import win32file
149+       import pywintypes
150+       LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
151+       LOCK_SH = 0
152+       LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
153+       __overlapped = pywintypes.OVERLAPPED()
154+elif os.name == 'posix':
155+       import fcntl
156+       LOCK_EX = fcntl.LOCK_EX
157+       LOCK_SH = fcntl.LOCK_SH
158+       LOCK_NB = fcntl.LOCK_NB
159+else:
160+       raise RuntimeError("Locking only defined for nt and posix platforms")
161+
162+if os.name == 'nt':
163+       def lock(file, flags):
164+               hfile = win32file._get_osfhandle(file.fileno())
165+               win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped)
166+
167+       def unlock(file):
168+               hfile = win32file._get_osfhandle(file.fileno())
169+               win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped)
170+
171+elif os.name =='posix':
172+       def lock(file, flags):
173+               fcntl.flock(file.fileno(), flags)
174+
175+       def unlock(file):
176+               fcntl.flock(file.fileno(), fcntl.LOCK_UN)
177Index: django/core/files/__init__.py
178===================================================================
179Index: django/core/files/filemove.py
180===================================================================
181--- django/core/files/filemove.py       (revision 0)
182+++ django/core/files/filemove.py       (revision 0)
183@@ -0,0 +1,53 @@
184+import os
185+
186+__all__ = ('file_move_safe',)
187+
188+try:
189+    import shutil
190+    file_move = shutil.move
191+except ImportError:
192+    file_move = os.rename
193+
194+def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False):
195+    """
196+    Moves a file from one location to another in the safest way possible.
197+   
198+    First, it tries using shutils.move, which is OS-dependent but doesn't
199+    break with change of filesystems. Then it tries os.rename, which will
200+    break if it encounters a change in filesystems. Lastly, it streams
201+    it manually from one file to another in python.
202+
203+    Without ``allow_overwrite``, if the destination file exists, the
204+    file will raise an IOError.
205+    """
206+
207+    from django.core.files import filelocks
208+
209+    if old_file_name == new_file_name:
210+        # No file moving takes place.
211+        return
212+
213+    if not allow_overwrite and os.path.exists(new_file_name):
214+        raise IOError, "Django does not allow overwriting files."
215+
216+    try:
217+        file_move(old_file_name, new_file_name)
218+        return
219+    except OSError: # moving to another filesystem
220+        pass
221+
222+    new_file = open(new_file_name, 'wb')
223+    # exclusive lock
224+    filelocks.lock(new_file, filelocks.LOCK_EX)
225+    old_file = open(old_file_name, 'rb')
226+    current_chunk = None
227+
228+    while current_chunk != '':
229+        current_chunk = old_file.read(chunk_size)
230+        new_file.write(current_chunk)
231+
232+    new_file.close()
233+    old_file.close()
234+
235+    os.remove(old_file_name)
236+
237Index: django/newforms/fields.py
238===================================================================
239--- django/newforms/fields.py   (revision 7340)
240+++ django/newforms/fields.py   (working copy)
241@@ -416,9 +416,9 @@
242 
243 class UploadedFile(StrAndUnicode):
244     "A wrapper for files uploaded in a FileField"
245-    def __init__(self, filename, content):
246+    def __init__(self, filename, data):
247         self.filename = filename
248-        self.content = content
249+        self.data = data
250 
251     def __unicode__(self):
252         """
253@@ -445,12 +445,12 @@
254         elif not data and initial:
255             return initial
256         try:
257-            f = UploadedFile(data['filename'], data['content'])
258+            f = UploadedFile(data['filename'], data)
259         except TypeError:
260             raise ValidationError(self.error_messages['invalid'])
261         except KeyError:
262             raise ValidationError(self.error_messages['missing'])
263-        if not f.content:
264+        if not f.data.file_size:
265             raise ValidationError(self.error_messages['empty'])
266         return f
267 
268@@ -470,15 +470,26 @@
269         elif not data and initial:
270             return initial
271         from PIL import Image
272-        from cStringIO import StringIO
273+
274+        # We need to get the file, it either has a path
275+        # or we have to read it all into memory...
276+        if hasattr(data, 'temporary_file_path'):
277+            file = data.temporary_file_path()
278+        else:
279+            try:
280+                from cStringIO import StringIO
281+            except ImportError:
282+                from StringIO import StringIO
283+            file = StringIO(data.read())
284+
285         try:
286             # load() is the only method that can spot a truncated JPEG,
287             #  but it cannot be called sanely after verify()
288-            trial_image = Image.open(StringIO(f.content))
289+            trial_image = Image.open(file)
290             trial_image.load()
291             # verify() is the only method that can spot a corrupt PNG,
292             #  but it must be called immediately after the constructor
293-            trial_image = Image.open(StringIO(f.content))
294+            trial_image = Image.open(file)
295             trial_image.verify()
296         except Exception: # Python Imaging Library doesn't recognize it as an image
297             raise ValidationError(self.error_messages['invalid_image'])