backoffice: svg of carddefs relations (#57953)
gitea-wip/wcs/pipeline/head There was a failure building this commit Details

This commit is contained in:
Lauréline Guérin 2021-10-25 15:01:31 +02:00
parent 23f4247fcc
commit 5ffce24766
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
4 changed files with 246 additions and 4 deletions

View File

@ -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&#45;&gt;card_card_1_2</title>') == 6
assert resp.text.count('<title>card_card_1_1&#45;&gt;card_card_2_2</title>') == 9
assert resp.text.count('<title>card_card_2_1&#45;&gt;card_card_2_2</title>') == 6
assert resp.text.count('<title>card_card_3_1&#45;&gt;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&#45;&gt;card_card_1_2</title>') == 6
assert resp.text.count('<title>card_card_1_1&#45;&gt;card_card_2_2</title>') == 0
assert resp.text.count('<title>card_card_2_1&#45;&gt;card_card_2_2</title>') == 0
assert resp.text.count('<title>card_card_3_1&#45;&gt;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&#45;&gt;card_card_1_2</title>') == 0
assert resp.text.count('<title>card_card_1_1&#45;&gt;card_card_2_2</title>') == 0
assert resp.text.count('<title>card_card_2_1&#45;&gt;card_card_2_2</title>') == 6
assert resp.text.count('<title>card_card_3_1&#45;&gt;card_card_3_2</title>') == 0

View File

@ -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):

View File

@ -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)

View File

@ -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