From 6120dfc2338465ab6cbca41c5c59c7f16092a4f2 Mon Sep 17 00:00:00 2001
From: OndraRehounek <ondra.rehounek@seznam.cz>
Date: Fri, 15 Apr 2022 16:00:38 +0200
Subject: [PATCH] district & region: Unify template for program blocks, finish
 management command

---
 README.md                                     |  1 +
 district/blocks.py                            | 18 ++++-
 ..._program_block.html => program_block.html} |  6 +-
 .../blocks/redmine_program_block.html         | 81 -------------------
 .../commands/update_redmine_issues.py         | 54 ++++++++++---
 redmine_utils/models.py                       |  5 --
 redmine_utils/utils.py                        | 37 ++++++---
 region/blocks.py                              | 17 +++-
 ..._program_block.html => program_block.html} |  0
 .../region/blocks/redmine_program_block.html  | 81 -------------------
 10 files changed, 104 insertions(+), 196 deletions(-)
 rename district/templates/district/blocks/{static_program_block.html => program_block.html} (89%)
 delete mode 100644 district/templates/district/blocks/redmine_program_block.html
 delete mode 100644 redmine_utils/models.py
 rename region/templates/region/blocks/{static_program_block.html => program_block.html} (100%)
 delete mode 100644 region/templates/region/blocks/redmine_program_block.html

diff --git a/README.md b/README.md
index d2583839..d055038b 100644
--- a/README.md
+++ b/README.md
@@ -174,6 +174,7 @@ Přes CRON je třeba na pozadí spouštět Django `manage.py` commandy:
 * `clearsessions` - maže expirované sessions (denně až týdně)
 * `publish_scheduled_pages` - publikuje naplánované stránky (každou hodinu)
 * `update_callendars` - stáhne a aktualizuje kalendáře (několikrát denně)
+* `update_redmine_issues` - aktualizuje programované body MS a KS stránek napojených na Redmine (několikrát denně)
 
 ### Fulltextové vyhledávání v češtině
 
diff --git a/district/blocks.py b/district/blocks.py
index f4849df7..693f061c 100644
--- a/district/blocks.py
+++ b/district/blocks.py
@@ -223,10 +223,10 @@ class StaticProgramBlock(StructBlock):
     perex = TextBlock(label="Krátký text pod nadpisem", required=True)
     person = PageChooserBlock(label="Garant", page_type=["district.DistrictPersonPage"])
     completion_percentage = IntegerBlock(label="Procento dokončení", required=True)
-    program_items = ListBlock(ProgramItemBlock())
+    program_items = ListBlock(ProgramItemBlock(), label="Seznam bodů")
 
     class Meta:
-        template = "district/blocks/static_program_block.html"
+        template = "district/blocks/program_block.html"
         icon = "list-ul"
         label = "Blok programu"
 
@@ -236,9 +236,19 @@ class RedmineProgramBlock(StructBlock):
     perex = TextBlock(label="Krátký text pod nadpisem", required=True)
     person = PageChooserBlock(label="Garant", page_type=["district.DistrictPersonPage"])
     redmine_issue = IntegerBlock(label="Číslo Redmine issue", required=True)
-    completion_percentage = IntegerBlock(label="Procento dokončení", required=False)
+    completion_percentage = IntegerBlock(
+        label="Procento dokončení - bude doplněno automaticky",
+        required=False,
+        help_text="Hodnota se automaticky načte s Redmine",
+    )
+    program_items = ListBlock(
+        ProgramItemBlock(),
+        label="Seznam bodů - bude doplněno automaticky",
+        help_text="Hodnota se automaticky načte s Redmine",
+        required=False,
+    )
 
     class Meta:
-        template = "district/blocks/redmine_program_block.html"
+        template = "district/blocks/program_block.html"
         icon = "site"
         label = "Blok programu stahovaný z Redmine"
diff --git a/district/templates/district/blocks/static_program_block.html b/district/templates/district/blocks/program_block.html
similarity index 89%
rename from district/templates/district/blocks/static_program_block.html
rename to district/templates/district/blocks/program_block.html
index d205bb68..caf0108a 100644
--- a/district/templates/district/blocks/static_program_block.html
+++ b/district/templates/district/blocks/program_block.html
@@ -20,7 +20,7 @@
               <div>
                 <a href="https://redmine.pirati.cz/issues/28177" class="contact-line icon-link content-block--nostyle">
                   <i class="ico--info"></i>
