Ticket #5361: file-backends.diff
File file-backends.diff, 23.7 KB (added by , 17 years ago) |
---|
-
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 return self.backend.get_filesize(self.filename) 712 713 def open(self, mode='rb'): 714 return self.backend.open(self.filename, mode) 715 716 def save_file(self, filename, raw_contents, save=True): 717 self.filename = self.backend.save_file(filename, raw_contents) 718 719 # Save the object because it has changed unless save is False 720 if save: 721 self.obj.save() 722 723 class FileProxy(object): 724 def __init__(self, field): 725 self.field = field 726 self.cache_name = self.field.get_cache_name() 727 728 def __get__(self, instance=None, owner=None): 729 if instance is None: 730 raise AttributeError, "%s can only be accessed from %s instances." % (self.field.attname, self.owner.__name__) 731 return getattr(instance, self.cache_name) 732 733 def __set__(self, instance, value): 734 if hasattr(instance, self.cache_name): 735 raise AttributeError, "%s can not be set in this manner." % self.field.attname 736 instance.__dict__[self.field.attname] = value 737 attr = self.field.attr_class(instance, self.field, value) 738 setattr(instance, self.cache_name, attr) 739 697 740 class FileField(Field): 698 def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs): 699 self.upload_to = upload_to 741 attr_class = File 742 743 def __init__(self, verbose_name=None, name=None, upload_to='', backend=None, **kwargs): 744 if backend is None: 745 from django.db.models.fields.backends.filesystem import FileSystemBackend 746 backend = FileSystemBackend(location=upload_to) 747 self.backend = self.upload_to = backend 700 748 Field.__init__(self, verbose_name, name, **kwargs) 701 749 702 750 def get_db_prep_save(self, value): … … 704 752 # Need to convert UploadedFile objects provided via a form to unicode for database insertion 705 753 if value is None: 706 754 return None 707 return unicode(value )755 return unicode(value.filename) 708 756 709 757 def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): 710 758 field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow) … … 744 792 745 793 def contribute_to_class(self, cls, name): 746 794 super(FileField, self).contribute_to_class(cls, name) 795 setattr(cls, self.attname, FileProxy(self)) 747 796 setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self)) 748 797 setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self)) 749 798 setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self)) 750 799 setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save)) 751 800 dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls) 752 801 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) 802 def delete_file(self, instance, sender): 803 filename = getattr(instance, self.attname).filename 804 # If no other object of this type references the file, 805 # delete it from the backend. 806 if not sender._default_manager.filter(**{self.name: filename}): 807 self.backend.delete_file(filename) 761 808 762 809 def get_manipulator_field_objs(self): 763 810 return [oldforms.FileUploadField, oldforms.HiddenField] … … 768 815 def save_file(self, new_data, new_object, original_object, change, rel, save=True): 769 816 upload_field_name = self.get_manipulator_field_names('')[0] 770 817 if new_data.get(upload_field_name, False): 771 func = getattr(new_object, 'save_%s_file' % self.name)772 818 if rel: 773 f unc(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save)819 field = new_data[upload_field_name][0] 774 820 else: 775 func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save) 821 field = new_data[upload_field_name] 822 getattr(new_object, self.attname).save_file(field["filename"], field["content"], save) 776 823 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 824 def save_form_data(self, instance, data): 786 825 if data: 787 getattr(instance, "save_%s_file" % self.name)(data.filename, data.content, save=False)788 826 getattr(instance, self.attnamename).save_file(data.filename, data.content, save=False) 827 789 828 def formfield(self, **kwargs): 790 829 defaults = {'form_class': forms.FileField} 791 830 # If a file has been provided previously, then the form doesn't require … … 814 853 defaults.update(kwargs) 815 854 return super(FloatField, self).formfield(**defaults) 816 855 856 class ImageFile(File): 857 def get_width(self): 858 return self._get_image_dimensions()[0] 859 860 def get_height(self): 861 return self._get_image_dimensions()[1] 862 863 def _get_image_dimensions(self): 864 if not hasattr(self, '_dimensions_cache'): 865 from django.utils.images import get_image_dimensions 866 self._dimensions_cache = get_image_dimensions(self.open()) 867 return self._dimensions_cache 868 817 869 class ImageField(FileField): 870 attr_class = ImageFile 871 818 872 def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs): 819 873 self.width_field, self.height_field = width_field, height_field 820 874 FileField.__init__(self, verbose_name, name, **kwargs) … … 832 886 setattr(cls, 'get_%s_height' % self.name, curry(cls._get_FIELD_height, field=self)) 833 887 834 888 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 889 # If the image has height and/or width field(s) and they haven't 837 890 # changed, set the width and/or height field(s) back to their original 838 891 # 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() 892 if self.width_field or self.height_field: 893 if original_object and not change: 894 if self.width_field: 895 setattr(new_object, self.width_field, getattr(original_object, self.width_field)) 896 if self.height_field: 897 setattr(new_object, self.height_field, getattr(original_object, self.height_field)) 898 else: 899 from cStringIO import StringIO 900 from django.utils.images import get_image_dimensions 845 901 902 upload_field_name = self.get_manipulator_field_names('')[0] 903 if rel: 904 field = new_data[upload_field_name][0] 905 else: 906 field = new_data[upload_field_name] 907 908 # Get the width and height from the raw content to avoid extra 909 # unnecessary trips to the file backend. 910 width, height = get_image_dimensions(StringIO(field["content"])) 911 912 if self.width_field: 913 setattr(new_object, self.width_field, width) 914 if self.height_field: 915 setattr(new_object, self.height_field, height) 916 FileField.save_file(self, new_data, new_object, original_object, change, rel, save) 917 846 918 def formfield(self, **kwargs): 847 919 defaults = {'form_class': forms.ImageField} 848 920 return super(ImageField, self).formfield(**defaults) -
django/db/models/fields/backends/__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/db/models/fields/backends/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.db.models.fields.backends 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/fields/backends/__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/db/models/fields/backends/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.db.models.fields.backends 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/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