How to load an initial value for a ClearableFileInput / FileField (before form is bound)

Viewed 1287

How do I set an initial value for a FileField that will display in a ClearableFileInput widget when creating a new object (unbound form)?

I have tried the following but widget.value does not return a FeildFile instance if it is the first time the user is creating the object:

models.py

class MyModel(models.Model):
    myfile=models.FileField()

forms.py

class MyForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        self.fields['myfile'].initial= 'myfile.pdf'

    class Meta:
        model = Issuer
        fields = ['myfile']

This results in :

enter image description here

Similarly, setting a default value in the modelfield does not work:

class MyModel(models.Model):
    myfile=models.FileField(default='myfile.pdf')

The widget initial value is still None, but if it is left empty and saved the file object myfile.pdf will be created. The settings.MEDIA_URL and urls.py is definitely correct and the file is on the system because it is loaded after form save.

What I am missing is showing it as an initial value before a form is saved and an object created.

This answer suggests you can't provide initial data but you can provide an initial value with a url attribute to fake the appearance of a file. It's not clear how you would do this though.

Trying to create an initial file object in the form also returns widget.value = None

class MyForm(forms.ModelForm):

    f_path = os.path.join(settings.BASE_DIR + settings.MEDIA_URL, 'myfile.pdf')

    f = open(f_path)
    myfile = File(f)

    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        self.fields['privacy_policy'].initial = self.myfile
4 Answers

Your last block of code is quite close.

You are actually telling the user through the ClearableFileInput widget that if the user does not input anything in the file input, the initial value will be your pdf file.

But, this is only for display, you have to actually put the logic when you save the instance! (FileField won't do it for you) So the right thing is to override the save() method of your form, and in the case of a creation, put the actual file as the value. Like this:

class MyForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        path = os.path.join(settings.MEDIA_ROOT, 'myfile.pdf')

        self.default_file = open(path)

        super(MyForm, self).__init__(*args, **kwargs)
        self.fields['privacy_policy'].initial = File(self.default_file)
        self.fields['privacy_policy'].initial.url = default_storage.url('myfile.pdf')

    def save(self, commit=True):
        # Actually put the default file as a value if no input for creation
        if not self.instance.pk and not self.cleaned_data.get('privacy_policy'):
            self.instance.privacy_policy = self.default_file
            self.instance.privacy_policy._committed = True
        super().save(commit)

You can't. The only way to set the value of a file input is by the user to select it. This is done for security reasons. Otherwise, you would be able to create a Javascript that automatically uploads a specific file from the client's computer. it has nothing to with Django (model, form) its a browser feature, we only do the things with Django form if it is possible in Html and javascript.

your form:

class MyForm(forms.ModelForm):

f_path = os.path.join(settings.BASE_DIR + settings.MEDIA_URL, 'myfile.pdf')

f = open(f_path)
myfile = File(f)

def __init__(self, *args, **kwargs):
    super(MyForm, self).__init__(*args, **kwargs)
    self.fields['privacy_policy'].value = self.myfile

the default ClearableFileField template is

{% if widget.is_initial %}{{ widget.initial_text }}: <a href="{{ widget.value.url }}">{{ widget.value }}</a>{% if not widget.required %}
<input type="checkbox" name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}">
<label for="{{ widget.checkbox_id }}">{{ widget.clear_checkbox_label }}</label>{% endif %}<br>
{{ widget.input_text }}:{% endif %}
<input type="{{ widget.type }}" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>

The widget is_initial should be true to see the link.

class ClearableFileInput(FileInput):
    ...

    def is_initial(self, value):
        """
        Return whether value is considered to be initial value.
        """
        return bool(value and getattr(value, 'url', False))

     ....

I have managed set a ClearableFileInput by creating a model with the custom url and then setting specifying that model as the instance.

In your example it would be something like this (untested):

model = MyModel(myfile='abc.txt')
form = MyForm(instance=model)

However, I have found that if you have FILES included in the form (i.e.: MyForm(request.POST, request.FILES, instance=model)` the FILES takes precedence.

I have found this very finicky, so hope this helps.

From Django Documentation

FileField.upload_to

This attribute provides a way of setting the upload directory and file name, and can be set in two ways. In both cases, the value is passed to the Storage.save() method.

If you specify a string value or a Path, it may contain strftime() formatting, which will be replaced by the date/time of the file upload (so that uploaded files don’t fill up the given directory). For example:

class MyModel(models.Model):
    # file will be uploaded to MEDIA_ROOT/uploads
    upload = models.FileField(upload_to='uploads/')
    # or...
    # file will be saved to MEDIA_ROOT/uploads/2015/01/30
    upload = models.FileField(upload_to='uploads/%Y/%m/%d/')

For example, say your MEDIA_ROOT is set to '/home/media', and upload_to is set to 'photos/%Y/%m/%d'. The '%Y/%m/%d' part of upload_to is strftime() formatting; '%Y' is the four-digit year, '%m' is the two-digit month and '%d' is the two-digit day. If you upload a file on Jan. 15, 2007, it will be saved in the directory /home/media/photos/2007/01/15.

Related