├── .gitignore ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.MD ├── mystore ├── frontend │ ├── .editorconfig │ ├── .gitignore │ ├── babel.config.js │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ ├── logo.png │ │ │ └── logo.svg │ │ ├── components │ │ │ ├── Alert.vue │ │ │ ├── CheckoutCard.vue │ │ │ ├── DeleteProfile.vue │ │ │ ├── Footer.vue │ │ │ ├── FormInput.vue │ │ │ ├── Images.vue │ │ │ ├── NavBar.vue │ │ │ ├── PreCheckoutCard.vue │ │ │ ├── SnackBar.vue │ │ │ ├── StarRating.vue │ │ │ ├── UpdatePassword.vue │ │ │ └── UpdateProfile.vue │ │ ├── main.js │ │ ├── plugins │ │ │ └── vuetify.js │ │ ├── router │ │ │ └── index.js │ │ ├── store │ │ │ └── index.js │ │ ├── validation.js │ │ └── views │ │ │ ├── About.vue │ │ │ ├── Cart.vue │ │ │ ├── Checkout.vue │ │ │ ├── Detail.vue │ │ │ ├── GuestForm.vue │ │ │ ├── Home.vue │ │ │ ├── LoginForm.vue │ │ │ ├── PasswordReset.vue │ │ │ ├── PasswordResetConfirm.vue │ │ │ ├── PasswordResetDone.vue │ │ │ ├── PreCheckout.vue │ │ │ ├── Profile.vue │ │ │ └── RegisterForm.vue │ └── vue.config.js ├── manage.py ├── mystore │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── orders │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── screenshots │ ├── admin.png │ ├── screenshot(1).png │ ├── screenshot(2).png │ └── screenshot(3).png ├── store │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── signals.py │ ├── tests.py │ ├── urls.py │ ├── utils.py │ └── views.py ├── templates │ └── admin │ │ └── base_site.html └── users │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ ├── 0001_initial.py │ ├── 0002_profile_phone.py │ ├── 0003_alter_profile_phone.py │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── signals.py │ ├── templates │ └── users │ │ ├── email_base.html │ │ ├── user_reset_password.html │ │ └── user_reset_password.txt │ ├── tests.py │ ├── urls.py │ └── views.py └── vetur.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Vue 2 | node_modules 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | downloads/ 14 | eggs/ 15 | .eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | pip-wheel-metadata/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | db.sqlite3 56 | 57 | # Sphinx documentation 58 | docs/_build/ 59 | 60 | # PyBuilder 61 | target/ 62 | 63 | # pyenv 64 | .python-version 65 | 66 | # Environments 67 | .env 68 | .venv 69 | tmp/ 70 | env/ 71 | venv/ 72 | ENV/ 73 | env.bak/ 74 | venv.bak/ 75 | 76 | # mypy 77 | .mypy_cache/ 78 | .dmypy.json 79 | dmypy.json 80 | 81 | # Editor directories and files 82 | .idea 83 | .vscode 84 | *.suo 85 | *.ntvs* 86 | *.njsproj 87 | *.sln 88 | *.sw? 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License (ISC) 2 | 3 | Copyright 4 | 5 | Permission to use, copy, modify, and/or distribute this software 6 | for any purpose with or without fee is hereby granted, 7 | provided that the above copyright notice and this permission 8 | notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS 11 | ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 12 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. 13 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER 15 | RESULTING FROM LOSS OF USE, DATA OR PROFITS, 16 | WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 17 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | django = "==4.0.10" 8 | psycopg2 = "*" 9 | djangorestframework = "*" 10 | django-cors-headers = "*" 11 | django-rest-passwordreset = "*" 12 | pillow = "*" 13 | boto3 = "*" 14 | django-storages = "*" 15 | 16 | [dev-packages] 17 | 18 | [requires] 19 | python_version = "3.9" 20 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "b7e1719004ca00a020a4f39ac368dccd115097d29ad4f4a98bf93649f3ae94a6" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.9" 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:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac", 22 | "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506" 23 | ], 24 | "markers": "python_version >= '3.7'", 25 | "version": "==3.6.0" 26 | }, 27 | "boto3": { 28 | "hashes": [ 29 | "sha256:1e543a27a7f0cddef298492d70bf7ea67db02960ec704c1af26d2b543cdefb43", 30 | "sha256:df541c06ff1018675097e102f2f2aa2abc31ea5d197c7fc229afbe30950ba131" 31 | ], 32 | "index": "pypi", 33 | "version": "==1.26.93" 34 | }, 35 | "botocore": { 36 | "hashes": [ 37 | "sha256:39c2ad2d44c7e1ef25a98e957b63fa760b87bcf32423a033dceeb359976c37d7", 38 | "sha256:940201ae2f5edbf4ccbf1066e345cf7f5224568bd702fa9e80a92e5c479fa649" 39 | ], 40 | "markers": "python_version >= '3.7'", 41 | "version": "==1.29.93" 42 | }, 43 | "django": { 44 | "hashes": [ 45 | "sha256:2c2f73c16b11cb272c6d5e3b063f0d1be06f378d8dc6005fbe8542565db659cc", 46 | "sha256:4496eb4f65071578b703fdc6e6f29302553c7440e3f77baf4cefa4a4e091fc3d" 47 | ], 48 | "index": "pypi", 49 | "version": "==4.0.10" 50 | }, 51 | "django-cors-headers": { 52 | "hashes": [ 53 | "sha256:5fbd58a6fb4119d975754b2bc090f35ec160a8373f276612c675b00e8a138739", 54 | "sha256:684180013cc7277bdd8702b80a3c5a4b3fcae4abb2bf134dceb9f5dfe300228e" 55 | ], 56 | "index": "pypi", 57 | "version": "==3.14.0" 58 | }, 59 | "django-rest-passwordreset": { 60 | "hashes": [ 61 | "sha256:0d76eb8d0be9636f4404fd35e8a0842c968effeac1d141c55a45aef1ebce54c7", 62 | "sha256:f888fcb589c46e2f86f9686751de4cc1350c519aa3b120b993b1d675fd96fad6" 63 | ], 64 | "index": "pypi", 65 | "version": "==1.3.0" 66 | }, 67 | "django-storages": { 68 | "hashes": [ 69 | "sha256:31dc5a992520be571908c4c40d55d292660ece3a55b8141462b4e719aa38eab3", 70 | "sha256:cbadd15c909ceb7247d4ffc503f12a9bec36999df8d0bef7c31e57177d512688" 71 | ], 72 | "index": "pypi", 73 | "version": "==1.13.2" 74 | }, 75 | "djangorestframework": { 76 | "hashes": [ 77 | "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8", 78 | "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08" 79 | ], 80 | "index": "pypi", 81 | "version": "==3.14.0" 82 | }, 83 | "jmespath": { 84 | "hashes": [ 85 | "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", 86 | "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe" 87 | ], 88 | "markers": "python_version >= '3.7'", 89 | "version": "==1.0.1" 90 | }, 91 | "pillow": { 92 | "hashes": [ 93 | "sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33", 94 | "sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b", 95 | "sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e", 96 | "sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35", 97 | "sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153", 98 | "sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9", 99 | "sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569", 100 | "sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57", 101 | "sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8", 102 | "sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1", 103 | "sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264", 104 | "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157", 105 | "sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9", 106 | "sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133", 107 | "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9", 108 | "sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab", 109 | "sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6", 110 | "sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5", 111 | "sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df", 112 | "sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503", 113 | "sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b", 114 | "sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa", 115 | "sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327", 116 | "sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493", 117 | "sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d", 118 | "sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4", 119 | "sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4", 120 | "sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35", 121 | "sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2", 122 | "sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c", 123 | "sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011", 124 | "sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a", 125 | "sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e", 126 | "sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f", 127 | "sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848", 128 | "sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57", 129 | "sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f", 130 | "sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c", 131 | "sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9", 132 | "sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5", 133 | "sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9", 134 | "sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d", 135 | "sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0", 136 | "sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1", 137 | "sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e", 138 | "sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815", 139 | "sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0", 140 | "sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b", 141 | "sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd", 142 | "sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c", 143 | "sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3", 144 | "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab", 145 | "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858", 146 | "sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5", 147 | "sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee", 148 | "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343", 149 | "sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb", 150 | "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47", 151 | "sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed", 152 | "sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837", 153 | "sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286", 154 | "sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28", 155 | "sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628", 156 | "sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df", 157 | "sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d", 158 | "sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d", 159 | "sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a", 160 | "sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6", 161 | "sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336", 162 | "sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132", 163 | "sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070", 164 | "sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe", 165 | "sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a", 166 | "sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd", 167 | "sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391", 168 | "sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a", 169 | "sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12" 170 | ], 171 | "index": "pypi", 172 | "version": "==9.4.0" 173 | }, 174 | "psycopg2": { 175 | "hashes": [ 176 | "sha256:093e3894d2d3c592ab0945d9eba9d139c139664dcf83a1c440b8a7aa9bb21955", 177 | "sha256:190d51e8c1b25a47484e52a79638a8182451d6f6dff99f26ad9bd81e5359a0fa", 178 | "sha256:1a5c7d7d577e0eabfcf15eb87d1e19314c8c4f0e722a301f98e0e3a65e238b4e", 179 | "sha256:1e5a38aa85bd660c53947bd28aeaafb6a97d70423606f1ccb044a03a1203fe4a", 180 | "sha256:322fd5fca0b1113677089d4ebd5222c964b1760e361f151cbb2706c4912112c5", 181 | "sha256:4cb9936316d88bfab614666eb9e32995e794ed0f8f6b3b718666c22819c1d7ee", 182 | "sha256:920bf418000dd17669d2904472efeab2b20546efd0548139618f8fa305d1d7ad", 183 | "sha256:922cc5f0b98a5f2b1ff481f5551b95cd04580fd6f0c72d9b22e6c0145a4840e0", 184 | "sha256:a5246d2e683a972e2187a8714b5c2cf8156c064629f9a9b1a873c1730d9e245a", 185 | "sha256:b9ac1b0d8ecc49e05e4e182694f418d27f3aedcfca854ebd6c05bb1cffa10d6d", 186 | "sha256:d3ef67e630b0de0779c42912fe2cbae3805ebaba30cda27fea2a3de650a9414f", 187 | "sha256:f5b6320dbc3cf6cfb9f25308286f9f7ab464e65cfb105b64cc9c52831748ced2", 188 | "sha256:fc04dd5189b90d825509caa510f20d1d504761e78b8dfb95a0ede180f71d50e5" 189 | ], 190 | "index": "pypi", 191 | "version": "==2.9.5" 192 | }, 193 | "python-dateutil": { 194 | "hashes": [ 195 | "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", 196 | "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" 197 | ], 198 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 199 | "version": "==2.8.2" 200 | }, 201 | "pytz": { 202 | "hashes": [ 203 | "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0", 204 | "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a" 205 | ], 206 | "version": "==2022.7.1" 207 | }, 208 | "s3transfer": { 209 | "hashes": [ 210 | "sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd", 211 | "sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947" 212 | ], 213 | "markers": "python_version >= '3.7'", 214 | "version": "==0.6.0" 215 | }, 216 | "six": { 217 | "hashes": [ 218 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 219 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 220 | ], 221 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 222 | "version": "==1.16.0" 223 | }, 224 | "sqlparse": { 225 | "hashes": [ 226 | "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34", 227 | "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268" 228 | ], 229 | "markers": "python_version >= '3.5'", 230 | "version": "==0.4.3" 231 | }, 232 | "tzdata": { 233 | "hashes": [ 234 | "sha256:2b88858b0e3120792a3c0635c23daf36a7d7eeeca657c323da299d2094402a0d", 235 | "sha256:fe5f866eddd8b96e9fcba978f8e503c909b19ea7efda11e52e39494bad3a7bfa" 236 | ], 237 | "markers": "sys_platform == 'win32'", 238 | "version": "==2022.7" 239 | }, 240 | "urllib3": { 241 | "hashes": [ 242 | "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305", 243 | "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42" 244 | ], 245 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 246 | "version": "==1.26.15" 247 | } 248 | }, 249 | "develop": {} 250 | } 251 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Shopping Cart 2 | 3 | ## Stripe Integration 4 | [Instructions on how to connect Stripe](https://codingpr.com/stripe-vue-blog/). 5 | 6 | ## Features 7 | 8 | - Log in 9 | - Auth Token login 10 | - via username & password 11 | - Register 12 | - Reset password 13 | - Forgot password 14 | - Change password 15 | - Change profile 16 | - Customer address and phone number 17 | - Star rating system 18 | - Inventory management system 19 | 20 | ## Functionality Store Admin 21 | 22 | - Upload products through django admin 23 | - Images will uploaded to AWS S3 24 | 25 | ## Requirements 26 | 27 | @vue/cli 4.5.13 | Python 3.9.4 | Nodejs ^16.13.1 | Postgresql 28 | 29 | - Database engine and email service 30 | - AWS S3 account 31 | 32 | ## Setup django 33 | 34 | - [Fork Repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo). 35 | - Create a new directory 36 | - cd to your new directory 37 | - Clone Repo 38 | 39 | ``` python 40 | git clone https://github.com//django_store.git 41 | ``` 42 | - cd django_store 43 | - Create your virtual environment 44 | 45 | ```python 46 | pip install pipenv 47 | pipenv install 48 | pipenv shell 49 | ``` 50 | 51 | - Configure settings.py (database connection, email and AWS) 52 | - Run your migrations 53 | 54 | ```python 55 | # Use py manage.py on windows 56 | 57 | python manage.py makemigrations 58 | python manage.py migrate 59 | ``` 60 | - Create your superuser 61 | 62 | ```python 63 | # Use py manage.py on windows 64 | 65 | python manage.py createsuperuser 66 | ``` 67 | - Activate local project 68 | 69 | ```python 70 | # Use py manage.py on windows 71 | 72 | python manage.py runserver 73 | ``` 74 | 75 | ## Setup Vue 76 | 77 | - Open new terminal 78 | 79 | ```javascript 80 | cd mystore 81 | cd frontend 82 | npm install 83 | npm run serve 84 | ``` 85 | -------------------------------------------------------------------------------- /mystore/frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /mystore/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | products.js 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /mystore/frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /mystore/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.21.4", 12 | "core-js": "^3.20.1", 13 | "vee-validate": "^3.4.14", 14 | "vue": "^2.6.14", 15 | "vue-router": "^3.5.3", 16 | "vuetify": "^2.6.2", 17 | "vuex": "^3.4.0", 18 | "vuex-persist": "^3.1.3" 19 | }, 20 | "devDependencies": { 21 | "@vue/cli-plugin-babel": "^4.5.15", 22 | "@vue/cli-plugin-eslint": "^4.5.15", 23 | "@vue/cli-plugin-router": "^4.5.15", 24 | "@vue/cli-plugin-vuex": "^4.5.15", 25 | "@vue/cli-service": "^4.5.15", 26 | "@vue/eslint-config-standard": "^5.1.2", 27 | "babel-eslint": "^10.1.0", 28 | "eslint": "^6.7.2", 29 | "eslint-plugin-import": "^2.25.3", 30 | "eslint-plugin-node": "^11.1.0", 31 | "eslint-plugin-promise": "^4.2.1", 32 | "eslint-plugin-standard": "^4.0.0", 33 | "eslint-plugin-vue": "^6.2.2", 34 | "sass": "=1.32.13", 35 | "sass-loader": "^10.2.0", 36 | "vue-cli-plugin-vuetify": "^2.4.5", 37 | "vue-template-compiler": "^2.6.14", 38 | "vuetify-loader": "^1.7.3" 39 | }, 40 | "eslintConfig": { 41 | "root": true, 42 | "env": { 43 | "node": true 44 | }, 45 | "extends": [ 46 | "plugin:vue/essential", 47 | "@vue/standard" 48 | ], 49 | "parserOptions": { 50 | "parser": "babel-eslint" 51 | }, 52 | "rules": { 53 | "dot-notation": [ 54 | 0, 55 | { 56 | "allowKeywords": true, 57 | "allowPattern": "^[a-z]+(_[a-z]+)+$" 58 | } 59 | ], 60 | "camelcase": "off" 61 | } 62 | }, 63 | "browserslist": [ 64 | "> 1%", 65 | "last 2 versions", 66 | "not dead" 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /mystore/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blixor/vue-Django-store/4f7d84209af81e53bc8d3cb5fd052a29f8dfe079/mystore/frontend/public/favicon.ico -------------------------------------------------------------------------------- /mystore/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 12 | 13 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /mystore/frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 26 | -------------------------------------------------------------------------------- /mystore/frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blixor/vue-Django-store/4f7d84209af81e53bc8d3cb5fd052a29f8dfe079/mystore/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /mystore/frontend/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | Artboard 46 2 | -------------------------------------------------------------------------------- /mystore/frontend/src/components/Alert.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 33 | -------------------------------------------------------------------------------- /mystore/frontend/src/components/CheckoutCard.vue: -------------------------------------------------------------------------------- 1 | 57 | 81 | -------------------------------------------------------------------------------- /mystore/frontend/src/components/DeleteProfile.vue: -------------------------------------------------------------------------------- 1 | 56 | 82 | -------------------------------------------------------------------------------- /mystore/frontend/src/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /mystore/frontend/src/components/FormInput.vue: -------------------------------------------------------------------------------- 1 | 49 | 79 | -------------------------------------------------------------------------------- /mystore/frontend/src/components/Images.vue: -------------------------------------------------------------------------------- 1 | 14 | 30 | -------------------------------------------------------------------------------- /mystore/frontend/src/components/NavBar.vue: -------------------------------------------------------------------------------- 1 | 103 | 104 | 133 | -------------------------------------------------------------------------------- /mystore/frontend/src/components/PreCheckoutCard.vue: -------------------------------------------------------------------------------- 1 | 36 | 51 | -------------------------------------------------------------------------------- /mystore/frontend/src/components/SnackBar.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 31 | -------------------------------------------------------------------------------- /mystore/frontend/src/components/StarRating.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 40 | -------------------------------------------------------------------------------- /mystore/frontend/src/components/UpdatePassword.vue: -------------------------------------------------------------------------------- 1 | 61 | 105 | -------------------------------------------------------------------------------- /mystore/frontend/src/components/UpdateProfile.vue: -------------------------------------------------------------------------------- 1 | 108 | 160 | -------------------------------------------------------------------------------- /mystore/frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import vuetify from './plugins/vuetify' 6 | 7 | Vue.config.productionTip = false 8 | 9 | new Vue({ 10 | router, 11 | store, 12 | vuetify, 13 | render: h => h(App) 14 | }).$mount('#app') 15 | -------------------------------------------------------------------------------- /mystore/frontend/src/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuetify from 'vuetify/lib/framework' 3 | 4 | Vue.use(Vuetify) 5 | 6 | export default new Vuetify({ 7 | }) 8 | -------------------------------------------------------------------------------- /mystore/frontend/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import store from '../store/index' 4 | import Home from '../views/Home' 5 | 6 | Vue.use(VueRouter) 7 | 8 | const routes = [ 9 | { 10 | path: '/', 11 | name: 'Home', 12 | component: Home 13 | }, 14 | { 15 | path: '/detail/:slug', 16 | name: 'Detail', 17 | props: true, 18 | component: () => import('../views/Detail') 19 | }, 20 | { 21 | path: '/profile', 22 | name: 'Profile', 23 | component: () => import('../views/Profile') 24 | }, 25 | { 26 | path: '/cart', 27 | name: 'Cart', 28 | component: () => import('../views/Cart') 29 | }, 30 | { 31 | path: '/register', 32 | name: 'RegisterForm', 33 | component: () => import('../views/RegisterForm') 34 | }, 35 | { 36 | path: '/login', 37 | name: 'LoginForm', 38 | component: () => import('../views/LoginForm') 39 | }, 40 | { 41 | path: '/password_reset', 42 | name: 'PasswordReset', 43 | component: () => import('../views/PasswordReset') 44 | }, 45 | { 46 | path: '/password_reset/done', 47 | name: 'PasswordResetDone', 48 | component: () => import('../views/PasswordResetDone') 49 | }, 50 | { 51 | path: '/api/password_reset/confirm/:token?', 52 | name: 'PasswordResetConfirm', 53 | component: () => import('../views/PasswordResetConfirm') 54 | }, 55 | { 56 | path: '/pre-checkout', 57 | name: 'PreCheckout', 58 | component: () => import('../views/PreCheckout') 59 | }, 60 | { 61 | path: '/checkout', 62 | name: 'Checkout', 63 | component: () => import('../views/Checkout') 64 | }, 65 | { 66 | path: '/guest-form', 67 | name: 'GuestForm', 68 | component: () => import('../views/GuestForm') 69 | }, 70 | { 71 | path: '/about', 72 | name: 'About', 73 | // route level code-splitting 74 | // this generates a separate chunk (about.[hash].js) for this route 75 | // which is lazy-loaded when the route is visited. 76 | component: () => import(/* webpackChunkName: "about" */ '../views/About') 77 | } 78 | ] 79 | 80 | const router = new VueRouter({ 81 | routes 82 | }) 83 | 84 | // Check if user is login before showing login route. 85 | router.beforeEach((to, from, next) => { 86 | const isAuthenticated = store.getters.isLoggedIn 87 | if (to.name === 'LoginForm' && isAuthenticated) next({ name: 'Home' }) 88 | else next() 89 | if (to.name === 'RegisterForm' && isAuthenticated) next({ name: 'Home' }) 90 | else next() 91 | if (to.name === 'Profile' && !isAuthenticated) next({ name: 'Home' }) 92 | else next() 93 | }) 94 | 95 | export default router 96 | -------------------------------------------------------------------------------- /mystore/frontend/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import axios from 'axios' 4 | import router from '@/router' 5 | import VuexPersistence from 'vuex-persist' 6 | 7 | axios.defaults.xsrfHeaderName = 'X-CSRFToken' 8 | axios.defaults.xsrfCookieName = 'csrftoken' 9 | 10 | Vue.use(Vuex) 11 | 12 | export default new Vuex.Store({ 13 | state: { 14 | snackbar: { 15 | show: false, 16 | color: '', 17 | text: '' 18 | }, 19 | status: '', 20 | token: '', 21 | user: {}, 22 | products: [], 23 | stars: [], 24 | cart: [] 25 | }, 26 | mutations: { 27 | SET_PRODUCTS (state, payload) { 28 | state.products = payload 29 | }, 30 | AUTH_REQUEST (state) { 31 | state.status = 'loading' 32 | }, 33 | AUTH_ERROR (state) { 34 | state.status = 'Please try again!' 35 | }, 36 | AUTH_SUCCESS (state, payload) { 37 | state.status = 'success' 38 | state.token = payload.token 39 | state.user = payload.user 40 | }, 41 | PROFILE_UPDATE (state, payload) { 42 | const user = state.user 43 | user.address = payload.profile.address 44 | user.city = payload.profile.city 45 | user.state = payload.profile.state 46 | user.zipcode = payload.profile.zipcode 47 | user.phone = payload.profile.phone 48 | }, 49 | LOGOUT (state) { 50 | state.status = '' 51 | state.token = '' 52 | state.user = {} 53 | }, 54 | CHECKOUT_SUCCESS (state) { 55 | state.stars = [] 56 | state.cart = [] 57 | }, 58 | starRating (state, payload) { 59 | const { itemId, value } = payload 60 | const i = state.stars.findIndex((item) => { 61 | return item.itemId === itemId 62 | }) 63 | if (i === -1) { 64 | state.stars.push({ itemId, value }) 65 | } else { 66 | state.stars[i].value = value 67 | } 68 | }, 69 | addCart (state, payload) { 70 | const { itemId, qty, image, name, price } = payload 71 | // Validation 72 | const checkProduct = state.products.find((product) => { 73 | return product.id === itemId && product.price === price 74 | }) 75 | if (!checkProduct) { 76 | return 77 | } 78 | // Find product index and push items or increase qty. 79 | const indx = state.cart.findIndex((item) => { 80 | return item.itemId === itemId 81 | }) 82 | if (indx === -1) { 83 | state.cart.push({ name, itemId, qty, image, price }) 84 | } else { 85 | state.cart[indx].qty += qty 86 | } 87 | }, 88 | cartSnack (state, payload) { 89 | state.snackbar = payload 90 | state.snackbar.color = payload.color 91 | state.snackbar.text = payload.text 92 | }, 93 | guestUser (state, payload) { 94 | state.user = payload 95 | }, 96 | remove (state, itemId) { 97 | // Find product index and delete from cart. 98 | const index = state.cart.findIndex((item) => { 99 | return item.itemId === itemId 100 | }) 101 | state.cart.splice(index, 1) 102 | } 103 | }, 104 | plugins: [new VuexPersistence().plugin], 105 | getters: { 106 | isLoggedIn: state => !!state.token, 107 | authStatus: state => state.status, 108 | cartLen: (state) => { 109 | const res = state.cart.map(item => { 110 | return item.qty 111 | }) 112 | return res.reduce((total, num) => total + num, 0) 113 | }, 114 | subTotal: (state) => { 115 | let res = 0 116 | state.cart.map(item => { 117 | res += item.price * item.qty 118 | }) 119 | return parseFloat(res).toFixed(2) 120 | }, 121 | tax: (_state, getters) => (parseFloat(getters.subTotal) * 0.115).toFixed(2), 122 | grandTotal: (_state, getters) => { 123 | const total = parseFloat(getters.subTotal) + parseFloat(getters.tax) 124 | return total.toFixed(2) 125 | } 126 | }, 127 | actions: { 128 | async getProducts ({ commit }) { 129 | try { 130 | const res = await axios.get('products/') 131 | if (res) { 132 | commit('SET_PRODUCTS', res.data) 133 | } 134 | } catch (error) { 135 | commit('cartSnack', { 136 | show: true, 137 | color: 'red darken-3', 138 | text: 'Server Error' 139 | }) 140 | } 141 | }, 142 | async register ({ commit }, user) { 143 | commit('AUTH_REQUEST') 144 | const res = await axios.post('users/register/', user) 145 | if (res.status === 201) { 146 | router.push('/login') 147 | } else { 148 | commit('AUTH_ERROR') 149 | commit('cartSnack', { 150 | show: true, 151 | color: 'red darken-3', 152 | text: res.data.username ? res.data.username[0] : 'An error has ocurred, check your password' 153 | }) 154 | delete axios.defaults.headers.common['Authorization'] 155 | } 156 | }, 157 | async login ({ commit }, user) { 158 | try { 159 | commit('AUTH_REQUEST') 160 | axios.defaults.headers.common['X-CSRFToken'] = user.csrftoken 161 | const res = await axios.post('users/login/', user) 162 | if (res.status === 200) { 163 | const authToken = res.data.token 164 | const user = res.data 165 | axios.defaults.headers.common['Authorization'] = authToken 166 | commit('AUTH_SUCCESS', { 167 | token: authToken, 168 | user: user 169 | }) 170 | } 171 | } catch (error) { 172 | commit('AUTH_ERROR') 173 | delete axios.defaults.headers.common['Authorization'] 174 | } 175 | }, 176 | // user update 177 | async update ({ commit, state }, profile) { 178 | const id = state.user.user_id 179 | const token = state.token 180 | const res = await axios.patch(`users/update/${id}/`, profile, { 181 | headers: { 182 | Authorization: `Token ${token}` 183 | } 184 | }) 185 | if (res) { 186 | const profile = res.data 187 | commit('PROFILE_UPDATE', { 188 | profile: profile 189 | }) 190 | commit('cartSnack', { 191 | show: true, 192 | color: 'success', 193 | text: 'Update Profile Success!' 194 | }) 195 | } 196 | }, 197 | async checkout ({ commit, state, getters }) { 198 | try { 199 | const res = await axios.post('orders/order/', { 200 | order: { 201 | cart: state.cart, 202 | rating: state.stars, 203 | subtotal: getters.subTotal, 204 | tax: getters.tax, 205 | total: getters.grandTotal, 206 | first_name: state.user.first_name, 207 | last_name: state.user.last_name, 208 | email: state.user.email, 209 | phone: state.user.phone, 210 | address: state.user.address, 211 | city: state.user.city, 212 | state: state.user.state, 213 | zipcode: state.user.zipcode 214 | } 215 | }) 216 | if (res.status === 200) { 217 | commit('CHECKOUT_SUCCESS') 218 | // TODO: push thankyou page 219 | } 220 | } catch (error) { 221 | commit('cartSnack', { 222 | show: true, 223 | color: 'red darken-3', 224 | text: 'An error has ocurred!' 225 | }) 226 | } 227 | }, 228 | // deactivate user 229 | async delete ({ commit, state }, user) { 230 | const id = state.user.user_id 231 | const token = state.token 232 | try { 233 | const res = await axios.post(`users/delete/${id}/`, user, { 234 | headers: { 235 | Authorization: `Token ${token}` 236 | } 237 | }) 238 | if (res.status === 204) { 239 | commit('LOGOUT') 240 | delete axios.defaults.headers.common['Authorization'] 241 | router.push('/') 242 | } 243 | } catch (error) { 244 | commit('cartSnack', { 245 | show: true, 246 | color: 'red darken-3', 247 | text: 'An error has ocurred, check your password' 248 | }) 249 | } 250 | }, 251 | async changePassword ({ commit, state }, passwords) { 252 | const id = state.user.user_id 253 | const token = state.token 254 | const res = await axios.put(`users/delete/${id}/`, passwords, { 255 | headers: { 256 | Authorization: `Token ${token}` 257 | } 258 | }) 259 | if (res) { 260 | commit('LOGOUT') 261 | delete axios.defaults.headers.common['Authorization'] 262 | router.push('/login') 263 | commit('cartSnack', { 264 | show: true, 265 | color: 'success', 266 | text: 'Password Change Success!' 267 | }) 268 | } 269 | }, 270 | async passwordReset ({ commit }, email) { 271 | const res = await axios.post('api/password_reset/', { 272 | email: email 273 | }) 274 | if (res.data.status === 'OK') { 275 | router.push('/password_reset/done') 276 | } 277 | }, 278 | async resetPasswordConfirm ({ commit }, payload) { 279 | const { token, password } = payload 280 | try { 281 | const res = await axios.post(`api/password_reset/confirm/?token=${token}`, { 282 | password: password, 283 | token: token 284 | }) 285 | if (res.data.status === 'OK') { 286 | router.push('/login') 287 | } 288 | } catch (error) { 289 | commit('cartSnack', { 290 | show: true, 291 | color: 'red darken-3', 292 | text: 'An error has ocurred, try again!' 293 | }) 294 | } 295 | }, 296 | async logout ({ commit }) { 297 | const res = await axios.post('users/logout/') 298 | if (res) { 299 | commit('LOGOUT') 300 | delete axios.defaults.headers.common['Authorization'] 301 | router.push('/login') 302 | } 303 | } 304 | } 305 | }) 306 | -------------------------------------------------------------------------------- /mystore/frontend/src/validation.js: -------------------------------------------------------------------------------- 1 | import { extend, setInteractionMode } from 'vee-validate' 2 | import { required, email, max, min, alpha_spaces, digits } from 'vee-validate/dist/rules' 3 | 4 | setInteractionMode('eager') 5 | 6 | extend('required', { 7 | ...required, 8 | message: '{_field_} can not be empty' 9 | }) 10 | 11 | extend('max', { 12 | ...max, 13 | message: '{_field_} may not be greater than {length} characters' 14 | }) 15 | 16 | extend('min', { 17 | ...min, 18 | message: '{_field_} may not be less than {length} characters' 19 | }) 20 | 21 | extend('digits', { 22 | ...digits, 23 | message: '{_field_} needs to be {length} digits. ({_value_})' 24 | }) 25 | 26 | extend('email', email) 27 | 28 | extend('alpha_spaces', alpha_spaces) 29 | -------------------------------------------------------------------------------- /mystore/frontend/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /mystore/frontend/src/views/Cart.vue: -------------------------------------------------------------------------------- 1 | 94 | 95 | 113 | -------------------------------------------------------------------------------- /mystore/frontend/src/views/Checkout.vue: -------------------------------------------------------------------------------- 1 | 117 | 118 | 177 | -------------------------------------------------------------------------------- /mystore/frontend/src/views/Detail.vue: -------------------------------------------------------------------------------- 1 | 99 | 100 | 145 | -------------------------------------------------------------------------------- /mystore/frontend/src/views/GuestForm.vue: -------------------------------------------------------------------------------- 1 | 127 | 182 | -------------------------------------------------------------------------------- /mystore/frontend/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 114 | 115 | 129 | -------------------------------------------------------------------------------- /mystore/frontend/src/views/LoginForm.vue: -------------------------------------------------------------------------------- 1 | 73 | 129 | -------------------------------------------------------------------------------- /mystore/frontend/src/views/PasswordReset.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 86 | -------------------------------------------------------------------------------- /mystore/frontend/src/views/PasswordResetConfirm.vue: -------------------------------------------------------------------------------- 1 | 71 | 110 | -------------------------------------------------------------------------------- /mystore/frontend/src/views/PasswordResetDone.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 31 | -------------------------------------------------------------------------------- /mystore/frontend/src/views/PreCheckout.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 76 | -------------------------------------------------------------------------------- /mystore/frontend/src/views/Profile.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 37 | -------------------------------------------------------------------------------- /mystore/frontend/src/views/RegisterForm.vue: -------------------------------------------------------------------------------- 1 | 97 | 152 | -------------------------------------------------------------------------------- /mystore/frontend/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transpileDependencies: [ 3 | 'vuetify' 4 | ], 5 | devServer: { 6 | proxy: 'http://127.0.0.1:8000/' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /mystore/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 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mystore.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /mystore/mystore/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blixor/vue-Django-store/4f7d84209af81e53bc8d3cb5fd052a29f8dfe079/mystore/mystore/__init__.py -------------------------------------------------------------------------------- /mystore/mystore/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for mystore 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.2/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', 'mystore.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /mystore/mystore/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for mystore project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | from pathlib import Path 15 | 16 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 17 | BASE_DIR = Path(__file__).resolve().parent.parent 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = os.environ.get('SECRET_KEY') 25 | 26 | # TODO: CHANGE DEBUG TO FALSE IN PRODUCTION! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = [] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_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 | # custom 42 | 'store.apps.StoreConfig', 43 | 'users.apps.UsersConfig', 44 | 'orders.apps.OrdersConfig', 45 | 'rest_framework', 46 | 'rest_framework.authtoken', 47 | 'django_rest_passwordreset', 48 | 'corsheaders', 49 | ] 50 | 51 | MIDDLEWARE = [ 52 | 'django.middleware.security.SecurityMiddleware', 53 | 'django.contrib.sessions.middleware.SessionMiddleware', 54 | 'corsheaders.middleware.CorsMiddleware', 55 | 'django.middleware.common.CommonMiddleware', 56 | 'django.middleware.csrf.CsrfViewMiddleware', 57 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 58 | 'django.contrib.messages.middleware.MessageMiddleware', 59 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 60 | ] 61 | 62 | CORS_ALLOWED_ORIGINS = [ 63 | "http://localhost:8080", 64 | ] 65 | 66 | CORS_ALLOW_METHODS = [ 67 | 'GET', 68 | 'POST', 69 | 'PATCH', 70 | 'PUT', 71 | ] 72 | 73 | CORS_ALLOW_HEADERS = [ 74 | 'headers', 75 | 'content-type', 76 | 'Authorization', 77 | 'X-CSRFToken', 78 | ] 79 | 80 | CSRF_TRUSTED_ORIGINS = [ 81 | "http://localhost:8080", 82 | ] 83 | 84 | ROOT_URLCONF = 'mystore.urls' 85 | 86 | CSRF_COOKIE_NAME = "csrftoken" 87 | 88 | TEMPLATES = [ 89 | { 90 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 91 | 'DIRS': [BASE_DIR / 'templates'], 92 | 'APP_DIRS': True, 93 | 'OPTIONS': { 94 | 'context_processors': [ 95 | 'django.template.context_processors.debug', 96 | 'django.template.context_processors.request', 97 | 'django.contrib.auth.context_processors.auth', 98 | 'django.contrib.messages.context_processors.messages', 99 | ], 100 | }, 101 | }, 102 | ] 103 | 104 | WSGI_APPLICATION = 'mystore.wsgi.application' 105 | 106 | 107 | # Auth token 108 | REST_FRAMEWORK = { 109 | 'DEFAULT_AUTHENTICATION_CLASSES': [ 110 | 'rest_framework.authentication.TokenAuthentication', 111 | ] 112 | } 113 | 114 | 115 | # Database 116 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 117 | 118 | DATABASES = { 119 | 'default': { 120 | 'ENGINE': 'django.db.backends.postgresql', 121 | 'NAME': 'shop_example_db', 122 | 'USER': os.environ.get('DATABASE_USER'), 123 | 'PASSWORD': os.environ.get('DATABASE_PASS'), 124 | 'HOST': '127.0.0.1', 125 | 'PORT': '5432', 126 | } 127 | } 128 | 129 | 130 | # Password validation 131 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 132 | 133 | AUTH_PASSWORD_VALIDATORS = [ 134 | { 135 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 136 | }, 137 | { 138 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 139 | }, 140 | { 141 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 142 | }, 143 | { 144 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 145 | }, 146 | ] 147 | 148 | 149 | # Internationalization 150 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 151 | 152 | LANGUAGE_CODE = 'en-us' 153 | 154 | TIME_ZONE = 'America/Puerto_Rico' 155 | 156 | USE_I18N = True 157 | 158 | USE_L10N = True 159 | 160 | USE_TZ = True 161 | 162 | 163 | # Static files (CSS, JavaScript, Images) 164 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 165 | 166 | STATIC_URL = '/static/' 167 | 168 | # Default primary key field type 169 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 170 | 171 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 172 | 173 | # Email config 174 | EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' 175 | EMAIL_PORT = 2525 176 | EMAIL_USE_TLS = True 177 | EMAIL_HOST = os.environ.get('EMAIL_HOST') 178 | EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER') 179 | EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD') 180 | MAIL_FROM_ADDRESS = os.environ.get('MAIL_FROM_ADDRESS') 181 | 182 | 183 | # Aws config 184 | AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID') 185 | AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY') 186 | AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME') 187 | 188 | AWS_S3_FILE_OVERWRITE = False 189 | AWS_DEFAULT_ACL = None 190 | AWS_S3_REGION_NAME = "us-east-2" 191 | AWS_S3_SIGNATURE_VERSION = "s3v4" 192 | 193 | DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' 194 | -------------------------------------------------------------------------------- /mystore/mystore/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | 4 | 5 | urlpatterns = [ 6 | path('', include('store.urls'), name='home'), 7 | path('users/', include('users.urls'), name='users'), 8 | path('orders/', include('orders.urls'), name='orders'), 9 | path('admin/', admin.site.urls), 10 | path('api/password_reset/', 11 | include('django_rest_passwordreset.urls', namespace='password_reset')) 12 | ] 13 | -------------------------------------------------------------------------------- /mystore/mystore/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mystore 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.2/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', 'mystore.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /mystore/orders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blixor/vue-Django-store/4f7d84209af81e53bc8d3cb5fd052a29f8dfe079/mystore/orders/__init__.py -------------------------------------------------------------------------------- /mystore/orders/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Order 3 | 4 | 5 | class OrderAdmin(admin.ModelAdmin): 6 | list_display = ('id', 'full_name', 'phone_number', 7 | 'full_address', 'status', 'created_at') 8 | 9 | 10 | admin.site.register(Order, OrderAdmin) 11 | -------------------------------------------------------------------------------- /mystore/orders/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class OrdersConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'orders' 7 | -------------------------------------------------------------------------------- /mystore/orders/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.8 on 2021-11-27 20:28 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | import django.utils.timezone 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Order', 18 | fields=[ 19 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('cart', models.JSONField(null=True)), 21 | ('first_name', models.CharField(max_length=150)), 22 | ('last_name', models.CharField(max_length=150)), 23 | ('email', models.EmailField(max_length=254)), 24 | ('phone', models.CharField(max_length=50, null=True, validators=[django.core.validators.RegexValidator('\\d{3}?-?\\d{3}?-?\\d{4}', 'Only ten numbers and dashes allowed.')])), 25 | ('address', models.CharField(max_length=150)), 26 | ('city', models.CharField(max_length=50)), 27 | ('state', models.CharField(max_length=5)), 28 | ('zipcode', models.CharField(max_length=5, null=True)), 29 | ('subtotal', models.CharField(max_length=10, null=True)), 30 | ('tax', models.CharField(max_length=10, null=True)), 31 | ('total', models.CharField(max_length=10, null=True)), 32 | ('status', models.CharField(choices=[('PN', 'Pending'), ('RT', 'Return'), ('CM', 'Completed')], default='PN', max_length=2)), 33 | ('created_at', models.DateTimeField(default=django.utils.timezone.now)), 34 | ('updated_at', models.DateTimeField(auto_now=True)), 35 | ], 36 | options={ 37 | 'ordering': ['status', 'created_at'], 38 | }, 39 | ), 40 | ] 41 | -------------------------------------------------------------------------------- /mystore/orders/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blixor/vue-Django-store/4f7d84209af81e53bc8d3cb5fd052a29f8dfe079/mystore/orders/migrations/__init__.py -------------------------------------------------------------------------------- /mystore/orders/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | from django.core.validators import RegexValidator 4 | 5 | 6 | phone_validator = RegexValidator( 7 | r'\d{3}?-?\d{3}?-?\d{4}', 'Only ten numbers and dashes allowed.') 8 | 9 | 10 | class Order(models.Model): 11 | PENDING = 'PN' 12 | RETURN = 'RT' 13 | COMPLETED = 'CM' 14 | STATUS = [ 15 | (PENDING, 'Pending'), 16 | (RETURN, 'Return'), 17 | (COMPLETED, 'Completed'), 18 | ] 19 | cart = models.JSONField(encoder=None, blank=False, null=True) 20 | first_name = models.CharField(max_length=150, blank=False) 21 | last_name = models.CharField(max_length=150, blank=False) 22 | email = models.EmailField(blank=False) 23 | phone = models.CharField(max_length=50, blank=False, 24 | null=True, validators=[phone_validator]) 25 | address = models.CharField(max_length=150, blank=False, null=False) 26 | city = models.CharField(max_length=50, blank=False, null=False) 27 | state = models.CharField(max_length=5, blank=False, null=False) 28 | zipcode = models.CharField(max_length=5, blank=False, null=True) 29 | subtotal = models.CharField(max_length=10, blank=False, null=True) 30 | tax = models.CharField(max_length=10, blank=False, null=True) 31 | total = models.CharField(max_length=10, blank=False, null=True) 32 | status = models.CharField(max_length=2, choices=STATUS, default=PENDING,) 33 | created_at = models.DateTimeField(default=timezone.now) 34 | updated_at = models.DateTimeField(auto_now=True) 35 | 36 | 37 | class Meta: 38 | ordering = ['status', 'created_at'] 39 | 40 | 41 | def __str__(self): 42 | return '%s #%i' % (self.first_name, self.id) 43 | 44 | 45 | def full_name(self): 46 | return '%s %s' % (self.first_name, self.last_name) 47 | 48 | 49 | def full_address(self): 50 | return '%s %s %s %s' % (self.address, self.city, self.state, self.zipcode) 51 | 52 | 53 | def phone_number(self): 54 | return '%s-%s-%s' % (self.phone[:3], self.phone[3:6], self.phone[6:10]) 55 | -------------------------------------------------------------------------------- /mystore/orders/serializers.py: -------------------------------------------------------------------------------- 1 | from .models import Order 2 | from rest_framework import serializers 3 | 4 | 5 | class OrderSerializers(serializers.ModelSerializer): 6 | 7 | class Meta: 8 | model = Order 9 | fields = ['cart', 'first_name', 'last_name', 'email', 'phone', 10 | 'address', 'city', 'state', 'zipcode','subtotal', 'tax', 'total'] 11 | -------------------------------------------------------------------------------- /mystore/orders/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /mystore/orders/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from rest_framework import routers 3 | from .views import OrderViewSet 4 | 5 | router = routers.DefaultRouter() 6 | router.register(r'order', OrderViewSet) 7 | 8 | urlpatterns = [ 9 | path('', include(router.urls)), 10 | path('api-post/', include('rest_framework.urls', namespace='order')), 11 | ] 12 | -------------------------------------------------------------------------------- /mystore/orders/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import viewsets, status 2 | from rest_framework.response import Response 3 | 4 | from .serializers import OrderSerializers 5 | from .models import Order 6 | 7 | from store.models import Product, Rating 8 | 9 | 10 | class OrderViewSet(viewsets.ModelViewSet): 11 | queryset = Order.objects.all().order_by('-created_at') 12 | serializer_class = OrderSerializers 13 | 14 | def create(self, request): 15 | if request.method == 'POST': 16 | order = request.data.get('order') 17 | cart = request.data.get('order')['cart'] 18 | rating = request.data.get('order')['rating'] 19 | 20 | for i in cart: 21 | # Product inventory subtraction 22 | i.pop('image') 23 | item_id = i.get('itemId') 24 | qty = i.get('qty') 25 | p = Product.objects.get(pk=item_id) 26 | 27 | if p.inventory == 0: 28 | return Response(status=status.HTTP_409_CONFLICT) 29 | 30 | p.inventory = p.inventory - qty 31 | p.save() 32 | 33 | if rating: 34 | for i in rating: 35 | item_id = i.get('itemId') 36 | value = i.get('value') 37 | r = Rating.objects.get(product=item_id) 38 | if value == 1: 39 | r.one += 1 40 | elif value == 2: 41 | r.two += 1 42 | elif value == 3: 43 | r.three += 1 44 | elif value == 4: 45 | r.four += 1 46 | elif value == 5: 47 | r.five += 1 48 | r.save() 49 | 50 | serializer = self.serializer_class(data=order, 51 | context={'request': request}) 52 | if serializer.is_valid(): 53 | serializer.save() 54 | return Response(status=status.HTTP_200_OK) 55 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 56 | return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED) 57 | -------------------------------------------------------------------------------- /mystore/screenshots/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blixor/vue-Django-store/4f7d84209af81e53bc8d3cb5fd052a29f8dfe079/mystore/screenshots/admin.png -------------------------------------------------------------------------------- /mystore/screenshots/screenshot(1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blixor/vue-Django-store/4f7d84209af81e53bc8d3cb5fd052a29f8dfe079/mystore/screenshots/screenshot(1).png -------------------------------------------------------------------------------- /mystore/screenshots/screenshot(2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blixor/vue-Django-store/4f7d84209af81e53bc8d3cb5fd052a29f8dfe079/mystore/screenshots/screenshot(2).png -------------------------------------------------------------------------------- /mystore/screenshots/screenshot(3).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blixor/vue-Django-store/4f7d84209af81e53bc8d3cb5fd052a29f8dfe079/mystore/screenshots/screenshot(3).png -------------------------------------------------------------------------------- /mystore/store/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blixor/vue-Django-store/4f7d84209af81e53bc8d3cb5fd052a29f8dfe079/mystore/store/__init__.py -------------------------------------------------------------------------------- /mystore/store/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Product, Rating 3 | 4 | 5 | class ProductAdmin(admin.ModelAdmin): 6 | list_display = ('id', 'name', 'price', 'inventory', 'updated_at') 7 | prepopulated_fields = {"slug": ("name",)} 8 | 9 | admin.site.register(Product, ProductAdmin) 10 | 11 | 12 | class RatingAdmin(admin.ModelAdmin): 13 | list_display = ('product', 'one', 'two', 'three', 'four', 'five') 14 | 15 | admin.site.register(Rating, RatingAdmin) 16 | -------------------------------------------------------------------------------- /mystore/store/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class StoreConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'store' 7 | 8 | def ready(self): 9 | import store.signals 10 | -------------------------------------------------------------------------------- /mystore/store/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.8 on 2021-11-27 20:28 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import django.utils.timezone 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Product', 18 | fields=[ 19 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('name', models.CharField(max_length=50)), 21 | ('slug', models.SlugField(max_length=20, unique=True)), 22 | ('image', models.ImageField(upload_to='profile_pics')), 23 | ('price', models.DecimalField(decimal_places=2, max_digits=5)), 24 | ('qty', models.PositiveSmallIntegerField(default=1, help_text='Customer quantity initialization')), 25 | ('inventory', models.PositiveSmallIntegerField()), 26 | ('description', models.TextField(max_length=200)), 27 | ('created_at', models.DateTimeField(default=django.utils.timezone.now)), 28 | ('updated_at', models.DateTimeField(auto_now=True)), 29 | ], 30 | options={ 31 | 'ordering': ['id'], 32 | }, 33 | ), 34 | migrations.CreateModel( 35 | name='Rating', 36 | fields=[ 37 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 38 | ('one', models.PositiveIntegerField(blank=True, default=0, null=True)), 39 | ('two', models.PositiveIntegerField(blank=True, default=0, null=True)), 40 | ('three', models.PositiveIntegerField(blank=True, default=0, null=True)), 41 | ('four', models.PositiveIntegerField(blank=True, default=0, null=True)), 42 | ('five', models.PositiveIntegerField(blank=True, default=0, null=True)), 43 | ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rating', to='store.product')), 44 | ], 45 | options={ 46 | 'ordering': ['product'], 47 | }, 48 | ), 49 | ] 50 | -------------------------------------------------------------------------------- /mystore/store/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blixor/vue-Django-store/4f7d84209af81e53bc8d3cb5fd052a29f8dfe079/mystore/store/migrations/__init__.py -------------------------------------------------------------------------------- /mystore/store/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | from django.template.defaultfilters import slugify 4 | 5 | from .utils import image_resize 6 | 7 | 8 | class Product(models.Model): 9 | name = models.CharField(max_length=50, null=False) 10 | slug = models.SlugField(max_length=20, null=False, unique=True) 11 | image = models.ImageField(upload_to='profile_pics') 12 | price = models.DecimalField(max_digits=5, decimal_places=2, null=False) 13 | qty = models.PositiveSmallIntegerField(null=False, default=1, help_text='Customer quantity initialization') 14 | inventory = models.PositiveSmallIntegerField(null=False) 15 | description = models.TextField(max_length=200, null=False) 16 | created_at = models.DateTimeField(default=timezone.now) 17 | updated_at = models.DateTimeField(auto_now=True) 18 | 19 | class Meta: 20 | ordering = ['id'] 21 | 22 | def __str__(self): 23 | return self.name 24 | 25 | def save(self, *args, **kwargs): 26 | image_resize(self.image, 500, 500) 27 | if not self.slug: 28 | self.slug = slugify(self.name) 29 | super().save(*args, **kwargs) 30 | 31 | 32 | class Rating(models.Model): 33 | product = models.ForeignKey(Product, related_name='rating', on_delete=models.CASCADE) 34 | one = models.PositiveIntegerField(default=0, null=True, blank=True) 35 | two = models.PositiveIntegerField(default=0, null=True, blank=True) 36 | three = models.PositiveIntegerField(default=0, null=True, blank=True) 37 | four = models.PositiveIntegerField(default=0, null=True, blank=True) 38 | five = models.PositiveIntegerField(default=0, null=True, blank=True) 39 | 40 | class Meta: 41 | ordering = ['product'] 42 | 43 | def __str__(self): 44 | # Extract all rating values and return max key. 45 | # Reverse this list if there is a tie and you want the last key. 46 | rating_list = { 47 | '1': self.one, 48 | '2': self.two, 49 | '3': self.three, 50 | '4': self.four, 51 | '5': self.five 52 | } 53 | return str(max(rating_list, key=rating_list.get)) 54 | -------------------------------------------------------------------------------- /mystore/store/serializers.py: -------------------------------------------------------------------------------- 1 | from .models import Product 2 | from rest_framework import serializers 3 | 4 | 5 | class ProductSerializer(serializers.ModelSerializer): 6 | rating = serializers.StringRelatedField(many=True) 7 | 8 | class Meta: 9 | model= Product 10 | fields = ['id', 'name', 'slug', 'image', 'price', 11 | 'qty', 'inventory', 'description', 'rating'] 12 | -------------------------------------------------------------------------------- /mystore/store/signals.py: -------------------------------------------------------------------------------- 1 | from django.db.models.signals import post_save 2 | from django.dispatch.dispatcher import receiver 3 | 4 | from .models import Product, Rating 5 | 6 | 7 | @receiver(post_save, sender=Product) 8 | def create_rating(sender, instance, created, **kwargs): 9 | if created: 10 | Rating.objects.create(product=instance) 11 | -------------------------------------------------------------------------------- /mystore/store/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from .models import Rating 3 | 4 | 5 | class QuestionModelTests(TestCase): 6 | def max_star(self): 7 | return '%s %s %s' %(self.one, self.two, self.three) 8 | -------------------------------------------------------------------------------- /mystore/store/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from rest_framework import routers 3 | from .views import ProductViewSet 4 | 5 | 6 | router = routers.DefaultRouter() 7 | 8 | router.register(r'products', ProductViewSet) 9 | 10 | urlpatterns = [ 11 | path('', include(router.urls)), 12 | path('api/', include('rest_framework.urls', namespace='rest_framework')) 13 | ] 14 | -------------------------------------------------------------------------------- /mystore/store/utils.py: -------------------------------------------------------------------------------- 1 | from django.core.files import File 2 | from pathlib import Path 3 | from io import BytesIO 4 | from PIL import Image 5 | 6 | 7 | image_types = { 8 | "jpg": "JPEG", 9 | "jpeg": "JPEG", 10 | "png": "PNG", 11 | "gif": "GIF", 12 | "tif": "TIFF", 13 | "tiff": "TIFF", 14 | } 15 | 16 | 17 | def image_resize(image, width, height): 18 | # Open the image using Pillow 19 | img = Image.open(image) 20 | # check if either the width or height is greater than the max 21 | if img.width > width or img.height > height: 22 | output_size = (width, height) 23 | # Create a new resized “thumbnail” version of the image with Pillow 24 | img.thumbnail(output_size) 25 | # Find the file name of the image 26 | img_filename = Path(image.file.name).name 27 | # Spilt the filename on “.” to get the file extension only 28 | img_suffix = Path(image.file.name).name.split(".")[-1] 29 | # Use the file extension to determine the file type from the image_types dictionary 30 | img_format = image_types[img_suffix] 31 | # Save the resized image into the buffer, noting the correct file type 32 | buffer = BytesIO() 33 | img.save(buffer, format=img_format) 34 | # Wrap the buffer in File object 35 | file_object = File(buffer) 36 | # Save the new resized file as usual, which will save to S3 using django-storages 37 | image.save(img_filename, file_object) 38 | -------------------------------------------------------------------------------- /mystore/store/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import viewsets 2 | 3 | from .serializers import ProductSerializer 4 | from .models import Product 5 | 6 | 7 | # Get all products in random order 8 | class ProductViewSet(viewsets.ModelViewSet): 9 | queryset = Product.objects.all().order_by('?') 10 | serializer_class = ProductSerializer 11 | -------------------------------------------------------------------------------- /mystore/templates/admin/base_site.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base.html" %} 2 | 3 | {% block extrahead %}{{ block.super }} 4 | 21 | {% endblock%} 22 | 23 | {% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | My Store{% endblock %} 24 | 25 | {% block branding %} 26 |

