Compare commits

..

No commits in common. "master" and "3.4.3" have entirely different histories.

121 changed files with 710 additions and 9241 deletions

View File

@ -17,9 +17,8 @@ before_install:
- sudo /etc/init.d/postgresql start 9.4
- psql -c 'create database taiga;' -U postgres
install:
- travis_retry pip install pipenv
- travis_retry pipenv sync --dev
- travis_retry pip install -r requirements-devel.txt
script:
- travis_retry pipenv run coverage run --source=taiga --omit='*tests*,*commands*,*migrations*,*admin*,*.jinja,*dashboard*,*settings*,*wsgi*,*questions*,*documents*' -m pytest -v --tb=native
- travis_retry coverage run --source=taiga --omit='*tests*,*commands*,*migrations*,*admin*,*.jinja,*dashboard*,*settings*,*wsgi*,*questions*,*documents*' -m py.test -v --tb=native
after_success:
- coveralls

View File

@ -2,72 +2,6 @@
## Unreleased
## 4.1.0 (2019-02-04)
### Misc
- Fix Close sprints
### Features:
- Negative filters
- Activate the Ukrainian language
## 4.0.4 (2019-01-15)
### Misc
- Minor bug fixes.
## 4.0.3 (2018-12-11)
### Misc
- Add extra requirements for oauthlib
## 4.0.2 (2018-12-04)
### Misc
- Update messages catalog.
## 4.0.1 (2018-11-28)
### Misc
- Minor bug fix.
## 4.0.0 Larix cajanderi (2018-11-28)
### Features
- Custom home section (https://tree.taiga.io/project/taiga/issue/3059)
- Custom fields (https://tree.taiga.io/project/taiga/issue/3725):
- Dropdown
- Checkbox
- Number
- Bulk move unfinished objects in sprint (https://tree.taiga.io/project/taiga/issue/5451)
- Paginate history activity
- Improve notifications area (https://tree.taiga.io/project/taiga/issue/2165 and
https://tree.taiga.io/project/taiga/issue/3752)
### Misc
- Minor icon changes
- Lots of small bugfixes
## 3.4.5 (2018-10-15)
### Features
- Prevent local Webhooks
## 3.4.4 (2018-09-19)
### Misc
- Small fixes
## 3.4.3 (2018-09-19)
### Misc

53
Pipfile
View File

@ -1,53 +0,0 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
asana = "==0.6.7"
bleach = "==2.1.4"
celery = "==4.0.2"
cryptography = "==2.3.1"
diff-match-patch = "==20121119"
django-ipware = "==1.1.6"
django-jinja = "==2.3.1"
django-picklefield = "==0.3.2"
django-pglocks = "==1.0.2"
django-sampledatahelper = "==0.4.1"
django-sites = "==0.9"
django-sr = "==0.0.4"
djmail = "==1.0.1"
easy-thumbnails = "==2.4.1"
fn = "==0.4.3"
gunicorn = "==19.7.1"
kombu = "==4.0.2" # The same as celery
netaddr = "==0.7.19"
premailer = "==3.0.1"
psd-tools = "==1.4"
"psycopg2-binary" = "==2.7.5"
python-dateutil = "==2.7.5"
python-magic = "==0.4.13"
pytz = "*"
raven = "==6.1.0"
redis = "==2.10.5"
requests = "==2.20.0"
requests_oauthlib = "*"
serpy = "==0.1.1"
webcolors = "==1.7"
CairoSVG = "==2.0.3"
Django = "~=1.11.15"
Markdown = "==3.0.1"
Pillow = "==4.1.1"
Unidecode = "==0.4.20"
Pygments = "==2.2.0"
oauthlib = {extras = ["signedtoken"],version = "*"}
[dev-packages]
coverage = "*"
coveralls = "*"
pytest = "*"
pytest-django = "*"
factory-boy = "*"
[requires]
python_version = "3.6"

805
Pipfile.lock generated
View File

@ -1,805 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "2505838cdc4b5390130990ae63647a4447c5f146ab37ca8bf29f627091112cf7"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.6"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"amqp": {
"hashes": [
"sha256:073dd02fdd73041bffc913b767866015147b61f2a9bc104daef172fc1a0066eb",
"sha256:eed41946890cd43e8dee44a316b85cf6fee5a1a34bb4a562b660a358eb529e1b"
],
"version": "==2.3.2"
},
"asana": {
"hashes": [
"sha256:412398ff0f72104f5fb602653b994cdd36de89aedff2d37229b237f300f5f01b",
"sha256:d576601116764050c4cf63b417f1c24700b76cf6686f0e51e6b0b77d450e7973"
],
"index": "pypi",
"version": "==0.6.7"
},
"asn1crypto": {
"hashes": [
"sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87",
"sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49"
],
"version": "==0.24.0"
},
"billiard": {
"hashes": [
"sha256:42d9a227401ac4fba892918bba0a0c409def5435c4b483267ebfe821afaaba0e"
],
"version": "==3.5.0.5"
},
"bleach": {
"hashes": [
"sha256:0ee95f6167129859c5dce9b1ca291ebdb5d8cd7e382ca0e237dfd0dad63f63d8",
"sha256:24754b9a7d530bf30ce7cbc805bc6cce785660b4a10ff3a43633728438c105ab"
],
"index": "pypi",
"version": "==2.1.4"
},
"cairocffi": {
"hashes": [
"sha256:15386c3a9e08823d6826c4491eaccc7b7254b1dc587a3b9ce60c350c3f990337"
],
"version": "==0.9.0"
},
"cairosvg": {
"hashes": [
"sha256:d2da5aaa31ded26affd5cdffc371ec4cc48800bc2d822a9c28504360482418a1"
],
"index": "pypi",
"version": "==2.0.3"
},
"celery": {
"hashes": [
"sha256:0e5b7e0d7f03aa02061abfd27aa9da05b6740281ca1f5228a54fbf7fe74d8afa",
"sha256:e3d5a6c56a73ff8f2ddd4d06dc37f4c2afe4bb4da7928b884d0725ea865ef54d"
],
"index": "pypi",
"version": "==4.0.2"
},
"certifi": {
"hashes": [
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
],
"version": "==2018.11.29"
},
"cffi": {
"hashes": [
"sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743",
"sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef",
"sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50",
"sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f",
"sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30",
"sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93",
"sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257",
"sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b",
"sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3",
"sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e",
"sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc",
"sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04",
"sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6",
"sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359",
"sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596",
"sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b",
"sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd",
"sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95",
"sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5",
"sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e",
"sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6",
"sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca",
"sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31",
"sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1",
"sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2",
"sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085",
"sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801",
"sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4",
"sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184",
"sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917",
"sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f",
"sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb"
],
"version": "==1.11.5"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"contextlib2": {
"hashes": [
"sha256:509f9419ee91cdd00ba34443217d5ca51f5a364a404e1dce9e8979cea969ca48",
"sha256:f5260a6e679d2ff42ec91ec5252f4eeffdcf21053db9113bd0a8e4d953769c00"
],
"version": "==0.5.5"
},
"cryptography": {
"hashes": [
"sha256:02602e1672b62e803e08617ec286041cc453e8d43f093a5f4162095506bc0beb",
"sha256:10b48e848e1edb93c1d3b797c83c72b4c387ab0eb4330aaa26da8049a6cbede0",
"sha256:17db09db9d7c5de130023657be42689d1a5f60502a14f6f745f6f65a6b8195c0",
"sha256:227da3a896df1106b1a69b1e319dce218fa04395e8cc78be7e31ca94c21254bc",
"sha256:2cbaa03ac677db6c821dac3f4cdfd1461a32d0615847eedbb0df54bb7802e1f7",
"sha256:31db8febfc768e4b4bd826750a70c79c99ea423f4697d1dab764eb9f9f849519",
"sha256:4a510d268e55e2e067715d728e4ca6cd26a8e9f1f3d174faf88e6f2cb6b6c395",
"sha256:6a88d9004310a198c474d8a822ee96a6dd6c01efe66facdf17cb692512ae5bc0",
"sha256:76936ec70a9b72eb8c58314c38c55a0336a2b36de0c7ee8fb874a4547cadbd39",
"sha256:7e3b4aecc4040928efa8a7cdaf074e868af32c58ffc9bb77e7bf2c1a16783286",
"sha256:8168bcb08403ef144ff1fb880d416f49e2728101d02aaadfe9645883222c0aa5",
"sha256:8229ceb79a1792823d87779959184a1bf95768e9248c93ae9f97c7a2f60376a1",
"sha256:8a19e9f2fe69f6a44a5c156968d9fc8df56d09798d0c6a34ccc373bb186cee86",
"sha256:8d10113ca826a4c29d5b85b2c4e045ffa8bad74fb525ee0eceb1d38d4c70dfd6",
"sha256:be495b8ec5a939a7605274b6e59fbc35e76f5ad814ae010eb679529671c9e119",
"sha256:dc2d3f3b1548f4d11786616cf0f4415e25b0fbecb8a1d2cd8c07568f13fdde38",
"sha256:e4aecdd9d5a3d06c337894c9a6e2961898d3f64fe54ca920a72234a3de0f9cb3",
"sha256:e79ab4485b99eacb2166f3212218dd858258f374855e1568f728462b0e6ee0d9",
"sha256:f995d3667301e1754c57b04e0bae6f0fa9d710697a9f8d6712e8cca02550910f"
],
"index": "pypi",
"version": "==2.3.1"
},
"cssselect": {
"hashes": [
"sha256:066d8bc5229af09617e24b3ca4d52f1f9092d9e061931f4184cd572885c23204",
"sha256:3b5103e8789da9e936a68d993b70df732d06b8bb9a337a05ed4eb52c17ef7206"
],
"version": "==1.0.3"
},
"cssutils": {
"hashes": [
"sha256:a2fcf06467553038e98fea9cfe36af2bf14063eb147a70958cfcaa8f5786acaf",
"sha256:c74dbe19c92f5052774eadb15136263548dd013250f1ed1027988e7fef125c8d"
],
"version": "==1.0.2"
},
"diff-match-patch": {
"hashes": [
"sha256:9dba5611fbf27893347349fd51cc1911cb403682a7163373adacc565d11e2e4c"
],
"index": "pypi",
"version": "==20121119"
},
"django": {
"hashes": [
"sha256:29268cc47816a44f27308e60f71da635f549c47d8a1d003b28de55141df75791",
"sha256:37f5876c1fbfd66085001f4c06fa0bf96ef05442c53daf8d4294b6f29e7fa6b8"
],
"index": "pypi",
"version": "==1.11.16"
},
"django-ipware": {
"hashes": [
"sha256:93a90f9dd8caf2c633172aa8c8ba4e76e2b44f92a6942fa35e7624281e81ea03"
],
"index": "pypi",
"version": "==1.1.6"
},
"django-jinja": {
"hashes": [
"sha256:5e826a0cce967f40e6fdc037ea23667a2d3cd072807c4c87ffcc010b3c59121f",
"sha256:ebfde44cb716e57a9cdff6c1a4935fc49c7419ea4cd0b2b89bcecc696b9c0c86"
],
"index": "pypi",
"version": "==2.3.1"
},
"django-pglocks": {
"hashes": [
"sha256:9405ee54bbf157bb16b814f20ea7ad9d82d5cf26f9bf3ea8e3a71032179844cf"
],
"index": "pypi",
"version": "==1.0.2"
},
"django-picklefield": {
"hashes": [
"sha256:5489fef164de43725242d56e65e016137d3df0d1a00672bda72d807f5b2b0d99",
"sha256:fab48a427c6310740755b242128f9300283bef159ffee42d3231a274c65d9ae2"
],
"index": "pypi",
"version": "==0.3.2"
},
"django-sampledatahelper": {
"hashes": [
"sha256:96d0a599054979eb9669d44a1735236da42d56be0c45d4bcd34c2a3acefb259d"
],
"index": "pypi",
"version": "==0.4.1"
},
"django-sites": {
"hashes": [
"sha256:fa1470bd72be589f3891b7cd28dd2d782d8b34539665d9334e49154566f3d916"
],
"index": "pypi",
"version": "==0.9"
},
"django-sr": {
"hashes": [
"sha256:3586b852ae8af1b4b2796590534b0b867b523f47a5779b2ccb6ce010efc57e34"
],
"index": "pypi",
"version": "==0.0.4"
},
"djmail": {
"hashes": [
"sha256:370f57230bf79004840ddd1cb8150a5b7676b2aa14bc5b62027106cb708a47a0"
],
"index": "pypi",
"version": "==1.0.1"
},
"docopt": {
"hashes": [
"sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"
],
"version": "==0.6.2"
},
"easy-thumbnails": {
"hashes": [
"sha256:5cc51c6ec7de110355d0f8cd56c9ede6e2949e87c2fcb34bc864a20ecd424270",
"sha256:6e41e70a182a6d00af9f3f3a6d1cc87cb7da060a3d56982da51d266e40fc9b59"
],
"index": "pypi",
"version": "==2.4.1"
},
"fn": {
"hashes": [
"sha256:f8cd80cdabf15367a2f07e7a9951fdc013d7200412743d85b88f2c896c95bada"
],
"index": "pypi",
"version": "==0.4.3"
},
"gunicorn": {
"hashes": [
"sha256:75af03c99389535f218cc596c7de74df4763803f7b63eb09d77e92b3956b36c6",
"sha256:eee1169f0ca667be05db3351a0960765620dad53f53434262ff8901b68a1b622"
],
"index": "pypi",
"version": "==19.7.1"
},
"html5lib": {
"hashes": [
"sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3",
"sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736"
],
"version": "==1.0.1"
},
"idna": {
"hashes": [
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
],
"version": "==2.7"
},
"jinja2": {
"hashes": [
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
],
"version": "==2.10"
},
"kombu": {
"hashes": [
"sha256:385bf38e6de7f3851f674671dbfe24572ce999608d293a85fb8a630654d8bd9c",
"sha256:d0fc6f2a36610a308f838db4b832dad79a681b516ac1d1a1f9d42edb58cc11a2"
],
"index": "pypi",
"version": "==4.0.2"
},
"lxml": {
"hashes": [
"sha256:02bc220d61f46e9b9d5a53c361ef95e9f5e1d27171cd461dddb17677ae2289a5",
"sha256:22f253b542a342755f6cfc047fe4d3a296515cf9b542bc6e261af45a80b8caf6",
"sha256:2f31145c7ff665b330919bfa44aacd3a0211a76ca7e7b441039d2a0b0451e415",
"sha256:36720698c29e7a9626a0dc802ef8885f8f0239bfd1689628ecd459a061f2807f",
"sha256:438a1b0203545521f6616132bfe0f4bca86f8a401364008b30e2b26ec408ce85",
"sha256:4815892904c336bbaf73dafd54f45f69f4021c22b5bad7332176bbf4fb830568",
"sha256:5be031b0f15ad63910d8e5038b489d95a79929513b3634ad4babf77100602588",
"sha256:5c93ae37c3c588e829b037fdfbd64a6e40c901d3f93f7beed6d724c44829a3ad",
"sha256:60842230678674cdac4a1cf0f707ef12d75b9a4fc4a565add4f710b5fcf185d5",
"sha256:62939a8bb6758d1bf923aa1c13f0bcfa9bf5b2fc0f5fa917a6e25db5fe0cfa4e",
"sha256:75830c06a62fe7b8fe3bbb5f269f0b308f19f3949ac81cfd40062f47c1455faf",
"sha256:81992565b74332c7c1aff6a913a3e906771aa81c9d0c68c68113cffcae45bc53",
"sha256:8c892fb0ee52c594d9a7751c7d7356056a9682674b92cc1c4dc968ff0f30c52f",
"sha256:9d862e3cf4fc1f2837dedce9c42269c8c76d027e49820a548ac89fdcee1e361f",
"sha256:a623965c086a6e91bb703d4da62dabe59fe88888e82c4117d544e11fd74835d6",
"sha256:a7783ab7f6a508b0510490cef9f857b763d796ba7476d9703f89722928d1e113",
"sha256:aab09fbe8abfa3b9ce62aaf45aca2d28726b1b9ee44871dbe644050a2fff4940",
"sha256:abf181934ac3ef193832fb973fd7f6149b5c531903c2ec0f1220941d73eee601",
"sha256:ae07fa0c115733fce1e9da96a3ac3fa24801742ca17e917e0c79d63a01eeb843",
"sha256:b9c78242219f674ab645ec571c9a95d70f381319a23911941cd2358a8e0521cf",
"sha256:bccb267678b870d9782c3b44d0cefe3ba0e329f9af8c946d32bf3778e7a4f271",
"sha256:c4df4d27f4c93b2cef74579f00b1d3a31a929c7d8023f870c4b476f03a274db4",
"sha256:caf0e50b546bb60dfa99bb18dfa6748458a83131ecdceaf5c071d74907e7e78a",
"sha256:d3266bd3ac59ac4edcd5fa75165dee80b94a3e5c91049df5f7c057ccf097551c",
"sha256:db0d213987bcd4e6d41710fb4532b22315b0d8fb439ff901782234456556aed1",
"sha256:dbbd5cf7690a40a9f0a9325ab480d0fccf46d16b378eefc08e195d84299bfae1",
"sha256:e16e07a0ec3a75b5ee61f2b1003c35696738f937dc8148fbda9fe2147ccb6e61",
"sha256:e175a006725c7faadbe69e791877d09936c0ef2cf49d01b60a6c1efcb0e8be6f",
"sha256:edd9c13a97f6550f9da2236126bb51c092b3b1ce6187f2bd966533ad794bbb5e",
"sha256:fa39ea60d527fbdd94215b5e5552f1c6a912624521093f1384a491a8ad89ad8b"
],
"version": "==4.2.5"
},
"markdown": {
"hashes": [
"sha256:c00429bd503a47ec88d5e30a751e147dcb4c6889663cd3e2ba0afe858e009baa",
"sha256:d02e0f9b04c500cde6637c11ad7c72671f359b87b9fe924b2383649d8841db7c"
],
"index": "pypi",
"version": "==3.0.1"
},
"markupsafe": {
"hashes": [
"sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432",
"sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b",
"sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9",
"sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af",
"sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834",
"sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd",
"sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d",
"sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7",
"sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b",
"sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3",
"sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c",
"sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2",
"sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7",
"sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36",
"sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1",
"sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e",
"sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1",
"sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c",
"sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856",
"sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550",
"sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492",
"sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672",
"sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401",
"sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6",
"sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6",
"sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c",
"sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd",
"sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1"
],
"version": "==1.1.0"
},
"netaddr": {
"hashes": [
"sha256:38aeec7cdd035081d3a4c306394b19d677623bf76fa0913f6695127c7753aefd",
"sha256:56b3558bd71f3f6999e4c52e349f38660e54a7a8a9943335f73dfc96883e08ca"
],
"index": "pypi",
"version": "==0.7.19"
},
"oauthlib": {
"hashes": [
"sha256:ac35665a61c1685c56336bda97d5eefa246f1202618a1d6f34fccb1bdd404162",
"sha256:d883b36b21a6ad813953803edfa563b1b579d79ca758fe950d1bc9e8b326025b"
],
"version": "==2.1.0"
},
"olefile": {
"hashes": [
"sha256:133b031eaf8fd2c9399b78b8bc5b8fcbe4c31e85295749bb17a87cba8f3c3964"
],
"version": "==0.46"
},
"pillow": {
"hashes": [
"sha256:00b6a5f28d00f720235a937ebc2f50f4292a5c7e2d6ab9a8b26153b625c4f431",
"sha256:025208f835383f425e93d574842f9c5d28918cd4cdf632c1ce2e72ab80d8fcc8",
"sha256:059a9b4e064b70e1396a3ae64781a91512f773cae548c24b12014616f723f22d",
"sha256:17f7702f22729ffeb69f5226abf3261ef2d2eb73ab5854c1294daa3bdb5bdfb7",
"sha256:20a3549d7a83e969eb900c726d54b34673efc1d0e3c9856b8227350f7e21d968",
"sha256:24258e1875c8a9de1b176bf1873436397669440d1561b06b00eb270bffefcb42",
"sha256:318b4404c8ca34cc1514d60de81ac4a0b0d11031a70341c2b7cd4fc01c914d89",
"sha256:33a71986741227c8c085ee5929171cd4c9376b2eee189cdc7acc7d81e1a3ca84",
"sha256:3499deb97561d6cc75de725fcf744491dd2d10d6213a29c4d62e3980e1522715",
"sha256:3ad24690882b68599b9e6b25309000881eebcc731ca499f8dcc549fab006e4a3",
"sha256:3c05df947656d8538dfb39fb8ddb9fe3594c9345911aa19f07e2ed0a8d148a6d",
"sha256:48cdae5e5291d355fc215ede2ea93738e243c2467b11e41fa5010a76fd278fc5",
"sha256:4b382c0ee6ac822673e1a57c2e9878d2ac4cd52038be097bac8535a2ee60ec0e",
"sha256:6458293cf299f02f17f58a1ee4b91f77b8ce7a38bc0e757838767f1389479953",
"sha256:8ef6627adfe9314b4132d4f5207563ba147e3977019ab1ca3f0b11b04c83c84f",
"sha256:9c508bf0b2aadad4349f69aebe080977dbf0cd055cefc15793c4165851a96933",
"sha256:9d7c0706cd86fc17643d78674cdac7f05590a2da1d71c42c2ebfb27df3889f17",
"sha256:a2a873b54881c4cf4d8b37f3c426c2b8f797b341e3893650763a62252bda3922",
"sha256:b79fc81352a3c907a1223499d790a4a7b77be342b794e19628046c4c95676356",
"sha256:c040a047209edaf860ce6dd5b55de718e047144b26b0ee4198dd19907c128eac",
"sha256:d71992826cfed66f5ad68364b2c6c3c1ab305294a642deae96ad77004981fb0f",
"sha256:e467d0977997b43ca80b7af42de3d1cfa779988f6507965e6d4fb1a004e963a0",
"sha256:ecf810b5019ce62846a9203bab82eb02bb9ed60e258b2fd89e2ca19a7010da46",
"sha256:f4fb801bfd2bcbfc4a7f2819c95ea6a1cfef197420ae9849b01b08b9970a51b3",
"sha256:f63404731fa5fa0c21d00af119b867e30208e3fc148c9b13fb6a541a8df203b2",
"sha256:f8e8f3f20e32f73f81ec408061f7a81ada07d7b3fac0787bdd233b93e4ff7d9c",
"sha256:fb3eaba16b6cf01f12860edccac40f98362bc17225575f3bcabb333d0b4ed6dc"
],
"index": "pypi",
"version": "==4.1.1"
},
"premailer": {
"hashes": [
"sha256:25c97c4c1838a8045ed1da1a14bd82ce458687878aa162378a78aa82a6aec6af",
"sha256:4e71cc09ad1438f827d1070ffac54ceb3a6a07c995fa82cb34c1ef163adeb432"
],
"index": "pypi",
"version": "==3.0.1"
},
"psd-tools": {
"hashes": [
"sha256:1a8dd69783c84217f3a25938916c289e5653543c5610e9013769520e8bd65b3c",
"sha256:7f3fd8577fb627405a204a65d4ccea48299f95338a71fbd9709bdb9d7789cc4e",
"sha256:f519a08049c19b3ef37ccc2b39af11ac5ded59d2674fc4c28103e4db57a9aa99"
],
"index": "pypi",
"version": "==1.4"
},
"psycopg2-binary": {
"hashes": [
"sha256:04afb59bbbd2eab3148e6816beddc74348078b8c02a1113ea7f7822f5be4afe3",
"sha256:098b18f4d8857a8f9b206d1dc54db56c2255d5d26458917e7bcad61ebfe4338f",
"sha256:0bf855d4a7083e20ead961fda4923887094eaeace0ab2d76eb4aa300f4bbf5bd",
"sha256:197dda3ffd02057820be83fe4d84529ea70bf39a9a4daee1d20ffc74eb3d042e",
"sha256:278ef63afb4b3d842b4609f2c05ffbfb76795cf6a184deeb8707cd5ed3c981a5",
"sha256:3cbf8c4fc8f22f0817220891cf405831559f4d4c12c4f73913730a2ea6c47a47",
"sha256:4305aed922c4d9d6163ab3a41d80b5a1cfab54917467da8168552c42cad84d32",
"sha256:47ee296f704fb8b2a616dec691cdcfd5fa0f11943955e88faa98cbd1dc3b3e3d",
"sha256:4a0e38cb30457e70580903367161173d4a7d1381eb2f2cfe4e69b7806623f484",
"sha256:4d6c294c6638a71cafb82a37f182f24321f1163b08b5d5ca076e11fe838a3086",
"sha256:4f3233c366500730f839f92833194fd8f9a5c4529c8cd8040aa162c3740de8e5",
"sha256:5221f5a3f4ca2ddf0d58e8b8a32ca50948be9a43351fda797eb4e72d7a7aa34d",
"sha256:5c6ca0b507540a11eaf9e77dee4f07c131c2ec80ca0cffa146671bf690bc1c02",
"sha256:789bd89d71d704db2b3d5e67d6d518b158985d791d3b2dec5ab85457cfc9677b",
"sha256:7b94d29239efeaa6a967f3b5971bd0518d2a24edd1511edbf4a2c8b815220d07",
"sha256:89bc65ef3301c74cf32db25334421ea6adbe8f65601ea45dcaaf095abed910bb",
"sha256:89d6d3a549f405c20c9ae4dc94d7ed2de2fa77427a470674490a622070732e62",
"sha256:97521704ac7127d7d8ba22877da3c7bf4a40366587d238ec679ff38e33177498",
"sha256:a395b62d5f44ff6f633231abe568e2203b8fabf9797cd6386aa92497df912d9a",
"sha256:a6d32c37f714c3f34158f3fa659f3a8f2658d5f53c4297d45579b9677cc4d852",
"sha256:a89ee5c26f72f2d0d74b991ce49e42ddeb4ac0dc2d8c06a0f2770a1ab48f4fe0",
"sha256:b4c8b0ef3608e59317bfc501df84a61e48b5445d45f24d0391a24802de5f2d84",
"sha256:b5fcf07140219a1f71e18486b8dc28e2e1b76a441c19374805c617aa6d9a9d55",
"sha256:b86f527f00956ecebad6ab3bb30e3a75fedf1160a8716978dd8ce7adddedd86f",
"sha256:be4c4aa22ba22f70de36c98b06480e2f1697972d49eb20d525f400d204a6d272",
"sha256:c2ac7aa1a144d4e0e613ac7286dae85671e99fe7a1353954d4905629c36b811c",
"sha256:de26ef4787b5e778e8223913a3e50368b44e7480f83c76df1f51d23bd21cea16",
"sha256:e70ebcfc5372dc7b699c0110454fc4263967f30c55454397e5769eb72c0eb0ce",
"sha256:eadbd32b6bc48b67b0457fccc94c86f7ccc8178ab839f684eb285bb592dc143e",
"sha256:ecbc6dfff6db06b8b72ae8a2f25ff20fbdcb83cb543811a08f7cb555042aa729"
],
"index": "pypi",
"version": "==2.7.5"
},
"pycparser": {
"hashes": [
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
],
"version": "==2.19"
},
"pygments": {
"hashes": [
"sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d",
"sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"
],
"index": "pypi",
"version": "==2.2.0"
},
"pyjwt": {
"hashes": [
"sha256:00414bfef802aaecd8cc0d5258b6cb87bd8f553c2986c2c5f29b19dd5633aeb7",
"sha256:ddec8409c57e9d371c6006e388f91daf3b0b43bdf9fcbf99451fb7cf5ce0a86d"
],
"version": "==1.7.0"
},
"python-dateutil": {
"hashes": [
"sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93",
"sha256:88f9287c0174266bb0d8cedd395cfba9c58e87e5ad86b2ce58859bc11be3cf02"
],
"index": "pypi",
"version": "==2.7.5"
},
"python-magic": {
"hashes": [
"sha256:604eace6f665809bebbb07070508dfa8cabb2d7cb05be9a56706c60f864f1289",
"sha256:a1ec69e76cc513b1af164c02982607f96ff3bb668162a688f2b1bb5f6a5fe05d"
],
"index": "pypi",
"version": "==0.4.13"
},
"pytz": {
"hashes": [
"sha256:31cb35c89bd7d333cd32c5f278fca91b523b0834369e757f4c5641ea252236ca",
"sha256:8e0f8568c118d3077b46be7d654cc8167fa916092e28320cde048e54bfc9f1e6"
],
"index": "pypi",
"version": "==2018.7"
},
"raven": {
"hashes": [
"sha256:02cabffb173b99d860a95d4908e8b1864aad1b8452146e13fd7e212aa576a884",
"sha256:56dc9062dd42bca97350e5048ff417c914376366caa3b1b5f788b27ddc0a34b7"
],
"index": "pypi",
"version": "==6.1.0"
},
"redis": {
"hashes": [
"sha256:5dfbae6acfc54edf0a7a415b99e0b21c0a3c27a7f787b292eea727b1facc5533",
"sha256:97156b37d7cda4e7d8658be1148c983984e1a975090ba458cc7e244025191dbd"
],
"index": "pypi",
"version": "==2.10.5"
},
"requests": {
"hashes": [
"sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c",
"sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279"
],
"index": "pypi",
"version": "==2.20.0"
},
"requests-oauthlib": {
"hashes": [
"sha256:50a8ae2ce8273e384895972b56193c7409601a66d4975774c60c2aed869639ca",
"sha256:883ac416757eada6d3d07054ec7092ac21c7f35cb1d2cf82faf205637081f468"
],
"version": "==0.8.0"
},
"sampledata": {
"hashes": [
"sha256:b06916ef010d3c1a9db8aa314a144f24ad421f28597ff8c568603c451391a2cf"
],
"version": "==0.3.7"
},
"serpy": {
"hashes": [
"sha256:b1481f8cb93d767b23903d1df6cc0a7120cb0694095b6695eb78d9d453b23c65",
"sha256:b774bfdf0c3b245660639e9fc5f311a8bbceb2725aaba72fce1fec00b453286e"
],
"index": "pypi",
"version": "==0.1.1"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
},
"tinycss": {
"hashes": [
"sha256:12306fb50e5e9e7eaeef84b802ed877488ba80e35c672867f548c0924a76716e"
],
"version": "==0.4"
},
"unidecode": {
"hashes": [
"sha256:ed4418b4b1b190487753f1cca6299e8076079258647284414e6d607d1f8a00e0",
"sha256:eedac7bfd886f43484787206f6a141b232e2b2a58652c54d06499b187fd84660"
],
"index": "pypi",
"version": "==0.4.20"
},
"urllib3": {
"hashes": [
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
],
"version": "==1.24.1"
},
"vine": {
"hashes": [
"sha256:52116d59bc45392af9fdd3b75ed98ae48a93e822cee21e5fda249105c59a7a72",
"sha256:6849544be74ec3638e84d90bc1cf2e1e9224cc10d96cd4383ec3f69e9bce077b"
],
"version": "==1.1.4"
},
"webcolors": {
"hashes": [
"sha256:e47e68644d41c0b1f1e4d939cfe4039bdf1ab31234df63c7a4f59d4766487206"
],
"index": "pypi",
"version": "==1.7"
},
"webencodings": {
"hashes": [
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
],
"version": "==0.5.1"
}
},
"develop": {
"atomicwrites": {
"hashes": [
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
],
"version": "==1.2.1"
},
"attrs": {
"hashes": [
"sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
"sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
],
"version": "==18.2.0"
},
"certifi": {
"hashes": [
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
],
"version": "==2018.11.29"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"coverage": {
"hashes": [
"sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f",
"sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe",
"sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d",
"sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0",
"sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607",
"sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d",
"sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b",
"sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3",
"sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e",
"sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815",
"sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36",
"sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1",
"sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14",
"sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c",
"sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794",
"sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b",
"sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840",
"sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd",
"sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82",
"sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952",
"sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389",
"sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f",
"sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4",
"sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da",
"sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647",
"sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d",
"sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42",
"sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478",
"sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b",
"sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb",
"sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9"
],
"index": "pypi",
"version": "==4.5.2"
},
"coveralls": {
"hashes": [
"sha256:ab638e88d38916a6cedbf80a9cd8992d5fa55c77ab755e262e00b36792b7cd6d",
"sha256:b2388747e2529fa4c669fb1e3e2756e4e07b6ee56c7d9fce05f35ccccc913aa0"
],
"index": "pypi",
"version": "==1.5.1"
},
"docopt": {
"hashes": [
"sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"
],
"version": "==0.6.2"
},
"factory-boy": {
"hashes": [
"sha256:6f25cc4761ac109efd503f096e2ad99421b1159f01a29dbb917359dcd68e08ca",
"sha256:d552cb872b310ae78bd7429bf318e42e1e903b1a109e899a523293dfa762ea4f"
],
"index": "pypi",
"version": "==2.11.1"
},
"faker": {
"hashes": [
"sha256:c61a41d0dab8865b850bd00454fb11e90f3fd2a092d8bc90120d1e1c01cff906",
"sha256:f909ff9133ce0625ca388b6838190630ad7a593f87eaf058d872338a76241d5d"
],
"version": "==1.0.0"
},
"idna": {
"hashes": [
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
],
"version": "==2.7"
},
"more-itertools": {
"hashes": [
"sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
"sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
"sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
],
"version": "==4.3.0"
},
"pluggy": {
"hashes": [
"sha256:447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095",
"sha256:bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f"
],
"version": "==0.8.0"
},
"py": {
"hashes": [
"sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694",
"sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6"
],
"version": "==1.7.0"
},
"pytest": {
"hashes": [
"sha256:3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec",
"sha256:e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660"
],
"index": "pypi",
"version": "==3.10.1"
},
"pytest-django": {
"hashes": [
"sha256:49e9ffc856bc6a1bec1c26c5c7b7213dff7cc8bc6b64d624c4d143d04aff0bcf",
"sha256:b379282feaf89069cb790775ab6bbbd2bd2038a68c7ef9b84a41898e0b551081"
],
"index": "pypi",
"version": "==3.4.3"
},
"python-dateutil": {
"hashes": [
"sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93",
"sha256:88f9287c0174266bb0d8cedd395cfba9c58e87e5ad86b2ce58859bc11be3cf02"
],
"index": "pypi",
"version": "==2.7.5"
},
"requests": {
"hashes": [
"sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c",
"sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279"
],
"index": "pypi",
"version": "==2.20.0"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
},
"text-unidecode": {
"hashes": [
"sha256:5a1375bb2ba7968740508ae38d92e1f889a0832913cb1c447d5e2046061a396d",
"sha256:801e38bd550b943563660a91de8d4b6fa5df60a542be9093f7abf819f86050cc"
],
"version": "==1.2"
},
"urllib3": {
"hashes": [
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
],
"version": "==1.24.1"
}
}
}

View File

@ -96,7 +96,7 @@ python manage.py loaddata initial_project_templates
python manage.py sample_data
```
**IMPORTANT: Taiga only runs with python 3.5+**
**IMPORTANT: Taiga only runs with python 3.4+**
Initial auth data: admin/123123

View File

@ -1,6 +1,3 @@
[pytest]
DJANGO_SETTINGS_MODULE = settings.testing
filterwarnings =
once
ignore::django.utils.deprecation.RemovedInDjango20Warning
ignore::DeprecationWarning:taiga.base.api.serializers
python_paths = .

11
requirements-devel.txt Normal file
View File

@ -0,0 +1,11 @@
-r requirements.txt
coverage==4.4.1
coveralls==1.1
django-slowdown==0.0.1
factory_boy==2.8.1
py==1.4.34
pytest-django==3.1.2
pytest-pythonpath==0.7.1
pytest==3.1.1
transifex-client==0.12.4

View File

@ -1,63 +1,45 @@
-i https://pypi.org/simple
amqp==2.3.2
#djangorestframework==2.3.13 # It's not necessary since Taiga 1.7
CairoSVG==2.0.3
Django==1.11.2
git+git://github.com/Python-Markdown/markdown.git@a4d4b61b5ce4a7dd96ddc13c66b8716d3ad8eb51 # This version solves 'infinite execution' and other issues.
Pillow==4.1.1
PyJWT==1.5.0
Unidecode==0.4.20
amqp==2.1.4
asana==0.6.7
asn1crypto==0.24.0
billiard==3.5.0.5
bleach==2.1.4
cairocffi==0.9.0
cairosvg==2.0.3
bleach==2.0.0
celery==4.0.2
certifi==2018.11.29
cffi==1.11.5
chardet==3.0.4
contextlib2==0.5.5
cryptography==2.3.1
cssselect==1.0.3
kombu==4.0.2
cryptography==1.9
cssutils==1.0.2
diff-match-patch==20121119
django-ipware==1.1.6
django-jinja==2.3.1
django-pglocks==1.0.2
django-picklefield==0.3.2
django-sampledatahelper==0.4.1
django-sites==0.9
django-sr==0.0.4
django==1.11.16
djmail==1.0.1
docopt==0.6.2
easy-thumbnails==2.4.1
fn==0.4.3
git+https://github.com/Xof/django-pglocks.git
gunicorn==19.7.1
html5lib==1.0.1
idna==2.7
jinja2==2.10
kombu==4.0.2
lxml==4.2.5
markdown==3.0.1
markupsafe==1.1.0
jinja2==2.9.6
idna==2.5
lxml==3.8.0
netaddr==0.7.19
oauthlib==2.1.0
olefile==0.46
pillow==4.1.1
premailer==3.0.1
psd-tools==1.4
psycopg2-binary==2.7.5
pycparser==2.19
psycopg2==2.7.4
pygments==2.2.0
pyjwt==1.7.0
python-dateutil==2.7.5
pyjwkest==1.3.2
python-dateutil==2.6.0
python-magic==0.4.13
pytz==2018.7
pytz==2017.2
raven==6.1.0
redis==2.10.5
requests-oauthlib==0.8.0
requests==2.20.0
sampledata==0.3.7
requests==2.17.3
serpy==0.1.1
six==1.11.0
tinycss==0.4
unidecode==0.4.20
urllib3==1.24.1
vine==1.1.4
six==1.10.0
webcolors==1.7
webencodings==0.5.1

View File

@ -242,7 +242,11 @@ You need transifex-client, install it.
1. Install transifex-client, use
$ pip install --upgrade transifex-client
$ pip install --upgrade -r requirements-devel.txt
or
$ pip install --upgrade transifex-client==0.12.2
2. Create ~/.transifexrc file:

View File

@ -0,0 +1,4 @@
from .celery import *
# To use celery in memory
#task_always_eager = True

View File

@ -148,7 +148,7 @@ LANGUAGES = [
("tr", "Türkçe"), # Turkish
#("tt", "татар теле"), # Tatar
#("udm", "удмурт кыл"), # Udmurt
("uk", "Українська"), # Ukrainian
#("uk", "Українська"), # Ukrainian
#("ur", "اردو‏"), # Urdu
#("vi", "Tiếng Việt"), # Vietnamese
("zh-hans", "中文(简体)"), # Simplified Chinese
@ -309,7 +309,6 @@ INSTALLED_APPS = [
"taiga.projects.issues",
"taiga.projects.wiki",
"taiga.projects.contact",
"taiga.projects.settings",
"taiga.searches",
"taiga.timeline",
"taiga.mdrender",
@ -548,7 +547,6 @@ EXPORTS_TTL = 60 * 60 * 24 # 24 hours
CELERY_ENABLED = False
WEBHOOKS_ENABLED = False
WEBHOOKS_BLOCK_PRIVATE_ADDRESS = False
# If is True /front/sitemap.xml show a valid sitemap of taiga-front client

View File

@ -16,13 +16,12 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.api.permissions import (TaigaResourcePermission, IsAuthenticated)
from .testing import *
class UserProjectSettingsPermission(TaigaResourcePermission):
retrieve_perms = IsAuthenticated()
create_perms = IsAuthenticated()
update_perms = IsAuthenticated()
partial_update_perms = IsAuthenticated()
destroy_perms = IsAuthenticated()
list_perms = IsAuthenticated()
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'taiga',
'USERNAME': 'postgres',
}
}

View File

@ -2,10 +2,7 @@
ignore = E41,E266
max-line-length = 120
exclude =
.cache,
.git,
.tox,
.venv,
*__pycache__*,
*tests*,
*scripts*,

View File

@ -375,18 +375,14 @@ class IsProjectAdminFromWebhookLogFilterBackend(FilterBackend, BaseIsProjectAdmi
class BaseRelatedFieldsFilter(FilterBackend):
filter_name = None
param_name = None
exclude_param_name = None
def __init__(self, filter_name=None, param_name=None, exclude_param_name=None):
def __init__(self, filter_name=None, param_name=None):
if filter_name:
self.filter_name = filter_name
if param_name:
self.param_name = param_name
if exclude_param_name:
self.exclude_param_name
def _prepare_filter_data(self, query_param_value):
def _transform_value(value):
try:
@ -400,57 +396,48 @@ class BaseRelatedFieldsFilter(FilterBackend):
values = map(_transform_value, values)
return list(values)
def _get_queryparams(self, params, mode=''):
param_name = self.exclude_param_name if mode == 'exclude' else self.param_name or self.filter_name
def _get_queryparams(self, params):
param_name = self.param_name or self.filter_name
raw_value = params.get(param_name, None)
if raw_value:
value = self._prepare_filter_data(raw_value)
if None in value:
qs_in_kwargs = {"{}__in".format(self.filter_name): [v for v in value if v is not None]}
qs_isnull_kwargs = {"{}__isnull".format(self.filter_name): True}
return Q(**qs_in_kwargs) | Q(**qs_isnull_kwargs)
else:
return Q(**{"{}__in".format(self.filter_name): value})
return {"{}__in".format(self.filter_name): value}
return None
def _prepare_filter_query(self, query):
return query
def _prepare_exclude_query(self, query):
return ~Q(query)
def filter_queryset(self, request, queryset, view):
operations = {
"filter": self._prepare_filter_query,
"exclude": self._prepare_exclude_query,
}
for mode, prepare_method in operations.items():
query = self._get_queryparams(request.QUERY_PARAMS, mode=mode)
if query:
queryset = queryset.filter(prepare_method(query))
query = self._get_queryparams(request.QUERY_PARAMS)
if query:
if isinstance(query, dict):
queryset = queryset.filter(**query)
else:
queryset = queryset.filter(query)
return super().filter_queryset(request, queryset, view)
class OwnersFilter(BaseRelatedFieldsFilter):
filter_name = 'owner'
exclude_param_name = 'exclude_owner'
class AssignedToFilter(BaseRelatedFieldsFilter):
filter_name = 'assigned_to'
exclude_param_name = 'exclude_assigned_to'
class AssignedUsersFilter(FilterModelAssignedUsers, BaseRelatedFieldsFilter):
filter_name = 'assigned_users'
exclude_param_name = 'exclude_assigned_users'
def _get_queryparams(self, params, mode=''):
param_name = self.exclude_param_name if mode == 'exclude' else self.param_name or self.filter_name
def _get_queryparams(self, params):
param_name = self.param_name or self.filter_name
raw_value = params.get(param_name, None)
if raw_value:
value = self._prepare_filter_data(raw_value)
UserStoryModel = apps.get_model("userstories", "UserStory")
@ -474,65 +461,38 @@ class AssignedUsersFilter(FilterModelAssignedUsers, BaseRelatedFieldsFilter):
class StatusesFilter(BaseRelatedFieldsFilter):
filter_name = 'status'
exclude_param_name = 'exclude_status'
class IssueTypesFilter(BaseRelatedFieldsFilter):
filter_name = 'type'
param_name = 'type'
exclude_param_name = 'exclude_type'
class PrioritiesFilter(BaseRelatedFieldsFilter):
filter_name = 'priority'
exclude_param_name = 'exclude_priority'
class SeveritiesFilter(BaseRelatedFieldsFilter):
filter_name = 'severity'
exclude_param_name = 'exclude_severity'
class TagsFilter(FilterBackend):
filter_name = 'tags'
exclude_param_name = 'exclude_tags'
def __init__(self, filter_name=None, exclude_param_name=None):
def __init__(self, filter_name=None):
if filter_name:
self.filter_name = filter_name
if exclude_param_name:
self.exclude_param_name = exclude_param_name
def _get_tags_queryparams(self, params, mode=''):
param_name = self.exclude_param_name if mode == "exclude" else self.filter_name
tags = params.get(param_name, None)
def _get_tags_queryparams(self, params):
tags = params.get(self.filter_name, None)
if tags:
return tags.split(",")
return None
def _prepare_filter_query(self, query):
return Q(tags__contains=query)
def _prepare_exclude_query(self, tags):
queries = [Q(tags__contains=[tag]) for tag in tags]
query = queries.pop()
for item in queries:
query |= item
return ~Q(query)
def filter_queryset(self, request, queryset, view):
operations = {
"filter": self._prepare_filter_query,
"exclude": self._prepare_exclude_query,
}
for mode, prepare_method in operations.items():
query = self._get_tags_queryparams(request.QUERY_PARAMS, mode=mode)
if query:
queryset = queryset.filter(prepare_method(query))
query_tags = self._get_tags_queryparams(request.QUERY_PARAMS)
if query_tags:
queryset = queryset.filter(tags__contains=query_tags)
return super().filter_queryset(request, queryset, view)
@ -671,22 +631,18 @@ class QFilter(FilterBackend):
class RoleFilter(BaseRelatedFieldsFilter):
filter_name = "role_id"
param_name = "role"
exclude_param_name = "exclude_role"
def filter_queryset(self, request, queryset, view):
Membership = apps.get_model('projects', 'Membership')
operations = {
"filter": self._prepare_filter_query,
"exclude": self._prepare_exclude_query,
}
for mode, qs_method in operations.items():
query = self._get_queryparams(request.QUERY_PARAMS, mode=mode)
if query:
memberships = Membership.objects.filter(query).exclude(user__isnull=True).values_list("user_id", flat=True)
if memberships:
queryset = queryset.filter(qs_method(Q(assigned_to__in=memberships)))
query = self._get_queryparams(request.QUERY_PARAMS)
if query:
if isinstance(query, dict):
memberships = Membership.objects.filter(**query).values_list("user_id", flat=True)
queryset = queryset.filter(assigned_to__in=memberships)
else:
memberships = Membership.objects.filter(query).values_list("user_id", flat=True)
if memberships:
queryset = queryset.filter(assigned_to__in=memberships)
return FilterBackend.filter_queryset(self, request, queryset, view)
@ -694,24 +650,20 @@ class RoleFilter(BaseRelatedFieldsFilter):
class UserStoriesRoleFilter(FilterModelAssignedUsers, BaseRelatedFieldsFilter):
filter_name = "role_id"
param_name = "role"
exclude_param_name = 'exclude_role'
def filter_queryset(self, request, queryset, view):
Membership = apps.get_model('projects', 'Membership')
query = self._get_queryparams(request.QUERY_PARAMS)
operations = {
"filter": self._prepare_filter_query,
"exclude": self._prepare_exclude_query,
}
for mode, qs_method in operations.items():
query = self._get_queryparams(request.QUERY_PARAMS, mode=mode)
if query:
memberships = Membership.objects.filter(query).exclude(user__isnull=True).values_list("user_id", flat=True)
if memberships:
user_story_model = apps.get_model("userstories", "UserStory")
queryset = queryset.filter(
qs_method(Q(self.get_assigned_users_filter(user_story_model, memberships)))
)
if query:
if isinstance(query, dict):
memberships = Membership.objects.filter(**query).values_list("user_id", flat=True)
else:
memberships = Membership.objects.filter(query).values_list("user_id", flat=True)
if memberships:
user_story_model = apps.get_model("userstories", "UserStory")
queryset = queryset.filter(
self.get_assigned_users_filter(user_story_model, memberships)
)
return FilterBackend.filter_queryset(self, request, queryset, view)

View File

@ -68,7 +68,7 @@ def psd_image_factory(data, *args):
Image.register_open("PSD", psd_image_factory)
def get_thumbnail(file_obj, thumbnailer_size):
def get_thumbnail_url(file_obj, thumbnailer_size):
# Ugly hack to temporary ignore tiff files
relative_name = file_obj
if isinstance(file_obj, FieldFile):
@ -79,18 +79,9 @@ def get_thumbnail(file_obj, thumbnailer_size):
return None
try:
thumbnailer = get_thumbnailer(file_obj)
return thumbnailer[thumbnailer_size]
path_url = get_thumbnailer(file_obj)[thumbnailer_size].url
thumb_url = get_absolute_url(path_url)
except InvalidImageFormatError:
return None
thumb_url = None
def get_thumbnail_url(file_obj, thumbnailer_size):
thumbnail = get_thumbnail(file_obj, thumbnailer_size)
if not thumbnail:
return None
path_url = thumbnail.url
thumb_url = get_absolute_url(path_url)
return thumb_url

View File

@ -17,13 +17,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import ipaddress
import socket
from urllib.parse import urlparse
import django_sites as sites
from django.core.urlresolvers import reverse as django_reverse
from django.utils.translation import ugettext as _
URL_TEMPLATE = "{scheme}://{domain}/{path}"
@ -48,29 +43,3 @@ def get_absolute_url(path):
def reverse(viewname, *args, **kwargs):
"""Same behavior as django's reverse but uses django_sites to compute absolute url."""
return get_absolute_url(django_reverse(viewname, *args, **kwargs))
class HostnameException(Exception):
pass
class IpAddresValueError(ValueError):
pass
def validate_private_url(url):
host = urlparse(url).hostname
port = urlparse(url).port
try:
socket_args, *others = socket.getaddrinfo(host, port)
except Exception:
raise HostnameException(_("Host access error"))
destination_address = socket_args[4][0]
try:
ipa = ipaddress.ip_address(destination_address)
except ValueError:
raise IpAddresValueError(_("IP Address error"))
if ipa.is_private:
raise IpAddresValueError("Private IP Address not allowed")

View File

@ -91,22 +91,6 @@ def emit_event_for_model(obj, *, type:str="change", channel:str="events",
sessionid=sessionid,
data=data)
def emit_event_for_user_notification(user_id,
*,
session_id: str=None,
event_type: str=None,
data: dict=None):
"""
Sends a user notification event.
"""
return emit_event(
data,
"web_notifications.{}".format(user_id),
sessionid=session_id
)
def emit_live_notification_for_model(obj, user, history, *, type:str="change", channel:str="events",
sessionid:str="not-existing"):
"""

View File

@ -19,8 +19,6 @@
from taiga.users.models import User
from taiga.projects.models import Membership
from taiga.permissions.choices import ANON_PERMISSIONS
def resolve_users_bindings(users_bindings):
new_users_bindings = {}
@ -52,15 +50,3 @@ def create_memberships(users_bindings, project, creator, role_name):
is_admin=False,
invited_by=creator,
)
def set_base_permissions_for_project(project):
if project.is_private:
return
anon_permissions = list(
map(lambda perm: perm[0], ANON_PERMISSIONS))
project.anon_permissions = list(
set((project.anon_permissions or []) + anon_permissions))
project.public_permissions = list(
set((project.public_permissions or []) + anon_permissions))
project.save()

View File

@ -262,7 +262,6 @@ class TrelloImporter:
project=project
)
import_service.create_memberships(options.get('users_bindings', {}), project, self._user, "trello")
import_service.set_base_permissions_for_project(project)
return project
def _import_user_stories_data(self, data, project, options):

View File

@ -10,8 +10,8 @@ msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: Catalan (http://www.transifex.com/taiga-agile-llc/taiga-back/"
"language/ca/)\n"
@ -196,8 +196,8 @@ msgstr ""
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr ""
@ -480,14 +480,6 @@ msgstr ""
" Comentari: %(comment)s\n"
" "
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr ""
@ -991,7 +983,7 @@ msgid "The payload is not a valid json"
msgstr "El payload no és un arxiu json vàlid"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "El projecte no existeix"
@ -3118,15 +3110,15 @@ msgstr ""
msgid "The tag doesn't exist."
msgstr ""
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr ""
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr ""
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr ""
@ -4284,7 +4276,3 @@ msgstr ""
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr ""
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

View File

@ -26,9 +26,9 @@ msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-11-14 12:59+0000\n"
"Last-Translator: Hans Raaf\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-09-04 08:41+0000\n"
"Last-Translator: Jonas Zürcher <info@jonaszuercher.ch>\n"
"Language-Team: German (http://www.transifex.com/taiga-agile-llc/taiga-back/"
"language/de/)\n"
"MIME-Version: 1.0\n"
@ -234,8 +234,8 @@ msgstr ""
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "Blockiertes Element"
@ -547,17 +547,9 @@ msgstr ""
"Kommentar: %(comment)s\n"
" "
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr "User-Story erstellt"
msgstr "User story erstellt"
#: taiga/events/events.py:109
msgid "User story changed"
@ -577,15 +569,15 @@ msgstr "Aufgabe erzeugt"
#: taiga/events/events.py:120
msgid "Task changed"
msgstr "Aufgabe geändert"
msgstr "Task geändert"
#: taiga/events/events.py:123
msgid "Task deleted"
msgstr "Aufgabe gelöscht"
msgstr "Task gelöscht"
#: taiga/events/events.py:125
msgid "Task #{} - {}"
msgstr "Aufgabe #{} - {}"
msgstr "Task #{} - {}"
#: taiga/events/events.py:128
msgid "Issue created"
@ -1166,7 +1158,7 @@ msgid "The payload is not a valid json"
msgstr "Die Nutzlast ist kein gültiges json"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "Das Projekt existiert nicht"
@ -2841,8 +2833,8 @@ msgid ""
msgstr ""
"\n"
"<h1>Neues Ticket wurde erstellt</h1>\n"
"<p>Hallo %(user)s,<br />%(changer)s hat ein neues Ticket in "
"%(project)s erstellt</p>\n"
"<p>Hallo %(user)s,<br />%(changer)s hat ein neues Ticket erstellt in "
"%(project)s</p>\n"
"<p>Ticket #%(ref)s %(subject)s</p>\n"
"<a class=\"button\" href=\"%(url)s\" title=\"See Issue #%(ref)s %(subject)s"
"\">Ticket ansehen</a>\n"
@ -2862,7 +2854,7 @@ msgid ""
msgstr ""
"\n"
"Neues Ticket wurde erstellt\n"
"Hallo %(user)s, %(changer)s hat ein neues Ticket in %(project)s erstellt\n"
"Hallo %(user)s, %(changer)s hat ein neues Ticket erstellt in %(project)s\n"
"Ticket ansehen #%(ref)s %(subject)s auf %(url)s\n"
"\n"
"---\n"
@ -2988,8 +2980,8 @@ msgid ""
msgstr ""
"\n"
"<h1>Neuer Sprint wurde erstellt</h1>\n"
"<p>Hallo %(user)s,<br />%(changer)s hat einen neuen Sprint in "
"%(project)s erstellt</p>\n"
"<p>Hallo %(user)s,<br />%(changer)s hat einen neuen Sprint erstellt in "
"%(project)s</p>\n"
"<p>Sprint %(name)s</p>\n"
"<a class=\"button\" href=\"%(url)s\" title=\"See Sprint %(name)s\">See "
"sprint</a>\n"
@ -3009,7 +3001,7 @@ msgid ""
msgstr ""
"\n"
"Neuer Sprint wurde erstellt\n"
"Hallo %(user)s, %(changer)s hat einen neuen Sprint in %(project)s erstellt\n"
"Hallo %(user)s, %(changer)s hat einen neuen Sprint erstellt in %(project)s\n"
"Sprint ansehen %(name)s at %(url)s\n"
"\n"
"---\n"
@ -3058,7 +3050,7 @@ msgid ""
msgstr ""
"\n"
"Sprint wurde gelöscht\n"
"Hallo %(user)s, %(changer)s hat einen Sprint in %(project)s gelöscht \n"
"Hallo %(user)s, %(changer)s hat einen Sprint gelöscht in %(project)s\n"
"Sprint %(name)s\n"
"\n"
"---\n"
@ -3135,8 +3127,8 @@ msgid ""
msgstr ""
"\n"
"<h1>Neue Aufgabe wurde erstellt</h1>\n"
"<p>Hallo %(user)s,<br />%(changer)s hat eine neue Aufgabe in "
"%(project)s erstellt</p>\n"
"<p>Hallo %(user)s,<br />%(changer)s hat eine neue Aufgabe erstellt in "
"%(project)s</p>\n"
"<p>Aufgabe #%(ref)s %(subject)s</p>\n"
"<a class=\"button\" href=\"%(url)s\" title=\"See Task #%(ref)s %(subject)s"
"\">Aufgabe ansehen</a>\n"
@ -3156,7 +3148,7 @@ msgid ""
msgstr ""
"\n"
"Neue Aufgabe wurde erstellt\n"
"Hallo %(user)s, %(changer)s hat eine neue Aufgabe in %(project)s erstellt\n"
"Hallo %(user)s, %(changer)s hat eine neue Aufgabe erstellt in %(project)s\n"
"Aufgabe ansehen #%(ref)s %(subject)s auf %(url)s\n"
"\n"
"\n"
@ -3187,8 +3179,8 @@ msgid ""
msgstr ""
"\n"
"<h1>Aufgabe wurde gelöscht</h1>\n"
"<p>Hallo %(user)s,<br />%(changer)s hat eine Aufgabe in %(project)s "
"gelöscht</p>\n"
"<p>Hallo %(user)s,<br />%(changer)s hat eine Aufgabe gelöscht in "
"%(project)s</p>\n"
"<p>Aufgabe #%(ref)s %(subject)s</p>\n"
"<p><small>Das Taiga Team</small></p>\n"
" "
@ -3206,7 +3198,7 @@ msgid ""
msgstr ""
"\n"
"Aufgabe wurde gelöscht\n"
"Hallo %(user)s, %(changer)s hat eine Aufgabe in %(project)s gelöscht\n"
"Hallo %(user)s, %(changer)s hat eine Aufgabe gelöscht in %(project)s\n"
"Aufgabe #%(ref)s %(subject)s\n"
"\n"
"---\n"
@ -3283,8 +3275,8 @@ msgid ""
msgstr ""
"\n"
"<h1>Neue User-Story wurde erstellt</h1>\n"
"<p>Hallo %(user)s,<br />%(changer)s hat eine neue User-Story in "
"%(project)s erstellt</p>\n"
"<p>Hallo %(user)s,<br />%(changer)s hat eine neue User-Story erstellt in "
"%(project)s</p>\n"
"<p>User-Story #%(ref)s %(subject)s</p>\n"
"<a class=\"button\" href=\"%(url)s\" title=\"See User Story #%(ref)s "
"%(subject)s\">User-Story ansehen</a>\n"
@ -3305,8 +3297,8 @@ msgid ""
msgstr ""
"\n"
"Neue User-Story wurde erstellt\n"
"Hallo %(user)s, %(changer)s hat eine neue User-Story in %(project)s "
"erstellt\n"
"Hallo %(user)s, %(changer)s hat eine neue User-Story erstellt in "
"%(project)s\n"
"User-Story ansehen #%(ref)s %(subject)s auf %(url)s\n"
"\n"
"---\n"
@ -3336,8 +3328,8 @@ msgid ""
msgstr ""
"\n"
"<h1>User-Story wurde gelöscht</h1>\n"
"<p>Hallo %(user)s,<br />%(changer)s hat eine User-Story in %(project)s "
"gelöscht</p>\n"
"<p>Hallo %(user)s,<br />%(changer)s hat eine User-Story gelöscht in "
"%(project)s</p>\n"
"<p>User-Story #%(ref)s %(subject)s</p>\n"
"<p><small>Das Taiga Team</small></p>\n"
"\n"
@ -3356,7 +3348,7 @@ msgid ""
msgstr ""
"\n"
"User-Story wurde gelöscht\n"
"Hallo %(user)s, %(changer)s hat eine User-Story in %(project)s gelöscht\n"
"Hallo %(user)s, %(changer)s hat eine User-Story gelöscht in %(project)s\n"
"User-Story #%(ref)s %(subject)s\n"
"\n"
"---\n"
@ -3438,8 +3430,8 @@ msgid ""
msgstr ""
"\n"
"<h1>Neue Wiki Seite wurde erstellt</h1>\n"
"<p>Hallo %(user)s,<br />%(changer)s hat eine neue Wiki Seite in "
"%(project)s erstellt</p>\n"
"<p>Hallo %(user)s,<br />%(changer)s hat eine neue Wiki Seite erstellt in "
"%(project)s</p>\n"
"<p>Wiki Seite %(page)s</p>\n"
"<a class=\"button\" href=\"%(url)s\" title=\"Wiki page %(page)s\">Wiki Seite "
"ansehen</a>\n"
@ -3463,8 +3455,8 @@ msgstr ""
"\n"
"Neue Wiki Seite wurde erstellt\n"
"\n"
"Hallo %(user)s, %(changer)s hat eine neue Wiki Seite in %(project)s "
"erstellt\n"
"Hallo %(user)s, %(changer)s hat eine neue Wiki Seite erstellt in "
"%(project)s\n"
"\n"
"Wiki Seite ansehen %(page)s auf %(url)s\n"
"\n"
@ -3495,8 +3487,8 @@ msgid ""
msgstr ""
"\n"
"<h1>Wiki Seite wurde gelöscht</h1>\n"
"<p>Hallo %(user)s,<br />%(changer)s hat eine Wiki Seite in "
"%(project)s gelöscht</p>\n"
"<p>Hallo %(user)s,<br />%(changer)s hat eine Wiki Seite gelöscht in "
"%(project)s</p>\n"
"<p>Wiki Seite %(page)s</p>\n"
"<p><small>Das Taiga Team</small></p>\n"
"\n"
@ -3518,7 +3510,7 @@ msgstr ""
"\n"
"Wiki Seite wurde gelöscht\n"
"\n"
"Hallo %(user)s, %(changer)s hat eine Wiki Seite in %(project)s gelöscht\n"
"Hallo %(user)s, %(changer)s hat eine Wiki Seite gelöscht in %(project)s\n"
"\n"
"Wiki Seite %(page)s\n"
"\n"
@ -3565,7 +3557,7 @@ msgstr ""
#: taiga/projects/services/members.py:133
msgid "Project without owner"
msgstr "Projekt ohne Eigentümer"
msgstr ""
#: taiga/projects/services/members.py:138
msgid "You have reached your current limit of memberships for private projects"
@ -3657,27 +3649,27 @@ msgstr "Dieser Tag existiert bereits"
#: taiga/projects/tagging/validators.py:54
#: taiga/projects/tagging/validators.py:81
msgid "The color is not a valid HEX color."
msgstr "Diese Farbe ist keine gültige HEX Farbe."
msgstr ""
#: taiga/projects/tagging/validators.py:67
#: taiga/projects/tagging/validators.py:101
#: taiga/projects/tagging/validators.py:114
#: taiga/projects/tagging/validators.py:121
msgid "The tag doesn't exist."
msgstr "Dieses Tag existiert nicht."
msgstr ""
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr ""
"Sie haben nicht die Berechtigung, diesen Sprint auf diese Aufgabe zu setzen"
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr ""
"Sie haben nicht die Berechtigung, diese User-Story auf diese Aufgabe zu "
"setzen"
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr ""
"Sie haben nicht die Berechtigung, diesen Status auf diese Aufgabe zu setzen."
@ -3688,7 +3680,7 @@ msgstr "User-Story Befehl "
#: taiga/projects/tasks/models.py:61
msgid "taskboard order"
msgstr "Aufgabenlisten Sortierung "
msgstr "Taskboard Befehl "
#: taiga/projects/tasks/models.py:69
msgid "is iocaine"
@ -3700,7 +3692,7 @@ msgstr "Ungültige milestone id."
#: taiga/projects/tasks/validators.py:72
msgid "Invalid task status id."
msgstr "Ungültige Aufgaben Status Id."
msgstr "Ungültige task status id."
#: taiga/projects/tasks/validators.py:85
msgid "Invalid user story id."
@ -4973,7 +4965,3 @@ msgstr "Antwort Header"
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "Dauer"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

View File

@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-02-15 17:25+0000\n"
"Last-Translator: Miguel Gonzalez <migonzalvar@gmail.com>\n"
"Language-Team: English (http://www.transifex.com/taiga-agile-llc/taiga-back/"
@ -196,8 +196,8 @@ msgstr ""
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "Blocked element"
@ -518,14 +518,6 @@ msgstr ""
" Comment: %(comment)s\n"
" "
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr ""
@ -1131,7 +1123,7 @@ msgid "The payload is not a valid json"
msgstr "The payload is not a valid json"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "The project doesn't exist"
@ -3675,15 +3667,15 @@ msgstr "The color is not a valid HEX color."
msgid "The tag doesn't exist."
msgstr "The tag doesn't exist."
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr "You don't have permissions to set this sprint to this task."
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr "You don't have permissions to set this user story to this task."
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr "You don't have permissions to set this status to this task."
@ -5029,7 +5021,3 @@ msgstr "response headers"
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "duration"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

View File

@ -21,8 +21,8 @@ msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: Spanish (http://www.transifex.com/taiga-agile-llc/taiga-back/"
"language/es/)\n"
@ -217,8 +217,8 @@ msgstr "Adjunta una imagen válida. El fichero no es una imagen o está dañada.
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "Elemento bloqueado"
@ -536,14 +536,6 @@ msgstr ""
"\n"
"Comentario: %(comment)s"
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr ""
@ -1149,7 +1141,7 @@ msgid "The payload is not a valid json"
msgstr "El payload no es un json válido"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "El proyecto no existe"
@ -3689,15 +3681,15 @@ msgstr "El color no tiene un código hexadecimal válido."
msgid "The tag doesn't exist."
msgstr "El tag no existe"
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr "No tienes permisos para asignar este sprint a esta tarea."
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr "No tienes permisos para asignar esta historia a esta tarea."
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr "No tienes permisos para asignar este estado a esta tarea."
@ -5051,7 +5043,3 @@ msgstr "cabeceras de la respuesta"
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "duración"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

View File

@ -4,16 +4,14 @@
#
# Translators:
# Translators:
# Amirhoshang Hoseinpour Dehkordi <amir.hoseinpour@gmail.com>, 2018
# Vahid Dayyani <vahid.dayyani@ymail.com>, 2018
msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-12-02 10:54+0000\n"
"Last-Translator: Amirhoshang Hoseinpour Dehkordi <amir.hoseinpour@gmail."
"com>\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: Persian (Iran) (http://www.transifex.com/taiga-agile-llc/"
"taiga-back/language/fa_IR/)\n"
"MIME-Version: 1.0\n"
@ -28,7 +26,7 @@ msgstr "ثبت نام عمومی غیرفعال است."
#: taiga/auth/api.py:93
msgid "You must accept our terms of service and privacy policy"
msgstr "شما باید موارد سرویس و سیاست های امنیت ما را قبول کنید."
msgstr ""
#: taiga/auth/api.py:102
msgid "invalid register type"
@ -75,7 +73,7 @@ msgstr "نام کاربری نامعتبر"
#: taiga/auth/validators.py:42 taiga/users/validators.py:50
msgid ""
"Required. 255 characters or fewer. Letters, numbers and /./-/_ characters'"
msgstr "255 کاراکتر یا کمتر ضروری است. حروف و اعداد و . و - و ـ مجاز است."
msgstr "ضروری است. ۲۵۵ کاراکتر یا کمتر. حروف و اعداد و . و - و ـ مجاز است."
#: taiga/base/api/fields.py:294
msgid "This field is required."
@ -88,7 +86,7 @@ msgstr "مقدار نامعتبر."
#: taiga/base/api/fields.py:484
#, python-format
msgid "'%s' value must be either True or False."
msgstr "'%s' می‌بایست صحیح یا غلط باشد."
msgstr "'%s' می‌بایست True (صحیح) یا False (غلط) باشد."
#: taiga/base/api/fields.py:549
msgid ""
@ -198,8 +196,8 @@ msgstr ""
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "المان مسدودشده"
@ -517,93 +515,85 @@ msgstr ""
" دیدگاه: %(comment)s\n"
" "
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr "خطای دسترسی به هاست"
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr "خطای آدرس آی پی"
#: taiga/events/events.py:106
msgid "User story created"
msgstr "گزارش کاربر ساخته شد."
msgstr ""
#: taiga/events/events.py:109
msgid "User story changed"
msgstr "گزارش کاربر تغییر کرد."
msgstr ""
#: taiga/events/events.py:112
msgid "User story deleted"
msgstr "گزارش کاربر حذف شد."
msgstr ""
#: taiga/events/events.py:114
msgid "US #{} - {}"
msgstr "US #{} - {}"
msgstr ""
#: taiga/events/events.py:117
msgid "Task created"
msgstr "وظیفه ساخته شد."
msgstr ""
#: taiga/events/events.py:120
msgid "Task changed"
msgstr "وظیفه تغییر کرد."
msgstr ""
#: taiga/events/events.py:123
msgid "Task deleted"
msgstr "وظیفه حذف شد."
msgstr ""
#: taiga/events/events.py:125
msgid "Task #{} - {}"
msgstr "وظیفه #{} - {}"
msgstr ""
#: taiga/events/events.py:128
msgid "Issue created"
msgstr "موضوع ساخته شد."
msgstr ""
#: taiga/events/events.py:131
msgid "Issue changed"
msgstr "موضوع تغییر کرد."
msgstr ""
#: taiga/events/events.py:134
msgid "Issue deleted"
msgstr "موضوع حذف شد."
msgstr ""
#: taiga/events/events.py:136
msgid "Issue: #{} - {}"
msgstr "موضوع: #{} - {}"
msgstr ""
#: taiga/events/events.py:139
msgid "Wiki Page created"
msgstr "صفحه ویکی ساخته شد."
msgstr ""
#: taiga/events/events.py:142
msgid "Wiki Page changed"
msgstr "صفحه ویکی تغییر داده شد."
msgstr ""
#: taiga/events/events.py:145
msgid "Wiki Page deleted"
msgstr "صفحه ویکی حذف شد."
msgstr ""
#: taiga/events/events.py:147
msgid "Wiki Page: {}"
msgstr "صفحه ویکی: {}"
msgstr ""
#: taiga/events/events.py:150
msgid "Sprint created"
msgstr "سرعتی ساخته شد."
msgstr ""
#: taiga/events/events.py:153
msgid "Sprint changed"
msgstr "سرعتی تغییر داده شد."
msgstr ""
#: taiga/events/events.py:156
msgid "Sprint deleted"
msgstr "سرعتی حذف شد."
msgstr ""
#: taiga/events/events.py:158
msgid "Sprint: {}"
msgstr "سرعتی: {}"
msgstr ""
#: taiga/export_import/api.py:127
msgid "We needed at least one role"
@ -1134,7 +1124,7 @@ msgid "The payload is not a valid json"
msgstr "داده‌ها یک JSON معتبر نیستند"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "پروژه وجود ندارد"
@ -1784,27 +1774,27 @@ msgstr "کاربر می‌بایست قبلاً از اعضای پروژه بو
#: taiga/projects/api.py:642
msgid "You can't delete user story due date by default"
msgstr "شما سر رسید گزارش کاربر را نمی توانید بصورت عادی حذف کنید."
msgstr ""
#: taiga/projects/api.py:658
msgid "Project already have due dates"
msgstr "پروژه موعد سر رسید دارد."
msgstr ""
#: taiga/projects/api.py:718
msgid "You can't delete task due date by default"
msgstr "شما سر رسید وظیفه کاربر را نمی توانید بصورت عادی حذف کنید."
msgstr ""
#: taiga/projects/api.py:734
msgid "Project already have task due dates"
msgstr "پروژه موعد سر رسید وظیفه دارد."
msgstr ""
#: taiga/projects/api.py:858
msgid "You can't delete issue due date by default"
msgstr "شما سر رسید موضوع کاربر را نمی توانید بصورت عادی حذف کنید."
msgstr ""
#: taiga/projects/api.py:874
msgid "Project already have issue due dates"
msgstr "پروژه موعد سر رسید موضوع دارد."
msgstr ""
#: taiga/projects/api.py:1023
msgid ""
@ -2053,11 +2043,11 @@ msgstr "موردی با همین نام وجود دارد."
#: taiga/projects/due_dates/models.py:21
msgid "due date"
msgstr "موعد سر رسید"
msgstr ""
#: taiga/projects/due_dates/models.py:24
msgid "reason for the due date"
msgstr "دلیل موعد سر رسید."
msgstr ""
#: taiga/projects/epics/api.py:94
msgid "You don't have permissions to set this status to this epic."
@ -2214,7 +2204,7 @@ msgstr "آزاد شد"
#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:164
#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:175
msgid "Not set"
msgstr "تنظیم نشده"
msgstr ""
#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:286
#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:91
@ -2569,12 +2559,12 @@ msgstr "ارزش"
#: taiga/projects/models.py:614 taiga/projects/models.py:671
#: taiga/projects/models.py:789
msgid "by default"
msgstr "بصورت عمومی"
msgstr ""
#: taiga/projects/models.py:618 taiga/projects/models.py:675
#: taiga/projects/models.py:793
msgid "days to due"
msgstr "روز تا موعد"
msgstr ""
#: taiga/projects/models.py:823
msgid "default owner's role"
@ -2594,7 +2584,7 @@ msgstr "وضعیت‌های استوری‌های کاربری"
#: taiga/projects/models.py:849
msgid "us duedates"
msgstr "موعد ما"
msgstr ""
#: taiga/projects/models.py:850 taiga/projects/userstories/models.py:45
#: taiga/projects/userstories/models.py:78
@ -2607,7 +2597,7 @@ msgstr "وضعیت‌های وظایف"
#: taiga/projects/models.py:852
msgid "task duedates"
msgstr "موعد وظیفه ها"
msgstr ""
#: taiga/projects/models.py:853
msgid "issue statuses"
@ -2619,7 +2609,7 @@ msgstr "انواع موضوعات"
#: taiga/projects/models.py:855
msgid "issue duedates"
msgstr "موعد موضوعات"
msgstr ""
#: taiga/projects/models.py:856
msgid "priorities"
@ -3665,15 +3655,15 @@ msgstr "این رنگ یک کد HEX معتبر نیست."
msgid "The tag doesn't exist."
msgstr "تگ موجود نیست."
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr "دسترسی لازم برای تنظیم پیشرفت این وظیفه را ندارید."
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr "دسترسی لازم برای تنظیم استوری کاربری این وظیفه را ندارید."
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr "دسترسی لازم برای تعیین وضعیت این وظیفه را ندارید."
@ -4223,7 +4213,7 @@ msgstr "؟"
#. Translators: User story point value (value = 0)
#: taiga/projects/translations.py:47
msgid "0"
msgstr "0"
msgstr "۰"
#. Translators: User story point value (value = 0.5)
#: taiga/projects/translations.py:49
@ -4463,7 +4453,7 @@ msgstr "تاریخ تکمیل"
#: taiga/projects/userstories/models.py:102
msgid "assigned users"
msgstr "کاربران متصل شده"
msgstr ""
#: taiga/projects/userstories/models.py:111
msgid "generated from issue"
@ -4726,11 +4716,11 @@ msgstr "تاریخ عضویت"
#: taiga/users/models.py:155
msgid "accepted terms"
msgstr "قبول شرایط"
msgstr ""
#: taiga/users/models.py:156
msgid "new terms read"
msgstr "خواندن شرایط جدید"
msgstr ""
#: taiga/users/models.py:158
msgid "default language"
@ -4974,7 +4964,7 @@ msgstr "نام کاربری نامعتبر. نام کاربری دیگری ان
#: taiga/users/validators.py:73
msgid "Read new terms has to be true'"
msgstr "خواندن شرایط جدید باید صحیح باشد"
msgstr ""
#: taiga/userstorage/api.py:53
msgid ""
@ -5016,7 +5006,3 @@ msgstr "هدرهای پاسخ"
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "مدت زمان"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr "آی پی آدرس غیر مجاز"

View File

@ -11,8 +11,8 @@ msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: Finnish (http://www.transifex.com/taiga-agile-llc/taiga-back/"
"language/fi/)\n"
@ -199,8 +199,8 @@ msgstr ""
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "Estetty elementti"
@ -489,14 +489,6 @@ msgstr ""
"\n"
"Kommentti: %(comment)s"
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr ""
@ -1086,7 +1078,7 @@ msgid "The payload is not a valid json"
msgstr "The payload is not a valid json"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "Projektia ei löydy"
@ -3453,15 +3445,15 @@ msgstr ""
msgid "The tag doesn't exist."
msgstr ""
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr ""
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr ""
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr ""
@ -4713,7 +4705,3 @@ msgstr "response headers"
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "duration"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

View File

@ -32,8 +32,8 @@ msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: French (http://www.transifex.com/taiga-agile-llc/taiga-back/"
"language/fr/)\n"
@ -231,8 +231,8 @@ msgstr ""
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "Élément bloqué"
@ -528,14 +528,6 @@ msgstr ""
" Commentaire : %(comment)s\n"
" "
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr ""
@ -1139,7 +1131,7 @@ msgid "The payload is not a valid json"
msgstr "Le payload n'est pas un json valide"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "Le projet n'existe pas"
@ -3447,15 +3439,15 @@ msgstr "La couleur n'est pas un code HEX valide."
msgid "The tag doesn't exist."
msgstr "Ce mot-clé n'existe pas."
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr "Vous n'avez pas la permission d'affecter ce sprint à cette tâche."
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr "Vous n'avez pas la permission d'affecter ce récit à cette tâche."
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr "Vous n'avez pas la permission d'affecter ce statut à ce problème."
@ -4770,7 +4762,3 @@ msgstr "en-têtes de la réponse"
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "durée"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

View File

@ -18,8 +18,8 @@ msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: Italian (http://www.transifex.com/taiga-agile-llc/taiga-back/"
"language/it/)\n"
@ -211,8 +211,8 @@ msgstr ""
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "Elemento bloccato"
@ -532,14 +532,6 @@ msgstr ""
"\n"
"Commento: %(comment)s"
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr ""
@ -1213,7 +1205,7 @@ msgid "The payload is not a valid json"
msgstr "Il carico non è un json valido"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "Il progetto non esiste"
@ -3894,16 +3886,16 @@ msgstr "Il colore non e' un codice HEX valido."
msgid "The tag doesn't exist."
msgstr "Il tag non esiste."
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr "Non hai i permessi per aggiungere questo sprint a questo compito."
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr ""
"Non hai i permessi per aggiungere questa storia utente a questo compito."
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr "Non hai i permessi per aggiungere questo stato a questo compito."
@ -5312,7 +5304,3 @@ msgstr "header della risposta"
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "durata"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

View File

@ -10,14 +10,13 @@
# Shun Yanaura <metroplexity@gmail.com>, 2016
# Suguru Sato <usagi.vs.tanuki@gmail.com>, 2016
# Tomonori Tanabe <tanb+github@me.com>, 2015
# Masaki Honda <mh35jp@gmail.com>, 2018
msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-12-28 01:50+0000\n"
"Last-Translator: Masaki Honda <mh35jp@gmail.com>\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: Japanese (http://www.transifex.com/taiga-agile-llc/taiga-back/"
"language/ja/)\n"
"MIME-Version: 1.0\n"
@ -32,7 +31,7 @@ msgstr "パブリックなレジスタは無効です。"
#: taiga/auth/api.py:93
msgid "You must accept our terms of service and privacy policy"
msgstr "利用規約とプライバシーポリシーに同意する必要があります"
msgstr ""
#: taiga/auth/api.py:102
msgid "invalid register type"
@ -208,8 +207,8 @@ msgstr ""
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "ブロックされた要素"
@ -503,14 +502,6 @@ msgstr ""
"\n"
"コメント: %(comment)s"
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr ""
@ -1118,7 +1109,7 @@ msgid "The payload is not a valid json"
msgstr "ペイロードは有効なjsonではありません。"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "プロジェクトは存在していません。"
@ -3257,15 +3248,15 @@ msgstr ""
msgid "The tag doesn't exist."
msgstr ""
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr ""
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr ""
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr ""
@ -4400,7 +4391,3 @@ msgstr "レスポンスヘッダー"
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "期限"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

View File

@ -15,8 +15,8 @@ msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: Korean (http://www.transifex.com/taiga-agile-llc/taiga-back/"
"language/ko/)\n"
@ -208,8 +208,8 @@ msgstr ""
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "차단된 엘리먼트"
@ -504,14 +504,6 @@ msgstr ""
" 댓글: %(comment)s\n"
" "
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr ""
@ -1116,7 +1108,7 @@ msgid "The payload is not a valid json"
msgstr "페이로드의 json이 유효하지 않습니다."
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "프로젝트가 존재하지 않습니다."
@ -3653,15 +3645,15 @@ msgstr "색이 유효하지 않은 HEX색 입니다."
msgid "The tag doesn't exist."
msgstr "태그가 존재하지 않습니다."
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr "이 태스크의 스프린트를 설정할 권한이 없습니다."
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr "이 태스크의 유저 스토리를 설정할 권한이 없습니다."
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr "이 태스크의 상태를 설정할 권한이 없습니다."
@ -5011,7 +5003,3 @@ msgstr "응답 헤더"
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "지속시간"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

View File

@ -9,8 +9,8 @@ msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: Norwegian Bokmål (http://www.transifex.com/taiga-agile-llc/"
"taiga-back/language/nb/)\n"
@ -198,8 +198,8 @@ msgstr ""
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "Blokkert element"
@ -490,14 +490,6 @@ msgstr ""
" Kommentar: %(comment)s\n"
" "
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr ""
@ -1001,7 +993,7 @@ msgid "The payload is not a valid json"
msgstr "Payloaden er ikke gyldig json"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "Prosjektet eksisterer ikke"
@ -3134,16 +3126,16 @@ msgstr ""
msgid "The tag doesn't exist."
msgstr ""
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr "Du har ikke tillatelse til å sette denne sprinten til denne oppgaven."
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr ""
"Du har ikke tillatelse til å sette denne brukerhistorien til denne oppgaven."
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr "Du har ikke tillatelse til å sette denne statusen til denne oppgaven."
@ -4306,7 +4298,3 @@ msgstr ""
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "varighet"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

View File

@ -6,14 +6,13 @@
# Translators:
# Dajo Hein, 2015
# Haroun Pacquee, 2015
# Joannes Anthonius Rommers <ja.rommers@lge.com>, 2018
msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-11-02 07:56+0000\n"
"Last-Translator: Joannes Anthonius Rommers <ja.rommers@lge.com>\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: Dutch (http://www.transifex.com/taiga-agile-llc/taiga-back/"
"language/nl/)\n"
"MIME-Version: 1.0\n"
@ -28,7 +27,7 @@ msgstr "Publieke registratie is uitgeschakeld."
#: taiga/auth/api.py:93
msgid "You must accept our terms of service and privacy policy"
msgstr "U moet onze servicevoorwaarden en ons privacybeleid accepteren"
msgstr ""
#: taiga/auth/api.py:102
msgid "invalid register type"
@ -56,7 +55,7 @@ msgstr "Gebruiker is al geregistreerd."
#: taiga/auth/services.py:141
msgid "This user is already a member of the project."
msgstr "Deze gebruiker is al lid van het project."
msgstr ""
#: taiga/auth/services.py:165
msgid "Error on creating new user."
@ -106,7 +105,7 @@ msgstr ""
#: taiga/base/api/fields.py:638
msgid "You email domain is not allowed"
msgstr "Jou e-mail domein is niet toegestaan"
msgstr ""
#: taiga/base/api/fields.py:647
msgid "Enter a valid email address."
@ -208,11 +207,11 @@ msgstr ""
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "Geblokkeerd element"
msgstr ""
#: taiga/base/api/pagination.py:228
msgid "Page is not 'last', nor can it be converted to an int."
@ -365,7 +364,7 @@ msgstr "Preconditie fout"
#: taiga/base/exceptions.py:219
msgid "No room left for more projects."
msgstr "Er is geen ruimte over voor meer projecten."
msgstr ""
#: taiga/base/filters.py:105 taiga/base/filters.py:526
msgid "Error in filter params types."
@ -487,9 +486,6 @@ msgid ""
"%(comment)s</p>\n"
" "
msgstr ""
"\n"
"<h3>commentaar:</h3>\n"
"<p>%(comment)s</p>"
#: taiga/base/templates/emails/updates-body-text.jinja:6
#, python-format
@ -502,93 +498,85 @@ msgstr ""
" Commentaar: %(comment)s\n"
" "
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr "Host toegang fout"
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr "IP adres fout"
#: taiga/events/events.py:106
msgid "User story created"
msgstr "User story aangemaakt"
msgstr ""
#: taiga/events/events.py:109
msgid "User story changed"
msgstr "User story gewijzigd"
msgstr ""
#: taiga/events/events.py:112
msgid "User story deleted"
msgstr "User story verwijderd"
msgstr ""
#: taiga/events/events.py:114
msgid "US #{} - {}"
msgstr "US #{} - {}"
msgstr ""
#: taiga/events/events.py:117
msgid "Task created"
msgstr "Taak aangemaakt"
msgstr ""
#: taiga/events/events.py:120
msgid "Task changed"
msgstr "Taak gewijzigd"
msgstr ""
#: taiga/events/events.py:123
msgid "Task deleted"
msgstr "Taak verwijderd"
msgstr ""
#: taiga/events/events.py:125
msgid "Task #{} - {}"
msgstr "Taak #{} - {}"
msgstr ""
#: taiga/events/events.py:128
msgid "Issue created"
msgstr "Probleem aangemaakt"
msgstr ""
#: taiga/events/events.py:131
msgid "Issue changed"
msgstr "Probleem gewijzigd"
msgstr ""
#: taiga/events/events.py:134
msgid "Issue deleted"
msgstr "Probleem verwijderd"
msgstr ""
#: taiga/events/events.py:136
msgid "Issue: #{} - {}"
msgstr "Probleem: #{} - {}"
msgstr ""
#: taiga/events/events.py:139
msgid "Wiki Page created"
msgstr "Wiki Pagina aangemaakt"
msgstr ""
#: taiga/events/events.py:142
msgid "Wiki Page changed"
msgstr "Wiki Pagina gewijzigd"
msgstr ""
#: taiga/events/events.py:145
msgid "Wiki Page deleted"
msgstr "Wiki Pagina verwijderd"
msgstr ""
#: taiga/events/events.py:147
msgid "Wiki Page: {}"
msgstr "Wiki pagina: {}"
msgstr ""
#: taiga/events/events.py:150
msgid "Sprint created"
msgstr "Sprint aangemaakt"
msgstr ""
#: taiga/events/events.py:153
msgid "Sprint changed"
msgstr "Sprint gewijzigd"
msgstr ""
#: taiga/events/events.py:156
msgid "Sprint deleted"
msgstr "Sprint verwijderd"
msgstr ""
#: taiga/events/events.py:158
msgid "Sprint: {}"
msgstr "Sprint: {}"
msgstr ""
#: taiga/export_import/api.py:127
msgid "We needed at least one role"
@ -625,23 +613,23 @@ msgstr "fout bij importeren van standaard projectattributen waarden"
#: taiga/export_import/services/store.py:779
msgid "error importing custom attributes"
msgstr "fout bij het importeren van aangepaste attributen"
msgstr "fout bij importeren eigen attributen"
#: taiga/export_import/services/store.py:783
msgid "error importing sprints"
msgstr "fout bij het importeren van sprints"
msgstr "fout bij importeren sprints"
#: taiga/export_import/services/store.py:787
msgid "error importing issues"
msgstr "fout bij het importeren van problemen"
msgstr "fout bij importeren issues"
#: taiga/export_import/services/store.py:791
msgid "error importing user stories"
msgstr "fout bij het importeren van user stories"
msgstr "fout bij importeren user stories"
#: taiga/export_import/services/store.py:795
msgid "error importing epics"
msgstr "fout bij het importeren van epics"
msgstr ""
#: taiga/export_import/services/store.py:799
msgid "error importing tasks"
@ -665,7 +653,7 @@ msgstr "fout bij importeren tijdlijnen"
#: taiga/export_import/services/store.py:837
msgid "unexpected error importing project"
msgstr "onverwachte fout tijdens het importeren van het project"
msgstr ""
#: taiga/export_import/tasks.py:62 taiga/export_import/tasks.py:63
msgid "Error generating project dump"
@ -697,11 +685,11 @@ msgstr "Fout bij laden project dump"
#: taiga/export_import/tasks.py:121
msgid "Error loading your project dump file"
msgstr "Fout tijdens het laden van de project dump file"
msgstr ""
#: taiga/export_import/tasks.py:135
msgid " -- no detail info --"
msgstr "-- geen gedetailleerde informatie --"
msgstr ""
#: taiga/export_import/templates/emails/dump_project-body-html.jinja:4
#, python-format
@ -897,7 +885,7 @@ msgstr "Naam gedupliceerd voor het project"
#: taiga/external_apps/api.py:43 taiga/external_apps/api.py:70
#: taiga/external_apps/api.py:77
msgid "Authentication required"
msgstr "Authenticatie verreist"
msgstr ""
#: taiga/external_apps/models.py:35
#: taiga/projects/custom_attributes/models.py:36
@ -915,11 +903,11 @@ msgstr "naam"
#: taiga/external_apps/models.py:37
msgid "Icon url"
msgstr "Icoon url"
msgstr ""
#: taiga/external_apps/models.py:38
msgid "web"
msgstr "web"
msgstr ""
#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62
#: taiga/projects/custom_attributes/models.py:37
@ -933,17 +921,17 @@ msgstr "omschrijving"
#: taiga/external_apps/models.py:41
msgid "Next url"
msgstr "Volgende url"
msgstr ""
#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26
#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:88
#: taiga/projects/votes/models.py:52
msgid "user"
msgstr "gebruiker"
msgstr ""
#: taiga/external_apps/models.py:59
msgid "application"
msgstr "applicatie"
msgstr ""
#: taiga/feedback/models.py:25 taiga/users/models.py:147
msgid "full name"
@ -1035,7 +1023,7 @@ msgid "The payload is not a valid json"
msgstr "De payload is geen geldige json"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "Het project bestaat niet"
@ -3188,15 +3176,15 @@ msgstr ""
msgid "The tag doesn't exist."
msgstr ""
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr ""
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr ""
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr ""
@ -4362,7 +4350,3 @@ msgstr "response headers"
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "duur"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

View File

@ -13,8 +13,8 @@ msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: Polish (http://www.transifex.com/taiga-agile-llc/taiga-back/"
"language/pl/)\n"
@ -204,8 +204,8 @@ msgstr ""
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "Element zablokowany"
@ -502,14 +502,6 @@ msgstr ""
" Komentarz: %(comment)s\n"
" "
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr ""
@ -1117,7 +1109,7 @@ msgid "The payload is not a valid json"
msgstr "Źródło nie jest prawidłowym plikiem json"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "Projekt nie istnieje"
@ -3513,16 +3505,16 @@ msgstr "Nieprawidłowy kolor w systemie HEX."
msgid "The tag doesn't exist."
msgstr "Tag nie istnieje"
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr "Nie masz uprawnień do ustawiania sprintu dla tego zadania."
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr ""
"Nie masz uprawnień do ustawiania historyjki użytkownika dla tego zadania"
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr "Nie masz uprawnień do ustawiania statusu dla tego zadania"
@ -4821,7 +4813,3 @@ msgstr "nagłówki odpowiedzi"
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "czas trwania"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

View File

@ -18,7 +18,6 @@
# Lucas Boscaini <lucasboscaini@gmail.com>, 2017
# Mairieli Wessel <mairieliw@alunos.utfpr.edu.br>, 2016
# Marlon Carvalho <m.lopes@qiwi.com>, 2015
# Michael Douglas Meneses de Souza <michael.douglas.meneses.2@gmail.com>, 2018
# Michel Wilhelm <michelwilhelm@gmail.com>, 2016
# pedromvm <pedromvm@gmail.com>, 2015
# Pedro Rangel Raft <me@pedroraft.com>, 2017
@ -30,10 +29,9 @@ msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-11-17 17:51+0000\n"
"Last-Translator: Michael Douglas Meneses de Souza <michael.douglas."
"meneses.2@gmail.com>\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/taiga-agile-llc/"
"taiga-back/language/pt_BR/)\n"
"MIME-Version: 1.0\n"
@ -48,7 +46,7 @@ msgstr "Registro público está desabilitado. "
#: taiga/auth/api.py:93
msgid "You must accept our terms of service and privacy policy"
msgstr "Você deve aceitar os termos do serviço e a política de privacidade"
msgstr ""
#: taiga/auth/api.py:102
msgid "invalid register type"
@ -219,8 +217,8 @@ msgstr ""
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "Elemento bloqeado"
@ -517,93 +515,85 @@ msgstr ""
" Comentário: %(comment)s\n"
" "
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr "História de Usuário criada"
msgstr ""
#: taiga/events/events.py:109
msgid "User story changed"
msgstr "História de Usuário alterada"
msgstr ""
#: taiga/events/events.py:112
msgid "User story deleted"
msgstr "História de Usuário excluída"
msgstr ""
#: taiga/events/events.py:114
msgid "US #{} - {}"
msgstr "US #{} - {}"
msgstr ""
#: taiga/events/events.py:117
msgid "Task created"
msgstr "Tarefa criada"
msgstr ""
#: taiga/events/events.py:120
msgid "Task changed"
msgstr "Tarefa alterada"
msgstr ""
#: taiga/events/events.py:123
msgid "Task deleted"
msgstr "Tarefa excluída"
msgstr ""
#: taiga/events/events.py:125
msgid "Task #{} - {}"
msgstr "Tarefa #{} - {}"
msgstr ""
#: taiga/events/events.py:128
msgid "Issue created"
msgstr "Issue criada"
msgstr ""
#: taiga/events/events.py:131
msgid "Issue changed"
msgstr "Issue alterada"
msgstr ""
#: taiga/events/events.py:134
msgid "Issue deleted"
msgstr "Issue excluída"
msgstr ""
#: taiga/events/events.py:136
msgid "Issue: #{} - {}"
msgstr "Issue: #{} - {}"
msgstr ""
#: taiga/events/events.py:139
msgid "Wiki Page created"
msgstr "Página Wiki criada"
msgstr ""
#: taiga/events/events.py:142
msgid "Wiki Page changed"
msgstr "Página Wiki alterada"
msgstr ""
#: taiga/events/events.py:145
msgid "Wiki Page deleted"
msgstr "Página Wiki excluída"
msgstr ""
#: taiga/events/events.py:147
msgid "Wiki Page: {}"
msgstr "Página Wiki: {}"
msgstr ""
#: taiga/events/events.py:150
msgid "Sprint created"
msgstr "Sprint criada"
msgstr ""
#: taiga/events/events.py:153
msgid "Sprint changed"
msgstr "Sprint alterada"
msgstr ""
#: taiga/events/events.py:156
msgid "Sprint deleted"
msgstr "Sprint excluída"
msgstr ""
#: taiga/events/events.py:158
msgid "Sprint: {}"
msgstr "Sprint: {}"
msgstr ""
#: taiga/export_import/api.py:127
msgid "We needed at least one role"
@ -1138,7 +1128,7 @@ msgid "The payload is not a valid json"
msgstr "A carga não é um json válido"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "O projeto não existe"
@ -1281,7 +1271,7 @@ msgstr "O parâmetro url é necessário"
#: taiga/importers/jira/api.py:158
msgid "Invalid project_type {}"
msgstr "project_type inválido {}"
msgstr ""
#: taiga/importers/jira/api.py:192
msgid "Invalid Jira server configuration."
@ -1520,7 +1510,7 @@ msgstr "Pedido invalido: %s há %s"
#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82
#, python-format
msgid "Unauthorized: %s at %s"
msgstr "Não autorizado: %s até %s"
msgstr ""
#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86
#, python-format
@ -3626,17 +3616,17 @@ msgstr ""
msgid "The tag doesn't exist."
msgstr ""
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr "Você não tem permissão para colocar esse sprint para essa tarefa."
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr ""
"Você não tem permissão para colocar essa história de usuário para essa "
"tarefa."
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr "Você não tem permissão para colocar esse status para essa tarefa."
@ -4912,7 +4902,3 @@ msgstr "cabeçalhos de resposta"
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "duração"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

View File

@ -24,8 +24,8 @@ msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: Russian (http://www.transifex.com/taiga-agile-llc/taiga-back/"
"language/ru/)\n"
@ -219,8 +219,8 @@ msgstr ""
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "Заблокированный элемент"
@ -536,14 +536,6 @@ msgstr ""
" Комментарий: %(comment)s\n"
" "
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr ""
@ -1148,7 +1140,7 @@ msgid "The payload is not a valid json"
msgstr "Нагрузочный файл не является правильным json-файлом"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "Проект не существует"
@ -3647,16 +3639,16 @@ msgstr "Этот некорректный шестнадцатеричный ф
msgid "The tag doesn't exist."
msgstr "Тэг не существует"
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr "У вас нет прав, чтобы назначить этот спринт для этой задачи."
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr ""
"У вас нет прав, чтобы назначить эту историю от пользователя этой задаче."
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr "У вас нет прав, чтобы установить этот статус для этой задачи."
@ -5009,7 +5001,3 @@ msgstr "заголовки ответа"
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "длительность"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

View File

@ -10,8 +10,8 @@ msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: Swedish (http://www.transifex.com/taiga-agile-llc/taiga-back/"
"language/sv/)\n"
@ -201,8 +201,8 @@ msgstr ""
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "Blockerat element"
@ -488,14 +488,6 @@ msgid ""
" "
msgstr ""
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr ""
@ -983,7 +975,7 @@ msgid "The payload is not a valid json"
msgstr "Datasträngen är inte korrekt json"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "Projektet existerar inte"
@ -3104,15 +3096,15 @@ msgstr ""
msgid "The tag doesn't exist."
msgstr ""
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr "Du har inte behörighet åt att sätta sprinten till en uppgift"
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr "Du har inte behörighet att sätta använderhistorien till en uppgift."
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr "Du har inte behörighet att sätta status till en uppgift. "
@ -4267,7 +4259,3 @@ msgstr "responstitel"
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "varaktighet"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

View File

@ -11,8 +11,8 @@ msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: Turkish (http://www.transifex.com/taiga-agile-llc/taiga-back/"
"language/tr/)\n"
@ -208,8 +208,8 @@ msgstr ""
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "Engellenmiş nesne"
@ -497,14 +497,6 @@ msgstr ""
"\n"
"Yorumlar: %(comment)s"
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr ""
@ -1091,7 +1083,7 @@ msgid "The payload is not a valid json"
msgstr ""
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "Proje mevcut değil."
@ -3278,15 +3270,15 @@ msgstr ""
msgid "The tag doesn't exist."
msgstr ""
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr "Bu görev için sprint ayarlamanız için izniniz yok."
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr "Bu görev için kullanıcı hikayesi ayarlama izniniz yok."
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr "Bu görev için bu durumu ayarlama izniniz yok."
@ -4438,7 +4430,3 @@ msgstr "cevap başlıkları"
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "süre"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,6 @@
#
# Translators:
# Translators:
# Andy zhan <daliangzao189@126.com>, 2018
# Ares <yangzibin.cn@hotmail.com>, 2017
# gm l <linguangmo@gmail.com>, 2016
# Hanbing Yin <yin_suk@hotmail.com>, 2016
@ -18,16 +17,16 @@
# Yang Yu <yuyangmkdir@yahoo.com>, 2016
# yonee <yangqo@hotmail.com>, 2015
# 5791113 <yonglong.ma@outlook.com>, 2016
# zangyg <zangyungang@gmail.com>, 2018
# yungang <zangyungang@gmail.com>, 2018
# 5791113 <yonglong.ma@outlook.com>, 2016
# 朱坚 <garyzhu2009@gmail.com>, 2017
msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-12-16 07:10+0000\n"
"Last-Translator: Andy zhan <daliangzao189@126.com>\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: Chinese Simplified (http://www.transifex.com/taiga-agile-llc/"
"taiga-back/language/zh-Hans/)\n"
"MIME-Version: 1.0\n"
@ -207,8 +206,8 @@ msgstr "请上传一张有效的图片。所上传的不是图片或已损坏"
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr "冻结的元素"
@ -519,14 +518,6 @@ msgstr ""
" 评论: %(comment)s\n"
" "
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr ""
@ -1127,7 +1118,7 @@ msgid "The payload is not a valid json"
msgstr "内容不是一个合法的JSON"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "项目不存在"
@ -3611,15 +3602,15 @@ msgstr "该颜色不是一个合法的HEX颜色"
msgid "The tag doesn't exist."
msgstr "该标签不存在"
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr "你无权对这个任务设置该冲刺任务。"
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr "你无权对这个任务设置该用户故事。"
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr "你无权对这个任务设置该状态。"
@ -4650,11 +4641,11 @@ msgstr "加入日期"
#: taiga/users/models.py:155
msgid "accepted terms"
msgstr "已接受的条款"
msgstr ""
#: taiga/users/models.py:156
msgid "new terms read"
msgstr "阅读新条款"
msgstr ""
#: taiga/users/models.py:158
msgid "default language"
@ -4895,7 +4886,7 @@ msgstr "无效用户名,请尝试其他的。"
#: taiga/users/validators.py:73
msgid "Read new terms has to be true'"
msgstr "还没有阅读新条款"
msgstr ""
#: taiga/userstorage/api.py:53
msgid ""
@ -4937,7 +4928,3 @@ msgstr "响应Header头"
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "持续时间"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr "IP地址被禁用"

View File

@ -14,8 +14,8 @@ msgid ""
msgstr ""
"Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
"POT-Creation-Date: 2018-09-18 17:14+0200\n"
"PO-Revision-Date: 2018-08-10 09:46+0000\n"
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
"Language-Team: Chinese Traditional (http://www.transifex.com/taiga-agile-llc/"
"taiga-back/language/zh-Hant/)\n"
@ -196,8 +196,8 @@ msgstr "上傳有效圖片,你所上傳的檔案非圖檔或已損壞"
#: taiga/hooks/api.py:69 taiga/projects/api.py:461 taiga/projects/api.py:494
#: taiga/projects/api.py:970 taiga/projects/epics/api.py:200
#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:233
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:259
#: taiga/projects/tasks/api.py:284 taiga/projects/userstories/api.py:346
#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:258
#: taiga/projects/tasks/api.py:283 taiga/projects/userstories/api.py:346
#: taiga/projects/userstories/api.py:398 taiga/webhooks/api.py:71
msgid "Blocked element"
msgstr ""
@ -485,14 +485,6 @@ msgstr ""
"\n"
"評論: %(comment)s"
#: taiga/base/utils/urls.py:68
msgid "Host access error"
msgstr ""
#: taiga/base/utils/urls.py:74
msgid "IP Address error"
msgstr ""
#: taiga/events/events.py:106
msgid "User story created"
msgstr ""
@ -1078,7 +1070,7 @@ msgid "The payload is not a valid json"
msgstr "載荷為無效json"
#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:211
#: taiga/projects/issues/api.py:142 taiga/projects/tasks/api.py:210
#: taiga/projects/userstories/api.py:290
msgid "The project doesn't exist"
msgstr "專案不存在"
@ -3463,15 +3455,15 @@ msgstr ""
msgid "The tag doesn't exist."
msgstr ""
#: taiga/projects/tasks/api.py:108 taiga/projects/tasks/api.py:117
#: taiga/projects/tasks/api.py:107 taiga/projects/tasks/api.py:116
msgid "You don't have permissions to set this sprint to this task."
msgstr "無權限更動此任務下的衝刺任務"
#: taiga/projects/tasks/api.py:111
#: taiga/projects/tasks/api.py:110
msgid "You don't have permissions to set this user story to this task."
msgstr "無權限更動此務下的使用者故事"
#: taiga/projects/tasks/api.py:114
#: taiga/projects/tasks/api.py:113
msgid "You don't have permissions to set this status to this task."
msgstr "無權限更動此任務下的狀態"
@ -4704,7 +4696,3 @@ msgstr "回應標頭 "
#: taiga/webhooks/models.py:46
msgid "duration"
msgstr "期間"
#: taiga/webhooks/validators.py:42
msgid "Not allowed IP Address"
msgstr ""

View File

@ -37,7 +37,7 @@ class AutolinkExtension(markdown.Extension):
* GitHub only accepts URLs with protocols or "www.", whereas Gruber's regex
accepts things like "foo.com/bar".
"""
def extendMarkdown(self, md):
def extendMarkdown(self, md, md_globals):
url_re = r'(?i)\b((?:(?:ftp|https?)://|www\d{0,3}[.])([^\s<>]+))'
autolink = AutolinkPattern(url_re, md)
md.inlinePatterns.add('gfm-autolink', autolink, '_end')

View File

@ -19,7 +19,7 @@ class AutomailPattern(markdown.inlinepatterns.Pattern):
class AutomailExtension(markdown.Extension):
"""An extension that turns all email addresses into links."""
def extendMarkdown(self, md):
def extendMarkdown(self, md, md_globals):
mail_re = r'\b(?i)([a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]+)\b'
automail = AutomailPattern(mail_re, md)
md.inlinePatterns.add('gfm-automail', automail, '_end')

View File

@ -152,7 +152,7 @@ EMOJIS_SET = {
class EmojifyExtension(Extension):
def extendMarkdown(self, md):
def extendMarkdown(self, md, md_globals):
md.registerExtension(self)
md.preprocessors.add('emojify',
EmojifyPreprocessor(md),

View File

@ -30,7 +30,7 @@ from markdown.util import etree, AtomicString
class MentionsExtension(Extension):
def extendMarkdown(self, md):
def extendMarkdown(self, md, md_globals):
MENTION_RE = r"(@)([\w.-]+)"
mentionsPattern = MentionsPattern(MENTION_RE)
mentionsPattern.md = md

View File

@ -36,7 +36,7 @@ class TaigaReferencesExtension(Extension):
self.project = project
return super().__init__(*args, **kwargs)
def extendMarkdown(self, md):
def extendMarkdown(self, md, md_globals):
TAIGA_REFERENCE_RE = r'(?<=^|(?<=[^a-zA-Z0-9-\[]))#(\d+)'
referencesPattern = TaigaReferencesPattern(TAIGA_REFERENCE_RE, self.project)
referencesPattern.md = md

View File

@ -28,6 +28,6 @@ class SemiSaneListExtension(markdown.Extension):
newlines.
"""
def extendMarkdown(self, md):
def extendMarkdown(self, md, md_globals):
md.parser.blockprocessors['olist'] = SemiSaneOListProcessor(md.parser)
md.parser.blockprocessors['ulist'] = SemiSaneUListProcessor(md.parser)

View File

@ -31,7 +31,7 @@ class SpacedLinkExtension(markdown.Extension):
extension adds such support.
"""
def extendMarkdown(self, md):
def extendMarkdown(self, md, md_globals):
md.inlinePatterns["link"] = \
markdown.inlinepatterns.LinkPattern(SPACED_LINK_RE, md)
md.inlinePatterns["reference"] = \

View File

@ -14,6 +14,6 @@ class StrikethroughExtension(markdown.Extension):
For example: ``~~strike~~``.
"""
def extendMarkdown(self, md):
def extendMarkdown(self, md, md_globals):
pattern = markdown.inlinepatterns.SimpleTagPattern(STRIKE_RE, 'del')
md.inlinePatterns.add('gfm-strikethrough', pattern, '_end')

View File

@ -28,7 +28,7 @@ from taiga.front.templatetags.functions import resolve
class TargetBlankLinkExtension(markdown.Extension):
"""An extension that add target="_blank" to all external links."""
def extendMarkdown(self, md):
def extendMarkdown(self, md, md_globals):
md.treeprocessors.add("target_blank_links",
TargetBlankLinksTreeprocessor(md),
"<prettify")

View File

@ -34,7 +34,7 @@ class WikiLinkExtension(Extension):
self.project = project
return super().__init__(*args, **kwargs)
def extendMarkdown(self, md):
def extendMarkdown(self, md, md_globals):
WIKILINK_RE = r"\[\[([\w0-9_ -]+)(\|[^\]]+)?\]\]"
md.inlinePatterns.add("wikilinks",
WikiLinksPattern(md, WIKILINK_RE, self.project),

View File

@ -45,7 +45,6 @@ from taiga.projects.epics.models import Epic
from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.issues.models import Issue
from taiga.projects.likes.mixins.viewsets import LikedResourceMixin, FansViewSetMixin
from taiga.projects.notifications.apps import signal_members_added
from taiga.projects.notifications.mixins import WatchersViewSetMixin
from taiga.projects.notifications.choices import NotifyLevel
from taiga.projects.mixins.on_destroy import MoveOnDestroyMixin
@ -113,8 +112,8 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin,
qs = project_utils.attach_notify_policies(qs)
qs = project_utils.attach_is_fan(qs, user=self.request.user)
qs = project_utils.attach_my_role_permissions(qs, user=self.request.user)
qs = project_utils.attach_my_role_permissions(qs, user=self.request.user)
qs = project_utils.attach_closed_milestones(qs)
qs = project_utils.attach_my_homepage(qs, user=self.request.user)
else:
qs = project_utils.attach_extra_info(qs, user=self.request.user)
@ -981,10 +980,6 @@ class MembershipViewSet(BlockedByProjectMixin, ModelCrudViewSet):
invitation_extra_text=invitation_extra_text,
callback=self.post_save,
precall=self.pre_save)
signal_members_added.send(sender=self.__class__,
user=self.request.user,
project=project,
new_members=members)
except exc.ValidationError as err:
return response.BadRequest(err.message_dict)

View File

@ -53,17 +53,17 @@ def connect_memberships_signals():
sender=apps.get_model("projects", "Membership"),
dispatch_uid='membership_pre_delete')
# On membership object is created, reorder and create notify policies
signals.post_save.connect(handlers.membership_post_save,
# On membership object is deleted, update notify policies of all objects relation.
signals.post_save.connect(handlers.create_notify_policy,
sender=apps.get_model("projects", "Membership"),
dispatch_uid='membership_post_save')
dispatch_uid='create-notify-policy')
def disconnect_memberships_signals():
signals.pre_delete.disconnect(sender=apps.get_model("projects", "Membership"),
dispatch_uid='membership_pre_delete')
signals.post_save.disconnect(sender=apps.get_model("projects", "Membership"),
dispatch_uid='membership_post_save')
dispatch_uid='create-notify-policy')
## US Statuses Signals

View File

@ -15,13 +15,12 @@
from django.conf import settings
from taiga.base.utils.thumbnails import get_thumbnail_url, get_thumbnail
from taiga.base.utils.thumbnails import get_thumbnail_url
def get_timeline_image_thumbnail_name(attachment):
def get_timeline_image_thumbnail_url(attachment):
if attachment.attached_file:
thumbnail = get_thumbnail(attachment.attached_file, settings.THN_ATTACHMENT_TIMELINE)
return thumbnail.name if thumbnail else None
return get_thumbnail_url(attachment.attached_file, settings.THN_ATTACHMENT_TIMELINE)
return None
@ -30,7 +29,6 @@ def get_card_image_thumbnail_url(attachment):
return get_thumbnail_url(attachment.attached_file, settings.THN_ATTACHMENT_CARD)
return None
def get_attachment_image_preview_url(attachment):
if attachment.attached_file:
return get_thumbnail_url(attachment.attached_file, settings.THN_ATTACHMENT_PREVIEW)

View File

@ -24,17 +24,11 @@ MULTILINE_TYPE = "multiline"
RICHTEXT_TYPE = "richtext"
DATE_TYPE = "date"
URL_TYPE = "url"
DROPDOWN_TYPE = "dropdown"
CHECKBOX_TYPE = "checkbox"
NUMBER_TYPE = "number"
TYPES_CHOICES = (
(TEXT_TYPE, _("Text")),
(MULTILINE_TYPE, _("Multi-Line Text")),
(RICHTEXT_TYPE, _("Rich text")),
(DATE_TYPE, _("Date")),
(URL_TYPE, _("Url")),
(DROPDOWN_TYPE, _("Dropdown")),
(CHECKBOX_TYPE, _("Checkbox")),
(NUMBER_TYPE, _("Number")),
(URL_TYPE, _("Url"))
)

View File

@ -1,57 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2018-10-22 16:24
from __future__ import unicode_literals
import django.core.serializers.json
from django.db import migrations, models
import taiga.base.db.models.fields.json
class Migration(migrations.Migration):
dependencies = [
('custom_attributes', '0012_auto_20161201_1628'),
]
operations = [
migrations.AddField(
model_name='epiccustomattribute',
name='extra',
field=taiga.base.db.models.fields.json.JSONField(blank=True, default=None, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True),
),
migrations.AddField(
model_name='issuecustomattribute',
name='extra',
field=taiga.base.db.models.fields.json.JSONField(blank=True, default=None, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True),
),
migrations.AddField(
model_name='taskcustomattribute',
name='extra',
field=taiga.base.db.models.fields.json.JSONField(blank=True, default=None, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True),
),
migrations.AddField(
model_name='userstorycustomattribute',
name='extra',
field=taiga.base.db.models.fields.json.JSONField(blank=True, default=None, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True),
),
migrations.AlterField(
model_name='epiccustomattribute',
name='type',
field=models.CharField(choices=[('text', 'Text'), ('multiline', 'Multi-Line Text'), ('richtext', 'Rich text'), ('date', 'Date'), ('url', 'Url'), ('dropdown', 'Dropdown')], default='text', max_length=16, verbose_name='type'),
),
migrations.AlterField(
model_name='issuecustomattribute',
name='type',
field=models.CharField(choices=[('text', 'Text'), ('multiline', 'Multi-Line Text'), ('richtext', 'Rich text'), ('date', 'Date'), ('url', 'Url'), ('dropdown', 'Dropdown')], default='text', max_length=16, verbose_name='type'),
),
migrations.AlterField(
model_name='taskcustomattribute',
name='type',
field=models.CharField(choices=[('text', 'Text'), ('multiline', 'Multi-Line Text'), ('richtext', 'Rich text'), ('date', 'Date'), ('url', 'Url'), ('dropdown', 'Dropdown')], default='text', max_length=16, verbose_name='type'),
),
migrations.AlterField(
model_name='userstorycustomattribute',
name='type',
field=models.CharField(choices=[('text', 'Text'), ('multiline', 'Multi-Line Text'), ('richtext', 'Rich text'), ('date', 'Date'), ('url', 'Url'), ('dropdown', 'Dropdown')], default='text', max_length=16, verbose_name='type'),
),
]

View File

@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2018-10-25 07:11
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('custom_attributes', '0013_auto_20181022_1624'),
]
operations = [
migrations.AlterField(
model_name='epiccustomattribute',
name='type',
field=models.CharField(choices=[('text', 'Text'), ('multiline', 'Multi-Line Text'), ('richtext', 'Rich text'), ('date', 'Date'), ('url', 'Url'), ('dropdown', 'Dropdown'), ('checkbox', 'Checkbox'), ('number', 'Number')], default='text', max_length=16, verbose_name='type'),
),
migrations.AlterField(
model_name='issuecustomattribute',
name='type',
field=models.CharField(choices=[('text', 'Text'), ('multiline', 'Multi-Line Text'), ('richtext', 'Rich text'), ('date', 'Date'), ('url', 'Url'), ('dropdown', 'Dropdown'), ('checkbox', 'Checkbox'), ('number', 'Number')], default='text', max_length=16, verbose_name='type'),
),
migrations.AlterField(
model_name='taskcustomattribute',
name='type',
field=models.CharField(choices=[('text', 'Text'), ('multiline', 'Multi-Line Text'), ('richtext', 'Rich text'), ('date', 'Date'), ('url', 'Url'), ('dropdown', 'Dropdown'), ('checkbox', 'Checkbox'), ('number', 'Number')], default='text', max_length=16, verbose_name='type'),
),
migrations.AlterField(
model_name='userstorycustomattribute',
name='type',
field=models.CharField(choices=[('text', 'Text'), ('multiline', 'Multi-Line Text'), ('richtext', 'Rich text'), ('date', 'Date'), ('url', 'Url'), ('dropdown', 'Dropdown'), ('checkbox', 'Checkbox'), ('number', 'Number')], default='text', max_length=16, verbose_name='type'),
),
]

View File

@ -41,7 +41,7 @@ class AbstractCustomAttribute(models.Model):
order = models.BigIntegerField(null=False, blank=False, default=timestamp_ms, verbose_name=_("order"))
project = models.ForeignKey("projects.Project", null=False, blank=False, related_name="%(class)ss",
verbose_name=_("project"))
extra = JSONField(blank=True, default=None, null=True)
created_date = models.DateTimeField(null=False, blank=False, default=timezone.now,
verbose_name=_("created date"))
modified_date = models.DateTimeField(null=False, blank=False,

View File

@ -32,7 +32,6 @@ class BaseCustomAttributeSerializer(serializers.LightSerializer):
type = Field()
order = Field()
project = Field(attr="project_id")
extra = Field()
created_date = Field()
modified_date = Field()

View File

@ -24,8 +24,6 @@ from taiga.base import response
from taiga.base.decorators import detail_route
from taiga.base.api import ReadOnlyListViewSet
from taiga.mdrender.service import render as mdrender
from taiga.projects.notifications import services as notifications_services
from taiga.projects.notifications.apps import signal_mentions
from . import permissions
from . import serializers
@ -59,11 +57,6 @@ class HistoryViewSet(ReadOnlyListViewSet):
return response.Ok(serializer.data)
def _get_new_mentions(self, obj: object, old_comment: str, new_comment: str):
old_mentions = notifications_services.get_mentions(obj.project, old_comment)
submitted_mentions = notifications_services.get_mentions(obj, new_comment)
return list(set(submitted_mentions) - set(old_mentions))
@detail_route(methods=['get'])
def comment_versions(self, request, pk):
obj = self.get_object()
@ -113,20 +106,11 @@ class HistoryViewSet(ReadOnlyListViewSet):
}
})
new_mentions = self._get_new_mentions(obj, history_entry.comment, comment)
history_entry.edit_comment_date = timezone.now()
history_entry.comment = comment
history_entry.comment_html = mdrender(obj.project, comment)
history_entry.comment_versions = comment_versions
history_entry.save()
if new_mentions:
signal_mentions.send(sender=self.__class__,
user=self.request.user,
obj=obj,
mentions=new_mentions)
return response.Ok()
@detail_route(methods=['post'])
@ -181,17 +165,6 @@ class HistoryViewSet(ReadOnlyListViewSet):
self.check_permissions(request, "retrieve", obj)
qs = services.get_history_queryset_by_model_instance(obj)
qs = services.prefetch_owners_in_history_queryset(qs)
history_type = self.request.GET.get('type')
if history_type == 'activity':
qs = qs.filter(diff__isnull=False, comment__exact='').exclude(diff__exact='')
if self.request.GET.get(self.page_kwarg):
qs = qs.order_by("-created_at")
page = self.paginate_queryset(qs)
serializer = self.get_pagination_serializer(page)
return response.Ok(serializer.data)
return self.response_for_queryset(qs)

View File

@ -28,7 +28,7 @@ from taiga.base.utils.iterators import as_tuple
from taiga.base.utils.iterators import as_dict
from taiga.mdrender.service import render as mdrender
from taiga.projects.attachments.services import get_timeline_image_thumbnail_name
from taiga.projects.attachments.services import get_timeline_image_thumbnail_url
import os
@ -198,14 +198,13 @@ def _generic_extract(obj:object, fields:list, default=None) -> dict:
@as_tuple
def extract_attachments(obj) -> list:
for attach in obj.attachments.all():
# Force the creation of a thumbnail for the timeline
thumbnail_file = get_timeline_image_thumbnail_name(attach)
thumb_url = get_timeline_image_thumbnail_url(attach)
yield {"id": attach.id,
"filename": os.path.basename(attach.attached_file.name),
"url": attach.attached_file.url,
"attached_file": str(attach.attached_file),
"thumbnail_file": thumbnail_file,
"thumb_url": thumb_url,
"is_deprecated": attach.is_deprecated,
"description": attach.description,
"order": attach.order}

View File

@ -30,7 +30,7 @@ from .choices import HistoryType
from .choices import HISTORY_TYPE_CHOICES
from taiga.base.utils.diff import make_diff as make_diff_from_dicts
from taiga.projects.custom_attributes.choices import CHECKBOX_TYPE, NUMBER_TYPE, TEXT_TYPE
from taiga.projects.custom_attributes.choices import TEXT_TYPE
# This keys has been removed from freeze_impl so we can have objects where the
# previous diff has value for the attribute and we want to prevent their propagation
@ -262,19 +262,13 @@ class HistoryEntry(models.Model):
if aid in oldcustattrs and aid in newcustattrs:
changes = make_diff_from_dicts(oldcustattrs[aid], newcustattrs[aid],
excluded_keys=("name"))
newcustattr = newcustattrs.get(aid, {})
if changes:
change_type = newcustattr.get("type", TEXT_TYPE)
if change_type in [NUMBER_TYPE, CHECKBOX_TYPE]:
old_value = oldcustattrs[aid].get("value")
new_value = newcustattrs[aid].get("value")
value_diff = [old_value, new_value]
else:
old_value = oldcustattrs[aid].get("value", "")
new_value = newcustattrs[aid].get("value", "")
value_diff = get_diff_of_htmls(old_value,
new_value)
old_value = oldcustattrs[aid].get("value", "")
new_value = newcustattrs[aid].get("value", "")
value_diff = get_diff_of_htmls(old_value, new_value)
change = {
"name": newcustattr.get("name", ""),
"changes": changes,
@ -285,15 +279,8 @@ class HistoryEntry(models.Model):
elif aid in oldcustattrs and aid not in newcustattrs:
custom_attributes["deleted"].append(oldcustattrs[aid])
elif aid not in oldcustattrs and aid in newcustattrs:
newcustattr = newcustattrs.get(aid, {})
change_type = newcustattr.get("type", TEXT_TYPE)
if change_type in [NUMBER_TYPE, CHECKBOX_TYPE]:
old_value = None
new_value = newcustattrs[aid].get("value")
value_diff = [old_value, new_value]
else:
new_value = newcustattrs[aid].get("value", "")
value_diff = get_diff_of_htmls("", new_value)
new_value = newcustattrs[aid].get("value", "")
value_diff = get_diff_of_htmls("", new_value)
newcustattrs[aid]["value_diff"] = value_diff
custom_attributes["new"].append(newcustattrs[aid])

View File

@ -28,12 +28,9 @@ from taiga.base.api.mixins import BlockedByProjectMixin
from taiga.base.api.utils import get_object_or_404
from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.milestones.models import Milestone
from taiga.projects.mixins.by_ref import ByRefMixin
from taiga.projects.models import Project, IssueStatus, Severity, Priority, IssueType
from taiga.projects.notifications.mixins import AssignedToSignalMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchersViewSetMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
from taiga.projects.occ import OCCResourceMixin
from taiga.projects.tagging.api import TaggedResourceMixin
from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin
@ -47,10 +44,8 @@ from . import serializers
from . import validators
class IssueViewSet(AssignedToSignalMixin, OCCResourceMixin, VotedResourceMixin,
HistoryResourceMixin, WatchedResourceMixin, ByRefMixin,
TaggedResourceMixin, BlockedByProjectMixin,
ModelCrudViewSet):
class IssueViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
ByRefMixin, TaggedResourceMixin, BlockedByProjectMixin, ModelCrudViewSet):
validator_class = validators.IssueValidator
queryset = models.Issue.objects.all()
permission_classes = (permissions.IssuePermission, )
@ -253,22 +248,6 @@ class IssueViewSet(AssignedToSignalMixin, OCCResourceMixin, VotedResourceMixin,
return response.BadRequest(validator.errors)
@list_route(methods=["POST"])
def bulk_update_milestone(self, request, **kwargs):
validator = validators.UpdateMilestoneBulkValidator(data=request.DATA)
if not validator.is_valid():
return response.BadRequest(validator.errors)
data = validator.data
project = get_object_or_404(Project, pk=data["project_id"])
milestone = get_object_or_404(Milestone, pk=data["milestone_id"])
self.check_permissions(request, "bulk_update_milestone", project)
ret = services.update_issues_milestone_in_bulk(data["bulk_issues"], milestone)
return response.Ok(ret)
class IssueVotersViewSet(VotersViewSetMixin, ModelListViewSet):
permission_classes = (permissions.IssueVotersPermission,)

View File

@ -25,11 +25,6 @@ def connect_issues_signals():
from taiga.projects.tagging import signals as tagging_handlers
from . import signals as handlers
# Cached prev object version
signals.pre_save.connect(handlers.cached_prev_issue,
sender=apps.get_model("issues", "Issue"),
dispatch_uid="cached_prev_issue")
# Finished date
signals.pre_save.connect(handlers.set_finished_date_when_edit_issue,
sender=apps.get_model("issues", "Issue"),
@ -40,14 +35,6 @@ def connect_issues_signals():
sender=apps.get_model("issues", "Issue"),
dispatch_uid="tags_normalization_issue")
# Open/Close US and Milestone
signals.post_save.connect(handlers.try_to_close_or_open_milestone_when_create_or_edit_issue,
sender=apps.get_model("issues", "Issue"),
dispatch_uid="try_to_close_or_open_milestone_when_create_or_edit_issue")
signals.post_delete.connect(handlers.try_to_close_milestone_when_delete_issue,
sender=apps.get_model("issues", "Issue"),
dispatch_uid="try_to_close_milestone_when_delete_issue")
def connect_issues_custom_attributes_signals():
from taiga.projects.custom_attributes import signals as custom_attributes_handlers
@ -63,19 +50,11 @@ def connect_all_issues_signals():
def disconnect_issues_signals():
signals.pre_save.disconnect(sender=apps.get_model("issues", "Issue"),
dispatch_uid="cached_prev_issue")
signals.pre_save.disconnect(sender=apps.get_model("issues", "Issue"),
dispatch_uid="set_finished_date_when_edit_issue")
signals.pre_save.disconnect(sender=apps.get_model("issues", "Issue"),
dispatch_uid="tags_normalization_issue")
signals.post_save.disconnect(sender=apps.get_model("issues", "Issue"),
dispatch_uid="try_to_close_or_open_milestone_when_create_or_edit_issue")
signals.post_delete.disconnect(sender=apps.get_model("issues", "Issue"),
dispatch_uid="try_to_close_milestone_when_delete_issue")
def disconnect_issues_custom_attributes_signals():
signals.post_save.disconnect(sender=apps.get_model("issues", "Issue"),

View File

@ -35,7 +35,6 @@ class IssuePermission(TaigaResourcePermission):
filters_data_perms = AllowAny()
csv_perms = AllowAny()
bulk_create_perms = HasProjectPerm('add_issue')
bulk_update_milestone_perms = HasProjectPerm('modify_issue')
delete_comment_perms= HasProjectPerm('modify_issue')
upvote_perms = IsAuthenticated() & HasProjectPerm('view_issues')
downvote_perms = IsAuthenticated() & HasProjectPerm('view_issues')

View File

@ -26,9 +26,6 @@ from django.db import connection
from django.utils.translation import ugettext as _
from taiga.base.utils import db, text
from taiga.events import events
from taiga.projects.history.services import take_snapshot
from taiga.projects.issues.apps import (
connect_issues_signals,
disconnect_issues_signals)
@ -75,38 +72,10 @@ def create_issues_in_bulk(bulk_data, callback=None, precall=None, **additional_f
return issues
def snapshot_issues_in_bulk(bulk_data, user):
for issue_data in bulk_data:
try:
issue = models.Issue.objects.get(pk=issue_data['issue_id'])
take_snapshot(issue, user=user)
except models.Issue.DoesNotExist:
pass
def update_issues_milestone_in_bulk(bulk_data: list, milestone: object):
"""
Update the milestone some issues adding
`bulk_data` should be a list of dicts with the following format:
[{'task_id': <value>}, ...]
"""
issue_milestones = {e["issue_id"]: milestone.id for e in bulk_data}
issue_ids = issue_milestones.keys()
events.emit_event_for_ids(ids=issue_ids,
content_type="issues.issues",
projectid=milestone.project.pk)
db.update_attr_in_bulk_for_ids(issue_milestones, "milestone_id",
model=models.Issue)
return issue_milestones
#####################################################
# CSV
#####################################################
def issues_to_csv(project, queryset):
csv_data = io.StringIO()
fieldnames = ["id", "ref", "subject", "description", "sprint_id", "sprint",

View File

@ -16,22 +16,9 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from contextlib import suppress
from django.core.exceptions import ObjectDoesNotExist
from django.utils import timezone
####################################
# Signals for cached prev task
####################################
# Define the previous version of the task for use it on the post_save handler
def cached_prev_issue(sender, instance, **kwargs):
instance.prev = None
if instance.id:
instance.prev = sender.objects.get(id=instance.id)
####################################
# Signals for set finished date
####################################
@ -43,48 +30,3 @@ def set_finished_date_when_edit_issue(sender, instance, **kwargs):
instance.finished_date = timezone.now()
elif not instance.status.is_closed and instance.finished_date:
instance.finished_date = None
def try_to_close_or_open_milestone_when_create_or_edit_issue(sender, instance, created, **kwargs):
if instance._importing:
return
_try_to_close_or_open_milestone_when_create_or_edit_issue(instance)
def try_to_close_milestone_when_delete_issue(sender, instance, **kwargs):
if instance._importing:
return
_try_to_close_milestone_when_delete_issue(instance)
# Milestone
def _try_to_close_or_open_milestone_when_create_or_edit_issue(instance):
if instance._importing:
return
from taiga.projects.milestones import services as milestone_service
if instance.milestone_id:
if milestone_service.calculate_milestone_is_closed(instance.milestone):
milestone_service.close_milestone(instance.milestone)
else:
milestone_service.open_milestone(instance.milestone)
if instance.prev and instance.prev.milestone_id and instance.prev.milestone_id != instance.milestone_id:
if milestone_service.calculate_milestone_is_closed(instance.prev.milestone):
milestone_service.close_milestone(instance.prev.milestone)
else:
milestone_service.open_milestone(instance.prev.milestone)
def _try_to_close_milestone_when_delete_issue(instance):
if instance._importing:
return
from taiga.projects.milestones import services as milestone_service
with suppress(ObjectDoesNotExist):
if instance.milestone_id and milestone_service.calculate_milestone_is_closed(instance.milestone):
milestone_service.close_milestone(instance.milestone)

View File

@ -15,13 +15,10 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.utils.translation import ugettext as _
from taiga.base.api import serializers
from taiga.base.api import validators
from taiga.base.exceptions import ValidationError
from taiga.base.fields import PgArrayField
from taiga.projects.milestones.models import Milestone
from taiga.projects.mixins.validators import AssignedToValidator
from taiga.projects.notifications.mixins import EditableWatchedResourceSerializer
from taiga.projects.notifications.validators import WatchersValidator
@ -46,35 +43,3 @@ class IssuesBulkValidator(ProjectExistsValidator, validators.Validator):
project_id = serializers.IntegerField()
milestone_id = serializers.IntegerField(required=False)
bulk_issues = serializers.CharField()
# Milestone bulk validators
class _IssueMilestoneBulkValidator(validators.Validator):
issue_id = serializers.IntegerField()
class UpdateMilestoneBulkValidator(ProjectExistsValidator, validators.Validator):
project_id = serializers.IntegerField()
milestone_id = serializers.IntegerField()
bulk_issues = _IssueMilestoneBulkValidator(many=True)
def validate_milestone_id(self, attrs, source):
filters = {
"project__id": attrs["project_id"],
"id": attrs[source]
}
if not Milestone.objects.filter(**filters).exists():
raise ValidationError(_("The milestone isn't valid for the project"))
return attrs
def validate_bulk_tasks(self, attrs, source):
filters = {
"project__id": attrs["project_id"],
"id__in": [issue["issue_id"] for issue in attrs[source]]
}
if models.Issue.objects.filter(**filters).count() != len(filters["id__in"]):
raise ValidationError(_("All the issues must be from the same project"))
return attrs

View File

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2018-09-18 13:55
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('projects', '0060_auto_20180614_1338'),
]
operations = [
migrations.AlterUniqueTogether(
name='issuestatus',
unique_together=set([('project', 'name'), ('project', 'slug')]),
),
]

View File

@ -27,17 +27,11 @@ from taiga.base.api.mixins import BlockedByProjectMixin
from taiga.base.api.utils import get_object_or_404
from taiga.base.utils.db import get_object_or_none
from taiga.projects.models import Project
from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchersViewSetMixin
from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.tasks.validators import UpdateMilestoneBulkValidator as \
TasksUpdateMilestoneValidator
from taiga.projects.issues.validators import UpdateMilestoneBulkValidator as \
IssuesUpdateMilestoneValidator
from . import serializers
from . import services
from . import validators
from . import models
from . import permissions
@ -148,69 +142,6 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin,
return response.Ok(milestone_stats)
@detail_route(methods=["POST"])
def move_userstories_to_sprint(self, request, pk=None, **kwargs):
milestone = get_object_or_404(models.Milestone, pk=pk)
self.check_permissions(request, "move_related_items", milestone)
validator = validators.UpdateMilestoneBulkValidator(data=request.DATA)
if not validator.is_valid():
return response.BadRequest(validator.errors)
data = validator.data
project = get_object_or_404(Project, pk=data["project_id"])
milestone_result = get_object_or_404(models.Milestone, pk=data["milestone_id"])
if data["bulk_stories"]:
self.check_permissions(request, "move_uss_to_sprint", project)
services.update_userstories_milestone_in_bulk(data["bulk_stories"], milestone_result)
services.snapshot_userstories_in_bulk(data["bulk_stories"], request.user)
return response.NoContent()
@detail_route(methods=["POST"])
def move_tasks_to_sprint(self, request, pk=None, **kwargs):
milestone = get_object_or_404(models.Milestone, pk=pk)
self.check_permissions(request, "move_related_items", milestone)
validator = TasksUpdateMilestoneValidator(data=request.DATA)
if not validator.is_valid():
return response.BadRequest(validator.errors)
data = validator.data
project = get_object_or_404(Project, pk=data["project_id"])
milestone_result = get_object_or_404(models.Milestone, pk=data["milestone_id"])
if data["bulk_tasks"]:
self.check_permissions(request, "move_tasks_to_sprint", project)
services.update_tasks_milestone_in_bulk(data["bulk_tasks"], milestone_result)
services.snapshot_tasks_in_bulk(data["bulk_tasks"], request.user)
return response.NoContent()
@detail_route(methods=["POST"])
def move_issues_to_sprint(self, request, pk=None, **kwargs):
milestone = get_object_or_404(models.Milestone, pk=pk)
self.check_permissions(request, "move_related_items", milestone)
validator = IssuesUpdateMilestoneValidator(data=request.DATA)
if not validator.is_valid():
return response.BadRequest(validator.errors)
data = validator.data
project = get_object_or_404(Project, pk=data["project_id"])
milestone_result = get_object_or_404(models.Milestone, pk=data["milestone_id"])
if data["bulk_issues"]:
self.check_permissions(request, "move_issues_to_sprint", project)
services.update_issues_milestone_in_bulk(data["bulk_issues"], milestone_result)
services.snapshot_issues_in_bulk(data["bulk_issues"], request.user)
return response.NoContent()
class MilestoneWatchersViewSet(WatchersViewSetMixin, ModelListViewSet):
permission_classes = (permissions.MilestoneWatchersPermission,)

View File

@ -33,11 +33,6 @@ class MilestonePermission(TaigaResourcePermission):
stats_perms = HasProjectPerm('view_milestones')
watch_perms = IsAuthenticated() & HasProjectPerm('view_milestones')
unwatch_perms = IsAuthenticated() & HasProjectPerm('view_milestones')
move_related_items_perms = HasProjectPerm('modify_milestone')
move_uss_to_sprint_perms = HasProjectPerm('modify_us')
move_tasks_to_sprint_perms = HasProjectPerm('modify_task')
move_issues_to_sprint_perms = HasProjectPerm('modify_issue')
class MilestoneWatchersPermission(TaigaResourcePermission):
enought_perms = IsProjectAdmin() | IsSuperUser()

View File

@ -16,29 +16,16 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.utils import db
from taiga.events import events
from taiga.projects.history.services import take_snapshot
from taiga.projects.services import apply_order_updates
from taiga.projects.issues.models import Issue
from taiga.projects.tasks.models import Task
from taiga.projects.userstories.models import UserStory
from django.utils import timezone
from . import models
def calculate_milestone_is_closed(milestone):
all_us_closed = all([user_story.is_closed for user_story in milestone.user_stories.all()])
all_tasks_closed = all([task.status is not None and task.status.is_closed for task in
milestone.tasks.all()])
all_issues_closed = all([issue.is_closed for issue in milestone.issues.all()])
uss_check = milestone.user_stories.all().count() > 0 \
and all_tasks_closed and all_us_closed and all_issues_closed
issues_check = milestone.issues.all().count() > 0 and all_issues_closed \
and all_tasks_closed and all_us_closed
tasks_check = milestone.tasks.all().count() > 0 and all_tasks_closed \
and all_issues_closed and all_us_closed
return uss_check or issues_check or tasks_check
return (milestone.user_stories.all().count() > 0 and
all([task.status is not None and task.status.is_closed for task in milestone.tasks.all()]) and
all([user_story.is_closed for user_story in milestone.user_stories.all()]))
def close_milestone(milestone):
@ -51,137 +38,3 @@ def open_milestone(milestone):
if milestone.closed:
milestone.closed = False
milestone.save(update_fields=["closed",])
def update_userstories_milestone_in_bulk(bulk_data: list, milestone: object):
"""
Update the milestone and the milestone order of some user stories adding
the extra orders needed to keep consistency.
`bulk_data` should be a list of dicts with the following format:
[{'us_id': <value>, 'order': <value>}, ...]
"""
user_stories = milestone.user_stories.all()
us_orders = {us.id: getattr(us, "sprint_order") for us in user_stories}
new_us_orders = {}
for e in bulk_data:
new_us_orders[e["us_id"]] = e["order"]
# The base orders where we apply the new orders must containg all
# the values
us_orders[e["us_id"]] = e["order"]
apply_order_updates(us_orders, new_us_orders)
us_milestones = {e["us_id"]: milestone.id for e in bulk_data}
user_story_ids = us_milestones.keys()
events.emit_event_for_ids(ids=user_story_ids,
content_type="userstories.userstory",
projectid=milestone.project.pk)
us_instance_list = []
us_values = []
for us_id in user_story_ids:
us = UserStory.objects.get(pk=us_id)
us_instance_list.append(us)
us_values.append({'milestone_id': milestone.id})
db.update_in_bulk(us_instance_list, us_values)
db.update_attr_in_bulk_for_ids(us_orders, "sprint_order", UserStory)
# Updating the milestone for the tasks
Task.objects.filter(
user_story_id__in=[e["us_id"] for e in bulk_data]).update(
milestone=milestone)
return us_orders
def snapshot_userstories_in_bulk(bulk_data, user):
for us_data in bulk_data:
try:
us = UserStory.objects.get(pk=us_data['us_id'])
take_snapshot(us, user=user)
except UserStory.DoesNotExist:
pass
def update_tasks_milestone_in_bulk(bulk_data: list, milestone: object):
"""
Update the milestone and the milestone order of some tasks adding
the extra orders needed to keep consistency.
`bulk_data` should be a list of dicts with the following format:
[{'task_id': <value>, 'order': <value>}, ...]
"""
tasks = milestone.tasks.all()
task_orders = {task.id: getattr(task, "taskboard_order") for task in tasks}
new_task_orders = {}
for e in bulk_data:
new_task_orders[e["task_id"]] = e["order"]
# The base orders where we apply the new orders must containg all
# the values
task_orders[e["task_id"]] = e["order"]
apply_order_updates(task_orders, new_task_orders)
task_milestones = {e["task_id"]: milestone.id for e in bulk_data}
task_ids = task_milestones.keys()
events.emit_event_for_ids(ids=task_ids,
content_type="tasks.task",
projectid=milestone.project.pk)
task_instance_list = []
task_values = []
for task_id in task_ids:
task = Task.objects.get(pk=task_id)
task_instance_list.append(task)
task_values.append({'milestone_id': milestone.id})
db.update_in_bulk(task_instance_list, task_values)
db.update_attr_in_bulk_for_ids(task_orders, "taskboard_order", Task)
return task_milestones
def snapshot_tasks_in_bulk(bulk_data, user):
for task_data in bulk_data:
try:
task = Task.objects.get(pk=task_data['task_id'])
take_snapshot(task, user=user)
except Task.DoesNotExist:
pass
def update_issues_milestone_in_bulk(bulk_data: list, milestone: object):
"""
Update the milestone some issues adding
`bulk_data` should be a list of dicts with the following format:
[{'task_id': <value>}, ...]
"""
issue_milestones = {e["issue_id"]: milestone.id for e in bulk_data}
issue_ids = issue_milestones.keys()
events.emit_event_for_ids(ids=issue_ids,
content_type="issues.issues",
projectid=milestone.project.pk)
issues_instance_list = []
issues_values = []
for issue_id in issue_ids:
issue = Issue.objects.get(pk=issue_id)
issues_instance_list.append(issue)
issues_values.append({'milestone_id': milestone.id})
db.update_in_bulk(issues_instance_list, issues_values)
return issue_milestones
def snapshot_issues_in_bulk(bulk_data, user):
for issue_data in bulk_data:
try:
issue = Issue.objects.get(pk=issue_data['issue_id'])
take_snapshot(issue, user=user)
except Issue.DoesNotExist:
pass

View File

@ -19,17 +19,15 @@
from django.utils.translation import ugettext as _
from taiga.base.exceptions import ValidationError
from taiga.base.api import serializers
from taiga.base.api import validators
from taiga.projects.notifications.validators import WatchersValidator
from taiga.projects.userstories.models import UserStory
from taiga.projects.validators import DuplicatedNameInProjectValidator
from taiga.projects.validators import ProjectExistsValidator
from taiga.projects.notifications.validators import WatchersValidator
from . import models
class MilestoneExistsValidator:
def validate_milestone_id(self, attrs, source):
def validate_sprint_id(self, attrs, source):
value = attrs[source]
if not models.Milestone.objects.filter(pk=value).exists():
msg = _("There's no milestone with that id")
@ -41,28 +39,3 @@ class MilestoneValidator(WatchersValidator, DuplicatedNameInProjectValidator, va
class Meta:
model = models.Milestone
read_only_fields = ("id", "created_date", "modified_date")
# bulk validators
class _UserStoryMilestoneBulkValidator(validators.Validator):
us_id = serializers.IntegerField()
order = serializers.IntegerField()
class UpdateMilestoneBulkValidator(MilestoneExistsValidator,
ProjectExistsValidator,
validators.Validator):
project_id = serializers.IntegerField()
milestone_id = serializers.IntegerField()
bulk_stories = _UserStoryMilestoneBulkValidator(many=True)
def validate_bulk_stories(self, attrs, source):
filters = {
"project__id": attrs["project_id"],
"id__in": [us["us_id"] for us in attrs[source]]
}
if UserStory.objects.filter(**filters).count() != len(filters["id__in"]):
raise ValidationError(_("All the user stories must be from the same project"))
return attrs

View File

@ -1,16 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2018 Taiga Agile LLC
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
default_app_config = "taiga.projects.notifications.apps.NotificationsAppConfig"

View File

@ -17,12 +17,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.db.models import Q
from django.utils import timezone
from taiga.base import response
from taiga.base.api import ModelCrudViewSet
from taiga.base.api import GenericViewSet
from taiga.base.api.utils import get_object_or_404
from taiga.projects.notifications.choices import NotifyLevel
from taiga.projects.models import Project
@ -54,53 +50,3 @@ class NotifyPolicyViewSet(ModelCrudViewSet):
return models.NotifyPolicy.objects.filter(user=self.request.user).filter(
Q(project__owner=self.request.user) | Q(project__memberships__user=self.request.user)
).distinct()
class WebNotificationsViewSet(GenericViewSet):
serializer_class = serializers.WebNotificationSerializer
resource_model = models.WebNotification
def check_permissions(self, request, obj=None):
return obj and request.user.is_authenticated() and \
request.user.pk == obj.user_id
def list(self, request):
if self.request.user.is_anonymous():
return response.Ok({})
queryset = models.WebNotification.objects\
.filter(user=self.request.user)
if request.GET.get("only_unread", False):
queryset = queryset.filter(read__isnull=True)
queryset = queryset.order_by('-read', '-created')
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_pagination_serializer(page)
return response.Ok({
"objects": serializer.data,
"total": queryset.count()
})
serializer = self.get_serializer(queryset, many=True)
return response.Ok(serializer.data)
def patch(self, request, *args, **kwargs):
self.check_permissions(request)
resource_id = kwargs.get("resource_id", None)
resource = get_object_or_404(self.resource_model, pk=resource_id)
resource.read = timezone.now()
resource.save()
return response.Ok({})
def post(self, request):
self.check_permissions(request)
models.WebNotification.objects.filter(user=self.request.user)\
.update(read=timezone.now())
return response.Ok()

View File

@ -1,46 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2018 Taiga Agile LLC
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django import dispatch
from django.apps import AppConfig
signal_assigned_to = dispatch.Signal(providing_args=["user", "obj"])
signal_assigned_users = dispatch.Signal(providing_args=["user", "obj",
"new_assigned_users"])
signal_watchers_added = dispatch.Signal(providing_args=["user", "obj",
"new_watchers"])
signal_members_added = dispatch.Signal(providing_args=["user", "project",
"new_members"])
signal_mentions = dispatch.Signal(providing_args=["user", "obj",
"mentions"])
signal_comment = dispatch.Signal(providing_args=["user", "obj",
"watchers"])
signal_comment_mentions = dispatch.Signal(providing_args=["user", "obj",
"mentions"])
class NotificationsAppConfig(AppConfig):
name = "taiga.projects.notifications"
verbose_name = "Notifications"
def ready(self):
from . import signals as handlers
signal_assigned_to.connect(handlers.on_assigned_to)
signal_assigned_users.connect(handlers.on_assigned_users)
signal_watchers_added.connect(handlers.on_watchers_added)
signal_members_added.connect(handlers.on_members_added)
signal_mentions.connect(handlers.on_mentions)
signal_comment.connect(handlers.on_comment)
signal_comment_mentions.connect(handlers.on_comment_mentions)

View File

@ -31,22 +31,3 @@ NOTIFY_LEVEL_CHOICES = (
(NotifyLevel.all, _("All")),
(NotifyLevel.none, _("None")),
)
class WebNotificationType(enum.IntEnum):
assigned = 1
mentioned = 2
added_as_watcher = 3
added_as_member = 4
comment = 5
mentioned_in_comment = 6
WEB_NOTIFICATION_TYPE_CHOICES = (
(WebNotificationType.assigned, _("Assigned")),
(WebNotificationType.mentioned, _("Mentioned")),
(WebNotificationType.added_as_watcher, _("Added as watcher")),
(WebNotificationType.added_as_member, _("Added as member")),
(WebNotificationType.comment, _("Comment")),
(WebNotificationType.mentioned_in_comment, _("Mentioned in comment")),
)

View File

@ -1,37 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2018-10-10 11:24
from __future__ import unicode_literals
from django.conf import settings
import django.core.serializers.json
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import taiga.base.db.models.fields.json
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('notifications', '0007_notifypolicy_live_notify_level'),
]
operations = [
migrations.CreateModel(
name='WebNotification',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now)),
('read', models.DateTimeField(default=None, null=True)),
('event_type', models.PositiveIntegerField()),
('data', taiga.base.db.models.fields.json.JSONField(encoder=django.core.serializers.json.DjangoJSONEncoder)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='web_notifications', to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='notifypolicy',
name='web_notify_level',
field=models.BooleanField(default=True),
),
]

View File

@ -29,12 +29,6 @@ from taiga.base.api.utils import get_object_or_404
from taiga.base.fields import WatchersField, MethodField
from taiga.projects.notifications import services
from . apps import signal_assigned_to
from . apps import signal_assigned_users
from . apps import signal_comment
from . apps import signal_comment_mentions
from . apps import signal_mentions
from . apps import signal_watchers_added
from . serializers import WatcherSerializer
@ -53,8 +47,6 @@ class WatchedResourceMixin:
"""
_not_notify = False
_old_watchers = None
_old_mentions = []
@detail_route(methods=["POST"])
def watch(self, request, pk=None):
@ -94,38 +86,13 @@ class WatchedResourceMixin:
# some text fields for extract mentions and add them
# to watchers before obtain a complete list of
# notifiable users.
services.analize_object_for_watchers(obj, history.comment,
history.owner)
services.analize_object_for_watchers(obj, history.comment, history.owner)
# Get a complete list of notifiable users for current
# object and send the change notification to them.
services.send_notifications(obj, history=history)
def update(self, request, *args, **kwargs):
obj = self.get_object_or_none()
if obj and obj.id:
if hasattr(obj, "watchers"):
self._old_watchers = [
watcher.id for watcher in self.get_object().get_watchers()
]
mention_fields = ['description', 'content']
for field_name in mention_fields:
old_mentions = self._get_old_mentions_in_field(obj, field_name)
if not len(old_mentions):
continue
self._old_mentions = old_mentions
return super().update(request, *args, **kwargs)
def post_save(self, obj, created=False):
self.create_web_notifications_for_added_watchers(obj)
self.create_web_notifications_for_mentioned_users(obj)
mentions = self.create_web_notifications_for_mentions_in_comments(obj)
exclude = mentions + [self.request.user.id]
self.create_web_notifications_for_comment(obj, exclude)
self.send_notifications(obj)
super().post_save(obj, created)
@ -133,84 +100,6 @@ class WatchedResourceMixin:
self.send_notifications(obj)
super().pre_delete(obj)
def create_web_notifications_for_comment(self, obj, exclude: list=None):
if "comment" in self.request.DATA:
watchers = [
watcher_id for watcher_id in obj.watchers
if watcher_id not in exclude
]
signal_comment.send(sender=self.__class__,
user=self.request.user,
obj=obj,
watchers=watchers)
def create_web_notifications_for_added_watchers(self, obj):
if not hasattr(obj, "watchers"):
return
new_watchers = [
watcher_id for watcher_id in obj.watchers
if watcher_id not in self._old_watchers
and watcher_id != self.request.user.id
]
signal_watchers_added.send(sender=self.__class__,
user=self.request.user,
obj=obj,
new_watchers=new_watchers)
def create_web_notifications_for_mentioned_users(self, obj):
"""
Detect and notify mentioned users
"""
submitted_mentions = self._get_submitted_mentions(obj)
new_mentions = list(set(submitted_mentions) - set(self._old_mentions))
if new_mentions:
signal_mentions.send(sender=self.__class__,
user=self.request.user,
obj=obj,
mentions=new_mentions)
def create_web_notifications_for_mentions_in_comments(self, obj):
"""
Detect and notify mentioned users
"""
new_mentions_in_comment = self._get_mentions_in_comment(obj)
if new_mentions_in_comment:
signal_comment_mentions.send(sender=self.__class__,
user=self.request.user,
obj=obj,
mentions=new_mentions_in_comment)
return [user.id for user in new_mentions_in_comment]
def _get_submitted_mentions(self, obj):
mention_fields = ['description', 'content']
for field_name in mention_fields:
new_mentions = self._get_new_mentions_in_field(obj, field_name)
if len(new_mentions) > 0:
return new_mentions
return []
def _get_mentions_in_comment(self, obj):
comment = self.request.DATA.get('comment')
if comment:
return services.get_mentions(obj, comment)
return []
def _get_old_mentions_in_field(self, obj, field_name):
if not hasattr(obj, field_name):
return []
return services.get_mentions(obj, getattr(obj, field_name))
def _get_new_mentions_in_field(self, obj, field_name):
value = self.request.DATA.get(field_name)
if not value:
return []
return services.get_mentions(obj, value)
class WatchedModelMixin(object):
"""
@ -385,47 +274,3 @@ class WatchersViewSetMixin:
def get_queryset(self):
resource = self.resource_model.objects.get(pk=self.kwargs.get("resource_id"))
return resource.get_watchers()
class AssignedToSignalMixin:
_old_assigned_to = None
def pre_save(self, obj):
if obj.id:
self._old_assigned_to = self.get_object().assigned_to
super().pre_save(obj)
def post_save(self, obj, created=False):
if obj.assigned_to and obj.assigned_to != self._old_assigned_to \
and self.request.user != obj.assigned_to:
signal_assigned_to.send(sender=self.__class__,
user=self.request.user,
obj=obj)
super().post_save(obj, created)
class AssignedUsersSignalMixin:
_old_assigned_users = None
def update(self, request, *args, **kwargs):
obj = self.get_object_or_none()
if hasattr(obj, "assigned_users") and obj.id:
self._old_assigned_users = [
user for user in obj.assigned_users.all()
].copy()
result = super().update(request, *args, **kwargs)
if result and obj.assigned_users:
new_assigned_users = [
user for user in obj.assigned_users.all()
if user not in self._old_assigned_users
and user != self.request.user
]
signal_assigned_users.send(sender=self.__class__,
user=self.request.user,
obj=obj,
new_assigned_users=new_assigned_users)
return result

View File

@ -23,7 +23,6 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from taiga.base.db.models.fields import JSONField
from taiga.projects.history.choices import HISTORY_TYPE_CHOICES
from .choices import NOTIFY_LEVEL_CHOICES, NotifyLevel
@ -38,7 +37,6 @@ class NotifyPolicy(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="notify_policies")
notify_level = models.SmallIntegerField(choices=NOTIFY_LEVEL_CHOICES)
live_notify_level = models.SmallIntegerField(choices=NOTIFY_LEVEL_CHOICES, default=NotifyLevel.involved)
web_notify_level = models.BooleanField(default=True, null=False, blank=True)
created_at = models.DateTimeField(default=timezone.now)
modified_at = models.DateTimeField()
@ -96,11 +94,3 @@ class Watched(models.Model):
verbose_name = _("Watched")
verbose_name_plural = _("Watched")
unique_together = ("content_type", "object_id", "user", "project")
class WebNotification(models.Model):
created = models.DateTimeField(default=timezone.now, db_index=True)
read = models.DateTimeField(default=None, null=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="web_notifications")
event_type = models.PositiveIntegerField()
data = JSONField()

View File

@ -16,13 +16,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.contrib.contenttypes.models import ContentType
from taiga.base.api import serializers
from taiga.base.fields import Field, DateTimeField, MethodField
from taiga.users.gravatar import get_user_gravatar_id
from taiga.users.models import get_user_model_safe
from taiga.users.services import get_user_photo_url, get_user_big_photo_url
from . import models
@ -32,8 +27,7 @@ class NotifyPolicySerializer(serializers.ModelSerializer):
class Meta:
model = models.NotifyPolicy
fields = ('id', 'project', 'project_name', 'notify_level',
'live_notify_level', 'web_notify_level')
fields = ('id', 'project', 'project_name', 'notify_level', "live_notify_level")
def get_project_name(self, obj):
return obj.project.name
@ -45,67 +39,3 @@ class WatcherSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model_safe()
fields = ('id', 'username', 'full_name')
class WebNotificationSerializer(serializers.ModelSerializer):
class Meta:
model = models.WebNotification
fields = ('id', 'event_type', 'user', 'data', 'created', 'read')
class ProjectSerializer(serializers.LightSerializer):
id = Field()
slug = Field()
name = Field()
class ObjectSerializer(serializers.LightSerializer):
id = Field()
ref = MethodField()
subject = MethodField()
content_type = MethodField()
def get_ref(self, obj):
return obj.ref if hasattr(obj, 'ref') else None
def get_subject(self, obj):
return obj.subject if hasattr(obj, 'subject') else None
def get_content_type(self, obj):
content_type = ContentType.objects.get_for_model(obj)
return content_type.model if content_type else None
class UserSerializer(serializers.LightSerializer):
id = Field()
name = MethodField()
photo = MethodField()
big_photo = MethodField()
gravatar_id = MethodField()
username = Field()
is_profile_visible = MethodField()
date_joined = DateTimeField()
def get_name(self, obj):
return obj.get_full_name()
def get_photo(self, obj):
return get_user_photo_url(obj)
def get_big_photo(self, obj):
return get_user_big_photo_url(obj)
def get_gravatar_id(self, obj):
return get_user_gravatar_id(obj)
def get_is_profile_visible(self, obj):
return obj.is_active and not obj.is_system
class NotificationDataSerializer(serializers.LightDictSerializer):
project = ProjectSerializer()
user = UserSerializer()
class ObjectNotificationSerializer(NotificationDataSerializer):
obj = ObjectSerializer()

View File

@ -44,10 +44,6 @@ from .models import HistoryChangeNotification, Watched
from .squashing import squash_history_entries
def remove_lr_cr(s):
return s.replace("\n", "").replace("\r", "")
def notify_policy_exists(project, user) -> bool:
"""
Check if policy exists for specified project
@ -77,8 +73,7 @@ def create_notify_policy(project, user, level=NotifyLevel.involved,
def create_notify_policy_if_not_exists(project, user,
level=NotifyLevel.involved,
live_level=NotifyLevel.involved,
web_level=True):
live_level=NotifyLevel.involved):
"""
Given a project and user, create notification policy for it.
"""
@ -87,11 +82,7 @@ def create_notify_policy_if_not_exists(project, user,
result = model_cls.objects.get_or_create(
project=project,
user=user,
defaults={
"notify_level": level,
"live_notify_level": live_level,
"web_notify_level": web_level
}
defaults={"notify_level": level, "live_notify_level": live_level}
)
return result[0]
except IntegrityError as e:
@ -104,39 +95,27 @@ def analize_object_for_watchers(obj: object, comment: str, user: object):
Generic implementation for analize model objects and
extract mentions from it and add it to watchers.
"""
if not hasattr(obj, "add_watcher"):
if not hasattr(obj, "get_project"):
return
mentions = get_object_mentions(obj, comment)
if mentions:
for user in mentions:
obj.add_watcher(user)
# Adding the person who edited the object to the watchers
if comment and not user.is_system:
obj.add_watcher(user)
def get_object_mentions(obj: object, comment: str):
"""
Generic implementation for analize model objects and
extract mentions from it.
"""
if not hasattr(obj, "get_project"):
if not hasattr(obj, "add_watcher"):
return
texts = (getattr(obj, "description", ""),
getattr(obj, "content", ""),
comment,)
return get_mentions(obj.get_project(), "\n".join(texts))
def get_mentions(project: object, text: str):
from taiga.mdrender.service import render_and_extract
_, data = render_and_extract(project, text)
_, data = render_and_extract(obj.get_project(), "\n".join(texts))
return data.get("mentions")
if data["mentions"]:
for user in data["mentions"]:
obj.add_watcher(user)
# Adding the person who edited the object to the watchers
if comment and not user.is_system:
obj.add_watcher(user)
def _filter_by_permissions(obj, user):
@ -317,11 +296,10 @@ def send_sync_notifications(notification_id):
msg_id = 'taiga-system'
now = datetime.datetime.now()
project_name = remove_lr_cr(notification.project.name)
format_args = {
"unsubscribe_url": resolve_front_url('settings-mail-notifications'),
"project_slug": notification.project.slug,
"project_name": project_name,
"project_name": notification.project.name,
"msg_id": msg_id,
"time": int(now.timestamp()),
"domain": domain

View File

@ -1,141 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2018 Taiga Agile LLC
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.contrib.contenttypes.models import ContentType
from django.db import transaction
from django.utils import timezone
from taiga.events import events
from taiga.events import middleware as mw
from . import choices
from . import models
from . import serializers
def _filter_recipients(project, user, recipients):
notify_policies = models.NotifyPolicy.objects.filter(
user_id__in=recipients,
project=project,
web_notify_level=True).exclude(user_id=user.id).all()
return [notify_policy.user_id for notify_policy in notify_policies]
def _push_to_web_notifications(event_type, data, recipients,
serializer_class=None):
if not serializer_class:
serializer_class = serializers.ObjectNotificationSerializer
serializer = serializer_class(data)
for user_id in recipients:
with transaction.atomic():
models.WebNotification.objects.create(
event_type=event_type.value,
created=timezone.now(),
user_id=user_id,
data=serializer.data,
)
session_id = mw.get_current_session_id()
events.emit_event_for_user_notification(user_id,
session_id=session_id,
event_type=event_type.value,
data=serializer.data)
def on_assigned_to(sender, user, obj, **kwargs):
event_type = choices.WebNotificationType.assigned
data = {
"project": obj.project,
"user": user,
"obj": obj,
}
recipients = _filter_recipients(obj.project, user,
[obj.assigned_to.id])
_push_to_web_notifications(event_type, data, recipients)
def on_assigned_users(sender, user, obj, new_assigned_users, **kwargs):
event_type = choices.WebNotificationType.assigned
data = {
"project": obj.project,
"user": user,
"obj": obj,
}
recipients = _filter_recipients(obj.project, user,
[user.id for user in new_assigned_users])
_push_to_web_notifications(event_type, data, recipients)
def on_watchers_added(sender, user, obj, new_watchers, **kwargs):
event_type = choices.WebNotificationType.added_as_watcher
data = {
"project": obj.project,
"user": user,
"obj": obj,
}
recipients = _filter_recipients(obj.project, user, new_watchers)
_push_to_web_notifications(event_type, data, recipients)
def on_members_added(sender, user, project, new_members, **kwargs):
serializer_class = serializers.NotificationDataSerializer
event_type = choices.WebNotificationType.added_as_member
data = {
"project": project,
"user": user,
}
recipients = _filter_recipients(project, user,
[member.user_id for member in new_members
if member.user_id])
_push_to_web_notifications(event_type, data, recipients, serializer_class)
def on_mentions(sender, user, obj, mentions, **kwargs):
content_type = ContentType.objects.get_for_model(obj)
valid_content_types = ['issue', 'task', 'userstory']
if content_type.model in valid_content_types:
event_type = choices.WebNotificationType.mentioned
data = {
"project": obj.project,
"user": user,
"obj": obj,
}
recipients = _filter_recipients(obj.project, user,
[user.id for user in mentions])
_push_to_web_notifications(event_type, data, recipients)
def on_comment_mentions(sender, user, obj, mentions, **kwargs):
event_type = choices.WebNotificationType.mentioned_in_comment
data = {
"project": obj.project,
"user": user,
"obj": obj,
}
recipients = _filter_recipients(obj.project, user,
[user.id for user in mentions])
_push_to_web_notifications(event_type, data, recipients)
def on_comment(sender, user, obj, watchers, **kwargs):
event_type = choices.WebNotificationType.comment
data = {
"project": obj.project,
"user": user,
"obj": obj,
}
recipients = _filter_recipients(obj.project, user, watchers)
_push_to_web_notifications(event_type, data, recipients)

View File

@ -290,8 +290,6 @@ class ProjectSerializer(serializers.LightSerializer):
is_fan = Field(attr="is_fan_attr")
my_homepage = MethodField()
def get_members(self, obj):
assert hasattr(obj, "members_attr"), "instance must have a members_attr attribute"
if obj.members_attr is None:
@ -376,13 +374,6 @@ class ProjectSerializer(serializers.LightSerializer):
def get_logo_big_url(self, obj):
return services.get_logo_big_thumbnail_url(obj)
def get_my_homepage(self, obj):
assert hasattr(obj, "my_homepage_attr"), "instance must have a my_homepage_attr attribute"
if obj.my_homepage_attr is None:
return False
return obj.my_homepage_attr
class ProjectDetailSerializer(ProjectSerializer):
epic_statuses = Field(attr="epic_statuses_attr")

View File

@ -1,75 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2017 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2017 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2017 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2017 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.db.models import Q
from taiga.base import response
from taiga.base.api import ModelCrudViewSet, ReadOnlyListViewSet
from taiga.projects.settings.choices import HOMEPAGE_CHOICES
from taiga.projects.models import Project
from . import models
from . import permissions
from . import serializers
from . import services
from . import validators
class UserProjectSettingsViewSet(ModelCrudViewSet):
serializer_class = serializers.UserProjectSettingsSerializer
permission_classes = (permissions.UserProjectSettingsPermission,)
validator_class = validators.UserProjectSettingsValidator
def _build_user_project_settings(self):
projects = Project.objects.filter(
Q(owner=self.request.user) |
Q(memberships__user=self.request.user)
).distinct()
for project in projects:
services.create_user_project_settings_if_not_exists(
project, self.request.user)
def get_queryset(self):
if self.request.user.is_anonymous():
return models.UserProjectSettings.objects.none()
self._build_user_project_settings()
return models.UserProjectSettings.objects.filter(user=self.request.user)\
.filter(
Q(project__owner=self.request.user) |
Q(project__memberships__user=self.request.user)
).distinct()
def list(self, request, *args, **kwargs):
qs = self.get_queryset()
project_id = request.QUERY_PARAMS.get("project", None)
if project_id:
qs = qs.filter(project_id=project_id)
serializer = self.get_serializer(qs, many=True)
return response.Ok(serializer.data)
class SectionsViewSet(ReadOnlyListViewSet):
def list(self, request, *args, **kwargs):
return response.Response(HOMEPAGE_CHOICES)

View File

@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2017 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2017 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2017 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2017 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import enum
from django.utils.translation import ugettext_lazy as _
class Section(enum.IntEnum):
timeline = 1
epics = 2
backlog = 3
kanban = 4
issues = 5
wiki = 6
HOMEPAGE_CHOICES = (
(Section.timeline, _("Timeline")),
(Section.epics, _("Epics")),
(Section.backlog, _("Backlog")),
(Section.kanban, _("Kanban")),
(Section.issues, _("Issues")),
(Section.wiki, _("TeamWiki")),
)

View File

@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2018-09-24 11:49
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import taiga.projects.settings.choices
class Migration(migrations.Migration):
initial = True
dependencies = [
('projects', '0061_auto_20180918_1355'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='UserProjectSettings',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('homepage', models.SmallIntegerField(choices=[(taiga.projects.settings.choices.Section(1), 'Timeline'), (taiga.projects.settings.choices.Section(2), 'Epics'), (taiga.projects.settings.choices.Section(3), 'Backlog'), (taiga.projects.settings.choices.Section(4), 'Kanban'), (taiga.projects.settings.choices.Section(5), 'Issues'), (taiga.projects.settings.choices.Section(6), 'TeamWiki')], default=taiga.projects.settings.choices.Section(1))),
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
('modified_at', models.DateTimeField()),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_project_settings', to='projects.Project')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_project_settings', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['created_at'],
},
),
migrations.AlterUniqueTogether(
name='userprojectsettings',
unique_together=set([('project', 'user')]),
),
]

View File

@ -1,49 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2017 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2017 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2017 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2017 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.conf import settings
from django.db import models
from django.utils import timezone
from .choices import HOMEPAGE_CHOICES, Section
class UserProjectSettings(models.Model):
"""
This class represents a persistence for
project user notifications preference.
"""
project = models.ForeignKey("projects.Project", related_name="user_project_settings")
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="user_project_settings")
homepage = models.SmallIntegerField(choices=HOMEPAGE_CHOICES,
default=Section.timeline)
created_at = models.DateTimeField(default=timezone.now)
modified_at = models.DateTimeField()
_importing = None
class Meta:
unique_together = ("project", "user",)
ordering = ["created_at"]
def save(self, *args, **kwargs):
if not self._importing or not self.modified_date:
self.modified_at = timezone.now()
return super().save(*args, **kwargs)

View File

@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2017 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2017 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2017 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2017 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.api import serializers
from . import models
from taiga.projects.settings.utils import get_allowed_sections
class UserProjectSettingsSerializer(serializers.ModelSerializer):
project_name = serializers.SerializerMethodField("get_project_name")
allowed_sections = serializers.SerializerMethodField("get_allowed_sections")
class Meta:
model = models.UserProjectSettings
fields = ('id', 'project', 'project_name', 'homepage', 'allowed_sections')
def get_project_name(self, obj):
return obj.project.name
def get_allowed_sections(self, obj):
return get_allowed_sections(obj)

View File

@ -1,67 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2017 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2017 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2017 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2017 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.apps import apps
from django.db import IntegrityError
from django.utils.translation import ugettext as _
from taiga.base import exceptions as exc
from taiga.projects.settings.choices import Section
def user_project_settings_exists(project, user) -> bool:
"""
Check if policy exists for specified project
and user.
"""
model_cls = apps.get_model("settings", "UserProjectSettings")
qs = model_cls.objects.filter(project=project,
user=user)
return qs.exists()
def create_user_project_settings(project, user, homepage=Section.timeline):
"""
Given a project and user, create notification policy for it.
"""
model_cls = apps.get_model("settings", "UserProjectSettings")
try:
return model_cls.objects.create(project=project,
user=user,
homepage=homepage)
except IntegrityError as e:
raise exc.IntegrityError(
_("Notify exists for specified user and project")) from e
def create_user_project_settings_if_not_exists(project, user,
homepage=Section.timeline):
"""
Given a project and user, create notification policy for it.
"""
model_cls = apps.get_model("settings", "UserProjectSettings")
try:
result = model_cls.objects.get_or_create(
project=project,
user=user,
defaults={"homepage": homepage}
)
return result[0]
except IntegrityError as e:
raise exc.IntegrityError(
_("Notify exists for specified user and project")) from e

View File

@ -1,17 +0,0 @@
from taiga.permissions.services import is_project_admin, user_has_perm
from taiga.projects.settings.choices import Section
def get_allowed_sections(obj):
sections = [Section.timeline]
active_modules = {'epics': 'view_epics', 'backlog': 'view_us',
'kanban': 'view_us', 'wiki': 'view_wiki_pages',
'issues': 'view_issues'}
for key in active_modules:
module_name = "is_{}_activated".format(key)
if getattr(obj.project, module_name) and \
user_has_perm(obj.user, active_modules[key], obj.project):
sections.append(getattr(Section, key))
return sections

View File

@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2017 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2017 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2017 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2017 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.utils.translation import ugettext as _
from taiga.base.api import validators
from taiga.base.exceptions import ValidationError
from taiga.projects.settings.utils import get_allowed_sections
from . import models
class UserProjectSettingsValidator(validators.ModelValidator):
class Meta:
model = models.UserProjectSettings
read_only_fields = ('id', 'created_at', 'modified_at', 'project',
'user')
def validate_homepage(self, attrs, source):
if attrs[source] not in get_allowed_sections(self.object):
msg = _("You don't have access to this section")
raise ValidationError(msg)
return attrs

View File

@ -18,7 +18,6 @@
from django.apps import apps
from django.conf import settings
from django.db.models import F
from taiga.projects.notifications.services import create_notify_policy_if_not_exists
@ -33,21 +32,15 @@ def membership_post_delete(sender, instance, using, **kwargs):
instance.project.update_role_points()
def membership_post_save(sender, instance, using, **kwargs):
if not instance.user:
return
create_notify_policy_if_not_exists(instance.project, instance.user)
## Notify policy
# Set project on top on user projects list
membership = apps.get_model("projects", "Membership")
membership.objects.filter(user=instance.user) \
.update(user_order=F('user_order') + 1)
membership.objects.filter(user=instance.user, project=instance.project)\
.update(user_order=0)
def create_notify_policy(sender, instance, using, **kwargs):
if instance.user:
create_notify_policy_if_not_exists(instance.project, instance.user)
## Project attributes
def project_post_save(sender, instance, created, **kwargs):
"""
Populate new project dependen default data

View File

@ -30,9 +30,7 @@ from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.milestones.models import Milestone
from taiga.projects.mixins.by_ref import ByRefMixin
from taiga.projects.models import Project, TaskStatus
from taiga.projects.notifications.mixins import AssignedToSignalMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchersViewSetMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
from taiga.projects.occ import OCCResourceMixin
from taiga.projects.tagging.api import TaggedResourceMixin
from taiga.projects.userstories.models import UserStory
@ -47,10 +45,8 @@ from . import validators
from . import utils as tasks_utils
class TaskViewSet(AssignedToSignalMixin, OCCResourceMixin, VotedResourceMixin,
HistoryResourceMixin, WatchedResourceMixin, ByRefMixin,
TaggedResourceMixin, BlockedByProjectMixin,
ModelCrudViewSet):
class TaskViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
ByRefMixin, TaggedResourceMixin, BlockedByProjectMixin, ModelCrudViewSet):
validator_class = validators.TaskValidator
queryset = models.Task.objects.all()
permission_classes = (permissions.TaskPermission,)
@ -78,7 +74,6 @@ class TaskViewSet(AssignedToSignalMixin, OCCResourceMixin, VotedResourceMixin,
"created_date",
"modified_date",
"assigned_to",
"us_order",
"subject",
"total_voters")
@ -275,23 +270,6 @@ class TaskViewSet(AssignedToSignalMixin, OCCResourceMixin, VotedResourceMixin,
return response.Ok(tasks_serialized.data)
@list_route(methods=["POST"])
def bulk_update_milestone(self, request, **kwargs):
validator = validators.UpdateMilestoneBulkValidator(data=request.DATA)
if not validator.is_valid():
return response.BadRequest(validator.errors)
data = validator.data
project = get_object_or_404(Project, pk=data["project_id"])
milestone = get_object_or_404(Milestone, pk=data["milestone_id"])
self.check_permissions(request, "bulk_update_milestone", project)
ret = services.update_tasks_milestone_in_bulk(data["bulk_tasks"], milestone)
services.snapshot_tasks_in_bulk(data["bulk_tasks"], request.user)
return response.Ok(ret)
def _bulk_update_order(self, order_field, request, **kwargs):
validator = validators.UpdateTasksOrderBulkValidator(data=request.DATA)
if not validator.is_valid():

View File

@ -91,7 +91,3 @@ class Task(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, DueDateM
def __str__(self):
return "({1}) {0}".format(self.ref, self.subject)
@property
def is_closed(self):
return self.status is not None and self.status.is_closed

View File

@ -35,7 +35,6 @@ class TaskPermission(TaigaResourcePermission):
csv_perms = AllowAny()
bulk_create_perms = HasProjectPerm('add_task')
bulk_update_order_perms = HasProjectPerm('modify_task')
bulk_update_milestone_perms = HasProjectPerm('modify_task')
upvote_perms = IsAuthenticated() & HasProjectPerm('view_tasks')
downvote_perms = IsAuthenticated() & HasProjectPerm('view_tasks')
watch_perms = IsAuthenticated() & HasProjectPerm('view_tasks')

View File

@ -112,39 +112,6 @@ def snapshot_tasks_in_bulk(bulk_data, user):
pass
def update_tasks_milestone_in_bulk(bulk_data: list, milestone: object):
"""
Update the milestone and the milestone order of some tasks adding
the extra orders needed to keep consistency.
`bulk_data` should be a list of dicts with the following format:
[{'task_id': <value>, 'order': <value>}, ...]
"""
tasks = milestone.tasks.all()
task_orders = {task.id: getattr(task, "taskboard_order") for task in tasks}
new_task_orders = {}
for e in bulk_data:
new_task_orders[e["task_id"]] = e["order"]
# The base orders where we apply the new orders must containg all
# the values
task_orders[e["task_id"]] = e["order"]
apply_order_updates(task_orders, new_task_orders)
task_milestones = {e["task_id"]: milestone.id for e in bulk_data}
task_ids = task_milestones.keys()
events.emit_event_for_ids(ids=task_ids,
content_type="tasks.task",
projectid=milestone.project.pk)
db.update_attr_in_bulk_for_ids(task_milestones, "milestone_id",
model=models.Task)
db.update_attr_in_bulk_for_ids(task_orders, "taskboard_order", models.Task)
return task_milestones
#####################################################
# CSV
#####################################################

View File

@ -31,9 +31,9 @@ def cached_prev_task(sender, instance, **kwargs):
instance.prev = sender.objects.get(id=instance.id)
######################################
# Signals for close Task and Milestone
######################################
####################################
# Signals for close US and Milestone
####################################
def try_to_close_or_open_us_and_milestone_when_create_or_edit_task(sender, instance, created, **kwargs):
_try_to_close_or_open_us_when_create_or_edit_task(instance)

View File

@ -153,36 +153,3 @@ class UpdateTasksOrderBulkValidator(ProjectExistsValidator, validators.Validator
"if it exists, to the same status, user story and/or milestone."))
return attrs
# Milestone bulk validators
class _TaskMilestoneBulkValidator(validators.Validator):
task_id = serializers.IntegerField()
order = serializers.IntegerField()
class UpdateMilestoneBulkValidator(ProjectExistsValidator, validators.Validator):
project_id = serializers.IntegerField()
milestone_id = serializers.IntegerField()
bulk_tasks = _TaskMilestoneBulkValidator(many=True)
def validate_milestone_id(self, attrs, source):
filters = {
"project__id": attrs["project_id"],
"id": attrs[source]
}
if not Milestone.objects.filter(**filters).exists():
raise ValidationError(_("The milestone isn't valid for the project"))
return attrs
def validate_bulk_tasks(self, attrs, source):
filters = {
"project__id": attrs["project_id"],
"id__in": [task["task_id"] for task in attrs[source]]
}
if models.Task.objects.filter(**filters).count() != len(filters["id__in"]):
raise ValidationError(_("All the tasks must be from the same project"))
return attrs

View File

@ -39,7 +39,6 @@ from taiga.projects.history.services import take_snapshot
from taiga.projects.milestones.models import Milestone
from taiga.projects.mixins.by_ref import ByRefMixin
from taiga.projects.models import Project, UserStoryStatus
from taiga.projects.notifications.mixins import AssignedUsersSignalMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchersViewSetMixin
from taiga.projects.occ import OCCResourceMixin
@ -56,10 +55,8 @@ from . import services
from . import validators
class UserStoryViewSet(AssignedUsersSignalMixin, OCCResourceMixin,
VotedResourceMixin, HistoryResourceMixin,
WatchedResourceMixin, ByRefMixin, TaggedResourceMixin,
BlockedByProjectMixin, ModelCrudViewSet):
class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
ByRefMixin, TaggedResourceMixin, BlockedByProjectMixin, ModelCrudViewSet):
validator_class = validators.UserStoryValidator
queryset = models.UserStory.objects.all()
permission_classes = (permissions.UserStoryPermission,)

Some files were not shown because too many files have changed in this diff Show More