écraser avec un import pour les blocs de champs (#60722) #1213
|
@ -257,6 +257,55 @@ def test_block_delete(pub):
|
||||||
assert 'This block is still used' in resp
|
assert 'This block is still used' in resp
|
||||||
|
|
||||||
|
|
||||||
|
def test_block_export_overwrite(pub):
|
||||||
|
create_superuser(pub)
|
||||||
|
BlockDef.wipe()
|
||||||
|
block = BlockDef()
|
||||||
|
block.name = 'foobar'
|
||||||
|
block.fields = [fields.StringField(id='123', required=True, label='Test')]
|
||||||
|
block.store()
|
||||||
|
|
||||||
|
app = login(get_app(pub))
|
||||||
|
resp = app.get('/backoffice/forms/blocks/%s/' % block.id)
|
||||||
|
resp = resp.click(href=re.compile('^export$'))
|
||||||
|
xml_export = resp.text
|
||||||
|
|
||||||
|
block.slug = 'new-slug'
|
||||||
|
block.name = 'New foobar'
|
||||||
|
block.fields = [fields.StringField(id='123', required=True, label='Test bebore overwrite')]
|
||||||
|
block.store()
|
||||||
|
|
||||||
|
resp = app.get('/backoffice/forms/blocks/%s/' % block.id)
|
||||||
|
resp = resp.click('Overwrite')
|
||||||
|
resp = resp.form.submit('cancel').follow()
|
||||||
|
resp = resp.click('Overwrite')
|
||||||
|
resp = resp.form.submit()
|
||||||
|
assert 'There were errors processing your form.' in resp
|
||||||
|
|
||||||
|
resp.form['file'] = Upload('block', xml_export.encode('utf-8'))
|
||||||
|
resp = resp.form.submit()
|
||||||
|
resp = resp.follow()
|
||||||
|
assert BlockDef.count() == 1
|
||||||
|
|
||||||
|
block.refresh_from_storage()
|
||||||
|
assert block.fields[0].label == 'Test'
|
||||||
|
assert block.name == 'foobar'
|
||||||
|
assert block.slug == 'new-slug' # not overwritten
|
||||||
|
|
||||||
|
# unknown reference
|
||||||
|
block.fields = [fields.StringField(id='1', data_source={'type': 'foobar'})]
|
||||||
|
block.store()
|
||||||
|
resp = app.get('/backoffice/forms/blocks/%s/' % block.id)
|
||||||
|
resp = resp.click(href=re.compile('^export$'))
|
||||||
|
xml_export = resp.text
|
||||||
|
resp = app.get('/backoffice/forms/blocks/%s/' % block.id)
|
||||||
|
resp = resp.click('Overwrite')
|
||||||
|
resp.form['file'] = Upload('block', xml_export.encode('utf-8'))
|
||||||
|
resp = resp.form.submit()
|
||||||
|
assert 'Invalid File (Unknown referenced objects)' in resp
|
||||||
|
assert '<ul><li>Unknown datasources: foobar</li></ul>' in resp
|
||||||
|
|
||||||
|
|
||||||
def test_block_edit_duplicate_delete_field(pub):
|
def test_block_edit_duplicate_delete_field(pub):
|
||||||
create_superuser(pub)
|
create_superuser(pub)
|
||||||
BlockDef.wipe()
|
BlockDef.wipe()
|
||||||
|
@ -453,14 +502,14 @@ def test_block_edit_field_warnings(pub):
|
||||||
blockdef.store()
|
blockdef.store()
|
||||||
resp = app.get('/backoffice/forms/blocks/%s/' % blockdef.id)
|
resp = app.get('/backoffice/forms/blocks/%s/' % blockdef.id)
|
||||||
assert 'more than 30 fields' not in resp.text
|
assert 'more than 30 fields' not in resp.text
|
||||||
assert '<div id="new-field"><h3>New Field</h3>' in resp.text
|
assert resp.pyquery('#new-field')
|
||||||
assert resp.pyquery('#fields-list a[title="Duplicate"]').length
|
assert resp.pyquery('#fields-list a[title="Duplicate"]').length
|
||||||
|
|
||||||
blockdef.fields.extend([fields.StringField(id='%d' % i, label='field %d' % i) for i in range(21, 51)])
|
blockdef.fields.extend([fields.StringField(id='%d' % i, label='field %d' % i) for i in range(21, 51)])
|
||||||
blockdef.store()
|
blockdef.store()
|
||||||
resp = app.get('/backoffice/forms/blocks/%s/' % blockdef.id)
|
resp = app.get('/backoffice/forms/blocks/%s/' % blockdef.id)
|
||||||
assert 'This block of fields contains 60 fields.' in resp.text
|
assert 'This block of fields contains 60 fields.' in resp.text
|
||||||
assert '<div id="new-field"><h3>New Field</h3>' not in resp.text
|
assert not resp.pyquery('#new-field')
|
||||||
assert not resp.pyquery('#fields-list a[title="Duplicate"]').length
|
assert not resp.pyquery('#fields-list a[title="Duplicate"]').length
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1035,7 +1035,7 @@ def test_card_edit_field_warnings(pub):
|
||||||
resp = app.get('/backoffice/cards/%s/fields/' % carddef.id)
|
resp = app.get('/backoffice/cards/%s/fields/' % carddef.id)
|
||||||
assert 'more than 200 fields' not in resp.text
|
assert 'more than 200 fields' not in resp.text
|
||||||
assert 'first field should be of type "page"' in resp.text
|
assert 'first field should be of type "page"' in resp.text
|
||||||
assert '<div id="new-field"><h3>New Field</h3>' in resp.text
|
assert resp.pyquery('#new-field')
|
||||||
|
|
||||||
carddef.fields.extend([fields.StringField(id='%d' % i, label='field %d' % i) for i in range(10, 210)])
|
carddef.fields.extend([fields.StringField(id='%d' % i, label='field %d' % i) for i in range(10, 210)])
|
||||||
carddef.store()
|
carddef.store()
|
||||||
|
@ -1049,7 +1049,7 @@ def test_card_edit_field_warnings(pub):
|
||||||
resp = app.get('/backoffice/cards/%s/fields/' % carddef.id)
|
resp = app.get('/backoffice/cards/%s/fields/' % carddef.id)
|
||||||
assert 'This card model contains 410 fields.' in resp.text
|
assert 'This card model contains 410 fields.' in resp.text
|
||||||
assert 'first field should be of type "page"' in resp.text
|
assert 'first field should be of type "page"' in resp.text
|
||||||
assert '<div id="new-field"><h3>New Field</h3>' not in resp.text
|
assert not resp.pyquery('#new-field')
|
||||||
assert '>Duplicate<' not in resp.text
|
assert '>Duplicate<' not in resp.text
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3600,7 +3600,7 @@ def test_form_edit_field_warnings(pub):
|
||||||
resp = app.get('/backoffice/forms/%s/fields/' % formdef.id)
|
resp = app.get('/backoffice/forms/%s/fields/' % formdef.id)
|
||||||
assert 'more than 200 fields' not in resp.text
|
assert 'more than 200 fields' not in resp.text
|
||||||
assert 'first field should be of type "page"' in resp.text
|
assert 'first field should be of type "page"' in resp.text
|
||||||
assert '<div id="new-field"><h3>New Field</h3>' in resp.text
|
assert resp.pyquery('#new-field')
|
||||||
|
|
||||||
formdef.fields.extend([fields.StringField(id='%d' % i, label='field %d' % i) for i in range(10, 210)])
|
formdef.fields.extend([fields.StringField(id='%d' % i, label='field %d' % i) for i in range(10, 210)])
|
||||||
formdef.store()
|
formdef.store()
|
||||||
|
@ -3615,7 +3615,7 @@ def test_form_edit_field_warnings(pub):
|
||||||
assert 'This form contains 410 fields.' in resp.text
|
assert 'This form contains 410 fields.' in resp.text
|
||||||
assert 'no new fields can be added.' in resp.text
|
assert 'no new fields can be added.' in resp.text
|
||||||
assert 'first field should be of type "page"' in resp.text
|
assert 'first field should be of type "page"' in resp.text
|
||||||
assert '<div id="new-field"><h3>New Field</h3>' not in resp.text
|
assert not resp.pyquery('#new-field')
|
||||||
assert '>Duplicate<' not in resp.text
|
assert '>Duplicate<' not in resp.text
|
||||||
assert resp.pyquery('aside .errornotice')
|
assert resp.pyquery('aside .errornotice')
|
||||||
assert not resp.pyquery('aside form[action=new]')
|
assert not resp.pyquery('aside form[action=new]')
|
||||||
|
@ -3628,7 +3628,7 @@ def test_form_edit_field_warnings(pub):
|
||||||
|
|
||||||
resp = app.get('/backoffice/forms/%s/fields/' % formdef.id)
|
resp = app.get('/backoffice/forms/%s/fields/' % formdef.id)
|
||||||
assert 'no new fields should be added.' in resp.text
|
assert 'no new fields should be added.' in resp.text
|
||||||
assert '<div id="new-field"><h3>New Field</h3>' in resp.text
|
assert resp.pyquery('#new-field')
|
||||||
assert '>Duplicate<' in resp.text
|
assert '>Duplicate<' in resp.text
|
||||||
assert not resp.pyquery('aside .errornotice')
|
assert not resp.pyquery('aside .errornotice')
|
||||||
assert resp.pyquery('aside form[action=new]')
|
assert resp.pyquery('aside form[action=new]')
|
||||||
|
|
|
@ -54,6 +54,7 @@ class BlockDirectory(FieldsDirectory):
|
||||||
'inspect',
|
'inspect',
|
||||||
'duplicate',
|
'duplicate',
|
||||||
('history', 'snapshots_dir'),
|
('history', 'snapshots_dir'),
|
||||||
|
'overwrite',
|
||||||
]
|
]
|
||||||
field_def_page_class = BlockFieldDefPage
|
field_def_page_class = BlockFieldDefPage
|
||||||
blacklisted_types = ['page', 'table', 'table-select', 'tablerows', 'ranked-items', 'blocks', 'computed']
|
blacklisted_types = ['page', 'table', 'table-select', 'tablerows', 'ranked-items', 'blocks', 'computed']
|
||||||
|
@ -106,6 +107,14 @@ class BlockDirectory(FieldsDirectory):
|
||||||
r = TemplateIO(html=True)
|
r = TemplateIO(html=True)
|
||||||
r += htmltext('<div id="appbar">')
|
r += htmltext('<div id="appbar">')
|
||||||
r += htmltext('<h2>%s</h2>') % self.objectdef.name
|
r += htmltext('<h2>%s</h2>') % self.objectdef.name
|
||||||
|
r += htmltext('<span class="actions">')
|
||||||
|
r += htmltext('<a class="extra-actions-menu-opener"></a>')
|
||||||
|
r += htmltext('<ul class="extra-actions-menu">')
|
||||||
|
|||||||
|
r += htmltext('<li><a href="export">%s</a></li>') % _('Export')
|
||||||
|
r += htmltext('<li><a href="delete" rel="popup">%s</a></li>') % _('Delete')
|
||||||
|
r += htmltext('</ul>')
|
||||||
|
r += htmltext('<a href="settings" rel="popup" role="button">%s</a>') % _('Settings')
|
||||||
|
r += htmltext('</span>')
|
||||||
r += htmltext('</div>')
|
r += htmltext('</div>')
|
||||||
r += utils.last_modification_block(obj=self.objectdef)
|
r += utils.last_modification_block(obj=self.objectdef)
|
||||||
r += get_session().display_message()
|
r += get_session().display_message()
|
||||||
|
@ -132,17 +141,25 @@ class BlockDirectory(FieldsDirectory):
|
||||||
|
|
||||||
def get_new_field_form_sidebar(self, page_id):
|
def get_new_field_form_sidebar(self, page_id):
|
||||||
r = TemplateIO(html=True)
|
r = TemplateIO(html=True)
|
||||||
r += htmltext('<ul id="sidebar-actions">')
|
|
||||||
r += htmltext('<li><a href="delete" rel="popup">%s</a></li>') % _('Delete')
|
|
||||||
r += htmltext('<li><a href="duplicate" rel="popup">%s</a></li>') % _('Duplicate')
|
|
||||||
r += htmltext('<li><a href="export">%s</a></li>') % _('Export')
|
|
||||||
if get_publisher().snapshot_class:
|
|
||||||
r += htmltext('<li><a rel="popup" href="history/save">%s</a></li>') % _('Save snapshot')
|
|
||||||
r += htmltext('<li><a href="history/">%s</a></li>') % _('History')
|
|
||||||
r += htmltext('<li><a href="inspect">%s</a></li>') % _('Inspector')
|
|
||||||
r += htmltext('<li><a href="settings" rel="popup">%s</a></li>') % _('Settings')
|
|
||||||
r += htmltext('</ul>')
|
|
||||||
r += super().get_new_field_form_sidebar(page_id=page_id)
|
r += super().get_new_field_form_sidebar(page_id=page_id)
|
||||||
|
r += htmltext('<h3>%s</h3>') % _('Actions')
|
||||||
|
r += htmltext('<ul class="sidebar--buttons">')
|
||||||
|
r += htmltext('<li><a class="button button-paragraph" href="duplicate" rel="popup">%s</a>') % _(
|
||||||
fpeters
commented
Et les autres actions dans la barre latérale. Et les autres actions dans la barre latérale.
|
|||||||
|
'Duplicate'
|
||||||
|
)
|
||||||
|
if get_publisher().snapshot_class:
|
||||||
|
r += htmltext('<li><a class="button button-paragraph" href="history/save">%s</a>') % _(
|
||||||
|
'Save snapshot'
|
||||||
|
)
|
||||||
|
r += htmltext('<li><a class="button button-paragraph" rel="popup" href="overwrite">%s</a>') % _(
|
||||||
|
'Overwrite'
|
||||||
fpeters
commented
Dont la nouvelle action pour écraser. Dont la nouvelle action pour écraser.
|
|||||||
|
)
|
||||||
|
r += htmltext('</ul>')
|
||||||
|
r += htmltext('<h3>%s</h3>') % _('Navigation')
|
||||||
|
r += htmltext('<ul class="sidebar--buttons">')
|
||||||
|
r += htmltext('<li><a class="button button-paragraph" href="history/">%s</a></li>') % _('History')
|
||||||
|
r += htmltext('<li><a class="button button-paragraph" href="inspect">%s</a></li>') % _('Inspector')
|
||||||
|
r += htmltext('</ul>')
|
||||||
return r.getvalue()
|
return r.getvalue()
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
|
@ -209,6 +226,41 @@ class BlockDirectory(FieldsDirectory):
|
||||||
content_type='application/x-wcs-form',
|
content_type='application/x-wcs-form',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def overwrite(self):
|
||||||
|
form = Form(enctype='multipart/form-data')
|
||||||
|
form.widgets.append(
|
||||||
|
HtmlWidget(
|
||||||
|
'<div class="warningnotice"><p>%s</p></div>'
|
||||||
|
% _('Field data will be lost if overwriting with an incompatible block.')
|
||||||
fpeters
commented
Un peu du mal à trouver une phrase adéquate ici; en pratique on utiliser des uuid depuis le début pour les champs des blocs, et le stockage se fait dans des colonnes jsonb, donc un revert de l'écrasement rendrait accès aux données. Mais c'est quand même bien de décourager. Un peu du mal à trouver une phrase adéquate ici; en pratique on utiliser des uuid depuis le début pour les champs des blocs, et le stockage se fait dans des colonnes jsonb, donc un revert de l'écrasement rendrait accès aux données. Mais c'est quand même bien de décourager.
|
|||||||
|
)
|
||||||
|
)
|
||||||
|
form.add(FileWidget, 'file', title=_('File'), required=True)
|
||||||
|
form.add_submit('submit', _('Submit'))
|
||||||
|
form.add_submit('cancel', _('Cancel'))
|
||||||
|
if form.get_widget('cancel').parse():
|
||||||
|
return redirect('.')
|
||||||
|
|
||||||
|
if form.is_submitted() and not form.has_errors():
|
||||||
|
try:
|
||||||
|
return self.overwrite_submit(form)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
get_response().breadcrumb.append(('overwrite', _('Overwrite')))
|
||||||
|
get_response().set_title(title=_('Overwrite'))
|
||||||
|
r = TemplateIO(html=True)
|
||||||
|
r += htmltext('<h2>%s</h2>') % _('Overwrite')
|
||||||
|
r += form.render()
|
||||||
|
return r.getvalue()
|
||||||
|
|
||||||
|
def overwrite_submit(self, form):
|
||||||
|
blockdef = BlocksDirectory.import_blockdef(form)
|
||||||
|
self.objectdef.name = blockdef.name
|
||||||
|
self.objectdef.digest_template = blockdef.digest_template
|
||||||
|
self.objectdef.fields = blockdef.fields
|
||||||
|
self.objectdef.store(comment=_('Overwritten'))
|
||||||
|
return redirect('.')
|
||||||
|
|
||||||
def settings(self):
|
def settings(self):
|
||||||
get_response().breadcrumb.append(('settings', _('Settings')))
|
get_response().breadcrumb.append(('settings', _('Settings')))
|
||||||
form = Form()
|
form = Form()
|
||||||
|
@ -409,7 +461,8 @@ class BlocksDirectory(Directory):
|
||||||
r += form.render()
|
r += form.render()
|
||||||
return r.getvalue()
|
return r.getvalue()
|
||||||
|
|
||||||
def import_submit(self, form):
|
@classmethod
|
||||||
|
def import_blockdef(cls, form):
|
||||||
fp = form.get_widget('file').parse().fp
|
fp = form.get_widget('file').parse().fp
|
||||||
|
|
||||||
error, reason = False, None
|
error, reason = False, None
|
||||||
|
@ -433,6 +486,10 @@ class BlocksDirectory(Directory):
|
||||||
form.set_error('file', msg)
|
form.set_error('file', msg)
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
|
|
||||||
|
return blockdef
|
||||||
|
|
||||||
|
def import_submit(self, form):
|
||||||
|
blockdef = self.import_blockdef(form)
|
||||||
initial_blockdef_name = blockdef.name
|
initial_blockdef_name = blockdef.name
|
||||||
blockdef_names = [x.name for x in BlockDef.select()]
|
blockdef_names = [x.name for x in BlockDef.select()]
|
||||||
copy_no = 1
|
copy_no = 1
|
||||||
|
|
|
@ -527,8 +527,8 @@ class FieldsDirectory(Directory):
|
||||||
)
|
)
|
||||||
return r.getvalue()
|
return r.getvalue()
|
||||||
|
|
||||||
r += htmltext('<div id="new-field">')
|
|
||||||
r += htmltext('<h3>%s</h3>') % _('New Field')
|
r += htmltext('<h3>%s</h3>') % _('New Field')
|
||||||
|
r += htmltext('<div id="new-field">')
|
||||||
get_request().form = None # ignore the eventual ?page=x
|
get_request().form = None # ignore the eventual ?page=x
|
||||||
form = self.get_new_field_form(page_id)
|
form = self.get_new_field_form(page_id)
|
||||||
r += form.render()
|
r += form.render()
|
||||||
|
|
|
@ -222,13 +222,8 @@ div#new-action, div#new-trigger, div#new-field {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div#new-field {
|
form#import-fields {
|
||||||
margin: 2em 0 4px 0;
|
margin-top: 1em;
|
||||||
padding: 5px 5px;
|
|
||||||
fpeters
commented
Petit ajustement stylistique pour que le titre "Nouveau champ" apparaisse comme les titres Actions/Navigation/Applications. Petit ajustement stylistique pour que le titre "Nouveau champ" apparaisse comme les titres Actions/Navigation/Applications.
|
|||||||
}
|
|
||||||
|
|
||||||
div#new-field form {
|
|
||||||
margin-bottom: 2em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
aside#sidebar div.news h3,
|
aside#sidebar div.news h3,
|
||||||
|
|
Loading…
Reference in New Issue
Premier commit pour reprendre la barre latérale, en suivant ce qui a été fait pour l'écran d'un formulaire, quelques actions dans le menu kebab.