From ad9b3b56cbf908f84b4d9b7b62f33ac9cb8bc364 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 4 Nov 2022 14:53:07 +0000 Subject: [PATCH 01/16] additional_checks: Rename title https://github.com/Open-Telecoms-Data/cove-ofds/issues/13#issuecomment-1302762080 --- cove_ofds/templates/cove_ofds/additional_checks_table.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cove_ofds/templates/cove_ofds/additional_checks_table.html b/cove_ofds/templates/cove_ofds/additional_checks_table.html index ed406cc..dfc1002 100644 --- a/cove_ofds/templates/cove_ofds/additional_checks_table.html +++ b/cove_ofds/templates/cove_ofds/additional_checks_table.html @@ -661,7 +661,7 @@

{% trans 'Links to spans data' %}

{% if 'node_not_used_in_any_spans' in additional_checks %} -

{% trans 'Orphaned nodes' %}

+

{% trans 'Unreferenced nodes' %}

{% trans 'Your data contains nodes that are not referenced by any spans.' %}

From a28ec7d78646db52b8b630f6f1a915de8b140a83 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 4 Nov 2022 15:16:02 +0000 Subject: [PATCH 02/16] additional_checks: Show name not matching errors together Show *_reference_name_set_but_not_in_original in the same block as *_name_does_not_match errors https://github.com/Open-Telecoms-Data/cove-ofds/issues/13#issuecomment-1302762080 --- cove_ofds/process.py | 35 ++++ .../cove_ofds/additional_checks_table.html | 167 ------------------ 2 files changed, 35 insertions(+), 167 deletions(-) diff --git a/cove_ofds/process.py b/cove_ofds/process.py index 509fff2..e228259 100644 --- a/cove_ofds/process.py +++ b/cove_ofds/process.py @@ -298,6 +298,41 @@ def process(self, process_data: dict) -> dict: context["additional_checks"], lambda i: i["type"] ) + # The library returns *_name_does_not_match and *_reference_name_set_but_not_in_original as different types, + # but in this UI we don't care - we just want to show them as one section. + # So join the 2 types of errors into 1 list. + for f1, f2 in [ + ( + "node_phase_reference_name_does_not_match", + "node_phase_reference_name_set_but_not_in_original", + ), + ( + "span_phase_reference_name_does_not_match", + "span_phase_reference_name_set_but_not_in_original", + ), + ( + "contract_related_phase_reference_name_does_not_match", + "contract_related_phase_reference_name_set_but_not_in_original", + ), + ( + "node_organisation_reference_name_does_not_match", + "node_organisation_reference_name_set_but_not_in_original", + ), + ( + "span_organisation_reference_name_does_not_match", + "span_organisation_reference_name_set_but_not_in_original", + ), + ( + "phase_organisation_reference_name_does_not_match", + "phase_organisation_reference_name_set_but_not_in_original", + ), + ]: + new_list = context["additional_checks"].get(f1, []) + context[ + "additional_checks" + ].get(f2, []) + if new_list: + context["additional_checks"][f1] = new_list + with open(self.data_filename, "w") as fp: json.dump(context, fp, indent=4) diff --git a/cove_ofds/templates/cove_ofds/additional_checks_table.html b/cove_ofds/templates/cove_ofds/additional_checks_table.html index dfc1002..18dac9a 100644 --- a/cove_ofds/templates/cove_ofds/additional_checks_table.html +++ b/cove_ofds/templates/cove_ofds/additional_checks_table.html @@ -293,85 +293,6 @@

{% trans 'Phase names' %}

{% endif %} {% endif %} -{% if 'node_phase_reference_name_set_but_not_in_original' in additional_checks %} -

{% trans 'TODO node_phase_reference_name_set_but_not_in_original' %}

-

{% trans 'TODO' %}

-
- - - - - - - - {% for additional_check in additional_checks.node_phase_reference_name_set_but_not_in_original %} - - - - - {% endfor %} - -
{% trans 'Node Id' %}{% trans 'Network ID' %}
- {{ additional_check.node_id }} - - {{ additional_check.network_id }} -
-{% endif %} - - - - -{% if 'span_phase_reference_name_set_but_not_in_original' in additional_checks %} -

{% trans 'TODO span_phase_reference_name_set_but_not_in_original' %}

-

{% trans 'TODO' %}

- - - - - - - - - {% for additional_check in additional_checks.span_phase_reference_name_set_but_not_in_original %} - - - - - {% endfor %} - -
{% trans 'Span Id' %}{% trans 'Network ID' %}
- {{ additional_check.span_id }} - - {{ additional_check.network_id }} -
-{% endif %} - - -{% if 'contract_related_phase_reference_name_set_but_not_in_original' in additional_checks %} -

{% trans 'TODO span_phase_reference_name_set_but_not_in_original' %}

-

{% trans 'TODO' %}

- - - - - - - - - {% for additional_check in additional_checks.contract_related_phase_reference_name_set_but_not_in_original %} - - - - - {% endfor %} - -
{% trans 'Contract Id' %}{% trans 'Network ID' %}
- {{ additional_check.contract_id }} - - {{ additional_check.network_id }} -
-{% endif %} - {% if 'node_organisation_reference_id_not_found' in additional_checks or 'node_organisation_reference_id_not_found' in additional_checks or 'phase_organisation_reference_id_not_found' in additional_checks %}

{% trans 'Organisation references' %}

@@ -534,94 +455,6 @@

{% trans 'Organisation names' %}

{% endif %} -{% if 'node_organisation_reference_name_set_but_not_in_original' in additional_checks %} -

{% trans 'TODO node_organisation_reference_name_set_but_not_in_original' %}

-

{% trans 'TODO ' %}

- - - - - - - - - - {% for additional_check in additional_checks.node_organisation_reference_name_set_but_not_in_original %} - - - - - - {% endfor %} - -
{% trans 'Field' %}{% trans 'Node Id' %}{% trans 'Network ID' %}
- {{ additional_check.field }} - - {{ additional_check.node_id }} - - {{ additional_check.network_id }} -
-{% endif %} - - -{% if 'span_organisation_reference_name_set_but_not_in_original' in additional_checks %} -

{% trans 'TODO span_organisation_reference_name_set_but_not_in_original' %}

-

{% trans 'TODO ' %}

- - - - - - - - - - {% for additional_check in additional_checks.span_organisation_reference_name_set_but_not_in_original %} - - - - - - {% endfor %} - -
{% trans 'Field' %}{% trans 'Span Id' %}{% trans 'Network ID' %}
- {{ additional_check.field }} - - {{ additional_check.span_id }} - - {{ additional_check.network_id }} -
-{% endif %} - - - - -{% if 'phase_organisation_reference_name_set_but_not_in_original' in additional_checks %} -

{% trans 'TODO phase_organisation_reference_name_set_but_not_in_original' %}

-

{% trans 'TODO ' %}

- - - - - - - - - {% for additional_check in additional_checks.phase_organisation_reference_name_set_but_not_in_original %} - - - - - {% endfor %} - -
{% trans 'Phase Id' %}{% trans 'Network ID' %}
- {{ additional_check.phase_id }} - - {{ additional_check.network_id }} -
-{% endif %} - - {% if 'node_international_connections_country_not_set' in additional_checks %}

{% trans 'International connection countries' %}

{% trans 'Your data contains nodes with international connections that do not specify a country. `.country` must be set for each international connection in `/nodes/internationalConnections`.' %}

