How to associate a generated file with a Django model - django

I want to create a file and associate it with the FileField of my model. Here's my simplified attempt:
#instantiate my form with the POST data
form = CSSForm(request.POST)
#generate a css object from a ModelForm
css = form.save(commit=False)
#generate some css:
css_string = "body {color: #a9f;}"
#create a css file:
filename = "myfile.css"
#try to write the file and associate it with the model
with open(filename, 'wb') as f:
df = File(f) #create django File object
df.write(css_string)
css.css_file = df
css.save()
The call to save() throws a "seek of closed file" exception. If I move the save() to the with block, it produces an unsupported operation "read". At the moment, the files are being created in my media directory, but are empty. If I just render the css_string with the HttpResponse then I see the expected css.
The docs don't seem to have an example on how to link a generated file and a database field. How do I do this?

Django FileField would either be a django.core.files.File, which is a file instance or django.core.files.base.ContentFile, which takes a string as parameter and compose a ContentFile. Since you already had the file content as a string, sounds like ContentFile is the way to go(I couldn't test it but it should work):
from django.core.files.base import ContentFile
# create an in memory instance
css = form.save(commit=False)
# file content as string
css_string = "body {color: #a9f;}"
# create ContentFile instance
css_file = ContentFile(css_string)
# assign the file to the FileField
css.css_file.save('myfile.css', css_file)
css.save()
Check django doc about FileField details.

Related

Django SuspiciousFileOperation

I have a model that contains a FileField:
class Foo(models.Model):
fileobj = models.FileField(upload_to="bar/baz")
I am generating a file, and saving it in /tmp/ as part of the save method. This file then needs to be set as the "fileobj" of the model instance.
Currently, I'm trying this:
with open(
f"/tmp/{self.number}.pdf", "r"
) as h:
self.fileobj = File(h)
Unfortunately, this fails with: django.core.exceptions.SuspiciousFileOperation:, because the file exists outside of the django project.
I've tried reading the docs, but they didn't help much. Does django take a file, and upon assigning it as a FileField, move it to the media directory, or do I need to manually put it there myself, before attaching it to the model instance. If the second case, what is the point of "upload_to"?
You can use InMemoryUploadedFile object like this.
import os
import io
with open(path, 'rb') as h:
f = InMemoryUploadedFile(io.BytesIO(h.read()), 'fileobj',
'name.pdf', 'application/pdf',
os.path.getsize(path), None)
self.fileobj = f

How to validate contents of a CSV file using Django forms

I have a web app that needs to do the following:
Present a form to request a client side file for CSV import.
Validate the data in the CSV file or ask for another filename.
At one point, I was doing the CSV data validation in the view, after the form.is_valid() call from getting the filename (i.e. I have the imported CSV file into memory in a dictionary using csv.DictReader). After running into problems trying to pass errors back to the original form, I'm now trying to validate the CONTENTS of the CSV file in the form's clean() method.
I'm currently stumped on how to access the in memory file from clean() as the request.FILES object isn't valid. Note that I have no problems presenting the form to the client browser and then manipulating the resulting CSV file. The real issue is how to validate the contents of the CSV file - if I assume the data format is correct I can import it to my models. I'll post my forms.py file to show where I currently am after moving the code from the view to the form:
forms.py
import csv
from django import forms
from io import TextIOWrapper
class CSVImportForm(forms.Form):
filename = forms.FileField(label='Select a CSV file to import:',)
def clean(self):
cleaned_data = super(CSVImportForm, self).clean()
f = TextIOWrapper(request.FILES['filename'].file, encoding='ASCII')
result_csvlist = csv.DictReader(f)
# first line (only) contains additional information about the event
# let's validate that against its form definition
event_info = next(result_csvlist)
f_eventinfo = ResultsForm(event_info)
if not f_eventinfo.is_valid():
raise forms.ValidationError("Error validating 1st line of data (after header) in CSV")
return cleaned_data
class ResultsForm(forms.Form):
RESULT_CHOICES = (('Won', 'Won'),
('Lost', 'Lost'),
('Tie', 'Tie'),
('WonByForfeit', 'WonByForfeit'),
('LostByForfeit', 'LostByForfeit'))
Team1 = forms.CharField(min_length=10, max_length=11)
Team2 = forms.CharField(min_length=10, max_length=11)
Result = forms.ChoiceField(choices=RESULT_CHOICES)
Score = forms.CharField()
Event = forms.CharField()
Venue = forms.CharField()
Date = forms.DateField()
Div = forms.CharField()
Website = forms.URLField(required=False)
TD = forms.CharField(required=False)
I'd love input on what's the "best" method to validate the contents of an uploaded CSV file and present that information back to the client browser!
I assume that when you want to access that file is in this line inside the clean method:
f = TextIOWrapper(request.FILES['filename'].file, encoding='ASCII')
You can't use that line because request doesn't exist but you can access your form's fields so you can try this instead:
f = TextIOWrapper(self.cleaned_data.get('filename'), encoding='ASCII')
Since you have done super.clean in the first line in your method, that should work. Then, if you want to add custom error message to you form you can do it like this:
from django.forms.util import ErrorList
errors = form._errors.setdefault("filename", ErrorList())
errors.append(u"CSV file incorrect")
Hope it helps.

django RequestFactory file upload

I try to create a request, using RequestFactory and post with file, but I don't get request.FILES.
from django.test.client import RequestFactory
from django.core.files import temp as tempfile
tdir = tempfile.gettempdir()
file = tempfile.NamedTemporaryFile(suffix=".file", dir=tdir)
file.write(b'a' * (2 ** 24))
file.seek(0)
post_data = {'file': file}
request = self.factory.post('/', post_data)
print request.FILES # get an empty request.FILES : <MultiValueDict: {}>
How can I get request.FILES with my file ?
If you open the file first and then assign request.FILES to the open file object you can access your file.
request = self.factory.post('/')
with open(file, 'r') as f:
request.FILES['file'] = f
request.FILES['file'].read()
Now you can access request.FILES like you normally would. Remember that when you leave the open block request.FILES will be a closed file object.
I made a few tweaks to #Einstein 's answer to get it to work for a test that saves the uploaded file in S3:
request = request_factory.post('/')
with open('my_absolute_file_path', 'rb') as f:
request.FILES['my_file_upload_form_field'] = f
request.FILES['my_file_upload_form_field'].read()
f.seek(0)
...
Without opening the file as 'rb' I was getting some unusual encoding errors with the file data
Without f.seek(0) the file that I uploaded to S3 was zero bytes
You need to provide proper content type, proper file object before updating your FILES.
from django.core.files.uploadedfile import File
# Let django know we are uploading files by stating content type
content_type = "multipart/form-data; boundary=------------------------1493314174182091246926147632"
request = self.factory.post('/', content_type=content_type)
# Create file object that contain both `size` and `name` attributes
my_file = File(open("/path/to/file", "rb"))
# Update FILES dictionary to include our new file
request.FILES.update({"field_name": my_file})
the boundary=------------------------1493314174182091246926147632 is part of the multipart form type. I copied it from a POST request done by my webbrowser.
All the previous answers didn't work for me. This seems to be an alternative solution:
from django.core.files.uploadedfile import SimpleUploadedFile
with open(file, "rb") as f:
file_upload = SimpleUploadedFile("file", f.read(), content_type="text/html")
data = {
"file" : file_upload
}
request = request_factory.post("/api/whatever", data=data, format='multipart')
Be sure that 'file' is really the name of your file input field in your form.
I got that error when it was not (use name, not id_name)

Django: Copy FileFields

I'm trying to copy a file using a hardlink, where the file is stored as a Django FileField. I'd like to use a hardlink to save space and copy time (no changes are expected to be made to the original file or copy). However, I'm getting some odd errors when I try to call new_file.save() from the snippet below.
AttributeError: 'file' object has no attribute '_committed'
My thinking is that after making the hardlink, I can just open the linked file and store it to the Django new File instance's FileFile. Am I missing a step here or something?
models.py
class File(models.Model):
stored_file = models.FileField()
elsewhere.py
import os
original_file = File.objects.get(id=1)
original_file_path = original_file.file.path
new_file = File()
new_file_path = '/path/to/new/file'
os.makedirs(os.path.realpath(os.path.dirname(new_file_path)))
os.link(original_file_path, new_file_path)
new_file.stored_file = file(new_file_path)
new_file.save()
There is no need to create hardlink, just duplicate the file holder:
new_file = File(stored_file=original_file.stored_file)
new_file.save()
update
If you want to specify file to FileField or ImageField, you could simply
new_file = File(stored_file=new_file_path)
# or
new_file = File()
new_file.stored_file = new_file_path
# or
from django.core.files.base import File
# from django.core.files.images import ImageFile # for ImageField
new_file.stored_file = File(new_file_path)
the field accepts path in basestring or File() instance, the code in your question uses file() and hence is not accepted.
I think I solved this issue, but not sure why it works. I wrapped the file object in a "DjangoFile" class (I imported as DjangoFile to avoid clashing with my previously defined File model).
from django.core.files.base import File as DjangoFile
...
new_file.stored_file = DjangoFile(file(new_file_path))
new_file.save()
This approached seemed to save the file OK.

using csvimpoter in django

I want to import the entire csv file in a model without reading row by row from the file.Please help me on this by providing a example model and a source code to import.
If you're opening the file from disk, you can wrap your file object in django.core.files.File and pass it to the save method of the model field you're saving it to:
from django.core.files import File
csv_file = open("sample.csv", "rb")
csv_file = File(csv_file)
my_model_instance.my_file_field.save("sample.csv", csv_file)
See https://docs.djangoproject.com/en/1.3/ref/files/file/#additional-methods-on-files-attached-to-objects
If you're dealing with an uploaded file from request.FILES, you can assign it directly to your model instance's FileField:
my_model_instance.my_file = request.FILES["csvfile"]
my_model_instance.save()
Don't forget enctype="multipart/form-data" on the form or request.FILES will be empty.