udata 8.0.2.dev29304__py2.py3-none-any.whl → 9.1.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of udata might be problematic. Click here for more details.

Files changed (86) hide show
  1. udata/__init__.py +1 -1
  2. udata/api/__init__.py +2 -0
  3. udata/api/commands.py +0 -2
  4. udata/api_fields.py +41 -3
  5. udata/commands/db.py +88 -48
  6. udata/core/dataservices/factories.py +33 -0
  7. udata/core/dataservices/models.py +42 -4
  8. udata/core/dataservices/rdf.py +106 -0
  9. udata/core/dataset/csv.py +8 -1
  10. udata/core/dataset/models.py +1 -2
  11. udata/core/dataset/rdf.py +37 -128
  12. udata/core/discussions/models.py +20 -0
  13. udata/core/organization/csv.py +5 -3
  14. udata/core/reports/__init__.py +0 -0
  15. udata/core/reports/api.py +44 -0
  16. udata/core/reports/constants.py +30 -0
  17. udata/core/reports/models.py +58 -0
  18. udata/core/reuse/csv.py +3 -0
  19. udata/core/site/api.py +33 -2
  20. udata/core/site/rdf.py +6 -1
  21. udata/core/spam/models.py +6 -0
  22. udata/core/topic/models.py +3 -2
  23. udata/core/topic/parsers.py +3 -2
  24. udata/core/user/apiv2.py +28 -0
  25. udata/db/__init__.py +0 -0
  26. udata/db/tasks.py +6 -0
  27. udata/features/notifications/__init__.py +0 -1
  28. udata/forms/fields.py +2 -2
  29. udata/harvest/api.py +19 -1
  30. udata/harvest/backends/base.py +118 -10
  31. udata/harvest/backends/dcat.py +28 -7
  32. udata/harvest/models.py +6 -0
  33. udata/harvest/tests/dcat/bnodes.xml +13 -2
  34. udata/harvest/tests/test_dcat_backend.py +21 -0
  35. udata/migrations/2024-06-11-fix-reuse-datasets-references.py +35 -0
  36. udata/models/__init__.py +1 -0
  37. udata/rdf.py +113 -2
  38. udata/routing.py +1 -1
  39. udata/settings.py +3 -1
  40. udata/static/admin.js +17 -17
  41. udata/static/admin.js.map +1 -1
  42. udata/static/chunks/{18.ad41fb75ac4226e1f3ce.js → 18.1922fd0b2b7fad122991.js} +3 -3
  43. udata/static/chunks/18.1922fd0b2b7fad122991.js.map +1 -0
  44. udata/static/chunks/{7.11ac4de064ae59691d49.js → 7.e2106342e94ee09393b1.js} +2 -2
  45. udata/static/chunks/7.e2106342e94ee09393b1.js.map +1 -0
  46. udata/static/common.js +1 -1
  47. udata/static/common.js.map +1 -1
  48. udata/storage/s3.py +3 -3
  49. udata/tasks.py +1 -0
  50. udata/tests/api/test_dataservices_api.py +26 -2
  51. udata/tests/api/test_datasets_api.py +1 -1
  52. udata/tests/api/test_reports_api.py +87 -0
  53. udata/tests/apiv2/test_me_api.py +40 -0
  54. udata/tests/dataset/test_dataset_rdf.py +19 -1
  55. udata/tests/frontend/test_auth.py +1 -4
  56. udata/tests/organization/test_csv_adapter.py +0 -1
  57. udata/tests/plugin.py +2 -0
  58. udata/tests/site/test_site_api.py +0 -1
  59. udata/tests/site/test_site_rdf.py +66 -0
  60. udata/tests/test_discussions.py +24 -34
  61. udata/tests/test_model.py +3 -2
  62. udata/tests/test_utils.py +1 -1
  63. udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
  64. udata/translations/ar/LC_MESSAGES/udata.po +128 -64
  65. udata/translations/de/LC_MESSAGES/udata.mo +0 -0
  66. udata/translations/de/LC_MESSAGES/udata.po +128 -64
  67. udata/translations/es/LC_MESSAGES/udata.mo +0 -0
  68. udata/translations/es/LC_MESSAGES/udata.po +128 -64
  69. udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
  70. udata/translations/fr/LC_MESSAGES/udata.po +128 -64
  71. udata/translations/it/LC_MESSAGES/udata.mo +0 -0
  72. udata/translations/it/LC_MESSAGES/udata.po +128 -64
  73. udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
  74. udata/translations/pt/LC_MESSAGES/udata.po +128 -64
  75. udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
  76. udata/translations/sr/LC_MESSAGES/udata.po +128 -64
  77. udata/translations/udata.pot +129 -65
  78. udata/uris.py +14 -13
  79. {udata-8.0.2.dev29304.dist-info → udata-9.1.0.dist-info}/METADATA +26 -7
  80. {udata-8.0.2.dev29304.dist-info → udata-9.1.0.dist-info}/RECORD +84 -72
  81. udata/static/chunks/18.ad41fb75ac4226e1f3ce.js.map +0 -1
  82. udata/static/chunks/7.11ac4de064ae59691d49.js.map +0 -1
  83. {udata-8.0.2.dev29304.dist-info → udata-9.1.0.dist-info}/LICENSE +0 -0
  84. {udata-8.0.2.dev29304.dist-info → udata-9.1.0.dist-info}/WHEEL +0 -0
  85. {udata-8.0.2.dev29304.dist-info → udata-9.1.0.dist-info}/entry_points.txt +0 -0
  86. {udata-8.0.2.dev29304.dist-info → udata-9.1.0.dist-info}/top_level.txt +0 -0
