Generating Unique Reference Numbers in Django

May 20, 2021

At some point when developing any application you will probably need to generate some unique reference number. Some potential solutions for this could include using a UUID, database auto increment ID or try build something yourself using the current timestamp or something. The UUID option may not be feasible if you want something more human readable and I have not seen a perfect timestamp based implementation in my work experience.

Given this my goto for solving this problem is to use an auto increment field in the database. Auto incrementing fields are guaranteed to be unique and provide a simple way to solve this problem. If you need to store a reference number, and not calculate it at runtime, I have found the following approach works well.

class Reference(models.Model):
    """Generate unique reference numbers for other models.

    The only purpose of this model is to generate unique reference numbers.
    """

    # Django models need at least 1 field
    created_date = models.DateTimeField(auto_now_add=True)

    @classmethod
    def generate(cls, prefix: str) -> str:
        """Generate a unique reference number prefixed with the provided prefix.

        For example, you could generate an invoice number as follows:

        Reference.generate(prefix="INV") # INV-000001 etc.
        """

        instance = cls.objects.create()
        suffix = f"{instance.pk}".zfill(6)
        return f"{prefix}-{suffix}"


# Hypothetical models that may require a unique reference number
class Invoice(models.Model):
    """Holds invoice data."""

    invoice_number = models.CharField(max_length=25)
    total = models.DecimalField(max_digits=8, decimal_places=2)
    created_date = models.DateField()


class SupportTicket(models.Model):
    """Holds support ticket data."""

    ticket_number = models.CharField(max_length=25)
    description = models.TextField()
    created_date = models.DateField()


# Service layer functions
def create_invoice(*, total: Decimal) -> str:
    """Creates an invoice and return the invoice number."""

    created_date = timezone.now()
    invoice_number = Reference.generate(prefix="INV")

    logger.info("creating invoice")

    invoice = Invoice.objects.create(
        invoice_number=invoice_number,
        total=total,
        created_date=created_date,
    )
    return invoice.invoice_number

def create_support_ticket(*, description: str) -> str:
    """Create a support ticket and return the ticket number."""

    created_date = timezone.now()
    ticket_number = Reference.generate(prefix="TCK")

    logger.info("creating support ticket")

    ticket = SupportTicket.objects.create(
        ticket_number=ticket_number,
        description=description,
        created_date=created_date,
    )
    return ticket.ticket_number