backoffice: svg of carddefs relations (#57953)
gitea-wip/wcs/pipeline/head There was a failure building this commit
Details
gitea-wip/wcs/pipeline/head There was a failure building this commit
Details
This commit is contained in:
parent
23f4247fcc
commit
5ffce24766
|
@ -6,6 +6,7 @@ from webtest import Upload
|
|||
|
||||
from wcs import fields
|
||||
from wcs.admin.settings import UserFieldsFormDef
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.categories import CardDefCategory
|
||||
from wcs.formdef import FormDef
|
||||
|
@ -636,3 +637,155 @@ def test_card_category_management_roles(pub, backoffice_user, backoffice_role):
|
|||
resp = app.get(carddata.get_backoffice_url())
|
||||
assert 'inspect' in resp.text
|
||||
resp = app.get(carddata.get_backoffice_url() + 'inspect')
|
||||
|
||||
|
||||
def test_cards_svg(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
CardDefCategory.wipe()
|
||||
cat1 = CardDefCategory(name='Foo')
|
||||
cat1.store()
|
||||
cat2 = CardDefCategory(name='Foo')
|
||||
cat2.store()
|
||||
|
||||
ds1 = {'type': 'carddef:card-1-2'}
|
||||
ds2 = {'type': 'carddef:card-2-2'}
|
||||
ds3 = {'type': 'carddef:card-3-2'}
|
||||
|
||||
BlockDef.wipe()
|
||||
block1 = BlockDef()
|
||||
block1.name = 'block 1'
|
||||
block1.fields = [
|
||||
fields.StringField(id='1', label='string', type='string', varname='block foo 1', data_source=ds1),
|
||||
fields.ItemField(id='2', label='item', type='item', varname='block foo 2', data_source=ds1),
|
||||
fields.ItemsField(id='3', label='items', type='items', varname='block foo 3', data_source=ds1),
|
||||
fields.StringField(id='10', label='string', type='string', varname='block fooo 10', data_source=ds2),
|
||||
fields.ItemField(id='20', label='item', type='item', varname='block fooo 20', data_source=ds2),
|
||||
fields.ItemsField(id='30', label='items', type='items', varname='block fooo 30', data_source=ds2),
|
||||
]
|
||||
block1.store()
|
||||
block2 = BlockDef()
|
||||
block2.name = 'block 2'
|
||||
block2.fields = [
|
||||
fields.StringField(id='1', label='string', type='string', varname='block bar 1', data_source=ds2),
|
||||
fields.ItemField(id='2', label='item', type='item', varname='block bar 2', data_source=ds2),
|
||||
fields.ItemsField(id='3', label='items', type='items', varname='block bar 3', data_source=ds2),
|
||||
]
|
||||
block2.store()
|
||||
block3 = BlockDef()
|
||||
block3.name = 'block 3'
|
||||
block3.fields = [
|
||||
fields.StringField(id='1', label='string', type='string', varname='block baz 1', data_source=ds3),
|
||||
fields.ItemField(id='2', label='item', type='item', varname='block baz 2', data_source=ds3),
|
||||
fields.ItemsField(id='3', label='items', type='items', varname='block baz 3', data_source=ds3),
|
||||
]
|
||||
block3.store()
|
||||
|
||||
CardDef.wipe()
|
||||
|
||||
carddef11 = CardDef()
|
||||
carddef11.name = 'card 1-1'
|
||||
carddef11.category_id = cat1.id
|
||||
carddef11.fields = [
|
||||
fields.StringField(id='1', label='string', type='string', varname='foo 1', data_source=ds1),
|
||||
fields.ItemField(id='2', label='item', type='item', varname='foo 2', data_source=ds1),
|
||||
fields.ItemsField(id='3', label='items', type='items', varname='foo 3', data_source=ds1),
|
||||
fields.BlockField(id='4', label='block', type='block:%s' % block1.slug),
|
||||
fields.StringField(id='10', label='string', type='string', varname='fooo 10', data_source=ds2),
|
||||
fields.ItemField(id='20', label='item', type='item', varname='fooo 20', data_source=ds2),
|
||||
fields.ItemsField(id='30', label='items', type='items', varname='fooo 30', data_source=ds2),
|
||||
fields.BlockField(id='40', label='block', type='block:%s' % block2.slug),
|
||||
]
|
||||
carddef11.store()
|
||||
carddef12 = CardDef()
|
||||
carddef12.name = 'card 1-2'
|
||||
carddef12.category_id = cat1.id
|
||||
carddef12.fields = []
|
||||
carddef12.store()
|
||||
carddef13 = CardDef()
|
||||
carddef13.name = 'card 1-3'
|
||||
carddef13.category_id = cat1.id
|
||||
carddef13.fields = []
|
||||
carddef13.store()
|
||||
|
||||
carddef21 = CardDef()
|
||||
carddef21.name = 'card 2-1'
|
||||
carddef21.category_id = cat2.id
|
||||
carddef21.fields = [
|
||||
fields.StringField(id='1', label='string', type='string', varname='bar 1', data_source=ds2),
|
||||
fields.ItemField(id='2', label='item', type='item', varname='bar 2', data_source=ds2),
|
||||
fields.ItemsField(id='3', label='items', type='items', varname='bar 3', data_source=ds2),
|
||||
fields.BlockField(id='4', label='block', type='block:%s' % block2.slug),
|
||||
]
|
||||
carddef21.store()
|
||||
carddef22 = CardDef()
|
||||
carddef22.name = 'card 2-2'
|
||||
carddef22.category_id = cat2.id
|
||||
carddef22.fields = []
|
||||
carddef22.store()
|
||||
|
||||
carddef31 = CardDef()
|
||||
carddef31.name = 'card 3-1'
|
||||
carddef31.fields = [
|
||||
fields.StringField(id='1', label='string', type='string', varname='baz 1', data_source=ds3),
|
||||
fields.ItemField(id='2', label='item', type='item', varname='baz 2', data_source=ds3),
|
||||
fields.ItemsField(id='3', label='items', type='items', varname='baz 3', data_source=ds3),
|
||||
fields.BlockField(id='4', label='block', type='block:%s' % block3.slug),
|
||||
]
|
||||
carddef31.store()
|
||||
carddef32 = CardDef()
|
||||
carddef32.name = 'card 3-2'
|
||||
carddef32.fields = []
|
||||
carddef32.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/cards/svg')
|
||||
# cards
|
||||
assert '<title>card_card_1_1</title' in resp
|
||||
assert '<title>card_card_1_2</title' in resp
|
||||
assert '<title>card_card_1_3</title' not in resp
|
||||
assert '<title>card_card_2_1</title' in resp
|
||||
assert '<title>card_card_2_2</title' in resp
|
||||
assert '<title>card_card_3_1</title' in resp
|
||||
assert '<title>card_card_3_2</title' in resp
|
||||
# and relations
|
||||
assert resp.text.count('<title>card_card_1_1->card_card_1_2</title>') == 6
|
||||
assert resp.text.count('<title>card_card_1_1->card_card_2_2</title>') == 9
|
||||
assert resp.text.count('<title>card_card_2_1->card_card_2_2</title>') == 6
|
||||
assert resp.text.count('<title>card_card_3_1->card_card_3_2</title>') == 6
|
||||
|
||||
resp = app.get('/backoffice/cards/svg?show-orphans=on')
|
||||
assert '<title>card_card_1_3</title' in resp
|
||||
|
||||
resp = app.get('/backoffice/cards/categories/%s/svg' % cat1.id)
|
||||
# cards
|
||||
assert '<title>card_card_1_1</title' in resp
|
||||
assert '<title>card_card_1_2</title' in resp
|
||||
assert '<title>card_card_1_3</title' not in resp
|
||||
assert '<title>card_card_2_1</title' not in resp
|
||||
assert '<title>card_card_2_2</title' not in resp
|
||||
assert '<title>card_card_3_1</title' not in resp
|
||||
assert '<title>card_card_3_2</title' not in resp
|
||||
# and relations
|
||||
assert resp.text.count('<title>card_card_1_1->card_card_1_2</title>') == 6
|
||||
assert resp.text.count('<title>card_card_1_1->card_card_2_2</title>') == 0
|
||||
assert resp.text.count('<title>card_card_2_1->card_card_2_2</title>') == 0
|
||||
assert resp.text.count('<title>card_card_3_1->card_card_3_2</title>') == 0
|
||||
|
||||
resp = app.get('/backoffice/cards/categories/%s/svg?show-orphans=on' % cat1.id)
|
||||
assert '<title>card_card_1_3</title' in resp
|
||||
|
||||
resp = app.get('/backoffice/cards/categories/%s/svg' % cat2.id)
|
||||
# cards
|
||||
assert '<title>card_card_1_1</title' not in resp
|
||||
assert '<title>card_card_1_2</title' not in resp
|
||||
assert '<title>card_card_2_1</title' in resp
|
||||
assert '<title>card_card_2_2</title' in resp
|
||||
assert '<title>card_card_3_1</title' not in resp
|
||||
assert '<title>card_card_3_2</title' not in resp
|
||||
# and relations
|
||||
assert resp.text.count('<title>card_card_1_1->card_card_1_2</title>') == 0
|
||||
assert resp.text.count('<title>card_card_1_1->card_card_2_2</title>') == 0
|
||||
assert resp.text.count('<title>card_card_2_1->card_card_2_2</title>') == 6
|
||||
assert resp.text.count('<title>card_card_3_1->card_card_3_2</title>') == 0
|
||||
|
|
|
@ -18,7 +18,7 @@ from quixote import get_publisher, get_request, get_response, redirect
|
|||
from quixote.directory import Directory
|
||||
from quixote.html import TemplateIO, htmltext
|
||||
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.carddef import CardDef, get_cards_graph
|
||||
from wcs.categories import CardDefCategory, Category, WorkflowCategory
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.qommon import _, misc, template
|
||||
|
@ -253,6 +253,13 @@ class CardDefCategoryPage(CategoryPage):
|
|||
object_class = CardDef
|
||||
usage_title = _('Card models in this category')
|
||||
empty_message = _('No card model associated to this category.')
|
||||
_q_exports = CategoryPage._q_exports + ['svg']
|
||||
|
||||
def svg(self):
|
||||
response = get_response()
|
||||
response.set_content_type('image/svg+xml')
|
||||
show_orphans = get_request().form.get('show-orphans') == 'on'
|
||||
return get_cards_graph(category=self.category, show_orphans=show_orphans)
|
||||
|
||||
|
||||
class WorkflowCategoryPage(CategoryPage):
|
||||
|
|
|
@ -14,13 +14,13 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from quixote import get_publisher, get_response, get_session, redirect
|
||||
from quixote import get_publisher, get_request, get_response, get_session, redirect
|
||||
from quixote.html import TemplateIO, htmltext
|
||||
|
||||
from wcs.admin import utils
|
||||
from wcs.admin.categories import CardDefCategoriesDirectory
|
||||
from wcs.admin.forms import FormDefPage, FormDefUI, FormsDirectory, OptionsDirectory
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.carddef import CardDef, get_cards_graph
|
||||
from wcs.categories import CardDefCategory
|
||||
from wcs.workflows import Workflow
|
||||
|
||||
|
@ -225,7 +225,7 @@ class CardDefPage(FormDefPage):
|
|||
|
||||
|
||||
class CardsDirectory(FormsDirectory):
|
||||
_q_exports = ['', 'new', ('import', 'p_import'), 'categories']
|
||||
_q_exports = ['', 'new', ('import', 'p_import'), 'categories', 'svg']
|
||||
|
||||
category_class = CardDefCategory
|
||||
categories = CardDefCategoriesDirectory()
|
||||
|
@ -293,5 +293,11 @@ class CardsDirectory(FormsDirectory):
|
|||
self.imported_formdef.store()
|
||||
return response
|
||||
|
||||
def svg(self):
|
||||
response = get_response()
|
||||
response.set_content_type('image/svg+xml')
|
||||
show_orphans = get_request().form.get('show-orphans') == 'on'
|
||||
return get_cards_graph(show_orphans=show_orphans)
|
||||
|
||||
def _q_lookup(self, component):
|
||||
return self.formdef_page_class(component)
|
||||
|
|
|
@ -14,9 +14,12 @@
|
|||
# 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 io
|
||||
import sys
|
||||
import types
|
||||
from subprocess import PIPE, Popen
|
||||
|
||||
from django.utils.encoding import force_bytes
|
||||
from quixote import get_publisher
|
||||
|
||||
from wcs.carddata import CardData
|
||||
|
@ -282,3 +285,76 @@ class CardDef(FormDef):
|
|||
continue
|
||||
varnames.extend(Field.get_referenced_varnames(formdef, criteria.value))
|
||||
return varnames
|
||||
|
||||
|
||||
def get_cards_graph(category=None, show_orphans=False):
|
||||
out = io.StringIO()
|
||||
out.write('digraph main {\n')
|
||||
out.write('node [shape=box,style=filled];\n')
|
||||
out.write('edge [];\n')
|
||||
|
||||
criterias = []
|
||||
if category is not None:
|
||||
criterias = [Equal('category_id', str(category.id))]
|
||||
carddefs = CardDef.select(clause=criterias)
|
||||
carddefs_slugs = [c.url_name for c in carddefs]
|
||||
|
||||
def check_relations(carddef_ref, fields, check_blocks=True, prefix=''):
|
||||
cardinality = {
|
||||
'string': '1..n',
|
||||
'item': '1..n',
|
||||
'items': 'n..n',
|
||||
}
|
||||
for field in fields:
|
||||
data_source = getattr(field, 'data_source', None)
|
||||
if data_source and data_source['type'].startswith('carddef:'):
|
||||
slug = field.data_source['type'].split(':')[1]
|
||||
if not show_orphans and slug not in carddefs_slugs:
|
||||
# don't report extra category relations
|
||||
continue
|
||||
label = '%s%s %s' % (prefix, field.varname or field.label, cardinality.get(field.key))
|
||||
yield '%s -> card_%s [label="%s"];' % (
|
||||
carddef_ref,
|
||||
slug.replace('-', '_'),
|
||||
label,
|
||||
)
|
||||
if check_blocks and field.key == 'block':
|
||||
yield from check_relations(
|
||||
carddef_ref,
|
||||
field.block.fields,
|
||||
check_blocks=False,
|
||||
prefix='%s (block) ' % (field.varname or field.label),
|
||||
)
|
||||
|
||||
records = []
|
||||
relations = []
|
||||
|
||||
for carddef in carddefs:
|
||||
carddef_ref = 'card_%s' % carddef.url_name.replace('-', '_')
|
||||
record = '%s [shape=record,label="<card>%s' % (carddef_ref, carddef.name)
|
||||
relations += list(check_relations(carddef_ref, carddef.get_all_fields()))
|
||||
record += '"];'
|
||||
records.append(record)
|
||||
if not show_orphans:
|
||||
for record in records[:]:
|
||||
if not [x for x in relations if record.split()[0] in x.split()]:
|
||||
records.remove(record)
|
||||
|
||||
for record in records:
|
||||
out.write('%s\n' % record)
|
||||
|
||||
for relation in relations:
|
||||
out.write('%s\n' % relation)
|
||||
|
||||
out.write('}\n')
|
||||
|
||||
out = out.getvalue()
|
||||
try:
|
||||
with Popen(['dot', '-Tsvg'], stdin=PIPE, stdout=PIPE) as process:
|
||||
out = process.communicate(force_bytes(out))[0]
|
||||
if process.returncode != 0:
|
||||
return ''
|
||||
except OSError:
|
||||
return ''
|
||||
|
||||
return out
|
||||
|
|
Loading…
Reference in New Issue