pythondjangodjango-modelsimagefieldfilefield

How do I detect if my user has uploaded an ImageField or FileField?


I have the following model and function to set the upload paths for either image files or other files (which will be audio files).

def upload_to(instance, filename):
    # Check for image or sound file and put in appropriate directory
    if isinstance(instance, models.ImageField):
        print("detected image file")
        return "x/img/%s" % (filename)
    else:
        print("detected non-image file")
        return "x/aud/%s" % (filename)

class Tile(models.Model):
    image = models.ImageField(upload_to=upload_to, default="../static/x/img/default.svg")
    sound = models.FileField(upload_to=upload_to, null=True, blank=True)

The condition in upload_to is not correct, I realize, as instance is an instance of the tile object with all fields, but I'm not sure what to do in the upload_to function to find out if the file that was just uploaded by the user is an ImageField or FileField.

Based on Warren's suggestion, here is the solution I have have implemented in case anyone is curious, with less omitted for clarity:

def upload_image_to(instance, filename):    
    filename = filenamer_helper(instance, filename)
    return f"x/img/{instance.user.id}/{instance.grid.id}/{filename}"


def upload_audio_to(instance, filename):
    filename = filenamer_helper(instance, filename)
    return f"x/aud/{instance.user.id}/{instance.grid.id}/{filename}"


# Adds normalized 3-digit number to beginning of filename
def filenamer_helper(instance, filename):
    needs_tile_number = False
    # Check if tile number is already in the filename
    if f"{instance.tile_number}_" not in filename[0:4]:
        needs_tile_number = True
    # Prepend normalized ### tile number to filename if needed
    if needs_tile_number:
        if needs_tile_number and instance.tile_number < 10:
            filename = f"00{instance.tile_number}_{filename}"
        elif needs_tile_number and instance.tile_number < 100:
            filename = f"0{instance.tile_number}_{filename}"
        else:
            filename = f"{instance.tile_number}_{filename}"
    return filename
    

class Tile(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_tiles")
    grid = models.ForeignKey(Grid, on_delete=models.CASCADE, related_name="grid_tiles")
    tile_number = models.IntegerField(default=0)
    image = models.ImageField(upload_to=upload_image_to, default="../static/x/img/default.svg")
    audio = models.FileField(upload_to=upload_audio_to, null=True, blank=True)
    text = models.CharField(max_length=100, null=True, blank=True)

Solution

  • Could you have two separate upload_to functions, one for each field/file type?

    def upload_to_image(instance, filename):
        return "x/img/%s" % (filename)
    
    def upload_to_sound(instance, filename):
        return "x/aud/%s" % (filename)
    
    class Tile(models.Model):
        image = models.ImageField(upload_to=upload_to_image, default="../static/x/img/default.svg")
        sound = models.FileField(upload_to=upload_to_sound, null=True, blank=True)
    

    In fact, if you just need separate directories for each field type, it looks like you should be able to pass that directly to the upload_to field:

    class Tile(models.Model):
        image = models.ImageField(upload_to="x/img/", default="../static/x/img/default.svg")
        sound = models.FileField(upload_to="x/aud/", null=True, blank=True)
    

    and omit the callback functions entirely.