-                  <span>Plnění programu: {{ self.completion_percentage }}%</span>
+                  <span>Plnění programu: {{ self.completion_percentage | default_if_none:'' }}%</span>
                 </a>
               </div>
             </div>
@@ -43,10 +43,10 @@
         <tr>
           <td>
             <a href="{{ item.issue_link }}" target="_blank">
-              {{ item.title }}
+              {{ item.title | default_if_none:'' }}
             </a>
           </td>
-          <td>{{ item.completion_percentage }} %</td>
+          <td>{{ item.completion_percentage | default_if_none:'' }} %</td>
         </tr>
       {% endfor %}
       </tbody>
diff --git a/district/templates/district/blocks/redmine_program_block.html b/district/templates/district/blocks/redmine_program_block.html
deleted file mode 100644
index 94240293..00000000
--- a/district/templates/district/blocks/redmine_program_block.html
+++ /dev/null
@@ -1,81 +0,0 @@
-<article class="mt-8">
-  <div class="lg:flex lg:space-x-16">
-    <div class="lg:w-3/5">
-      <h2 class="head-heavy-sm mb-2 lg:mb-4">
-        {{ self.headline }}
-      </h2>
-      <div itemprop="description" class="w-full content-block">
-        <p>
-          {{ self.perex }}
-        </p>
-      </div>
-    </div>
-    <div class="pt-8 lg:w-2/5 md:pt-0">
-      <div class="card">
-        <div class="card__body">
-          {% include "shared/person_badge_snippet.html" with person_page=self.person %}
-
-          <div class="content-block">
-            <div class="space-y-4 mt-8">
-              <div>
-                <a href="https://redmine.pirati.cz/issues/{{ self.redmine_issue }}" class="contact-line icon-link content-block--nostyle">
-                  <i class="ico--info"></i>
-                  <span>
-                    Plnění programu:
-                    <span data-redmine-issue-id="{{ self.redmine_issue }}">10</span>%
-                  </span>
-                </a>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-
-  <div class="mt-4">
-    <table class="table table--striped table--bordered w-full">
-      <thead>
-      <tr>
-        <td>Název</td>
-        <td>Stav plnění</td>
-      </tr>
-      </thead>
-      <tbody data-redmine-table-id="{{ self.redmine_issue }}">
-      </tbody>
-    </table>
-  </div>
-</article>
-
-<script>
-  (function () {
-    const redmineIssueId = '{{ self.redmine_issue }}'
-
-    fetch(`https://redmine.pirati.cz/issues/${redmineIssueId}.json`)
-      .then(response => response.json())
-      .then(data => handleOverallData(data.issue || {}))
-
-    fetch(`https://redmine.pirati.cz/issues.json?parent_id=${redmineIssueId}&sort=id:as`)
-      .then(response => response.json())
-      .then(data => handleIssueList(data.issues || []))
-
-    function handleOverallData(issue) {
-      document.querySelector('[data-redmine-issue-id="{{ self.redmine_issue }}"]').innerText = issue.done_ratio || 0
-    }
-
-    function handleIssueList(issueList) {
-      const table = document.querySelector('[data-redmine-table-id="{{ self.redmine_issue }}"]')
-      for (const issue of issueList) {
-        table.innerHTML +=
-          `<tr>
-            <td>
-              <a href="https://redmine.pirati.cz/issues/${issue.id}" target="_blank">
-                ${issue.subject || ''}
-              </a>
-            </td>
-            <td>${issue.done_ratio || 0} %</td>
-          </tr>`
-      }
-    }
-  })()
-</script>
diff --git a/redmine_utils/management/commands/update_redmine_issues.py b/redmine_utils/management/commands/update_redmine_issues.py
index b42ddb11..cc0ae3a5 100644
--- a/redmine_utils/management/commands/update_redmine_issues.py
+++ b/redmine_utils/management/commands/update_redmine_issues.py
@@ -1,9 +1,11 @@
 from django.core.management.base import BaseCommand
+from wagtail.core.blocks import StructValue
 
+from district.blocks import ProgramItemBlock
 from district.models import DistrictProgramPage
 from region.models import RegionProgramPage
 
-from ...utils import get_issue_list, get_issue_overall
+from ...utils import get_issue_list, get_issue_overall_percentage
 
 
 class Command(BaseCommand):
@@ -17,14 +19,48 @@ class Command(BaseCommand):
         self.stdout.write("Updating Redmine issues...")
 
         for model in updated_models:
