I am trying to create a form where users will be allowed to upload any image file + SWF files. Django's ImageField does not support SWF so I need to override it.
What I want to do is to check if the file is SWF, if True, return it. If it's not SWF, call the original method which will take care of the file validation.
However, I am not sure how to implement that. Here is an example of what I am trying to achieve, but it does not work:
from hexagonit import swfheader
class SwfImageField(forms.ImageField):
def to_python(self, data):
try:
swfheader.parse(data)
return data
except:
return super(SwfImageField, self).to_python(data)
What is actually does is allowing only SWF files at the moment.
An alternative and possibly easiest solution is to use a standard FileField with a custom validator:
def my_validate(value):
ext = os.path.splitext(value.name)[1] # [0] returns path filename
valid = ['.jpg', '.swf']
if ext not in valid:
raise ValidationError("Unsupported file extension.")
class MyForm(forms.Form):
file = forms.FileField(validators=[my_validate])
may be it will be useful:
from django.core.validators import FileExtensionValidator
class MyModel(models.Model):
......
my_field = models.FileField('Name', validators=[FileExtensionValidator(['svg', 'jpg', 'png', '.....'])])
Related
I have a standard Django admin form.
When the file is uploaded in the file selection box, I would like to leave only the file name, instead of the full path in the static.
Is this possible without editing the template, but only by overriding a formset, form, or model methods?
In the picture above the button, change the display of the line "ws_document_studygroup/2021/2/123123123123123123png" to "123123123123123123png". But without changing the real path in the model.
Please advise the best practice.
You could try adding an #property getter to your model class:
import os
class Document:
def __init__(self, full_path: str):
self.full_path = full_path
#property
def filename(self) -> str:
return os.path.basename(self.full_path)
The os.path.basename function takes a path and returns the path segment following the final slash character (in other words, the filename).
>>> doc = Document("ws_document_studygroup/2021/2/123123123123123123.png")
>>> doc.filename
123123123123123123.png
So all you need to do is use this property in your template.
I found different resolve this issue.
I override ClearableFileInput widget and clearable_file_input.html template
from django.forms import ClearableFileInput
import os
class CustomClearableFileInput(ClearableFileInput):
template_name = 'custom_clearable_file_input.html'
def format_value(self, value):
"""
Return the file object if it has a defined url attribute.
"""
if self.is_initial(value):
setattr(value, 'file_short_name', os.path.basename(str(value)))
return value
and in template file change string to:
{{ widget.value.file_short_name }}
And just add widget to file field:
class StudyGroupDocumentsForm(forms.ModelForm):
file = forms.FileField(widget=CustomClearableFileInput)
class Meta:
model = StudyGroupDocuments
fields = '__all__'
And add the form to Inline.
Hope it is helpful for someone.
I'm trying to create a folder and upload an image to in it. My model is similar to this:
class XYZ(models.Model):
def code(self):
syms = ['#','#','$','%','&','*','+','-']
return ''.join(x+random.choice(syms) for x in [self.name[-2:],self.name[2:]])
def make_folder(self):
os.mkdir(os.getcwd()+'/XYZ/'+folder)
def save(self):
self.make_folder()
super(XYZ,self).save(*args,**kwargs)
name = models.CharField(max_length=20)
folder = property(code)
image = models.ImageField(upload_to='HELP_HERE')
I've tried using folder, self.folder, property(code) but it says it isn't defined, how do I access this attribute? Is this the correct approach?
I've also set in the configs.py my MEDIA_URL and MEDIA_ROOT
I think a more simple and understandable way to do this would be to avoid using the inner class function and doing something like this:
# Reference this function outside of your model class.
def xyz_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/xyz_<id>/<filename>
return 'xyz_{0}/{1}'.format(instance.id, filename)
Then, on your models, reference the function in your upload_to field like so:
class XYZ(models.Model):
image = models.ImageField(upload_to=xyz_directory_path)
With your media config set up, this should work. There's a really nice and simple explanation about file handling here.
I'm creating a Rest API with Django Rest Framework, and in some call, I need upload one of different file types.
For do this, I am using drf-extra-fields and I have this problem:
When the client convert file to Base64, the filename change and lose the file extension, for suply this, I created in serializer, new CharField called "file_extension":
class MySerializer(serializers.ModelSerializer):
# Some other fields...
file = FileField(required=False)
file_extension = serializers.CharField(required=False)
class Meta:
# model and fields...
I created my custom Base64FileField, and set some allowed types and my method for get file extension:
class FileField(Base64FileField):
ALLOWED_TYPES = ['pdf', 'txt', 'xml']
def get_file_extension(self, filename, decoded_file):
# Not working because the filename not include extension.
return filename.split('.')[-1]
For solve this, I thinked set filename in serializer with file extension (taked for the other field) before get_file_extension is called.
Thanks you!
Use filetype to identify the type of the decoded_file
filetype - PyPI
import filetype
class FileField(Base64FileField):
ALLOWED_TYPES = ['pdf', 'txt', 'xml']
def get_file_extension(self, filename, decoded_file):
kind = filetype.guess(decoded_file)
return kind.extension
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 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.