0001-backoffice-svg-of-carddefs-relations-57953.patch
tests/admin_pages/test_card.py | ||
---|---|---|
6 | 6 | |
7 | 7 |
from wcs import fields |
8 | 8 |
from wcs.admin.settings import UserFieldsFormDef |
9 |
from wcs.blocks import BlockDef |
|
9 | 10 |
from wcs.carddef import CardDef |
10 | 11 |
from wcs.categories import CardDefCategory |
11 | 12 |
from wcs.formdef import FormDef |
... | ... | |
636 | 637 |
resp = app.get(carddata.get_backoffice_url()) |
637 | 638 |
assert 'inspect' in resp.text |
638 | 639 |
resp = app.get(carddata.get_backoffice_url() + 'inspect') |
640 | ||
641 | ||
642 |
def test_cards_svg(pub): |
|
643 |
create_superuser(pub) |
|
644 | ||
645 |
CardDefCategory.wipe() |
|
646 |
cat1 = CardDefCategory(name='Foo') |
|
647 |
cat1.store() |
|
648 |
cat2 = CardDefCategory(name='Foo') |
|
649 |
cat2.store() |
|
650 | ||
651 |
ds1 = {'type': 'carddef:card-1-2'} |
|
652 |
ds2 = {'type': 'carddef:card-2-2'} |
|
653 |
ds3 = {'type': 'carddef:card-3-2'} |
|
654 | ||
655 |
BlockDef.wipe() |
|
656 |
block1 = BlockDef() |
|
657 |
block1.name = 'block 1' |
|
658 |
block1.fields = [ |
|
659 |
fields.StringField(id='1', label='string', type='string', varname='block foo 1', data_source=ds1), |
|
660 |
fields.ItemField(id='2', label='item', type='item', varname='block foo 2', data_source=ds1), |
|
661 |
fields.ItemsField(id='3', label='items', type='items', varname='block foo 3', data_source=ds1), |
|
662 |
fields.StringField(id='10', label='string', type='string', varname='block fooo 10', data_source=ds2), |
|
663 |
fields.ItemField(id='20', label='item', type='item', varname='block fooo 20', data_source=ds2), |
|
664 |
fields.ItemsField(id='30', label='items', type='items', varname='block fooo 30', data_source=ds2), |
|
665 |
] |
|
666 |
block1.store() |
|
667 |
block2 = BlockDef() |
|
668 |
block2.name = 'block 2' |
|
669 |
block2.fields = [ |
|
670 |
fields.StringField(id='1', label='string', type='string', varname='block bar 1', data_source=ds2), |
|
671 |
fields.ItemField(id='2', label='item', type='item', varname='block bar 2', data_source=ds2), |
|
672 |
fields.ItemsField(id='3', label='items', type='items', varname='block bar 3', data_source=ds2), |
|
673 |
] |
|
674 |
block2.store() |
|
675 |
block3 = BlockDef() |
|
676 |
block3.name = 'block 3' |
|
677 |
block3.fields = [ |
|
678 |
fields.StringField(id='1', label='string', type='string', varname='block baz 1', data_source=ds3), |
|
679 |
fields.ItemField(id='2', label='item', type='item', varname='block baz 2', data_source=ds3), |
|
680 |
fields.ItemsField(id='3', label='items', type='items', varname='block baz 3', data_source=ds3), |
|
681 |
] |
|
682 |
block3.store() |
|
683 | ||
684 |
CardDef.wipe() |
|
685 | ||
686 |
carddef11 = CardDef() |
|
687 |
carddef11.name = 'card 1-1' |
|
688 |
carddef11.category_id = cat1.id |
|
689 |
carddef11.fields = [ |
|
690 |
fields.StringField(id='1', label='string', type='string', varname='foo 1', data_source=ds1), |
|
691 |
fields.ItemField(id='2', label='item', type='item', varname='foo 2', data_source=ds1), |
|
692 |
fields.ItemsField(id='3', label='items', type='items', varname='foo 3', data_source=ds1), |
|
693 |
fields.BlockField(id='4', label='block', type='block:%s' % block1.slug), |
|
694 |
fields.StringField(id='10', label='string', type='string', varname='fooo 10', data_source=ds2), |
|
695 |
fields.ItemField(id='20', label='item', type='item', varname='fooo 20', data_source=ds2), |
|
696 |
fields.ItemsField(id='30', label='items', type='items', varname='fooo 30', data_source=ds2), |
|
697 |
fields.BlockField(id='40', label='block', type='block:%s' % block2.slug), |
|
698 |
] |
|
699 |
carddef11.store() |
|
700 |
carddef12 = CardDef() |
|
701 |
carddef12.name = 'card 1-2' |
|
702 |
carddef12.category_id = cat1.id |
|
703 |
carddef12.fields = [] |
|
704 |
carddef12.store() |
|
705 |
carddef13 = CardDef() |
|
706 |
carddef13.name = 'card 1-3' |
|
707 |
carddef13.category_id = cat1.id |
|
708 |
carddef13.fields = [] |
|
709 |
carddef13.store() |
|
710 | ||
711 |
carddef21 = CardDef() |
|
712 |
carddef21.name = 'card 2-1' |
|
713 |
carddef21.category_id = cat2.id |
|
714 |
carddef21.fields = [ |
|
715 |
fields.StringField(id='1', label='string', type='string', varname='bar 1', data_source=ds2), |
|
716 |
fields.ItemField(id='2', label='item', type='item', varname='bar 2', data_source=ds2), |
|
717 |
fields.ItemsField(id='3', label='items', type='items', varname='bar 3', data_source=ds2), |
|
718 |
fields.BlockField(id='4', label='block', type='block:%s' % block2.slug), |
|
719 |
] |
|
720 |
carddef21.store() |
|
721 |
carddef22 = CardDef() |
|
722 |
carddef22.name = 'card 2-2' |
|
723 |
carddef22.category_id = cat2.id |
|
724 |
carddef22.fields = [] |
|
725 |
carddef22.store() |
|
726 | ||
727 |
carddef31 = CardDef() |
|
728 |
carddef31.name = 'card 3-1' |
|
729 |
carddef31.fields = [ |
|
730 |
fields.StringField(id='1', label='string', type='string', varname='baz 1', data_source=ds3), |
|
731 |
fields.ItemField(id='2', label='item', type='item', varname='baz 2', data_source=ds3), |
|
732 |
fields.ItemsField(id='3', label='items', type='items', varname='baz 3', data_source=ds3), |
|
733 |
fields.BlockField(id='4', label='block', type='block:%s' % block3.slug), |
|
734 |
] |
|
735 |
carddef31.store() |
|
736 |
carddef32 = CardDef() |
|
737 |
carddef32.name = 'card 3-2' |
|
738 |
carddef32.fields = [] |
|
739 |
carddef32.store() |
|
740 | ||
741 |
app = login(get_app(pub)) |
|
742 | ||
743 |
resp = app.get('/backoffice/cards/svg') |
|
744 |
# cards |
|
745 |
assert '<title>card_card_1_1</title' in resp |
|
746 |
assert '<title>card_card_1_2</title' in resp |
|
747 |
assert '<title>card_card_1_3</title' not in resp |
|
748 |
assert '<title>card_card_2_1</title' in resp |
|
749 |
assert '<title>card_card_2_2</title' in resp |
|
750 |
assert '<title>card_card_3_1</title' in resp |
|
751 |
assert '<title>card_card_3_2</title' in resp |
|
752 |
# and relations |
|
753 |
assert resp.text.count('<title>card_card_1_1->card_card_1_2</title>') == 6 |
|
754 |
assert resp.text.count('<title>card_card_1_1->card_card_2_2</title>') == 9 |
|
755 |
assert resp.text.count('<title>card_card_2_1->card_card_2_2</title>') == 6 |
|
756 |
assert resp.text.count('<title>card_card_3_1->card_card_3_2</title>') == 6 |
|
757 | ||
758 |
resp = app.get('/backoffice/cards/svg?show-orphans=on') |
|
759 |
assert '<title>card_card_1_3</title' in resp |
|
760 | ||
761 |
resp = app.get('/backoffice/cards/categories/%s/svg' % cat1.id) |
|
762 |
# cards |
|
763 |
assert '<title>card_card_1_1</title' in resp |
|
764 |
assert '<title>card_card_1_2</title' in resp |
|
765 |
assert '<title>card_card_1_3</title' not in resp |
|
766 |
assert '<title>card_card_2_1</title' not in resp |
|
767 |
assert '<title>card_card_2_2</title' not in resp |
|
768 |
assert '<title>card_card_3_1</title' not in resp |
|
769 |
assert '<title>card_card_3_2</title' not in resp |
|
770 |
# and relations |
|
771 |
assert resp.text.count('<title>card_card_1_1->card_card_1_2</title>') == 6 |
|
772 |
assert resp.text.count('<title>card_card_1_1->card_card_2_2</title>') == 0 |
|
773 |
assert resp.text.count('<title>card_card_2_1->card_card_2_2</title>') == 0 |
|
774 |
assert resp.text.count('<title>card_card_3_1->card_card_3_2</title>') == 0 |
|
775 | ||
776 |
resp = app.get('/backoffice/cards/categories/%s/svg?show-orphans=on' % cat1.id) |
|
777 |
assert '<title>card_card_1_3</title' in resp |
|
778 | ||
779 |
resp = app.get('/backoffice/cards/categories/%s/svg' % cat2.id) |
|
780 |
# cards |
|
781 |
assert '<title>card_card_1_1</title' not in resp |
|
782 |
assert '<title>card_card_1_2</title' not in resp |
|
783 |
assert '<title>card_card_2_1</title' in resp |
|
784 |
assert '<title>card_card_2_2</title' in resp |
|
785 |
assert '<title>card_card_3_1</title' not in resp |
|
786 |
assert '<title>card_card_3_2</title' not in resp |
|
787 |
# and relations |
|
788 |
assert resp.text.count('<title>card_card_1_1->card_card_1_2</title>') == 0 |
|
789 |
assert resp.text.count('<title>card_card_1_1->card_card_2_2</title>') == 0 |
|
790 |
assert resp.text.count('<title>card_card_2_1->card_card_2_2</title>') == 6 |
|
791 |
assert resp.text.count('<title>card_card_3_1->card_card_3_2</title>') == 0 |
wcs/admin/categories.py | ||
---|---|---|
18 | 18 |
from quixote.directory import Directory |
19 | 19 |
from quixote.html import TemplateIO, htmltext |
20 | 20 | |
21 |
from wcs.carddef import CardDef |
|
21 |
from wcs.carddef import CardDef, get_cards_graph
|
|
22 | 22 |
from wcs.categories import CardDefCategory, Category, WorkflowCategory |
23 | 23 |
from wcs.formdef import FormDef |
24 | 24 |
from wcs.qommon import _, misc, template |
... | ... | |
253 | 253 |
object_class = CardDef |
254 | 254 |
usage_title = _('Card models in this category') |
255 | 255 |
empty_message = _('No card model associated to this category.') |
256 |
_q_exports = CategoryPage._q_exports + ['svg'] |
|
257 | ||
258 |
def svg(self): |
|
259 |
response = get_response() |
|
260 |
response.set_content_type('image/svg+xml') |
|
261 |
show_orphans = get_request().form.get('show-orphans') == 'on' |
|
262 |
return get_cards_graph(category=self.category, show_orphans=show_orphans) |
|
256 | 263 | |
257 | 264 | |
258 | 265 |
class WorkflowCategoryPage(CategoryPage): |
wcs/backoffice/cards.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU General Public License |
15 | 15 |
# along with this program; if not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
from quixote import get_publisher, get_response, get_session, redirect |
|
17 |
from quixote import get_publisher, get_request, get_response, get_session, redirect
|
|
18 | 18 |
from quixote.html import TemplateIO, htmltext |
19 | 19 | |
20 | 20 |
from wcs.admin import utils |
21 | 21 |
from wcs.admin.categories import CardDefCategoriesDirectory |
22 | 22 |
from wcs.admin.forms import FormDefPage, FormDefUI, FormsDirectory, OptionsDirectory |
23 |
from wcs.carddef import CardDef |
|
23 |
from wcs.carddef import CardDef, get_cards_graph
|
|
24 | 24 |
from wcs.categories import CardDefCategory |
25 | 25 |
from wcs.workflows import Workflow |
26 | 26 | |
... | ... | |
225 | 225 | |
226 | 226 | |
227 | 227 |
class CardsDirectory(FormsDirectory): |
228 |
_q_exports = ['', 'new', ('import', 'p_import'), 'categories'] |
|
228 |
_q_exports = ['', 'new', ('import', 'p_import'), 'categories', 'svg']
|
|
229 | 229 | |
230 | 230 |
category_class = CardDefCategory |
231 | 231 |
categories = CardDefCategoriesDirectory() |
... | ... | |
293 | 293 |
self.imported_formdef.store() |
294 | 294 |
return response |
295 | 295 | |
296 |
def svg(self): |
|
297 |
response = get_response() |
|
298 |
response.set_content_type('image/svg+xml') |
|
299 |
show_orphans = get_request().form.get('show-orphans') == 'on' |
|
300 |
return get_cards_graph(show_orphans=show_orphans) |
|
301 | ||
296 | 302 |
def _q_lookup(self, component): |
297 | 303 |
return self.formdef_page_class(component) |
wcs/carddef.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU General Public License |
15 | 15 |
# along with this program; if not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import io |
|
17 | 18 |
import sys |
18 | 19 |
import types |
20 |
from subprocess import PIPE, Popen |
|
19 | 21 | |
22 |
from django.utils.encoding import force_bytes |
|
20 | 23 |
from quixote import get_publisher |
21 | 24 | |
22 | 25 |
from wcs.carddata import CardData |
... | ... | |
282 | 285 |
continue |
283 | 286 |
varnames.extend(Field.get_referenced_varnames(formdef, criteria.value)) |
284 | 287 |
return varnames |
288 | ||
289 | ||
290 |
def get_cards_graph(category=None, show_orphans=False): |
|
291 |
out = io.StringIO() |
|
292 |
out.write('digraph main {\n') |
|
293 |
out.write('node [shape=box,style=filled];\n') |
|
294 |
out.write('edge [];\n') |
|
295 | ||
296 |
criterias = [] |
|
297 |
if category is not None: |
|
298 |
criterias = [Equal('category_id', str(category.id))] |
|
299 |
carddefs = CardDef.select(clause=criterias) |
|
300 |
carddefs_slugs = [c.url_name for c in carddefs] |
|
301 | ||
302 |
def check_relations(carddef_ref, fields, check_blocks=True, prefix=''): |
|
303 |
cardinality = { |
|
304 |
'string': '1..n', |
|
305 |
'item': '1..n', |
|
306 |
'items': 'n..n', |
|
307 |
} |
|
308 |
for field in fields: |
|
309 |
data_source = getattr(field, 'data_source', None) |
|
310 |
if data_source and data_source['type'].startswith('carddef:'): |
|
311 |
slug = field.data_source['type'].split(':')[1] |
|
312 |
if not show_orphans and slug not in carddefs_slugs: |
|
313 |
# don't report extra category relations |
|
314 |
continue |
|
315 |
label = '%s%s %s' % (prefix, field.varname or field.label, cardinality.get(field.key)) |
|
316 |
yield '%s -> card_%s [label="%s"];' % ( |
|
317 |
carddef_ref, |
|
318 |
slug.replace('-', '_'), |
|
319 |
label, |
|
320 |
) |
|
321 |
if check_blocks and field.key == 'block': |
|
322 |
yield from check_relations( |
|
323 |
carddef_ref, |
|
324 |
field.block.fields, |
|
325 |
check_blocks=False, |
|
326 |
prefix='%s (block) ' % (field.varname or field.label), |
|
327 |
) |
|
328 | ||
329 |
records = [] |
|
330 |
relations = [] |
|
331 | ||
332 |
for carddef in carddefs: |
|
333 |
carddef_ref = 'card_%s' % carddef.url_name.replace('-', '_') |
|
334 |
record = '%s [shape=record,label="<card>%s' % (carddef_ref, carddef.name) |
|
335 |
relations += list(check_relations(carddef_ref, carddef.get_all_fields())) |
|
336 |
record += '"];' |
|
337 |
records.append(record) |
|
338 |
if not show_orphans: |
|
339 |
for record in records[:]: |
|
340 |
if not [x for x in relations if record.split()[0] in x.split()]: |
|
341 |
records.remove(record) |
|
342 | ||
343 |
for record in records: |
|
344 |
out.write('%s\n' % record) |
|
345 | ||
346 |
for relation in relations: |
|
347 |
out.write('%s\n' % relation) |
|
348 | ||
349 |
out.write('}\n') |
|
350 | ||
351 |
out = out.getvalue() |
|
352 |
try: |
|
353 |
with Popen(['dot', '-Tsvg'], stdin=PIPE, stdout=PIPE) as process: |
|
354 |
out = process.communicate(force_bytes(out))[0] |
|
355 |
if process.returncode != 0: |
|
356 |
return '' |
|
357 |
except OSError: |
|
358 |
return '' |
|
359 | ||
360 |
return out |
|
285 |
- |