admin: add export/import for categories (#59628) #558
|
@ -1,6 +1,10 @@
|
|||
import pytest
|
||||
import io
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from wcs.categories import Category
|
||||
import pytest
|
||||
from webtest import Upload
|
||||
|
||||
from wcs.categories import CardDefCategory, Category
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
|
||||
|
@ -241,3 +245,61 @@ def test_categories_edit_roles(pub):
|
|||
|
||||
resp = app.get('/backoffice/forms/categories/1/edit')
|
||||
assert resp.form['export_roles$element0'].value == role_a.id
|
||||
|
||||
|
||||
def test_categories_export(pub):
|
||||
Category.wipe()
|
||||
category = Category(name='foobar')
|
||||
category.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/forms/categories/1/')
|
||||
resp = resp.click('Export')
|
||||
xml_export = resp.text
|
||||
|
||||
xml_export_fd = io.StringIO(xml_export)
|
||||
imported_category = Category.import_from_xml(xml_export_fd)
|
||||
assert imported_category.name == category.name
|
||||
|
||||
|
||||
def test_categories_import(pub):
|
||||
app = login(get_app(pub))
|
||||
|
||||
Category.wipe()
|
||||
category = Category(name='foobar')
|
||||
category.store()
|
||||
category_xml = ET.tostring(category.export_to_xml(include_id=True))
|
||||
Category.wipe()
|
||||
CardDefCategory.wipe()
|
||||
|
||||
# import to wrong category kind
|
||||
resp = app.get('/backoffice/cards/categories/')
|
||||
resp = resp.click(href='import')
|
||||
resp.forms[0]['file'] = Upload('category.wcs', category_xml)
|
||||
resp = resp.forms[0].submit()
|
||||
assert 'Invalid File' in resp.text
|
||||
assert Category.count() == 0
|
||||
assert CardDefCategory.count() == 0
|
||||
|
||||
# successful import
|
||||
resp = app.get('/backoffice/forms/categories/')
|
||||
resp = resp.click(href='import')
|
||||
resp.forms[0]['file'] = Upload('category.wcs', category_xml)
|
||||
resp = resp.forms[0].submit()
|
||||
assert Category.count() == 1
|
||||
assert {x.slug for x in Category.select()} == {'foobar'}
|
||||
|
||||
# repeat import -> slug change
|
||||
resp = app.get('/backoffice/forms/categories/')
|
||||
resp = resp.click(href='import')
|
||||
resp.forms[0]['file'] = Upload('category.wcs', category_xml)
|
||||
resp = resp.forms[0].submit()
|
||||
assert Category.count() == 2
|
||||
assert {x.slug for x in Category.select()} == {'foobar', 'foobar-2'}
|
||||
|
||||
# cancel
|
||||
resp = app.get('/backoffice/forms/categories/')
|
||||
resp = resp.click(href='import')
|
||||
resp.forms[0]['file'] = Upload('category.wcs', category_xml)
|
||||
resp = resp.forms[0].submit('cancel')
|
||||
assert Category.count() == 2
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from quixote import get_publisher, get_request, get_response, redirect
|
||||
from quixote import get_publisher, get_request, get_response, get_session, redirect
|
||||
from quixote.directory import Directory
|
||||
from quixote.html import TemplateIO, htmltext
|
||||
|
||||
|
@ -38,7 +38,15 @@ from wcs.formdef import FormDef
|
|||
from wcs.mail_templates import MailTemplate
|
||||
from wcs.qommon import _, misc, template
|
||||
from wcs.qommon.errors import AccessForbiddenError, TraversalError
|
||||
from wcs.qommon.form import Form, HtmlWidget, SingleSelectWidget, StringWidget, WidgetList, WysiwygTextWidget
|
||||
from wcs.qommon.form import (
|
||||
FileWidget,
|
||||
Form,
|
||||
HtmlWidget,
|
||||
SingleSelectWidget,
|
||||
StringWidget,
|
||||
WidgetList,
|
||||
WysiwygTextWidget,
|
||||
)
|
||||
from wcs.workflows import Workflow
|
||||
|
||||
|
||||
|
@ -198,6 +206,7 @@ class CategoryPage(Directory):
|
|||
_q_exports = [
|
||||
'',
|
||||
'edit',
|
||||
'export',
|
||||
'delete',
|
||||
'description',
|
||||
('history', 'snapshots_dir'),
|
||||
|
@ -301,6 +310,13 @@ class CategoryPage(Directory):
|
|||
r += form.render()
|
||||
return r.getvalue()
|
||||
|
||||
def export(self):
|
||||
return misc.xml_response(
|
||||
self.category,
|
||||
filename=f'category-{self.category.slug}.wcs',
|
||||
content_type='text/xml',
|
||||
)
|
||||
|
||||
|
||||
class CardDefCategoryPage(CategoryPage):
|
||||
category_class = CardDefCategory
|
||||
|
@ -358,7 +374,7 @@ class DataSourceCategoryPage(CategoryPage):
|
|||
|
||||
|
||||
class CategoriesDirectory(Directory):
|
||||
_q_exports = ['', 'new', 'update_order', ('application', 'applications_dir')]
|
||||
_q_exports = ['', 'new', ('import', 'p_import'), 'update_order', ('application', 'applications_dir')]
|
||||
|
||||
base_section = 'forms'
|
||||
category_class = Category
|
||||
|
@ -425,6 +441,51 @@ class CategoriesDirectory(Directory):
|
|||
r += form.render()
|
||||
return r.getvalue()
|
||||
|
||||
def p_import(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
import_title = _('Import category')
|
||||
|
||||
form.add(FileWidget, 'file', title=_('File'), required=True)
|
||||
form.add_submit('submit', import_title)
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
|
||||
if form.get_submit() == 'cancel':
|
||||
return redirect('.')
|
||||
|
||||
if form.is_submitted() and not form.has_errors():
|
||||
try:
|
||||
return self.import_submit(form)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
get_response().breadcrumb.append(('import', _('Import')))
|
||||
get_response().set_title(import_title)
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<h2>%s</h2>') % import_title
|
||||
r += htmltext('<p>%s</p>') % _('You can install a new category by uploading a file.')
|
||||
r += form.render()
|
||||
return r.getvalue()
|
||||
|
||||
def import_submit(self, form):
|
||||
fp = form.get_widget('file').parse().fp
|
||||
|
||||
try:
|
||||
category = self.category_class.import_from_xml(fp)
|
||||
get_session().message = ('info', _('This category has been successfully imported.'))
|
||||
except ValueError as e:
|
||||
form.set_error('file', _('Invalid File'))
|
||||
raise e
|
||||
|
||||
try:
|
||||
# check slug unicity
|
||||
self.category_class.get_by_slug(category.slug)
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
category.url_name = None # a new one will be set in .store()
|
||||
category.store()
|
||||
return redirect('%s/' % category.id)
|
||||
|
||||
def _q_lookup(self, component):
|
||||
return self.category_page_class(component)
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
{% block sidebar-content %}
|
||||
<h3>{% trans "Actions" %}</h3>
|
||||
<a class="button button-paragraph" rel="popup" href="new">{% trans "New Category" %}</a>
|
||||
<a class="button button-paragraph" rel="popup" href="import">{% trans "Import" %}</a>
|
||||
|
||||
{% include 'wcs/backoffice/includes/applications.html' %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
|
||||
{% block appbar-actions %}
|
||||
{% if not category.is_readonly %}
|
||||
<a href="delete" rel="popup">{% trans "Delete" %}</a>
|
||||
<a class="extra-actions-menu-opener"></a>
|
||||
<ul class="extra-actions-menu">
|
||||
<li><a href="export">{% trans "Export" %}</a></li>
|
||||
<li><a href="delete" rel="popup">{% trans "Delete" %}</a></li>
|
||||
</ul>
|
||||
<a href="edit">{% trans "Edit" %}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
Loading…
Reference in New Issue