Create a Mongoengine FileField from a GridFs file_id or GridFSProxy - python

I have a string file_id of my file that is stored in mongodb in a fs collection (GridFs).
I need to store the file as a mongoengine FileField in a Document, and then return the file to an endpoint ... so access the file's content, content_type etc.
I am not sure how to create a FileField instance with the GridFs string id? And is it possible to get the content and content_type from the FileField?
The tutorials that I have seen all involve creating the FileField by writing the contents to mongodb, the difference is my contents are already in the GridFs and I have the string id.
class Test(Document):
file = FileField()
test = Test()
test.upload.put(image_file, content_type='image/png')
So far I have been able to create a GridFsProxy object using the id, and can read the file using this.
class Test(Document):
file = FileField()
file_id = StringField() # bb2832e0-2ca4-44bc-8b1b-e01a77003b92
file_proxy = GridFSProxy(Test.file_id)
file_proxy.read() # Gives me the file content
file_proxy.get(file_id).content_type #can return name, length etc.
test = Test()
test.file = file_proxy.read() # in mongodb I see it as an ObjectID
If i store the read() results of the GridFSProxy into the FileField(); it is stored in MongoDb as an ObjectID and then later when I retrieve the object I don't seem to be able to get the content_type of the file.
I need the content_type as it is important for how I return the file contents.
I'm not sure how to create the FileField using just the file_id and then use it when i retrieve the Document.
Any insight into using the FileField (and GridFSProxy) will be helpful.

The FileField is basically just a reference (ObjectId) pointing to the actual grid fs documents (stored in fs.chunks/fs.files). Accessing the content_type should be straightforward, you shouldn't have to use the GridFSProxy class at all, see below:
from mongoengine import *
class Test(Document):
file = FileField()
test = Test()
image_bytes = open("path/to/image.png", "rb")
test.file.put(image_bytes, content_type='image/png', filename='test123.png')
test.save()
Test.objects.as_pymongo() # [{u'_id': ObjectId('5cdac41d992db9bfcaa870df'), u'file': ObjectId('5cdac419992db9bfcaa870dd')}]
t = Test.objects.first()
t.file # <GridFSProxy: 5cdac419992db9bfcaa870dd>
t.file.content_type # 'image/png'
t.file.filename # 'test123.png'
content = t.file.read()

Related

Uploading a file in django database in a specific root with diferrent folders everytime

