【问题标题】:ValueError: Cannot assign must be an instanceValueError:无法分配必须是实例
【发布时间】:2018-06-03 11:09:56
【问题描述】:

我有以下预先存在的数据库表,其中包含 ID 和描述。为了正确关联 ForeignKey,我必须在 User 表之前加载它。

class QVDSSSecurityDimension(models.Model):

    coid = models.CharField(db_column='CM_Data_Restriction', serialize=False, max_length=10, primary_key = True)  # Field name made lowercase.
    coid_name = models.CharField(db_column='CM_Company_Name', max_length=50, blank=True, null=True)  # Field name made lowercase.

    class Meta:
        managed = False
        db_table = 'QV_DSS_Security_Dimension'

我的自定义用户模型基于以下内容:

class User(AbstractBaseUser, PermissionsMixin):

    email = models.EmailField(unique=True)
    username = models.CharField(max_length=7, unique=True)
    formattedusername = models.CharField(max_length=11, unique=True, primary_key = True)
    first_name = models.CharField(max_length=40)
    last_name = models.CharField(max_length=140)
    date_joined = models.DateTimeField(default=timezone.now)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_cfo = models.BooleanField(default=False)
    facility = models.CharField(max_length=140)
    officename = models.CharField(max_length=100)
    jobdescription = models.CharField(max_length=140)
    positioncode = models.CharField(max_length = 100)
    positiondescription = models.CharField(max_length=140)
    coid = models.ForeignKey(QVDSSSecurityDimension, null=True, blank = True, db_constraint=False)
    streetaddress = models.CharField(max_length=140)
    title = models.CharField(max_length=100)

    USERNAME_FIELD = 'username'

    class Meta:
        app_label = 'accounts'
        db_table = "user"

    def save(self, *args, **kwargs):
        self.formattedusername = '{domain}\{username}'.format(
            domain='HCA', username=self.username)
        super(User, self).save(*args, **kwargs);

    def get_short_name(self):
        return self.username

#    REQUIRED_FIELDS = "username"

    def __str__(self):
        return '%s - %s %s' % (self.username, self.first_name, self.last_name)

一切都适用于 makemigrations 和 migrate,但如果数据库中不存在 User.coid 我在尝试登录时收到以下错误:

ValueError: Cannot assign "'08732'": "User.coid" must be a "QVDSSSecurityDimension" instance.

我验证 QVDSSSecurityDimension 中确实存在 coid,但由于表中没有带有 coid 的用户,因此会引发该错误。如果用户使用 AD 进行验证并且自定义用户表中不存在他们的 coid,如何使我的登录仍然正常工作?

我用 db_constraint = True 尝试了 Null = True 和 Blank = True ,但似乎没有任何效果。用户存入数据库后,coid会存在,但此错误发生在此之前。

这是我的个人资料视图

