-->

django file upload - image deleted when edit templ

2019-09-03 20:36发布

问题:

I am using django to allow a user to upload images along with text to describe the image title.

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.

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).

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.

It seems the user must 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.

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?

Here is my models.py code:

EDIT # 2 - added FillableModelWithLanguageVersion & LanguageVersion

# 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')

Here is my views.py 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)

I have a related post here.

edit

Here is my forms.py 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 > 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 > 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']

回答1:

Your problem comes from the fill method of your model. It always sets attachment_document. In order to prevent that - try this:

    if form.is_valid():
        cd = form.cleaned_data
        attachment_document = cd.pop('attachment_document')
        if attachment_document:
            #  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.attachment_document = attachment_document
        attachment_details.fill(cd)
        attachment_details.save()
        messages.success(request, _('successfully updated.'))
        return redirect(settings.MENU_DETAIL_LINK_ATTACHMENT_DETAILS)


回答2:

Didn't try this but it looks like you didn't set the instance argument when posting edited form. You could try it like this and then simply call the save method on the form:

elif request.method == 'POST':
    form = AttachmentDetailsForm(
        available_languages,
        language_preference,
        False,  # file_required
        request.POST,
        request.FILES,
        instance=attachment_details
    )
    if form.is_valid():
        form.save()
        messages.success(request, _('successfully updated.'))
        return redirect(settings.MENU_DETAIL_LINK_ATTACHMENT_DETAILS)