diff --git a/.travis.yml b/.travis.yml index c033e2b..4b95de8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,13 @@ language: python sudo: false -python: 2.7 -env: - - TOX_ENV=py27-django111 - - TOX_ENV=flake8 +matrix: + include: + - python: 3.6 + env: TOX_ENV=py36-django111 + - python: 3.7 + env: TOX_ENV=py37-django111 + - python: 3.7 + env: TOX_ENV=py37-flake8 install: - pip install tox script: diff --git a/CHANGELOG.md b/CHANGELOG.md index adbeb8c..a3491e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,15 @@ -x.x.x +2.0.0 ------ -* Support Django 1.11 -* Dropped support for Django 1.7-1.10 -* Changed the base image in Dockerfile from python:2.7 to python:2.7-stretch -* Used pipenv to manage requirements - +* Upgraded to Python 3 1.2.5 ----- +* Added Django 1.11 support +* Dropped support for Django 1.7-1.10 +* Changed the base image in Dockerfile from python:2.7 to python:2.7-stretch +* Used pipenv to manage requirements * Removed bare excepts for flake8 ([#89]( https://github.com/unt-libraries/django-premis-event-service/pull/89)) diff --git a/Dockerfile b/Dockerfile index ddcbb94..8d55560 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # vim: set ft=conf -FROM python:2.7-stretch +FROM python:3.7-stretch RUN echo "US/Central" > /etc/timezone RUN dpkg-reconfigure -f noninteractive tzdata @@ -17,7 +17,7 @@ RUN apt-get update -qq && apt-get install -y mysql-client netcat RUN pip install -U pip setuptools RUN pip install pipenv -RUN pipenv install --dev --system +RUN pipenv install --dev --system --deploy --ignore-pipfile ADD wait-for-mysqld.sh /wait-for-mysqld.sh ADD appstart.sh /appstart.sh diff --git a/Pipfile b/Pipfile index e41f6b6..d80a08c 100644 --- a/Pipfile +++ b/Pipfile @@ -4,19 +4,18 @@ verify_ssl = true name = "pypi" [packages] -lxml = "~=3.3.0" -codalib = "==1.0.3" +lxml = "~=4.2.0" +codalib = {git='https://github.com/unt-libraries/codalib'} sqlparse = "*" -mysql-python = "*" -Django = "~=1.11.0" +mysqlclient = "~=1.3.14" +Django = "~=1.11.28" [dev-packages] django-debug-toolbar = "<=1.9" tox = ">=3.13.0" pytest-django = ">=3.2.1" -mock = "==1.3.0" factory-boy = "==2.5.2" "flake8" = "*" [requires] -python_version = "2.7" +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 9d7866c..5ebe68c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "d2920d2d0661484bfaebb1b4f611fd4adb203d22d7c919bbdd6457854829ee60" + "sha256": "0dacaafc5ee1961c2eda3d7118d343647a646b4bf87e48de6c8a330870f5a508" }, "pipfile-spec": 6, "requires": { - "python_version": "2.7" + "python_version": "3.7" }, "sources": [ { @@ -17,100 +17,113 @@ }, "default": { "codalib": { - "hashes": [ - "sha256:768908580ea67baa6de60b218f292227ad0023760d12130cf13098f0cb2fe387" - ], - "index": "pypi", - "version": "==1.0.3" + "git": "https://github.com/unt-libraries/codalib", + "ref": "3c8e30097de74a300f7c503802117361a98e5261" }, "django": { "hashes": [ - "sha256:52a66d7f8b036d02da0a4472359e8be1727424fc1e4b4f5c684ef97de7b569e1", - "sha256:c85b8c95366e187ca0581d45a6e508107ca4bd38cb45c24aa09d3572074c523d" + "sha256:014e3392058d94f40569206a24523ce254d55ad2f9f46c6550b0fe2e4f94cf3f", + "sha256:4200aefb6678019a0acf0005cd14cfce3a5e6b9b90d06145fcdd2e474ad4329c" ], "index": "pypi", - "version": "==1.11.23" + "version": "==1.11.29" }, "lxml": { "hashes": [ - "sha256:00f760d13c3e7ef2c297ca99e461b01b08d1cd9980d41562f31459b374a7fde3", - "sha256:0b6248141bd9551fed16afc9d34dacaa3b32ef4a854e1df41c58c4c47d06dcfa", - "sha256:3ecc3a4cd21a1033f461b340d5e115b955b47d33e57214b6a78d364f0e72e335", - "sha256:40434af0a82fa45d8ce34ac2dc7dab014fd0b9c1893499f473cd87cb3944fe38", - "sha256:c46a72c529398d34eabd05e12948868d9d958caede12f1a37e86d00efc423c92", - "sha256:f5fa7d233224e0a441ca23aeab43476c93e416b34b5ce640ca4d63fcb5bfda0e" + "sha256:16cf8bac33ec17049617186d63006ba49da7c5be417042877a49f0ef6d7a195d", + "sha256:18f2d8f14cc61e66e8a45f740d15b6fc683c096f733db1f8d0ee15bcac9843de", + "sha256:260868f69d14a64dd1de9cf92e133d2f71514d288de4906f109bdf48ca9b756a", + "sha256:29b8acd8ecdf772266dbac491f203c71664b0b07ad4309ba2c3bb131306332fc", + "sha256:2b05e5e06f8e8c63595472dc887d0d6e0250af754a35ba690f6a6abf2ef85691", + "sha256:30d6ec05fb607a5b7345549f642c7c7a5b747b634f6d5e935596b910f243f96f", + "sha256:3bf683f0237449ebc1851098f664410e3c99ba3faa8c9cc82c6acfe857df1767", + "sha256:3ce5488121eb15513c4b239dadd67f9e7959511bd766aac6be0c35e80274f298", + "sha256:48be0c375350a5519bb9474b42a9c0e7ab709fb45f11bfcd33de876791137896", + "sha256:49bc343ca3b30cd860845433bb9f62448a54ff87b632175108bacbc5dc63e49e", + "sha256:4cc7531e86a43ea66601763c5914c3d3adb297f32e4284957609b90d41825fca", + "sha256:4e9822fad564d82035f0b6d701a890444560210f8a8648b8f15850f8fe883cd9", + "sha256:51a9a441aefc8c93512bad5efe867d2ff086e7249ce0fc3b47c310644b352936", + "sha256:5bbed9efc8aeb69929140f71a30e655bf496b45b766861513960e1b11168d475", + "sha256:60a5323b2bc893ca1059d283d6695a172d51cc95a70c25b3e587e1aad5459c38", + "sha256:7035d9361f3ceec9ccc1dd3482094d1174580e7e1bf6870b77ea758f7cad15d2", + "sha256:76d62cc048bda0ebf476689ad3eb8e65e6827e43a7521be3b163071020667b8c", + "sha256:78163b578e6d1836012febaa1865e095ccc7fc826964dd69a2dbfe401618a1f7", + "sha256:83b58b2b5904d50de03a47e2f56d24e9da4cf7e3b0d66fb4510b18fca0faf910", + "sha256:a07447e46fffa5bb4d7a0af0a6505c8517e9bd197cfd2aec79e499b6e86cde49", + "sha256:a17d808b3edca4aaf6b295b5a388c844a0b7f79aca2d79eec5acc1461db739e3", + "sha256:a378fd61022cf4d3b492134c3bc48204ac2ff19e0813b23e07c3dd95ae8df0bc", + "sha256:aa7d096a44ae3d475c5ed763e24cf302d32462e78b61bba73ce1ad0efb8f522a", + "sha256:ade8785c93a985956ba6499d5ea6d0a362e24b4a9ba07dd18920fd67cccf63ea", + "sha256:cc039668f91d8af8c4094cfb5a67c7ae733967fdc84c0507fe271db81480d367", + "sha256:d89f1ffe98744c4b5c11f00fb843a4e72f68a6279b5e38168167f1b3c0fdd84c", + "sha256:e691b6ef6e27437860016bd6c32e481bdc2ed3af03289707a38b9ca422105f40", + "sha256:e750da6ac3ca624ae3303df448664012f9b6f9dfbc5d50048ea8a12ce2f8bc29", + "sha256:eca305b200549906ea25648463aeb1b3b220b716415183eaa99c998a846936d9", + "sha256:f52fe795e08858192eea167290033b5ff24f50f51781cb78d989e8d63cfe73d1" ], "index": "pypi", - "version": "==3.3.6" + "version": "==4.2.6" }, - "mysql-python": { + "mysqlclient": { "hashes": [ - "sha256:811040b647e5d5686f84db415efd697e6250008b112b6909ba77ac059e140c74" + "sha256:062d78953acb23066c0387a8f3bd0ecf946626f599145bb7fd201460e8f773e1", + "sha256:3981ae9ce545901a36a8b7aed76ed02960a429f75dc53b7ad77fb2f9ab7cd56b", + "sha256:b3591a00c0366de71d65108627899710d9cfb00e575c4d211aa8de59b1f130c9" ], "index": "pypi", - "version": "==1.2.5" + "version": "==1.3.14" }, "pytz": { "hashes": [ - "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", - "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7" + "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", + "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" ], - "version": "==2019.2" + "version": "==2019.3" }, "sqlparse": { "hashes": [ - "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", - "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" + "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e", + "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548" ], "index": "pypi", - "version": "==0.3.0" - }, - "tzlocal": { - "hashes": [ - "sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048", - "sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590" - ], - "version": "==2.0.0" + "version": "==0.3.1" } }, "develop": { - "atomicwrites": { + "appdirs": { "hashes": [ - "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", - "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" ], - "version": "==1.3.0" + "version": "==1.4.3" }, - "attrs": { + "asgiref": { "hashes": [ - "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", - "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" + "sha256:8036f90603c54e93521e5777b2b9a39ba1bad05773fcf2d208f0299d1df58ce5", + "sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c" ], - "version": "==19.1.0" + "version": "==3.2.7" }, - "configparser": { + "attrs": { "hashes": [ - "sha256:45d1272aad6cfd7a8a06cf5c73f2ceb6a190f6acc1fa707e7f82a4c053b28b18", - "sha256:bc37850f0cc42a1725a796ef7d92690651bf1af37d744cc63161dac62cabee17" + "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", + "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" ], - "markers": "python_version < '3.2'", - "version": "==3.8.1" + "version": "==19.3.0" }, - "contextlib2": { + "distlib": { "hashes": [ - "sha256:509f9419ee91cdd00ba34443217d5ca51f5a364a404e1dce9e8979cea969ca48", - "sha256:f5260a6e679d2ff42ec91ec5252f4eeffdcf21053db9113bd0a8e4d953769c00" + "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21" ], - "markers": "python_version < '3'", - "version": "==0.5.5" + "version": "==0.3.0" }, "django": { "hashes": [ - "sha256:52a66d7f8b036d02da0a4472359e8be1727424fc1e4b4f5c684ef97de7b569e1", - "sha256:c85b8c95366e187ca0581d45a6e508107ca4bd38cb45c24aa09d3572074c523d" + "sha256:014e3392058d94f40569206a24523ce254d55ad2f9f46c6550b0fe2e4f94cf3f", + "sha256:4200aefb6678019a0acf0005cd14cfce3a5e6b9b90d06145fcdd2e474ad4329c" ], "index": "pypi", - "version": "==1.11.23" + "version": "==1.11.29" }, "django-debug-toolbar": { "hashes": [ @@ -127,16 +140,6 @@ ], "version": "==0.3" }, - "enum34": { - "hashes": [ - "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", - "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", - "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", - "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1" - ], - "markers": "python_version < '3.4'", - "version": "==1.1.6" - }, "factory-boy": { "hashes": [ "sha256:102c8141511443df01d354610d3b268924100654316709b43ac04648b50bf703", @@ -154,34 +157,19 @@ }, "flake8": { "hashes": [ - "sha256:19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", - "sha256:8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696" + "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", + "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" ], "index": "pypi", - "version": "==3.7.8" - }, - "funcsigs": { - "hashes": [ - "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", - "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" - ], - "markers": "python_version < '3.3'", - "version": "==1.0.2" - }, - "functools32": { - "hashes": [ - "sha256:89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0", - "sha256:f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d" - ], - "markers": "python_version < '3.2'", - "version": "==3.2.3.post2" + "version": "==3.7.9" }, "importlib-metadata": { "hashes": [ - "sha256:23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", - "sha256:80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3" + "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f", + "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e" ], - "version": "==0.19" + "markers": "python_version < '3.8'", + "version": "==1.6.0" }, "mccabe": { "hashes": [ @@ -190,58 +178,33 @@ ], "version": "==0.6.1" }, - "mock": { - "hashes": [ - "sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6", - "sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb" - ], - "index": "pypi", - "version": "==1.3.0" - }, "more-itertools": { "hashes": [ - "sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4", - "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc", - "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9" + "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c", + "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507" ], - "markers": "python_version <= '2.7'", - "version": "==5.0.0" + "version": "==8.2.0" }, "packaging": { "hashes": [ - "sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", - "sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe" - ], - "version": "==19.1" - }, - "pathlib2": { - "hashes": [ - "sha256:2156525d6576d21c4dcaddfa427fae887ef89a7a9de5cbfe0728b3aafa78427e", - "sha256:446014523bb9be5c28128c4d2a10ad6bb60769e78bd85658fe44a450674e0ef8" - ], - "markers": "python_version == '3.4.*' or python_version < '3'", - "version": "==2.3.4" - }, - "pbr": { - "hashes": [ - "sha256:56e52299170b9492513c64be44736d27a512fa7e606f21942160b68ce510b4bc", - "sha256:9b321c204a88d8ab5082699469f52cc94c5da45c51f114113d01b3d993c24cdf" + "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3", + "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752" ], - "version": "==5.4.2" + "version": "==20.3" }, "pluggy": { "hashes": [ - "sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", - "sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c" + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" ], - "version": "==0.12.0" + "version": "==0.13.1" }, "py": { "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa", + "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0" ], - "version": "==1.8.0" + "version": "==1.8.1" }, "pycodestyle": { "hashes": [ @@ -259,64 +222,47 @@ }, "pyparsing": { "hashes": [ - "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", - "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "version": "==2.4.2" + "version": "==2.4.7" }, "pytest": { "hashes": [ - "sha256:8fc39199bdda3d9d025d3b1f4eb99a192c20828030ea7c9a0d2840721de7d347", - "sha256:d100a02770f665f5dcf7e3f08202db29857fee6d15f34c942be0a511f39814f0" + "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172", + "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970" ], - "version": "==4.6.5" + "version": "==5.4.1" }, "pytest-django": { "hashes": [ - "sha256:264fb4c506db5d48a6364c311a0b00b7b48a52715bad8839b2d8bee9b99ed6bb", - "sha256:4adfe5fb3ed47f0ba55506dd3daf688b1f74d5e69148c10ad2dd2f79f40c0d62" + "sha256:64f99d565dd9497af412fcab2989fe40982c1282d4118ff422b407f3f7275ca5", + "sha256:664e5f42242e5e182519388f01b9f25d824a9feb7cd17d8f863c8d776f38baf9" ], "index": "pypi", - "version": "==3.5.1" + "version": "==3.9.0" }, "pytz": { "hashes": [ - "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", - "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7" - ], - "version": "==2019.2" - }, - "scandir": { - "hashes": [ - "sha256:2586c94e907d99617887daed6c1d102b5ca28f1085f90446554abf1faf73123e", - "sha256:2ae41f43797ca0c11591c0c35f2f5875fa99f8797cb1a1fd440497ec0ae4b022", - "sha256:2b8e3888b11abb2217a32af0766bc06b65cc4a928d8727828ee68af5a967fa6f", - "sha256:2c712840c2e2ee8dfaf36034080108d30060d759c7b73a01a52251cc8989f11f", - "sha256:4d4631f6062e658e9007ab3149a9b914f3548cb38bfb021c64f39a025ce578ae", - "sha256:67f15b6f83e6507fdc6fca22fedf6ef8b334b399ca27c6b568cbfaa82a364173", - "sha256:7d2d7a06a252764061a020407b997dd036f7bd6a175a5ba2b345f0a357f0b3f4", - "sha256:8c5922863e44ffc00c5c693190648daa6d15e7c1207ed02d6f46a8dcc2869d32", - "sha256:92c85ac42f41ffdc35b6da57ed991575bdbe69db895507af88b9f499b701c188", - "sha256:b24086f2375c4a094a6b51e78b4cf7ca16c721dcee2eddd7aa6494b42d6d519d", - "sha256:cb925555f43060a1745d0a321cca94bcea927c50114b623d73179189a4e100ac" + "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", + "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" ], - "markers": "python_version < '3.5'", - "version": "==1.10.0" + "version": "==2019.3" }, "six": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.12.0" + "version": "==1.14.0" }, "sqlparse": { "hashes": [ - "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", - "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" + "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e", + "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548" ], "index": "pypi", - "version": "==0.3.0" + "version": "==0.3.1" }, "toml": { "hashes": [ @@ -327,41 +273,32 @@ }, "tox": { "hashes": [ - "sha256:dab0b0160dd187b654fc33d690ee1d7bf328bd5b8dc6ef3bb3cc468969c659ba", - "sha256:ee35ffce74933a6c6ac10c9a0182e41763140a5a5070e21b114feca56eaccdcd" + "sha256:a4a6689045d93c208d77230853b28058b7513f5123647b67bf012f82fa168303", + "sha256:b2c4b91c975ea5c11463d9ca00bebf82654439c5df0f614807b9bdec62cc9471" ], "index": "pypi", - "version": "==3.13.2" - }, - "typing": { - "hashes": [ - "sha256:91dfe6f3f706ee8cc32d38edbbf304e9b7583fb37108fef38229617f8b3eba23", - "sha256:c8cabb5ab8945cd2f54917be357d134db9cc1eb039e59d1606dc1e60cb1d9d36", - "sha256:f38d83c5a7a7086543a0f649564d661859c5146a85775ab90c0d2f93ffaa9714" - ], - "markers": "python_version < '3.5'", - "version": "==3.7.4.1" + "version": "==3.14.6" }, "virtualenv": { "hashes": [ - "sha256:94a6898293d07f84a98add34c4df900f8ec64a570292279f6d91c781d37fd305", - "sha256:f6fc312c031f2d2344f885de114f1cb029dfcffd26aa6e57d2ee2296935c4e7d" + "sha256:00cfe8605fb97f5a59d52baab78e6070e72c12ca64f51151695407cc0eb8a431", + "sha256:c8364ec469084046c779c9a11ae6340094e8a0bf1d844330fc55c1cefe67c172" ], - "version": "==16.7.4" + "version": "==20.0.17" }, "wcwidth": { "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1", + "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1" ], - "version": "==0.1.7" + "version": "==0.1.9" }, "zipp": { "hashes": [ - "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", - "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" + "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", + "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" ], - "version": "==0.6.0" + "version": "==3.1.0" } } } diff --git a/README.rst b/README.rst index eca365f..a0b734d 100644 --- a/README.rst +++ b/README.rst @@ -19,7 +19,7 @@ due to its widespread use in the digital libraries world. Dependencies ------------ -* Python 2.7+ (not Python 3) +* Python 3 * Django 1.11 * lxml (requires libxml2-dev to be installed on your system) * pipenv @@ -92,16 +92,11 @@ Install the requirements using pipenv_ .. code-block :: sh - $ pipenv --python 2.7 # (to create the virtualenv) + $ pipenv --python 3.7 # (to create the virtualenv) $ pipenv install --dev $ pipenv shell # (to enter the virtualenv) $ exit # (to leave the virtualenv) -If you're using OS X Sierra or later, you will need to specify 2.7.15, because -a change in Mac handling of OpenSSL broke pip for earlier versions of Python. -(This will show up as an error saying you have the wrong version of TLS when -you try to use pip/pipenv.) - If you need to generate a requirements.txt file, you can do so with ``pipenv lock -r > requirements.txt``. diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 57713a9..4869e6c 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -12,7 +12,7 @@ We'll elaborate a bit in this section. Dependencies ============ -- Python 2.7.x +- Python 3.7.x - Django 1.11 - libxml2-dev libxslt-dev - Django Admin - ``django.contrib.admin`` @@ -32,7 +32,7 @@ Install 1. Install the package. :: - $ pip install git+https://github.com/unt-libraries/django-premis-event-service@1.2.5 + $ pip install git+https://github.com/unt-libraries/django-premis-event-service $ # check https://github.com/unt-libraries/django-premis-event-service/releases for the latest release 2. Add ``premis_event_service`` to your ``INSTALLED_APPS``. Be sure to add ``django.contrib.admin`` if it is not already present. :: diff --git a/premis_event_service/migrations/0001_initial.py b/premis_event_service/migrations/0001_initial.py index 7453883..69c331e 100644 --- a/premis_event_service/migrations/0001_initial.py +++ b/premis_event_service/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/premis_event_service/migrations/0002_add_event_ordinal.py b/premis_event_service/migrations/0002_add_event_ordinal.py index 1c7629f..fa2749a 100644 --- a/premis_event_service/migrations/0002_add_event_ordinal.py +++ b/premis_event_service/migrations/0002_add_event_ordinal.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models @@ -144,7 +144,7 @@ class Migration(migrations.Migration): name='event_identifier', field=models.CharField( help_text=( - b'Unique identifier for an event. ' + 'Unique identifier for an event. ' 'example: urn:uuid:12345678-1234-5678-1234-567812345678' ), unique=True, diff --git a/premis_event_service/migrations/0003_auto_20190820_1755.py b/premis_event_service/migrations/0003_auto_20190820_1755.py index b2abed6..a91f2fd 100644 --- a/premis_event_service/migrations/0003_auto_20190820_1755.py +++ b/premis_event_service/migrations/0003_auto_20190820_1755.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.13 on 2019-08-20 17:55 -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/premis_event_service/migrations/0004_auto_20191008_1646.py b/premis_event_service/migrations/0004_auto_20191008_1646.py new file mode 100644 index 0000000..2e5911c --- /dev/null +++ b/premis_event_service/migrations/0004_auto_20191008_1646.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.25 on 2019-10-08 16:46 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('premis_event_service', '0003_auto_20190820_1755'), + ] + + operations = [ + migrations.AlterField( + model_name='agent', + name='agent_identifier', + field=models.CharField(db_index=True, help_text='Short identifier for agent to be used in url.', max_length=255, unique=True), + ), + migrations.AlterField( + model_name='agent', + name='agent_name', + field=models.CharField(help_text='Name of agent.', max_length=255), + ), + migrations.AlterField( + model_name='agent', + name='agent_note', + field=models.TextField(blank=True, help_text='Optional note about agent.'), + ), + migrations.AlterField( + model_name='agent', + name='agent_type', + field=models.CharField(choices=[('Personal', 'Personal'), ('Organization', 'Organization'), ('Event', 'Event'), ('Software', 'Software')], help_text='A high-level characterization of the type of agent.', max_length=13), + ), + migrations.AlterField( + model_name='event', + name='event_added', + field=models.DateTimeField(auto_now=True, db_index=True, help_text='Date/Time event was added to system.'), + ), + migrations.AlterField( + model_name='event', + name='event_date_time', + field=models.DateTimeField(db_index=True, help_text='Date/Time event was completed.'), + ), + migrations.AlterField( + model_name='event', + name='event_detail', + field=models.TextField(blank=True, help_text='Additional information about the event.'), + ), + migrations.AlterField( + model_name='event', + name='event_identifier_type', + field=models.CharField(help_text='The categorization of the nature of the identifier used.', max_length=255), + ), + migrations.AlterField( + model_name='event', + name='event_outcome', + field=models.CharField(db_index=True, help_text='A categorization of the overall result of the event in terms of success, partial success, or failure.', max_length=255), + ), + migrations.AlterField( + model_name='event', + name='event_outcome_detail', + field=models.TextField(blank=True, help_text='A non-coded detailed description of the result of the event.'), + ), + migrations.AlterField( + model_name='event', + name='event_type', + field=models.CharField(db_index=True, help_text='A categorization of the nature of the event use controlled vocabulary.', max_length=255), + ), + migrations.AlterField( + model_name='event', + name='linking_agent_identifier_type', + field=models.CharField(help_text='A designation of the domain in which the linking agent identifier is unique.', max_length=255), + ), + migrations.AlterField( + model_name='event', + name='linking_agent_identifier_value', + field=models.CharField(db_index=True, help_text='The value of the linking agent identifier.', max_length=255), + ), + migrations.AlterField( + model_name='event', + name='linking_agent_role', + field=models.CharField(help_text='The role of the agent in relation to this event.', max_length=255), + ), + migrations.AlterField( + model_name='linkobject', + name='object_identifier', + field=models.CharField(help_text='Unique identifier for this object.', max_length=255, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='linkobject', + name='object_role', + field=models.CharField(help_text='A high-level characterization of the role of the object.', max_length=255, null=True), + ), + migrations.AlterField( + model_name='linkobject', + name='object_type', + field=models.CharField(help_text='High-level characterization of the type of object identifier.', max_length=255), + ), + ] diff --git a/premis_event_service/models.py b/premis_event_service/models.py index 029de4c..bcb26b4 100644 --- a/premis_event_service/models.py +++ b/premis_event_service/models.py @@ -36,7 +36,7 @@ class Agent(models.Model): help_text="Optional note about agent." ) - def __unicode__(self): + def __str__(self): return self.agent_name def get_absolute_url(self): @@ -67,7 +67,7 @@ class LinkObject(models.Model): null=True ) - def __unicode__(self): + def __str__(self): return self.object_identifier @@ -121,7 +121,7 @@ def search(self, **kwargs): def searchunfilt(self, max_ordinal=None): if not max_ordinal: max_ordinal = self.get_queryset().all().aggregate(models.Max('ordinal')) - max_ordinal = max_ordinal.values()[0] + max_ordinal = list(max_ordinal.values())[0] if max_ordinal: max_ordinal += 1 # This should never happen in practice. But with test factories, etc., @@ -212,7 +212,7 @@ class Event(models.Model): through_fields=('event_id', 'linkobject_id') ) - def __unicode__(self): + def __str__(self): return self.event_identifier class Meta: diff --git a/premis_event_service/presentation.py b/premis_event_service/presentation.py index 0315cd8..0ac81c3 100644 --- a/premis_event_service/presentation.py +++ b/premis_event_service/presentation.py @@ -1,6 +1,6 @@ from datetime import datetime import uuid -from urlparse import urlparse +from urllib.parse import urlparse from lxml import etree from django.shortcuts import get_object_or_404 @@ -72,7 +72,7 @@ def premisEventXMLToObject(eventXML): try: Event.objects.get(event_identifier=newEventObject.event_identifier) raise DuplicateEventError(newEventObject.event_identifier) - except Event.DoesNotExist as e: + except Event.DoesNotExist: pass except DuplicateEventError as e: raise e @@ -131,25 +131,25 @@ def premisAgentXMLToObject(agentXML): namespaces=PREMIS_NSMAP )[0].text.strip() agent_object.agent_identifier = agent_identifier - except Exception, e: + except Exception as e: raise Exception("Unable to set 'agent_identifier' attribute: %s" % e) try: agent_object.agent_name = agent_root.xpath( "//premis:agentName", namespaces=PREMIS_NSMAP )[0].text.strip() - except Exception, e: + except Exception as e: raise Exception("Unable to set 'agent_name' attribute: %s" % e) try: agent_object.agent_type = agent_root.xpath( "//premis:agentType", namespaces=PREMIS_NSMAP )[0].text.strip() - except Exception, e: + except Exception as e: raise Exception("Unable to set 'agent_type' attribute: %s" % e) try: agent_object.agent_note = agent_root.xpath( "//premis:agentNote", namespaces=PREMIS_NSMAP )[0].text.strip() - except Exception, e: + except Exception: pass return agent_object @@ -211,7 +211,7 @@ def objectToPremisEventXML(eventObject): """ eventXML = etree.Element("%sevent" % PREMIS, nsmap=PREMIS_NSMAP) - for fieldName, chain in translateDict.iteritems(): + for fieldName, chain in translateDict.items(): if not hasattr(eventObject, fieldName): continue baseName = chain[0] diff --git a/premis_event_service/views.py b/premis_event_service/views.py index b6817d6..04b9990 100644 --- a/premis_event_service/views.py +++ b/premis_event_service/views.py @@ -2,7 +2,7 @@ import math from datetime import datetime import json -import urllib +import urllib.parse from lxml import etree from django.conf import settings @@ -24,12 +24,12 @@ premisAgentXMLgetObject, objectToPremisEventXML, objectToPremisAgentXML, objectToAgentXML, DuplicateEventError, PREMIS_NSMAP, xpath_map) -from settings import ARK_NAAN +from .settings import ARK_NAAN ARK_ID_REGEX = re.compile(r'ark:/'+str(ARK_NAAN)+r'/\w.*') MAINTENANCE_MSG = settings.MAINTENANCE_MSG EVENT_UPDATE_TRANSLATION_DICT = xpath_map -XML_HEADER = "\n%s" +XML_HEADER = b"\n%s" EVENT_SEARCH_PER_PAGE = 200 @@ -126,14 +126,14 @@ def get_page_offsets(query_set, page, page_range, page_lims, per_page=20): ordinals = [evt.ordinal for ei, evt in enumerate(offset_qs) if ei and not ei % per_page] ordinals = ordinals[::-1] offsets_lo = [p for p in page_range if p < page] - offsets_lo = zip(offsets_lo, ordinals) + offsets_lo = list(zip(offsets_lo, ordinals)) limit = (pN-page+1) * per_page if pN > page else 1 ordinals = [] if limit: offset_qs = query_set.filter(ordinal__lte=page_max_ord).order_by('-ordinal')[0:limit] ordinals = [evt.ordinal for ei, evt in enumerate(offset_qs) if not ei % per_page] offsets_hi = [p for p in page_range if p >= page] - offsets_hi = zip(offsets_hi, ordinals) + offsets_hi = list(zip(offsets_hi, ordinals)) return tuple(offsets_lo+offsets_hi) @@ -255,7 +255,7 @@ def json_event_search(request): 'href': "http://%s%s?%s" % ( request.META.get('HTTP_HOST'), request.path, - urllib.urlencode(args_cur) + urllib.parse.urlencode(args_cur) ) }, { @@ -263,7 +263,7 @@ def json_event_search(request): 'href': "http://%s%s?%s" % ( request.META.get('HTTP_HOST'), request.path, - urllib.urlencode(args_first) + urllib.parse.urlencode(args_first) ) }, { @@ -271,7 +271,7 @@ def json_event_search(request): 'href': "http://%s%s?%s" % ( request.META.get('HTTP_HOST'), request.path, - urllib.urlencode(args_last) + urllib.parse.urlencode(args_last) ) }, ] @@ -285,7 +285,7 @@ def json_event_search(request): 'href': "http://%s%s?%s" % ( request.META.get('HTTP_HOST'), request.path, - urllib.urlencode(args) + urllib.parse.urlencode(args) ) }, ) @@ -298,7 +298,7 @@ def json_event_search(request): 'href': "http://%s%s?%s" % ( request.META.get('HTTP_HOST'), request.path, - urllib.urlencode(args) + urllib.parse.urlencode(args) ) }, ) @@ -507,7 +507,7 @@ def app_event(request, identifier=None): ) except DuplicateEventError as e: return HttpResponse( - "An event with id='{}' exists.".format(e.message), + "An event with id='{}' exists.".format(e), status=409, content_type="text/plain" ) if type(newEvent) == HttpResponse: diff --git a/setup.py b/setup.py index bc37cbf..ebe2b71 100755 --- a/setup.py +++ b/setup.py @@ -9,12 +9,14 @@ install_requires = [ 'lxml >= 3.0.0', - 'codalib>=1.0.1' + 'codalib' ] +dependency_links = ['codalib @ git+https://github.com/unt-libraries/codalib'] + setup( name="django-premis-event-service", - version="1.2.5", + version="2.0.0", packages=find_packages(exclude=["tests", ]), include_package_data=True, license="BSD", @@ -32,8 +34,8 @@ "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 2.6", - "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], ) diff --git a/tests/test_models.py b/tests/test_models.py index 6d707cb..333b2ad 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -7,9 +7,9 @@ class TestAgent: - def test_unicode(self): + def test_str(self): agent = factories.AgentFactory.build() - assert agent.agent_name == unicode(agent) + assert agent.agent_name == str(agent) def test_get_absolute_url(self): agent = factories.AgentFactory.build() @@ -19,16 +19,16 @@ def test_get_absolute_url(self): class TestLinkObject: - def test_unicode(self): + def test_str(self): link_object = factories.LinkObjectFactory.build() - assert link_object.object_identifier == unicode(link_object) + assert link_object.object_identifier == str(link_object) class TestEvent: - def test_unicode(self): + def test_str(self): event = factories.EventFactory.build() - assert event.event_identifier == unicode(event) + assert event.event_identifier == str(event) @pytest.mark.django_db def test_link_objects_string_with_link_objects(self): diff --git a/tests/test_presentation.py b/tests/test_presentation.py index ae76688..a403537 100644 --- a/tests/test_presentation.py +++ b/tests/test_presentation.py @@ -1,6 +1,6 @@ from datetime import datetime -from mock import patch +from unittest.mock import patch from lxml import etree, objectify import pytest @@ -41,7 +41,7 @@ def test_validate_event_fixture(self, event_xml, premis_schema): assert isinstance(exml, etree._Element) assert isinstance(premis_schema, etree.XMLSchema) # validate event fixture doc against premis v2.3 XSD - premis_schema.assert_(exml) + premis_schema.assertValid(exml) def test_returns_event(self, event_xml): tree = etree.fromstring(event_xml.obj_xml) @@ -232,7 +232,7 @@ def test_validate_agent_fixture(self, agent_xml, premis_schema): assert isinstance(axml, etree._Element) assert isinstance(premis_schema, etree.XMLSchema) # validate agent fixture doc against premis v2.3 XSD - premis_schema.assert_(axml) + premis_schema.assertValid(axml) def test_returns_agent(self, agent_xml): agent = presentation.premisAgentXMLToObject(agent_xml.obj_xml) @@ -279,7 +279,7 @@ def test_raises_exception_when_agent_identifier_is_missing(self, agent_xml): presentation.premisAgentXMLToObject(etree.tostring(xml_obj)) expected_message = "Unable to set 'agent_identifier'" - assert expected_message in str(e), 'The exception message matches' + assert expected_message in str(e.value), 'The exception message matches' def test_sets_agent_type(self, agent_xml): agent = presentation.premisAgentXMLToObject(agent_xml.obj_xml) @@ -294,7 +294,7 @@ def test_raises_exception_when_agent_type_is_missing(self, agent_xml): presentation.premisAgentXMLToObject(etree.tostring(xml_obj)) expected_message = "Unable to set 'agent_type'" - assert expected_message in str(e), 'The exception message matches' + assert expected_message in str(e.value), 'The exception message matches' def test_sets_agent_name(self, agent_xml): agent = presentation.premisAgentXMLToObject(agent_xml.obj_xml) @@ -309,7 +309,7 @@ def test_raises_exception_when_agent_name_is_missing(self, agent_xml): presentation.premisAgentXMLToObject(etree.tostring(xml_obj)) expected_message = "Unable to set 'agent_name'" - assert expected_message in str(e), 'The exception message matches' + assert expected_message in str(e.value), 'The exception message matches' def test_sets_agent_note(self, agent_xml): agent = presentation.premisAgentXMLToObject(agent_xml.obj_xml) @@ -332,7 +332,7 @@ def test_validate_event_fixture(self, event_xml, premis_schema): assert isinstance(exml, etree._Element) assert isinstance(premis_schema, etree.XMLSchema) # validate event fixture doc against premis v2.3 XSD - premis_schema.assert_(exml) + premis_schema.assertValid(exml) def test_returns_correct_event_object(self, event_xml): tree = etree.fromstring(event_xml.entry_xml) @@ -528,7 +528,7 @@ def test_link_object_role(self): def test_validate_eventxml(self, premis_schema): event = factories.EventFactory() event_xml = presentation.objectToPremisEventXML(event) - premis_schema.assert_(event_xml) + premis_schema.assertValid(event_xml) @pytest.mark.django_db @@ -583,7 +583,7 @@ def test_agent_type(self): def test_validate_agentxml(self, premis_schema): agent = factories.AgentFactory() agent_xml = presentation.objectToAgentXML(agent) - premis_schema.assert_(agent_xml) + premis_schema.assertValid(agent_xml) @pytest.mark.django_db diff --git a/tests/test_views.py b/tests/test_views.py index a16cdcd..b9ea5f7 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -129,7 +129,7 @@ def test_json_agent_payload(rf): agent = factories.AgentFactory.create() request = rf.get('/') response = views.json_agent(request, agent.agent_identifier) - payload = json.loads(response.content) + payload = json.loads(response.content.decode('utf-8')) assert agent.get_absolute_url() in payload['id'] assert payload['type'] == agent.agent_type @@ -182,7 +182,8 @@ def test_eventXML(rf): request = rf.get('/') response = views.eventXML(request) assert response.status_code == 200 - assert "So you would like XML for the event with identifier" in response.content + assert b"So you would like XML for the event with identifier" in \ + response.content def test_findEvent_returns_ok(rf): @@ -231,8 +232,8 @@ def test_findEvent_finds_multiple_events(rf): new_event, old_event = sorted([event1, event2], key=lambda e: e.event_date_time) # Check that only the oldest event is present in the xml content. - assert old_event.event_identifier in response.content - assert new_event.event_identifier not in response.content + assert old_event.event_identifier in response.content.decode('utf-8') + assert new_event.event_identifier not in response.content.decode('utf-8') class TestAppAgent: @@ -269,7 +270,7 @@ def test_post_response_content(self, rf, agent_xml): response = views.app_agent(request) identifier = agent_xml.identifier - assert identifier in response.content + assert identifier in response.content.decode('utf-8') @pytest.mark.xfail(reason='POST request without body raises uncaught exception.') def test_post_without_body_is_handled(self, rf): @@ -341,8 +342,8 @@ def test_put_response_content(self, agent_xml, rf): updated_agent = models.Agent.objects.get(agent_identifier=identifier) - assert updated_agent.agent_name in response.content - assert updated_agent.agent_type in response.content + assert updated_agent.agent_name in response.content.decode('utf-8') + assert updated_agent.agent_type in response.content.decode('utf-8') def test_get_with_identifier_returns_ok(self, rf): agent = factories.AgentFactory.create() @@ -360,12 +361,12 @@ def test_get_with_identifier_response_content(self, rf): agent = factories.AgentFactory.create() request = rf.get('/') response = views.app_agent(request, agent.agent_identifier) - assert agent.agent_identifier in response.content + assert agent.agent_identifier in response.content.decode('utf-8') def test_head_without_identifier(self, rf): request = rf.head('/') response = views.app_agent(request) - assert response.content == '', 'The message body must be empty' + assert response.content == b'', b'The message body must be empty' assert response.status_code == 200 def test_head_and_get_headers_match_without_identifier(self, rf): @@ -383,7 +384,7 @@ def test_head_with_identifier(self, rf): agent = factories.AgentFactory.create() request = rf.head('/') response = views.app_agent(request, agent.agent_identifier) - assert response.content == '', 'The message body must be empty' + assert response.content == b'', b'The message body must be empty' assert response.status_code == 200 def test_head_and_get_headers_match_with_identifier(self, rf): @@ -409,12 +410,12 @@ def response_has_event(self, response, event): xml = objectify.fromstring(response.content) if len(xml.entry) != 1: return False - if event.event_identifier not in response.content: + if event.event_identifier not in response.content.decode('utf-8'): return False return True def response_includes_event(self, response, event): - return event.event_identifier in response.content + return event.event_identifier in response.content.decode('utf-8') def test_post_returns_created(self, event_xml, rf): request = rf.post( @@ -445,7 +446,7 @@ def test_post_response_content(self, event_xml, rf): response = views.app_event(request) identifier = event_xml.identifier - assert identifier in response.content + assert identifier in response.content.decode('utf-8') def test_post_creates_event(self, event_xml, rf): assert models.Event.objects.count() == 0 @@ -458,6 +459,20 @@ def test_post_creates_event(self, event_xml, rf): views.app_event(request) assert models.Event.objects.count() == 1 + def test_post_DuplicateEventError(self, event_xml, rf): + request = rf.post( + '/', + event_xml.entry_xml, + content_type='application/xml', + HTTP_HOST='example.com') + + # Try to POST the same event twice. + views.app_event(request) + response = views.app_event(request) + assert response.status_code == 409 + expected_response = "An event with id='{}' exists.".format(event_xml.identifier) + assert expected_response in response.content.decode('utf-8') + def test_put_returns_ok(self, event_xml, rf): identifier = event_xml.identifier factories.EventFactory.create(event_identifier=identifier) @@ -496,8 +511,7 @@ def test_list_number_of_events(self, rf): factories.EventFactory.create_batch(self.RESULTS_PER_PAGE * 2) request = rf.get('/') response = views.app_event(request) - xml = objectify.fromstring(response.content) - + xml = objectify.fromstring(response.content.decode('utf-8')) assert len(xml.entry) == self.RESULTS_PER_PAGE @pytest.mark.xfail(reason='Global name DATE_FORMAT is not defined.') @@ -528,7 +542,6 @@ def test_list_filtering_by_linking_object_id(self, rf): url = '/?link_object_id={0}'.format(linking_object.object_identifier) request = rf.get(url) response = views.app_event(request) - assert self.response_has_event(response, event) def test_list_filtering_by_event_outcome(self, rf): @@ -575,8 +588,7 @@ def test_list_ordering_descending(self, rf): # Pure Python vs the Django ORM sort items with matching keys differently. request = rf.get('?orderby=event_identifier&orderdir=descending') response = views.app_event(request) - - list_events = objectify.fromstring(response.content).entry + list_events = objectify.fromstring(response.content.decode('utf-8')).entry ordered_event_ids = [f.title for f in list_events] event_ids = [f.event_identifier for f in events] @@ -610,7 +622,7 @@ def test_get_with_identifier_content(self, rf): event = factories.EventFactory.create() request = rf.get('/', HTTP_HOST='example.com') response = views.app_event(request, event.event_identifier) - assert event.event_identifier in response.content + assert event.event_identifier in response.content.decode('utf-8') def test_delete_returns_ok(self, rf): event = factories.EventFactory.create() @@ -633,7 +645,7 @@ def test_delete_response_content(self, rf): event = factories.EventFactory.create() request = rf.delete('/', HTTP_HOST='example.com') response = views.app_event(request, event.event_identifier) - assert event.event_identifier in response.content + assert event.event_identifier in response.content.decode('utf-8') def test_delete_removes_event(self, rf): event = factories.EventFactory.create() @@ -644,7 +656,7 @@ def test_delete_removes_event(self, rf): def test_head_without_identifier(self, rf): request = rf.head('/') response = views.app_event(request) - assert response.content == '', 'The message body must be empty' + assert response.content == b'', b'The message body must be empty' assert response.status_code == 200 def test_head_and_get_headers_match_without_identifier(self, rf): @@ -662,7 +674,7 @@ def test_head_with_identifier(self, rf): event = factories.EventFactory.create() request = rf.head('/') response = views.app_event(request, event.event_identifier) - assert response.content == '', 'The message body must be empty' + assert response.content == b'', b'The message body must be empty' assert response.status_code == 200 def test_head_and_get_headers_match_with_identifier(self, rf): @@ -744,7 +756,7 @@ class TestJsonEventSearch: def response_has_entry(self, response, event): """True if the event is the only Event in the response content.""" - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) entries = data['feed']['entry'] filtered_entry = entries[0] @@ -755,7 +767,7 @@ def response_has_entry(self, response, event): return True def response_includes_event(self, response, event): - events = json.loads(response.content)['feed']['entry'] + events = json.loads(response.content.decode('utf-8'))['feed']['entry'] event_ids = [e['identifier'] for e in events] return event.event_identifier in event_ids @@ -775,7 +787,7 @@ def test_no_results(self, rf): """ request = rf.get('/') response = views.json_event_search(request) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) assert data.get('feed') is not None assert data.get('feed', {}).get('entry') is not None assert not len(data.get('feed', {}).get('entry')) @@ -786,14 +798,14 @@ def test_results_per_page(self, rf): factories.EventFactory.create_batch(per_page*4) request = rf.get('/') response = views.json_event_search(request) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) assert len(data['feed']['entry']) == per_page def test_opensearch_query(self, rf): factories.EventFactory.create_batch(10) request = rf.get('/fakefield=true') response = views.json_event_search(request) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) assert data['feed']['opensearch:Query'] == request.GET @@ -801,7 +813,7 @@ def test_opensearch_itemsPerPage(self, rf): factories.EventFactory.create_batch(10) request = rf.get('/') response = views.json_event_search(request) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) assert data['feed']['opensearch:itemsPerPage'] == self.RESULTS_PER_PAGE @@ -809,7 +821,7 @@ def test_opensearch_startIndex(self, rf): factories.EventFactory.create_batch(10) request = rf.get('/') response = views.json_event_search(request) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) assert data['feed']['opensearch:startIndex'] == '1' @@ -818,7 +830,7 @@ def test_opensearch_totalResults(self, rf): factories.EventFactory.create_batch(num_events) request = rf.get('/') response = views.json_event_search(request) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) assert data['feed']['opensearch:totalResults'] == num_events @@ -827,7 +839,7 @@ def test_pagination_links(self, rf): factories.EventFactory.create_batch(num_events) request = rf.get('/?page=2') response = views.json_event_search(request) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) assert len(data['feed']['link']) == 5 diff --git a/tests/urls.py b/tests/urls.py index 2252770..9ac5370 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,7 +1,7 @@ import os from django.conf.urls import include, url -import settings +from . import settings from django.contrib import admin admin.autodiscover() diff --git a/tox.ini b/tox.ini old mode 100644 new mode 100755 index 83650cb..fdae8fe --- a/tox.ini +++ b/tox.ini @@ -8,8 +8,8 @@ exclude = *migrations/* [tox] envlist= - py27-django111 - flake8 + py3{6,7}-django111 + py37-flake8 [testenv] passenv = @@ -18,12 +18,13 @@ passenv = # In docker, tests run under mysql, because that PES_BACKEND is explicitly # set in docker-compose. PES_BACKEND -basepython = python2.7 +basepython = python3 whitelist_externals = python pip deps = pipenv +skip_install = true commands = pipenv install --dev --ignore-pipfile @@ -36,11 +37,12 @@ commands = # Reset to original dependencies. pipenv install --dev --ignore-pipfile -[testenv:flake8] -basepython = python2.7 +[testenv:py37-flake8] +basepython = python3.7 deps = pipenv flake8 +skip_install = true commands = pipenv install --dev --ignore-pipfile pipenv run flake8 setup.py premis_event_service tests