def profile(request):
    owner = User.objects.get (formattedusername=request.user.formattedusername)
    reportdetail = QVReportAccess.objects.filter(ntname = owner.formattedusername, active = 1).values('report_name_sc')

    reportIds = QVReportAccess.objects.filter(ntname = owner.formattedusername).values_list('report_id', flat=True)
    reportaccess = QvReportList.objects.filter(report_id__in= reportIds).values_list('report_name_sc', flat = True).distinct()
    reportGroups = QVReportAccess.objects.filter(ntname = owner.formattedusername).values_list('report_group_id', flat=True)
    reportlist = QvReportList.objects.filter(~Q(report_id__in= reportIds)).exclude(active=0)

    allreports = 'All Reports'

    if allreports in reportaccess:
        bhreportgrouplist = None
        cereportgrouplist = None
        finreportgrouplist = None
        careportgrouplist = None
        pireportgrouplist = None
        screportgrouplist = None
        dssreportgrouplist = None
        psgreportgrouplist = None
        othreportgrouplist = None
        showbutton = None
    else:
        bhreportgrouplist = QvReportList.objects.filter(~Q(report_id__in= reportIds)).filter(report_group_id = 200)
        cereportgrouplist = QvReportList.objects.filter(~Q(report_id__in= reportIds)).filter(report_group_id = 500)
        finreportgrouplist = QvReportList.objects.filter(~Q(report_id__in= reportIds)).filter(report_group_id = 600)
        careportgrouplist = QvReportList.objects.filter(~Q(report_id__in= reportIds)).filter(report_group_id = 800)
        pireportgrouplist = QvReportList.objects.filter(~Q(report_id__in= reportIds)).filter(report_group_id = 1100)
        screportgrouplist = QvReportList.objects.filter(~Q(report_id__in= reportIds)).filter(report_group_id = 1200)
        dssreportgrouplist = QvReportList.objects.filter(~Q(report_id__in= reportIds)).filter(report_group_id = 1300)
        psgreportgrouplist = QvReportList.objects.filter(~Q(report_id__in= reportIds)).filter(report_group_id = 1400)
        othreportgrouplist = QvReportList.objects.filter(~Q(report_id__in= reportIds)).filter(report_group_id = 99999)
        showbutton = ""



    args = {'user':owner, 'applicationaccess':reportaccess, 'applicationlist':reportlist, 'bhgrouplist':bhreportgrouplist, 'cegrouplist':cereportgrouplist, 'fingrouplist':finreportgrouplist
          , 'cagrouplist':careportgrouplist, 'pigrouplist':pireportgrouplist, 'scgrouplist':screportgrouplist, 'dssgrouplist':dssreportgrouplist, 'psggrouplist':psgreportgrouplist
          , 'othgrouplist':othreportgrouplist, 'showbutton':showbutton}

    return render(request, 'accounts/profile.html', args)

我能够在没有 DSSSecurityDimension 的情况下从我的格式化用户名中获取 coid,因为它是从 AD 中提取的,但是我需要来自 DSSSecurityDimension 的描述,这就是为什么我在配置文件模板上提取 coid,但来自 User.coid不是 DSSSecurityDimension.coid。

我的模板中的 Coid 使用以下 li 渲染

<li>Coid: {{ user.coid }}</li>

这行代码在 python 库中,而不是我的视图,这告诉我它的模型有问题:

Traceback (most recent call last):
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\core\handlers\exception.py", line 41, in inner
    response = get_response(request)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\core\handlers\base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\core\handlers\base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\contrib\auth\views.py", line 54, in inner
    return func(*args, **kwargs)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\contrib\auth\views.py", line 150, in login
    )(request)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\views\generic\base.py", line 68, in view
    return self.dispatch(request, *args, **kwargs)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\utils\decorators.py", line 67, in _wrapper
    return bound_func(*args, **kwargs)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\views\decorators\debug.py", line 76, in sensitive_post_parameters_w
rapper
    return view(request, *args, **kwargs)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\utils\decorators.py", line 63, in bound_func
    return func.__get__(self, type(self))(*args2, **kwargs2)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\utils\decorators.py", line 67, in _wrapper
    return bound_func(*args, **kwargs)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\utils\decorators.py", line 149, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\utils\decorators.py", line 63, in bound_func
    return func.__get__(self, type(self))(*args2, **kwargs2)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\utils\decorators.py", line 67, in _wrapper
    return bound_func(*args, **kwargs)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\views\decorators\cache.py", line 57, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\utils\decorators.py", line 63, in bound_func
    return func.__get__(self, type(self))(*args2, **kwargs2)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\contrib\auth\views.py", line 90, in dispatch
    return super(LoginView, self).dispatch(request, *args, **kwargs)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\views\generic\base.py", line 88, in dispatch
    return handler(request, *args, **kwargs)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\views\generic\edit.py", line 182, in post
    if form.is_valid():
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\forms\forms.py", line 183, in is_valid
    return self.is_bound and not self.errors
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\forms\forms.py", line 175, in errors
    self.full_clean()
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\forms\forms.py", line 385, in full_clean
    self._clean_form()
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\forms\forms.py", line 412, in _clean_form
    cleaned_data = self.clean()
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\contrib\auth\forms.py", line 187, in clean
    self.user_cache = authenticate(self.request, username=username, password=password)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\contrib\auth\__init__.py", line 70, in authenticate
    user = _authenticate_with_backend(backend, backend_path, request, credentials)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\contrib\auth\__init__.py", line 115, in _authenticate_with_backend
    return backend.authenticate(*args, **credentials)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django_python3_ldap\auth.py", line 23, in authenticate
    return ldap.authenticate(*args, **kwargs)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django_python3_ldap\ldap.py", line 205, in authenticate
    return c.get_user(**kwargs)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django_python3_ldap\ldap.py", line 115, in get_user
    return self._get_or_create_user(self._connection.response[0])
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django_python3_ldap\ldap.py", line 67, in _get_or_create_user
    **user_lookup
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\db\models\query.py", line 482, in update_or_create
    obj, created = self._create_object_from_params(lookup, params)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\db\models\query.py", line 498, in _create_object_from_params
    obj = self.create(**params)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\db\models\query.py", line 392, in create
    obj = self.model(**kwargs)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\contrib\auth\base_user.py", line 68, in __init__
    super(AbstractBaseUser, self).__init__(*args, **kwargs)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\db\models\base.py", line 554, in __init__
    _setattr(self, field.name, rel_obj)
  File "C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django\db\models\fields\related_descriptors.py", line 216, in __set__
    self.field.remote_field.model._meta.object_name,
