udata 10.1.3.dev34275__py2.py3-none-any.whl → 10.1.3.dev34303__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.
- udata/core/dataservices/models.py +1 -0
- udata/core/user/api.py +8 -1
- udata/core/user/models.py +16 -11
- udata/core/user/tasks.py +81 -2
- udata/core/user/tests/test_user_model.py +29 -12
- udata/harvest/actions.py +5 -0
- udata/harvest/backends/base.py +22 -2
- udata/harvest/models.py +19 -0
- udata/harvest/tests/test_actions.py +12 -0
- udata/harvest/tests/test_base_backend.py +74 -8
- udata/settings.py +5 -0
- udata/static/chunks/{11.822f6ccb39c92c796d13.js → 11.55ab79044cda0271b595.js} +3 -3
- udata/static/chunks/{11.822f6ccb39c92c796d13.js.map → 11.55ab79044cda0271b595.js.map} +1 -1
- udata/static/chunks/{13.d9c1735d14038b94c17e.js → 13.f29411b06be1883356a3.js} +2 -2
- udata/static/chunks/{13.d9c1735d14038b94c17e.js.map → 13.f29411b06be1883356a3.js.map} +1 -1
- udata/static/chunks/{17.81c57c0dedf812e43013.js → 17.3bd0340930d4a314ce9c.js} +2 -2
- udata/static/chunks/{17.81c57c0dedf812e43013.js.map → 17.3bd0340930d4a314ce9c.js.map} +1 -1
- udata/static/chunks/{19.ba0bb2baa40e899d440b.js → 19.3e0e8651d948e04b8cf2.js} +3 -3
- udata/static/chunks/{19.ba0bb2baa40e899d440b.js.map → 19.3e0e8651d948e04b8cf2.js.map} +1 -1
- udata/static/chunks/{5.0652a860afda96795a53.js → 5.0fa1408dae4e76b87b2e.js} +3 -3
- udata/static/chunks/{5.0652a860afda96795a53.js.map → 5.0fa1408dae4e76b87b2e.js.map} +1 -1
- udata/static/chunks/{6.92d7c2ec6d20005774ef.js → 6.d663709d877baa44a71e.js} +3 -3
- udata/static/chunks/{6.92d7c2ec6d20005774ef.js.map → 6.d663709d877baa44a71e.js.map} +1 -1
- udata/static/chunks/{8.0f42630e6d8ff782928e.js → 8.494b003a94383b142c18.js} +2 -2
- udata/static/chunks/{8.0f42630e6d8ff782928e.js.map → 8.494b003a94383b142c18.js.map} +1 -1
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- udata/templates/mail/account_inactivity.html +29 -0
- udata/templates/mail/account_inactivity.txt +22 -0
- udata/templates/mail/inactive_account_deleted.html +5 -0
- udata/templates/mail/inactive_account_deleted.txt +6 -0
- udata/tests/api/test_me_api.py +1 -1
- udata/tests/api/test_user_api.py +47 -8
- udata/tests/user/test_user_tasks.py +144 -0
- {udata-10.1.3.dev34275.dist-info → udata-10.1.3.dev34303.dist-info}/METADATA +5 -1
- {udata-10.1.3.dev34275.dist-info → udata-10.1.3.dev34303.dist-info}/RECORD +40 -35
- {udata-10.1.3.dev34275.dist-info → udata-10.1.3.dev34303.dist-info}/LICENSE +0 -0
- {udata-10.1.3.dev34275.dist-info → udata-10.1.3.dev34303.dist-info}/WHEEL +0 -0
- {udata-10.1.3.dev34275.dist-info → udata-10.1.3.dev34303.dist-info}/entry_points.txt +0 -0
- {udata-10.1.3.dev34275.dist-info → udata-10.1.3.dev34303.dist-info}/top_level.txt +0 -0
udata/static/common.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!function(e){function __webpack_require__(c){if(a[c])return a[c].exports;var r=a[c]={exports:{},id:c,loaded:!1};return e[c].call(r.exports,r,r.exports,__webpack_require__),r.loaded=!0,r.exports}var c=window.webpackJsonp;window.webpackJsonp=function(f,
|
|
1
|
+
!function(e){function __webpack_require__(c){if(a[c])return a[c].exports;var r=a[c]={exports:{},id:c,loaded:!1};return e[c].call(r.exports,r,r.exports,__webpack_require__),r.loaded=!0,r.exports}var c=window.webpackJsonp;window.webpackJsonp=function(f,b){for(var t,d,_=0,n=[];_<f.length;_++)d=f[_],r[d]&&n.push.apply(n,r[d]),r[d]=0;for(t in b){var i=b[t];switch(typeof i){case"object":e[t]=function(c){var a=c.slice(1),r=c[0];return function(c,f,b){e[r].apply(this,[c,f,b].concat(a))}}(i);break;case"function":e[t]=i;break;default:e[t]=e[i]}}for(c&&c(f,b);n.length;)n.shift().call(null,__webpack_require__);if(b[0])return a[0]=0,__webpack_require__(0)};var a={},r={31:0};__webpack_require__.e=function(e,c){if(0===r[e])return c.call(null,__webpack_require__);if(void 0!==r[e])r[e].push(c);else{r[e]=[c];var a=document.getElementsByTagName("head")[0],f=document.createElement("script");f.type="text/javascript",f.charset="utf-8",f.async=!0,f.src=__webpack_require__.p+"chunks/"+e+"."+{0:"4dd4f378428d1eed85c8",1:"0a96f54313c89e541ccb",2:"c67aa76cc078c193aed5",3:"174a03e48d267820919b",4:"98dca3045c034c154d5f",5:"0fa1408dae4e76b87b2e",6:"d663709d877baa44a71e",7:"896bbffe39433bb598f7",8:"494b003a94383b142c18",9:"07515e5187f475bce828",10:"471164b2a9fe15614797",11:"55ab79044cda0271b595",12:"1be61e0201691821bff6",13:"f29411b06be1883356a3",14:"bc3f6bc0a67517e7b30f",15:"2f5d8e3d4aa4c46188d7",16:"aaa39ef2d7e82594efc8",17:"3bd0340930d4a314ce9c",18:"56444ebd2456a4ba2201",19:"3e0e8651d948e04b8cf2",20:"3d4e02d1205a3c9e26c5",21:"af1610ce169cf6d1cf4e",22:"6a69068a7dd9d767dea1",23:"663b89b79d755a70deef",24:"d149cf025cbbca231d88",25:"1fafd6424761185cc14b",26:"402064cda3665d56f7fc",27:"997802f273a0c88f7b6d",28:"e68c475c6ca1e2eec92c",29:"2f4a04c61d75f2ef18ab",30:"e97e10c9246818e2b4b2",32:"03cc9867716dd58b9302",33:"011b367beb543774f950"}[e]+".js",a.appendChild(f)}},__webpack_require__.m=e,__webpack_require__.c=a,__webpack_require__.p="/static/"}(function(e){for(var c in e)if(Object.prototype.hasOwnProperty.call(e,c))switch(typeof e[c]){case"function":break;case"object":e[c]=function(c){var a=c.slice(1),r=e[c[0]];return function(e,c,f){r.apply(this,[e,c,f].concat(a))}}(e[c]);break;default:e[c]=e[e[c]]}return e}([]));
|
|
2
2
|
//# sourceMappingURL=common.js.map
|
udata/static/common.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["webpack:///common.js","webpack:///webpack/bootstrap 8b0f5ee30dfbfb6c23e7"],"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\":\"4dd4f378428d1eed85c8\",\"1\":\"0a96f54313c89e541ccb\",\"2\":\"c67aa76cc078c193aed5\",\"3\":\"174a03e48d267820919b\",\"4\":\"98dca3045c034c154d5f\",\"5\":\"0652a860afda96795a53\",\"6\":\"92d7c2ec6d20005774ef\",\"7\":\"896bbffe39433bb598f7\",\"8\":\"0f42630e6d8ff782928e\",\"9\":\"07515e5187f475bce828\",\"10\":\"471164b2a9fe15614797\",\"11\":\"822f6ccb39c92c796d13\",\"12\":\"1be61e0201691821bff6\",\"13\":\"d9c1735d14038b94c17e\",\"14\":\"bc3f6bc0a67517e7b30f\",\"15\":\"2f5d8e3d4aa4c46188d7\",\"16\":\"aaa39ef2d7e82594efc8\",\"17\":\"81c57c0dedf812e43013\",\"18\":\"56444ebd2456a4ba2201\",\"19\":\"ba0bb2baa40e899d440b\",\"20\":\"3d4e02d1205a3c9e26c5\",\"21\":\"af1610ce169cf6d1cf4e\",\"22\":\"6a69068a7dd9d767dea1\",\"23\":\"663b89b79d755a70deef\",\"24\":\"d149cf025cbbca231d88\",\"25\":\"1fafd6424761185cc14b\",\"26\":\"402064cda3665d56f7fc\",\"27\":\"997802f273a0c88f7b6d\",\"28\":\"e68c475c6ca1e2eec92c\",\"29\":\"2f4a04c61d75f2ef18ab\",\"30\":\"e97e10c9246818e2b4b2\",\"32\":\"03cc9867716dd58b9302\",\"33\":\"011b367beb543774f950\"}[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\":\"4dd4f378428d1eed85c8\",\"1\":\"0a96f54313c89e541ccb\",\"2\":\"c67aa76cc078c193aed5\",\"3\":\"174a03e48d267820919b\",\"4\":\"98dca3045c034c154d5f\",\"5\":\"0652a860afda96795a53\",\"6\":\"92d7c2ec6d20005774ef\",\"7\":\"896bbffe39433bb598f7\",\"8\":\"0f42630e6d8ff782928e\",\"9\":\"07515e5187f475bce828\",\"10\":\"471164b2a9fe15614797\",\"11\":\"822f6ccb39c92c796d13\",\"12\":\"1be61e0201691821bff6\",\"13\":\"d9c1735d14038b94c17e\",\"14\":\"bc3f6bc0a67517e7b30f\",\"15\":\"2f5d8e3d4aa4c46188d7\",\"16\":\"aaa39ef2d7e82594efc8\",\"17\":\"81c57c0dedf812e43013\",\"18\":\"56444ebd2456a4ba2201\",\"19\":\"ba0bb2baa40e899d440b\",\"20\":\"3d4e02d1205a3c9e26c5\",\"21\":\"af1610ce169cf6d1cf4e\",\"22\":\"6a69068a7dd9d767dea1\",\"23\":\"663b89b79d755a70deef\",\"24\":\"d149cf025cbbca231d88\",\"25\":\"1fafd6424761185cc14b\",\"26\":\"402064cda3665d56f7fc\",\"27\":\"997802f273a0c88f7b6d\",\"28\":\"e68c475c6ca1e2eec92c\",\"29\":\"2f4a04c61d75f2ef18ab\",\"30\":\"e97e10c9246818e2b4b2\",\"32\":\"03cc9867716dd58b9302\",\"33\":\"011b367beb543774f950\"}[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 8b0f5ee30dfbfb6c23e7\n **/"],"sourceRoot":""}
|
|
1
|
+
{"version":3,"sources":["webpack:///common.js","webpack:///webpack/bootstrap 0ab1659a41a61777d788"],"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\":\"4dd4f378428d1eed85c8\",\"1\":\"0a96f54313c89e541ccb\",\"2\":\"c67aa76cc078c193aed5\",\"3\":\"174a03e48d267820919b\",\"4\":\"98dca3045c034c154d5f\",\"5\":\"0fa1408dae4e76b87b2e\",\"6\":\"d663709d877baa44a71e\",\"7\":\"896bbffe39433bb598f7\",\"8\":\"494b003a94383b142c18\",\"9\":\"07515e5187f475bce828\",\"10\":\"471164b2a9fe15614797\",\"11\":\"55ab79044cda0271b595\",\"12\":\"1be61e0201691821bff6\",\"13\":\"f29411b06be1883356a3\",\"14\":\"bc3f6bc0a67517e7b30f\",\"15\":\"2f5d8e3d4aa4c46188d7\",\"16\":\"aaa39ef2d7e82594efc8\",\"17\":\"3bd0340930d4a314ce9c\",\"18\":\"56444ebd2456a4ba2201\",\"19\":\"3e0e8651d948e04b8cf2\",\"20\":\"3d4e02d1205a3c9e26c5\",\"21\":\"af1610ce169cf6d1cf4e\",\"22\":\"6a69068a7dd9d767dea1\",\"23\":\"663b89b79d755a70deef\",\"24\":\"d149cf025cbbca231d88\",\"25\":\"1fafd6424761185cc14b\",\"26\":\"402064cda3665d56f7fc\",\"27\":\"997802f273a0c88f7b6d\",\"28\":\"e68c475c6ca1e2eec92c\",\"29\":\"2f4a04c61d75f2ef18ab\",\"30\":\"e97e10c9246818e2b4b2\",\"32\":\"03cc9867716dd58b9302\",\"33\":\"011b367beb543774f950\"}[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\":\"4dd4f378428d1eed85c8\",\"1\":\"0a96f54313c89e541ccb\",\"2\":\"c67aa76cc078c193aed5\",\"3\":\"174a03e48d267820919b\",\"4\":\"98dca3045c034c154d5f\",\"5\":\"0fa1408dae4e76b87b2e\",\"6\":\"d663709d877baa44a71e\",\"7\":\"896bbffe39433bb598f7\",\"8\":\"494b003a94383b142c18\",\"9\":\"07515e5187f475bce828\",\"10\":\"471164b2a9fe15614797\",\"11\":\"55ab79044cda0271b595\",\"12\":\"1be61e0201691821bff6\",\"13\":\"f29411b06be1883356a3\",\"14\":\"bc3f6bc0a67517e7b30f\",\"15\":\"2f5d8e3d4aa4c46188d7\",\"16\":\"aaa39ef2d7e82594efc8\",\"17\":\"3bd0340930d4a314ce9c\",\"18\":\"56444ebd2456a4ba2201\",\"19\":\"3e0e8651d948e04b8cf2\",\"20\":\"3d4e02d1205a3c9e26c5\",\"21\":\"af1610ce169cf6d1cf4e\",\"22\":\"6a69068a7dd9d767dea1\",\"23\":\"663b89b79d755a70deef\",\"24\":\"d149cf025cbbca231d88\",\"25\":\"1fafd6424761185cc14b\",\"26\":\"402064cda3665d56f7fc\",\"27\":\"997802f273a0c88f7b6d\",\"28\":\"e68c475c6ca1e2eec92c\",\"29\":\"2f4a04c61d75f2ef18ab\",\"30\":\"e97e10c9246818e2b4b2\",\"32\":\"03cc9867716dd58b9302\",\"33\":\"011b367beb543774f950\"}[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 0ab1659a41a61777d788\n **/"],"sourceRoot":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{% extends 'mail/base.html' %}
|
|
2
|
+
{% from 'mail/button.html' import mail_button %}
|
|
3
|
+
|
|
4
|
+
{% block body %}
|
|
5
|
+
<p style="margin: 0;padding: 0;">
|
|
6
|
+
{{ _(
|
|
7
|
+
'Your account (%(user_email)s) has been inactive for %(inactivity_years)d years or more.',
|
|
8
|
+
user_email=user.email,
|
|
9
|
+
inactivity_years=config.YEARS_OF_INACTIVITY_BEFORE_DEACTIVATION
|
|
10
|
+
)
|
|
11
|
+
}}
|
|
12
|
+
</p>
|
|
13
|
+
<br/>
|
|
14
|
+
<p style="margin: 0;padding: 0;"><b>
|
|
15
|
+
{{ _(
|
|
16
|
+
'If you want to keep your account, please log in with your account on %(site)s.',
|
|
17
|
+
site=config.SITE_TITLE
|
|
18
|
+
)
|
|
19
|
+
}}
|
|
20
|
+
</b></p>
|
|
21
|
+
<br/>
|
|
22
|
+
<p style="margin: 0;padding: 0;">
|
|
23
|
+
{{ _(
|
|
24
|
+
'Without logging in, your account will be deleted within %(notify_delay)d days.',
|
|
25
|
+
notify_delay=config.DAYS_BEFORE_ACCOUNT_INACTIVITY_NOTIFY_DELAY
|
|
26
|
+
)
|
|
27
|
+
}}
|
|
28
|
+
</p>
|
|
29
|
+
{% endblock %}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{% extends 'mail/base.txt' %}
|
|
2
|
+
|
|
3
|
+
{% block body %}
|
|
4
|
+
{{ _(
|
|
5
|
+
'Your account (%(user_email)s) has been inactive for %(inactivity_years)d years or more.',
|
|
6
|
+
user_email=user.email,
|
|
7
|
+
inactivity_years=config.YEARS_OF_INACTIVITY_BEFORE_DEACTIVATION
|
|
8
|
+
)
|
|
9
|
+
}}
|
|
10
|
+
|
|
11
|
+
{{ _(
|
|
12
|
+
'If you want to keep your account, please log in with your account on %(site)s.',
|
|
13
|
+
site=config.SITE_TITLE
|
|
14
|
+
)
|
|
15
|
+
}}
|
|
16
|
+
|
|
17
|
+
{{ _(
|
|
18
|
+
'Without logging in, your account will be deleted within %(notify_delay)d days.',
|
|
19
|
+
notify_delay=config.DAYS_BEFORE_ACCOUNT_INACTIVITY_NOTIFY_DELAY
|
|
20
|
+
)
|
|
21
|
+
}}
|
|
22
|
+
{% endblock %}
|
udata/tests/api/test_me_api.py
CHANGED
|
@@ -332,7 +332,7 @@ class MeAPITest(APITestCase):
|
|
|
332
332
|
|
|
333
333
|
# The discussions are kept but the messages are anonymized
|
|
334
334
|
self.assertEqual(len(discussion.discussion), 2)
|
|
335
|
-
self.assertEqual(discussion.discussion[0].
|
|
335
|
+
self.assertEqual(discussion.discussion[0].posted_by.fullname, "DELETED DELETED")
|
|
336
336
|
self.assertEqual(discussion.discussion[1].content, other_disc_msg_content)
|
|
337
337
|
|
|
338
338
|
# The datasets are unchanged
|
udata/tests/api/test_user_api.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from flask import url_for
|
|
2
2
|
|
|
3
3
|
from udata.core import storages
|
|
4
|
+
from udata.core.discussions.factories import DiscussionFactory, MessageDiscussionFactory
|
|
4
5
|
from udata.core.user.factories import AdminFactory, UserFactory
|
|
5
|
-
from udata.models import Follow
|
|
6
|
+
from udata.models import Discussion, Follow
|
|
6
7
|
from udata.tests.helpers import capture_mails, create_test_image
|
|
7
8
|
from udata.utils import faker
|
|
8
9
|
|
|
@@ -352,36 +353,74 @@ class UserAPITest(APITestCase):
|
|
|
352
353
|
def test_delete_user(self):
|
|
353
354
|
user = AdminFactory()
|
|
354
355
|
self.login(user)
|
|
355
|
-
|
|
356
|
+
user_to_delete = UserFactory()
|
|
356
357
|
file = create_test_image()
|
|
358
|
+
discussion = DiscussionFactory(
|
|
359
|
+
user=user_to_delete,
|
|
360
|
+
discussion=[
|
|
361
|
+
MessageDiscussionFactory(posted_by=user_to_delete),
|
|
362
|
+
MessageDiscussionFactory(posted_by=user_to_delete),
|
|
363
|
+
],
|
|
364
|
+
)
|
|
357
365
|
|
|
358
366
|
response = self.post(
|
|
359
|
-
url_for("api.user_avatar", user=
|
|
367
|
+
url_for("api.user_avatar", user=user_to_delete),
|
|
360
368
|
{"file": (file, "test.png")},
|
|
361
369
|
json=False,
|
|
362
370
|
)
|
|
363
371
|
with capture_mails() as mails:
|
|
364
|
-
response = self.delete(url_for("api.user", user=
|
|
372
|
+
response = self.delete(url_for("api.user", user=user_to_delete))
|
|
365
373
|
self.assertEqual(list(storages.avatars.list_files()), [])
|
|
366
374
|
self.assert204(response)
|
|
367
375
|
self.assertEquals(len(mails), 1)
|
|
368
376
|
|
|
369
|
-
|
|
370
|
-
response = self.delete(url_for("api.user", user=
|
|
377
|
+
user_to_delete.reload()
|
|
378
|
+
response = self.delete(url_for("api.user", user=user_to_delete))
|
|
371
379
|
self.assert410(response)
|
|
372
380
|
response = self.delete(url_for("api.user", user=user))
|
|
373
381
|
self.assert403(response)
|
|
374
382
|
|
|
383
|
+
# discussions are kept by default
|
|
384
|
+
discussion.reload()
|
|
385
|
+
assert len(discussion.discussion) == 2
|
|
386
|
+
assert discussion.discussion[1].content != "DELETED"
|
|
387
|
+
|
|
375
388
|
def test_delete_user_without_notify(self):
|
|
376
389
|
user = AdminFactory()
|
|
377
390
|
self.login(user)
|
|
378
|
-
|
|
391
|
+
user_to_delete = UserFactory()
|
|
379
392
|
|
|
380
393
|
with capture_mails() as mails:
|
|
381
|
-
response = self.delete(url_for("api.user", user=
|
|
394
|
+
response = self.delete(url_for("api.user", user=user_to_delete, no_mail=True))
|
|
382
395
|
self.assert204(response)
|
|
383
396
|
self.assertEqual(len(mails), 0)
|
|
384
397
|
|
|
398
|
+
def test_delete_user_with_comments_deletion(self):
|
|
399
|
+
user = AdminFactory()
|
|
400
|
+
self.login(user)
|
|
401
|
+
user_to_delete = UserFactory()
|
|
402
|
+
discussion_only_user = DiscussionFactory(
|
|
403
|
+
user=user_to_delete,
|
|
404
|
+
discussion=[
|
|
405
|
+
MessageDiscussionFactory(posted_by=user_to_delete),
|
|
406
|
+
MessageDiscussionFactory(posted_by=user_to_delete),
|
|
407
|
+
],
|
|
408
|
+
)
|
|
409
|
+
discussion_with_other = DiscussionFactory(
|
|
410
|
+
user=user,
|
|
411
|
+
discussion=[
|
|
412
|
+
MessageDiscussionFactory(posted_by=user),
|
|
413
|
+
MessageDiscussionFactory(posted_by=user_to_delete),
|
|
414
|
+
],
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
response = self.delete(url_for("api.user", user=user_to_delete, delete_comments=True))
|
|
418
|
+
self.assert204(response)
|
|
419
|
+
|
|
420
|
+
assert Discussion.objects(id=discussion_only_user.id).first() is None
|
|
421
|
+
discussion_with_other.reload()
|
|
422
|
+
assert discussion_with_other.discussion[1].content == "DELETED"
|
|
423
|
+
|
|
385
424
|
def test_contact_points(self):
|
|
386
425
|
user = AdminFactory()
|
|
387
426
|
self.login(user)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from flask import current_app
|
|
5
|
+
|
|
6
|
+
from udata.core.discussions.factories import DiscussionFactory
|
|
7
|
+
from udata.core.user import tasks
|
|
8
|
+
from udata.core.user.factories import UserFactory
|
|
9
|
+
from udata.core.user.models import User
|
|
10
|
+
from udata.i18n import gettext as _
|
|
11
|
+
from udata.tests.api import APITestCase
|
|
12
|
+
from udata.tests.helpers import capture_mails
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class UserTasksTest(APITestCase):
|
|
16
|
+
@pytest.mark.options(YEARS_OF_INACTIVITY_BEFORE_DEACTIVATION=3)
|
|
17
|
+
def test_notify_inactive_users(self):
|
|
18
|
+
notification_comparison_date = (
|
|
19
|
+
datetime.utcnow()
|
|
20
|
+
- timedelta(days=current_app.config["YEARS_OF_INACTIVITY_BEFORE_DEACTIVATION"] * 365)
|
|
21
|
+
+ timedelta(days=current_app.config["DAYS_BEFORE_ACCOUNT_INACTIVITY_NOTIFY_DELAY"])
|
|
22
|
+
- timedelta(days=1) # add margin
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
inactive_user = UserFactory(current_login_at=notification_comparison_date)
|
|
26
|
+
UserFactory(current_login_at=datetime.utcnow()) # Active user
|
|
27
|
+
|
|
28
|
+
with capture_mails() as mails:
|
|
29
|
+
tasks.notify_inactive_users()
|
|
30
|
+
|
|
31
|
+
# Assert (only one) mail has been sent
|
|
32
|
+
self.assertEqual(len(mails), 1)
|
|
33
|
+
self.assertEqual(mails[0].send_to, set([inactive_user.email]))
|
|
34
|
+
self.assertEqual(
|
|
35
|
+
mails[0].subject,
|
|
36
|
+
_("Inactivity of your {site} account").format(site=current_app.config["SITE_TITLE"]),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# We shouldn't notify users twice
|
|
40
|
+
with capture_mails() as mails:
|
|
41
|
+
tasks.notify_inactive_users()
|
|
42
|
+
|
|
43
|
+
self.assertEqual(len(mails), 0)
|
|
44
|
+
|
|
45
|
+
@pytest.mark.options(YEARS_OF_INACTIVITY_BEFORE_DEACTIVATION=3)
|
|
46
|
+
@pytest.mark.options(MAX_NUMBER_OF_USER_INACTIVITY_NOTIFICATIONS=10)
|
|
47
|
+
def test_notify_inactive_users_max_notifications(self):
|
|
48
|
+
notification_comparison_date = (
|
|
49
|
+
datetime.utcnow()
|
|
50
|
+
- timedelta(days=current_app.config["YEARS_OF_INACTIVITY_BEFORE_DEACTIVATION"] * 365)
|
|
51
|
+
+ timedelta(days=current_app.config["DAYS_BEFORE_ACCOUNT_INACTIVITY_NOTIFY_DELAY"])
|
|
52
|
+
- timedelta(days=1) # add margin
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
NB_USERS_TO_NOTIFY = 15
|
|
56
|
+
|
|
57
|
+
[UserFactory(current_login_at=notification_comparison_date) for _ in range(15)]
|
|
58
|
+
UserFactory(current_login_at=datetime.utcnow()) # Active user
|
|
59
|
+
|
|
60
|
+
with capture_mails() as mails:
|
|
61
|
+
tasks.notify_inactive_users()
|
|
62
|
+
|
|
63
|
+
# Assert MAX_NUMBER_OF_USER_INACTIVITY_NOTIFICATIONS mails have been sent
|
|
64
|
+
self.assertEqual(
|
|
65
|
+
len(mails), current_app.config["MAX_NUMBER_OF_USER_INACTIVITY_NOTIFICATIONS"]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Second batch
|
|
69
|
+
with capture_mails() as mails:
|
|
70
|
+
tasks.notify_inactive_users()
|
|
71
|
+
|
|
72
|
+
# Assert what's left have been sent
|
|
73
|
+
self.assertEqual(
|
|
74
|
+
len(mails),
|
|
75
|
+
NB_USERS_TO_NOTIFY - current_app.config["MAX_NUMBER_OF_USER_INACTIVITY_NOTIFICATIONS"],
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
@pytest.mark.options(YEARS_OF_INACTIVITY_BEFORE_DEACTIVATION=3)
|
|
79
|
+
def test_delete_inactive_users(self):
|
|
80
|
+
deletion_comparison_date = (
|
|
81
|
+
datetime.utcnow()
|
|
82
|
+
- timedelta(days=current_app.config["YEARS_OF_INACTIVITY_BEFORE_DEACTIVATION"] * 365)
|
|
83
|
+
- timedelta(days=1) # add margin
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
notification_comparison_date = (
|
|
87
|
+
datetime.utcnow()
|
|
88
|
+
- timedelta(days=current_app.config["DAYS_BEFORE_ACCOUNT_INACTIVITY_NOTIFY_DELAY"])
|
|
89
|
+
- timedelta(days=1) # add margin
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
inactive_user_to_delete = UserFactory(
|
|
93
|
+
current_login_at=deletion_comparison_date,
|
|
94
|
+
inactive_deletion_notified_at=notification_comparison_date,
|
|
95
|
+
)
|
|
96
|
+
UserFactory(current_login_at=datetime.utcnow()) # Active user
|
|
97
|
+
discussion = DiscussionFactory(user=inactive_user_to_delete)
|
|
98
|
+
discussion_title = discussion.title
|
|
99
|
+
|
|
100
|
+
with capture_mails() as mails:
|
|
101
|
+
tasks.delete_inactive_users()
|
|
102
|
+
|
|
103
|
+
# Assert (only one) mail has been sent
|
|
104
|
+
self.assertEqual(len(mails), 1)
|
|
105
|
+
self.assertEqual(mails[0].send_to, set([inactive_user_to_delete.email]))
|
|
106
|
+
self.assertEqual(
|
|
107
|
+
mails[0].subject,
|
|
108
|
+
_(
|
|
109
|
+
_("Deletion of your inactive {site} account").format(
|
|
110
|
+
site=current_app.config["SITE_TITLE"]
|
|
111
|
+
)
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Assert user has been deleted but not its discussion
|
|
116
|
+
inactive_user_to_delete.reload()
|
|
117
|
+
discussion.reload()
|
|
118
|
+
self.assertEqual(inactive_user_to_delete.fullname, "DELETED DELETED")
|
|
119
|
+
self.assertEqual(discussion.title, discussion_title)
|
|
120
|
+
|
|
121
|
+
@pytest.mark.options(YEARS_OF_INACTIVITY_BEFORE_DEACTIVATION=3)
|
|
122
|
+
def test_keep_inactive_users_that_logged_in(self):
|
|
123
|
+
notification_comparison_date = (
|
|
124
|
+
datetime.utcnow()
|
|
125
|
+
- timedelta(days=current_app.config["DAYS_BEFORE_ACCOUNT_INACTIVITY_NOTIFY_DELAY"])
|
|
126
|
+
- timedelta(days=1) # add margin
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
inactive_user_that_logged_in_since_notification = UserFactory(
|
|
130
|
+
current_login_at=datetime.utcnow(),
|
|
131
|
+
inactive_deletion_notified_at=notification_comparison_date,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
with capture_mails() as mails:
|
|
135
|
+
tasks.delete_inactive_users()
|
|
136
|
+
|
|
137
|
+
# Assert no mail has been sent
|
|
138
|
+
self.assertEqual(len(mails), 0)
|
|
139
|
+
|
|
140
|
+
# Assert user hasn't been deleted and won't be deleted
|
|
141
|
+
self.assertEqual(User.objects().count(), 1)
|
|
142
|
+
user = User.objects().first()
|
|
143
|
+
self.assertEqual(user, inactive_user_that_logged_in_since_notification)
|
|
144
|
+
self.assertIsNone(user.inactive_deletion_notified_at)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: udata
|
|
3
|
-
Version: 10.1.3.
|
|
3
|
+
Version: 10.1.3.dev34303
|
|
4
4
|
Summary: Open data portal
|
|
5
5
|
Home-page: https://github.com/opendatateam/udata
|
|
6
6
|
Author: Opendata Team
|
|
@@ -140,11 +140,15 @@ It is collectively taken care of by members of the
|
|
|
140
140
|
|
|
141
141
|
## Current (in progress)
|
|
142
142
|
|
|
143
|
+
- Add inactive users notification and deletion jobs [#3274](https://github.com/opendatateam/udata/pull/3274)
|
|
144
|
+
- these jobs can be scheduled daily for example
|
|
145
|
+
- `YEARS_OF_INACTIVITY_BEFORE_DELETION` setting must be configured at least to activate it
|
|
143
146
|
- **breaking change** Migrate dataservices fields, migration needed [#3262](https://github.com/opendatateam/udata/pull/3262)
|
|
144
147
|
- Add views as Dataservice metrics and sort [#3280](https://github.com/opendatateam/udata/pull/3280)
|
|
145
148
|
- Fix creating/updating ID for resources [#3239](https://github.com/opendatateam/udata/pull/3239)
|
|
146
149
|
- Cannot respond to a transfer after first response [#3255](https://github.com/opendatateam/udata/pull/3255)
|
|
147
150
|
- Cannot update resource filetype after creation [#3254](https://github.com/opendatateam/udata/pull/3254)
|
|
151
|
+
- Add autoarchive to dataservices [#3283](https://github.com/opendatateam/udata/pull/3283)
|
|
148
152
|
|
|
149
153
|
## 10.1.2 (2025-03-10)
|
|
150
154
|
|