Django FileField storage option
Asked Answered
T

1

5

I have this model:

class UserProfile(models.Model):
    #..........
    photo = models.ImageField(upload_to = get_upload_file_name,
                              storage = OverwriteStorage(),
                              blank = True, null = True,
                              height_field = 'photo_height',
                              width_field = 'photo_width')

And this is my storage function:

class OverwriteStorage(FileSystemStorage):
    def _save(self, name, content):
        self.delete(r'.*')
        return super(OverwriteStorage, self)._save(name, content)

    def get_available_name(self, name):
        return name

How can I do the following 2 things:

  1. Whenever a user uploads a file (i.e. an image), I want to delete the old one, no matter if the name is the same or not. I tried to delete anything that matches the regex above, but this is not working.

  2. If the user uploads an image called "me.jpg" I want to rename it differently, depending on the user username for example. So I will do something like return super(OverwriteStorage, self)._save(SOMETHING_ELSE_HERE, content) How to do this? can I pass an additional parameter to the OverwriteStorage funcion?

And an additional third question: I've created a ModelForm for this form. So a user can upload an image. So when someone presses 'choose file', a windows window pops up in order to browse and choose a photo. How can I only display certain files here? (eg. only .jpg and .jpeg files)

Thanks!

EDIT: the get_upload_file_name function

def get_upload_file_name(instance, filename):
    return "%s/%s/profile_photo/%s" % (instance.user.username[0].lower(), instance.user.username, filename)

EDIT2: I have included my models.py

import datetime
import os
import urllib2, urlparse
import re

from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.utils.translation import ugettext_lazy as _
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
from django.core.files.storage import FileSystemStorage
from django.contrib.staticfiles import finders
from django.conf import settings
from django.core.files.base import ContentFile
from django.forms import widgets

now = datetime.datetime.now()

def get_upload_file_name(instance, filename):
    now = datetime.datetime.now()

    file_name = str(now.year)  + '_' + \
                str(now.month) + '_' + \
                str(now.day)   + '_' + \
                str(now.hour)  + '_' + \
                str(now.minute)+ '_' + \
                str(now.second)+ '.' + \
                filename.split('.')[-1]

    return "%s/%s/profile_photo/%s" % (instance.user.username[0].lower(),
                                       instance.user.username,
                                       file_name)

class OverwriteStorage(FileSystemStorage):
    def _save(self, name, content):
        self.delete(name)
        return super(OverwriteStorage, self)._save(name, content)

class UserProfileManager(models.Manager):

    def create_user_profile(self, user):
        user_profile = self.create(user = user)
        return user_profile

class UserProfile(models.Model):

    ### it is now.year - 13 because legitimate persons on this website should be over 14 years old
    YEARS = tuple(
                  zip([format(x,'04d') for x in range(now.year-120, now.year-13)],
                      [format(x,'04d') for x in range(now.year-120, now.year-13)]
                      )
                  )
    MONTHS = (
              ('January','January'),('February','February'),('March','March'),('April','April'),
              ('May','May'), ('June','June'),('July','July'),('August','August'),
              ('September','September'),('October','October'),('November','November'), ('December', 'December')

             )
    GENDERS = (('M', 'Male'), ('F', 'Female'))

    user = models.OneToOneField(User, related_name = 'MoreAboutUser', unique=True, verbose_name=_('user'))
    year_of_birth = models.CharField(max_length=10, blank = True,  null = True, choices=YEARS)
    month_of_birth = models.CharField(max_length=10, blank = True,  null = True, choices=MONTHS)
    gender = models.CharField(max_length=1, blank = True,  null = True, choices=GENDERS)
    photo = models.ImageField(upload_to = get_upload_file_name,
                              blank = True, null = True,
                              height_field = 'photo_height',
                              width_field = 'photo_width',
                              #widget = widgets.FileInput(attrs={'accept': 'image/gif,image/png,image/jpeg'})
                              )
    photo_height = models.PositiveIntegerField(blank = True, default = 0)
    photo_width = models.PositiveIntegerField(blank = True, default = 0)
    creation_time = models.DateTimeField(auto_now_add = True, auto_now = False)
    update_time = models.DateTimeField(auto_now_add = False, auto_now = True)

    class Meta:
            verbose_name = _('user profile')
            verbose_name_plural = _('user profiles')

    def __unicode__(self):
        return self.user.username

    objects = UserProfileManager()

    def get_profile_photo_url(self):
        if self.photo and hasattr(self.photo, 'url'):
            return self.photo.url
        else:
            return '/static/images/generic_profile_photo.jpg'

def create_user_profile(sender, instance, created, **kwargs):

    if created:
        try:
            profile = UserProfile.objects.create_user_profile(user = instance)
            profile.save()
        except:
            pass

post_save.connect(create_user_profile, sender=User)
Trimerous answered 28/3, 2014 at 11:42 Comment(0)
S
8

The storage API doesn't know anything about your model, so it cannot take other fields' values in consideration — it doesn't know the name of the old file stored in that field, nor it knows which user owns the model record.

You seem on the right track with providing an upload_to = get_upload_file_name option to your ImageField; the get_upload_file_name function (which you haven't posted in your question) will be able to construct the user-based filename for the image, as it gets a reference to the model instance, so it knows the user who owns the model instance.

As for the deletion of the old file, you will likely need to implement that in your model's save() method. It is too late at that point to find the old filename from your existing model instance, as its photo field will be already updated with its new value; still you can retrieve a copy of the existing record from the database and delete the old file through it. Here is an example implementation:

class UserProfile(models.Model):

    ...

    def save(self, *args, **kwargs):

        # grab a copy of the old record from the database
        oldrec = type(self).objects.get(pk=self.pk)

        # save the current instance, and keep the result
        result = super(UserProfile, self).save(*args, **kwargs)

        # if the path is the same, the file is overwritten already
        # and no deletion is necessary
        if oldrec.photo.path != self.photo.path:
            oldrec.photo.delete()

        # return the result received from the parent save() method
        return result

To specify acceptable types for your file selection, you will need to provide an accept attribute to the <input> tag that will be rendered for your field; you'll need to override the widget in a custom form.

Scout answered 4/4, 2014 at 8:57 Comment(4)
right, I have now modified get_upload_file_name and can rename the input file. However, I;m not sure what you do you mean that I should do in the case of replace. But an ideea came up to me: why not to delete the old image in the get_file_name function? :) and for the widget, I get an error :( 'widget' unexepctedTrimerous
In the get_upload_file_name function you still cannot be certain that the model instance will get to be saved at all (e.g. if it fails validation, or if its save method is never called). Deleting the file that early might leave you with a stale record with a missing file. Ideally, you should delete the old file only after you save the new one (making sure the old file doesn't have the same name, so you don't actually delete the new file). Can you post your full photo = models.ImageField(...) definition so that I can help you with the unexpected widget parameter.Scout
I have included my models.py. Maybe you cand guide me as well how to delete the old image. I understood the ideea, but I am not sure how to do it exactlyTrimerous
How are you preparing the form for you model? Do you have a custom form for the UserProfile model? You'll need one in order to override the widget for the photo form field, apparently you can't override it from the model field itself.Scout

© 2022 - 2024 — McMap. All rights reserved.