udata/static/common.js CHANGED
@@ -1,2 +1,2 @@
1
- !function(e){function __webpack_require__(a){if(c[a])return c[a].exports;var f=c[a]={exports:{},id:a,loaded:!1};return e[a].call(f.exports,f,f.exports,__webpack_require__),f.loaded=!0,f.exports}var a=window.webpackJsonp;window.webpackJsonp=function(b,r){for(var t,_,n=0,d=[];n<b.length;n++)_=b[n],f[_]&&d.push.apply(d,f[_]),f[_]=0;for(t in r){var i=r[t];switch(typeof i){case"object":e[t]=function(a){var c=a.slice(1),f=a[0];return function(a,b,r){e[f].apply(this,[a,b,r].concat(c))}}(i);break;case"function":e[t]=i;break;default:e[t]=e[i]}}for(a&&a(b,r);d.length;)d.shift().call(null,__webpack_require__);if(r[0])return c[0]=0,__webpack_require__(0)};var c={},f={31:0};__webpack_require__.e=function(e,a){if(0===f[e])return a.call(null,__webpack_require__);if(void 0!==f[e])f[e].push(a);else{f[e]=[a];var c=document.getElementsByTagName("head")[0],b=document.createElement("script");b.type="text/javascript",b.charset="utf-8",b.async=!0,b.src=__webpack_require__.p+"chunks/"+e+"."+{0:"93c3ae13b5b94753ee80",1:"ff8196d42cab0f031d3f",2:"614b3e73b072982fd9b1",3:"1ca609753b5a02de0ca3",4:"95e6a070a0eb2ed08fdd",5:"cc2e7bf65ef32f9c8604",6:"cad898a38692eda28965",7:"11ac4de064ae59691d49",8:"17d1afb591ad1495bd03",9:"d5b992e9ef51921aeb57",10:"e97ea231893dc435d6b5",11:"ae54612e36c6d46f85db",12:"59f035a9438e2baf5126",13:"d8ccb992a49875966313",14:"e64890872b31c55fcdf7",15:"92b2cef3032ea35da04a",16:"4565605e68bab129a471",17:"15ecbcf8f0b2968689e8",18:"ad41fb75ac4226e1f3ce",19:"f993a75d5bfe2382548d",20:"14a92e5c503f74ff40df",21:"ccbfce6680a2b11a5284",22:"bc79367e5f4c8ad00038",23:"5486b3fc83b194d55191",24:"e905e3f6ba089a34b07c",25:"e20b4daac9fb2bdceee6",26:"c8f33e73b9117b70aef8",27:"6779f92692327013b446",28:"280c392f19bc9e908abf",29:"daadfa33135efe4e5534",30:"b2464d08ccbeb40ec74e",32:"fa73a4eb8875b89f452f",33:"70f8e7b1964892a24e67"}[e]+".js",c.appendChild(b)}},__webpack_require__.m=e,__webpack_require__.c=c,__webpack_require__.p="/static/"}(function(e){for(var a in e)if(Object.prototype.hasOwnProperty.call(e,a))switch(typeof e[a]){case"function":break;case"object":e[a]=function(a){var c=a.slice(1),f=e[a[0]];return function(e,a,b){f.apply(this,[e,a,b].concat(c))}}(e[a]);break;default:e[a]=e[e[a]]}return e}([]));
1
+ !function(e){function __webpack_require__(a){if(c[a])return c[a].exports;var f=c[a]={exports:{},id:a,loaded:!1};return e[a].call(f.exports,f,f.exports,__webpack_require__),f.loaded=!0,f.exports}var a=window.webpackJsonp;window.webpackJsonp=function(b,r){for(var t,_,n=0,d=[];n<b.length;n++)_=b[n],f[_]&&d.push.apply(d,f[_]),f[_]=0;for(t in r){var i=r[t];switch(typeof i){case"object":e[t]=function(a){var c=a.slice(1),f=a[0];return function(a,b,r){e[f].apply(this,[a,b,r].concat(c))}}(i);break;case"function":e[t]=i;break;default:e[t]=e[i]}}for(a&&a(b,r);d.length;)d.shift().call(null,__webpack_require__);if(r[0])return c[0]=0,__webpack_require__(0)};var c={},f={31:0};__webpack_require__.e=function(e,a){if(0===f[e])return a.call(null,__webpack_require__);if(void 0!==f[e])f[e].push(a);else{f[e]=[a];var c=document.getElementsByTagName("head")[0],b=document.createElement("script");b.type="text/javascript",b.charset="utf-8",b.async=!0,b.src=__webpack_require__.p+"chunks/"+e+"."+{0:"93c3ae13b5b94753ee80",1:"ff8196d42cab0f031d3f",2:"614b3e73b072982fd9b1",3:"1ca609753b5a02de0ca3",4:"95e6a070a0eb2ed08fdd",5:"cc2e7bf65ef32f9c8604",6:"cad898a38692eda28965",7:"e2106342e94ee09393b1",8:"17d1afb591ad1495bd03",9:"d5b992e9ef51921aeb57",10:"e97ea231893dc435d6b5",11:"ae54612e36c6d46f85db",12:"59f035a9438e2baf5126",13:"d8ccb992a49875966313",14:"e64890872b31c55fcdf7",15:"92b2cef3032ea35da04a",16:"4565605e68bab129a471",17:"15ecbcf8f0b2968689e8",18:"1922fd0b2b7fad122991",19:"f993a75d5bfe2382548d",20:"14a92e5c503f74ff40df",21:"ccbfce6680a2b11a5284",22:"bc79367e5f4c8ad00038",23:"5486b3fc83b194d55191",24:"e905e3f6ba089a34b07c",25:"e20b4daac9fb2bdceee6",26:"c8f33e73b9117b70aef8",27:"6779f92692327013b446",28:"280c392f19bc9e908abf",29:"daadfa33135efe4e5534",30:"b2464d08ccbeb40ec74e",32:"12df4e65a3476781ee9a",33:"70f8e7b1964892a24e67"}[e]+".js",c.appendChild(b)}},__webpack_require__.m=e,__webpack_require__.c=c,__webpack_require__.p="/static/"}(function(e){for(var a in e)if(Object.prototype.hasOwnProperty.call(e,a))switch(typeof e[a]){case"function":break;case"object":e[a]=function(a){var c=a.slice(1),f=e[a[0]];return function(e,a,b){f.apply(this,[e,a,b].concat(c))}}(e[a]);break;default:e[a]=e[e[a]]}return e}([]));
2
2
  //# sourceMappingURL=common.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["webpack:///common.js","webpack:///webpack/bootstrap e110b7df1b5fdbca3a9e"],"names":["modules","__webpack_require__","moduleId","installedModules","exports","module","id","loaded","call","parentJsonpFunction","window","chunkIds","moreModules","chunkId","i","callbacks","length","installedChunks","push","apply","_m","args","slice","templateId","a","b","c","this","concat","shift","31","e","callback","undefined","head","document","getElementsByTagName","script","createElement","type","charset","async","src","p","0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","32","33","appendChild","m","Object","prototype","hasOwnProperty","fn"],"mappings":"CAAS,SAAUA,GCwDnB,QAAAC,qBAAAC,GAGA,GAAAC,EAAAD,GACA,MAAAC,GAAAD,GAAAE,OAGA,IAAAC,GAAAF,EAAAD,IACAE,WACAE,GAAAJ,EACAK,QAAA,EAUA,OANAP,GAAAE,GAAAM,KAAAH,EAAAD,QAAAC,IAAAD,QAAAH,qBAGAI,EAAAE,QAAA,EAGAF,EAAAD,QA3EA,GAAAK,GAAAC,OAAA,YACAA,QAAA,sBAAAC,EAAAC,GAIA,IADA,GAAAV,GAAAW,EAAAC,EAAA,EAAAC,KACQD,EAAAH,EAAAK,OAAoBF,IAC5BD,EAAAF,EAAAG,GACAG,EAAAJ,IACAE,EAAAG,KAAAC,MAAAJ,EAAAE,EAAAJ,IACAI,EAAAJ,GAAA,CAEA,KAAAX,IAAAU,GAAA,CACA,GAAAQ,GAAAR,EAAAV,EAGA,cAAAkB,IACA,aAEApB,EAAAE,GAAA,SAAAkB,GACA,GAAAC,GAAAD,EAAAE,MAAA,GAAAC,EAAAH,EAAA,EACA,iBAAAI,EAAAC,EAAAC,GACA1B,EAAAuB,GAAAJ,MAAAQ,MAAAH,EAAAC,EAAAC,GAAAE,OAAAP,MAEMD,EACN,MACA,gBAEApB,EAAAE,GAAAkB,CACA,MACA,SAEApB,EAAAE,GAAAF,EAAAoB,IAKA,IADAX,KAAAE,EAAAC,GACAG,EAAAC,QACAD,EAAAc,QAAArB,KAAA,KAAAP,oBACA,IAAAW,EAAA,GAEA,MADAT,GAAA,KACAF,oBAAA,GAKA,IAAAE,MAKAc,GACAa,GAAA,EA6BA7B,qBAAA8B,EAAA,SAAAlB,EAAAmB,GAEA,OAAAf,EAAAJ,GACA,MAAAmB,GAAAxB,KAAA,KAAAP,oBAGA,IAAAgC,SAAAhB,EAAAJ,GACAI,EAAAJ,GAAAK,KAAAc,OACI,CAEJf,EAAAJ,IAAAmB,EACA,IAAAE,GAAAC,SAAAC,qBAAA,WACAC,EAAAF,SAAAG,cAAA,SACAD,GAAAE,KAAA,kBACAF,EAAAG,QAAA,QACAH,EAAAI,OAAA,EAEAJ,EAAAK,IAAAzC,oBAAA0C,EAAA,UAAA9B,EAAA,KAAsE+B,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,wBAAk5B/D,GAAA,MACx9BqB,EAAA2C,YAAAxC,KAKApC,oBAAA6E,EAAA9E,EAGAC,oBAAAyB,EAAAvB,EAGAF,oBAAA0C,EAAA,YDIW,SAAS3C,GAEnB,IAAI,GAAIc,KAAKd,GACZ,GAAG+E,OAAOC,UAAUC,eAAezE,KAAKR,EAASc,GAChD,aAAcd,GAAQc,IACtB,IAAK,WAAY,KACjB,KAAK,SAEJd,EAAQc,GAAM,SAASM,GACtB,GAAIC,GAAOD,EAAGE,MAAM,GAAI4D,EAAKlF,EAAQoB,EAAG,GACxC,OAAO,UAAUI,EAAEC,EAAEC,GACpBwD,EAAG/D,MAAMQ,MAAOH,EAAEC,EAAEC,GAAGE,OAAOP,MAE9BrB,EAAQc,GACV,MACD,SAECd,EAAQc,GAAKd,EAAQA,EAAQc,IAKhC,MAAOd","file":"common.js","sourcesContent":["/******/ (function(modules) { // webpackBootstrap\n/******/ \t// install a JSONP callback for chunk loading\n/******/ \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n/******/ \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules) {\n/******/ \t\t// add \"moreModules\" to the modules object,\n/******/ \t\t// then flag all \"chunkIds\" as loaded and fire callback\n/******/ \t\tvar moduleId, chunkId, i = 0, callbacks = [];\n/******/ \t\tfor(;i < chunkIds.length; i++) {\n/******/ \t\t\tchunkId = chunkIds[i];\n/******/ \t\t\tif(installedChunks[chunkId])\n/******/ \t\t\t\tcallbacks.push.apply(callbacks, installedChunks[chunkId]);\n/******/ \t\t\tinstalledChunks[chunkId] = 0;\n/******/ \t\t}\n/******/ \t\tfor(moduleId in moreModules) {\n/******/ \t\t\tvar _m = moreModules[moduleId];\n/******/\n/******/ \t\t\t// Check if module is deduplicated\n/******/ \t\t\tswitch(typeof _m) {\n/******/ \t\t\tcase \"object\":\n/******/ \t\t\t\t// Module can be created from a template\n/******/ \t\t\t\tmodules[moduleId] = (function(_m) {\n/******/ \t\t\t\t\tvar args = _m.slice(1), templateId = _m[0];\n/******/ \t\t\t\t\treturn function (a,b,c) {\n/******/ \t\t\t\t\t\tmodules[templateId].apply(this, [a,b,c].concat(args));\n/******/ \t\t\t\t\t};\n/******/ \t\t\t\t}(_m));\n/******/ \t\t\t\tbreak;\n/******/ \t\t\tcase \"function\":\n/******/ \t\t\t\t// Normal module\n/******/ \t\t\t\tmodules[moduleId] = _m;\n/******/ \t\t\t\tbreak;\n/******/ \t\t\tdefault:\n/******/ \t\t\t\t// Module is a copy of another module\n/******/ \t\t\t\tmodules[moduleId] = modules[_m];\n/******/ \t\t\t\tbreak;\n/******/ \t\t\t}\n/******/ \t\t}\n/******/ \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);\n/******/ \t\twhile(callbacks.length)\n/******/ \t\t\tcallbacks.shift().call(null, __webpack_require__);\n/******/ \t\tif(moreModules[0]) {\n/******/ \t\t\tinstalledModules[0] = 0;\n/******/ \t\t\treturn __webpack_require__(0);\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// object to store loaded and loading chunks\n/******/ \t// \"0\" means \"already loaded\"\n/******/ \t// Array means \"loading\", array contains callbacks\n/******/ \tvar installedChunks = {\n/******/ \t\t31:0\n/******/ \t};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId])\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\texports: {},\n/******/ \t\t\tid: moduleId,\n/******/ \t\t\tloaded: false\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.loaded = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/ \t// This file contains only the entry chunk.\n/******/ \t// The chunk loading function for additional chunks\n/******/ \t__webpack_require__.e = function requireEnsure(chunkId, callback) {\n/******/ \t\t// \"0\" is the signal for \"already loaded\"\n/******/ \t\tif(installedChunks[chunkId] === 0)\n/******/ \t\t\treturn callback.call(null, __webpack_require__);\n/******/\n/******/ \t\t// an array means \"currently loading\".\n/******/ \t\tif(installedChunks[chunkId] !== undefined) {\n/******/ \t\t\tinstalledChunks[chunkId].push(callback);\n/******/ \t\t} else {\n/******/ \t\t\t// start chunk loading\n/******/ \t\t\tinstalledChunks[chunkId] = [callback];\n/******/ \t\t\tvar head = document.getElementsByTagName('head')[0];\n/******/ \t\t\tvar script = document.createElement('script');\n/******/ \t\t\tscript.type = 'text/javascript';\n/******/ \t\t\tscript.charset = 'utf-8';\n/******/ \t\t\tscript.async = true;\n/******/\n/******/ \t\t\tscript.src = __webpack_require__.p + \"chunks/\" + chunkId + \".\" + {\"0\":\"93c3ae13b5b94753ee80\",\"1\":\"ff8196d42cab0f031d3f\",\"2\":\"614b3e73b072982fd9b1\",\"3\":\"1ca609753b5a02de0ca3\",\"4\":\"95e6a070a0eb2ed08fdd\",\"5\":\"cc2e7bf65ef32f9c8604\",\"6\":\"cad898a38692eda28965\",\"7\":\"11ac4de064ae59691d49\",\"8\":\"17d1afb591ad1495bd03\",\"9\":\"d5b992e9ef51921aeb57\",\"10\":\"e97ea231893dc435d6b5\",\"11\":\"ae54612e36c6d46f85db\",\"12\":\"59f035a9438e2baf5126\",\"13\":\"d8ccb992a49875966313\",\"14\":\"e64890872b31c55fcdf7\",\"15\":\"92b2cef3032ea35da04a\",\"16\":\"4565605e68bab129a471\",\"17\":\"15ecbcf8f0b2968689e8\",\"18\":\"ad41fb75ac4226e1f3ce\",\"19\":\"f993a75d5bfe2382548d\",\"20\":\"14a92e5c503f74ff40df\",\"21\":\"ccbfce6680a2b11a5284\",\"22\":\"bc79367e5f4c8ad00038\",\"23\":\"5486b3fc83b194d55191\",\"24\":\"e905e3f6ba089a34b07c\",\"25\":\"e20b4daac9fb2bdceee6\",\"26\":\"c8f33e73b9117b70aef8\",\"27\":\"6779f92692327013b446\",\"28\":\"280c392f19bc9e908abf\",\"29\":\"daadfa33135efe4e5534\",\"30\":\"b2464d08ccbeb40ec74e\",\"32\":\"fa73a4eb8875b89f452f\",\"33\":\"70f8e7b1964892a24e67\"}[chunkId] + \".js\";\n/******/ \t\t\thead.appendChild(script);\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"/static/\";\n/******/ })\n/************************************************************************/\n/******/ ((function(modules) {\n\t// Check all modules for deduplicated modules\n\tfor(var i in modules) {\n\t\tif(Object.prototype.hasOwnProperty.call(modules, i)) {\n\t\t\tswitch(typeof modules[i]) {\n\t\t\tcase \"function\": break;\n\t\t\tcase \"object\":\n\t\t\t\t// Module can be created from a template\n\t\t\t\tmodules[i] = (function(_m) {\n\t\t\t\t\tvar args = _m.slice(1), fn = modules[_m[0]];\n\t\t\t\t\treturn function (a,b,c) {\n\t\t\t\t\t\tfn.apply(this, [a,b,c].concat(args));\n\t\t\t\t\t};\n\t\t\t\t}(modules[i]));\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t// Module is a copy of another module\n\t\t\t\tmodules[i] = modules[modules[i]];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn modules;\n}([])));\n\n\n/** WEBPACK FOOTER **\n ** common.js\n **/"," \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, callbacks = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId])\n \t\t\t\tcallbacks.push.apply(callbacks, installedChunks[chunkId]);\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tvar _m = moreModules[moduleId];\n\n \t\t\t// Check if module is deduplicated\n \t\t\tswitch(typeof _m) {\n \t\t\tcase \"object\":\n \t\t\t\t// Module can be created from a template\n \t\t\t\tmodules[moduleId] = (function(_m) {\n \t\t\t\t\tvar args = _m.slice(1), templateId = _m[0];\n \t\t\t\t\treturn function (a,b,c) {\n \t\t\t\t\t\tmodules[templateId].apply(this, [a,b,c].concat(args));\n \t\t\t\t\t};\n \t\t\t\t}(_m));\n \t\t\t\tbreak;\n \t\t\tcase \"function\":\n \t\t\t\t// Normal module\n \t\t\t\tmodules[moduleId] = _m;\n \t\t\t\tbreak;\n \t\t\tdefault:\n \t\t\t\t// Module is a copy of another module\n \t\t\t\tmodules[moduleId] = modules[_m];\n \t\t\t\tbreak;\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);\n \t\twhile(callbacks.length)\n \t\t\tcallbacks.shift().call(null, __webpack_require__);\n \t\tif(moreModules[0]) {\n \t\t\tinstalledModules[0] = 0;\n \t\t\treturn __webpack_require__(0);\n \t\t}\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// \"0\" means \"already loaded\"\n \t// Array means \"loading\", array contains callbacks\n \tvar installedChunks = {\n \t\t31:0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n \t// This file contains only the entry chunk.\n \t// The chunk loading function for additional chunks\n \t__webpack_require__.e = function requireEnsure(chunkId, callback) {\n \t\t// \"0\" is the signal for \"already loaded\"\n \t\tif(installedChunks[chunkId] === 0)\n \t\t\treturn callback.call(null, __webpack_require__);\n\n \t\t// an array means \"currently loading\".\n \t\tif(installedChunks[chunkId] !== undefined) {\n \t\t\tinstalledChunks[chunkId].push(callback);\n \t\t} else {\n \t\t\t// start chunk loading\n \t\t\tinstalledChunks[chunkId] = [callback];\n \t\t\tvar head = document.getElementsByTagName('head')[0];\n \t\t\tvar script = document.createElement('script');\n \t\t\tscript.type = 'text/javascript';\n \t\t\tscript.charset = 'utf-8';\n \t\t\tscript.async = true;\n\n \t\t\tscript.src = __webpack_require__.p + \"chunks/\" + chunkId + \".\" + {\"0\":\"93c3ae13b5b94753ee80\",\"1\":\"ff8196d42cab0f031d3f\",\"2\":\"614b3e73b072982fd9b1\",\"3\":\"1ca609753b5a02de0ca3\",\"4\":\"95e6a070a0eb2ed08fdd\",\"5\":\"cc2e7bf65ef32f9c8604\",\"6\":\"cad898a38692eda28965\",\"7\":\"11ac4de064ae59691d49\",\"8\":\"17d1afb591ad1495bd03\",\"9\":\"d5b992e9ef51921aeb57\",\"10\":\"e97ea231893dc435d6b5\",\"11\":\"ae54612e36c6d46f85db\",\"12\":\"59f035a9438e2baf5126\",\"13\":\"d8ccb992a49875966313\",\"14\":\"e64890872b31c55fcdf7\",\"15\":\"92b2cef3032ea35da04a\",\"16\":\"4565605e68bab129a471\",\"17\":\"15ecbcf8f0b2968689e8\",\"18\":\"ad41fb75ac4226e1f3ce\",\"19\":\"f993a75d5bfe2382548d\",\"20\":\"14a92e5c503f74ff40df\",\"21\":\"ccbfce6680a2b11a5284\",\"22\":\"bc79367e5f4c8ad00038\",\"23\":\"5486b3fc83b194d55191\",\"24\":\"e905e3f6ba089a34b07c\",\"25\":\"e20b4daac9fb2bdceee6\",\"26\":\"c8f33e73b9117b70aef8\",\"27\":\"6779f92692327013b446\",\"28\":\"280c392f19bc9e908abf\",\"29\":\"daadfa33135efe4e5534\",\"30\":\"b2464d08ccbeb40ec74e\",\"32\":\"fa73a4eb8875b89f452f\",\"33\":\"70f8e7b1964892a24e67\"}[chunkId] + \".js\";\n \t\t\thead.appendChild(script);\n \t\t}\n \t};\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/static/\";\n\n\n\n/** WEBPACK FOOTER **\n ** webpack/bootstrap e110b7df1b5fdbca3a9e\n **/"],"sourceRoot":""}
