Skip to content

Commit

Permalink
Add a background task to fetch qgis versions (#297)
Browse files Browse the repository at this point in the history
  • Loading branch information
dimasciput authored Jul 19, 2023
1 parent 0a86431 commit 6a7d849
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 0 deletions.
27 changes: 27 additions & 0 deletions qgis-app/plugins/tasks/update_qgis_versions.py
Original file line number Diff line number Diff line change
@@ -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()
94 changes: 94 additions & 0 deletions qgis-app/plugins/tests/test_task.py
Original file line number Diff line number Diff line change
@@ -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 = '<some xml content>'
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 = '<some xml content>'
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+')
37 changes: 37 additions & 0 deletions qgis-app/plugins/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -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'))
49 changes: 49 additions & 0 deletions qgis-app/plugins/utils.py
Original file line number Diff line number Diff line change
@@ -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
Binary file removed qgis-app/whoosh_index/MAIN_6wzk7090d2stn8v8.seg
Binary file not shown.
Binary file removed qgis-app/whoosh_index/_MAIN_1.toc
Binary file not shown.

0 comments on commit 6a7d849

Please sign in to comment.