udata 7.0.8.dev28841__py2.py3-none-any.whl → 8.0.1__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 (56) hide show
  1. udata/__init__.py +1 -1
  2. udata/api/__init__.py +6 -4
  3. udata/api/oauth2.py +2 -1
  4. udata/api_fields.py +254 -0
  5. udata/core/badges/models.py +2 -1
  6. udata/core/dataservices/__init__.py +0 -0
  7. udata/core/dataservices/api.py +84 -0
  8. udata/core/dataservices/models.py +130 -0
  9. udata/core/dataset/apiv2.py +2 -0
  10. udata/core/dataset/models.py +1 -0
  11. udata/core/dataset/rdf.py +21 -1
  12. udata/core/metrics/commands.py +18 -3
  13. udata/core/metrics/models.py +2 -3
  14. udata/core/organization/api_fields.py +28 -3
  15. udata/core/organization/models.py +3 -1
  16. udata/core/owned.py +39 -2
  17. udata/core/spatial/api.py +5 -10
  18. udata/core/spatial/models.py +7 -2
  19. udata/core/spatial/tasks.py +7 -0
  20. udata/core/spatial/tests/test_api.py +26 -0
  21. udata/core/user/api.py +11 -7
  22. udata/core/user/models.py +13 -2
  23. udata/harvest/backends/dcat.py +14 -8
  24. udata/harvest/tests/dcat/catalog.xml +1 -0
  25. udata/harvest/tests/test_dcat_backend.py +3 -0
  26. udata/routing.py +6 -0
  27. udata/settings.py +4 -1
  28. udata/static/admin.css +2 -2
  29. udata/static/admin.css.map +1 -1
  30. udata/static/chunks/{0.6f1698738c9b0618b673.js → 0.93c3ae13b5b94753ee80.js} +3 -3
  31. udata/static/chunks/0.93c3ae13b5b94753ee80.js.map +1 -0
  32. udata/static/chunks/{14.f4037a917d5364cb564b.js → 14.e64890872b31c55fcdf7.js} +2 -2
  33. udata/static/chunks/14.e64890872b31c55fcdf7.js.map +1 -0
  34. udata/static/chunks/{2.7c89fae92899be371ed3.js → 2.614b3e73b072982fd9b1.js} +2 -2
  35. udata/static/chunks/2.614b3e73b072982fd9b1.js.map +1 -0
  36. udata/static/chunks/{5.3dc97ea195d251881552.js → 5.48417db6b33328fa9d6a.js} +2 -2
  37. udata/static/chunks/5.48417db6b33328fa9d6a.js.map +1 -0
  38. udata/static/common.js +1 -1
  39. udata/static/common.js.map +1 -1
  40. udata/tasks.py +1 -0
  41. udata/tests/api/__init__.py +3 -0
  42. udata/tests/api/test_dataservices_api.py +236 -0
  43. udata/tests/api/test_organizations_api.py +78 -5
  44. udata/tests/api/test_user_api.py +47 -13
  45. udata/tests/plugin.py +5 -0
  46. {udata-7.0.8.dev28841.dist-info → udata-8.0.1.dist-info}/METADATA +17 -3
  47. {udata-7.0.8.dev28841.dist-info → udata-8.0.1.dist-info}/RECORD +51 -46
  48. udata/core/metrics/api.py +0 -10
  49. udata/static/chunks/0.6f1698738c9b0618b673.js.map +0 -1
  50. udata/static/chunks/14.f4037a917d5364cb564b.js.map +0 -1
  51. udata/static/chunks/2.7c89fae92899be371ed3.js.map +0 -1
  52. udata/static/chunks/5.3dc97ea195d251881552.js.map +0 -1
  53. {udata-7.0.8.dev28841.dist-info → udata-8.0.1.dist-info}/LICENSE +0 -0
  54. {udata-7.0.8.dev28841.dist-info → udata-8.0.1.dist-info}/WHEEL +0 -0
  55. {udata-7.0.8.dev28841.dist-info → udata-8.0.1.dist-info}/entry_points.txt +0 -0
  56. {udata-7.0.8.dev28841.dist-info → udata-8.0.1.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,d,_=0,n=[];_<b.length;_++)d=b[_],f[d]&&n.push.apply(n,f[d]),f[d]=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);n.length;)n.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:"6f1698738c9b0618b673",1:"ff8196d42cab0f031d3f",2:"7c89fae92899be371ed3",3:"1ca609753b5a02de0ca3",4:"95e6a070a0eb2ed08fdd",5:"3dc97ea195d251881552",6:"f84539bd4c419b36cc19",7:"11ac4de064ae59691d49",8:"17d1afb591ad1495bd03",9:"07503e7f7ec02919f696",10:"e97ea231893dc435d6b5",11:"7266fef2dddc1db403d9",12:"59f035a9438e2baf5126",13:"91b177d7d531fd55cf5d",14:"f4037a917d5364cb564b",15:"92b2cef3032ea35da04a",16:"e866757bab9f6b0a3f1b",17:"15ecbcf8f0b2968689e8",18:"ad41fb75ac4226e1f3ce",19:"619b83ac597516dcd03e",20:"14a92e5c503f74ff40df",21:"ccbfce6680a2b11a5284",22:"bc79367e5f4c8ad00038",23:"5486b3fc83b194d55191",24:"e905e3f6ba089a34b07c",25:"e20b4daac9fb2bdceee6",26:"c8f33e73b9117b70aef8",27:"6779f92692327013b446",28:"280c392f19bc9e908abf",29:"daadfa33135efe4e5534",30:"b2464d08ccbeb40ec74e",32:"3251a701a677ab35e980",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,d,_=0,n=[];_<b.length;_++)d=b[_],f[d]&&n.push.apply(n,f[d]),f[d]=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);n.length;)n.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:"48417db6b33328fa9d6a",6:"f84539bd4c419b36cc19",7:"11ac4de064ae59691d49",8:"17d1afb591ad1495bd03",9:"07503e7f7ec02919f696",10:"e97ea231893dc435d6b5",11:"7266fef2dddc1db403d9",12:"59f035a9438e2baf5126",13:"91b177d7d531fd55cf5d",14:"e64890872b31c55fcdf7",15:"92b2cef3032ea35da04a",16:"e866757bab9f6b0a3f1b",17:"15ecbcf8f0b2968689e8",18:"ad41fb75ac4226e1f3ce",19:"619b83ac597516dcd03e",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}([]));