From 1ba5b827c843ecb9adbc9bb3b0a02c501269d6c8 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 4 Nov 2022 15:26:12 +0000 Subject: [PATCH 03/16] libcoveweb2: Add static files from upstream --- libcoveweb2/static/dataexplore/css/style.css | 156 ++++++++++ .../glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../glyphicons-halflings-regular.svg | 288 ++++++++++++++++++ .../glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes .../static/dataexplore/fonts/tick/tick.css | 13 + .../static/dataexplore/fonts/tick/tick.eot | Bin 0 -> 2328 bytes .../static/dataexplore/fonts/tick/tick.svg | 48 +++ .../static/dataexplore/fonts/tick/tick.ttf | Bin 0 -> 2168 bytes .../static/dataexplore/fonts/tick/tick.woff | Bin 0 -> 1604 bytes .../static/dataexplore/fonts/tick/tick.woff2 | Bin 0 -> 1064 bytes 12 files changed, 505 insertions(+) create mode 100644 libcoveweb2/static/dataexplore/css/style.css create mode 100644 libcoveweb2/static/dataexplore/fonts/bootstrap/glyphicons-halflings-regular.eot create mode 100644 libcoveweb2/static/dataexplore/fonts/bootstrap/glyphicons-halflings-regular.svg create mode 100644 libcoveweb2/static/dataexplore/fonts/bootstrap/glyphicons-halflings-regular.ttf create mode 100644 libcoveweb2/static/dataexplore/fonts/bootstrap/glyphicons-halflings-regular.woff create mode 100644 libcoveweb2/static/dataexplore/fonts/bootstrap/glyphicons-halflings-regular.woff2 create mode 100644 libcoveweb2/static/dataexplore/fonts/tick/tick.css create mode 100644 libcoveweb2/static/dataexplore/fonts/tick/tick.eot create mode 100644 libcoveweb2/static/dataexplore/fonts/tick/tick.svg create mode 100644 libcoveweb2/static/dataexplore/fonts/tick/tick.ttf create mode 100644 libcoveweb2/static/dataexplore/fonts/tick/tick.woff create mode 100644 libcoveweb2/static/dataexplore/fonts/tick/tick.woff2 diff --git a/libcoveweb2/static/dataexplore/css/style.css b/libcoveweb2/static/dataexplore/css/style.css new file mode 100644 index 0000000..e702171 --- /dev/null +++ b/libcoveweb2/static/dataexplore/css/style.css @@ -0,0 +1,156 @@ +.page-header a { + text-decoration:none; +} + +.page-header a.underline:hover { + text-decoration:underline; +} + +.page-header a.btn { + float:right +} + +h1.application-name { + margin-top: 0; +} + +.title360 { + font-size: 1.4em; + margin-top: 32px; +} + +.language-select { + margin: 1px 0; +} + +#accordion .panel-heading { + cursor: pointer; +} + +#more-information .panel-heading { + cursor: pointer; +} + +.heading-in-panel { + margin-top:0px; +} + +.pointer { + cursor: pointer; +} + +.panel-title a { + text-decoration:none; +} + +.panel-title .glyphicon { + padding-right:15px; +} + +#footer, .above-footer { + padding-top: 9px; + margin: 20px 0 40px; + border-top: 1px solid #eee; +} + +show-open-if-noscript { + display:block; +} + +#footer ul { + padding-left: 0; +} + +#footer ul li { + list-style-type: none; + +} + +.thumbnail { + background-color: #f4f8fb; + text-align: center; + } + +.copy-button { + padding: 3px; + position: relative; + top: -2px; + right: -5px; + color: grey +} + +.copy-div { + margin-bottom: 5px; + margin-top: -3px +} + +.copy-span { + border: 1px solid #DDD; + border-radius: 4px; + padding: 4px; +} + +a:hover { + cursor:pointer; +} + +.left-space { + margin-left:10px +} + +.indent { + padding-left:20px +} + +.success-button { + padding-bottom: 20px; +} + +.spinner { + margin-bottom: 20px; +} + +.explanation { + font-size: 1.2em; + margin-bottom: 0px; +} + +a.anchor::before { + display: block; + content: " "; + margin-top: -30px; + height: 30px; + visibility: hidden; +} + +.message { + margin-left:30px; + margin-bottom: 10px; +} +.message span { + margin-left: -30px; + padding-right:15px; +} +.key-facts ul { + padding-left: 0; + list-style: none; +} +.key-facts ul li { + margin: 4px 0; +} + +.key-facts .glyphicon-flag { + color: #a94442; +} + +.no-bottom-margin { + margin-bottom: 0px; +} + +table { + table-layout: fixed; +} + +table td { + overflow-wrap: break-word; +} diff --git a/libcoveweb2/static/dataexplore/fonts/bootstrap/glyphicons-halflings-regular.eot b/libcoveweb2/static/dataexplore/fonts/bootstrap/glyphicons-halflings-regular.eot new file mode 100644 index 0000000000000000000000000000000000000000..b93a4953fff68df523aa7656497ee339d6026d64 GIT binary patch literal 20127 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libcoveweb2/static/dataexplore/fonts/bootstrap/glyphicons-halflings-regular.ttf b/libcoveweb2/static/dataexplore/fonts/bootstrap/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1413fc609ab6f21774de0cb7e01360095584f65b GIT binary patch literal 45404 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 G`u_lkUM?>H literal 0 HcmV?d00001 diff --git a/libcoveweb2/static/dataexplore/fonts/bootstrap/glyphicons-halflings-regular.woff2 b/libcoveweb2/static/dataexplore/fonts/bootstrap/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..64539b54c3751a6d9adb44c8e3a45ba5a73b77f0 GIT binary patch literal 18028 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 literal 0 HcmV?d00001 diff --git a/libcoveweb2/static/dataexplore/fonts/tick/tick.css b/libcoveweb2/static/dataexplore/fonts/tick/tick.css new file mode 100644 index 0000000..b39c894 --- /dev/null +++ b/libcoveweb2/static/dataexplore/fonts/tick/tick.css @@ -0,0 +1,13 @@ +/* This stylesheet generated by Transfonter (http://transfonter.org) on January 11, 2017 12:20 AM */ + +@font-face { + font-family: 'tick'; + src: url('tick.eot'); + src: url('tick.eot?#iefix') format('embedded-opentype'), + url('tick.woff2') format('woff2'), + url('tick.woff') format('woff'), + url('tick.ttf') format('truetype'), + url('tick.svg#tick') format('svg'); + font-weight: 500; + font-style: normal; +} diff --git a/libcoveweb2/static/dataexplore/fonts/tick/tick.eot b/libcoveweb2/static/dataexplore/fonts/tick/tick.eot new file mode 100644 index 0000000000000000000000000000000000000000..ce7dfb2bce603bbe0b4313cc9dc776040ce08194 GIT binary patch literal 2328 zcmdTGOKcNYaOS=J`4g}0*a;BFv5mb>AznL<9n*l)hSESO38jFk5CYP~A%XY@7l6ab;o(CgXA8eBk<2mThIa3I zeHfCY3|fw8WBeBgiqksT=Ygrx*hFJ=`43wAg=YU0F_eq^JlP@{h){_@y-4fc_%C~pD8MUQX^iaR-LWi-}XfjKz%^#do5qd z-{nhJubt3$tbmI@B0hXKzja8!yPv!iZ>%(yc#>}-QnRm&Owo>d;{!O#A2n78Pg<6Q z%Ic$891Ut~*DSO|Y+toSko}I2L8c5?WH*Vo)P#5+dXZA&Szgc@X4V?KMfjBH&&Y7iTGPHWa}IwN}MbNl65+_<*5V_ zcqQ4I3}%4t4AxVPhmCLV{dMopAJo-dY2A8ISvv>_FKqAaRRXk+o$k+g&tuZ_@o#`va-?AxkKoFPN;LnbNCjUk_tG3DuxdU5w;dE_`^*4Sxe zNJC4cIwT=QKUtUE3g=waoknMOa06mb&tgwf;T>ioCo4>1vMfoQOHvE>%7n;V(g>(B zuk}Ni6 zs>`=AY`zxm)DuyoJ)wp~)@hC3c#{1MZvwQ2Sy04EIG&{-G-DA)D_|aG#o2>uP{*Lo zgF3S!pEPkb>MUstVp~;-#W;%wcMOFR*wLw|>Y1W$#ufeWX;oD^JB-liUMmt-+z4w4 z1pJCyb9y)QFov<1>`@XXrd2bAoToO89!ca9-I`PTsN$DHX6wv{4yV=~$3A2FMAJ?) zu-4nlC5PnDT!HWa=a@>Vt!9Y6JV2^e57^J_A-qi4B^=9brhBF1lD~sYaKb3E$ivhy z6Zt~7O@5gyY2{YT$obyB&N$va=w-3A_rR^t?i5b>{bm>Q=_NGxgt`vovF6jIcv^3n z!QB2XWM3ZgrlZ3LZ_`n}KI%(HnD5Aq@JKH^>+|xM>20ZC-)L9Y{v1|Y{pqme_nsz3 z81b9K-YE4F>NcOR)e#4MGgKplFI0`XZJdfBY`1Yv@+WOvBAEpnm%$64+PFgeo1&^o z)NR2}vx!q4K-k6^HOrKZbEA zWFjvdX{{nR4nUUT^5UZrOZ++E5Tn%PD5-Om>1?65&xUyc`^dPZiqspJ6?He!Z4_NW aAyqeVf6d**eKzmZhIiv18$x@1pZy2-79Ohr literal 0 HcmV?d00001 diff --git a/libcoveweb2/static/dataexplore/fonts/tick/tick.svg b/libcoveweb2/static/dataexplore/fonts/tick/tick.svg new file mode 100644 index 0000000..5fd20d5 --- /dev/null +++ b/libcoveweb2/static/dataexplore/fonts/tick/tick.svg @@ -0,0 +1,48 @@ + + + + +Created by FontForge 20161013 at Wed Jan 11 03:13:24 2017 + By ,,, +Anonymous + + + + + + + + + + + + + + + diff --git a/libcoveweb2/static/dataexplore/fonts/tick/tick.ttf b/libcoveweb2/static/dataexplore/fonts/tick/tick.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d6bc052e86c998ae2bc4846a2a6aa0a1e30b21fd GIT binary patch literal 2168 zcmdT_&2JM|5TAK(zx)xe*Rc~Ij$<2pokF~J96P1~r6i?+(k6%kra}lv6Neb$4@gpx zLlHNmC{@*7P$`F=Dz%4J^j4q}s#MWa)dL3(l|bc!)bEO1DuFw1y$N4`LHGIX{AS+F z?3;Pxd4K@mgA2gn#Ms!u@iWEWR{*r5Bn|K0_1YMuz#=|Ryt(f{CMQqK7XWaU_|jB$ zvXOf4SU>UiXudP`?m`q|YzctxqIK!yr zV9V?}$yQnv??OM4HUow7w5AJqVGToWVPOV#LNp6J+T2Ea@QR3S--66ROsL{GP0BKR z3|IIm(0Hb~+KNu(-=1N|<|uI#5rjb^zW`o4QVF8)N~%2-$^xAkJk2%lH^0C0&z--2 zQrEsp>-K}n`bI!Rv7Rw!UFbgvYfbnJm7d}LpNw7e5Ol^3%;GXGQz%TF4Q^qVDG5}S z#Q|e8H)6)R6>gC3^Ual78de_!D7BR zC`M<87-+6xTf3t(80zq$g+-xSFjM)0DS)sT#$t|!ZCo4_KFNP4X`A>XB68%6NwTq3 zmpDU);)hI9+#AD&n=$3_&w6S1Om+MiV$K{jvt*$q(_NC#qMxkGUWIe6>Ta{UH?#q< zuWz|8rSL8*nU@tNFDRY=y!qktrD3bf z@A7Tc^%EW4e1!tc}Qn==2I4~Wu2wWVeF_Y@i=F((2kLC z61%z;RXttOt%RZ2>V2aQmPn`W zV41q!c?T;bzX~HzqtzOHwMukRbl#C#y|z@XFA`jU3QWOi0+lIxk3$)zNwi4HFkYUn zEE0Xu!_X7Ebzeg*56?L)k;Aj}&5FH;C}e5$(|s&xmi7vz^4a-Hy%x=8`>kv?OOoe2 ztas$*e#lW?UVJnXA$1@QaVlMolDb%{}?Qg;*Q W*ZP_`&(@hb^mgK7Q&>NJ&;AEUa1RCm literal 0 HcmV?d00001 diff --git a/libcoveweb2/static/dataexplore/fonts/tick/tick.woff b/libcoveweb2/static/dataexplore/fonts/tick/tick.woff new file mode 100644 index 0000000000000000000000000000000000000000..533d3dbbafd5d596e04157989544e1a9f46104d9 GIT binary patch literal 1604 zcmY*Z3pkWn7(V~pD~vQvxnzeXV~Q{ngKQ-kH72@9%a#@vIQ4FCrEY(iZ zM(FCnGs-2GC~T$Vl1rZGqM{3HWA^-QyU*_VpZ7WEeb4uu?>qlF=ZkRV^8o^2BW(a0 zSWk)kn`7huH$LBOGqf{?svd?A-dL*B21h>B2#^~>7y%Yo>AKyF0|26g+zG;+=n`h> zCldt&fMY`L0q1;KhrBKP4u&(J76UcRp8yPSQF}yT(2n>Oata2O3gGtyMoFRFDTp8d z@H7|OROViZNDSB60Bx)=s9er`*b7CdVe9EbFiA=H!+T}nktV+eR@H&uE01i_Dh|Kdt#VGnx)!(Yw z@O4PFli`(*7{}wRLEKxS+Q0Jys4Mk|cjc||Mw4;@IWE-Y_if`=>8{jRuGK27YMYna?KbfzO;v86YBgcWR1LF=uYAE3-y_3?*_q*j}e=c@Pm z@poAS>G;VlhI3w-c0GFhWvsQSX-z?lGHgPN>Uxl3KN0jnXYE5a$613ixXe>4?c}8s zxl7rjjeQN?ybmsQ9gm~}FZh3A9PY_?ApjJnfG-PC^RVfrVmQIlX z&Dik8m%Mw%xb+iwPR7~ru~mwx$-drQ&!ehBspy(2z5LFF{H{{YVyPi7%6*IAVoq8B zw~er__;XPw?PgJ3_B?U3jo&HG&ll)VkxTbGQieBPY~Hwfsfpl5#ixPk2BFovJ6*4f zi%z$+YsWb4kY9~Ib?Je?YjHKdWgu@6CzVCMaK9_-Y}HWb{f_kIzlK|e`l=m%WHh^s zH-{ZP%Dkf0^HHZR`6$@|jnWHkO9&SR3twkjGs{Hjm;Jb}9sXEHKpgukZw?>ZQZk2q zE-Bq*B8ih&2Hh$so?(?*6nWtMRqkCkaeSkb$cWzjDw)C!t z-4gY(;+ZcN&&E;l_ULTk@xx;omj=bet%3c~>9jN}6E)i>(|aN+)zjw^cR2OLP+sSH z`Mj^=_K4+aIXwxHB^pzM<(=bYL3*!>V=DJWw4JY>x#wk*)GkXQwX}@Y*9BDHrq|nF z>lpW_?e5gouX(z;b*Bnh%a^>t?wdq@O#`u~IksQ7Vj3C>GE6PSH{Cw%x|L{Zbp6Jh z@{TFG4`b_}Hcz7sMBbc9C2z71vF%NcJV!tDA~`!FM4ng~vU^q@a>Z7cHyUc`vg$~) zc7y4G>VP*5^EV%BwCqlmzftqhX_}xls&8A_c>HAqols4h$#r&4yGn7i9&u#b4L-ZR zXsYDhLhaRZL5`xYOL{R|B z#N?50Paf7+-1j;(fcRRFY)D2VK0{3QrJtuP(B!T+2b6Z;j+uU|a^NvD#|R_;t!;k+ Dhu>Vq literal 0 HcmV?d00001 diff --git a/libcoveweb2/static/dataexplore/fonts/tick/tick.woff2 b/libcoveweb2/static/dataexplore/fonts/tick/tick.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..d2c61a3a61558e428e7130fe760b24a65f3e5fb0 GIT binary patch literal 1064 zcmV+@1lRj_Pew8T0RR9100bxi4gdfE00?*h00Ynf0RR9100000000000000000000 z0000#Mn+Uk92zDDRse!x2m}!b3WgMfX$ukn0X70810Vz-1%hq|N*lli$d1uV-w9#D zloL?S)A8`QR=%HP=jM(JOo~Y&IY!5-8igW_vUTihfmChxhxfFX4lvWs{M>386(ONC@DW{>E1g((N<{+|_;JE1gIrRmMb!>z+6JbWS2zlb#8A zWYgI8%o z&`3Fzv3X2yUOhYZ*1!64yrjM<4=6cTC8hrJLh*`OhNj-UVRF`-aRJVbrU|SmZ0>py z7H~ztum~H`sGl6Hbt4@rb=_(=B;`hHchynV{l~JjG)mv4ukFTt=%n-BNB0AFGn`8~ zVA-qrD$en}0gCOHZjd%TP(mCT-J5-_nTK`YxE<2|HV923-wQ~6bZ!>$*q?&lNk)UV z+7RCfk7SoffL=l934vbmezu;l-*kTMm(m$tUKl#m@ifBi0=QTD_zo}e$|CXRrEkbb zNvTI4y#wcF798D|k)#Z6snbUiT)z&c!l{$T;(KpTJ4ioDdbG&m$)h6*3c8y--U0XX z6+eGM%XA-WHl)Hc%W8%uDJx3^EWK z0b(lYzx$aC%y~v Date: Fri, 4 Nov 2022 15:44:35 +0000 Subject: [PATCH 04/16] additional_checks: Link to schema docs https://github.com/Open-Telecoms-Data/cove-ofds/issues/13#issuecomment-1302762080 --- .../cove_ofds/additional_checks_table.html | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cove_ofds/templates/cove_ofds/additional_checks_table.html b/cove_ofds/templates/cove_ofds/additional_checks_table.html index 18dac9a..7f791f3 100644 --- a/cove_ofds/templates/cove_ofds/additional_checks_table.html +++ b/cove_ofds/templates/cove_ofds/additional_checks_table.html @@ -4,6 +4,12 @@ {% if 'span_start_node_not_found' in additional_checks or 'span_end_node_not_found' in additional_checks %}

