I have a model with an ImageField that I display using easy_thumbnails (|thumbnail_url).
My question is how do I display a default image if the ImageField is empty?
I would like this logic in the Model/View, NOT in the html/template.
e.g.:
DEFAULT_PICTURE = 'default.jpg'
def get_picture(self):
if self.picture:
return self.picture
else:
from DEFAULT_PICTURE
What object should get_picture() return that is compatible with easy_thumbnails?
I tried to create a new File object, like here, but it did not work.
Can you kindly provide a working example of returning an existing file to display with easy_thumbnails?
Chris (easy_thumbnails) answered here, and also on SO.
His suggestion to create a new ImageFieldFile is good, but easy_thumbnails stilled failed because the newly created ImageFieldFile had an empty instance.
So either set instance = self:
DEFAULT_PICTURE = 'default.jpg'
def get_picture(self):
if self.picture:
return self.picture
else:
return ImageFieldFile(instance=self, field=FileField(), name=DEFAULT_PICTURE)
or change alias.py line 116:
if not hasattr(target, 'instance'):
return None
should be...
if not hasattr(target, 'instance') or not target.instance:
return None
This is the code that I have used previously. It could probably be optimized a bit (and probably shouldn't have the hard coded media path), but it worked well for my small project.
def get_picture(self):
if self.picture:
return '<img src="%s" />' % self.picture['thumbnail'].url
return '{img src="/media/img/admin/icon-no.gif" alt="No Image"}'
With this, you just need to ensure that you have icon-no.gif in the appropriate path.
Related
Hello I'm having trouble here with multiple image with same field.
As far as I know in django tutorial they telling this.
for f in request.FILES.getlist('files'):
# do something (validate here maybe)
in which I don't quite get it. Like do i do manual validation? If so why?
Anyway there is another approach they give
files = forms.FileField(widget=ClearableFileInput(attrs={'multiple': True})
This one does not work in the way I want. It's self.cleaned_data['files'] only gives one output (There is a similar problem here) and django/multiupload was having a bug on my experience and sadly it was too slow to fix :(.
What I want was to validate each file and give errors to each via ImageField because I like it was validating a file versus I code it myself.
Thus I made a prototype code.
forms.py
class ImageForm(forms.Form):
# validate each image here
image = forms.ImageField()
class BaseForm(forms.Form):
# first form
ping = forms.CharField()
#images = SomeThingMultipleFileField that will raise multiple errors each validate image.
# since no option I decided to do that. below.
# so for decoration that images is required.
images = forms.ImageField()
def handle(self, request, *args, **kwargs):
#custom function
image_list = []
errors = []
# validate each image in images via another form
# if there is errors e.g this field is required will be append to errors = []
for image in request.FILES.getlist('images'):
data = ImageForm(image)
if data.is_valid():
image_list.append(data.cleaned_data['image'])
else:
errors.append(data.errors)
if errors:
# raise errors
# return the data
views.py
def base(request):
# this is an api
# expected input should be from the code or format
# {'ping': 'test', 'images': 1.jpg, 'images': 2.jpg}
# This is not the actual view code.
data = forms.BaseForm(request.POST, request.FILES)
if data.is_valid():
value = data.handle(request)
return JSONResponse({'data': value})
return JSONResponse({'errors': data.errors})
Not elegant to be honest but having trouble now and no more options I can think off but that.
The problem in my code is that
data = ImageForm(image)
does not reading the file thus image_list is always empty
So anyone can help me here?. Im stuck
Any better approach?
I wonder also for a general error like if one image is not valid it triggers like {'files': 'One of the images is not valid.'}
so far, I tested again so my bad
it seems it requires the format of data, files in ordinary forms.
in order to do so.
forms.py
... # previous code
# data = ImageForm(image) , old code
data = ImageForm({}, {'image': image})
in this way,it fills up the default QueryDict: {}, MultiValueDict in the args
Number 3 can be answered.
instead of
# previous code
else:
errors.append(error)
now should be
raise ValidationError(_('Your error'))
Any Better approach?
Not much I can think off sadly. So anyone stumble here. feel free to comment. Much appreciated for the help.
I have a Django app where users would upload Images and certain functions would take the image file path to process them and save the result in the same model.
all of this would happen in the file upload view the problem is My functions take the file path which isn't created/committed in the DB yet as I don't save before calling those functions.
I tried overriding the save method in the models.py and it didn't work so how can I call the functions after the upload in a convenient way ??
here's the function:
# The view for analysing images
def upload_view(request,pk=None):
patient = get_object_or_404(Patient,pk=pk)
if request.method == 'POST':
form = forms.ImageForm(request.POST,request.FILES)
if form.is_valid():
image = form.save(commit=False)
image.patient = patient
image.enhanced = main(image.original.path)
image.segmented = segment(image.enhanced.path)
image.enhanced.name = image.enhanced.path.split('media')[1]
image.segmented.name = image.enhanced.path.split('media')[1]
messages.success(request,"Image added successfully!")
image.save()
return HttpResponseRedirect(reverse_lazy('patients:patient_detail', kwargs={'pk' : image.patient.pk}))
else:
form = forms.ImageForm()
return render(request, 'patients/upload.html', {'form': form})
else:
form = forms.ImageForm()
return render(request, 'patients/upload.html', {'form': form})
image.original is the uploaded image
the problem is the file path isn't passed correctly and the functions return errors bec of that. (it worked when I made the processing in a different view where it was accessed after the upload)
Before you call save() on your model, the path to your image doesn't exist, or is temporary. You can fix this by first creating the model from the form, no need for commit=False, assuming main and segment are PIL based functions that return images, you can use the ImageField.save() method to set the name at the same time:
if form.is_valid():
image = form.save() # this creates the model, the upload paths are created here!
image.patient = patient
image.enhanced.save("enhanced_" + image.original.name, main(image.original.path)) # I made this up to avoid conflicts in your storage
image.segmented.save("segmented_" + image.original.name, segment(image.enhanced.path))
image.save() # this updates the model with the new images
messages.success(request,"Image added successfully!") # I moved this here, more accurate, in case save fails....
return HttpResponseRedirect(reverse_lazy('patients:patient_detail', kwargs={'pk' : image.patient.pk}))
As you can see, you need two hits to the database to save your images. This assumes that enhanced and segmented fields are not required in the model.
Also, because image transformation is an expensive task, I'd check how to get this out of the request cycle, by using something like Celery or Django-RQ, that way, your app can still service request while making the transformations on the background.
I'm trying to subclass ImageField to add compression support. This is my current code.
(Right now I'm assuming that the files will be PNGs).
from django.utils.six import with_metaclass, string_types
from django.core.files.images import ImageFile
class CompressImageField(with_metaclass(models.SubfieldBase, models.ImageField)):
"""
Compress image files on upload-field
"""
def to_python(self, value):
value = super(CompressImageField, self).to_python(value)
if isinstance(value, ImageFile):
im = Image.open(value.name)
im.save(value.name, optimize=True, quality=100)
return ImageFile(value.name)
return value
However what is stored in the database is just u''.
Sometimes to_python() receives a unicode string, sometimes a ImageField. I'm guessing this is the serialization part, although super(CompressImageField, self).to_python(value) always returns the same type as value, often with an unchanged value.
Not sure what happens here. Any help is appreciated. Hopefully I've made myself clear enough.
You can use a django package that already do that.
Django-imagefit
Django-imagekit
Django-image-tools
I am trying to implement a MultiValueField for IP Adress/Domain Name entries. It works as expected for entering data.
My Problem is that if I want to display the form bound to specific data, the IP Address/Domain Name field stays empty. All other fields are filled with the desired data. If I use a normal CharField, I get the data that I would expect. But it does not work with my custom field.
I have tracked it down to the fact that my custom MultiWidget does not call its decompress method.
Here is my Field:
class accessIPField(forms.MultiValueField):
"""
custom Field for access IP
"""
def __init__(self, *args, **kwargs):
self.fields=(
forms.IPAddressField(label='IP Adress'),
forms.CharField(max_length=50,label='Domain Name')
)
self.widget=accessIPWidget()
super(accessIPField,self).__init__(self.fields,self.widget, *args, **kwargs)
def compress(self,data_list):
if data_list:
return " ".join(data_list)
And here is my widget:
class accessIPWidget(forms.MultiWidget):
"""
Widget to display IP Adress / Domain name pairs
"""
def __init__(self,*args,**kwargs):
self.widgets=(forms.TextInput(),forms.TextInput())
super(accessIPWidget,self).__init__(self.widgets,*args,**kwargs)
def decompress(self,value):
print 'decompress called'
if value:
return value.rsplit()
return [None,None]
def format_output(self, rendered_widgets):
return u'\n'.join(rendered_widgets)
The whole thing is called (in a larger context) as
self.fields['access_IPs'] = accessIPField()
Now as you can see, I put a print statement in my compress method, and I never get to see that statement. Also, if I rename compress to something like foobar, I would expect (according to the django code for MultiWidget) to get the NotImplementedError, which is not the case. Any suggestions?
I am using python 2.6.5, django 1.1 on ubuntu server 10.04.
It turns out that the problem was with the value_from_datadict() method as implemented by MultiWidget. First of all, it allready returned a list, so that is why decompress() was not called in the first place. Secondly, it allways returen a [None,None] list, so that is why the bound form stayed empty.
I needed to implement my own (within my accessIPWidget class):
def value_from_datadict(self, data, files, name):
try:
return data.get(name,None).rsplit()
except AttributeError:
return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
Now the last line is what the original method did. In order to get the data into the bound form, I needed to add data.get(name,None).rsplit().
As far as I understand, the original value_from_datadict method only works for unbound fields. Because it changes the name of the original field to name + '_%s', which is what you get when pressing the submit button. In order to fill in a bound method, the datadict needs to be queried for 'name' only.
Hm, not shure if there is a way around this, but it seems to me that this behaviour should at least be documented somewhere.
Maybe I misunderstood something?
I have been playing around with django-photologue for a while, and find this a great alternative to all other image handlings apps out there.
One thing though, I also use django-cumulus to push my uploads to my CDN instead of running it on my local machine / server.
When I used imagekit, I could always pass a upload_to='whatever' but I cannot seem to do this with photologue as it automatically inserts the imagefield. How would I go about achieving some sort of an overwrite?
Regards
I think you can hook into the pre_save signal of the Photo model, and change the upload_to field, just before the instance is saved to the database.
Take a look at this:
http://docs.djangoproject.com/en/dev/topics/signals/
Managed to find a workaround for it, however this requires you to make the changes in photologue/models.py
if PHOTOLOGUE_PATH is not None:
if callable(PHOTOLOGUE_PATH):
get_storage_path = PHOTOLOGUE_PATH
else:
parts = PHOTOLOGUE_PATH.split('.')
module_name = '.'.join(parts[:-1])
module = __import__(module_name)
get_storage_path = getattr(module, parts[-1])
else:
def get_storage_path(instance, filename):
dirname = 'photos'
if hasattr(instance, 'upload_to'):
if callable(instance.upload_to):
dirname = instance.upload_to()
else: dirname = instance.upload_to
return os.path.join(PHOTOLOGUE_DIR,
os.path.normpath( force_unicode(datetime.now().strftime(smart_str(dirname))) ),
filename)
And then in your apps models do the following:
class MyModel(ImageModel):
text = ...
name = ...
def upload_to(self):
return 'yourdirectorynamehere'
You can use the setting PHOTOLOGUE_PATH to provide your own callable. Define a method which takes instance and filename as parameters then return whatever you want. For example in your settings.py:
import photologue
...
def PHOTOLOGUE_PATH(instance, filename):
folder = 'myphotos' # Add your logic here
return os.path.join(photologue.models.PHOTOLOGUE_DIR, folder, filename)
Presumably (although I've not tested this) you could find out more about the Photo instance (e.g. what other instances it relates to) and choose the folder accordingly.