Ejemplo n.º 1
0
class TestRun(TCMSActionModel):
    run_id = models.AutoField(primary_key=True)

    product_version = models.ForeignKey('management.Version',
                                        related_name='version_run',
                                        on_delete=models.CASCADE)
    plan_text_version = models.IntegerField()

    start_date = models.DateTimeField(auto_now_add=True, db_index=True)
    stop_date = models.DateTimeField(null=True, blank=True, db_index=True)
    summary = models.TextField()
    notes = models.TextField(blank=True)
    estimated_time = DurationField(default=0)

    plan = models.ForeignKey('testplans.TestPlan',
                             related_name='run',
                             on_delete=models.CASCADE)
    environment_id = models.IntegerField(default=0)
    build = models.ForeignKey('management.Build',
                              related_name='build_run',
                              on_delete=models.CASCADE)
    manager = models.ForeignKey(settings.AUTH_USER_MODEL,
                                related_name='manager',
                                on_delete=models.CASCADE)
    default_tester = models.ForeignKey(settings.AUTH_USER_MODEL,
                                       null=True,
                                       blank=True,
                                       related_name='default_tester',
                                       on_delete=models.CASCADE)

    env_value = models.ManyToManyField('management.EnvValue',
                                       through='testruns.EnvRunValueMap')

    tag = models.ManyToManyField('management.Tag',
                                 through='testruns.TestRunTag',
                                 related_name='run')

    cc = models.ManyToManyField('auth.User', through='testruns.TestRunCC')
    auto_update_run_status = models.BooleanField(default=False)

    class Meta:
        db_table = u'test_runs'
        unique_together = ('run_id', 'product_version', 'plan_text_version')

    def __str__(self):
        return self.summary

    @classmethod
    def to_xmlrpc(cls, query=None):
        from tcms.xmlrpc.serializer import TestRunXMLRPCSerializer
        from tcms.xmlrpc.utils import distinct_filter

        _query = query or {}
        qs = distinct_filter(TestRun, _query).order_by('pk')
        s = TestRunXMLRPCSerializer(model_class=cls, queryset=qs)
        return s.serialize_queryset()

    @classmethod
    def list(cls, query):
        conditions = []

        mapping = {
            'search':
            lambda value: Q(run_id__icontains=value) | Q(summary__icontains=
                                                         value),
            'summary':
            lambda value: Q(summary__icontains=value),
            'product':
            lambda value: Q(build__product=value),
            'product_version':
            lambda value: Q(product_version=value),
            'plan':
            lambda value: Q(plan__plan_id=int(value))
            if is_int(value) else Q(plan__name__icontains=value),
            'build':
            lambda value: Q(build=value),
            'env_group':
            lambda value: Q(plan__env_group=value),
            'people_id':
            lambda value: Q(manager__id=value) | Q(default_tester__id=value),
            'manager':
            lambda value: Q(manager=value),
            'default_tester':
            lambda value: Q(default_tester=value),
            'tag__name__in':
            lambda value: Q(tag__name__in=value),
            'env_value__value__in':
            lambda value: Q(env_value__value__in=value),
            'case_run__assignee':
            lambda value: Q(case_run__assignee=value),
            'status':
            lambda value: {
                'running': Q(stop_date__isnull=True),
                'finished': Q(stop_date__isnull=False),
            }[value.lower()],
            'people':
            lambda value: {
                'default_tester': Q(default_tester=value),
                'manager': Q(manager=value),
                'people': Q(manager=value) | Q(default_tester=value),
                # TODO: Remove first one after upgrade to newer version.
                # query.set can return either '' or None sometimes, so
                # currently keeping these two lines here is a workaround.
                '': Q(manager=value) | Q(default_tester=value),
                None: Q(manager=value) | Q(default_tester=value),
            }[query.get('people_type')],
        }

        conditions = [
            mapping[key](value) for key, value in query.items()
            if value and key in mapping
        ]

        runs = cls.objects.filter(*conditions)

        value = query.get('sortby')
        if value:
            runs = runs.order_by(value)

        return runs.distinct()

    def belong_to(self, user):
        if self.manager == user or self.plan.author == user:
            return True

        return False

    def clear_estimated_time(self):
        """Converts a integer to time"""
        return format_timedelta(self.estimated_time)

    def check_all_case_runs(self, case_run_id=None):
        tcrs = self.case_run.all()
        tcrs = tcrs.select_related('case_run_status')

        for tcr in tcrs:
            if not tcr.is_finished():
                return False

        return True

    def get_absolute_url(self, request=None):
        # Upward compatibility code
        if request:
            return request.build_absolute_uri(
                reverse('testruns-get', args=[
                    self.pk,
                ]))

        return self.get_url(request)

    def get_notify_addrs(self):
        """
        Get the all related mails from the run
        """
        to = [self.manager.email]
        to.extend(self.cc.values_list('email', flat=True))
        if self.default_tester_id:
            to.append(self.default_tester.email)

        for tcr in self.case_run.select_related('assignee').all():
            if tcr.assignee_id:
                to.append(tcr.assignee.email)
        return list(set(to))

    def get_url_path(self):
        return reverse('testruns-get', args=[
            self.pk,
        ])

    # FIXME: rewrite to use multiple values INSERT statement
    def add_case_run(self,
                     case,
                     case_run_status=1,
                     assignee=None,
                     case_text_version=None,
                     build=None,
                     notes=None,
                     sortkey=0):
