Merge pull request #360 from marfyl/master

Add UUIDModel and UUIDField
This commit is contained in:
Asif Saif Uddin 2019-05-16 05:45:53 +06:00 committed by GitHub
commit 25dde41418
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 212 additions and 9 deletions

View file

@ -51,4 +51,4 @@
| Jack Cushman <jcushman@law.harvard.edu>
| Zach Cheung <kuroro.zhang@gmail.com>
| Daniel Andrlik <daniel@andrlik.org>
| marfyl <github.com/marfyl>

View file

@ -17,6 +17,7 @@ master (unreleased)
- Fix patched `save` in FieldTracker
- Upgrades test requirements (pytest, pytest-django, pytest-cov) and
skips tox test with Python 3.5 and Django (trunk)
- Add UUIDModel and UUIDField support.
3.1.2 (2018.05.09)
------------------

View file

@ -154,3 +154,29 @@ If no marker is found in the content, the first two paragraphs (where
paragraphs are blocks of text separated by a blank line) are taken to
be the excerpt. This number can be customized by setting the
``SPLIT_DEFAULT_PARAGRAPHS`` setting.
UUIDField
----------
A ``UUIDField`` subclass that provides an UUID field. You can
add this field to any model definition.
With the param ``primary_key`` you can set if this field is the
primary key for the model, default is True.
Param ``version`` is an integer that set default UUID version.
Versions 1,3,4 and 5 are supported, default is 4.
If ``editable`` is set to false the field will not be displayed in the admin
or any other ModelForm, default is False.
.. code-block:: python
from django.db import models
from model_utils.fields import UUIDField
class MyAppModel(models.Model):
uuid = UUIDField(primary_key=True, version=4, editable=False)

View file

@ -86,3 +86,26 @@ SoftDeletableModel
This abstract base class just provides field ``is_removed`` which is
set to True instead of removing the instance. Entities returned in
default manager are limited to not-deleted instances.
UUIDModel
------------------
This abstract base class provides ``id`` field on any model that inherits from it
which will be the primary key.
If you dont want to set ``id`` as primary key or change the field name, you can be override it
with our `UUIDField`_
Also you can override the default uuid version. Versions 1,3,4 and 5 are now supported.
.. code-block:: python
from model_utils.models import UUIDModel
class MyAppModel(UUIDModel):
pass
.. _`UUIDField`: https://github.com/jazzband/django-model-utils/blob/master/docs/fields.rst#uuidfield

View file

@ -1,8 +1,10 @@
from __future__ import unicode_literals
import django
import uuid
from django.db import models
from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.encoding import python_2_unicode_compatible
from django.utils.timezone import now
@ -17,6 +19,7 @@ class AutoCreatedField(models.DateTimeField):
By default, sets editable=False, default=datetime.now.
"""
def __init__(self, *args, **kwargs):
kwargs.setdefault('editable', False)
kwargs.setdefault('default', now)
@ -30,6 +33,7 @@ class AutoLastModifiedField(AutoCreatedField):
By default, sets editable=False and default=datetime.now.
"""
def pre_save(self, model_instance, add):
value = now()
if not model_instance.pk:
@ -53,6 +57,7 @@ class StatusField(models.CharField):
Also features a ``no_check_for_status`` argument to make sure
South can handle this field when it freezes a model.
"""
def __init__(self, *args, **kwargs):
kwargs.setdefault('max_length', 100)
self.check_for_status = not kwargs.pop('no_check_for_status', False)
@ -93,6 +98,7 @@ class MonitorField(models.DateTimeField):
changes.
"""
def __init__(self, *args, **kwargs):
kwargs.setdefault('default', now)
monitor = kwargs.pop('monitor', None)
@ -144,7 +150,9 @@ SPLIT_MARKER = getattr(settings, 'SPLIT_MARKER', '<!-- split -->')
# the number of paragraphs after which to split if no marker
SPLIT_DEFAULT_PARAGRAPHS = getattr(settings, 'SPLIT_DEFAULT_PARAGRAPHS', 2)
_excerpt_field_name = lambda name: '_%s_excerpt' % name
def _excerpt_field_name(name):
return '_%s_excerpt' % name
def get_excerpt(content):
@ -252,3 +260,49 @@ class SplitField(models.TextField):
name, path, args, kwargs = super(SplitField, self).deconstruct()
kwargs['no_excerpt_field'] = True
return name, path, args, kwargs
class UUIDField(models.UUIDField):
"""
A field for storing universally unique identifiers. Use Python UUID class.
"""
def __init__(self, primary_key=True, version=4, editable=False, *args, **kwargs):
"""
Parameters
----------
primary_key : bool
If True, this field is the primary key for the model.
version : int
An integer that set default UUID version.
editable : bool
If False, the field will not be displayed in the admin or any other ModelForm,
default is false.
Raises
------
ValidationError
UUID version 2 is not supported.
"""
if version == 2:
raise ValidationError(
'UUID version 2 is not supported.')
if version < 1 or version > 5:
raise ValidationError(
'UUID version is not valid.')
if version == 1:
default = uuid.uuid1
elif version == 3:
default = uuid.uuid3
elif version == 4:
default = uuid.uuid4
elif version == 5:
default = uuid.uuid5
kwargs.setdefault('primary_key', primary_key)
kwargs.setdefault('editable', editable)
kwargs.setdefault('default', default)
super(UUIDField, self).__init__(*args, **kwargs)