-            program_pages = model.objects.all()
-            for page_content in model.objects.all().values_list("content", flat=True):
-                for program_block in page_content:
+            for page in model.objects.all():
+                for program_block in page.content:
                     if program_block.block_type == "redmine_program_block":
-                        # block_content_dict = program_block.value
-                        redmine_issue = program_block.value["redmine_issue"]
-                        program_block.value["headline"] = "test"
-                        get_issue_overall(redmine_issue)
-                        get_issue_list(redmine_issue)
+                        fill_data_from_redmine(program_block.value)
+
+                page.save()
 
         self.stdout.write("\nUpdating Redmine issues finished")
+
+
+def fill_data_from_redmine(program_block_value: dict):
+    """
+    Naplní hondnotu completion_percentage a program_items pro RedmineProgramBlock
+    """
+    redmine_issue_id = program_block_value["redmine_issue"]
+    fill_overall_percentage_from_redmine(program_block_value, redmine_issue_id)
+    fill_program_items_from_redmine(program_block_value, redmine_issue_id)
+
+
+def fill_overall_percentage_from_redmine(program_block_value: dict, issue_id: int):
+    """
+    Naplní hondnotu completion_percentage pro RedmineProgramBlock hodnoutou z Redmine.
+    """
+    program_block_value["completion_percentage"] = get_issue_overall_percentage(
+        issue_id
+    )
+
+
+def fill_program_items_from_redmine(program_block_value: dict, issue_id: int):
+    """
+    Naplní hondnotu program_items pro RedmineProgramBlock. Nejdříve hodnotu (list)
+    promaže a pak z getných issues naplní pomocí StructValue
+    """
+    issue_list = get_issue_list(issue_id)
+    program_block_value["program_items"].clear()
+
+    for issue in issue_list:
+        sv = StructValue(ProgramItemBlock)
+        sv.update(
+            {
+                "completion_percentage": issue.get("done_ratio", None),
+                "title": issue.get("subject", ""),
+            }
+        )
+        program_block_value["program_items"].append(sv)
diff --git a/redmine_utils/models.py b/redmine_utils/models.py
deleted file mode 100644
index d07ff1c1..00000000
--- a/redmine_utils/models.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from django.db import models
-
-
-class RedmineIssue(models.Model):
-    pass
diff --git a/redmine_utils/utils.py b/redmine_utils/utils.py
index e06990d3..4d55d071 100644
--- a/redmine_utils/utils.py
+++ b/redmine_utils/utils.py
@@ -1,24 +1,41 @@
 import json
+import logging
+from typing import Dict, List
 
 import requests
 
+logger = logging.getLogger(__name__)
 
-def get_issue_overall(issue_id):
+
+def get_issue_overall_percentage(issue_id: int) -> int or None:
+    """
+    Getne stav issue na redmine a vrátí její procento dokončení (nebo None)
+    """
     response = requests.get("https://redmine.pirati.cz/issues/{}.json".format(issue_id))
-    print(response.text)
 
+    if response.status_code != 200 or not response.text:
+        logger.error(
+            "Nepodařilo se stáhnout info o Redmine issue",
+            extra={"redmine_issue": issue_id},
+        )
+        return None
+
+    return json.loads(response.text).get("issue", {}).get("done_ratio", None)
 
-def get_issue_list(issue_id):
+
+def get_issue_list(issue_id: int) -> List[Dict]:
+    """
+    Getne list dceřiných issues a vrátí je jako list dictionaries.
+    """
     response = requests.get(
         "https://redmine.pirati.cz/issues.json?parent_id={}&sort=id:as".format(issue_id)
     )
 
     if response.status_code != 200 or not response.text:
-        # TODO log
-        return
-
-    data = json.loads(response.text)
-
-    print(response)
+        logger.error(
+            "Nepodařilo se stáhnout dílčí issues pro Redmine issue",
+            extra={"redmine_issue": issue_id},
+        )
+        return []
 
-    return data["issues"]
+    return json.loads(response.text).get("issues", [])
diff --git a/region/blocks.py b/region/blocks.py
index d54020cb..e173f963 100644
--- a/region/blocks.py
+++ b/region/blocks.py
@@ -223,10 +223,10 @@ class StaticProgramBlock(StructBlock):
     perex = TextBlock(label="Krátký text pod nadpisem", required=True)
     person = PageChooserBlock(label="Garant", page_type=["region.RegionPersonPage"])
     completion_percentage = IntegerBlock(label="Procento dokončení", required=True)
