We need to store a few smallish files to the database (yes, I'm well aware of the counterarguments, but setting up e.g. FileField to work in several environments seems very tedious for a couple of files, and having files on the database will also solve backup requirements).
However, I was surprised to find out that even though BinaryField can be set editable, Django Admin does not create a file upload widget for it.
The only functionality we need for the BinaryField is the possibility to upload a file and replace the existing file. Other than that, the Django Admin fulfills all our requirements.
How can we do this modification to Django Admin?
You will want to create a custom Widget specifically for BinaryField which has to read the file contents before putting them into the database.
class BinaryFileInput(forms.ClearableFileInput):
def is_initial(self, value):
"""
Return whether value is considered to be initial value.
"""
return bool(value)
def format_value(self, value):
"""Format the size of the value in the db.
We can't render it's name or url, but we'd like to give some information
as to wether this file is not empty/corrupt.
"""
if self.is_initial(value):
return f'{len(value)} bytes'
def value_from_datadict(self, data, files, name):
"""Return the file contents so they can be put in the db."""
upload = super().value_from_datadict(data, files, name)
if upload:
return upload.read()
And then you need to use it in admin in the following way:
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides = {
models.BinaryField: {'widget': BinaryFileInput()},
}
fields = ('name', 'your_binary_file')
Note:
BinaryField doesn't have a url or a file name so you will not be able to check what's in the db
After uploading the file you will be able to see just the byte size of the value stored in the db
You might want to extend the widget to be able to download the file
by reading it's contents
Related
How to make only one field of these two fields?
is it possible?
class MyModel(models.Model):
video_file = models.FileField(blank=True)
video = models.URLField(blank=True)
def clean(self, *args, **kwargs):
if not self.video_file and not self.video: # This will check for None or Empty
raise ValidationError({'video_file': 'Even one of field1 or field2 should have a value.'})
elif self.video_file and self.video: # This will check for None or Empty
raise ValidationError({'video_file': 'Even one of field1 or field2 should have a value.'})
if self.video == '':
self.video = self.video_file.url
super(MyModel, self).save(*args, **kwargs)```
**UPDATED**
I think this is the solution, my bad.
Having only one option
The easiest solution might be to not accept 2 different types, and only support either Image upload or Image URL. I'd suggest Image upload only if you're going to implement this solution.
However, if having those 2 options is a requirement you can take a look at the solutions I've listed below.
Checking at a controller level (Simple solution)
One solution is to check if both fields are populated at the controller level, or View in django jargon. If both are populated you can throw some error and handle it from there.
Changing the model and handling at service level (Recommended)
The above solution might work, but that wouldn't be the ideal solution for the long run.
I'd recommend you to change your model to only have a FileField, then in service layer you can directly upload if user uploads a file, however if user passes a URL you can download the image and save it.
You can also make the DB field a UrlField, and if user uploads a file, you can upload it to some external storage bucket like s3 or cloudinary and save the URL in your database.
As for the constraint, you can apply the constraint as mentioned above in solution 2 of adding the constraint in controller or some other way using django magic.
Previously my Django form had a field that supported uploading a single file:
image = forms.ImageField(required=False, help_text=_('Optional image (2.5 MB or less)'), allow_empty_file=True)
If the user tried to edit the form after submitting it, I displayed the previously uploaded file by binding it to the form like so:
if request.method == 'POST':
...
else:
data = {'title': model.title}
file = {'image': model.file}
form = MyForm(data, file, instance=model)
To enable uploading multiple files, I've now changed the widget to:
image = forms.ImageField(required=False, help_text=_('Optional image (2.5 MB or less)'), allow_empty_file=True, widget=forms.ClearableFileInput(attrs={'multiple': True}))
This works fine when uploading the files, but I can't find a way to bind more than one file to the form when it comes to editing it.
Is there a solution using the current widget, or would it involve using a custom or multiple widgets?
Thanks.
This isn't possible with the standard widget.
A ClearableFileInput with multiple enabled provides access to the selected files via a MultiValueDict.
You can bind a MultiValueDict to an ImageField when initialising the form, but it will only ever bind the last image. This is because the widget extends FileInput, and its function value_from_datadict(self, data, files, name) returns files.get(name), rather than files.getlist(name).
So, in short, you have to come up with a custom solution. This answer helped me to see this.
In my views.py file, there are some functions like follows:
def upload_file(request):
"""
upload a file and store it into the database,
and the file will be predicted in another view immediately.
"""
def predict(request):
"""
make prediction of the file uploaded in the 'upload_file' function.
"""
How to access the file which uploaded in upload_file function in the predict function? Here is my thought:
1. read the last row in the database, but this sounds a little silly.
2. use a cache system and retrieve the file from cache?
Is there any useful solution for this problem? please give me any hints or other resources.
Thanks for anyone's help.
As it is described here, you can acces the storage url as Follows :
Class MyModel(models.Model):
photo = models.ImageField()
model = MyModel.objects.get(pk=1) # for instance
model.photo.url
>>> 'https://media.example.com/mymodels/example_image.jpg'
I am using django 1.4 and using django model's filefield to upload some document via modelform. I am having following issues:
When I submit form it says:
Data truncated for column 'file_name' at row 1
Following is my model for this:
class App(models.Model):
user_name=models.CharField(max_length=50)
email=models.CharField(max_length=50)
status=models.CharField(max_length=10,choices=APPLICATIONSTATUSCHOICE)
archived=models.BooleanField()
mark_spam=models.BooleanField()
date=models.DateField()
file_name=models.FileField(upload_to=PATH+"/")
def getPath(self):
return PATH
def __unicode__(self):
return self.user_name
def send_email(self):
pass
Here is the code for model form:
class AppForm(ModelForm):
class Meta:
model=App
exclude=('status','archived','mark_spam')
email=forms.EmailField()
def save(self,commit=True):
app=super(AppForm,self).save(commit=False)
app.status='sent'
app.save()
Also it is storing file with its original name,can I have it with something unique name as I am from PHP background and in PHP I normally save it like <mysql auto id>.<filextension>, so how can I do it in django. My first impression was that all this will be automatically done via django while it just save it with name of its own choice but I need to save names into db also so want to name them according to my choice. How can it be done and what is problem in my code that is giving above mentioned error?
How long is the file_name you are trying to store?
By default, FileField instances are created as varchar(100) columns in your database. As with other fields, you can change the maximum length using the max_length argument. That might be resulting the data truncation error.
Also you can rename your file whatever you want. the upload_to can be a callable that takes the instance and the name of the uploaded file as input and then you can specify the exact name and path you want to store.
Eg.
def get_file_name(instance, filename):
return '/'.join(['blah', instance.user.username, filename])
...
file_name=models.FileField(upload_to=get_file_name)
...
I'm trying to create some kind of 'media manager' model which will allow the user to upload different kings of media (images, swfs, pdfs) similar to the way WordPress does. My media model looks something like this:
class Media(models.Model):
id = models.AutoField(primary_key=True)
url = models.FileField(upload_to="uploads")
mimetype = models.CharField(max_length=64, editable=False)
created = models.DateTimeField(auto_now_add=True, editable=False)
When a user uploads a file, I want to first determine what kind of file it is and if it's an image, manipulate it further. I want to be able to to specify the dimensions (crop) of the uploaded image via a view, so when I call the .save() method, the model will resize and crop the image, upload it and populate the database with the url to the file.
I also want to ensure that the upload of the image is done AFTER the post processing (cropping etc), I have no need to keep the original file.
So the question I am asking is how do I got about passing parameters to the FileFields save method (so I can pass dynamic properties for image post processing) and how can I ensure the post processing is done BEFORE the image is uploaded?
Edit: When I say before the image is uploaded, I mean before it's saved to it's final destination. I understand the image has to go int othe tmp folder first before I can post process it. Sorry for the misleading question.
Hope someone can help :)
You cannot do anything before the image is uploaded (because you have nothing to work with).
But if you want modify the image before saving it into db, you can do it in model's save() method, before calling parent's save()
If you are uploading it via admin, override method save_model() in admin.py, ie:
def save_model(self, request, obj, form, change):
file = request.FILES.get('url') # name of field
if file:
# proceed your code
return super(AdminClassName, self).save_model(request, obj, form, change)
Here is my code how to change file before actually upload it. I think you should get my idea
from django.core.files.uploadedfile import InMemoryUploadedFile
#....
#some form
def clean_avatar(self):
av = self.cleaned_data['avatar']
resized = make_avatar(av,65) # My custom function than returns image
return InMemoryUploadedFile(resized, av.field_name, av.name, av.content_type, resized.len, av.charset)
You can read django code for InMemoryUploadedFile "documentation".
And in your resize/crop function you should use StringIO not file to save result
How could the processing be done before the image is uploaded? That doesn't make sense. The server doesn't have any access to the file until you upload it.
If you actually want to handle the file before it's saved, you can write a custom upload handler. You can test there whether the file is an image, then crop it appropriately, before saving. (You'll need the Python Imaging Library for both of those tasks.)