5

I have a primary key ID CharField in my model Image, I want to create unique IDs for newly created objects. I try to achieve this by overriding the model's save method:

    def save(self, *args, **kwargs):
        if not self.pk: # is created
            self.id = uuid.uuid4().hex
            while Image.objects.filter(id=self.id).exists():
                self.id = uuid.uuid4().hex
        return super().save(*args,**kwargs)

The problem is, save() doesn't seem to be called when I create objects with Image.objects.create(), it is only called when I create the object with image=Image(...) and then call image.save(). As a result, the newly created object has no id assigned unless specified, which causes PostgreSQL to throw a non_unique primary key error.

How can I make sure that unique IDs are created upon calling Image.objects.create()?

Django version: 1.11.3

UPDATE: I realized that the overridden save() method was not called either. Turns out the problem was I was calling model's save method in a migration. As it is pointed out in this post custom model methods are not available in migrations. I will have to copy the model's save method to the migration file.

6
  • 2
    The point of UUID (universally unique identifier) is that you can always assume that two UUIDs are distinct. You'd be better off setting your id field to have default=uuid.uuid4 (no parentheses after the uuid4) and this will happen automatically on object creation. Commented Mar 27, 2019 at 14:01
  • I wish :( Database was designed before me and now it is very difficult to change the primary key field without losing data so this is not an option for me. Commented Mar 27, 2019 at 14:05
  • by the docs To create an object, instantiate it using keyword arguments to the model class, then call save() to save it to the database. so please clarify why you think so? Commented Mar 27, 2019 at 14:06
  • is you model pk name id? Commented Mar 27, 2019 at 14:09
  • docs also say that To create and save an object in a single step, use the create() method. so I would expect this using create() cause a problem. Besides, code base is big and there are different ways that create objects (e.g. get_or_create()). I don't want to go through the whole code base and hunt down these methods to see whether they use create() or save(). Commented Mar 27, 2019 at 14:26

2 Answers 2

4

This can't be done in general. In between your if statement checking that the ID doesn't exist yet and you setting it, something else could add a new row with that ID. Which is why other solutions are used -- an auto-incrementing ID that the database ensures is unique, or a UUID that has a really tiny chance of being unique.

Luckily, you use one those. With UUIDs the custom is to just assume that they are unique.

The way to do it is to set a function returning the unique ID as the field's default:

def uuid_hex():
    return uuid.uuid4().hex

class YourModel(models.Model):
    id = CharField(unique=True, primary_key=True, default=uuid_hex, null=False)
Sign up to request clarification or add additional context in comments.

3 Comments

Forgot that the default has to be an actual function, not a lambda. Does this work?
I think it does. The problem was that I was trying to do this in a migration (see my update).
@yam: ah yes, in a migration you don't get the real model classes, you get something model-like that has the same fields as the models had at the time the migrations were made.
1

You should call the real save method while overriding the model save method:

def save(self, *args, **kwargs):
    if not self.pk: # is created
        self.id = uuid.uuid4().hex
        while Image.objects.filter(id=self.id).exists():
            self.id = uuid.uuid4().hex
    super(Image, self).save(*args, **kwargs)

Check docs: Overriding predefined model methods

Also it's not a good idea to override the default id. If you need another unique field to be used as something like an ID then i recommend adding another field alongside the default id field.

4 Comments

I'm sorry, I'd forgotten to add that part. I do call the save method. Updating my post.
Oh, okay then, but still i don't think this is a good idea. Assigning id in django level is not a good idea. let the main id be created by database which is what it does best and add another field.
I agree. Unfortunately the database was designed this way by someone else and it turns out to be very difficult (if not impossible) to change the ID field into a self-incrementing integer field without breaking M2M relationships with Django migrations. So this is the only other solution I could think of.
I see. I would've tried to convert the data to a new structure that fits better with both django and new technologies if the amount of data isn't huge but still i don't have enough information to actually recommend that. So best of lucks ;)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.