Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add minimum required version to rdmo xml exports and check at import #1232

Open
wants to merge 3 commits into
base: 2.3.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rdmo/conditions/renderers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def render_document(self, xml, conditions):
xml.startElement('rdmo', {
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
'version': self.version,
'required': self.required,
'created': self.created
})
for condition in conditions:
Expand Down
5 changes: 5 additions & 0 deletions rdmo/core/renderers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import re
from io import StringIO

from django.conf import settings
from django.utils.encoding import smart_str
from django.utils.timezone import get_current_timezone, now
from django.utils.xmlutils import SimplerXMLGenerator
Expand Down Expand Up @@ -51,6 +52,10 @@ def render_document(self, xml, data):
def version(self):
return __version__

@property
def required(self):
return settings.EXPORT_MIN_REQUIRED_VERSION

@property
def created(self):
return now().astimezone(get_current_timezone()).isoformat()
2 changes: 2 additions & 0 deletions rdmo/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,8 @@

EXPORT_CONTENT_DISPOSITION = 'attachment'

EXPORT_MIN_REQUIRED_VERSION = '2.1.0'

PROJECT_TABLE_PAGE_SIZE = 20

PROJECT_VISIBILITY = True
Expand Down
7 changes: 7 additions & 0 deletions rdmo/core/tests/test_renderers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from django.conf import settings

from rdmo import __version__

from ..renderers import BaseXMLRenderer


Expand All @@ -7,6 +11,7 @@ def render_document(self, xml, data):
xml.startElement('rdmo', {
'xmlns:dc': "http://purl.org/dc/elements/1.1/",
'version': self.version,
'required': self.required,
'created': self.created
})
self.render_text_element(xml, 'text', {}, data['text'])
Expand All @@ -17,6 +22,8 @@ def test_render():
renderer = TestRenderer()
xml = renderer.render({'text': 'test'})
assert '<text>test</text>' in xml
assert f'version="{__version__}"' in xml
assert f'required="{settings.EXPORT_MIN_REQUIRED_VERSION}"' in xml


def test_render_ascii_code():
Expand Down
53 changes: 41 additions & 12 deletions rdmo/core/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,49 @@ def validate_root(root: Optional[xmlElement]) -> Tuple[bool, Optional[str]]:


def validate_and_get_xml_version_from_root(root: xmlElement) -> Tuple[Optional[Version], list]:
unparsed_root_version = root.attrib.get('version') or LEGACY_RDMO_XML_VERSION
root_version, rdmo_version = parse(unparsed_root_version), parse(RDMO_INSTANCE_VERSION)
if root_version > rdmo_version:
logger.info('Import failed version validation (%s > %s)', root_version, rdmo_version)
rdmo_version = parse(RDMO_INSTANCE_VERSION)

# Extract version attributes from the XML root
unparsed_required_version = root.attrib.get('required') # New required version field
unparsed_root_version = root.attrib.get('version') or LEGACY_RDMO_XML_VERSION # Fallback to legacy default

# Validate the 'required' attribute if it exists
if unparsed_required_version:
try:
required_version = parse(unparsed_required_version)
except ValueError:
logger.info('Import failed: Invalid "required" format in XML (%s)', unparsed_required_version)
errors = [_('The "required" attribute in this RDMO XML file is not a valid version.')]
return None, errors

if required_version > rdmo_version:
logger.info('Import failed: Required version (%s) > RDMO instance version (%s)', required_version,
rdmo_version)
errors = [
_('This RDMO XML file requires a newer RDMO version to be imported.'),
f'Required version: {required_version}, Current version: {rdmo_version}.'
]
return None, errors

# Fallback to validate the legacy 'version' field
try:
xml_version = parse(unparsed_root_version)

except ValueError:
logger.info('Import failed: Invalid "version" format in XML (%s)', unparsed_root_version)
errors = [_('The "version" attribute in this RDMO XML file is not a valid version.')]
return None, errors

# RDMO 1.x can not import XMLs from RDMO 2.x
if rdmo_version < parse('2.0.0') and xml_version >= parse('2.0.0'):
logger.info('Import failed: Backwards compatibility is not supported for RDMO versions < 2.0.0')
errors = [
_('This RDMO XML file does not have a valid version number.'),
f'XML Version ({root_version}) is greater than RDMO instance version {rdmo_version}'
_('This RDMO XML file was created with a newer version of RDMO and cannot be imported.'),
_('Backwards compatibility is not supported for RDMO versions lower than 2.0.0.')
]
return None, errors
return root_version, []

return xml_version, []

def validate_legacy_elements(elements: dict, root_version: Version) -> list:

Expand Down Expand Up @@ -117,13 +149,13 @@ def parse_xml_to_elements(xml_file=None) -> Tuple[OrderedDict, list]:
return OrderedDict(), errors

# step 3.1.1: validate the legacy elements
legacy_errors = validate_legacy_elements(elements, parse(root.attrib.get('version', LEGACY_RDMO_XML_VERSION)))
legacy_errors = validate_legacy_elements(elements, root_version)
if legacy_errors:
errors.extend(legacy_errors)
return OrderedDict(), errors

# step 4: convert elements from previous versions
elements = convert_elements(elements, parse(root.attrib.get('version', LEGACY_RDMO_XML_VERSION)))
elements = convert_elements(elements, root_version)

# step 5: order the elements and return
# ordering of elements is done in the import_elements function
Expand Down Expand Up @@ -232,9 +264,6 @@ def strip_ns(tag, ns_map):