I´m trying to upload file into database, but when i want put the function uoload to and i want to that function store the file in a root with the data the user submit in the for, for example year, course, section, I get that information and the file they uploaded I want to store in that subfolders, the code that I´m using only store the data in the Media_root, but not in the carpets how can I do in order to store the files in the subfolder I want. I'n my model document I store the information of the document, like the autor, the title,etc.
class File(models.Model):
titulo_file=models.ForeignKey(Document,on_delete=models.CASCADE,null=True,verbose_ name='Título de la Tesis')
file = models.FileField(null=True,blank=True, upload_to=generate_path)
Here the function I use to upload the file, Int only works to store the file into a folder with it´s same name.
def generate_path(instance, filename):
path =os.path.join(instance.file.name,filename)
return path
In my view, after the user submit the information, I use a os.path.join, that do the path with the information submited like this:
year/course/section
I want to send that path to my generate_path and store in that location the file, I tried with session but it doesn't work, what can I do?
models.py
class Year(models.Model):
year_number=models.CharField(max_length=10,primary_key=True)
class Course(models.Model):
name=models.CharField(max_length=50,primary_key=True)
class Section(models.Model):
numberSection=models.IntegerField(null=True,blank=True)
year = models.ForeignKey(Year, on_delete=models.CASCADE, null=True)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
thopic_documents=(
('CS','CS'),
('SE','SE'),
('IS','IS'),
('IT','IT'),
)
class Document(models.Model):
title=models.CharField(max_length=200,primary_key=True)
thopic=models.CharField(max_length=50,choices=thopic_documents, default=None,null=True)
class Historial(models.Model):
id_section=models.ForeignKey(Section,on_delete=models.CASCADE)
title_document=models.ForeignKey(Document,on_delete=models.CASCADE,)
To get the year/course/section you need to be able to navigate from a File to a Section. However, the many-many relationship that exists between Document and Section through Historical makes it unclear how you would do that. Which Historical would you choose?
It may be better if File was linked directly to Historical:
class File(models.Model):
titulo_file=models.ForeignKey(Historical, on_delete=models.CASCADE,null=True,verbose_name='Título de la Tesis')
file = models.FileField(null=True,blank=True, upload_to=generate_path)
If that was the case, you could then implement your generate_path() such as:
def generate_path(instance, filename):
section = instance.titulo_file.id_section
year = section.year.year_number
course = section.course.name
return os.path.join(str(year), course, str(section.numberSection), filename)
To do the same thing with the model as it currently stands, you would have to do something like this:
def generate_path(instance, filename):
section = instance.titulo_file.historical_set.first().id_section
year = section.year.year_number
course = section.course.name
return os.path.join(str(year), course, str(section.numberSection), filename)
That example uses historical_set.first() to get the first Historical linked the Document. Maybe that would be ok, but otherwise you'd need to know which Historical to use.
Where a file with the same name is uploaded and you don't want to overwrite it, you could implement your own storage:
from django.core.files.storage import FileSystemStorage
class UseExistingStorage(FileSystemStorage):
def save(self, name, content, max_length=None):
if not self.exists(name):
return super().save(name, content, max_length)
return name # Don't save when the file exists, just return the name
Then reference the UseExistingStorage from your File model.
file = models.FileField(null=True,blank=True, upload_to=generate_path, storage=UseExistingStorage())

Handle File upload, resize with Pillow, Store with SQLAlchemy, Serve File with Flask

So I am trying to handle a file upload then store that file as a binary into the database. After I store it I try to serve the file at a given URL. I can't seem to find a method that works here. I need to use a database because I am hosting with Google app engine. Here is a few relevant parts of my code.
Serving Handler:
#app.route('/photo/<path:filename>')
def uploaded_photo(filename):
try:
photo = db_session.query(Photo)\
.filter_by(filename=filename).one()
except:
return 'There was an error with the photo.'
return send_file(
io.BytesIO(photo.image_blob),
attachment_filename=photo.filename,
mimetype='image/jpeg')
How I store my image using Pillow.
Photo(
filename=filename,
image_blob=image.tobytes()))
The Photo Model:
class Photo(Base):
__tablename__ = 'photos'
id = Column(Integer, primary_key=True)
item_id = Column(Integer, ForeignKey('item.id'))
filename = Column(String)
image_blob = Column(LargeBinary)
item = relationship("Item", back_populates="photos")
What happens is that when you load the Image url, /photo/. A blank photo appears. However, the upload part goes smoothly.
Dont store images on the database rather use a static folder where you can save the files. just save the documents name and details on the database for file retrieval.
You can check this documentation. link for uploading.... Here is for downloading... link

Convert blob to Django ImageField in python?

I read user image in BLOB file, but i want save it to image format in django model.
How can i convert this file to image file(.jpeg) and save it in django models.ImageField?
I use python 2.7 and django 1.9.
my model is:
class Staff(models.Model):
user = models.ForeignKey(User)
cn = models.CharField(max_length=100)
new_comer = models.NullBooleanField()
change_position = models.NullBooleanField()
change_manager = models.NullBooleanField()
acting = models.NullBooleanField()
expelled = models.NullBooleanField()
active = models.NullBooleanField()
avatar = models.ImageField(upload_to='/images/')
Please help me...
You need to try something like this.
import io
from django.core.files.base import File
# Set values of your model filed.
staff_instance = Staff()
staff_instance.user = user_instance
...
...
with io.BytesIO(blob_file) as stream:
django_file = File(stream)
staff_instance.avatar.save(some_file_name, django_file)
staff_instance.save()
I assume that blob is a byte array of the file.
To make it a file i need to convert it to a stream.
Thus I thought BytesIO would be a good choice.
You can directly save file to disk but if you want django to upload it to your upload_to directory, you need to use django.core.files.base.File class.
When you run django.core.files.base() method file will be saved to desired directory.
I guess you will use this for an data migration process, not in a view.
If this is the case than you could put this code at a django command.
Then you can use any django and project related resources.
Let me know if it helps.

