Skip to content

Commit

Permalink
Merge pull request jazzband#360 from marfyl/master
Browse files Browse the repository at this point in the history
Add UUIDModel and UUIDField
  • Loading branch information
auvipy authored May 15, 2019
2 parents 77bbdb1 + fa9a037 commit 25dde41
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 9 deletions.
2 changes: 1 addition & 1 deletion AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@
| Jack Cushman <[email protected]>
| Zach Cheung <[email protected]>
| Daniel Andrlik <[email protected]>
| marfyl <github.com/marfyl>
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
------------------
Expand Down
26 changes: 26 additions & 0 deletions docs/fields.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
23 changes: 23 additions & 0 deletions docs/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
56 changes: 55 additions & 1 deletion model_utils/fields.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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)
Expand All @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -93,6 +98,7 @@ class MonitorField(models.DateTimeField):
changes.
"""

def __init__(self, *args, **kwargs):
kwargs.setdefault('default', now)
monitor = kwargs.pop('monitor', None)
Expand Down Expand Up @@ -144,7 +150,9 @@ def deconstruct(self):
# 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):
Expand Down Expand Up @@ -252,3 +260,49 @@ def deconstruct(self):
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)
32 changes: 28 additions & 4 deletions model_utils/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,25 @@
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):
"""
Expand Down Expand Up @@ -135,3 +144,18 @@ def delete(self, using=None, soft=True, *args, **kwargs):
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
21 changes: 18 additions & 3 deletions tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@
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,
Expand All @@ -19,6 +24,7 @@
StatusModel,
TimeFramedModel,
TimeStampedModel,
UUIDModel,
)
from tests.fields import MutableField
from tests.managers import CustomSoftDeleteManager
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
40 changes: 40 additions & 0 deletions tests/test_fields/test_uuid_field.py
Original file line number Diff line number Diff line change
@@ -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)
20 changes: 20 additions & 0 deletions tests/test_models/test_uuid_model.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 25dde41

Please sign in to comment.