2
2
  //# sourceMappingURL=common.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["webpack:///common.js","webpack:///webpack/bootstrap ee03f1fa39a515ac0121"],"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\":\"6f1698738c9b0618b673\",\"1\":\"ff8196d42cab0f031d3f\",\"2\":\"7c89fae92899be371ed3\",\"3\":\"1ca609753b5a02de0ca3\",\"4\":\"95e6a070a0eb2ed08fdd\",\"5\":\"3dc97ea195d251881552\",\"6\":\"f84539bd4c419b36cc19\",\"7\":\"11ac4de064ae59691d49\",\"8\":\"17d1afb591ad1495bd03\",\"9\":\"07503e7f7ec02919f696\",\"10\":\"e97ea231893dc435d6b5\",\"11\":\"7266fef2dddc1db403d9\",\"12\":\"59f035a9438e2baf5126\",\"13\":\"91b177d7d531fd55cf5d\",\"14\":\"f4037a917d5364cb564b\",\"15\":\"92b2cef3032ea35da04a\",\"16\":\"e866757bab9f6b0a3f1b\",\"17\":\"15ecbcf8f0b2968689e8\",\"18\":\"ad41fb75ac4226e1f3ce\",\"19\":\"619b83ac597516dcd03e\",\"20\":\"14a92e5c503f74ff40df\",\"21\":\"ccbfce6680a2b11a5284\",\"22\":\"bc79367e5f4c8ad00038\",\"23\":\"5486b3fc83b194d55191\",\"24\":\"e905e3f6ba089a34b07c\",\"25\":\"e20b4daac9fb2bdceee6\",\"26\":\"c8f33e73b9117b70aef8\",\"27\":\"6779f92692327013b446\",\"28\":\"280c392f19bc9e908abf\",\"29\":\"daadfa33135efe4e5534\",\"30\":\"b2464d08ccbeb40ec74e\",\"32\":\"3251a701a677ab35e980\",\"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\":\"6f1698738c9b0618b673\",\"1\":\"ff8196d42cab0f031d3f\",\"2\":\"7c89fae92899be371ed3\",\"3\":\"1ca609753b5a02de0ca3\",\"4\":\"95e6a070a0eb2ed08fdd\",\"5\":\"3dc97ea195d251881552\",\"6\":\"f84539bd4c419b36cc19\",\"7\":\"11ac4de064ae59691d49\",\"8\":\"17d1afb591ad1495bd03\",\"9\":\"07503e7f7ec02919f696\",\"10\":\"e97ea231893dc435d6b5\",\"11\":\"7266fef2dddc1db403d9\",\"12\":\"59f035a9438e2baf5126\",\"13\":\"91b177d7d531fd55cf5d\",\"14\":\"f4037a917d5364cb564b\",\"15\":\"92b2cef3032ea35da04a\",\"16\":\"e866757bab9f6b0a3f1b\",\"17\":\"15ecbcf8f0b2968689e8\",\"18\":\"ad41fb75ac4226e1f3ce\",\"19\":\"619b83ac597516dcd03e\",\"20\":\"14a92e5c503f74ff40df\",\"21\":\"ccbfce6680a2b11a5284\",\"22\":\"bc79367e5f4c8ad00038\",\"23\":\"5486b3fc83b194d55191\",\"24\":\"e905e3f6ba089a34b07c\",\"25\":\"e20b4daac9fb2bdceee6\",\"26\":\"c8f33e73b9117b70aef8\",\"27\":\"6779f92692327013b446\",\"28\":\"280c392f19bc9e908abf\",\"29\":\"daadfa33135efe4e5534\",\"30\":\"b2464d08ccbeb40ec74e\",\"32\":\"3251a701a677ab35e980\",\"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 ee03f1fa39a515ac0121\n **/"],"sourceRoot":""}
