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):
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)
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):
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)