【问题标题】:Django ModelForms - testing forms with model that have M2M inline instance using an intermediate modelDjango ModelForms - 使用中间模型测试具有 M2M 内联实例的模型的表单
【发布时间】:2014-01-13 13:26:46
【问题描述】:

我有发票/估计 django 应用程序,我需要为它编写测试。由于我是这方面的初学者,因此测试表格对我来说很难。 这是代码 - 用于模型、表单和管理员:

# MODELS 
class Invoice(models.Model):
    subject = models.ForeignKey(
    Subject,
    verbose_name= _("Subject")
    )
    date = models.DateField(default=date.today())
    tasks = models.ManyToManyField(
    Task,
    through='TaskCount',
    )
    discount = models.DecimalField(
    max_digits=10, 
    decimal_places=2, 
    default=0
    )
    tip1 = models.BooleanField();
    tip2 = models.BooleanField();
    tax_a = models.BooleanField();
    notes = models.CharField(max_length=500, blank=True)
    expire = models.DateField(null=True, blank=True)
    invoicenumber = models.IntegerField("Invoice number")
    subtotal = models.DecimalField(max_digits=10, decimal_places=2,)
    amount = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    adi_start_date = models.DateField(null=True, blank=True)
    adi_end_date = models.DateField(null=True, blank=True)


class People(models.Model):
    first_name = models.CharField(
        "First name",max_length=50, blank=True
    )
    last_name = models.CharField(
        "Last name",max_length=50, blank=True
    )
    ....
    .... # other fields
    def __unicode__(self):
    return u"%s %s" % (self.first_name, self.last_name)

# model for inlines
class TaskCount(models.Model):
    count_items = models.PositiveIntegerField("Qty", default=1)
    task = models.ForeignKey(
    'Task',
    null=False)
    estimate = models.ForeignKey(
        'Estimate', 
        null=True, 
        blank=True
    )
    invoice = models.ForeignKey(
        'Invoice',
        related_name='task_count',
        null=True,
        blank=True
    )

    def __unicode__(self):
        return u"%s" % (str(self.task.price))

class Task(models.Model):
    label = models.CharField(
        "Task name",
        max_length=150
    )
    price = models.DecimalField(
        "Price per session", 
        max_digits=10, 
        decimal_places=2,
        default=0
    )
    is_adi = models.BooleanField(
        default=False,
        help_text=_("")
    )

# FORMS

class InvoiceAdminForm(forms.ModelForm):
    class Meta:
        model = Invoice

    def __init__(self, *args, **kwargs):
        super(InvoiceAdminForm, self).__init__(*args, **kwargs)
    ....

# ADMIN

# inline
class TaskCountInline(admin.TabularInline):
    model = TaskCount
    extra = 0
    fields = ('num_items', 'task')

    def __init__(self, *args, **kwargs):
        super(TaskCountInline, self).__init__(*args, **kwargs)

    ...

class InvoiceAdmin(admin.ModelAdmin):
    list_display = ('subject', 'date', 'amount',
        'discount', 'invoicenumber','get_pdf',
        'edit_item', 'delete_item')
    list_display_links = ('edit_item',)
    filter_horizontal = ('tasks',)
    search_fields = ['subject__first_name','subject__last_name']
    actions = ['create_pdf']
    inlines = (TaskCountInline,)
    form = InvoiceAdminForm

    fieldsets = (
    .... # fieldsets ovverride
    )

    formfield_overrides = {
    models.CharField: {'widget': forms.Textarea},
    }

    def get_pdf(self, obj):
        opts = self.model._meta
        return '<a href=%(reverse_url)s>' \
               '<img src="/static/admin/img/blue-document-pdf.png"/>' \
               '</a>' % {
           'reverse_url':reverse(
                       'admin:%s_%s_pdf' % (
                            opts.app_label, 
                            opts.object_name.lower()),
            args=(obj.id,)
            ),
            }
    get_pdf.short_description = _("PDF")
    get_pdf.allow_tags = True

    def change_view(self, request, object_id, form_url='', extra_context=None):
    ...


    def get_urls(self):
        urls = super(InvoiceAdmin, self).get_urls()
        info = self.model._meta.app_label, self.model._meta.module_name
        extra_urls = patterns('',
            url(r'^(?P<object_id>\d+)/pdf/$',
                self.admin_site.admin_view(self.pdf_view),
                name='%s_%s_pdf' % info),
        )
        return extra_urls+urls

    def pdf_view(self, request, object_id, extra_context=None):
        """
        view for generating PDF objects
        """
        ....


    def get_form(self, request, obj=None, **kwargs):
        form = super(InvoiceAdmin, self).get_form(request, obj, **kwargs)
        form.base_fields['invoicenumber'].initial =self.__get_last_invoice_nr_plus_one()
        ....
        return form

    def __get_last_invoice_nr_plus_one(self):
        ...