View file

@ -4,16 +4,25 @@ import django
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.utils.translation import ugettext_lazy as _
from model_utils.fields import (
AutoCreatedField,
AutoLastModifiedField,
StatusField,
MonitorField,
UUIDField,
)
from model_utils.managers import (
QueryManager,
SoftDeletableManager,
)
if django.VERSION >= (1, 9, 0):
from django.db.models.functions import Now
now = Now()
else:
from django.utils.timezone import now
from model_utils.managers import QueryManager, SoftDeletableManager
from model_utils.fields import AutoCreatedField, AutoLastModifiedField, \
StatusField, MonitorField
class TimeStampedModel(models.Model):
"""
@ -135,3 +144,18 @@ class SoftDeletableModel(models.Model):
self.save(using=using)
else:
return super(SoftDeletableModel, self).delete(using=using, *args, **kwargs)
class UUIDModel(models.Model):
"""
This abstract base class provides id field on any model that inherits from it
which will be the primary key.
"""
id = UUIDField(
primary_key=True,
version=4,
editable=False,
)
class Meta:
abstract = True

View file

@ -8,7 +8,12 @@ from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from model_utils import Choices
from model_utils.fields import SplitField, MonitorField, StatusField
from model_utils.fields import (
SplitField,
MonitorField,
StatusField,
UUIDField,
)
from model_utils.managers import (
QueryManager,
InheritanceManager,
@ -19,6 +24,7 @@ from model_utils.models import (
StatusModel,
TimeFramedModel,
TimeStampedModel,
UUIDModel,
)
from tests.fields import MutableField
from tests.managers import CustomSoftDeleteManager
@ -169,8 +175,8 @@ class Post(models.Model):
objects = models.Manager()
public = QueryManager(published=True)
public_confirmed = QueryManager(models.Q(published=True) &
models.Q(confirmed=True))
public_confirmed = QueryManager(
models.Q(published=True) & models.Q(confirmed=True))
public_reversed = QueryManager(published=True).order_by("-order")
class Meta:
@ -369,6 +375,7 @@ class StringyDescriptor(object):
"""
Descriptor that returns a string version of the underlying integer value.
"""
def __init__(self, name):
self.name = name
@ -422,3 +429,11 @@ class JoinItemForeignKey(models.Model):
on_delete=models.CASCADE
)
objects = JoinManager()
class CustomUUIDModel(UUIDModel):
pass
class CustomNotPrimaryUUIDModel(models.Model):
uuid = UUIDField(primary_key=False)

View file

@ -0,0 +1,40 @@
from __future__ import unicode_literals
import uuid
from django.core.exceptions import ValidationError
from django.test import TestCase
from model_utils.fields import UUIDField
class UUIDFieldTests(TestCase):
def test_uuid_version_default(self):
instance = UUIDField()
self.assertEqual(instance.default, uuid.uuid4)
def test_uuid_version_1(self):
instance = UUIDField(version=1)
self.assertEqual(instance.default, uuid.uuid1)
def test_uuid_version_2_error(self):
self.assertRaises(ValidationError, UUIDField, 'version', 2)
def test_uuid_version_3(self):
instance = UUIDField(version=3)
self.assertEqual(instance.default, uuid.uuid3)
def test_uuid_version_4(self):
instance = UUIDField(version=4)
self.assertEqual(instance.default, uuid.uuid4)
def test_uuid_version_5(self):
instance = UUIDField(version=5)
self.assertEqual(instance.default, uuid.uuid5)
def test_uuid_version_bellow_min(self):
self.assertRaises(ValidationError, UUIDField, 'version', 0)
def test_uuid_version_above_max(self):
self.assertRaises(ValidationError, UUIDField, 'version', 6)

View file

@ -0,0 +1,20 @@
from __future__ import unicode_literals
from django.test import TestCase
from tests.models import CustomUUIDModel, CustomNotPrimaryUUIDModel
class UUIDFieldTests(TestCase):
def test_uuid_model_with_uuid_field_as_primary_key(self):
instance = CustomUUIDModel()
instance.save()
self.assertEqual(instance.id.__class__.__name__, 'UUID')
self.assertEqual(instance.id, instance.pk)
def test_uuid_model_with_uuid_field_as_not_primary_key(self):
instance = CustomNotPrimaryUUIDModel()
instance.save()
self.assertEqual(instance.uuid.__class__.__name__, 'UUID')
self.assertNotEqual(instance.uuid, instance.pk)