diff --git a/qgis-app/plugins/tasks/update_qgis_versions.py b/qgis-app/plugins/tasks/update_qgis_versions.py new file mode 100644 index 00000000..890d836c --- /dev/null +++ b/qgis-app/plugins/tasks/update_qgis_versions.py @@ -0,0 +1,27 @@ +from celery import shared_task +from base.models.site_preferences import SitePreference +from plugins.utils import get_qgis_versions + + +@shared_task +def update_qgis_versions(): + """ + This background task fetches the QGIS versions from the GitHub QGIS releases + and then updates the current QGIS version in the database. + """ + site_preference = SitePreference.objects.first() + if not site_preference: + site_preference = SitePreference.objects.create() + + qgis_versions = get_qgis_versions() + stored_qgis_versions = site_preference.qgis_versions.split(',') + for qgis_version in qgis_versions: + if qgis_version not in stored_qgis_versions: + stored_qgis_versions.append(qgis_version) + stored_qgis_versions = list(filter(None, stored_qgis_versions)) + stored_qgis_versions = [tuple(map(int, v.split('.'))) for v in stored_qgis_versions] + stored_qgis_versions.sort(reverse=True) + stored_qgis_versions = ['.'.join(map(str, v)) for v in stored_qgis_versions] + + site_preference.qgis_versions = ','.join(stored_qgis_versions) + site_preference.save() diff --git a/qgis-app/plugins/tests/test_task.py b/qgis-app/plugins/tests/test_task.py new file mode 100644 index 00000000..7cf22545 --- /dev/null +++ b/qgis-app/plugins/tests/test_task.py @@ -0,0 +1,94 @@ +import os + +from django.test import TestCase, override_settings +from django.conf import settings + +from preferences import preferences +from unittest.mock import patch, MagicMock + +from base.models.site_preferences import SitePreference +from plugins.tasks.generate_plugins_xml import generate_plugins_xml +from plugins.tasks.update_qgis_versions import update_qgis_versions + + +class TestPluginTask(TestCase): + @patch.object(SitePreference.objects, 'first') + @patch.object(SitePreference.objects, 'create') + @patch('plugins.tasks.update_qgis_versions.get_qgis_versions') + def test_update_qgis_versions(self, mock_get_qgis_versions, mock_create, mock_first): + mock_create.return_value = MagicMock() + mock_get_qgis_versions.return_value = ['3.16', '3.11', '3.12'] + site_preference = MagicMock() + site_preference.qgis_versions = '3.16,3.17' + mock_first.return_value = site_preference + + update_qgis_versions() + + self.assertEqual(site_preference.qgis_versions, '3.17,3.16,3.12,3.11') + site_preference.save.assert_called_once() + + @patch.object(SitePreference.objects, 'first') + @patch.object(SitePreference.objects, 'create') + @patch('plugins.tasks.update_qgis_versions.get_qgis_versions') + def test_update_qgis_versions_no_site_preference(self, mock_get_qgis_versions, mock_create, mock_first): + mock_get_qgis_versions.return_value = ['3.16', '3.16', '3.16'] + mock_first.return_value = None + mock_create.return_value = MagicMock() + update_qgis_versions() + mock_create.assert_called_once() + + @override_settings(DEFAULT_PLUGINS_SITE='http://test_plugins_site') + @patch('requests.get') + @patch('os.path.exists', return_value=False) + @patch('os.mkdir') + @patch('builtins.open', new_callable=MagicMock) + def test_generate_plugins_xml(self, mock_open, mock_mkdir, mock_exists, mock_get): + # Given + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = '' + mock_get.return_value = mock_response + preferences.SitePreference.qgis_versions = '3.24,3.25' + + expected_folder_path = os.path.join(settings.MEDIA_ROOT, 'cached_xmls') + + # When + generate_plugins_xml() + + # Then + mock_mkdir.assert_called_once_with(expected_folder_path) + expected_calls = [ + ((f'{settings.DEFAULT_PLUGINS_SITE}/plugins/plugins_new.xml?qgis=3.24',),), + ((f'{settings.DEFAULT_PLUGINS_SITE}/plugins/plugins_new.xml?qgis=3.25',),) + ] + mock_get.assert_has_calls(expected_calls, any_order=True) + mock_open.assert_any_call(os.path.join(expected_folder_path, 'plugins_3.24.xml'), 'w+') + mock_open.assert_any_call(os.path.join(expected_folder_path, 'plugins_3.25.xml'), 'w+') + + @override_settings(DEFAULT_PLUGINS_SITE='http://test_plugins_site') + @patch('requests.get') + @patch('os.path.exists', return_value=False) + @patch('os.mkdir') + @patch('builtins.open', new_callable=MagicMock) + def test_generate_plugins_xml_with_custom_site(self, mock_open, mock_mkdir, mock_exists, mock_get): + # Given + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = '' + mock_get.return_value = mock_response + preferences.SitePreference.qgis_versions = '3.24,3.25' + + expected_folder_path = os.path.join(settings.MEDIA_ROOT, 'cached_xmls') + + # When + generate_plugins_xml('http://custom_plugins_site') + + # Then + mock_mkdir.assert_called_once_with(expected_folder_path) + expected_calls = [ + (('http://custom_plugins_site/plugins/plugins_new.xml?qgis=3.24',),), + (('http://custom_plugins_site/plugins/plugins_new.xml?qgis=3.25',),) + ] + mock_get.assert_has_calls(expected_calls, any_order=True) + mock_open.assert_any_call(os.path.join(expected_folder_path, 'plugins_3.24.xml'), 'w+') + mock_open.assert_any_call(os.path.join(expected_folder_path, 'plugins_3.25.xml'), 'w+') diff --git a/qgis-app/plugins/tests/test_utils.py b/qgis-app/plugins/tests/test_utils.py new file mode 100644 index 00000000..c41cd8a2 --- /dev/null +++ b/qgis-app/plugins/tests/test_utils.py @@ -0,0 +1,37 @@ +from unittest.mock import patch, Mock +from django.test import TestCase +from plugins.utils import ( + get_qgis_versions, + extract_version +) + + +class TestQGISGitHubReleases(TestCase): + + @patch('requests.get') + def test_get_qgis_versions(self, mock_get): + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = [ + {'tag_name': 'final_3_22_10', 'html_url': 'https://github.com/qgis/QGIS/releases/tag/final-3_22_10'}, + {'tag_name': 'beta_3_23_0', 'html_url': 'https://github.com/qgis/QGIS/releases/tag/beta-3_23_0'} + ] + mock_get.return_value = mock_response + + versions = get_qgis_versions() + self.assertIn('3.22', versions) + + @patch('requests.get') + def test_get_github_releases_failed_request(self, mock_get): + mock_response = Mock() + mock_response.status_code = 404 + mock_get.return_value = mock_response + + with self.assertRaises(Exception) as context: + get_qgis_versions() + self.assertTrue('Request failed' in str(context.exception)) + + def test_extract_version(self): + self.assertEqual(extract_version('final-3.22.10'), '3.22') + self.assertEqual(extract_version('beta-3.23.0'), '3.23') + self.assertIsNone(extract_version('invalid-tag')) diff --git a/qgis-app/plugins/utils.py b/qgis-app/plugins/utils.py new file mode 100644 index 00000000..e180e22b --- /dev/null +++ b/qgis-app/plugins/utils.py @@ -0,0 +1,49 @@ +import requests +import re + + +def extract_version(tag): + """ + Extracts the major and minor version from a given tag. + + The tag should be in the format of x.y.z where x, y, and z are + numbers representing major, minor, and patch versions respectively. + + Args: + tag (str): The version tag to be processed. + + Returns: + str: The major and minor version as x.y, or None if no match. + """ + match = re.search(r'(\d+\.\d+\.\d+)', tag) + if match: + version = match.group(1) + version_parts = version.split('.') + return '.'.join(version_parts[:-1]) + else: + return None + + +def get_qgis_versions(): + """ + Fetches all releases from the QGIS GitHub repository and extracts their + major and minor versions. + + Returns: + list: A list of unique major and minor versions of the releases. + + Raises: + Exception: If the request to the GitHub API fails. + """ + url = 'https://api.github.com/repos/qgis/QGIS/releases' + response = requests.get(url) + if response.status_code != 200: + raise Exception('Request failed') + releases = response.json() + all_versions = [] + for release in releases: + tag_name = release['tag_name'].replace('_', '.') + version = extract_version(tag_name) + if version not in all_versions: + all_versions.append(version) + return all_versions diff --git a/qgis-app/whoosh_index/MAIN_6wzk7090d2stn8v8.seg b/qgis-app/whoosh_index/MAIN_6wzk7090d2stn8v8.seg deleted file mode 100644 index 2d585eae..00000000 Binary files a/qgis-app/whoosh_index/MAIN_6wzk7090d2stn8v8.seg and /dev/null differ diff --git a/qgis-app/whoosh_index/_MAIN_1.toc b/qgis-app/whoosh_index/_MAIN_1.toc deleted file mode 100644 index 1d9c769d..00000000 Binary files a/qgis-app/whoosh_index/_MAIN_1.toc and /dev/null differ