ValueError: Cannot assign "'08732'": "User.coid" must be a "QVDSSSecurityDimension" instance.

登录在我的 settings.py 中处理

以下内容:

AUTHENTICATION_BACKENDS = (
    'django_python3_ldap.auth.LDAPBackend',
    'django.contrib.auth.backends.ModelBackend',
)

AUTH_USER_MODEL = "accounts.User"

# The URL of the LDAP server.
LDAP_AUTH_URL = "ldap://ip of server"


# Initiate TLS on connection.
LDAP_AUTH_USE_TLS = False

# The LDAP search base for looking up users.
LDAP_AUTH_SEARCH_BASE = "DC=domainname,DC=corpad,DC=net"

LDAP_AUTH_OBJECT_CLASS = "user"

# User model fields mapped to the LDAP
# attributes that represent them.
LDAP_AUTH_USER_FIELDS = {
    "username": "sAMAccountName",
    "first_name": "givenName",
    "last_name": "sn",
    "email": "mail",
    "coid": "extensionAttribute10",
    "facility": "company",
    "officename":"physicalDeliveryOfficeName",
   "streetaddress": "streetAddress",
    "jobdescription":"corpadNet2001-CORPds-JobCodeDescription",
    "positioncode":"corpadNet2001-CORPds-PositionCode",
    "positiondescription":"corpadNet2001-CORPds-PositionCodeDescription",
    "title":"title",
}

# A tuple of django model fields used to uniquely identify a user.
LDAP_AUTH_USER_LOOKUP_FIELDS = ("username",)

# Path to a callable that takes a dict of {model_field_name: value},
# returning a dict of clean model data.
# Use this to customize how data loaded from LDAP is saved to the User model.
LDAP_AUTH_CLEAN_USER_DATA = "django_python3_ldap.utils.clean_user_data"

# Path to a callable that takes a user model and a dict of {ldap_field_name: [value]},
# and saves any additional user relationships based on the LDAP data.
# Use this to customize how data loaded from LDAP is saved to User model relations.
# For customizing non-related User model fields, use LDAP_AUTH_CLEAN_USER_DATA.
LDAP_AUTH_SYNC_USER_RELATIONS = "django_python3_ldap.utils.sync_user_relations"

# Path to a callable that takes a dict of {ldap_field_name: value},
# returning a list of [ldap_search_filter]. The search filters will then be AND'd
# together when creating the final search filter.
LDAP_AUTH_FORMAT_SEARCH_FILTERS = "django_python3_ldap.utils.format_search_filters"

# Path to a callable that takes a dict of {model_field_name: value}, and returns
# a string of the username to bind to the LDAP server.
# Use this to support different types of LDAP server.
LDAP_AUTH_FORMAT_USERNAME = "django_python3_ldap.utils.format_username_active_directory"

【问题讨论】:

  • 您可以发布您用于登录的视图吗?
  • 我在视图中添加了更多细节,但我认为问题在于数据的建模方式,因为我使用的是 ldap3 身份验证。我的登录名是用户的网络登录名和密码,一旦用户被验证,数据库会将记录写入我的用户表,这是用户模型中的保存。但是,它甚至没有达到这一点,因为我的外键的关系在数据库中没有 coid。如果我用测试用户的 coid 手动添加一个虚拟用户,它可以正常工作,因为 coid 已经存在于我的用户表中。