{% trans 'Node references' %}

{% trans 'Your data contains spans with node references that cannot be resolved. `Span.start` and `Span.end` must match the `.id` of exactly one node in the `/nodes` array.' %}

+ {% if 'span_start_node_not_found' in additional_checks %} +

{% trans 'Schema Documentation' %}

+ {% endif %} + {% if 'span_end_node_not_found' in additional_checks %} +

{% trans 'Schema Documentation' %}

+ {% endif %} @@ -47,6 +53,7 @@

{% trans 'Node references' %}

{% if 'node_location_type_incorrect' in additional_checks %}

{% trans 'Node location type' %}

{% trans 'Your data contains nodes with incorrect location types. `/nodes/location/type` must be `Point`.' %}

+

{% trans 'Schema Documentation' %}

@@ -73,6 +80,7 @@

{% trans 'Node location type' %}

{% if 'node_location_coordinates_incorrect' in additional_checks %}

{% trans 'Node location coordinates' %}

{% trans 'Your data contains nodes with incorrectly formatted location coordinates. `/nodes/location/coordinates` must be a single position, i.e. an array of numbers.' %}

+

{% trans 'Schema Documentation' %}

@@ -99,6 +107,7 @@

{% trans 'Node location coordinates' %}

{% if 'span_route_type_incorrect' in additional_checks %}

{% trans 'Span route geometry' %}

{% trans 'Your data contains spans with incorrect route types. `/spans/route/type` must be `LineString`.' %}

+

{% trans 'Schema Documentation' %}

@@ -125,6 +134,7 @@

{% trans 'Span route geometry' %}

{% if 'span_route_coordinates_incorrect' in additional_checks %}

{% trans 'Span route coordinates' %}

{% trans 'Your data contains spans with incorrectly formatted route coordinates. `/spans/route/coordinates` must be an array of positions, i.e. an array of arrays of numbers.' %}

+

{% trans 'Schema Documentation' %}

@@ -151,6 +161,7 @@

{% trans 'Span route coordinates' %}

{% if 'node_phase_reference_id_not_found' in additional_checks or 'span_phase_reference_id_not_found' in additional_checks or 'contract_related_phase_reference_id_not_found' in additional_checks %}

{% trans 'Phase references' %}

{% trans 'Your data contains phase references that cannot be resolved. The `.id` of each phase reference must match the `.id` of exactly one phase in the `/phases` array.' %}

+

{% trans 'Schema Documentation' %}

{% if 'node_phase_reference_id_not_found' in additional_checks %}
@@ -225,6 +236,7 @@

{% trans 'Phase references' %}

{% if 'node_phase_reference_name_does_not_match' in additional_checks or 'span_phase_reference_name_does_not_match' in additional_checks or 'contract_related_phase_reference_name_does_not_match' in additional_checks %}

{% trans 'Phase names' %}

{% trans 'Your data contains phase references with inconsistent names. The `.name` of each phase reference must match the `.name` of the phase it references.' %}

+

{% trans 'Schema Documentation' %}

{% if 'node_phase_reference_name_does_not_match' in additional_checks %}
@@ -297,6 +309,7 @@

{% trans 'Phase names' %}

{% if 'node_organisation_reference_id_not_found' in additional_checks or 'node_organisation_reference_id_not_found' in additional_checks or 'phase_organisation_reference_id_not_found' in additional_checks %}

{% trans 'Organisation references' %}

{% trans 'Your data contains organisation references that cannot be resolved. The `.id` of each organisation reference must match the `.id` of exactly one organisation in the `/organisations` array.' %}

+

{% trans 'Schema Documentation' %}

{% if 'node_organisation_reference_id_not_found' in additional_checks %}
@@ -378,6 +391,7 @@

{% trans 'Organisation references' %}

{% if 'node_organisation_reference_name_does_not_match' in additional_checks %}

{% trans 'Organisation names' %}

{% trans 'Your data contains organisation references with inconsistent names. The `.name` of each organisation reference must match the `.name` of the organisation it references.' %}

+

{% trans 'Schema Documentation' %}

{% if 'node_organisation_reference_name_does_not_match' in additional_checks %}
@@ -458,6 +472,7 @@

{% trans 'Organisation names' %}

{% if 'node_international_connections_country_not_set' in additional_checks %}

{% trans 'International connection countries' %}

{% trans 'Your data contains nodes with international connections that do not specify a country. `.country` must be set for each international connection in `/nodes/internationalConnections`.' %}

+

{% trans 'Schema Documentation' %}

@@ -484,12 +499,14 @@

{% trans 'International connection countries' %}

{% if 'has_links_with_external_node_data' in additional_checks %}

{% trans 'Links to nodes data' %}

{% trans 'Your data contains links to API endpoints or bulk files for nodes. The additional data available from the links has not been checked. You can check the data by embedding it in network package and submitting it to CoVE.' %}

+

{% trans 'Schema Documentation' %}

{% endif %} {% if 'has_links_with_external_span_data' in additional_checks %}

{% trans 'Links to spans data' %}

{% trans 'Your data contains links to API endpoints or bulk files for nodes. The additional data available from the links has not been checked. You can check the data by embedding it in network package and submitting it to CoVE.' %}

+

{% trans 'Schema Documentation' %}

