From 7bec3ff5ddf81230d92cc5353b12c877b2fd18d7 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 6 Mar 2021 20:58:57 +1100 Subject: [PATCH 001/137] django-q --- InvenTree/InvenTree/settings.py | 13 +++++++++++++ requirements.txt | 1 + 2 files changed, 14 insertions(+) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 1a298240bc..0826c7d042 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -211,6 +211,7 @@ INSTALLED_APPS = [ 'djmoney', # django-money integration 'djmoney.contrib.exchange', # django-money exchange rates 'error_report', # Error reporting in the admin interface + 'django_q', ] MIDDLEWARE = CONFIG.get('middleware', [ @@ -285,6 +286,18 @@ REST_FRAMEWORK = { WSGI_APPLICATION = 'InvenTree.wsgi.application' +# django-q configuration +Q_CLUSTER = { + 'name': 'InvenTree', + 'workers': 4, + 'timeout': 90, + 'retry': 120, + 'queue_limit': 50, + 'bulk': 10, + 'orm': 'default', + 'sync': True, +} + # Markdownx configuration # Ref: https://neutronx.github.io/django-markdownx/customization/ MARKDOWNX_MEDIA_PATH = datetime.now().strftime('markdownx/%Y/%m/%d') diff --git a/requirements.txt b/requirements.txt index 7bbc14bd54..2c8bd9f3e7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,5 +30,6 @@ django-error-report==0.2.0 # Error report viewer for the admin interface django-test-migrations==1.1.0 # Unit testing for database migrations python-barcode[images]==0.13.1 # Barcode generator qrcode[pil]==6.1 # QR code generator +django-q==1.3.4 # Background task scheduling inventree # Install the latest version of the InvenTree API python library From 45b3c68930842b662c9e40958016e0c09b18567e Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 6 Mar 2021 21:41:19 +1100 Subject: [PATCH 002/137] New status info --- InvenTree/InvenTree/context.py | 14 ++++++++++++-- InvenTree/InvenTree/status.py | 31 +++++++++++++++++++------------ InvenTree/templates/stats.html | 15 ++++++++++++++- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/InvenTree/InvenTree/context.py b/InvenTree/InvenTree/context.py index 9fee5deaab..1d07511fa9 100644 --- a/InvenTree/InvenTree/context.py +++ b/InvenTree/InvenTree/context.py @@ -30,10 +30,20 @@ def health_status(request): request._inventree_health_status = True - return { - "system_healthy": InvenTree.status.check_system_health(), + status = { + 'django_q_running': InvenTree.status.is_q_cluster_running(), } + all_healthy = True + + for k in status.keys(): + if status[k] is not True: + all_healthy = False + + status['system_healthy'] = all_healthy + + return status + def status_codes(request): """ diff --git a/InvenTree/InvenTree/status.py b/InvenTree/InvenTree/status.py index ec2422a254..e02e476a51 100644 --- a/InvenTree/InvenTree/status.py +++ b/InvenTree/InvenTree/status.py @@ -1,15 +1,32 @@ """ Provides system status functionality checks. """ +# -*- coding: utf-8 -*- -from django.utils.translation import ugettext as _ +from __future__ import unicode_literals import logging +from django.utils.translation import ugettext as _ + +from django_q.monitor import Stat logger = logging.getLogger(__name__) +def is_q_cluster_running(**kwargs): + """ + Return True if at least one cluster worker is running + """ + + clusters = Stat.get_all() + + for cluster in clusters: + print("Cluster:", cluster) + + return len(clusters) > 0 + + def check_system_health(**kwargs): """ Check that the InvenTree system is running OK. @@ -19,7 +36,7 @@ def check_system_health(**kwargs): result = True - if not check_celery_worker(**kwargs): + if not is_q_cluster_running(**kwargs): result = False logger.warning(_("Celery worker check failed")) @@ -27,13 +44,3 @@ def check_system_health(**kwargs): logger.warning(_("InvenTree system health checks failed")) return result - - -def check_celery_worker(**kwargs): - """ - Check that a celery worker is running. - """ - - # TODO - Checks that the configured celery worker thing is running - - return True diff --git a/InvenTree/templates/stats.html b/InvenTree/templates/stats.html index 7b8a9bb93a..30d3f3d881 100644 --- a/InvenTree/templates/stats.html +++ b/InvenTree/templates/stats.html @@ -13,8 +13,9 @@ {% trans "Instance Name" %} {% inventree_instance_name %} + {% if user.is_staff %} - + {% trans "Server status" %} {% if system_healthy %} @@ -24,6 +25,18 @@ {% endif %} + + + {% trans "Background Worker" %} + + {% if django_q_running %} + {% trans "Operational" %} + {% else %} + {% trans "Not running" %} + {% endif %} + + + {% endif %} {% if not system_healthy %} {% for issue in system_issues %} From 660fed9196df26ae8f96d35c490608b50cfcda3d Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 10 Mar 2021 14:03:09 +1100 Subject: [PATCH 003/137] Remove unused code from settings.py --- InvenTree/InvenTree/settings.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 0826c7d042..0a72dce33e 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -414,11 +414,6 @@ CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', }, - 'qr-code': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'LOCATION': 'qr-code-cache', - 'TIMEOUT': 3600 - } } # Password validation From 5b68d82fa316486ec820de30ba2ce0cb2506ef54 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 10 Mar 2021 14:03:19 +1100 Subject: [PATCH 004/137] Skeleton for background tasks --- InvenTree/InvenTree/tasks.py | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 InvenTree/InvenTree/tasks.py diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py new file mode 100644 index 0000000000..0ebfc5a2fd --- /dev/null +++ b/InvenTree/InvenTree/tasks.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import json +import requests +import logging + +from datetime import timedelta + + +logger = logging.getLogger(__name__) + + +def delete_successful_tasks(): + """ + Delete successful task logs + which are more than a week old. + """ + + pass + +def check_for_updates(): + """ + Check if there is an update for InvenTree + """ + + response = requests.get('https://api.github.com/repos/inventree/inventree/releases/latest') + + if not response.status_code == 200: + logger.warning(f'Unexpected status code from GitHub API: {response.status_code}') + return + + data = json.loads(response.text) + + # TODO + + +def test(x): + print(f"Running at task! {x}") \ No newline at end of file From 1532a0c3a18e2e32fc5491dde5b9fe11cb6d2f53 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 11 Mar 2021 17:18:57 +1100 Subject: [PATCH 005/137] Add InvenTree/apps.py --- InvenTree/InvenTree/apps.py | 12 ++++++++++++ InvenTree/InvenTree/settings.py | 1 + 2 files changed, 13 insertions(+) create mode 100644 InvenTree/InvenTree/apps.py diff --git a/InvenTree/InvenTree/apps.py b/InvenTree/InvenTree/apps.py new file mode 100644 index 0000000000..a13c5b8d31 --- /dev/null +++ b/InvenTree/InvenTree/apps.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- + +from django.apps import AppConfig + + +class InvenTreeConfig(AppConfig): + name = 'InvenTree' + + def ready(self): + + print("Starting background tasks") + pass diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 0a72dce33e..323eade258 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -188,6 +188,7 @@ INSTALLED_APPS = [ 'build.apps.BuildConfig', 'common.apps.CommonConfig', 'company.apps.CompanyConfig', + 'InvenTree.apps.InvenTreeConfig', 'label.apps.LabelConfig', 'order.apps.OrderConfig', 'part.apps.PartConfig', From 3cf5aec289fdac940a74ca6278f2503837fe2b6f Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 11 Mar 2021 19:21:28 +1100 Subject: [PATCH 006/137] Refactor --- InvenTree/InvenTree/apps.py | 28 ++++++++++++++++++++++++++-- InvenTree/InvenTree/settings.py | 2 +- InvenTree/InvenTree/tasks.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/InvenTree/InvenTree/apps.py b/InvenTree/InvenTree/apps.py index a13c5b8d31..0b5283706c 100644 --- a/InvenTree/InvenTree/apps.py +++ b/InvenTree/InvenTree/apps.py @@ -1,6 +1,12 @@ # -*- coding: utf-8 -*- from django.apps import AppConfig +import logging + +import InvenTree.tasks + + +logger = logging.getLogger(__name__) class InvenTreeConfig(AppConfig): @@ -8,5 +14,23 @@ class InvenTreeConfig(AppConfig): def ready(self): - print("Starting background tasks") - pass + self.start_background_tasks() + + def start_background_tasks(self): + + try: + from django_q.models import Schedule + except (AppRegistryNotReady): + return + + logger.info("Starting background tasks...") + + InvenTree.tasks.schedule_task( + 'InvenTree.tasks.delete_successful_tasks', + schedule_type=Schedule.WEEKLY, + ) + + InvenTree.tasks.schedule_task( + 'InvenTree.tasks.check_for_updates', + schedule_type=Schedule.DAILY + ) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 323eade258..2c96fa8706 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -188,13 +188,13 @@ INSTALLED_APPS = [ 'build.apps.BuildConfig', 'common.apps.CommonConfig', 'company.apps.CompanyConfig', - 'InvenTree.apps.InvenTreeConfig', 'label.apps.LabelConfig', 'order.apps.OrderConfig', 'part.apps.PartConfig', 'report.apps.ReportConfig', 'stock.apps.StockConfig', 'users.apps.UsersConfig', + 'InvenTree.apps.InvenTreeConfig', # InvenTree app runs last # Third part add-ons 'django_filters', # Extended filter functionality diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index 0ebfc5a2fd..e34c7e8c21 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -7,10 +7,35 @@ import logging from datetime import timedelta +from django.core.exceptions import AppRegistryNotReady + logger = logging.getLogger(__name__) +def schedule_task(taskname, **kwargs): + """ + Create a scheduled task. + If the task has already been scheduled, ignore! + """ + + try: + from django_q.models import Schedule + except (AppRegistryNotReady): + logger.warning("Could not start background tasks - App registry not ready") + return + + if Schedule.objects.filter(func=taskname).exists(): + logger.info(f"Scheduled task '{taskname}' already exists. (Skipping)") + else: + logger.info(f"Creating scheduled task '{taskname}'") + + Schedule.objects.create( + func=taskname, + **kwargs + ) + + def delete_successful_tasks(): """ Delete successful task logs @@ -32,8 +57,12 @@ def check_for_updates(): data = json.loads(response.text) + print("Response:") + print(data) # TODO + return data + def test(x): print(f"Running at task! {x}") \ No newline at end of file From 18defcff160acca611518b95f9dd444bc0c98a26 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 11 Mar 2021 19:56:22 +1100 Subject: [PATCH 007/137] Read version number from GitHub --- InvenTree/InvenTree/tasks.py | 42 ++++++++++++++++++++++++++++------ InvenTree/InvenTree/tests.py | 13 +++++++++++ InvenTree/InvenTree/version.py | 23 +++++++++++++++++++ InvenTree/common/models.py | 2 +- 4 files changed, 72 insertions(+), 8 deletions(-) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index e34c7e8c21..61b6885b03 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import re import json import requests import logging @@ -35,7 +36,7 @@ def schedule_task(taskname, **kwargs): **kwargs ) - + def delete_successful_tasks(): """ Delete successful task logs @@ -49,6 +50,12 @@ def check_for_updates(): Check if there is an update for InvenTree """ + try: + import common.models + import InvenTree.version + except AppRegistryNotReady: + return + response = requests.get('https://api.github.com/repos/inventree/inventree/releases/latest') if not response.status_code == 200: @@ -57,12 +64,33 @@ def check_for_updates(): data = json.loads(response.text) - print("Response:") - print(data) - # TODO + tag = data.get('tag_name', None) - return data + if not tag: + logger.warning(f"'tag_name' missing from GitHub response") + return + match = re.match(r"^.*(\d+)\.(\d+)\.(\d+).*$", tag) -def test(x): - print(f"Running at task! {x}") \ No newline at end of file + if not len(match.groups()) == 3: + logger.warning(f"Version '{tag}' did not match expected pattern") + return + + try: + latest_version = [int(x) for x in match.groups()] + except (ValueError): + logger.warning(f"Version '{tag}' not integer format") + return + + if not len(latest_version) == 3: + logger.warning(f"Version '{tag}' is not correct format") + return + + logger.info(f"Latest InvenTree version: '{tag}'") + + # Save the version to the database + common.models.InvenTreeSetting.set_setting( + 'INVENTREE_LATEST_VERSION', + tag, + None + ) diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index 96f32e7f57..1cb382e338 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -7,6 +7,7 @@ from django.core.exceptions import ValidationError from .validators import validate_overage, validate_part_name from . import helpers +from . import version from mptt.exceptions import InvalidMove @@ -269,3 +270,15 @@ class TestSerialNumberExtraction(TestCase): with self.assertRaises(ValidationError): e("10, a, 7-70j", 4) + + +class TestVersionNumber(TestCase): + + def test_tuple(self): + + v = version.inventreeVersionTuple() + self.assertEqual(len(v), 3) + + s = '.'.join(v) + + self.assertTrue(s in version.inventreeVersion()) diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py index 4d3d546789..6aa5c2616c 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -4,9 +4,11 @@ Provides information on the current InvenTree version import subprocess import django +import re import common.models + INVENTREE_SW_VERSION = "0.1.8 pre" # Increment this number whenever there is a significant change to the API that any clients need to know about @@ -23,6 +25,27 @@ def inventreeVersion(): return INVENTREE_SW_VERSION +def inventreeVersionTuple(): + """ Return the InvenTree version string as (maj, min, sub) tuple """ + + match = re.match(r"^.*(\d+)\.(\d+)\.(\d+).*$", INVENTREE_SW_VERSION) + + return [int(g) for g in match.groups()] + + +def versionTupleToInt(version): + """ + Convert a version tuple (x, y, z) to an integer. + This simple integer can then be used for direct version comparison + """ + + n = version[0] * 1000 * 1000 + n += version[1] * 1000 + n += version[2] + + return n + + def inventreeApiVersion(): return INVENTREE_API_VERSION diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 06c06bde05..23e0773605 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -486,7 +486,7 @@ class InvenTreeSetting(models.Model): create: If True, create a new setting if the specified key does not exist. """ - if not user.is_staff: + if user is not None and not user.is_staff: return try: From 4925f24ca9983e7f06e2a7bcdd465382b2be6698 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 11 Mar 2021 20:07:59 +1100 Subject: [PATCH 008/137] Add "up to date" info to the "about" window --- InvenTree/InvenTree/context.py | 2 ++ InvenTree/InvenTree/version.py | 32 +++++++++++++++++++++++++++++--- InvenTree/templates/about.html | 19 +++++++++++++++---- InvenTree/templates/navbar.html | 17 +++++++++++++---- 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/InvenTree/InvenTree/context.py b/InvenTree/InvenTree/context.py index 1d07511fa9..43e8b904b9 100644 --- a/InvenTree/InvenTree/context.py +++ b/InvenTree/InvenTree/context.py @@ -42,6 +42,8 @@ def health_status(request): status['system_healthy'] = all_healthy + status['up_to_date'] = InvenTree.version.isInvenTreeUpToDate() + return status diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py index 6aa5c2616c..bd11c50882 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -25,10 +25,13 @@ def inventreeVersion(): return INVENTREE_SW_VERSION -def inventreeVersionTuple(): +def inventreeVersionTuple(version=None): """ Return the InvenTree version string as (maj, min, sub) tuple """ - match = re.match(r"^.*(\d+)\.(\d+)\.(\d+).*$", INVENTREE_SW_VERSION) + if version is None: + version = INVENTREE_SW_VERSION + + match = re.match(r"^.*(\d+)\.(\d+)\.(\d+).*$", str(version)) return [int(g) for g in match.groups()] @@ -44,7 +47,30 @@ def versionTupleToInt(version): n += version[2] return n - + + +def isInvenTreeUpToDate(): + """ + Test if the InvenTree instance is "up to date" with the latest version. + + A background task periodically queries GitHub for latest version, + and stores it to the database as INVENTREE_LATEST_VERSION + """ + + latest = common.models.InvenTreeSetting.get_setting('INVENTREE_LATEST_VERSION', None) + + # No record for "latest" version - we must assume we are up to date! + if not latest: + return True + + # Extract "tuple" version + version = inventreeVersionTuple(latest) + version_int = versionTupleToInt(version) + + inventree_int = versionTupleToInt(inventreeVersionTuple()) + + return inventree_int >= version_int + def inventreeApiVersion(): return INVENTREE_API_VERSION diff --git a/InvenTree/templates/about.html b/InvenTree/templates/about.html index cedfb40ca1..30f9bd19d7 100644 --- a/InvenTree/templates/about.html +++ b/InvenTree/templates/about.html @@ -19,19 +19,30 @@ - {% trans "InvenTree Version" %}{% inventree_version %} + {% trans "InvenTree Version" %} + + {% inventree_version %} + {% if up_to_date %} + {% trans "Up to Date" %} + {% else %} + {% trans "Update Available" %} + {% endif %} + - {% trans "Django Version" %}{% django_version %} + {% trans "Django Version" %} + {% django_version %} - {% trans "Commit Hash" %}{% inventree_commit_hash %} + {% trans "Commit Hash" %} + {% inventree_commit_hash %} - {% trans "Commit Date" %}{% inventree_commit_date %} + {% trans "Commit Date" %} + {% inventree_commit_date %} diff --git a/InvenTree/templates/navbar.html b/InvenTree/templates/navbar.html index 6e3fe024ca..c52e87d753 100644 --- a/InvenTree/templates/navbar.html +++ b/InvenTree/templates/navbar.html @@ -59,8 +59,8 @@ {% endif %} From bfb0cb3b4754109657fb29c353d450f4de94f5dd Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 12 Mar 2021 15:27:28 +1100 Subject: [PATCH 009/137] Add a "heartbeat" task which runs every 5 minutes - Allows us to track if the worker is running - Due to Stat.get_all() not always working --- InvenTree/InvenTree/apps.py | 10 +++++++++- InvenTree/InvenTree/status.py | 23 ++++++++++++++++++++--- InvenTree/InvenTree/tasks.py | 17 +++++++++++++---- InvenTree/templates/navbar.html | 4 +++- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/InvenTree/InvenTree/apps.py b/InvenTree/InvenTree/apps.py index 0b5283706c..902ef2c648 100644 --- a/InvenTree/InvenTree/apps.py +++ b/InvenTree/InvenTree/apps.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- -from django.apps import AppConfig import logging +from django.apps import AppConfig +from django.core.exceptions import AppRegistryNotReady + import InvenTree.tasks @@ -34,3 +36,9 @@ class InvenTreeConfig(AppConfig): 'InvenTree.tasks.check_for_updates', schedule_type=Schedule.DAILY ) + + InvenTree.tasks.schedule_task( + 'InvenTree.tasks.heartbeat', + schedule_type=Schedule.MINUTES, + minutes=5 + ) \ No newline at end of file diff --git a/InvenTree/InvenTree/status.py b/InvenTree/InvenTree/status.py index e02e476a51..1f6b01053c 100644 --- a/InvenTree/InvenTree/status.py +++ b/InvenTree/InvenTree/status.py @@ -6,9 +6,11 @@ Provides system status functionality checks. from __future__ import unicode_literals import logging +from datetime import datetime, timedelta from django.utils.translation import ugettext as _ +from django_q.models import Success from django_q.monitor import Stat logger = logging.getLogger(__name__) @@ -21,10 +23,25 @@ def is_q_cluster_running(**kwargs): clusters = Stat.get_all() - for cluster in clusters: - print("Cluster:", cluster) + if len(clusters) > 0: + return True - return len(clusters) > 0 + """ + Sometimes Stat.get_all() returns []. + In this case we have the 'heartbeat' task running every five minutes. + Check to see if we have a result within the last ten minutes + """ + + now = datetime.now() + past = now - timedelta(minutes=10) + + results = Success.objects.filter( + func='InvenTree.tasks.heartbeat', + started__gte=past + ) + + # If any results are returned, then the background worker is running! + return results.exists() def check_system_health(**kwargs): diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index 61b6885b03..985b9e24da 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -6,8 +6,6 @@ import json import requests import logging -from datetime import timedelta - from django.core.exceptions import AppRegistryNotReady @@ -37,6 +35,17 @@ def schedule_task(taskname, **kwargs): ) +def heartbeat(): + """ + Simple task which runs at 5 minute intervals, + so we can determine that the background worker + is actually running. + + (There is probably a less "hacky" way of achieving this) + """ + pass + + def delete_successful_tasks(): """ Delete successful task logs @@ -45,6 +54,7 @@ def delete_successful_tasks(): pass + def check_for_updates(): """ Check if there is an update for InvenTree @@ -52,7 +62,6 @@ def check_for_updates(): try: import common.models - import InvenTree.version except AppRegistryNotReady: return @@ -67,7 +76,7 @@ def check_for_updates(): tag = data.get('tag_name', None) if not tag: - logger.warning(f"'tag_name' missing from GitHub response") + logger.warning("'tag_name' missing from GitHub response") return match = re.match(r"^.*(\d+)\.(\d+)\.(\d+).*$", tag) diff --git a/InvenTree/templates/navbar.html b/InvenTree/templates/navbar.html index c52e87d753..acd71f0cd8 100644 --- a/InvenTree/templates/navbar.html +++ b/InvenTree/templates/navbar.html @@ -59,8 +59,10 @@ {% endif %}