1
+ {"version":3,"sources":["webpack:///common.js","webpack:///webpack/bootstrap e7dc5561085d000a9a79"],"names":["modules","__webpack_require__","moduleId","installedModules","exports","module","id","loaded","call","parentJsonpFunction","window","chunkIds","moreModules","chunkId","i","callbacks","length","installedChunks","push","apply","_m","args","slice","templateId","a","b","c","this","concat","shift","31","e","callback","undefined","head","document","getElementsByTagName","script","createElement","type","charset","async","src","p","0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","32","33","appendChild","m","Object","prototype","hasOwnProperty","fn"],"mappings":"CAAS,SAAUA,GCwDnB,QAAAC,qBAAAC,GAGA,GAAAC,EAAAD,GACA,MAAAC,GAAAD,GAAAE,OAGA,IAAAC,GAAAF,EAAAD,IACAE,WACAE,GAAAJ,EACAK,QAAA,EAUA,OANAP,GAAAE,GAAAM,KAAAH,EAAAD,QAAAC,IAAAD,QAAAH,qBAGAI,EAAAE,QAAA,EAGAF,EAAAD,QA3EA,GAAAK,GAAAC,OAAA,YACAA,QAAA,sBAAAC,EAAAC,GAIA,IADA,GAAAV,GAAAW,EAAAC,EAAA,EAAAC,KACQD,EAAAH,EAAAK,OAAoBF,IAC5BD,EAAAF,EAAAG,GACAG,EAAAJ,IACAE,EAAAG,KAAAC,MAAAJ,EAAAE,EAAAJ,IACAI,EAAAJ,GAAA,CAEA,KAAAX,IAAAU,GAAA,CACA,GAAAQ,GAAAR,EAAAV,EAGA,cAAAkB,IACA,aAEApB,EAAAE,GAAA,SAAAkB,GACA,GAAAC,GAAAD,EAAAE,MAAA,GAAAC,EAAAH,EAAA,EACA,iBAAAI,EAAAC,EAAAC,GACA1B,EAAAuB,GAAAJ,MAAAQ,MAAAH,EAAAC,EAAAC,GAAAE,OAAAP,MAEMD,EACN,MACA,gBAEApB,EAAAE,GAAAkB,CACA,MACA,SAEApB,EAAAE,GAAAF,EAAAoB,IAKA,IADAX,KAAAE,EAAAC,GACAG,EAAAC,QACAD,EAAAc,QAAArB,KAAA,KAAAP,oBACA,IAAAW,EAAA,GAEA,MADAT,GAAA,KACAF,oBAAA,GAKA,IAAAE,MAKAc,GACAa,GAAA,EA6BA7B,qBAAA8B,EAAA,SAAAlB,EAAAmB,GAEA,OAAAf,EAAAJ,GACA,MAAAmB,GAAAxB,KAAA,KAAAP,oBAGA,IAAAgC,SAAAhB,EAAAJ,GACAI,EAAAJ,GAAAK,KAAAc,OACI,CAEJf,EAAAJ,IAAAmB,EACA,IAAAE,GAAAC,SAAAC,qBAAA,WACAC,EAAAF,SAAAG,cAAA,SACAD,GAAAE,KAAA,kBACAF,EAAAG,QAAA,QACAH,EAAAI,OAAA,EAEAJ,EAAAK,IAAAzC,oBAAA0C,EAAA,UAAA9B,EAAA,KAAsE+B,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,wBAAk5B/D,GAAA,MACx9BqB,EAAA2C,YAAAxC,KAKApC,oBAAA6E,EAAA9E,EAGAC,oBAAAyB,EAAAvB,EAGAF,oBAAA0C,EAAA,YDIW,SAAS3C,GAEnB,IAAI,GAAIc,KAAKd,GACZ,GAAG+E,OAAOC,UAAUC,eAAezE,KAAKR,EAASc,GAChD,aAAcd,GAAQc,IACtB,IAAK,WAAY,KACjB,KAAK,SAEJd,EAAQc,GAAM,SAASM,GACtB,GAAIC,GAAOD,EAAGE,MAAM,GAAI4D,EAAKlF,EAAQoB,EAAG,GACxC,OAAO,UAAUI,EAAEC,EAAEC,GACpBwD,EAAG/D,MAAMQ,MAAOH,EAAEC,EAAEC,GAAGE,OAAOP,MAE9BrB,EAAQc,GACV,MACD,SAECd,EAAQc,GAAKd,EAAQA,EAAQc,IAKhC,MAAOd","file":"common.js","sourcesContent":["/******/ (function(modules) { // webpackBootstrap\n/******/ \t// install a JSONP callback for chunk loading\n/******/ \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n/******/ \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules) {\n/******/ \t\t// add \"moreModules\" to the modules object,\n/******/ \t\t// then flag all \"chunkIds\" as loaded and fire callback\n/******/ \t\tvar moduleId, chunkId, i = 0, callbacks = [];\n/******/ \t\tfor(;i < chunkIds.length; i++) {\n/******/ \t\t\tchunkId = chunkIds[i];\n/******/ \t\t\tif(installedChunks[chunkId])\n/******/ \t\t\t\tcallbacks.push.apply(callbacks, installedChunks[chunkId]);\n/******/ \t\t\tinstalledChunks[chunkId] = 0;\n/******/ \t\t}\n/******/ \t\tfor(moduleId in moreModules) {\n/******/ \t\t\tvar _m = moreModules[moduleId];\n/******/\n/******/ \t\t\t// Check if module is deduplicated\n/******/ \t\t\tswitch(typeof _m) {\n/******/ \t\t\tcase \"object\":\n/******/ \t\t\t\t// Module can be created from a template\n/******/ \t\t\t\tmodules[moduleId] = (function(_m) {\n/******/ \t\t\t\t\tvar args = _m.slice(1), templateId = _m[0];\n/******/ \t\t\t\t\treturn function (a,b,c) {\n/******/ \t\t\t\t\t\tmodules[templateId].apply(this, [a,b,c].concat(args));\n/******/ \t\t\t\t\t};\n/******/ \t\t\t\t}(_m));\n/******/ \t\t\t\tbreak;\n/******/ \t\t\tcase \"function\":\n/******/ \t\t\t\t// Normal module\n/******/ \t\t\t\tmodules[moduleId] = _m;\n/******/ \t\t\t\tbreak;\n/******/ \t\t\tdefault:\n/******/ \t\t\t\t// Module is a copy of another module\n/******/ \t\t\t\tmodules[moduleId] = modules[_m];\n/******/ \t\t\t\tbreak;\n/******/ \t\t\t}\n/******/ \t\t}\n/******/ \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);\n/******/ \t\twhile(callbacks.length)\n/******/ \t\t\tcallbacks.shift().call(null, __webpack_require__);\n/******/ \t\tif(moreModules[0]) {\n/******/ \t\t\tinstalledModules[0] = 0;\n/******/ \t\t\treturn __webpack_require__(0);\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// object to store loaded and loading chunks\n/******/ \t// \"0\" means \"already loaded\"\n/******/ \t// Array means \"loading\", array contains callbacks\n/******/ \tvar installedChunks = {\n/******/ \t\t31:0\n/******/ \t};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId])\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\texports: {},\n/******/ \t\t\tid: moduleId,\n/******/ \t\t\tloaded: false\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.loaded = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/ \t// This file contains only the entry chunk.\n/******/ \t// The chunk loading function for additional chunks\n/******/ \t__webpack_require__.e = function requireEnsure(chunkId, callback) {\n/******/ \t\t// \"0\" is the signal for \"already loaded\"\n/******/ \t\tif(installedChunks[chunkId] === 0)\n/******/ \t\t\treturn callback.call(null, __webpack_require__);\n/******/\n/******/ \t\t// an array means \"currently loading\".\n/******/ \t\tif(installedChunks[chunkId] !== undefined) {\n/******/ \t\t\tinstalledChunks[chunkId].push(callback);\n/******/ \t\t} else {\n/******/ \t\t\t// start chunk loading\n/******/ \t\t\tinstalledChunks[chunkId] = [callback];\n/******/ \t\t\tvar head = document.getElementsByTagName('head')[0];\n/******/ \t\t\tvar script = document.createElement('script');\n/******/ \t\t\tscript.type = 'text/javascript';\n/******/ \t\t\tscript.charset = 'utf-8';\n/******/ \t\t\tscript.async = true;\n/******/\n/******/ \t\t\tscript.src = __webpack_require__.p + \"chunks/\" + chunkId + \".\" + {\"0\":\"93c3ae13b5b94753ee80\",\"1\":\"ff8196d42cab0f031d3f\",\"2\":\"614b3e73b072982fd9b1\",\"3\":\"1ca609753b5a02de0ca3\",\"4\":\"95e6a070a0eb2ed08fdd\",\"5\":\"cc2e7bf65ef32f9c8604\",\"6\":\"cad898a38692eda28965\",\"7\":\"e2106342e94ee09393b1\",\"8\":\"17d1afb591ad1495bd03\",\"9\":\"d5b992e9ef51921aeb57\",\"10\":\"e97ea231893dc435d6b5\",\"11\":\"ae54612e36c6d46f85db\",\"12\":\"59f035a9438e2baf5126\",\"13\":\"d8ccb992a49875966313\",\"14\":\"e64890872b31c55fcdf7\",\"15\":\"92b2cef3032ea35da04a\",\"16\":\"4565605e68bab129a471\",\"17\":\"15ecbcf8f0b2968689e8\",\"18\":\"1922fd0b2b7fad122991\",\"19\":\"f993a75d5bfe2382548d\",\"20\":\"14a92e5c503f74ff40df\",\"21\":\"ccbfce6680a2b11a5284\",\"22\":\"bc79367e5f4c8ad00038\",\"23\":\"5486b3fc83b194d55191\",\"24\":\"e905e3f6ba089a34b07c\",\"25\":\"e20b4daac9fb2bdceee6\",\"26\":\"c8f33e73b9117b70aef8\",\"27\":\"6779f92692327013b446\",\"28\":\"280c392f19bc9e908abf\",\"29\":\"daadfa33135efe4e5534\",\"30\":\"b2464d08ccbeb40ec74e\",\"32\":\"12df4e65a3476781ee9a\",\"33\":\"70f8e7b1964892a24e67\"}[chunkId] + \".js\";\n/******/ \t\t\thead.appendChild(script);\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"/static/\";\n/******/ })\n/************************************************************************/\n/******/ ((function(modules) {\n\t// Check all modules for deduplicated modules\n\tfor(var i in modules) {\n\t\tif(Object.prototype.hasOwnProperty.call(modules, i)) {\n\t\t\tswitch(typeof modules[i]) {\n\t\t\tcase \"function\": break;\n\t\t\tcase \"object\":\n\t\t\t\t// Module can be created from a template\n\t\t\t\tmodules[i] = (function(_m) {\n\t\t\t\t\tvar args = _m.slice(1), fn = modules[_m[0]];\n\t\t\t\t\treturn function (a,b,c) {\n\t\t\t\t\t\tfn.apply(this, [a,b,c].concat(args));\n\t\t\t\t\t};\n\t\t\t\t}(modules[i]));\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t// Module is a copy of another module\n\t\t\t\tmodules[i] = modules[modules[i]];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn modules;\n}([])));\n\n\n/** WEBPACK FOOTER **\n ** common.js\n **/"," \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, callbacks = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId])\n \t\t\t\tcallbacks.push.apply(callbacks, installedChunks[chunkId]);\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tvar _m = moreModules[moduleId];\n\n \t\t\t// Check if module is deduplicated\n \t\t\tswitch(typeof _m) {\n \t\t\tcase \"object\":\n \t\t\t\t// Module can be created from a template\n \t\t\t\tmodules[moduleId] = (function(_m) {\n \t\t\t\t\tvar args = _m.slice(1), templateId = _m[0];\n \t\t\t\t\treturn function (a,b,c) {\n \t\t\t\t\t\tmodules[templateId].apply(this, [a,b,c].concat(args));\n \t\t\t\t\t};\n \t\t\t\t}(_m));\n \t\t\t\tbreak;\n \t\t\tcase \"function\":\n \t\t\t\t// Normal module\n \t\t\t\tmodules[moduleId] = _m;\n \t\t\t\tbreak;\n \t\t\tdefault:\n \t\t\t\t// Module is a copy of another module\n \t\t\t\tmodules[moduleId] = modules[_m];\n \t\t\t\tbreak;\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);\n \t\twhile(callbacks.length)\n \t\t\tcallbacks.shift().call(null, __webpack_require__);\n \t\tif(moreModules[0]) {\n \t\t\tinstalledModules[0] = 0;\n \t\t\treturn __webpack_require__(0);\n \t\t}\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// \"0\" means \"already loaded\"\n \t// Array means \"loading\", array contains callbacks\n \tvar installedChunks = {\n \t\t31:0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n \t// This file contains only the entry chunk.\n \t// The chunk loading function for additional chunks\n \t__webpack_require__.e = function requireEnsure(chunkId, callback) {\n \t\t// \"0\" is the signal for \"already loaded\"\n \t\tif(installedChunks[chunkId] === 0)\n \t\t\treturn callback.call(null, __webpack_require__);\n\n \t\t// an array means \"currently loading\".\n \t\tif(installedChunks[chunkId] !== undefined) {\n \t\t\tinstalledChunks[chunkId].push(callback);\n \t\t} else {\n \t\t\t// start chunk loading\n \t\t\tinstalledChunks[chunkId] = [callback];\n \t\t\tvar head = document.getElementsByTagName('head')[0];\n \t\t\tvar script = document.createElement('script');\n \t\t\tscript.type = 'text/javascript';\n \t\t\tscript.charset = 'utf-8';\n \t\t\tscript.async = true;\n\n \t\t\tscript.src = __webpack_require__.p + \"chunks/\" + chunkId + \".\" + {\"0\":\"93c3ae13b5b94753ee80\",\"1\":\"ff8196d42cab0f031d3f\",\"2\":\"614b3e73b072982fd9b1\",\"3\":\"1ca609753b5a02de0ca3\",\"4\":\"95e6a070a0eb2ed08fdd\",\"5\":\"cc2e7bf65ef32f9c8604\",\"6\":\"cad898a38692eda28965\",\"7\":\"e2106342e94ee09393b1\",\"8\":\"17d1afb591ad1495bd03\",\"9\":\"d5b992e9ef51921aeb57\",\"10\":\"e97ea231893dc435d6b5\",\"11\":\"ae54612e36c6d46f85db\",\"12\":\"59f035a9438e2baf5126\",\"13\":\"d8ccb992a49875966313\",\"14\":\"e64890872b31c55fcdf7\",\"15\":\"92b2cef3032ea35da04a\",\"16\":\"4565605e68bab129a471\",\"17\":\"15ecbcf8f0b2968689e8\",\"18\":\"1922fd0b2b7fad122991\",\"19\":\"f993a75d5bfe2382548d\",\"20\":\"14a92e5c503f74ff40df\",\"21\":\"ccbfce6680a2b11a5284\",\"22\":\"bc79367e5f4c8ad00038\",\"23\":\"5486b3fc83b194d55191\",\"24\":\"e905e3f6ba089a34b07c\",\"25\":\"e20b4daac9fb2bdceee6\",\"26\":\"c8f33e73b9117b70aef8\",\"27\":\"6779f92692327013b446\",\"28\":\"280c392f19bc9e908abf\",\"29\":\"daadfa33135efe4e5534\",\"30\":\"b2464d08ccbeb40ec74e\",\"32\":\"12df4e65a3476781ee9a\",\"33\":\"70f8e7b1964892a24e67\"}[chunkId] + \".js\";\n \t\t\thead.appendChild(script);\n \t\t}\n \t};\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/static/\";\n\n\n\n/** WEBPACK FOOTER **\n ** webpack/bootstrap e7dc5561085d000a9a79\n **/"],"sourceRoot":""}
udata/storage/s3.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Any, Optional
1
+ from typing import Any
2
2
  import boto3