{% endif %} From 45a3c28c9bdb85f6c47aa8e1452fcef82c87ec0e Mon Sep 17 00:00:00 2001 From: James Date: Fri, 4 Nov 2022 16:02:30 +0000 Subject: [PATCH 05/16] additional_checks: Move external links feedback outside of additional checks box https://github.com/Open-Telecoms-Data/cove-ofds/issues/19 --- cove_ofds/process.py | 32 +++++++++++++++++- .../cove_ofds/additional_checks_table.html | 14 -------- cove_ofds/templates/cove_ofds/explore.html | 33 +++++++++++++++++++ 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/cove_ofds/process.py b/cove_ofds/process.py index e228259..1f476e1 100644 --- a/cove_ofds/process.py +++ b/cove_ofds/process.py @@ -291,8 +291,38 @@ def process(self, process_data: dict) -> dict: schema = OFDSSchema() worker = PythonValidate(schema) - context = {"additional_checks": worker.validate(data)} + + # has_links_with_external_node_data and has_links_with_external_span_data are shown in a different bit of UI. + # Set variables and move out of additional_checks + context["has_links_with_external_node_data"] = ( + True + if [ + r + for r in context["additional_checks"] + if r["type"] == "has_links_with_external_node_data" + ] + else False + ) + context["has_links_with_external_span_data"] = ( + True + if [ + r + for r in context["additional_checks"] + if r["type"] == "has_links_with_external_span_data" + ] + else False + ) + context["additional_checks"] = [ + r + for r in context["additional_checks"] + if ( + r["type"] != "has_links_with_external_node_data" + and r["type"] != "has_links_with_external_span_data" + ) + ] + + # Count and group what's left context["additional_checks_count"] = len(context["additional_checks"]) context["additional_checks"] = group_data_list_by( context["additional_checks"], lambda i: i["type"] diff --git a/cove_ofds/templates/cove_ofds/additional_checks_table.html b/cove_ofds/templates/cove_ofds/additional_checks_table.html index 7f791f3..94a502a 100644 --- a/cove_ofds/templates/cove_ofds/additional_checks_table.html +++ b/cove_ofds/templates/cove_ofds/additional_checks_table.html @@ -496,20 +496,6 @@

{% trans 'International connection countries' %}

{% endif %} -{% if 'has_links_with_external_node_data' in additional_checks %} -

{% trans 'Links to nodes data' %}

-

{% trans 'Your data contains links to API endpoints or bulk files for nodes. The additional data available from the links has not been checked. You can check the data by embedding it in network package and submitting it to CoVE.' %}

-

{% trans 'Schema Documentation' %}

-{% endif %} - - -{% if 'has_links_with_external_span_data' in additional_checks %} -

{% trans 'Links to spans data' %}

-

{% trans 'Your data contains links to API endpoints or bulk files for nodes. The additional data available from the links has not been checked. You can check the data by embedding it in network package and submitting it to CoVE.' %}

-

{% trans 'Schema Documentation' %}

-{% endif %} - - {% if 'node_not_used_in_any_spans' in additional_checks %}

{% trans 'Unreferenced nodes' %}

{% trans 'Your data contains nodes that are not referenced by any spans.' %}

diff --git a/cove_ofds/templates/cove_ofds/explore.html b/cove_ofds/templates/cove_ofds/explore.html index 53cf444..e533271 100644 --- a/cove_ofds/templates/cove_ofds/explore.html +++ b/cove_ofds/templates/cove_ofds/explore.html @@ -20,6 +20,39 @@

+ + {% if has_links_with_external_node_data %} +
+
+
+

+ {% trans 'Links to nodes data' %} +

+
+
+

{% trans 'Your data contains links to API endpoints or bulk files for nodes. The additional data available from the links has not been checked. You can check the data by embedding it in network package and submitting it to CoVE.' %}

+

{% trans 'Schema Documentation' %}

+
+
+
+ {% endif %} + + {% if has_links_with_external_span_data %} +
+
+
+

+ {% trans 'Links to spans data' %} +

+
+
+

{% trans 'Your data contains links to API endpoints or bulk files for nodes. The additional data available from the links has not been checked. You can check the data by embedding it in network package and submitting it to CoVE.' %}

+

{% trans 'Schema Documentation' %}

+
+
+
+ {% endif %} +
From b0458f91ea0eea08703fc15fc9e4fc02b5aae46e Mon Sep 17 00:00:00 2001 From: Duncan Dewhurst Date: Mon, 7 Nov 2022 15:06:39 +1300 Subject: [PATCH 06/16] cove_ofds/base.html: Update header and footer --- cove_ofds/templates/cove_ofds/base.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cove_ofds/templates/cove_ofds/base.html b/cove_ofds/templates/cove_ofds/base.html index f29d422..bc832da 100644 --- a/cove_ofds/templates/cove_ofds/base.html +++ b/cove_ofds/templates/cove_ofds/base.html @@ -24,16 +24,16 @@
@@ -56,7 +56,7 @@

Open Fibre Data Standard (OFDS)

