Search code examples
djangodjango-modelsdjango-formsuuid

Duplicate UUID if we enter a new input from html?


getting error- i saved one form from html into my database while when i tried saving the next it gave me this error- IntegrityError at customer (1062, "Duplicate entry 'ad138e46-edc0-11va-b065-a41g7252ecb4'' for key 'customer.PRIMARY'") kindly explain my uuid in models.py is -

class customer(models.Model):
    customerid = models.CharField(default=str(uuid.uuid4()), max_length=500, primary_key=True)
    customername=models.CharField(max_length=100)

kindly help

Updated

Form.py

class createcustomerform(ModelForm):
    class Meta:
        model=customer
        fields=[
'customername']

Updated Models.py

import uuid
from uuid import UUID
from django.contrib.auth.models import User
from django.dispatch.dispatcher import receiver
from django.utils.translation import ugettext_lazy as _
from django.db.models.signals import pre_save
class customer(UUIDMixin,models.Model):
    customername=models.CharField(max_length=100)
    def __str__(self):
        return self.customername
class UUIDMixin(models.Model):
       uuid = models.UUIDField(blank=True,db_index=True,default=None,help_text=_('Unique identifier'),max_length=255,null=True,unique=True,verbose_name=_('UUID'))
       class Meta:
            abstract = True
@classmethod
def check_uuid_exists(cls, _uuid):
        #Determine whether UUID exists """
          manager = getattr(cls, '_default_manager')
          return manager.filter(uuid=_uuid).exists()
          
@classmethod
def get_available_uuid(cls):
        #Return an Available UUID """
          row_uuid = uuid.uuid4()
          while cls.check_uuid_exists(uuid=row_uuid):
            row_uuid = uuid.uuid4()
            return row_uuid       
@receiver(pre_save)
def uuid_mixin_pre_save(sender, instance, **kwargs):
    if issubclass(sender, UUIDMixin):
        if not instance.uuid:
            manager = getattr(instance.__class__, '_default_manager')
            use_uuid = uuid.uuid4()
        while manager.filter(uuid=use_uuid):
            use_uuid = uuid.uuid4()
            instance.uuid = use_uuid
            #Automatically populate the uuid field of UUIDMixin models if not already populated.

Solution

  • You should use a proper UUIDField and avoid setting a default value.

    Make sure the value is set when the object is created, and also ensure that the value is unique - obviously the chance of duplication in a UUID is incredibly small which is the whole point.

    You could create yourself a model mixin which will add a uuid to your model(s) and ensure that the value is set when the object is saved;

    
    
    class UUIDMixin(models.Model):
        """
        Mixin for models contain a unique UUID, gets auto-populated on save
        """
        uuid = models.UUIDField(
            blank=True,
            db_index=True,
            # default=uuid.uuid4,
            # NB: default is set to None in migration to avoid duplications
            default=None,
            help_text=_('Unique identifier'),
            max_length=255,
            null=True,
            unique=True,
            verbose_name=_('UUID'),
        )
    
        class Meta:
            """Metadata for the UUIDMixin class"""
            abstract = True
    
        @classmethod
        def check_uuid_exists(cls, _uuid):
            """ Determine whether UUID exists """
            manager = getattr(cls, '_default_manager')
            return manager.filter(uuid=_uuid).exists()
    
        @classmethod
        def get_available_uuid(cls):
            """ Return an Available UUID """
            row_uuid = uuid.uuid4()
            while cls.check_uuid_exists(uuid=row_uuid):
                row_uuid = uuid.uuid4()
            return row_uuid
    
    
    @receiver(pre_save)
    def uuid_mixin_pre_save(sender, instance, **kwargs):
        """
        Automatically populate the uuid field of UUIDMixin models if not already
        populated.
        """
        if issubclass(sender, UUIDMixin):
            if not instance.uuid:
                manager = getattr(instance.__class__, '_default_manager')
                use_uuid = uuid.uuid4()
                while manager.filter(uuid=use_uuid):
                    use_uuid = uuid.uuid4()
                instance.uuid = use_uuid
    

    Based on your comment, let me explain more.

    The above is an abstract model, meaning it doesn't create a table itself, it can just be used by other (concrete) models so that they can use what it defines.

    It's a benefit of classes & inheritance. Allowing you to not duplicate code on things that will be useful on many models.

    No, in the meta you'll see abstract = True. So you define your models like MyModel(UUIDMixin, models.Model) and it gets a uuid field from this abstract model along with whatever you define.

    You would use it by doing something like this;

    class Customer(UUIDMixin, models.Model):
        name = models.CharField(max_length=100)
    

    If you really want to use the UUID as a primary key, maybe setup two mixins, a PrimaryUUIDMixin and the UUIDMixin, because on the whole a smaller value on the primary key may be more efficient.

    You also mentioned Undefined variable: _

    Typically in django you'd see an import at the top of the file like this;

    from django.utils.translation import ugettext_lazy as _
    

    This is then used to wrap strings so that they can be translated, for example, _("Hello")

    Even if you don't translate your project, it's common to just include this anyway. You can read up on that here; https://docs.djangoproject.com/en/3.1/topics/i18n/translation/#standard-translation