Skip to content

Commit

Permalink
bugfix: IDField增加deconstruct方法以修复迁移文件生成异常
Browse files Browse the repository at this point in the history
  • Loading branch information
Guo-Zhang committed Aug 24, 2023
1 parent a57498d commit b26458b
Show file tree
Hide file tree
Showing 19 changed files with 315 additions and 23 deletions.
12 changes: 11 additions & 1 deletion django_quanttide/models/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,20 @@ class IDField(models.UUIDField):
def __init__(self, **options):
options.setdefault('primary_key', True)
options.setdefault('editable', not options['primary_key'])
options.setdefault('default', uuid.uuid4)
options.setdefault('null', not options['primary_key'])
options.setdefault('blank', not options['primary_key'])
options.setdefault('default', uuid.uuid4 if options['primary_key'] else None)
options.setdefault('verbose_name', 'ID')
super().__init__(**options)

def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
# 将所有字段参数添加到 kwargs
for arg in ['primary_key', 'editable', 'null', 'blank', 'default', 'verbose_name']:
if hasattr(self, arg):
kwargs[arg] = getattr(self, arg)
return name, path, args, kwargs


class NumberField(models.IntegerField):
"""
Expand Down
22 changes: 22 additions & 0 deletions examples/ra/manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ra.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)


if __name__ == "__main__":
main()
Empty file added examples/ra/members/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions examples/ra/members/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions examples/ra/members/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class MembersConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "members"
45 changes: 45 additions & 0 deletions examples/ra/members/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Generated by Django 4.2.3 on 2023-08-24 10:10

from django.db import migrations
import django_quanttide.models.fields
import uuid


class Migration(migrations.Migration):
initial = True

dependencies = []

operations = [
migrations.CreateModel(
name="Member",
fields=[
(
"id",
django_quanttide.models.fields.IDField(
blank=False,
default=uuid.uuid4,
editable=False,
null=False,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"org_user_id",
django_quanttide.models.fields.IDField(
blank=True,
default=None,
editable=True,
null=True,
primary_key=False,
verbose_name="组织用户ID",
),
),
],
options={
"abstract": False,
},
),
]
Empty file.
5 changes: 5 additions & 0 deletions examples/ra/members/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django_quanttide import models as quanttide_models


class Member(quanttide_models.Model):
org_user_id = quanttide_models.IDField(primary_key=False, verbose_name='组织用户ID')
3 changes: 3 additions & 0 deletions examples/ra/members/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
3 changes: 3 additions & 0 deletions examples/ra/members/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.shortcuts import render

# Create your views here.
Empty file added examples/ra/ra/__init__.py
Empty file.
16 changes: 16 additions & 0 deletions examples/ra/ra/asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
ASGI config for ra project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ra.settings")

application = get_asgi_application()
125 changes: 125 additions & 0 deletions examples/ra/ra/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"""
Django settings for ra project.
Generated by 'django-admin startproject' using Django 4.2.3.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-#l6(ym0f+r4&0)wn*q=vd7*-8yvvj2oiu5ma0a^#u=q$)!&q-d"

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django_quanttide",
"members"
]

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]

ROOT_URLCONF = "ra.urls"

TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]

WSGI_APPLICATION = "ra.wsgi.application"


# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases

DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}


# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]


# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/

LANGUAGE_CODE = "en-us"

TIME_ZONE = "UTC"

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/

STATIC_URL = "static/"

# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
22 changes: 22 additions & 0 deletions examples/ra/ra/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""
URL configuration for ra project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path

urlpatterns = [
path("admin/", admin.site.urls),
]
16 changes: 16 additions & 0 deletions examples/ra/ra/wsgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
WSGI config for ra project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ra.settings")

application = get_wsgi_application()
1 change: 1 addition & 0 deletions tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class ExampleModel(models.Model):
stage = models.StageField()
created_at = models.CreatedAtField()
updated_at = models.UpdatedAtField()
related_id = models.IDField(primary_key=False, verbose_name='关联ID')

class Meta:
verbose_name = '示例模型'
Expand Down
18 changes: 15 additions & 3 deletions tests/test_models_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,31 @@


class IDFieldTestCase(SimpleTestCase):
def test_defaults(self):
def test_init_primary_key(self):
field = IDField()
self.assertTrue(field.primary_key)
self.assertFalse(field.editable)
self.assertEqual(field.verbose_name, 'ID')
self.assertIsInstance(field.default(), uuid.UUID)

def test_custom_options(self):
def test_init_non_primary_key(self):
field = IDField(primary_key=False, verbose_name='关联ID')
self.assertFalse(field.primary_key)
self.assertTrue(field.editable)
self.assertTrue(field.null)
self.assertTrue(field.blank)
self.assertIs(field.default, None)
self.assertEqual(field.verbose_name, '关联ID')
self.assertIsInstance(field.default(), uuid.UUID)

def test_deconstruct_primary_key(self):
field = IDField()
name, path, args, kwargs = field.deconstruct()
self.assertIsInstance(kwargs['default'](), uuid.UUID)

def test_deconstruct_non_primary_key(self):
field = IDField(primary_key=False, verbose_name='关联ID')
name, path, args, kwargs = field.deconstruct()
self.assertIsNone(kwargs['default'])


class NumberFieldTestCase(TestCase):
Expand Down
24 changes: 5 additions & 19 deletions tests/test_models.py → tests/test_models_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
from django.db.utils import IntegrityError

from tests.models import (
ExampleModel,
ParentModel, ChildModel
ExampleModel
)


Expand All @@ -24,6 +23,9 @@ def setUp(self):

def test_defaults(self):
example = self.example
self.assertIsInstance(example.id, uuid.UUID)
self.assertIsInstance(example.created_at, datetime.datetime)
self.assertIsInstance(example.updated_at, datetime.datetime)
self.assertEqual(example.number, 1)
self.assertEqual(example.name, 'example')
self.assertEqual(example.verbose_name, 'example instance')
Expand All @@ -32,9 +34,7 @@ def test_defaults(self):
self.assertEqual(example.type, 'book')
self.assertEqual(example.status, 'draft')
self.assertEqual(example.stage, 1)
self.assertIsInstance(example.id, uuid.UUID)
self.assertIsInstance(example.created_at, datetime.datetime)
self.assertIsInstance(example.updated_at, datetime.datetime)
self.assertIsNone(example.related_id)

def test_name_unique(self):
example2 = ExampleModel(name='example', verbose_name='example instance 2')
Expand All @@ -53,17 +53,3 @@ def test_updated_at(self):
example.name = 'updated-example'
example.save()
self.assertLessEqual(example.updated_at - example.created_at, datetime.timedelta(seconds=1))


class PolymorphicModelTestCase(TestCase):
def setUp(self):
self.instance = ParentModel.objects.create()
self.child_instance = ChildModel.objects.create()

def test_default_type(self):
# Test that the default type is returned when polymorphic_ctype is None
self.assertEqual('parentmodel', self.instance.type)

def test_child_model_type(self):
print(self.child_instance.__class__.__name__)
self.assertEqual('childmodel', self.child_instance.type)
Loading

0 comments on commit b26458b

Please sign in to comment.