1
+ {"version":3,"sources":["webpack:///common.js","webpack:///webpack/bootstrap ede79b8cd6024fb06864"],"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\":\"48417db6b33328fa9d6a\",\"6\":\"f84539bd4c419b36cc19\",\"7\":\"11ac4de064ae59691d49\",\"8\":\"17d1afb591ad1495bd03\",\"9\":\"07503e7f7ec02919f696\",\"10\":\"e97ea231893dc435d6b5\",\"11\":\"7266fef2dddc1db403d9\",\"12\":\"59f035a9438e2baf5126\",\"13\":\"91b177d7d531fd55cf5d\",\"14\":\"e64890872b31c55fcdf7\",\"15\":\"92b2cef3032ea35da04a\",\"16\":\"e866757bab9f6b0a3f1b\",\"17\":\"15ecbcf8f0b2968689e8\",\"18\":\"ad41fb75ac4226e1f3ce\",\"19\":\"619b83ac597516dcd03e\",\"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\":\"48417db6b33328fa9d6a\",\"6\":\"f84539bd4c419b36cc19\",\"7\":\"11ac4de064ae59691d49\",\"8\":\"17d1afb591ad1495bd03\",\"9\":\"07503e7f7ec02919f696\",\"10\":\"e97ea231893dc435d6b5\",\"11\":\"7266fef2dddc1db403d9\",\"12\":\"59f035a9438e2baf5126\",\"13\":\"91b177d7d531fd55cf5d\",\"14\":\"e64890872b31c55fcdf7\",\"15\":\"92b2cef3032ea35da04a\",\"16\":\"e866757bab9f6b0a3f1b\",\"17\":\"15ecbcf8f0b2968689e8\",\"18\":\"ad41fb75ac4226e1f3ce\",\"19\":\"619b83ac597516dcd03e\",\"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 ede79b8cd6024fb06864\n **/"],"sourceRoot":""}
udata/tasks.py CHANGED
@@ -162,6 +162,7 @@ def init_app(app):
162
162
  import udata.core.tags.tasks # noqa
163
163
  import udata.core.activity.tasks # noqa
164
164
  import udata.core.dataset.tasks # noqa
165
+ import udata.core.spatial.tasks # noqa
165
166
  import udata.core.reuse.tasks # noqa
166
167
  import udata.core.user.tasks # noqa
167
168
  import udata.core.organization.tasks # noqa
@@ -29,5 +29,8 @@ class APITestCase(FrontTestCase):
29
29
  def put(self, url, data=None, json=True, *args, **kwargs):
30
30
  return self.api.put(url, data=data, json=json, *args, **kwargs)
31
31
 
32
+ def patch(self, url, data=None, json=True, *args, **kwargs):
33
+ return self.api.patch(url, data=data, json=json, *args, **kwargs)
34
+
32
35
  def delete(self, url, data=None, *args, **kwargs):
33
36
  return self.api.delete(url, data=data, *args, **kwargs)
