Skip to content

Commit fe1dbc9

Browse files
committed
refactor: implement DB-level UUIDv7 generation and fix filter_validity return type
- Replace Python uuidv7() with cross-database GenerateUUIDv7 Func for native UUIDv7. - Change filter_validity in OpenIMISHistoryMixin to return a list of Q objects. - Update imports in history_model.py and openimis_model.py.
1 parent 110bd83 commit fe1dbc9

4 files changed

Lines changed: 74 additions & 27 deletions

File tree

core/models/history_model.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from django.db import models, transaction
66
from django.db.models import F
77
from dirtyfields import DirtyFieldsMixin
8-
from core.utils import CachedManager, CachedModelMixin, uuidv7
8+
from core.utils import CachedManager, CachedModelMixin, uuidv7 # , GenerateUUIDv7
99
from simple_history.utils import bulk_update_with_history, bulk_create_with_history
1010
from django.db.models import (
1111
DateTimeField, Model, IntegerField,
@@ -50,7 +50,11 @@ class HistoryModel(DirtyFieldsMixin, CachedModelMixin, Model):
5050
version = IntegerField(default=1)
5151

5252
id = models.UUIDField(
53-
primary_key=True, db_column="UUID", default=None, editable=False
53+
primary_key=True,
54+
db_column="UUID",
55+
# db_default=GenerateUUIDv7(),need django 5
56+
default=None,
57+
editable=False
5458
)
5559
objects = HistoryModelManager()
5660
is_deleted = models.BooleanField(db_column="isDeleted", default=False)

core/models/openimis_model.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
Q, UUIDField, DateTimeField, BooleanField, Model, IntegerField, BigAutoField, JSONField,
77
)
88
from simple_history.models import HistoricalRecords
9-
from core.utils import CachedManager, CachedModelMixin, filter_validity as core_filter_validity, uuidv7
9+
from core.utils import CachedManager, CachedModelMixin, filter_validity as core_filter_validity, uuidv7 # , GenerateUUIDv7
1010
from simple_history.utils import get_history_manager_for_model
1111
import datetime as base_datetime
1212

@@ -158,9 +158,9 @@ def copy(self, exclude_fields=["id", "uuid"]):
158158
def filter_validity(arg="validity", prefix="", **kwargs):
159159
validity = kwargs.get(arg, None)
160160
if not validity:
161-
return Q(active=True)
161+
return [Q(active=True)]
162162
else:
163-
return Q(active=False) | Q(date_deactivated__gte=validity)
163+
return [Q(active=False) | Q(date_deactivated__gte=validity)]
164164

165165
class Meta:
166166
abstract = True

core/utils.py

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,14 @@
2424
import threading
2525
from django.db import transaction
2626
# from simple_history.utils import update_change_reason
27-
import time
28-
import os
29-
27+
from django.db.models import Func, UUIDField
28+
from django.db import db_connection
29+
from uuid6 import uuid7 as uuidv7 # noqa: F401
3030
try:
3131
from simple_history.models import HistoricalRecords
3232
except Exception:
3333
HistoricalRecords = None
3434

35-
3635
_request_local = threading.local()
3736

3837
logger = logging.getLogger(__file__)
@@ -603,29 +602,72 @@ def clean_fk(instance):
603602
return field_values
604603

605604

606-
def uuidv7() -> uuid.UUID:
605+
class GenerateUUIDv7(Func):
607606
"""
608-
Generate a UUIDv7.
609-
"""
610-
# random bytes
611-
value = bytearray(os.urandom(16))
607+
Cross-database UUIDv7 generator.
612608
613-
# current timestamp in ms
614-
timestamp = int(time.time() * 1000)
609+
Usage:
610+
from yourapp.db_functions import GenerateUUIDv7
615611
616-
# timestamp
617-
value[0] = (timestamp >> 40) & 0xFF
618-
value[1] = (timestamp >> 32) & 0xFF
619-
value[2] = (timestamp >> 24) & 0xFF
620-
value[3] = (timestamp >> 16) & 0xFF
621-
value[4] = (timestamp >> 8) & 0xFF
622-
value[5] = timestamp & 0xFF
612+
class YourModel(models.Model):
613+
id = models.UUIDField(
614+
primary_key=True,
615+
db_default=GenerateUUIDv7(),
616+
editable=False,
617+
)
618+
"""
619+
output_field = UUIDField()
620+
template = '%(function)s()'
621+
622+
def as_sql(self, compiler, connection, **extra_context):
623+
vendor = db_connection.vendor
624+
625+
if vendor == 'postgresql':
626+
# PostgreSQL 18+ has native uuidv7()
627+
# Older versions use our custom uuid_generate_v7()
628+
pg_version = getattr(connection, 'pg_version', 0)
629+
if pg_version >= 180000:
630+
function = 'uuidv7'
631+
else:
632+
function = 'uuid_generate_v7'
623633

624-
# version and variant
625-
value[6] = (value[6] & 0x0F) | 0x70
626-
value[8] = (value[8] & 0x3F) | 0x80
634+
elif vendor == 'microsoft':
635+
# SQL Server - uses our custom function
636+
# Change to 'dbo.uuid_v8mssql' if you want the optimized version
637+
# for better clustered index performance
638+
function = 'dbo.uuid_v7'
627639

628-
return uuid.UUID(bytes=bytes(value))
640+
else:
641+
# Fallback for other databases (or raise error)
642+
function = 'uuid_generate_v7'
643+
644+
extra_context['function'] = function
645+
return super().as_sql(compiler, connection, **extra_context)
646+
647+
648+
class RandomUUID(Func):
649+
"""Cross-database random UUID for Django 4.2"""
650+
function = None # Will be overridden per backend
651+
output_field = UUIDField()
652+
arity = 0
653+
654+
def as_postgresql(self, compiler, connection, **extra_context):
655+
# PostgreSQL 13+ has gen_random_uuid() built-in
656+
return self.as_sql(compiler, connection, function='gen_random_uuid', **extra_context)
657+
658+
def as_microsoft(self, compiler, connection, **extra_context): # MSSQL
659+
return self.as_sql(compiler, connection, function='NEWID', **extra_context)
660+
661+
# Optional: fallback for other DBs (e.g. SQLite for dev)
662+
def as_sql(self, compiler, connection, **extra_context):
663+
# if connection.vendor == 'postgresql':
664+
# return self.as_postgresql(compiler, connection, **extra_context)
665+
# elif connection.vendor in ('microsoft', 'mssql'):
666+
# return self.as_microsoft(compiler, connection, **extra_context)
667+
if extra_context.get('function', self.function) is None:
668+
# You can raise or use a default
669+
raise NotImplementedError(f"RandomUUID not supported on {connection.vendor}")
670+
return super().as_sql(compiler, connection, **extra_context)
629671

630672

631673
class CachedModelMixin:

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"zxcvbn",
3434
"django-ratelimit",
3535
"pandas",
36+
"uuid6",
3637
],
3738
classifiers=[
3839
"Environment :: Web Environment",

0 commit comments

Comments
 (0)