3
3
  from flask import current_app
4
4
  import json
@@ -33,14 +33,14 @@ def store_bytes(bucket: str, filename: str, bytes: bytes):
33
33
  def store_as_json(bucket: str, filename: str, value):
34
34
  return store_bytes(bucket, filename, bytes(json.dumps(value).encode('UTF-8')))
35
35
 
36
- def get_bytes(bucket: str, filename: str) -> Optional[bytes]:
36
+ def get_bytes(bucket: str, filename: str) -> bytes | None:
37
37
  client = get_client()
38
38
  try:
39
39
  return client.get_object(Bucket=bucket, Key=filename)['Body'].read()
40
40
  except client.exceptions.NoSuchKey:
41
41
  return None
42
42
 
43
- def get_from_json(bucket: str, filename: str) -> Optional[Any]:
43
+ def get_from_json(bucket: str, filename: str) -> Any | None:
44
44
  bytes = get_bytes(bucket, filename)
45
45
  if bytes is None:
46
46
  return None
udata/tasks.py CHANGED
@@ -170,6 +170,7 @@ def init_app(app):
170
170
  import udata.core.badges.tasks # noqa
171
171
  import udata.core.storages.tasks # noqa
172
172
  import udata.harvest.tasks # noqa
173
+ import udata.db.tasks # noqa
173
174
 
