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.
Related
I have changed upload_to attribute of my models image field. How can I re-upload all images to new paths?
So, I think 're-uploading' is the wrong way to think about it -- re-uploading the images will still leave the old ones lying around, which (depending on how many images you have) could be a massive waste of space. One way to do this instead would be by the following two step process:
1) Move the files manually, on your server, to the new upload_to location via whatever method is OS appropriate. This could probably all be done with one mv command on linux, if that's what you're hosting on.
2) If you just changed the upload_to attribute, and didn't change the MEDIA_ROOT settings or anything else, what you need to change is the Imagefield's name property. An ImageField's name properly usually is a joining of your upload_to string and your image's filename (this then gets appended to MEDIA_URL to form the images url or MEDIA_ROOT to form the actual upload path). So you could update the models in your Django shell by typing something like this:
import os
from my_app import MyModel
newpath = 'your/new/upload_to/'
for obj in MyModel.objects.all():
image_name = os.path.split(obj.my_img_field.name)[1]
obj.my_img_field.name = newpath + image_name
obj.save()
You can check to see if everything worked properly by calling obj.my_img_field.url and seeing if that points where it should.
Here's a little snippet that I made when I needed to do this on many models and didn't want to do this on th OS level.
For use with strftime this have to be modified though.
models = (YourModel1, YourModel2)
for Model in models:
for field in Model._meta.get_fields():
if not hasattr(field, 'upload_to'):
continue
for instance in Model.objects.all():
f = getattr(instance, field.name)
if not f:
continue
if field.upload_to not in str(f):
filename = os.path.basename(f.name)
new_path = os.path.join(field.upload_to, filename)
os.makedirs(
os.path.join(
settings.MEDIA_ROOT,
field.upload_to
),
exist_ok=True
)
try:
shutil.move(
os.path.join(settings.MEDIA_ROOT, f.name),
os.path.join(settings.MEDIA_ROOT, new_path)
)
setattr(instance, field.name, new_path)
except FileNotFoundError as e:
logger.error("Not found {}".format(field.name))
logger.error(str(e))
else:
instance.save()
I am building a Django project where users can upload pictures. I am wondering what I should do to not show the original picture name.
I want the url to be something like /pic/randomnumber, and when the picture is downloaded from the website, it would have the name randomnumber.jpg. For example, all the pictures on Tumblr have the name tumblr_blabla.jpg.
I think this is something that should be done in models.py, but I am not quite sure how to implement it.
IMO you should write method save in your model
Something like that:
from PIL import Image
import os
class YOURS_MODEL_NAME(models.Model):
photo = models.ImageField(upload_to="photos")
def save(self, miniature=True):
super(YOURS_MODEL_NAME, self).save()
if miniature:
filepath = self.photo.path
image = Image.open(filepath)
new_filepath = filepath.split('.')
new_filepath = '.'.join("HERE YOU CAN ADD EVERYTHING TO PATH TO THIS PHOTO") + "." + new_filepath[-1].lower()
try:
image.save(new_filepath, quality=90, optimize=1)
except:
image.save(new_filepath, quality=90)
photo_name = self.photo.name.split('.')
photo_name = '.'.join("HERE YOU CAN ADD EVERYTHING YOU WANT TO 'PHOTO NAME'") + "." + photo_name[-1].lower()
self.photo = photo_name
self.save(miniature=False)
# remove old image
os.remove(filepath)
The upload_to argument in your Model definition can be a callable function which you use to customize the name of the file. Taken from the Django docs on
FileField (of which ImageField is a subclass):
upload_to takes two arguments: instance and filename, (where filename is the original filename, which you may also chose to ignore).
Something similar to this in models.py should do the trick:
def random_filename(instance, filename):
file_name = "random_string" # use your choice for generating a random string!
return file_name
class SomeModel(models.Model):
file = models.ImageField(upload_to=random_filename)
(this is similar to the answer this question about FileFields).
If you are going down this path, I would recommend that you use either the hash/checksum or date/time of the file upload. Something along these lines should work (although I haven't tested it myself!):
from hashlib import sha1
def unique_filename(instance, field):
filehash = sha1()
for chunk in getattr(instance, field).chunks():
filehash.update(chunk)
return filehash
class SomeModel(models.Model):
file = models.ImageField(upload_to=unique_filename(field='file'))
Hope this helps!
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.
I'm trying to use django, and mongoengine to provide the storage backend only with GridFS. I still have a MySQL database.
I'm running into a strange (to me) error when I'm deleting from the django admin and am wondering if I am doing something incorrectly.
my code looks like this:
# settings.py
from mongoengine import connect
connect("mongo_storage")
# models.py
from mongoengine.django.storage import GridFSStorage
class MyFile(models.Model):
name = models.CharField(max_length=50)
content = models.FileField(upload_to="appsfiles", storage=GridFSStorage())
creation_time = models.DateTimeField(auto_now_add=True)
last_update_time = models.DateTimeField(auto_now=True)
I am able to upload files just fine, but when I delete them, something seems to break and the mongo database seems to get in an unworkable state until I manually delete all FileDocument.objects. When this happens I can't upload files or delete them from the django interface.
From the stack trace I have:
/home/projects/vector/src/mongoengine/django/storage.py in _get_doc_with_name
doc = [d for d in docs if getattr(d, self.field).name == name] ...
▼ Local vars
Variable Value
_[1]
[]
d
docs
Error in formatting: cannot set options after executing query
name
u'testfile.pdf'
self
/home/projects/vector/src/mongoengine/fields.py in __getattr__
raise AttributeError
Am I using this feature incorrectly?
UPDATE:
thanks to #zeekay's answer I was able to get a working gridfs storage plugin to work. I ended up not using mongoengine at all. I put my adapted solution on github. There is a clear sample project showing how to use it. I also uploaded the project to pypi.
Another Update:
I'd highly recommend the django-storages project. It has lots of storage backed options and is used by many more people than my original proposed solution.
I think you are better off not using MongoEngine for this, I haven't had much luck with it either. Here is a drop-in replacement for mongoengine.django.storage.GridFSStorage, which works with the admin.
from django.core.files.storage import Storage
from django.conf import settings
from pymongo import Connection
from gridfs import GridFS
class GridFSStorage(Storage):
def __init__(self, host='localhost', port=27017, collection='fs'):
for s in ('host', 'port', 'collection'):
name = 'GRIDFS_' + s.upper()
if hasattr(settings, name):
setattr(self, s, getattr(settings, name))
for s, v in zip(('host', 'port', 'collection'), (host, port, collection)):
if v:
setattr(self, s, v)
self.db = Connection(host=self.host, port=self.port)[self.collection]
self.fs = GridFS(self.db)
def _save(self, name, content):
self.fs.put(content, filename=name)
return name
def _open(self, name, *args, **kwars):
return self.fs.get_last_version(filename=name)
def delete(self, name):
oid = fs.get_last_version(filename=name)._id
self.fs.delete(oid)
def exists(self, name):
return self.fs.exists({'filename': name})
def size(self, name):
return self.fs.get_last_version(filename=name).length
GRIDFS_HOST, GRIDFS_PORT and GRIDFS_COLLECTION can be defined in your settings or passed as host, port, collection keyword arguments to GridFSStorage in your model's FileField.
I referred to Django's custom storage documenation, and loosely followed this answer to a similar question.
I would like to create dynamically the destination of my uploaded files.
But, it seems that the 'upload_to' option is only available for a models, not for forms. So the following code is wrong.
class MyForm(forms.Form):
fichier = forms.FileField(**upload_to='files/%m-%Y/'**)
In the view handling the uploaded file, the destination is static. How can I make it dynamic ?
Thank you.
class YourFileModel(models.Model)
def upload_path(self, name):
name = do_sth_with_name(name)
folder = generate_folder_name(self.id, self.whatever_field)
return 'uploads/' + folder + '/' + name
file = models.FileField(upload_to=upload_path)
edit after comment
def handle_uploaded_file(file):
# generate dynamic path
# save file to that path
example here http://docs.djangoproject.com/en/dev/topics/http/file-uploads/#handling-uploaded-files
if form from model, override the save() method
class YourForm(forms.ModelForm):
fichier = forms.FileField()
def save(self):
if self.cleaned_data['fichier']:
file = handle_uploaded_file(self.cleaned_data['fichier'])
super(YourForm, self).save()
if not form from model, call the upload handler in your view
def your_view(request):
#####
if form.is_valid():
file = handle_uploaded_file(form.cleaned_data['fichier'])
Instead of a string, supply a callable -- i.e. the name of a function that takes the model instance and a string, and returns the desired name. See FileField docs for specifics. One thing they don't say (at least I can't find it in the docs) is that if the returned filename starts with '/' then it is an absolute path, otherwise it is relative to your /media directory.