@@ -0,0 +1,236 @@
1
+ from pprint import pprint
2
+ from flask import url_for
3
+
4
+ from udata.core.dataservices.models import Dataservice
5
+ from udata.core.dataset.factories import (DatasetFactory, LicenseFactory)
6
+ from udata.i18n import gettext as _
7
+ from udata.core.user.factories import UserFactory
8
+ from udata.core.organization.factories import OrganizationFactory
9
+ from udata.core.organization.models import Member
10
+
11
+ from . import APITestCase
12
+
13
+ class DataserviceAPITest(APITestCase):
14
+ modules = []
15
+
16
+ def test_dataservice_api_create(self):
17
+ user = self.login()
18
+ datasets = DatasetFactory.create_batch(3)
19
+ license = LicenseFactory.create()
20
+
21
+ response = self.post(url_for('api.dataservices'), {
22
+ 'title': 'My API',
23
+ 'base_api_url': 'https://example.org',
24
+ })
25
+ self.assert201(response)
26
+ self.assertEqual(Dataservice.objects.count(), 1)
27
+
28
+ dataservice = Dataservice.objects.first()
29
+
30
+ response = self.get(url_for('api.dataservice', dataservice=dataservice))
31
+ self.assert200(response)
32
+
33
+ self.assertEqual(response.json['title'], 'My API')
34
+ self.assertEqual(response.json['base_api_url'], 'https://example.org')
35
+ self.assertEqual(response.json['owner']['id'], str(user.id))
36
+
37
+ response = self.patch(url_for('api.dataservice', dataservice=dataservice), {
38
+ 'title': 'Updated title',
39
+ 'tags': ['hello', 'world'],
40
+ 'private': True,
41
+ 'datasets': [datasets[0].id, datasets[2].id],
42
+ 'license': license.id,
43
+ 'extras': {
44
+ 'foo': 'bar',
45
+ },
46
+ })
47
+ self.assert200(response)
48
+
49
+ self.assertEqual(response.json['title'], 'Updated title')
50
+ self.assertEqual(response.json['base_api_url'], 'https://example.org')
51
+ self.assertEqual(response.json['tags'], ['hello', 'world'])
52
+ self.assertEqual(response.json['private'], True)
53
+ self.assertEqual(response.json['datasets'][0]['title'], datasets[0].title)
54
+ self.assertEqual(response.json['datasets'][1]['title'], datasets[2].title)
55
+ self.assertEqual(response.json['extras'], {
56
+ 'foo': 'bar',
57
+ })
58
+ self.assertEqual(response.json['license'], license.title)
59
+ self.assertEqual(response.json['self_api_url'], 'http://local.test/api/1/dataservices/updated-title/')
60
+ dataservice.reload()
61
+ self.assertEqual(dataservice.title, 'Updated title')
62
+ self.assertEqual(dataservice.base_api_url, 'https://example.org')
63
+ self.assertEqual(dataservice.tags, ['hello', 'world'])
64
+ self.assertEqual(dataservice.private, True)
65
+ self.assertEqual(dataservice.datasets[0].title, datasets[0].title)
66
+ self.assertEqual(dataservice.datasets[1].title, datasets[2].title)
67
+ self.assertEqual(dataservice.extras, {
68
+ 'foo': 'bar',
69
+ })
70
+ self.assertEqual(dataservice.license.title, license.title)
71
+ self.assertEqual(dataservice.self_api_url(), 'http://local.test/api/1/dataservices/updated-title/')
72
+
73
+ response = self.delete(url_for('api.dataservice', dataservice=dataservice))
74
+ self.assert204(response)
75
+
76
+ self.assertEqual(Dataservice.objects.count(), 1)
77
+
78
+ dataservice.reload()
79
+ self.assertEqual(dataservice.title, 'Updated title')
80
+ self.assertEqual(dataservice.base_api_url, 'https://example.org')
81
+ self.assertIsNotNone(dataservice.deleted_at)
82
+
83
+ # response = self.get(url_for('api.dataservice', dataservice=dataservice))
84
+ # self.assert410(response)
85
+
86
+
87
+ def test_dataservice_api_index(self):
88
+ self.login()
89
+ self.post(url_for('api.dataservices'), {
90
+ 'title': 'B',
91
+ 'base_api_url': 'https://example.org/B',
92
+ })
93
+ self.post(url_for('api.dataservices'), {
94
+ 'title': 'C',
95
+ 'base_api_url': 'https://example.org/C',
96
+ })
97
+ self.post(url_for('api.dataservices'), {
98
+ 'title': 'A',
99
+ 'base_api_url': 'https://example.org/A',
100
+ })
101
+ response = self.post(url_for('api.dataservices'), {
102
+ 'title': 'X',
103
+ 'base_api_url': 'https://example.org/X',
104
+ 'private': True,
105
+ })
106
+
107
+ self.assertEqual(Dataservice.objects.count(), 4)
108
+
109
+ response = self.get(url_for('api.dataservices'))
110
+ self.assert200(response)
111
+
112
+ self.assertEqual(response.json['previous_page'], None)
113
+ self.assertEqual(response.json['next_page'], None)
114
+ self.assertEqual(response.json['page'], 1)
115
+ self.assertEqual(response.json['total'], 3)
116
+ self.assertEqual(len(response.json['data']), 3)
117
+ self.assertEqual(response.json['data'][0]['title'], 'B')
118
+ self.assertEqual(response.json['data'][1]['title'], 'C')
119
+ self.assertEqual(response.json['data'][2]['title'], 'A')
120
+
121
+ response = self.get(url_for('api.dataservices', sort='title'))
122
+ self.assert200(response)
123
+
124
+ self.assertEqual(response.json['previous_page'], None)
125
+ self.assertEqual(response.json['next_page'], None)
126
+ self.assertEqual(response.json['page'], 1)
127
+ self.assertEqual(response.json['total'], 3)
128
+ self.assertEqual(len(response.json['data']), 3)
129
+ self.assertEqual(response.json['data'][0]['title'], 'A')
130
+ self.assertEqual(response.json['data'][1]['title'], 'B')
131
+ self.assertEqual(response.json['data'][2]['title'], 'C')
132
+
133
+ response = self.get(url_for('api.dataservices', sort='-title'))
134
+ self.assert200(response)
135
+
136
+ self.assertEqual(response.json['previous_page'], None)
137
+ self.assertEqual(response.json['next_page'], None)
138
+ self.assertEqual(response.json['page'], 1)
139
+ self.assertEqual(response.json['total'], 3)
140
+ self.assertEqual(len(response.json['data']), 3)
141
+ self.assertEqual(response.json['data'][0]['title'], 'C')
142
+ self.assertEqual(response.json['data'][1]['title'], 'B')
143
+ self.assertEqual(response.json['data'][2]['title'], 'A')
144
+
145
+
146
+ response = self.get(url_for('api.dataservices', page_size=1))
147
+ self.assert200(response)
148
+
149
+ self.assertEqual(response.json['previous_page'], None)
150
+ assert response.json['next_page'].endswith(url_for('api.dataservices', page_size=1, page=2))
151
+ self.assertEqual(response.json['page'], 1)
152
+ self.assertEqual(response.json['total'], 3)
153
+ self.assertEqual(len(response.json['data']), 1)
154
+ self.assertEqual(response.json['data'][0]['title'], 'B')
155
+
156
+ def test_dataservice_api_create_with_validation_error(self):
157
+ self.login()
158
+ response = self.post(url_for('api.dataservices'), {
159
+ 'base_api_url': 'https://example.org',
160
+ })
161
+ self.assert400(response)
162
+ self.assertEqual(Dataservice.objects.count(), 0)
163
+
164
+ def test_dataservice_api_create_with_unkwown_license(self):
165
+ self.login()
166
+ response = self.post(url_for('api.dataservices'), {
167
+ 'title': 'My title',
168
+ 'base_api_url': 'https://example.org',
169
+ 'license': 'unwkown-license',
170
+ })
171
+ self.assert400(response)
172
+ self.assertEqual(response.json['errors']['license'], ["Unknown reference 'unwkown-license'"])
173
+ self.assertEqual(Dataservice.objects.count(), 0)
174
+
175
+
176
+ def test_dataservice_api_create_with_unkwown_contact_point(self):
177
+ self.login()
178
+
179
+ response = self.post(url_for('api.dataservices'), {
180
+ 'title': 'My title',
181
+ 'base_api_url': 'https://example.org',
182
+ 'contact_point': '66212433e42ab56639ad516e',
183
+ })
184
+ self.assert400(response)
185
+ self.assertEqual(response.json['errors']['contact_point'], ["Unknown reference '66212433e42ab56639ad516e'"])
186
+ self.assertEqual(Dataservice.objects.count(), 0)
187
+
188
+
189
+ def test_dataservice_api_create_with_custom_user_or_org(self):
190
+ other = UserFactory()
191
+ other_member = Member(user=other, role='editor')
192
+ other_org = OrganizationFactory(members=[other_member])
193
+
194
+ me = self.login()
195
+ me_member = Member(user=me, role='editor')
196
+ me_org = OrganizationFactory(members=[me_member])
197
+
198
+ response = self.post(url_for('api.dataservices'), {
199
+ 'title': 'My title',
200
+ 'base_api_url': 'https://example.org',
201
+ 'owner': other.id,
202
+ })
203
+ self.assert400(response)
204
+ self.assertEqual(response.json['errors']['owner'], [_("You can only set yourself as owner")])
205
+ self.assertEqual(Dataservice.objects.count(), 0)
206
+
207
+ response = self.post(url_for('api.dataservices'), {
208
+ 'title': 'My title',
209
+ 'base_api_url': 'https://example.org',
210
+ 'organization': other_org.id,
211
+ })
212
+ self.assert400(response)
213
+ self.assertEqual(response.json['errors']['organization'], [_("Permission denied for this organization")])
214
+ self.assertEqual(Dataservice.objects.count(), 0)
215
+
216
+
217
+ response = self.post(url_for('api.dataservices'), {
218
+ 'title': 'My title',
219
+ 'base_api_url': 'https://example.org',
220
+ 'owner': me.id,
221
+ })
222
+ self.assert201(response)
223
+ dataservice = Dataservice.objects(id=response.json['id']).first()
224
+ self.assertEqual(dataservice.owner.id, me.id)
225
+ self.assertEqual(dataservice.organization, None)
226
+
227
+
228
+ response = self.post(url_for('api.dataservices'), {
229
+ 'title': 'My title',
230
+ 'base_api_url': 'https://example.org',
231
+ 'organization': me_org.id,
232
+ })
233
+ self.assert201(response)
234
+ dataservice = Dataservice.objects(id=response.json['id']).first()
235
+ self.assertEqual(dataservice.owner, None)
236
+ self.assertEqual(dataservice.organization.id, me_org.id)
@@ -191,8 +191,7 @@ class MembershipAPITest:
191
191
  user = api.login()
