0

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)
ragmats
  • 91
  • 8

1 Answers1

1

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.

Warren Henning
  • 140
  • 1
  • 7
  • I should have mentioned I was trying to avoid having separate upload_to functions because they do some other stuff that I omitted having to do with file re-naming, that is the same for both images and audio and seemed wrong to duplicate. But I can probably put renaming in its own function too, so this will probably work. However, if there is a way to detect the field type, I thought I should know how to do it. – ragmats Aug 12 '22 at 05:38
  • If you had to have a single `upload_to` function, you'd have to look at what fields are set and handle each case. The instance is going to be an instance of the Tile object. `def upload_to(instance, filename): if instance.image: ... elif instance.sound: ...` – Warren Henning Aug 12 '22 at 13:33
  • You can pull the common functionality into a helper function and call that. Or just have a tiny amount of duplication because it only occurs twice and it's close together in the code. – Warren Henning Aug 12 '22 at 13:36
  • Thanks, I have done this and updated my question to show the implementation. – ragmats Aug 12 '22 at 15:24