diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..88d3879 --- /dev/null +++ b/.gitignore @@ -0,0 +1,153 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/django +# Edit at https://www.toptal.com/developers/gitignore?templates=django + +### Django ### +*.log +*.pot +*.pyc +__pycache__/ +local_settings.py +db.sqlite3 +db.sqlite3-journal +media + +# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ +# in your Git repository. Update and uncomment the following line accordingly. +# /staticfiles/ + +### Django.Python Stack ### +# Byte-compiled / optimized / DLL files +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo + +# Django stuff: + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# End of https://www.toptal.com/developers/gitignore/api/django + +.vscode +migrations +docs \ No newline at end of file diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..44dd9b2 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,2 @@ +[FORMAT] +good-names=i,j,ex,pk diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..1490e9a --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,52 @@ +Description of PR that completes issue here... + +## Changes + +- Item 1 +- Item 2 +- Item 3 + +## Requests / Responses + +If this PR contains code that defines a new request/response, or changes an existing one, please put the JSON representations here. + +**Request** + +POST `/products` Creates a new product + +```json +{ + "title": "Kite", + "product_type_id": 1, + "description": "Red. It flies high.", + "quantity": 5 +} +``` + +**Response** + +HTTP/1.1 201 OK + +```json +{ + "id": 54, + "title": "Kite", + "product_type_id": 1, + "description": "Red. It flies high.", + "quantity": 5 +} +``` + +## Testing + +Description of how to test code... + +- [ ] Run migrations +- [ ] Run test suite +- [ ] Seed database + + +## Related Issues + +- Fixes #85 +- Fixes #22 \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..927d29a --- /dev/null +++ b/Pipfile @@ -0,0 +1,31 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +astroid = "==2.3.1" +autopep8 = "==1.4.4" +colorama = "==0.4.1" +django-cors-headers = "==3.1.1" +django-safedelete = "==0.5.2" +djangorestframework = "==3.10.3" +gunicorn = "==20.0.0" +isort = "==4.3.21" +lazy-object-proxy = "==1.4.2" +mccabe = "==0.6.1" +pep8 = "==1.7.1" +pycodestyle = "==2.5.0" +pylint = "==2.4.3" +pytz = "==2019.2" +six = "==1.12.0" +sqlparse = "==0.3.0" +wrapt = "==1.11.2" +Django = "==2.2.6" +wheel = "*" +pillow = "*" + +[requires] +python_version = "3.8.1" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..568f246 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,220 @@ +{ + "_meta": { + "hash": { + "sha256": "b1517b935ffaaf1f90789f8c7b8229d35918757d79e5d5e432df087dd28d2934" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8.1" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "astroid": { + "hashes": [ + "sha256:98c665ad84d10b18318c5ab7c3d203fe11714cbad2a4aef4f44651f415392754", + "sha256:b7546ffdedbf7abcfbff93cd1de9e9980b1ef744852689decc5aeada324238c6" + ], + "index": "pypi", + "version": "==2.3.1" + }, + "autopep8": { + "hashes": [ + "sha256:4d8eec30cc81bc5617dbf1218201d770dc35629363547f17577c61683ccfb3ee" + ], + "index": "pypi", + "version": "==1.4.4" + }, + "colorama": { + "hashes": [ + "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", + "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48" + ], + "index": "pypi", + "version": "==0.4.1" + }, + "django": { + "hashes": [ + "sha256:4025317ca01f75fc79250ff7262a06d8ba97cd4f82e93394b2a0a6a4a925caeb", + "sha256:a8ca1033acac9f33995eb2209a6bf18a4681c3e5269a878e9a7e0b7384ed1ca3" + ], + "index": "pypi", + "version": "==2.2.6" + }, + "django-cors-headers": { + "hashes": [ + "sha256:5762ec9c2d59f38c76828dc1d4308baca4bc0d3e1d6f217683e7a24a1c4611a3", + "sha256:ee02f4b699e9b6645602a46d0adb430ee940a1bf8df64f77e516f8d7711fee60" + ], + "index": "pypi", + "version": "==3.1.1" + }, + "django-safedelete": { + "hashes": [ + "sha256:c6fd891a15778f03675fe2a29829fcd649fa7b4cd9e11a12c8f27ce94523618b" + ], + "index": "pypi", + "version": "==0.5.2" + }, + "djangorestframework": { + "hashes": [ + "sha256:5488aed8f8df5ec1d70f04b2114abc52ae6729748a176c453313834a9ee179c8", + "sha256:dc81cbf9775c6898a580f6f1f387c4777d12bd87abf0f5406018d32ccae71090" + ], + "index": "pypi", + "version": "==3.10.3" + }, + "gunicorn": { + "hashes": [ + "sha256:0806b5e8a2eb8ba9ac1be65d7b743ec896fc25f5d6cb16c5e051540157b315bb", + "sha256:ef69dea4814df95e64e3f40b47b7ffedc6911c5009233be9d01cfd0d14aa3f50" + ], + "index": "pypi", + "version": "==20.0.0" + }, + "isort": { + "hashes": [ + "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", + "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd" + ], + "index": "pypi", + "version": "==4.3.21" + }, + "lazy-object-proxy": { + "hashes": [ + "sha256:02b260c8deb80db09325b99edf62ae344ce9bc64d68b7a634410b8e9a568edbf", + "sha256:18f9c401083a4ba6e162355873f906315332ea7035803d0fd8166051e3d402e3", + "sha256:1f2c6209a8917c525c1e2b55a716135ca4658a3042b5122d4e3413a4030c26ce", + "sha256:2f06d97f0ca0f414f6b707c974aaf8829c2292c1c497642f63824119d770226f", + "sha256:616c94f8176808f4018b39f9638080ed86f96b55370b5a9463b2ee5c926f6c5f", + "sha256:63b91e30ef47ef68a30f0c3c278fbfe9822319c15f34b7538a829515b84ca2a0", + "sha256:77b454f03860b844f758c5d5c6e5f18d27de899a3db367f4af06bec2e6013a8e", + "sha256:83fe27ba321e4cfac466178606147d3c0aa18e8087507caec78ed5a966a64905", + "sha256:84742532d39f72df959d237912344d8a1764c2d03fe58beba96a87bfa11a76d8", + "sha256:874ebf3caaf55a020aeb08acead813baf5a305927a71ce88c9377970fe7ad3c2", + "sha256:9f5caf2c7436d44f3cec97c2fa7791f8a675170badbfa86e1992ca1b84c37009", + "sha256:a0c8758d01fcdfe7ae8e4b4017b13552efa7f1197dd7358dc9da0576f9d0328a", + "sha256:a4def978d9d28cda2d960c279318d46b327632686d82b4917516c36d4c274512", + "sha256:ad4f4be843dace866af5fc142509e9b9817ca0c59342fdb176ab6ad552c927f5", + "sha256:ae33dd198f772f714420c5ab698ff05ff900150486c648d29951e9c70694338e", + "sha256:b4a2b782b8a8c5522ad35c93e04d60e2ba7f7dcb9271ec8e8c3e08239be6c7b4", + "sha256:c462eb33f6abca3b34cdedbe84d761f31a60b814e173b98ede3c81bb48967c4f", + "sha256:fd135b8d35dfdcdb984828c84d695937e58cc5f49e1c854eb311c4d6aa03f4f1" + ], + "index": "pypi", + "version": "==1.4.2" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "index": "pypi", + "version": "==0.6.1" + }, + "pep8": { + "hashes": [ + "sha256:b22cfae5db09833bb9bd7c8463b53e1a9c9b39f12e304a8d0bba729c501827ee", + "sha256:fe249b52e20498e59e0b5c5256aa52ee99fc295b26ec9eaa85776ffdb9fe6374" + ], + "index": "pypi", + "version": "==1.7.1" + }, + "pillow": { + "hashes": [ + "sha256:006de60d7580d81f4a1a7e9f0173dc90a932e3905cc4d47ea909bc946302311a", + "sha256:0a2e8d03787ec7ad71dc18aec9367c946ef8ef50e1e78c71f743bc3a770f9fae", + "sha256:0eeeae397e5a79dc088d8297a4c2c6f901f8fb30db47795113a4a605d0f1e5ce", + "sha256:11c5c6e9b02c9dac08af04f093eb5a2f84857df70a7d4a6a6ad461aca803fb9e", + "sha256:2fb113757a369a6cdb189f8df3226e995acfed0a8919a72416626af1a0a71140", + "sha256:4b0ef2470c4979e345e4e0cc1bbac65fda11d0d7b789dbac035e4c6ce3f98adb", + "sha256:59e903ca800c8cfd1ebe482349ec7c35687b95e98cefae213e271c8c7fffa021", + "sha256:5abd653a23c35d980b332bc0431d39663b1709d64142e3652890df4c9b6970f6", + "sha256:5f9403af9c790cc18411ea398a6950ee2def2a830ad0cfe6dc9122e6d528b302", + "sha256:6b4a8fd632b4ebee28282a9fef4c341835a1aa8671e2770b6f89adc8e8c2703c", + "sha256:6c1aca8231625115104a06e4389fcd9ec88f0c9befbabd80dc206c35561be271", + "sha256:795e91a60f291e75de2e20e6bdd67770f793c8605b553cb6e4387ce0cb302e09", + "sha256:7ba0ba61252ab23052e642abdb17fd08fdcfdbbf3b74c969a30c58ac1ade7cd3", + "sha256:7c9401e68730d6c4245b8e361d3d13e1035cbc94db86b49dc7da8bec235d0015", + "sha256:81f812d8f5e8a09b246515fac141e9d10113229bc33ea073fec11403b016bcf3", + "sha256:895d54c0ddc78a478c80f9c438579ac15f3e27bf442c2a9aa74d41d0e4d12544", + "sha256:8de332053707c80963b589b22f8e0229f1be1f3ca862a932c1bcd48dafb18dd8", + "sha256:92c882b70a40c79de9f5294dc99390671e07fc0b0113d472cbea3fde15db1792", + "sha256:95edb1ed513e68bddc2aee3de66ceaf743590bf16c023fb9977adc4be15bd3f0", + "sha256:b63d4ff734263ae4ce6593798bcfee6dbfb00523c82753a3a03cbc05555a9cc3", + "sha256:bd7bf289e05470b1bc74889d1466d9ad4a56d201f24397557b6f65c24a6844b8", + "sha256:cc3ea6b23954da84dbee8025c616040d9aa5eaf34ea6895a0a762ee9d3e12e11", + "sha256:cc9ec588c6ef3a1325fa032ec14d97b7309db493782ea8c304666fb10c3bd9a7", + "sha256:d3d07c86d4efa1facdf32aa878bd508c0dc4f87c48125cc16b937baa4e5b5e11", + "sha256:d8a96747df78cda35980905bf26e72960cba6d355ace4780d4bdde3b217cdf1e", + "sha256:e38d58d9138ef972fceb7aeec4be02e3f01d383723965bfcef14d174c8ccd039", + "sha256:eb472586374dc66b31e36e14720747595c2b265ae962987261f044e5cce644b5", + "sha256:fbd922f702582cb0d71ef94442bfca57624352622d75e3be7a1e7e9360b07e72" + ], + "index": "pypi", + "version": "==8.0.1" + }, + "pycodestyle": { + "hashes": [ + "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", + "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + ], + "index": "pypi", + "version": "==2.5.0" + }, + "pylint": { + "hashes": [ + "sha256:7b76045426c650d2b0f02fc47c14d7934d17898779da95288a74c2a7ec440702", + "sha256:856476331f3e26598017290fd65bebe81c960e806776f324093a46b76fb2d1c0" + ], + "index": "pypi", + "version": "==2.4.3" + }, + "pytz": { + "hashes": [ + "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", + "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7" + ], + "index": "pypi", + "version": "==2019.2" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "index": "pypi", + "version": "==1.12.0" + }, + "sqlparse": { + "hashes": [ + "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", + "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" + ], + "index": "pypi", + "version": "==0.3.0" + }, + "wheel": { + "hashes": [ + "sha256:645f81da70dcd993844005762acbb7e8628306bec414c90539d276fbd4fc7cbe", + "sha256:e17f05e14282d0e666327f800ef43123b40f3dbc13a86193b604f2ebfae0dabc" + ], + "index": "pypi", + "version": "==0.36.0" + }, + "wrapt": { + "hashes": [ + "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1" + ], + "index": "pypi", + "version": "==1.11.2" + } + }, + "develop": {} +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..c955e48 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# Bangazon Platform API + +## Prerequisites + +### Mac OS + +```sh +brew install libtiff libjpeg webp little-cms2 +``` + +### Linux (WSL) + +```sh +sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \ + libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \ + libharfbuzz-dev libfribidi-dev libxcb1-dev +``` + +### Install apidoc + +```sh +npm install apidoc -g +``` + +## Setup + +1. Clone this repository and change to the directory in the terminal. +1. Run `pipenv shell` +1. Run `pipenv install` +1. Type this exact thing into the terminal to run the migrations and seed the database: `./seed_data.sh` + +Now that your database is set up all you have to do is run the command: + +```sh +python manage.py runserver +``` + +## Bangazon ERD + +Open the [Bangazon database diagram](https://dbdiagram.io/d/5bad7831a3794b0014b3ccc7) in the browser to view the tables and relationships for your database. Note that the tables names and field names are written in Pascal case, and not in snake case. Your database has everything in snake case, so while the field names are different, the resources and relationships are identical to the ERD. + +## Postman Request Collection + +1. Open Postman +1. Click Import from the navbar +1. Choose the Link option +1. Paste in this URL: + `https://www.getpostman.com/collections/c29b98258d312bf240b7` +1. Your should be prompted to import **Bangazon Python API**. +1. Click the Import button to complete the process. + +To test it out, expand the Profile sub-collection, double-click on Login and send the request. You should get a response back that looks like this. + +```json +{ + "valid": true, + "token": "9ba45f09651c5b0c404f37a2d2572c026c146690", + "id": 5 +} +``` + +## Documentation + +To view browser-based documentation for the project, follow these steps. + +1. Run `./renderdocs.sh` +1. `cd docs` +1. Then start a simple web server like `http-server` or `serve`. +1. In your web browser, go to the URL provided by your web server. + +![documentation site](./bangazon-docs.png) \ No newline at end of file diff --git a/apidoc.json b/apidoc.json new file mode 100644 index 0000000..f949bdb --- /dev/null +++ b/apidoc.json @@ -0,0 +1,7 @@ +{ + "name": "Bangazon Platform API", + "version": "0.1.0", + "description": "Bangazon Platform API", + "title": "Bangazon Platform API", + "url" : "https://api.bangazon.com" +} diff --git a/bangazon-docs.png b/bangazon-docs.png new file mode 100644 index 0000000..a085cfc Binary files /dev/null and b/bangazon-docs.png differ diff --git a/bangazon/__init__.py b/bangazon/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bangazon/settings.py b/bangazon/settings.py new file mode 100644 index 0000000..b7be37e --- /dev/null +++ b/bangazon/settings.py @@ -0,0 +1,144 @@ +""" +Django settings for bangazon project. + +Generated by 'django-admin startproject' using Django 2.2.4. + +For more information on this file, see +https://docs.djangoproject.com/en/2.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/2.2/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = ')!ktne!^&jd0sshf7h2*1zm*_b_m8m9+699)^7yi9_6^0!ktnm' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'rest_framework', + 'rest_framework.authtoken', + 'corsheaders', + 'bangazonapi', + 'safedelete', +] + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework.authentication.TokenAuthentication', + ), + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.AllowAny', + ], + 'DEFAULT_RENDERER_CLASSES': ( + 'rest_framework.renderers.JSONRenderer', + ), + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', + 'PAGE_SIZE': 10 +} + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +CORS_ORIGIN_WHITELIST = ( + 'http://localhost:3000', + 'http://127.0.0.1:3000' +) + +ROOT_URLCONF = 'bangazon.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'bangazon.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/2.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + + } +} + + +# Password validation +# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/2.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + +APPEND_SLASH = False + +MEDIA_ROOT = 'media' +MEDIA_URL = '/media/' diff --git a/bangazon/urls.py b/bangazon/urls.py new file mode 100644 index 0000000..09578ae --- /dev/null +++ b/bangazon/urls.py @@ -0,0 +1,30 @@ +from django.conf import settings +from django.conf.urls import url, include +from django.conf.urls.static import static +from rest_framework import routers +from rest_framework.authtoken.views import obtain_auth_token +from bangazonapi.models import * +from bangazonapi.views import * + +# pylint: disable=invalid-name +router = routers.DefaultRouter(trailing_slash=False) +router.register(r'products', Products, 'product') +router.register(r'productcategories', ProductCategories, 'productcategory') +router.register(r'lineitems', LineItems, base_name='orderproduct') +router.register(r'customers', Customers, 'customer') +router.register(r'users', Users, 'user') +router.register(r'orders', Orders, 'order') +router.register(r'cart', Cart, 'cart') +router.register(r'paymenttypes', Payments, 'payment') +router.register(r'profile', Profile, 'profile') + + +# Wire up our API using automatic URL routing. +# Additionally, we include login URLs for the browsable API. +urlpatterns = [ + url(r'^', include(router.urls)), + url(r'^register$', register_user), + url(r'^login$', login_user), + url(r'^api-token-auth$', obtain_auth_token), + url(r'^api-auth', include('rest_framework.urls', namespace='rest_framework')), +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/bangazon/wsgi.py b/bangazon/wsgi.py new file mode 100644 index 0000000..42f302d --- /dev/null +++ b/bangazon/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for kennywood project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'bangazon.settings') + +application = get_wsgi_application() diff --git a/bangazonapi/__init__.py b/bangazonapi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bangazonapi/admin.py b/bangazonapi/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/bangazonapi/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/bangazonapi/apps.py b/bangazonapi/apps.py new file mode 100644 index 0000000..87ee355 --- /dev/null +++ b/bangazonapi/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class BangazonapiConfig(AppConfig): + name = 'bangazonapi' diff --git a/bangazonapi/fixtures/customers.json b/bangazonapi/fixtures/customers.json new file mode 100644 index 0000000..e0a3d7f --- /dev/null +++ b/bangazonapi/fixtures/customers.json @@ -0,0 +1,38 @@ +[ +{ + "model": "bangazonapi.customer", + "pk": 4, + "fields": { + "user": 5, + "phone_number": "555-1212", + "address": "100 Infinity Way" + } +}, +{ + "model": "bangazonapi.customer", + "pk": 5, + "fields": { + "user": 6, + "phone_number": "555-1212", + "address": "100 Endless Way" + } +}, +{ + "model": "bangazonapi.customer", + "pk": 6, + "fields": { + "user": 7, + "phone_number": "555-1212", + "address": "100 Dauntless Way" + } +}, +{ + "model": "bangazonapi.customer", + "pk": 7, + "fields": { + "user": 8, + "phone_number": "555-1212", + "address": "100 Indefatiguable Way" + } +} +] diff --git a/bangazonapi/fixtures/favoritesellers.json b/bangazonapi/fixtures/favoritesellers.json new file mode 100644 index 0000000..9fd6ad0 --- /dev/null +++ b/bangazonapi/fixtures/favoritesellers.json @@ -0,0 +1,26 @@ +[ + { + "pk": 1, + "model": "bangazonapi.favorite", + "fields": { + "customer_id": 7, + "seller_id": 5 + } + }, + { + "pk": 2, + "model": "bangazonapi.favorite", + "fields": { + "customer_id": 7, + "seller_id": 6 + } + }, + { + "pk": 3, + "model": "bangazonapi.favorite", + "fields": { + "customer_id": 7, + "seller_id": 7 + } + } +] \ No newline at end of file diff --git a/bangazonapi/fixtures/order.json b/bangazonapi/fixtures/order.json new file mode 100644 index 0000000..8858568 --- /dev/null +++ b/bangazonapi/fixtures/order.json @@ -0,0 +1,92 @@ +[ + { + "pk": 1, + "model": "bangazonapi.order", + "fields": { + "customer_id": 5, + "created_date": "2019-08-16", + "payment_type_id": 1 + } + }, + { + "pk": 2, + "model": "bangazonapi.order", + "fields": { + "customer_id": 7, + "created_date": "2019-04-12", + "payment_type_id": null + } + }, + { + "pk": 3, + "model": "bangazonapi.order", + "fields": { + "customer_id": 5, + "created_date": "2019-03-26", + "payment_type_id": 1 + } + }, + { + "pk": 4, + "model": "bangazonapi.order", + "fields": { + "customer_id": 6, + "created_date": "2019-01-16", + "payment_type_id": 2 + } + }, + { + "pk": 5, + "model": "bangazonapi.order", + "fields": { + "customer_id": 6, + "created_date": "2019-05-22", + "payment_type_id": 2 + } + }, + { + "pk": 6, + "model": "bangazonapi.order", + "fields": { + "customer_id": 5, + "created_date": "2019-07-01", + "payment_type_id": 1 + } + }, + { + "pk": 7, + "model": "bangazonapi.order", + "fields": { + "customer_id": 6, + "created_date": "2019-05-27", + "payment_type_id": 2 + } + }, + { + "pk": 8, + "model": "bangazonapi.order", + "fields": { + "customer_id": 5, + "created_date": "2019-03-14", + "payment_type_id": null + } + }, + { + "pk": 9, + "model": "bangazonapi.order", + "fields": { + "customer_id": 6, + "created_date": "2018-12-08", + "payment_type_id": null + } + }, + { + "pk": 10, + "model": "bangazonapi.order", + "fields": { + "customer_id": 4, + "created_date": "2018-11-03", + "payment_type_id": null + } + } +] \ No newline at end of file diff --git a/bangazonapi/fixtures/order_product.json b/bangazonapi/fixtures/order_product.json new file mode 100644 index 0000000..3483758 --- /dev/null +++ b/bangazonapi/fixtures/order_product.json @@ -0,0 +1,82 @@ +[ + { + "model": "bangazonapi.orderproduct", + "pk": 1, + "fields": { + "order_id": 9, + "product_id": 1 + } + }, + { + "model": "bangazonapi.orderproduct", + "pk": 2, + "fields": { + "order_id": 10, + "product_id": 3 + } + }, + { + "model": "bangazonapi.orderproduct", + "pk": 3, + "fields": { + "order_id": 8, + "product_id": 21 + } + }, + { + "model": "bangazonapi.orderproduct", + "pk": 7, + "fields": { + "order_id": 8, + "product_id": 5 + } + }, + { + "model": "bangazonapi.orderproduct", + "pk": 4, + "fields": { + "order_id": 2, + "product_id": 52 + } + }, + { + "model": "bangazonapi.orderproduct", + "pk": 5, + "fields": { + "order_id": 2, + "product_id": 33 + } + }, + { + "model": "bangazonapi.orderproduct", + "pk": 6, + "fields": { + "order_id": 2, + "product_id": 71 + } + }, + { + "model": "bangazonapi.orderproduct", + "pk": 7, + "fields": { + "order_id": 3, + "product_id": 50 + } + }, + { + "model": "bangazonapi.orderproduct", + "pk": 8, + "fields": { + "order_id": 3, + "product_id": 50 + } + }, + { + "model": "bangazonapi.orderproduct", + "pk": 9, + "fields": { + "order_id": 3, + "product_id": 45 + } + } +] diff --git a/bangazonapi/fixtures/payment.json b/bangazonapi/fixtures/payment.json new file mode 100644 index 0000000..be16250 --- /dev/null +++ b/bangazonapi/fixtures/payment.json @@ -0,0 +1,35 @@ +[ + { + "model": "bangazonapi.payment", + "pk": 1, + "fields": { + "merchant_name": "Visa", + "account_number": "24ijio68948fj8439", + "customer_id": "5", + "expiration_date": "2020-01-01", + "create_date": "2019-11-11" + } + }, + { + "model": "bangazonapi.payment", + "pk": 2, + "fields": { + "merchant_name": "Mastercard", + "account_number": "39j3984fj9sofi9", + "customer_id": "6", + "expiration_date": "2020-02-01", + "create_date": "2019-12-12" + } + }, + { + "model": "bangazonapi.payment", + "pk": 3, + "fields": { + "merchant_name": "Visa", + "account_number": "fj0398fjw0g89434", + "customer_id": "7", + "expiration_date": "2020-03-01", + "create_date": "2019-03-11" + } + } +] \ No newline at end of file diff --git a/bangazonapi/fixtures/product.json b/bangazonapi/fixtures/product.json new file mode 100644 index 0000000..93d2cf2 --- /dev/null +++ b/bangazonapi/fixtures/product.json @@ -0,0 +1,1502 @@ +[ + { + "model": "bangazonapi.product", + "pk": 1, + "fields": { + "name": "Optima", + "customer_id": 7, + "price": 1655.15, + "description": "2008 Kia", + "quantity": 3, + "created_date": "2019-05-21", + "category_id": 2, + "location": "Onguday", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 2, + "fields": { + "name": "Golf", + "customer_id": 4, + "price": 653.59, + "description": "1994 Volkswagen", + "quantity": 4, + "created_date": "2019-07-10", + "category_id": 2, + "location": "Zhongshan", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 3, + "fields": { + "name": "Durango", + "customer_id": 6, + "price": 541.17, + "description": "1998 Dodge", + "quantity": 2, + "created_date": "2019-05-16", + "category_id": 2, + "location": "Górki Wielkie", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 4, + "fields": { + "name": "H1", + "customer_id": 5, + "price": 1448.54, + "description": "2004 Hummer", + "quantity": 3, + "created_date": "2019-05-24", + "category_id": 2, + "location": "Santa Maria", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 5, + "fields": { + "name": "LR3", + "customer_id": 7, + "price": 1478.47, + "description": "2008 Land Rover", + "quantity": 1, + "created_date": "2019-04-04", + "category_id": 2, + "location": "Tagbilaran City", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 6, + "fields": { + "name": "Tacoma Xtra", + "customer_id": 4, + "price": 1385.85, + "description": "1995 Toyota", + "quantity": 1, + "created_date": "2019-08-25", + "category_id": 2, + "location": "Talakag", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 7, + "fields": { + "name": "Camry", + "customer_id": 7, + "price": 991.91, + "description": "2011 Toyota", + "quantity": 1, + "created_date": "2019-06-25", + "category_id": 2, + "location": "Krajan Dukuhseti", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 8, + "fields": { + "name": "Viper", + "customer_id": 6, + "price": 1650.55, + "description": "2003 Dodge", + "quantity": 4, + "created_date": "2019-07-18", + "category_id": 2, + "location": "Ul", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 9, + "fields": { + "name": "Celica", + "customer_id": 5, + "price": 1344.43, + "description": "1982 Toyota", + "quantity": 1, + "created_date": "2019-08-03", + "category_id": 2, + "location": "Al ‘Āliyah", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 10, + "fields": { + "name": "Land Cruiser", + "customer_id": 5, + "price": 1768.97, + "description": "2008 Toyota", + "quantity": 3, + "created_date": "2019-10-07", + "category_id": 2, + "location": "Niverville", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 11, + "fields": { + "name": "Montero", + "customer_id": 6, + "price": 1581.38, + "description": "1995 Mitsubishi", + "quantity": 2, + "created_date": "2018-10-22", + "category_id": 2, + "location": "Kalumpang", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 12, + "fields": { + "name": "E-Series", + "customer_id": 4, + "price": 1342.37, + "description": "1989 Ford", + "quantity": 3, + "created_date": "2018-11-13", + "category_id": 2, + "location": "Florestópolis", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 13, + "fields": { + "name": "Express 3500", + "customer_id": 4, + "price": 714.78, + "description": "2003 Chevrolet", + "quantity": 2, + "created_date": "2019-01-02", + "category_id": 2, + "location": "Cabo", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 14, + "fields": { + "name": "Vantage", + "customer_id": 7, + "price": 1739.53, + "description": "2007 Aston Martin", + "quantity": 2, + "created_date": "2019-03-31", + "category_id": 2, + "location": "Palecenan", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 15, + "fields": { + "name": "Astro", + "customer_id": 6, + "price": 762.55, + "description": "2000 Chevrolet", + "quantity": 2, + "created_date": "2019-03-06", + "category_id": 2, + "location": "Albarraque", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 16, + "fields": { + "name": "Sunbird", + "customer_id": 6, + "price": 1800.33, + "description": "1991 Pontiac", + "quantity": 1, + "created_date": "2019-08-09", + "category_id": 2, + "location": "Luoxi", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 17, + "fields": { + "name": "XT", + "customer_id": 5, + "price": 964.47, + "description": "1989 Subaru", + "quantity": 2, + "created_date": "2019-07-04", + "category_id": 2, + "location": "Harbin", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 18, + "fields": { + "name": "Impala", + "customer_id": 6, + "price": 954.15, + "description": "2007 Chevrolet", + "quantity": 1, + "created_date": "2019-02-10", + "category_id": 2, + "location": "Ke’erlun", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 19, + "fields": { + "name": "MPV", + "customer_id": 5, + "price": 845.13, + "description": "1997 Mazda", + "quantity": 4, + "created_date": "2019-01-05", + "category_id": 2, + "location": "Utama Wetan", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 20, + "fields": { + "name": "Mazda6", + "customer_id": 4, + "price": 1900.67, + "description": "2005 Mazda", + "quantity": 2, + "created_date": "2018-12-13", + "category_id": 2, + "location": "Tuburan", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 21, + "fields": { + "name": "Rio", + "customer_id": 5, + "price": 1240.2, + "description": "2013 Kia", + "quantity": 1, + "created_date": "2019-07-30", + "category_id": 2, + "location": "Presnenskiy", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 22, + "fields": { + "name": "Safari", + "customer_id": 4, + "price": 1547.74, + "description": "1986 Pontiac", + "quantity": 2, + "created_date": "2019-01-19", + "category_id": 2, + "location": "Połomia", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 23, + "fields": { + "name": "Celica", + "customer_id": 6, + "price": 1306.31, + "description": "1978 Toyota", + "quantity": 2, + "created_date": "2019-06-25", + "category_id": 2, + "location": "Zhenxing", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 24, + "fields": { + "name": "Talon", + "customer_id": 6, + "price": 788.94, + "description": "1993 Eagle", + "quantity": 4, + "created_date": "2019-08-27", + "category_id": 2, + "location": "Kuluran", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 25, + "fields": { + "name": "Silverado 2500", + "customer_id": 4, + "price": 1257.48, + "description": "2003 Chevrolet", + "quantity": 2, + "created_date": "2019-04-09", + "category_id": 2, + "location": "Feteira Pequena", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 26, + "fields": { + "name": "Eclipse", + "customer_id": 5, + "price": 1095.95, + "description": "1999 Mitsubishi", + "quantity": 3, + "created_date": "2019-03-22", + "category_id": 2, + "location": "Pôrto Barra do Ivinheima", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 27, + "fields": { + "name": "Range Rover", + "customer_id": 4, + "price": 1564.25, + "description": "2002 Land Rover", + "quantity": 1, + "created_date": "2019-05-14", + "category_id": 2, + "location": "Yong’an", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 28, + "fields": { + "name": "Corvette", + "customer_id": 4, + "price": 1478.17, + "description": "1964 Chevrolet", + "quantity": 3, + "created_date": "2019-05-30", + "category_id": 2, + "location": "Benchu", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 29, + "fields": { + "name": "Lancer Evolution", + "customer_id": 5, + "price": 1562.53, + "description": "2002 Mitsubishi", + "quantity": 2, + "created_date": "2018-10-27", + "category_id": 2, + "location": "Molsheim", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 30, + "fields": { + "name": "Shadow", + "customer_id": 7, + "price": 1581.43, + "description": "1993 Dodge", + "quantity": 3, + "created_date": "2019-05-01", + "category_id": 2, + "location": "Lescar", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 31, + "fields": { + "name": "Transit Connect", + "customer_id": 4, + "price": 1441.52, + "description": "2012 Ford", + "quantity": 2, + "created_date": "2018-11-30", + "category_id": 2, + "location": "Biliran", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 32, + "fields": { + "name": "DB9", + "customer_id": 5, + "price": 1912.51, + "description": "2008 Aston Martin", + "quantity": 3, + "created_date": "2019-01-06", + "category_id": 2, + "location": "Paris 07", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 33, + "fields": { + "name": "Stratus", + "customer_id": 7, + "price": 1199.91, + "description": "2001 Dodge", + "quantity": 1, + "created_date": "2019-04-06", + "category_id": 2, + "location": "Tianning", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 34, + "fields": { + "name": "Chariot", + "customer_id": 6, + "price": 1298.59, + "description": "1992 Mitsubishi", + "quantity": 1, + "created_date": "2019-04-27", + "category_id": 2, + "location": "Cimara", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 35, + "fields": { + "name": "V70", + "customer_id": 6, + "price": 1419.97, + "description": "2002 Volvo", + "quantity": 3, + "created_date": "2018-11-04", + "category_id": 2, + "location": "Even Yehuda", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 36, + "fields": { + "name": "GTO", + "customer_id": 6, + "price": 1807.33, + "description": "1999 Mitsubishi", + "quantity": 4, + "created_date": "2018-10-14", + "category_id": 2, + "location": "Sanquan", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 37, + "fields": { + "name": "Silverado 2500", + "customer_id": 4, + "price": 775.5, + "description": "2009 Chevrolet", + "quantity": 1, + "created_date": "2019-09-17", + "category_id": 2, + "location": "Xihu", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 38, + "fields": { + "name": "C70", + "customer_id": 7, + "price": 734.81, + "description": "2012 Volvo", + "quantity": 4, + "created_date": "2019-08-12", + "category_id": 2, + "location": "Ipís", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 39, + "fields": { + "name": "Esteem", + "customer_id": 7, + "price": 1453.83, + "description": "1996 Suzuki", + "quantity": 1, + "created_date": "2019-05-24", + "category_id": 2, + "location": "Hat Yai", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 40, + "fields": { + "name": "Eldorado", + "customer_id": 6, + "price": 901.87, + "description": "1999 Cadillac", + "quantity": 4, + "created_date": "2019-05-11", + "category_id": 2, + "location": "Kaeng Khoi", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 41, + "fields": { + "name": "Impreza", + "customer_id": 6, + "price": 1913.81, + "description": "1998 Subaru", + "quantity": 4, + "created_date": "2019-03-15", + "category_id": 2, + "location": "Dingjiaqiao", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 42, + "fields": { + "name": "Xterra", + "customer_id": 6, + "price": 1045.9, + "description": "2010 Nissan", + "quantity": 4, + "created_date": "2019-01-15", + "category_id": 2, + "location": "Lunenburg", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 43, + "fields": { + "name": "Sidekick", + "customer_id": 7, + "price": 1113.66, + "description": "1990 Suzuki", + "quantity": 4, + "created_date": "2019-10-09", + "category_id": 2, + "location": "Canmore", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 44, + "fields": { + "name": "Astro", + "customer_id": 7, + "price": 1486.31, + "description": "2000 Chevrolet", + "quantity": 4, + "created_date": "2019-03-25", + "category_id": 2, + "location": "Denver", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 45, + "fields": { + "name": "Corvette", + "customer_id": 6, + "price": 795.8, + "description": "1987 Chevrolet", + "quantity": 2, + "created_date": "2019-10-07", + "category_id": 2, + "location": "Ueno", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 46, + "fields": { + "name": "Mountaineer", + "customer_id": 6, + "price": 1188.66, + "description": "2008 Mercury", + "quantity": 1, + "created_date": "2019-08-19", + "category_id": 2, + "location": "Staryy Oskol", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 47, + "fields": { + "name": "Rendezvous", + "customer_id": 6, + "price": 1489.6, + "description": "2003 Buick", + "quantity": 2, + "created_date": "2018-12-09", + "category_id": 2, + "location": "Marechal Cândido Rondon", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 48, + "fields": { + "name": "Expedition EL", + "customer_id": 5, + "price": 1642.57, + "description": "2009 Ford", + "quantity": 4, + "created_date": "2019-05-04", + "category_id": 2, + "location": "Kranuan", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 49, + "fields": { + "name": "Highlander", + "customer_id": 6, + "price": 1192.71, + "description": "2005 Toyota", + "quantity": 4, + "created_date": "2018-10-15", + "category_id": 2, + "location": "Guaxupé", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 50, + "fields": { + "name": "Escalade EXT", + "customer_id": 4, + "price": 926.92, + "description": "2008 Cadillac", + "quantity": 2, + "created_date": "2019-02-01", + "category_id": 2, + "location": "Lokavec", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 51, + "fields": { + "name": "Truck", + "customer_id": 5, + "price": 1428.75, + "description": "1996 Mitsubishi", + "quantity": 2, + "created_date": "2019-04-29", + "category_id": 2, + "location": "Batukuta", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 52, + "fields": { + "name": "900", + "customer_id": 7, + "price": 1296.98, + "description": "1987 Saab", + "quantity": 2, + "created_date": "2019-03-19", + "category_id": 2, + "location": "Vratsa", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 53, + "fields": { + "name": "Grand Prix", + "customer_id": 5, + "price": 666.98, + "description": "1965 Pontiac", + "quantity": 1, + "created_date": "2019-07-28", + "category_id": 2, + "location": "Jesenice", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 54, + "fields": { + "name": "RX-7", + "customer_id": 4, + "price": 1071.34, + "description": "1984 Mazda", + "quantity": 3, + "created_date": "2018-11-03", + "category_id": 2, + "location": "Pingling", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 55, + "fields": { + "name": "Ram Van 1500", + "customer_id": 7, + "price": 1028.79, + "description": "1995 Dodge", + "quantity": 1, + "created_date": "2019-07-01", + "category_id": 2, + "location": "Heerlen", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 56, + "fields": { + "name": "M-Class", + "customer_id": 4, + "price": 807.88, + "description": "2011 Mercedes-Benz", + "quantity": 1, + "created_date": "2019-06-17", + "category_id": 2, + "location": "Lý Sơn", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 57, + "fields": { + "name": "Ram 2500", + "customer_id": 5, + "price": 605.84, + "description": "1997 Dodge", + "quantity": 3, + "created_date": "2018-12-26", + "category_id": 2, + "location": "Puerto Obaldía", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 58, + "fields": { + "name": "Golf", + "customer_id": 5, + "price": 692.12, + "description": "1987 Volkswagen", + "quantity": 4, + "created_date": "2019-01-09", + "category_id": 2, + "location": "Lizhuangzi", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 59, + "fields": { + "name": "Vibe", + "customer_id": 5, + "price": 1141.28, + "description": "2009 Pontiac", + "quantity": 1, + "created_date": "2019-03-03", + "category_id": 2, + "location": "Muyi", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 60, + "fields": { + "name": "Elantra", + "customer_id": 6, + "price": 1989.84, + "description": "1994 Hyundai", + "quantity": 1, + "created_date": "2019-01-19", + "category_id": 2, + "location": "Sosnovoborsk", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 61, + "fields": { + "name": "Tracker", + "customer_id": 6, + "price": 1850.74, + "description": "2000 Chevrolet", + "quantity": 1, + "created_date": "2019-05-15", + "category_id": 2, + "location": "Kogon", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 62, + "fields": { + "name": "Sunbird", + "customer_id": 7, + "price": 1073.42, + "description": "1989 Pontiac", + "quantity": 2, + "created_date": "2018-10-26", + "category_id": 2, + "location": "Gareba", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 63, + "fields": { + "name": "Express 3500", + "customer_id": 6, + "price": 855.27, + "description": "2012 Chevrolet", + "quantity": 1, + "created_date": "2019-06-27", + "category_id": 2, + "location": "Gelatik", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 64, + "fields": { + "name": "1500 Club Coupe", + "customer_id": 6, + "price": 1737.03, + "description": "1992 GMC", + "quantity": 3, + "created_date": "2019-07-18", + "category_id": 2, + "location": "Lescar", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 65, + "fields": { + "name": "Optima", + "customer_id": 4, + "price": 1475.41, + "description": "2007 Kia", + "quantity": 1, + "created_date": "2019-01-19", + "category_id": 2, + "location": "Kafr Takhārīm", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 66, + "fields": { + "name": "B-Series Plus", + "customer_id": 4, + "price": 1756.62, + "description": "1997 Mazda", + "quantity": 3, + "created_date": "2019-05-16", + "category_id": 2, + "location": "Dobri Dol", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 67, + "fields": { + "name": "Sky", + "customer_id": 7, + "price": 1578.19, + "description": "2008 Saturn", + "quantity": 4, + "created_date": "2019-05-13", + "category_id": 2, + "location": "Rungis", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 68, + "fields": { + "name": "CX-9", + "customer_id": 5, + "price": 1750.74, + "description": "2011 Mazda", + "quantity": 4, + "created_date": "2019-05-21", + "category_id": 2, + "location": "Qiankeng", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 69, + "fields": { + "name": "Esteem", + "customer_id": 4, + "price": 1457.3, + "description": "1996 Suzuki", + "quantity": 1, + "created_date": "2019-01-10", + "category_id": 2, + "location": "Korolevo", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 70, + "fields": { + "name": "1500", + "customer_id": 4, + "price": 1258.88, + "description": "1995 GMC", + "quantity": 3, + "created_date": "2019-04-16", + "category_id": 2, + "location": "Pryamitsyno", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 71, + "fields": { + "name": "Sebring", + "customer_id": 6, + "price": 1045.66, + "description": "1999 Chrysler", + "quantity": 4, + "created_date": "2019-05-18", + "category_id": 2, + "location": "Namibe", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 72, + "fields": { + "name": "Escape", + "customer_id": 4, + "price": 1747.41, + "description": "2000 Ford", + "quantity": 1, + "created_date": "2019-09-10", + "category_id": 2, + "location": "Bilqās", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 73, + "fields": { + "name": "Coachbuilder", + "customer_id": 5, + "price": 807.53, + "description": "1990 Buick", + "quantity": 4, + "created_date": "2019-02-12", + "category_id": 2, + "location": "Ushiku", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 74, + "fields": { + "name": "Allante", + "customer_id": 7, + "price": 763.3, + "description": "1993 Cadillac", + "quantity": 2, + "created_date": "2019-05-15", + "category_id": 2, + "location": "Elvas", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 75, + "fields": { + "name": "Sentra", + "customer_id": 5, + "price": 1172.25, + "description": "1991 Nissan", + "quantity": 4, + "created_date": "2019-09-12", + "category_id": 2, + "location": "Kaset Sombun", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 76, + "fields": { + "name": "Type 2", + "customer_id": 4, + "price": 974.69, + "description": "1985 Volkswagen", + "quantity": 2, + "created_date": "2019-08-03", + "category_id": 2, + "location": "Kotabunan", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 77, + "fields": { + "name": "Town & Country", + "customer_id": 5, + "price": 621.62, + "description": "1998 Chrysler", + "quantity": 2, + "created_date": "2019-03-19", + "category_id": 2, + "location": "Mętków", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 78, + "fields": { + "name": "Cooper", + "customer_id": 5, + "price": 625.27, + "description": "2004 MINI", + "quantity": 1, + "created_date": "2018-10-27", + "category_id": 2, + "location": "Cilaja", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 79, + "fields": { + "name": "S8", + "customer_id": 7, + "price": 1210.76, + "description": "2007 Audi", + "quantity": 1, + "created_date": "2018-11-08", + "category_id": 2, + "location": "Dargaz", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 80, + "fields": { + "name": "Grand Cherokee", + "customer_id": 6, + "price": 1083.19, + "description": "2000 Jeep", + "quantity": 1, + "created_date": "2019-08-31", + "category_id": 2, + "location": "Welchman Hall", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 81, + "fields": { + "name": "Pathfinder", + "customer_id": 6, + "price": 1585.89, + "description": "2010 Nissan", + "quantity": 4, + "created_date": "2019-05-22", + "category_id": 2, + "location": "Komsomolets", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 82, + "fields": { + "name": "Toronado", + "customer_id": 4, + "price": 1191.55, + "description": "1992 Oldsmobile", + "quantity": 3, + "created_date": "2018-12-09", + "category_id": 2, + "location": "Dushan", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 83, + "fields": { + "name": "Passat", + "customer_id": 6, + "price": 1301.75, + "description": "2008 Volkswagen", + "quantity": 4, + "created_date": "2019-08-24", + "category_id": 2, + "location": "Gerdu", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 84, + "fields": { + "name": "3500", + "customer_id": 6, + "price": 782.94, + "description": "1994 Chevrolet", + "quantity": 2, + "created_date": "2019-05-16", + "category_id": 2, + "location": "Iaçu", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 85, + "fields": { + "name": "S40", + "customer_id": 6, + "price": 699.63, + "description": "2001 Volvo", + "quantity": 4, + "created_date": "2018-12-23", + "category_id": 2, + "location": "Jayapura", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 86, + "fields": { + "name": "1500 Club Coupe", + "customer_id": 6, + "price": 676.11, + "description": "1997 GMC", + "quantity": 2, + "created_date": "2019-07-26", + "category_id": 2, + "location": "Santo Tomas", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 87, + "fields": { + "name": "Legend", + "customer_id": 5, + "price": 1019.99, + "description": "1995 Acura", + "quantity": 1, + "created_date": "2019-05-01", + "category_id": 2, + "location": "Titab", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 88, + "fields": { + "name": "Element", + "customer_id": 5, + "price": 1727.41, + "description": "2003 Honda", + "quantity": 3, + "created_date": "2019-05-28", + "category_id": 2, + "location": "Dukoh", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 89, + "fields": { + "name": "RX-7", + "customer_id": 4, + "price": 580.92, + "description": "1992 Mazda", + "quantity": 3, + "created_date": "2019-09-28", + "category_id": 2, + "location": "Xuebu", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 90, + "fields": { + "name": "Protege", + "customer_id": 7, + "price": 589.58, + "description": "1998 Mazda", + "quantity": 4, + "created_date": "2018-12-20", + "category_id": 2, + "location": "Usa River", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 91, + "fields": { + "name": "LUV", + "customer_id": 5, + "price": 1725.64, + "description": "1979 Chevrolet", + "quantity": 3, + "created_date": "2019-07-01", + "category_id": 2, + "location": "Tsuruga", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 92, + "fields": { + "name": "Versa", + "customer_id": 6, + "price": 888.68, + "description": "2009 Nissan", + "quantity": 1, + "created_date": "2018-11-20", + "category_id": 2, + "location": "Rangah", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 93, + "fields": { + "name": "Sable", + "customer_id": 7, + "price": 1352.23, + "description": "1991 Mercury", + "quantity": 1, + "created_date": "2019-09-07", + "category_id": 2, + "location": "Ipiaú", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 94, + "fields": { + "name": "Eclipse", + "customer_id": 4, + "price": 1139.37, + "description": "2002 Mitsubishi", + "quantity": 2, + "created_date": "2019-08-29", + "category_id": 2, + "location": "Pryazovs’ke", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 95, + "fields": { + "name": "Charger", + "customer_id": 7, + "price": 727.07, + "description": "1970 Dodge", + "quantity": 3, + "created_date": "2019-01-08", + "category_id": 2, + "location": "Świdwin", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 96, + "fields": { + "name": "Savana 2500", + "customer_id": 5, + "price": 523.52, + "description": "2003 GMC", + "quantity": 4, + "created_date": "2019-09-18", + "category_id": 2, + "location": "Muli", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 97, + "fields": { + "name": "Escalade EXT", + "customer_id": 5, + "price": 1565.45, + "description": "2010 Cadillac", + "quantity": 3, + "created_date": "2019-06-06", + "category_id": 2, + "location": "Tharyarwady", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 98, + "fields": { + "name": "Explorer Sport Trac", + "customer_id": 5, + "price": 1611.83, + "description": "2000 Ford", + "quantity": 4, + "created_date": "2019-01-05", + "category_id": 2, + "location": "Santa Praxedes", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 99, + "fields": { + "name": "Continental Flying Spur", + "customer_id": 6, + "price": 1467.39, + "description": "2011 Bentley", + "quantity": 1, + "created_date": "2019-05-08", + "category_id": 2, + "location": "Yambol", + "image_path": "" + } + }, + { + "model": "bangazonapi.product", + "pk": 100, + "fields": { + "name": "Equinox", + "customer_id": 4, + "price": 537.51, + "description": "2012 Chevrolet", + "quantity": 1, + "created_date": "2019-01-28", + "category_id": 2, + "location": "Sison", + "image_path": "" + } + } +] \ No newline at end of file diff --git a/bangazonapi/fixtures/product_category.json b/bangazonapi/fixtures/product_category.json new file mode 100644 index 0000000..5878b08 --- /dev/null +++ b/bangazonapi/fixtures/product_category.json @@ -0,0 +1,44 @@ +[ + { + "model": "bangazonapi.productcategory", + "pk": 1, + "fields": { + "name": "Tools" + } + }, + { + "model": "bangazonapi.productcategory", + "pk": 2, + "fields": { + "name": "Auto" + } + }, + { + "model": "bangazonapi.productcategory", + "pk": 3, + "fields": { + "name": "Technology" + } + }, + { + "model": "bangazonapi.productcategory", + "pk": 4, + "fields": { + "name": "Arts & Crafts" + } + }, + { + "model": "bangazonapi.productcategory", + "pk": 5, + "fields": { + "name": "Clothes" + } + }, + { + "model": "bangazonapi.productcategory", + "pk": 6, + "fields": { + "name": "Games/Toys" + } + } +] \ No newline at end of file diff --git a/bangazonapi/fixtures/productrating.json b/bangazonapi/fixtures/productrating.json new file mode 100644 index 0000000..be27dda --- /dev/null +++ b/bangazonapi/fixtures/productrating.json @@ -0,0 +1,38 @@ +[ + { + "model": "bangazonapi.productrating", + "pk": 1, + "fields": { + "product": 50, + "customer": 4, + "rating": 4 + } + }, + { + "model": "bangazonapi.productrating", + "pk": 2, + "fields": { + "product": 50, + "customer": 5, + "rating": 3 + } + }, + { + "model": "bangazonapi.productrating", + "pk": 3, + "fields": { + "product": 50, + "customer": 6, + "rating": 5 + } + }, + { + "model": "bangazonapi.productrating", + "pk": 4, + "fields": { + "product": 50, + "customer": 7, + "rating": 1 + } + } +] \ No newline at end of file diff --git a/bangazonapi/fixtures/tokens.json b/bangazonapi/fixtures/tokens.json new file mode 100644 index 0000000..4ce3f72 --- /dev/null +++ b/bangazonapi/fixtures/tokens.json @@ -0,0 +1,34 @@ +[ +{ + "model": "authtoken.token", + "pk": "9ba45f09651c5b0c404f37a2d2572c026c146688", + "fields": { + "user": 7, + "created": "2019-10-10T23:41:08.334Z" + } +}, +{ + "model": "authtoken.token", + "pk": "9ba45f09651c5b0c404f37a2d2572c026c146690", + "fields": { + "user": 5, + "created": "2019-10-10T23:41:08.334Z" + } +}, +{ + "model": "authtoken.token", + "pk": "9ba45f09651c5b0c404f37a2d2572c026c146694", + "fields": { + "user": 6, + "created": "2019-10-10T23:41:08.334Z" + } +}, +{ + "model": "authtoken.token", + "pk": "9ba45f09651c5b0c404f37a2d2572c026c14669c", + "fields": { + "user": 8, + "created": "2019-10-10T23:41:08.334Z" + } +} +] diff --git a/bangazonapi/fixtures/users.json b/bangazonapi/fixtures/users.json new file mode 100644 index 0000000..52048ef --- /dev/null +++ b/bangazonapi/fixtures/users.json @@ -0,0 +1,74 @@ +[ +{ + "model": "auth.user", + "pk": 5, + "fields": { + "password": "pbkdf2_sha256$150000$fHDURJBIASpx$trZS1MWc6YiNe5EYNBap+P+zMAwpNgNbUZH/b9bgvdw=", + "last_login": null, + "is_superuser": false, + "username": "steve", + "first_name": "Steve", + "last_name": "Brownlee", + "email": "steve@stevebrownlee.com", + "is_staff": false, + "is_active": true, + "date_joined": "2019-10-10T19:12:13.676Z", + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "auth.user", + "pk": 6, + "fields": { + "password": "pbkdf2_sha256$150000$GviXrBarvuwF$gzlskakTbrLRnnJxv2UKsJlvCA2A5u5CjFcrdXjDAVM=", + "last_login": null, + "is_superuser": false, + "username": "joe", + "first_name": "Joe", + "last_name": "Shepherd", + "email": "joe@joeshepherd.com", + "is_staff": false, + "is_active": true, + "date_joined": "2019-10-10T22:47:43.564Z", + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "auth.user", + "pk": 7, + "fields": { + "password": "pbkdf2_sha256$150000$fpOjFWJQRWRe$98+6dDrxFfYX191Hlk+lD3fTvmCnDbOQ4Wn4L+pTqTk=", + "last_login": null, + "is_superuser": false, + "username": "jisie", + "first_name": "Jisie", + "last_name": "David", + "email": "jisie@jisiedavid.com", + "is_staff": false, + "is_active": true, + "date_joined": "2019-10-10T22:48:19.899Z", + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "auth.user", + "pk": 8, + "fields": { + "password": "pbkdf2_sha256$150000$HBu8sLcgV3lh$o2p9+aH/F9Dk9tIztT2CJDQs5R1gEt925BoKM/XzNXQ=", + "last_login": null, + "is_superuser": false, + "username": "brenda", + "first_name": "Brenda", + "last_name": "Long", + "email": "brenda@brendalong.com", + "is_staff": false, + "is_active": true, + "date_joined": "2019-10-10T22:48:53.984Z", + "groups": [], + "user_permissions": [] + } +} +] diff --git a/bangazonapi/models/__init__.py b/bangazonapi/models/__init__.py new file mode 100644 index 0000000..cca5943 --- /dev/null +++ b/bangazonapi/models/__init__.py @@ -0,0 +1,10 @@ +from .customer import Customer +from .order import Order +from .orderproduct import OrderProduct +from .payment import Payment +from .product import Product +from .productcategory import ProductCategory +from .recommendation import Recommendation +from .rating import Rating +from .favorite import Favorite +from .productrating import ProductRating diff --git a/bangazonapi/models/customer.py b/bangazonapi/models/customer.py new file mode 100644 index 0000000..acfbf19 --- /dev/null +++ b/bangazonapi/models/customer.py @@ -0,0 +1,9 @@ +from django.db import models +from django.contrib.auth.models import User + + +class Customer(models.Model): + + user = models.OneToOneField(User, on_delete=models.DO_NOTHING,) + phone_number = models.CharField(max_length=15) + address = models.CharField(max_length=55) diff --git a/bangazonapi/models/favorite.py b/bangazonapi/models/favorite.py new file mode 100644 index 0000000..be8dc46 --- /dev/null +++ b/bangazonapi/models/favorite.py @@ -0,0 +1,12 @@ +from django.core.validators import MaxValueValidator, MinValueValidator +from django.db import models +from .customer import Customer +from .productcategory import ProductCategory +from .orderproduct import OrderProduct +from safedelete.models import SafeDeleteModel +from safedelete.models import SOFT_DELETE + +class Favorite(models.Model): + + customer = models.ForeignKey(Customer, on_delete=models.DO_NOTHING,) + seller = models.ForeignKey(Customer, on_delete=models.DO_NOTHING, related_name='favorited_seller') diff --git a/bangazonapi/models/order.py b/bangazonapi/models/order.py new file mode 100644 index 0000000..5ec43fd --- /dev/null +++ b/bangazonapi/models/order.py @@ -0,0 +1,10 @@ +"""Customer order model""" +from django.db import models +from .customer import Customer +from .payment import Payment + + +class Order(models.Model): + customer = models.ForeignKey(Customer, on_delete=models.DO_NOTHING,) + payment_type = models.ForeignKey(Payment, on_delete=models.DO_NOTHING, null=True) + created_date = models.DateField(default="0000-00-00",) diff --git a/bangazonapi/models/orderproduct.py b/bangazonapi/models/orderproduct.py new file mode 100644 index 0000000..ad8b31d --- /dev/null +++ b/bangazonapi/models/orderproduct.py @@ -0,0 +1,12 @@ +from django.db import models + + +class OrderProduct(models.Model): + + order = models.ForeignKey("Order", + on_delete=models.DO_NOTHING, + related_name="lineitems") + + product = models.ForeignKey("Product", + on_delete=models.DO_NOTHING, + related_name="lineitems") diff --git a/bangazonapi/models/payment.py b/bangazonapi/models/payment.py new file mode 100644 index 0000000..8bc34d5 --- /dev/null +++ b/bangazonapi/models/payment.py @@ -0,0 +1,13 @@ +from django.db import models +from .customer import Customer +from safedelete.models import SafeDeleteModel +from safedelete.models import SOFT_DELETE + +class Payment(SafeDeleteModel): + + _safedelete_policy = SOFT_DELETE + merchant_name = models.CharField(max_length=25,) + account_number = models.CharField(max_length=25) + customer = models.ForeignKey(Customer, on_delete=models.DO_NOTHING, related_name="payment_types") + expiration_date = models.DateField(default="0000-00-00",) + create_date = models.DateField(default="0000-00-00",) diff --git a/bangazonapi/models/product.py b/bangazonapi/models/product.py new file mode 100644 index 0000000..af2b0fc --- /dev/null +++ b/bangazonapi/models/product.py @@ -0,0 +1,70 @@ +from django.core.validators import MaxValueValidator, MinValueValidator +from django.db import models +from safedelete.models import SafeDeleteModel +from safedelete.models import SOFT_DELETE +from .customer import Customer +from .productcategory import ProductCategory +from .orderproduct import OrderProduct +from .productrating import ProductRating + + +class Product(SafeDeleteModel): + + _safedelete_policy = SOFT_DELETE + name = models.CharField(max_length=50,) + customer = models.ForeignKey( + Customer, on_delete=models.DO_NOTHING, related_name='products') + price = models.FloatField( + validators=[MinValueValidator(0.00), MaxValueValidator(10000.00)],) + description = models.CharField(max_length=255,) + quantity = models.IntegerField(validators=[MinValueValidator(0)],) + created_date = models.DateField(auto_now_add=True) + category = models.ForeignKey( + ProductCategory, on_delete=models.DO_NOTHING, related_name='products') + location = models.CharField(max_length=50,) + image_path = models.ImageField( + upload_to='products', height_field=None, + width_field=None, max_length=None, null=True) + + @property + def number_sold(self): + """number_sold property of a product + + Returns: + int -- Number items on completed orders + """ + sold = OrderProduct.objects.filter( + product=self, order__payment_type__isnull=False) + return sold.count() + + @property + def can_be_rated(self): + """can_be_rated property, which will be calculated per user + + Returns: + boolean -- If the user can rate the product or not + """ + return self.__can_be_rated + + @can_be_rated.setter + def can_be_rated(self, value): + self.__can_be_rated = value + + @property + def average_rating(self): + """Average rating calculated attribute for each product + + Returns: + number -- The average rating for the product + """ + ratings = ProductRating.objects.filter(product=self) + total_rating = 0 + for rating in ratings: + total_rating += rating.rating + + avg = total_rating / len(ratings) + return avg + + class Meta: + verbose_name = ("product") + verbose_name_plural = ("products") diff --git a/bangazonapi/models/productcategory.py b/bangazonapi/models/productcategory.py new file mode 100644 index 0000000..3e2408c --- /dev/null +++ b/bangazonapi/models/productcategory.py @@ -0,0 +1,10 @@ +from django.db import models + + +class ProductCategory(models.Model): + + name = models.CharField(max_length=55) + + class Meta: + verbose_name = ("productcategory") + verbose_name_plural = ("productcategories") \ No newline at end of file diff --git a/bangazonapi/models/productrating.py b/bangazonapi/models/productrating.py new file mode 100644 index 0000000..143e824 --- /dev/null +++ b/bangazonapi/models/productrating.py @@ -0,0 +1,17 @@ +from django.db import models +from django.core.validators import MaxValueValidator, MinValueValidator +from .customer import Customer + + +class ProductRating(models.Model): + + product = models.ForeignKey("Product", on_delete=models.CASCADE, related_name="ratings") + customer = models.ForeignKey(Customer, on_delete=models.CASCADE) + rating = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(5)]) + +class Meta: + verbose_name = ("productrating") + verbose_name_plural = ("productratings") + +def __str__(self): + return self.rating diff --git a/bangazonapi/models/rating.py b/bangazonapi/models/rating.py new file mode 100644 index 0000000..5e5e5ac --- /dev/null +++ b/bangazonapi/models/rating.py @@ -0,0 +1,15 @@ +from django.db import models +from django.core.validators import MaxValueValidator, MinValueValidator +from .customer import Customer +from .product import Product + +class Rating(models.Model): + + customer = models.ForeignKey(Customer, on_delete=models.DO_NOTHING,) + product = models.ForeignKey(Product, on_delete=models.DO_NOTHING,) + score = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(5)],) + + class Meta: + verbose_name = ("rating") + verbose_name_plural = ("ratings") + diff --git a/bangazonapi/models/recommendation.py b/bangazonapi/models/recommendation.py new file mode 100644 index 0000000..fcfba55 --- /dev/null +++ b/bangazonapi/models/recommendation.py @@ -0,0 +1,10 @@ +from django.db import models +from .customer import Customer +from .product import Product + + +class Recommendation(models.Model): + + customer = models.ForeignKey(Customer, related_name='recommendations', on_delete=models.DO_NOTHING,) + product = models.ForeignKey(Product, on_delete=models.DO_NOTHING,) + recommender = models.ForeignKey(Customer, related_name='recommendations', on_delete=models.DO_NOTHING,) diff --git a/bangazonapi/tests.py b/bangazonapi/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/bangazonapi/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/bangazonapi/views/__init__.py b/bangazonapi/views/__init__.py new file mode 100644 index 0000000..35b7ee7 --- /dev/null +++ b/bangazonapi/views/__init__.py @@ -0,0 +1,11 @@ +from .register import register_user +from .register import login_user +from .order import Orders +from .paymenttype import Payments +from .product import Products +from .cart import Cart +from .profile import Profile +from .productcategory import ProductCategories +from .lineitem import LineItems +from .customer import Customers +from .user import Users diff --git a/bangazonapi/views/cart.py b/bangazonapi/views/cart.py new file mode 100644 index 0000000..f33033a --- /dev/null +++ b/bangazonapi/views/cart.py @@ -0,0 +1,133 @@ +"""View module for handling requests about customer shopping cart""" +import datetime +from rest_framework.viewsets import ViewSet +from rest_framework.response import Response +from rest_framework import status +from bangazonapi.models import Order, Customer, Product, OrderProduct +from .product import ProductSerializer +from .order import OrderSerializer + + +class Cart(ViewSet): + """Shopping cart for Bangazon eCommerce""" + + def create(self, request): + """ + @api {POST} /cart POST new line items to cart + @apiName AddLineItem + @apiGroup ShoppingCart + + @apiSuccessExample {json} Success + HTTP/1.1 204 No Content + @apiParam {Number} product_id Id of product to add + """ + current_user = Customer.objects.get(user=request.auth.user) + + try: + open_order = Order.objects.get( + customer=current_user, payment_type__isnull=True) + except Order.DoesNotExist as ex: + open_order = Order() + open_order.created_date = datetime.datetime.now() + open_order.customer = current_user + open_order.save() + + line_item = OrderProduct() + line_item.product = Product.objects.get(pk=request.data["product_id"]) + line_item.order = open_order + line_item.save() + + return Response({}, status=status.HTTP_204_NO_CONTENT) + + + def destroy(self, request, pk=None): + """ + @api {DELETE} /cart/:id DELETE line item from cart + @apiName RemoveLineItem + @apiGroup ShoppingCart + + @apiParam {id} id Product Id to remove from cart + @apiSuccessExample {json} Success + HTTP/1.1 204 No Content + """ + current_user = Customer.objects.get(user=request.auth.user) + open_order = Order.objects.get( + customer=current_user, payment_type=None) + + line_item = OrderProduct.objects.filter( + product__id=pk, + order=open_order + )[0] + line_item.delete() + + return Response({}, status=status.HTTP_204_NO_CONTENT) + + + def list(self, request): + """ + @api {GET} /cart GET line items in cart + @apiName GetCart + @apiGroup ShoppingCart + + @apiSuccess (200) {Number} id Order cart + @apiSuccess (200) {String} url URL of order + @apiSuccess (200) {String} created_date Date created + @apiSuccess (200) {Object} payment_type Payment id use to complete order + @apiSuccess (200) {String} customer URI for customer + @apiSuccess (200) {Number} size Number of items in cart + @apiSuccess (200) {Object[]} line_items Line items in cart + @apiSuccess (200) {Number} line_items.id Line item id + @apiSuccess (200) {Object} line_items.product Product in cart + @apiSuccessExample {json} Success + { + "id": 2, + "url": "http://localhost:8000/orders/2", + "created_date": "2019-04-12", + "payment_type": null, + "customer": "http://localhost:8000/customers/7", + "products": [ + { + "id": 52, + "url": "http://localhost:8000/products/52", + "name": "900", + "price": 1296.98, + "number_sold": 0, + "description": "1987 Saab", + "quantity": 2, + "created_date": "2019-03-19", + "location": "Vratsa", + "image_path": null, + "average_rating": 0, + "category": { + "url": "http://localhost:8000/productcategories/2", + "name": "Auto" + } + } + ], + "size": 1 + } + """ + current_user = Customer.objects.get(user=request.auth.user) + try: + open_order = Order.objects.get( + customer=current_user, payment_type=None) + + products_on_order = Product.objects.filter( + lineitems__order=open_order) + + serialized_order = OrderSerializer( + open_order, many=False, context={'request': request}) + + product_list = ProductSerializer( + products_on_order, many=True, context={'request': request}) + + final = { + "order": serialized_order.data + } + final["order"]["products"] = product_list.data + final["order"]["size"] = len(products_on_order) + + except Order.DoesNotExist as ex: + return Response({'message': ex.args[0]}, status=status.HTTP_404_NOT_FOUND) + + return Response(final["order"]) diff --git a/bangazonapi/views/customer.py b/bangazonapi/views/customer.py new file mode 100644 index 0000000..274653d --- /dev/null +++ b/bangazonapi/views/customer.py @@ -0,0 +1,44 @@ +from django.http import HttpResponseServerError +from rest_framework.viewsets import ViewSet +from rest_framework.response import Response +from rest_framework import serializers +from rest_framework import status +from bangazonapi.models import Customer + + +class CustomerSerializer(serializers.HyperlinkedModelSerializer): + """JSON serializer for customers""" + class Meta: + model = Customer + url = serializers.HyperlinkedIdentityField( + view_name='customer', lookup_field='id' + ) + fields = ('id', 'url', 'user', 'phone_number', 'address') + depth = 1 + + +class Customers(ViewSet): + + def update(self, request, pk=None): + """ + @api {PUT} /customers/:id PUT changes to customer profile + @apiName UpdateCustomer + @apiGroup Customer + + @apiHeader {String} Authorization Auth token + @apiHeaderExample {String} Authorization + Token 9ba45f09651c5b0c404f37a2d2572c026c146611 + + @apiParam {id} id Customer Id to update + @apiSuccessExample {json} Success + HTTP/1.1 204 No Content + """ + customer = Customer.objects.get(user=request.auth.user) + customer.user.last_name = request.data["last_name"] + customer.user.email = request.data["email"] + customer.address = request.data["address"] + customer.phone_number = request.data["phone_number"] + customer.user.save() + customer.save() + + return Response({}, status=status.HTTP_204_NO_CONTENT) diff --git a/bangazonapi/views/lineitem.py b/bangazonapi/views/lineitem.py new file mode 100644 index 0000000..20096f8 --- /dev/null +++ b/bangazonapi/views/lineitem.py @@ -0,0 +1,87 @@ + +"""View module for handling requests about line items""" +from django.http import HttpResponseServerError +from rest_framework.viewsets import ViewSet +from rest_framework.response import Response +from rest_framework import serializers +from rest_framework import status +from bangazonapi.models import OrderProduct, Order, Product, Customer + + +class LineItemSerializer(serializers.HyperlinkedModelSerializer): + """JSON serializer for line items """ + class Meta: + model = OrderProduct + url = serializers.HyperlinkedIdentityField( + view_name='lineitem', + lookup_field='id' + ) + fields = ('id', 'url', 'order', 'product') + +class LineItems(ViewSet): + """Line items for Bangazon orders""" + + # TIP: By setting this class attribute, then a `basename` parameter + # does not need to be set on the route in urls.py:11 and allow + # the serializer (see above) use the `view_name='lineitem'` + # argument for the HyperlinkedIdentityField. If this is NOT set + # then the following exception gets thrown. + # + # ImproperlyConfigured at /lineitems/4 + # Could not resolve URL for hyperlinked relationship using view name + # "orderproduct-detail". You may have failed to include the related + # model in your API, or incorrectly configured the `lookup_field` + # attribute on this field. + # queryset = OrderProduct.objects.all() + + def retrieve(self, request, pk=None): + """ + @api {GET} /cart/:id DELETE line item from cart + @apiName RemoveLineItem + @apiGroup ShoppingCart + + @apiHeader {String} Authorization Auth token + @apiHeaderExample {String} Authorization + Token 9ba45f09651c5b0c404f37a2d2572c026c146611 + + @apiParam {id} id Product Id to remove from cart + @apiSuccessExample {json} Success + HTTP/1.1 204 No Content + """ + try: + # line_item = OrderProduct.objects.get(pk=pk) + customer = Customer.objects.get(user=request.auth.user) + line_item = OrderProduct.objects.get(pk=pk, order__customer=customer) + + serializer = LineItemSerializer(line_item, context={'request': request}) + + return Response(serializer.data) + + except OrderProduct.DoesNotExist as ex: + return Response({'message': ex.args[0]}, status=status.HTTP_404_NOT_FOUND) + + def destroy(self, request, pk=None): + """ + @api {DELETE} /cart/:id DELETE line item from cart + @apiName RemoveLineItem + @apiGroup ShoppingCart + + @apiHeader {String} Authorization Auth token + @apiHeaderExample {String} Authorization + Token 9ba45f09651c5b0c404f37a2d2572c026c146611 + + @apiParam {id} id Product Id to remove from cart + @apiSuccessExample {json} Success + HTTP/1.1 204 No Content + """ + try: + customer = Customer.objects.get(user=request.auth.user) + order_product = OrderProduct.objects.get(pk=pk, order__customer=customer) + + return Response({}, status=status.HTTP_204_NO_CONTENT) + + except OrderProduct.DoesNotExist as ex: + return Response({'message': ex.args[0]}, status=status.HTTP_404_NOT_FOUND) + + except Exception as ex: + return Response({'message': ex.args[0]}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/bangazonapi/views/order.py b/bangazonapi/views/order.py new file mode 100644 index 0000000..5b5931c --- /dev/null +++ b/bangazonapi/views/order.py @@ -0,0 +1,152 @@ +"""View module for handling requests about customer order""" +import datetime +from django.http import HttpResponseServerError +from rest_framework.viewsets import ViewSet +from rest_framework.response import Response +from rest_framework import serializers +from rest_framework import status +from rest_framework.decorators import action +from bangazonapi.models import Order, Payment, Customer, Product, OrderProduct +from .product import ProductSerializer + + +class OrderLineItemSerializer(serializers.HyperlinkedModelSerializer): + """JSON serializer for line items """ + + product = ProductSerializer(many=False) + + class Meta: + model = OrderProduct + url = serializers.HyperlinkedIdentityField( + view_name='lineitem', + lookup_field='id' + ) + fields = ('id', 'product') + depth = 1 + +class OrderSerializer(serializers.HyperlinkedModelSerializer): + """JSON serializer for customer orders""" + + lineitems = OrderLineItemSerializer(many=True) + + class Meta: + model = Order + url = serializers.HyperlinkedIdentityField( + view_name='order', + lookup_field='id' + ) + fields = ('id', 'url', 'created_date', 'payment_type', 'customer', 'lineitems') + + +class Orders(ViewSet): + """View for interacting with customer orders""" + + def retrieve(self, request, pk=None): + """ + @api {GET} /cart/:id GET single order + @apiName GetOrder + @apiGroup Orders + + @apiHeader {String} Authorization Auth token + @apiHeaderExample {String} Authorization + Token 9ba45f09651c5b0c404f37a2d2572c026c146611 + + + @apiSuccess (200) {id} id Order id + @apiSuccess (200) {String} url Order URI + @apiSuccess (200) {String} created_date Date order was created + @apiSuccess (200) {String} payment_type Payment URI + @apiSuccess (200) {String} customer Customer URI + + @apiSuccessExample {json} Success + { + "id": 1, + "url": "http://localhost:8000/orders/1", + "created_date": "2019-08-16", + "payment_type": "http://localhost:8000/paymenttypes/1", + "customer": "http://localhost:8000/customers/5" + } + """ + try: + customer = Customer.objects.get(user=request.auth.user) + order = Order.objects.get(pk=pk, customer=customer) + serializer = OrderSerializer(order, context={'request': request}) + return Response(serializer.data) + + except Order.DoesNotExist as ex: + return Response( + {'message': 'The requested order does not exist, or you do not have permission to access it.'}, + status=status.HTTP_404_NOT_FOUND + ) + + except Exception as ex: + return HttpResponseServerError(ex) + + def update(self, request, pk=None): + """ + @api {PUT} /order/:id PUT new payment for order + @apiName AddPayment + @apiGroup Orders + + @apiHeader {String} Authorization Auth token + @apiHeaderExample {String} Authorization + Token 9ba45f09651c5b0c404f37a2d2572c026c146611 + + @apiParam {id} id Order Id route parameter + @apiParam {id} payment_type Payment Id to pay for the order + @apiParamExample {json} Input + { + "payment_type": 6 + } + + @apiSuccessExample {json} Success + HTTP/1.1 204 No Content + """ + customer = Customer.objects.get(user=request.auth.user) + order = Order.objects.get(pk=pk, customer=customer) + order.payment_type = request.data["payment_type"] + order.save() + + return Response({}, status=status.HTTP_204_NO_CONTENT) + + def list(self, request): + """ + @api {GET} /orders GET customer orders + @apiName GetOrders + @apiGroup Orders + + @apiHeader {String} Authorization Auth token + @apiHeaderExample {String} Authorization + Token 9ba45f09651c5b0c404f37a2d2572c026c146611 + + @apiParam {id} payment_id Query param to filter by payment used + + @apiSuccess (200) {Object[]} orders Array of order objects + @apiSuccess (200) {id} orders.id Order id + @apiSuccess (200) {String} orders.url Order URI + @apiSuccess (200) {String} orders.created_date Date order was created + @apiSuccess (200) {String} orders.payment_type Payment URI + @apiSuccess (200) {String} orders.customer Customer URI + + @apiSuccessExample {json} Success + [ + { + "id": 1, + "url": "http://localhost:8000/orders/1", + "created_date": "2019-08-16", + "payment_type": "http://localhost:8000/paymenttypes/1", + "customer": "http://localhost:8000/customers/5" + } + ] + """ + customer = Customer.objects.get(user=request.auth.user) + orders = Order.objects.filter(customer=customer) + + payment = self.request.query_params.get('payment_id', None) + if payment is not None: + orders = orders.filter(payment__id=payment) + + json_orders = OrderSerializer( + orders, many=True, context={'request': request}) + + return Response(json_orders.data) diff --git a/bangazonapi/views/paymenttype.py b/bangazonapi/views/paymenttype.py new file mode 100644 index 0000000..c5b2456 --- /dev/null +++ b/bangazonapi/views/paymenttype.py @@ -0,0 +1,91 @@ +"""View module for handling requests about customer payment types""" +from django.http import HttpResponseServerError +from rest_framework.viewsets import ViewSet +from rest_framework.response import Response +from rest_framework import serializers +from rest_framework import status +from bangazonapi.models import Payment, Customer + + +class PaymentSerializer(serializers.HyperlinkedModelSerializer): + """JSON serializer for Payment + + Arguments: + serializers + """ + class Meta: + model = Payment + url = serializers.HyperlinkedIdentityField( + view_name='payment', + lookup_field='id' + ) + fields = ('id', 'url', 'merchant_name', 'account_number', + 'expiration_date', 'create_date') + + +class Payments(ViewSet): + + def create(self, request): + """Handle POST operations + + Returns: + Response -- JSON serialized payment instance + """ + new_payment = Payment() + new_payment.merchant_name = request.data["merchant_name"] + new_payment.account_number = request.data["account_number"] + new_payment.expiration_date = request.data["create_date"] + new_payment.create_date = request.data["expiration_date"] + customer = Customer.objects.get(user=request.auth.user) + new_payment.customer = customer + new_payment.save() + + serializer = PaymentSerializer( + new_payment, context={'request': request}) + + return Response(serializer.data, status=status.HTTP_201_CREATED) + + def retrieve(self, request, pk=None): + """Handle GET requests for single payment type + + Returns: + Response -- JSON serialized payment_type instance + """ + try: + payment_type = Payment.objects.get(pk=pk) + serializer = PaymentSerializer( + payment_type, context={'request': request}) + return Response(serializer.data) + except Exception as ex: + return HttpResponseServerError(ex) + + def destroy(self, request, pk=None): + """Handle DELETE requests for a single payment type + + Returns: + Response -- 200, 404, or 500 status code + """ + try: + payment = Payment.objects.get(pk=pk) + payment.delete() + + return Response({}, status=status.HTTP_204_NO_CONTENT) + + except Payment.DoesNotExist as ex: + return Response({'message': ex.args[0]}, status=status.HTTP_404_NOT_FOUND) + + except Exception as ex: + return Response({'message': ex.args[0]}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + def list(self, request): + """Handle GET requests to payment type resource""" + payment_types = Payment.objects.all() + + customer_id = self.request.query_params.get('customer', None) + + if customer_id is not None: + payment_types = payment_types.filter(customer__id=customer_id) + + serializer = PaymentSerializer( + payment_types, many=True, context={'request': request}) + return Response(serializer.data) diff --git a/bangazonapi/views/product.py b/bangazonapi/views/product.py new file mode 100644 index 0000000..7f1c882 --- /dev/null +++ b/bangazonapi/views/product.py @@ -0,0 +1,277 @@ +"""View module for handling requests about products""" +import base64 +from django.core.files.base import ContentFile +from django.http import HttpResponseServerError +from rest_framework.viewsets import ViewSet +from rest_framework.response import Response +from rest_framework import serializers +from rest_framework import status +from bangazonapi.models import Product, Customer, ProductCategory +from rest_framework.permissions import IsAuthenticatedOrReadOnly +from rest_framework.parsers import MultiPartParser, FormParser + + +class ProductSerializer(serializers.ModelSerializer): + """JSON serializer for products""" + class Meta: + model = Product + fields = ('id', 'name', 'price', 'number_sold', 'description', + 'quantity', 'created_date', 'location', 'image_path', + 'average_rating', 'can_be_rated', ) + depth = 1 + + +class Products(ViewSet): + """Request handlers for Products in the Bangazon Platform""" + permission_classes = (IsAuthenticatedOrReadOnly,) + + def create(self, request): + """ + @api {POST} /products POST new product + @apiName CreateProduct + @apiGroup Product + + @apiHeader {String} Authorization Auth token + @apiHeaderExample {String} Authorization + Token 9ba45f09651c5b0c404f37a2d2572c026c146611 + + @apiParam {String} name Short form name of product + @apiParam {Number} price Cost of product + @apiParam {String} description Long form description of product + @apiParam {Number} quantity Number of items to sell + @apiParam {String} location City where product is located + @apiParam {Number} category_id Category of product + @apiParamExample {json} Input + { + "name": "Kite", + "price": 14.99, + "description": "It flies high", + "quantity": 60, + "location": "Pittsburgh", + "category_id": 4 + } + + @apiSuccess (200) {Object} product Created product + @apiSuccess (200) {id} product.id Product Id + @apiSuccess (200) {String} product.name Short form name of product + @apiSuccess (200) {String} product.description Long form description of product + @apiSuccess (200) {Number} product.price Cost of product + @apiSuccess (200) {Number} product.quantity Number of items to sell + @apiSuccess (200) {Date} product.created_date City where product is located + @apiSuccess (200) {String} product.location City where product is located + @apiSuccess (200) {String} product.image_path Path to product image + @apiSuccess (200) {Number} product.average_rating Average customer rating of product + @apiSuccess (200) {Number} product.number_sold How many items have been purchased + @apiSuccess (200) {Object} product.category Category of product + @apiSuccessExample {json} Success + { + "id": 101, + "url": "http://localhost:8000/products/101", + "name": "Kite", + "price": 14.99, + "number_sold": 0, + "description": "It flies high", + "quantity": 60, + "created_date": "2019-10-23", + "location": "Pittsburgh", + "image_path": null, + "average_rating": 0, + "category": { + "url": "http://localhost:8000/productcategories/6", + "name": "Games/Toys" + } + } + """ + new_product = Product() + new_product.name = request.data["name"] + new_product.price = request.data["price"] + new_product.description = request.data["description"] + new_product.quantity = request.data["quantity"] + new_product.location = request.data["location"] + + customer = Customer.objects.get(user=request.auth.user) + new_product.customer = customer + + product_category = ProductCategory.objects.get(pk=request.data["category_id"]) + new_product.category = product_category + + if "image_path" in request.data: + format, imgstr = request.data["image_path"].split(';base64,') + ext = format.split('/')[-1] + data = ContentFile(base64.b64decode(imgstr), name=f'{new_product.id}-{request.data["name"]}.{ext}') + + new_product.image_path = data + + new_product.save() + + serializer = ProductSerializer( + new_product, context={'request': request}) + + return Response(serializer.data, status=status.HTTP_201_CREATED) + + def retrieve(self, request, pk=None): + """ + @api {GET} /products/:id GET product + @apiName GetProduct + @apiGroup Product + + @apiParam {id} id Product Id + + @apiSuccess (200) {Object} product Created product + @apiSuccess (200) {id} product.id Product Id + @apiSuccess (200) {String} product.name Short form name of product + @apiSuccess (200) {String} product.description Long form description of product + @apiSuccess (200) {Number} product.price Cost of product + @apiSuccess (200) {Number} product.quantity Number of items to sell + @apiSuccess (200) {Date} product.created_date City where product is located + @apiSuccess (200) {String} product.location City where product is located + @apiSuccess (200) {String} product.image_path Path to product image + @apiSuccess (200) {Number} product.average_rating Average customer rating of product + @apiSuccess (200) {Number} product.number_sold How many items have been purchased + @apiSuccess (200) {Object} product.category Category of product + @apiSuccessExample {json} Success + { + "id": 101, + "url": "http://localhost:8000/products/101", + "name": "Kite", + "price": 14.99, + "number_sold": 0, + "description": "It flies high", + "quantity": 60, + "created_date": "2019-10-23", + "location": "Pittsburgh", + "image_path": null, + "average_rating": 0, + "category": { + "url": "http://localhost:8000/productcategories/6", + "name": "Games/Toys" + } + } + """ + try: + product = Product.objects.get(pk=pk) + serializer = ProductSerializer(product, context={'request': request}) + return Response(serializer.data) + except Exception as ex: + return HttpResponseServerError(ex) + + def update(self, request, pk=None): + """ + @api {PUT} /products/:id PUT changes to product + @apiName UpdateProduct + @apiGroup Product + + @apiHeader {String} Authorization Auth token + @apiHeaderExample {String} Authorization + Token 9ba45f09651c5b0c404f37a2d2572c026c146611 + + @apiParam {id} id Product Id to update + @apiSuccessExample {json} Success + HTTP/1.1 204 No Content + """ + product = Product.objects.get(pk=pk) + product.name = request.data["name"] + product.price = request.data["price"] + product.description = request.data["description"] + product.quantity = request.data["quantity"] + product.created_date = request.data["created_date"] + product.location = request.data["location"] + + customer = Customer.objects.get(user=request.auth.user) + product.customer = customer + + product_category = ProductCategory.objects.get(pk=request.data["category_id"]) + product.category = product_category + product.save() + + return Response({}, status=status.HTTP_204_NO_CONTENT) + + def destroy(self, request, pk=None): + """ + @api {DELETE} /products/:id DELETE product + @apiName DeleteProduct + @apiGroup Product + + @apiHeader {String} Authorization Auth token + @apiHeaderExample {String} Authorization + Token 9ba45f09651c5b0c404f37a2d2572c026c146611 + + @apiParam {id} id Product Id to delete + @apiSuccessExample {json} Success + HTTP/1.1 204 No Content + """ + try: + product = Product.objects.get(pk=pk) + product.delete() + + return Response({}, status=status.HTTP_204_NO_CONTENT) + + except Product.DoesNotExist as ex: + return Response({'message': ex.args[0]}, status=status.HTTP_404_NOT_FOUND) + + except Exception as ex: + return Response({'message': ex.args[0]}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + def list(self, request): + """ + @api {GET} /products GET all products + @apiName ListProducts + @apiGroup Product + + @apiSuccess (200) {Object[]} products Array of products + @apiSuccessExample {json} Success + [ + { + "id": 101, + "url": "http://localhost:8000/products/101", + "name": "Kite", + "price": 14.99, + "number_sold": 0, + "description": "It flies high", + "quantity": 60, + "created_date": "2019-10-23", + "location": "Pittsburgh", + "image_path": null, + "average_rating": 0, + "category": { + "url": "http://localhost:8000/productcategories/6", + "name": "Games/Toys" + } + } + ] + """ + products = Product.objects.all() + + # Support filtering by category and/or quantity + category = self.request.query_params.get('category', None) + quantity = self.request.query_params.get('quantity', None) + order = self.request.query_params.get('order_by', None) + direction = self.request.query_params.get('direction', None) + number_sold = self.request.query_params.get('number_sold', None) + + if order is not None: + order_filter = order + + if direction is not None: + if direction == "desc": + order_filter = f'-{order}' + + products = products.order_by(order_filter) + + if category is not None: + products = products.filter(category__id=category) + + if quantity is not None: + products = products.order_by("-created_date")[:int(quantity)] + + if number_sold is not None: + def sold_filter(product): + if product.number_sold <= int(number_sold): + return True + return False + + products = filter(sold_filter, products) + + serializer = ProductSerializer( + products, many=True, context={'request': request}) + return Response(serializer.data) diff --git a/bangazonapi/views/productcategory.py b/bangazonapi/views/productcategory.py new file mode 100644 index 0000000..260f766 --- /dev/null +++ b/bangazonapi/views/productcategory.py @@ -0,0 +1,66 @@ +""" + Author: Daniel Krusch + Purpose: To convert product category data to json + Methods: GET, POST +""" + +"""View module for handling requests about product categories""" +from django.http import HttpResponseServerError +from rest_framework.viewsets import ViewSet +from rest_framework.response import Response +from rest_framework import serializers +from rest_framework import status +from bangazonapi.models import ProductCategory +from rest_framework.permissions import IsAuthenticatedOrReadOnly + + +class ProductCategorySerializer(serializers.HyperlinkedModelSerializer): + """JSON serializer for product category""" + class Meta: + model = ProductCategory + url = serializers.HyperlinkedIdentityField( + view_name='productcategory', + lookup_field='id' + ) + fields = ('id', 'url', 'name') + + +class ProductCategories(ViewSet): + """Categories for products""" + permission_classes = (IsAuthenticatedOrReadOnly,) + + def create(self, request): + """Handle POST operations + + Returns: + Response -- JSON serialized product category instance + """ + new_product_category = ProductCategory() + new_product_category.name = request.data["name"] + new_product_category.save() + + serializer = ProductCategorySerializer(new_product_category, context={'request': request}) + + return Response(serializer.data, status=status.HTTP_201_CREATED) + + def retrieve(self, request, pk=None): + """Handle GET requests for single category""" + try: + category = ProductCategory.objects.get(pk=pk) + serializer = ProductCategorySerializer(category, context={'request': request}) + return Response(serializer.data) + except Exception as ex: + return HttpResponseServerError(ex) + + def list(self, request): + """Handle GET requests to ProductCategory resource""" + product_category = ProductCategory.objects.all() + + # Support filtering ProductCategorys by area id + # name = self.request.query_params.get('name', None) + # if name is not None: + # ProductCategories = ProductCategories.filter(name=name) + + serializer = ProductCategorySerializer( + product_category, many=True, context={'request': request}) + return Response(serializer.data) diff --git a/bangazonapi/views/profile.py b/bangazonapi/views/profile.py new file mode 100644 index 0000000..e648635 --- /dev/null +++ b/bangazonapi/views/profile.py @@ -0,0 +1,369 @@ +"""View module for handling requests about customer profiles""" +import datetime +from django.http import HttpResponseServerError +from django.contrib.auth.models import User +from rest_framework import serializers, status +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticatedOrReadOnly +from rest_framework.response import Response +from rest_framework.viewsets import ViewSet +from bangazonapi.models import Order, Customer, Product, OrderProduct, Favorite +from .product import ProductSerializer +from .order import OrderSerializer + +class Profile(ViewSet): + """Request handlers for user profile info in the Bangazon Platform""" + permission_classes = (IsAuthenticatedOrReadOnly,) + + def list(self, request): + """ + @api {GET} /profile GET user profile info + @apiName GetProfile + @apiGroup UserProfile + + @apiHeader {String} Authorization Auth token + @apiHeaderExample {String} Authorization + Token 9ba45f09651c5b0c404f37a2d2572c026c146611 + + @apiSuccess (200) {Number} id Profile id + @apiSuccess (200) {String} url URI of customer profile + @apiSuccess (200) {Object} user Related user object + @apiSuccess (200) {String} user.first_name Customer first name + @apiSuccess (200) {String} user.last_name Customer last name + @apiSuccess (200) {String} user.email Customer email + @apiSuccess (200) {String} phone_number Customer phone number + @apiSuccess (200) {String} address Customer address + @apiSuccess (200) {Object[]} payment_types Array of user's payment types + + @apiSuccessExample {json} Success + HTTP/1.1 200 OK + { + "id": 7, + "url": "http://localhost:8000/customers/7", + "user": { + "first_name": "Brenda", + "last_name": "Long", + "email": "brenda@brendalong.com" + }, + "phone_number": "555-1212", + "address": "100 Indefatiguable Way", + "payment_types": [ + { + "url": "http://localhost:8000/paymenttypes/3", + "deleted": null, + "merchant_name": "Visa", + "account_number": "fj0398fjw0g89434", + "expiration_date": "2020-03-01", + "create_date": "2019-03-11", + "customer": "http://localhost:8000/customers/7" + } + ] + } + """ + try: + current_user = Customer.objects.get(user__id=4) + serializer = ProfileSerializer(current_user, many=False, context={'request': request}) + return Response(serializer.data) + except Exception as ex: + return HttpResponseServerError(ex) + + @action(methods=['get', 'post', 'delete'], detail=False) + def cart(self, request): + """Shopping cart manipulation""" + + current_user = Customer.objects.get(user=request.auth.user) + + if request.method == "DELETE": + """ + @api {DELETE} /profile/cart DELETE all line items in cart + @apiName DeleteCart + @apiGroup UserProfile + + @apiHeader {String} Authorization Auth token + @apiHeaderExample {String} Authorization + Token 9ba45f09651c5b0c404f37a2d2572c026c146611 + + @apiSuccessExample {json} Success + HTTP/1.1 204 No Content + @apiError (404) {String} message Not found message. + """ + try: + open_order = Order.objects.get(customer=current_user, payment_type=None) + line_items = OrderProduct.objects.filter(order=open_order) + line_items.delete() + open_order.delete() + except Order.DoesNotExist as ex: + return Response({'message': ex.args[0]}, status=status.HTTP_404_NOT_FOUND) + + return Response({}, status=status.HTTP_204_NO_CONTENT) + + if request.method == "GET": + """ + @api {GET} /profile/cart GET line items in cart + @apiName GetCart + @apiGroup UserProfile + + @apiHeader {String} Authorization Auth token + @apiHeaderExample {String} Authorization + Token 9ba45f09651c5b0c404f37a2d2572c026c146611 + + @apiSuccess (200) {Number} id Order cart + @apiSuccess (200) {String} url URL of order + @apiSuccess (200) {String} created_date Date created + @apiSuccess (200) {Object} payment_type Payment Id used to complete order + @apiSuccess (200) {String} customer URI for customer + @apiSuccess (200) {Number} size Number of items in cart + @apiSuccess (200) {Object[]} line_items Line items in cart + @apiSuccess (200) {Number} line_items.id Line item id + @apiSuccess (200) {Object} line_items.product Product in cart + @apiSuccessExample {json} Success + { + "id": 2, + "url": "http://localhost:8000/orders/2", + "created_date": "2019-04-12", + "payment_type": null, + "customer": "http://localhost:8000/customers/7", + "line_items": [ + { + "id": 4, + "product": { + "id": 52, + "url": "http://localhost:8000/products/52", + "name": "900", + "price": 1296.98, + "number_sold": 0, + "description": "1987 Saab", + "quantity": 2, + "created_date": "2019-03-19", + "location": "Vratsa", + "image_path": null, + "average_rating": 0, + "category": { + "url": "http://localhost:8000/productcategories/2", + "name": "Auto" + } + } + } + ], + "size": 1 + } + @apiError (404) {String} message Not found message + """ + try: + open_order = Order.objects.get(customer=current_user, payment_type=None) + line_items = OrderProduct.objects.filter(order=open_order) + line_items = LineItemSerializer(line_items, many=True, context={'request': request}) + + cart = {} + cart["order"] = OrderSerializer(open_order, many=False, context={'request': request}).data + cart["order"]["line_items"] = line_items.data + cart["order"]["size"] = len(line_items.data) + + + except Order.DoesNotExist as ex: + return Response({'message': ex.args[0]}, status=status.HTTP_404_NOT_FOUND) + + return Response(cart["order"]) + + if request.method == "POST": + """ + @api {POST} /profile/cart POST new product to cart + @apiName AddToCart + @apiGroup UserProfile + + @apiHeader {String} Authorization Auth token + @apiHeaderExample {String} Authorization + Token 9ba45f09651c5b0c404f37a2d2572c026c146611 + + @apiSuccess (200) {Object} line_item Line items in cart + @apiSuccess (200) {Number} line_item.id Line item id + @apiSuccess (200) {Object} line_item.product Product in cart + @apiSuccess (200) {Object} line_item.order Open order for cart + @apiSuccessExample {json} Success + { + "id": 14, + "product": { + "url": "http://localhost:8000/products/52", + "deleted": null, + "name": "900", + "price": 1296.98, + "description": "1987 Saab", + "quantity": 2, + "created_date": "2019-03-19", + "location": "Vratsa", + "image_path": null, + "customer": "http://localhost:8000/customers/7", + "category": "http://localhost:8000/productcategories/2" + }, + "order": { + "url": "http://localhost:8000/orders/2", + "created_date": "2019-04-12", + "customer": "http://localhost:8000/customers/7", + "payment_type": null + } + } + + @apiError (404) {String} message Not found message + """ + + try: + open_order = Order.objects.get(customer=current_user) + print(open_order) + except Order.DoesNotExist as ex: + open_order = Order() + open_order.created_date = datetime.datetime.now() + open_order.customer = current_user + open_order.save() + + line_item = OrderProduct() + line_item.product = Product.objects.get( + pk=request.data["product_id"]) + line_item.order = open_order + line_item.save() + + line_item_json = LineItemSerializer(line_item, many=False, context={'request': request}) + + return Response(line_item_json.data) + + return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED) + + @action(methods=['get'], detail=False) + def favoritesellers(self, request): + """ + @api {GET} /profile/favoritesellers GET favorite sellers + @apiName GetFavoriteSellers + @apiGroup UserProfile + + @apiHeader {String} Authorization Auth token + @apiHeaderExample {String} Authorization + Token 9ba45f09651c5b0c404f37a2d2572c026c146611 + + @apiSuccess (200) {id} id Favorite id + @apiSuccess (200) {Object} seller Favorited seller + @apiSuccess (200) {String} seller.url Seller URI + @apiSuccess (200) {String} seller.phone_number Seller phone number + @apiSuccess (200) {String} seller.address Seller address + @apiSuccess (200) {String} seller.user Seller user profile URI + @apiSuccessExample {json} Success + [ + { + "id": 1, + "seller": { + "url": "http://localhost:8000/customers/5", + "phone_number": "555-1212", + "address": "100 Endless Way", + "user": "http://localhost:8000/users/6" + } + }, + { + "id": 2, + "seller": { + "url": "http://localhost:8000/customers/6", + "phone_number": "555-1212", + "address": "100 Dauntless Way", + "user": "http://localhost:8000/users/7" + } + }, + { + "id": 3, + "seller": { + "url": "http://localhost:8000/customers/7", + "phone_number": "555-1212", + "address": "100 Indefatiguable Way", + "user": "http://localhost:8000/users/8" + } + } + ] + """ + customer = Customer.objects.get(user=request.auth.user) + favorites = Favorite.objects.filter(customer=customer) + + serializer = FavoriteSerializer( + favorites, many=True, context={'request': request}) + return Response(serializer.data) + + +class LineItemSerializer(serializers.HyperlinkedModelSerializer): + """JSON serializer for products + + Arguments: + serializers + """ + product = ProductSerializer(many=False) + class Meta: + model = OrderProduct + fields = ('id', 'product') + depth = 1 + +class UserSerializer(serializers.HyperlinkedModelSerializer): + """JSON serializer for customer profile + + Arguments: + serializers + """ + class Meta: + model = User + fields = ('first_name', 'last_name', 'email') + depth = 1 + + +class ProfileSerializer(serializers.HyperlinkedModelSerializer): + """JSON serializer for customer profile + + Arguments: + serializers + """ + user = UserSerializer(many=False) + + class Meta: + model = Customer + url = serializers.HyperlinkedIdentityField( + view_name='customer', + lookup_field='id', + ) + fields = ('id', 'url', 'user', 'phone_number', 'address', 'payment_types') + depth = 1 + + +class FavoriteUserSerializer(serializers.HyperlinkedModelSerializer): + """JSON serializer for favorite sellers user + + Arguments: + serializers + """ + + class Meta: + model = User + fields = ('first_name', 'last_name', 'username') + depth = 1 + + +class FavoriteSellerSerializer(serializers.HyperlinkedModelSerializer): + """JSON serializer for favorite sellers + + Arguments: + serializers + """ + + user = FavoriteUserSerializer(many=False) + + class Meta: + model = Customer + fields = ('id', 'url', 'user',) + depth = 1 + + + +class FavoriteSerializer(serializers.HyperlinkedModelSerializer): + """JSON serializer for favorites + + Arguments: + serializers + """ + + seller = FavoriteSellerSerializer(many=False) + + class Meta: + model = Favorite + fields = ('id', 'seller') + depth = 2 diff --git a/bangazonapi/views/register.py b/bangazonapi/views/register.py new file mode 100644 index 0000000..5415635 --- /dev/null +++ b/bangazonapi/views/register.py @@ -0,0 +1,80 @@ +"""Register user""" +import json +from django.http import HttpResponse, HttpResponseNotAllowed +from django.contrib.auth import authenticate +from django.contrib.auth.models import User +from django.views.decorators.csrf import csrf_exempt +from rest_framework import status +from rest_framework.authtoken.models import Token +from bangazonapi.models import Customer + + +@csrf_exempt +def login_user(request): + '''Handles the authentication of a user + + Method arguments: + request -- The full HTTP request object + ''' + + body = request.body.decode('utf-8') + req_body = json.loads(body) + + # If the request is a HTTP POST, try to pull out the relevant information. + if request.method == 'POST': + + # Use the built-in authenticate method to verify + name = req_body['username'] + pass_word = req_body['password'] + authenticated_user = authenticate(username=name, password=pass_word) + + # If authentication was successful, respond with their token + if authenticated_user is not None: + token = Token.objects.get(user=authenticated_user) + data = json.dumps({"valid": True, "token": token.key, "id": authenticated_user.id}) + return HttpResponse(data, content_type='application/json') + + else: + # Bad login details were provided. So we can't log the user in. + data = json.dumps({"valid": False}) + return HttpResponse(data, content_type='application/json') + + return HttpResponseNotAllowed(permitted_methods=['POST']) + + +@csrf_exempt +def register_user(request): + '''Handles the creation of a new user for authentication + + Method arguments: + request -- The full HTTP request object + ''' + + # Load the JSON string of the request body into a dict + req_body = json.loads(request.body.decode()) + + # Create a new user by invoking the `create_user` helper method + # on Django's built-in User model + new_user = User.objects.create_user( + username=req_body['username'], + email=req_body['email'], + password=req_body['password'], + first_name=req_body['first_name'], + last_name=req_body['last_name'] + ) + + customer = Customer.objects.create( + phone_number=req_body['phone_number'], + address=req_body['address'], + user=new_user + ) + + # Commit the user to the database by saving it + customer.save() + + # Use the REST Framework's token generator on the new user account + token = Token.objects.create(user=new_user) + + # Return the token to the client + data = json.dumps({"token": token.key, "id": new_user.id}) + return HttpResponse(data, content_type='application/json', status=status.HTTP_201_CREATED) diff --git a/bangazonapi/views/user.py b/bangazonapi/views/user.py new file mode 100644 index 0000000..0e9d03b --- /dev/null +++ b/bangazonapi/views/user.py @@ -0,0 +1,52 @@ +from django.http import HttpResponseServerError +from rest_framework.viewsets import ViewSet +from rest_framework.response import Response +from rest_framework import serializers +from rest_framework import status +from django.contrib.auth.models import User + + +class UserSerializer(serializers.HyperlinkedModelSerializer): + """JSON serializer for Users + + Arguments: + serializers + """ + class Meta: + model = User + url = serializers.HyperlinkedIdentityField( + view_name='user', + lookup_field = 'id' + ) + fields = ('id', 'url', 'username', 'password', 'first_name', 'last_name', 'email', 'is_active', 'date_joined') + + +class Users(ViewSet): + """Users for Bangazon + Purpose: Allow a user to communicate with the Bangazon database to GET PUT POST and DELETE Users. + Methods: GET PUT(id) POST +""" + + + def retrieve(self, request, pk=None): + """Handle GET requests for single customer + Purpose: Allow a user to communicate with the Bangazon database to retrieve one user + Methods: GET + Returns: + Response -- JSON serialized customer instance + """ + try: + user = User.objects.get(pk=pk) + serializer = UserSerializer(user, context={'request': request}) + return Response(serializer.data) + except Exception as ex: + return HttpResponseServerError(ex) + + + + def list(self, request): + """Handle GET requests to user resource""" + users = User.objects.all() + serializer = UserSerializer( + users, many=True, context={'request': request}) + return Response(serializer.data) \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..e82cb9d --- /dev/null +++ b/manage.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'bangazon.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/renderdocs.sh b/renderdocs.sh new file mode 100755 index 0000000..4671c95 --- /dev/null +++ b/renderdocs.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +apidoc -v --debug -i ./bangazonapi/views -f .py -o docs/ \ No newline at end of file diff --git a/seed_data.sh b/seed_data.sh new file mode 100755 index 0000000..234ccf0 --- /dev/null +++ b/seed_data.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +rm -rf bangazonapi/migrations +rm db.sqlite3 +python manage.py makemigrations bangazonapi +python manage.py migrate +python manage.py loaddata users +python manage.py loaddata tokens +python manage.py loaddata customers +python manage.py loaddata product_category +python manage.py loaddata product +python manage.py loaddata productrating +python manage.py loaddata payment +python manage.py loaddata order +python manage.py loaddata order_product +python manage.py loaddata favoritesellers diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..81e271a --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +from .product import ProductTests +from .order import OrderTests +from .payments import PaymentTests \ No newline at end of file diff --git a/tests/order.py b/tests/order.py new file mode 100644 index 0000000..a5a7bad --- /dev/null +++ b/tests/order.py @@ -0,0 +1,84 @@ +import json +from rest_framework import status +from rest_framework.test import APITestCase + + +class OrderTests(APITestCase): + def setUp(self) -> None: + """ + Create a new account and create sample category + """ + url = "/register" + data = {"username": "steve", "password": "Admin8*", "email": "steve@stevebrownlee.com", + "address": "100 Infinity Way", "phone_number": "555-1212", "first_name": "Steve", "last_name": "Brownlee"} + response = self.client.post(url, data, format='json') + json_response = json.loads(response.content) + self.token = json_response["token"] + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + # Create a product category + url = "/productcategories" + data = {"name": "Sporting Goods"} + self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token) + response = self.client.post(url, data, format='json') + + # Create a product + url = "/products" + data = { "name": "Kite", "price": 14.99, "quantity": 60, "description": "It flies high", "category_id": 1, "location": "Pittsburgh" } + self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token) + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + + def test_add_product_to_order(self): + """ + Ensure we can add a product to an order. + """ + # Add product to order + url = "/cart" + data = { "product_id": 1 } + self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token) + response = self.client.post(url, data, format='json') + + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + # Get cart and verify product was added + url = "/cart" + self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token) + response = self.client.get(url, None, format='json') + json_response = json.loads(response.content) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(json_response["id"], 1) + self.assertEqual(json_response["size"], 1) + self.assertEqual(len(json_response["lineitems"]), 1) + + + def test_remove_product_from_order(self): + """ + Ensure we can remove a product from an order. + """ + # Add product + self.test_add_product_to_order() + + # Remove product from cart + url = "/cart/1" + data = { "product_id": 1 } + self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token) + response = self.client.delete(url, data, format='json') + + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + # Get cart and verify product was removed + url = "/cart" + self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token) + response = self.client.get(url, None, format='json') + json_response = json.loads(response.content) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(json_response["size"], 0) + self.assertEqual(len(json_response["lineitems"]), 0) + + # TODO: Complete order by adding payment type + + # TODO: New line item is not added to closed order \ No newline at end of file diff --git a/tests/payments.py b/tests/payments.py new file mode 100644 index 0000000..73fe6d1 --- /dev/null +++ b/tests/payments.py @@ -0,0 +1,43 @@ +import datetime +import json +from rest_framework import status +from rest_framework.test import APITestCase + + +class PaymentTests(APITestCase): + def setUp(self) -> None: + """ + Create a new account and create sample category + """ + url = "/register" + data = {"username": "steve", "password": "Admin8*", "email": "steve@stevebrownlee.com", + "address": "100 Infinity Way", "phone_number": "555-1212", "first_name": "Steve", "last_name": "Brownlee"} + response = self.client.post(url, data, format='json') + json_response = json.loads(response.content) + self.token = json_response["token"] + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + + def test_create_payment_type(self): + """ + Ensure we can add a payment type for a customer. + """ + # Add product to order + url = "/paymenttypes" + data = { + "merchant_name": "American Express", + "account_number": "111-1111-1111", + "expiration_date": "2024-12-31", + "create_date": datetime.date.today() + } + self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token) + response = self.client.post(url, data, format='json') + json_response = json.loads(response.content) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(json_response["merchant_name"], "American Express") + self.assertEqual(json_response["account_number"], "111-1111-1111") + self.assertEqual(json_response["expiration_date"], "2024-12-31") + self.assertEqual(json_response["create_date"], str(datetime.date.today())) + + # TODO: Delete payment type \ No newline at end of file diff --git a/tests/product.py b/tests/product.py new file mode 100644 index 0000000..6ea0649 --- /dev/null +++ b/tests/product.py @@ -0,0 +1,100 @@ +import json +import datetime +from rest_framework import status +from rest_framework.test import APITestCase + + +class ProductTests(APITestCase): + def setUp(self) -> None: + """ + Create a new account and create sample category + """ + url = "/register" + data = {"username": "steve", "password": "Admin8*", "email": "steve@stevebrownlee.com", + "address": "100 Infinity Way", "phone_number": "555-1212", "first_name": "Steve", "last_name": "Brownlee"} + response = self.client.post(url, data, format='json') + json_response = json.loads(response.content) + self.token = json_response["token"] + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + url = "/productcategories" + data = {"name": "Sporting Goods"} + self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token) + + response = self.client.post(url, data, format='json') + json_response = json.loads(response.content) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(json_response["name"], "Sporting Goods") + + def test_create_product(self): + """ + Ensure we can create a new product. + """ + url = "/products" + data = { + "name": "Kite", + "price": 14.99, + "quantity": 60, + "description": "It flies high", + "category_id": 1, + "location": "Pittsburgh" + } + self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token) + response = self.client.post(url, data, format='json') + json_response = json.loads(response.content) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(json_response["name"], "Kite") + self.assertEqual(json_response["price"], 14.99) + self.assertEqual(json_response["quantity"], 60) + self.assertEqual(json_response["description"], "It flies high") + self.assertEqual(json_response["location"], "Pittsburgh") + + def test_update_product(self): + """ + Ensure we can update a product. + """ + self.test_create_product() + + url = "/products/1" + data = { + "name": "Kite", + "price": 24.99, + "quantity": 40, + "description": "It flies very high", + "category_id": 1, + "created_date": datetime.date.today(), + "location": "Pittsburgh" + } + self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token) + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + response = self.client.get(url, data, format='json') + json_response = json.loads(response.content) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(json_response["name"], "Kite") + self.assertEqual(json_response["price"], 24.99) + self.assertEqual(json_response["quantity"], 40) + self.assertEqual(json_response["description"], "It flies very high") + self.assertEqual(json_response["location"], "Pittsburgh") + + def test_get_all_products(self): + """ + Ensure we can get a collection of products. + """ + self.test_create_product() + self.test_create_product() + self.test_create_product() + + url = "/products" + + response = self.client.get(url, None, format='json') + json_response = json.loads(response.content) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(json_response), 3) + + # TODO: Delete product + + # TODO: Product can be rated. Assert average rating exists.