192
192
  data = {'comment': 'a comment'}
193
193
 
194
- api_url = url_for('api.request_membership', org=organization)
195
- response = api.post(api_url, data)
194
+ response = api.post(url_for('api.request_membership', org=organization), data)
196
195
  assert201(response)
197
196
 
198
197
  organization.reload()
@@ -209,14 +208,13 @@ class MembershipAPITest:
209
208
  assert request.handled_by is None
210
209
  assert request.refusal_comment is None
211
210
 
212
- def test_request_existing_pending_membership(self, api):
211
+ def test_request_existing_pending_membership_do_not_duplicate_it(self, api):
213
212
  user = api.login()
214
213
  previous_request = MembershipRequest(user=user, comment='previous')
215
214
  organization = OrganizationFactory(requests=[previous_request])
216
215
  data = {'comment': 'a comment'}
217
216
 
218
- api_url = url_for('api.request_membership', org=organization)
219
- response = api.post(api_url, data)
217
+ response = api.post(url_for('api.request_membership', org=organization), data)
220
218
  assert200(response)
221
219
 
222
220
  organization.reload()
@@ -233,6 +231,81 @@ class MembershipAPITest:
233
231
  assert request.handled_by is None
234
232
  assert request.refusal_comment is None
235
233
 
234
+ def test_get_membership_requests(self, api):
235
+ user = api.login()
236
+ applicant = UserFactory(email="thibaud@example.org")
237
+ membership_request = MembershipRequest(user=applicant, comment='test')
238
+ member = Member(user=user, role='admin')
239
+ organization = OrganizationFactory(
240
+ members=[member], requests=[membership_request])
241
+
242
+ response = api.get(url_for('api.request_membership', org=organization))
243
+ assert200(response)
244
+
245
+ assert len(response.json) == 1
246
+ assert response.json[0]['comment'] == 'test'
247
+ assert response.json[0]['user']['email'] == 'thibaud@example.org' # Can see email of applicant
248
+
249
+ def test_only_org_member_can_get_membership_requests(self, api):
250
+ api.login()
251
+ applicant = UserFactory(email="thibaud@example.org")
252
+ membership_request = MembershipRequest(user=applicant, comment='test')
253
+ organization = OrganizationFactory(
254
+ members=[], requests=[membership_request])
255
+
256
+ response = api.get(url_for('api.request_membership', org=organization))
257
+ assert403(response)
258
+
259
+
260
+ def test_get_members_with_or_without_email(self, api):
261
+ admin = Member(user=UserFactory(email="admin@example.org"), role='admin', since="2024-04-14")
262
+ editor = Member(user=UserFactory(email="editor@example.org"), role='editor')
263
+ other = UserFactory(email="other@example.org")
264
+
265
+ organization = OrganizationFactory(members=[admin, editor])
266
+
267
+ # Admin can see emails
268
+ api.login(admin.user)
269
+ response = api.get(url_for('api.organization', org=organization))
270
+ assert200(response)
271
+
272
+ members = response.json['members']
273
+ assert len(members) == 2
274
+ assert members[0]['role'] == 'admin'
275
+ assert members[0]['since'] == '2024-04-14T00:00:00+00:00'
276
+ assert members[0]['user']['email'] == 'admin@example.org'
277
+
278
+ assert members[1]['role'] == 'editor'
279
+ assert members[1]['user']['email'] == 'editor@example.org'
280
+
281
+ # Editor can see emails
282
+ api.login(editor.user)
283
+ response = api.get(url_for('api.organization', org=organization))
284
+ assert200(response)
285
+
286
+ members = response.json['members']
287
+ assert len(members) == 2
288
+ assert members[0]['role'] == 'admin'
289
+ assert members[0]['since'] == '2024-04-14T00:00:00+00:00'
290
+ assert members[0]['user']['email'] == 'admin@example.org'
291
+
292
+ assert members[1]['role'] == 'editor'
293
+ assert members[1]['user']['email'] == 'editor@example.org'
294
+
295
+ # Others cannot see emails
296
+ api.login(other)
297
+ response = api.get(url_for('api.organization', org=organization))
298
+ assert200(response)
299
+
300
+ members = response.json['members']
301
+ assert len(members) == 2
302
+ assert members[0]['role'] == 'admin'
303
+ assert members[0]['since'] == '2024-04-14T00:00:00+00:00'
304
+ assert members[0]['user']['email'] is None
305
+
306
+ assert members[1]['role'] == 'editor'
307
+ assert members[1]['user']['email'] is None
308
+
236
309
  def test_accept_membership(self, api):