Adding FieldFile objects without form in django

I have a django model as follow:
class XML(ExtensibleModel):
xml = models.FileField(upload_to='xml',blank=True, null=True)
Here, I store some xmls files. Before, I submited the files to my server by a html form. Now, I copy the files by ssh, and I want to keep storing the new files in this model. The problem is that I can't do it. I tried with the follow code
f = open(FILENAME,'r')
A = XML(xml = f)
A.save()
but, I get this error:
'file' object has no attribute '_committed'
Any idea?
Try using a django file instead of just an open file.
from django.core.files import File
...
f = open(FILENAME,'r')
A=XML()
A.xml.save(filename, File(f), save=True)
A.save()

FileField in Tastypie

I have a model for which I am making an api in tastypie. I have a field which stores the path to a file which I maintain manually (I am not using FileField since users are not uploading the files). Here is a gist of a model:
class FooModel(models.Model):
path = models.CharField(max_length=255, null=True)
...
def getAbsPath(self):
"""
returns the absolute path to a file stored at location self.path
"""
...
Here is my tastypie config:
class FooModelResource(ModelResource):
file = fields.FileField()
class Meta:
queryset = FooModel.objects.all()
def dehydrate_file(self, bundle):
from django.core.files import File
path = bundle.obj.getAbsPath()
return File(open(path, 'rb'))
In the api in the file field this returns full path to a file. I want tastypie to be able to serve the actual file or at least an url to a file. How do I do that? Any code snippets are appreciated.
Thank you
Decide on a URL scheme how your files will be exposed through the APIs first. You don't really need file or dehydrate_file (unless you want to change the representation of the file for the model itself in Tastypie). Instead just add an additional action on the ModelResource. Example:
class FooModelResource(ModelResource):
file = fields.FileField()
class Meta:
queryset = FooModel.objects.all()
def override_urls(self):
return [
url(r"^(?P<resource_name>%s)/(?P<pk>\w[\w/-]*)/download%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('download_detail'), name="api_download_detail"),
]
def download_detail(self, request, **kwargs):
"""
Send a file through TastyPie without loading the whole file into
memory at once. The FileWrapper will turn the file object into an
iterator for chunks of 8KB.
No need to build a bundle here only to return a file, lets look into the DB directly
"""
filename = self._meta.queryset.get(pk=kwargs[pk]).file
wrapper = FileWrapper(file(filename))
response = HttpResponse(wrapper, content_type='text/plain') #or whatever type you want there
response['Content-Length'] = os.path.getsize(filename)
return response
GET .../api/foomodel/3/
Returns:
{
...
'file' : 'localpath/filename.ext',
...
}
GET .../api/foomodel/3/download/
Returns:
...contents of actual file...
Alternatively you could create a non-ORM Sub Resource file in FooModel. You would have to define resource_uri (how to uniquely identify each instance of the resource), and override dispatch_detail to do exactly what download_detail above does.
The only conversion tastypie does on a FileField is to look for an 'url' attribute on what you return, and return that if it exists, else it will return the string-ized object, which as you have noticed is just the filename.
If you want to return the file content as a field, you will need to handle the encoding of the file. You have a few options:
Simplest: Use CharField and use the base64 module to convert the bytes read from the file into a string
More general but functionally equivalent: write a custom tastypie Serializer that knows how to turn File objects into string representations of their contents
Override the get_detail function of your resource to serve just the file using whatever content-type is appropriate, to avoid the JSON/XML serialization overhead.

Categories