0001-backoffice-add-support-for-custom-views-4507.patch
tests/test_api.py | ||
---|---|---|
2031 | 2031 |
assert len(ods_sheet.findall('.//{%s}table-row' % ods.NS['table'])) == 311 |
2032 | 2032 | |
2033 | 2033 | |
2034 |
def test_api_list_formdata_custom_view(pub, local_user): |
|
2035 |
Role.wipe() |
|
2036 |
role = Role(name='test') |
|
2037 |
role.store() |
|
2038 | ||
2039 |
FormDef.wipe() |
|
2040 |
formdef = FormDef() |
|
2041 |
formdef.name = 'test' |
|
2042 |
formdef.workflow_roles = {'_receiver': role.id} |
|
2043 |
formdef.fields = [fields.StringField(id='0', label='foobar', varname='foobar'),] |
|
2044 |
formdef.store() |
|
2045 | ||
2046 |
data_class = formdef.data_class() |
|
2047 |
data_class.wipe() |
|
2048 | ||
2049 |
for i in range(30): |
|
2050 |
formdata = data_class() |
|
2051 |
formdata.data = {'0': 'FOO BAR %d' % i} |
|
2052 |
formdata.user_id = local_user.id |
|
2053 |
formdata.just_created() |
|
2054 |
if i % 3 == 0: |
|
2055 |
formdata.jump_status('new') |
|
2056 |
else: |
|
2057 |
formdata.jump_status('finished') |
|
2058 |
formdata.store() |
|
2059 | ||
2060 |
# add proper role to user |
|
2061 |
local_user.roles = [role.id] |
|
2062 |
local_user.store() |
|
2063 | ||
2064 |
# check it now gets the data |
|
2065 |
resp = get_app(pub).get(sign_uri('/api/forms/test/list', user=local_user)) |
|
2066 |
assert len(resp.json) == 30 |
|
2067 | ||
2068 |
custom_view = pub.custom_view_class() |
|
2069 |
custom_view.title = 'custom view' |
|
2070 |
custom_view.formdef = formdef |
|
2071 |
custom_view.columns = {'list': [{'id': '0'}]} |
|
2072 |
custom_view.filters = {"filter": "done", "filter-status": "on"} |
|
2073 |
custom_view.visibility = 'any' |
|
2074 |
custom_view.store() |
|
2075 | ||
2076 |
resp = get_app(pub).get(sign_uri('/api/forms/test/list/custom-view', user=local_user)) |
|
2077 |
assert len(resp.json['data']) == 20 |
|
2078 | ||
2079 | ||
2034 | 2080 |
def test_api_global_geojson(pub, local_user): |
2035 | 2081 |
Role.wipe() |
2036 | 2082 |
role = Role(name='test') |
tests/test_backoffice_pages.py | ||
---|---|---|
122 | 122 |
Workflow.wipe() |
123 | 123 |
Category.wipe() |
124 | 124 |
FormDef.wipe() |
125 |
pub.custom_view_class.wipe() |
|
125 | 126 |
formdef = FormDef() |
126 | 127 |
formdef.name = 'form title' |
127 | 128 |
if set_receiver: |
... | ... | |
415 | 416 | |
416 | 417 |
resp = resp.click(re.compile('^2$')) # second page |
417 | 418 |
assert resp.text.count('data-link') == 5 |
418 |
assert resp.form['offset'].value == '5' |
|
419 |
assert resp.forms['listing-settings']['offset'].value == '5'
|
|
419 | 420 | |
420 | 421 |
resp = resp.click(re.compile('^3$')) # third page |
421 | 422 |
assert resp.text.count('data-link') == 5 |
422 |
assert resp.form['offset'].value == '10' |
|
423 |
assert resp.forms['listing-settings']['offset'].value == '10'
|
|
423 | 424 | |
424 | 425 |
resp = resp.click(re.compile('^4$')) # fourth page |
425 | 426 |
assert resp.text.count('data-link') == 2 |
426 |
assert resp.form['offset'].value == '15' |
|
427 |
assert resp.forms['listing-settings']['offset'].value == '15'
|
|
427 | 428 | |
428 | 429 |
with pytest.raises(IndexError): # no fifth page |
429 | 430 |
resp = resp.click(re.compile('^5$')) |
... | ... | |
437 | 438 |
# try an overbound offset |
438 | 439 |
resp = app.get('/backoffice/management/form-title/?limit=5&offset=30') |
439 | 440 |
resp = resp.follow() |
440 |
assert resp.form['offset'].value == '0' |
|
441 |
assert resp.forms['listing-settings']['offset'].value == '0'
|
|
441 | 442 | |
442 | 443 | |
443 | 444 |
def test_backoffice_listing_order(pub): |
... | ... | |
541 | 542 |
assert resp.text.count('data-link') == 17 # 17 rows |
542 | 543 |
assert resp.text.count('FOO BAR') == 0 # no field 1 column |
543 | 544 | |
545 |
# change column order |
|
546 |
assert resp.forms['listing-settings']['columns-order'].value == 'id,time,last_update_time,user-label,2,status,1,3,anonymised' |
|
547 |
resp.forms['listing-settings']['columns-order'].value = 'user-label,id,time,last_update_time,2,status,1,3,anonymised' |
|
548 |
resp = resp.forms['listing-settings'].submit() |
|
549 |
assert resp.text.find('<span>User Label</span>') < resp.text.find('<span>Number</span>') |
|
550 | ||
544 | 551 | |
545 | 552 |
def test_backoffice_channel_column(pub): |
546 | 553 |
if not pub.site_options.has_section('variables'): |
... | ... | |
571 | 578 | |
572 | 579 |
app = login(get_app(pub)) |
573 | 580 |
resp = app.get('/backoffice/management/form-title/') |
574 |
assert not 'submission_agent' in resp.form.fields |
|
581 |
assert not 'submission_agent' in resp.forms['listing-settings'].fields
|
|
575 | 582 | |
576 | 583 |
formdef = FormDef.get_by_urlname('form-title') |
577 | 584 |
formdef.backoffice_submission_roles = user.roles |
578 | 585 |
formdef.store() |
579 | 586 |
resp = app.get('/backoffice/management/form-title/') |
580 | 587 |
assert resp.text.count('</th>') == 8 # six columns |
581 |
resp.form['submission_agent'].checked = True |
|
582 |
resp = resp.form.submit() |
|
588 |
resp.forms['listing-settings']['submission_agent'].checked = True
|
|
589 |
resp = resp.forms['listing-settings'].submit()
|
|
583 | 590 |
assert resp.text.count('</th>') == 9 # seven columns |
584 | 591 |
assert resp.text.count('data-link') == 17 # 17 rows |
585 | 592 |
assert not '>agent<' in resp.text |
... | ... | |
592 | 599 |
formdata.store() |
593 | 600 | |
594 | 601 |
resp = app.get('/backoffice/management/form-title/') |
595 |
resp.form['submission_agent'].checked = True |
|
596 |
resp = resp.form.submit() |
|
602 |
resp.forms['listing-settings']['submission_agent'].checked = True
|
|
603 |
resp = resp.forms['listing-settings'].submit()
|
|
597 | 604 |
assert resp.text.count('>agent<') == 17 |
598 | 605 | |
599 | 606 |
resp = resp.click('Export as CSV File') |
600 | 607 |
assert len(resp.text.splitlines()) == 18 # 17 + header line |
601 |
assert resp.text.count(',agent,') == 17
|
|
608 |
assert resp.text.count(',agent') == 17 |
|
602 | 609 | |
603 | 610 | |
604 | 611 |
def test_backoffice_image_column(pub): |
... | ... | |
669 | 676 |
create_environment(pub) |
670 | 677 |
app = login(get_app(pub)) |
671 | 678 |
resp = app.get('/backoffice/management/form-title/') |
672 |
assert not 'filter-2-value' in resp.form.fields |
|
679 |
assert not 'filter-2-value' in resp.forms['listing-settings'].fields
|
|
673 | 680 | |
674 | 681 |
formdef = FormDef.get_by_urlname('form-title') |
675 | 682 |
formdef.fields[1].in_filters = True |
676 | 683 |
formdef.store() |
677 | 684 |
resp = app.get('/backoffice/management/form-title/') |
678 |
assert 'filter-2-value' in resp.form.fields |
|
685 |
assert 'filter-2-value' in resp.forms['listing-settings'].fields
|
|
679 | 686 | |
680 | 687 |
# same check for items field |
681 | 688 |
formdef.fields.append( |
682 | 689 |
fields.ItemsField(id='4', label='4th field', type='items', items=['foo', 'bar', 'baz'])) |
683 | 690 |
formdef.store() |
684 | 691 |
resp = app.get('/backoffice/management/form-title/') |
685 |
assert not 'filter-4-value' in resp.form.fields |
|
692 |
assert not 'filter-4-value' in resp.forms['listing-settings'].fields
|
|
686 | 693 | |
687 | 694 |
formdef.fields[-1].in_filters = True |
688 | 695 |
formdef.store() |
689 | 696 |
resp = app.get('/backoffice/management/form-title/') |
690 |
assert 'filter-4-value' in resp.form.fields |
|
697 |
assert 'filter-4-value' in resp.forms['listing-settings'].fields
|
|
691 | 698 | |
692 | 699 | |
693 | 700 |
def test_backoffice_bool_filter(pub): |
... | ... | |
705 | 712 | |
706 | 713 |
app = login(get_app(pub)) |
707 | 714 |
resp = app.get('/backoffice/management/form-title/') |
708 |
resp.form['filter-4'].checked = True |
|
709 |
resp = resp.form.submit() |
|
715 |
resp.forms['listing-settings']['filter-4'].checked = True
|
|
716 |
resp = resp.forms['listing-settings'].submit()
|
|
710 | 717 | |
711 |
assert resp.form['filter-4-value'].value == '' |
|
718 |
assert resp.forms['listing-settings']['filter-4-value'].value == ''
|
|
712 | 719 | |
713 |
resp.form['filter-4-value'].value = 'true' |
|
714 |
resp = resp.form.submit() |
|
720 |
resp.forms['listing-settings']['filter-4-value'].value = 'true'
|
|
721 |
resp = resp.forms['listing-settings'].submit()
|
|
715 | 722 |
assert resp.text.count('<td>Yes</td>') > 0 |
716 | 723 |
assert resp.text.count('<td>No</td>') == 0 |
717 | 724 | |
718 |
resp.form['filter-4-value'].value = 'false' |
|
719 |
resp = resp.form.submit() |
|
725 |
resp.forms['listing-settings']['filter-4-value'].value = 'false'
|
|
726 |
resp = resp.forms['listing-settings'].submit()
|
|
720 | 727 |
assert resp.text.count('<td>Yes</td>') == 0 |
721 | 728 |
assert resp.text.count('<td>No</td>') > 0 |
722 | 729 | |
... | ... | |
747 | 754 | |
748 | 755 |
app = login(get_app(pub)) |
749 | 756 |
resp = app.get('/backoffice/management/form-title/') |
750 |
resp.form['filter-4'].checked = True |
|
751 |
resp = resp.form.submit() |
|
757 |
resp.forms['listing-settings']['filter-4'].checked = True
|
|
758 |
resp = resp.forms['listing-settings'].submit()
|
|
752 | 759 | |
753 |
assert resp.form['filter-4-value'].value == '' |
|
760 |
assert resp.forms['listing-settings']['filter-4-value'].value == ''
|
|
754 | 761 | |
755 |
resp.form['filter-4-value'].value = 'â' |
|
756 |
resp = resp.form.submit() |
|
762 |
resp.forms['listing-settings']['filter-4-value'].value = 'â'
|
|
763 |
resp = resp.forms['listing-settings'].submit()
|
|
757 | 764 |
assert resp.text.count(u'<td>â</td>') > 0 |
758 | 765 |
assert resp.text.count(u'<td>b</td>') == 0 |
759 | 766 |
assert resp.text.count(u'<td>d</td>') == 0 |
760 | 767 | |
761 |
resp.form['filter-4-value'].value = 'b' |
|
762 |
resp = resp.form.submit() |
|
768 |
resp.forms['listing-settings']['filter-4-value'].value = 'b'
|
|
769 |
resp = resp.forms['listing-settings'].submit()
|
|
763 | 770 |
assert resp.text.count(u'<td>â</td>') == 0 |
764 | 771 |
assert resp.text.count(u'<td>b</td>') > 0 |
765 | 772 |
assert resp.text.count(u'<td>d</td>') == 0 |
766 | 773 | |
767 | 774 |
if not pub.is_using_postgresql(): |
768 | 775 |
# in pickle all options are always displayed |
769 |
resp.form['filter-4-value'].value = 'c' |
|
770 |
resp = resp.form.submit() |
|
776 |
resp.forms['listing-settings']['filter-4-value'].value = 'c'
|
|
777 |
resp = resp.forms['listing-settings'].submit()
|
|
771 | 778 |
assert resp.text.count(u'<td>â</td>') == 0 |
772 | 779 |
assert resp.text.count(u'<td>b</td>') == 0 |
773 | 780 |
assert resp.text.count(u'<td>c</td>') == 0 |
... | ... | |
775 | 782 |
else: |
776 | 783 |
# in postgresql, option 'c' is never used so not even listed |
777 | 784 |
with pytest.raises(ValueError): |
778 |
resp.form['filter-4-value'].value = 'c' |
|
785 |
resp.forms['listing-settings']['filter-4-value'].value = 'c'
|
|
779 | 786 | |
780 | 787 |
# check json view used to fill select filters from javascript |
781 | 788 |
resp2 = app.get(resp.request.path + 'filter-options?filter_field_id=4&' + resp.request.query_string) |
... | ... | |
785 | 792 |
resp2 = app.get(resp.request.path + 'filter-options?filter_field_id=7&' + resp.request.query_string, status=404) |
786 | 793 | |
787 | 794 |
for status in ('all', 'waiting', 'pending', 'done', 'accepted'): |
788 |
resp.form['filter'] = status |
|
789 |
resp = resp.form.submit() |
|
795 |
resp.forms['listing-settings']['filter'] = status
|
|
796 |
resp = resp.forms['listing-settings'].submit()
|
|
790 | 797 |
resp2 = app.get(resp.request.path + 'filter-options?filter_field_id=4&' + resp.request.query_string) |
791 | 798 |
if status == 'accepted': |
792 | 799 |
assert [x['id'] for x in resp2.json['data']] == [] |
... | ... | |
834 | 841 | |
835 | 842 |
app = login(get_app(pub)) |
836 | 843 |
resp = app.get('/backoffice/management/form-title/') |
837 |
resp.form['filter-4'].checked = True |
|
838 |
resp.form['filter-5'].checked = True |
|
839 |
resp = resp.form.submit() |
|
844 |
resp.forms['listing-settings']['filter-4'].checked = True
|
|
845 |
resp.forms['listing-settings']['filter-5'].checked = True
|
|
846 |
resp = resp.forms['listing-settings'].submit()
|
|
840 | 847 | |
841 |
assert resp.form['filter-4-value'].value == '' |
|
842 |
assert resp.form['filter-5-value'].value == '' |
|
843 |
assert [x[0] for x in resp.form['filter-4-value'].options] == ['', 'a', 'b'] |
|
844 |
assert [x[0] for x in resp.form['filter-5-value'].options] == ['', 'A', 'B', 'C'] |
|
848 |
assert resp.forms['listing-settings']['filter-4-value'].value == ''
|
|
849 |
assert resp.forms['listing-settings']['filter-5-value'].value == ''
|
|
850 |
assert [x[0] for x in resp.forms['listing-settings']['filter-4-value'].options] == ['', 'a', 'b']
|
|
851 |
assert [x[0] for x in resp.forms['listing-settings']['filter-5-value'].options] == ['', 'A', 'B', 'C']
|
|
845 | 852 | |
846 |
resp.form['filter-4-value'].value = 'a' |
|
847 |
resp = resp.form.submit() |
|
848 |
assert [x[0] for x in resp.form['filter-4-value'].options] == ['', 'a', 'b'] |
|
849 |
assert [x[0] for x in resp.form['filter-5-value'].options] == ['', 'A', 'B', 'C'] |
|
853 |
resp.forms['listing-settings']['filter-4-value'].value = 'a'
|
|
854 |
resp = resp.forms['listing-settings'].submit()
|
|
855 |
assert [x[0] for x in resp.forms['listing-settings']['filter-4-value'].options] == ['', 'a', 'b']
|
|
856 |
assert [x[0] for x in resp.forms['listing-settings']['filter-5-value'].options] == ['', 'A', 'B', 'C']
|
|
850 | 857 | |
851 |
resp.form['filter-4-value'].value = 'b' |
|
852 |
resp = resp.form.submit() |
|
853 |
assert [x[0] for x in resp.form['filter-4-value'].options] == ['', 'a', 'b'] |
|
854 |
assert [x[0] for x in resp.form['filter-5-value'].options] == ['', 'B'] |
|
858 |
resp.forms['listing-settings']['filter-4-value'].value = 'b'
|
|
859 |
resp = resp.forms['listing-settings'].submit()
|
|
860 |
assert [x[0] for x in resp.forms['listing-settings']['filter-4-value'].options] == ['', 'a', 'b']
|
|
861 |
assert [x[0] for x in resp.forms['listing-settings']['filter-5-value'].options] == ['', 'B']
|
|
855 | 862 | |
856 |
resp.form['filter-5-value'].value = 'B' |
|
857 |
resp = resp.form.submit() |
|
858 |
assert [x[0] for x in resp.form['filter-4-value'].options] == ['', 'a', 'b'] |
|
859 |
assert [x[0] for x in resp.form['filter-5-value'].options] == ['', 'B'] |
|
863 |
resp.forms['listing-settings']['filter-5-value'].value = 'B'
|
|
864 |
resp = resp.forms['listing-settings'].submit()
|
|
865 |
assert [x[0] for x in resp.forms['listing-settings']['filter-4-value'].options] == ['', 'a', 'b']
|
|
866 |
assert [x[0] for x in resp.forms['listing-settings']['filter-5-value'].options] == ['', 'B']
|
|
860 | 867 | |
861 |
resp.form['filter-4-value'].value = '' |
|
862 |
resp = resp.form.submit() |
|
863 |
assert [x[0] for x in resp.form['filter-4-value'].options] == ['', 'a', 'b'] |
|
864 |
assert [x[0] for x in resp.form['filter-5-value'].options] == ['', 'A', 'B', 'C'] |
|
868 |
resp.forms['listing-settings']['filter-4-value'].value = ''
|
|
869 |
resp = resp.forms['listing-settings'].submit()
|
|
870 |
assert [x[0] for x in resp.forms['listing-settings']['filter-4-value'].options] == ['', 'a', 'b']
|
|
871 |
assert [x[0] for x in resp.forms['listing-settings']['filter-5-value'].options] == ['', 'A', 'B', 'C']
|
|
865 | 872 | |
866 | 873 | |
867 | 874 |
def test_backoffice_bofield_item_filter(pub): |
... | ... | |
894 | 901 | |
895 | 902 |
app = login(get_app(pub)) |
896 | 903 |
resp = app.get('/backoffice/management/form-title/') |
897 |
resp.form['filter-bo0-1'].checked = True |
|
898 |
resp = resp.form.submit() |
|
904 |
resp.forms['listing-settings']['filter-bo0-1'].checked = True
|
|
905 |
resp = resp.forms['listing-settings'].submit()
|
|
899 | 906 | |
900 |
assert resp.form['filter-bo0-1-value'].value == '' |
|
907 |
assert resp.forms['listing-settings']['filter-bo0-1-value'].value == ''
|
|
901 | 908 | |
902 |
resp.form['filter-bo0-1-value'].value = 'â' |
|
903 |
resp = resp.form.submit() |
|
909 |
resp.forms['listing-settings']['filter-bo0-1-value'].value = 'â'
|
|
910 |
resp = resp.forms['listing-settings'].submit()
|
|
904 | 911 |
assert resp.text.count(u'<td>â</td>') > 0 |
905 | 912 |
assert resp.text.count(u'<td>b</td>') == 0 |
906 | 913 |
assert resp.text.count(u'<td>d</td>') == 0 |
907 | 914 | |
908 |
resp.form['filter-bo0-1-value'].value = 'b' |
|
909 |
resp = resp.form.submit() |
|
915 |
resp.forms['listing-settings']['filter-bo0-1-value'].value = 'b'
|
|
916 |
resp = resp.forms['listing-settings'].submit()
|
|
910 | 917 |
assert resp.text.count(u'<td>â</td>') == 0 |
911 | 918 |
assert resp.text.count(u'<td>b</td>') > 0 |
912 | 919 |
assert resp.text.count(u'<td>d</td>') == 0 |
913 | 920 | |
914 | 921 |
if not pub.is_using_postgresql(): |
915 | 922 |
# in pickle all options are always displayed |
916 |
resp.form['filter-bo0-1-value'].value = 'c' |
|
917 |
resp = resp.form.submit() |
|
923 |
resp.forms['listing-settings']['filter-bo0-1-value'].value = 'c'
|
|
924 |
resp = resp.forms['listing-settings'].submit()
|
|
918 | 925 |
assert resp.text.count(u'<td>â</td>') == 0 |
919 | 926 |
assert resp.text.count(u'<td>b</td>') == 0 |
920 | 927 |
assert resp.text.count(u'<td>c</td>') == 0 |
... | ... | |
922 | 929 |
else: |
923 | 930 |
# in postgresql, option 'c' is never used so not even listed |
924 | 931 |
with pytest.raises(ValueError): |
925 |
resp.form['filter-bo0-1-value'].value = 'c' |
|
932 |
resp.forms['listing-settings']['filter-bo0-1-value'].value = 'c'
|
|
926 | 933 | |
927 | 934 |
# check json view used to fill select filters from javascript |
928 | 935 |
resp2 = app.get(resp.request.path + 'filter-options?filter_field_id=bo0-1&' + resp.request.query_string) |
... | ... | |
931 | 938 |
assert [x['id'] for x in resp2.json['data']] == ['d'] |
932 | 939 | |
933 | 940 |
for status in ('all', 'waiting', 'pending', 'done', 'accepted'): |
934 |
resp.form['filter'] = status |
|
935 |
resp = resp.form.submit() |
|
941 |
resp.forms['listing-settings']['filter'] = status
|
|
942 |
resp = resp.forms['listing-settings'].submit()
|
|
936 | 943 |
resp2 = app.get(resp.request.path + 'filter-options?filter_field_id=bo0-1&' + resp.request.query_string) |
937 | 944 |
if status == 'accepted': |
938 | 945 |
assert [x['id'] for x in resp2.json['data']] == [] |
... | ... | |
966 | 973 | |
967 | 974 |
app = login(get_app(pub)) |
968 | 975 |
resp = app.get('/backoffice/management/form-title/') |
969 |
resp.form['filter-4'].checked = True |
|
970 |
resp = resp.form.submit() |
|
976 |
resp.forms['listing-settings']['filter-4'].checked = True
|
|
977 |
resp = resp.forms['listing-settings'].submit()
|
|
971 | 978 | |
972 |
assert resp.form['filter-4-value'].value == '' |
|
979 |
assert resp.forms['listing-settings']['filter-4-value'].value == ''
|
|
973 | 980 | |
974 |
resp.form['filter-4-value'].value = 'â' |
|
975 |
resp = resp.form.submit() |
|
981 |
resp.forms['listing-settings']['filter-4-value'].value = 'â'
|
|
982 |
resp = resp.forms['listing-settings'].submit()
|
|
976 | 983 |
assert resp.text.count(u'<td>â, b</td>') > 0 |
977 | 984 |
assert resp.text.count(u'<td>â</td>') > 0 |
978 | 985 |
assert resp.text.count(u'<td>b, d</td>') == 0 |
979 | 986 | |
980 |
resp.form['filter-4-value'].value = 'b' |
|
981 |
resp = resp.form.submit() |
|
987 |
resp.forms['listing-settings']['filter-4-value'].value = 'b'
|
|
988 |
resp = resp.forms['listing-settings'].submit()
|
|
982 | 989 |
assert resp.text.count(u'<td>â, b</td>') > 0 |
983 | 990 |
assert resp.text.count(u'<td>â</td>') == 0 |
984 | 991 |
assert resp.text.count(u'<td>b, d</td>') > 0 |
... | ... | |
986 | 993 |
if pub.is_using_postgresql(): |
987 | 994 |
# option 'c' is never used so not even listed |
988 | 995 |
with pytest.raises(ValueError): |
989 |
resp.form['filter-4-value'].value = 'c' |
|
996 |
resp.forms['listing-settings']['filter-4-value'].value = 'c'
|
|
990 | 997 |
else: |
991 |
resp.form['filter-4-value'].value = 'c' |
|
992 |
resp = resp.form.submit() |
|
998 |
resp.forms['listing-settings']['filter-4-value'].value = 'c'
|
|
999 |
resp = resp.forms['listing-settings'].submit()
|
|
993 | 1000 |
assert resp.text.count(u'<td>â, b</td>') == 0 |
994 | 1001 |
assert resp.text.count(u'<td>â</td>') == 0 |
995 | 1002 |
assert resp.text.count(u'<td>b, d</td>') == 0 |
... | ... | |
1020 | 1027 |
assert resp.text.splitlines()[1].split(',')[7] == 'aa' |
1021 | 1028 | |
1022 | 1029 |
resp = app.get('/backoffice/management/form-title/') |
1023 |
resp.forms[0]['filter'] = 'all'
|
|
1024 |
resp = resp.forms[0].submit()
|
|
1030 |
resp.forms['listing-settings']['filter'] = 'all'
|
|
1031 |
resp = resp.forms['listing-settings'].submit()
|
|
1025 | 1032 |
resp_csv = resp.click('Export as CSV File') |
1026 | 1033 |
assert len(resp_csv.text.splitlines()) == 51 |
1027 | 1034 | |
1028 | 1035 |
# test status filter |
1029 |
resp.forms[0]['filter'] = 'pending'
|
|
1030 |
resp.forms[0]['filter-2'].checked = True
|
|
1031 |
resp = resp.forms[0].submit()
|
|
1032 |
resp.forms[0]['filter-2-value'] = 'baz'
|
|
1033 |
resp = resp.forms[0].submit()
|
|
1036 |
resp.forms['listing-settings']['filter'] = 'pending'
|
|
1037 |
resp.forms['listing-settings']['filter-2'].checked = True
|
|
1038 |
resp = resp.forms['listing-settings'].submit()
|
|
1039 |
resp.forms['listing-settings']['filter-2-value'] = 'baz'
|
|
1040 |
resp = resp.forms['listing-settings'].submit()
|
|
1034 | 1041 |
resp_csv = resp.click('Export as CSV File') |
1035 | 1042 |
assert len(resp_csv.text.splitlines()) == 9 |
1036 | 1043 | |
1037 | 1044 |
# test criteria filters |
1038 |
resp.forms[0]['filter-start'].checked = True
|
|
1039 |
resp = resp.forms[0].submit()
|
|
1040 |
resp.forms[0]['filter-start-value'] = datetime.datetime(2015, 2, 1).strftime('%Y-%m-%d')
|
|
1041 |
resp = resp.forms[0].submit()
|
|
1045 |
resp.forms['listing-settings']['filter-start'].checked = True
|
|
1046 |
resp = resp.forms['listing-settings'].submit()
|
|
1047 |
resp.forms['listing-settings']['filter-start-value'] = datetime.datetime(2015, 2, 1).strftime('%Y-%m-%d')
|
|
1048 |
resp = resp.forms['listing-settings'].submit()
|
|
1042 | 1049 |
resp_csv = resp.click('Export as CSV File') |
1043 | 1050 |
assert len(resp_csv.text.splitlines()) == 1 |
1044 | 1051 | |
1045 |
resp.forms[0]['filter-start-value'] = datetime.datetime(2014, 2, 1).strftime('%Y-%m-%d')
|
|
1046 |
resp = resp.forms[0].submit()
|
|
1047 |
resp.forms[0]['filter-2-value'] = 'baz'
|
|
1048 |
resp = resp.forms[0].submit()
|
|
1052 |
resp.forms['listing-settings']['filter-start-value'] = datetime.datetime(2014, 2, 1).strftime('%Y-%m-%d')
|
|
1053 |
resp = resp.forms['listing-settings'].submit()
|
|
1054 |
resp.forms['listing-settings']['filter-2-value'] = 'baz'
|
|
1055 |
resp = resp.forms['listing-settings'].submit()
|
|
1049 | 1056 |
resp_csv = resp.click('Export as CSV File') |
1050 | 1057 |
assert len(resp_csv.text.splitlines()) == 9 |
1051 | 1058 |
assert 'Created' in resp_csv.text.splitlines()[0] |
1052 | 1059 | |
1053 | 1060 |
# test column selection |
1054 |
resp.form['time'].checked = False |
|
1055 |
resp = resp.forms[0].submit()
|
|
1061 |
resp.forms['listing-settings']['time'].checked = False
|
|
1062 |
resp = resp.forms['listing-settings'].submit()
|
|
1056 | 1063 |
resp_csv = resp.click('Export as CSV File') |
1057 | 1064 |
assert 'Created' not in resp_csv.text.splitlines()[0] |
1058 | 1065 | |
... | ... | |
1117 | 1124 |
assert 'Channel' not in resp_csv.text.splitlines()[0] |
1118 | 1125 | |
1119 | 1126 |
# add submission channel column |
1120 |
resp.form['submission_channel'].checked = True |
|
1121 |
resp = resp.forms[0].submit()
|
|
1127 |
resp.forms['listing-settings']['submission_channel'].checked = True
|
|
1128 |
resp = resp.forms['listing-settings'].submit()
|
|
1122 | 1129 |
resp_csv = resp.click('Export as CSV File') |
1123 |
assert resp_csv.text.splitlines()[0].split(',')[1] == 'Channel' |
|
1124 |
assert resp_csv.text.splitlines()[1].split(',')[1] == 'Web' |
|
1130 |
assert resp_csv.text.splitlines()[0].split(',')[-1] == 'Channel'
|
|
1131 |
assert resp_csv.text.splitlines()[1].split(',')[-1] == 'Web'
|
|
1125 | 1132 | |
1126 | 1133 | |
1127 | 1134 |
def test_backoffice_csv_export_anonymised(pub): |
... | ... | |
1137 | 1144 |
assert resp_csv.text.splitlines()[0].split(',')[-1] != 'Anonymised' |
1138 | 1145 | |
1139 | 1146 |
# add anonymised column |
1140 |
resp.form['anonymised'].checked = True |
|
1141 |
resp = resp.forms[0].submit()
|
|
1147 |
resp.forms['listing-settings']['anonymised'].checked = True
|
|
1148 |
resp = resp.forms['listing-settings'].submit()
|
|
1142 | 1149 |
resp_csv = resp.click('Export as CSV File') |
1143 | 1150 |
assert resp_csv.text.splitlines()[0].split(',')[-1] == 'Anonymised' |
1144 | 1151 |
assert resp_csv.text.splitlines()[1].split(',')[-1] == 'No' |
... | ... | |
1265 | 1272 |
assert 'To Status "Finished"' in resp.text |
1266 | 1273 |
assert not '<h2>Filters</h2>' in resp.text |
1267 | 1274 | |
1268 |
resp.forms[0]['filter-end-value'] = '2013-01-01'
|
|
1269 |
resp = resp.forms[0].submit()
|
|
1275 |
resp.forms['listing-settings']['filter-end-value'] = '2013-01-01'
|
|
1276 |
resp = resp.forms['listing-settings'].submit()
|
|
1270 | 1277 |
assert 'Total number of records: 0' in resp.text |
1271 | 1278 |
assert '<h2>Filters</h2>' in resp.text |
1272 | 1279 |
assert 'End: 2013-01-01' in resp.text |
... | ... | |
1388 | 1395 |
app = login(get_app(pub)) |
1389 | 1396 |
resp = app.get('/backoffice/management/form-title/') |
1390 | 1397 |
resp = resp.click('Statistics') |
1391 |
assert 'filter' not in resp.forms[0].fields # status is not displayed by default
|
|
1398 |
assert 'filter' not in resp.forms['listing-settings'].fields # status is not displayed by default
|
|
1392 | 1399 |
assert not '<h2>Filters</h2>' in resp.text |
1393 | 1400 | |
1394 | 1401 |
# add 'status' as a filter |
1395 |
resp.forms[0]['filter-status'].checked = True
|
|
1396 |
resp = resp.forms[0].submit()
|
|
1397 |
assert 'filter' in resp.forms[0].fields
|
|
1402 |
resp.forms['listing-settings']['filter-status'].checked = True
|
|
1403 |
resp = resp.forms['listing-settings'].submit()
|
|
1404 |
assert 'filter' in resp.forms['listing-settings'].fields
|
|
1398 | 1405 |
assert not '<h2>Filters</h2>' in resp.text |
1399 | 1406 | |
1400 |
assert resp.forms[0]['filter'].value == 'all'
|
|
1401 |
resp.forms[0]['filter'].value = 'pending'
|
|
1402 |
resp = resp.forms[0].submit()
|
|
1407 |
assert resp.forms['listing-settings']['filter'].value == 'all'
|
|
1408 |
resp.forms['listing-settings']['filter'].value = 'pending'
|
|
1409 |
resp = resp.forms['listing-settings'].submit()
|
|
1403 | 1410 |
assert 'Total number of records: 17' in resp.text |
1404 | 1411 |
assert '<h2>Filters</h2>' in resp.text |
1405 | 1412 |
assert 'Status: Pending' in resp.text |
1406 | 1413 | |
1407 |
resp.forms[0]['filter'].value = 'done'
|
|
1408 |
resp = resp.forms[0].submit()
|
|
1414 |
resp.forms['listing-settings']['filter'].value = 'done'
|
|
1415 |
resp = resp.forms['listing-settings'].submit()
|
|
1409 | 1416 |
assert 'Total number of records: 33' in resp.text |
1410 | 1417 |
assert '<h2>Filters</h2>' in resp.text |
1411 | 1418 |
assert 'Status: Done' in resp.text |
1412 | 1419 | |
1413 |
resp.forms[0]['filter'].value = 'rejected'
|
|
1414 |
resp = resp.forms[0].submit()
|
|
1420 |
resp.forms['listing-settings']['filter'].value = 'rejected'
|
|
1421 |
resp = resp.forms['listing-settings'].submit()
|
|
1415 | 1422 |
assert 'Total number of records: 0' in resp.text |
1416 | 1423 |
assert '<h2>Filters</h2>' in resp.text |
1417 | 1424 |
assert 'Status: Rejected' in resp.text |
1418 | 1425 | |
1419 |
resp.forms[0]['filter'].value = 'all'
|
|
1420 |
resp = resp.forms[0].submit()
|
|
1426 |
resp.forms['listing-settings']['filter'].value = 'all'
|
|
1427 |
resp = resp.forms['listing-settings'].submit()
|
|
1421 | 1428 |
assert 'Total number of records: 50' in resp.text |
1422 | 1429 | |
1423 | 1430 | |
... | ... | |
1429 | 1436 |
resp = resp.click('Statistics') |
1430 | 1437 |
assert not 'filter-2-value' in resp.form.fields |
1431 | 1438 | |
1432 |
resp.forms[0]['filter-2'].checked = True
|
|
1433 |
resp = resp.forms[0].submit()
|
|
1434 |
resp.forms[0]['filter-2-value'].value = 'bar'
|
|
1435 |
resp = resp.forms[0].submit()
|
|
1439 |
resp.forms['listing-settings']['filter-2'].checked = True
|
|
1440 |
resp = resp.forms['listing-settings'].submit()
|
|
1441 |
resp.forms['listing-settings']['filter-2-value'].value = 'bar'
|
|
1442 |
resp = resp.forms['listing-settings'].submit()
|
|
1436 | 1443 |
assert 'Total number of records: 13' in resp.text |
1437 | 1444 | |
1438 |
resp.forms[0]['filter-2-value'].value = 'baz'
|
|
1439 |
resp = resp.forms[0].submit()
|
|
1445 |
resp.forms['listing-settings']['filter-2-value'].value = 'baz'
|
|
1446 |
resp = resp.forms['listing-settings'].submit()
|
|
1440 | 1447 |
assert 'Total number of records: 24' in resp.text |
1441 | 1448 | |
1442 |
resp.forms[0]['filter-2-value'].value = 'foo'
|
|
1443 |
resp = resp.forms[0].submit()
|
|
1449 |
resp.forms['listing-settings']['filter-2-value'].value = 'foo'
|
|
1450 |
resp = resp.forms['listing-settings'].submit()
|
|
1444 | 1451 |
assert 'Total number of records: 13' in resp.text |
1445 | 1452 |
assert '<h2>Filters</h2>' in resp.text |
1446 | 1453 |
assert '2nd field: foo' in resp.text |
1447 | 1454 | |
1448 | 1455 |
# check it's also possible to get back to the complete list |
1449 |
resp.forms[0]['filter-2-value'].value = ''
|
|
1450 |
resp = resp.forms[0].submit()
|
|
1456 |
resp.forms['listing-settings']['filter-2-value'].value = ''
|
|
1457 |
resp = resp.forms['listing-settings'].submit()
|
|
1451 | 1458 |
assert 'Total number of records: 50' in resp.text |
1452 | 1459 | |
1453 | 1460 |
# check it also works with item fields with a data source |
1454 | 1461 |
resp = app.get('/backoffice/management/form-title/') |
1455 | 1462 |
resp = resp.click('Statistics') |
1456 |
resp.forms[0]['filter-3'].checked = True
|
|
1457 |
resp = resp.forms[0].submit()
|
|
1458 |
resp.forms[0]['filter-3-value'].value = 'A'
|
|
1459 |
resp = resp.forms[0].submit()
|
|
1463 |
resp.forms['listing-settings']['filter-3'].checked = True
|
|
1464 |
resp = resp.forms['listing-settings'].submit()
|
|
1465 |
resp.forms['listing-settings']['filter-3-value'].value = 'A'
|
|
1466 |
resp = resp.forms['listing-settings'].submit()
|
|
1460 | 1467 |
assert 'Total number of records: 13' in resp.text |
1461 | 1468 |
assert '<h2>Filters</h2>' in resp.text |
1462 | 1469 |
assert '3rd field: aa' in resp.text |
... | ... | |
6185 | 6192 |
resp.form['comment'] = 'plop' |
6186 | 6193 |
resp = resp.form.submit('submit') |
6187 | 6194 |
assert resp.location == 'http://example.net/backoffice/management/form-title/%s/#' % formdata.id |
6195 | ||
6196 | ||
6197 |
def test_backoffice_custom_view(pub): |
|
6198 |
create_superuser(pub) |
|
6199 |
create_environment(pub) |
|
6200 | ||
6201 |
app = login(get_app(pub)) |
|
6202 |
resp = app.get('/backoffice/management/form-title/') |
|
6203 |
assert resp.text.count('<span>User Label</span>') == 1 |
|
6204 |
assert resp.text.count('<tr') == 18 |
|
6205 | ||
6206 |
# columns |
|
6207 |
resp.forms['listing-settings']['user-label'].checked = False |
|
6208 |
resp = resp.forms['listing-settings'].submit() |
|
6209 |
# filters |
|
6210 |
resp.forms[0]['filter-2'].checked = True |
|
6211 |
resp = resp.forms['listing-settings'].submit() |
|
6212 | ||
6213 |
resp.forms['listing-settings']['filter-2-value'] = 'baz' |
|
6214 |
resp = resp.forms['listing-settings'].submit() |
|
6215 | ||
6216 |
assert resp.text.count('<span>User Label</span>') == 0 |
|
6217 |
assert resp.text.count('<tr') == 9 |
|
6218 | ||
6219 |
resp.forms['save-custom-view']['title'] = 'custom test view' |
|
6220 |
resp = resp.forms['save-custom-view'].submit() |
|
6221 |
assert resp.location.endswith('/custom-test-view/') |
|
6222 |
resp = resp.follow() |
|
6223 |
assert resp.text.count('<span>User Label</span>') == 0 |
|
6224 |
assert resp.text.count('<tr') == 9 |
|
6225 | ||
6226 |
resp.forms['listing-settings']['filter-2-value'] = 'foo' |
|
6227 |
resp = resp.forms['listing-settings'].submit() |
|
6228 |
assert resp.text.count('<tr') == 6 |
|
6229 |
assert resp.forms['save-custom-view']['update'].checked is True |
|
6230 |
resp = resp.forms['save-custom-view'].submit() |
|
6231 |
assert resp.location.endswith('/custom-test-view/') |
|
6232 |
resp = resp.follow() |
|
6233 |
assert resp.text.count('<tr') == 6 |
|
6234 | ||
6235 |
resp = app.get('/backoffice/management/other-form/') |
|
6236 |
assert 'custom test view' not in resp |
|
6237 | ||
6238 |
# check it's not possible to create a view without any columns |
|
6239 |
for field_key in resp.forms['listing-settings'].fields: |
|
6240 |
if not field_key: |
|
6241 |
continue |
|
6242 |
if field_key.startswith('filter'): |
|
6243 |
continue |
|
6244 |
if resp.forms['listing-settings'][field_key].attrs.get('type') != 'checkbox': |
|
6245 |
continue |
|
6246 |
resp.forms['listing-settings'][field_key].checked = False |
|
6247 |
resp = resp.forms['listing-settings'].submit() |
|
6248 |
resp.forms['save-custom-view']['title'] = 'custom test view' |
|
6249 |
resp = resp.forms['save-custom-view'].submit().follow() |
|
6250 |
assert 'Views must have at least one column.' in resp.text |
|
6251 | ||
6252 | ||
6253 |
def test_backoffice_custom_view_delete(pub): |
|
6254 |
create_superuser(pub) |
|
6255 |
create_environment(pub) |
|
6256 | ||
6257 |
app = login(get_app(pub)) |
|
6258 |
resp = app.get('/backoffice/management/form-title/') |
|
6259 | ||
6260 |
# columns |
|
6261 |
resp.forms['listing-settings']['user-label'].checked = False |
|
6262 |
resp = resp.forms['listing-settings'].submit() |
|
6263 |
resp.forms['save-custom-view']['title'] = 'custom test view' |
|
6264 |
resp = resp.forms['save-custom-view'].submit() |
|
6265 |
assert resp.location.endswith('/custom-test-view/') |
|
6266 |
resp = resp.follow() |
|
6267 |
resp = resp.click('Delete View') |
|
6268 |
resp = resp.form.submit() |
|
6269 |
assert resp.location.endswith('/management/form-title/') |
|
6270 |
resp = resp.follow() |
|
6271 |
assert 'custom test view' not in resp.text |
|
6272 | ||
6273 | ||
6274 |
def test_backoffice_custom_map_view(pub): |
|
6275 |
test_backoffice_custom_view(pub) |
|
6276 | ||
6277 |
formdef = FormDef.get_by_urlname('form-title') |
|
6278 |
formdef.geolocations = {'base': 'Geolocafoobar'} |
|
6279 |
formdef.store() |
|
6280 | ||
6281 |
app = login(get_app(pub)) |
|
6282 |
resp = app.get('/backoffice/management/form-title/') |
|
6283 |
resp = resp.click('custom test view') |
|
6284 |
assert resp.text.count('<span>User Label</span>') == 0 |
|
6285 |
assert resp.text.count('<tr') == 6 |
|
6286 |
resp = resp.click('Plot on a Map') |
|
6287 |
assert resp.forms['listing-settings']['filter-2-value'].value == 'foo' |
|
6288 | ||
6289 | ||
6290 |
def test_backoffice_custom_view_visibility(pub): |
|
6291 |
create_environment(pub) |
|
6292 |
create_superuser(pub) |
|
6293 | ||
6294 |
formdef = FormDef.get_by_urlname('form-title') |
|
6295 |
agent = pub.user_class(name='agent') |
|
6296 |
agent.roles = [formdef.workflow_roles['_receiver']] |
|
6297 |
agent.store() |
|
6298 | ||
6299 |
account = PasswordAccount(id='agent') |
|
6300 |
account.set_password('agent') |
|
6301 |
account.user_id = agent.id |
|
6302 |
account.store() |
|
6303 | ||
6304 |
app = login(get_app(pub), username='agent', password='agent') |
|
6305 |
resp = app.get('/backoffice/management/form-title/') |
|
6306 | ||
6307 |
# columns |
|
6308 |
resp.forms['listing-settings']['user-label'].checked = False |
|
6309 |
resp = resp.forms['listing-settings'].submit() |
|
6310 | ||
6311 |
assert resp.text.count('<span>User Label</span>') == 0 |
|
6312 | ||
6313 |
resp.forms['save-custom-view']['title'] = 'custom test view' |
|
6314 |
assert 'visibility' not in resp.forms['save-custom-view'].fields |
|
6315 |
resp = resp.forms['save-custom-view'].submit() |
|
6316 |
assert resp.location.endswith('/custom-test-view/') |
|
6317 |
resp = resp.follow() |
|
6318 |
assert resp.text.count('<span>User Label</span>') == 0 |
|
6319 | ||
6320 |
# second agent |
|
6321 |
agent2 = pub.user_class(name='agent2') |
|
6322 |
agent2.roles = [formdef.workflow_roles['_receiver']] |
|
6323 |
agent2.store() |
|
6324 | ||
6325 |
account = PasswordAccount(id='agent2') |
|
6326 |
account.set_password('agent2') |
|
6327 |
account.user_id = agent2.id |
|
6328 |
account.store() |
|
6329 | ||
6330 |
app = login(get_app(pub), username='agent2', password='agent2') |
|
6331 |
resp = app.get('/backoffice/management/form-title/') |
|
6332 |
assert 'custom test view' not in resp |
|
6333 | ||
6334 |
# shared custom view |
|
6335 |
app = login(get_app(pub)) |
|
6336 |
resp = app.get('/backoffice/management/form-title/') |
|
6337 |
resp = resp.forms['listing-settings'].submit() |
|
6338 |
resp.forms['save-custom-view']['title'] = 'shared view' |
|
6339 |
resp.forms['save-custom-view']['visibility'] = 'any' |
|
6340 |
resp = resp.forms['save-custom-view'].submit() |
|
6341 | ||
6342 |
app = login(get_app(pub), username='agent2', password='agent2') |
|
6343 |
resp = app.get('/backoffice/management/form-title/') |
|
6344 |
resp = resp.click('shared view') |
|
6345 | ||
6346 | ||
6347 |
def test_carddata_custom_view(pub, studio): |
|
6348 |
CardDef.wipe() |
|
6349 |
user = create_user(pub) |
|
6350 |
app = login(get_app(pub)) |
|
6351 |
carddef = CardDef() |
|
6352 |
carddef.name = 'foo' |
|
6353 |
carddef.fields = [ |
|
6354 |
fields.StringField(id='1', label='Test', type='string', varname='foo'), |
|
6355 |
] |
|
6356 |
carddef.backoffice_submission_roles = user.roles |
|
6357 |
carddef.workflow_roles = {'_editor': user.roles[0]} |
|
6358 |
carddef.store() |
|
6359 |
carddef.data_class().wipe() |
|
6360 | ||
6361 |
for i in range(50): |
|
6362 |
carddata = carddef.data_class()() |
|
6363 |
carddata.data = {'1': 'FOO %s' % i} |
|
6364 |
carddata.just_created() |
|
6365 |
carddata.store() |
|
6366 | ||
6367 |
resp = app.get('/backoffice/data/foo/') |
|
6368 |
if pub.is_using_postgresql(): |
|
6369 |
assert resp.text.count('<tr') == 21 # header + rows of data |
|
6370 |
else: |
|
6371 |
# no pagination |
|
6372 |
assert resp.text.count('<tr') == 51 # header + rows of data |
|
6373 | ||
6374 |
resp = resp.forms['listing-settings'].submit() |
|
6375 |
resp.forms['save-custom-view']['title'] = 'card view' |
|
6376 |
resp = resp.forms['save-custom-view'].submit() |
|
6377 |
assert resp.location.endswith('/card-view/') |
|
6378 |
resp = resp.follow() |
tests/utilities.py | ||
---|---|---|
9 | 9 |
import sys |
10 | 10 |
import threading |
11 | 11 | |
12 |
from wcs import sql, sessions |
|
12 |
from wcs import sql, sessions, custom_views
|
|
13 | 13 | |
14 | 14 |
from webtest import TestApp |
15 | 15 |
from quixote import cleanup, get_publisher |
... | ... | |
79 | 79 |
pub.user_class = sql.SqlUser |
80 | 80 |
pub.tracking_code_class = sql.TrackingCode |
81 | 81 |
pub.session_class = sql.Session |
82 |
pub.custom_view_class = sql.CustomView |
|
82 | 83 |
pub.is_using_postgresql = lambda: True |
83 | 84 |
else: |
84 | 85 |
pub.user_class = User |
85 | 86 |
pub.tracking_code_class = TrackingCode |
86 | 87 |
pub.session_class = sessions.BasicSession |
88 |
pub.custom_view_class = custom_views.CustomView |
|
87 | 89 |
pub.is_using_postgresql = lambda: False |
88 | 90 | |
89 | 91 |
pub.session_manager_class = sessions.StorageSessionManager |
... | ... | |
165 | 167 |
sql.do_user_table() |
166 | 168 |
sql.do_tracking_code_table() |
167 | 169 |
sql.do_session_table() |
170 |
sql.do_custom_views_table() |
|
168 | 171 |
sql.do_meta_table() |
169 | 172 | |
170 | 173 |
conn.close() |
wcs/api.py | ||
---|---|---|
31 | 31 |
from .qommon.errors import (AccessForbiddenError, QueryError, TraversalError, |
32 | 32 |
UnknownNameIdAccessForbiddenError, RequestError) |
33 | 33 |
from .qommon.form import ComputedExpressionWidget, ConditionWidget |
34 |
from .qommon.storage import Equal |
|
34 | 35 | |
35 | 36 |
from wcs.categories import Category |
36 | 37 |
from wcs.conditions import Condition, ValidationError |
... | ... | |
202 | 203 |
return ApiFormdataPage(self.formdef, formdata) |
203 | 204 | |
204 | 205 |
def _q_traverse(self, path): |
206 |
if len(path) == 2 and path[0] == 'list': |
|
207 |
if path[1] == '': |
|
208 |
path = ['list'] # default view, with trailing slash |
|
209 |
else: |
|
210 |
# custom view |
|
211 |
for view in self.get_custom_views([Equal('slug', path[1])]): |
|
212 |
self.view = view |
|
213 |
path = ['list'] |
|
214 | ||
205 | 215 |
self.is_webhook = False |
206 | 216 |
if len(path) > 1: |
207 | 217 |
# webhooks have their own access checks, request cannot be blocked |
wcs/backoffice/data_management.py | ||
---|---|---|
80 | 80 | |
81 | 81 |
class CardPage(FormPage): |
82 | 82 |
_q_exports = ['', 'csv', 'xls', 'ods', 'json', 'export', 'map', 'geojson', 'add', |
83 |
('save-view', 'save_view'), ('delete-view', 'delete_view'), |
|
83 | 84 |
('import-csv', 'import_csv'), |
84 | 85 |
('data-sample-csv', 'data_sample_csv')] |
86 |
admin_permisison = 'cards' |
|
85 | 87 | |
86 |
def __init__(self, component): |
|
88 |
def __init__(self, component=None, formdef=None, view=None):
|
|
87 | 89 |
try: |
88 |
self.formdef = CardDef.get_by_urlname(component) |
|
90 |
self.formdef = formdef if formdef else CardDef.get_by_urlname(component)
|
|
89 | 91 |
except KeyError: |
90 | 92 |
raise errors.TraversalError() |
91 |
self.add = CardFillPage(component) |
|
93 |
self.add = CardFillPage(self.formdef.url_name) |
|
94 |
if view: |
|
95 |
self.view = view |
|
92 | 96 | |
93 | 97 |
def can_user_add_cards(self): |
94 | 98 |
if not self.formdef.backoffice_submission_roles: |
... | ... | |
104 | 108 |
return htmltext('<span class="actions"><a href="./add/">%s</a></span>') % _('Add') |
105 | 109 | |
106 | 110 |
def get_default_filters(self, mode): |
111 |
if self.view: |
|
112 |
return self.view.get_default_filters() |
|
107 | 113 |
return () |
108 | 114 | |
109 | 115 |
def get_default_columns(self): |
110 |
field_ids = ['id', 'time'] |
|
111 |
for field in self.formdef.get_all_fields(): |
|
112 |
if hasattr(field, 'get_view_value') and field.include_in_listing: |
|
113 |
field_ids.append(field.id) |
|
116 |
if self.view: |
|
117 |
field_ids = self.view.get_columns() |
|
118 |
else: |
|
119 |
field_ids = ['id', 'time'] |
|
120 |
for field in self.formdef.get_all_fields(): |
|
121 |
if hasattr(field, 'get_view_value') and field.include_in_listing: |
|
122 |
field_ids.append(field.id) |
|
114 | 123 |
return field_ids |
115 | 124 | |
116 | 125 |
def get_filter_from_query(self, default=Ellipsis): |
... | ... | |
270 | 279 |
return redirect('import-csv?job=%s' % job.id) |
271 | 280 | |
272 | 281 |
def _q_lookup(self, component): |
282 | ||
283 |
if not self.view: |
|
284 |
for view in self.get_custom_views(): |
|
285 |
if view.slug == component: |
|
286 |
return self.__class__(formdef=self.formdef, view=view) |
|
287 | ||
273 | 288 |
try: |
274 | 289 |
filled = self.formdef.data_class().get(component) |
275 | 290 |
except KeyError: |
wcs/backoffice/management.py | ||
---|---|---|
1010 | 1010 | |
1011 | 1011 |
class FormPage(Directory): |
1012 | 1012 |
_q_exports = ['', 'csv', 'stats', 'xls', 'ods', 'json', 'export', 'map', |
1013 |
'geojson', ('filter-options', 'filter_options')] |
|
1014 | ||
1015 |
def __init__(self, component): |
|
1016 |
try: |
|
1017 |
self.formdef = FormDef.get_by_urlname(component) |
|
1018 |
except KeyError: |
|
1019 |
raise errors.TraversalError() |
|
1020 |
get_response().breadcrumb.append( (component + '/', self.formdef.name) ) |
|
1013 |
'geojson', ('filter-options', 'filter_options'), |
|
1014 |
('save-view', 'save_view'), ('delete-view', 'delete_view'),] |
|
1015 |
view = None |
|
1016 |
admin_permisison = 'forms' |
|
1017 | ||
1018 |
def __init__(self, component=None, formdef=None, view=None): |
|
1019 |
self.view_type = None |
|
1020 |
if component: |
|
1021 |
try: |
|
1022 |
self.formdef = FormDef.get_by_urlname(component) |
|
1023 |
except KeyError: |
|
1024 |
raise errors.TraversalError() |
|
1025 |
get_response().breadcrumb.append((component + '/', self.formdef.name)) |
|
1026 |
else: |
|
1027 |
self.formdef = formdef |
|
1028 |
self.view = view |
|
1029 |
get_response().breadcrumb.append((view.slug + '/', view.title)) |
|
1021 | 1030 | |
1022 | 1031 |
def check_access(self, api_name=None): |
1023 | 1032 |
session = get_session() |
... | ... | |
1033 | 1042 |
else: |
1034 | 1043 |
raise errors.AccessUnauthorizedError() |
1035 | 1044 | |
1045 |
def get_custom_views(self, criterias=None): |
|
1046 |
for view in get_publisher().custom_view_class.select(clause=criterias): |
|
1047 |
if view.match(get_request().user, self.formdef): |
|
1048 |
yield view |
|
1049 | ||
1036 | 1050 |
def get_formdata_sidebar_actions(self, qs=''): |
1037 | 1051 |
r = TemplateIO(html=True) |
1038 | 1052 |
r += htmltext(' <li><a data-base-href="ods" href="ods%s">%s</a></li>') % ( |
... | ... | |
1054 | 1068 |
r += htmltext('<ul id="sidebar-actions">') |
1055 | 1069 |
r += self.get_formdata_sidebar_actions(qs=qs) |
1056 | 1070 |
r += htmltext('</ul>') |
1071 |
views = list(self.get_custom_views()) |
|
1072 |
if views: |
|
1073 |
r += htmltext('<h3>%s</h3>') % _('Custom Views') |
|
1074 |
r += htmltext('<ul id="sidebar-custom-views">') |
|
1075 |
view_type = 'map' if self.view_type == 'map' else '' |
|
1076 |
for view in views: |
|
1077 |
if self.view: |
|
1078 |
active = bool(self.view.slug == view.slug) |
|
1079 |
r += htmltext('<li class="active">' if active else '<li>') |
|
1080 |
r += htmltext('<a href="../%s/%s">%s</a></li>') % (view.slug, view_type, view.title) |
|
1081 |
else: |
|
1082 |
r += htmltext('<li><a href="%s/%s">%s</a></li>') % (view.slug, view_type, view.title) |
|
1083 |
r += htmltext('</ul>') |
|
1057 | 1084 |
return r.getvalue() |
1058 | 1085 | |
1059 | 1086 |
def get_default_filters(self, mode): |
1087 |
if self.view: |
|
1088 |
return self.view.get_default_filters() |
|
1060 | 1089 |
if mode == 'listing': |
1061 | 1090 |
# enable status filter by default |
1062 | 1091 |
return ('status',) |
... | ... | |
1135 | 1164 |
FakeField('end', 'period-date', _('End')), |
1136 | 1165 |
] |
1137 | 1166 |
default_filters = self.get_default_filters(mode) |
1167 | ||
1138 | 1168 |
filter_fields = [] |
1139 | 1169 |
for field in period_fake_fields + self.get_formdef_fields(): |
1140 | 1170 |
field.enabled = False |
... | ... | |
1148 | 1178 |
field.enabled = 'filter-%s' % field.id in get_request().form |
1149 | 1179 |
else: |
1150 | 1180 |
field.enabled = (field.id in default_filters) |
1151 |
if field.type in ('item', 'items'): |
|
1181 |
if not self.view and field.type in ('item', 'items'):
|
|
1152 | 1182 |
field.enabled = field.in_filters |
1153 | 1183 | |
1154 |
r += htmltext('<h3><span>%s</span> <span class="change">(<a id="filter-settings">%s</a>)</span></h3>' % ( |
|
1155 |
_('Filters'), _('change'))) |
|
1184 |
r += htmltext('<h3><span>%s</span>') % _('Options') |
|
1185 |
r += htmltext('<span class="change">(') |
|
1186 |
r += htmltext('<a id="filter-settings">%s</a>') % _('filters') |
|
1187 |
if self.view_type in ('table', 'map'): |
|
1188 |
if self.view_type == 'table': |
|
1189 |
columns_settings_labels = (_('Columns Settings'), _('columns')) |
|
1190 |
elif self.view_type == 'map': |
|
1191 |
columns_settings_labels = (_('Marker Settings'), _('markers')) |
|
1192 |
r += htmltext(' - <a id="columns-settings" title="%s">%s</a>') % columns_settings_labels |
|
1193 |
r += htmltext(')</span></h3>') |
|
1194 | ||
1195 |
filters_dict = {} |
|
1196 |
if self.view: |
|
1197 |
filters_dict.update(self.view.get_filters_dict()) |
|
1198 |
filters_dict.update(get_request().form) |
|
1156 | 1199 | |
1157 | 1200 |
for filter_field in filter_fields: |
1158 | 1201 |
if not filter_field.enabled: |
1159 | 1202 |
continue |
1160 | 1203 | |
1161 | 1204 |
filter_field_key = 'filter-%s-value' % filter_field.id |
1162 |
filter_field_value = get_request().form.get(filter_field_key)
|
|
1205 |
filter_field_value = filters_dict.get(filter_field_key)
|
|
1163 | 1206 | |
1164 | 1207 |
if filter_field.type == 'status': |
1165 | 1208 |
r += htmltext('<div class="widget">') |
... | ... | |
1214 | 1257 |
options.insert(0, (None, '', '')) |
1215 | 1258 |
attrs = {'data-refresh-options': str(filter_field.id)} |
1216 | 1259 |
else: |
1217 |
current_filter = get_request().form.get('filter-%s-value' % filter_field.id)
|
|
1260 |
current_filter = filters_dict.get('filter-%s-value' % filter_field.id)
|
|
1218 | 1261 |
options = [(current_filter, '', current_filter or '')] |
1219 | 1262 |
attrs = {'data-remote-options': str(filter_field.id)} |
1220 | 1263 |
get_response().add_javascript(['jquery.js', '../../i18n.js', 'qommon.forms.js', 'select2.js']) |
... | ... | |
1263 | 1306 |
return r.getvalue() |
1264 | 1307 | |
1265 | 1308 |
def get_fields_sidebar(self, selected_filter, fields, offset=None, |
1266 |
limit=None, order_by=None, columns_settings_label=None,
|
|
1309 |
limit=None, order_by=None, |
|
1267 | 1310 |
query=None, criterias=None): |
1268 | 1311 |
get_response().add_javascript(['jquery.js', 'jquery-ui.js', 'wcs.listing.js']) |
1269 | 1312 | |
... | ... | |
1292 | 1335 | |
1293 | 1336 |
r += self.get_filter_sidebar(selected_filter=selected_filter, query=query, criterias=criterias) |
1294 | 1337 | |
1295 |
r += htmltext('<button class="refresh">%s</button>') % _('Refresh') |
|
1296 | ||
1297 |
if columns_settings_label: |
|
1298 |
r += htmltext('<button id="columns-settings">%s</button>') % columns_settings_label |
|
1338 |
r += htmltext('<button class="refresh" hidden>%s</button>') % _('Refresh') |
|
1299 | 1339 | |
1340 |
if self.view_type in ('table', 'map'): |
|
1300 | 1341 |
# column settings dialog content |
1301 | 1342 |
r += htmltext('<div style="display: none;">') |
1302 |
r += htmltext('<ul id="columns-filter">') |
|
1303 |
for field in self.get_formdef_fields(): |
|
1343 |
r += htmltext('<ul id="columns-filter" class="objects-list columns-filter">') |
|
1344 |
column_order = [] |
|
1345 |
field_ids = [x.id for x in fields] |
|
1346 | ||
1347 |
def get_column_position(x): |
|
1348 |
if x.id in field_ids: |
|
1349 |
return field_ids.index(x.id) |
|
1350 |
return 9999 |
|
1351 | ||
1352 |
for field in sorted(self.get_formdef_fields(), key=get_column_position): |
|
1304 | 1353 |
if not hasattr(field, str('get_view_value')): |
1305 | 1354 |
continue |
1306 |
r += htmltext('<li><input type="checkbox" name="%s"') % field.id |
|
1307 |
if field.id in [x.id for x in fields]:
|
|
1355 |
r += htmltext('<li><span class="handle">⣿</span><label><input type="checkbox" name="%s"') % field.id
|
|
1356 |
if field.id in field_ids:
|
|
1308 | 1357 |
r += htmltext(' checked="checked"') |
1309 |
r += htmltext(' id="fields-column-%s"') % field.id |
|
1310 | 1358 |
r += htmltext('/>') |
1311 |
r += htmltext('<label for="fields-column-%s">%s</label>') % ( |
|
1312 |
field.id, misc.ellipsize(field.label, 70)) |
|
1359 |
r += htmltext('%s</label>') % misc.ellipsize(field.label, 70) |
|
1313 | 1360 |
r += htmltext('</li>') |
1361 |
column_order.append(str(field.id)) |
|
1314 | 1362 |
r += htmltext('</ul>') |
1315 | 1363 |
r += htmltext('</div>') |
1364 |
r += htmltext('<input type="hidden" name="columns-order" value="%s">' % ','.join(column_order)) |
|
1316 | 1365 |
r += htmltext('</form>') |
1366 | ||
1367 |
r += self.get_custom_view_form().render() |
|
1368 |
r += htmltext('<button id="save-view">%s</button>') % _('Save View') |
|
1369 |
if self.can_delete_view(): |
|
1370 |
r += htmltext(' <a data-popup id="delete-view" href="./delete-view" class="button">%s</a>') % _('Delete View') |
|
1371 | ||
1317 | 1372 |
return r.getvalue() |
1318 | 1373 | |
1374 |
def get_custom_view_form(self): |
|
1375 |
form = Form(method='post', id='save-custom-view', hidden='hidden', action='save-view') |
|
1376 |
form.add(HiddenWidget, 'qs', value=get_request().get_query()) |
|
1377 |
form.add(StringWidget, 'title', title=_('Title'), required=True, |
|
1378 |
value=self.view.title if self.view else None) |
|
1379 |
if get_publisher().get_backoffice_root().is_accessible(self.admin_permisison): |
|
1380 |
# admins can create views accessible to everyone |
|
1381 |
form.add(RadiobuttonsWidget, 'visibility', title=_('Visibility'), |
|
1382 |
value=self.view.visibility if self.view else 'owner', |
|
1383 |
options=[ |
|
1384 |
('owner', _('to me only'), 'owner'), |
|
1385 |
('any', _('to any users'), 'any') |
|
1386 |
]) |
|
1387 |
if self.view and (self.view.user_id == get_request().user.id or |
|
1388 |
get_publisher().get_backoffice_root().is_accessible('forms')): |
|
1389 |
form.add(CheckboxWidget, 'update', title=_('Update existing view settings'), value=True) |
|
1390 |
form.add_submit('submit', _('Save View')) |
|
1391 |
form.add_submit('cancel', _('Cancel')) |
|
1392 |
return form |
|
1393 | ||
1394 |
def save_view(self): |
|
1395 |
form = self.get_custom_view_form() |
|
1396 |
if form.get_widget('update') and form.get_widget('update').parse(): |
|
1397 |
custom_view = self.view |
|
1398 |
else: |
|
1399 |
custom_view = get_publisher().custom_view_class() |
|
1400 |
custom_view.title = form.get_widget('title').parse() |
|
1401 |
if not custom_view.title: |
|
1402 |
get_session().message = ('error', _('Missing title.')) |
|
1403 |
return redirect('.') |
|
1404 |
custom_view.user = get_request().user |
|
1405 |
custom_view.formdef = self.formdef |
|
1406 |
custom_view.set_from_qs(form.get_widget('qs').parse()) |
|
1407 |
if not custom_view.columns['list']: |
|
1408 |
get_session().message = ('error', _('Views must have at least one column.')) |
|
1409 |
return redirect('.') |
|
1410 |
if form.get_widget('visibility'): |
|
1411 |
custom_view.visibility = form.get_widget('visibility').parse() |
|
1412 |
custom_view.store() |
|
1413 |
if self.view: |
|
1414 |
return redirect('../' + custom_view.slug + '/') |
|
1415 |
else: |
|
1416 |
return redirect(custom_view.slug + '/') |
|
1417 | ||
1418 |
def can_delete_view(self): |
|
1419 |
if not self.view: |
|
1420 |
return False |
|
1421 |
if str(self.view.user_id) == str(get_request().user.id): |
|
1422 |
return True |
|
1423 |
return get_publisher().get_backoffice_root().is_accessible(self.admin_permisison) |
|
1424 | ||
1425 |
def delete_view(self): |
|
1426 |
if not self.can_delete_view(): |
|
1427 |
raise errors.AccessForbiddenError() |
|
1428 |
form = Form(enctype='multipart/form-data') |
|
1429 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
1430 |
'You are about to remove the \"%s\" custom view.') % self.view.title)) |
|
1431 |
if self.view.visibility == 'any': |
|
1432 |
form.widgets.append(HtmlWidget('<div class="warningnotice"<p>%s</p></div>' % _( |
|
1433 |
'Beware this view is available to all users, and will thus be removed for everyone.'))) |
|
1434 |
form.add_submit('delete', _('Delete')) |
|
1435 |
form.add_submit('cancel', _('Cancel')) |
|
1436 |
if form.get_widget('cancel').parse(): |
|
1437 |
return redirect('.') |
|
1438 |
if not form.is_submitted() or form.has_errors(): |
|
1439 |
r = TemplateIO(html=True) |
|
1440 |
r += htmltext('<h2>%s</h2>') % (_('Delete Custom View')) |
|
1441 |
r += form.render() |
|
1442 |
return r.getvalue() |
|
1443 |
else: |
|
1444 |
self.view.remove_self() |
|
1445 |
return redirect('..') |
|
1446 | ||
1319 | 1447 |
def get_formdef_fields(self): |
1320 | 1448 |
fields = [] |
1321 | 1449 |
fields.append(FakeField('id', 'id', _('Number'))) |
... | ... | |
1333 | 1461 |
return fields |
1334 | 1462 | |
1335 | 1463 |
def get_default_columns(self): |
1336 |
field_ids = ['id', 'time', 'last_update_time', 'user-label'] |
|
1337 |
for field in self.formdef.get_all_fields(): |
|
1338 |
if hasattr(field, 'get_view_value') and field.include_in_listing: |
|
1339 |
field_ids.append(field.id) |
|
1340 |
field_ids.append('status') |
|
1464 |
if self.view: |
|
1465 |
field_ids = self.view.get_columns() |
|
1466 |
else: |
|
1467 |
field_ids = ['id', 'time', 'last_update_time', 'user-label'] |
|
1468 |
for field in self.formdef.get_all_fields(): |
|
1469 |
if hasattr(field, 'get_view_value') and field.include_in_listing: |
|
1470 |
field_ids.append(field.id) |
|
1471 |
field_ids.append('status') |
|
1341 | 1472 |
return field_ids |
1342 | 1473 | |
1343 | 1474 |
def get_fields_from_query(self, ignore_form=False): |
... | ... | |
1350 | 1481 |
if field.id in field_ids: |
1351 | 1482 |
fields.append(field) |
1352 | 1483 | |
1484 |
if 'columns-order' in get_request().form or self.view: |
|
1485 |
if ignore_form or 'columns-order' not in get_request().form: |
|
1486 |
field_order = field_ids |
|
1487 |
else: |
|
1488 |
field_order = get_request().form['columns-order'].split(',') |
|
1489 | ||
1490 |
def field_position(x): |
|
1491 |
if x.id in field_order: |
|
1492 |
return field_order.index(x.id) |
|
1493 |
return 9999 |
|
1494 | ||
1495 |
fields.sort(key=field_position) |
|
1496 | ||
1353 | 1497 |
if not fields: |
1354 | 1498 |
return self.get_fields_from_query(ignore_form=True) |
1355 | 1499 | |
... | ... | |
1358 | 1502 |
def get_filter_from_query(self, default='waiting'): |
1359 | 1503 |
if 'filter' in get_request().form: |
1360 | 1504 |
return get_request().form['filter'] |
1505 |
if self.view: |
|
1506 |
view_filter = self.view.get_filter() |
|
1507 |
if view_filter: |
|
1508 |
return view_filter |
|
1361 | 1509 |
if self.formdef.workflow.possible_status: |
1362 | 1510 |
return default |
1363 | 1511 |
return 'all' |
... | ... | |
1371 | 1519 |
] |
1372 | 1520 |
filter_fields = [] |
1373 | 1521 |
criterias = [] |
1522 | ||
1523 |
filters_dict = {} |
|
1524 |
if self.view: |
|
1525 |
filters_dict.update(self.view.get_filters_dict()) |
|
1526 |
filters_dict.update(get_request().form) |
|
1527 | ||
1374 | 1528 |
for filter_field in period_fake_fields + self.get_formdef_fields(): |
1375 | 1529 |
if filter_field.type not in ('item', 'bool', 'items', 'period-date'): |
1376 | 1530 |
continue |
... | ... | |
1380 | 1534 |
if filter_field.varname: |
1381 | 1535 |
# if this is a field with a varname and filter-%(varname)s is |
1382 | 1536 |
# present in the query string, enable this filter. |
1383 |
if get_request().form.get('filter-%s' % filter_field.varname):
|
|
1537 |
if filters_dict.get('filter-%s' % filter_field.varname):
|
|
1384 | 1538 |
filter_field_key = 'filter-%s' % filter_field.varname |
1385 | 1539 | |
1386 |
if get_request().form.get('filter-%s' % filter_field.id):
|
|
1540 |
if filters_dict.get('filter-%s' % filter_field.id):
|
|
1387 | 1541 |
# if there's a filter-%(id)s, it is used to enable the actual |
1388 | 1542 |
# filter, and the value will be found in filter-%s-value. |
1389 | 1543 |
filter_field_key = 'filter-%s-value' % filter_field.id |
... | ... | |
1392 | 1546 |
# if there's not known filter key, skip. |
1393 | 1547 |
continue |
1394 | 1548 | |
1395 |
filter_field_value = get_request().form.get(filter_field_key)
|
|
1549 |
filter_field_value = filters_dict.get(filter_field_key)
|
|
1396 | 1550 |
if not filter_field_value: |
1397 | 1551 |
continue |
1398 | 1552 | |
... | ... | |
1449 | 1603 |
return mass_actions |
1450 | 1604 | |
1451 | 1605 |
def _q_index(self): |
1606 |
self.view_type = 'table' |
|
1452 | 1607 |
self.check_access() |
1453 | 1608 |
get_logger().info('backoffice - form %s - listing' % self.formdef.name) |
1454 | 1609 | |
... | ... | |
1467 | 1622 |
else: |
1468 | 1623 |
limit = get_request().form.get('limit', 0) |
1469 | 1624 |
offset = get_request().form.get('offset', 0) |
1470 |
order_by = get_request().form.get('order_by', |
|
1471 |
get_publisher().get_site_option('default-sort-order') or '-receipt_time') |
|
1625 |
order_by = get_request().form.get('order_by') |
|
1626 |
if self.view and not order_by: |
|
1627 |
order_by = self.view.order_by |
|
1628 |
if not order_by: |
|
1629 |
order_by = get_publisher().get_site_option('default-sort-order') or '-receipt_time' |
|
1472 | 1630 |
query = get_request().form.get('q') |
1473 | 1631 | |
1474 | 1632 |
qs = '' |
... | ... | |
1510 | 1668 |
get_response().filter = {'raw': True} |
1511 | 1669 |
return table |
1512 | 1670 | |
1513 |
html_top('management', '%s - %s' % (_('Listing'), self.formdef.name)) |
|
1671 |
view_name = self.view.title if self.view else _('Listing') |
|
1672 |
html_top('management', '%s - %s' % (view_name, self.formdef.name)) |
|
1514 | 1673 |
r = TemplateIO(html=True) |
1515 | 1674 |
r += htmltext('<div id="appbar">') |
1516 |
r += htmltext('<h2>%s - %s</h2>') % (self.formdef.name, _('Listing'))
|
|
1675 |
r += htmltext('<h2>%s - %s</h2>') % (self.formdef.name, view_name)
|
|
1517 | 1676 |
r += get_session().display_message() |
1518 | 1677 |
r += self.listing_top_actions() |
1519 | 1678 |
r += htmltext('</div>') |
... | ... | |
1526 | 1685 |
get_response().filter['sidebar'] = self.get_formdata_sidebar(qs) + \ |
1527 | 1686 |
self.get_fields_sidebar(selected_filter, fields, limit=limit, |
1528 | 1687 |
query=query, criterias=criterias, |
1529 |
offset=offset, order_by=order_by, |
|
1530 |
columns_settings_label=_('Columns Settings')) |
|
1688 |
offset=offset, order_by=order_by) |
|
1531 | 1689 | |
1532 | 1690 |
return r.getvalue() |
1533 | 1691 | |
... | ... | |
1841 | 1999 |
selected_filter = self.get_filter_from_query(default='all') |
1842 | 2000 |
criterias = self.get_criterias_from_query() |
1843 | 2001 |
order_by = get_request().form.get('order_by', None) |
2002 |
if self.view and not order_by: |
|
2003 |
order_by = self.view.order_by |
|
1844 | 2004 |
query = get_request().form.get('q') if not anonymise else None |
1845 | 2005 |
offset = None |
1846 | 2006 |
if 'offset' in get_request().form: |
... | ... | |
1872 | 2032 |
'receipt_time': datetime.datetime(*filled.receipt_time[:6]), |
1873 | 2033 |
'last_update_time': datetime.datetime(*filled.last_update_time[:6]), |
1874 | 2034 |
} for filled in items] |
1875 |
if isinstance(self.formdef, CardDef): |
|
2035 |
if isinstance(self.formdef, CardDef) or self.view: |
|
2036 |
# for cards and custom views return results in a dictionary, as it |
|
2037 |
# provides a better path for evolutions |
|
1876 | 2038 |
output = {'data': output} |
1877 | 2039 |
return json.dumps(output, |
1878 | 2040 |
cls=misc.JSONEncoder) |
... | ... | |
1980 | 2142 |
return IcsDirectory() |
1981 | 2143 | |
1982 | 2144 |
def map(self): |
2145 |
self.view_type = 'map' |
|
1983 | 2146 |
get_response().add_javascript(['qommon.map.js']) |
1984 | 2147 |
html_top('management', '%s - %s' % (_('Form'), self.formdef.name)) |
1985 | 2148 |
r = TemplateIO(html=True) |
... | ... | |
1995 | 2158 | |
1996 | 2159 |
fields = self.get_fields_from_query() |
1997 | 2160 |
selected_filter = self.get_filter_from_query() |
1998 |
get_response().filter['sidebar'] = self.get_fields_sidebar(selected_filter, |
|
1999 |
fields, columns_settings_label=_('Markers Settings')) |
|
2161 | ||
2162 |
qs = '' |
|
2163 |
if get_request().get_query(): |
|
2164 |
qs = '?' + get_request().get_query() |
|
2165 |
get_response().filter['sidebar'] = self.get_formdata_sidebar(qs) + \ |
|
2166 |
self.get_fields_sidebar(selected_filter, fields) |
|
2000 | 2167 | |
2001 | 2168 |
r += htmltext('<h2>%s - %s</h2>') % (self.formdef.name, _('Map')) |
2002 | 2169 |
r += htmltext('<div %s></div>' % ' '.join(['%s="%s"' % x for x in attrs.items()])) |
... | ... | |
2212 | 2379 |
if component == 'ics': |
2213 | 2380 |
return self.ics() |
2214 | 2381 | |
2382 |
if not self.view: |
|
2383 |
for view in self.get_custom_views([Equal('slug', component)]): |
|
2384 |
return self.__class__(formdef=self.formdef, view=view) |
|
2385 | ||
2215 | 2386 |
try: |
2216 | 2387 |
filled = self.formdef.data_class().get(component) |
2217 | 2388 |
except KeyError: |
wcs/custom_views.py | ||
---|---|---|
1 |
# w.c.s. - web application for online forms |
|
2 |
# Copyright (C) 2005-2020 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software; you can redistribute it and/or modify |
|
5 |
# it under the terms of the GNU General Public License as published by |
|
6 |
# the Free Software Foundation; either version 2 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU General Public License |
|
15 |
# along with this program; if not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
from django.utils.six.moves.urllib import parse as urlparse |
|
18 |
from quixote import get_publisher |
|
19 | ||
20 |
from wcs.carddef import CardDef |
|
21 |
from wcs.formdef import FormDef |
|
22 |
from wcs.qommon.storage import StorableObject |
|
23 |
from wcs.qommon.misc import simplify |
|
24 | ||
25 | ||
26 |
class CustomView(StorableObject): |
|
27 |
_names = 'custom-views' |
|
28 | ||
29 |
title = None |
|
30 |
slug = None |
|
31 |
user_id = None |
|
32 |
visibility = 'owner' |
|
33 |
formdef_type = None |
|
34 |
formdef_id = None |
|
35 |
columns = None |
|
36 |
filters = None |
|
37 |
order_by = None |
|
38 | ||
39 |
@property |
|
40 |
def user(self): |
|
41 |
return get_publisher().user_class.get(self.user_id) |
|
42 | ||
43 |
@user.setter |
|
44 |
def user(self, value): |
|
45 |
self.user_id = str(value.id) |
|
46 | ||
47 |
@property |
|
48 |
def formdef(self): |
|
49 |
if self.formdef_type == 'formdef': |
|
50 |
return FormDef.get(self.formdef_id) |
|
51 |
else: |
|
52 |
return CardDef.get(self.formdef_id) |
|
53 | ||
54 |
@formdef.setter |
|
55 |
def formdef(self, value): |
|
56 |
self.formdef_id = str(value.id) |
|
57 |
self.formdef_type = value.xml_root_node |
|
58 | ||
59 |
def match(self, user, formdef): |
|
60 |
if self.visibility == 'owner' and self.user_id != str(user.id): |
|
61 |
return False |
|
62 |
if self.formdef_type != formdef.xml_root_node: |
|
63 |
return False |
|
64 |
if self.formdef_id != str(formdef.id): |
|
65 |
return False |
|
66 |
return True |
|
67 | ||
68 |
def set_from_qs(self, qs): |
|
69 |
parsed_qs = urlparse.parse_qsl(qs) |
|
70 |
self.columns = { |
|
71 |
'list': [ |
|
72 |
{'id': key} for (key, value) in parsed_qs if value == 'on' and not key.startswith('filter-') |
|
73 |
], |
|
74 |
} |
|
75 | ||
76 |
columns_order = [x[1] for x in parsed_qs if x[0] == 'columns-order'] |
|
77 |
if columns_order: |
|
78 |
field_order = columns_order[0].split(',') |
|
79 | ||
80 |
def field_position(x): |
|
81 |
if x['id'] in field_order: |
|
82 |
return field_order.index(x['id']) |
|
83 |
return 9999 |
|
84 | ||
85 |
self.columns['list'].sort(key=field_position) |
|
86 | ||
87 |
order_by = [x[1] for x in parsed_qs if x[0] == 'order_by'] |
|
88 |
if order_by: |
|
89 |
self.order_by = order_by[0] |
|
90 | ||
91 |
self.filters = {key: value for (key, value) in parsed_qs if key.startswith('filter')} |
|
92 | ||
93 |
def ensure_slug(self): |
|
94 |
if self.slug: |
|
95 |
return |
|
96 |
existing_slugs = { |
|
97 |
x.slug: True |
|
98 |
for x in self.select(ignore_errors=True) |
|
99 |
if (x.user_id == self.user_id and x.visibility == 'owner' and self.visibility == 'owner') |
|
100 |
and x.formdef_type == self.formdef_type |
|
101 |
and x.formdef_id == self.formdef_id |
|
102 |
} |
|
103 |
base_slug = simplify(self.title) |
|
104 |
self.slug = base_slug |
|
105 |
i = 2 |
|
106 |
while self.slug in existing_slugs: |
|
107 |
self.slug = '%s-%s' % (base_slug, i) |
|
108 |
i += 1 |
|
109 | ||
110 |
def store(self, *args, **kwargs): |
|
111 |
self.ensure_slug() |
|
112 |
return super(CustomView, self).store(*args, **kwargs) |
|
113 | ||
114 |
def get_columns(self): |
|
115 |
if self.columns and 'list' in self.columns: |
|
116 |
return [x['id'] for x in self.columns['list']] |
|
117 |
else: |
|
118 |
return [] |
|
119 | ||
120 |
def get_filter(self): |
|
121 |
return self.filters.get('filter') |
|
122 | ||
123 |
def get_filters_dict(self): |
|
124 |
return self.filters |
|
125 | ||
126 |
def get_default_filters(self): |
|
127 |
return [key[7:] for key in self.filters if key.startswith('filter-')] |
wcs/publisher.py | ||
---|---|---|
46 | 46 |
from .root import RootDirectory |
47 | 47 |
from .backoffice import RootDirectory as BackofficeRootDirectory |
48 | 48 |
from .admin import RootDirectory as AdminRootDirectory |
49 |
from . import custom_views |
|
49 | 50 |
from . import sessions |
50 | 51 |
from .qommon.cron import CronJob |
51 | 52 | |
... | ... | |
148 | 149 |
self.user_class = sql.SqlUser |
149 | 150 |
self.tracking_code_class = sql.TrackingCode |
150 | 151 |
self.session_class = sql.Session |
152 |
self.custom_view_class = sql.CustomView |
|
151 | 153 |
sql.get_connection(new=True) |
152 | 154 |
else: |
153 | 155 |
self.user_class = User |
154 | 156 |
self.tracking_code_class = TrackingCode |
155 | 157 |
self.session_class = sessions.BasicSession |
158 |
self.custom_view_class = custom_views.CustomView |
|
156 | 159 | |
157 | 160 |
self.session_manager_class = sessions.StorageSessionManager |
158 | 161 |
self.set_session_manager(self.session_manager_class(session_class=self.session_class)) |
... | ... | |
298 | 301 |
sql.do_session_table() |
299 | 302 |
sql.do_user_table() |
300 | 303 |
sql.do_tracking_code_table() |
304 |
sql.do_custom_views_table() |
|
301 | 305 |
sql.do_meta_table() |
302 | 306 |
from .formdef import FormDef |
303 | 307 |
from .carddef import CardDef |
wcs/qommon/static/css/dc2/admin.css | ||
---|---|---|
1084 | 1084 |
} |
1085 | 1085 | |
1086 | 1086 |
ul#field-filter, |
1087 |
ul#columns-filter {
|
|
1087 |
ul.columns-filter {
|
|
1088 | 1088 |
list-style: none; |
1089 | 1089 |
padding-left: 0; |
1090 | 1090 |
margin-left: 0; |
1091 |
} |
|
1092 | ||
1093 |
ul#field-filter { |
|
1091 | 1094 |
-webkit-column-count: 2; |
1092 | 1095 |
-moz-column-count: 2; |
1093 | 1096 |
column-count: 2; |
1094 | 1097 |
} |
1095 | 1098 | |
1099 |
ul.columns-filter span.handle { |
|
1100 |
padding: 0; |
|
1101 |
position: absolute; |
|
1102 |
width: 2em; |
|
1103 |
cursor: move; |
|
1104 |
display: inline-block; |
|
1105 |
padding: 0 0.5ex; |
|
1106 |
text-align: center; |
|
1107 |
width: 1em; |
|
1108 |
} |
|
1109 | ||
1110 |
ul.columns-filter li { |
|
1111 |
padding-left: 0; |
|
1112 |
} |
|
1113 | ||
1114 |
ul.columns-filter li label { |
|
1115 |
padding-left: 2em; |
|
1116 |
} |
|
1117 | ||
1096 | 1118 |
ul.multipage li { |
1097 | 1119 |
margin-left: 2em; |
1098 | 1120 |
} |
... | ... | |
1293 | 1315 |
} |
1294 | 1316 |
} |
1295 | 1317 | |
1318 |
a#columns-settings, |
|
1296 | 1319 |
a#filter-settings { |
1297 | 1320 |
cursor: pointer; |
1298 | 1321 |
} |
... | ... | |
1883 | 1906 |
margin-top: 1em; |
1884 | 1907 |
white-space: pre-line; |
1885 | 1908 |
} |
1909 | ||
1910 |
#sidebar-custom-views .active { |
|
1911 |
font-weight: bold; |
|
1912 |
} |
wcs/qommon/static/js/wcs.listing.js | ||
---|---|---|
201 | 201 |
/* column settings */ |
202 | 202 |
$('#columns-settings').click(function() { |
203 | 203 |
var dialog = $('<form>'); |
204 |
$('#columns-filter').clone().appendTo(dialog); |
|
205 |
$(dialog).find('input').each(function(idx, elem) { |
|
206 |
$(this).attr('id', 'dlg-' + $(this).attr('id')); |
|
207 |
}); |
|
208 |
$(dialog).find('label').each(function(idx, elem) { |
|
209 |
$(this).attr('for', 'dlg-' + $(this).attr('for')); |
|
210 |
}); |
|
204 |
var $dialog_filter = $('#columns-filter').clone().attr('id', null); |
|
205 |
$dialog_filter.appendTo(dialog); |
|
206 |
$dialog_filter.sortable({handle: '.handle'}) |
|
211 | 207 |
$(dialog).dialog({ |
212 | 208 |
modal: true, |
213 | 209 |
resizable: false, |
214 |
title: $('#columns-settings').text(),
|
|
210 |
title: $('#columns-settings').attr('title'),
|
|
215 | 211 |
width: '30em'}); |
216 | 212 |
$(dialog).dialog('option', 'buttons', [ |
217 | 213 |
{text: $('form#listing-settings button.refresh').text(), |
218 | 214 |
click: function() { |
219 |
$(this).find('input[type="checkbox"]').each(function(idx, elem) {
|
|
220 |
var checked = $(elem).prop('checked');
|
|
221 |
$('form#listing-settings input[name="' + $(elem).attr('name') + '"]').attr('checked', checked);
|
|
222 |
$('form#listing-settings input[name="' + $(elem).attr('name') + '"]').prop('checked', checked);
|
|
223 |
});
|
|
215 |
var $container = $('#columns-filter').parent();
|
|
216 |
$('#columns-filter').remove();
|
|
217 |
$dialog_filter.attr('id', 'columns-filter');
|
|
218 |
$dialog_filter.appendTo($container);
|
|
219 |
$('[name="columns-order"]').val($('#columns-filter input:checked').map(function() { return $(this).attr('name'); }).get().join());
|
|
224 | 220 |
$(this).dialog('close'); |
225 | 221 |
$('form#listing-settings').submit(); |
226 | 222 |
} |
... | ... | |
302 | 298 |
return false; |
303 | 299 |
}); |
304 | 300 | |
301 |
$('button#save-view').on('click', function() { |
|
302 |
var div_dialog = $('<div>'); |
|
303 |
$('#save-custom-view').clone().attr('hidden', null).appendTo(div_dialog); |
|
304 |
$(div_dialog).find('[name=qs]').val($('form#listing-settings').serialize()); |
|
305 |
$(div_dialog).find('.buttons').hide(); |
|
306 |
var dialog = $(div_dialog).dialog({ |
|
307 |
modal: true, |
|
308 |
resizable: false, |
|
309 |
title: $(this).text(), |
|
310 |
width: 'auto', |
|
311 |
buttons: [ |
|
312 |
{text: $(div_dialog).find('.cancel-button').text(), |
|
313 |
class: 'cancel-button', |
|
314 |
click: function() { $(this).dialog('close'); } |
|
315 |
}, |
|
316 |
{text: $(div_dialog).find('.submit-button').text(), |
|
317 |
class: 'submit-button', |
|
318 |
click: function() { $(div_dialog).find('.submit-button button').click(); return false; } |
|
319 |
} |
|
320 |
] |
|
321 |
}); |
|
322 |
return false; |
|
323 |
}); |
|
324 | ||
305 | 325 |
/* automatically refresh on filter change */ |
306 | 326 |
$('form#listing-settings select').change(function() { |
307 | 327 |
$('form#listing-settings').submit(); |
wcs/sql.py | ||
---|---|---|
16 | 16 | |
17 | 17 |
import psycopg2 |
18 | 18 |
import psycopg2.extensions |
19 |
import psycopg2.extras |
|
19 | 20 |
import datetime |
20 | 21 |
import time |
21 | 22 |
import re |
... | ... | |
38 | 39 | |
39 | 40 |
import wcs.categories |
40 | 41 |
import wcs.carddata |
42 |
import wcs.custom_views |
|
41 | 43 |
import wcs.formdata |
42 | 44 |
import wcs.tracking_code |
43 | 45 |
import wcs.users |
... | ... | |
47 | 49 |
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) |
48 | 50 |
psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY) |
49 | 51 | |
52 |
# automatically adapt dictionaries into json fields |
|
53 |
psycopg2.extensions.register_adapter(dict, psycopg2.extras.Json) |
|
54 | ||
55 | ||
50 | 56 |
SQL_TYPE_MAPPING = { |
51 | 57 |
'title': None, |
52 | 58 |
'subtitle': None, |
... | ... | |
754 | 760 |
cur.close() |
755 | 761 | |
756 | 762 | |
763 |
def do_custom_views_table(): |
|
764 |
conn, cur = get_connection_and_cursor() |
|
765 |
table_name = 'custom_views' |
|
766 | ||
767 |
cur.execute('''SELECT COUNT(*) FROM information_schema.tables |
|
768 |
WHERE table_schema = 'public' |
|
769 |
AND table_name = %s''', (table_name,)) |
|
770 |
if cur.fetchone()[0] == 0: |
|
771 |
cur.execute('''CREATE TABLE %s (id varchar PRIMARY KEY, |
|
772 |
title varchar, |
|
773 |
slug varchar, |
|
774 |
user_id varchar, |
|
775 |
visibility varchar, |
|
776 |
formdef_type varchar, |
|
777 |
formdef_id varchar, |
|
778 |
order_by varchar, |
|
779 |
columns jsonb, |
|
780 |
filters jsonb |
|
781 |
)''' % table_name) |
|
782 |
cur.execute('''SELECT column_name FROM information_schema.columns |
|
783 |
WHERE table_schema = 'public' |
|
784 |
AND table_name = %s''', (table_name,)) |
|
785 |
existing_fields = set([x[0] for x in cur.fetchall()]) |
|
786 | ||
787 |
needed_fields = set([x[0] for x in CustomView._table_static_fields]) |
|
788 | ||
789 |
# delete obsolete fields |
|
790 |
for field in (existing_fields - needed_fields): |
|
791 |
cur.execute('''ALTER TABLE %s DROP COLUMN %s''' % (table_name, field)) |
|
792 | ||
793 |
conn.commit() |
|
794 |
cur.close() |
|
795 | ||
796 | ||
757 | 797 |
@guard_postgres |
758 | 798 |
def do_meta_table(conn=None, cur=None, insert_current_sql_level=True): |
759 | 799 |
own_conn = False |
... | ... | |
2103 | 2143 |
return [] |
2104 | 2144 | |
2105 | 2145 | |
2146 |
class CustomView(SqlMixin, wcs.custom_views.CustomView): |
|
2147 |
_table_name = 'custom_views' |
|
2148 |
_table_static_fields = [ |
|
2149 |
('id', 'varchar'), |
|
2150 |
('title', 'varchar'), |
|
2151 |
('slug', 'varchar'), |
|
2152 |
('user_id', 'varchar'), |
|
2153 |
('visibility', 'varchar'), |
|
2154 |
('formdef_type', 'varchar'), |
|
2155 |
('formdef_id', 'varchar'), |
|
2156 |
('order_by', 'varchar'), |
|
2157 |
('columns', 'jsonb'), |
|
2158 |
('filters', 'jsonb'), |
|
2159 |
] |
|
2160 | ||
2161 |
@guard_postgres |
|
2162 |
@invalidate_substitution_cache |
|
2163 |
def store(self): |
|
2164 |
self.ensure_slug() |
|
2165 |
sql_dict = { |
|
2166 |
'id': self.id, |
|
2167 |
'title': self.title, |
|
2168 |
'slug': self.slug, |
|
2169 |
'user_id': self.user_id, |
|
2170 |
'visibility': self.visibility, |
|
2171 |
'formdef_type': self.formdef_type, |
|
2172 |
'formdef_id': self.formdef_id, |
|
2173 |
'order_by': self.order_by, |
|
2174 |
'columns': self.columns, |
|
2175 |
'filters': self.filters, |
|
2176 |
} |
|
2177 | ||
2178 |
conn, cur = get_connection_and_cursor() |
|
2179 |
if not self.id: |
|
2180 |
column_names = sql_dict.keys() |
|
2181 |
sql_dict['id'] = self.get_new_id() |
|
2182 |
sql_statement = '''INSERT INTO %s (%s) |
|
2183 |
VALUES (%s) |
|
2184 |
RETURNING id''' % ( |
|
2185 |
self._table_name, |
|
2186 |
', '.join(column_names), |
|
2187 |
', '.join(['%%(%s)s' % x for x in column_names])) |
|
2188 |
while True: |
|
2189 |
try: |
|
2190 |
cur.execute(sql_statement, sql_dict) |
|
2191 |
except psycopg2.IntegrityError: |
|
2192 |
conn.rollback() |
|
2193 |
sql_dict['id'] = self.get_new_id() |
|
2194 |
else: |
|
2195 |
break |
|
2196 |
self.id = str_encode(cur.fetchone()[0]) |
|
2197 |
else: |
|
2198 |
column_names = sql_dict.keys() |
|
2199 |
sql_dict['id'] = self.id |
|
2200 |
sql_statement = '''UPDATE %s SET %s WHERE id = %%(id)s RETURNING id''' % ( |
|
2201 |
self._table_name, |
|
2202 |
', '.join(['%s = %%(%s)s' % (x, x) for x in column_names])) |
|
2203 |
cur.execute(sql_statement, sql_dict) |
|
2204 |
if cur.fetchone() is None: |
|
2205 |
raise AssertionError() |
|
2206 | ||
2207 |
conn.commit() |
|
2208 |
cur.close() |
|
2209 | ||
2210 |
@classmethod |
|
2211 |
def _row2ob(cls, row): |
|
2212 |
o = cls() |
|
2213 |
for field, value in zip(cls._table_static_fields, tuple(row)): |
|
2214 |
if field[1] == 'varchar': |
|
2215 |
setattr(o, field[0], str_encode(value)) |
|
2216 |
elif field[1] == 'jsonb': |
|
2217 |
setattr(o, field[0], value) |
|
2218 |
return o |
|
2219 | ||
2220 |
@classmethod |
|
2221 |
def get_data_fields(cls): |
|
2222 |
return [] |
|
2223 | ||
2224 | ||
2106 | 2225 |
class classproperty(object): |
2107 | 2226 |
def __init__(self, f): |
2108 | 2227 |
self.f = f |
... | ... | |
2333 | 2452 |
return result |
2334 | 2453 | |
2335 | 2454 | |
2336 |
SQL_LEVEL = 36
|
|
2455 |
SQL_LEVEL = 37
|
|
2337 | 2456 | |
2338 | 2457 | |
2339 | 2458 |
def migrate_global_views(conn, cur): |
... | ... | |
2464 | 2583 |
# 25: create session_table |
2465 | 2584 |
# 32: add last_update_time column to session table |
2466 | 2585 |
do_session_table() |
2586 |
if sql_level < 37: |
|
2587 |
# 37: create custom_views tabl |
|
2588 |
do_custom_views_table() |
|
2467 | 2589 |
if sql_level < 30: |
2468 | 2590 |
# 30: actually remove evo.who on anonymised formdatas |
2469 | 2591 |
from wcs.formdef import FormDef |
2470 |
- |