237
310
  user = api.login()
238
311
  applicant = UserFactory()
@@ -4,7 +4,7 @@ from udata.core import storages
4
4
  from udata.core.user.factories import AdminFactory, UserFactory
5
5
  from udata.models import Follow
6
6
  from udata.utils import faker
7
- from udata.tests.helpers import create_test_image
7
+ from udata.tests.helpers import capture_mails, create_test_image
8
8
 
9
9
  from . import APITestCase
10
10
 
@@ -153,6 +153,8 @@ class UserAPITest(APITestCase):
153
153
 
154
154
  def test_user_api_full_text_search_first_name(self):
155
155
  '''It should find users based on first name'''
156
+ self.login(AdminFactory())
157
+
156
158
  for i in range(4):
157
159
  UserFactory(
158
160
  first_name='test-{0}'.format(i) if i % 2 else faker.word())
@@ -164,6 +166,8 @@ class UserAPITest(APITestCase):
164
166
 
165
167
  def test_user_api_full_text_search_last_name(self):
166
168
  '''It should find users based on last name'''
169
+ self.login(AdminFactory())
170
+
167
171
  for i in range(4):
168
172
  UserFactory(
169
173
  last_name='test-{0}'.format(i) if i % 2 else faker.word())
@@ -175,6 +179,8 @@ class UserAPITest(APITestCase):
175
179
 