-    program_items = ListBlock(ProgramItemBlock())
+    program_items = ListBlock(ProgramItemBlock(), label="Seznam bodů")
 
     class Meta:
-        template = "region/blocks/static_program_block.html"
+        template = "region/blocks/program_block.html"
         icon = "list-ul"
         label = "Blok programu"
 
@@ -236,8 +236,19 @@ class RedmineProgramBlock(StructBlock):
     perex = TextBlock(label="Krátký text pod nadpisem", required=True)
     person = PageChooserBlock(label="Garant", page_type=["region.RegionPersonPage"])
     redmine_issue = IntegerBlock(label="Číslo Redmine issue", required=True)
+    completion_percentage = IntegerBlock(
+        label="Procento dokončení - bude doplněno automaticky",
+        required=False,
+        help_text="Hodnota se automaticky načte s Redmine",
+    )
+    program_items = ListBlock(
+        ProgramItemBlock(),
+        label="Seznam bodů - bude doplněno automaticky",
+        help_text="Hodnota se automaticky načte s Redmine",
+        required=False,
+    )
 
     class Meta:
-        template = "region/blocks/redmine_program_block.html"
+        template = "region/blocks/program_block.html"
         icon = "site"
         label = "Blok programu stahovaný z Redmine"
diff --git a/region/templates/region/blocks/static_program_block.html b/region/templates/region/blocks/program_block.html
similarity index 100%
rename from region/templates/region/blocks/static_program_block.html
rename to region/templates/region/blocks/program_block.html
diff --git a/region/templates/region/blocks/redmine_program_block.html b/region/templates/region/blocks/redmine_program_block.html
deleted file mode 100644
index 94240293..00000000
--- a/region/templates/region/blocks/redmine_program_block.html
+++ /dev/null
@@ -1,81 +0,0 @@
-<article class="mt-8">
-  <div class="lg:flex lg:space-x-16">
-    <div class="lg:w-3/5">
-      <h2 class="head-heavy-sm mb-2 lg:mb-4">
-        {{ self.headline }}
-      </h2>
-      <div itemprop="description" class="w-full content-block">
-        <p>
-          {{ self.perex }}
-        </p>
-      </div>
-    </div>
-    <div class="pt-8 lg:w-2/5 md:pt-0">
-      <div class="card">
-        <div class="card__body">
-          {% include "shared/person_badge_snippet.html" with person_page=self.person %}
-
-          <div class="content-block">
-            <div class="space-y-4 mt-8">
-              <div>
-                <a href="https://redmine.pirati.cz/issues/{{ self.redmine_issue }}" class="contact-line icon-link content-block--nostyle">
-                  <i class="ico--info"></i>
-                  <span>
-                    Plnění programu:
-                    <span data-redmine-issue-id="{{ self.redmine_issue }}">10</span>%
-                  </span>
-                </a>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-
-  <div class="mt-4">
-    <table class="table table--striped table--bordered w-full">
-      <thead>
-      <tr>
-        <td>Název</td>
-        <td>Stav plnění</td>
-      </tr>
-      </thead>
-      <tbody data-redmine-table-id="{{ self.redmine_issue }}">
-      </tbody>
-    </table>
-  </div>
-</article>
-
-<script>
-  (function () {
-    const redmineIssueId = '{{ self.redmine_issue }}'
-
-    fetch(`https://redmine.pirati.cz/issues/${redmineIssueId}.json`)
-      .then(response => response.json())
-      .then(data => handleOverallData(data.issue || {}))
-
-    fetch(`https://redmine.pirati.cz/issues.json?parent_id=${redmineIssueId}&sort=id:as`)
-      .then(response => response.json())
-      .then(data => handleIssueList(data.issues || []))
-
-    function handleOverallData(issue) {
-      document.querySelector('[data-redmine-issue-id="{{ self.redmine_issue }}"]').innerText = issue.done_ratio || 0
-    }
-
-    function handleIssueList(issueList) {
-      const table = document.querySelector('[data-redmine-table-id="{{ self.redmine_issue }}"]')
-      for (const issue of issueList) {
-        table.innerHTML +=
-          `<tr>
-            <td>
-              <a href="https://redmine.pirati.cz/issues/${issue.id}" target="_blank">
-                ${issue.subject || ''}
-              </a>
-            </td>
-            <td>${issue.done_ratio || 0} %</td>
-          </tr>`
-      }
-    }
-  })()
-</script>
-- 
GitLab