Ejemplo n.º 2
0
class TestCase(TCMSActionModel):
    case_id = models.AutoField(max_length=10, primary_key=True)
    create_date = models.DateTimeField(db_column='creation_date',
                                       auto_now_add=True)
    is_automated = models.IntegerField(db_column='isautomated', default=0)
    is_automated_proposed = models.BooleanField(default=False)
    script = models.TextField(blank=True, null=True)
    arguments = models.TextField(blank=True, null=True)
    extra_link = models.CharField(max_length=1024,
                                  default=None,
                                  blank=True,
                                  null=True)
    summary = models.CharField(max_length=255)
    requirement = models.CharField(max_length=255, blank=True, null=True)
    alias = models.CharField(max_length=255, blank=True)
    estimated_time = DurationField(db_column='estimated_time', default=0)
    notes = models.TextField(blank=True, null=True)

    case_status = models.ForeignKey(TestCaseStatus, on_delete=models.CASCADE)
    category = models.ForeignKey(Category,
                                 related_name='category_case',
                                 on_delete=models.CASCADE)
    priority = models.ForeignKey('management.Priority',
                                 related_name='priority_case',
                                 on_delete=models.CASCADE)
    author = models.ForeignKey(settings.AUTH_USER_MODEL,
                               related_name='cases_as_author',
                               on_delete=models.CASCADE)
    default_tester = models.ForeignKey(settings.AUTH_USER_MODEL,
                                       related_name='cases_as_default_tester',
                                       blank=True,
                                       null=True,
                                       on_delete=models.CASCADE)
    reviewer = models.ForeignKey(settings.AUTH_USER_MODEL,
                                 related_name='cases_as_reviewer',
                                 null=True,
                                 on_delete=models.CASCADE)

    # FIXME: related_name should be cases instead of case. But now keep it
    # named case due to historical reason.
    plan = models.ManyToManyField('testplans.TestPlan',
                                  related_name='case',
                                  through='testcases.TestCasePlan')

    component = models.ManyToManyField('management.Component',
                                       related_name='cases',
                                       through='testcases.TestCaseComponent')

    tag = models.ManyToManyField('management.Tag',
                                 related_name='case',
                                 through='testcases.TestCaseTag')

    # Auto-generated attributes from back-references:
    # 'texts' : list of TestCaseTexts (from TestCaseTexts.case)
    class Meta:
        db_table = u'test_cases'

    def __str__(self):
        return self.summary

    @classmethod
    def to_xmlrpc(cls, query=None):
        from tcms.xmlrpc.serializer import TestCaseXMLRPCSerializer
        from tcms.xmlrpc.utils import distinct_filter

        _query = query or {}
        qs = distinct_filter(TestCase, _query).order_by('pk')
        s = TestCaseXMLRPCSerializer(model_class=cls, queryset=qs)
        return s.serialize_queryset()

    @classmethod
    def create(cls, author, values):
        """
        Create the case element based on models/forms.
        """
        case = cls.objects.create(
            author=author,
            is_automated=values['is_automated'],
            is_automated_proposed=values['is_automated_proposed'],
            # sortkey = values['sortkey'],
            script=values['script'],
            arguments=values['arguments'],
            extra_link=values['extra_link'],
            summary=values['summary'],
            requirement=values['requirement'],
            alias=values['alias'],
            estimated_time=values['estimated_time'],
            case_status=values['case_status'],
            category=values['category'],
            priority=values['priority'],
            default_tester=values['default_tester'],
            notes=values['notes'],
        )
        tags = values.get('tag')
        if tags:
            map(lambda c: case.add_tag(c), tags)
        return case

    @classmethod
    def update(cls, case_ids, values):
        if isinstance(case_ids, int):
            case_ids = [
                case_ids,
            ]

        fields = [field.name for field in cls._meta.fields]

        tcs = cls.objects.filter(pk__in=case_ids)
        _values = dict((k, v) for k, v in values.items()
                       if k in fields and v is not None and v != u'')
        if values['notes'] == u'':
            _values['notes'] = u''
        if values['script'] == u'':
            _values['script'] = u''
        tcs.update(**_values)
        return tcs

    @classmethod
    def list(cls, query, plan=None):
        """List the cases with request"""
        from django.db.models import Q

        if not plan:
            q = cls.objects
        else:
            q = cls.objects.filter(plan=plan)

        if query.get('case_id_set'):
            q = q.filter(pk__in=query['case_id_set'])

        if query.get('search'):
            q = q.filter(
                Q(pk__icontains=query['search'])
                | Q(summary__icontains=query['search'])
                | Q(author__email__startswith=query['search']))

        if query.get('summary'):
            q = q.filter(Q(summary__icontains=query['summary']))

        if query.get('author'):
            q = q.filter(
                Q(author__first_name__startswith=query['author'])
                | Q(author__last_name__startswith=query['author'])
                | Q(author__username__icontains=query['author'])
                | Q(author__email__startswith=query['author']))

        if query.get('default_tester'):
            q = q.filter(
                Q(default_tester__first_name__startswith=query[
                    'default_tester']) |
                Q(default_tester__last_name__startswith=query['default_tester']
                  ) |
                Q(default_tester__username__icontains=query['default_tester'])
                | Q(default_tester__email__startswith=query['default_tester']))

        if query.get('tag__name__in'):
            q = q.filter(tag__name__in=query['tag__name__in'])

        if query.get('category'):
            q = q.filter(category__name=query['category'].name)

        if query.get('priority'):
            q = q.filter(priority__in=query['priority'])

        if query.get('case_status'):
            q = q.filter(case_status__in=query['case_status'])

        # If plan exists, remove leading and trailing whitespace from it.
        plan_str = query.get('plan', '').strip()
        if plan_str:
            try:
                # Is it an integer?  If so treat as a plan_id:
                plan_id = int(plan_str)
                q = q.filter(plan__plan_id=plan_id)
            except ValueError:
                # Not an integer - treat plan_str as a plan name:
                q = q.filter(plan__name__icontains=plan_str)
        del plan_str

        if query.get('product'):
            q = q.filter(category__product=query['product'])

        if query.get('component'):
            q = q.filter(component=query['component'])

        if query.get('bug_id'):
            q = q.filter(case_bug__bug_id__in=query['bug_id'])

        if query.get('is_automated'):
            q = q.filter(is_automated=query['is_automated'])

        if query.get('is_automated_proposed'):
            q = q.filter(is_automated_proposed=query['is_automated_proposed'])

        return q.distinct()

    @classmethod
    def list_confirmed(cls):
        confirmed_case_status = TestCaseStatus.get_CONFIRMED()

        query = {
            'case_status_id': confirmed_case_status.case_status_id,
        }

        return cls.list(query)

    @classmethod
    def mail_scene(cls,
                   objects,
                   field=None,
                   value=None,
                   ctype=None,
                   object_pk=None):
        tcs = objects.select_related()
        scence_templates = {
            'reviewer': {
                'template_name':
                'mail/change_case_reviewer.txt',
                'subject':
                'You have been speicific to be the reviewer of cases',
                'recipients':
                list(set(tcs.values_list('reviewer__email', flat=True))),
                'context': {
                    'test_cases': tcs
                },
            }
        }

        return scence_templates.get(field)

    def add_bug(self,
                bug_id,
                bug_system_id,
                summary=None,
                description=None,
                case_run=None,
                bz_external_track=False):
        bug, created = self.case_bug.get_or_create(
            bug_id=bug_id,
            case_run=case_run,
            bug_system_id=bug_system_id,
            summary=summary,
            description=description,
        )

        if created:
            if bz_external_track:
                bug_system = BugSystem.objects.get(pk=bug_system_id)
                it = IssueTrackerType.from_name(
                    bug_system.tracker_type)(bug_system)
                if not it.is_adding_testcase_to_issue_disabled():
                    it.add_testcase_to_issue([self], bug)
                else:
                    raise ValueError(
                        'Enable linking test cases by configuring API parameters '
                        'for this Issue Tracker!')
        else:
            raise ValueError('Bug %s already exist.' % bug_id)

    def add_component(self, component):
        return TestCaseComponent.objects.get_or_create(case=self,
                                                       component=component)

    def add_tag(self, tag):
        return TestCaseTag.objects.get_or_create(case=self, tag=tag)

    def update_tags(self, new_tags):
        '''
        Update case.tag
        so that case.tag == new_tags
        '''
        if new_tags is None or not isinstance(new_tags, list):
            return
        owning_tags = set(self.tag.iterator())
        new_tags = set(new_tags)
        tags_to_remove = owning_tags.difference(new_tags)
        tags_to_add = new_tags.difference(owning_tags)
        map(lambda c: self.add_tag(c), tags_to_add)
        map(lambda c: self.remove_tag(c), tags_to_remove)

    def add_text(self,
                 action,
                 effect,
                 setup,
                 breakdown,
                 author=None,
                 create_date=datetime.now(),
                 case_text_version=1,
                 action_checksum=None,
                 effect_checksum=None,
                 setup_checksum=None,
                 breakdown_checksum=None):
        if not author:
            author = self.author

        new_action_checksum = checksum(action)
        new_effect_checksum = checksum(effect)
        new_setup_checksum = checksum(setup)
        new_breakdown_checksum = checksum(breakdown)

        old_action, old_effect, old_setup, old_breakdown = self.text_checksum()
        if old_action != new_action_checksum \
                or old_effect != new_effect_checksum \
                or old_setup != new_setup_checksum \
                or old_breakdown != new_breakdown_checksum:
            case_text_version = self.latest_text_version() + 1

            latest_text = TestCaseText.objects.create(
                case=self,
                case_text_version=case_text_version,
                create_date=create_date,
                author=author,
                action=action,
                effect=effect,
                setup=setup,
                breakdown=breakdown,
                action_checksum=action_checksum or new_action_checksum,
                effect_checksum=effect_checksum or new_effect_checksum,
                setup_checksum=setup_checksum or new_setup_checksum,
                breakdown_checksum=breakdown_checksum
                or new_breakdown_checksum)
        else:
            latest_text = self.latest_text()

        return latest_text

    def add_to_plan(self, plan):
        TestCasePlan.objects.get_or_create(case=self, plan=plan)

    def clear_components(self):
        return TestCaseComponent.objects.filter(case=self, ).delete()

    def clear_estimated_time(self):
        """Converts a integer to time"""
        return format_timedelta(self.estimated_time)

    def get_bugs(self):
        return Bug.objects.select_related(
            'case_run', 'bug_system').filter(case__case_id=self.case_id)

    def get_components(self):
        return self.component.all()

    def get_component_names(self):
        return self.component.values_list('name', flat=True)

    def get_choiced(self, obj_value, choices):
        for x in choices:
            if x[0] == obj_value:
                return x[1]

    def get_is_automated(self):
        return self.get_choiced(self.is_automated, AUTOMATED_CHOICES)

    def get_is_automated_form_value(self):
        if self.is_automated == 2:
            return [0, 1]

        return (self.is_automated, )

    def get_is_automated_status(self):
        return self.get_is_automated() + (self.is_automated_proposed
                                          and ' (Autoproposed)' or '')

    def get_previous_and_next(self, pk_list):
        current_idx = pk_list.index(self.pk)
        prev = TestCase.objects.get(pk=pk_list[current_idx - 1])
        try:
            next = TestCase.objects.get(pk=pk_list[current_idx + 1])
        except IndexError:
            next = TestCase.objects.get(pk=pk_list[0])

        return (prev, next)

    def get_text_with_version(self, case_text_version=None):
        if case_text_version:
            try:
                return TestCaseText.objects.get(
                    case__case_id=self.case_id,
                    case_text_version=case_text_version)
            except TestCaseText.DoesNotExist:
                return NoneText

        return self.latest_text()

    def latest_text(self, text_required=True):
        text = self.text
        if not text_required:
            text = text.defer('action', 'effect', 'setup', 'breakdown')
        qs = text.order_by('-case_text_version')[0:1]
        return NoneText if len(qs) == 0 else qs[0]

    def latest_text_version(self):
        qs = self.text.order_by('-case_text_version').only(
            'case_text_version')[0:1]
        return 0 if len(qs) == 0 else qs[0].case_text_version

    def text_exist(self):
        return self.text.exists()

    def text_checksum(self):
        qs = self.text.order_by('-case_text_version').only(
            'action_checksum', 'effect_checksum', 'setup_checksum',
            'breakdown_checksum')[0:1]
        if len(qs) == 0:
            return None, None, None, None
        else:
            text = qs[0]
            return (text.action_checksum, text.effect_checksum,
                    text.setup_checksum, text.breakdown_checksum)

    def mail(self, template, subject, context={}, to=[], request=None):
        from tcms.core.utils.mailto import mailto

        if not to:
            to = self.author.email

        to = list(set(to))
        mailto(template, subject, to, context, request)

    def remove_bug(self, bug_id, run_id=None):
        query = Bug.objects.filter(bug_id=bug_id, case=self.pk)
        if run_id:
            query = query.filter(case_run=run_id)
        else:
            query = query.filter(case_run__isnull=True)

        query.delete()

    def remove_component(self, component):
        # note: cannot use self.component.remove(component) on a ManyToManyField
        # which specifies an intermediary model so we use the model manager!
        self.component.through.objects.filter(case=self.pk,
                                              component=component.pk).delete()

    def remove_plan(self, plan):
        self.plan.through.objects.filter(case=self.pk, plan=plan.pk).delete()

    def remove_tag(self, tag):
        self.tag.through.objects.filter(case=self.pk, tag=tag.pk).delete()

    def get_url_path(self, request=None):
        return reverse('testcases-get', args=[
            self.pk,
        ])

    def _get_email_conf(self):
        try:
            return self.email_settings
        except ObjectDoesNotExist:
            return TestCaseEmailSettings.objects.create(case=self)

    emailing = property(_get_email_conf)
