This is an issue which, I believe, is related to Ticket #10788
(http://code.djangoproject.com/ticket/10788).
The following code snippet used to work with Django 1.0.2, but with
the latest svn version the slug is not being set to the filename
produced by get_path because, as I understand it, get_path will not be
called until the instance is saved to the database.
def get_path(instance, filename):
salt = hashlib.sha1(str(random.random())).hexdigest()
name = hashlib.sha1(salt+filename).hexdigest()[:16]
return '%s.jpg' % (name)
class Photo(models.Model):
photo = models.ImageField(upload_to=get_path, blank=False)
...
slug = models.CharField(max_length=16, unique=True)
def save(self):
self.slug = self.photo.url.split('/')[-1:][0].split('.')[0]
super(Photo, self).save()
I could call instance.save() twice in my view to force it to use the
correct name, but this feels a bit messy to me. What's the best way to
get the correct value for the slug field prior to saving it to the
database, given that the filename is 'randomly' generated?
Cheers,
Andrew
e.g. Generated filename:-
ebc151e5aad64f4b.jpg
Derived url for Photo view:-
http://mydomain.com/photo/ebc151e5aad64f4b
From reading the above mentioned ticket and it's referenced discussion
threads, I can't see any way of generating a slug field from the
filename before the instance is saved. At the moment I am saving the
instance to generate the hashed filename, creating the slug based on
the dynamically generated filename, and then saving to the database
again.
Doesn't seem ideal to me, especially as it worked without the extra
save in 1.0.2, and I'm using this dynamic filename/slug method on
several projects.
Cheers,
Andrew
I'm not seeking to have the new behaviour reversed - if the powers
that be say the new way is more robust, I'm happy to go along with
that. But I think it is important to realise that it is a backwards
incompatible change, and there are real use cases for the old
behaviour.
> There's a subtle bug in this line of your code:
>
> self.slug = self.photo.url.split('/')[-1:][0].split('.')[0]
>
> It assumes that the expression will never result in a slug whose
> length is more than 16. But that's not necessarily true because when
> Django tries to save your photo file and finds another file with the
> same name, it will append an underscore to the filename until it
> generates a unique filename. In that case, your slug will extend
> beyond its 16 character limit. Since your filenames are generated
> randomly, it's not very likely that you will ever hit this edge case.
> But just be aware.
Duplicate filenames are checked for elsewhere in my code (I only
presented a truncated version for clarity), so there shouldn't be a
case where underscores are used, and the slug will always be unique
(unless there are so many uploads, I run out of 16 hex digit
combinations!).
> As for your problem of computing a slug based on the filename, perhaps
> the following will help:
>
> def get_path(instance, filename):
> salt = hashlib.sha1(str(random.random())).hexdigest()
> name = hashlib.sha1(salt+filename).hexdigest()[:16]
> # Save generated filename in an attribute of this model instance:
> instance._my_filename = name
> return '%s.jpg' % (name)
>
> Then, in the model save do:
>
> if not self.slug:
> self.slug = self._my_filename
>
> If this does work, you might still have to add some more defensive
> code around there to check that the instance "hasattr" _my_filename
> and if it doesn't, may be just generated a random 16 character slug.
I don't think this will work, as get_path() will not be called until
the model is saved. So the _my_filename attribute will not be
available to the code in the save method before the super(Photo,
self).save() line.
Would it be possible to define the hashed filename in the save method,
and then pass it to the upload_to argument of the ImageField?
Thank you for your reply,
Andrew
In answer to my own question, this seems to work:-
def get_path(instance, name):
return instance._my_filename
class Photo(models.Model):
photo = models.ImageField(upload_to=get_path, blank=False)
...
slug = models.CharField(max_length=16, unique=True)
def save(self):
name = hashlib.sha1(str(random.random())).hexdigest()[:16]
self._my_filename = '%s.jpg' % (name)
self.slug = name
super(Post, self).save()
Cheers,
Andrew
Talking to myself again, I've changed the save method slightly:-
def save(self):
if not self.slug:
name = hashlib.sha1(str(random.random())).hexdigest()[:16]
self._my_filename = '%s.jpg' % (name)
self.slug = name
super(Post, self).save()
and added a blank=True to the slug field so that it only sets it on
the first save.
If there is a better way of doing all this, feel free to let me know.
If not, I hope this is of use to somebody.
Thanks for your pointers, Rajesh.
Cheers,
Andrew
--
Mirat Can Bayrak <miratca...@gmail.com>
The slug field has a unique=True argument, so if the same file name
does happen to be produced, it can't be saved to the database, and the
user will have to try again.