标签: django


【解决方案1】:

我想我看到了问题所在。您希望Usercoid 字段包含相关QVDSSSecurityDimension 对象的实际ID,但这不是ForeignKey 默认的工作方式。

Usercoid 字段实际上是一个仅存在于 Python 逻辑中的属性。在数据库级别,User 表中没有 coid 列。这是因为 Django 创建了另一个未在模型中显式声明的字段,称为 coid_id,用于保存相关对象的 ID。 是数据库中实际存在的列和值。

换句话说,如果您列出User 对象的属性,您会看到它有两个字段:coidcoid_idcoid 将检索QVDSSSecurityDimension 的实例,而coid_id 将保存您尝试设置的实际值。

请检查Django's documentation 关于此。 This post 还可以帮助您了解 coidcoid_id 的工作原理。

这就是我认为您正在寻找的:

class User(AbstractBaseUser, PermissionsMixin):
    ...
    co = models.ForeignKey(QVDSSSecurityDimension, null=True, blank=True)
    ...

我将 coid 重命名为 co 以使其更直观,因为它不包含实际 ID。我还删除了db_constraint=False,因为默认的db_constraint=True 应该可以正常工作。

当您尝试将QVDSSSecurityDimension 设置为您的用户时,您可以这样做:

some_user = User.objects.first()
some_dimension = QVDSSSecurityDimension.objects.get(coid='08732')

some_user.co = some_dimension
some_user.save()

或:

some_user = User.objects.first()
some_dimension = QVDSSSecurityDimension.objects.get(coid='08732')

some_user.co_id = some_dimension.coid
some_user.save()

或:

some_user = User.objects.first()
some_dimension_id = '08732'

some_user.co_id = some_dimension_id
some_user.save()

看到区别了吗?我希望这有助于澄清一些事情。 :)

编辑

具体来说,问题出在设置变量LDAP_AUTH_USER_FIELDS 中,它试图将extensionAttribute10 中的ID(从LDAP 接收到的数据)映射到Usercoid 字段,该字段需要一个QVDSSSecurityDimension 实例。解决方案是将LDAP_AUTH_USER_FIELDS 中的密钥从coid 更改为coid_id

【讨论】:

  • 也许我不清楚,但我想你可能误解了。我的用户模型是使用 LDAP 身份验证从活动目录创建的。所有用户都有一个 coid。一旦用户通过他们的网络登录进行验证,他们来自 AD 的用户信息将被写入数据库表用户。如果数据库中存在具有相同 coid 的现有用户,我当前的逻辑将起作用,但如果没有,则会引发上述错误。我只是想使用 QVDSSSecurityDimension 在 coid 上创建关系,以便我可以提取名称。
  • 嗯,我明白了。但是从错误来看,很明显在某些时候您的代码正试图将字符串分配给需要QVDSSSecurityDimension 实例的字段。您可以编辑您的问题,包括您用于登录的代码吗?或者至少是根据回溯抛出错误的特定行中的代码。
  • 对于登录没有自定义代码,它都是 ldap3 库的一部分,一旦配置它就可以工作。登录后,我们没有使用 DSS Security 中的 coid,仅在提交时使用,但如果用户中不存在 coid,应用程序不会让我走那么远,当应用程序第一次出现时它不会部署。我已经为个人资料添加了我的视图,这发生在登录之后。
  • 感谢编辑!好的,我的理论是问题出在django-python3-ldap 库中的_get_or_create_user 内部。听起来该方法将coid = '08732' 作为参数,但它实际上应该接收coid_id = '08732'。你能在C:\Users\HFA9592\AppData\Local\Programs\Python\Python36\lib\site-packages\django_python3_ldap\ldap.py 的第 115 行 before 中设置一个断点,然后告诉我self._connection.response[0] 对象里面有什么吗?
  • self._connection.response[0] 是来自 AD 的用户信息。我把 print self._connection.reponse[0] 放在上面。这是很多数据。
猜你喜欢
  • 1970-01-01
  • 2019-11-14
  • 2020-04-04
  • 2021-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多