def convert_elements(elements, version: Version):
if not isinstance(version, Version):
raise TypeError('Version should be of parsed version type.')

if version < parse('2.0.0'):
validate_pre_conversion_for_missing_key_in_legacy_elements(elements, version)
elements = convert_legacy_elements(elements)
Expand Down
1 change: 1 addition & 0 deletions rdmo/domain/renderers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def render_document(self, xml, attributes):
xml.startElement('rdmo', {
'xmlns:dc': "http://purl.org/dc/elements/1.1/",
'version': self.version,
'required': self.required,
'created': self.created
})
for attribute in attributes:
Expand Down
11 changes: 8 additions & 3 deletions rdmo/management/tests/helpers_xml.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
from packaging.version import parse

from rdmo import __version__
from rdmo.core.xml import parse_xml_to_elements, read_xml, resolve_file

current_version = parse(__version__)

xml_test_files = {
"xml/elements/catalogs.xml":
None,
"xml/elements/updated-and-changed/optionsets-1.xml":
None,
'file-does-not-exist.xml':
'This file does not exists',
'This field may not be blank.', # or 'This file does not exists'
"xml/error.xml":
"The content of the XML file does not consist of well-formed data or markup. XML Parsing Error: syntax error: line 1, column 0", # noqa: E501
"xml/project.xml":
"This XML does not contain RDMO content.",
'xml/error-version.xml':
'This RDMO XML file does not have a valid version number. XML Version (99.9.9) is greater',
'The "version" attribute in this RDMO XML file is not a valid version.',
'xml/error-version-required.xml':
f'This RDMO XML file requires a newer RDMO version to be imported. Required version: 99.9.9, Current version: {current_version}', # noqa: E501
'xml/elements/legacy/catalog-error-key.xml':
'XML Parsing Error: Missing legacy elements',
}

xml_error_files = {k: v for k,v in xml_test_files.items() if v is not None}
xml_error_files['file-does-not-exist.xml'] = 'This field may not be blank.'

def read_xml_and_parse_to_root_and_elements(file):
errors = []
Expand Down
2 changes: 2 additions & 0 deletions rdmo/management/tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ def test_import(db, settings, xml_file_path, error_message):
with pytest.raises(CommandError) as e:
call_command('import', xml_file, stdout=stdout, stderr=stderr)

if error_message == 'This field may not be blank.':
error_message = 'This file does not exists' # overwrite error message for cli import
assert str(e.value).startswith(error_message)
2 changes: 2 additions & 0 deletions rdmo/options/renderers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def render_document(self, xml, optionsets):
xml.startElement('rdmo', {
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
'version': self.version,
'required': self.required,
'created': self.created
})
for optionset in optionsets:
Expand All @@ -25,6 +26,7 @@ def render_document(self, xml, options):
xml.startElement('rdmo', {
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
'version': self.version,
'required': self.required,
'created': self.created
})
for option in options:
Expand Down
1 change: 1 addition & 0 deletions rdmo/projects/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def render_document(self, xml, project):
xml.startElement('project', {
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
'version': self.version,
'required': self.required,
'created': self.created
})
self.render_text_element(xml, 'title', {}, project['title'])
Expand Down
5 changes: 5 additions & 0 deletions rdmo/questions/renderers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def render_document(self, xml, catalogs):
xml.startElement('rdmo', {
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
'version': self.version,
'required': self.required,
'created': self.created
})
for catalog in catalogs:
Expand All @@ -35,6 +36,7 @@ def render_document(self, xml, sections):
xml.startElement('rdmo', {
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
'version': self.version,
'required': self.required,
'created': self.created
})
for section in sections:
Expand All @@ -49,6 +51,7 @@ def render_document(self, xml, pages):
xml.startElement('rdmo', {
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
'version': self.version,
'required': self.required,
'created': self.created
})
for page in pages:
Expand All @@ -63,6 +66,7 @@ def render_document(self, xml, questionsets):
xml.startElement('rdmo', {
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
'version': self.version,
'required': self.required,
'created': self.created
})
for questionset in questionsets:
Expand All @@ -77,6 +81,7 @@ def render_document(self, xml, questions):
xml.startElement('rdmo', {
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
'version': self.version,
'required': self.required,
'created': self.created
})
for question in questions:
Expand Down
1 change: 1 addition & 0 deletions rdmo/tasks/renderers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def render_document(self, xml, tasks):
xml.startElement('rdmo', {
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
'version': self.version,
'required': self.required,
'created': self.created
})
for task in tasks:
Expand Down
1 change: 1 addition & 0 deletions rdmo/views/renderers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def render_document(self, xml, views):
xml.startElement('rdmo', {
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
'version': self.version,
'required': self.required,
'created': self.created
})
for view in views:
Expand Down
3 changes: 3 additions & 0 deletions testing/xml/error-version-required.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<rdmo xmlns:dc="http://purl.org/dc/elements/1.1/" created="2023-04-20T09:39:10.351808+02:00" version="99.9.9" required="99.9.9">
</rdmo>
2 changes: 1 addition & 1 deletion testing/xml/error-version.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<rdmo xmlns:dc="http://purl.org/dc/elements/1.1/" created="2023-04-20T09:39:10.351808+02:00" version="99.9.9">
<rdmo xmlns:dc="http://purl.org/dc/elements/1.1/" created="2023-04-20T09:39:10.351808+02:00" version="error.version.0">
</rdmo>
Loading