what else do I need to add to that "file = models.FileField()"
this is what I have done but am still not getting any results, why that?
class Course(models.Model):
TOPIC_CHOICES = (
("History", "History"),
("Chemistry", "Chemistry"),
("Computer", "Computer")
)
lecturer = models.ForeignKey(Lecturer, on_delete=models.CASCADE)
category = models.CharField(choices=TOPIC_CHOICES, max_length=100)
topic = models.CharField(max_length=250)
file = models.FileField()
date_created = models.DateTimeField(default=datetime.now)
def __str__(self):
return f"{self.lecturer}: {self.topic}"
According to Django documentation, FileField takes two optional arguments.
upload_to: Sets the upload directory. The value of this argument can have several types. It can be String, Path, or a callable function. Here is an example:
upload = models.FileField(upload_to='uploads/')
If you want to define a function for this argument which returns the upload directory, you have to define it based on Django's specification of such function. The function should have the instance and filename arguments. Here is an example:
def user_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
return 'user_{0}/{1}'.format(instance.user.id, filename)
class MyModel(models.Model):
upload = models.FileField(upload_to=user_directory_path)
storage: A storage object, or a callable which returns a storage object. This argument is used to specify a storage setting for your file-upload field. This argument enables you to choose the appropriate storage environment at runtime.
from django.conf import settings
from django.db import models
from .storages import MyLocalStorage, MyRemoteStorage
def select_storage():
return MyLocalStorage() if settings.DEBUG else MyRemoteStorage()
class MyModel(models.Model):
my_file = models.FileField(storage=select_storage)
Another use-case of this argument is having different storage environments for different types of files.
from django.conf import settings
from django.db import models
from .storages import LargeFilesStorage
class MyModel(models.Model):
my_file = models.FileField(storage=LargeFilesStorage())
As these arguments are optional, you can instantiate a FileField without them. The default values for these arguments are: upload_to='', storage=None
Related
Consider the following model:
STOCK_IMAGE_DIR = os.path.join(settings.MEDIA_ROOT, 'stock_images')
class Product(models.Model):
stock_image = models.FilePathField(path=STOCK_IMAGE_DIR, default='image.png')
When accessing the stock_image of a product, it only returns the name of the image:
>>> p = Product.objects.first()
>>> p.stock_image
'image.png'
How can I output the entire path of the file?
import os
p = Product.objects.first()
print(os.path.join(Product._meta.get_field('stock_image').path, p.stock_image))
Or
print(os.path.join(STOCK_IMAGE_DIR, p.stock_image))
Or, if you store the path in the class you can add a property for getting the full path
class Product(models.Model):
STOCK_IMAGE_DIR = os.path.join(settings.MEDIA_ROOT, 'stock_images')
stock_image = models.FilePathField(path=STOCK_IMAGE_DIR, default='image.png')
#property
def stock_image_path(self):
return os.path.join(self.STOCK_IMAGE_DIR, self.stock_image)
Then you can just use the property
p = Product.objects.first()
print(p.stock_image_path)
While #IainShelvington provides a solution to obtain the path, you might want to use a FileField field [Django-doc], or if you work with images, like the name stock_image suggests, with an ImageField field [Django-doc].
If you model this like:
STOCK_IMAGE_DIR = os.path.join(settings.MEDIA_ROOT, 'stock_images')
class Product(models.Model):
stock_image = models.ImageField(upload_to=STOCK_IMAGE_DIR, default='image.png')
then the some_product.stock_image will be an ImageFieldFile [GitHub]. This is an object that mimics a File, and has for example a path attribute to obtain the path where the file is stored:
some_product.stock_image.path
Furthermore a FileField and ImageField make it more convenient to work with forms where you upload images, and furthermore these have a .url attribute to obtain the URL such that the server can serve media files.
I am uploading two files using django rest framework, i want to upload these two files into the same folder and create said folder with a random name.
So far I can upload both files to different random folders using the following:
from uuid import uuid4
def path_and_rename(path, dataset):
def wrapper(instance, filename):
main_folder = '{}/{}/'.format(path, uuid4().hex)
name = '{}.csv'.format(dataset)
return os.path.join(main_folder, name)
return wrapper
class Dataset(Model):
trainFile = FileField(null=False, blank=False,
validators=[FileExtensionValidator(['csv'])],
upload_to=path_and_rename('files/', 'train'))
testFile = FileField(null=False, blank=False,
validators=[FileExtensionValidator(['csv'])],
upload_to=path_and_rename('files/', 'test'))
class DatasetSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Dataset
fields = (
'id',
'trainFile',
'testFile',
)
read_only_fields = ('created',)
How could I get both files to be uploaded to the same random folder?
You are calling path_to_rename(), and in turn uuid4() on two separate occasions so you're going to get two random UUIDs. As a sidenote, for better code readability, your "wrapper" should be the outer function.
Try:
def upload_to_wrapper(upload_dir, dataset):
def path_and_rename(instance, filename):
return os.path.join(upload_dir, '{}.csv'.format(dataset))
return path_and_rename
class Dataset(Model):
upload_dir = 'files/{}'.format(uuid4().hex)
trainFile = FileField(null=False, blank=False,
validators=[FileExtensionValidator(['csv'])],
upload_to=upload_to_wrapper(upload_dir, 'train'))
testFile = FileField(null=False, blank=False,
validators=[FileExtensionValidator(['csv'])],
upload_to=upload_to_wrapper(upload_dir, 'test'))
Since the first FileField is required and the fields will be uploaded in order, you could use an alternate upload_to function for all of your fields apart from the first that relies on the initial upload path. Something like:
import posixpath
def dataset_file(dataset):
def wrapper(instance, filename):
main_folder = posixpath.split(instance.trainFile)[0]
name = '{}.csv'.format(dataset)
return posixpath.join(main_folder, name)
return wrapper
```
I am working on some Django project (first time) and after lot of searching i have no clue how to proper update object attribute after creation. I have sucha models.py
from django.db import models
import os
# Create your models here.
class Place(models.Model):
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Add templates folder for dynamic usage, os independent
TEMPLATE_DIR = os.path.join(BASE_DIR, "templates/places")
name = models.CharField(max_length=100, unique=True)
display_name = models.CharField(max_length=100)
floor = models.DecimalField(max_digits=3, decimal_places=0)
template = models.FilePathField(path=TEMPLATE_DIR, match=".*html")
url = models.URLField(unique=True)
# Display name of object in object list view
def __str__(self):
return self.name
Question is how to update url attribute after object creation of url to this particular object, it should be something like (base url + object_id i know object_id is created after object creation) but after searching documentation i dont have ideas how to do this properly.
I tried get_absolute_path but with no success.
Maybe some kind post_save method override ?
One option would be to override the model save method and detect if the model instance is being added or updated and update the url field accordingly:
class Place(models.Model):
# ...
def save(self, *args, **kwargs):
is_adding = self._state.adding
super(Place, self).save(self, *args, **kwargs)
if is_adding:
url = os.path.join(self.TEMPLATE_DIR, str(self.pk))
super(Place, self).save(self, *args, **kwargs)
However, you needn't actually store the url as you can derive the value from other model fields when required. Hence, you can delete the url field and create a url method instead:
class Place(models.Model):
# ...
#property
def url(self):
return os.path.join(self.TEMPLATE_DIR, str(self.pk))
Here, the #property decoration here allows you to access place.url as if it was a model field.
This might be a good time to introduce something like Django celery into you're project and run this task async after creation.
You could have a task in a file like this:
# I'm making assumptions on project architecture..
from app.places import Place
from app.places.utils import PLACE_BASE_URL
#shared_task
def task_update_place_url(place_id):
"""
Simple method to update Place url after creation.
"""
place = Place.objects.get(pk=place_id)
place.url = PLACE_BASE_URL + place_id
place.save()
Then inside of your view when you create your Place you can do this:
import json
from django.http.response import HttpResponse
from app.places.models import Place
from app.places.tasks import task_update_place_url
def create_place(request):
"""
Creates a new Place and update url in task
"""
data = json.loads(request)
place = Place.objects.create(**data)
task_update_place_url.delay(place.id)
return HttpResponse("Finished")
I'm working on a little Django project and I have a filefield in my models.py.
Everything works fine, but I don't like that Django is editing the filenames after upload. It changes spaces into underscores and it removes (square) brackets and stuff like that.
Is there any way to stop Django from doing this?
I can see that it is to make the website safer, more secure and also just avoid errors. But, I'm the only one who's going to be able to upload files anyway.
Hopefully someone knows if (and how) this is possible :)
Thanks!
edit:
Here's the FileField in the models.py:
file = models.FileField(upload_to=file_path)
Here's my upload_to:
def file_path(instance, filename):
extension = filename.split('.')[-1]
new_filename = '%s - %s.%s' % (str(instance.model.object_number), str(instance.model.object_name), str(extension))
return '/'.join(['files', str(instance.model.object_theme), str(instance.model.object_number), new_filename])
Yes, you can write your own upload_to function, as explained in the documentation: https://docs.djangoproject.com/en/1.9/ref/models/fields/#django.db.models.FileField.upload_to
def user_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
return 'user_{0}/{1}'.format(instance.user.id, filename)
class MyModel(models.Model):
upload = models.FileField(upload_to=user_directory_path)
If you want to avoid overwriting files with identical names, you have to add that functionality back. The default storage uses this function django.core.file.storage.get_availble_name()
You can read the source code on github
Edit: It looks as though your upload_to function doesn't return anything. It should return a pathname as a string.
import os
def file_path(instance, filename):
base, extension = os.path.splitext(filename)
return '{model.object_number} - {model.object_name}{extension}'.format(
model=instance.model, extension=extension)
You need to create your own storage class
mystorage.py
import re
from django.utils.encoding import force_text
from django.utils.functional import keep_lazy_text
from django.core.files.storage import FileSystemStorage
#keep_lazy_text
def get_valid_filename(s):
"""
>>> get_valid_filename(" tawanda's portrait in 2019.jpg ")
'tawandas portrait in 2019.jpg'
"""
s = force_text(s).strip()
return re.sub(r'(?u)[^-\w. ]', '', s)
class CleanFileNameStorage(FileSystemStorage):
def get_valid_name(self, name):
"""
Return a filename, based on the provided filename, that's suitable for
use in the target storage system.
"""
return get_valid_filename(name)
If you want to apply this storage to all models you can assign the Django setting DEFAULT_FILE_STORAGE with your custom class e.g
DEFAULT_FILE_STORAGE = 'mystorage.CleanFileNameStorage'
If this is only for a particular model then:
models.py
def myfile_save_to(instance, filename):
return 'my_files/{filename}'.format(filename=filename)
class MyFileModel(models.Model):
my_file = models.FileField(
upload_to=myfile_save_to,
storage=CleanFileNameStorage(),
)
Remember if you specify the storage on a model level you must initialize the class e.g. CleanFileNameStorage() if you don't you will get argument errors
I have a Django app where users upload photos and descriptions. Here's a typical model which facilitates that user behavior:
class Photo(models.Model):
description = models.TextField(validators=[MaxLengthValidator(500)])
submitted_on = models.DateTimeField(auto_now_add=True)
image_file = models.ImageField(upload_to=upload_to_location, null=True, blank=True )
Notice the image_file attribute has upload_to argument, which is fed the upload directory and file name of the image_file. The upload_to_location method takes care of that; assume it works correctly.
Now I want to upload each image to Azure Cloud Storage. The python snippet to do that is explained here. Using that, I tried to write my own custom storage that saves images to Azure. It's buggy though, and I need help in cleaning it up. Here's what I've done:
Changed the image_file attribute in models.py to:
image_file = models.ImageField("Tasveer dalo:",upload_to=upload_to_location, storage=OverwriteStorage(), null=True, blank=True )
And then created a separate storage.py in my app folder that has:
from django.conf import settings
from django.core.files.storage import Storage
from azure.storage.blob import BlobService
class OverwriteStorage(Storage):
def __init__(self,option=None):
if not option:
pass
def _save(name,content):
blob_service = BlobService(account_name='accname', account_key='key')
PROJECT_ROOT = path.dirname(path.abspath(path.dirname(__file__)))
try:
blob_service.put_block_blob_from_path(
'containername',
name,
path.join(path.join(PROJECT_ROOT,'uploads'),name),
x_ms_blob_content_type='image/jpg'
)
return name
except:
print(sys.exc_info()[1])
return 0
def get_available_name(self,name):
return name
This set up doesn't work, and returns the error: _save() takes exactly 2 arguments (3 given). Exception Location: /home/hassan/.virtualenvs/redditpk/local/lib/python2.7/site-packages/django/core/files/storage.py in save, line 48
How do I make this work? Has anyone used Azure-Storage python SDK with their Django projects in this way? Please advise.
Note: Originally, I was using the django-storages library, which obfuscated storage details from me, reducing everything to just some configuration to be entered in settings.py. But now, I need to remove django-storages from the equation, and solely use the Azure-Storage python SDK for the purpose.
Note: Ask for more information in case you need it
According your error message, you missed parameter in function _save() which should be complete in the format like _save(self,name,content).
And additionally, it seems that you want put the images directly to Azure Storage which are uploaded from client forms. If so, I found a repo in github which builds a custom azure storage class for Django models. We can get leverage it to modify your application. For more details, refer to https://github.com/Rediker-Software/django-azure-storage/blob/master/azure_storage/storage.py
And here are my code snippets,
models.py:
from django.db import models
from django.conf import settings
from django.core.files.storage import Storage
from azure.storage.blob import BlobService
accountName = 'accountName'
accountKey = 'accountKey'
class OverwriteStorage(Storage):
def __init__(self,option=None):
if not option:
pass
def _save(self,name,content):
blob_service = BlobService(account_name=accountName, account_key=accountKey)
import mimetypes
content.open()
content_type = None
if hasattr(content.file, 'content_type'):
content_type = content.file.content_type
else:
content_type = mimetypes.guess_type(name)[0]
content_str = content.read()
blob_service.put_blob(
'mycontainer',
name,
content_str,
x_ms_blob_type='BlockBlob',
x_ms_blob_content_type=content_type
)
content.close()
return name
def get_available_name(self,name):
return name
def upload_path(instance, filename):
return 'uploads-from-custom-storage-{}'.format(filename)
class Photo(models.Model):
image_file = models.ImageField(upload_to=upload_path, storage=OverwriteStorage(), null=True, blank=True )