[Answered ]-Custom Id in Django Models

1👍

This problem is pretty similar to one I had solved for a previous project of mine. What I had done for this was to simply use the default id for the primary key, while using some extra fields to make the composite identifier needed.

To ensure the uniqueness and the restarting of the count I had made a model which would (only by the logic, no actual constraints) only have one row in it. Whenever a new instance of the model which needs this identifier would be created this row would be updated in a transaction and it’s stored value would be used.

The implementation of it is as follows:

from django.db import models, transaction

import datetime


class TokenCounter(models.Model):
    counter = models.IntegerField(default=0)
    last_update = models.DateField(auto_now=True)

    @classmethod
    def get_new_token(cls):
        with transaction.atomic():
            token_counter = cls.objects.select_for_update().first()
            if token_counter is None:
                token_counter = cls.objects.create()
            if token_counter.last_update.year != datetime.date.today().year:
                token_counter.counter = 0
            token_counter.counter += 1
            token_counter.save()
            return_value = token_counter.counter
        return return_value

    def save(self, *args, **kwargs):
        if self.pk:
            self.__class__.objects.exclude(pk=self.pk).delete()
        super().save(*args, **kwargs)

Next suppose you need to use this in some other model:

class YourModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    yearly_token = models.IntegerField(default=TokenCounter.get_new_token)

    @property
    def token_number(self):
        return '{}{}'.format(self.created_at.year, str(self.yearly_token).zfill(4))
    
    @classmethod
    def get_from_token(cls, token):
        year = int(token[:4])
        yearly_token = int(token[4:])
        try:
            obj = cls.objects.get(created_at__year=year, yearly_token=yearly_token)
        except cls.DoesNotExist:
            obj = None
        return obj

Note: This might not be very refined as the code was written when I was very inexperienced, and there may be many areas where it can be refined. For example you can add a unique_for_year in the yearly_token field so:

yearly_token = models.IntegerField(default=TokenCounter.get_new_token, unique_for_year='created_at')

Leave a comment