application: increment version number on bundle generation (#88373)
gitea/hobo/pipeline/head This commit looks good
Details
gitea/hobo/pipeline/head This commit looks good
Details
This commit is contained in:
parent
159f93a783
commit
d123e136ff
|
@ -15,15 +15,62 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django import forms
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from hobo.applications.models import Application, Version
|
||||
|
||||
|
||||
class GenerateForm(forms.Form):
|
||||
number = forms.CharField(label=_('Version Number'), max_length=100)
|
||||
number = forms.RegexField(
|
||||
label=_('Version Number'),
|
||||
max_length=100,
|
||||
regex=r'^\d+\.\d+$',
|
||||
help_text=_('The version number consists of two numbers separated by a dot. Example: 1.0'),
|
||||
)
|
||||
notes = forms.CharField(label=_('Version notes'), widget=forms.Textarea, required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.latest_version = kwargs.pop('latest_version')
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.latest_version:
|
||||
try:
|
||||
old_version = [int(n) for n in self.latest_version.number.split('.')]
|
||||
except ValueError:
|
||||
old_version = None
|
||||
if old_version:
|
||||
self.initial['number'] = '.'.join(str(n) for n in old_version[:2])
|
||||
self.initial['notes'] = self.latest_version.notes
|
||||
|
||||
def clean_number(self):
|
||||
number = self.cleaned_data['number']
|
||||
if not self.latest_version:
|
||||
return number
|
||||
try:
|
||||
old_number = [int(n) for n in self.latest_version.number.split('.')]
|
||||
except ValueError:
|
||||
return number
|
||||
new_number = [int(n) for n in self.cleaned_data['number'].split('.')]
|
||||
if old_number[:2] > new_number:
|
||||
raise forms.ValidationError(
|
||||
_('The version number must be equal to or greater than the previous one.')
|
||||
)
|
||||
return number
|
||||
|
||||
def get_cleaned_number(self):
|
||||
number = [int(n) for n in self.cleaned_data['number'].split('.')]
|
||||
number.append(int(now().strftime('%Y%m%d')))
|
||||
if not self.latest_version:
|
||||
return '%s.%s.%s.0' % tuple(number)
|
||||
try:
|
||||
old_number = [int(n) for n in self.latest_version.number.split('.')]
|
||||
except ValueError:
|
||||
return '%s.%s.%s.0' % tuple(number)
|
||||
if number != old_number[:3]:
|
||||
return '%s.%s.%s.0' % tuple(number)
|
||||
last_part = old_number[3]
|
||||
return '%s.%s.%s.%s' % (*number, last_part + 1)
|
||||
|
||||
|
||||
class InstallForm(forms.Form):
|
||||
bundle = forms.FileField(label=_('Application'))
|
||||
|
|
|
@ -390,23 +390,17 @@ class GenerateView(FormView):
|
|||
form_class = GenerateForm
|
||||
template_name = 'hobo/applications/generate.html'
|
||||
|
||||
def get_initial(self):
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
self.app = get_object_or_404(Application, slug=self.kwargs['app_slug'], editable=True)
|
||||
version = self.app.version_set.order_by('last_update_timestamp').last()
|
||||
if version:
|
||||
self.initial['number'] = version.number
|
||||
self.initial['notes'] = version.notes
|
||||
return super().get_initial()
|
||||
kwargs['latest_version'] = self.app.version_set.order_by('last_update_timestamp').last()
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
app = self.app
|
||||
|
||||
latest_version = app.version_set.order_by('last_update_timestamp').last()
|
||||
if latest_version and latest_version.number == form.cleaned_data['number']:
|
||||
version = latest_version
|
||||
else:
|
||||
version = Version(application=app)
|
||||
version.number = form.cleaned_data['number']
|
||||
version = Version(application=app)
|
||||
version.number = form.get_cleaned_number()
|
||||
version.notes = form.cleaned_data['notes']
|
||||
version.save()
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import base64
|
||||
import copy
|
||||
import datetime
|
||||
import io
|
||||
import json
|
||||
import random
|
||||
|
@ -9,6 +10,7 @@ import tarfile
|
|||
import httmock
|
||||
import pytest
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.timezone import now
|
||||
from httmock import HTTMock
|
||||
from pyquery import PyQuery
|
||||
from webtest import Upload
|
||||
|
@ -323,7 +325,7 @@ def test_create_application(app, admin_user, settings, analyze):
|
|||
resp = resp.form.submit().follow()
|
||||
assert 'Test Card' in resp.text
|
||||
version = Version.objects.latest('pk')
|
||||
assert version.number == '1.0'
|
||||
assert version.number == '1.0.%s.0' % now().strftime('%Y%m%d')
|
||||
assert version.notes == 'Foo bar blah.'
|
||||
resp = resp.click('Download')
|
||||
assert resp.content_type == 'application/x-tar'
|
||||
|
@ -332,44 +334,21 @@ def test_create_application(app, admin_user, settings, analyze):
|
|||
assert b'<carddef/>' in resp.content
|
||||
assert b'"icon": null' in resp.content
|
||||
assert b'"documentation_url": "http://foo.bar"' in resp.content
|
||||
assert b'"version_number": "1.0"' in resp.content
|
||||
assert b'"version_number": "1.0.%s.0"' % now().strftime('%Y%m%d').encode() in resp.content
|
||||
assert b'"version_notes": "Foo bar blah."' in resp.content
|
||||
assert b'"visible": false' in resp.content
|
||||
|
||||
resp = app.get('/applications/manifest/test/versions/')
|
||||
versions = [e.text() for e in resp.pyquery('h3').items()]
|
||||
assert versions.count('1.0') == 1
|
||||
assert versions.count('1.0.%s.0' % now().strftime('%Y%m%d')) == 1
|
||||
assert resp.text.count('Creating application bundle') == 1
|
||||
assert 'Compare' not in resp
|
||||
resp = resp.click(href='/applications/manifest/test/download/%s/' % version.pk)
|
||||
assert resp.content_type == 'application/x-tar'
|
||||
assert resp.headers['Content-Disposition'] == 'attachment; filename="test-1.0.tar"'
|
||||
assert b'"version_number": "1.0"' in resp.content
|
||||
|
||||
# generate again without changing version number
|
||||
resp = app.get('/applications/manifest/test/generate/')
|
||||
assert resp.form['number'].value == '1.0' # last one
|
||||
assert resp.form['notes'].value == 'Foo bar blah.' # last one
|
||||
resp.form['notes'] = 'Foo bar blahha.'
|
||||
resp = resp.form.submit().follow()
|
||||
same_version = Version.objects.latest('pk')
|
||||
assert same_version.number == '1.0'
|
||||
assert same_version.notes == 'Foo bar blahha.'
|
||||
assert same_version.pk == version.pk
|
||||
resp = resp.click('Download')
|
||||
assert resp.content_type == 'application/x-tar'
|
||||
assert b'"version_number": "1.0"' in resp.content
|
||||
assert b'"version_notes": "Foo bar blahha."' in resp.content
|
||||
|
||||
resp = app.get('/applications/manifest/test/versions/')
|
||||
versions = [e.text() for e in resp.pyquery('h3').items()]
|
||||
assert versions.count('1.0') == 1
|
||||
assert resp.text.count('Creating application bundle') == 2
|
||||
assert 'Compare' not in resp
|
||||
resp = resp.click(href='/applications/manifest/test/download/%s/' % same_version.pk)
|
||||
assert resp.content_type == 'application/x-tar'
|
||||
assert resp.headers['Content-Disposition'] == 'attachment; filename="test-1.0.tar"'
|
||||
assert b'"version_number": "1.0"' in resp.content
|
||||
assert resp.headers[
|
||||
'Content-Disposition'
|
||||
] == 'attachment; filename="test-1.0.%s.0.tar"' % now().strftime('%Y%m%d')
|
||||
assert b'"version_number": "1.0.%s.0"' % now().strftime('%Y%m%d').encode() in resp.content
|
||||
|
||||
# add an icon
|
||||
resp = app.get('/applications/manifest/test/metadata/')
|
||||
|
@ -408,68 +387,88 @@ def test_create_application(app, admin_user, settings, analyze):
|
|||
assert b'<carddef/>' in resp.content
|
||||
assert b'"icon": "foo' in resp.content
|
||||
assert b'"documentation_url": ""' in resp.content
|
||||
assert b'"version_number": "2.0"' in resp.content
|
||||
assert b'"version_number": "2.0.%s.0"' % now().strftime('%Y%m%d').encode() in resp.content
|
||||
assert b'"version_notes": "Foo bar blah. But with an icon."' in resp.content
|
||||
assert b'"visible": true' in resp.content
|
||||
version = Version.objects.latest('pk')
|
||||
assert version.number == '2.0'
|
||||
assert version.number == '2.0.%s.0' % now().strftime('%Y%m%d')
|
||||
assert version.notes == 'Foo bar blah. But with an icon.'
|
||||
assert version.pk != same_version.pk
|
||||
|
||||
resp = app.get('/applications/manifest/test/versions/')
|
||||
versions = [e.text() for e in resp.pyquery('h3').items()]
|
||||
assert versions.count('1.0') == 1
|
||||
assert versions.count('2.0') == 1
|
||||
assert versions.count('1.0.%s.0' % now().strftime('%Y%m%d')) == 1
|
||||
assert versions.count('2.0.%s.0' % now().strftime('%Y%m%d')) == 1
|
||||
assert resp.text.count('Compare') == 2
|
||||
assert resp.text.count('Creating application bundle') == 3
|
||||
resp = resp.click(href='/applications/manifest/test/download/%s/' % same_version.pk)
|
||||
assert resp.text.count('Creating application bundle') == 2
|
||||
resp = resp.click(
|
||||
href='/applications/manifest/test/download/%s/' % Version.objects.get(number__startswith='1.0').pk
|
||||
)
|
||||
assert resp.content_type == 'application/x-tar'
|
||||
assert resp.headers['Content-Disposition'] == 'attachment; filename="test-1.0.tar"'
|
||||
assert b'"version_number": "1.0"' in resp.content
|
||||
assert resp.headers[
|
||||
'Content-Disposition'
|
||||
] == 'attachment; filename="test-1.0.%s.0.tar"' % now().strftime('%Y%m%d')
|
||||
assert b'"version_number": "1.0.%s.0"' % now().strftime('%Y%m%d').encode() in resp.content
|
||||
resp = app.get('/applications/manifest/test/versions/')
|
||||
resp = resp.click(href='/applications/manifest/test/download/%s/' % version.pk)
|
||||
resp = resp.click(
|
||||
href='/applications/manifest/test/download/%s/' % Version.objects.get(number__startswith='2.0').pk
|
||||
)
|
||||
assert resp.content_type == 'application/x-tar'
|
||||
assert resp.headers['Content-Disposition'] == 'attachment; filename="test-2.0.tar"'
|
||||
assert b'"version_number": "2.0"' in resp.content
|
||||
assert resp.headers[
|
||||
'Content-Disposition'
|
||||
] == 'attachment; filename="test-2.0.%s.0.tar"' % now().strftime('%Y%m%d')
|
||||
assert b'"version_number": "2.0.%s.0"' % now().strftime('%Y%m%d').encode() in resp.content
|
||||
|
||||
resp = app.get('/applications/manifest/test/generate/')
|
||||
assert resp.form['number'].value == '2.0' # last one
|
||||
assert resp.form['notes'].value == 'Foo bar blah. But with an icon.' # last one
|
||||
resp.form['number'] = '1.0' # old number
|
||||
resp = app.get('/applications/manifest/test/')
|
||||
resp = resp.click('Generate application bundle')
|
||||
resp.form['number'] = '2.0'
|
||||
resp.form['notes'] = 'Foo bar blah. But with an icon.'
|
||||
resp = resp.form.submit().follow()
|
||||
new_version = Version.objects.latest('pk')
|
||||
assert new_version.number == '1.0'
|
||||
assert new_version.notes == 'Foo bar blah. But with an icon.'
|
||||
assert new_version.pk != version.pk # new version created
|
||||
resp = resp.click('Download')
|
||||
assert resp.content_type == 'application/x-tar'
|
||||
# uncompressed tar, primitive check of contents
|
||||
assert b'<formdef/>' in resp.content
|
||||
assert b'<carddef/>' in resp.content
|
||||
assert b'"icon": "foo' in resp.content
|
||||
assert b'"documentation_url": ""' in resp.content
|
||||
assert b'"version_number": "2.0.%s.1"' % now().strftime('%Y%m%d').encode() in resp.content
|
||||
assert b'"version_notes": "Foo bar blah. But with an icon."' in resp.content
|
||||
assert b'"visible": true' in resp.content
|
||||
version = Version.objects.latest('pk')
|
||||
assert version.number == '2.0.%s.1' % now().strftime('%Y%m%d')
|
||||
assert version.notes == 'Foo bar blah. But with an icon.'
|
||||
|
||||
resp = app.get('/applications/manifest/test/versions/')
|
||||
versions = [e.text() for e in resp.pyquery('h3').items()]
|
||||
assert versions.count('1.0') == 2
|
||||
assert versions.count('2.0') == 1
|
||||
assert resp.text.count('Creating application bundle') == 4
|
||||
assert resp.text.count('Creating application bundle') == 3
|
||||
assert resp.text.count('Compare') == 3
|
||||
resp = resp.click('Compare', index=0)
|
||||
assert 'Compare version 1.0 to:' in resp
|
||||
assert 'Compare version 2.0.%s.1 to:' % now().strftime('%Y%m%d') in resp
|
||||
version1 = Version.objects.get(number__startswith='1.0')
|
||||
version2 = Version.objects.filter(number__startswith='2.0').latest('-pk')
|
||||
version3 = Version.objects.filter(number__startswith='2.0').latest('pk')
|
||||
assert resp.form['version'].options == [
|
||||
(str(version.pk), False, '2.0'),
|
||||
(str(same_version.pk), False, '1.0'),
|
||||
(str(version2.pk), False, version2.number),
|
||||
(str(version1.pk), False, version1.number),
|
||||
]
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith(
|
||||
'/applications/manifest/test/version/compare/?version1=%s&version2=%s'
|
||||
% (new_version.pk, version.pk)
|
||||
% (version3.pk, version2.pk)
|
||||
)
|
||||
resp = resp.follow()
|
||||
assert 'Version 2.0' in resp
|
||||
assert 'Version 1.0' in resp
|
||||
assert 'Version %s' % version3.number in resp
|
||||
assert 'Version %s' % version2.number in resp
|
||||
resp = resp.click('Compare elements definitions')
|
||||
assert 'Version 2.0' in resp
|
||||
assert 'Version 1.0' in resp
|
||||
assert 'Version %s' % version3.number in resp
|
||||
assert 'Version %s' % version2.number in resp
|
||||
assert (
|
||||
'https://wcs.example.invalid/api/export-import/forms/test-form/redirect/?application=test&version1=2.0&version2=1.0&compare'
|
||||
'https://wcs.example.invalid/api/export-import/forms/test-form/redirect/?application=test&version1=%s&version2=%s&compare'
|
||||
% (version2.number, version3.number)
|
||||
in resp
|
||||
)
|
||||
assert (
|
||||
'https://wcs.example.invalid/api/export-import/cards/test-card/redirect/?application=test&version1=2.0&version2=1.0&compare'
|
||||
'https://wcs.example.invalid/api/export-import/cards/test-card/redirect/?application=test&version1=%s&version2=%s&compare'
|
||||
% (version2.number, version3.number)
|
||||
in resp
|
||||
)
|
||||
assert 'Test Form <span class="extra-info">- Form</span>' in resp
|
||||
|
@ -479,16 +478,18 @@ def test_create_application(app, admin_user, settings, analyze):
|
|||
Element.objects.filter(type='cards').delete()
|
||||
resp = app.get(
|
||||
'/applications/manifest/test/version/compare/?version1=%s&version2=%s&mode=elements'
|
||||
% (new_version.pk, version.pk)
|
||||
% (version3.pk, version2.pk)
|
||||
)
|
||||
assert 'Version 2.0' in resp
|
||||
assert 'Version 1.0' in resp
|
||||
assert 'Version %s' % version3.number in resp
|
||||
assert 'Version %s' % version2.number in resp
|
||||
assert (
|
||||
'https://wcs.example.invalid/api/export-import/forms/test-form/redirect/?application=test&version1=2.0&version2=1.0&compare'
|
||||
'https://wcs.example.invalid/api/export-import/forms/test-form/redirect/?application=test&version1=%s&version2=%s&compare'
|
||||
% (version2.number, version3.number)
|
||||
in resp
|
||||
)
|
||||
assert (
|
||||
'https://wcs.example.invalid/api/export-import/cards/test-card/redirect/?application=test&version1=2.0&version2=1.0&compare'
|
||||
'https://wcs.example.invalid/api/export-import/cards/test-card/redirect/?application=test&version1=%s&version2=%s&compare'
|
||||
% (version2.number, version3.number)
|
||||
not in resp
|
||||
)
|
||||
assert 'Test Form <span class="extra-info">- Form</span>' in resp
|
||||
|
@ -521,6 +522,123 @@ def test_create_application(app, admin_user, settings, analyze):
|
|||
app.get('/applications/manifest/test/delete/%s/' % application.relation_set.first().pk, status=404)
|
||||
|
||||
|
||||
def test_application_version_number(app, admin_user, settings):
|
||||
Wcs.objects.create(base_url='https://wcs.example.invalid', slug='foobar', title='Foobar')
|
||||
|
||||
settings.KNOWN_SERVICES = {
|
||||
'wcs': {
|
||||
'blah': {
|
||||
# simulate an instance from another collectivity
|
||||
'title': 'Unknown',
|
||||
'url': 'https://unknown.example.invalid/',
|
||||
'orig': 'example.org',
|
||||
'secret': 'xxx',
|
||||
},
|
||||
'foobar': {
|
||||
'title': 'Foobar',
|
||||
'url': 'https://wcs.example.invalid/',
|
||||
'orig': 'example.org',
|
||||
'secret': 'xxx',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
login(app)
|
||||
|
||||
application = Application.objects.create(name='Test', slug='test')
|
||||
resp = app.get('/applications/manifest/test/generate/')
|
||||
resp.form['number'] = 'X'
|
||||
resp = resp.form.submit()
|
||||
assert resp.context['form'].errors == {'number': ['Enter a valid value.']}
|
||||
resp.form['number'] = 'X.0'
|
||||
resp = resp.form.submit()
|
||||
assert resp.context['form'].errors == {'number': ['Enter a valid value.']}
|
||||
resp.form['number'] = '1.1.1'
|
||||
resp = resp.form.submit()
|
||||
assert resp.context['form'].errors == {'number': ['Enter a valid value.']}
|
||||
resp.form['number'] = '1.1'
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp.form.submit()
|
||||
last_version = Version.objects.latest('pk')
|
||||
assert last_version.application == application
|
||||
assert last_version.number == '1.1.%s.0' % now().strftime('%Y%m%d')
|
||||
|
||||
resp = app.get('/applications/manifest/test/generate/')
|
||||
assert resp.form['number'].value == '1.1'
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp.form.submit()
|
||||
last_version = Version.objects.latest('pk')
|
||||
assert last_version.application == application
|
||||
assert last_version.number == '1.1.%s.1' % now().strftime('%Y%m%d')
|
||||
|
||||
resp = app.get('/applications/manifest/test/generate/')
|
||||
assert resp.form['number'].value == '1.1'
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp.form.submit()
|
||||
last_version = Version.objects.latest('pk')
|
||||
assert last_version.application == application
|
||||
assert last_version.number == '1.1.%s.2' % now().strftime('%Y%m%d')
|
||||
|
||||
resp = app.get('/applications/manifest/test/generate/')
|
||||
resp.form['number'] = '1.0'
|
||||
resp = resp.form.submit()
|
||||
assert resp.context['form'].errors == {
|
||||
'number': ['The version number must be equal to or greater than the previous one.']
|
||||
}
|
||||
|
||||
last_version.number = '1.1.%s.2' % (now() - datetime.timedelta(days=1)).strftime('%Y%m%d')
|
||||
last_version.save()
|
||||
resp = app.get('/applications/manifest/test/generate/')
|
||||
assert resp.form['number'].value == '1.1'
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp.form.submit()
|
||||
last_version = Version.objects.latest('pk')
|
||||
assert last_version.application == application
|
||||
assert last_version.number == '1.1.%s.0' % now().strftime('%Y%m%d')
|
||||
|
||||
last_version.number = 'garbage'
|
||||
last_version.save()
|
||||
resp = app.get('/applications/manifest/test/generate/')
|
||||
assert resp.form['number'].value == ''
|
||||
resp.form['number'] = '1.1'
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp.form.submit()
|
||||
last_version = Version.objects.latest('pk')
|
||||
assert last_version.application == application
|
||||
assert last_version.number == '1.1.%s.0' % now().strftime('%Y%m%d')
|
||||
|
||||
last_version.number = '1'
|
||||
last_version.save()
|
||||
resp = app.get('/applications/manifest/test/generate/')
|
||||
assert resp.form['number'].value == '1'
|
||||
resp.form['number'] = '1.1'
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp.form.submit()
|
||||
last_version = Version.objects.latest('pk')
|
||||
assert last_version.application == application
|
||||
assert last_version.number == '1.1.%s.0' % now().strftime('%Y%m%d')
|
||||
|
||||
last_version.number = '1.1'
|
||||
last_version.save()
|
||||
resp = app.get('/applications/manifest/test/generate/')
|
||||
assert resp.form['number'].value == '1.1'
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp.form.submit()
|
||||
last_version = Version.objects.latest('pk')
|
||||
assert last_version.application == application
|
||||
assert last_version.number == '1.1.%s.0' % now().strftime('%Y%m%d')
|
||||
|
||||
last_version.number = '1.1.%s.1.1' % now().strftime('%Y%m%d')
|
||||
last_version.save()
|
||||
resp = app.get('/applications/manifest/test/generate/')
|
||||
assert resp.form['number'].value == '1.1'
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp.form.submit()
|
||||
last_version = Version.objects.latest('pk')
|
||||
assert last_version.application == application
|
||||
assert last_version.number == '1.1.%s.2' % now().strftime('%Y%m%d')
|
||||
|
||||
|
||||
def test_manifest_ordering(app, admin_user, settings):
|
||||
Wcs.objects.create(base_url='https://wcs.example.invalid', slug='foobar', title='Foobar')
|
||||
|
||||
|
|
Loading…
Reference in New Issue