开发者问题收集

Django Admin 站点 'int' 在模型表单提交时没有 len() 错误

2020-12-03
294

当尝试在 Django 管理站点中提交模型表单 (/add/) 时,我收到以下错误:

TypeError 类型为“int”的对象没有 len()

Environment:


Request Method: POST
Request URL: http://localhost:8000/admin/app/card/add/

Django Version: 3.1.4
Python Version: 3.8.6
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'app',
 'crispy_forms',
 'django_filters',
 'markdownify',
 'ckeditor',
 'ckeditor_uploader',
 'rest_framework',
 'smart_selects',
 'storages',
 'widget_tweaks']
Installed 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']



Traceback (most recent call last):
  File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/core/handlers/base.py", line 179, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/contrib/admin/options.py", line 614, in wrapper
    return self.admin_site.admin_view(view)(*args, **kwargs)
  File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/utils/decorators.py", line 130, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/contrib/admin/sites.py", line 233, in inner
    return view(request, *args, **kwargs)
  File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/contrib/admin/options.py", line 1653, in add_view
    return self.changeform_view(request, None, form_url, extra_context)
  File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/utils/decorators.py", line 130, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/contrib/admin/options.py", line 1534, in changeform_view
    return self._changeform_view(request, object_id, form_url, extra_context)
  File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/contrib/admin/options.py", line 1582, in _changeform_view
    change_message = self.construct_change_message(request, form, formsets, add)
  File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/contrib/admin/options.py", line 1055, in construct_change_message
    return construct_change_message(form, formsets, add)
  File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/contrib/admin/utils.py", line 503, in construct_change_message
    changed_data = form.changed_data
  File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/utils/functional.py", line 48, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/forms/forms.py", line 449, in changed_data
    if field.has_changed(initial_value, data_value):
  File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/forms/models.py", line 1385, in has_changed
    if len(initial) != len(data):

Exception Type: TypeError at /admin/app/card/add/
Exception Value: object of type 'int' has no len()

Models.py:

class Card(models.Model):
    visible = models.BooleanField(default=True, verbose_name="Visible to Customers?")
    software = models.ForeignKey(Software, default=1, on_delete=models.CASCADE)
    product = ChainedForeignKey(
        Product,
        chained_field="software", 
        chained_model_field="software",
        auto_choose=True,
        show_all=False,
        sort=True,
        default=0,)

    utility = models.ForeignKey(Utility, verbose_name='Utilities', default=1, on_delete=models.CASCADE)
    function = ChainedForeignKey(
        Function,
        chained_field="utility", 
        chained_model_field="utility",
        auto_choose=True,
        show_all=False,
        sort=True,
        default=0,)
   
    error_code = models.ForeignKey(ErrorCode, default=0, on_delete=models.CASCADE, help_text="Please only use for error codes, if no code, leave empty.")
    message = RichTextUploadingField(default="", help_text='Message can include markdown for styling.')
    tags = RichTextUploadingField(default="", blank=True, null=False, help_text='Add Text that can be used as tags for searching for this card.')
    encountered = models.ManyToManyField(Encountered, default=0, verbose_name='Encountered in', help_text='Version or Year. Select "N/A" if unknown.')
    resolved = models.ForeignKey(Resolved, default=0, on_delete=models.CASCADE, verbose_name='Resolved in', help_text='If not resolved, enter "N/A".')
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    card_created_by = models.ForeignKey(
        'auth.User',
        related_name='card_created_by',
        on_delete=models.CASCADE,
        verbose_name='Created by',
        null=True)
    last_edited_by = models.ForeignKey(
        'auth.User',
        related_name='last_edited_by',
        on_delete=models.CASCADE,
        verbose_name='Last edited by',
        null=True)
    card_view_count = models.IntegerField(default=0)

class Solution(models.Model):
    card = models.ForeignKey(Card, on_delete=models.CASCADE, null=True)
    cause = RichTextUploadingField(blank=False, help_text='Message can include markdown for styling.')
    cause_solution = RichTextUploadingField(blank=False, verbose_name='Solution', help_text='Message can include markdown for styling.')
    solution_edited_by = models.ForeignKey(
        'auth.User',
        related_name='solution_edited_by',
        verbose_name='Last edited by',
        on_delete=models.CASCADE,
        blank=True,
        null=True
    )
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

Admin.py:

