applification: avoir un lien entre les objets importés et l'application hobo (#74372) #102
|
@ -57,6 +57,7 @@ def test_data_sources_from_carddefs(pub):
|
|||
create_superuser(pub)
|
||||
CardDef.wipe()
|
||||
pub.custom_view_class.wipe()
|
||||
NamedDataSource.wipe()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/settings/data-sources/')
|
||||
|
@ -80,10 +81,15 @@ def test_data_sources_from_carddefs(pub):
|
|||
resp = app.get('/backoffice/settings/data-sources/')
|
||||
assert 'Data Sources from Card Models' in resp.text
|
||||
assert 'There are no data sources from card models.' not in resp.text
|
||||
assert '<li><a href="http://example.net/backoffice/data/foo/">foo</a></li>' in resp.text
|
||||
assert resp.pyquery('.section .objects-list li:first-child a').text() == 'foo'
|
||||
assert (
|
||||
'<li><a href="http://example.net/backoffice/data/foo/datasource-card-view/">foo - datasource card view</a></li>'
|
||||
in resp.text
|
||||
resp.pyquery('.section .objects-list li:first-child a').attr['href']
|
||||
== 'http://example.net/backoffice/data/foo/'
|
||||
)
|
||||
assert resp.pyquery('.section .objects-list li:last-child a').text() == 'foo - datasource card view'
|
||||
assert (
|
||||
resp.pyquery('.section .objects-list li:last-child a').attr['href']
|
||||
== 'http://example.net/backoffice/data/foo/datasource-card-view/'
|
||||
)
|
||||
|
||||
|
||||
|
@ -106,6 +112,8 @@ def test_data_sources_agenda_without_chrono(pub):
|
|||
def test_data_sources_agenda(pub, chrono_url):
|
||||
create_superuser(pub)
|
||||
NamedDataSource.wipe()
|
||||
CardDef.wipe()
|
||||
pub.custom_view_class.wipe()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/settings/data-sources/')
|
||||
|
@ -125,21 +133,22 @@ def test_data_sources_agenda(pub, chrono_url):
|
|||
resp = app.get('/backoffice/settings/data-sources/')
|
||||
assert 'Agendas' in resp.text
|
||||
assert 'There are no agendas.' not in resp.text
|
||||
assert '<li><a href="%s/">foobar (foobar)</a></li>' % data_source.id in resp.text
|
||||
assert resp.pyquery('.section .objects-list li:first-child a').text() == 'foobar (foobar)'
|
||||
assert resp.pyquery('.section .objects-list li:first-child a').attr['href'] == data_source.get_admin_url()
|
||||
|
||||
data_source.external_status = 'not-found'
|
||||
data_source.store()
|
||||
resp = app.get('/backoffice/settings/data-sources/')
|
||||
assert (
|
||||
'<li><a href="%s/">foobar (foobar) - <span class="extra-info">not found</span></a></li>'
|
||||
% data_source.id
|
||||
in resp.text
|
||||
)
|
||||
assert resp.pyquery('.section .objects-list li:first-child a').text() == 'foobar (foobar) - not found'
|
||||
assert resp.pyquery('.section .objects-list li:first-child a').attr['href'] == data_source.get_admin_url()
|
||||
assert resp.pyquery('.section .objects-list li:first-child a span.extra-info').text() == 'not found'
|
||||
|
||||
|
||||
def test_data_sources_users(pub):
|
||||
create_superuser(pub)
|
||||
NamedDataSource.wipe()
|
||||
CardDef.wipe()
|
||||
pub.custom_view_class.wipe()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/settings/data-sources/')
|
||||
|
@ -153,7 +162,8 @@ def test_data_sources_users(pub):
|
|||
data_source.store()
|
||||
resp = app.get('/backoffice/settings/data-sources/')
|
||||
assert 'There are no users data sources defined.' not in resp
|
||||
assert '<li><a href="%s/">foobar (foobar)</a></li>' % data_source.id in resp
|
||||
assert resp.pyquery('.section .objects-list li:first-child a').text() == 'foobar (foobar)'
|
||||
assert resp.pyquery('.section .objects-list li:first-child a').attr['href'] == data_source.get_admin_url()
|
||||
|
||||
|
||||
def test_data_sources_new(pub):
|
||||
|
|
|
@ -59,7 +59,7 @@ def test_workflows_default(pub):
|
|||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/workflows/')
|
||||
assert 'Default' in resp.text
|
||||
resp = resp.click(href=r'^_default/')
|
||||
resp = resp.click(href=r'/backoffice/workflows/_default/$')
|
||||
assert 'Just Submitted' in resp.text
|
||||
assert 'This is the default workflow' in resp.text
|
||||
# makes sure it cannot be edited
|
||||
|
@ -219,9 +219,9 @@ def test_workflows_category(pub):
|
|||
|
||||
resp = app.get('/backoffice/workflows/')
|
||||
assert [x.attrib['href'] for x in resp.pyquery('.single-links a')] == [
|
||||
'_default/',
|
||||
'_carddef_default/',
|
||||
'1/',
|
||||
'http://example.net/backoffice/workflows/_default/',
|
||||
'http://example.net/backoffice/workflows/_carddef_default/',
|
||||
'http://example.net/backoffice/workflows/1/',
|
||||
]
|
||||
assert 'Uncategorised' not in resp.text
|
||||
|
||||
|
@ -235,9 +235,9 @@ def test_workflows_category(pub):
|
|||
# a category is defined -> an implicit "Uncategorised" section is displayed.
|
||||
resp = app.get('/backoffice/workflows/')
|
||||
assert [x.attrib['href'] for x in resp.pyquery('.single-links a')] == [
|
||||
'_default/',
|
||||
'_carddef_default/',
|
||||
'1/',
|
||||
'http://example.net/backoffice/workflows/_default/',
|
||||
'http://example.net/backoffice/workflows/_carddef_default/',
|
||||
'http://example.net/backoffice/workflows/1/',
|
||||
]
|
||||
assert 'Uncategorised' in resp.text
|
||||
|
||||
|
@ -257,9 +257,9 @@ def test_workflows_category(pub):
|
|||
resp = app.get('/backoffice/workflows/')
|
||||
assert '<h2>a new category' in resp.text
|
||||
assert [x.attrib['href'] for x in resp.pyquery('.single-links a')] == [
|
||||
'_default/',
|
||||
'_carddef_default/',
|
||||
'1/',
|
||||
'http://example.net/backoffice/workflows/_default/',
|
||||
'http://example.net/backoffice/workflows/_carddef_default/',
|
||||
'http://example.net/backoffice/workflows/1/',
|
||||
]
|
||||
assert 'Uncategorised' not in resp.text
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import xml.etree.ElementTree as ET
|
|||
|
||||
import pytest
|
||||
|
||||
from wcs.applications import Application, ApplicationElement
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.categories import (
|
||||
|
@ -22,6 +23,7 @@ from wcs.data_sources import NamedDataSource
|
|||
from wcs.fields import BlockField, CommentField, ComputedField, PageField, StringField
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.mail_templates import MailTemplate
|
||||
from wcs.sql import Equal
|
||||
from wcs.wf.form import WorkflowFormFieldsFormDef
|
||||
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef, WorkflowVariablesFieldsFormDef
|
||||
from wcs.wscalls import NamedWsCall
|
||||
|
@ -41,6 +43,8 @@ coucou = 1234
|
|||
'''
|
||||
)
|
||||
|
||||
Application.wipe()
|
||||
ApplicationElement.wipe()
|
||||
Category.wipe()
|
||||
FormDef.wipe()
|
||||
CardDefCategory.wipe()
|
||||
|
@ -56,6 +60,7 @@ coucou = 1234
|
|||
DataSourceCategory.wipe()
|
||||
NamedDataSource.wipe()
|
||||
NamedWsCall.wipe()
|
||||
pub.custom_view_class.wipe()
|
||||
|
||||
return pub
|
||||
|
||||
|
@ -481,6 +486,12 @@ def test_export_import_redirect_url(pub):
|
|||
mail_template_category = MailTemplateCategory(name='Test')
|
||||
mail_template_category.store()
|
||||
|
||||
comment_template = CommentTemplate(name='Test')
|
||||
comment_template.store()
|
||||
|
||||
comment_template_category = CommentTemplateCategory(name='Test')
|
||||
comment_template_category.store()
|
||||
|
||||
elements = [
|
||||
('forms', '/backoffice/forms/%s/' % formdef.id),
|
||||
('cards', '/backoffice/cards/%s/' % carddef.id),
|
||||
|
@ -497,6 +508,11 @@ def test_export_import_redirect_url(pub):
|
|||
'mail-templates-categories',
|
||||
'/backoffice/workflows/mail-templates/categories/%s/' % mail_template_category.id,
|
||||
),
|
||||
('comment-templates', '/backoffice/workflows/comment-templates/%s/' % comment_template.id),
|
||||
(
|
||||
'comment-templates-categories',
|
||||
'/backoffice/workflows/comment-templates/categories/%s/' % comment_template_category.id,
|
||||
),
|
||||
]
|
||||
for object_type, obj_url in elements:
|
||||
resp = get_app(pub).get(sign_uri('/api/export-import/%s/' % object_type))
|
||||
|
@ -521,7 +537,11 @@ def create_bundle(elements, *args):
|
|||
manifest_json = {
|
||||
'application': 'Test',
|
||||
'slug': 'test',
|
||||
'description': '',
|
||||
'icon': 'foo.png',
|
||||
'description': 'Foo Bar',
|
||||
'documentation_url': 'http://foo.bar',
|
||||
'version_number': '42.0',
|
||||
'version_notes': 'foo bar blah',
|
||||
'elements': elements,
|
||||
}
|
||||
manifest_fd = io.BytesIO(json.dumps(manifest_json, indent=2).encode())
|
||||
|
@ -529,6 +549,13 @@ def create_bundle(elements, *args):
|
|||
tarinfo.size = len(manifest_fd.getvalue())
|
||||
tar.addfile(tarinfo, fileobj=manifest_fd)
|
||||
|
||||
icon_fd = io.BytesIO(
|
||||
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='
|
||||
)
|
||||
tarinfo = tarfile.TarInfo('foo.png')
|
||||
tarinfo.size = len(icon_fd.getvalue())
|
||||
tar.addfile(tarinfo, fileobj=icon_fd)
|
||||
|
||||
for path, obj in args:
|
||||
tarinfo = tarfile.TarInfo(path)
|
||||
if hasattr(obj, 'export_for_application'):
|
||||
|
@ -603,6 +630,12 @@ def test_export_import_bundle_import(pub):
|
|||
mail_template.category = mail_template_category
|
||||
mail_template.store()
|
||||
|
||||
comment_template_category = CommentTemplateCategory(name='Test')
|
||||
comment_template_category.store()
|
||||
comment_template = CommentTemplate(name='Test')
|
||||
comment_template.category = comment_template_category
|
||||
comment_template.store()
|
||||
|
||||
wscall = NamedWsCall(name='Test')
|
||||
wscall.store()
|
||||
|
||||
|
@ -619,6 +652,8 @@ def test_export_import_bundle_import(pub):
|
|||
{'type': 'workflows', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'mail-templates-categories', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'mail-templates', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'comment-templates-categories', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'comment-templates', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'data-sources-categories', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'data-sources', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'wscalls', 'slug': 'test', 'name': 'test'},
|
||||
|
@ -635,6 +670,8 @@ def test_export_import_bundle_import(pub):
|
|||
('data-sources/test', data_source),
|
||||
('mail-templates-categories/test', mail_template_category),
|
||||
('mail-templates/test', mail_template),
|
||||
('comment-templates-categories/test', comment_template_category),
|
||||
('comment-templates/test', comment_template),
|
||||
('roles/test', role),
|
||||
('wscalls/test', wscall),
|
||||
)
|
||||
|
@ -648,6 +685,8 @@ def test_export_import_bundle_import(pub):
|
|||
Workflow.wipe()
|
||||
MailTemplateCategory.wipe()
|
||||
MailTemplate.wipe()
|
||||
CommentTemplateCategory.wipe()
|
||||
CommentTemplate.wipe()
|
||||
DataSourceCategory.wipe()
|
||||
NamedDataSource.wipe()
|
||||
pub.role_class.wipe()
|
||||
|
@ -664,7 +703,7 @@ def test_export_import_bundle_import(pub):
|
|||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'completed'
|
||||
assert resp.json['data']['completion_status'] == '17/17 (100%)'
|
||||
assert resp.json['data']['completion_status'] == '19/19 (100%)'
|
||||
|
||||
assert Category.count() == 1
|
||||
assert FormDef.count() == 1
|
||||
|
@ -683,11 +722,37 @@ def test_export_import_bundle_import(pub):
|
|||
assert MailTemplateCategory.count() == 1
|
||||
assert MailTemplate.count() == 1
|
||||
assert MailTemplate.select()[0].category_id == MailTemplateCategory.select()[0].id
|
||||
assert CommentTemplateCategory.count() == 1
|
||||
assert CommentTemplate.count() == 1
|
||||
assert CommentTemplate.select()[0].category_id == CommentTemplateCategory.select()[0].id
|
||||
assert DataSourceCategory.count() == 1
|
||||
assert NamedDataSource.count() == 1
|
||||
assert NamedDataSource.select()[0].category_id == DataSourceCategory.select()[0].id
|
||||
assert NamedWsCall.count() == 1
|
||||
assert pub.custom_view_class().count() == 1
|
||||
assert Application.count() == 1
|
||||
application = Application.select()[0]
|
||||
assert application.slug == 'test'
|
||||
assert application.name == 'Test'
|
||||
assert application.description == 'Foo Bar'
|
||||
assert application.documentation_url == 'http://foo.bar'
|
||||
assert application.version_number == '42.0'
|
||||
assert application.version_notes == 'foo bar blah'
|
||||
assert application.icon.base_filename == 'foo.png'
|
||||
assert application.editable is False
|
||||
assert ApplicationElement.count() == 15
|
||||
|
||||
# create some links to elements not present in manifest: they should be unlinked
|
||||
element1 = ApplicationElement()
|
||||
element1.application_id = application.id
|
||||
element1.object_type = 'foobar'
|
||||
element1.object_id = '42'
|
||||
element1.store()
|
||||
element2 = ApplicationElement()
|
||||
element2.application_id = application.id
|
||||
element2.object_type = 'foobarblah'
|
||||
element2.object_id = '35'
|
||||
element2.store()
|
||||
|
||||
# run new import to check it doesn't duplicate objects
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), bundle)
|
||||
|
@ -705,10 +770,34 @@ def test_export_import_bundle_import(pub):
|
|||
assert Workflow.count() == 1
|
||||
assert MailTemplateCategory.count() == 1
|
||||
assert MailTemplate.count() == 1
|
||||
assert CommentTemplateCategory.count() == 1
|
||||
assert CommentTemplate.count() == 1
|
||||
assert DataSourceCategory.count() == 1
|
||||
assert NamedDataSource.count() == 1
|
||||
assert pub.custom_view_class().count() == 1
|
||||
assert NamedWsCall.count() == 1
|
||||
assert Application.count() == 1
|
||||
assert ApplicationElement.count() == 15
|
||||
assert (
|
||||
ApplicationElement.select(
|
||||
[
|
||||
Equal('application_id', application.id),
|
||||
Equal('object_type', element1.object_type),
|
||||
Equal('object_id', element1.object_id),
|
||||
]
|
||||
)
|
||||
== []
|
||||
)
|
||||
assert (
|
||||
ApplicationElement.select(
|
||||
[
|
||||
Equal('application_id', application.id),
|
||||
Equal('object_type', element2.object_type),
|
||||
Equal('object_id', element2.object_id),
|
||||
]
|
||||
)
|
||||
== []
|
||||
)
|
||||
|
||||
# change immutable attributes and check they are not reset
|
||||
formdef = FormDef.select()[0]
|
||||
|
@ -756,3 +845,273 @@ def test_export_import_formdef_do_not_overwrite_table_name(pub):
|
|||
formdef = FormDef.select()[0]
|
||||
assert formdef.table_name == 'formdata_%s_test2' % formdef.id
|
||||
assert formdef.data_class().count() == 1
|
||||
|
||||
|
||||
def test_export_import_bundle_declare(pub):
|
||||
workflow_category = WorkflowCategory(name='test')
|
||||
workflow_category.store()
|
||||
|
||||
workflow = Workflow(name='test')
|
||||
workflow.store()
|
||||
|
||||
block_category = BlockCategory(name='test')
|
||||
block_category.store()
|
||||
|
||||
block = BlockDef(name='test')
|
||||
block.store()
|
||||
|
||||
role = pub.role_class(name='test')
|
||||
role.store()
|
||||
|
||||
category = Category(name='Test')
|
||||
category.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'Test'
|
||||
formdef.store()
|
||||
|
||||
card_category = CardDefCategory(name='Test')
|
||||
card_category.store()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'Test'
|
||||
carddef.store()
|
||||
|
||||
ds_category = DataSourceCategory(name='Test')
|
||||
ds_category.store()
|
||||
data_source = NamedDataSource(name='Test')
|
||||
data_source.store()
|
||||
|
||||
mail_template_category = MailTemplateCategory(name='Test')
|
||||
mail_template_category.store()
|
||||
mail_template = MailTemplate(name='Test')
|
||||
mail_template.store()
|
||||
|
||||
comment_template_category = CommentTemplateCategory(name='Test')
|
||||
comment_template_category.store()
|
||||
comment_template = CommentTemplate(name='Test')
|
||||
comment_template.store()
|
||||
|
||||
wscall = NamedWsCall(name='Test')
|
||||
wscall.store()
|
||||
|
||||
bundle = create_bundle(
|
||||
[
|
||||
{'type': 'forms-categories', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'forms', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'cards-categories', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'cards', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'blocks-categories', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'blocks', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'roles', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'workflows-categories', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'workflows', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'mail-templates-categories', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'mail-templates', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'comment-templates-categories', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'comment-templates', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'data-sources-categories', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'data-sources', 'slug': 'test', 'name': 'test'},
|
||||
{'type': 'wscalls', 'slug': 'test', 'name': 'test'},
|
||||
],
|
||||
('forms-categories/test', category),
|
||||
('forms/test', formdef),
|
||||
('cards-categories/test', card_category),
|
||||
('cards/test', carddef),
|
||||
('blocks-categories/test', block_category),
|
||||
('blocks/test', block),
|
||||
('workflows-categories/test', workflow_category),
|
||||
('workflows/test', workflow),
|
||||
('data-sources-categories/test', ds_category),
|
||||
('data-sources/test', data_source),
|
||||
('mail-templates-categories/test', mail_template_category),
|
||||
('mail-templates/test', mail_template),
|
||||
('comment-templates-categories/test', comment_template_category),
|
||||
('comment-templates/test', comment_template),
|
||||
('roles/test', role),
|
||||
('wscalls/test', wscall),
|
||||
)
|
||||
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-declare/'), bundle)
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'completed'
|
||||
assert resp.json['data']['completion_status'] == '15/15 (100%)'
|
||||
|
||||
assert Application.count() == 1
|
||||
application = Application.select()[0]
|
||||
assert application.slug == 'test'
|
||||
assert application.name == 'Test'
|
||||
assert application.description == 'Foo Bar'
|
||||
assert application.documentation_url == 'http://foo.bar'
|
||||
assert application.version_number == '42.0'
|
||||
assert application.version_notes == 'foo bar blah'
|
||||
assert application.icon.base_filename == 'foo.png'
|
||||
assert application.editable is True
|
||||
assert ApplicationElement.count() == 15
|
||||
|
||||
# create some links to elements not present in manifest: they should be unlinked
|
||||
element1 = ApplicationElement()
|
||||
element1.application_id = application.id
|
||||
element1.object_type = 'foobar'
|
||||
element1.object_id = '42'
|
||||
element1.store()
|
||||
element2 = ApplicationElement()
|
||||
element2.application_id = application.id
|
||||
element2.object_type = 'foobarblah'
|
||||
element2.object_id = '35'
|
||||
element2.store()
|
||||
# and remove an object to have an unkown reference in manifest
|
||||
MailTemplate.wipe()
|
||||
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-declare/'), bundle)
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'completed'
|
||||
|
||||
assert Application.count() == 1
|
||||
assert ApplicationElement.count() == 14
|
||||
assert (
|
||||
ApplicationElement.select(
|
||||
[
|
||||
Equal('application_id', application.id),
|
||||
Equal('object_type', element1.object_type),
|
||||
Equal('object_id', element1.object_id),
|
||||
]
|
||||
)
|
||||
== []
|
||||
)
|
||||
assert (
|
||||
ApplicationElement.select(
|
||||
[
|
||||
Equal('application_id', application.id),
|
||||
Equal('object_type', element2.object_type),
|
||||
Equal('object_id', element2.object_id),
|
||||
]
|
||||
)
|
||||
== []
|
||||
)
|
||||
assert (
|
||||
ApplicationElement.select(
|
||||
[
|
||||
Equal('application_id', application.id),
|
||||
Equal('object_type', MailTemplate.xml_root_node),
|
||||
]
|
||||
)
|
||||
== []
|
||||
)
|
||||
|
||||
|
||||
def test_export_import_bundle_unlink(pub):
|
||||
application = Application()
|
||||
application.slug = 'test'
|
||||
application.name = 'Test'
|
||||
application.version_number = 'foo'
|
||||
application.store()
|
||||
|
||||
other_application = Application()
|
||||
other_application.slug = 'other-test'
|
||||
other_application.name = 'Other Test'
|
||||
other_application.version_number = 'foo'
|
||||
other_application.store()
|
||||
|
||||
workflow_category = WorkflowCategory(name='test')
|
||||
workflow_category.store()
|
||||
ApplicationElement.update_or_create_for_object(application, workflow_category)
|
||||
|
||||
workflow = Workflow(name='test')
|
||||
workflow.store()
|
||||
ApplicationElement.update_or_create_for_object(application, workflow)
|
||||
|
||||
block_category = BlockCategory(name='test')
|
||||
block_category.store()
|
||||
ApplicationElement.update_or_create_for_object(application, block_category)
|
||||
|
||||
block = BlockDef(name='test')
|
||||
block.store()
|
||||
ApplicationElement.update_or_create_for_object(application, block)
|
||||
|
||||
category = Category(name='Test')
|
||||
category.store()
|
||||
ApplicationElement.update_or_create_for_object(application, category)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'Test'
|
||||
formdef.store()
|
||||
ApplicationElement.update_or_create_for_object(application, formdef)
|
||||
|
||||
card_category = CardDefCategory(name='Test')
|
||||
card_category.store()
|
||||
ApplicationElement.update_or_create_for_object(application, card_category)
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'Test'
|
||||
carddef.store()
|
||||
ApplicationElement.update_or_create_for_object(application, carddef)
|
||||
|
||||
ds_category = DataSourceCategory(name='Test')
|
||||
ds_category.store()
|
||||
ApplicationElement.update_or_create_for_object(application, ds_category)
|
||||
data_source = NamedDataSource(name='Test')
|
||||
data_source.store()
|
||||
ApplicationElement.update_or_create_for_object(application, data_source)
|
||||
|
||||
mail_template_category = MailTemplateCategory(name='Test')
|
||||
mail_template_category.store()
|
||||
ApplicationElement.update_or_create_for_object(application, mail_template_category)
|
||||
mail_template = MailTemplate(name='Test')
|
||||
mail_template.store()
|
||||
ApplicationElement.update_or_create_for_object(application, mail_template)
|
||||
|
||||
comment_template_category = CommentTemplateCategory(name='Test')
|
||||
comment_template_category.store()
|
||||
ApplicationElement.update_or_create_for_object(application, comment_template_category)
|
||||
comment_template = CommentTemplate(name='Test')
|
||||
comment_template.store()
|
||||
ApplicationElement.update_or_create_for_object(application, comment_template)
|
||||
|
||||
wscall = NamedWsCall(name='Test')
|
||||
wscall.store()
|
||||
ApplicationElement.update_or_create_for_object(application, wscall)
|
||||
|
||||
element = ApplicationElement()
|
||||
element.application_id = application.id
|
||||
element.object_type = 'foobar'
|
||||
element.object_id = '42'
|
||||
element.store()
|
||||
|
||||
other_element = ApplicationElement()
|
||||
other_element.application_id = other_application.id
|
||||
other_element.object_type = 'foobar'
|
||||
other_element.object_id = '42'
|
||||
other_element.store()
|
||||
|
||||
assert Application.count() == 2
|
||||
assert ApplicationElement.count() == 17
|
||||
|
||||
get_app(pub).post(sign_uri('/api/export-import/unlink/'), {'application': 'test'})
|
||||
|
||||
assert Application.count() == 1
|
||||
assert ApplicationElement.count() == 1
|
||||
|
||||
assert (
|
||||
Application.count(
|
||||
[
|
||||
Equal('id', other_application.id),
|
||||
]
|
||||
)
|
||||
== 1
|
||||
)
|
||||
assert (
|
||||
ApplicationElement.count(
|
||||
[
|
||||
Equal('application_id', other_application.id),
|
||||
]
|
||||
)
|
||||
== 1
|
||||
)
|
||||
|
||||
# again
|
||||
get_app(pub).post(sign_uri('/api/export-import/unlink/'), {'application': 'test'})
|
||||
assert Application.count() == 1
|
||||
assert ApplicationElement.count() == 1
|
||||
|
|
|
@ -12,6 +12,7 @@ from django.utils.encoding import force_bytes
|
|||
|
||||
from wcs import fields
|
||||
from wcs.admin.settings import UserFieldsFormDef
|
||||
from wcs.applications import Application
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.fields import DateField, ItemField, StringField
|
||||
|
@ -500,6 +501,21 @@ def test_unused_file_removal_job(pub):
|
|||
# 1 attachment
|
||||
assert len(glob.glob(os.path.join(pub.app_dir, 'unused-files/attachments/*/*'))) == 1
|
||||
|
||||
application = Application()
|
||||
application.name = 'App 1'
|
||||
application.slug = 'app-1'
|
||||
application.icon = PicklableUpload('icon.png', 'image/png')
|
||||
application.icon.receive([b'foobar'])
|
||||
application.version_number = '1'
|
||||
application.store()
|
||||
assert application.icon.qfilename in os.listdir(os.path.join(pub.app_dir, 'uploads'))
|
||||
clean_unused_files(pub)
|
||||
assert application.icon.qfilename in os.listdir(os.path.join(pub.app_dir, 'uploads'))
|
||||
|
||||
Application.remove_object(application.id)
|
||||
clean_unused_files(pub)
|
||||
assert application.icon.qfilename not in os.listdir(os.path.join(pub.app_dir, 'uploads'))
|
||||
|
||||
# unknown unused-files-behaviour: do nothing
|
||||
pub.site_options.set('options', 'unused-files-behaviour', 'foo')
|
||||
formdata = formdef.data_class()()
|
||||
|
|
|
@ -169,6 +169,8 @@ def create_temporary_pub(pickle_mode=False, lazy_mode=False):
|
|||
TestDef.do_table()
|
||||
TestResult.do_table()
|
||||
sql.WorkflowTrace.do_table()
|
||||
sql.Application.do_table()
|
||||
sql.ApplicationElement.do_table()
|
||||
sql.init_global_table()
|
||||
|
||||
|
||||
conn.close()
|
||||
|
|
|
@ -21,6 +21,7 @@ from quixote.html import TemplateIO, htmltext
|
|||
from wcs.admin import utils
|
||||
from wcs.admin.categories import BlockCategoriesDirectory, get_categories
|
||||
from wcs.admin.fields import FieldDefPage, FieldsDirectory
|
||||
from wcs.backoffice.applications import ApplicationsDirectory
|
||||
from wcs.backoffice.snapshots import SnapshotsDirectory
|
||||
from wcs.blocks import BlockDef, BlockdefImportError
|
||||
from wcs.categories import BlockCategory
|
||||
|
@ -262,13 +263,14 @@ class BlockDirectory(FieldsDirectory):
|
|||
|
||||
|
||||
class BlocksDirectory(Directory):
|
||||
_q_exports = ['', 'new', 'categories', ('import', 'p_import')]
|
||||
_q_exports = ['', 'new', 'categories', ('import', 'p_import'), ('application', 'applications_dir')]
|
||||
do_not_call_in_templates = True
|
||||
categories = BlockCategoriesDirectory()
|
||||
|
||||
def __init__(self, section):
|
||||
super().__init__()
|
||||
self.section = section
|
||||
self.applications_dir = ApplicationsDirectory(BlockDef.xml_root_node)
|
||||
|
||||
def _q_traverse(self, path):
|
||||
if not get_publisher().get_backoffice_root().is_global_accessible('forms'):
|
||||
|
@ -284,21 +286,35 @@ class BlocksDirectory(Directory):
|
|||
return BlockDirectory(self.section, block)
|
||||
|
||||
def _q_index(self):
|
||||
from wcs.applications import Application
|
||||
|
||||
html_top(self.section, title=_('Fields Blocks'))
|
||||
get_response().add_javascript(['popup.js'])
|
||||
context = {
|
||||
'view': self,
|
||||
'applications': Application.select_for_object_type(BlockDef.xml_root_node),
|
||||
'has_sidebar': True,
|
||||
}
|
||||
blocks = BlockDef.select(order_by='name')
|
||||
Application.populate_objects(blocks)
|
||||
context.update(self.get_list_context(blocks))
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/blocks.html'],
|
||||
context=context,
|
||||
is_django_native=True,
|
||||
)
|
||||
|
||||
def get_list_context(self, blocks):
|
||||
categories = BlockCategory.select()
|
||||
BlockCategory.sort_by_position(categories)
|
||||
blocks = BlockDef.select(order_by='name')
|
||||
if categories:
|
||||
categories.append(BlockCategory(_('Misc')))
|
||||
for category in categories:
|
||||
category.blocks = [x for x in blocks if x.category_id == category.id]
|
||||
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/blocks.html'],
|
||||
context={'view': self, 'blocks': blocks, 'categories': categories, 'has_sidebar': True},
|
||||
is_django_native=True,
|
||||
)
|
||||
return {
|
||||
'blocks': blocks,
|
||||
'categories': categories,
|
||||
}
|
||||
|
||||
def new(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
|
|
|
@ -19,6 +19,7 @@ from quixote.directory import Directory
|
|||
from quixote.html import TemplateIO, htmltext
|
||||
|
||||
from wcs.admin import utils
|
||||
from wcs.backoffice.applications import ApplicationsDirectory
|
||||
from wcs.backoffice.snapshots import SnapshotsDirectory
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.carddef import CardDef, get_cards_graph
|
||||
|
@ -358,7 +359,7 @@ class DataSourceCategoryPage(CategoryPage):
|
|||
|
||||
|
||||
class CategoriesDirectory(Directory):
|
||||
_q_exports = ['', 'new', 'update_order']
|
||||
_q_exports = ['', 'new', 'update_order', ('application', 'applications_dir')]
|
||||
|
||||
base_section = 'forms'
|
||||
category_class = Category
|
||||
|
@ -368,16 +369,24 @@ class CategoriesDirectory(Directory):
|
|||
|
||||
do_not_call_in_templates = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.applications_dir = ApplicationsDirectory(self.category_class.xml_root_node)
|
||||
|
||||
def _q_index(self):
|
||||
from wcs.applications import Application
|
||||
|
||||
get_response().add_javascript(['biglist.js', 'qommon.wysiwyg.js', 'popup.js'])
|
||||
html_top('categories', title=_('Categories'))
|
||||
categories = self.category_class.select()
|
||||
self.category_class.sort_by_position(categories)
|
||||
Application.populate_objects(categories)
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/categories.html'],
|
||||
context={
|
||||
'view': self,
|
||||
'categories': categories,
|
||||
'applications': Application.select_for_object_type(self.category_class.xml_root_node),
|
||||
'has_sidebar': True,
|
||||
},
|
||||
is_django_native=True,
|
||||
|
|
|
@ -20,6 +20,7 @@ from quixote.html import TemplateIO, htmltext
|
|||
|
||||
from wcs.admin import utils
|
||||
from wcs.admin.categories import CommentTemplateCategoriesDirectory, get_categories
|
||||
from wcs.backoffice.applications import ApplicationsDirectory
|
||||
from wcs.backoffice.snapshots import SnapshotsDirectory
|
||||
from wcs.categories import CommentTemplateCategory
|
||||
from wcs.comment_templates import CommentTemplate
|
||||
|
@ -40,10 +41,14 @@ from wcs.qommon.form import (
|
|||
|
||||
|
||||
class CommentTemplatesDirectory(Directory):
|
||||
_q_exports = ['', 'new', 'categories', ('import', 'p_import')]
|
||||
_q_exports = ['', 'new', 'categories', ('import', 'p_import'), ('application', 'applications_dir')]
|
||||
do_not_call_in_templates = True
|
||||
categories = CommentTemplateCategoriesDirectory()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.applications_dir = ApplicationsDirectory(CommentTemplate.xml_root_node)
|
||||
|
||||
def _q_traverse(self, path):
|
||||
if not get_publisher().get_backoffice_root().is_global_accessible('workflows'):
|
||||
raise errors.AccessForbiddenError()
|
||||
|
@ -54,25 +59,35 @@ class CommentTemplatesDirectory(Directory):
|
|||
return CommentTemplatePage(component)
|
||||
|
||||
def _q_index(self):
|
||||
from wcs.applications import Application
|
||||
|
||||
html_top('comment_templates', title=_('Comment Templates'))
|
||||
get_response().add_javascript(['popup.js'])
|
||||
comment_templates = CommentTemplate.select(order_by='name')
|
||||
Application.populate_objects(comment_templates)
|
||||
context = {
|
||||
'view': self,
|
||||
'applications': Application.select_for_object_type(CommentTemplate.xml_root_node),
|
||||
'has_sidebar': True,
|
||||
}
|
||||
context.update(self.get_list_context(comment_templates))
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/comment-templates.html'],
|
||||
context=context,
|
||||
is_django_native=True,
|
||||
)
|
||||
|
||||
def get_list_context(self, comment_templates):
|
||||
categories = CommentTemplateCategory.select()
|
||||
CommentTemplateCategory.sort_by_position(categories)
|
||||
comment_templates = CommentTemplate.select(order_by='name')
|
||||
if categories:
|
||||
categories.append(CommentTemplateCategory(_('Misc')))
|
||||
for category in categories:
|
||||
category.comment_templates = [x for x in comment_templates if x.category_id == category.id]
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/comment-templates.html'],
|
||||
context={
|
||||
'view': self,
|
||||
'comment_templates': comment_templates,
|
||||
'categories': categories,
|
||||
'has_sidebar': True,
|
||||
},
|
||||
is_django_native=True,
|
||||
)
|
||||
return {
|
||||
'comment_templates': comment_templates,
|
||||
'categories': categories,
|
||||
}
|
||||
|
||||
def new(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
|
|
|
@ -20,6 +20,7 @@ from quixote.html import TemplateIO, htmltext
|
|||
|
||||
from wcs.admin import utils
|
||||
from wcs.admin.categories import DataSourceCategoriesDirectory, get_categories
|
||||
from wcs.backoffice.applications import ApplicationsDirectory
|
||||
from wcs.backoffice.snapshots import SnapshotsDirectory
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.categories import DataSourceCategory
|
||||
|
@ -498,9 +499,14 @@ class NamedDataSourcesDirectory(Directory):
|
|||
'categories',
|
||||
('import', 'p_import'),
|
||||
('sync-agendas', 'sync_agendas'),
|
||||
('application', 'applications_dir'),
|
||||
]
|
||||
categories = DataSourceCategoriesDirectory()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.applications_dir = ApplicationsDirectory(NamedDataSource.xml_root_node)
|
||||
|
||||
def _q_traverse(self, path):
|
||||
if (
|
||||
not get_publisher().get_backoffice_root().is_global_accessible('forms')
|
||||
|
@ -512,12 +518,33 @@ class NamedDataSourcesDirectory(Directory):
|
|||
return super()._q_traverse(path)
|
||||
|
||||
def _q_index(self):
|
||||
from wcs.applications import Application
|
||||
|
||||
html_top('datasources', title=_('Data Sources'))
|
||||
get_response().add_javascript(['popup.js'])
|
||||
context = {
|
||||
'view': self,
|
||||
'has_chrono': has_chrono(get_publisher()),
|
||||
'has_users': True,
|
||||
'applications': Application.select_for_object_type(NamedDataSource.xml_root_node),
|
||||
'has_sidebar': True,
|
||||
}
|
||||
data_sources = NamedDataSource.select(order_by='name')
|
||||
Application.populate_objects(data_sources)
|
||||
context.update(self.get_list_context(data_sources))
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/data-sources.html'],
|
||||
context=context,
|
||||
is_django_native=True,
|
||||
)
|
||||
|
||||
def get_list_context(self, objects, application=None):
|
||||
from wcs.applications import Application
|
||||
|
||||
data_sources = []
|
||||
user_data_sources = []
|
||||
agenda_data_sources = []
|
||||
for ds in NamedDataSource.select(order_by='name'):
|
||||
for ds in objects:
|
||||
if ds.external == 'agenda':
|
||||
agenda_data_sources.append(ds)
|
||||
elif ds.type == 'wcs:users':
|
||||
|
@ -532,20 +559,18 @@ class NamedDataSourcesDirectory(Directory):
|
|||
category.data_sources = [x for x in data_sources if x.category_id == category.id]
|
||||
generated_data_sources = list(CardDef.get_carddefs_as_data_source())
|
||||
generated_data_sources.sort(key=lambda x: misc.simplify(x[1]))
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/data-sources.html'],
|
||||
context={
|
||||
'data_sources': data_sources,
|
||||
'categories': categories,
|
||||
'user_data_sources': user_data_sources,
|
||||
'has_chrono': has_chrono(get_publisher()),
|
||||
'has_users': True,
|
||||
'agenda_data_sources': agenda_data_sources,
|
||||
'generated_data_sources': generated_data_sources,
|
||||
'has_sidebar': True,
|
||||
},
|
||||
is_django_native=True,
|
||||
)
|
||||
if application:
|
||||
carddefs = application.get_objects_for_object_type(CardDef.xml_root_node, lightweight=True)
|
||||
generated_data_sources = [g for g in generated_data_sources if g[0] in carddefs]
|
||||
else:
|
||||
Application.populate_objects([g[0] for g in generated_data_sources])
|
||||
return {
|
||||
'data_sources': data_sources,
|
||||
'categories': categories,
|
||||
'user_data_sources': user_data_sources,
|
||||
'agenda_data_sources': agenda_data_sources,
|
||||
'generated_data_sources': generated_data_sources,
|
||||
}
|
||||
|
||||
def _new(self, url, breadcrumb, title, ds_type=None):
|
||||
get_response().breadcrumb.append((url, breadcrumb))
|
||||
|
|
|
@ -23,6 +23,7 @@ from quixote import get_publisher, get_request, get_response, get_session, redir
|
|||
from quixote.directory import AccessControlled, Directory
|
||||
from quixote.html import TemplateIO, htmlescape, htmltext
|
||||
|
||||
from wcs.backoffice.applications import ApplicationsDirectory
|
||||
from wcs.backoffice.snapshots import SnapshotsDirectory
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.categories import Category
|
||||
|
@ -1712,7 +1713,15 @@ class NamedDataSourcesDirectoryInForms(NamedDataSourcesDirectory):
|
|||
|
||||
|
||||
class FormsDirectory(AccessControlled, Directory):
|
||||
_q_exports = ['', 'new', ('import', 'p_import'), 'blocks', 'categories', ('data-sources', 'data_sources')]
|
||||
_q_exports = [
|
||||
'',
|
||||
'new',
|
||||
('import', 'p_import'),
|
||||
'blocks',
|
||||
'categories',
|
||||
('data-sources', 'data_sources'),
|
||||
('application', 'applications_dir'),
|
||||
]
|
||||
|
||||
category_class = Category
|
||||
categories = CategoriesDirectory()
|
||||
|
@ -1736,6 +1745,10 @@ class FormsDirectory(AccessControlled, Directory):
|
|||
'Do note it is disabled by default.'
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.applications_dir = ApplicationsDirectory(self.formdef_class.xml_root_node)
|
||||
|
||||
def html_top(self, title):
|
||||
return html_top(self.section, title)
|
||||
|
||||
|
@ -1757,16 +1770,33 @@ class FormsDirectory(AccessControlled, Directory):
|
|||
return False
|
||||
|
||||
def _q_index(self):
|
||||
from wcs.applications import Application
|
||||
|
||||
self.html_top(title=self.top_title)
|
||||
get_response().add_javascript(['widget_list.js', 'select2.js', 'popup.js'])
|
||||
|
||||
context = {
|
||||
'view': self,
|
||||
'has_roles': bool(get_publisher().role_class.count()),
|
||||
'applications': Application.select_for_object_type(self.formdef_class.xml_root_node),
|
||||
'has_sidebar': True,
|
||||
}
|
||||
formdefs = self.formdef_class.select(order_by='name', ignore_errors=True, lightweight=True)
|
||||
Application.populate_objects(formdefs)
|
||||
context.update(self.get_list_context(formdefs))
|
||||
context.update(self.get_extra_index_context_data())
|
||||
|
||||
return template.QommonTemplateResponse(
|
||||
templates=[self.index_template_name], context=context, is_django_native=True
|
||||
)
|
||||
|
||||
def get_list_context(self, formdefs):
|
||||
global_access = is_global_accessible(self.section)
|
||||
|
||||
categories = self.category_class.select()
|
||||
self.category_class.sort_by_position(categories)
|
||||
categories.append(self.category_class(_('Misc')))
|
||||
|
||||
formdefs = self.formdef_class.select(order_by='name', ignore_errors=True, lightweight=True)
|
||||
has_form_with_category_set = False
|
||||
for category in categories:
|
||||
if not global_access:
|
||||
|
@ -1786,18 +1816,10 @@ class FormsDirectory(AccessControlled, Directory):
|
|||
# no form with a category set, do not display "Misc" title
|
||||
categories[-1].name = None
|
||||
|
||||
context = {
|
||||
'view': self,
|
||||
return {
|
||||
'objects': formdefs,
|
||||
'categories': categories,
|
||||
'has_roles': bool(get_publisher().role_class.count()),
|
||||
'has_sidebar': True,
|
||||
}
|
||||
context.update(self.get_extra_index_context_data())
|
||||
|
||||
return template.QommonTemplateResponse(
|
||||
templates=[self.index_template_name], context=context, is_django_native=True
|
||||
)
|
||||
|
||||
def get_extra_index_context_data(self):
|
||||
return {
|
||||
|
|
|
@ -20,6 +20,7 @@ from quixote.html import TemplateIO, htmltext
|
|||
|
||||
from wcs.admin import utils
|
||||
from wcs.admin.categories import MailTemplateCategoriesDirectory, get_categories
|
||||
from wcs.backoffice.applications import ApplicationsDirectory
|
||||
from wcs.backoffice.snapshots import SnapshotsDirectory
|
||||
from wcs.categories import MailTemplateCategory
|
||||
from wcs.mail_templates import MailTemplate
|
||||
|
@ -40,10 +41,14 @@ from wcs.qommon.form import (
|
|||
|
||||
|
||||
class MailTemplatesDirectory(Directory):
|
||||
_q_exports = ['', 'new', 'categories', ('import', 'p_import')]
|
||||
_q_exports = ['', 'new', 'categories', ('import', 'p_import'), ('application', 'applications_dir')]
|
||||
do_not_call_in_templates = True
|
||||
categories = MailTemplateCategoriesDirectory()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.applications_dir = ApplicationsDirectory(MailTemplate.xml_root_node)
|
||||
|
||||
def _q_traverse(self, path):
|
||||
if not get_publisher().get_backoffice_root().is_global_accessible('workflows'):
|
||||
raise errors.AccessForbiddenError()
|
||||
|
@ -54,25 +59,35 @@ class MailTemplatesDirectory(Directory):
|
|||
return MailTemplatePage(component)
|
||||
|
||||
def _q_index(self):
|
||||
from wcs.applications import Application
|
||||
|
||||
html_top('mail_templates', title=_('Mail Templates'))
|
||||
get_response().add_javascript(['popup.js'])
|
||||
mail_templates = MailTemplate.select(order_by='name')
|
||||
Application.populate_objects(mail_templates)
|
||||
context = {
|
||||
'view': self,
|
||||
'applications': Application.select_for_object_type(MailTemplate.xml_root_node),
|
||||
'has_sidebar': True,
|
||||
}
|
||||
context.update(self.get_list_context(mail_templates))
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/mail-templates.html'],
|
||||
context=context,
|
||||
is_django_native=True,
|
||||
)
|
||||
|
||||
def get_list_context(self, mail_templates):
|
||||
categories = MailTemplateCategory.select()
|
||||
MailTemplateCategory.sort_by_position(categories)
|
||||
mail_templates = MailTemplate.select(order_by='name')
|
||||
if categories:
|
||||
categories.append(MailTemplateCategory(_('Misc')))
|
||||
for category in categories:
|
||||
category.mail_templates = [x for x in mail_templates if x.category_id == category.id]
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/mail-templates.html'],
|
||||
context={
|
||||
'view': self,
|
||||
'mail_templates': mail_templates,
|
||||
'categories': categories,
|
||||
'has_sidebar': True,
|
||||
},
|
||||
is_django_native=True,
|
||||
)
|
||||
return {
|
||||
'mail_templates': mail_templates,
|
||||
'categories': categories,
|
||||
}
|
||||
|
||||
def new(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
|
|
|
@ -28,6 +28,7 @@ from quixote.directory import Directory
|
|||
from quixote.html import TemplateIO, htmltext
|
||||
|
||||
from wcs.admin.categories import WorkflowCategoriesDirectory, get_categories
|
||||
from wcs.backoffice.applications import ApplicationsDirectory
|
||||
from wcs.backoffice.snapshots import SnapshotsDirectory
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.categories import WorkflowCategory
|
||||
|
@ -1954,6 +1955,7 @@ class WorkflowsDirectory(Directory):
|
|||
('data-sources', 'data_sources'),
|
||||
('mail-templates', 'mail_templates'),
|
||||
('comment-templates', 'comment_templates'),
|
||||
('application', 'applications_dir'),
|
||||
]
|
||||
|
||||
data_sources = NamedDataSourcesDirectoryInWorkflows()
|
||||
|
@ -1962,6 +1964,10 @@ class WorkflowsDirectory(Directory):
|
|||
category_class = WorkflowCategory
|
||||
categories = WorkflowCategoriesDirectory()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.applications_dir = ApplicationsDirectory(Workflow.xml_root_node)
|
||||
|
||||
def html_top(self, title):
|
||||
return html_top('workflows', title)
|
||||
|
||||
|
@ -1983,9 +1989,26 @@ class WorkflowsDirectory(Directory):
|
|||
return False
|
||||
|
||||
def _q_index(self):
|
||||
from wcs.applications import Application
|
||||
|
||||
self.html_top(title=_('Workflows'))
|
||||
get_response().add_javascript(['popup.js'])
|
||||
|
||||
context = {
|
||||
'view': self,
|
||||
'is_global_accessible': is_global_accessible(),
|
||||
'applications': Application.select_for_object_type(Workflow.xml_root_node),
|
||||
'has_sidebar': True,
|
||||
}
|
||||
workflows = Workflow.select(order_by='name')
|
||||
Application.populate_objects(workflows)
|
||||
context.update(self.get_list_context(workflows))
|
||||
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/workflows.html'], context=context, is_django_native=True
|
||||
)
|
||||
|
||||
def get_list_context(self, workflow_qs, application=False):
|
||||
formdef_workflows = [Workflow.get_default_workflow()]
|
||||
workflows_in_formdef_use = set(formdef_workflows[0].id)
|
||||
for formdef in FormDef.select(lightweight=True):
|
||||
|
@ -1998,9 +2021,12 @@ class WorkflowsDirectory(Directory):
|
|||
|
||||
shared_workflows = []
|
||||
unused_workflows = []
|
||||
workflows = formdef_workflows + carddef_workflows
|
||||
if application:
|
||||
workflows = []
|
||||
else:
|
||||
workflows = formdef_workflows + carddef_workflows
|
||||
|
||||
for workflow in Workflow.select(order_by='name'):
|
||||
for workflow in workflow_qs:
|
||||
if str(workflow.id) in workflows_in_formdef_use and str(workflow.id) in workflows_in_carddef_use:
|
||||
shared_workflows.append(workflow)
|
||||
elif str(workflow.id) in workflows_in_formdef_use:
|
||||
|
@ -2066,15 +2092,9 @@ class WorkflowsDirectory(Directory):
|
|||
x for x in workflows + unused_workflows if x.category_id == str(category.id)
|
||||
]
|
||||
|
||||
context = {
|
||||
return {
|
||||
'categories': categories,
|
||||
'view': self,
|
||||
'has_sidebar': True,
|
||||
'is_global_accessible': is_global_accessible(),
|
||||
}
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/workflows.html'], context=context, is_django_native=True
|
||||
)
|
||||
|
||||
def new(self):
|
||||
get_response().breadcrumb.append(('new', _('New')))
|
||||
|
|
|
@ -19,6 +19,7 @@ from quixote.directory import Directory
|
|||
from quixote.html import TemplateIO, htmltext
|
||||
|
||||
from wcs.admin import utils
|
||||
from wcs.backoffice.applications import ApplicationsDirectory
|
||||
from wcs.backoffice.snapshots import SnapshotsDirectory
|
||||
from wcs.qommon import _, errors, misc, template
|
||||
from wcs.qommon.backoffice.menu import html_top
|
||||
|
@ -181,18 +182,31 @@ class NamedWsCallPage(Directory):
|
|||
|
||||
|
||||
class NamedWsCallsDirectory(Directory):
|
||||
_q_exports = ['', 'new', ('import', 'p_import')]
|
||||
_q_exports = ['', 'new', ('import', 'p_import'), ('application', 'applications_dir')]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.applications_dir = ApplicationsDirectory(NamedWsCall.xml_root_node)
|
||||
|
||||
def _q_traverse(self, path):
|
||||
get_response().breadcrumb.append(('wscalls/', _('Webservice Calls')))
|
||||
return super()._q_traverse(path)
|
||||
|
||||
def _q_index(self):
|
||||
from wcs.applications import Application
|
||||
|
||||
html_top('wscalls', title=_('Webservice Calls'))
|
||||
get_response().add_javascript(['popup.js'])
|
||||
wscalls = NamedWsCall.select(order_by='name')
|
||||
Application.populate_objects(wscalls)
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/wscalls.html'],
|
||||
context={'view': self, 'wscalls': NamedWsCall.select(order_by='name'), 'has_sidebar': True},
|
||||
context={
|
||||
'view': self,
|
||||
'wscalls': wscalls,
|
||||
'applications': Application.select_for_object_type(NamedWsCall.xml_root_node),
|
||||
'has_sidebar': True,
|
||||
},
|
||||
is_django_native=True,
|
||||
)
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ from django.shortcuts import redirect
|
|||
from django.urls import reverse
|
||||
|
||||
from wcs.api_utils import is_url_signed
|
||||
from wcs.applications import Application, ApplicationElement
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.categories import (
|
||||
|
@ -39,7 +40,7 @@ from wcs.comment_templates import CommentTemplate
|
|||
from wcs.data_sources import NamedDataSource, StubNamedDataSource
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.mail_templates import MailTemplate
|
||||
from wcs.sql import Role
|
||||
from wcs.sql import Equal, Role
|
||||
from wcs.workflows import Workflow
|
||||
from wcs.wscalls import NamedWsCall
|
||||
|
||||
|
@ -247,7 +248,7 @@ class BundleImportJob(AfterJob):
|
|||
tar_io = io.BytesIO(self.tar_content)
|
||||
with tarfile.open(fileobj=tar_io) as self.tar:
|
||||
manifest = json.loads(self.tar.extractfile('manifest.json').read().decode())
|
||||
self.app_name = manifest.get('application')
|
||||
self.application = Application.update_or_create_from_manifest(manifest, self.tar)
|
||||
|
||||
# count number of actions
|
||||
self.total_count = 0
|
||||
|
@ -260,6 +261,9 @@ class BundleImportJob(AfterJob):
|
|||
)
|
||||
self.total_count += len([x for x in manifest.get('elements') if x.get('type') in object_types])
|
||||
|
||||
# init cache of application elements, from imported manifest
|
||||
self.application_elements = set()
|
||||
|
||||
# first pass on formdef/carddef/blockdef/workflows to create them empty
|
||||
# (name and slug); so they can be found for sure in import pass
|
||||
for type in ('forms', 'cards', 'blocks', 'workflows'):
|
||||
|
@ -269,6 +273,9 @@ class BundleImportJob(AfterJob):
|
|||
for type in object_types:
|
||||
self.install([x for x in manifest.get('elements') if x.get('type') == type])
|
||||
|
||||
# remove obsolete application elements
|
||||
self.unlink_obsolete_objects()
|
||||
|
||||
def pre_install(self, elements):
|
||||
for element in elements:
|
||||
element_klass = klasses[element['type']]
|
||||
|
@ -289,7 +296,8 @@ class BundleImportJob(AfterJob):
|
|||
new_object = element_klass()
|
||||
new_object.slug = slug
|
||||
new_object.name = '[pre-import] %s' % xml_node_text(tree.find('name'))
|
||||
new_object.store(comment=_('Application (%s)') % self.app_name)
|
||||
new_object.store(comment=_('Application (%s)') % self.application.name)
|
||||
self.link_object(new_object)
|
||||
self.increment_count()
|
||||
|
||||
def install(self, elements):
|
||||
|
@ -304,7 +312,8 @@ class BundleImportJob(AfterJob):
|
|||
if existing_object is None or not hasattr(existing_object, 'id'):
|
||||
raise KeyError()
|
||||
except KeyError:
|
||||
new_object.store(comment=_('Application (%s)') % self.app_name)
|
||||
new_object.store(comment=_('Application (%s)') % self.application.name)
|
||||
self.link_object(new_object)
|
||||
self.increment_count()
|
||||
continue
|
||||
# replace
|
||||
|
@ -327,9 +336,20 @@ class BundleImportJob(AfterJob):
|
|||
'disabled',
|
||||
):
|
||||
setattr(new_object, attr, getattr(existing_object, attr))
|
||||
new_object.store(comment=_('Application (%s) update') % self.app_name)
|
||||
new_object.store(comment=_('Application (%s) update') % self.application.name)
|
||||
self.link_object(new_object)
|
||||
self.increment_count()
|
||||
|
||||
def link_object(self, obj):
|
||||
element = ApplicationElement.update_or_create_for_object(self.application, obj)
|
||||
self.application_elements.add((element.object_type, element.object_id))
|
||||
|
||||
def unlink_obsolete_objects(self):
|
||||
known_elements = ApplicationElement.select([Equal('application_id', self.application.id)])
|
||||
for element in known_elements:
|
||||
if (element.object_type, element.object_id) not in self.application_elements:
|
||||
ApplicationElement.remove_object(element.id)
|
||||
|
||||
|
||||
@signature_required
|
||||
def bundle_import(request):
|
||||
|
@ -337,3 +357,57 @@ def bundle_import(request):
|
|||
job.store()
|
||||
job.run(spool=True)
|
||||
return JsonResponse({'err': 0, 'url': job.get_api_status_url()})
|
||||
|
||||
|
||||
class BundleDeclareJob(BundleImportJob):
|
||||
def execute(self):
|
||||
object_types = [x for x in klasses if x != 'roles']
|
||||
|
||||
tar_io = io.BytesIO(self.tar_content)
|
||||
with tarfile.open(fileobj=tar_io) as self.tar:
|
||||
manifest = json.loads(self.tar.extractfile('manifest.json').read().decode())
|
||||
self.application = Application.update_or_create_from_manifest(manifest, self.tar, editable=True)
|
||||
|
||||
# count number of actions
|
||||
self.total_count = len([x for x in manifest.get('elements') if x.get('type') in object_types])
|
||||
|
||||
# init cache of application elements, from manifest
|
||||
self.application_elements = set()
|
||||
|
||||
# declare elements
|
||||
for type in object_types:
|
||||
self.declare([x for x in manifest.get('elements') if x.get('type') == type])
|
||||
|
||||
# remove obsolete application elements
|
||||
self.unlink_obsolete_objects()
|
||||
|
||||
def declare(self, elements):
|
||||
for element in elements:
|
||||
element_klass = klasses[element['type']]
|
||||
element_slug = element['slug']
|
||||
existing_object = element_klass.get_by_slug(element_slug, ignore_errors=True)
|
||||
if existing_object:
|
||||
self.link_object(existing_object)
|
||||
self.increment_count()
|
||||
|
||||
|
||||
@signature_required
|
||||
def bundle_declare(request):
|
||||
job = BundleDeclareJob(tar_content=request.body)
|
||||
job.store()
|
||||
job.run(spool=True)
|
||||
return JsonResponse({'err': 0, 'url': job.get_api_status_url()})
|
||||
|
||||
|
||||
@signature_required
|
||||
def unlink(request):
|
||||
if request.method == 'POST' and request.POST.get('application'):
|
||||
applications = Application.select([Equal('slug', request.POST['application'])])
|
||||
if applications:
|
||||
application = applications[0]
|
||||
elements = ApplicationElement.select([Equal('application_id', application.id)])
|
||||
for element in elements:
|
||||
ApplicationElement.remove_object(element.id)
|
||||
Application.remove_object(application.id)
|
||||
|
||||
return JsonResponse({'err': 0})
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
# w.c.s. - web application for online forms
|
||||
# Copyright (C) 2005-2023 Entr'ouvert
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import collections
|
||||
import mimetypes
|
||||
|
||||
from quixote import get_publisher
|
||||
|
||||
from wcs import sql
|
||||
from wcs.qommon.upload_storage import PicklableUpload
|
||||
|
||||
|
||||
class Application(sql.Application):
|
||||
id = None
|
||||
slug = None
|
||||
name = None
|
||||
description = None
|
||||
documentation_url = None
|
||||
icon = None
|
||||
version_number = None
|
||||
version_notes = None
|
||||
editable = False
|
||||
visible = True
|
||||
created_at = None
|
||||
updated_at = None
|
||||
|
||||
@classmethod
|
||||
def get_by_slug(cls, slug, ignore_errors=True):
|
||||
objects = cls.select([sql.Equal('slug', slug)])
|
||||
if objects:
|
||||
return objects[0]
|
||||
if ignore_errors:
|
||||
return None
|
||||
raise KeyError(slug)
|
||||
|
||||
@classmethod
|
||||
def update_or_create_from_manifest(cls, manifest, tar, editable=False):
|
||||
application = cls.get_by_slug(manifest.get('slug'), ignore_errors=True)
|
||||
if application is None:
|
||||
application = cls()
|
||||
application.slug = manifest.get('slug')
|
||||
application.name = manifest.get('application')
|
||||
application.description = manifest.get('description')
|
||||
application.documentation_url = manifest.get('documentation_url')
|
||||
if manifest.get('icon'):
|
||||
application.icon = PicklableUpload(manifest['icon'], mimetypes.guess_type(manifest['icon'])[0])
|
||||
application.icon.receive([tar.extractfile(manifest['icon']).read()])
|
||||
else:
|
||||
application.icon = None
|
||||
application.version_number = manifest.get('version_number') or 'unknown'
|
||||
application.version_notes = manifest.get('version_notes')
|
||||
application.editable = editable
|
||||
application.visible = True
|
||||
application.store()
|
||||
return application
|
||||
|
||||
@classmethod
|
||||
def select_for_object_type(cls, object_type):
|
||||
elements = ApplicationElement.select([sql.Equal('object_type', object_type)])
|
||||
application_ids = [e.application_id for e in elements]
|
||||
return cls.get_ids(application_ids, ignore_errors=True, order_by='name')
|
||||
|
||||
@classmethod
|
||||
def populate_objects(cls, objects):
|
||||
object_types = {o.xml_root_node for o in objects}
|
||||
elements = ApplicationElement.select([sql.Contains('object_type', object_types)])
|
||||
elements_by_objects = collections.defaultdict(list)
|
||||
for element in elements:
|
||||
elements_by_objects[(element.object_type, element.object_id)].append(element)
|
||||
application_ids = [e.application_id for e in elements]
|
||||
applications_by_ids = {a.id: a for a in cls.get_ids(application_ids, ignore_errors=True)}
|
||||
for obj in objects:
|
||||
applications = []
|
||||
elements = elements_by_objects.get((obj.xml_root_node, obj.id)) or []
|
||||
for element in elements:
|
||||
application = applications_by_ids.get(element.application_id)
|
||||
applications.append(application)
|
||||
obj._applications = sorted(applications, key=lambda a: a.name)
|
||||
|
||||
@classmethod
|
||||
def load_for_object(cls, obj):
|
||||
elements = ApplicationElement.select(
|
||||
[sql.Equal('object_type', obj.xml_root_node), sql.Equal('object_id', obj.id)]
|
||||
)
|
||||
application_ids = [e.application_id for e in elements]
|
||||
applications_by_ids = {a.id: a for a in cls.get_ids(application_ids, ignore_errors=True)}
|
||||
applications = []
|
||||
for element in elements:
|
||||
application = applications_by_ids.get(element.application_id)
|
||||
applications.append(application)
|
||||
obj._applications = sorted(applications, key=lambda a: a.name)
|
||||
|
||||
def get_objects_for_object_type(self, object_type, lightweight=True):
|
||||
elements = ApplicationElement.select(
|
||||
[sql.Equal('application_id', self.id), sql.Equal('object_type', object_type)]
|
||||
)
|
||||
object_ids = [e.object_id for e in elements]
|
||||
select_kwargs = {}
|
||||
if object_type == 'formdef':
|
||||
select_kwargs['lightweight'] = lightweight
|
||||
return (
|
||||
get_publisher()
|
||||
.get_object_class(object_type)
|
||||
.get_ids(object_ids, ignore_errors=True, order_by='name', **select_kwargs)
|
||||
)
|
||||
|
||||
|
||||
class ApplicationElement(sql.ApplicationElement):
|
||||
id = None
|
||||
application_id = None
|
||||
object_type = None
|
||||
object_id = None
|
||||
created_at = None
|
||||
updated_at = None
|
||||
|
||||
@classmethod
|
||||
def update_or_create_for_object(cls, application, obj):
|
||||
elements = cls.select(
|
||||
[
|
||||
sql.Equal('application_id', application.id),
|
||||
sql.Equal('object_type', obj.xml_root_node),
|
||||
sql.Equal('object_id', obj.id),
|
||||
]
|
||||
)
|
||||
if elements:
|
||||
element = elements[0]
|
||||
element.store()
|
||||
return element
|
||||
element = cls()
|
||||
element.application_id = application.id
|
||||
element.object_type = obj.xml_root_node
|
||||
element.object_id = obj.id
|
||||
element.store()
|
||||
return element
|
|
@ -0,0 +1,140 @@
|
|||
# w.c.s. - web application for online forms
|
||||
# Copyright (C) 2005-2023 Entr'ouvert
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# 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_response, redirect
|
||||
from quixote.directory import Directory
|
||||
|
||||
from wcs.qommon import errors, misc, template
|
||||
from wcs.qommon.backoffice.menu import html_top
|
||||
|
||||
|
||||
class ApplicationsDirectory(Directory):
|
||||
fpeters
commented
On pourrait avoir un On pourrait avoir un `_q_index` qui ferait juste redirect('..'), pour ne pas donner une 404 sur un chemin partiel.
lguerin
commented
fait fait
|
||||
_q_exports = ['']
|
||||
|
||||
def __init__(self, object_type):
|
||||
self.object_type = object_type
|
||||
|
||||
def _q_index(self):
|
||||
return redirect('..')
|
||||
|
||||
def _q_lookup(self, component):
|
||||
from wcs.applications import Application
|
||||
|
||||
application = Application.get_by_slug(component, ignore_errors=True)
|
||||
if not application:
|
||||
raise errors.TraversalError()
|
||||
return ApplicationDirectory(self.object_type, application)
|
||||
|
||||
|
||||
class ApplicationDirectory(Directory):
|
||||
_q_exports = ['', 'icon', 'logo']
|
||||
|
||||
formdef_objects_template = 'wcs/backoffice/application_formdefs.html'
|
||||
carddef_objects_template = 'wcs/backoffice/application_formdefs.html'
|
||||
workflow_objects_template = 'wcs/backoffice/application_workflows.html'
|
||||
block_objects_template = 'wcs/backoffice/application_blocks.html'
|
||||
mailtemplate_objects_template = 'wcs/backoffice/application_mailtemplates.html'
|
||||
commenttemplate_objects_template = 'wcs/backoffice/application_commenttemplates.html'
|
||||
datasource_objects_template = 'wcs/backoffice/application_datasources.html'
|
||||
wscall_objects_template = 'wcs/backoffice/application_wscalls.html'
|
||||
|
||||
def __init__(self, object_type, application):
|
||||
fpeters
commented
Ça devrait plutôt être application/%s/ Actuellement de https://.../backoffice/forms/application/1/ c'est un lien /backoffice/forms/1/ qui est ajouté à l'href. (pour le moment comme c'est toujours le dernier élément du fil d'ariane et qu'il n'apparait pas, on ne risque pas de clic sur le mauvais lien, mais autant éviter la surprise si jamais des sous-pages sont ajoutées). Ça devrait plutôt être application/%s/
Actuellement de https://.../backoffice/forms/application/1/ c'est un lien /backoffice/forms/1/ qui est ajouté à l'href. (pour le moment comme c'est toujours le dernier élément du fil d'ariane et qu'il n'apparait pas, on ne risque pas de clic sur le mauvais lien, mais autant éviter la surprise si jamais des sous-pages sont ajoutées).
lguerin
commented
fait fait
|
||||
self.object_type = object_type
|
||||
self.application = application
|
||||
|
||||
def _q_traverse(self, path):
|
||||
get_response().breadcrumb.append(('application/%s/' % self.application.slug, self.application.name))
|
||||
return super()._q_traverse(path)
|
||||
|
||||
def _q_index(self):
|
||||
html_top('', self.application.name)
|
||||
return template.QommonTemplateResponse(templates=[self.get_template()], context=self.get_context())
|
||||
|
||||
def get_template(self):
|
||||
if hasattr(self, '%s_objects_template' % self.object_type.replace('-', '')):
|
||||
return getattr(self, '%s_objects_template' % self.object_type.replace('-', ''))
|
||||
return 'wcs/backoffice/application_objects.html'
|
||||
|
||||
def get_context(self):
|
||||
context = {
|
||||
'application': self.application,
|
||||
}
|
||||
objects = self.application.get_objects_for_object_type(self.object_type)
|
||||
if hasattr(self, 'get_%s_objects_context' % self.object_type.replace('-', '')):
|
||||
context.update(
|
||||
getattr(self, 'get_%s_objects_context' % self.object_type.replace('-', ''))(objects)
|
||||
)
|
||||
else:
|
||||
context['objects'] = objects
|
||||
return context
|
||||
|
||||
def get_formdef_objects_context(self, objects):
|
||||
from wcs.admin.forms import FormsDirectory
|
||||
|
||||
return FormsDirectory().get_list_context(objects)
|
||||
|
||||
def get_carddef_objects_context(self, objects):
|
||||
from wcs.backoffice.cards import CardsDirectory
|
||||
|
||||
return CardsDirectory().get_list_context(objects)
|
||||
|
||||
def get_workflow_objects_context(self, objects):
|
||||
from wcs.admin.workflows import WorkflowsDirectory
|
||||
|
||||
return WorkflowsDirectory().get_list_context(objects, application=True)
|
||||
|
||||
def get_block_objects_context(self, objects):
|
||||
from wcs.admin.blocks import BlocksDirectory
|
||||
|
||||
return BlocksDirectory(None).get_list_context(objects)
|
||||
|
||||
def get_mailtemplate_objects_context(self, objects):
|
||||
from wcs.admin.mail_templates import MailTemplatesDirectory
|
||||
|
||||
return MailTemplatesDirectory().get_list_context(objects)
|
||||
|
||||
def get_commenttemplate_objects_context(self, objects):
|
||||
from wcs.admin.comment_templates import CommentTemplatesDirectory
|
||||
|
||||
return CommentTemplatesDirectory().get_list_context(objects)
|
||||
|
||||
def get_datasource_objects_context(self, objects):
|
||||
from wcs.admin.data_sources import NamedDataSourcesDirectory
|
||||
|
||||
return NamedDataSourcesDirectory().get_list_context(objects, self.application)
|
||||
|
||||
def icon(self):
|
||||
return self._icon(size=(16, 16))
|
||||
|
||||
def logo(self):
|
||||
return self._icon(size=(64, 64))
|
||||
|
||||
def _icon(self, size):
|
||||
response = get_response()
|
||||
|
||||
if self.application.icon and self.application.icon.can_thumbnail():
|
||||
try:
|
||||
content = misc.get_thumbnail(
|
||||
self.application.icon.get_fs_filename(),
|
||||
content_type=self.application.icon.content_type,
|
||||
size=size,
|
||||
)
|
||||
response.set_content_type('image/png')
|
||||
return content
|
||||
except misc.ThumbnailError:
|
||||
raise errors.TraversalError()
|
||||
else:
|
||||
raise errors.TraversalError()
|
|
@ -237,7 +237,7 @@ class CardDefPage(FormDefPage):
|
|||
|
||||
|
||||
class CardsDirectory(FormsDirectory):
|
||||
_q_exports = ['', 'new', ('import', 'p_import'), 'categories', 'svg']
|
||||
_q_exports = ['', 'new', ('import', 'p_import'), 'categories', 'svg', ('application', 'applications_dir')]
|
||||
|
||||
category_class = CardDefCategory
|
||||
categories = CardDefCategoriesDirectory()
|
||||
|
|
|
@ -1883,6 +1883,7 @@ class FormDef(StorableObject):
|
|||
return odict
|
||||
|
||||
def __setstate__(self, dict):
|
||||
super().__setstate__(dict)
|
||||
self.__dict__ = dict
|
||||
self._workflow = None
|
||||
self._start_page = None
|
||||
|
@ -2077,6 +2078,7 @@ def clean_unused_files(publisher, **kwargs):
|
|||
known_filenames.update([x for x in glob.glob(os.path.join(publisher.app_dir, 'attachments/*/*'))])
|
||||
|
||||
def accumulate_filenames():
|
||||
from wcs.applications import Application
|
||||
from wcs.carddef import CardDef
|
||||
|
||||
for formdef in FormDef.select(ignore_migration=True) + CardDef.select(ignore_migration=True):
|
||||
|
@ -2094,6 +2096,10 @@ def clean_unused_files(publisher, **kwargs):
|
|||
if is_upload(field_data):
|
||||
yield field_data.get_fs_filename()
|
||||
|
||||
for application in Application.select():
|
||||
if is_upload(application.icon):
|
||||
yield application.icon.get_fs_filename()
|
||||
|
||||
used_filenames = set()
|
||||
for filename in accumulate_filenames():
|
||||
if not filename: # alternative storage
|
||||
|
|
|
@ -406,6 +406,8 @@ class WcsPublisher(QommonPublisher):
|
|||
sql.Audit.do_table()
|
||||
sql.TestDef.do_table()
|
||||
sql.TestResult.do_table()
|
||||
sql.Application.do_table()
|
||||
sql.ApplicationElement.do_table()
|
||||
sql.do_meta_table()
|
||||
from .carddef import CardDef
|
||||
from .formdef import FormDef
|
||||
|
@ -495,7 +497,15 @@ class WcsPublisher(QommonPublisher):
|
|||
def get_object_class(self, object_type):
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.categories import BlockCategory, CardDefCategory, Category, WorkflowCategory
|
||||
from wcs.categories import (
|
||||
BlockCategory,
|
||||
CardDefCategory,
|
||||
Category,
|
||||
CommentTemplateCategory,
|
||||
DataSourceCategory,
|
||||
MailTemplateCategory,
|
||||
WorkflowCategory,
|
||||
)
|
||||
from wcs.comment_templates import CommentTemplate
|
||||
from wcs.data_sources import NamedDataSource
|
||||
from wcs.formdef import FormDef
|
||||
|
@ -516,6 +526,9 @@ class WcsPublisher(QommonPublisher):
|
|||
CardDefCategory,
|
||||
WorkflowCategory,
|
||||
BlockCategory,
|
||||
MailTemplateCategory,
|
||||
CommentTemplateCategory,
|
||||
DataSourceCategory,
|
||||
):
|
||||
if klass.xml_root_node == object_type:
|
||||
return klass
|
||||
|
|
|
@ -701,7 +701,7 @@ def can_thumbnail(content_type):
|
|||
return False
|
||||
|
||||
|
||||
def get_thumbnail(filepath, content_type=None):
|
||||
def get_thumbnail(filepath, content_type=None, size=None):
|
||||
if not filepath or not can_thumbnail(content_type or ''):
|
||||
raise ThumbnailError()
|
||||
|
||||
|
@ -711,11 +711,16 @@ def get_thumbnail(filepath, content_type=None):
|
|||
os.mkdir(thumbs_dir)
|
||||
except FileExistsError:
|
||||
pass
|
||||
thumb_filepath = os.path.join(thumbs_dir, hashlib.sha256(force_bytes(filepath)).hexdigest())
|
||||
thumb_filepath = force_bytes(filepath)
|
||||
if size:
|
||||
thumb_filepath = '%s-%s-%s' % (thumb_filepath, *size)
|
||||
thumb_filepath = os.path.join(thumbs_dir, hashlib.sha256(force_bytes(thumb_filepath)).hexdigest())
|
||||
if os.path.exists(thumb_filepath):
|
||||
with open(thumb_filepath, 'rb') as f:
|
||||
return f.read()
|
||||
|
||||
size = size or (500, 300)
|
||||
|
||||
# generate thumbnail
|
||||
if content_type == 'application/pdf':
|
||||
try:
|
||||
|
@ -761,7 +766,7 @@ def get_thumbnail(filepath, content_type=None):
|
|||
image = image.rotate(90, expand=1)
|
||||
|
||||
try:
|
||||
image.thumbnail((500, 300))
|
||||
image.thumbnail(size)
|
||||
except (ValueError, SyntaxError):
|
||||
# PIL can raise syntax error on broken PNG files
|
||||
# * File "PIL/PngImagePlugin.py", line 119, in read
|
||||
|
|
|
@ -2635,3 +2635,7 @@ span.test-failure::before {
|
|||
font-family: FontAwesome;
|
||||
content: "\f00d"; /* times */
|
||||
}
|
||||
|
||||
.application-logo, .application-icon {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
import _thread
|
||||
import builtins
|
||||
import copy
|
||||
import copyreg
|
||||
import errno
|
||||
import operator
|
||||
|
@ -451,6 +452,17 @@ class StorableObject:
|
|||
def __init__(self, id=None):
|
||||
self.id = id
|
||||
|
||||
def __getstate__(self):
|
||||
odict = copy.copy(self.__dict__)
|
||||
if '_applications' in odict:
|
||||
del odict['_applications']
|
||||
return odict
|
||||
|
||||
def __setstate__(self, ndict):
|
||||
self.__dict__ = ndict
|
||||
if hasattr(self, '_applications'):
|
||||
delattr(self, '_applications')
|
||||
|
||||
def is_readonly(self):
|
||||
return getattr(self, 'readonly', False)
|
||||
|
||||
|
@ -1084,6 +1096,15 @@ class StorableObject:
|
|||
return None, None
|
||||
return snapshots[0].timestamp, snapshots[0].user_id
|
||||
|
||||
def get_applications(self):
|
||||
from wcs.applications import Application
|
||||
|
||||
if getattr(self, '_applications', None) is None:
|
||||
Application.load_for_object(self)
|
||||
return self._applications
|
||||
|
||||
applications = property(get_applications)
|
||||
|
||||
@classonlymethod
|
||||
def wipe(cls):
|
||||
tmpdir = tempfile.mkdtemp(prefix='wiping', dir=os.path.join(get_publisher().app_dir))
|
||||
|
|
201
wcs/sql.py
|
@ -4691,6 +4691,201 @@ class Audit(SqlMixin):
|
|||
return first_id
|
||||
|
||||
|
||||
class Application(SqlMixin):
|
||||
_table_name = 'applications'
|
||||
_table_static_fields = [
|
||||
('id', 'serial'),
|
||||
('slug', 'varchar'),
|
||||
('name', 'varchar'),
|
||||
('description', 'text'),
|
||||
('documentation_url', 'varchar'),
|
||||
('icon', 'bytea'),
|
||||
('version_number', 'varchar'),
|
||||
('version_notes', 'text'),
|
||||
('editable', 'boolean'),
|
||||
('visible', 'boolean'),
|
||||
('created_at', 'timestamptz'),
|
||||
('updated_at', 'timestamptz'),
|
||||
]
|
||||
|
||||
id = None
|
||||
|
||||
@classmethod
|
||||
@guard_postgres
|
||||
def do_table(cls):
|
||||
conn, cur = get_connection_and_cursor()
|
||||
table_name = cls._table_name
|
||||
|
||||
cur.execute(
|
||||
'''SELECT COUNT(*) FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = %s''',
|
||||
lguerin marked this conversation as resolved
Outdated
pducroquet
commented
Selon les volumes, effectivement, un index sur (object_type, object_id) ne serait pas de trop. Tout dépendra des volumes. Selon les volumes, effectivement, un index sur (object_type, object_id) ne serait pas de trop. Tout dépendra des volumes.
lguerin
commented
index ajouté index ajouté
|
||||
(table_name,),
|
||||
)
|
||||
if cur.fetchone()[0] == 0:
|
||||
cur.execute(
|
||||
'''CREATE TABLE %s (id SERIAL PRIMARY KEY,
|
||||
lguerin marked this conversation as resolved
Outdated
pducroquet
commented
Donc à chaque appel de do_table, on va supprimer et recréer la contrainte, et donc l'index lié. Pas fan du tout. Donc à chaque appel de do_table, on va supprimer et recréer la contrainte, et donc l'index lié. Pas fan du tout.
fpeters
commented
Il y aurait / tu aurais une syntaxe en "IF NOT EXISTS" pour faire ça ? Il y aurait / tu aurais une syntaxe en "IF NOT EXISTS" pour faire ça ?
lguerin
commented
j'ai fait une requête pour aller voir dans `information_schema.constraint_column_usage j'ai fait une requête pour aller voir dans `information_schema.constraint_column_usage
|
||||
slug VARCHAR NOT NULL,
|
||||
name VARCHAR NOT NULL,
|
||||
description TEXT,
|
||||
documentation_url VARCHAR,
|
||||
icon BYTEA,
|
||||
version_number VARCHAR NOT NULL,
|
||||
version_notes TEXT,
|
||||
editable BOOLEAN,
|
||||
visible BOOLEAN,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
|
||||
)'''
|
||||
% table_name
|
||||
)
|
||||
cur.execute('CREATE UNIQUE INDEX IF NOT EXISTS %s_slug ON %s (slug)' % (table_name, table_name))
|
||||
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
@guard_postgres
|
||||
def store(self):
|
||||
sql_dict = {x[0]: getattr(self, x[0], None) for x in self._table_static_fields if x[0] != 'id'}
|
||||
sql_dict['updated_at'] = localtime()
|
||||
if self.icon:
|
||||
sql_dict['icon'] = bytearray(pickle.dumps(self.icon, protocol=2))
|
||||
|
||||
conn, cur = get_connection_and_cursor()
|
||||
column_names = list(sql_dict.keys())
|
||||
if not self.id:
|
||||
sql_dict['created_at'] = sql_dict['updated_at']
|
||||
sql_statement = '''INSERT INTO %s (id, %s)
|
||||
VALUES (DEFAULT, %s)
|
||||
RETURNING id''' % (
|
||||
self._table_name,
|
||||
', '.join(column_names),
|
||||
', '.join(['%%(%s)s' % x for x in column_names]),
|
||||
)
|
||||
cur.execute(sql_statement, sql_dict)
|
||||
self.id = cur.fetchone()[0]
|
||||
else:
|
||||
sql_dict['id'] = self.id
|
||||
sql_statement = '''UPDATE %s SET %s WHERE id = %%(id)s RETURNING id''' % (
|
||||
self._table_name,
|
||||
', '.join(['%s = %%(%s)s' % (x, x) for x in column_names]),
|
||||
)
|
||||
cur.execute(sql_statement, sql_dict)
|
||||
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
@classmethod
|
||||
def _row2ob(cls, row, **kwargs):
|
||||
o = cls.__new__(cls)
|
||||
for field, value in zip(cls._table_static_fields, tuple(row)):
|
||||
if value and field[1] in ('bytea'):
|
||||
value = pickle_loads(value)
|
||||
setattr(o, field[0], value)
|
||||
return o
|
||||
|
||||
@classmethod
|
||||
def get_data_fields(cls):
|
||||
return []
|
||||
|
||||
|
||||
class ApplicationElement(SqlMixin):
|
||||
_table_name = 'application_elements'
|
||||
_table_static_fields = [
|
||||
('id', 'serial'),
|
||||
('application_id', 'integer'),
|
||||
('object_type', 'varchar'),
|
||||
('object_id', 'varchar'),
|
||||
('created_at', 'timestamptz'),
|
||||
('updated_at', 'timestamptz'),
|
||||
]
|
||||
|
||||
id = None
|
||||
|
||||
@classmethod
|
||||
@guard_postgres
|
||||
def do_table(cls):
|
||||
conn, cur = get_connection_and_cursor()
|
||||
table_name = cls._table_name
|
||||
|
||||
cur.execute(
|
||||
'''SELECT COUNT(*) FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = %s''',
|
||||
(table_name,),
|
||||
)
|
||||
if cur.fetchone()[0] == 0:
|
||||
cur.execute(
|
||||
'''CREATE TABLE %s (id SERIAL PRIMARY KEY,
|
||||
application_id INTEGER NOT NULL,
|
||||
object_type varchar NOT NULL,
|
||||
object_id varchar NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
|
||||
)'''
|
||||
% table_name
|
||||
)
|
||||
|
||||
cur.execute(
|
||||
'CREATE INDEX IF NOT EXISTS %s_object_idx ON %s (object_type, object_id)'
|
||||
% (table_name, table_name)
|
||||
)
|
||||
cur.execute(
|
||||
'''SELECT COUNT(*) FROM information_schema.constraint_column_usage
|
||||
WHERE table_name = %s
|
||||
AND constraint_name=%s''',
|
||||
(table_name, '%s_unique' % table_name),
|
||||
)
|
||||
if cur.fetchone()[0] == 0:
|
||||
cur.execute(
|
||||
'ALTER TABLE %s ADD CONSTRAINT %s_unique UNIQUE (application_id, object_type, object_id)'
|
||||
% (table_name, table_name)
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
@guard_postgres
|
||||
def store(self):
|
||||
sql_dict = {x[0]: getattr(self, x[0], None) for x in self._table_static_fields if x[0] != 'id'}
|
||||
sql_dict['updated_at'] = localtime()
|
||||
|
||||
conn, cur = get_connection_and_cursor()
|
||||
column_names = list(sql_dict.keys())
|
||||
if not self.id:
|
||||
sql_dict['created_at'] = sql_dict['updated_at']
|
||||
sql_statement = '''INSERT INTO %s (id, %s)
|
||||
VALUES (DEFAULT, %s)
|
||||
RETURNING id''' % (
|
||||
self._table_name,
|
||||
', '.join(column_names),
|
||||
', '.join(['%%(%s)s' % x for x in column_names]),
|
||||
)
|
||||
cur.execute(sql_statement, sql_dict)
|
||||
self.id = cur.fetchone()[0]
|
||||
else:
|
||||
sql_dict['id'] = self.id
|
||||
sql_statement = '''UPDATE %s SET %s WHERE id = %%(id)s RETURNING id''' % (
|
||||
self._table_name,
|
||||
', '.join(['%s = %%(%s)s' % (x, x) for x in column_names]),
|
||||
)
|
||||
cur.execute(sql_statement, sql_dict)
|
||||
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
@classmethod
|
||||
def _row2ob(cls, row, **kwargs):
|
||||
o = cls.__new__(cls)
|
||||
for attr, value in zip([x[0] for x in cls._table_static_fields], row):
|
||||
setattr(o, attr, value)
|
||||
return o
|
||||
|
||||
@classmethod
|
||||
def get_data_fields(cls):
|
||||
return []
|
||||
|
||||
|
||||
class classproperty:
|
||||
def __init__(self, f):
|
||||
self.f = f
|
||||
|
@ -5067,7 +5262,7 @@ def get_period_total(
|
|||
# latest migration, number + description (description is not used
|
||||
# programmaticaly but will make sure git conflicts if two migrations are
|
||||
# separately added with the same number)
|
||||
SQL_LEVEL = (83, 'add test_result table')
|
||||
SQL_LEVEL = (84, 'add application tables')
|
||||
|
||||
|
||||
def migrate_global_views(conn, cur):
|
||||
|
@ -5253,6 +5448,10 @@ def migrate():
|
|||
if sql_level < 83:
|
||||
# 83: add test_result table
|
||||
TestResult.do_table()
|
||||
if sql_level < 84:
|
||||
# 84: add application tables
|
||||
Application.do_table()
|
||||
ApplicationElement.do_table()
|
||||
if sql_level < 52:
|
||||
# 2: introduction of formdef_id in views
|
||||
# 5: add concerned_roles_array, is_at_endpoint and fts to views
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{% extends "wcs/backoffice/application_objects.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'wcs/backoffice/includes/blocks.html' %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,5 @@
|
|||
{% extends "wcs/backoffice/application_objects.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'wcs/backoffice/includes/comment-templates.html' %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,5 @@
|
|||
{% extends "wcs/backoffice/application_objects.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'wcs/backoffice/includes/data-sources.html' %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,5 @@
|
|||
{% extends "wcs/backoffice/application_objects.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'wcs/backoffice/includes/forms.html' %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,5 @@
|
|||
{% extends "wcs/backoffice/application_objects.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'wcs/backoffice/includes/mail-templates.html' %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "wcs/backoffice/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar-title %}
|
||||
{% if application.icon %}
|
||||
<img src="logo" alt="" class="application-logo" />
|
||||
{% endif %}
|
||||
{{ application.name }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for item in objects %}
|
||||
<li>
|
||||
<a href="{{ item.get_admin_url }}">{{ item.name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
|
@ -0,0 +1,5 @@
|
|||
{% extends "wcs/backoffice/application_objects.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'wcs/backoffice/includes/workflows.html' %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,5 @@
|
|||
{% extends "wcs/backoffice/application_objects.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'wcs/backoffice/includes/wscalls.html' with wscalls=objects %}
|
||||
{% endblock %}
|
|
@ -10,31 +10,10 @@
|
|||
|
||||
<h3>{% trans "Navigation" %}</h3>
|
||||
<a class="button button-paragraph" href="categories/">{% trans "Categories" %}</a>
|
||||
|
||||
{% include 'wcs/backoffice/includes/applications.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% if categories %}
|
||||
{% for category in categories %}
|
||||
{% if category.blocks %}
|
||||
<div class="section">
|
||||
<h2>{{ category.name }}</h2>
|
||||
<ul class="objects-list single-links">
|
||||
{% for block in category.blocks %}
|
||||
<li><a href="{{ block.id }}/">{{ block.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% elif blocks %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for block in blocks %}
|
||||
<li><a href="{{ block.id }}/">{{ block.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="infonotice">
|
||||
{% trans "There are no field blocks defined." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include 'wcs/backoffice/includes/blocks.html' %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
<a class="button button-paragraph" href="../forms/blocks/">{% trans "Fields blocks" %}</a>
|
||||
<a class="button button-paragraph" href="../forms/data-sources/">{% trans "Data sources" %}</a>
|
||||
{% endif %}
|
||||
|
||||
{% include 'wcs/backoffice/includes/applications.html' %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
{% block sidebar-content %}
|
||||
<h3>{% trans "Actions" %}</h3>
|
||||
<a class="button button-paragraph" rel="popup" href="new">{% trans "New Category" %}</a>
|
||||
|
||||
{% include 'wcs/backoffice/includes/applications.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
@ -13,7 +15,12 @@
|
|||
{% if categories %}
|
||||
<ul class="biglist sortable" id="category-list">
|
||||
{% for category in categories %}
|
||||
<li class="biglistitem" id="itemId_{{ category.id }}"><a href="{{ category.id }}/">{{ category.name }}</a></li>
|
||||
<li class="biglistitem" id="itemId_{{ category.id }}">
|
||||
<a href="{{ category.id }}/">
|
||||
{% include 'wcs/backoffice/includes/application_icons.html' with object=category %}
|
||||
{{ category.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
|
|
|
@ -10,31 +10,10 @@
|
|||
|
||||
<h3>{% trans "Navigation" %}</h3>
|
||||
<a class="button button-paragraph" href="categories/">{% trans "Categories" %}</a>
|
||||
|
||||
{% include 'wcs/backoffice/includes/applications.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% if categories %}
|
||||
{% for category in categories %}
|
||||
{% if category.comment_templates %}
|
||||
<div class="section">
|
||||
<h2>{{ category.name }}</h2>
|
||||
<ul class="objects-list single-links">
|
||||
{% for comment_template in category.comment_templates %}
|
||||
<li><a href="{{ comment_template.id }}/">{{ comment_template.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% elif comment_templates %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for comment_template in comment_templates %}
|
||||
<li><a href="{{ comment_template.id }}/">{{ comment_template.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="infonotice">
|
||||
{% trans "There are no comment templates defined." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include 'wcs/backoffice/includes/comment-templates.html' %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -16,85 +16,10 @@
|
|||
|
||||
<h3>{% trans "Navigation" %}</h3>
|
||||
<a class="button button-paragraph" href="categories/">{% trans "Categories" %}</a>
|
||||
|
||||
{% include 'wcs/backoffice/includes/applications.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if has_users %}
|
||||
<div class="section foldable">
|
||||
<h2>{% trans "Users Data Sources" %}</h2>
|
||||
{% if user_data_sources %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for data_source in user_data_sources %}
|
||||
<li><a href="{{ data_source.id }}/">{{ data_source.name }} ({{ data_source.slug }})</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div>
|
||||
{% trans "There are no users data sources defined." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if categories %}
|
||||
{% for category in categories %}
|
||||
{% if category.data_sources %}
|
||||
<div class="section foldable">
|
||||
<h2>{{ category.name }}</h2>
|
||||
<ul class="objects-list single-links">
|
||||
{% for data_source in category.data_sources %}
|
||||
<li><a href="{{ data_source.id }}/">{{ data_source.name }} ({{ data_source.slug }})</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% if data_sources %}
|
||||
<div class="section foldable">
|
||||
<h2>{% trans "Manually Configured Data Sources" %}</h2>
|
||||
<ul class="objects-list single-links">
|
||||
{% for data_source in data_sources %}
|
||||
<li><a href="{{ data_source.id }}/">{{ data_source.name }} ({{ data_source.slug }})</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<div>
|
||||
{% trans "There are no data sources defined." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div class="section foldable">
|
||||
<h2>{% trans "Data Sources from Card Models" %} - {% trans "automatically configured" %}</h2>
|
||||
{% if generated_data_sources %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for data_source in generated_data_sources %}
|
||||
<li><a href="{{ data_source.0.get_url }}{% if data_source.3 %}{{ data_source.3.get_url_slug }}/{% endif %}">{{ data_source.1 }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div>
|
||||
{% trans "There are no data sources from card models." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if has_chrono %}
|
||||
<div class="section foldable">
|
||||
<h2>{% trans "Agendas" %} - {% trans "automatically configured" %}</h2>
|
||||
{% if agenda_data_sources %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for data_source in agenda_data_sources %}
|
||||
<li><a href="{{ data_source.id }}/">{{ data_source.name }} ({{ data_source.slug }}){% if data_source.external_status == 'not-found' %} - <span class="extra-info">{% trans "not found" %}</span>{% endif %}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div>
|
||||
{% trans "There are no agendas." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include 'wcs/backoffice/includes/data-sources.html' %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -7,23 +7,7 @@
|
|||
{% if not has_roles %}
|
||||
<p>{% trans "You first have to define roles." %}</p>
|
||||
{% elif objects %}
|
||||
{% for category in categories %}
|
||||
{% if category.objects %}
|
||||
<div class="section">
|
||||
{% if category.name %}<h2>{{ category.name }}</h2>{% endif %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for item in category.objects %}
|
||||
<li {% if item.disabled %}class="disabled"{% endif %}><a href="{{ item.id }}/">{{ item.name }}
|
||||
{% if item.disabled and item.disabled_redirection %}
|
||||
<span class="extra-info">- {% trans "redirection" %}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% include 'wcs/backoffice/includes/forms.html' %}
|
||||
{% else %}
|
||||
<div class="infonotice">
|
||||
{% block no-objects %}
|
||||
|
@ -47,5 +31,7 @@
|
|||
<a class="button button-paragraph" href="blocks/">{% trans "Fields blocks" %}</a>
|
||||
<a class="button button-paragraph" href="data-sources/">{% trans "Data sources" %}</a>
|
||||
{% endif %}
|
||||
|
||||
{% include 'wcs/backoffice/includes/applications.html' %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{% for application in object.applications %}
|
||||
{% if application.icon %}
|
||||
<img src="application/{{ application.slug }}/icon" alt="" class="application-icon" width="16" />
|
||||
fpeters
commented
Pour les liens application/.../ je serais à préférer l'utilisation du slug, plutôt que l'id de l'application. Pour les Pour les liens application/.../ je serais à préférer l'utilisation du slug, plutôt que l'id de l'application.
Pour les `<img>`, comme on connait les dimension, on pourrait les mettre dans les attributs, width="16" height="16" ici; ça éviterait un petit temps de décalage du texte au moment où l'icône apparait.
lguerin
commented
fait, j'ai posé le slug dans toutes les urls, et ajouté une méthode get_by_slug à Application (qui n'hérite pas de StorableObject) fait, j'ai posé le slug dans toutes les urls, et ajouté une méthode get_by_slug à Application (qui n'hérite pas de StorableObject)
lguerin
commented
et j'ai posé un width=16 aussi, mais pas le height; l'icône n'est pas forcément carrée (mais elle fait 16px de large, toujours) et j'ai posé un width=16 aussi, mais pas le height; l'icône n'est pas forcément carrée (mais elle fait 16px de large, toujours)
|
||||
{% endif %}
|
||||
{% endfor %}
|
|
@ -0,0 +1,13 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% if applications %}
|
||||
<h3>{% trans "Applications" %}</h3>
|
||||
{% for application in applications %}
|
||||
<a class="button button-paragraph" href="application/{{ application.slug }}/">
|
||||
{% if application.icon %}
|
||||
<img src="application/{{ application.slug }}/icon" alt="" class="application-icon" width="16" />
|
||||
{% endif %}
|
||||
{{ application.name }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
|
@ -0,0 +1,35 @@
|
|||
{% load i18n %}
|
||||
{% if categories %}
|
||||
{% for category in categories %}
|
||||
{% if category.blocks %}
|
||||
<div class="section">
|
||||
<h2>{{ category.name }}</h2>
|
||||
<ul class="objects-list single-links">
|
||||
{% for block in category.blocks %}
|
||||
<li>
|
||||
<a href="{{ block.get_admin_url }}">
|
||||
{% if not application %}{% include 'wcs/backoffice/includes/application_icons.html' with object=block %}{% endif %}
|
||||
{{ block.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% elif blocks %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for block in blocks %}
|
||||
<li>
|
||||
<a href="{{ block.get_admin_url }}">
|
||||
{% if not application %}{% include 'wcs/backoffice/includes/application_icons.html' with object=block %}{% endif %}
|
||||
{{ block.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="infonotice">
|
||||
{% trans "There are no field blocks defined." %}
|
||||
</div>
|
||||
{% endif %}
|
|
@ -0,0 +1,35 @@
|
|||
{% load i18n %}
|
||||
{% if categories %}
|
||||
{% for category in categories %}
|
||||
{% if category.comment_templates %}
|
||||
<div class="section">
|
||||
<h2>{{ category.name }}</h2>
|
||||
<ul class="objects-list single-links">
|
||||
{% for comment_template in category.comment_templates %}
|
||||
<li>
|
||||
<a href="{{ comment_template.get_admin_url }}">
|
||||
{% if not application %}{% include 'wcs/backoffice/includes/application_icons.html' with object=comment_template %}{% endif %}
|
||||
{{ comment_template.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% elif comment_templates %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for comment_template in comment_templates %}
|
||||
<li>
|
||||
<a href="{{ comment_template.get_admin_url }}">
|
||||
{% if not application %}{% include 'wcs/backoffice/includes/application_icons.html' with object=comment_template %}{% endif %}
|
||||
{{ comment_template.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="infonotice">
|
||||
{% trans "There are no comment templates defined." %}
|
||||
</div>
|
||||
{% endif %}
|
|
@ -0,0 +1,102 @@
|
|||
{% load i18n %}
|
||||
<div class="section foldable">
|
||||
<h2>{% trans "Users Data Sources" %}</h2>
|
||||
{% if user_data_sources %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for data_source in user_data_sources %}
|
||||
<li>
|
||||
<a href="{{ data_source.get_admin_url }}">
|
||||
{% if not application %}{% include 'wcs/backoffice/includes/application_icons.html' with object=data_source %}{% endif %}
|
||||
{{ data_source.name }} ({{ data_source.slug }})
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div>
|
||||
{% trans "There are no users data sources defined." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if categories %}
|
||||
{% for category in categories %}
|
||||
{% if category.data_sources %}
|
||||
<div class="section foldable">
|
||||
<h2>{{ category.name }}</h2>
|
||||
<ul class="objects-list single-links">
|
||||
{% for data_source in category.data_sources %}
|
||||
<li>
|
||||
<a href="{{ data_source.get_admin_url }}">
|
||||
{% if not application %}{% include 'wcs/backoffice/includes/application_icons.html' with object=data_source %}{% endif %}
|
||||
{{ data_source.name }} ({{ data_source.slug }})
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% if data_sources %}
|
||||
<div class="section foldable">
|
||||
<h2>{% trans "Manually Configured Data Sources" %}</h2>
|
||||
<ul class="objects-list single-links">
|
||||
{% for data_source in data_sources %}
|
||||
<li>
|
||||
<a href="{{ data_source.get_admin_url }}">
|
||||
{% if not application %}{% include 'wcs/backoffice/includes/application_icons.html' with object=data_source %}{% endif %}
|
||||
{{ data_source.name }} ({{ data_source.slug }})
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<div>
|
||||
{% trans "There are no data sources defined." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div class="section foldable">
|
||||
<h2>{% trans "Data Sources from Card Models" %} - {% trans "automatically configured" %}</h2>
|
||||
{% if generated_data_sources %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for data_source in generated_data_sources %}
|
||||
<li>
|
||||
<a href="{{ data_source.0.get_url }}{% if data_source.3 %}{{ data_source.3.get_url_slug }}/{% endif %}">
|
||||
{% if not application %}{% include 'wcs/backoffice/includes/application_icons.html' with object=data_source.0 %}{% endif %}
|
||||
{{ data_source.1 }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div>
|
||||
{% trans "There are no data sources from card models." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if has_chrono %}
|
||||
<div class="section foldable">
|
||||
<h2>{% trans "Agendas" %} - {% trans "automatically configured" %}</h2>
|
||||
{% if agenda_data_sources %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for data_source in agenda_data_sources %}
|
||||
<li>
|
||||
<a href="{{ data_source.get_admin_url }}">
|
||||
{% if not application %}{% include 'wcs/backoffice/includes/application_icons.html' with object=data_source %}{% endif %}
|
||||
{{ data_source.name }} ({{ data_source.slug }}){% if data_source.external_status == 'not-found' %} - <span class="extra-info">{% trans "not found" %}</span>{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div>
|
||||
{% trans "There are no agendas." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
|
@ -0,0 +1,20 @@
|
|||
{% load i18n %}
|
||||
{% for category in categories %}
|
||||
{% if category.objects %}
|
||||
<div class="section">
|
||||
{% if category.name %}<h2>{{ category.name }}</h2>{% endif %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for item in category.objects %}
|
||||
<li {% if item.disabled %}class="disabled"{% endif %}><a href="{{ item.get_admin_url }}">
|
||||
{% if not application %}{% include 'wcs/backoffice/includes/application_icons.html' with object=item %}{% endif %}
|
||||
{{ item.name }}
|
||||
{% if item.disabled and item.disabled_redirection %}
|
||||
<span class="extra-info">- {% trans "redirection" %}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
|
@ -0,0 +1,35 @@
|
|||
{% load i18n %}
|
||||
{% if categories %}
|
||||
{% for category in categories %}
|
||||
{% if category.mail_templates %}
|
||||
<div class="section">
|
||||
<h2>{{ category.name }}</h2>
|
||||
<ul class="objects-list single-links">
|
||||
{% for mail_template in category.mail_templates %}
|
||||
<li>
|
||||
<a href="{{ mail_template.get_admin_url }}">
|
||||
{% if not application %}{% include 'wcs/backoffice/includes/application_icons.html' with object=mail_template %}{% endif %}
|
||||
{{ mail_template.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% elif mail_templates %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for mail_template in mail_templates %}
|
||||
<li>
|
||||
<a href="{{ mail_template.get_admin_url }}">
|
||||
{% if not application %}{% include 'wcs/backoffice/includes/application_icons.html' with object=mail_template %}{% endif %}
|
||||
{{ mail_template.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="infonotice">
|
||||
{% trans "There are no mail templates defined." %}
|
||||
</div>
|
||||
{% endif %}
|
|
@ -0,0 +1,20 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% for category in categories %}
|
||||
{% if category.objects %}
|
||||
<div class="section">
|
||||
{% if category.name %}<h2>{{ category.name }}</h2>{% endif %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for item in category.objects %}
|
||||
<li class="{{ item.css_class }}">
|
||||
<a href="{{ item.get_admin_url }}">
|
||||
{% if not application %}{% include 'wcs/backoffice/includes/application_icons.html' with object=item %}{% endif %}
|
||||
{{ item.name }}
|
||||
{% if item.usage_label %}<span class="badge">{{ item.usage_label }}</span>{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
|
@ -0,0 +1,11 @@
|
|||
{% load i18n %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for wscall in wscalls %}
|
||||
<li>
|
||||
<a href="{{ wscall.id }}/">
|
||||
{% include 'wcs/backoffice/includes/application_icons.html' with object=wscall %}
|
||||
{{ wscall.name }} ({{ wscall.slug }})
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
|
@ -10,31 +10,10 @@
|
|||
|
||||
<h3>{% trans "Navigation" %}</h3>
|
||||
<a class="button button-paragraph" href="categories/">{% trans "Categories" %}</a>
|
||||
|
||||
{% include 'wcs/backoffice/includes/applications.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% if categories %}
|
||||
{% for category in categories %}
|
||||
{% if category.mail_templates %}
|
||||
<div class="section">
|
||||
<h2>{{ category.name }}</h2>
|
||||
<ul class="objects-list single-links">
|
||||
{% for mail_template in category.mail_templates %}
|
||||
<li><a href="{{ mail_template.id }}/">{{ mail_template.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% elif mail_templates %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for mail_template in mail_templates %}
|
||||
<li><a href="{{ mail_template.id }}/">{{ mail_template.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="infonotice">
|
||||
{% trans "There are no mail templates defined." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include 'wcs/backoffice/includes/mail-templates.html' %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -4,21 +4,7 @@
|
|||
{% block appbar-title %}{% trans "Workflows" %}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% for category in categories %}
|
||||
{% if category.objects %}
|
||||
<div class="section">
|
||||
{% if category.name %}<h2>{{ category.name }}</h2>{% endif %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for item in category.objects %}
|
||||
<li class="{{ item.css_class }}"><a href="{{ item.id }}/">{{ item.name }}
|
||||
{% if item.usage_label %}<span class="badge">{{ item.usage_label }}</span>{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% include 'wcs/backoffice/includes/workflows.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar-content %}
|
||||
|
@ -33,4 +19,6 @@
|
|||
<a class="button button-paragraph" href="mail-templates/">{% trans "Mail Templates" %}</a>
|
||||
<a class="button button-paragraph" href="comment-templates/">{% trans "Comment Templates" %}</a>
|
||||
{% endif %}
|
||||
|
||||
{% include 'wcs/backoffice/includes/applications.html' %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -7,15 +7,13 @@
|
|||
<h3>{% trans "Actions" %}</h3>
|
||||
<a class="button button-paragraph" href="new">{% trans "New webservice call" %}</a>
|
||||
<a class="button button-paragraph" rel="popup" href="import">{% trans "Import" %}</a>
|
||||
|
||||
{% include 'wcs/backoffice/includes/applications.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% if wscalls %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for wscall in wscalls %}
|
||||
<li><a href="{{ wscall.id }}/">{{ wscall.name }} ({{ wscall.slug }})</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% include 'wcs/backoffice/includes/wscalls.html' %}
|
||||
{% else %}
|
||||
<div class="infonotice">
|
||||
{% trans "There are no webservice calls defined." %}
|
||||
|
|
|
@ -26,6 +26,8 @@ urlpatterns = [
|
|||
path('__provision__/', api.provisionning),
|
||||
path('api/export-import/', api_export_import.index, name='api-export-import'),
|
||||
path('api/export-import/bundle-import/', api_export_import.bundle_import),
|
||||
path('api/export-import/bundle-declare/', api_export_import.bundle_declare),
|
||||
path('api/export-import/unlink/', api_export_import.unlink),
|
||||
re_path(
|
||||
r'^api/export-import/(?P<objects>[\w-]+)/$',
|
||||
api_export_import.objects_list,
|
||||
fpeters
commented
En évolutions diverses, je note ici (mais pas pour ce ticket déjà bien chargé),
En évolutions diverses, je note ici (mais pas pour ce ticket déjà bien chargé),
* affichage de l'info de l'application sur la page de l'objet en lui-même (pas uniquement sur la page d'index); ça passe peut-être d'abord par revoir/unifier les barres latérales.
lguerin
commented
j'y pensais aussi, mais pour ce premier ticket je me suis dit que c'était déjà bien assez j'y pensais aussi, mais pour ce premier ticket je me suis dit que c'était déjà bien assez
|
||||
|
|
Dans wcs/publisher.py, il manque un appel aux .do_table() dans initialize_sql; (manque aussi TestDef et TestResult, on dirait, je vais faire un ticket).
Idéalement il n'y aurait pas à répéter ça, les tests appelleraient initialize_sql, je ne sais plus ce qui compliquait ça. (mais ça relève d'un autre ticket également, ici juste copier les deux lignes dans publisher.py sera très bien).
ajouté dans wcs/publisher.py