我正在尝试测试 InvoiceAdminForm。这是测试代码:

class InvoiceAdminFormTestCase(TestCase):
def setUp(self):
    self.subject = Preople.objects.create(
    ...
    )
    self.task1 = Task.objects.create(
    ...
    )
    self.task2 = Task.objects.create(
    ...
    )

    self.data = {
    'subject':self.subject.id,
    'csrfmiddlewaretoken':csrf._get_new_csrf_key(),
    'tip1': 1,
    'task_count-0-task': self.task1.id,
    'subtotal': 67.00,
    'amount': 69.68,
    '_continue': 'Save and continue edit',
    'task_count-0-id': '',
    'task_count-__prefix__-task': '',
    'notes': 'example  notes',
    'task_count-0-invoice':'',
    'task_count-1-num_items': '1',
    'invoicenumber': '4',
    'task_count-__prefix__-invoice': '',
    'adi_end_date': '',
    'task_count-MAX_NUM_FORMS': '1000',
    'discount': '0',
    'dataìe': '25/11/2013',
    'task_count-1-id': '',
    'adi_start_date': '',
    'task_count-1-invoice': '',
    'task_count-0-num_items': '1',
    'task_count-TOTAL_FORMS': '2',
    'task_count-__prefix__-num_items': '1',
    'task_count-__prefix__-id': '',
    'task_count-INITIAL_FORMS': '0',
    'task_count-1-task': self.task2.id,
    'expire': ''
    }
    self.form = InvoiceAdminForm(data=self.data)

由于缺少必填字段“任务”,测试失败。我无法解释这一点。我尝试使用管理界面保存表单,并且它可以正常工作。使用 ipdb 调试器,我保存了数据查询字典,并在表单案例中使用它。因此,用于填充表单的数据相同,但结果不同。

这是回溯:

# ./manage.py test gpf2
Creating test database for alias 'default'...
======================================================================
FAIL: test_valid_form (gpf2.tests.test_forms.InvoiceAdminFormTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/data_ext4/src/proj7/gpf2/tests/test_forms.py", line 113, in test_valid_form
self.assertTrue(self.form.is_valid(), msg=self.form.errors)
AssertionError: <ul class="errorlist"><li>tasks<ul class="errorlist"><li>This field is required..</li></ul></li></ul>

----------------------------------------------------------------------
Ran 13 tests in 0.205s

FAILED (failures=1)
Destroying test database for alias 'default'...

有什么帮助吗?

谢谢

【问题讨论】:

  • 错误的回溯是什么?
  • @SimeonVisser,添加了回溯

标签: python django django-forms django-admin django-testing


【解决方案1】:

您正在使用内联来编辑相关任务,因此管理员从您的表单中删除了 tasks 字段。如果您在管理员之外使用表单,tasks 字段仍然存在,因此您需要提供值。或者,您可以在表单的 Meta 选项上明确指定 fields,不包括 tasks 字段。

管理员为其用法生成了不同的表单,要查看此内容,请尝试:

from django.test import RequestFactory
from django.contrib.auth.models import User
from django.contrib import admin

# import InvoiceAdmin and Invoice here...

rf = RequestFactory()
user = User.objects.get(username="a_user_with_sufficient_permissions")
request = rf.get("/admin/foo/")
request.user = user
a = InvoiceAdmin(Invoice, admin.site)
print a.get_form(request).Meta.fields  # the form as modified by the admin
print a.form.Meta.fields  # the form as specified by you

【讨论】:

  • 谢谢。如果删除了tasks 字段,为什么表单的clean() 会失败?另外:为什么相同的查询字典使用管理界面有效,而不是通过代码填充表单?
  • 该字段已被管理员删除,但仅限于 inside 管理员。你可以自己看看这个,让我编辑我的答案...
  • @Luke 我编辑了我的答案。至于查询字典:对于管理员,它包含内联表单集的数据;对于您的表单,如果需要包含任务 ID 列表
  • 谢谢,但我仍然不清楚如何继续。如何修改我的测试用例才能成功?谢谢
  • 'tasks': [self.task1.id] 添加到self.data,例如(然后您可以删除所有其他带有task_ 前缀的键)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-09-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多