rename uploaded image based on the field that its uploaded from

Hello, Im uploading 2 images in a form for a user

my model looks like this,

class Image(models.Model):
    def wrapper(instance, filename):
        ext = filename.split(".")[-1].lower()

        if ext not in ["jpg", "png", "gif", "jpeg"]:
            raise ValidationError(f"invalid image extension: {filename}")

            filename = f"{}, {ext}"
            filename = f"{filename}.{ext}"
        return os.path.join("images", filename)

    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="user_img", db_index=True, primary_key=True)
    head_image = models.ImageField(upload_to=wrapper)
    body_image = models.ImageField(upload_to=wrapper)

the form and upload is working, but what I want to do is rename each of the 2 uploaded images like this,

field1: head image > renamed to > (userID)_head.(extension) > ie, 25_head.jpg

same for 2nd img field,

body image > renamed to 25_body.jpg

the wrapper func takes instance+filename, but has no way of knowing whether its a “body” or “head” image

how do I pass this data to the wrapper function? Thanks

You could create 2 separate functions, either explicit or implicit with lambda:

# target determines 'head' vs 'body'
def wrapper(instance, filename, target): ...

# in your model:
    head_image = models.ImageField(upload_to=lambda inst, fn: wrapper(inst, fn, 'head'))
    body_image = models.ImageField(upload_to=lambda inst, fn: wrapper(inst, fn, 'body'))

or use a decorated function closure (prolly easier to read):

def wrapper(target):
    def inner(instance, filename):
    return inner

# in your model:
    head_image = models.ImageField(upload_to=wrapper('head'))
    body_image = models.ImageField(upload_to=wrapper('body'))
1 Like

Very good examples @jerch. I’ll give yet another one, using functools partial.

from functools import partial

# Update your wrapper function to accept a target
def wrapper(instance, filename, target):

    # on your model
    head_image = models.ImageField(upload_to=partial(wrapper, target="head")
    body_image = models.ImageField(upload_to=partial(wrapper, target="body")

1 Like

And I’ll jump into this thread to point out that there is no requirement, and very little benefit, to ensuring that the file name as it is stored in the file system have any relationship to how that file is represented to a user. (And in fact, creates a source of problems if you ever need to relocate those files to a different storage environment.)

awesome, ill try this, thanks for the help

reason is I need some way to identify what type of img it is and display it in the right position inside a template ie,

head img goes to Head img section, body to body section

whats a better way to do this?

A database purist would tell you that overloading the semantics of a file name with “process-based meaning” is a violation of the principles of normalization.

That type of information would be most appropriately stored as meta-data associated with that field.

For example, all our systems handling uploaded files have a separate “FileHandler” model. This model contains fields for things like the original file name, file type (e.g. “csv”, “txt”, “pdf”, etc), upload date, uploaded by, description, etc.

Then, each model that can be associated with an uploaded file either has a one-to-one relationship with that model (if only one file is appropriate), or a many-to-many relationship.

As a result, we don’t know or care what the underlying directory structure - or file names - that are used for the storage of those files. The file field tracks that, which is all that is important for that field. Everything else is managed by the metadata associated with that file.

interesting thanks for the explanation, the site Im building is for like 300-400 users (it wont grow much more than that), does it make sense to setup metadata for these?

maybe something like

class Image(model.Model)
  user = OneToOneField(etc)
  image = ImageField(etc)
  metadata = OneToOneFIeld(ImageHandler, etc)

class ImageHandler(model.Model)
  size = IntField
  extension = StringField ("jpeg, png, etc")
  description = StringField("body/head")

Just a positive note, coming from other python web frameworks (flask, fastapi), the community in django is phenomenal, easy to get help on dumb questions, makes it very productive and easy to get stuff done.

this is working now, thanks for the help!

Maybe. Maybe not.

The degree to which you adhere to any specific set of architectural standards is always a judgement call. All such standards and principles are a trade-off of sorts. In essence, you’re making a decision between short-term objectives and long-term viability. (“You can pay me now or you can pay me later…”) And those are decisions that can only be made in the context of your business model.

But in your specific case, what model is using that Image model? Understanding the larger context may be helpful here.

What I was thinking of would be closer to:

class Image(model.Model):
    head_image = OneToOneField('ImageHandler')
    body_image = OneToOneField('ImageHandler')

class ImageHandler(models.Model):
    image = ImageField()
    size = IntegerField(...)
    extension = CharField(...)
    description = CharField(...)

Here, the ImageHandler is effectively a direct replacement for the ImageField in your Image model. It has become a separate model to allow for the explict management of your metadata associated with that field.

In this case, you would not actually need an explicit “file type” field, because those semantics are defined by the one-to-one relationship of the Image field. (By definition, the ImageHandler being referred to by the head_image is, by definition, a file type of head.)

1 Like