views:

1032

answers:

3

I'm trying to rename a file after it's uploaded in the model's save method. I'm renaming the file to a combination the files primary key and a slug of the file title.

I have it working when a file is first uploaded, when a new file is uploaded, and when there are no changes to the file or file title.

However, when the title of the file is changed, and the system tries to rename the old file to the new path I get the following error:

WindowsError at /admin/main/file/1/
(32, 'The process cannot access the file because it is being used by another process')

I don't really know how to get around this. I've tried just coping the file to the new path. This works, but I don't know I can delete the old version.

Shortened Model:

class File(models.Model):
    nzb = models.FileField(upload_to='files/')
    name = models.CharField(max_length=256)
    name_slug = models.CharField(max_length=256, blank=True, null=True, editable=False)

    def save(self):
        # Create the name slug.
        self.name_slug = re.sub('[^a-zA-Z0-9]', '-', self.name).strip('-').lower()
        self.name_slug = re.sub('[-]+', '-', self.name_slug)

        # Need the primary key for naming the file.
        super(File, self).save()

        # Create the system paths we need.
        orignal_nzb = u'%(1)s%(2)s' % {'1': settings.MEDIA_ROOT, '2': self.nzb}
        renamed_nzb = u'%(1)sfiles/%(2)s_%(3)s.nzb' % {'1': settings.MEDIA_ROOT, '2': self.pk, '3': self.name_slug}

        # Rename the file.
        if orignal_nzb not in renamed_nzb:
            if os.path.isfile(renamed_nzb):
                os.remove(renamed_nzb)

            # Fails when name is updated.
            os.rename(orignal_nzb, renamed_nzb)

        self.nzb = 'files/%(1)s_%(2)s.nzb' % {'1': self.pk, '2': self.name_slug}

        super(File, self).save()

I suppose the question is, does anyone know how I can rename an uploaded file when the uploaded file isn't be re-uploaded? That's the only time it appears to be locked/in-use.


Update:

Tyler's approach is working, except when a new file is uploaded the primary key is not available and his technique below is throwing an error.

if not instance.pk:
    instance.save()

Error:

maximum recursion depth exceeded while calling a Python object

Is there any way to grab the primary key?

+5  A: 

I think you should look more closely at the upload_to field. This would probably be simpler than messing around with renaming during save.

http://docs.djangoproject.com/en/dev/ref/models/fields/#filefield

This may also be a callable, such as a function, which will be called to obtain the upload path, including the filename. This callable must be able to accept two arguments, and return a Unix-style path (with forward slashes) to be passed along to the storage system. The two arguments that will be passed are:

S.Lott
But isn't that callable only used when a file is actually uploaded? If so, specifying it wouldn't suffice to only rename an exisiting file when a model instance is saved.
Guðmundur H
I believe Guðmundur is correct. If the upload_to field was used and the title of the file was updated the file wouldn't end up being renamed with the new title.
Ty
My point is that renaming after the fact isn't well supported. Renaming earlier in the process is well supported. Perhaps rethinking the solution to fit better with what Django does out of the box would be helpful.
S.Lott
Check this out Ty: http://www.djangosnippets.org/snippets/1129/ A model that renames files after saving to have PK-values as names.
Guðmundur H
Before trying to include the name, I was naming the file as the primary key. It worked perfectly because the PK never changes and the file never has to be renamed. If I can't use the value of another field, I'll probably go back to that route. I just wanted the files names to be more meaningful.
Ty
@S.Lott: According to the docs, if I were to use upload_to I'd be in the same boat I'm in now, except I wouldn't even be able to access the primary key.
Ty
I've taken your advice. The code is much cleaner now. However, I can not access the primary key in the upload_to function that's called. Do you know how I can get the primary key?
Ty
Do you mean the File.id field? It doesn't have a value until after the save(). Perhaps you should open a new question with your revised code.
S.Lott
A: 

Once uploaded, all you have is an image object in memory, right?

You could save this object yourself in the folder of your choice, and then edit the database entry by hand.

You'd be bypassing the whole Django ORM, and is not something I'd do unlessI couldn't find a more Django way.

voyager
I'm not exactly sure what you mean by this.
Ty
I just modifyed the whole answer to make it clearer. Hope it helps.
voyager
+3  A: 

My other answer is deprecated, use this instead:

class File(models.Model):
    nzb = models.FileField(upload_to=get_filename)
    ...
    def get_filename(instance, filename):
        if not instance.pk:
            instance.save()
        # Create the name slug.
        name_slug = re.sub('[^a-zA-Z0-9]', '-', instance.name).strip('-').lower()
        name_slug = re.sub('[-]+', '-', name_slug)

        filename = u'filess/%(2)s_%(3)s.nzb' % {'2': instance.pk, '3': name_slug}

        return filename

As of 1.0, upload_to can be callable, in which case it is expected to return the filename, including path (relative to MEDIA_ROOT).

tghw
This works pretty well, however, instance.save() throws this error: "maximum recursion depth exceeded in __instancecheck__". Do you know what that's about?
Ty
If the file name (or in this case name_slug) changes and the file isn't re-uploaded the file name isn't changed. Not a big deal, but it's what I was gunning for. This whould still be cleaner then how I was doing it before. Do you know how to fix that error?
Ty
Also, I could only get it to work if "get_filename" was defind outside of the File class, and above it.
Ty