174
175
  entrypoints.get_enabled('udata.tasks', app)
175
176
 
@@ -1,4 +1,3 @@
1
- from pprint import pprint
2
1
  from flask import url_for
3
2
 
4
3
  from udata.core.dataservices.models import Dataservice
@@ -85,23 +84,30 @@ class DataserviceAPITest(APITestCase):
85
84
 
86
85
 
87
86
  def test_dataservice_api_index(self):
87
+ dataset_a = DatasetFactory()
88
+ dataset_b = DatasetFactory()
89
+
88
90
  self.login()
89
91
  self.post(url_for('api.dataservices'), {
90
92
  'title': 'B',
91
93
  'base_api_url': 'https://example.org/B',
94
+ 'datasets': [dataset_b.id],
92
95
  })
93
96
  self.post(url_for('api.dataservices'), {
94
97
  'title': 'C',
95
98
  'base_api_url': 'https://example.org/C',
99
+ 'datasets': [dataset_a.id, dataset_b.id],
96
100
  })
97
101
  self.post(url_for('api.dataservices'), {
98
102
  'title': 'A',
99
103
  'base_api_url': 'https://example.org/A',
104
+ 'datasets': [dataset_a.id],
100
105
  })
101
- response = self.post(url_for('api.dataservices'), {
106
+ self.post(url_for('api.dataservices'), {
102
107
  'title': 'X',
103
108
  'base_api_url': 'https://example.org/X',
104
109
  'private': True,
110
+ 'datasets': [dataset_a.id],
105
111
  })
106
112
 
107
113
  self.assertEqual(Dataservice.objects.count(), 4)
@@ -115,8 +121,12 @@ class DataserviceAPITest(APITestCase):
115
121
  self.assertEqual(response.json['total'], 3)
116
122
  self.assertEqual(len(response.json['data']), 3)
117
123
  self.assertEqual(response.json['data'][0]['title'], 'B')
124
+ self.assertEqual(response.json['data'][0]['datasets'][0]['id'], str(dataset_b.id))
118
125
  self.assertEqual(response.json['data'][1]['title'], 'C')
126
+ self.assertEqual(response.json['data'][1]['datasets'][0]['id'], str(dataset_a.id))
127
+ self.assertEqual(response.json['data'][1]['datasets'][1]['id'], str(dataset_b.id))
119
128
  self.assertEqual(response.json['data'][2]['title'], 'A')
129
+ self.assertEqual(response.json['data'][2]['datasets'][0]['id'], str(dataset_a.id))
120
130
 
121
131
  response = self.get(url_for('api.dataservices', sort='title'))
122
132
  self.assert200(response)
@@ -153,6 +163,20 @@ class DataserviceAPITest(APITestCase):
153
163
  self.assertEqual(len(response.json['data']), 1)
154
164
  self.assertEqual(response.json['data'][0]['title'], 'B')
155
165
 
166
+ response = self.get(url_for('api.dataservices', sort='title', dataset=str(dataset_a.id)))
167
+ self.assert200(response)
168
+
169
+ self.assertEqual(response.json['total'], 2)
170
+ self.assertEqual(response.json['data'][0]['title'], 'A')
171
+ self.assertEqual(response.json['data'][0]['datasets'][0]['id'], str(dataset_a.id))
172
+ self.assertEqual(response.json['data'][1]['title'], 'C')
173
+ self.assertEqual(response.json['data'][1]['datasets'][0]['id'], str(dataset_a.id))
174
+ self.assertEqual(response.json['data'][1]['datasets'][1]['id'], str(dataset_b.id))
175
+
176
+ def test_dataservice_api_index_with_wrong_dataset_id(self):
177
+ response = self.get(url_for('api.dataservices', sort='title', dataset=str("xxx")))
178
+ self.assert400(response)
179
+
156
180
  def test_dataservice_api_create_with_validation_error(self):
157
181
  self.login()
158
182
  response = self.post(url_for('api.dataservices'), {
@@ -761,7 +761,7 @@ class DatasetAPITest(APITestCase):
761
761
  data['resources'].append(resource_data)
762
762
  response = self.put(url_for('api.dataset', dataset=dataset), data)
763
763
  self.assert400(response)
764
- assert response.json['errors']['resources'][0]['schema']['url'] == [_('Invalid URL')]
764
+ assert response.json['errors']['resources'][0]['schema']['url'] == [_('Invalid URL "{url}"').format(url="test")]
765
765
 
766
766
  resource_data['schema'] = {'name': 'unknown-schema'}
767
767
  data['resources'].append(resource_data)
@@ -0,0 +1,87 @@
1
+ from flask import url_for
2
+
3
+ from udata.core.dataset.factories import (DatasetFactory)
4
+ from udata.core.dataset.models import Dataset
5
+ from udata.core.reports.models import Report
6
+ from udata.core.reports.constants import REASON_ILLEGAL_CONTENT, REASON_SPAM, reports_reasons_translations
7
+ from udata.i18n import gettext as _
8
+
9
+ from . import APITestCase
10
+
11
+
12
+ class ReportsReasonsAPITest(APITestCase):
13
+ modules = []
14
+
15
+ def test_reports_reasons_api(self):
16
+ response = self.get(url_for('api.reports_reasons'))
17
+ self.assert200(response)
18
+ self.assertEqual(response.json, reports_reasons_translations())
19
+
20
+ class ReportsAPITest(APITestCase):
21
+ modules = []
22
+
23
+ def test_reports_api_create(self):
24
+ user = self.login()
25
+ illegal_dataset = DatasetFactory.create(owner=user)
26
+ spam_dataset = DatasetFactory.create(owner=user)
27
+
28
+ response = self.post(url_for('api.reports'), {
29
+ 'object_type': 'Dataset',
30
+ 'object_id': illegal_dataset.id,
31
+ 'message': 'This is not appropriate',
32
+ 'reason': REASON_ILLEGAL_CONTENT,
33
+ })
34
+ self.assert201(response)
35
+ self.assertEqual(Report.objects.count(), 1)
36
+
37
+ response = self.post(url_for('api.reports'), {
38
+ 'object_type': 'Dataset',
39
+ 'object_id': spam_dataset.id,
40
+ 'message': 'This is spammy',
41
+ 'reason': REASON_SPAM,
42
+ })
43
+ self.assert201(response)
44
+ self.assertEqual(Report.objects.count(), 2)
45
+
46
+ reports: list[Report] = list(Report.objects())
47
+ self.assertEqual(Dataset.__name__, reports[0].object_type)
48
+ self.assertEqual(illegal_dataset.id, reports[0].object_id)
49
+ self.assertEqual('This is not appropriate', reports[0].message)
50
+ self.assertEqual(REASON_ILLEGAL_CONTENT, reports[0].reason)
51
+ self.assertEqual(user.id, reports[0].by.id)
52
+
53
+ self.assertEqual(Dataset.__name__, reports[1].object_type)
54
+ self.assertEqual(spam_dataset.id, reports[1].object_id)
55
+ self.assertEqual('This is spammy', reports[1].message)
56
+ self.assertEqual(REASON_SPAM, reports[1].reason)
57
+ self.assertEqual(user.id, reports[1].by.id)
58
+
59
+ response = self.delete(url_for('api.dataset', dataset=illegal_dataset))
60
+ self.assert204(response)
61
+
62
+ reports[0].reload()
63
+ self.assertEqual(Dataset.__name__, reports[0].object_type)
64
+ self.assertEqual(illegal_dataset.id, reports[0].object_id)
65
+ self.assertEqual('This is not appropriate', reports[0].message)
66
+ self.assertEqual(REASON_ILLEGAL_CONTENT, reports[0].reason)
67
+ self.assertEqual(user.id, reports[0].by.id)
68
+ self.assertIsNotNone(reports[0].object_deleted_at)
69
+
70
+ reports[1].reload()
71
+ self.assertEqual(Dataset.__name__, reports[1].object_type)
72
+ self.assertEqual(spam_dataset.id, reports[1].object_id)
73
+ self.assertEqual('This is spammy', reports[1].message)
74
+ self.assertEqual(REASON_SPAM, reports[1].reason)
75
+ self.assertEqual(user.id, reports[1].by.id)
76
+ self.assertIsNone(reports[1].object_deleted_at)
77
+
78
+ # We should take action on manual delete in the database too
79
+ spam_dataset.delete()
80
+
81
+ reports[1].reload()
82
+ self.assertIsNotNone(reports[1].object_deleted_at)
83
+
84
+ response = self.get(url_for('api.reports'))
85
+ self.assert200(response)
86
+ self.assertEqual(len(response.json['data']), 2)
87
+
@@ -0,0 +1,40 @@
1
+ from flask import url_for
2
+
3
+ from udata.models import Member
4
+ from udata.core.organization.factories import OrganizationFactory
5
+ from udata.core.topic.factories import TopicFactory
6
+ from udata.tests.api import APITestCase
7
+
8
+
9
+ class MeAPIv2Test(APITestCase):
10
+ modules = []
11
+
12
+ def test_my_org_topics(self):
13
+ user = self.login()
14
+ member = Member(user=user, role='editor')
15
+ organization = OrganizationFactory(members=[member])
16
+ topics = [
17
+ TopicFactory(organization=organization, private=False, tags=['energy']),
18
+ TopicFactory(organization=organization, private=True),
19
+ TopicFactory(owner=user),
20
+ ]
21
+ # another topic that shouldn't pop up
22
+ TopicFactory()
23
+
24
+ response = self.get(url_for('apiv2.my_org_topics'))
25
+ assert response.status_code == 200
26
+ data = response.json['data']
27
+ assert len(data) == 3
28
+ assert all(
29
+ str(topic.id) in [remote_topic["id"] for remote_topic in data]
30
+ for topic in topics
31
+ )
32
+ assert 'rel' in data[0]['datasets']
33
+
34
+ # topic parser is already tested in topics test
35
+ # we're just making sure one of theme is working
36
+ response = self.get(url_for('apiv2.my_org_topics', tag='energy'))
37
+ assert response.status_code == 200
38
+ data = response.json['data']
39
+ assert len(data) == 1
40
+ assert data[0]['id'] == str(topics[0].id)
@@ -20,9 +20,12 @@ from udata.core.dataset.rdf import (
20
20
  temporal_from_rdf, frequency_to_rdf, frequency_from_rdf,
21
21
  EU_RDF_REQUENCIES
22
22
  )
23
+ from udata.rdf import (
24
+ TAG_TO_EU_HVD_CATEGORIES
25
+ )
23
26
  from udata.core.organization.factories import OrganizationFactory
24
27
  from udata.i18n import gettext as _
25
- from udata.rdf import DCAT, DCT, FREQ, SPDX, SCHEMA, SKOS
28
+ from udata.rdf import DCAT, DCATAP, DCT, FREQ, SPDX, SCHEMA, SKOS, HVD_LEGISLATION
26
29
  from udata.utils import faker
27
30
  from udata.tests.helpers import assert200, assert_redirects
28
31
 
@@ -181,6 +184,21 @@ class DatasetToRdfTest:
181
184
  assert str(d.identifier) == 'https://somewhere.org/dataset'
182
185
  assert d.value(DCT.identifier) == Literal('an-identifier')
183
186
 
187
+ def test_hvd_dataset(self):
188
+ '''Test that a dataset tagged hvd has appropriate DCAT-AP HVD properties'''
189
+ dataset = DatasetFactory(
190
+ resources=ResourceFactory.build_batch(3),
191
+ tags=['hvd', 'mobilite', 'test']
192
+ )
193
+ d = dataset_to_rdf(dataset)
194
+
195
+ assert d.value(DCATAP.applicableLegislation).identifier == URIRef(HVD_LEGISLATION)
196
+ assert d.value(DCATAP.hvdCategory).identifier == URIRef(
197
+ TAG_TO_EU_HVD_CATEGORIES['mobilite']
198
+ )
199
+ for distrib in d.objects(DCAT.distribution):
200
+ assert distrib.value(DCATAP.applicableLegislation).identifier == URIRef(HVD_LEGISLATION)
201
+
184
202
 
185
203
  @pytest.mark.usefixtures('clean_db')
186
204
  class RdfToDatasetTest:
@@ -1,8 +1,5 @@
1
- from unittest.mock import patch
2
- from flask import url_for, current_app, get_flashed_messages
3
- from flask_login import current_user
1
+ from flask import url_for, current_app
4
2
  from flask_security.utils import hash_data
5
- from udata.auth.views import send_change_email_confirmation_instructions
6
3
  from udata.core.user.factories import AdminFactory
7
4
 
8
5
  from . import FrontTestCase
@@ -1,4 +1,3 @@
1
- from datetime import datetime, timedelta
2
1
 
3
2
  import pytest
4
3
 
udata/tests/plugin.py CHANGED
@@ -8,6 +8,7 @@ from flask import json, template_rendered, url_for, current_app
8
8
  from flask.testing import FlaskClient
9
9
  from lxml import etree
10
10
  from werkzeug.urls import url_encode
11
+ from flask_principal import Identity, identity_changed
11
12
 
12
13
  from udata import settings
13
14
  from udata.app import create_app
@@ -49,6 +50,7 @@ class TestClient(FlaskClient):
49
50
  session['_fresh'] = True
50
51
  session['_id'] = current_app.login_manager._session_identifier_generator()
51
52
  current_app.login_manager._update_request_context_with_user(user)
53
+ identity_changed.send(current_app._get_current_object(), identity=Identity(user.id))
52
54
  return user
53
55
 
54
56
  def logout(self):
@@ -1,4 +1,3 @@
1
- from datetime import date
2
1
 
3
2
  from flask import url_for
4
3
 
@@ -6,6 +6,7 @@ from rdflib import URIRef, Literal, Graph
6
6
  from rdflib.namespace import RDF, FOAF
7
7
  from rdflib.resource import Resource
8
8
 
9
+ from udata.core.dataservices.factories import DataserviceFactory, HarvestMetadataFactory
9
10
  from udata.core.dataset.factories import DatasetFactory
10
11
  from udata.core.dataset.models import Dataset
11
12
  from udata.core.organization.factories import OrganizationFactory
@@ -228,3 +229,68 @@ class SiteRdfViewsTest:
228
229
  url = url_for('api.site_rdf_catalog_format', format='unknown')
229
230
  response = client.get(url)
230
231
  assert404(response)
232
+
233
+ def test_catalog_rdf_filter_tag(self, client):
234
+ DatasetFactory.create_batch(4, tags=['my-tag'])
235
+ DatasetFactory.create_batch(3)
236
+ url = url_for('api.site_rdf_catalog_format', format='xml', tag='my-tag')
237
+
238
+ response = client.get(url, headers={'Accept': 'application/xml'})
239
+ assert200(response)
240
+
241
+ graph = Graph().parse(data=response.data, format='xml')
242
+
243
+ datasets = list(graph.subjects(RDF.type, DCAT.Dataset))
244
+ assert len(datasets) == 4
245
+
246
+ for dat in datasets:
247
+ assert graph.value(dat, DCAT.keyword) == Literal('my-tag')
248
+
249
+ def test_catalog_rdf_dataservices(self, client):
250
+ dataset_a = DatasetFactory.create()
251
+ dataset_b = DatasetFactory.create()
252
+ dataset_c = DatasetFactory.create()
253
+
254
+ dataservice_a = DataserviceFactory.create(datasets=[dataset_a.id], harvest=HarvestMetadataFactory())
255
+ dataservice_b = DataserviceFactory.create(datasets=[dataset_b.id])
256
+ dataservice_x = DataserviceFactory.create(datasets=[dataset_a.id, dataset_c.id])
257
+ dataservice_y = DataserviceFactory.create(datasets=[])
258
+
259
+ response = client.get(url_for('api.site_rdf_catalog_format', format='xml'), headers={'Accept': 'application/xml'})
260
+ assert200(response)
261
+
262
+ graph = Graph().parse(data=response.data, format='xml')
263
+
264
+ datasets = list(graph.subjects(RDF.type, DCAT.Dataset))
265
+ assert len(datasets) == 3
266
+
267
+ dataservices = list(graph.subjects(RDF.type, DCAT.DataService))
268
+ assert len(dataservices) == 4
269
+
270
+ # Test first page contains the dataservice without dataset
271
+ response = client.get(url_for('api.site_rdf_catalog_format', format='xml', page_size=1), headers={'Accept': 'application/xml'})
272
+ assert200(response)
273
+
274
+ graph = Graph().parse(data=response.data, format='xml')
275
+
276
+ datasets = list(graph.subjects(RDF.type, DCAT.Dataset))
277
+ assert len(datasets) == 1
278
+ assert str(graph.value(datasets[0], DCT.identifier)) == str(dataset_c.id)
279
+
280
+ dataservices = list(graph.subjects(RDF.type, DCAT.DataService))
281
+ assert len(dataservices) == 2
282
+ assert sorted([str(d.id) for d in [dataservice_x, dataservice_y]]) == sorted([str(graph.value(d, DCT.identifier)) for d in dataservices])
283
+
284
+ # Test second page doesn't contains the dataservice without dataset
285
+ response = client.get(url_for('api.site_rdf_catalog_format', format='xml', page_size=1, page=2), headers={'Accept': 'application/xml'})
286
+ assert200(response)
287
+
288
+ graph = Graph().parse(data=response.data, format='xml')
289
+
290
+ datasets = list(graph.subjects(RDF.type, DCAT.Dataset))
291
+ assert len(datasets) == 1
292
+ assert str(graph.value(datasets[0], DCT.identifier)) == str(dataset_b.id)
293
+
294
+ dataservices = list(graph.subjects(RDF.type, DCAT.DataService))
295
+ assert len(dataservices) == 1
296
+ assert str(graph.value(dataservices[0], DCT.identifier)) == str(dataservice_b.id)
@@ -76,7 +76,7 @@ class DiscussionsTest(APITestCase):
76
76
  discussion_id = None
77
77
  def check_signal(args):
78
78
  self.assertIsNotNone(discussion_id)
79
- self.assertIn(f'http://local.test/api/1/datasets/{dataset.id}/#discussion-{discussion_id}', args[1]['message'])
79
+ self.assertIn(f'http://local.test/api/1/datasets/{dataset.slug}/#discussion-{discussion_id}', args[1]['message'])
80
80
 
81
81
  with assert_emit(on_new_potential_spam, assertions_callback=check_signal):
82
82
  response = self.post(url_for('api.discussions'), {
@@ -122,6 +122,29 @@ class DiscussionsTest(APITestCase):
122
122
  self.assertStatus(response, 200)
123
123
  self.assertFalse(discussion.reload().is_spam())
124
124
 
125
+
126
+ @pytest.mark.options(SPAM_WORDS=['spam'])
127
+ def test_spam_by_owner(self):
128
+ user = self.login()
129
+ dataset = Dataset.objects.create(title='Test dataset', owner=user)
130
+
131
+ with assert_not_emit(on_new_potential_spam):
132
+ response = self.post(url_for('api.discussions'), {
133
+ 'title': 'spam and blah',
134
+ 'comment': 'bla bla',
135
+ 'subject': {
136
+ 'class': 'Dataset',
137
+ 'id': dataset.id,
138
+ }
139
+ })
140
+ self.assertStatus(response, 201)
141
+
142
+ with assert_not_emit(on_new_potential_spam):
143
+ response = self.post(url_for('api.discussion', id=response.json['id']), {
144
+ 'comment': 'A comment with spam by owner'
145
+ })
146
+ self.assertStatus(response, 200)
147
+
125
148
  @pytest.mark.options(SPAM_WORDS=['spam'])
126
149
  def test_spam_in_new_discussion_comment(self):
127
150
  self.login()
@@ -495,39 +518,6 @@ class DiscussionsTest(APITestCase):
495
518
  {'comment': "can't comment"})
496
519
  self.assert403(response)
497
520
 
498
- @pytest.mark.options(SPAM_WORDS=['spam'], SPAM_ALLOWED_LANGS=['fr'])
499
- def test_close_discussion_with_spam(self):
500
- owner = self.login()
501
- dataset = Dataset.objects.create(title='Test dataset', owner=owner)
502
- user = UserFactory()
503
- message = Message(content='Premier message', posted_by=user)
504
- discussion = Discussion.objects.create(
505
- subject=dataset,
506
- user=user,
507
- title='test discussion',
508
- discussion=[message]
509
- )
510
- on_new_discussion.send(discussion) # Updating metrics.
511
-
512
- with assert_not_emit(on_discussion_closed):
513
- with assert_emit(on_new_potential_spam):
514
- response = self.post(url_for('api.discussion', id=discussion.id), {
515
- 'comment': 'This is a suspicious, real suspicious message in english.',
516
- 'close': True,
517
- })
518
- self.assert200(response)
519
-
520
- discussion.reload()
521
- self.assertFalse(discussion.is_spam())
522
- self.assertTrue(discussion.discussion[1].is_spam())
523
- self.assertTrue('signal_close' in discussion.discussion[1].spam.callbacks)
524
-
525
- with assert_emit(on_discussion_closed):
526
- admin = self.login(AdminFactory())
527
- response = self.delete(url_for('api.discussion_comment_spam', id=discussion.id, cidx=1))
528
- self.assertStatus(response, 200)
529
- self.assertFalse(discussion.reload().discussion[1].is_spam())
530
-
531
521
  def test_close_discussion_permissions(self):
532
522
  dataset = Dataset.objects.create(title='Test dataset')
533
523
  user = UserFactory()
udata/tests/test_model.py CHANGED
@@ -6,6 +6,7 @@ from datetime import date, datetime, timedelta
6
6
  from mongoengine.errors import ValidationError
7
7
  from mongoengine.fields import BaseField
8
8
 
9
+ from udata.i18n import _
9
10
  from udata.settings import Defaults
10
11
  from udata.models import Dataset
11
12
  from udata.mongo import db, validate_config, build_test_config
@@ -366,7 +367,7 @@ class URLFieldTest:
366
367
 
367
368
  def test_not_valid(self):
368
369
  obj = URLTester(url='invalid')
369
- with pytest.raises(ValidationError, match='Invalid URL'):
370
+ with pytest.raises(ValidationError, match=_('Invalid URL "{url}"').format(url="invalid")):
370
371
  obj.save()
371
372
 
372
373
  def test_strip_spaces(self):
@@ -384,7 +385,7 @@ class URLFieldTest:
384
385
  def test_public_private(self):
385
386
  url = 'http://10.10.0.2/path/'
386
387
  PrivateURLTester(url=url).save()
387
- with pytest.raises(ValidationError, match='private URL'):
388
+ with pytest.raises(ValidationError, match=_('is a private URL')):
388
389
  URLTester(url=url).save()
389
390
 
390
391
 
udata/tests/test_utils.py CHANGED
@@ -1,4 +1,4 @@
1
- from datetime import date, datetime, timezone
1
+ from datetime import date, datetime
2
2
 
3
3
  from udata.utils import (
4
4
  get_by, daterange_start, daterange_end, to_bool, to_iso, to_iso_date,
Binary file