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)
signature.asc
Description: OpenPGP digital signature