├── .gitignore ├── .vscode └── settings.json ├── Pipfile ├── Pipfile.lock ├── README.md ├── config ├── __init__.py ├── asgi.py ├── authentication.py ├── settings.py ├── urls.py └── wsgi.py ├── core ├── __init__.py ├── admin.py ├── apps.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── mega_seed.py ├── migrations │ └── __init__.py ├── models.py └── tests.py ├── manage.py ├── rooms ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20191216_0937.py │ └── __init__.py ├── models.py ├── permissions.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── uploads └── room_photos │ ├── 1.webp │ ├── 10.webp │ ├── 11.webp │ ├── 12.webp │ ├── 13.webp │ ├── 14.webp │ ├── 15.webp │ ├── 16.webp │ ├── 17.webp │ ├── 18.webp │ ├── 19.webp │ ├── 2.webp │ ├── 20.webp │ ├── 21.webp │ ├── 22.webp │ ├── 23.webp │ ├── 24.webp │ ├── 25.webp │ ├── 26.webp │ ├── 27.webp │ ├── 28.webp │ ├── 29.webp │ ├── 3.webp │ ├── 30.webp │ ├── 31.webp │ ├── 4.webp │ ├── 5.webp │ ├── 6.webp │ ├── 7.webp │ ├── 8.webp │ └── 9.webp └── users ├── __init__.py ├── admin.py ├── apps.py ├── migrations ├── 0001_initial.py └── __init__.py ├── models.py ├── permissions.py ├── serializers.py ├── tests.py ├── urls.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | .DS_Store -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/Users/serranoarevalo/.local/share/virtualenvs/airbnb-api-JyI_rFP2/bin/python", 3 | "python.linting.flake8Enabled": true, 4 | "python.linting.pylintEnabled": false, 5 | "python.linting.enabled": true, 6 | "python.formatting.provider": "black", 7 | "python.linting.flake8Args": ["--max-line-length=91"] 8 | } 9 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | flake8 = "*" 8 | black = "*" 9 | 10 | [packages] 11 | django = "*" 12 | pillow = "*" 13 | django-seed = "*" 14 | djangorestframework = "*" 15 | pyjwt = "*" 16 | 17 | [requires] 18 | python_version = "3.7" 19 | 20 | [pipenv] 21 | allow_prereleases = true 22 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "957eee3568763fb58cd1aef9059bd828780b68f826795b4552b26dc90d7d8619" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "asgiref": { 20 | "hashes": [ 21 | "sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0", 22 | "sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5" 23 | ], 24 | "version": "==3.2.3" 25 | }, 26 | "django": { 27 | "hashes": [ 28 | "sha256:315b11ea265dd15348d47f2cbb044ef71da2018f6e582fed875c889758e6f844", 29 | "sha256:b61295749be7e1c42467c55bcabdaee9fbe9496fdf9ed2e22cef44d9de2ff953" 30 | ], 31 | "index": "pypi", 32 | "version": "==3.0.1" 33 | }, 34 | "django-seed": { 35 | "hashes": [ 36 | "sha256:da5dc54494c2d4274a6b9f9a2aea69cb15c3cce80777e2d30b3a5a082c0a7ee8" 37 | ], 38 | "index": "pypi", 39 | "version": "==0.1.9" 40 | }, 41 | "djangorestframework": { 42 | "hashes": [ 43 | "sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4", 44 | "sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f" 45 | ], 46 | "index": "pypi", 47 | "version": "==3.11.0" 48 | }, 49 | "faker": { 50 | "hashes": [ 51 | "sha256:202ad3b2ec16ae7c51c02904fb838831f8d2899e61bf18db1e91a5a582feab11", 52 | "sha256:92c84a10bec81217d9cb554ee12b3838c8986ce0b5d45f72f769da22e4bb5432" 53 | ], 54 | "version": "==3.0.0" 55 | }, 56 | "pillow": { 57 | "hashes": [ 58 | "sha256:047d9473cf68af50ac85f8ee5d5f21a60f849bc17d348da7fc85711287a75031", 59 | "sha256:0f66dc6c8a3cc319561a633b6aa82c44107f12594643efa37210d8c924fc1c71", 60 | "sha256:12c9169c4e8fe0a7329e8658c7e488001f6b4c8e88740e76292c2b857af2e94c", 61 | "sha256:248cffc168896982f125f5c13e9317c059f74fffdb4152893339f3be62a01340", 62 | "sha256:27faf0552bf8c260a5cee21a76e031acaea68babb64daf7e8f2e2540745082aa", 63 | "sha256:285edafad9bc60d96978ed24d77cdc0b91dace88e5da8c548ba5937c425bca8b", 64 | "sha256:384b12c9aa8ef95558abdcb50aada56d74bc7cc131dd62d28c2d0e4d3aadd573", 65 | "sha256:38950b3a707f6cef09cd3cbb142474357ad1a985ceb44d921bdf7b4647b3e13e", 66 | "sha256:4aad1b88933fd6dc2846552b89ad0c74ddbba2f0884e2c162aa368374bf5abab", 67 | "sha256:4ac6148008c169603070c092e81f88738f1a0c511e07bd2bb0f9ef542d375da9", 68 | "sha256:4deb1d2a45861ae6f0b12ea0a786a03d19d29edcc7e05775b85ec2877cb54c5e", 69 | "sha256:59aa2c124df72cc75ed72c8d6005c442d4685691a30c55321e00ed915ad1a291", 70 | "sha256:5a47d2123a9ec86660fe0e8d0ebf0aa6bc6a17edc63f338b73ea20ba11713f12", 71 | "sha256:5cc901c2ab9409b4b7ac7b5bcc3e86ac14548627062463da0af3b6b7c555a871", 72 | "sha256:6c1db03e8dff7b9f955a0fb9907eb9ca5da75b5ce056c0c93d33100a35050281", 73 | "sha256:7ce80c0a65a6ea90ef9c1f63c8593fcd2929448613fc8da0adf3e6bfad669d08", 74 | "sha256:809c19241c14433c5d6135e1b6c72da4e3b56d5c865ad5736ab99af8896b8f41", 75 | "sha256:83792cb4e0b5af480588601467c0764242b9a483caea71ef12d22a0d0d6bdce2", 76 | "sha256:846fa202bd7ee0f6215c897a1d33238ef071b50766339186687bd9b7a6d26ac5", 77 | "sha256:9f5529fc02009f96ba95bea48870173426879dc19eec49ca8e08cd63ecd82ddb", 78 | "sha256:a423c2ea001c6265ed28700df056f75e26215fd28c001e93ef4380b0f05f9547", 79 | "sha256:ac4428094b42907aba5879c7c000d01c8278d451a3b7cccd2103e21f6397ea75", 80 | "sha256:b1ae48d87f10d1384e5beecd169c77502fcc04a2c00a4c02b85f0a94b419e5f9", 81 | "sha256:bf4e972a88f8841d8fdc6db1a75e0f8d763e66e3754b03006cbc3854d89f1cb1", 82 | "sha256:c6414f6aad598364aaf81068cabb077894eb88fed99c6a65e6e8217bab62ae7a", 83 | "sha256:c710fcb7ee32f67baf25aa9ffede4795fd5d93b163ce95fdc724383e38c9df96", 84 | "sha256:c7be4b8a09852291c3c48d3c25d1b876d2494a0a674980089ac9d5e0d78bd132", 85 | "sha256:c9e5ffb910b14f090ac9c38599063e354887a5f6d7e6d26795e916b4514f2c1a", 86 | "sha256:e0697b826da6c2472bb6488db4c0a7fa8af0d52fa08833ceb3681358914b14e5", 87 | "sha256:e9a3edd5f714229d41057d56ac0f39ad9bdba6767e8c888c951869f0bdd129b0" 88 | ], 89 | "index": "pypi", 90 | "version": "==6.2.1" 91 | }, 92 | "pyjwt": { 93 | "hashes": [ 94 | "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e", 95 | "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96" 96 | ], 97 | "index": "pypi", 98 | "version": "==1.7.1" 99 | }, 100 | "python-dateutil": { 101 | "hashes": [ 102 | "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", 103 | "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" 104 | ], 105 | "version": "==2.8.1" 106 | }, 107 | "pytz": { 108 | "hashes": [ 109 | "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", 110 | "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" 111 | ], 112 | "version": "==2019.3" 113 | }, 114 | "six": { 115 | "hashes": [ 116 | "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", 117 | "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" 118 | ], 119 | "version": "==1.13.0" 120 | }, 121 | "sqlparse": { 122 | "hashes": [ 123 | "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", 124 | "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" 125 | ], 126 | "version": "==0.3.0" 127 | }, 128 | "text-unidecode": { 129 | "hashes": [ 130 | "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", 131 | "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93" 132 | ], 133 | "version": "==1.3" 134 | } 135 | }, 136 | "develop": { 137 | "appdirs": { 138 | "hashes": [ 139 | "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", 140 | "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" 141 | ], 142 | "version": "==1.4.3" 143 | }, 144 | "attrs": { 145 | "hashes": [ 146 | "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", 147 | "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" 148 | ], 149 | "version": "==19.3.0" 150 | }, 151 | "black": { 152 | "hashes": [ 153 | "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b", 154 | "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539" 155 | ], 156 | "index": "pypi", 157 | "version": "==19.10b0" 158 | }, 159 | "click": { 160 | "hashes": [ 161 | "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", 162 | "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" 163 | ], 164 | "version": "==7.0" 165 | }, 166 | "entrypoints": { 167 | "hashes": [ 168 | "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", 169 | "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" 170 | ], 171 | "version": "==0.3" 172 | }, 173 | "flake8": { 174 | "hashes": [ 175 | "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", 176 | "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" 177 | ], 178 | "index": "pypi", 179 | "version": "==3.7.9" 180 | }, 181 | "mccabe": { 182 | "hashes": [ 183 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 184 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 185 | ], 186 | "version": "==0.6.1" 187 | }, 188 | "pathspec": { 189 | "hashes": [ 190 | "sha256:e285ccc8b0785beadd4c18e5708b12bb8fcf529a1e61215b3feff1d1e559ea5c" 191 | ], 192 | "version": "==0.6.0" 193 | }, 194 | "pycodestyle": { 195 | "hashes": [ 196 | "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", 197 | "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" 198 | ], 199 | "version": "==2.5.0" 200 | }, 201 | "pyflakes": { 202 | "hashes": [ 203 | "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", 204 | "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" 205 | ], 206 | "version": "==2.1.1" 207 | }, 208 | "regex": { 209 | "hashes": [ 210 | "sha256:032fdcc03406e1a6485ec09b826eac78732943840c4b29e503b789716f051d8d", 211 | "sha256:0e6cf1e747f383f52a0964452658c04300a9a01e8a89c55ea22813931b580aa8", 212 | "sha256:106e25a841921d8259dcef2a42786caae35bc750fb996f830065b3dfaa67b77e", 213 | "sha256:1768cf42a78a11dae63152685e7a1d90af7a8d71d2d4f6d2387edea53a9e0588", 214 | "sha256:27d1bd20d334f50b7ef078eba0f0756a640fd25f5f1708d3b5bed18a5d6bced9", 215 | "sha256:29b20f66f2e044aafba86ecf10a84e611b4667643c42baa004247f5dfef4f90b", 216 | "sha256:4850c78b53acf664a6578bba0e9ebeaf2807bb476c14ec7e0f936f2015133cae", 217 | "sha256:57eacd38a5ec40ed7b19a968a9d01c0d977bda55664210be713e750dd7b33540", 218 | "sha256:724eb24b92fc5fdc1501a1b4df44a68b9c1dda171c8ef8736799e903fb100f63", 219 | "sha256:77ae8d926f38700432807ba293d768ba9e7652df0cbe76df2843b12f80f68885", 220 | "sha256:78b3712ec529b2a71731fbb10b907b54d9c53a17ca589b42a578bc1e9a2c82ea", 221 | "sha256:7bbbdbada3078dc360d4692a9b28479f569db7fc7f304b668787afc9feb38ec8", 222 | "sha256:8d9ef7f6c403e35e73b7fc3cde9f6decdc43b1cb2ff8d058c53b9084bfcb553e", 223 | "sha256:a83049eb717ae828ced9cf607845929efcb086a001fc8af93ff15c50012a5716", 224 | "sha256:adc35d38952e688535980ae2109cad3a109520033642e759f987cf47fe278aa1", 225 | "sha256:c29a77ad4463f71a506515d9ec3a899ed026b4b015bf43245c919ff36275444b", 226 | "sha256:cfd31b3300fefa5eecb2fe596c6dee1b91b3a05ece9d5cfd2631afebf6c6fadd", 227 | "sha256:d3ee0b035816e0520fac928de31b6572106f0d75597f6fa3206969a02baba06f", 228 | "sha256:d508875793efdf6bab3d47850df8f40d4040ae9928d9d80864c1768d6aeaf8e3", 229 | "sha256:ef0b828a7e22e58e06a1cceddba7b4665c6af8afeb22a0d8083001330572c147", 230 | "sha256:faad39fdbe2c2ccda9846cd21581063086330efafa47d87afea4073a08128656" 231 | ], 232 | "version": "==2019.12.20" 233 | }, 234 | "toml": { 235 | "hashes": [ 236 | "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", 237 | "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" 238 | ], 239 | "version": "==0.10.0" 240 | }, 241 | "typed-ast": { 242 | "hashes": [ 243 | "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", 244 | "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", 245 | "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", 246 | "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", 247 | "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", 248 | "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", 249 | "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", 250 | "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", 251 | "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", 252 | "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", 253 | "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", 254 | "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", 255 | "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", 256 | "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", 257 | "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", 258 | "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", 259 | "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", 260 | "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", 261 | "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", 262 | "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" 263 | ], 264 | "version": "==1.4.0" 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Airbnb API 2 | 3 | REST & GraphQL API of the Airbnb Clone using Django REST Framework and Graphene GraphQL 4 | 5 | ### API Actions 6 | 7 | - [x] JWT Authentication 8 | - [x] Login (JWT) 9 | - [x] Create Account 10 | - [x] See Profile 11 | - [x] Edit Profile 12 | - [x] Add/Room From Favourites 13 | - [x] List Rooms 14 | - [x] See Room 15 | - [x] Create Room 16 | - [x] Edit Room 17 | - [x] Delete Room 18 | - [x] Search Rooms 19 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/config/__init__.py -------------------------------------------------------------------------------- /config/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for config project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /config/authentication.py: -------------------------------------------------------------------------------- 1 | import jwt 2 | from django.conf import settings 3 | from rest_framework import authentication 4 | from users.models import User 5 | 6 | 7 | class JWTAuthentication(authentication.BaseAuthentication): 8 | def authenticate(self, request): 9 | try: 10 | token = request.META.get("HTTP_AUTHORIZATION") 11 | if token is None: 12 | return None 13 | xjwt, jwt_token = token.split(" ") 14 | decoded = jwt.decode(jwt_token, settings.SECRET_KEY, algorithms=["HS256"]) 15 | pk = decoded.get("pk") 16 | user = User.objects.get(pk=pk) 17 | return (user, None) 18 | except (ValueError, jwt.exceptions.DecodeError, User.DoesNotExist): 19 | return None 20 | -------------------------------------------------------------------------------- /config/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for config project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.0. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "cc)*5=(s+i2-&9x7&&&o+y7$g5!db3tvu85ykok#mwxf#6gir2" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | 34 | DJANGO_APPS = [ 35 | "django.contrib.admin", 36 | "django.contrib.auth", 37 | "django.contrib.contenttypes", 38 | "django.contrib.sessions", 39 | "django.contrib.messages", 40 | "django.contrib.staticfiles", 41 | ] 42 | 43 | PROJECT_APPS = [ 44 | "core.apps.CoreConfig", 45 | "users.apps.UsersConfig", 46 | "rooms.apps.RoomsConfig", 47 | ] 48 | 49 | THIRD_PARTY_APPS = [ 50 | "rest_framework", 51 | ] 52 | 53 | INSTALLED_APPS = DJANGO_APPS + PROJECT_APPS + THIRD_PARTY_APPS 54 | 55 | MIDDLEWARE = [ 56 | "django.middleware.security.SecurityMiddleware", 57 | "django.contrib.sessions.middleware.SessionMiddleware", 58 | "django.middleware.common.CommonMiddleware", 59 | "django.middleware.csrf.CsrfViewMiddleware", 60 | "django.contrib.auth.middleware.AuthenticationMiddleware", 61 | "django.contrib.messages.middleware.MessageMiddleware", 62 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 63 | ] 64 | 65 | ROOT_URLCONF = "config.urls" 66 | 67 | TEMPLATES = [ 68 | { 69 | "BACKEND": "django.template.backends.django.DjangoTemplates", 70 | "DIRS": [], 71 | "APP_DIRS": True, 72 | "OPTIONS": { 73 | "context_processors": [ 74 | "django.template.context_processors.debug", 75 | "django.template.context_processors.request", 76 | "django.contrib.auth.context_processors.auth", 77 | "django.contrib.messages.context_processors.messages", 78 | ], 79 | }, 80 | }, 81 | ] 82 | 83 | WSGI_APPLICATION = "config.wsgi.application" 84 | 85 | 86 | # Database 87 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases 88 | 89 | DATABASES = { 90 | "default": { 91 | "ENGINE": "django.db.backends.sqlite3", 92 | "NAME": os.path.join(BASE_DIR, "db.sqlite3"), 93 | } 94 | } 95 | 96 | 97 | # Password validation 98 | # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators 99 | 100 | AUTH_PASSWORD_VALIDATORS = [ 101 | { 102 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 103 | }, 104 | {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, 105 | {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, 106 | {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, 107 | ] 108 | 109 | 110 | # Internationalization 111 | # https://docs.djangoproject.com/en/3.0/topics/i18n/ 112 | 113 | LANGUAGE_CODE = "en-us" 114 | 115 | TIME_ZONE = "UTC" 116 | 117 | USE_I18N = True 118 | 119 | USE_L10N = True 120 | 121 | USE_TZ = True 122 | 123 | 124 | # Static files (CSS, JavaScript, Images) 125 | # https://docs.djangoproject.com/en/3.0/howto/static-files/ 126 | 127 | STATIC_URL = "/static/" 128 | 129 | MEDIA_ROOT = os.path.join(BASE_DIR, "uploads") 130 | 131 | MEDIA_URL = "/media/" 132 | 133 | 134 | # Auth 135 | 136 | AUTH_USER_MODEL = "users.User" 137 | 138 | 139 | # Django Rest Framework 140 | 141 | REST_FRAMEWORK = { 142 | "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", 143 | "PAGE_SIZE": 10, 144 | "DEFAULT_AUTHENTICATION_CLASSES": [ 145 | "config.authentication.JWTAuthentication", 146 | "rest_framework.authentication.SessionAuthentication", 147 | ], 148 | } 149 | 150 | if not DEBUG: 151 | REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"] = [ 152 | "rest_framework.renderers.JSONRenderer", 153 | ] 154 | -------------------------------------------------------------------------------- /config/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | from django.conf import settings 4 | from django.conf.urls.static import static 5 | 6 | urlpatterns = [ 7 | path("admin/", admin.site.urls), 8 | path("api/v1/rooms/", include("rooms.urls")), 9 | path("api/v1/users/", include("users.urls")), 10 | ] 11 | 12 | if settings.DEBUG: 13 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 14 | -------------------------------------------------------------------------------- /config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for config project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/core/__init__.py -------------------------------------------------------------------------------- /core/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreConfig(AppConfig): 5 | name = 'core' 6 | -------------------------------------------------------------------------------- /core/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/core/management/__init__.py -------------------------------------------------------------------------------- /core/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/core/management/commands/__init__.py -------------------------------------------------------------------------------- /core/management/commands/mega_seed.py: -------------------------------------------------------------------------------- 1 | import random 2 | from datetime import datetime 3 | from django.core.management.base import BaseCommand 4 | from django_seed import Seed 5 | from users.models import User 6 | from rooms.models import Room, Photo 7 | 8 | 9 | class Command(BaseCommand): 10 | 11 | help = "It seeds the DB with tons of stuff" 12 | 13 | def handle(self, *args, **options): 14 | user_seeder = Seed.seeder() 15 | user_seeder.add_entity(User, 20, {"is_staff": False, "is_superuser": False}) 16 | user_seeder.execute() 17 | 18 | users = User.objects.all() 19 | room_seeder = Seed.seeder() 20 | room_seeder.add_entity( 21 | Room, 22 | 150, 23 | { 24 | "user": lambda x: random.choice(users), 25 | "name": lambda x: room_seeder.faker.street_address(), 26 | "price": lambda x: random.randint(0, 300), 27 | "beds": lambda x: random.randint(0, 5), 28 | "bedrooms": lambda x: random.randint(0, 3), 29 | "bathrooms": lambda x: random.randint(0, 5), 30 | "instant_book": lambda x: random.choice([True, False]), 31 | "check_in": lambda x: datetime.now(), 32 | "check_out": lambda x: datetime.now(), 33 | }, 34 | ) 35 | room_seeder.execute() 36 | 37 | rooms = Room.objects.all() 38 | for room in rooms: 39 | for i in range(random.randint(5, 10)): 40 | Photo.objects.create( 41 | caption=room_seeder.faker.sentence(), 42 | room=room, 43 | file=f"room_photos/{random.randint(1, 31)}.webp", 44 | ) 45 | self.stdout.write(self.style.SUCCESS(f"Everything seeded")) 46 | -------------------------------------------------------------------------------- /core/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/core/migrations/__init__.py -------------------------------------------------------------------------------- /core/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class CoreModel(models.Model): 5 | 6 | created = models.DateTimeField(auto_now_add=True) 7 | modified = models.DateTimeField(auto_now=True) 8 | 9 | class Meta: 10 | abstract = True 11 | -------------------------------------------------------------------------------- /core/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /rooms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/rooms/__init__.py -------------------------------------------------------------------------------- /rooms/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from . import models 3 | 4 | 5 | @admin.register(models.Room) 6 | class RoomAdmin(admin.ModelAdmin): 7 | 8 | list_display = ( 9 | "name", 10 | "photo_number", 11 | ) 12 | 13 | 14 | @admin.register(models.Photo) 15 | class PhotoAdmin(admin.ModelAdmin): 16 | pass 17 | -------------------------------------------------------------------------------- /rooms/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class RoomsConfig(AppConfig): 5 | name = 'rooms' 6 | -------------------------------------------------------------------------------- /rooms/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0 on 2019-12-16 09:37 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Photo', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('created', models.DateTimeField(auto_now_add=True)), 19 | ('modified', models.DateTimeField(auto_now=True)), 20 | ('file', models.ImageField(upload_to='')), 21 | ('caption', models.CharField(max_length=140)), 22 | ], 23 | options={ 24 | 'abstract': False, 25 | }, 26 | ), 27 | migrations.CreateModel( 28 | name='Room', 29 | fields=[ 30 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 31 | ('created', models.DateTimeField(auto_now_add=True)), 32 | ('modified', models.DateTimeField(auto_now=True)), 33 | ('name', models.CharField(max_length=140)), 34 | ('address', models.CharField(max_length=140)), 35 | ('price', models.IntegerField(help_text='USD per night')), 36 | ('beds', models.IntegerField(default=1)), 37 | ('lat', models.DecimalField(decimal_places=6, max_digits=10)), 38 | ('lng', models.DecimalField(decimal_places=6, max_digits=10)), 39 | ('bedrooms', models.IntegerField(default=1)), 40 | ('bathrooms', models.IntegerField(default=1)), 41 | ('check_in', models.TimeField(default='00:00:00')), 42 | ('check_out', models.TimeField(default='00:00:00')), 43 | ('instant_book', models.BooleanField(default=False)), 44 | ], 45 | options={ 46 | 'abstract': False, 47 | }, 48 | ), 49 | ] 50 | -------------------------------------------------------------------------------- /rooms/migrations/0002_auto_20191216_0937.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0 on 2019-12-16 09:37 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('rooms', '0001_initial'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name='room', 20 | name='user', 21 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rooms', to=settings.AUTH_USER_MODEL), 22 | ), 23 | migrations.AddField( 24 | model_name='photo', 25 | name='room', 26 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='photos', to='rooms.Room'), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /rooms/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/rooms/migrations/__init__.py -------------------------------------------------------------------------------- /rooms/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from core.models import CoreModel 3 | 4 | 5 | class Room(CoreModel): 6 | 7 | name = models.CharField(max_length=140) 8 | address = models.CharField(max_length=140) 9 | price = models.IntegerField(help_text="USD per night") 10 | beds = models.IntegerField(default=1) 11 | lat = models.DecimalField(max_digits=10, decimal_places=6) 12 | lng = models.DecimalField(max_digits=10, decimal_places=6) 13 | bedrooms = models.IntegerField(default=1) 14 | bathrooms = models.IntegerField(default=1) 15 | check_in = models.TimeField(default="00:00:00") 16 | check_out = models.TimeField(default="00:00:00") 17 | instant_book = models.BooleanField(default=False) 18 | user = models.ForeignKey( 19 | "users.User", on_delete=models.CASCADE, related_name="rooms" 20 | ) 21 | 22 | def __str__(self): 23 | return self.name 24 | 25 | def photo_number(self): 26 | return self.photos.count() 27 | 28 | photo_number.short_description = "Photo Count" 29 | 30 | class Meta: 31 | ordering = ["-pk"] 32 | 33 | 34 | class Photo(CoreModel): 35 | 36 | file = models.ImageField() 37 | room = models.ForeignKey( 38 | "rooms.Room", related_name="photos", on_delete=models.CASCADE 39 | ) 40 | caption = models.CharField(max_length=140) 41 | 42 | def __str__(self): 43 | return self.room.name 44 | -------------------------------------------------------------------------------- /rooms/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import BasePermission 2 | 3 | 4 | class IsOwner(BasePermission): 5 | def has_object_permission(self, request, view, room): 6 | return room.user == request.user 7 | -------------------------------------------------------------------------------- /rooms/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from users.serializers import UserSerializer 3 | from .models import Room, Photo 4 | 5 | 6 | class PhotoSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = Photo 9 | exclude = ("room",) 10 | 11 | 12 | class RoomSerializer(serializers.ModelSerializer): 13 | 14 | is_fav = serializers.SerializerMethodField() 15 | user = UserSerializer(read_only=True) 16 | photos = PhotoSerializer(read_only=True, many=True) 17 | 18 | class Meta: 19 | model = Room 20 | exclude = ("modified",) 21 | read_only_fields = ("user", "id", "created", "updated") 22 | 23 | def validate(self, data): 24 | if self.instance: 25 | check_in = data.get("check_in", self.instance.check_in) 26 | check_out = data.get("check_out", self.instance.check_out) 27 | else: 28 | check_in = data.get("check_in") 29 | check_out = data.get("check_out") 30 | if check_in == check_out: 31 | raise serializers.ValidationError("Not enough time between changes") 32 | return data 33 | 34 | def get_is_fav(self, obj): 35 | request = self.context.get("request") 36 | if request: 37 | user = request.user 38 | if user.is_authenticated: 39 | return obj in user.favs.all() 40 | return False 41 | 42 | def create(self, validated_data): 43 | request = self.context.get("request") 44 | room = Room.objects.create(**validated_data, user=request.user) 45 | return room 46 | -------------------------------------------------------------------------------- /rooms/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /rooms/urls.py: -------------------------------------------------------------------------------- 1 | from rest_framework.routers import DefaultRouter 2 | from . import views 3 | 4 | app_name = "rooms" 5 | router = DefaultRouter() 6 | router.register("", views.RoomViewSet) 7 | 8 | 9 | urlpatterns = router.urls 10 | -------------------------------------------------------------------------------- /rooms/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.viewsets import ModelViewSet 2 | from rest_framework.decorators import action 3 | from rest_framework import permissions 4 | from .models import Room 5 | from .serializers import RoomSerializer 6 | from .permissions import IsOwner 7 | 8 | 9 | class RoomViewSet(ModelViewSet): 10 | 11 | queryset = Room.objects.all() 12 | serializer_class = RoomSerializer 13 | 14 | def get_permissions(self): 15 | if self.action == "list" or self.action == "retrieve": 16 | permission_classes = [permissions.AllowAny] 17 | elif self.action == "create": 18 | permission_classes = [permissions.IsAuthenticated] 19 | else: 20 | permission_classes = [IsOwner] 21 | return [permission() for permission in permission_classes] 22 | 23 | @action(detail=False) 24 | def search(self, request): 25 | max_price = request.GET.get("max_price", None) 26 | min_price = request.GET.get("min_price", None) 27 | beds = request.GET.get("beds", None) 28 | bedrooms = request.GET.get("bedrooms", None) 29 | bathrooms = request.GET.get("bathrooms", None) 30 | lat = request.GET.get("lat", None) 31 | lng = request.GET.get("lng", None) 32 | filter_kwargs = {} 33 | if max_price is not None: 34 | filter_kwargs["price__lte"] = max_price 35 | if min_price is not None: 36 | filter_kwargs["price__gte"] = min_price 37 | if beds is not None: 38 | filter_kwargs["beds__gte"] = beds 39 | if bedrooms is not None: 40 | filter_kwargs["bedrooms__gte"] = bedrooms 41 | if bathrooms is not None: 42 | filter_kwargs["bathrooms__gte"] = bathrooms 43 | paginator = self.paginator 44 | if lat is not None and lng is not None: 45 | filter_kwargs["lat__gte"] = float(lat) - 0.005 46 | filter_kwargs["lat__lte"] = float(lat) + 0.005 47 | filter_kwargs["lng__gte"] = float(lng) - 0.005 48 | filter_kwargs["lng__lte"] = float(lng) + 0.005 49 | try: 50 | rooms = Room.objects.filter(**filter_kwargs) 51 | except ValueError: 52 | rooms = Room.objects.all() 53 | results = paginator.paginate_queryset(rooms, request) 54 | serializer = RoomSerializer(results, many=True) 55 | return paginator.get_paginated_response(serializer.data) 56 | -------------------------------------------------------------------------------- /uploads/room_photos/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/1.webp -------------------------------------------------------------------------------- /uploads/room_photos/10.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/10.webp -------------------------------------------------------------------------------- /uploads/room_photos/11.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/11.webp -------------------------------------------------------------------------------- /uploads/room_photos/12.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/12.webp -------------------------------------------------------------------------------- /uploads/room_photos/13.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/13.webp -------------------------------------------------------------------------------- /uploads/room_photos/14.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/14.webp -------------------------------------------------------------------------------- /uploads/room_photos/15.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/15.webp -------------------------------------------------------------------------------- /uploads/room_photos/16.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/16.webp -------------------------------------------------------------------------------- /uploads/room_photos/17.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/17.webp -------------------------------------------------------------------------------- /uploads/room_photos/18.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/18.webp -------------------------------------------------------------------------------- /uploads/room_photos/19.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/19.webp -------------------------------------------------------------------------------- /uploads/room_photos/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/2.webp -------------------------------------------------------------------------------- /uploads/room_photos/20.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/20.webp -------------------------------------------------------------------------------- /uploads/room_photos/21.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/21.webp -------------------------------------------------------------------------------- /uploads/room_photos/22.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/22.webp -------------------------------------------------------------------------------- /uploads/room_photos/23.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/23.webp -------------------------------------------------------------------------------- /uploads/room_photos/24.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/24.webp -------------------------------------------------------------------------------- /uploads/room_photos/25.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/25.webp -------------------------------------------------------------------------------- /uploads/room_photos/26.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/26.webp -------------------------------------------------------------------------------- /uploads/room_photos/27.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/27.webp -------------------------------------------------------------------------------- /uploads/room_photos/28.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/28.webp -------------------------------------------------------------------------------- /uploads/room_photos/29.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/29.webp -------------------------------------------------------------------------------- /uploads/room_photos/3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/3.webp -------------------------------------------------------------------------------- /uploads/room_photos/30.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/30.webp -------------------------------------------------------------------------------- /uploads/room_photos/31.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/31.webp -------------------------------------------------------------------------------- /uploads/room_photos/4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/4.webp -------------------------------------------------------------------------------- /uploads/room_photos/5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/5.webp -------------------------------------------------------------------------------- /uploads/room_photos/6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/6.webp -------------------------------------------------------------------------------- /uploads/room_photos/7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/7.webp -------------------------------------------------------------------------------- /uploads/room_photos/8.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/8.webp -------------------------------------------------------------------------------- /uploads/room_photos/9.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/uploads/room_photos/9.webp -------------------------------------------------------------------------------- /users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/users/__init__.py -------------------------------------------------------------------------------- /users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin 3 | from . import models 4 | 5 | 6 | @admin.register(models.User) 7 | class UserAdmin(UserAdmin): 8 | 9 | fieldsets = UserAdmin.fieldsets + ( 10 | ("Custom Profile", {"fields": ("avatar", "superhost", "favs")},), 11 | ) 12 | 13 | list_display = UserAdmin.list_display + ("room_count",) 14 | -------------------------------------------------------------------------------- /users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | name = 'users' 6 | -------------------------------------------------------------------------------- /users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0 on 2019-12-16 09:37 2 | 3 | import django.contrib.auth.models 4 | import django.contrib.auth.validators 5 | from django.db import migrations, models 6 | import django.utils.timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ('auth', '0011_update_proxy_permissions'), 15 | ('rooms', '0001_initial'), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='User', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('password', models.CharField(max_length=128, verbose_name='password')), 24 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 25 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 26 | ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), 27 | ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), 28 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 29 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), 30 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 31 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 32 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 33 | ('avatar', models.FileField(blank=True, upload_to='avatars')), 34 | ('superhost', models.BooleanField(default=False)), 35 | ('favs', models.ManyToManyField(related_name='favs', to='rooms.Room')), 36 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), 37 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), 38 | ], 39 | options={ 40 | 'verbose_name': 'user', 41 | 'verbose_name_plural': 'users', 42 | 'abstract': False, 43 | }, 44 | managers=[ 45 | ('objects', django.contrib.auth.models.UserManager()), 46 | ], 47 | ), 48 | ] 49 | -------------------------------------------------------------------------------- /users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/airbnb-api/686130776ae35639d34efabc2399c018a1c1b059/users/migrations/__init__.py -------------------------------------------------------------------------------- /users/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db import models 3 | 4 | 5 | class User(AbstractUser): 6 | 7 | avatar = models.ImageField(upload_to="avatars", blank=True) 8 | superhost = models.BooleanField(default=False) 9 | favs = models.ManyToManyField("rooms.Room", related_name="favs") 10 | 11 | def room_count(self): 12 | return self.rooms.count() 13 | 14 | room_count.short_description = "Room Count" 15 | -------------------------------------------------------------------------------- /users/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import BasePermission 2 | 3 | 4 | class IsSelf(BasePermission): 5 | def has_object_permission(self, request, view, user): 6 | return bool(user == request.user) 7 | -------------------------------------------------------------------------------- /users/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import User 3 | 4 | 5 | class UserSerializer(serializers.ModelSerializer): 6 | 7 | password = serializers.CharField(write_only=True) 8 | 9 | class Meta: 10 | model = User 11 | fields = ( 12 | "id", 13 | "username", 14 | "first_name", 15 | "last_name", 16 | "email", 17 | "avatar", 18 | "superhost", 19 | "password", 20 | ) 21 | read_only_fields = ("id", "superhost", "avatar") 22 | 23 | def validate_first_name(self, value): 24 | return value.upper() 25 | 26 | def create(self, validated_data): 27 | password = validated_data.get("password") 28 | user = super().create(validated_data) 29 | user.set_password(password) 30 | user.save() 31 | return user 32 | -------------------------------------------------------------------------------- /users/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /users/urls.py: -------------------------------------------------------------------------------- 1 | from rest_framework.routers import DefaultRouter 2 | from . import views 3 | 4 | app_name = "users" 5 | 6 | router = DefaultRouter() 7 | router.register("", views.UsersViewSet) 8 | urlpatterns = router.urls 9 | -------------------------------------------------------------------------------- /users/views.py: -------------------------------------------------------------------------------- 1 | import jwt 2 | from django.conf import settings 3 | from django.contrib.auth import authenticate 4 | from rest_framework.response import Response 5 | from rest_framework.decorators import action 6 | from rest_framework import status 7 | from rest_framework.viewsets import ModelViewSet 8 | from rest_framework.permissions import IsAdminUser, AllowAny 9 | from rooms.serializers import RoomSerializer 10 | from rooms.models import Room 11 | from .models import User 12 | from .serializers import UserSerializer 13 | from .permissions import IsSelf 14 | 15 | 16 | class UsersViewSet(ModelViewSet): 17 | 18 | queryset = User.objects.all() 19 | serializer_class = UserSerializer 20 | 21 | def get_permissions(self): 22 | 23 | if self.action == "list": 24 | permission_classes = [IsAdminUser] 25 | elif ( 26 | self.action == "create" 27 | or self.action == "retrieve" 28 | or self.action == "favs" 29 | ): 30 | permission_classes = [AllowAny] 31 | else: 32 | permission_classes = [IsSelf] 33 | return [permission() for permission in permission_classes] 34 | 35 | @action(detail=False, methods=["post"]) 36 | def login(self, request): 37 | username = request.data.get("username") 38 | password = request.data.get("password") 39 | if not username or not password: 40 | return Response(status=status.HTTP_400_BAD_REQUEST) 41 | user = authenticate(username=username, password=password) 42 | if user is not None: 43 | encoded_jwt = jwt.encode( 44 | {"pk": user.pk}, settings.SECRET_KEY, algorithm="HS256" 45 | ) 46 | return Response(data={"token": encoded_jwt, "id": user.pk}) 47 | else: 48 | return Response(status=status.HTTP_401_UNAUTHORIZED) 49 | 50 | @action(detail=True) 51 | def favs(self, request, pk): 52 | user = self.get_object() 53 | serializer = RoomSerializer(user.favs.all(), many=True).data 54 | return Response(serializer) 55 | 56 | @favs.mapping.put 57 | def toggle_favs(self, request, pk): 58 | pk = request.data.get("pk", None) 59 | user = self.get_object() 60 | if pk is not None: 61 | try: 62 | room = Room.objects.get(pk=pk) 63 | if room in user.favs.all(): 64 | user.favs.remove(room) 65 | else: 66 | user.favs.add(room) 67 | return Response() 68 | except Room.DoesNotExist: 69 | pass 70 | return Response(status=status.HTTP_400_BAD_REQUEST) 71 | --------------------------------------------------------------------------------