I have following models for category
class Category(models.Model):
name = models.CharField(max_length=30)
is_active=models.BooleanField()
photo=models.ImageField(upload_to='category')
def __unicode__(self):
name = str(self.name)
return name
def image(self):
return self.photo or 'DEFAULT_PIC.jpg'
class Meta:
permissions = (
('category','Category'),
('view_category', 'View category'),
)
My form class is as follows
class categoryForm(forms.Form):
name = forms.CharField(widget=forms.TextInput(attrs={'class':'box'}),max_length=32,label='Category',required=True)
is_active = forms.BooleanField(required=False)
id = forms.CharField(widget=forms.HiddenInput,required=False)
photo = forms.FileField(
required=False,
label='Select an Image',
help_text='max. 4 megabytes'
)
It is working perfect for insert and update both, now i am trying to load the image which has already been upload in the form (say i have already added one category with image , now i want to upload another image, so i am trying to show the preview of previously uploaded image)
I passed the data in the view and tried few hack in forms
formdata = categoryForm({'name':p.name,'is_active':p.is_active,'id':p.id,'photo':p.image()})
In form i made modification as below
def __init__(self, data=None, **kwargs):
# pass
super(categoryForm, self).__init__(data, **kwargs)
if self.data['photo']!='':
self.fields['uploaded_photo'] =forms.CharField(widget=forms.TextInput,required=False,label=mark_safe('<img src="/media/%s" height="100">'%(self.data['photo'])))
#self.fields['uploaded_photo'].widget.attrs['value']=self.data['photo']
Now it is perfectly showing previously uploaded image on label .
After that i tried to upload another image but it shows following error
TypeError at /update/category/
init() takes at most 2 arguments (3 given)
Script to handle image upload is as follows
formdata = categoryForm(request.POST,request.FILES)
if formdata.is_valid():
cd = formdata.cleaned_data
p1=Category()
p1.id=cd['id']
p1.name=cd['name']
p1.is_active=cd['is_active']
p1.photo=cd['photo']
p1.save()
Please let me know what is happening here
Try making life easy by using the ImageField
class ImageField(**kwargs)ΒΆ
Default widget: ClearableFileInput
Empty value: None
Normalizes to: An UploadedFile object that wraps the file content and file name into a single object.
Validates that file data has been bound to the form, and that the file is of an image format understood by PIL.
Error message keys: required, invalid, missing, empty, invalid_image
Using an ImageField requires that the Python Imaging Library is installed.
When you use an ImageField on a form, you must also remember to bind the file data to the form.
Check out the complete documentation here
Related
I have an ImageField in my model which I want to store the image url dynamically depending on a user session var.
Imaginary like this:
logo = models.ImageField(null=True, upload_to = 'empresas/'+codEmp+'/logo/')
And the var codEmp is a session variable:
request.session['codEmp']
Therefore if the codEmp of a user is for example 'McDonalds' it should save the following path: 'empresas/McDonalds/logo/imaginary_picture.jpg'.
I tried by init but I can't finish it and I'm not sure if it'll work.
class Empresa(models.Model):
def __init__(self, filter_on, *args, **kwargs):
super(Empresa, self).__init__(*args, **kwargs)
codEmp = filter_on
logo = models.ImageField(null=True, upload_to = 'empresas/'+codEmp+'/logo/')
I tried to do this in the ModelForm but it seems that forms.ImageField doesn't have an upload_to attribute.
The upload_to parameter may be a callable expected to accept 2 parameters: instance and filename. See here.
Problem
I need to display a gallery of images on a product page. This worked when we were at Django 1.6, but have since upgraded to Django 1.11 (Big Process). I am now stuck at how to get this to work within the new environment. Right now clicking Add Image brings up a pop up where I can select the image, and the regions associated with it (US, Canada, Spain, Etc..), but after clicking "Save" The popup title changes to Popup closing... and never closes - also the image is not added to the gallery. The image I upload itself IS added to the rest of the Images within filer, however it is not added to the ProductGallery Model.
What I've Got
Django: 1.11.7
Django-Filer: 1.2.7
Django-Suit: 0.2.25
Vanilla-Views: 1.0.4
I have Product models, these products have a many to many relationship to a ProductGallery model like so:
class Product(models.Model):
gallery = models.ManyToManyField('products.ProductGallery')
The ProductGallery is supposed to house Images and Videos allowing for upload of either, and providing one list to iterate through on the front end for display purposes.
The ProductGallery is defined as:
class ProductGallery(models.Model):
limit = models.Q(app_label='media', model='image') | models.Q(app_label='media', model='video')
order = models.PositiveIntegerField(default=0)
content_type = models.ForeignKey(ContentType, limit_choices_to=limit)
object_id = models.PositiveIntegerField(db_index=True)
content_object = generic.GenericForeignKey('content_type', 'object_id')
class Meta:
ordering = ('order',)
def __str__(self):
return six.text_type(self.content_object)
where media.image is defined as: (I'll ignore video for the time being)
class Image(CountryInfoModel, models.Model):
image = FilerImageField(null=True, blank=True)
def __str__(self):
return str(self.image.name or self.image.original_filename)
I've got a view for Adding new Media like so:
class AddMedia(LoginRequiredMixin, StaffuserRequiredMixin, JsonRequestResponseMixin, GenericView):
require_json = True
def post(self, request, *args, **kwargs):
object_id = self.request_json["objectId"]
object_var = self.request_json["objectVarName"]
content_type_id = self.request_json["contentType"]
order = self.request_json["order"]
media_id = self.request_json["mediaId"]
media_type = self.request_json["mediaType"]
content_type = _get_content_type_or_404(content_type_id)
content_object = _get_object_or_404(content_type, object_id)
model_var = getattr(content_object, object_var)
try:
if media_type.lower() == "image":
obj = Image.objects.get(pk=media_id)
elif media_type.lower() == "video":
obj = Video.objects.get(pk=media_id)
else:
raise Http404("Invalid mediaType parameter: {0}".format(media_type))
media_item = model_var.create(content_object=obj)
media_item.order = order
media_item.save()
except model_var.model.DoesNotExist:
pass
return self.render_json_response({'message': "Order successfully updated"})
And I think thats all the pieces there are to this. I am lost on why when I click "save" the Image is not saved to the ProductGallery model at all. I'd be happy to provide more context if needed, and any help is very much appreciated.
Just in case anyone else comes across this and wants to know how it was fixed.
It turns out that some of the django-admin template functionality had been overwritten and was causing some issues.
Specifically this project had overwritten parts of the save button functionality. The function dismissAddRelatedObjectPopup used to be named dismissAddAnotherPopup
These functions were overwritten to provide the custom functionality outlined above with the ProductGallery. Django went to call the function, but this was throwing an error on the popup related to something called SelectBoxwhich then broke the ajax call that was needed to save the model correctly.
Hopefully this can help someone in the future!
I want to create a model Changelog and make it editable from Admin page. Here is how it is defined in models.py:
class Changelog(models.Model):
id = models.AutoField(primary_key=True, auto_created=True)
title = models.TextField()
description = models.TextField()
link = models.TextField(null=True, blank=True)
picture = models.BinaryField(null=True, blank=True)
title and description are required, link and picture are optional. I wanted to keep this model as simple as possible, so I chose BinaryField over FileField. In this case I wouldn't need to worry about separate folder I need to backup, because DB will be self-contained (I don't need to store filename or any other attributes, just image content).
I quickly realized, that Django Admin doesn't have a widget for BinaryField, so I tried to use widget for FileField. Here is what I did to accomplish that (admin.py):
class ChangelogForm(forms.ModelForm):
picture = forms.FileField(required=False)
def save(self, commit=True):
if self.cleaned_data.get('picture') is not None:
data = self.cleaned_data['picture'].file.read()
self.instance.picture = data
return self.instance
def save_m2m(self):
# FIXME: this function is required by ModelAdmin, otherwise save process will fail
pass
class Meta:
model = Changelog
fields = ['title', 'description', 'link', 'picture']
class ChangelogAdmin(admin.ModelAdmin):
form = ChangelogForm
admin.site.register(Changelog, ChangelogAdmin)
As you can see it is a bit hacky. You also can create you own form field be subclassing forms.FileField, but code would be pretty much the same. It is working fine for me, but now I'm thinking is there are better/standard way to accomplish the same task?
A better and more standard way would be to create a Widget for this type of field.
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()
So instead of subclassing the whole form you would just use the widget where it's needed, e.g. in the following way:
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides = {
models.BinaryField: {'widget': BinaryFileInput()},
}
As you already noticed the code is much the same but this is the right place to put one field to be handled in a specific manner. Effectively you want to change one field's appearance and the way of handling when used in a form, while you don't need to change the whole form.
Update
Since writing that response Django has introduced an editable field on the models and in order to get this working you need to set the model field to editable=True which is false for BinaryField by default.
An alternative solution is to create a file storage backend that actually saves the binary data, along with file name and type, in a BinaryField in the database. This allows you to stay within the paradigm of FileField, and have the metadata if you need it.
This would be a lot more work if you had to do it yourself, but it is already done in the form of db_file_storage.
I actually followed #Ania's answer with some workaround since upload.read() was not saving image in the right encoding in Postrgres and image could not be rendered in an HTML template.
Furthermore, re-saving the object will clear the binary field due to None value in the uploading field (Change) [this is something that Django handles only for ImageField and FileField]
Finally, the clear checkbox was not properly working (data were deleted just because of the previous point, i.e. None in Change).
Here how I changed value_from_datadict() method to solve:
forms.py
class BinaryFileInput(forms.ClearableFileInput):
# omitted
def value_from_datadict(self, data, files, name):
"""Return the file contents so they can be put in the db."""
#print(data)
if 'image-clear' in data:
return None
else:
upload = super().value_from_datadict(data, files, name)
if upload:
binary_file_data = upload.read()
image_data = base64.b64encode(binary_file_data).decode('utf-8')
return image_data
else:
if YourObject.objects.filter(pk=data['pk']).exists():
return YourObject.objects.get(pk=data['pk']).get_image
else:
return None
Then I defined the field as a BinaryField() in models and retrieved the image data for the frontend with a #property:
models.py
image = models.BinaryField(verbose_name='Image', blank = True, null = True, editable=True) # editable in admin
#property
def get_image(self):
'''
store the image in Postgres as encoded string
then display that image in template using
<img src="data:image/<IMAGE_TYPE>;base64,<BASE64_ENCODED_IMAGE>">
'''
image_data = base64.b64encode(self.image).decode('utf-8')
return image_data
And finally is rendered in the template with:
yourtemplate.html
<img src="data:image/jpg;base64,{{object.get_image}}" alt="photo">
For modern Django, I found the following approach works best for me:
class BinaryField(forms.FileField):
def to_python(self, data):
data = super().to_python(data)
if data:
data = base64.b64encode(data.read()).decode('ascii')
return data
class BinaryFileInputAdmin(admin.ModelAdmin):
formfield_overrides = {
models.BinaryField: {'form_class': BinaryField},
}
The individual model field still needs editable=True, of course.
We are developing an intranet with Django which has to have a consistent and centralized file managmenet. We implemented a filemanager app which should handle all the uploads and downloads, do mimetype checks, permission checks etc.
The upload ist achieved through a Django form:
class UploadFileForm(forms.ModelForm):
class Meta:
model = PhysicalFile
fields = ('path', 'directory')
def save(self, commit=True):
"""
Override save method of ModelForm to create Physical File object of
uploaded file and to process meta data
"""
# Proceed with default behaviour but DO NOT commit!
# pfile contains the PhysicalFile object which is not yet written to DB
pfile = super(UploadFileForm, self).save(commit=False)
# set pfile's meta data according to:
# https://docs.djangoproject.com/en/1.11/ref/files/uploads/
pfile.name = self.cleaned_data['path'].name
pfile.size = self.cleaned_data['path'].size
pfile.mimetype = self.cleaned_data['path'].content_type
# NOW save to database, ignoring commit parameter
pfile.save()
return pfile
Now we need want to perform uploads in ANOTHER app (say a members app with profile picture upload) using the same form as above. However, it needs to be included into an app specific form. E.g. a form with name, address etc. Basically we would only need to save the corresponding foreignKey of the file into the memberModel and process the upload with the filemanger's form.
That is why we thought of a custom filed. But this is not working out at all..
class FilemanagerUploadField(models.ForeignKey):
def __init__(self, upload_to=None, *args, **kwargs):
# Will be used later to bind specific apps to specific directories
self.upload_to = upload_to
# Bind PhysicalFile as default Model
super(FilemanagerUploadField, self).__init__('filemanager.PhysicalFile')
def formfield(self, **kwargs):
""" Taken from django's FileField but does NOT WORK"""
defaults = {'form_class': forms.FileField, 'max_length': self.max_length}
if 'initial' in kwargs:
defaults['required'] = False
defaults.update(kwargs)
return super(FilemanagerUploadField, self).formfield(**defaults)
def save(self):
# somewho run form from here with uploaded data and return foreignKey
I am not really able to get a grip on those custom model fields... We need it to perform like a FileField (widget validation and stuff) but be saved like a ForeignKey (to the actual PhysicalFile Model in another app)...
If there is another way to achieve what we are looking for, please tell me.
tldr; Upload files in App A but let App B process it, save the file path, meta data etc, and return ForeignKey of the processed object to A to save it to database. Custom model field?
I have a following Banner class. Which is editable by admin.
class Banner(models.Model):
name = models.CharField(max_length = 128)
link = models.TextField(max_length = 450)
image = models.ImageField(upload_to = 'banner_images')
There are two problems.
When saving image it is saved with original file name. I would like to change it with some unique name so that is not clashed when image with the same name is uploaded again in the specified directory.
While updating the image, the first image file must be deleted. It is not happening...
Any suggestion will be helpful. Thanks in advance.
Try something like this:
from os import rename
class Banner(models.Model):
name = models.CharField(max_length = 128)
link = models.TextField(max_length = 450)
image = models.ImageField(upload_to = 'banner_images')
def save(self):
super(Banner, self).save()
new_filename = <insert code here to change name>
self.image.name = new_filename
rename(static_path+'banner_images/'+self.image, static_path+'banner_images/'+new_filename)
super(Banner, self).save()
I'm not sure if the super(Banner, self).save() called is required twice or not. The 1st might be needed to save the file, and the 2nd one to update the DB record.
1) upload_to can be a callable, on save you can modify it's filename (docs)
2) see https://code.djangoproject.com/ticket/6792, you have to delete it yourself,
Since I was having problem related to savings of image through admin I got following solution which answers all my queries...
First I found that even though admin keeps the original file name, if the file with same name already exists, it keeps on appending a count as a suffix to prevent duplicate file name... for example, if same file is uploaded it is stored as image, image_2, image_3 etc...
Second, while changing image through admin, it was not removing the original file. For that I wrote following code in admin.py. And it does the job well...
Code:
class BannerAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
#Remove the previous file if the object is not new
#and new file supplied.
if obj.id != None and len(request.FILES) > 0:
import os
old_obj = m.Banner.objects.get(id = obj.id)
os.remove(old_obj.image.path)
Hope this helps you if you have got the similar problem.