{% endblock %} {% block link %} -
  • {% trans "Open Fibre Data Standard" %}
  • +
  • {% trans "Open Fibre Data Standard Documentation" %}
  • {% endblock %} {% block bottomcontent1 %} From 536a9513659d01ace125b722ab4a350dba962735 Mon Sep 17 00:00:00 2001 From: Duncan Dewhurst Date: Mon, 7 Nov 2022 15:14:06 +1300 Subject: [PATCH 07/16] cove_ofds/explore.html: Various updates - Copy-edit text - Fix repeated ids and references - Use panel-info for schema version - Combine external links panels - Add introductory paragraphs to - validation errors - additional fields - additional checks - visualisation - Use panel-warning for additional fields - Use panel-info for no additional fields - Use panel-sucess and panel-danger for visualisation --- cove_ofds/templates/cove_ofds/explore.html | 69 ++++++++++------------ 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/cove_ofds/templates/cove_ofds/explore.html b/cove_ofds/templates/cove_ofds/explore.html index e533271..99d6341 100644 --- a/cove_ofds/templates/cove_ofds/explore.html +++ b/cove_ofds/templates/cove_ofds/explore.html @@ -8,46 +8,29 @@ {% block explore_content %}
    -
    +

    {% trans 'Schema Version' %}

    -

    {% trans 'Schema Version Used' %}: {{ schema_version_used }}

    +

    {% trans 'Your data was checked against schema version' %}: {{ schema_version_used }}

    - {% if has_links_with_external_node_data %} + {% if has_links_with_external_node_data or has_links_with_external_span_data %}
    -
    -
    +
    +

    - {% trans 'Links to nodes data' %} + {% trans 'Unchecked data' %}

    -
    -

    {% trans 'Your data contains links to API endpoints or bulk files for nodes. The additional data available from the links has not been checked. You can check the data by embedding it in network package and submitting it to CoVE.' %}

    -

    {% trans 'Schema Documentation' %}

    -
    -
    -
    - {% endif %} - - {% if has_links_with_external_span_data %} -
    -
    -
    -

    - {% trans 'Links to spans data' %} -

    -
    -
    -

    {% trans 'Your data contains links to API endpoints or bulk files for nodes. The additional data available from the links has not been checked. You can check the data by embedding it in network package and submitting it to CoVE.' %}

    -

    {% trans 'Schema Documentation' %}

    +
    +

    {% trans 'Your data contains links to API endpoints or bulk files. The additional data available from the links has not been checked. You can check the data by embedding it in network package and submitting it to CoVE. For more information, see ' %}/links.

    @@ -131,10 +114,11 @@

    - {% trans 'Validation Errors' %} + {% trans 'Structure and Format' %}

    +

    {% trans 'The structure and format of your data does not conform to the OFDS schema. You should check your mapping and data pipeline for errors. For more information, see the ' %}{% trans 'reference documentation' %}.

    {% include "cove_ofds/validation_table.html" %}
    @@ -142,11 +126,11 @@

    - {% trans 'Validation Errors' %} + {% trans 'Structure and Format' %}

    -

    {% trans 'There were no validation errors!' %}

    +

    {% trans 'The structure and format of your data conforms to the OFDS schema.' %}

    {% endif %} @@ -160,25 +144,32 @@

    {% if additional_fields_count %} -
    -
    +
    +

    {% trans 'Additional Fields' %}

    +

    {% trans 'Your data contains additional fields that are not part of the OFDS schema. You should:' %}

    +
      +
    • {% trans 'Check that additional fields are not the result of typos in field names or other errors in your mapping or data pipeline.' %}
    • +
    • {% trans 'Check whether the data in these fields could be provided by using a field in the OFDS schema.' %}
    • +
    • {% trans 'Document the structure, format and meaning of additional fields in your' %}{% trans 'data user guide' %}.
    • +
    +

    {% trans 'For more information, see ' %}{% trans 'how to add additional fields' %}.

    {% include "libcoveweb2/additional_fields_table.html" %}
    {% else %} -
    -
    +
    +

    {% trans 'Additional Fields' %}

    -

    {% trans 'There were no additional fields!' %}

    +

    {% trans 'Your data contains no additional fields. For more information, see ' %}{% trans 'how to add additional fields' %}.

    {% endif %} @@ -197,9 +188,10 @@

    {% if additional_checks_count %} +

    {% trans 'Your data failed the following additional checks. You should check your mapping and data pipeline for errors.' %}

    {% include "cove_ofds/additional_checks_table.html" %} {% else %} -

    {% trans 'All checks passed!' %}

    +

    {% trans 'Your data passed all additional checks.' %}

    {% endif %}
    @@ -223,19 +215,20 @@

    -
    -
    +

    - {% trans 'Map' %} + {% trans 'Visualisation' %}

    {% if can_download_geojson %} +

    {% trans 'The GeoJSON version of your data is visualised on the map below. You should check that nodes and spans appear in the correct location. If not, you may need to ' %}{% trans 'transform your coordinates to the correct coordinate reference system' %}.

    {% else %}
    -

    {% trans 'There are no GeoJSON files to visualise!' %}

    +

    {% trans 'Your data cannot be visualised because it is not available in GeoJSON format. For more information, see ' %}{% trans 'data conversion' %}.

    {% endif %}
    From 1906256bad740915c1874f66b5a8abfc6a20ed7e Mon Sep 17 00:00:00 2001 From: James Date: Mon, 7 Nov 2022 08:54:42 +0000 Subject: [PATCH 08/16] tests: Fix Broken by b0458f91ea0eea08703fc15fc9e4fc02b5aae46e --- cove_ofds/test_functional.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cove_ofds/test_functional.py b/cove_ofds/test_functional.py index 033ecdb..ffe35a4 100644 --- a/cove_ofds/test_functional.py +++ b/cove_ofds/test_functional.py @@ -32,7 +32,7 @@ def server_url(request, live_server): ("link_text", "url"), [ ( - "Open Fibre Data Standard", + "Open Fibre Data Standard Documentation", "https://open-fibre-data-standard.readthedocs.io/en/latest/", ), ], From fa778fbef084205708d902beef5f0650abb07267 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 7 Nov 2022 09:14:04 +0000 Subject: [PATCH 09/16] geojson: Show error messages correctly. Allow .geojson files. https://github.com/Open-Telecoms-Data/cove-ofds/issues/5#issuecomment-1304923663 --- cove_ofds/views.py | 4 ++-- cove_project/settings.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cove_ofds/views.py b/cove_ofds/views.py index b03af1f..340590b 100644 --- a/cove_ofds/views.py +++ b/cove_ofds/views.py @@ -50,13 +50,13 @@ def new_geojson(request): not request.FILES[field].content_type in settings.ALLOWED_GEOJSON_CONTENT_TYPES ): - form.add_error("file_upload", "This does not appear to be a JSON file") + form.add_error(field, "This does not appear to be a JSON file") if not [ e for e in settings.ALLOWED_GEOJSON_EXTENSIONS if str(request.FILES[field].name).lower().endswith(e) ]: - form.add_error("file_upload", "This does not appear to be a JSON file") + form.add_error(field, "This does not appear to be a JSON file") # Process if form.is_valid(): diff --git a/cove_project/settings.py b/cove_project/settings.py index e2556fb..8a96d98 100644 --- a/cove_project/settings.py +++ b/cove_project/settings.py @@ -161,4 +161,4 @@ ALLOWED_GEOJSON_CONTENT_TYPES = settings.ALLOWED_JSON_CONTENT_TYPES + [ "application/geo+json" ] -ALLOWED_GEOJSON_EXTENSIONS = settings.ALLOWED_JSON_EXTENSIONS +ALLOWED_GEOJSON_EXTENSIONS = settings.ALLOWED_JSON_EXTENSIONS + [".geojson"] From fa06664f2557dabbbd429efc60931f475fc03970 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 7 Nov 2022 10:36:31 +0000 Subject: [PATCH 10/16] csvs: Allow uploading multiple CSVs https://github.com/Open-Telecoms-Data/cove-ofds/issues/15 Also change to using settings from Django settings, which allows possibility that COVE app can override settings in libcoveweb --- cove_ofds/process.py | 49 +++++++ cove_ofds/templates/cove_ofds/index.html | 3 + cove_ofds/views.py | 2 + cove_project/settings.py | 9 ++ libcoveweb2/forms.py | 121 ++++++++++++++++-- libcoveweb2/settings.py | 3 + .../templates/libcoveweb2/new_csvs.html | 19 +++ libcoveweb2/urls.py | 1 + libcoveweb2/views.py | 63 +++++++-- 9 files changed, 250 insertions(+), 20 deletions(-) create mode 100644 libcoveweb2/templates/libcoveweb2/new_csvs.html diff --git a/cove_ofds/process.py b/cove_ofds/process.py index 1f476e1..7d893fb 100644 --- a/cove_ofds/process.py +++ b/cove_ofds/process.py @@ -97,6 +97,55 @@ def get_context(self): return context +class ConvertCSVsIntoJSON(ProcessDataTask): + """If User uploaded CSVs, convert to our primary format, JSON.""" + + def process(self, process_data: dict) -> dict: + if self.supplied_data.format != "csvs": + return process_data + + # check already done + # TODO + + output_dir = os.path.join(self.supplied_data.data_dir(), "unflatten") + + os.makedirs(output_dir, exist_ok=True) + + unflatten_kwargs = { + "output_name": os.path.join(output_dir, "unflattened.json"), + "root_list_path": "networks", + "input_format": "csv", + } + + flattentool.unflatten(self.supplied_data.upload_dir(), **unflatten_kwargs) + + process_data["json_data_filename"] = os.path.join( + self.supplied_data.data_dir(), "unflatten", "unflattened.json" + ) + + return process_data + + def get_context(self): + context = {} + # original format + if self.supplied_data.format == "csvs": + context["original_format"] = "csvs" + # Download data + filename = os.path.join( + self.supplied_data.data_dir(), "unflatten", "unflattened.json" + ) + if os.path.exists(filename): + context["can_download_json"] = True + context["download_json_url"] = os.path.join( + self.supplied_data.data_url(), "unflatten", "unflattened.json" + ) + context["download_json_size"] = os.stat(filename).st_size + else: + context["can_download_json"] = False + # Return + return context + + class ConvertGeoJSONIntoJSON(ProcessDataTask): """If User uploaded GeoJSON, convert to our primary format, JSON.""" diff --git a/cove_ofds/templates/cove_ofds/index.html b/cove_ofds/templates/cove_ofds/index.html index 28ef289..35dbdc8 100644 --- a/cove_ofds/templates/cove_ofds/index.html +++ b/cove_ofds/templates/cove_ofds/index.html @@ -10,6 +10,9 @@ + diff --git a/cove_ofds/views.py b/cove_ofds/views.py index 340590b..88ba64f 100644 --- a/cove_ofds/views.py +++ b/cove_ofds/views.py @@ -9,6 +9,7 @@ from cove_ofds.forms import NewGeoJSONUploadForm from cove_ofds.process import ( AdditionalFieldsChecksTask, + ConvertCSVsIntoJSON, ConvertGeoJSONIntoJSON, ConvertJSONIntoGeoJSON, ConvertJSONIntoSpreadsheets, @@ -85,6 +86,7 @@ def explore_ofds(request, pk): # Make sure uploads are in primary format WasJSONUploaded(db_data), ConvertSpreadsheetIntoJSON(db_data), + ConvertCSVsIntoJSON(db_data), ConvertGeoJSONIntoJSON(db_data), # Convert into output formats ConvertJSONIntoGeoJSON(db_data), diff --git a/cove_project/settings.py b/cove_project/settings.py index 8a96d98..146ed30 100644 --- a/cove_project/settings.py +++ b/cove_project/settings.py @@ -158,6 +158,15 @@ # https://github.com/OpenDataServices/cove/issues/1098 FILE_UPLOAD_PERMISSIONS = 0o644 +ALLOWED_JSON_CONTENT_TYPES = settings.ALLOWED_JSON_CONTENT_TYPES +ALLOWED_JSON_EXTENSIONS = settings.ALLOWED_JSON_EXTENSIONS + +ALLOWED_SPREADSHEET_CONTENT_TYPES = settings.ALLOWED_SPREADSHEET_CONTENT_TYPES +ALLOWED_SPREADSHEET_EXTENSIONS = settings.ALLOWED_SPREADSHEET_EXTENSIONS + +ALLOWED_CSV_CONTENT_TYPES = settings.ALLOWED_CSV_CONTENT_TYPES +ALLOWED_CSV_EXTENSIONS = settings.ALLOWED_CSV_EXTENSIONS + ALLOWED_GEOJSON_CONTENT_TYPES = settings.ALLOWED_JSON_CONTENT_TYPES + [ "application/geo+json" ] diff --git a/libcoveweb2/forms.py b/libcoveweb2/forms.py index 530f46d..e26dd29 100644 --- a/libcoveweb2/forms.py +++ b/libcoveweb2/forms.py @@ -1,18 +1,15 @@ from django import forms - -from libcoveweb2.settings import ( - ALLOWED_JSON_CONTENT_TYPES, - ALLOWED_JSON_EXTENSIONS, - ALLOWED_SPREADSHEET_CONTENT_TYPES, - ALLOWED_SPREADSHEET_EXTENSIONS, -) +from django.conf import settings class NewJSONUploadForm(forms.Form): file_upload = forms.FileField( widget=forms.FileInput( attrs={ - "accept": ",".join(ALLOWED_JSON_CONTENT_TYPES + ALLOWED_JSON_EXTENSIONS) + "accept": ",".join( + settings.ALLOWED_JSON_CONTENT_TYPES + + settings.ALLOWED_JSON_EXTENSIONS + ) } ) ) @@ -23,8 +20,114 @@ class NewSpreadsheetUploadForm(forms.Form): widget=forms.FileInput( attrs={ "accept": ",".join( - ALLOWED_SPREADSHEET_CONTENT_TYPES + ALLOWED_SPREADSHEET_EXTENSIONS + settings.ALLOWED_SPREADSHEET_CONTENT_TYPES + + settings.ALLOWED_SPREADSHEET_EXTENSIONS ) } ) ) + + +class NewCSVsUploadForm(forms.Form): + # I know it's hacky to copy and paste code like this but as this needs to be replaced by + # something that allows any number of uploads with no limits this will do for now + file_field_names = ["file_upload" + str(i) for i in range(0, 10)] + file_upload0 = forms.FileField( + widget=forms.FileInput( + attrs={ + "accept": ",".join( + settings.ALLOWED_CSV_CONTENT_TYPES + settings.ALLOWED_CSV_EXTENSIONS + ) + } + ) + ) + file_upload1 = forms.FileField( + widget=forms.FileInput( + attrs={ + "accept": ",".join( + settings.ALLOWED_CSV_CONTENT_TYPES + settings.ALLOWED_CSV_EXTENSIONS + ) + } + ), + required=False, + ) + file_upload2 = forms.FileField( + widget=forms.FileInput( + attrs={ + "accept": ",".join( + settings.ALLOWED_CSV_CONTENT_TYPES + settings.ALLOWED_CSV_EXTENSIONS + ) + } + ), + required=False, + ) + file_upload3 = forms.FileField( + widget=forms.FileInput( + attrs={ + "accept": ",".join( + settings.ALLOWED_CSV_CONTENT_TYPES + settings.ALLOWED_CSV_EXTENSIONS + ) + } + ), + required=False, + ) + file_upload4 = forms.FileField( + widget=forms.FileInput( + attrs={ + "accept": ",".join( + settings.ALLOWED_CSV_CONTENT_TYPES + settings.ALLOWED_CSV_EXTENSIONS + ) + } + ), + required=False, + ) + file_upload5 = forms.FileField( + widget=forms.FileInput( + attrs={ + "accept": ",".join( + settings.ALLOWED_CSV_CONTENT_TYPES + settings.ALLOWED_CSV_EXTENSIONS + ) + } + ), + required=False, + ) + file_upload6 = forms.FileField( + widget=forms.FileInput( + attrs={ + "accept": ",".join( + settings.ALLOWED_CSV_CONTENT_TYPES + settings.ALLOWED_CSV_EXTENSIONS + ) + } + ), + required=False, + ) + file_upload7 = forms.FileField( + widget=forms.FileInput( + attrs={ + "accept": ",".join( + settings.ALLOWED_CSV_CONTENT_TYPES + settings.ALLOWED_CSV_EXTENSIONS + ) + } + ), + required=False, + ) + file_upload8 = forms.FileField( + widget=forms.FileInput( + attrs={ + "accept": ",".join( + settings.ALLOWED_CSV_CONTENT_TYPES + settings.ALLOWED_CSV_EXTENSIONS + ) + } + ), + required=False, + ) + file_upload9 = forms.FileField( + widget=forms.FileInput( + attrs={ + "accept": ",".join( + settings.ALLOWED_CSV_CONTENT_TYPES + settings.ALLOWED_CSV_EXTENSIONS + ) + } + ), + required=False, + ) diff --git a/libcoveweb2/settings.py b/libcoveweb2/settings.py index bfc3e7a..17969cb 100644 --- a/libcoveweb2/settings.py +++ b/libcoveweb2/settings.py @@ -182,3 +182,6 @@ "application/vnd.oasis.opendocument.spreadsheet", ] ALLOWED_SPREADSHEET_EXTENSIONS = [".ods", ".xlsx"] + +ALLOWED_CSV_CONTENT_TYPES = ["text/csv"] +ALLOWED_CSV_EXTENSIONS = [".csv"] diff --git a/libcoveweb2/templates/libcoveweb2/new_csvs.html b/libcoveweb2/templates/libcoveweb2/new_csvs.html new file mode 100644 index 0000000..c294701 --- /dev/null +++ b/libcoveweb2/templates/libcoveweb2/new_csvs.html @@ -0,0 +1,19 @@ +{% extends request.current_app_base_template %} +{% load i18n %} +{% load bootstrap3 %} + +{% block content %} + +
    +
    + {% csrf_token %} + {% bootstrap_form forms.upload_form %} + {% buttons %} + + {% endbuttons %} + +
    + +{% endblock %} diff --git a/libcoveweb2/urls.py b/libcoveweb2/urls.py index d8a933a..e877bc9 100644 --- a/libcoveweb2/urls.py +++ b/libcoveweb2/urls.py @@ -6,6 +6,7 @@ urlpatterns = [ re_path(r"^new_json$", libcoveweb2.views.new_json, name="new_json"), + re_path(r"^new_csvs$", libcoveweb2.views.new_csvs, name="new_csvs"), re_path( r"^new_spreadsheet$", libcoveweb2.views.new_spreadsheet, name="new_spreadsheet" ), diff --git a/libcoveweb2/views.py b/libcoveweb2/views.py index deff4d6..1e08f67 100644 --- a/libcoveweb2/views.py +++ b/libcoveweb2/views.py @@ -1,16 +1,15 @@ +from django.conf import settings from django.core.exceptions import ValidationError from django.http import HttpResponseRedirect from django.shortcuts import render from django.utils.translation import gettext_lazy as _ -from libcoveweb2.forms import NewJSONUploadForm, NewSpreadsheetUploadForm -from libcoveweb2.models import SuppliedData, SuppliedDataFile -from libcoveweb2.settings import ( - ALLOWED_JSON_CONTENT_TYPES, - ALLOWED_JSON_EXTENSIONS, - ALLOWED_SPREADSHEET_CONTENT_TYPES, - ALLOWED_SPREADSHEET_EXTENSIONS, +from libcoveweb2.forms import ( + NewCSVsUploadForm, + NewJSONUploadForm, + NewSpreadsheetUploadForm, ) +from libcoveweb2.models import SuppliedData, SuppliedDataFile def new_json(request): @@ -23,11 +22,14 @@ def new_json(request): form = forms["upload_form"] if form.is_valid(): # Extra Validation - if not request.FILES["file_upload"].content_type in ALLOWED_JSON_CONTENT_TYPES: + if ( + not request.FILES["file_upload"].content_type + in settings.ALLOWED_JSON_CONTENT_TYPES + ): form.add_error("file_upload", "This does not appear to be a JSON file") if not [ e - for e in ALLOWED_JSON_EXTENSIONS + for e in settings.ALLOWED_JSON_EXTENSIONS if str(request.FILES["file_upload"].name).lower().endswith(e) ]: form.add_error("file_upload", "This does not appear to be a JSON file") @@ -45,6 +47,45 @@ def new_json(request): return render(request, "libcoveweb2/new_json.html", {"forms": forms}) +def new_csvs(request): + + forms = { + "upload_form": NewCSVsUploadForm(request.POST, request.FILES) + if request.POST + else NewCSVsUploadForm() + } + form = forms["upload_form"] + if form.is_valid(): + # Extra Validation + for field in form.file_field_names: + if request.FILES.get(field): + if ( + not request.FILES[field].content_type + in settings.ALLOWED_CSV_CONTENT_TYPES + ): + form.add_error(field, "This does not appear to be a CSV file") + if not [ + e + for e in settings.ALLOWED_CSV_EXTENSIONS + if str(request.FILES[field].name).lower().endswith(e) + ]: + form.add_error(field, "This does not appear to be a CSV file") + + # Process + if form.is_valid(): + supplied_data = SuppliedData() + supplied_data.format = "csvs" + supplied_data.save() + + for field in form.file_field_names: + if request.FILES.get(field): + supplied_data.save_file(request.FILES[field]) + + return HttpResponseRedirect(supplied_data.get_absolute_url()) + + return render(request, "libcoveweb2/new_csvs.html", {"forms": forms}) + + def new_spreadsheet(request): forms = { @@ -57,12 +98,12 @@ def new_spreadsheet(request): # Extra Validation if ( not request.FILES["file_upload"].content_type - in ALLOWED_SPREADSHEET_CONTENT_TYPES + in settings.ALLOWED_SPREADSHEET_CONTENT_TYPES ): form.add_error("file_upload", "This does not appear to be a spreadsheet") if not [ e - for e in ALLOWED_SPREADSHEET_EXTENSIONS + for e in settings.ALLOWED_SPREADSHEET_EXTENSIONS if str(request.FILES["file_upload"].name).lower().endswith(e) ]: form.add_error("file_upload", "This does not appear to be a spreadsheet") From d2371d0e6eff9bba11106f0218e0ca535ab32be7 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 7 Nov 2022 11:12:51 +0000 Subject: [PATCH 11/16] dokku: Change to better environmental variables system Now you can pass multiple Github actions secrets instead of having to make JSON with all of them. This allows replacing one without having to deal with all of them. And better secrets filtering in log files. --- .github/workflows/branch-deploy.yml | 6 +++++- .github/workflows/live-deploy.yml | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/branch-deploy.yml b/.github/workflows/branch-deploy.yml index d344018..dcf0149 100644 --- a/.github/workflows/branch-deploy.yml +++ b/.github/workflows/branch-deploy.yml @@ -28,7 +28,11 @@ jobs: restore-keys: | ${{ runner.os }}-pip- - run: pip install dokkusd - - run: python -m dokkusd.cli deploy --appname ${{ secrets.DOKKUSD_BRANCH_APP_NAME_PREFIX }}-${GITHUB_REF##*/} + - uses: oNaiPs/secrets-to-env-action@v1 + with: + secrets: ${{ toJSON(secrets) }} + prefix: ALLSECRETS_ + - run: python -m dokkusd.cli deploy --appname ${{ secrets.DOKKUSD_BRANCH_APP_NAME_PREFIX }}-${GITHUB_REF##*/} --environmentvariablesprefixedby ALLSECRETS_DOKKUSD_BRANCH_ENVIRONMENT_VARIABLE_ env: DOKKUSD_REMOTE_HOST: ${{ secrets.DOKKUSD_BRANCH_REMOTE_HOST }} DOKKUSD_REMOTE_PORT: ${{ secrets.DOKKUSD_BRANCH_REMOTE_PORT }} diff --git a/.github/workflows/live-deploy.yml b/.github/workflows/live-deploy.yml index a7e647d..96e76ec 100644 --- a/.github/workflows/live-deploy.yml +++ b/.github/workflows/live-deploy.yml @@ -28,7 +28,11 @@ jobs: restore-keys: | ${{ runner.os }}-pip- - run: pip install dokkusd - - run: python -m dokkusd.cli deploy --appname ${{ secrets.DOKKUSD_LIVE_APP_NAME }} + - uses: oNaiPs/secrets-to-env-action@v1 + with: + secrets: ${{ toJSON(secrets) }} + prefix: ALLSECRETS_ + - run: python -m dokkusd.cli deploy --appname ${{ secrets.DOKKUSD_LIVE_APP_NAME }} --environmentvariablesprefixedby ALLSECRETS_DOKKUSD_LIVE_ENVIRONMENT_VARIABLE_ env: DOKKUSD_REMOTE_HOST: ${{ secrets.DOKKUSD_LIVE_REMOTE_HOST }} DOKKUSD_REMOTE_PORT: ${{ secrets.DOKKUSD_LIVE_REMOTE_PORT }} From b2946fd765be09d420fae7fb3c868059ca092ccb Mon Sep 17 00:00:00 2001 From: James Date: Mon, 7 Nov 2022 11:34:23 +0000 Subject: [PATCH 12/16] csvs: Can download in a ZIP https://github.com/Open-Telecoms-Data/cove-ofds/issues/16 --- cove_ofds/process.py | 22 ++++++++++++++++++++++ cove_ofds/templates/cove_ofds/explore.html | 6 ++++++ 2 files changed, 28 insertions(+) diff --git a/cove_ofds/process.py b/cove_ofds/process.py index 7d893fb..353f076 100644 --- a/cove_ofds/process.py +++ b/cove_ofds/process.py @@ -1,5 +1,6 @@ import json import os.path +import zipfile import flattentool from libcoveofds.additionalfields import AdditionalFields @@ -279,6 +280,12 @@ def get_context(self): class ConvertJSONIntoSpreadsheets(ProcessDataTask): """Convert primary format (JSON) to spreadsheets""" + def __init__(self, supplied_data): + super().__init__(supplied_data) + self.csvs_zip_filename = os.path.join( + self.supplied_data.data_dir(), "flatten", "flattened.csvs.zip" + ) + def process(self, process_data: dict) -> dict: # TODO don't run if already done @@ -292,6 +299,12 @@ def process(self, process_data: dict) -> dict: flattentool.flatten(process_data["json_data_filename"], **flatten_kwargs) + # Make Zip file of all CSV files + with zipfile.ZipFile(self.csvs_zip_filename, "w") as out_zip: + for f in os.listdir(output_dir): + if os.path.isfile(os.path.join(output_dir, f)) and f.endswith(".csv"): + out_zip.write(os.path.join(output_dir, f), arcname=f) + return process_data def get_context(self): @@ -320,6 +333,15 @@ def get_context(self): context["download_ods_size"] = os.stat(ods_filename).st_size else: context["can_download_ods"] = False + # CSVs + if os.path.exists(self.csvs_zip_filename): + context["can_download_csvs_zip"] = True + context["download_csvs_zip_url"] = os.path.join( + self.supplied_data.data_url(), "flatten", "flattened.csvs.zip" + ) + context["download_csvs_zip_size"] = os.stat(ods_filename).st_size + else: + context["can_download_csvs_zip"] = False # done! return context diff --git a/cove_ofds/templates/cove_ofds/explore.html b/cove_ofds/templates/cove_ofds/explore.html index 99d6341..08429e1 100644 --- a/cove_ofds/templates/cove_ofds/explore.html +++ b/cove_ofds/templates/cove_ofds/explore.html @@ -99,6 +99,12 @@

    {% trans 'Spreadsheet (ODS)' %} ({{ download_ods_size|filesizeformat }}) {% endif %} + {% if can_download_csvs_zip %} +
  • + + {% trans 'CSVs in a ZIP file' %} ({{ download_csvs_zip_size|filesizeformat }}) +
  • + {% endif %} {% endif %}

    From bfd3172d6d8884cc63f2a8f5dfe799d9b4ee58cb Mon Sep 17 00:00:00 2001 From: James Date: Mon, 7 Nov 2022 14:47:08 +0000 Subject: [PATCH 13/16] input: JSON Text field input --- libcoveweb2/forms.py | 4 + libcoveweb2/models.py | 23 ++++++ .../templates/libcoveweb2/new_json.html | 12 +++ libcoveweb2/views.py | 80 +++++++++++++------ 4 files changed, 93 insertions(+), 26 deletions(-) diff --git a/libcoveweb2/forms.py b/libcoveweb2/forms.py index e26dd29..9e07085 100644 --- a/libcoveweb2/forms.py +++ b/libcoveweb2/forms.py @@ -15,6 +15,10 @@ class NewJSONUploadForm(forms.Form): ) +class NewJSONTextForm(forms.Form): + paste = forms.CharField(label="Paste (JSON only)", widget=forms.Textarea) + + class NewSpreadsheetUploadForm(forms.Form): file_upload = forms.FileField( widget=forms.FileInput( diff --git a/libcoveweb2/models.py b/libcoveweb2/models.py index 4e9cc76..b7fd2e7 100644 --- a/libcoveweb2/models.py +++ b/libcoveweb2/models.py @@ -44,6 +44,29 @@ def save_file(self, f, meta={}): for chunk in f.chunks(): destination.write(chunk) + def save_file_contents( + self, + filename: str, + contents: str, + content_type: str, + charset: str = None, + meta: dict = {}, + ): + + os.makedirs(self.upload_dir(), exist_ok=True) + + supplied_data_file = SuppliedDataFile() + supplied_data_file.supplied_data = self + supplied_data_file.filename = filename + supplied_data_file.size = len(contents) + supplied_data_file.content_type = content_type + supplied_data_file.charset = charset + supplied_data_file.meta = meta + supplied_data_file.save() + + with open(supplied_data_file.upload_dir_and_filename(), "w") as destination: + destination.write(contents) + class SuppliedDataFile(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) diff --git a/libcoveweb2/templates/libcoveweb2/new_json.html b/libcoveweb2/templates/libcoveweb2/new_json.html index 5157935..780debe 100644 --- a/libcoveweb2/templates/libcoveweb2/new_json.html +++ b/libcoveweb2/templates/libcoveweb2/new_json.html @@ -17,4 +17,16 @@
    +
    +
    + {% csrf_token %} + {% bootstrap_form forms.text_form %} + {% buttons %} + + {% endbuttons %} + +
    + {% endblock %} diff --git a/libcoveweb2/views.py b/libcoveweb2/views.py index 1e08f67..1b7257a 100644 --- a/libcoveweb2/views.py +++ b/libcoveweb2/views.py @@ -6,43 +6,71 @@ from libcoveweb2.forms import ( NewCSVsUploadForm, + NewJSONTextForm, NewJSONUploadForm, NewSpreadsheetUploadForm, ) from libcoveweb2.models import SuppliedData, SuppliedDataFile +JSON_FORM_CLASSES = { + "upload_form": NewJSONUploadForm, + "text_form": NewJSONTextForm, +} + def new_json(request): forms = { - "upload_form": NewJSONUploadForm(request.POST, request.FILES) - if request.POST - else NewJSONUploadForm() + form_name: form_class() for form_name, form_class in JSON_FORM_CLASSES.items() } - form = forms["upload_form"] - if form.is_valid(): - # Extra Validation - if ( - not request.FILES["file_upload"].content_type - in settings.ALLOWED_JSON_CONTENT_TYPES - ): - form.add_error("file_upload", "This does not appear to be a JSON file") - if not [ - e - for e in settings.ALLOWED_JSON_EXTENSIONS - if str(request.FILES["file_upload"].name).lower().endswith(e) - ]: - form.add_error("file_upload", "This does not appear to be a JSON file") - - # Process + request_data = None + if request.POST: + request_data = request.POST + if request_data: + if "paste" in request_data: + form_name = "text_form" + else: + form_name = "upload_form" + forms[form_name] = JSON_FORM_CLASSES[form_name](request_data, request.FILES) + form = forms[form_name] if form.is_valid(): - supplied_data = SuppliedData() - supplied_data.format = "json" - supplied_data.save() - - supplied_data.save_file(request.FILES["file_upload"]) - - return HttpResponseRedirect(supplied_data.get_absolute_url()) + # Extra Validation + if form_name == "upload_form": + if ( + not request.FILES["file_upload"].content_type + in settings.ALLOWED_JSON_CONTENT_TYPES + ): + form.add_error( + "file_upload", "This does not appear to be a JSON file" + ) + if not [ + e + for e in settings.ALLOWED_JSON_EXTENSIONS + if str(request.FILES["file_upload"].name).lower().endswith(e) + ]: + form.add_error( + "file_upload", "This does not appear to be a JSON file" + ) + elif form_name == "text_form": + pass # TODO + + # Process + if form.is_valid(): + supplied_data = SuppliedData() + supplied_data.format = "json" + supplied_data.save() + + if form_name == "upload_form": + supplied_data.save_file(request.FILES["file_upload"]) + elif form_name == "text_form": + supplied_data.save_file_contents( + "input.json", + form.cleaned_data["paste"], + "application/json", + None, + ) + + return HttpResponseRedirect(supplied_data.get_absolute_url()) return render(request, "libcoveweb2/new_json.html", {"forms": forms}) From c5bb5c61987b3ae0f3fadec855c6e5d9c1a84c2f Mon Sep 17 00:00:00 2001 From: James Date: Mon, 7 Nov 2022 14:59:57 +0000 Subject: [PATCH 14/16] input: Style input forms --- .../templates/cove_ofds/new_geojson.html | 29 +++++++--- .../templates/libcoveweb2/new_csvs.html | 29 +++++++--- .../templates/libcoveweb2/new_json.html | 57 +++++++++++++------ .../libcoveweb2/new_spreadsheet.html | 30 +++++++--- 4 files changed, 100 insertions(+), 45 deletions(-) diff --git a/cove_ofds/templates/cove_ofds/new_geojson.html b/cove_ofds/templates/cove_ofds/new_geojson.html index d3a0513..04f08f9 100644 --- a/cove_ofds/templates/cove_ofds/new_geojson.html +++ b/cove_ofds/templates/cove_ofds/new_geojson.html @@ -5,16 +5,27 @@ {% block content %} +
    -
    - {% csrf_token %} - {% bootstrap_form forms.upload_form %} - {% buttons %} - - {% endbuttons %} - + +
    +
    +
    {% csrf_token %} + {% bootstrap_form forms.upload_form %} + {% buttons %} + + {% endbuttons %} + +
    +
    {% endblock %} diff --git a/libcoveweb2/templates/libcoveweb2/new_csvs.html b/libcoveweb2/templates/libcoveweb2/new_csvs.html index c294701..05115cd 100644 --- a/libcoveweb2/templates/libcoveweb2/new_csvs.html +++ b/libcoveweb2/templates/libcoveweb2/new_csvs.html @@ -4,16 +4,27 @@ {% block content %} +
    -
    - {% csrf_token %} - {% bootstrap_form forms.upload_form %} - {% buttons %} - - {% endbuttons %} - + +
    +
    +
    {% csrf_token %} + {% bootstrap_form forms.upload_form %} + {% buttons %} + + {% endbuttons %} + +
    +
    {% endblock %} diff --git a/libcoveweb2/templates/libcoveweb2/new_json.html b/libcoveweb2/templates/libcoveweb2/new_json.html index 780debe..056ecf7 100644 --- a/libcoveweb2/templates/libcoveweb2/new_json.html +++ b/libcoveweb2/templates/libcoveweb2/new_json.html @@ -6,27 +6,48 @@
    -
    - {% csrf_token %} - {% bootstrap_form forms.upload_form %} - {% buttons %} - - {% endbuttons %} - + +
    +
    +
    {% csrf_token %} + {% bootstrap_form forms.upload_form %} + {% buttons %} + + {% endbuttons %} + +
    +
    +
    -
    - {% csrf_token %} - {% bootstrap_form forms.text_form %} - {% buttons %} - - {% endbuttons %} - + +
    +
    +
    {% csrf_token %} + {% bootstrap_form forms.text_form %} + {% buttons %} + + {% endbuttons %} + +
    +
    {% endblock %} diff --git a/libcoveweb2/templates/libcoveweb2/new_spreadsheet.html b/libcoveweb2/templates/libcoveweb2/new_spreadsheet.html index af745fd..e717d10 100644 --- a/libcoveweb2/templates/libcoveweb2/new_spreadsheet.html +++ b/libcoveweb2/templates/libcoveweb2/new_spreadsheet.html @@ -6,15 +6,27 @@
    -
    - {% csrf_token %} - {% bootstrap_form forms.upload_form %} - {% buttons %} - - {% endbuttons %} - + +
    +
    +
    {% csrf_token %} + {% bootstrap_form forms.upload_form %} + {% buttons %} + + {% endbuttons %} + +
    +
    + + {% endblock %} From b33c21038b31f07b445cf4aefafc2d235795b14f Mon Sep 17 00:00:00 2001 From: James Date: Mon, 7 Nov 2022 15:20:00 +0000 Subject: [PATCH 15/16] input: Forms in front page in tabs & remove spreadsheets option https://github.com/Open-Telecoms-Data/cove-ofds/issues/26 https://github.com/Open-Telecoms-Data/cove-ofds/issues/5#issuecomment-1304923663 --- cove_ofds/forms.py | 6 +- cove_ofds/templates/cove_ofds/index.html | 142 +++++++++++++++++++++-- cove_ofds/views.py | 24 +++- libcoveweb2/forms.py | 15 ++- libcoveweb2/views.py | 5 + 5 files changed, 175 insertions(+), 17 deletions(-) diff --git a/cove_ofds/forms.py b/cove_ofds/forms.py index 7eafc18..049f1e4 100644 --- a/cove_ofds/forms.py +++ b/cove_ofds/forms.py @@ -4,6 +4,7 @@ class NewGeoJSONUploadForm(forms.Form): nodes_file_upload = forms.FileField( + label="Select GeoJSON Nodes file", widget=forms.FileInput( attrs={ "accept": ",".join( @@ -11,9 +12,10 @@ class NewGeoJSONUploadForm(forms.Form): + settings.ALLOWED_GEOJSON_EXTENSIONS ) } - ) + ), ) spans_file_upload = forms.FileField( + label="Select GeoJSON Spans file", widget=forms.FileInput( attrs={ "accept": ",".join( @@ -21,5 +23,5 @@ class NewGeoJSONUploadForm(forms.Form): + settings.ALLOWED_GEOJSON_EXTENSIONS ) } - ) + ), ) diff --git a/cove_ofds/templates/cove_ofds/index.html b/cove_ofds/templates/cove_ofds/index.html index 35dbdc8..2cc1f54 100644 --- a/cove_ofds/templates/cove_ofds/index.html +++ b/cove_ofds/templates/cove_ofds/index.html @@ -4,17 +4,137 @@ {% block content %} - - - -
    - Upload GeoJSON + +

    + Use the form below to submit your data. You can submit data in either JSON, GeoJSON or CSV format. + For more information, see the publication format reference. +

    + + + + + +
    +
    + + +
    + +
    +
    +
    {% csrf_token %} + {% bootstrap_form forms.json.upload_form %} + {% buttons %} + + {% endbuttons %} + +
    +
    +
    + + +
    + +
    +
    +
    {% csrf_token %} + {% bootstrap_form forms.json.text_form %} + {% buttons %} + + {% endbuttons %} + +
    +
    +
    + + +
    +
    + + +
    + +
    +
    +
    {% csrf_token %} + {% bootstrap_form forms.geojson.upload_form %} + {% buttons %} + + {% endbuttons %} + +
    +
    +
    + +
    +
    + + +
    + +
    +
    +
    {% csrf_token %} + {% bootstrap_form forms.csvs.upload_form %} + {% buttons %} + + {% endbuttons %} + +
    +
    +
    + +
    + + {% endblock %} diff --git a/cove_ofds/views.py b/cove_ofds/views.py index 88ba64f..c3c6ec9 100644 --- a/cove_ofds/views.py +++ b/cove_ofds/views.py @@ -19,7 +19,7 @@ WasJSONUploaded, ) from libcoveweb2.models import SuppliedData -from libcoveweb2.views import explore_data_context +from libcoveweb2.views import CSVS_FORM_CLASSES, JSON_FORM_CLASSES, explore_data_context logger = logging.getLogger(__name__) @@ -31,9 +31,29 @@ def default(self, obj): return json.JSONEncoder.default(self, obj) +GEOJSON_FORM_CLASSES = { + "upload_form": NewGeoJSONUploadForm, +} + + def index(request): - return render(request, "cove_ofds/index.html", {}) + forms = { + "json": { + form_name: form_class() + for form_name, form_class in JSON_FORM_CLASSES.items() + }, + "csvs": { + form_name: form_class() + for form_name, form_class in CSVS_FORM_CLASSES.items() + }, + "geojson": { + form_name: form_class() + for form_name, form_class in GEOJSON_FORM_CLASSES.items() + }, + } + + return render(request, "cove_ofds/index.html", {"forms": forms}) def new_geojson(request): diff --git a/libcoveweb2/forms.py b/libcoveweb2/forms.py index 9e07085..3e9d846 100644 --- a/libcoveweb2/forms.py +++ b/libcoveweb2/forms.py @@ -11,7 +11,8 @@ class NewJSONUploadForm(forms.Form): + settings.ALLOWED_JSON_EXTENSIONS ) } - ) + ), + label="Select JSON file", ) @@ -37,15 +38,17 @@ class NewCSVsUploadForm(forms.Form): # something that allows any number of uploads with no limits this will do for now file_field_names = ["file_upload" + str(i) for i in range(0, 10)] file_upload0 = forms.FileField( + label="Select CSV file", widget=forms.FileInput( attrs={ "accept": ",".join( settings.ALLOWED_CSV_CONTENT_TYPES + settings.ALLOWED_CSV_EXTENSIONS ) } - ) + ), ) file_upload1 = forms.FileField( + label="Select CSV file", widget=forms.FileInput( attrs={ "accept": ",".join( @@ -56,6 +59,7 @@ class NewCSVsUploadForm(forms.Form): required=False, ) file_upload2 = forms.FileField( + label="Select CSV file", widget=forms.FileInput( attrs={ "accept": ",".join( @@ -66,6 +70,7 @@ class NewCSVsUploadForm(forms.Form): required=False, ) file_upload3 = forms.FileField( + label="Select CSV file", widget=forms.FileInput( attrs={ "accept": ",".join( @@ -76,6 +81,7 @@ class NewCSVsUploadForm(forms.Form): required=False, ) file_upload4 = forms.FileField( + label="Select CSV file", widget=forms.FileInput( attrs={ "accept": ",".join( @@ -86,6 +92,7 @@ class NewCSVsUploadForm(forms.Form): required=False, ) file_upload5 = forms.FileField( + label="Select CSV file", widget=forms.FileInput( attrs={ "accept": ",".join( @@ -96,6 +103,7 @@ class NewCSVsUploadForm(forms.Form): required=False, ) file_upload6 = forms.FileField( + label="Select CSV file", widget=forms.FileInput( attrs={ "accept": ",".join( @@ -106,6 +114,7 @@ class NewCSVsUploadForm(forms.Form): required=False, ) file_upload7 = forms.FileField( + label="Select CSV file", widget=forms.FileInput( attrs={ "accept": ",".join( @@ -116,6 +125,7 @@ class NewCSVsUploadForm(forms.Form): required=False, ) file_upload8 = forms.FileField( + label="Select CSV file", widget=forms.FileInput( attrs={ "accept": ",".join( @@ -126,6 +136,7 @@ class NewCSVsUploadForm(forms.Form): required=False, ) file_upload9 = forms.FileField( + label="Select CSV file", widget=forms.FileInput( attrs={ "accept": ",".join( diff --git a/libcoveweb2/views.py b/libcoveweb2/views.py index 1b7257a..d150a55 100644 --- a/libcoveweb2/views.py +++ b/libcoveweb2/views.py @@ -75,6 +75,11 @@ def new_json(request): return render(request, "libcoveweb2/new_json.html", {"forms": forms}) +CSVS_FORM_CLASSES = { + "upload_form": NewCSVsUploadForm, +} + + def new_csvs(request): forms = { From 7553afa0b91ce000cd7a8c03d8faf50ed901c255 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 7 Nov 2022 15:50:47 +0000 Subject: [PATCH 16/16] download: Start change of design and remove spreadsheets https://github.com/Open-Telecoms-Data/cove-ofds/issues/26 https://github.com/Open-Telecoms-Data/cove-ofds/issues/24 --- cove_ofds/templates/cove_ofds/explore.html | 189 +++++++++++++-------- 1 file changed, 121 insertions(+), 68 deletions(-) diff --git a/cove_ofds/templates/cove_ofds/explore.html b/cove_ofds/templates/cove_ofds/explore.html index 08429e1..82d2206 100644 --- a/cove_ofds/templates/cove_ofds/explore.html +++ b/cove_ofds/templates/cove_ofds/explore.html @@ -38,77 +38,130 @@

    -
    -
    +
    +
    +

    {% trans 'Download Data' %} -

    -
    -
    - -

    - {% if original_format == 'json' %} - {% trans 'You uploaded data in JSON format.' %} - {% elif original_format == 'spreadsheet' %} - {% trans 'You uploaded data in a spreadsheet format.' %} - {% elif original_format == 'geojson' %} - {% trans 'You uploaded data in a GeoJSON format.' %} - {% endif %} -

    - -

    - {% trans 'You can download your original data:' %} -

    - - - - {% if can_download_geojson or can_download_xlsx or can_download_ods or can_download_json %} -

    {% trans 'We converted this to the following formats for you:' %}

    - - {% endif %} +

    +
    +
    + +

    + For more information, see the publication format reference. +

    + +
    +
    + + +
    +
    +

    + {% trans 'JSON' %} {% if original_format == 'json' %}{% trans '(original)' %}{% endif %} +

    +
    +
    + {% if original_format == 'json' %} + + {% else %} + {% if can_download_json %} + + {% endif %} + {% endif %} +
    +
    + +
    +
    + +
    +
    +

    + {% trans 'GeoJSON' %} {% if original_format == 'geojson' %}{% trans '(original)' %}{% endif %} +

    +
    +
    + {% if original_format == 'geojson' %} + + {% else %} + {% if can_download_geojson %} + + {% endif %} + {% endif %} +
    +
    + +
    +
    + +
    +
    +

    + {% trans 'CSVs' %} {% if original_format == 'csvs' %}{% trans '(original)' %}{% endif %} +

    +
    +
    + {% if original_format == 'csvs' %} + + {% else %} + {% if can_download_csvs_zip %} + + {% endif %} + {% endif %} +
    +
    + +
    +
    +
    +