class SolutionsInlineFormset(forms.models.BaseInlineFormSet):
    def save_model(self, request, obj, form, change):
        obj.solution_edited_by = request.user
        obj.save()

    def clean(self):
        # get forms that actually have valid data
        count = 0
        for form in self.forms:
            try:
                if form.cleaned_data:
                    count += 1
            except AttributeError:
                # annoyingly, if a subform is invalid Django explicity raises
                # an AttributeError for cleaned_data
                pass
        if count < 1:
            raise forms.ValidationError('You must have at least one solution." \
                + " Please do not place solutions in the message field.')


class SolutionsInline(admin.TabularInline):
    model = Solution
    extra = 0
    ordering = ('-updated', 'id')
    readonly_fields = ('solution_edited_by',)
    formset = SolutionsInlineFormset


class CardAdmin(admin.ModelAdmin):
    model = Card
    list_display = ('id', 'card_view_count', 'product', 'software', 'utility', 'function', 'markdown_safe_message', 'updated')
    ordering = ('-updated', 'software', 'product', 'utility', 'card_view_count', )
    list_filter = ('software', 'product')
    formfield_overrides = {
        models.TextField: {'widget': CKEditorWidget},
    }

    inlines = [SolutionsInline, ]
    exclude = ['card_view_count', 'last_edited_by', 'card_created_by', ]
    readonly_fields = ('card_view_count', 'last_edited_by', 'card_created_by',)
    search_fields = ['id',
                     'message',
                     'tags',
                     'software__name',
                     'product__name',
                     'solution__cause',
                     'solution__cause_solution',
                     'error_code__code',
                     'function__name',
                     'utility__name',
                     'resolved__number',
                     'encountered__number',
                     ]
    save_as = True

    def save_model(self, request, obj, form, change):
        if change:
            obj.last_edited_by = request.user
        else:
            obj.card_created_by = request.user
        obj.save()

    def save_formset(self, request, form, formset, change):
        super(CardAdmin, self).save_formset(request, form, formset, change)
        instances = formset.save(commit=False)
        for i in instances:
            if hasattr(i, 'solution_edited_by') and not i.solution_edited_by:
                i.solution_edited_by = request.user
            i.save()

        formset.save_m2m()
        super(CardAdmin, self).save_formset(request, form, formset, change)

似乎无法找出导致此错误的原因。最近我唯一改变的就是在 list_display 中添加了“id”,并添加了 tags 字段。

跟踪了 /django/forms/models.py 中的错误行:

class ModelMultipleChoiceField(ModelChoiceField):
    """A MultipleChoiceField whose choices are a model QuerySet."""
    widget = SelectMultiple
    hidden_widget = MultipleHiddenInput
    default_error_messages = {
        'invalid_list': _('Enter a list of values.'),
        'invalid_choice': _('Select a valid choice. %(value)s is not one of the'
                            ' available choices.'),
        'invalid_pk_value': _('“%(pk)s” is not a valid value.')
    }
    def has_changed(self, initial, data):
        if self.disabled:
            return False
        if initial is None:
            initial = []
        if data is None:
            data = []
        if len(initial) != len(data):  # <-- Problem Child
            return True
        initial_set = {str(value) for value in self.prepare_value(initial)}
        data_set = {str(value) for value in data}
        return data_set != initial_set

有没有办法可以调试它以查看哪个变量或字段导致了这个问题?

如能得到任何帮助或建议,我们将不胜感激。

2个回答

您的回溯显示:

File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/forms/forms.py", line 449, in changed_data
    if field.has_changed(initial_value, data_value):
File "/home/corey/.virtualenvs/cardsite-_p-Jv6sl/lib/python3.8/site-packages/django/forms/models.py", line 1385, in has_changed
    if len(initial) != len(data):

因此,您尝试在 forms.py 中获取整数的 len()(您无法执行此操作)

您可以使用 initial_string = str(intial) 来存储一个字符串,然后可以使用 len(initial_string) 进行与之比较,但我认为有更好的方法可以完成您想要做的事情。(您想做什么?)

Colby
2020-12-03

问题最终出在 Card 类上的 encountered 属性上:

encountered = models.ManyToManyField(Encountered, default=0, verbose_name='Encountered in', help_text='Version or Year. Select "N/A" if unknown.')

与此同时,我刚刚将其更改为 ForeignKey

encountered = models.ForeignKey(Encountered, default=1, on_delete=models.CASCADE, verbose_name='Encountered in', help_text='Version or Year. Select "N/A" if unknown.')
Corey
2020-12-03