Compare test suites results feature PRODX-36767
Update Django to 4.2 version
Change-Id: Ieed17cfac12518262503043ccee03473a3023221
diff --git a/testrail_bot/control/celery_tasks/tasks.py b/testrail_bot/control/celery_tasks/tasks.py
index 71b6783..6450c68 100644
--- a/testrail_bot/control/celery_tasks/tasks.py
+++ b/testrail_bot/control/celery_tasks/tasks.py
@@ -24,3 +24,17 @@
@shared_task
def update_plot_data():
jenkins_pipeline.update_plot()
+
+
+@shared_task
+def get_test_passability_in_suite(diff_id: int, report_id: int):
+ try:
+ testrail_pipeline.get_test_passrate_in_suite(diff_id, report_id)
+ except BaseException as e:
+ print(f"Caught next exception: {e}")
+ traceback.print_exc()
+ from .. import models
+ r = models.SuitePassRate.objects.get(pk=report_id)
+ r.status = "Unexpected fail"
+ r.finished = True
+ r.save()
diff --git a/testrail_bot/control/celery_tasks/test_rail_api.py b/testrail_bot/control/celery_tasks/test_rail_api.py
index e5932bc..d77b511 100644
--- a/testrail_bot/control/celery_tasks/test_rail_api.py
+++ b/testrail_bot/control/celery_tasks/test_rail_api.py
@@ -66,7 +66,7 @@
run_name: str = None,
created_after: str = None,
created_before: str = None,
- created_by: int = None) -> \
+ created_by: int = None, **kwargs) -> \
Iterator[List[dict]]:
limit_step = 100
suite_id = api.cases.get_case(case_id=case_id)["suite_id"]
diff --git a/testrail_bot/control/celery_tasks/testrail_pipeline.py b/testrail_bot/control/celery_tasks/testrail_pipeline.py
index 175fe74..f1e4bf0 100644
--- a/testrail_bot/control/celery_tasks/testrail_pipeline.py
+++ b/testrail_bot/control/celery_tasks/testrail_pipeline.py
@@ -1,8 +1,11 @@
+import datetime
import difflib
-from typing import TextIO, List, Tuple, Optional, Iterator
+import json
+from typing import TextIO, List, Tuple, Optional, Iterator, Dict
from datetime import datetime as dt
from datetime import timedelta
+from itertools import islice
from . import filters
from .enums import StatusEnum
from . import test_rail_api
@@ -242,7 +245,7 @@
"{defect}</a>".format(defect=sim_result["defects"])
test_link = "<a href=https://mirantis.testrail.com/index.php?/tests/" \
"view/{test_id}>{test_id}</a>".format(
- test_id=sim_result["test_id"])
+ test_id=sim_result["test_id"])
status_id = int(sim_result['status_id'])
if status_id in [StatusEnum.retest, StatusEnum.failed,
StatusEnum.blocked]:
@@ -372,3 +375,99 @@
f.write("Test processing finished")
f.flush()
finish_report(report)
+
+
+def get_cases(project_id: int, suite_id: int,
+ limit: int = 250,
+ max_limit: int = 1000,
+ filter: str = None,
+ created_after: int = None,
+ created_before: int = None,
+ ) -> Iterator[Dict]:
+ for offset in range(0, max_limit, limit):
+ cases = test_rail_api.api.cases.get_cases(
+ project_id=project_id,
+ suite_id=suite_id,
+ limit=limit,
+ offset=offset,
+ created_after=created_after,
+ created_before=created_before,
+ filter=filter).get("cases")
+
+ if not cases:
+ return
+ for case in cases:
+ yield case
+
+
+def get_test_passrate_in_suite(diff_id: int, report_id: int) -> Dict:
+ """
+ Returns a percentage of passed tests for each test in the suite
+
+ :param diff_id: ID of models.DiffOfSuitesPassRates object in the DB to
+ retrieve settings for comparing test suites
+ :param report_id: ID of models.SuitePassRate object which contains a
+ results of fetching passrates for specific test suite
+
+ :return: dict with
+ {"test_title": {
+ "rate": 40,
+ "case_id": 12345
+ },
+ "test_title2": {
+ "rate": 80,
+ "case_id": 12346
+ }
+ }
+ """
+
+ diff_obj: models.DiffOfSuitesPassRates = \
+ models.DiffOfSuitesPassRates.objects.get(pk=diff_id)
+ report: models.SuitePassRate = models.SuitePassRate.objects.get(
+ pk=report_id)
+ suite_id = report.suite_id
+
+ project_id = test_rail_api.get_project_id("Mirantis Cloud Platform")
+ report.suite_name = test_rail_api.get_suite_by_id(suite_id)["name"]
+
+ end_lookup_date = datetime.datetime.now()
+ start_lookup_date = end_lookup_date + timedelta(days=-15)
+ _filters = {
+ "created_by": 109,
+ "plan_name": "[MCP2.0]OSCORE",
+ "created_before": int(dt.timestamp(end_lookup_date)),
+ "created_after": int(dt.timestamp(start_lookup_date)),
+ }
+
+ passrate_by_cases = dict()
+ params = dict(project_id=project_id,
+ suite_id=suite_id,
+ filter=diff_obj.test_keyword,
+ limit=200)
+ for _n, case in enumerate(get_cases(**params), start=1):
+ case_title = case['title']
+ case_id = case["id"]
+ report.status = f"Current case: {_n}"
+ report.save()
+ # Limit generator to the list with the length defined in the
+ # DiffOfSuitesPassRates
+ last_case_results = list(islice(
+ test_rail_api.get_result_history_for_case(case_id, **_filters),
+ diff_obj.limit))
+
+ passrate_by_cases[case_title] = dict()
+ passrate_by_cases[case_title]['case_id'] = case_id
+ if last_case_results:
+ passed_tests = [x for x in last_case_results
+ if x[-1]["status_id"] == StatusEnum.passed]
+ passrate_by_cases[case_title]['rate'] = \
+ len(passed_tests) * 100 / diff_obj.limit
+ else:
+ passrate_by_cases[case_title]['rate'] = "No result found"
+ report.passrate_by_tests = json.dumps(passrate_by_cases,
+ indent=4)
+ report.status = f"Current case: {_n}"
+ report.save()
+ report.finished = True
+ report.save()
+ return passrate_by_cases
diff --git a/testrail_bot/control/forms.py b/testrail_bot/control/forms.py
index 2bdffe3..a7f28bb 100644
--- a/testrail_bot/control/forms.py
+++ b/testrail_bot/control/forms.py
@@ -1,6 +1,6 @@
from datetime import date
from django import forms
-from .models import TestRailTestRun
+from .models import TestRailTestRun, DiffOfSuitesPassRates, SuitePassRate
class TestRunForm(forms.ModelForm):
@@ -29,3 +29,22 @@
"filter_func": "Leave blank if not used",
}
+
+class DiffPassRatesForm(forms.ModelForm):
+ class Meta:
+ model = DiffOfSuitesPassRates
+ fields = ["limit", "test_keyword"]
+ labels = {
+ "test_keyword": "Pattern to search by tests",
+ "limit": "Count of tests to define the passrate. Don't recommend "
+ "to use a number greater that 10"
+ }
+
+
+class SuitePassRateForm(forms.ModelForm):
+ class Meta:
+ model = SuitePassRate
+ fields = ["suite_id"]
+ labels = {
+ "suite_id": "Suite ID"
+ }
diff --git a/testrail_bot/control/migrations/0003_suitepassrate_diffofsuitespassrates.py b/testrail_bot/control/migrations/0003_suitepassrate_diffofsuitespassrates.py
new file mode 100644
index 0000000..6215a62
--- /dev/null
+++ b/testrail_bot/control/migrations/0003_suitepassrate_diffofsuitespassrates.py
@@ -0,0 +1,36 @@
+# Generated by Django 4.2.7 on 2023-11-20 17:46
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('control', '0002_testrailtestrun_checked_tests'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='SuitePassRate',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('suite_id', models.CharField(choices=[('10651', '[MCP2.0_ROCKY]Tempest'), ('10635', '[MCP2.0_STEIN]Tempest'), ('10653', '[MCP2.0_TRAIN]Tempest'), ('10710', '[MCP2.0_USSURI]Tempest'), ('10888', '[MCP2.0_VICTORIA]Tempest'), ('11167', '[MCP2.0_WALLABY]Tempest'), ('11188', '[MCP2.0_XENA]Tempest'), ('11170', '[MCP2.0_YOGA]Tempest'), ('11192', '[MCP2.0_ANTELOPE]Tempest'), ('11193', '[MCP2.0_ANTELOPE]Stepler'), ('10886', '[MCP2.0_USSURI]Stepler'), ('10887', '[MCP2.0_VICTORIA]Stepler'), ('11171', '[MCP2.0_YOGA]Stepler')], max_length=20)),
+ ('suite_name', models.CharField(blank=True, max_length=100)),
+ ('passrate_by_tests', models.JSONField(blank=True, default='{}')),
+ ('status', models.TextField(blank=True, max_length=300)),
+ ('finished', models.BooleanField(blank=True, default=False)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='DiffOfSuitesPassRates',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('started_at', models.DateTimeField(auto_created=True, auto_now=True)),
+ ('limit', models.IntegerField(blank=True, default=10)),
+ ('test_keyword', models.CharField(blank=True, default='', max_length=300)),
+ ('report1', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='report1', to='control.suitepassrate')),
+ ('report2', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='report2', to='control.suitepassrate')),
+ ],
+ ),
+ ]
diff --git a/testrail_bot/control/models.py b/testrail_bot/control/models.py
index f0e014f..0956b1a 100644
--- a/testrail_bot/control/models.py
+++ b/testrail_bot/control/models.py
@@ -78,3 +78,42 @@
class ActionLog(models.Model):
name = models.CharField(max_length=500)
date = models.DateTimeField(null=True)
+
+
+class SuitePassRate(models.Model):
+ SUITE_CHOICES = [
+ ("10651", "[MCP2.0_ROCKY]Tempest"),
+ ("10635", "[MCP2.0_STEIN]Tempest"),
+ ("10653", "[MCP2.0_TRAIN]Tempest"),
+ ("10710", "[MCP2.0_USSURI]Tempest"),
+ ("10888", "[MCP2.0_VICTORIA]Tempest"),
+ ("11167", "[MCP2.0_WALLABY]Tempest"),
+ ("11188", "[MCP2.0_XENA]Tempest"),
+ ("11170", "[MCP2.0_YOGA]Tempest"),
+ ("11192", "[MCP2.0_ANTELOPE]Tempest"),
+
+ ("11193", "[MCP2.0_ANTELOPE]Stepler"),
+ ("10886", "[MCP2.0_USSURI]Stepler"),
+ ("10887", "[MCP2.0_VICTORIA]Stepler"),
+ ("11171", "[MCP2.0_YOGA]Stepler"),
+ ]
+ suite_id = models.CharField(max_length=20, choices=SUITE_CHOICES)
+ suite_name = models.CharField(max_length=100, blank=True)
+ passrate_by_tests = models.JSONField(default="{}", blank=True)
+ status = models.TextField(max_length=300, blank=True)
+ finished = models.BooleanField(default=False, blank=True)
+
+
+class DiffOfSuitesPassRates(models.Model):
+ limit = models.IntegerField(default=10, blank=True)
+ test_keyword = models.CharField(default="", max_length=300, blank=True)
+ report1 = models.ForeignKey(to=SuitePassRate,
+ related_name="report1",
+ on_delete=models.CASCADE,
+ blank=True)
+ report2 = models.ForeignKey(to=SuitePassRate,
+ related_name="report2",
+ on_delete=models.CASCADE,
+ blank=True)
+ started_at = models.DateTimeField(auto_created=True,
+ auto_now=True)
diff --git a/testrail_bot/control/template_tags/custom_tags.py b/testrail_bot/control/template_tags/custom_tags.py
new file mode 100644
index 0000000..92e203a
--- /dev/null
+++ b/testrail_bot/control/template_tags/custom_tags.py
@@ -0,0 +1,10 @@
+from django.template.defaulttags import register
+
+
+@register.filter
+def is_numberic(value):
+ try:
+ float(value)
+ return True
+ except ValueError:
+ return False
diff --git a/testrail_bot/control/templates/base.html b/testrail_bot/control/templates/base.html
index d2dd9a3..4bd5e45 100644
--- a/testrail_bot/control/templates/base.html
+++ b/testrail_bot/control/templates/base.html
@@ -25,6 +25,8 @@
<li class="nav-item">
<a class="nav-link" href="{% url 'list_reports' %}">Reports</a></li>
<li class="nav-item">
+ <a class="nav-link" href="{% url 'list_of_comparing_reports' %}">Compare Suites</a></li>
+ <li class="nav-item">
<a class="nav-link" href="{% url 'help' %}">Help</a></li>
<li class="nav-item">
<a class="nav-link" href="{% url 'jenkins_plot' %}">Jenkins
diff --git a/testrail_bot/control/templates/control/compare_suites.html b/testrail_bot/control/templates/control/compare_suites.html
new file mode 100644
index 0000000..f5905fa
--- /dev/null
+++ b/testrail_bot/control/templates/control/compare_suites.html
@@ -0,0 +1,100 @@
+{% extends "base.html" %}
+{% load bootstrap5 %}
+{% load custom_tags %}
+{% block section %}
+
+{% if is_finished != None %}
+ {% if is_finished %}
+ <i>Generating completed</i>
+ {% else %}
+ <meta http-equiv="refresh" content="10">
+ <i>Generating in progress...</i>
+ {% endif %}
+{% endif %}
+
+<div class="p-2 border">
+ <h3>Compare Suites</h3>
+ <div class="input-group inline">
+ {% bootstrap_form_errors diff_form type='non_fields' %}
+ {% bootstrap_form_errors report1_form type='non_fields' %}
+ {% bootstrap_form_errors report2_form type='non_fields' %}
+ <form action="{% url 'submit_suites' %}" method="post" class="form">
+ {% csrf_token %}
+ <div class="col-xs-8 d-inline-flex">
+ {% bootstrap_field report1_form.suite_id size='sm' %}</div>
+ <div class="col-xs-8 d-inline-flex">
+ {% bootstrap_field report2_form.suite_id size='sm' %}</div>
+ <div class="col-xs-8">
+ {% bootstrap_field diff_form.test_keyword size='sm' %}</div>
+ <div class="col-xs-8">
+ {% bootstrap_field diff_form.limit size='sm' %}</div>
+
+ <input type="submit" class="btn btn-primary m-1"
+ value="Compare results" />
+ </form>
+ </div>
+
+ {% if is_finished or diff_table %}
+ Total: {{ diff_table | length }}
+ <table id="diff_table" class="p-4 table table-sm border">
+ <tbody>
+ <tr>
+ <th></th>
+ <th>{% if report1.finished %} Completed
+ {% else %} in progress...{% endif %}
+ </th>
+ <th>
+ {% if report2.finished %} Completed
+ {% else %} in progress...{% endif %}
+ </th>
+ </tr>
+ <tr>
+ <th></th>
+ <th>{{ report1.suite_name}}
+ </th>
+ <th>
+ {{ report2.suite_name}}
+ </th>
+ </tr>
+ <tr>
+ <th></th>
+ <th>
+ {{ report1.status}}
+ </th>
+ <th>
+ {{ report2.status}}
+ </th>
+ </tr>
+ {% for test_name, probabilities in diff_table.items %}
+ <tr>
+ <th scope="row">{{ test_name }}</th>
+ {% for prob in probabilities %}
+ {% if prob %}
+ <td>
+ <a href="https://mirantis.testrail.com/index.php?/cases/results/{{ prob.case_id }}"
+ class="text-decoration-none
+ {% if prob.rate|is_numberic %}
+ {% if prob.rate > 90 %}
+ badge bg-success
+ {% elif prob.rate > 10 %}
+ badge bg-warning
+ {% else %}
+ badge bg-danger
+ {% endif %}
+ {% endif%}
+">
+ {{ prob.rate }}</a>
+ </td>
+ {% else %}
+ <td> Not found </td>
+ {% endif %}
+ {% endfor %}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ {% endif %}
+<!-- for test purposes only -->
+<!-- <pre class="text-wrap"> {{data | safe}}</pre>-->
+</div>
+{% endblock %}
diff --git a/testrail_bot/control/templates/control/list_comparing_suites.html b/testrail_bot/control/templates/control/list_comparing_suites.html
new file mode 100644
index 0000000..8d90718
--- /dev/null
+++ b/testrail_bot/control/templates/control/list_comparing_suites.html
@@ -0,0 +1,29 @@
+{% extends "base.html" %}
+{% load bootstrap5 %}
+{% block section %}
+ <div class="p-2 border">
+ <input type="button"
+ onclick="location.href='{% url 'compare_suites' %}'"
+ class="btn btn-primary m-1"
+ value="Start new comparing" />
+
+ {% for report in reports %}
+ <a href="{% url 'report_comparing_suites' report.id %}"
+ class="list-group-item
+ {% if report.report1.finished and report.report2.finished %}
+ list-group-item-success
+ {% else %}
+ list-group-item-warning
+ {% endif %}
+ ">
+ <b>{{report.report1.suite_name}}</b> vs.
+ <b>{{report.report2.suite_name}}</b>
+
+ {% if report.test_keyword %}
+ [{{ report.test_keyword }}]
+ {% endif %}
+ started at {{ report.started_at |date:'Y-m-d H:i:s' }}
+ </a>
+ {% endfor %}
+ </div>
+{% endblock %}
diff --git a/testrail_bot/control/urls.py b/testrail_bot/control/urls.py
index fd8bebc..b5292f3 100644
--- a/testrail_bot/control/urls.py
+++ b/testrail_bot/control/urls.py
@@ -12,6 +12,15 @@
path("reports/<int:report_id>/", views.single_report, name="single_report"),
path('index/', views.index, name='index'),
path("help/", views.show_help, name="help"),
- path("update_jenkins_plot", views.update_jenkins_plot, name="update_jenkins"),
- path("jenkins_plot", views.jenkins_plot, name="jenkins_plot")
+ path("update_jenkins_plot",
+ views.update_jenkins_plot,
+ name="update_jenkins"),
+ path("jenkins_plot", views.jenkins_plot, name="jenkins_plot"),
+ path("compare_suites/new/", views.compare_suites, name="compare_suites"),
+ path("compare_suites/",
+ views.list_of_comparing_reports,
+ name="list_of_comparing_reports"),
+ path("compare_suites/submit/", views.submit_suites, name="submit_suites"),
+ path("compare_suites/<int:report_id>/", views.report_comparing_suites,
+ name="report_comparing_suites"),
]
diff --git a/testrail_bot/control/utils.py b/testrail_bot/control/utils.py
new file mode 100644
index 0000000..2dabafe
--- /dev/null
+++ b/testrail_bot/control/utils.py
@@ -0,0 +1,57 @@
+from parse import parse
+from typing import Dict, List
+
+
+def parse_title(test_name):
+ # Sometimes id can be without the closing ] symbol
+ if "[" in test_name and "]" not in test_name:
+ test_name += "]"
+ token_count = test_name.split(".").__len__()
+
+ if test_name.startswith("=="):
+ return test_name
+
+ if test_name.startswith(".setUp") or test_name.startswith(".tearDown"):
+ fmt = "{test_title}(" + "{}." * (token_count - 2) + "{class_name})"
+ r = parse(fmt, test_name)
+ return f"{r['class_name']}.{r['test_title']}".strip()
+ try:
+ fmt = "{}." * (token_count - 2) + "{class_name}.{test_title}[{id}]"
+ r = parse(fmt, test_name)
+ return f"{r['test_title']}[{r['id']}]"
+ except TypeError:
+ # return file_name.test_class.test_name in other complicated cases
+ return '.'.join(test_name.split(".")[:3])
+
+
+def short_names_for_dict(_dict):
+ __dict = {}
+ for _k in _dict.keys():
+ __k = parse_title(_k)
+ # Replace only those keys which are absent in the dict or empty
+ # (defined as "No result found")
+ if __dict.get(__k) == "No result found" or not __dict.get(__k):
+ __dict[__k] = _dict[_k]
+ return __dict
+
+
+def get_dict_diff(dict1: dict,
+ dict2: dict,
+ compare_by_key=None) -> Dict[str, List]:
+ all_keys = sorted(set(list(dict1.keys()) + list(dict2.keys())))
+
+ result = dict()
+ for k in all_keys:
+ if compare_by_key:
+ if dict1.get(k, {}).get(compare_by_key) == dict2.get(k, {}).get(
+ compare_by_key):
+ continue
+ else:
+ if dict1.get(k) == dict2.get(k):
+ continue
+ result[k] = [dict1.get(k), dict2.get(k)]
+ return result
+
+
+if __name__ == "__main__":
+ pass
diff --git a/testrail_bot/control/views.py b/testrail_bot/control/views.py
index c9fe8f3..f7b0a9d 100644
--- a/testrail_bot/control/views.py
+++ b/testrail_bot/control/views.py
@@ -7,7 +7,9 @@
from . import models
from . import forms
-from .celery_tasks.tasks import process_run, update_plot_data
+from .celery_tasks.tasks import process_run, update_plot_data, \
+ get_test_passability_in_suite
+from .utils import short_names_for_dict, get_dict_diff
def index(request):
@@ -55,7 +57,8 @@
def single_report(request, report_id):
report = models.TestRailReport.objects.get(pk=report_id)
data = report.path.read().decode("utf-8")
- if request.method == "POST" and request.is_ajax():
+ if request.method == "POST" \
+ and request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest':
return HttpResponse(
json.dumps({"data": data, "finished": report.finished}),
content_type="application/json")
@@ -141,3 +144,80 @@
request, "control/jenkins_plot.html",
{"update_date": update_date, "update_started": update_started,
"job_names": enumerate(job_names, 1)})
+
+
+def submit_suites(request):
+ form = forms.DiffPassRatesForm(request.POST)
+ if not form.is_valid():
+ print(f"{form.errors=}")
+ return
+ report1 = models.SuitePassRate(suite_id=request.POST["first-suite_id"])
+ report1.save()
+ report2 = models.SuitePassRate(suite_id=request.POST["second-suite_id"])
+ report2.save()
+
+ diff_model = models.DiffOfSuitesPassRates(
+ report1=report1,
+ report2=report2,
+ limit=form.cleaned_data["limit"],
+ test_keyword=form.cleaned_data["test_keyword"]
+ )
+ diff_model.save()
+ get_test_passability_in_suite.delay(diff_model.id, report1.id)
+ get_test_passability_in_suite.delay(diff_model.id, report2.id)
+
+ return redirect("report_comparing_suites", diff_model.id)
+
+
+def compare_suites(request):
+ if request.method == "POST":
+ return submit_suites(request)
+
+ diff_form = forms.DiffPassRatesForm()
+ report1_form = forms.SuitePassRateForm(prefix='first')
+ report2_form = forms.SuitePassRateForm(prefix='second')
+
+ return render(
+ request, "control/compare_suites.html",
+ {
+ "diff_form": diff_form,
+ "report1_form": report1_form,
+ "report2_form": report2_form,
+ "finished": None
+ })
+
+
+def list_of_comparing_reports(request):
+ list_of_reports = models.DiffOfSuitesPassRates.objects.all()
+ return render(
+ request, "control/list_comparing_suites.html",
+ {
+ "reports": list_of_reports
+ })
+
+
+def report_comparing_suites(request, report_id):
+ report = models.DiffOfSuitesPassRates.objects.get(pk=report_id)
+ passrate1 = short_names_for_dict(json.loads(
+ report.report1.passrate_by_tests))
+ passrate2 = short_names_for_dict(json.loads(
+ report.report2.passrate_by_tests))
+
+ diff_table = get_dict_diff(dict1=passrate1,
+ dict2=passrate2,
+ compare_by_key="rate")
+ diff_form = forms.DiffPassRatesForm(instance=report)
+ report1_form = forms.SuitePassRateForm(instance=report.report1,
+ prefix="first")
+ report2_form = forms.SuitePassRateForm(instance=report.report2,
+ prefix="second")
+
+ return render(
+ request, "control/compare_suites.html",
+ {"diff_form": diff_form,
+ "report1_form": report1_form,
+ "report2_form": report2_form,
+ "report1": report.report1,
+ "report2": report.report2,
+ "is_finished": report.report1.finished and report.report2.finished,
+ "diff_table": diff_table})
diff --git a/testrail_bot/requirements.txt b/testrail_bot/requirements.txt
index 41fe5c8..d0c566a 100644
--- a/testrail_bot/requirements.txt
+++ b/testrail_bot/requirements.txt
@@ -1,5 +1,5 @@
celery==4.4.6
-Django==3.0.7
+Django==4.2.7
django-bootstrap-v5==1.0.11
psycopg2-binary==2.8.5
redis==3.5.3
@@ -8,4 +8,6 @@
python-jenkins==1.7.0
matplotlib==3.3.2
retry==0.9.2
-
+psycopg2==2.9.9
+parse==1.19.1
+rpdb==0.1.6
diff --git a/testrail_bot/testrail_bot/settings.py b/testrail_bot/testrail_bot/settings.py
index a40279d..0dfc606 100644
--- a/testrail_bot/testrail_bot/settings.py
+++ b/testrail_bot/testrail_bot/settings.py
@@ -65,6 +65,9 @@
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
+ 'libraries': {
+ 'custom_tags': 'control.template_tags.custom_tags'
+ }
},
},
]