mirror of
https://github.com/pinry/pinry.git
synced 2026-05-07 23:16:40 +02:00
Merge pull request #128 from winkidney/feature/upgrade2new-django
Feature: Upgrade django to 1.11 LTS to fix webp saving error
This commit is contained in:
14
Makefile
Normal file
14
Makefile
Normal file
@@ -0,0 +1,14 @@
|
||||
backup-images:
|
||||
pipenv run python manage.py dumpdata django_images > db-backup.django_images.json
|
||||
backup-all:
|
||||
pipenv run python manage.py dumpdata > db-backup.all.json
|
||||
migrate:
|
||||
pipenv run python manage.py migrate
|
||||
recover-all:
|
||||
pipenv run python manage.py loaddata db-backup.all.json
|
||||
serve:
|
||||
pipenv run python manage.py runserver 0.0.0.0:8000
|
||||
install:
|
||||
pipenv install
|
||||
test:
|
||||
pipenv run python manage.py test
|
||||
11
Pipfile
11
Pipfile
@@ -1,25 +1,20 @@
|
||||
[[source]]
|
||||
|
||||
url = "https://pypi.python.org/simple"
|
||||
name = "pypi"
|
||||
verify_ssl = true
|
||||
|
||||
|
||||
[dev-packages]
|
||||
|
||||
"flake8" = "*"
|
||||
|
||||
qrcode = "*"
|
||||
|
||||
[packages]
|
||||
|
||||
django = "<1.9,>=1.8"
|
||||
django = ">=1.11,<1.12"
|
||||
pillow = "*"
|
||||
requests = "*"
|
||||
django-taggit = "*"
|
||||
django-images = "*"
|
||||
django-braces = "*"
|
||||
django-compressor = "*"
|
||||
django-tastypie = "==0.12.2"
|
||||
django-tastypie = "*"
|
||||
mock = "*"
|
||||
factory-boy = "<2.0,>=1.3"
|
||||
gunicorn = "*"
|
||||
|
||||
277
Pipfile.lock
generated
277
Pipfile.lock
generated
@@ -1,20 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "03110b40f521ca462a63d092602bae16130febac8dfea6f7f536e19ab622d975"
|
||||
},
|
||||
"host-environment-markers": {
|
||||
"implementation_name": "cpython",
|
||||
"implementation_version": "3.5.2",
|
||||
"os_name": "posix",
|
||||
"platform_machine": "x86_64",
|
||||
"platform_python_implementation": "CPython",
|
||||
"platform_release": "4.4.0-43-Microsoft",
|
||||
"platform_system": "Linux",
|
||||
"platform_version": "#1-Microsoft Wed Dec 31 14:42:53 PST 2014",
|
||||
"python_full_version": "3.5.2",
|
||||
"python_version": "3.5",
|
||||
"sys_platform": "linux"
|
||||
"sha256": "68c12441be13a252f7fdd1b5532c7befdfa56fc8307ab75a2b197a1946a54bf2"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@@ -29,192 +16,194 @@
|
||||
"default": {
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296",
|
||||
"sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d"
|
||||
"sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638",
|
||||
"sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"
|
||||
],
|
||||
"version": "==2018.1.18"
|
||||
"version": "==2018.8.24"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691",
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:d8e2fd119756ab10b43a31052c3c8efbc262064b81eecb7871372de4d37b1a94",
|
||||
"sha256:c7611cdd5e2539a443b7960c7cafd867d986c2720a1b44808deaa60ce3da50c7"
|
||||
"sha256:8176ac7985fe6737ce3d6b2531b4a2453cb7c3377c9db00bacb2b3320f4a1311",
|
||||
"sha256:b18235d82426f09733d2de9910cee975cf52ff05e5f836681eb957d105a05a40"
|
||||
],
|
||||
"version": "==1.8.18"
|
||||
"index": "pypi",
|
||||
"version": "==1.11.15"
|
||||
},
|
||||
"django-appconf": {
|
||||
"hashes": [
|
||||
"sha256:ddab987d14b26731352c01ee69c090a4ebfc9141ed223bef039d79587f22acd9",
|
||||
"sha256:6a4d9aea683b4c224d97ab8ee11ad2d29a37072c0c6c509896dd9857466fb261"
|
||||
"sha256:6a4d9aea683b4c224d97ab8ee11ad2d29a37072c0c6c509896dd9857466fb261",
|
||||
"sha256:ddab987d14b26731352c01ee69c090a4ebfc9141ed223bef039d79587f22acd9"
|
||||
],
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"django-braces": {
|
||||
"hashes": [
|
||||
"sha256:7e0cb698f3cd17cc58d50aea4a663233f44228144b81d85e1b9bad05aa7f3f80",
|
||||
"sha256:7c5b91c75240ccf2eb124fe3b09dd4978e8f4d412b553bb791920bd55839159c"
|
||||
"sha256:a457d74ea29478123c0c4652272681b3cea0bf1232187fd9f9b6f1d97d32a890",
|
||||
"sha256:ba68e98b817c6f01d71d10849f359979617b3fe4cefb7f289adefddced092ddc"
|
||||
],
|
||||
"version": "==1.12.0"
|
||||
"index": "pypi",
|
||||
"version": "==1.13.0"
|
||||
},
|
||||
"django-compressor": {
|
||||
"hashes": [
|
||||
"sha256:7732676cfb9d58498dfb522b036f75f3f253f72ea1345ac036434fdc418c2e57",
|
||||
"sha256:9616570e5b08e92fa9eadc7a1b1b49639cce07ef392fc27c74230ab08075b30f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.2"
|
||||
},
|
||||
"django-images": {
|
||||
"hashes": [
|
||||
"sha256:0f6e079fa33683d765b6dec23e370750d8861d6de290b0346512d917da2d1461"
|
||||
],
|
||||
"version": "==0.4.3"
|
||||
},
|
||||
"django-taggit": {
|
||||
"hashes": [
|
||||
"sha256:58aa3e59e0643446e102523f22d137300298e2a537b1c5b0c310d99143f2c2b8",
|
||||
"sha256:fd13e304ba37ff09e601c4797d893fb7d3e699a789b5afb0b09d686f94470441"
|
||||
"sha256:a21cbe7e0879f1364eef1c88a2eda89d593bf000ebf51c3f00423c6927075dce",
|
||||
"sha256:db4430ec99265341e05d0274edb0279163bd74357241f7b4d9274bdcb3338b17"
|
||||
],
|
||||
"version": "==0.22.2"
|
||||
"index": "pypi",
|
||||
"version": "==0.23.0"
|
||||
},
|
||||
"django-tastypie": {
|
||||
"hashes": [
|
||||
"sha256:7feccf811a8c2c60bc227e0d956015f0ea6097c804660e8f4a4ada0e74817c24",
|
||||
"sha256:21625ce191ccb734f47b90ef92b603d04366bb67d0eb52bd060a8d76e6706018"
|
||||
"sha256:1fbf61ec7467eec70bd1abcb14e3b1dc67e47cc3642ad16ed8a3709f4140678b"
|
||||
],
|
||||
"version": "==0.12.2"
|
||||
"index": "pypi",
|
||||
"version": "==0.14.1"
|
||||
},
|
||||
"factory-boy": {
|
||||
"hashes": [
|
||||
"sha256:bd5d87634946c8831c0d1389b5995da5dd64ccd97088eebc311eb0c9ef75ae3b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"gunicorn": {
|
||||
"hashes": [
|
||||
"sha256:75af03c99389535f218cc596c7de74df4763803f7b63eb09d77e92b3956b36c6",
|
||||
"sha256:eee1169f0ca667be05db3351a0960765620dad53f53434262ff8901b68a1b622"
|
||||
"sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471",
|
||||
"sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3"
|
||||
],
|
||||
"version": "==19.7.1"
|
||||
"index": "pypi",
|
||||
"version": "==19.9.0"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4",
|
||||
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f"
|
||||
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
|
||||
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
|
||||
],
|
||||
"version": "==2.6"
|
||||
"version": "==2.7"
|
||||
},
|
||||
"mock": {
|
||||
"hashes": [
|
||||
"sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1",
|
||||
"sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"pbr": {
|
||||
"hashes": [
|
||||
"sha256:60c25b7dfd054ef9bb0ae327af949dd4676aa09ac3a9471cdc871d8a9213f9ac",
|
||||
"sha256:05f61c71aaefc02d8e37c0a3eeb9815ff526ea28b3b76324769e6158d7f95be1"
|
||||
"sha256:1b8be50d938c9bb75d0eaf7eda111eec1bf6dc88a62a6412e33bf077457e0f45",
|
||||
"sha256:b486975c0cafb6beeb50ca0e17ba047647f229087bd74e37f4a7e2cac17d2caa"
|
||||
],
|
||||
"version": "==3.1.1"
|
||||
"version": "==4.2.0"
|
||||
},
|
||||
"pillow": {
|
||||
"hashes": [
|
||||
"sha256:718ec7a122b28d64afc5fbc3a9b99bb0545ef511373cac06fe7624520e82cb20",
|
||||
"sha256:801cca8923508311bf5d6d0f7da5362552e8208ebd8ec0d7b9f2cd2ff5705734",
|
||||
"sha256:43334f9581cd067945b8898cef9eb5714ee4883f8de0304c011f1dbdb1d4e2aa",
|
||||
"sha256:153ec6f18f7b61641e0e6e502acfaf4a06c9aba2ea11c0b4b3578ea9f13a4a4a",
|
||||
"sha256:25193f934d37d836a6b1f4c062ce574a96cbca7c6d9dc8ddfbbac7f9c54deaa4",
|
||||
"sha256:b85f703c2ffe539313e39ce0676bed0f355cec45a16e58c9ab7417445843047c",
|
||||
"sha256:8580fc58074a16b749905b26cf8363f7b628dd167ba0130f5382cdc91c86b509",
|
||||
"sha256:2fcde9954c8882d1c7f93bb828caa34a4c5e3ee69dbc7895dc8652ad972b455a",
|
||||
"sha256:1a5b93084e01328a1cb1ecdad99d11d75e881e89a95f88d85b523646553b36c2",
|
||||
"sha256:b2240f298482f823576f397bb9f32ea913ad9456c526e141bc6f0a022b37a3e8",
|
||||
"sha256:b1d33c63a55d0d85df0ad02b2c16158fb4d8153afa7b908f1a67330fac694cd6",
|
||||
"sha256:6977cf073d83358b34f93abf5c1f1193b88675fe0e4441e0e28318bc3dcba7a0",
|
||||
"sha256:1912b7230459fd53682dae32b83cbd8e5d642ba36d4be18566f00a9c063aa13d",
|
||||
"sha256:4bd4a71501b6d51db4abc07e1f43f5a6fed0a1a9583cca0b401d6af50284b0db",
|
||||
"sha256:0013f590a8f260df60bcfd65db19d18efc04e7f046c3c82a40e2e2b3292a937c",
|
||||
"sha256:a224651a81e45ef4f1d0164e256c5f6b4abb49f2ae8f22ba2f3a9d0ff338e608",
|
||||
"sha256:c793dfaa130847ccff958492b76ae8b9304e60b8a79a92962cb19e368276a22b",
|
||||
"sha256:0b899ee80920bb533f26581af9b4660bc12aff4562555afe74e429101ebf3c94",
|
||||
"sha256:9525cd680a6f9e80c6c0af03cf973e6505c59f60b4745f682cd1a449e54b31bb",
|
||||
"sha256:35f7d998b8e82fb3fb51ff88b30485eb81cd7dd56ec7e1a8deba23eb88532d44",
|
||||
"sha256:5b0d657460d9f3615876fec6306e97ca15a471f6169b622d76a47e270998acf1",
|
||||
"sha256:ddd16ab250b4fc97db1c47407e78c25216a75c29d29d10ad37e51b7a2ec7b2c3",
|
||||
"sha256:b9f63451084a718eccdeb1e382768c94647915653af4d6019f64560d9e98642b",
|
||||
"sha256:a370d1c570f1d72e877099651e752332444b1c5009381f043c9da5fd47f3ebae",
|
||||
"sha256:dc4b018d5c9b636f7546583c5591b9ea00c328c3e5871992ef5b95bac353f097",
|
||||
"sha256:e126ff4fed71e78333840c07279e1617f63cfca76d63ad5b27d65a7277206a3d",
|
||||
"sha256:fcf64c91fd44485100a2965d23bb0e227d093e91f7e776c5ca3b32574766eb56",
|
||||
"sha256:2c042352b430d678db50c78c5214e19638eff8b688941271da2de21fd298dfe5",
|
||||
"sha256:17fe25efc785194d48c38fad85dce470013ba19d2fb66639e149f14bccf1327f",
|
||||
"sha256:2e818dbe445e86fc6c266973fe540c35125c42eb2cf13a6095e9adaa89c0deb5",
|
||||
"sha256:135e9aa65150c53f7db85bf2bebb8a0e1a48ea850e80cf66e16dd04fa09d309c",
|
||||
"sha256:7dfbefdb3fb911ca9faed307bf309861e9995e36cca6b761c7ba6d9b77a9744a",
|
||||
"sha256:12f29d6c23424f704c66b5b68c02fe0b571504459605cfe36ab8158359b0e1bb",
|
||||
"sha256:f8d49be8c282df8d2e1ab6ab53ab8abd859b1fa6fed384457ee85c9eff64ef97",
|
||||
"sha256:82b172e3264e62372c01b5b009b5b1a02fbb9276cbe5cc57ab00a6d6e5ed9a18",
|
||||
"sha256:57aa6198ba8acba1313c3b743e267d821a60cac77e6026caf0b55ca58d3d23be",
|
||||
"sha256:d60c1625b108432ace8b1fa1a584017e5efa73f107d0f493c7f39c79bebf1d41",
|
||||
"sha256:82d1ff571489765df2816785d532e243bde213752156c227fca595723ec5ff42",
|
||||
"sha256:37cc0339abfa9e295c75d9a7f227d35cb44716feb95057f9449c4a9e9a17daf7",
|
||||
"sha256:931030d1d6282b7900e6b0a7ff9ecdb503b5e1e6781800dab2b71a9f39405bff",
|
||||
"sha256:5cd36804f9f06a914a883fe682df5711d16d7b4f44d43189c5f013e7cd91e149"
|
||||
"sha256:00def5b638994f888d1058e4d17c86dec8e1113c3741a0a8a659039aec59a83a",
|
||||
"sha256:026449b64e559226cdb8e6d8c931b5965d8fc90ec18ebbb0baa04c5b36503c72",
|
||||
"sha256:03dbb224ee196ef30ed2156d41b579143e1efeb422974719a5392fc035e4f574",
|
||||
"sha256:03eb0e04f929c102ae24bc436bf1c0c60a4e63b07ebd388e84d8b219df3e6acd",
|
||||
"sha256:1be66b9a89e367e7d20d6cae419794997921fe105090fafd86ef39e20a3baab2",
|
||||
"sha256:1e977a3ed998a599bda5021fb2c2889060617627d3ae228297a529a082a3cd5c",
|
||||
"sha256:22cf3406d135cfcc13ec6228ade774c8461e125c940e80455f500638429be273",
|
||||
"sha256:24adccf1e834f82718c7fc8e3ec1093738da95144b8b1e44c99d5fc7d3e9c554",
|
||||
"sha256:2a3e362c97a5e6a259ee9cd66553292a1f8928a5bdfa3622fdb1501570834612",
|
||||
"sha256:3832e26ecbc9d8a500821e3a1d3765bda99d04ae29ffbb2efba49f5f788dc934",
|
||||
"sha256:4fd1f0c2dc02aaec729d91c92cd85a2df0289d88e9f68d1e8faba750bb9c4786",
|
||||
"sha256:4fda62030f2c515b6e2e673c57caa55cb04026a81968f3128aae10fc28e5cc27",
|
||||
"sha256:5044d75a68b49ce36a813c82d8201384207112d5d81643937fc758c05302f05b",
|
||||
"sha256:522184556921512ec484cb93bd84e0bab915d0ac5a372d49571c241a7f73db62",
|
||||
"sha256:5914cff11f3e920626da48e564be6818831713a3087586302444b9c70e8552d9",
|
||||
"sha256:6661a7908d68c4a133e03dac8178287aa20a99f841ea90beeb98a233ae3fd710",
|
||||
"sha256:79258a8df3e309a54c7ef2ef4a59bb8e28f7e4a8992a3ad17c24b1889ced44f3",
|
||||
"sha256:7d74c20b8f1c3e99d3f781d3b8ff5abfefdd7363d61e23bdeba9992ff32cc4b4",
|
||||
"sha256:81918afeafc16ba5d9d0d4e9445905f21aac969a4ebb6f2bff4b9886da100f4b",
|
||||
"sha256:8194d913ca1f459377c8a4ed8f9b7ad750068b8e0e3f3f9c6963fcc87a84515f",
|
||||
"sha256:84d5d31200b11b3c76fab853b89ac898bf2d05c8b3da07c1fcc23feb06359d6e",
|
||||
"sha256:989981db57abffb52026b114c9a1f114c7142860a6d30a352d28f8cbf186500b",
|
||||
"sha256:a3d7511d3fad1618a82299aab71a5fceee5c015653a77ffea75ced9ef917e71a",
|
||||
"sha256:b3ef168d4d6fd4fa6685aef7c91400f59f7ab1c0da734541f7031699741fb23f",
|
||||
"sha256:c1c5792b6e74bbf2af0f8e892272c2a6c48efa895903211f11b8342e03129fea",
|
||||
"sha256:c5dcb5a56aebb8a8f2585042b2f5c496d7624f0bcfe248f0cc33ceb2fd8d39e7",
|
||||
"sha256:e2bed4a04e2ca1050bb5f00865cf2f83c0b92fd62454d9244f690fcd842e27a4",
|
||||
"sha256:e87a527c06319428007e8c30511e1f0ce035cb7f14bb4793b003ed532c3b9333",
|
||||
"sha256:f63e420180cbe22ff6e32558b612e75f50616fc111c5e095a4631946c782e109",
|
||||
"sha256:f8b3d413c5a8f84b12cd4c5df1d8e211777c9852c6be3ee9c094b626644d3eab"
|
||||
],
|
||||
"version": "==5.0.0"
|
||||
"index": "pypi",
|
||||
"version": "==5.2.0"
|
||||
},
|
||||
"psycopg2": {
|
||||
"hashes": [
|
||||
"sha256:aeaba399254ca79c299d9fe6aa811d3c3eac61458dee10270de7f4e71c624998",
|
||||
"sha256:1d90379d01d0dc50ae9b40c863933d87ff82d51dd7d52cea5d1cb7019afd72cd",
|
||||
"sha256:36030ca7f4b4519ee4f52a74edc4ec73c75abfb6ea1d80ac7480953d1c0aa3c3",
|
||||
"sha256:7cbc3b21ce2f681ca9ad2d8c0901090b23a30c955e980ebf1006d41f37068a95",
|
||||
"sha256:b178e0923c93393e16646155794521e063ec17b7cc9f943f15b7d4b39776ea2c",
|
||||
"sha256:fe6a7f87356116f5ea840c65b032af17deef0e1a5c34013a2962dd6f99b860dd",
|
||||
"sha256:6f302c486132f8dd11f143e919e236ea4467d53bf18c451cac577e6988ecbd05",
|
||||
"sha256:888bba7841116e529f407f15c6d28fe3ef0760df8c45257442ec2f14f161c871",
|
||||
"sha256:932a4c101af007cb3132b1f8a9ffef23386acc53dad46536dc5ba43a3235ae02",
|
||||
"sha256:179c52eb870110a8c1b460c86d4f696d58510ea025602cd3f81453746fccb94f",
|
||||
"sha256:33f9e1032095e1436fa9ec424abcbd4c170da934fb70e391c5d78275d0307c75",
|
||||
"sha256:092a80da1b052a181b6e6c765849c9b32d46c5dac3b81bf8c9b83e697f3cdbe8",
|
||||
"sha256:f3d3a88128f0c219bdc5b2d9ccd496517199660cea021c560a3252116df91cbd",
|
||||
"sha256:19983b77ec1fc2a210092aa0333ee48811fd9fb5f194c6cd5b927ed409aea5f8",
|
||||
"sha256:027ae518d0e3b8fff41990e598bc7774c3d08a3a20e9ecc0b59fb2aaaf152f7f",
|
||||
"sha256:363fbbf4189722fc46779be1fad2597e2c40b3f577dc618f353a46391cf5d235",
|
||||
"sha256:d74cf9234ba76426add5e123449be08993a9b13ff434c6efa3a07caa305a619f",
|
||||
"sha256:32702e3bd8bfe12b36226ba9846ed9e22336fc4bd710039d594b36bd432ae255",
|
||||
"sha256:8eb94c0625c529215b53c08fb4e461546e2f3fc96a49c13d5474b5ad7aeab6cf",
|
||||
"sha256:8ebba5314c609a05c6955e5773c7e0e57b8dd817e4f751f30de729be58fa5e78",
|
||||
"sha256:27467fd5af1dcc0a82d72927113b8f92da8f44b2efbdb8906bd76face95b596d",
|
||||
"sha256:b68e89bb086a9476fa85298caab43f92d0a6af135a5f433d1f6b6d82cafa7b55",
|
||||
"sha256:0b9851e798bae024ed1a2a6377a8dab4b8a128a56ed406f572f9f06194e4b275",
|
||||
"sha256:733166464598c239323142c071fa4c9b91c14359176e5ae7e202db6bcc1d2eb5",
|
||||
"sha256:ad75fe10bea19ad2188c5cb5fc4cdf53ee808d9b44578c94a3cd1e9fc2beb656",
|
||||
"sha256:8966829cb0d21a08a3c5ac971a2eb67c3927ae27c247300a8476554cc0ce2ae8",
|
||||
"sha256:8bf51191d60f6987482ef0cfe8511bbf4877a5aa7f313d7b488b53189cf26209"
|
||||
"sha256:0b9e48a1c1505699a64ac58815ca99104aacace8321e455072cee4f7fe7b2698",
|
||||
"sha256:0f4c784e1b5a320efb434c66a50b8dd7e30a7dc047e8f45c0a8d2694bfe72781",
|
||||
"sha256:0fdbaa32c9eb09ef09d425dc154628fca6fa69d2f7c1a33f889abb7e0efb3909",
|
||||
"sha256:11fbf688d5c953c0a5ba625cc42dea9aeb2321942c7c5ed9341a68f865dc8cb1",
|
||||
"sha256:19eaac4eb25ab078bd0f28304a0cb08702d120caadfe76bb1e6846ed1f68635e",
|
||||
"sha256:3232ec1a3bf4dba97fbf9b03ce12e4b6c1d01ea3c85773903a67ced725728232",
|
||||
"sha256:36f8f9c216fcca048006f6dd60e4d3e6f406afde26cfb99e063f137070139eaf",
|
||||
"sha256:59c1a0e4f9abe970062ed35d0720935197800a7ef7a62b3a9e3a70588d9ca40b",
|
||||
"sha256:6506c5ff88750948c28d41852c09c5d2a49f51f28c6d90cbf1b6808e18c64e88",
|
||||
"sha256:6bc3e68ee16f571681b8c0b6d5c0a77bef3c589012352b3f0cf5520e674e9d01",
|
||||
"sha256:6dbbd7aabbc861eec6b910522534894d9dbb507d5819bc982032c3ea2e974f51",
|
||||
"sha256:6e737915de826650d1a5f7ff4ac6cf888a26f021a647390ca7bafdba0e85462b",
|
||||
"sha256:6ed9b2cfe85abc720e8943c1808eeffd41daa73e18b7c1e1a228b0b91f768ccc",
|
||||
"sha256:711ec617ba453fdfc66616db2520db3a6d9a891e3bf62ef9aba4c95bb4e61230",
|
||||
"sha256:844dacdf7530c5c612718cf12bc001f59b2d9329d35b495f1ff25045161aa6af",
|
||||
"sha256:86b52e146da13c896e50c5a3341a9448151f1092b1a4153e425d1e8b62fec508",
|
||||
"sha256:985c06c2a0f227131733ae58d6a541a5bc8b665e7305494782bebdb74202b793",
|
||||
"sha256:a86dfe45f4f9c55b1a2312ff20a59b30da8d39c0e8821d00018372a2a177098f",
|
||||
"sha256:aa3cd07f7f7e3183b63d48300666f920828a9dbd7d7ec53d450df2c4953687a9",
|
||||
"sha256:b1964ed645ef8317806d615d9ff006c0dadc09dfc54b99ae67f9ba7a1ec9d5d2",
|
||||
"sha256:b2abbff9e4141484bb89b96eb8eae186d77bc6d5ffbec6b01783ee5c3c467351",
|
||||
"sha256:cc33c3a90492e21713260095f02b12bee02b8d1f2c03a221d763ce04fa90e2e9",
|
||||
"sha256:d7de3bf0986d777807611c36e809b77a13bf1888f5c8db0ebf24b47a52d10726",
|
||||
"sha256:db5e3c52576cc5b93a959a03ccc3b02cb8f0af1fbbdc80645f7a215f0b864f3a",
|
||||
"sha256:e168aa795ffbb11379c942cf95bf813c7db9aa55538eb61de8c6815e092416f5",
|
||||
"sha256:e9ca911f8e2d3117e5241d5fa9aaa991cb22fb0792627eeada47425d706b5ec8",
|
||||
"sha256:eccf962d41ca46e6326b97c8fe0a6687b58dfc1a5f6540ed071ff1474cea749e",
|
||||
"sha256:efa19deae6b9e504a74347fe5e25c2cb9343766c489c2ae921b05f37338b18d1",
|
||||
"sha256:f4b0460a21f784abe17b496f66e74157a6c36116fa86da8bf6aa028b9e8ad5fe",
|
||||
"sha256:f93d508ca64d924d478fb11e272e09524698f0c581d9032e68958cfbdd41faef"
|
||||
],
|
||||
"version": "==2.7.4"
|
||||
"index": "pypi",
|
||||
"version": "==2.7.5"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:95511bae634d69bc7329ba55e646499a842bc4ec342ad54a8cdb65645a0aad3c",
|
||||
"sha256:891c38b2a02f5bb1be3e4793866c8df49c7d19baabf9c1bad62547e0b4866aca"
|
||||
"sha256:1adb80e7a782c12e52ef9a8182bebeb73f1d7e24e374397af06fb4956c8dc5c0",
|
||||
"sha256:e27001de32f627c22380a688bcc43ce83504a7bc5da472209b4c70f02829f0b8"
|
||||
],
|
||||
"version": "==2.6.1"
|
||||
"version": "==2.7.3"
|
||||
},
|
||||
"python-mimeparse": {
|
||||
"hashes": [
|
||||
"sha256:a295f03ff20341491bfe4717a39cd0a8cc9afad619ba44b77e86b0ab8a2b8282",
|
||||
"sha256:76e4b03d700a641fd7761d3cd4fdbbdcd787eade1ebfac43f877016328334f78"
|
||||
"sha256:76e4b03d700a641fd7761d3cd4fdbbdcd787eade1ebfac43f877016328334f78",
|
||||
"sha256:a295f03ff20341491bfe4717a39cd0a8cc9afad619ba44b77e86b0ab8a2b8282"
|
||||
],
|
||||
"version": "==1.6.0"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053",
|
||||
"sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277"
|
||||
],
|
||||
"version": "==2018.5"
|
||||
},
|
||||
"rcssmin": {
|
||||
"hashes": [
|
||||
"sha256:ca87b695d3d7864157773a61263e5abb96006e9ff0e021eff90cbe0e1ba18270"
|
||||
@@ -223,10 +212,11 @@
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
|
||||
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
|
||||
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
|
||||
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
|
||||
],
|
||||
"version": "==2.18.4"
|
||||
"index": "pypi",
|
||||
"version": "==2.19.1"
|
||||
},
|
||||
"rjsmin": {
|
||||
"hashes": [
|
||||
@@ -236,25 +226,27 @@
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb",
|
||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"
|
||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
||||
],
|
||||
"version": "==1.11.0"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
|
||||
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
|
||||
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
|
||||
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
|
||||
],
|
||||
"version": "==1.22"
|
||||
"markers": "python_version < '4' and python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.2.*' and python_version >= '2.6' and python_version != '3.3.*'",
|
||||
"version": "==1.23"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37",
|
||||
"sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0"
|
||||
"sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0",
|
||||
"sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.5.0"
|
||||
},
|
||||
"mccabe": {
|
||||
@@ -266,8 +258,8 @@
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9",
|
||||
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766"
|
||||
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
|
||||
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
|
||||
],
|
||||
"version": "==2.3.1"
|
||||
},
|
||||
@@ -277,6 +269,21 @@
|
||||
"sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
|
||||
],
|
||||
"version": "==1.6.0"
|
||||
},
|
||||
"qrcode": {
|
||||
"hashes": [
|
||||
"sha256:037b0db4c93f44586e37f84c3da3f763874fcac85b2974a69a98e399ac78e1bf",
|
||||
"sha256:de4ffc15065e6ff20a551ad32b6b41264f3c75275675406ddfa8e3530d154be3"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==6.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
||||
],
|
||||
"version": "==1.11.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,15 @@ to skim through format.
|
||||
For more information and a working demo board visit `getpinry.com`_.
|
||||
|
||||
|
||||
Upgrade from 1.x
|
||||
------------------
|
||||
|
||||
For source code users:
|
||||
|
||||
Read our `online doc <doc/upgrade_from_1.x.md>`_ about how to upgrade to 2.x
|
||||
|
||||
For docker users, please contact us for help: )
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
|
||||
114
core/api.py
114
core/api.py
@@ -10,36 +10,101 @@ from .models import Pin, Image
|
||||
from users.models import User
|
||||
|
||||
|
||||
def _is_pin_owner(obj_or_list, user):
|
||||
assert obj_or_list is not None
|
||||
if not isinstance(obj_or_list, (tuple, list)):
|
||||
obj_or_list = (obj_or_list,)
|
||||
results = tuple(
|
||||
obj.submitter == user
|
||||
for obj in obj_or_list
|
||||
if isinstance(obj, Pin)
|
||||
)
|
||||
if len(results) <= 0:
|
||||
raise ValueError(
|
||||
"You should never check permission on %s with this function."
|
||||
% obj_or_list
|
||||
)
|
||||
return all(results)
|
||||
|
||||
|
||||
def _is_authenticated_and_owner(object_list, bundle):
|
||||
if bundle.request.user.is_anonymous():
|
||||
return object_list.none()
|
||||
return object_list.filter(submitter=bundle.request.user)
|
||||
|
||||
|
||||
class PinryAuthorization(DjangoAuthorization):
|
||||
"""
|
||||
Pinry-specific Authorization backend with object-level permission checking.
|
||||
"""
|
||||
def update_detail(self, object_list, bundle):
|
||||
def _is_obj_owner(self, object_list, bundle):
|
||||
klass = self.base_checks(bundle.request, bundle.obj.__class__)
|
||||
|
||||
if klass is False:
|
||||
raise Unauthorized("You are not allowed to access that resource.")
|
||||
return _is_pin_owner(bundle.obj, bundle.request.user)
|
||||
|
||||
permission = '%s.change_%s' % (klass._meta.app_label, klass._meta.model_name)
|
||||
|
||||
if not bundle.request.user.has_perm(permission, bundle.obj):
|
||||
raise Unauthorized("You are not allowed to access that resource.")
|
||||
def read_list(self, object_list, bundle):
|
||||
# This assumes a ``QuerySet`` from ``ModelResource``.
|
||||
return object_list
|
||||
|
||||
def read_detail(self, object_list, bundle):
|
||||
"""
|
||||
User can always read detail of any Pin object.
|
||||
"""
|
||||
return True
|
||||
|
||||
def create_detail(self, object_list, bundle):
|
||||
return self._is_obj_owner(object_list, bundle)
|
||||
|
||||
def update_detail(self, object_list, bundle):
|
||||
return self._is_obj_owner(object_list, bundle)
|
||||
|
||||
def delete_detail(self, object_list, bundle):
|
||||
klass = self.base_checks(bundle.request, bundle.obj.__class__)
|
||||
return self._is_obj_owner(object_list, bundle)
|
||||
|
||||
if klass is False:
|
||||
raise Unauthorized("You are not allowed to access that resource.")
|
||||
def update_list(self, object_list, bundle):
|
||||
return _is_authenticated_and_owner(object_list, bundle)
|
||||
|
||||
permission = '%s.delete_%s' % (klass._meta.app_label, klass._meta.model_name)
|
||||
def delete_list(self, object_list, bundle):
|
||||
return _is_authenticated_and_owner(object_list, bundle)
|
||||
|
||||
if not bundle.request.user.has_perm(permission, bundle.obj):
|
||||
raise Unauthorized("You are not allowed to access that resource.")
|
||||
|
||||
class ImageAuthorization(DjangoAuthorization):
|
||||
"""
|
||||
Pinry-specific Authorization backend with object-level permission checking.
|
||||
"""
|
||||
def __init__(self):
|
||||
DjangoAuthorization.__init__(self)
|
||||
|
||||
def read_list(self, object_list, bundle):
|
||||
return object_list
|
||||
|
||||
def read_detail(self, object_list, bundle):
|
||||
"""
|
||||
User can always read detail of any Pin object.
|
||||
"""
|
||||
return True
|
||||
|
||||
def create_detail(self, object_list, bundle):
|
||||
return bundle.request.user.is_authenticated()
|
||||
|
||||
def update_detail(self, object_list, bundle):
|
||||
return bundle.request.user.is_authenticated()
|
||||
|
||||
def delete_detail(self, object_list, bundle):
|
||||
return bundle.request.user.is_authenticated()
|
||||
|
||||
def update_list(self, object_list, bundle):
|
||||
if not bundle.request.user.is_authenticated():
|
||||
return object_list.none()
|
||||
return object_list
|
||||
|
||||
def delete_list(self, object_list, bundle):
|
||||
if not bundle.request.user.is_authenticated():
|
||||
return object_list.none()
|
||||
return object_list
|
||||
|
||||
|
||||
class UserResource(ModelResource):
|
||||
gravatar = fields.CharField(readonly=True)
|
||||
@@ -80,19 +145,28 @@ class ThumbnailResource(ModelResource):
|
||||
|
||||
|
||||
class ImageResource(ModelResource):
|
||||
standard = fields.ToOneField(ThumbnailResource, full=True,
|
||||
attribute=lambda bundle: filter_generator_for('standard')(bundle))
|
||||
thumbnail = fields.ToOneField(ThumbnailResource, full=True,
|
||||
attribute=lambda bundle: filter_generator_for('thumbnail')(bundle))
|
||||
square = fields.ToOneField(ThumbnailResource, full=True,
|
||||
attribute=lambda bundle: filter_generator_for('square')(bundle))
|
||||
standard = fields.ToOneField(
|
||||
ThumbnailResource, full=True,
|
||||
attribute=lambda bundle: filter_generator_for('standard')(bundle),
|
||||
related_name='thumbnail',
|
||||
)
|
||||
thumbnail = fields.ToOneField(
|
||||
ThumbnailResource, full=True,
|
||||
attribute=lambda bundle: filter_generator_for('thumbnail')(bundle),
|
||||
related_name='thumbnail',
|
||||
)
|
||||
square = fields.ToOneField(
|
||||
ThumbnailResource, full=True,
|
||||
attribute=lambda bundle: filter_generator_for('square')(bundle),
|
||||
related_name='thumbnail',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
fields = ['image', 'width', 'height']
|
||||
include_resource_uri = False
|
||||
resource_name = 'image'
|
||||
queryset = Image.objects.all()
|
||||
authorization = DjangoAuthorization()
|
||||
authorization = ImageAuthorization()
|
||||
|
||||
|
||||
class PinResource(ModelResource):
|
||||
@@ -127,8 +201,8 @@ class PinResource(ModelResource):
|
||||
def dehydrate_tags(self, bundle):
|
||||
return list(map(str, bundle.obj.tags.all()))
|
||||
|
||||
def build_filters(self, filters=None):
|
||||
orm_filters = super(PinResource, self).build_filters(filters)
|
||||
def build_filters(self, filters=None, ignore_bad_filters=False):
|
||||
orm_filters = super(PinResource, self).build_filters(filters, ignore_bad_filters=ignore_bad_filters)
|
||||
if filters and 'tag' in filters:
|
||||
orm_filters['tags__name__in'] = filters['tag'].split(',')
|
||||
return orm_filters
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.conf.urls import include, url
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from tastypie.api import Api
|
||||
@@ -13,7 +13,7 @@ v1_api.register(ThumbnailResource())
|
||||
v1_api.register(PinResource())
|
||||
v1_api.register(UserResource())
|
||||
|
||||
urlpatterns = patterns('',
|
||||
urlpatterns = [
|
||||
url(r'^api/', include(v1_api.urls, namespace='api')),
|
||||
|
||||
url(r'^pins/pin-form/$', TemplateView.as_view(template_name='core/pin_form.html'),
|
||||
@@ -28,4 +28,4 @@ urlpatterns = patterns('',
|
||||
name='recent-pins'),
|
||||
url(r'^$', TemplateView.as_view(template_name='core/pins.html'),
|
||||
name='recent-pins'),
|
||||
)
|
||||
]
|
||||
|
||||
0
django_images/__init__.py
Normal file
0
django_images/__init__.py
Normal file
38
django_images/migrations/0001_initial.py
Normal file
38
django_images/migrations/0001_initial.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django_images.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Image',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('image', models.ImageField(height_field=b'height', width_field=b'width', max_length=255, upload_to=django_images.models.hashed_upload_to)),
|
||||
('height', models.PositiveIntegerField(default=0, editable=False)),
|
||||
('width', models.PositiveIntegerField(default=0, editable=False)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Thumbnail',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('image', models.ImageField(height_field=b'height', width_field=b'width', max_length=255, upload_to=django_images.models.hashed_upload_to)),
|
||||
('size', models.CharField(max_length=100)),
|
||||
('height', models.PositiveIntegerField(default=0, editable=False)),
|
||||
('width', models.PositiveIntegerField(default=0, editable=False)),
|
||||
('original', models.ForeignKey(to='django_images.Image')),
|
||||
],
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='thumbnail',
|
||||
unique_together=set([('original', 'size')]),
|
||||
),
|
||||
]
|
||||
25
django_images/migrations/0002_auto_20180826_0814.py
Normal file
25
django_images/migrations/0002_auto_20180826_0814.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import core.utils
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_images', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='image',
|
||||
name='image',
|
||||
field=models.ImageField(upload_to=core.utils.upload_path, width_field='width', max_length=255, height_field='height'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='thumbnail',
|
||||
name='image',
|
||||
field=models.ImageField(upload_to=core.utils.upload_path, width_field='width', max_length=255, height_field='height'),
|
||||
),
|
||||
]
|
||||
0
django_images/migrations/__init__.py
Normal file
0
django_images/migrations/__init__.py
Normal file
132
django_images/models.py
Normal file
132
django_images/models.py
Normal file
@@ -0,0 +1,132 @@
|
||||
import hashlib
|
||||
import os.path
|
||||
from io import BytesIO
|
||||
|
||||
from django.db import models
|
||||
from django.core.files.uploadedfile import InMemoryUploadedFile
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.dispatch import receiver
|
||||
import PIL
|
||||
|
||||
try:
|
||||
from importlib import import_module
|
||||
except ImportError:
|
||||
from django.utils.importlib import import_module
|
||||
|
||||
from . import utils
|
||||
from .settings import IMAGE_SIZES, IMAGE_PATH, IMAGE_AUTO_DELETE
|
||||
|
||||
|
||||
def hashed_upload_to(instance, filename, **kwargs):
|
||||
image_type = 'original' if isinstance(instance, Image) else 'thumbnail'
|
||||
prefix = 'image/%s/by-md5/' % (image_type,)
|
||||
hasher = hashlib.md5()
|
||||
for chunk in instance.image.chunks():
|
||||
hasher.update(chunk)
|
||||
hash_ = hasher.hexdigest()
|
||||
base, ext = os.path.splitext(filename)
|
||||
return '%(prefix)s%(first)s/%(second)s/%(hash)s/%(base)s%(ext)s' % {
|
||||
'prefix': prefix,
|
||||
'first': hash_[0],
|
||||
'second': hash_[1],
|
||||
'hash': hash_,
|
||||
'base': base,
|
||||
'ext': ext,
|
||||
}
|
||||
|
||||
|
||||
if IMAGE_PATH is None:
|
||||
upload_to = hashed_upload_to
|
||||
else:
|
||||
if callable(IMAGE_PATH):
|
||||
upload_to = IMAGE_PATH
|
||||
else:
|
||||
parts = IMAGE_PATH.split('.')
|
||||
module_name = '.'.join(parts[:-1])
|
||||
module = import_module(module_name)
|
||||
upload_to = getattr(module, parts[-1])
|
||||
|
||||
|
||||
class Image(models.Model):
|
||||
image = models.ImageField(upload_to=upload_to,
|
||||
height_field='height', width_field='width',
|
||||
max_length=255)
|
||||
height = models.PositiveIntegerField(default=0, editable=False)
|
||||
width = models.PositiveIntegerField(default=0, editable=False)
|
||||
|
||||
def get_by_size(self, size):
|
||||
return self.thumbnail_set.get(size=size)
|
||||
|
||||
def get_absolute_url(self, size=None):
|
||||
if not size:
|
||||
return self.image.url
|
||||
try:
|
||||
return self.get_by_size(size).image.url
|
||||
except Thumbnail.DoesNotExist:
|
||||
return reverse('image-thumbnail', args=(self.id, size))
|
||||
|
||||
|
||||
class ThumbnailManager(models.Manager):
|
||||
def get_or_create_at_size(self, image_id, size):
|
||||
image = Image.objects.get(id=image_id)
|
||||
if size not in IMAGE_SIZES:
|
||||
raise ValueError("Received unknown size: %s" % size)
|
||||
try:
|
||||
thumbnail = image.get_by_size(size)
|
||||
except Thumbnail.DoesNotExist:
|
||||
img = utils.scale_and_crop(image.image, **IMAGE_SIZES[size])
|
||||
# save to memory
|
||||
buf = BytesIO()
|
||||
try:
|
||||
img.save(buf, img.format, **img.info)
|
||||
except IOError:
|
||||
if img.info.get('progression'):
|
||||
orig_MAXBLOCK = PIL.ImageFile.MAXBLOCK
|
||||
temp_MAXBLOCK = 1048576
|
||||
if orig_MAXBLOCK >= temp_MAXBLOCK:
|
||||
raise
|
||||
PIL.ImageFile.MAXBLOCK = temp_MAXBLOCK
|
||||
try:
|
||||
img.save(buf, img.format, **img.info)
|
||||
finally:
|
||||
PIL.ImageFile.MAXBLOCK = orig_MAXBLOCK
|
||||
else:
|
||||
raise
|
||||
# and save to storage
|
||||
original_dir, original_file = os.path.split(image.image.name)
|
||||
thumb_file = InMemoryUploadedFile(buf, "image", original_file,
|
||||
None, buf.tell(), None)
|
||||
thumbnail, created = image.thumbnail_set.get_or_create(
|
||||
size=size, defaults={'image': thumb_file})
|
||||
return thumbnail
|
||||
|
||||
|
||||
class Thumbnail(models.Model):
|
||||
original = models.ForeignKey(Image)
|
||||
image = models.ImageField(upload_to=upload_to,
|
||||
height_field='height', width_field='width',
|
||||
max_length=255)
|
||||
size = models.CharField(max_length=100)
|
||||
height = models.PositiveIntegerField(default=0, editable=False)
|
||||
width = models.PositiveIntegerField(default=0, editable=False)
|
||||
|
||||
objects = ThumbnailManager()
|
||||
|
||||
class Meta:
|
||||
unique_together = ('original', 'size')
|
||||
|
||||
def get_absolute_url(self):
|
||||
return self.image.url
|
||||
|
||||
|
||||
@receiver(models.signals.post_save)
|
||||
def original_changed(sender, instance, created, **kwargs):
|
||||
if isinstance(instance, Image):
|
||||
instance.thumbnail_set.all().delete()
|
||||
|
||||
|
||||
@receiver(models.signals.post_delete)
|
||||
def delete_image_files(sender, instance, **kwargs):
|
||||
if isinstance(instance, (Image, Thumbnail)) and IMAGE_AUTO_DELETE:
|
||||
if instance.image.storage.exists(instance.image.name):
|
||||
instance.image.delete(save=False)
|
||||
5
django_images/settings.py
Normal file
5
django_images/settings.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.conf import settings
|
||||
|
||||
IMAGE_PATH = getattr(settings, 'IMAGE_PATH', None)
|
||||
IMAGE_SIZES = getattr(settings, 'IMAGE_SIZES', {})
|
||||
IMAGE_AUTO_DELETE = getattr(settings, 'IMAGE_AUTO_DELETE', True)
|
||||
65
django_images/south_migrations/0001_initial.py
Normal file
65
django_images/south_migrations/0001_initial.py
Normal file
@@ -0,0 +1,65 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'Image'
|
||||
db.create_table(u'django_images_image', (
|
||||
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('image', self.gf('django.db.models.fields.files.ImageField')(max_length=255)),
|
||||
('height', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
|
||||
('width', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
|
||||
))
|
||||
db.send_create_signal(u'django_images', ['Image'])
|
||||
|
||||
# Adding model 'Thumbnail'
|
||||
db.create_table(u'django_images_thumbnail', (
|
||||
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('original', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['django_images.Image'])),
|
||||
('image', self.gf('django.db.models.fields.files.ImageField')(max_length=255)),
|
||||
('size', self.gf('django.db.models.fields.CharField')(max_length=100)),
|
||||
('height', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
|
||||
('width', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
|
||||
))
|
||||
db.send_create_signal(u'django_images', ['Thumbnail'])
|
||||
|
||||
# Adding unique constraint on 'Thumbnail', fields ['image', 'size']
|
||||
db.create_unique(u'django_images_thumbnail', ['image', 'size'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing unique constraint on 'Thumbnail', fields ['image', 'size']
|
||||
db.delete_unique(u'django_images_thumbnail', ['image', 'size'])
|
||||
|
||||
# Deleting model 'Image'
|
||||
db.delete_table(u'django_images_image')
|
||||
|
||||
# Deleting model 'Thumbnail'
|
||||
db.delete_table(u'django_images_thumbnail')
|
||||
|
||||
|
||||
models = {
|
||||
u'django_images.image': {
|
||||
'Meta': {'object_name': 'Image'},
|
||||
'height': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255'}),
|
||||
'width': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
|
||||
},
|
||||
u'django_images.thumbnail': {
|
||||
'Meta': {'unique_together': "(('image', 'size'),)", 'object_name': 'Thumbnail'},
|
||||
'height': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255'}),
|
||||
'original': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['django_images.Image']"}),
|
||||
'size': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'width': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['django_images']
|
||||
@@ -0,0 +1,45 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Removing unique constraint on 'Thumbnail', fields ['image', 'size']
|
||||
db.delete_unique(u'django_images_thumbnail', ['image', 'size'])
|
||||
|
||||
# Adding unique constraint on 'Thumbnail', fields ['original', 'size']
|
||||
db.create_unique(u'django_images_thumbnail', ['original_id', 'size'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing unique constraint on 'Thumbnail', fields ['original', 'size']
|
||||
db.delete_unique(u'django_images_thumbnail', ['original_id', 'size'])
|
||||
|
||||
# Adding unique constraint on 'Thumbnail', fields ['image', 'size']
|
||||
db.create_unique(u'django_images_thumbnail', ['image', 'size'])
|
||||
|
||||
|
||||
models = {
|
||||
u'django_images.image': {
|
||||
'Meta': {'object_name': 'Image'},
|
||||
'height': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255'}),
|
||||
'width': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
|
||||
},
|
||||
u'django_images.thumbnail': {
|
||||
'Meta': {'unique_together': "(('original', 'size'),)", 'object_name': 'Thumbnail'},
|
||||
'height': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255'}),
|
||||
'original': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['django_images.Image']"}),
|
||||
'size': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'width': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['django_images']
|
||||
0
django_images/south_migrations/__init__.py
Normal file
0
django_images/south_migrations/__init__.py
Normal file
0
django_images/templatetags/__init__.py
Normal file
0
django_images/templatetags/__init__.py
Normal file
8
django_images/templatetags/images.py
Normal file
8
django_images/templatetags/images.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def at_size(image, size):
|
||||
return image.get_absolute_url(size=size)
|
||||
208
django_images/tests.py
Normal file
208
django_images/tests.py
Normal file
@@ -0,0 +1,208 @@
|
||||
import mock
|
||||
import qrcode
|
||||
from django.test import TestCase
|
||||
from django.core.files.images import ImageFile
|
||||
from django.conf import settings
|
||||
from django.utils.six import BytesIO
|
||||
from django.core.urlresolvers import reverse
|
||||
from django_images.models import Image, Thumbnail
|
||||
from django_images.templatetags.images import at_size
|
||||
from django_images.utils import scale_and_crop
|
||||
|
||||
|
||||
class ImageModelTest(TestCase):
|
||||
def setUp(self):
|
||||
image_obj = BytesIO()
|
||||
qrcode_obj = qrcode.make('https://mirumee.com/')
|
||||
qrcode_obj.save(image_obj)
|
||||
self.image = Image.objects.create(width=370, height=370,
|
||||
image=ImageFile(image_obj, '01.png'))
|
||||
|
||||
def test_get_by_size(self):
|
||||
size = list(settings.IMAGE_SIZES.keys())[0]
|
||||
thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, size)
|
||||
self.image.get_by_size(size)
|
||||
|
||||
def test_get_absolute_url(self):
|
||||
url = self.image.get_absolute_url()
|
||||
self.assertEqual(url, self.image.image.url)
|
||||
# For thumbnail
|
||||
size = list(settings.IMAGE_SIZES.keys())[0]
|
||||
thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, size)
|
||||
url = self.image.get_absolute_url(size)
|
||||
self.assertEqual(url, thumb.image.url)
|
||||
# Fallback on creation url
|
||||
size = list(settings.IMAGE_SIZES.keys())[1]
|
||||
url = self.image.get_absolute_url(size)
|
||||
fallback_url = reverse('image-thumbnail', args=(self.image.id, size))
|
||||
self.assertEqual(url, fallback_url)
|
||||
|
||||
|
||||
class ThumbnailManagerModelTest(TestCase):
|
||||
def setUp(self):
|
||||
image_obj = BytesIO()
|
||||
qrcode_obj = qrcode.make('https://mirumee.com/')
|
||||
qrcode_obj.save(image_obj)
|
||||
self.image = Image.objects.create(width=370, height=370,
|
||||
image=ImageFile(image_obj, '01.png'))
|
||||
self.size = list(settings.IMAGE_SIZES.keys())[0]
|
||||
|
||||
def test_unknown_size(self):
|
||||
self.assertRaises(ValueError, Thumbnail.objects.get_or_create_at_size,
|
||||
self.image.id, 'foo')
|
||||
|
||||
# TODO: Test the image object and data
|
||||
def test_create(self):
|
||||
thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, self.size)
|
||||
self.assertEqual(self.image.thumbnail_set.count(), 1)
|
||||
|
||||
def test_get(self):
|
||||
thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, self.size)
|
||||
thumb2 = Thumbnail.objects.get_or_create_at_size(self.image.id, self.size)
|
||||
self.assertEqual(thumb.id, thumb2.id)
|
||||
|
||||
|
||||
class ThumbnailModelTest(TestCase):
|
||||
def setUp(self):
|
||||
image_obj = BytesIO()
|
||||
qrcode_obj = qrcode.make('https://mirumee.com/')
|
||||
qrcode_obj.save(image_obj)
|
||||
self.image = Image.objects.create(width=370, height=370,
|
||||
image=ImageFile(image_obj, '01.png'))
|
||||
size = list(settings.IMAGE_SIZES.keys())[0]
|
||||
self.thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, size)
|
||||
|
||||
def test_get_absolute_url(self):
|
||||
url = self.thumb.get_absolute_url()
|
||||
self.assertEqual(url, self.thumb.image.url)
|
||||
|
||||
|
||||
class PostSaveSignalOriginalChangedTestCase(TestCase):
|
||||
def setUp(self):
|
||||
image_obj = BytesIO()
|
||||
qrcode_obj = qrcode.make('https://mirumee.com/')
|
||||
qrcode_obj.save(image_obj)
|
||||
self.image = Image.objects.create(width=370, height=370,
|
||||
image=ImageFile(image_obj, '01.png'))
|
||||
size = list(settings.IMAGE_SIZES.keys())[0]
|
||||
self.thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, size)
|
||||
|
||||
def test_post_save_signal_original_changed(self):
|
||||
size = list(settings.IMAGE_SIZES.keys())[0]
|
||||
thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, size)
|
||||
self.image.delete()
|
||||
self.assertFalse(Thumbnail.objects.exists())
|
||||
|
||||
|
||||
class PostDeleteSignalDeleteImageFileTest(TestCase):
|
||||
def setUp(self):
|
||||
image_obj = BytesIO()
|
||||
qrcode_obj = qrcode.make('https://mirumee.com/')
|
||||
qrcode_obj.save(image_obj)
|
||||
self.image = Image.objects.create(width=370, height=370,
|
||||
image=ImageFile(image_obj, '01.png'))
|
||||
size = list(settings.IMAGE_SIZES.keys())[0]
|
||||
self.thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, size)
|
||||
|
||||
@mock.patch('django_images.models.IMAGE_AUTO_DELETE', True)
|
||||
def test_post_delete_signal_delete_image_files_enabled(self):
|
||||
storage = self.image.image.storage
|
||||
image_name = self.image.image.name
|
||||
thumb_name = self.thumb.image.name
|
||||
self.image.delete()
|
||||
self.assertFalse(storage.exists(image_name))
|
||||
self.assertFalse(storage.exists(thumb_name))
|
||||
|
||||
@mock.patch('django_images.models.IMAGE_AUTO_DELETE', False)
|
||||
def test_post_delete_signal_delete_image_files_disabled(self):
|
||||
storage = self.image.image.storage
|
||||
image_name = self.image.image.name
|
||||
thumb_name = self.thumb.image.name
|
||||
# Delete thumb
|
||||
self.thumb.delete()
|
||||
self.assertTrue(storage.exists(image_name))
|
||||
self.assertTrue(storage.exists(thumb_name))
|
||||
# Delete image
|
||||
self.image.delete()
|
||||
self.assertTrue(storage.exists(image_name))
|
||||
self.assertTrue(storage.exists(thumb_name))
|
||||
|
||||
|
||||
class AtSizeTemplateTagTest(TestCase):
|
||||
def setUp(self):
|
||||
image_obj = BytesIO()
|
||||
qrcode_obj = qrcode.make('https://mirumee.com/')
|
||||
qrcode_obj.save(image_obj)
|
||||
self.image = Image.objects.create(width=370, height=370,
|
||||
image=ImageFile(image_obj, '01.png'))
|
||||
size = list(settings.IMAGE_SIZES.keys())[0]
|
||||
self.thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, size)
|
||||
|
||||
def test_at_size(self):
|
||||
size = list(settings.IMAGE_SIZES.keys())[0]
|
||||
url = at_size(self.image, size)
|
||||
self.assertEqual(url, self.thumb.image.url)
|
||||
|
||||
|
||||
class ThumbnailViewTest(TestCase):
|
||||
def setUp(self):
|
||||
image_obj = BytesIO()
|
||||
qrcode_obj = qrcode.make('https://mirumee.com/')
|
||||
qrcode_obj.save(image_obj)
|
||||
self.image = Image.objects.create(width=370, height=370,
|
||||
image=ImageFile(image_obj, '01.png'))
|
||||
self.size = list(settings.IMAGE_SIZES.keys())[0]
|
||||
self.thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, self.size)
|
||||
|
||||
def test_redirect(self):
|
||||
url = reverse('image-thumbnail', args=[self.image.id, self.size])
|
||||
response = self.client.get(url)
|
||||
self.assertNotEqual(url, self.thumb.image.url)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, self.thumb.image.url)
|
||||
|
||||
def test_not_found(self):
|
||||
url = reverse('image-thumbnail', args=['42', self.size])
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_size_not_found(self):
|
||||
url = reverse('image-thumbnail', args=[self.image.id, '42'])
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
|
||||
class UtilsScaleAndDropTest(TestCase):
|
||||
def setUp(self):
|
||||
image_obj = BytesIO()
|
||||
qrcode_obj = qrcode.make('https://mirumee.com/')
|
||||
qrcode_obj.save(image_obj)
|
||||
self.imagefile = ImageFile(image_obj, '01.png')
|
||||
|
||||
def test_change_size(self):
|
||||
new_size = (10, 10)
|
||||
image = scale_and_crop(self.imagefile, new_size)
|
||||
self.assertEqual(new_size, image.im.size)
|
||||
|
||||
def test_crop(self):
|
||||
new_size = (10, 10)
|
||||
image = scale_and_crop(self.imagefile, new_size, crop=True)
|
||||
self.assertEqual(new_size, image.im.size)
|
||||
|
||||
def test_disabled_upscale(self):
|
||||
image = scale_and_crop(self.imagefile, (740, 740), upscale=False)
|
||||
self.assertLess(image.im.size[0], 371)
|
||||
self.assertLess(image.im.size[1], 371)
|
||||
|
||||
def test_enaabled_upscale(self):
|
||||
image = scale_and_crop(self.imagefile, (740, 740), upscale=True)
|
||||
self.assertGreater(image.im.size[0], 371)
|
||||
self.assertGreater(image.im.size[1], 371)
|
||||
|
||||
def test_not_change_quality(self):
|
||||
image = scale_and_crop(self.imagefile, (10, 10), quality=None)
|
||||
self.assertEqual(image.info.get('quality'), None)
|
||||
|
||||
def test_change_quality(self):
|
||||
image = scale_and_crop(self.imagefile, (10, 10), quality=50)
|
||||
self.assertEqual(image.info.get('quality'), 50)
|
||||
6
django_images/urls.py
Normal file
6
django_images/urls.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.conf.urls import url
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^thumbnail/(?P<image_id>\d+)/(?P<size>[^/]+)/$', views.thumbnail, name='image-thumbnail'),
|
||||
]
|
||||
73
django_images/utils.py
Normal file
73
django_images/utils.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from PIL import Image
|
||||
|
||||
|
||||
# this neat function is based on easy-thumbnails
|
||||
def scale_and_crop(image, size, crop=False, upscale=False, quality=None):
|
||||
"""
|
||||
Resize, crop and/or change quality of an image.
|
||||
|
||||
:param image: Source image file
|
||||
:param type: :class:`django.core.files.images.ImageFile`
|
||||
|
||||
:param size: Size as width & height, zero as either means unrestricted
|
||||
:type size: tuple of two int
|
||||
|
||||
:param crop: Truncate image or not
|
||||
:type crop: bool
|
||||
|
||||
:param upscale: Enable scale up
|
||||
:type upscale: bool
|
||||
|
||||
:param quality: Value between 1 to 95, or None for keep the same
|
||||
:type quality: int or NoneType
|
||||
|
||||
:return: Handled image
|
||||
:rtype: class:`PIL.Image`
|
||||
"""
|
||||
# Open image and store format/metadata.
|
||||
image.open()
|
||||
im = Image.open(image)
|
||||
im_format, im_info = im.format, im.info
|
||||
if quality:
|
||||
im_info['quality'] = quality
|
||||
|
||||
# Force PIL to load image data.
|
||||
im.load()
|
||||
|
||||
source_x, source_y = [float(v) for v in im.size]
|
||||
target_x, target_y = [float(v) for v in size]
|
||||
|
||||
if crop or not target_x or not target_y:
|
||||
scale = max(target_x / source_x, target_y / source_y)
|
||||
else:
|
||||
scale = min(target_x / source_x, target_y / source_y)
|
||||
|
||||
# Handle one-dimensional targets.
|
||||
if not target_x:
|
||||
target_x = source_x * scale
|
||||
elif not target_y:
|
||||
target_y = source_y * scale
|
||||
|
||||
if scale < 1.0 or (scale > 1.0 and upscale):
|
||||
im = im.resize((int(source_x * scale), int(source_y * scale)),
|
||||
resample=Image.ANTIALIAS)
|
||||
|
||||
if crop:
|
||||
# Use integer values now.
|
||||
source_x, source_y = im.size
|
||||
# Difference between new image size and requested size.
|
||||
diff_x = int(source_x - min(source_x, target_x))
|
||||
diff_y = int(source_y - min(source_y, target_y))
|
||||
if diff_x or diff_y:
|
||||
# Center cropping (default).
|
||||
halfdiff_x, halfdiff_y = diff_x // 2, diff_y // 2
|
||||
box = [halfdiff_x, halfdiff_y,
|
||||
min(source_x, int(target_x) + halfdiff_x),
|
||||
min(source_y, int(target_y) + halfdiff_y)]
|
||||
# Finally, crop the image!
|
||||
im = im.crop(box)
|
||||
|
||||
# Close image and replace format/metadata, as PIL blows this away.
|
||||
im.format, im.info = im_format, im_info
|
||||
image.close()
|
||||
return im
|
||||
14
django_images/views.py
Normal file
14
django_images/views.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from django.http import HttpResponseNotFound
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
|
||||
from . import models
|
||||
from .settings import IMAGE_SIZES
|
||||
|
||||
|
||||
def thumbnail(request, image_id, size):
|
||||
image = get_object_or_404(models.Image, id=image_id)
|
||||
if size not in IMAGE_SIZES:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
return redirect(models.Thumbnail.objects.get_or_create_at_size(image.id,
|
||||
size))
|
||||
59
doc/upgrade_from_1.x.md
Normal file
59
doc/upgrade_from_1.x.md
Normal file
@@ -0,0 +1,59 @@
|
||||
Guide of Upgrade from 1.x to 2.x
|
||||
---------------------------------------
|
||||
|
||||
If you have Pinry installed by srouce code, it's easy for
|
||||
you to upgrade from 1.x to 2.x, just follow these steps.
|
||||
|
||||
At first you should checkout the old branch.
|
||||
|
||||
```bash
|
||||
# it is not required if you are currently the old version
|
||||
git checkout 1.x version
|
||||
|
||||
# install and enter the shell
|
||||
pipenv install
|
||||
pipenv shell
|
||||
|
||||
# Upgrade to lastest version of django-images
|
||||
|
||||
pip install -U git+https://github.com/mirumee/django-images.git
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate --fake-initial
|
||||
|
||||
# clean action
|
||||
pip uninstall django-images
|
||||
|
||||
# It will ask if you want to remove an extra file named like
|
||||
# Uninstalling django-images-0.4.3:
|
||||
# -------------------------------------------
|
||||
# Would remove:
|
||||
# /path_to_your_python_packages/django_images-0.4.3.dist-info/*
|
||||
# /path_to_your_python_packages/django_images/*
|
||||
# Would not remove (might be manually added):
|
||||
# /path_to_your_python_packages/django_images/migrations/0002_auto_20180826_0845.py
|
||||
# Proceed (y/n)?
|
||||
# ------------------------------------------
|
||||
# Please remove the file it by hand if possible (this operation is optional)
|
||||
rm /path_to_your_python_packages/django_images/migrations/0002_auto_20180826_0845.py
|
||||
|
||||
# exit pipenv's virtualenv
|
||||
exit
|
||||
```
|
||||
|
||||
Ant then you should checkout to version 2.x (our current master)
|
||||
|
||||
```bash
|
||||
# If you are not the lastest version ,just call "git pull --rebase"
|
||||
# to upgrade to lastest version of Pinry (2.x)
|
||||
git checkout master
|
||||
make install
|
||||
make migrate
|
||||
|
||||
# Try to run as development server
|
||||
make serve
|
||||
|
||||
# If no error occurs, just enjoy it
|
||||
|
||||
```
|
||||
|
||||
And now, you can just run your server in the way you like.
|
||||
@@ -6,5 +6,8 @@ if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pinry.settings.development")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
if 'test' in sys.argv:
|
||||
from django.conf import settings
|
||||
settings.IS_TEST = True
|
||||
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
@@ -134,5 +134,8 @@ IMAGE_SIZES = {
|
||||
'square': {'crop': True, 'size': [125, 125]},
|
||||
}
|
||||
|
||||
# IS_TEST is a variable to mark if the test is running
|
||||
IS_TEST = False
|
||||
|
||||
# User custom settings
|
||||
IMAGE_AUTO_DELETE = True
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
from django.conf import settings
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.conf.urls import include, url
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.contrib import admin
|
||||
|
||||
from django.views.static import serve
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
urlpatterns = [
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'', include('core.urls', namespace='core')),
|
||||
url(r'', include('users.urls', namespace='users')),
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
urlpatterns += patterns('', url(r'^media/(?P<path>.*)$',
|
||||
'django.views.static.serve', {'document_root': settings.MEDIA_ROOT,}),)
|
||||
urlpatterns += [
|
||||
url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT, }),
|
||||
]
|
||||
|
||||
if settings.IS_TEST:
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
# For test running of django_images
|
||||
urlpatterns += [
|
||||
url(r'^__images/', include('django_images.urls')),
|
||||
]
|
||||
|
||||
@@ -33,11 +33,3 @@ class CombinedAuthBackend(object):
|
||||
return User.objects.get(pk=user_id)
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
def has_perm(self, user, perm, obj=None):
|
||||
"""
|
||||
A very simplistic authorization mechanism for now. Basically a pin owner can do anything with the pin.
|
||||
"""
|
||||
if obj and isinstance(obj, Pin):
|
||||
return obj.submitter == user
|
||||
return False
|
||||
|
||||
@@ -34,21 +34,6 @@ class CombinedAuthBackendTest(TestCase):
|
||||
def test_authenticate_unknown_user(self):
|
||||
self.assertIsNone(self.backend.authenticate(username='wrong-username', password='wrong-password'))
|
||||
|
||||
@mock.patch('requests.get', mock_requests_get)
|
||||
def test_has_perm_on_pin(self):
|
||||
image = Image.objects.create_for_url('http://testserver/mocked/screenshot.png')
|
||||
user = User.objects.get(username=self.username)
|
||||
pin = Pin.objects.create(submitter=user, image=image)
|
||||
self.assertTrue(self.backend.has_perm(user, 'add_pin', pin))
|
||||
|
||||
@mock.patch('requests.get', mock_requests_get)
|
||||
def test_has_perm_on_pin_unauthorized(self):
|
||||
image = Image.objects.create_for_url('http://testserver/mocked/screenshot.png')
|
||||
user = User.objects.get(username=self.username)
|
||||
other_user = User.objects.create_user('test', 'test@example.com', 'test')
|
||||
pin = Pin.objects.create(submitter=user, image=image)
|
||||
self.assertFalse(self.backend.has_perm(other_user, 'add_pin', pin))
|
||||
|
||||
|
||||
class CreateUserTest(TestCase):
|
||||
def test_create_post(self):
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from django.conf.urls import patterns, url
|
||||
from django.conf.urls import url
|
||||
from django.contrib.auth.views import login
|
||||
|
||||
from .views import CreateUser
|
||||
from . import views
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^private/$', 'users.views.private', name='private'),
|
||||
url(r'^register/$', CreateUser.as_view(), name='register'),
|
||||
url(r'^login/$', 'django.contrib.auth.views.login',
|
||||
urlpatterns = [
|
||||
url(r'^private/$', views.private, name='private'),
|
||||
url(r'^register/$', views.CreateUser.as_view(), name='register'),
|
||||
url(r'^login/$', login,
|
||||
{'template_name': 'users/login.html'}, name='login'),
|
||||
url(r'^logout/$', 'users.views.logout_user', name='logout'),
|
||||
)
|
||||
url(r'^logout/$', views.logout_user, name='logout'),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user