176
180
  def test_user_api_full_text_search_unicode(self):
177
181
  '''It should find user with special characters'''
182
+ self.login(AdminFactory())
183
+
178
184
  for i in range(4):
179
185
  UserFactory(
180
186
  first_name='test-{0}'.format(i) if i % 2 else faker.word())
@@ -189,14 +195,17 @@ class UserAPITest(APITestCase):
189
195
 
190
196
  def test_find_users_api_no_match(self):
191
197
  '''It should not find user if no match'''
198
+ self.login(AdminFactory())
192
199
  UserFactory.create_batch(3)
193
200
 
194
201
  response = self.get(url_for('api.users', q='xxxxxx'))
195
202
  self.assert200(response)
196
203
  self.assertEqual(len(response.json['data']), 0)
197
204
 
198
- def test_users(self):
205
+ def test_users_as_admin(self):
199
206
  '''It should provide a list of users'''
207
+ self.login(AdminFactory(email="thibaud@example.org"))
208
+
200
209
  user = UserFactory(
201
210
  about=faker.paragraph(),
202
211
  website=faker.url(),
@@ -204,14 +213,25 @@ class UserAPITest(APITestCase):
204
213
  metrics={'datasets': 10, 'followers': 1, 'following': 0, 'reuses': 2})
205
214
  response = self.get(url_for('api.users'))
206
215
  self.assert200(response)
207
- [json] = response.json['data']
208
- self.assertEqual(json['id'], str(user.id))
209
- self.assertEqual(json['slug'], user.slug)
210
- self.assertEqual(json['first_name'], user.first_name)
211
- self.assertEqual(json['last_name'], user.last_name)
212
- self.assertEqual(json['website'], user.website)
213
- self.assertEqual(json['about'], user.about)
214
- self.assertEqual(json['metrics'], user.metrics)
216
+ users = response.json['data']
217
+ self.assertEqual(users[1]['email'], "thibaud@example.org") # Admin user created for login
218
+ self.assertEqual(users[0]['id'], str(user.id))
219
+ self.assertEqual(users[0]['slug'], user.slug)
220
+ self.assertEqual(users[0]['first_name'], user.first_name)
221
+ self.assertEqual(users[0]['last_name'], user.last_name)
222
+ self.assertEqual(users[0]['website'], user.website)
223
+ self.assertEqual(users[0]['about'], user.about)
224
+ self.assertEqual(users[0]['metrics'], user.metrics)
225
+
226
+ def test_users_forbidden(self):
227
+ UserFactory()
228
+
229
+ response = self.get(url_for('api.users'))
230
+ self.assert401(response)
231
+
232
+ self.login()
233
+ response = self.get(url_for('api.users'))
234
+ self.assert403(response)
215
235
 
216
236
  def test_get_user(self):
217
237
  '''It should get a user'''
@@ -335,15 +355,29 @@ class UserAPITest(APITestCase):
335
355
  {'file': (file, 'test.png')},
336
356
  json=False,
