问题:Django-如何创建文件并将其保存到模型的FileField中?
这是我的模特。我想要做的是生成一个新文件,并在保存模型实例时覆盖现有文件:
class Kitten(models.Model):
claw_size = ...
license_file = models.FileField(blank=True, upload_to='license')
def save(self, *args, **kwargs):
#Generate a new license file overwriting any previous version
#and update file path
self.license_file = ???
super(Request,self).save(*args, **kwargs)
我看到很多有关如何上传文件的文档。但是,如何生成文件,将其分配给模型字段并将Django存储在正确的位置呢?
Here’s my model. What I want to do is generate a new file and overwrite the existing one whenever a model instance is saved:
class Kitten(models.Model):
claw_size = ...
license_file = models.FileField(blank=True, upload_to='license')
def save(self, *args, **kwargs):
#Generate a new license file overwriting any previous version
#and update file path
self.license_file = ???
super(Request,self).save(*args, **kwargs)
I see lots of documentation about how to upload a file. But how do I generate a file, assign it to a model field and have Django store it in the right place?
回答 0
您想看看Django文档中的FileField和FieldFile,尤其是FieldFile.save()。
基本上,FileField
在访问时声明为的字段为您提供class的实例FieldFile
,该实例为您提供了几种与基础文件进行交互的方法。因此,您需要做的是:
self.license_file.save(new_name, new_contents)
new_name
您要分配的文件名在哪里,并且new_contents
是文件的内容。请注意,new_contents
该实例必须是django.core.files.File
或的一个实例django.core.files.base.ContentFile
(有关详细信息,请参见给定的手册链接)。这两个选择可以归结为:
# Using File
f = open('/path/to/file')
self.license_file.save(new_name, File(f))
# Using ContentFile
self.license_file.save(new_name, ContentFile('A string with the file content'))
You want to have a look at FileField and FieldFile in the Django docs, and especially FieldFile.save().
Basically, a field declared as a FileField
, when accessed, gives you an instance of class FieldFile
, which gives you several methods to interact with the underlying file. So, what you need to do is:
self.license_file.save(new_name, new_contents)
where new_name
is the filename you wish assigned and new_contents
is the content of the file. Note that new_contents
must be an instance of either django.core.files.File
or django.core.files.base.ContentFile
(see given links to manual for the details). The two choices boil down to:
# Using File
f = open('/path/to/file')
self.license_file.save(new_name, File(f))
# Using ContentFile
self.license_file.save(new_name, ContentFile('A string with the file content'))
回答 1
接受的答案当然是一个很好的解决方案,但是这就是我生成CSV并从视图中提供它的方法。
我认为这是值得的,因为我花了点时间来摆弄所有想要的行为(覆盖现有文件,存储到正确的位置,不创建重复的文件等)。
的Django 1.4.1
Python 2.7.3
#Model
class MonthEnd(models.Model):
report = models.FileField(db_index=True, upload_to='not_used')
import csv
from os.path import join
#build and store the file
def write_csv():
path = join(settings.MEDIA_ROOT, 'files', 'month_end', 'report.csv')
f = open(path, "w+b")
#wipe the existing content
f.truncate()
csv_writer = csv.writer(f)
csv_writer.writerow(('col1'))
for num in range(3):
csv_writer.writerow((num, ))
month_end_file = MonthEnd()
month_end_file.report.name = path
month_end_file.save()
from my_app.models import MonthEnd
#serve it up as a download
def get_report(request):
month_end = MonthEnd.objects.get(file_criteria=criteria)
response = HttpResponse(month_end.report, content_type='text/plain')
response['Content-Disposition'] = 'attachment; filename=report.csv'
return response
Accepted answer is certainly a good solution, but here is the way I went about generating a CSV and serving it from a view.
Thought it was worth while putting this here as it took me a little bit of fiddling to get all the desirable behaviour (overwrite existing file, storing to the right spot, not creating duplicate files etc).
Django 1.4.1
Python 2.7.3
#Model
class MonthEnd(models.Model):
report = models.FileField(db_index=True, upload_to='not_used')
import csv
from os.path import join
#build and store the file
def write_csv():
path = join(settings.MEDIA_ROOT, 'files', 'month_end', 'report.csv')
f = open(path, "w+b")
#wipe the existing content
f.truncate()
csv_writer = csv.writer(f)
csv_writer.writerow(('col1'))
for num in range(3):
csv_writer.writerow((num, ))
month_end_file = MonthEnd()
month_end_file.report.name = path
month_end_file.save()
from my_app.models import MonthEnd
#serve it up as a download
def get_report(request):
month_end = MonthEnd.objects.get(file_criteria=criteria)
response = HttpResponse(month_end.report, content_type='text/plain')
response['Content-Disposition'] = 'attachment; filename=report.csv'
return response
回答 2
close()
在文件保存过程中,如果有异常情况,最好使用上下文管理器或进行调用。如果您的存储后端关闭等情况,可能会发生。
任何覆盖行为都应在存储后端中配置。例如S3Boto3Storage有一个设置AWS_S3_FILE_OVERWRITE
。如果您正在使用FileSystemStorage
,则可以编写自定义mixin。
如果您希望发生任何自定义的副作用(例如最近更新的时间戳记),则可能还需要调用模型的save方法而不是FileField的save方法。如果是这种情况,您还可以将文件的name属性设置为文件的名称-相对于MEDIA_ROOT
。它默认为文件的完整路径,如果不设置它,可能会引起问题-请参见File .__ init __()和File.name。
这是一个示例实例,其中self
是模型实例,其中my_file
是FileField / ImageFile,它调用save()
整个模型实例,而不仅仅是FileField:
import os
from django.core.files import File
with open(filepath, 'rb') as fi:
self.my_file = File(fi, name=os.path.basename(fi.name))
self.save()
It’s good practice to use a context manager or call close()
in case of exceptions during the file saving process. Could happen if your storage backend is down, etc.
Any overwrite behavior should be configured in your storage backend. For example S3Boto3Storage has a setting AWS_S3_FILE_OVERWRITE
. If you’re using FileSystemStorage
you can write a custom mixin.
You might also want to call the model’s save method instead of the FileField’s save method if you want any custom side-effects to happen, like last-updated timestamps. If that’s the case, you can also set the name attribute of the file to the name of the file – which is relative to MEDIA_ROOT
. It defaults to the full path of the file which can cause problems if you don’t set it – see File.__init__() and File.name.
Here’s an example where self
is the model instance where my_file
is the FileField / ImageFile, calling save()
on the whole model instance instead of just FileField:
import os
from django.core.files import File
with open(filepath, 'rb') as fi:
self.my_file = File(fi, name=os.path.basename(fi.name))
self.save()