Ticket #5361: filestorage.diff
File filestorage.diff, 28.1 KB (added by , 17 years ago) |
---|
-
django/core/filestorage/__init__.py
1 from StringIO import StringIO 2 3 class Backend(object): 4 def get_available_filename(self, filename): 5 # If the filename already exists, keep adding an underscore to the name 6 # of the file until the filename doesn't exist. 7 while self.file_exists(filename): 8 try: 9 dot_index = filename.rindex('.') 10 except ValueError: # filename has no dot 11 filename += '_' 12 else: 13 filename = filename[:dot_index] + '_' + filename[dot_index:] 14 return filename 15 16 def get_filename(self, filename): 17 return filename 18 19 class RemoteFile(StringIO): 20 """Sends files to a remote backend automatically, when necessary.""" 21 22 def __init__(self, data, mode, writer): 23 self._mode = mode 24 self._write_to_backend = writer 25 self._is_dirty = False 26 StringIO.__init__(self, data) 27 28 def write(self, data): 29 if 'w' not in self._mode: 30 raise AttributeError, "File was opened for read-only access." 31 StringIO.write(self, data) 32 self._is_dirty = True 33 34 def close(self): 35 if self._is_dirty: 36 self._write_to_backend(self.getvalue()) 37 StringIO.close(self) 38 -
django/core/filestorage/filesystem.py
1 import datetime 2 import os 3 4 from django.conf import settings 5 from django.utils.encoding import force_unicode, smart_str 6 7 from django.core.filestorage import Backend 8 9 class FileSystemBackend(Backend): 10 """Standard filesystem storage""" 11 12 def __init__(self, location='', media_root=None, media_url=None): 13 self.location = location 14 if media_root != None and media_url != None: 15 # Both were provided, so use them 16 pass 17 elif media_root is None and media_url is None: 18 # Neither were provided, so use global settings 19 from django.conf import settings 20 try: 21 media_root = settings.MEDIA_ROOT 22 media_url = settings.MEDIA_URL 23 except AttributeError: 24 raise ImproperlyConfigured, "Media settings not defined." 25 else: 26 # One or the other were provided, but not both 27 raise ImproperlyConfigured, "Both media_root and media_url must be provided." 28 self.media_root = media_root 29 self.media_url = media_url 30 31 def _get_directory_name(self): 32 return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.location)))) 33 34 def _get_absolute_path(self, filename): 35 return os.path.normpath(os.path.join(self.media_root, filename)) 36 37 # The following methods define the Backend API 38 39 def get_available_filename(self, filename): 40 from django.utils.text import get_valid_filename 41 f = os.path.join(self._get_directory_name(), get_valid_filename(os.path.basename(filename))) 42 return Backend.get_available_filename(self, os.path.normpath(f)) 43 44 def get_filesize(self, filename): 45 return os.path.getsize(self._get_absolute_path(filename)) 46 47 def get_absolute_url(self, filename): 48 import urlparse 49 return urlparse.urljoin(self.media_url, filename).replace('\\', '/') 50 51 def open(self, filename, mode='rb'): 52 return open(self._get_absolute_path(filename), mode) 53 54 def file_exists(self, filename): 55 return os.path.exists(self._get_absolute_path(filename)) 56 57 def save_file(self, filename, raw_contents): 58 directory = self._get_directory_name() 59 try: # Create the date-based directory if it doesn't exist. 60 os.makedirs(os.path.join(self.media_root, directory)) 61 except OSError: # Directory probably already exists. 62 pass 63 filename = self.get_available_filename(filename) 64 65 # Write the file to disk. 66 fp = open(self._get_absolute_path(filename), 'wb') 67 fp.write(raw_contents) 68 fp.close() 69 70 return filename 71 72 def delete_file(self, filename): 73 file_name = self._get_absolute_path(filename) 74 # If the file exists, delete it from the filesystem. 75 if os.path.exists(file_name): 76 os.remove(file_name) -
django/core/filestorage/__init__.py
1 from StringIO import StringIO 2 3 class Backend(object): 4 def get_available_filename(self, filename): 5 # If the filename already exists, keep adding an underscore to the name 6 # of the file until the filename doesn't exist. 7 while self.file_exists(filename): 8 try: 9 dot_index = filename.rindex('.') 10 except ValueError: # filename has no dot 11 filename += '_' 12 else: 13 filename = filename[:dot_index] + '_' + filename[dot_index:] 14 return filename 15 16 def get_filename(self, filename): 17 return filename 18 19 class RemoteFile(StringIO): 20 """Sends files to a remote backend automatically, when necessary.""" 21 22 def __init__(self, data, mode, writer): 23 self._mode = mode 24 self._write_to_backend = writer 25 self._is_dirty = False 26 StringIO.__init__(self, data) 27 28 def write(self, data): 29 if 'w' not in self._mode: 30 raise AttributeError, "File was opened for read-only access." 31 StringIO.write(self, data) 32 self._is_dirty = True 33 34 def close(self): 35 if self._is_dirty: 36 self._write_to_backend(self.getvalue()) 37 StringIO.close(self) 38 -
django/core/filestorage/filesystem.py
1 import datetime 2 import os 3 4 from django.conf import settings 5 from django.utils.encoding import force_unicode, smart_str 6 7 from django.core.filestorage import Backend 8 9 class FileSystemBackend(Backend): 10 """Standard filesystem storage""" 11 12 def __init__(self, location='', media_root=None, media_url=None): 13 self.location = location 14 if media_root != None and media_url != None: 15 # Both were provided, so use them 16 pass 17 elif media_root is None and media_url is None: 18 # Neither were provided, so use global settings 19 from django.conf import settings 20 try: 21 media_root = settings.MEDIA_ROOT 22 media_url = settings.MEDIA_URL 23 except AttributeError: 24 raise ImproperlyConfigured, "Media settings not defined." 25 else: 26 # One or the other were provided, but not both 27 raise ImproperlyConfigured, "Both media_root and media_url must be provided." 28 self.media_root = media_root 29 self.media_url = media_url 30 31 def _get_directory_name(self): 32 return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.location)))) 33 34 def _get_absolute_path(self, filename): 35 return os.path.normpath(os.path.join(self.media_root, filename)) 36 37 # The following methods define the Backend API 38 39 def get_available_filename(self, filename): 40 from django.utils.text import get_valid_filename 41 f = os.path.join(self._get_directory_name(), get_valid_filename(os.path.basename(filename))) 42 return Backend.get_available_filename(self, os.path.normpath(f)) 43 44 def get_filesize(self, filename): 45 return os.path.getsize(self._get_absolute_path(filename)) 46 47 def get_absolute_url(self, filename): 48 import urlparse 49 return urlparse.urljoin(self.media_url, filename).replace('\\', '/') 50 51 def open(self, filename, mode='rb'): 52 return open(self._get_absolute_path(filename), mode) 53 54 def file_exists(self, filename): 55 return os.path.exists(self._get_absolute_path(filename)) 56 57 def save_file(self, filename, raw_contents): 58 directory = self._get_directory_name() 59 try: # Create the date-based directory if it doesn't exist. 60 os.makedirs(os.path.join(self.media_root, directory)) 61 except OSError: # Directory probably already exists. 62 pass 63 filename = self.get_available_filename(filename) 64 65 # Write the file to disk. 66 fp = open(self._get_absolute_path(filename), 'wb') 67 fp.write(raw_contents) 68 fp.close() 69 70 return filename 71 72 def delete_file(self, filename): 73 file_name = self._get_absolute_path(filename) 74 # If the file exists, delete it from the filesystem. 75 if os.path.exists(file_name): 76 os.remove(file_name) -
django/db/models/base.py
18 18 import types 19 19 import sys 20 20 import os 21 from warnings import warn 21 22 22 23 class ModelBase(type): 23 24 "Metaclass for all models" … … 357 358 return getattr(self, cachename) 358 359 359 360 def _get_FIELD_filename(self, field): 360 if getattr(self, field.attname): # value is not blank 361 return os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname)) 362 return '' 361 warn("Use instance.%s.open() if you need access to the file." % field.attname, DeprecationWarning) 362 return field.backend._get_absolute_path(self.__dict__[field.attname]) 363 363 364 364 def _get_FIELD_url(self, field): 365 if getattr(self, field.attname): # value is not blank 366 import urlparse 367 return urlparse.urljoin(settings.MEDIA_URL, getattr(self, field.attname)).replace('\\', '/') 368 return '' 365 warn("Use instance.%s.get_absolute_url()." % field.attname, DeprecationWarning) 366 return getattr(self, field.attname).get_absolute_url() 369 367 370 368 def _get_FIELD_size(self, field): 371 return os.path.getsize(self._get_FIELD_filename(field)) 369 warn("Use instance.%s.get_filesize()." % field.attname, DeprecationWarning) 370 return getattr(self, field.attname).get_filesize() 372 371 373 372 def _save_FIELD_file(self, field, filename, raw_contents, save=True): 374 directory = field.get_directory_name() 375 try: # Create the date-based directory if it doesn't exist. 376 os.makedirs(os.path.join(settings.MEDIA_ROOT, directory)) 377 except OSError: # Directory probably already exists. 378 pass 379 filename = field.get_filename(filename) 373 warn("Use instance.%s.save_file()." % field.attname, DeprecationWarning) 374 return getattr(self, field.attname).save_file(filename, raw_contents, save) 380 375 381 # If the filename already exists, keep adding an underscore to the name of382 # the file until the filename doesn't exist.383 while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)):384 try:385 dot_index = filename.rindex('.')386 except ValueError: # filename has no dot387 filename += '_'388 else:389 filename = filename[:dot_index] + '_' + filename[dot_index:]390 391 # Write the file to disk.392 setattr(self, field.attname, filename)393 394 full_filename = self._get_FIELD_filename(field)395 fp = open(full_filename, 'wb')396 fp.write(raw_contents)397 fp.close()398 399 # Save the width and/or height, if applicable.400 if isinstance(field, ImageField) and (field.width_field or field.height_field):401 from django.utils.images import get_image_dimensions402 width, height = get_image_dimensions(full_filename)403 if field.width_field:404 setattr(self, field.width_field, width)405 if field.height_field:406 setattr(self, field.height_field, height)407 408 # Save the object because it has changed unless save is False409 if save:410 self.save()411 412 _save_FIELD_file.alters_data = True413 414 376 def _get_FIELD_width(self, field): 415 return self._get_image_dimensions(field)[0] 377 warn("Use instance.%s.get_width()." % field.attname, DeprecationWarning) 378 return getattr(self, field.attname).get_width() 416 379 417 380 def _get_FIELD_height(self, field): 418 return self._get_image_dimensions(field)[1] 381 warn("Use instance.%s.get_height()." % field.attname, DeprecationWarning) 382 return getattr(self, field.attname).get_height() 419 383 420 def _get_image_dimensions(self, field):421 cachename = "__%s_dimensions_cache" % field.name422 if not hasattr(self, cachename):423 from django.utils.images import get_image_dimensions424 filename = self._get_FIELD_filename(field)425 setattr(self, cachename, get_image_dimensions(filename))426 return getattr(self, cachename)427 428 384 ############################################ 429 385 # HELPER FUNCTIONS (CURRIED MODEL METHODS) # 430 386 ############################################ -
django/db/models/fields/__init__.py
694 694 defaults.update(kwargs) 695 695 return super(EmailField, self).formfield(**defaults) 696 696 697 class File(object): 698 def __init__(self, obj, field, filename): 699 self.obj = obj 700 self.field = field 701 self.backend = field.backend 702 self.filename = filename 703 704 def __str__(self): 705 return self.backend.get_filename(self.filename) 706 707 def get_absolute_url(self): 708 return self.backend.get_absolute_url(self.filename) 709 710 def get_filesize(self): 711 if not hasattr(self, '_filesize'): 712 self._filesize = self.backend.get_filesize(self.filename)) 713 return self._filesize 714 715 def open(self, mode='rb'): 716 return self.backend.open(self.filename, mode) 717 718 def save_file(self, filename, raw_contents, save=True): 719 self.filename = self.backend.save_file(filename, raw_contents) 720 721 # Save the object because it has changed, unless save is False 722 if save: 723 self.obj.save() 724 725 class FileProxy(object): 726 def __init__(self, field): 727 self.field = field 728 self.cache_name = self.field.get_cache_name() 729 730 def __get__(self, instance=None, owner=None): 731 if instance is None: 732 raise AttributeError, "%s can only be accessed from %s instances." % (self.field.attname, self.owner.__name__) 733 return getattr(instance, self.cache_name) 734 735 def __set__(self, instance, value): 736 if hasattr(instance, self.cache_name): 737 raise AttributeError, "%s can not be set in this manner." % self.field.attname 738 instance.__dict__[self.field.attname] = value 739 attr = self.field.attr_class(instance, self.field, value) 740 setattr(instance, self.cache_name, attr) 741 697 742 class FileField(Field): 698 def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs): 699 self.upload_to = upload_to 743 attr_class = File 744 745 def __init__(self, verbose_name=None, name=None, upload_to='', backend=None, **kwargs): 746 if backend is None: 747 from django.db.models.fields.backends.filesystem import FileSystemBackend 748 backend = FileSystemBackend(location=upload_to) 749 self.backend = self.upload_to = backend 700 750 Field.__init__(self, verbose_name, name, **kwargs) 701 751 702 752 def get_db_prep_save(self, value): … … 704 754 # Need to convert UploadedFile objects provided via a form to unicode for database insertion 705 755 if value is None: 706 756 return None 707 return unicode(value )757 return unicode(value.filename) 708 758 709 759 def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): 710 760 field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow) … … 744 794 745 795 def contribute_to_class(self, cls, name): 746 796 super(FileField, self).contribute_to_class(cls, name) 797 setattr(cls, self.attname, FileProxy(self)) 747 798 setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self)) 748 799 setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self)) 749 800 setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self)) 750 801 setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save)) 751 802 dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls) 752 803 753 def delete_file(self, instance): 754 if getattr(instance, self.attname): 755 file_name = getattr(instance, 'get_%s_filename' % self.name)() 756 # If the file exists and no other object of this type references it, 757 # delete it from the filesystem. 758 if os.path.exists(file_name) and \ 759 not instance.__class__._default_manager.filter(**{'%s__exact' % self.name: getattr(instance, self.attname)}): 760 os.remove(file_name) 804 def delete_file(self, instance, sender): 805 filename = getattr(instance, self.attname).filename 806 # If no other object of this type references the file, 807 # delete it from the backend. 808 if not sender._default_manager.filter(**{self.name: filename}): 809 self.backend.delete_file(filename) 761 810 762 811 def get_manipulator_field_objs(self): 763 812 return [oldforms.FileUploadField, oldforms.HiddenField] … … 768 817 def save_file(self, new_data, new_object, original_object, change, rel, save=True): 769 818 upload_field_name = self.get_manipulator_field_names('')[0] 770 819 if new_data.get(upload_field_name, False): 771 func = getattr(new_object, 'save_%s_file' % self.name)772 820 if rel: 773 f unc(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save)821 field = new_data[upload_field_name][0] 774 822 else: 775 func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save) 823 field = new_data[upload_field_name] 824 getattr(new_object, self.attname).save_file(field["filename"], field["content"], save) 776 825 777 def get_directory_name(self):778 return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to))))779 780 def get_filename(self, filename):781 from django.utils.text import get_valid_filename782 f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename)))783 return os.path.normpath(f)784 785 826 def save_form_data(self, instance, data): 786 827 if data: 787 getattr(instance, "save_%s_file" % self.name)(data.filename, data.content, save=False)788 828 getattr(instance, self.attnamename).save_file(data.filename, data.content, save=False) 829 789 830 def formfield(self, **kwargs): 790 831 defaults = {'form_class': forms.FileField} 791 832 # If a file has been provided previously, then the form doesn't require … … 814 855 defaults.update(kwargs) 815 856 return super(FloatField, self).formfield(**defaults) 816 857 858 class ImageFile(File): 859 def get_width(self): 860 return self._get_image_dimensions()[0] 861 862 def get_height(self): 863 return self._get_image_dimensions()[1] 864 865 def _get_image_dimensions(self): 866 if not hasattr(self, '_dimensions_cache'): 867 from django.utils.images import get_image_dimensions 868 self._dimensions_cache = get_image_dimensions(self.open()) 869 return self._dimensions_cache 870 817 871 class ImageField(FileField): 872 attr_class = ImageFile 873 818 874 def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs): 819 875 self.width_field, self.height_field = width_field, height_field 820 876 FileField.__init__(self, verbose_name, name, **kwargs) … … 832 888 setattr(cls, 'get_%s_height' % self.name, curry(cls._get_FIELD_height, field=self)) 833 889 834 890 def save_file(self, new_data, new_object, original_object, change, rel, save=True): 835 FileField.save_file(self, new_data, new_object, original_object, change, rel, save)836 891 # If the image has height and/or width field(s) and they haven't 837 892 # changed, set the width and/or height field(s) back to their original 838 893 # values. 839 if change and (self.width_field or self.height_field) and save: 840 if self.width_field: 841 setattr(new_object, self.width_field, getattr(original_object, self.width_field)) 842 if self.height_field: 843 setattr(new_object, self.height_field, getattr(original_object, self.height_field)) 844 new_object.save() 894 if self.width_field or self.height_field: 895 if original_object and not change: 896 if self.width_field: 897 setattr(new_object, self.width_field, getattr(original_object, self.width_field)) 898 if self.height_field: 899 setattr(new_object, self.height_field, getattr(original_object, self.height_field)) 900 else: 901 from cStringIO import StringIO 902 from django.utils.images import get_image_dimensions 845 903 904 upload_field_name = self.get_manipulator_field_names('')[0] 905 if rel: 906 field = new_data[upload_field_name][0] 907 else: 908 field = new_data[upload_field_name] 909 910 # Get the width and height from the raw content to avoid extra 911 # unnecessary trips to the file backend. 912 width, height = get_image_dimensions(StringIO(field["content"])) 913 914 if self.width_field: 915 setattr(new_object, self.width_field, width) 916 if self.height_field: 917 setattr(new_object, self.height_field, height) 918 FileField.save_file(self, new_data, new_object, original_object, change, rel, save) 919 846 920 def formfield(self, **kwargs): 847 921 defaults = {'form_class': forms.ImageField} 848 922 return super(ImageField, self).formfield(**defaults) -
django/utils/images.py
6 6 7 7 import ImageFile 8 8 9 def get_image_dimensions( path):10 """Returns the (width, height) of an image at a givenpath."""9 def get_image_dimensions(file_or_path): 10 """Returns the (width, height) of an image, given an open file or a path.""" 11 11 p = ImageFile.Parser() 12 fp = open(path, 'rb') 12 if hasattr(file_or_path, 'read'): 13 fp = file_or_path 14 else: 15 fp = open(file_or_path, 'rb') 13 16 while 1: 14 17 data = fp.read(1024) 15 18 if not data: … … 19 22 return p.image.size 20 23 break 21 24 fp.close() 22 return None 25 return None 26 No newline at end of file -
docs/db-api.txt
1848 1848 get_FOO_filename() 1849 1849 ------------------ 1850 1850 1851 **Deprecated in Django development version. See `managing files` for the new, 1852 preferred method for dealing with files.** 1853 1851 1854 For every ``FileField``, the object will have a ``get_FOO_filename()`` method, 1852 1855 where ``FOO`` is the name of the field. This returns the full filesystem path 1853 1856 to the file, according to your ``MEDIA_ROOT`` setting. … … 1858 1861 get_FOO_url() 1859 1862 ------------- 1860 1863 1864 **Deprecated in Django development version. See `managing files` for the new, 1865 preferred method for dealing with files.** 1866 1861 1867 For every ``FileField``, the object will have a ``get_FOO_url()`` method, 1862 1868 where ``FOO`` is the name of the field. This returns the full URL to the file, 1863 1869 according to your ``MEDIA_URL`` setting. If the value is blank, this method … … 1866 1872 get_FOO_size() 1867 1873 -------------- 1868 1874 1875 **Deprecated in Django development version. See `managing files` for the new, 1876 preferred method for dealing with files.** 1877 1869 1878 For every ``FileField``, the object will have a ``get_FOO_size()`` method, 1870 1879 where ``FOO`` is the name of the field. This returns the size of the file, in 1871 1880 bytes. (Behind the scenes, it uses ``os.path.getsize``.) … … 1873 1882 save_FOO_file(filename, raw_contents) 1874 1883 ------------------------------------- 1875 1884 1885 **Deprecated in Django development version. See `managing files` for the new, 1886 preferred method for dealing with files.** 1887 1876 1888 For every ``FileField``, the object will have a ``save_FOO_file()`` method, 1877 1889 where ``FOO`` is the name of the field. This saves the given file to the 1878 1890 filesystem, using the given filename. If a file with the given filename already … … 1882 1894 get_FOO_height() and get_FOO_width() 1883 1895 ------------------------------------ 1884 1896 1897 **Deprecated in Django development version. See `managing files` for the new, 1898 preferred method for dealing with files.** 1899 1885 1900 For every ``ImageField``, the object will have ``get_FOO_height()`` and 1886 1901 ``get_FOO_width()`` methods, where ``FOO`` is the name of the field. This 1887 1902 returns the height (or width) of the image, as an integer, in pixels. -
docs/model-api.txt
227 227 ``FileField`` 228 228 ~~~~~~~~~~~~~ 229 229 230 A file-upload field. Has one **required** argument:230 A file-upload field. **Requires** exactly one of the following two arguments: 231 231 232 232 ====================== =================================================== 233 233 Argument Description 234 234 ====================== =================================================== 235 235 ``upload_to`` A local filesystem path that will be appended to 236 236 your ``MEDIA_ROOT`` setting to determine the 237 output of the ``get_<fieldname>_url()`` helper 238 function. 237 final storage destination. If this argument is 238 supplied, the storage backend will default to 239 ``FileSystemBackend``. 240 ``backend`` **New in Django development version** 241 242 A storage backend object, which handles the storage 243 and retrieval of your files. See `managing files`_ 244 for details on how to provide this object. 239 245 ====================== =================================================== 240 246 241 This path may contain `strftime formatting`_, which will be replaced by the 242 date/time of the file upload (so that uploaded files don't fill up the given 243 directory). 247 .. _managing files: ../files/ 244 248 249 The ``upload_to`` path may contain `strftime formatting`_, which will be 250 replaced by the date/time of the file upload (so that uploaded files don't fill 251 up the given directory). 252 245 253 The admin represents this field as an ``<input type="file">`` (a file-upload 246 254 widget). 247 255 248 Using a ``FileField`` or an ``ImageField`` (see below) in a model takes a few249 s teps:256 Using a ``FileField`` or an ``ImageField`` (see below) in a model without a 257 specified backend takes a few steps: 250 258 251 259 1. In your settings file, you'll need to define ``MEDIA_ROOT`` as the 252 260 full path to a directory where you'd like Django to store uploaded