337
357
  )
338
- response = self.delete(url_for('api.user', user=other_user))
339
- self.assertEqual(list(storages.avatars.list_files()), [])
340
- self.assert204(response)
358
+ with capture_mails() as mails:
359
+ response = self.delete(url_for('api.user', user=other_user))
360
+ self.assertEqual(list(storages.avatars.list_files()), [])
361
+ self.assert204(response)
362
+ self.assertEquals(len(mails), 1)
363
+
341
364
  other_user.reload()
342
365
  response = self.delete(url_for('api.user', user=other_user))
343
366
  self.assert410(response)
344
367
  response = self.delete(url_for('api.user', user=user))
345
368
  self.assert403(response)
346
369
 
370
+ def test_delete_user_without_notify(self):
371
+ user = AdminFactory()
372
+ self.login(user)
373
+ other_user = UserFactory()
374
+
375
+ with capture_mails() as mails:
376
+ response = self.delete(url_for('api.user', user=other_user, no_mail=True))
377
+ self.assert204(response)
378
+ self.assertEqual(len(mails), 0)
379
+
380
+
347
381
  def test_contact_points(self):
348
382
  user = AdminFactory()
349
383
  self.login(user)
udata/tests/plugin.py CHANGED
@@ -192,6 +192,11 @@ class ApiClient(object):
192
192
  return self.client.put(url, data or {}, *args, **kwargs)
193
193
  return self.perform('put', url, data=data or {}, *args, **kwargs)
194
194
 
195
+ def patch(self, url, data=None, json=True, *args, **kwargs):
196
+ if not json:
197
+ return self.client.patch(url, data or {}, *args, **kwargs)
198
+ return self.perform('patch', url, data=data or {}, *args, **kwargs)
199
+
195
200
  def delete(self, url, data=None, *args, **kwargs):
196
201
  return self.perform('delete', url, data=data or {}, *args, **kwargs)
197
202
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: udata
3
- Version: 7.0.8.dev28841
3
+ Version: 8.0.1
4
4
  Summary: Open data portal
5
5
  Home-page: https://github.com/opendatateam/udata
6
6
  Author: Opendata Team
@@ -131,11 +131,25 @@ for publishing udata's roadmap and for building consensus around it.
131
131
  It is collectively taken care of by members of the
132
132
  [OpenDataTeam](https://github.com/opendatateam).
133
133
 
134
- [readthedocs-url]: https://udata.readthedocs.io/en/latest/
134
+ [readthedocs-url]: https://udata.readthedocs.io/en/v8.0.1/
135
135
 
136
136
  # Changelog
137
137
 
138
- ## Current (in progress)
138
+ ## 8.0.1 (2024-05-28)
139
+
140
+ - Add dataservices in beta [#2986](https://github.com/opendatateam/udata/pull/2986)
141
+ - Remove deprecated `metrics_for` route [#3022](https://github.com/opendatateam/udata/pull/3022)
142
+ - Fix spatial coverage fetching perfs. Need to schedule `compute-geozones-metrics` [#3018](https://github.com/opendatateam/udata/pull/3018)
143
+ - Delete a user without sending mail [#3031](https://github.com/opendatateam/udata/pull/3031)
144
+ - Convert known HVD categories used as theme to keywords [#3014](https://github.com/opendatateam/udata/pull/3014)
145
+ - Allow for series in CSW ISO 19139 DCAT backend [#3028](https://github.com/opendatateam/udata/pull/3028)
146
+ - Add `email` to membership request list API response, add `since` to org members API responses, add `email` to members of org on show org endpoint for org's admins and editors [#3038](https://github.com/opendatateam/udata/pull/3038)
147
+ - Add `resources_downloads` to datasets metrics [#3042](https://github.com/opendatateam/udata/pull/3042)
148
+ - Fix do not override resources extras on admin during update [#3043](https://github.com/opendatateam/udata/pull/3043)
149
+ - Endpoint /users is now protected by admin permissions [#3047](https://github.com/opendatateam/udata/pull/3047)
150
+ - Fix trailing `/` inside `GeoZone` routes not redirecting. Disallow `/` inside `GeoZone` ids [#3045](https://github.com/opendatateam/udata/pull/3045)
151
+
152
+ ## 8.0.0 (2024-04-23)
139
153
 
140
154
  - **breaking change** Migrate to Python 3.11 [#2992](https://github.com/opendatateam/udata/pull/2992) [#3021](https://github.com/opendatateam/udata/pull/3021)
141
155
  - **breaking change** Fix datetime serialization in extras (return ISO string in JSON). Warning, `ujson` shouldn't be installed anymore on the project to allow `cls` parameter to override the JSONEncoder [#3019](https://github.com/opendatateam/udata/pull/3019)