My Store

27 | {% endblock %} 28 | 29 | {% block nav-global %}{% endblock %} -------------------------------------------------------------------------------- /mystore/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blixor/vue-Django-store/4f7d84209af81e53bc8d3cb5fd052a29f8dfe079/mystore/users/__init__.py -------------------------------------------------------------------------------- /mystore/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Profile 3 | 4 | 5 | class ProfileAdmin(admin.ModelAdmin): 6 | list_display = ('user', 'id', 'phone') 7 | 8 | admin.site.register(Profile, ProfileAdmin) 9 | -------------------------------------------------------------------------------- /mystore/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'users' 7 | 8 | def ready(self): 9 | import users.signals 10 | -------------------------------------------------------------------------------- /mystore/users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.6 on 2021-08-19 17:22 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 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Profile', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('address', models.CharField(max_length=150, verbose_name='address')), 22 | ('city', models.CharField(max_length=50, verbose_name='city')), 23 | ('state', models.CharField(max_length=5, verbose_name='state')), 24 | ('zipcode', models.CharField(max_length=5, null=True, verbose_name='zipcode')), 25 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 26 | ], 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /mystore/users/migrations/0002_profile_phone.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.6 on 2021-08-19 18:04 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('users', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='profile', 16 | name='phone', 17 | field=models.CharField(max_length=50, null=True, validators=[django.core.validators.RegexValidator('^[0-9 ]*$', 'Only ten numbers allowed.')]), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /mystore/users/migrations/0003_alter_profile_phone.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.8 on 2021-10-06 00:59 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('users', '0002_profile_phone'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='profile', 16 | name='phone', 17 | field=models.CharField(max_length=50, null=True, validators=[django.core.validators.RegexValidator('\\d{3}?-?\\d{3}?-?\\d{4}', 'Only ten numbers and dashes allowed.')]), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /mystore/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blixor/vue-Django-store/4f7d84209af81e53bc8d3cb5fd052a29f8dfe079/mystore/users/migrations/__init__.py -------------------------------------------------------------------------------- /mystore/users/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | from django.core.validators import RegexValidator 4 | from django.utils.translation import gettext_lazy as _ 5 | 6 | 7 | phone_validator = RegexValidator( 8 | r'\d{3}?-?\d{3}?-?\d{4}', 'Only ten numbers and dashes allowed.') 9 | 10 | 11 | class Profile(models.Model): 12 | user = models.OneToOneField(User, on_delete=models.CASCADE) 13 | phone = models.CharField(max_length=50, blank=False, null=True, validators=[phone_validator]) 14 | address = models.CharField( 15 | _("address"), max_length=150, blank=False, null=False) 16 | city = models.CharField(_("city"), max_length=50, blank=False, null=False) 17 | state = models.CharField(_("state"), max_length=5, blank=False, null=False) 18 | zipcode = models.CharField( 19 | _("zipcode"), max_length=5, blank=False, null=True) 20 | 21 | 22 | def __str__(self): 23 | return f'{self.user.username} Profile' 24 | -------------------------------------------------------------------------------- /mystore/users/serializers.py: -------------------------------------------------------------------------------- 1 | from django.core import exceptions 2 | from django.contrib.auth.models import User 3 | from django.contrib.auth import password_validation 4 | from django.contrib.auth.password_validation import validate_password 5 | 6 | 7 | from rest_framework import serializers 8 | 9 | from .models import Profile 10 | 11 | 12 | class UserSerializer(serializers.ModelSerializer): 13 | class Meta: 14 | model = User 15 | extra_kwargs = {'email': {'required': True, 'allow_blank': False}, 16 | 'first_name': {'required': True, 'allow_blank': False}, 17 | 'last_name': {'required': True, 'allow_blank': False}} 18 | fields = ['username', 'password', 'first_name', 'last_name', 'email'] 19 | 20 | 21 | def validate(self, data): 22 | password = data.get('password') 23 | errors = dict() 24 | 25 | try: 26 | # validate the password and catch the exception 27 | password_validation.validate_password(password=password, user=User) 28 | 29 | # the exception raised here is different than serializers.ValidationError 30 | except exceptions.ValidationError as e: 31 | errors['password'] = list(e.messages) 32 | 33 | if errors: 34 | raise serializers.ValidationError(errors) 35 | 36 | return super(UserSerializer, self).validate(data) 37 | 38 | 39 | def create(self, validated_data): 40 | user = super().create(validated_data) 41 | user.set_password(validated_data['password']) 42 | user.save() 43 | return user 44 | 45 | 46 | class ProfileSerializer(serializers.ModelSerializer): 47 | 48 | class Meta: 49 | model = Profile 50 | fields = ['phone', 'address', 'city', 'state', 'zipcode'] 51 | 52 | 53 | class ChangePasswordSerializer(serializers.ModelSerializer): 54 | old_password = serializers.CharField(write_only=True, required=True) 55 | password = serializers.CharField( 56 | write_only=True, required=True, validators=[validate_password]) 57 | password2 = serializers.CharField(write_only=True, required=True) 58 | 59 | class Meta: 60 | model = User 61 | fields = ('old_password', 'password', 'password2') 62 | -------------------------------------------------------------------------------- /mystore/users/signals.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.urls import reverse 3 | from django.dispatch import receiver 4 | from django.db.models.signals import post_save 5 | from django.template.loader import render_to_string 6 | from django.core.mail import EmailMultiAlternatives 7 | 8 | from django_rest_passwordreset.signals import reset_password_token_created 9 | 10 | from rest_framework.authtoken.models import Token 11 | 12 | from django.contrib.auth.models import User 13 | 14 | from .models import Profile 15 | 16 | @receiver(post_save, sender=User) 17 | def create_user_profile(sender, instance, created, **kwargs): 18 | if created: 19 | Profile.objects.create(user=instance) 20 | 21 | 22 | @receiver(post_save, sender=User) 23 | def save_user_profile(sender, instance, **kwargs): 24 | instance.profile.save() 25 | 26 | 27 | @receiver(post_save, sender=settings.AUTH_USER_MODEL) 28 | def create_auth_token(sender, instance=None, created=False, **kwargs): 29 | if created: 30 | Token.objects.create(user=instance) 31 | 32 | 33 | @receiver(reset_password_token_created) 34 | def create(sender, instance, reset_password_token, *args, **kwargs): 35 | # send an e-mail to the user 36 | context = { 37 | 'current_user': reset_password_token.user, 38 | 'username': reset_password_token.user.username, 39 | 'email': reset_password_token.user.email, 40 | 'reset_password_url': "http://localhost:8080/#/api/password_reset/confirm/?token={}".format( 41 | reset_password_token.key) 42 | } 43 | # render email text 44 | email_html_message = render_to_string( 45 | 'users/user_reset_password.html', context) 46 | email_plaintext_message = render_to_string( 47 | 'users/user_reset_password.txt', context) 48 | 49 | msg = EmailMultiAlternatives( 50 | # title: 51 | 'Password Reset for {user}'.format( 52 | user=reset_password_token.user.username), 53 | # message: 54 | email_plaintext_message, 55 | # from: 56 | "noreply@somehost.local", 57 | # to: 58 | [reset_password_token.user.email] 59 | ) 60 | msg.attach_alternative(email_html_message, "text/html") 61 | msg.send() 62 | -------------------------------------------------------------------------------- /mystore/users/templates/users/email_base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Alerts e.g. approaching your limit 8 | 9 | 10 | 90 | 91 | 92 | 95 | {% block content %}{% endblock content %} 96 | 97 | 98 | -------------------------------------------------------------------------------- /mystore/users/templates/users/user_reset_password.html: -------------------------------------------------------------------------------- 1 | {% extends 'users/email_base.html' %} 2 | {% autoescape off %} 3 | 4 | {% block content %} 5 | 6 | 7 | 8 | 66 | 67 | 68 |
10 |
11 | 13 | 14 | 19 | 20 | 21 | 53 | 54 |
17 |

