title: django file upload - image deleted when edit template updated
body: <p>I am using django to allow a user to upload images along with text to describe the image title.</p>
  
  <p>The user should be able to edit the image title and change the image file using the edit template. This does work - to some degree.</p>
  
  <p>I can display the image file and the image title in the edit template for editing. The issue I have is when I do not include an image file in the file upload input (the user only wants to change the image title and not the image file), the code I have does change the image title but the image file is deleted from the database (the physical image file is not deleted from the file system).</p>
  
  <p>I can successfully update the image details but the user must include a new image file or the same image file in the file upload input field for the  image title the image details to be successfully updated.</p>
  
  <p>It seems the user <strong>must</strong> include an image file in the file upload input field. I am trying to get my code to work just like the django admin where the user does not have to include an image file each time the user changes the image title.</p>
  
  <p><strong>How do I allow a user to update the image details without having to re-upload the image file each time the user wants to change the image title?</strong></p>
  
  <p>Here is my models.py code:</p>
  
  <p><strong>EDIT # 2 - added FillableModelWithLanguageVersion &amp; LanguageVersion</strong></p>
  
  <pre><code># START: ATTACHMENT DETAILS MODEL.
  def _get_document_upload_location(instance, filename):
      """
      Using a function instead of a lambda to make migrations happy. DO NOT remove or rename this function in the future, as it will break migrations.
      @param instance: model instance that owns the FileField we're generating the upload filename for.
      @param filename: Original file name assigned by django.
      """
      return 'attachments/%d/%s' % (instance.user.id, uuid.uuid4())
  
  class AttachmentDetails(models.Model, FillableModelWithLanguageVersion):
      user = models.ForeignKey(User)
      language_version = models.ForeignKey('LanguageVersion')
      attachment_document = models.FileField(upload_to=_get_document_upload_location, null=True, blank=True)
      attachment_title = models.CharField(null=False, blank=False, max_length=250)
      attachment_timestamp_added = models.DateTimeField(auto_now_add=True, auto_now=False)
      attachment_timestamp_updated = models.DateTimeField(auto_now=True, auto_now_add=False)
  
      def __unicode__(self):
          return unicode(self.user)
  
      class Meta:
          verbose_name = ('Attachment Detail')
          verbose_name_plural = ('Attachment Details')
  
      @staticmethod
      def view_link():
          return reverse(settings.MENU_DETAIL_LINK_ATTACHMENT_DETAILS)
  
      @property
      def language_name(self):
          return LANGUAGES[self.language_version.language_code].name
  
      @property
      def language_name_english(self):
          return LANGUAGES[self.language_version.language_code].name_english
  
      @property
      def language_name_native(self):
          return LANGUAGES[self.language_version.language_code].name_native
  
  
  # delete file when AttachmentDetails model is deleted
  
  
  @receiver(post_delete,sender=AttachmentDetails,dispatch_uid='delete_attachment_details')
  def delete_attachment_details(sender, **kwargs):
      attachment_details = kwargs['instance']
      attachment_details.attachment_document.delete(save=False)
  # FINISH: ATTACHMENT DETAILS MODEL.
  
  class FillableModelWithLanguageVersion(object):
      def fill(self, fields):
          self.language_version = LanguageVersion.objects.get(user=self.user, language_code=fields['language_code'])
          for field, value in fields.iteritems():
              if field == 'language_code':
                  continue
              setattr(self, field, value)
  
  class LanguageVersion(models.Model):
      """Language version selection for a user"""
      user = models.ForeignKey(User)
      language_code = models.CharField(max_length=32)
      language_code_disabled = models.BooleanField(default=False)
      language_version_timestamp_added = models.DateTimeField(auto_now_add=True, auto_now=False)
      language_version_timestamp_updated = models.DateTimeField(auto_now=True, auto_now_add=False)  # the date the language version is updated when the user changes their subscription type.
  
      def __unicode__(self):
          return unicode(self.language_code)
  
      class Meta:
          unique_together = ('user', 'language_code')
          verbose_name = ('Language Versions')
          verbose_name_plural = ('Language Versions')
  </code></pre>
  
  <p>Here is my views.py code:</p>
  
  <pre><code>def attachment_details_edit(request, attachment_details_id):
      try:
          attachment_details = AttachmentDetails.objects.get(pk=attachment_details_id, user=request.user)
      except AttachmentDetails.DoesNotExist:
          return redirect(settings.MENU_DETAIL_LINK_ATTACHMENT_DETAILS)
      language_versions = LanguageVersion.objects.filter(user=request.user).select_related('language_version')
      available_languages = get_available_language_details(language_versions, request.user.userprofile.language_preference)
      attachment_details_num = request.user.attachmentdetails_set.count()
      language_code = attachment_details.language_version.language_code
      language_code_disabled = attachment_details.language_version.language_code_disabled
      language_preference = request.user.userprofile.language_preference
      if language_code_disabled:
          return redirect(settings.MENU_DETAIL_LINK_ATTACHMENT_DETAILS)
      if request.method == 'GET':
          language_code = attachment_details.language_version.language_code
          form = AttachmentDetailsForm(
                  available_languages,
                  language_preference=request.user.userprofile.language_preference,
                  file_required=True,
                  initial=dict(
                      model_to_dict(attachment_details),
                      language_code=language_code
                  )
          )
      elif request.method == 'POST':
          form = AttachmentDetailsForm(
              available_languages,
              language_preference,
              False,  # file_required
              request.POST,
              request.FILES
          )
          if form.is_valid():
              cd = form.cleaned_data
              if cd['attachment_document'] is not None:
                  #  delete the existing uploaded attachment when user updates the existing attachment with a replacement attachment.
                  print 'removing previously uploaded file'
                  attachment_details.attachment_document.delete(save=False)
              attachment_details.fill(cd)
              attachment_details.save()
              messages.success(request, _('successfully updated.'))
              return redirect(settings.MENU_DETAIL_LINK_ATTACHMENT_DETAILS)
  </code></pre>
  
  <p>I have a related post <a href="https://stackoverflow.com/questions/28579224/django-file-upload-hide-the-currently-displayed-image-link-in-the-edit-templat">here</a>.</p>
  
  <p><strong>edit</strong></p>
  
  <p>Here is my forms.py code:</p>
  
  <pre><code>class AttachmentDetailsForm(forms.ModelForm):
  
      required_css_class = 'required'
  
      def __init__(self, available_languages, language_preference, file_required, *args, **kwargs):
          """
          available_languages should be a valid choices list
          """
          super(AttachmentDetailsForm, self).__init__(*args, **kwargs)
          self.fields['language_code'] = forms.ChoiceField(choices=available_languages, initial=language_preference, label=_('Language'),)
          self.fields['attachment_document'] = forms.FileField(label=_('Attachment'), required=file_required)
  
      class Meta:
          model = AttachmentDetails
  
          fields = (
              'attachment_title',
          )
  
          labels = {
              'attachment_title': _('Attachment Title'),
          }
  
      def clean_attachment_document(self):
  
          if self.cleaned_data['attachment_document'] is not None:
  
              file_name = self.cleaned_data['attachment_document'].name.lower()
              extension = file_name.split('.')[-1]
  
              if extension not in settings.ALLOWED_ATTACHMENT_EXTENSIONS:
                  raise forms.ValidationError(_("Only file types .bmp, .gif, .jpg, .jpeg are permitted."))
  
              # use the following if condition when the file size of the attachment is to be measured in MB - MB calculation.
              #if self.cleaned_data['attachment_document'].size &gt; settings.MAX_ATTACHMENT_FILE_SIZE_MB * 1024 * 1024:
              #    raise forms.ValidationError('Maximum permitted attachment size is: %d MB.' % settings.MAX_ATTACHMENT_FILE_SIZE_MB)
  
              # use the following if condition when the file size of the attachment is to be measured in kB - kB calculation.
              if self.cleaned_data['attachment_document'].size &gt; settings.MAX_ATTACHMENT_FILE_SIZE_KB * 1024:
                  raise forms.ValidationError('Maximum permitted attachment size is: %d kB.' % settings.MAX_ATTACHMENT_FILE_SIZE_KB)
  
          return self.cleaned_data['attachment_document']
  </code></pre>