根据 Django 数据库中的当前项目数生成默认值 - 循环导入问题

Generating a default value based on the current number of items from a Django database - Circular Import Problem

我在做一个 Django 项目,但我真的想不通如何避免 Django 模型和自定义 python 文件之间的循环导入 generators.py

accounts/models.py

from django.db import models
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django.utils import timezone

from core.generators import make_id_number
class UserManager(BaseUserManager):

    def create_user(self, email, username, fname, lname, password, **others):
        
        if not email:
            raise ValueError(_('Please Provide Email Address!'))
        if not username:
            raise ValueError(_('Please Provide User Name!'))
        if not fname:
            raise ValueError(_('Please Provide First Name!'))
        if not lname:
            raise ValueError(_('Please Provide Last Name!'))
        
        email = self.normalize_email(email)
        user = self.model(
            email=email,
            username=username,
            fname=fname,
            lname=lname,
            password=password,
            **others
        )
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, username, fname, lname, password, **others):

        others.setdefault('is_staff', True)
        others.setdefault('is_superuser', True)
        others.setdefault('is_active', True)

        if others.get('is_staff') is False:
            raise ValueError(_('Superuser must have \'staff\' permissions!'))
        if others.get('is_active') is False:
            raise ValueError(_('Superuser must be active!'))
        if others.get('is_superuser') is False:
            raise ValueError(_('Superuser must have \'superuser\' permissions!'))

        return self.create_user(email, username, fname, lname, password, **others)

class User(AbstractBaseUser, PermissionsMixin):

    email = models.EmailField(_('Email'),max_length=150,unique=True)
    username = models.CharField(_('Username'),max_length=150,unique=True)
    fname = models.CharField(_('First Name'),max_length=150)
    mname = models.CharField(_('Middle Name'),max_length=150,null=True,blank=True)
    lname = models.CharField(_('Last Name'),max_length=150)
    date_joined = models.DateTimeField(_('Date Joined'),default=timezone.now)
    last_login = models.DateTimeField(_('Last Login'),auto_now=True)
    is_staff = models.BooleanField(_('Staff'),default=False)
    is_active = models.BooleanField(_('Active'),default=False)
    is_superuser = models.BooleanField(_('Superuser'),default=False)

    objects = UserManager()
    
    EMAIL_FIELD = 'email'
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email', 'fname' , 'lname']

    def __str__(self):
        return self.username

    def get_full_name(self):
        full_name = (f'{self.fname} {self.lname}')
        return full_name.strip()

    def get_short_name(self):
        return self.fname

class UserIdentification(models.Model):

    user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
    id_number = models.BigIntegerField(_('ID Number'), default=make_id_number, unique=True)
    reg_code = models.CharField(_('Registration Code'), max_length=150, unique=True)

    def __str__(self):
        return (f'{self.user.username}-{self.id_number:09d}')

core/generators.py

import pytz

from django.utils.crypto import get_random_string
from django.utils import timezone
from django.conf import settings

from accounts.models import UserIdentification

# Generate a random password with the given length and given allowed_chars.
# The default value of allowed_chars does not have "I" or "O" or letters and digits that look similar to avoid confusion.
def make_random_password(length=12, allowed_chars='!@#$%^&*()_+-=abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):
    return get_random_string(length, allowed_chars)


def make_id_number(local_tz=settings.TIME_ZONE):

    local_tz = pytz.timezone(local_tz)
    local_date = local_tz.normalize(timezone.now()).date()

    mm=int(local_date.month)
    dd=int(local_date.day)
    yy=int(local_date.year-2000)
    ccc=int(UserIdentification.objects.all().count())
    # ccc=0

    id_number=int(f'{mm:02d}{dd:02d}{yy:02d}{ccc+1:03d}')
    return id_number

如您所见,这肯定会导致循环导入问题,因为我正在从 core/generators.py 导入 make_id_number,这将查询数据库以获取使用该模型的当前用户数 UserIdentification 来自 accounts/models.py,这反过来会要求 core\generators.py 生成一个 ID 号。

如果有人能给我解决方案、提示或关于如何避免这种情况的教程,我将不胜感激。

在python

中有一个避免循环导入的技巧

只导入模块,不从模块导入

在models.py中:import core.generators

在generators.py中:import accounts.models

我知道了

感谢@caot 提醒我。我所做的是修改了 UserIdentificaton 模型 - 删除了 default=make_id_number 并添加了 def save(self, *args, **kwargs) 函数。

account/models.py

...
class UserIdentification(models.Model):

    user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
    id_number = models.BigIntegerField(_('ID Number'), unique=True, blank=True)
    reg_code = models.CharField(_('Registration Code'), max_length=150, unique=True, blank=True)

    def __str__(self):
        return (f'{self.user.username}-{self.id_number:09d}')

    def save(self, *args, **kwargs):
        ccc = UserIdentification.objects.all().count()+1
        self.id_number = make_id_number(ccc)
        super(UserIdentification, self).save(*args, **kwargs)

还从 core/generators.py

更新了 make_id_number 函数

core/generators.py

def make_id_number(ccc, local_tz=settings.TIME_ZONE):

    local_tz = pytz.timezone(local_tz)
    local_date = local_tz.normalize(timezone.now()).date()

    mm=int(local_date.month)
    dd=int(local_date.day)
    yy=int(local_date.year-2000)
    
    return int(f'{mm:02d}{dd:02d}{yy:02d}{ccc:03d}')

对此可能有更优化的答案。但我暂时坚持这个。

在代码中解决此类循环导入的最简单方法是在 core/generators.py 文件中的 make_id_number 函数中导入 UserIdentification。这将打破模块循环导入,并且仅在调用时在函数范围内导入它。

因此您的文件应如下所示:

# core/generators.py

import pytz

from django.utils.crypto import get_random_string
from django.utils import timezone
from django.conf import settings

###### Remove it from here #####

# Generate a random password with the given length and given allowed_chars.
# The default value of allowed_chars does not have "I" or "O" or letters and digits that look similar to avoid confusion.
def make_random_password(length=12, allowed_chars='!@#$%^&*()_+-=abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):
    return get_random_string(length, allowed_chars)


def make_id_number(local_tz=settings.TIME_ZONE):
    ##### Import it here #####
    from accounts.models import UserIdentification

    local_tz = pytz.timezone(local_tz)
    local_date = local_tz.normalize(timezone.now()).date()

    mm=int(local_date.month)
    dd=int(local_date.day)
    yy=int(local_date.year-2000)
    ccc=int(UserIdentification.objects.all().count())
    # ccc=0

    id_number=int(f'{mm:02d}{dd:02d}{yy:02d}{ccc+1:03d}')
    return id_number