Hi, from Acme Inc!

18 |
22 | 23 | 24 | 32 | 33 | 34 | 40 | 41 | 42 | 45 | 46 | 47 | 50 | 51 |
25 |

Hi, {{ username }}!

26 |

A password reset was requested for your account ({{ email }}) on django_store. 27 | If you did not authorize this, you may simply ignore this email. 28 | 29 | To continue with your password reset, simply click the link below, and you will be able to change your password. 30 |

31 |
35 | 37 | Reset Your Password Here! 38 |

or

39 |
43 |

{{ reset_password_url }}

44 |
48 | Thanks for choosing Acme Inc. 49 |
52 |
55 | 64 |
65 |
69 | {% endblock content %} 70 | 71 | {% endautoescape %} -------------------------------------------------------------------------------- /mystore/users/templates/users/user_reset_password.txt: -------------------------------------------------------------------------------- 1 | Hi, {{ username }}! 2 | 3 | A password reset was requested for your account ({{ email }}) on django_store. 4 | If you did not authorize this, you may simply ignore this email. 5 | 6 | To continue with your password reset, simply click the link below, and you will be able to change your password. 7 | 8 | {{ reset_password_url }} -------------------------------------------------------------------------------- /mystore/users/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /mystore/users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.contrib.auth import views as auth_views 3 | from django.views.decorators.csrf import csrf_exempt 4 | 5 | from .views import RegisterUser, CustomAuthToken, UpdateProfile, UpdateUser 6 | 7 | 8 | urlpatterns = [ 9 | path('register/', RegisterUser.as_view(), name='register'), 10 | path('login/', CustomAuthToken.as_view(), name='login'), 11 | path('logout/', csrf_exempt(auth_views.LogoutView.as_view(next_page='/', 12 | template_name=None)), name='logout'), 13 | path('update//', UpdateProfile.as_view(), name='update'), 14 | path('delete//', UpdateUser.as_view(), name="delete"), 15 | ] 16 | -------------------------------------------------------------------------------- /mystore/users/views.py: -------------------------------------------------------------------------------- 1 | from django.http.response import Http404 2 | from django.contrib.auth.models import User 3 | 4 | from rest_framework import status, authentication 5 | from rest_framework.views import APIView 6 | from rest_framework.response import Response 7 | from rest_framework.authtoken.models import Token 8 | from rest_framework.permissions import IsAuthenticated 9 | from rest_framework.authtoken.views import ObtainAuthToken 10 | 11 | from .models import Profile 12 | 13 | from .serializers import UserSerializer, ProfileSerializer, ChangePasswordSerializer 14 | 15 | 16 | class CustomAuthToken(ObtainAuthToken): 17 | 18 | def post(self, request, *args, **kwargs): 19 | serializer = self.serializer_class(data=request.data, 20 | context={'request': request}) 21 | serializer.is_valid(raise_exception=True) 22 | user = serializer.validated_data['user'] 23 | token, created = Token.objects.get_or_create(user=user) 24 | data = ({'token': token.key, 'user_id': user.pk, 'email': user.email, 'username': user.username, 25 | 'address': user.profile.address, 'city': user.profile.city, 'state': user.profile.state, 'zipcode': user.profile.zipcode, 26 | 'first_name': user.first_name, 'last_name': user.last_name, 'phone': user.profile.phone, 'staff': user.is_staff}) 27 | return Response(data=data, status=status.HTTP_200_OK) 28 | 29 | 30 | class RegisterUser(APIView): 31 | ''' 32 | Register new user 33 | ''' 34 | serializer_class = UserSerializer 35 | 36 | def post(self, request, format=None): 37 | serializer = self.serializer_class(data=request.data, 38 | context={'request': request}) 39 | if serializer.is_valid(): 40 | serializer.save() 41 | return Response(status=status.HTTP_201_CREATED) 42 | return Response(serializer.errors, status=status.HTTP_202_ACCEPTED) 43 | 44 | 45 | class UpdateProfile(APIView): 46 | ''' 47 | Get and Update user Profile. 48 | ''' 49 | serializer_class = ProfileSerializer 50 | authentication_classes = [authentication.TokenAuthentication] 51 | 52 | def get_object(self, pk): 53 | try: 54 | return Profile.objects.get(user=pk) 55 | except Profile.DoesNotExist: 56 | raise Http404 57 | 58 | def patch(self, request, pk, format=None): 59 | if request.user.is_authenticated: 60 | profile = self.get_object(pk) 61 | serializer = self.serializer_class(profile, data=request.data, 62 | context={'request': request}) 63 | if serializer.is_valid(): 64 | serializer.save() 65 | return Response(serializer.data, status=status.HTTP_200_OK) 66 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 67 | return Response(status=status.HTTP_401_UNAUTHORIZED) 68 | 69 | 70 | class UpdateUser(APIView): 71 | ''' 72 | Deactivate user and change password 73 | ''' 74 | permission_classes = [IsAuthenticated] 75 | authentication_classes = [authentication.TokenAuthentication] 76 | 77 | def get_object(self, pk): 78 | try: 79 | return User.objects.get(id=pk) 80 | except User.DoesNotExist: 81 | raise Http404 82 | 83 | def put(self, request, pk, format=None): 84 | user = self.get_object(pk) 85 | old_password = request.data.get("old_password") 86 | password = request.data.get("password") 87 | password2 = request.data.get('password2') 88 | serializer = ChangePasswordSerializer(user, data=request.data) 89 | 90 | if serializer.is_valid(): 91 | # validate passwords 92 | if password != password2: 93 | return Response({"password": "Password fields didn't match."}, 94 | status=status.HTTP_400_BAD_REQUEST) 95 | 96 | # check old_password 97 | if not user.check_password(old_password): 98 | return Response({"old_password": "Wrong password."}, 99 | status=status.HTTP_400_BAD_REQUEST) 100 | 101 | # set_password also hashes the password that the user will get 102 | user.set_password(password) 103 | user.save() 104 | return Response(status=status.HTTP_200_OK) 105 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 106 | 107 | # Deactivate user 108 | def post(self, request, pk, format=None): 109 | user = self.get_object(pk) 110 | 111 | # check old_password 112 | password = request.data.get("password") 113 | 114 | if not user.check_password(password): 115 | return Response({"password": "Wrong password."}, 116 | status=status.HTTP_400_BAD_REQUEST) 117 | 118 | request.user.is_active = False 119 | request.user.save() 120 | return Response(status=status.HTTP_204_NO_CONTENT) 121 | -------------------------------------------------------------------------------- /vetur.config.js: -------------------------------------------------------------------------------- 1 | // vetur.config.js 2 | /** @type {import('vls').VeturConfig} */ 3 | module.exports = { 4 | // **optional** default: `{}` 5 | // override vscode settings 6 | // Notice: It only affects the settings used by Vetur. 7 | settings: { 8 | 'vetur.useWorkspaceDependencies': true, 9 | 'vetur.experimental.templateInterpolationService': true, 10 | }, 11 | // **optional** default: `[{ root: './' }]` 12 | // support monorepos 13 | projects: [ 14 | './packages/repo2', // shorthand for only root. 15 | { 16 | // **required** 17 | // Where is your project? 18 | // It is relative to `vetur.config.js`. 19 | root: './packages/repo1', 20 | // **optional** default: `'package.json'` 21 | // Where is `package.json` in the project? 22 | // We use it to determine the version of vue. 23 | // It is relative to root property. 24 | package: './package.json', 25 | // **optional** 26 | // Where is TypeScript config file in the project? 27 | // It is relative to root property. 28 | tsconfig: './tsconfig.json', 29 | // **optional** default: `'./.vscode/vetur/snippets'` 30 | // Where is vetur custom snippets folders? 31 | snippetFolder: './.vscode/vetur/snippets', 32 | // **optional** default: `[]` 33 | // Register globally Vue component glob. 34 | // If you set it, you can get completion by that components. 35 | // It is relative to root property. 36 | // Notice: It won't actually do it. You need to use `require.context` or `Vue.component` 37 | globalComponents: [ 38 | './src/components/**/*.vue', 39 | ], 40 | }, 41 | ], 42 | }; 43 | --------------------------------------------------------------------------------