Ticket #2983: 2983.diff

File 2983.diff, 4.9 KB (added by SmileyChris, 6 years ago)
  • django/db/models/fields/files.py

     
    2222        self.field = field
    2323        self.storage = field.storage
    2424        self._committed = True
     25        self._replaced = []
    2526
    2627    def __eq__(self, other):
    2728        # Older code may be expecting FileField values to be simple strings.
     
    206207        return instance.__dict__[self.field.name]
    207208
    208209    def __set__(self, instance, value):
     210        if self.field.delete_replaced:
     211            previous_file = instance.__dict__.get(self.field.name)
    209212        instance.__dict__[self.field.name] = value
     213        if self.field.delete_replaced and previous_file:
     214            if previous_file:
     215                # Rather than just using value, we get the file from the
     216                # instance, so that the __get__ logic of the file descriptor is
     217                # processed. This ensures we will be dealing with a FileField
     218                # (or subclass of FileField) instance.
     219                file = getattr(instance, self.field.name)
     220                # Remember that the previous file was replaced (along with any
     221                # files which the previous file replaced too).
     222                file._replaced += previous_file._replaced
     223                file._replaced.append(previous_file)
    210224
    211225class FileField(Field):
    212226    # The class to wrap instance attributes in. Accessing the file object off
     
    216230    # The descriptor to use for accessing the attribute off of the class.
    217231    descriptor_class = FileDescriptor
    218232
    219     def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **kwargs):
     233    def __init__(self, verbose_name=None, name=None, upload_to='',
     234                 storage=None, delete_replaced=False, **kwargs):
    220235        for arg in ('primary_key', 'unique'):
    221236            if arg in kwargs:
    222237                raise TypeError("'%s' is not a valid argument for %s." % (arg, self.__class__))
     
    225240        self.upload_to = upload_to
    226241        if callable(upload_to):
    227242            self.generate_filename = upload_to
     243        self.delete_replaced = delete_replaced
    228244
    229245        kwargs['max_length'] = kwargs.get('max_length', 100)
    230246        super(FileField, self).__init__(verbose_name, name, **kwargs)
     
    247263    def pre_save(self, model_instance, add):
    248264        "Returns field's value just before saving."
    249265        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)
     266        if file:
     267            if self.delete_replaced:
     268                # Delete any files which this one replaced.
     269                queryset = model_instance._default_manager.exclude(
     270                                                    pk=model_instance.pk)
     271                for replaced_file in file._replaced:
     272                    if replaced_file._committed:
     273                        self.safe_delete_file(replaced_file, queryset)
     274                file._replaced = []
     275            if not file._committed:
     276                # Commit the file to storage prior to saving the model
     277                file.save(file.name, file, save=False)
    253278        return file
    254279
    255280    def contribute_to_class(self, cls, name):
     
    258283        signals.post_delete.connect(self.delete_file, sender=cls)
    259284
    260285    def delete_file(self, instance, sender, **kwargs):
     286        """
     287        Signal receiver which deletes an attached file from the backend when
     288        the model is deleted.
     289        """
    261290        file = getattr(instance, self.attname)
    262         # If no other object of this type references the file,
    263         # and it's not the default value for future objects,
    264         # delete it from the backend.
    265         if file and file.name != self.default and \
    266             not sender._default_manager.filter(**{self.name: file.name}):
     291        self.safe_delete_file(file, sender._default_manager.all())
     292   
     293    def safe_delete_file(self, file, queryset):
     294        """
     295        Deletes the file from the backend if no objects in the queryset
     296        reference the file and it's not the default value for future objects.
     297       
     298        Otherwise, the file is simply closed so it doesn't tie up resources.
     299        """
     300        if file:
     301            queryset = queryset.filter(**{self.name: file.name})
     302            if file.name != self.default and not queryset:
    267303                file.delete(save=False)
    268         elif file:
    269             # Otherwise, just close the file, so it doesn't tie up resources.
    270             file.close()
     304            else:
     305                file.close()
    271306
    272307    def get_directory_name(self):
    273308        return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to))))
Back to Top