On 12/20/2010 12:55 AM, Daniel Carvalho wrote:
> Everybody knows the default django FileField doesn't allow to remove an
> existing file...
> 
> This is a good replacement. Just use RemovableFileField instead of
> FileField in your models:
> http://djangosnippets.org/snippets/636/
> 
> 
> I want to implement on it another feature- When there is a file being
> uploaded, but the form fails validation for other reason, I want to keep
> the file in the server, so no need to upload the file again. The next
> time the form is submit I would take the file and save it in the model.
> 
> 
> BUT this is not possible because there is (apparently) no way to acess
> the request object inside RemovableFileField nor from the widget...
> 
> SO I think I have to implement this in the ModelForm, or in the
> ModelAdmin, which is a more ugly solution.
> (any ideas???)
> 
> 


If anyone wants to use it, I implemented this solution.

Just use FilesModelAdmin for your models that have file or image fields.

1. A checkbox will be displayed, to remove the current value, if the
field supports blank values.
2. The uploaded files are stored in session variables for later use, in
case the form fails validation you don't need to upload then again.
3. It works for inline models also.

Problem:
if the user is editing two forms at the same time in the same session,
the uploaded files will be mixed...




# coding: utf-8
from django.contrib import admin
from django import forms
from django.utils.safestring import mark_safe

from django.utils.translation import ugettext, ugettext_lazy as _

import django.core.files
import re
import tempfile, shutil

from django.db import models

# if we have a field called "imagem", the associated checkbox for
deleting the file will be "imagem"  + REMOVE_SUFFIX,  "imagem__remove"
REMOVE_SUFFIX = "__remove"

# The session variable name we will use to store the uploaded files
SESSION_NAME = "save_uploaded_files"

import threading

# remember the request value
# so it can be accessed from any place in the thread
def set_current_request(request):
   global local
   local = threading.local()
   local.current_request = request

def get_current_request():
   global local
   return local.current_request


class FileWidget(forms.FileInput):
   # if this file accepts blanks or not
   # This will define if we put a checkbox for removing the file
   blank = False

   def render(self, name, value, attrs=None):

      output = []
      if value:
         # file has a current value

         if hasattr(value, "url"):
            # the file value is the value stored in the model
            output.append('%s <a target="_blank" href="%s">%s</a> <br
/>%s ' % \
                             (ugettext('Currently:'), value.url, value,
ugettext('Change:')))

         else:
            # the file value is the file uploaded in the previous
request but not saved yet, because the form validation failed:
            output.append('%s %s <br />%s ' % \
                             (ugettext('Not saved yet:'), value,
ugettext('Change:')))

         # If it has a current value, and the field accepts blanks,
         # we place a checkbox for removing it
         if self.blank:
            request = get_current_request()

            name_checkbox = name + REMOVE_SUFFIX

            # This is important to remember the checkbox state,
            # when the form is submited but fails validation, and is
displayed again
            if request and name_checkbox in request.POST.keys():
               checked = 'checked="on"'
            else:
               checked = ''

            output.append('<input type="checkbox" name="%s"
id="id_%s_remover" %s /><label for="id_%s_remover"
class="vCheckboxLabel"> %s </label>' % (name_checkbox, name, checked,
name, ugettext('Remove')))

      output.append(super(FileWidget, self).render(name, value, attrs))

      return mark_safe(u''.join(output))

# The same, but accepts blanks
class FileWidgetBlank(FileWidget):
   blank = True


# This ModelAdmin offers this funtionalities:
# For each file or image field, keeps uploaded files in the server even
if the form fails validation, so they dont need to be uploaded again.
When the form is saved, the uploaded files are also saved to the model.
# Put a checkbox for removing the file, it it accepts blanks.
# This also works for inline models, which have to be FilesStackedInline
class FilesModelAdmin(admin.ModelAdmin):

   def formfield_for_dbfield(self, db_field, **kwargs):
      # is it file or image?
      if type(db_field) in [ models.ImageField, models.FileField]:
         if db_field.blank:
            # accept blank?
            kwargs['widget'] = FileWidgetBlank
         else:
            kwargs['widget'] = FileWidget
      return super(FilesModelAdmin,
self).formfield_for_dbfield(db_field,**kwargs)

   # This function is called before add_view and change_view
   # It saves the uploaded files, and at the same time restores previous
uploaded files.
   def manage_uploads(self, request):

      # this is for the request to be accessible from FileWidget

      # I could have implemented get_form doing this:
      #for name in form.base_fields:
      #   widget = form.base_fields[name].widget
      #   if type(widget) == MyFileWidgetBlank:
      #      widget.request = request
      # But that doesn't work for inline models fields.
      # I didn't found how to access these fields, so I use the thread
local variable ugly solution.

      set_current_request(request)

      if not SESSION_NAME in request.session:
         request.session[SESSION_NAME] = {}
      save = request.session[SESSION_NAME]

      if not request.POST:
         save.clear()

      else:
         # See the files that were uploaded in this request:
         for name in request.FILES.keys():
            file_actual = request.FILES[name]

            tmp = tempfile.mkstemp()[1]
            shutil.copyfileobj(file_actual.file, file(tmp, "w"))

            save[name] = (tmp, file_actual.name)

         # restore previous uploaded files
         for name in save.keys():
            (tmp, filename) = save[name]
            request.FILES[name] = django.core.files.base.File(file(tmp),
filename)

      request.session[SESSION_NAME] = save


   def add_view(self, request, form_url='', extra_context=None):
      self.manage_uploads(request)
      return super(FilesModelAdmin, self).add_view(request, form_url,
extra_context)

   def change_view(self, request, object_id, extra_context=None):
      self.manage_uploads(request)
      return super(FilesModelAdmin, self).change_view(request,
object_id, extra_context)

   def save_model(self, request, obj, form, change):
       super(FilesModelAdmin, self).save_model(request, obj, form, change)

       model_changed = False

       for name in request.POST.keys():
           if name.endswith(REMOVE_SUFFIX):

              # it is one of my checkboxs!

              # note: if it is in the request it means the checkbox is ON

              # know the file or image field name associated with this
checkbox
              name_file_field = name[0:len(name) - len(REMOVE_SUFFIX)]

              # Is it an inline?
              # for example, if this modeladmin has a inline called "pessoa"
              # then there will be a request parameter named,
"pessoa_set-0-imagem__remove"
              # which means the image field of "pessoa" 0
              match = re.search('^(\w+)_set-(\d+)-(\w+)$', name_file_field)
              if match:
                 name1 = match.group(1)
                 index = int(match.group(2))
                 name2 = match.group(3)

                 subobj = getattr(obj, name1 + "_set").all()[index]
                 setattr(subobj, name2, None)
                 subobj.save()

              else:
                 # note: when I do setattr(None) the file is phisically
removed
                 setattr(obj, name_file_field, None)

              model_changed = True

       if model_changed:
           obj.save()

       return obj



class FilesStackedInline(admin.StackedInline):
   def formfield_for_dbfield(self, db_field, **kwargs):
      if type(db_field) in [ models.ImageField, models.FileField]:
         if db_field.blank:
            kwargs['widget'] = FileWidgetBlank
         else:
            kwargs['widget'] = FileWidget
      return super(FilesStackedInline,
self).formfield_for_dbfield(db_field,**kwargs)



Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to