Changes between Initial Version and Version 1 of CustomUploadAndFilters


Ignore:
Timestamp:
Aug 23, 2006, 11:44:30 AM (18 years ago)
Author:
Enrico <rico.bl@…>
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • CustomUploadAndFilters

    v1 v1  
     1= Custom Upload Fields and Filters =
     2
     3I've made custom file upload fields with some extra features:
     4
     5* automatic upload_to path (based on app/model/field names)
     6* automatic renaming the filename based on the primary key
     7* maximum width and/or height for images
     8
     9Also, I've created filters to automatically resize/crop images directly from templates.
     10
     11The original idea came from [http://trac.studioquattro.biz/djangoutils/wiki/Thumbnail/ ImageWithThumbnailField], I've recreated as an exercise and to adapt it to my taste.
     12
     13There are similar approaches by [http://www.verdjn.com/ VERDJN].
     14
     15== Custom upload fields: AutoFileField and AutoImageField ==
     16
     17{{{
     18#!python
     19from django.db.models import ImageField, FileField, signals
     20from django.dispatch import dispatcher
     21from django.conf import settings
     22import shutil, os, glob
     23
     24# Helpers
     25from imaging import fit,fit_crop
     26from fs import change_basename
     27
     28def auto_rename(file_path, new_name):
     29    """
     30    Renames a file, keeping the extension.
     31   
     32    Parameters:
     33        - file_path: the file path relative to MEDIA_ROOT
     34        - new_name: the new basename of the file (no extension)
     35   
     36    Returns the new file path on success or the original file_path on error.
     37    """
     38   
     39    # Return if no file given
     40    if file_path == '':
     41        return ''
     42    #
     43   
     44    # Get the new name
     45    new_path = change_basename(file_path, new_name)
     46   
     47    # Changed?
     48    if new_path != file_path:
     49        # Try to rename
     50        try:
     51            shutil.move(os.path.join(settings.MEDIA_ROOT, file_path), os.path.join(settings.MEDIA_ROOT, new_path))
     52        except IOError:
     53            # Error? Restore original name
     54            new_path = file_path
     55        #
     56    #
     57   
     58    # Return the new path replacing backslashes (for Windows)
     59    return new_path
     60# def auto_rename
     61
     62def auto_resize(file_path, max_width=None, max_height=None, crop=False):
     63    """
     64    Resize an image to fit an area.
     65    Useful to avoid storing large files.
     66   
     67    If set to crop, will resize to the closest size and then crop.
     68   
     69    At least one of the max_width or max_height parameters must be set.
     70    """
     71   
     72    # Return if no file given or no maximum size passed
     73    if (not file_path) or ((not max_width) and (not max_height)):
     74        return
     75    #
     76   
     77    # Get the complete path using MEDIA_ROOT
     78    real_path = os.path.join(settings.MEDIA_ROOT, file_path)
     79   
     80    if (crop):
     81        fit_crop(real_path, max_width, max_height)
     82    else:
     83        fit(real_path, max_width, max_height)
     84    #
     85# def auto_resize
     86
     87def init_path(self, **kwargs):
     88    """
     89    Create a flag if there's an 'upload_to' parameter.
     90    If not found, fill with a dummy value.
     91    The flag will be used to create an automatic value on "post_init" signal.
     92    """
     93   
     94    # Flag to auto-fill the path if it is empty
     95    self.fill_path = ('upload_to' not in kwargs)
     96   
     97    if self.fill_path:
     98        # Dummy value to bypass attribute requirement
     99        kwargs['upload_to'] = '_'
     100    #
     101   
     102    return kwargs
     103# def init_path
     104
     105def set_field_path(self, instance = None):
     106    """
     107    Set up the "upload_to" for AutoFileField and AutoImageField or "path" for AutoFilePathField.
     108    Set a path based on the field hierarchy (app/model/field).
     109    """
     110   
     111    # Use the automatic path?
     112    if self.fill_path:
     113        setattr(self, 'upload_to', os.path.join(instance._meta.app_label, instance.__class__.__name__, self.name).lower())
     114    #
     115# def set_field_path
     116
     117class AutoFileField(FileField):
     118    """
     119    File field with:
     120    * automatic primary key based renaming
     121    * automatic upload_to (if not set)
     122    """
     123   
     124    def __init__(self, **kwargs):
     125        # Adjust the upload_to parameter
     126        kwargs = init_path(self, **kwargs)
     127       
     128        super(AutoFileField, self).__init__(**kwargs)
     129    # def __init__
     130   
     131    def _post_init(self, instance=None):
     132        set_field_path(self, instance)
     133    # def _post_init
     134   
     135    def _save(self, instance=None):
     136        if instance == None:
     137            return
     138        filename = auto_rename(getattr(instance, self.attname), '%s' % instance._get_pk_val())
     139        setattr(instance, self.attname, filename)
     140    # def _save
     141   
     142    def contribute_to_class(self, cls, name):
     143        super(AutoFileField, self).contribute_to_class(cls, name)
     144        dispatcher.connect(self._post_init, signals.post_init, sender=cls)
     145        dispatcher.connect(self._save, signals.pre_save, sender=cls)
     146    # def contribute_to_class
     147   
     148    def get_internal_type(self):
     149        return 'FileField'
     150    # def get_internal_type
     151# class AutoFileField
     152
     153class AutoImageField(ImageField):
     154    """
     155    Image field with:
     156    * automatic primary key based renaming
     157    * automatic upload_to (if not set)
     158    * optional resizing to a maximum width and/or height
     159    """
     160   
     161    def __init__(self, max_width=None, max_height=None, crop=False, **kwargs):
     162        # Adjust the upload_to parameter
     163        kwargs = init_path(self, **kwargs)
     164       
     165        # Image resizing properties
     166        self.max_width, self.max_height, self.crop = max_width, max_height, crop
     167       
     168        # Set fields for width and height
     169        self.width_field, self.height_field = 'width', 'height'
     170       
     171        super(AutoImageField, self).__init__(**kwargs)
     172    # def __init__
     173   
     174    def save_file(self, new_data, new_object, original_object, change, rel):
     175        # Original method
     176        super(AutoImageField, self).save_file(new_data, new_object, original_object, change, rel)
     177       
     178        # Get upload info
     179        upload_field_name = self.get_manipulator_field_names('')[0]
     180        field = new_data.get(upload_field_name, False)
     181       
     182        # File uploaded?
     183        if field:
     184            # Resize image
     185            auto_resize(getattr(new_object, self.attname), max_width=self.max_width, max_height=self.max_height, crop=self.crop)
     186        #
     187    # def save_file
     188   
     189    def delete_file(self, instance):
     190        """
     191        Deletes left-overs from thumbnail or crop template filters
     192        """
     193       
     194        super(AutoImageField, self).delete_file(instance)
     195       
     196        if getattr(instance, self.attname):
     197            # Get full path
     198            file_name = getattr(instance, 'get_%s_filename' % self.name)()
     199            # Get base dir, basename and extension
     200            basedir = os.path.dirname(file_name)
     201            base, ext = os.path.splitext(os.path.basename(file_name))
     202           
     203            # Delete left-overs from filters
     204            for file in glob.glob(os.path.join(basedir, base + '_*' + ext)):
     205                os.remove(os.path.join(basedir, file))
     206            #
     207        #
     208    # def delete_file
     209   
     210    def _post_init(self, instance=None):
     211        set_field_path(self, instance)
     212    # def _post_init
     213   
     214    def _save(self, instance=None):
     215        if instance == None:
     216            return
     217        filename = auto_rename(getattr(instance, self.attname), '%s' % instance._get_pk_val())
     218        setattr(instance, self.attname, filename)
     219    # def _save
     220   
     221    def contribute_to_class(self, cls, name):
     222        super(AutoImageField, self).contribute_to_class(cls, name)
     223        dispatcher.connect(self._post_init, signals.post_init, sender=cls)
     224        dispatcher.connect(self._save, signals.pre_save, sender=cls)
     225    # def contribute_to_class
     226
     227    def get_internal_type(self):
     228        return 'ImageField'
     229    # def get_internal_type
     230# class AutoImageField
     231}}}
     232
     233== Template filters: thumb and crop ==
     234
     235Remember to adjust your paths to your project.
     236
     237{{{
     238#!python
     239from django import template
     240from django.conf import settings
     241import os
     242
     243# Adjust your paths to 'imaging' and 'fs'
     244from project.custom.imaging import fit,fit_crop
     245from project.custom.fs import add_to_basename
     246
     247def parse_args(args = ''):
     248    """
     249    Parse filter arguments in the format:
     250        keyword_1=value_1,keyword_2=value_2
     251   
     252    Returns a keyword list
     253    """
     254    kwargs = {}
     255   
     256    if args:
     257        for arg in args.split(','):
     258            kw, val = arg.split('=', 1)
     259            kwargs[kw.lower()] = val
     260        # for
     261    #
     262   
     263    return kwargs
     264# def parse_args
     265
     266def resize(url, args = '', crop = False):
     267    """
     268    On-the-fly thumbnail or crop creation
     269    """
     270   
     271    kwargs = parse_args(args)
     272    call_kwargs = {}
     273
     274    if ('width' not in kwargs) and ('height' not in kwargs):
     275        return url
     276    #
     277   
     278    if crop:
     279        # Mark as a cropped image
     280        extra = '_c_'
     281    else:
     282        # Mark as a thumbnailed image
     283        extra = '_t_'
     284    #
     285   
     286    # Setup width and/or height
     287    if 'width' in kwargs:
     288        extra += 'w' + kwargs['width']
     289        call_kwargs['max_width'] = kwargs['width']
     290    #
     291    if 'height' in kwargs:
     292        extra += 'h' + kwargs['height']
     293        call_kwargs['max_height'] = kwargs['height']
     294    #
     295   
     296    # Remove MEDIA_URL
     297    url = url.replace(settings.MEDIA_URL, '')
     298   
     299    new_url = add_to_basename(url, extra)
     300    call_kwargs['save_as'] = os.path.join(settings.MEDIA_ROOT, new_url)
     301   
     302    if crop:
     303        # Make the cropping
     304        ok = fit_crop(os.path.join(settings.MEDIA_ROOT, url), **call_kwargs)
     305    else:
     306        # Create the thumbnail
     307        ok = fit(os.path.join(settings.MEDIA_ROOT, url), **call_kwargs)
     308    #
     309   
     310    # Something wrong with the image processing?
     311    if not ok:
     312        # Silently restore the original url
     313        new_url = url
     314    #
     315   
     316    # Add MEDIA_URL back to the URL and return
     317    return settings.MEDIA_URL + new_url
     318   
     319# def resize
     320
     321def thumb(url, args=''):
     322    """
     323    On-the-fly thumbnail creation
     324   
     325    Usage:
     326        {{ url|thumb:"width=10,height=20" }}
     327        {{ url|thumb:"width=10" }}
     328        {{ url|thumb:"height=20" }}
     329    """
     330   
     331    return resize(url, args)
     332#
     333
     334def crop(url, args=''):
     335    """
     336    On-the-fly image cropping
     337   
     338    Usage:
     339        {{ url|crop:"width=10,height=20" }}
     340        {{ url|crop:"width=10" }}
     341        {{ url|crop:"height=20" }}
     342    """
     343   
     344    return resize(url, args, crop=True)
     345#
     346
     347register = template.Library()
     348
     349register.filter('thumb', thumb)
     350register.filter('crop', crop)
     351}}}
Back to Top