Ejemplo n.º 3
0
class TestRun(TCMSActionModel):
    # Attribute names to get testrun statistics
    PERCENTAGES = ('failed_case_run_percent', 'passed_case_run_percent',
                   'completed_case_run_percent')

    run_id = models.AutoField(primary_key=True)
    errata_id = models.IntegerField(max_length=11, null=True, blank=True)

    product_version = models.ForeignKey('management.Version',
                                        related_name='version_run')
    plan_text_version = models.IntegerField()

    start_date = models.DateTimeField(auto_now_add=True, db_index=True)
    stop_date = models.DateTimeField(null=True, blank=True, db_index=True)
    summary = models.TextField()
    notes = models.TextField(blank=True)
    estimated_time = DurationField(max_length=11, default=0)

    plan = models.ForeignKey('testplans.TestPlan', related_name='run')
    environment_id = models.IntegerField(default=0)
    build = models.ForeignKey('management.TestBuild', related_name='build_run')
    manager = models.ForeignKey('auth.User', related_name='manager')
    default_tester = models.ForeignKey('auth.User',
                                       null=True,
                                       blank=True,
                                       related_name='default_tester')

    env_value = models.ManyToManyField('management.TCMSEnvValue',
                                       through='testruns.TCMSEnvRunValueMap')

    tag = models.ManyToManyField('management.TestTag',
                                 through='testruns.TestRunTag')

    cc = models.ManyToManyField('auth.User', through='testruns.TestRunCC')
    auto_update_run_status = models.BooleanField(default=False)

    class Meta:
        db_table = u'test_runs'
        unique_together = ('run_id', 'product_version', 'plan_text_version')

    def __unicode__(self):
        return self.summary

    @classmethod
    def to_xmlrpc(cls, query=None):
        from tcms.xmlrpc.serializer import TestRunXMLRPCSerializer
        from tcms.xmlrpc.utils import distinct_filter

        _query = query or {}
        qs = distinct_filter(TestRun, _query).order_by('pk')
        s = TestRunXMLRPCSerializer(model_class=cls, queryset=qs)
        return s.serialize_queryset()

    @classmethod
    def list(cls, query):
        from django.db.models import Q

        q = cls.objects

        if query.get('search'):
            q = q.filter(
                Q(run_id__icontains=query['search'])
                | Q(summary__icontains=query['search']))

        if query.get('summary'):
            q = q.filter(summary__icontains=query['summary'])

        if query.get('product'):
            q = q.filter(build__product=query['product'])

        if query.get('product_version'):
            q = q.filter(product_version=query['product_version'])

        plan_str = query.get('plan')
        if plan_str:
            try:
                # Is it an integer?  If so treat as a plan_id:
                plan_id = int(plan_str)
                q = q.filter(plan__plan_id=plan_id)
            except ValueError:
                # Not an integer - treat plan_str as a plan name:
                q = q.filter(plan__name__icontains=plan_str)
        del plan_str

        if query.get('build'):
            q = q.filter(build=query['build'])

        # New environment search
        if query.get('env_group'):
            q = q.filter(plan__env_group=query['env_group'])

        if query.get('people_id'):
            q = q.filter(
                Q(manager__id=query['people_id'])
                | Q(default_tester__id=query['people_id']))

        if query.get('people'):
            if query.get('people_type') == 'default_tester':
                q = q.filter(default_tester=query['people'])
            elif query.get('people_type') == 'manager':
                q = q.filter(manager=query['people'])
            else:
                q = q.filter(
                    Q(manager=query['people'])
                    | Q(default_tester=query['people']))

        if query.get('manager'):
            q = q.filter(manager=query['manager'])

        if query.get('default_tester'):
            q = q.filter(default_tester=query['default_tester'])

        if query.get('sortby'):
            q = q.order_by(query.get('sortby'))

        if query.get('status'):
            if query.get('status').lower() == 'running':
                q = q.filter(stop_date__isnull=True)
            if query.get('status').lower() == 'finished':
                q = q.filter(stop_date__isnull=False)

        if query.get('tag__name__in'):
            q = q.filter(tag__name__in=query['tag__name__in'])

        if query.get('case_run__assignee'):
            q = q.filter(case_run__assignee=query['case_run__assignee'])

        return q.distinct()

    def belong_to(self, user):
        if self.manager == user or self.plan.author == user:
            return True

        return False

    def clear_estimated_time(self):
        """Converts a integer to time"""
        return format_timedelta(self.estimated_time)

    def check_all_case_runs(self, case_run_id=None):
        tcrs = self.case_run.all()
        tcrs = tcrs.select_related('case_run_status')

        for tcr in tcrs:
            if not tcr.is_finished():
                return False

        return True

    def get_absolute_url(self, request=None):
        # Upward compatibility code
        if request:
            return request.build_absolute_uri(
                reverse('tcms.testruns.views.get', args=[
                    self.pk,
                ]))

        return self.get_url(request)

    def get_notify_addrs(self):
        """
        Get the all related mails from the run
        """
        to = [self.manager.email]
        to.extend(self.cc.values_list('email', flat=True))
        if self.default_tester_id:
            to.append(self.default_tester.email)

        for tcr in self.case_run.select_related('assignee').all():
            if tcr.assignee_id:
                to.append(tcr.assignee.email)
        return list(set(to))

    def get_url_path(self):
        return reverse('tcms.testruns.views.get', args=[
            self.pk,
        ])

    # FIXME: rewrite to use multiple values INSERT statement
    def add_case_run(self,
                     case,
                     case_run_status=1,
                     assignee=None,
                     case_text_version=None,
                     build=None,
                     notes=None,
                     sortkey=0):
