From 38f8932e7413cde486b4f4dd20d81437a67f9b2b Mon Sep 17 00:00:00 2001 From: JMP Date: Tue, 26 Feb 2019 17:32:56 +0100 Subject: [PATCH 01/12] UUID model added --- model_utils/models.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/model_utils/models.py b/model_utils/models.py index 2f21695..0cfafe6 100644 --- a/model_utils/models.py +++ b/model_utils/models.py @@ -1,5 +1,8 @@ from __future__ import unicode_literals +from model_utils.managers import QueryManager, SoftDeletableManager +from model_utils.fields import AutoCreatedField, AutoLastModifiedField, StatusField, MonitorField, UUIDField + import django from django.core.exceptions import ImproperlyConfigured from django.db import models @@ -10,10 +13,6 @@ if django.VERSION >= (1, 9, 0): 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 +134,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 From ca752948835039bea5a86215fd478beb58df8ab8 Mon Sep 17 00:00:00 2001 From: JMP Date: Tue, 26 Feb 2019 17:33:06 +0100 Subject: [PATCH 02/12] UUID field added --- model_utils/fields.py | 60 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/model_utils/fields.py b/model_utils/fields.py index 2799eac..5bb630e 100644 --- a/model_utils/fields.py +++ b/model_utils/fields.py @@ -1,11 +1,19 @@ from __future__ import unicode_literals import django +try: + import uuid # noqa + HAS_UUID = True +except ImportError: + HAS_UUID = False from django.db import models from django.conf import settings +from django.core.exceptions import ImproperlyConfigured from django.utils.encoding import python_2_unicode_compatible from django.utils.timezone import now +from model_utils.exceptions import UUIDVersionException + DEFAULT_CHOICES_NAME = 'STATUS' @@ -17,6 +25,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 +39,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 +63,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 +104,7 @@ class MonitorField(models.DateTimeField): changes. """ + def __init__(self, *args, **kwargs): kwargs.setdefault('default', now) monitor = kwargs.pop('monitor', None) @@ -144,7 +156,8 @@ SPLIT_MARKER = getattr(settings, 'SPLIT_MARKER', '') # 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 +265,48 @@ 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. Uses Python’s 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 + ------ + UUIDVersionException + UUID version 2 is not supported. + """ + if not HAS_UUID: + raise ImproperlyConfigured("'uuid' module is required for UUIDField.") + + kwargs.setdefault('primary_key', primary_key) + kwargs.setdefault('editable', editable) + + if version == 4: + default = uuid.uuid4 + elif version == 1: + default = uuid.uuid1 + elif version == 2: + raise UUIDVersionException("UUID version 2 is not supported.") + elif version == 3: + default = uuid.uuid3 + elif version == 5: + default = uuid.uuid5 + else: + raise UUIDVersionException("UUID version %s is not valid." % version) + + kwargs.setdefault('default', default) + super(UUIDField, self).__init__(*args, **kwargs) From 5bf7db036fde92b0752aeb3869bd679465be7584 Mon Sep 17 00:00:00 2001 From: JMP Date: Tue, 26 Feb 2019 17:34:20 +0100 Subject: [PATCH 03/12] UUIDModel doc --- docs/models.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/models.rst b/docs/models.rst index 51bde8f..4e04c29 100644 --- a/docs/models.rst +++ b/docs/models.rst @@ -55,3 +55,24 @@ 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](https://github.com/jazzband/django-model-utils/blob/master/docs/fields.rst#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 + from model_utils import Choices + + class MyAppModel(UUIDModel): + pass + From bdc6fb05fecdf9ab68ee0f6f62fdb76209b94b7a Mon Sep 17 00:00:00 2001 From: JMP Date: Tue, 26 Feb 2019 17:35:00 +0100 Subject: [PATCH 04/12] UUIDField doc --- docs/fields.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/fields.rst b/docs/fields.rst index 02ca6ef..1f0d5c5 100644 --- a/docs/fields.rst +++ b/docs/fields.rst @@ -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) + From 5ff0867bf92dd06ac3bc8aa4e168ae64c2c3a07f Mon Sep 17 00:00:00 2001 From: JMP Date: Tue, 26 Feb 2019 17:35:50 +0100 Subject: [PATCH 05/12] UUIDModels for testing purposes --- tests/models.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/models.py b/tests/models.py index 888aba5..72e48a4 100644 --- a/tests/models.py +++ b/tests/models.py @@ -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 @@ -159,8 +165,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: @@ -340,6 +346,7 @@ class StringyDescriptor(object): """ Descriptor that returns a string version of the underlying integer value. """ + def __init__(self, name): self.name = name @@ -393,3 +400,11 @@ class JoinItemForeignKey(models.Model): on_delete=models.CASCADE ) objects = JoinManager() + + +class CustomUUIDModel(UUIDModel): + pass + + +class CustomNotPrimaryUUIDModel(models.Model): + uuid = UUIDField(primary_key=False) From 58e57d55356eb9fdc3e78b38b9e38389e7798b8d Mon Sep 17 00:00:00 2001 From: JMP Date: Tue, 26 Feb 2019 17:36:23 +0100 Subject: [PATCH 06/12] UUIDField tests --- tests/test_fields/test_uuid_field.py | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/test_fields/test_uuid_field.py diff --git a/tests/test_fields/test_uuid_field.py b/tests/test_fields/test_uuid_field.py new file mode 100644 index 0000000..74faa4f --- /dev/null +++ b/tests/test_fields/test_uuid_field.py @@ -0,0 +1,34 @@ +from __future__ import unicode_literals + +import uuid + +from django.test import TestCase + +from model_utils.fields import UUIDField +from model_utils.exceptions import UUIDVersionException + + +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(UUIDVersionException, 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) From c23c622d17b24bcb83eb009a606806028898889e Mon Sep 17 00:00:00 2001 From: JMP Date: Tue, 26 Feb 2019 17:36:35 +0100 Subject: [PATCH 07/12] UUIDModel tests --- tests/test_models/test_uuid_model.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/test_models/test_uuid_model.py diff --git a/tests/test_models/test_uuid_model.py b/tests/test_models/test_uuid_model.py new file mode 100644 index 0000000..5559159 --- /dev/null +++ b/tests/test_models/test_uuid_model.py @@ -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) From 533501753f98b417b8569bc6ffab3f143fc4f64c Mon Sep 17 00:00:00 2001 From: JMP Date: Tue, 26 Feb 2019 17:36:59 +0100 Subject: [PATCH 08/12] UUID version custom exception --- model_utils/exceptions.py | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 model_utils/exceptions.py diff --git a/model_utils/exceptions.py b/model_utils/exceptions.py new file mode 100644 index 0000000..90566cb --- /dev/null +++ b/model_utils/exceptions.py @@ -0,0 +1,2 @@ +class UUIDVersionException(Exception): + pass From 430866abfaca568ed9486e4199d9aad3d07d5253 Mon Sep 17 00:00:00 2001 From: JMP Date: Tue, 26 Feb 2019 17:37:24 +0100 Subject: [PATCH 09/12] Update authors file --- AUTHORS.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 06a3e01..07e9967 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -50,4 +50,4 @@ | Jack Cushman | Zach Cheung | Daniel Andrlik - +| marfyl \ No newline at end of file From 015dd4831fbf887c7a26eb12802662ffd17d4949 Mon Sep 17 00:00:00 2001 From: JMP Date: Tue, 26 Feb 2019 17:50:32 +0100 Subject: [PATCH 10/12] Catch error with inherit class --- model_utils/fields.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/model_utils/fields.py b/model_utils/fields.py index 5bb630e..6c43ecc 100644 --- a/model_utils/fields.py +++ b/model_utils/fields.py @@ -1,14 +1,9 @@ from __future__ import unicode_literals import django -try: - import uuid # noqa - HAS_UUID = True -except ImportError: - HAS_UUID = False +import uuid from django.db import models from django.conf import settings -from django.core.exceptions import ImproperlyConfigured from django.utils.encoding import python_2_unicode_compatible from django.utils.timezone import now @@ -289,9 +284,6 @@ class UUIDField(models.UUIDField): UUIDVersionException UUID version 2 is not supported. """ - if not HAS_UUID: - raise ImproperlyConfigured("'uuid' module is required for UUIDField.") - kwargs.setdefault('primary_key', primary_key) kwargs.setdefault('editable', editable) From 2f142afd379a09ee69840e04e7a12fb279b13b65 Mon Sep 17 00:00:00 2001 From: JMP Date: Tue, 26 Feb 2019 17:55:02 +0100 Subject: [PATCH 11/12] Fix imports pep8 --- model_utils/models.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/model_utils/models.py b/model_utils/models.py index 0cfafe6..96c472b 100644 --- a/model_utils/models.py +++ b/model_utils/models.py @@ -1,12 +1,22 @@ from __future__ import unicode_literals -from model_utils.managers import QueryManager, SoftDeletableManager -from model_utils.fields import AutoCreatedField, AutoLastModifiedField, StatusField, MonitorField, UUIDField - 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() From e5955f780b57c5229a7cd26f219deb8bb5e061c7 Mon Sep 17 00:00:00 2001 From: jmmp Date: Tue, 26 Feb 2019 17:58:20 +0100 Subject: [PATCH 12/12] Update fields.py --- model_utils/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model_utils/fields.py b/model_utils/fields.py index 5bb630e..ff10e51 100644 --- a/model_utils/fields.py +++ b/model_utils/fields.py @@ -157,7 +157,7 @@ SPLIT_MARKER = getattr(settings, 'SPLIT_MARKER', '') SPLIT_DEFAULT_PARAGRAPHS = getattr(settings, 'SPLIT_DEFAULT_PARAGRAPHS', 2) -def _excerpt_field_name(name): return '_%s_excerpt' % name +_excerpt_field_name = lambda name: '_%s_excerpt' % name def get_excerpt(content):