Ejemplo n.º 4
0
class TestCase(TCMSActionModel):
    case_id = models.AutoField(primary_key=True)
    create_date = models.DateTimeField(db_column='creation_date',
                                       auto_now_add=True)
    is_automated = models.IntegerField(db_column='isautomated', default=0)
    is_automated_proposed = models.BooleanField(default=False)
    script = models.TextField(blank=True)
    arguments = models.TextField(blank=True)
    extra_link = models.CharField(max_length=1024,
                                  default=None,
                                  blank=True,
                                  null=True)
    summary = models.CharField(max_length=255, blank=True)
    requirement = models.CharField(max_length=255, blank=True)
    alias = models.CharField(max_length=255, blank=True)
    estimated_time = DurationField(db_column='estimated_time', default=0)
    notes = models.TextField(blank=True)

    case_status = models.ForeignKey(TestCaseStatus, on_delete=models.CASCADE)
    category = models.ForeignKey(TestCaseCategory,
                                 related_name='category_case',
                                 on_delete=models.CASCADE)
    priority = models.ForeignKey('management.Priority',
                                 related_name='priority_case',
                                 on_delete=models.CASCADE)
    author = models.ForeignKey('auth.User',
                               related_name='cases_as_author',
                               on_delete=models.CASCADE)
    default_tester = models.ForeignKey('auth.User',
                                       blank=True,
                                       null=True,
                                       related_name='cases_as_default_tester',
                                       on_delete=models.SET_NULL)
    reviewer = models.ForeignKey('auth.User',
                                 blank=True,
                                 null=True,
                                 related_name='cases_as_reviewer',
                                 on_delete=models.SET_NULL)

    attachments = models.ManyToManyField(
        'management.TestAttachment',
        related_name='cases',
        through='testcases.TestCaseAttachment')

    # FIXME: related_name should be cases instead of case. But now keep it
    # named case due to historical reason.
    plan = models.ManyToManyField('testplans.TestPlan',
                                  related_name='case',
                                  through='testcases.TestCasePlan')

    component = models.ManyToManyField('management.Component',
                                       related_name='cases',
                                       through='testcases.TestCaseComponent')

    tag = models.ManyToManyField('management.TestTag',
                                 related_name='cases',
                                 through='testcases.TestCaseTag')

    # Auto-generated attributes from back-references:
    # 'texts' : list of TestCaseTexts (from TestCaseTexts.case)
    class Meta:
        db_table = 'test_cases'

    def __str__(self):
        return self.summary

    @classmethod
    def to_xmlrpc(cls, query=None):
        from tcms.xmlrpc.serializer import TestCaseXMLRPCSerializer
        from tcms.xmlrpc.utils import distinct_filter

        _query = query or {}
        qs = distinct_filter(TestCase, _query).order_by('pk')
        s = TestCaseXMLRPCSerializer(model_class=cls, queryset=qs)
        return s.serialize_queryset()

    @classmethod
    def create(cls, author, values, plans=None):
        """
        Create the case element based on models/forms.
        """
        case = cls.objects.create(
            author=author,
            is_automated=values['is_automated'],
            is_automated_proposed=values['is_automated_proposed'],
            script=values['script'],
            arguments=values['arguments'],
            extra_link=values['extra_link'],
            summary=values['summary'],
            requirement=values['requirement'],
            alias=values['alias'],
            estimated_time=values['estimated_time'],
            case_status=values['case_status'],
            category=values['category'],
            priority=values['priority'],
            default_tester=values['default_tester'],
            notes=values['notes'],
        )

        tags = values.get('tag')
        if tags:
            for tag in tags:
                case.add_tag(tag)

        components = values.get('component')
        if components is not None:
            for component in components:
                case.add_component(component=component)