├── .gitignore ├── Procfile ├── README.md ├── amazon ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py ├── cart ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20201003_1259.py │ ├── 0003_auto_20201003_1333.py │ └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── manage.py ├── media └── products │ └── images │ ├── atwood.jpg │ ├── bengoo.jpg │ ├── gionee-pioneer-p5l-na-original-imaehhtpmwjg3uuh.jpeg │ └── job.jpg ├── requirements.txt └── shop ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── migrations ├── 0001_initial.py ├── 0002_auto_20200930_0715.py ├── 0003_auto_20200930_0716.py ├── 0004_auto_20200930_1049.py ├── 0005_product_stock.py ├── 0006_remove_product_available.py └── __init__.py ├── models.py ├── request_exposer.py ├── static ├── icons │ ├── favicon.ico │ ├── favicon.png │ ├── icon.png │ └── logo.png └── shop │ ├── cart.js │ ├── message.js │ ├── script.js │ └── styles.css ├── templates ├── auth │ ├── signin.html │ └── signup.html ├── base.html ├── base_auth.html └── shop │ ├── checkout.html │ ├── detail.html │ ├── index.html │ └── shopping_cart.html ├── templatetags ├── __init__.py └── get_image_url.py ├── tests.py ├── urls.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn amazon.wsgi -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # amazon-clone-django 2 | A simple amazon.com clone built using django. 3 | 4 | For styling I used Sass(scss). 5 | 6 | For serving media files, I used Amazon AWS S3 buckets. 7 | 8 | You can see the live demo here at heroku - [https://amazondjangoclone.herokuapp.com/](https://amazondjangoclone.herokuapp.com/) 9 | 10 | 11 | -------------------------------------------------------------------------------- /amazon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-coding-pie/amazon-clone-django/0d1b22aef11493a520f95c745a416b53769b44be/amazon/__init__.py -------------------------------------------------------------------------------- /amazon/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for amazon project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'amazon.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /amazon/settings.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import os 3 | import django_heroku 4 | 5 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 6 | BASE_DIR = Path(__file__).resolve().parent.parent 7 | 8 | # Quick-start development settings - unsuitable for production 9 | # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ 10 | 11 | # SECURITY WARNING: keep the secret key used in production secret! 12 | SECRET_KEY = os.environ.get('SECRET_KEY') 13 | # SECURITY WARNING: don't run with debug turned on in production! 14 | DEBUG = (os.environ.get('DEBUG_VALUE') == 'True') 15 | 16 | ALLOWED_HOSTS = ['amazondjangoclone.herokuapp.com'] 17 | 18 | 19 | # Application definition 20 | 21 | INSTALLED_APPS = [ 22 | 'django.contrib.admin', 23 | 'django.contrib.auth', 24 | 'django.contrib.contenttypes', 25 | 'django.contrib.sessions', 26 | 'django.contrib.messages', 27 | 'django.contrib.staticfiles', 28 | 'shop.apps.ShopConfig', 29 | 'ckeditor', 30 | 'cart.apps.CartConfig', 31 | ] 32 | 33 | MIDDLEWARE = [ 34 | 'django.middleware.security.SecurityMiddleware', 35 | 'django.contrib.sessions.middleware.SessionMiddleware', 36 | 'django.middleware.common.CommonMiddleware', 37 | 'django.middleware.csrf.CsrfViewMiddleware', 38 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 39 | 'django.contrib.messages.middleware.MessageMiddleware', 40 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 41 | 'shop.request_exposer.RequestExposerMiddleware' 42 | ] 43 | 44 | ROOT_URLCONF = 'amazon.urls' 45 | 46 | TEMPLATES = [ 47 | { 48 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 49 | 'DIRS': [], 50 | 'APP_DIRS': True, 51 | 'OPTIONS': { 52 | 'context_processors': [ 53 | 'django.template.context_processors.debug', 54 | 'django.template.context_processors.request', 55 | 'django.contrib.auth.context_processors.auth', 56 | 'django.contrib.messages.context_processors.messages', 57 | ], 58 | }, 59 | }, 60 | ] 61 | 62 | WSGI_APPLICATION = 'amazon.wsgi.application' 63 | 64 | 65 | # Database 66 | # https://docs.djangoproject.com/en/3.1/ref/settings/#databases 67 | 68 | DATABASES = { 69 | 'default': { 70 | 'ENGINE': 'django.db.backends.sqlite3', 71 | 'NAME': BASE_DIR / 'db.sqlite3', 72 | } 73 | } 74 | 75 | 76 | # Password validation 77 | # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators 78 | 79 | AUTH_PASSWORD_VALIDATORS = [ 80 | { 81 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 82 | }, 83 | { 84 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 85 | }, 86 | { 87 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 88 | }, 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 91 | }, 92 | ] 93 | 94 | 95 | # Internationalization 96 | # https://docs.djangoproject.com/en/3.1/topics/i18n/ 97 | 98 | LANGUAGE_CODE = 'en-us' 99 | 100 | TIME_ZONE = 'UTC' 101 | 102 | USE_I18N = True 103 | 104 | USE_L10N = True 105 | 106 | USE_TZ = True 107 | 108 | 109 | # Static files (CSS, JavaScript, Images) 110 | # https://docs.djangoproject.com/en/3.1/howto/static-files/ 111 | 112 | STATIC_URL = '/static/' 113 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') 114 | 115 | MEDIA_URL = '/media/' 116 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 117 | 118 | CKEDITOR_CONFIGS = { 119 | 'default': { 120 | 'toolbar': None, 121 | }, 122 | } 123 | 124 | CART_SESSION_ID = 'cart' 125 | 126 | LOGIN_URL = "shop:signin" 127 | 128 | django_heroku.settings(locals()) -------------------------------------------------------------------------------- /amazon/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | from django.conf import settings 4 | from django.conf.urls.static import static 5 | 6 | urlpatterns = [ 7 | path('admin/', admin.site.urls), 8 | path('', include('shop.urls')), 9 | path('cart/', include('cart.urls')), 10 | ] 11 | 12 | if settings.DEBUG: 13 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 14 | -------------------------------------------------------------------------------- /amazon/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for amazon project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'amazon.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /cart/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-coding-pie/amazon-clone-django/0d1b22aef11493a520f95c745a416b53769b44be/cart/__init__.py -------------------------------------------------------------------------------- /cart/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import CartItem, Cart 3 | 4 | # Register your models here. 5 | @admin.register(CartItem) 6 | class CartItemAdmin(admin.ModelAdmin): 7 | list_display = ['product', 'price', 'quantity', 'cart'] 8 | 9 | @admin.register(Cart) 10 | class CartAdmin(admin.ModelAdmin): 11 | list_display = ['user'] -------------------------------------------------------------------------------- /cart/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CartConfig(AppConfig): 5 | name = 'cart' 6 | -------------------------------------------------------------------------------- /cart/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-10-03 12:46 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('shop', '0004_auto_20200930_1049'), 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Cart', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 23 | ], 24 | ), 25 | migrations.CreateModel( 26 | name='CartItem', 27 | fields=[ 28 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 29 | ('price', models.DecimalField(decimal_places=2, max_digits=10)), 30 | ('quantity', models.IntegerField(default=0)), 31 | ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cart.cart')), 32 | ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.product')), 33 | ], 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /cart/migrations/0002_auto_20201003_1259.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-10-03 12:59 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('cart', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='cart', 18 | name='user', 19 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cart', to=settings.AUTH_USER_MODEL), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /cart/migrations/0003_auto_20201003_1333.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-10-03 13:33 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('cart', '0002_auto_20201003_1259'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='cartitem', 16 | name='cart', 17 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cart_items', to='cart.cart'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /cart/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-coding-pie/amazon-clone-django/0d1b22aef11493a520f95c745a416b53769b44be/cart/migrations/__init__.py -------------------------------------------------------------------------------- /cart/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | from shop.models import Product 4 | 5 | class Cart(models.Model): 6 | user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='cart') 7 | 8 | def __str__(self): 9 | return f"Cart of ${self.user.username}" 10 | 11 | class CartItem(models.Model): 12 | product = models.ForeignKey(Product, on_delete=models.CASCADE) 13 | price = models.DecimalField(max_digits=10, decimal_places=2) 14 | quantity = models.IntegerField(default=0) 15 | cart = models.ForeignKey(Cart, on_delete=models.CASCADE, related_name='cart_items') 16 | 17 | def __str__(self): 18 | return self.product.name 19 | -------------------------------------------------------------------------------- /cart/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /cart/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .import views 3 | 4 | app_name = 'cart' 5 | 6 | urlpatterns = [ 7 | path('', views.get_cart, name='get_cart'), 8 | path('add_to_cart/', views.add_to_cart, name='add_to_cart'), 9 | path('remove_from_cart/', views.remove_from_cart, name='remove_from_cart'), 10 | path('remove_cart/', views.remove_cart, name='remove_cart'), 11 | ] -------------------------------------------------------------------------------- /cart/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.http import JsonResponse 3 | from django.views.decorators.http import require_http_methods 4 | from shop.models import Product 5 | from .models import CartItem, Cart 6 | 7 | # Create your views here. 8 | def get_cart(request): 9 | # create a session with cart 10 | item_count = 0 11 | total = 0 12 | if request.user.is_authenticated: 13 | if Cart.objects.filter(user=request.user): 14 | cart = Cart.objects.filter(user=request.user)[0] 15 | items = CartItem.objects.filter(cart=cart) 16 | 17 | item_count = sum(item.quantity for item in items) 18 | total = sum(item.quantity * item.price for item in items) 19 | else: 20 | cart = request.session.get(settings.CART_SESSION_ID) 21 | 22 | if not cart: 23 | # create a new cart 24 | cart = request.session[settings.CART_SESSION_ID] = {} 25 | 26 | item_count = sum([product['quantity'] for product in cart.values()]) 27 | total = sum([product['quantity'] * float(product['price']) for product in cart.values()]) 28 | 29 | return JsonResponse({ 30 | 'status': 200, 31 | 'item_count': item_count, 32 | 'total': total, 33 | }) 34 | 35 | @require_http_methods(['POST']) 36 | def add_to_cart(request): 37 | id = request.POST.get('id') 38 | item = Product.objects.get(id=id) 39 | 40 | if not item: 41 | return JsonResponse({ 42 | 'status': 404, 43 | 'msg': 'Oops item not found' 44 | }) 45 | 46 | item_id = str(item.id) 47 | 48 | product_quantity = 1 49 | item_stock = item.stock 50 | 51 | if request.user.is_authenticated: 52 | cart = Cart.objects.get(user=request.user) 53 | if cart: 54 | product = CartItem.objects.filter(product=item, cart=cart) 55 | if item.stock >= 1: 56 | if product: 57 | # if product is already present 58 | product = product[0] 59 | product.quantity += 1 60 | product.save() 61 | item.stock -= 1 62 | item.save() 63 | 64 | product_quantity = product.quantity 65 | 66 | else: 67 | # create a new product 68 | new_item = CartItem(product=item, price=float(item.price), quantity=1, cart=cart) 69 | new_item.save() 70 | item.stock -= 1 71 | item.save() 72 | product_quantity = 1 73 | item_stock = item.stock 74 | else: 75 | cart = request.session.get(settings.CART_SESSION_ID) 76 | 77 | if not cart: 78 | # create a new cart 79 | cart = request.session[settings.CART_SESSION_ID] = {} 80 | 81 | # check if item is already in the cart 82 | if item.stock >= 1: 83 | if item_id in cart.keys(): 84 | # update the quantity 85 | cart[item_id]['quantity'] += 1 86 | item.stock -= 1 87 | item.save() 88 | 89 | else: 90 | # add as a new item 91 | cart[item_id] = { 92 | "quantity": 1, 93 | "price": str(item.price) 94 | } 95 | item.stock -= 1 96 | item.save() 97 | 98 | product_quantity = cart[item_id]['quantity'] 99 | item_stock = item.stock 100 | 101 | # explicitly say session has been modified 102 | request.session.modified = True 103 | 104 | return JsonResponse({ 105 | 'status': 200, 106 | 'msg': 'Item Added', 107 | 'product_quantity': product_quantity, 108 | 'item_stock': item_stock 109 | }) 110 | 111 | @require_http_methods(['POST']) 112 | def remove_from_cart(request): 113 | id = request.POST.get('id') 114 | item = Product.objects.get(id=id) 115 | 116 | if not item: 117 | return JsonResponse({ 118 | 'status': 404, 119 | 'msg': 'Oops item not found' 120 | }) 121 | 122 | item_id = str(item.id) 123 | 124 | product_quantity = 1 125 | item_stock = item.stock 126 | 127 | if request.user.is_authenticated: 128 | cart = Cart.objects.get(user=request.user) 129 | if cart: 130 | product = CartItem.objects.filter(product=item, cart=cart) 131 | if product: 132 | # if product is already present 133 | product = product[0] 134 | if not (product.quantity - 1) < 1: 135 | product.quantity -= 1 136 | item.stock += 1 137 | item.save() 138 | product.save() 139 | 140 | product_quantity = product.quantity 141 | item_stock = item.stock 142 | 143 | return JsonResponse({ 144 | 'status': 200, 145 | 'msg': 'Item Added', 146 | 'product_quantity': product_quantity, 147 | 'item_stock': item_stock 148 | }) 149 | 150 | @require_http_methods(['POST']) 151 | def remove_cart(request): 152 | id = request.POST.get('id') 153 | item = Product.objects.get(id=id) 154 | 155 | if not item: 156 | return JsonResponse({ 157 | 'status': 404, 158 | 'msg': 'Oops item not found' 159 | }) 160 | 161 | item_id = str(item.id) 162 | 163 | if request.user.is_authenticated: 164 | cart = Cart.objects.get(user=request.user) 165 | if cart: 166 | product = CartItem.objects.filter(product=item, cart=cart) 167 | if product: 168 | product = product[0] 169 | # if product is already present 170 | # first refill the item.stock 171 | item.stock += product.quantity 172 | item.save() 173 | product.delete() 174 | return JsonResponse({ 175 | 'status': 200, 176 | 'msg': 'Item Added' 177 | }) -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'amazon.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /media/products/images/atwood.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-coding-pie/amazon-clone-django/0d1b22aef11493a520f95c745a416b53769b44be/media/products/images/atwood.jpg -------------------------------------------------------------------------------- /media/products/images/bengoo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-coding-pie/amazon-clone-django/0d1b22aef11493a520f95c745a416b53769b44be/media/products/images/bengoo.jpg -------------------------------------------------------------------------------- /media/products/images/gionee-pioneer-p5l-na-original-imaehhtpmwjg3uuh.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-coding-pie/amazon-clone-django/0d1b22aef11493a520f95c745a416b53769b44be/media/products/images/gionee-pioneer-p5l-na-original-imaehhtpmwjg3uuh.jpeg -------------------------------------------------------------------------------- /media/products/images/job.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-coding-pie/amazon-clone-django/0d1b22aef11493a520f95c745a416b53769b44be/media/products/images/job.jpg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.2.10 2 | dj-database-url==0.5.0 3 | Django==3.1.1 4 | django-ckeditor==6.0.0 5 | django-heroku==0.3.1 6 | django-js-asset==1.2.2 7 | gunicorn==20.0.4 8 | Pillow==7.2.0 9 | psycopg2==2.8.6 10 | psycopg2-binary==2.8.6 11 | pytz==2020.1 12 | sqlparse==0.3.1 13 | whitenoise==5.2.0 14 | -------------------------------------------------------------------------------- /shop/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-coding-pie/amazon-clone-django/0d1b22aef11493a520f95c745a416b53769b44be/shop/__init__.py -------------------------------------------------------------------------------- /shop/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Category, Product 3 | 4 | # Register your models here. 5 | @admin.register(Category) 6 | class CategoryAdmin(admin.ModelAdmin): 7 | list_display = ('name', ) 8 | prepopulated_fields = { 9 | 'slug': ('name', ) 10 | } 11 | 12 | @admin.register(Product) 13 | class ProductAdmin(admin.ModelAdmin): 14 | list_display = ('name', 'price', 'manufactured_on', 'category', 'stock') 15 | prepopulated_fields = { 16 | "slug": ("name", ) 17 | } -------------------------------------------------------------------------------- /shop/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ShopConfig(AppConfig): 5 | name = 'shop' 6 | -------------------------------------------------------------------------------- /shop/forms.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django import forms 3 | 4 | class SignupForm(forms.Form): 5 | your_name = forms.CharField(label='Your name', max_length=100) 6 | email = forms.EmailField(label='Email') 7 | password = forms.CharField(label='Password', widget=forms.PasswordInput) 8 | re_enter_password = forms.CharField(label='Re-enter password', widget=forms.PasswordInput) 9 | 10 | def clean_your_name(self): 11 | your_name = self.cleaned_data.get('your_name') 12 | user = User.objects.filter(username=your_name) 13 | 14 | if user: 15 | raise forms.ValidationError("Username already exists!") 16 | 17 | return your_name 18 | 19 | def clean_email(self): 20 | email = self.cleaned_data.get('email') 21 | user = User.objects.filter(email=email) 22 | 23 | if user: 24 | raise forms.ValidationError("Email already taken!") 25 | 26 | return email 27 | 28 | def clean_re_enter_password(self): 29 | password = self.cleaned_data.get('password') 30 | re_enter_password = self.cleaned_data.get('re_enter_password') 31 | 32 | if not re_enter_password == password: 33 | raise forms.ValidationError("Password Doesn't match!") 34 | 35 | return re_enter_password 36 | 37 | def save(self, commit=True): 38 | username = self.cleaned_data.get('your_name') 39 | email = self.cleaned_data.get('email') 40 | password = self.cleaned_data.get('password') 41 | 42 | user = User.objects.create_user( 43 | username=username, 44 | email=email, 45 | password=password 46 | ) 47 | 48 | return user 49 | 50 | class SignInForm(forms.Form): 51 | email = forms.EmailField(label='Email') 52 | password = forms.CharField(label='Password', widget=forms.PasswordInput) -------------------------------------------------------------------------------- /shop/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-26 14:43 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Category', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('name', models.CharField(max_length=50)), 20 | ('slug', models.SlugField(max_length=200, unique=True)), 21 | ], 22 | ), 23 | migrations.CreateModel( 24 | name='Product', 25 | fields=[ 26 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 27 | ('name', models.CharField(max_length=60)), 28 | ('img', models.ImageField(blank=True, upload_to='products/images')), 29 | ('price', models.DecimalField(decimal_places=2, max_digits=6)), 30 | ('description', models.TextField()), 31 | ('slug', models.SlugField(max_length=200, unique=True)), 32 | ('available', models.BooleanField(default=True)), 33 | ('manufactured_on', models.DateTimeField()), 34 | ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='shop.category')), 35 | ], 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /shop/migrations/0002_auto_20200930_0715.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-30 07:15 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('shop', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='category', 15 | options={'ordering': ('name',), 'verbose_name': 'category', 'verbose_name_plural': 'catergories'}, 16 | ), 17 | migrations.AlterModelOptions( 18 | name='product', 19 | options={'ordering': ('name',), 'verbose_name': 'product', 'verbose_name_plural': 'products'}, 20 | ), 21 | migrations.AddField( 22 | model_name='product', 23 | name='features', 24 | field=models.TextField(blank=True), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /shop/migrations/0003_auto_20200930_0716.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-30 07:16 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('shop', '0002_auto_20200930_0715'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='product', 15 | name='name', 16 | field=models.CharField(max_length=200), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /shop/migrations/0004_auto_20200930_1049.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-30 10:49 2 | 3 | import ckeditor.fields 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('shop', '0003_auto_20200930_0716'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='product', 16 | name='features', 17 | field=ckeditor.fields.RichTextField(blank=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /shop/migrations/0005_product_stock.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-10-04 12:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('shop', '0004_auto_20200930_1049'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='product', 15 | name='stock', 16 | field=models.IntegerField(default=0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /shop/migrations/0006_remove_product_available.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-10-05 02:40 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('shop', '0005_product_stock'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='product', 15 | name='available', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /shop/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-coding-pie/amazon-clone-django/0d1b22aef11493a520f95c745a416b53769b44be/shop/migrations/__init__.py -------------------------------------------------------------------------------- /shop/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from ckeditor.fields import RichTextField 3 | 4 | # Create your models here. 5 | class Category(models.Model): 6 | name = models.CharField(max_length=50) 7 | slug = models.SlugField(max_length=200, unique=True) 8 | 9 | class Meta: 10 | ordering = ('name', ) 11 | verbose_name = 'category' 12 | verbose_name_plural = 'catergories' 13 | 14 | def __str__(self): 15 | return self.name 16 | 17 | class Product(models.Model): 18 | name = models.CharField(max_length=200) 19 | img = models.ImageField(upload_to='products/images', blank=True) 20 | price = models.DecimalField(max_digits=6, decimal_places=2) 21 | stock = models.IntegerField(default=0) 22 | description = models.TextField() 23 | slug = models.SlugField(max_length=200, unique=True) 24 | manufactured_on = models.DateTimeField() 25 | category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products') 26 | features = RichTextField(blank=True) 27 | 28 | class Meta: 29 | ordering = ('name', ) 30 | verbose_name = 'product' 31 | verbose_name_plural = 'products' 32 | 33 | def __str__(self): 34 | return self.name -------------------------------------------------------------------------------- /shop/request_exposer.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from . import models 3 | 4 | def RequestExposerMiddleware(get_response): 5 | def middleware(request): 6 | models.exposed_request = request 7 | response = get_response(request) 8 | return response 9 | 10 | return middleware -------------------------------------------------------------------------------- /shop/static/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-coding-pie/amazon-clone-django/0d1b22aef11493a520f95c745a416b53769b44be/shop/static/icons/favicon.ico -------------------------------------------------------------------------------- /shop/static/icons/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-coding-pie/amazon-clone-django/0d1b22aef11493a520f95c745a416b53769b44be/shop/static/icons/favicon.png -------------------------------------------------------------------------------- /shop/static/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-coding-pie/amazon-clone-django/0d1b22aef11493a520f95c745a416b53769b44be/shop/static/icons/icon.png -------------------------------------------------------------------------------- /shop/static/icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-coding-pie/amazon-clone-django/0d1b22aef11493a520f95c745a416b53769b44be/shop/static/icons/logo.png -------------------------------------------------------------------------------- /shop/static/shop/cart.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', () => { 2 | const item_count = document.querySelector('.item_count'); 3 | const addToCartBtns = document.querySelectorAll('.add_to_cart_btn'); 4 | 5 | const upBtns = document.querySelectorAll('.up_btn'); 6 | const downBtns = document.querySelectorAll('.down_btn'); 7 | const numbers = document.querySelectorAll('.number'); 8 | const deleteBtns = document.querySelectorAll('.delete_btn'); 9 | 10 | const total = document.querySelector('.total'); 11 | const item_total_count = document.querySelector('.item_total_count'); 12 | 13 | const availability = document.querySelectorAll('.cart_page .availability'); 14 | 15 | const getCookie = (name) => { 16 | let cookieValue = null; 17 | if (document.cookie && document.cookie !== '') { 18 | const cookies = document.cookie.split(';'); 19 | for (let i = 0; i < cookies.length; i++) { 20 | const cookie = cookies[i].trim(); 21 | if (cookie.substring(0, name.length + 1) === (name + '=')) { 22 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 23 | break; 24 | } 25 | } 26 | } 27 | return cookieValue; 28 | } 29 | const csrftoken = getCookie('csrftoken'); 30 | 31 | const getTotal = (isInCart) => { 32 | // fetch a request to /cart 33 | fetch('/cart') 34 | .then(res => res.json()) 35 | .then(data => { 36 | if (data.status === 200) { 37 | if (data.item_count > 99) { 38 | data.item_count = "99+" 39 | } 40 | item_count.innerHTML = data.item_count; 41 | if (isInCart) { 42 | total.innerHTML = `$${data.total}`; 43 | if (data.item_count > 1) { 44 | item_total_count.innerHTML = `${data.item_count} items`; 45 | } else { 46 | item_total_count.innerHTML = `${data.item_count} item`; 47 | } 48 | } 49 | } 50 | }) 51 | .catch(e => { 52 | console.log(e) 53 | }) 54 | } 55 | 56 | const addToCart = (e, isInCart) => { 57 | let id = e.target.dataset.id; 58 | 59 | const formData = new FormData(); 60 | formData.append("id", id); 61 | 62 | // fetch POST request to /cart/add_to_cart/ 63 | fetch('/cart/add_to_cart/', { 64 | method: 'POST', 65 | body: formData, 66 | headers: { 67 | 'X-CSRFToken': csrftoken 68 | } 69 | }) 70 | .then(res => res.json()) 71 | .then(data => { 72 | if (data.status === 200) { 73 | getTotal(isInCart); 74 | 75 | if (isInCart) { 76 | numbers.forEach(number => { 77 | if (number.dataset.id === id) { 78 | number.innerHTML = data.product_quantity 79 | } 80 | }); 81 | 82 | if (data.product_quantity > 1) { 83 | downBtns.forEach(btn => { 84 | if (btn.dataset.id === e.target.dataset.id) { 85 | btn.classList.remove('disabled'); 86 | } 87 | }); 88 | } 89 | 90 | if (data.item_stock <= 0) { 91 | availability.forEach(item => { 92 | if (item.dataset.id === e.target.dataset.id) { 93 | item.innerHTML = 'Out Of Stock'; 94 | item.classList.remove('available'); 95 | item.classList.add('not_available'); 96 | } 97 | }); 98 | e.target.classList.add('disabled'); 99 | } 100 | } 101 | 102 | if (data.item_stock <= 0) { 103 | e.target.disabled = true; 104 | } 105 | } 106 | }) 107 | .catch(e => [ 108 | console.log(e) 109 | ]); 110 | } 111 | 112 | const removeFromCart = (e) => { 113 | let id = e.target.dataset.id; 114 | 115 | numbers.forEach(number => { 116 | if (number.dataset.id === id) { 117 | if (parseInt(number.innerHTML) > 1) { 118 | 119 | const formData = new FormData(); 120 | formData.append("id", id); 121 | 122 | // fetch POST request to /cart/add_to_cart/ 123 | fetch('/cart/remove_from_cart/', { 124 | method: 'POST', 125 | body: formData, 126 | headers: { 127 | 'X-CSRFToken': csrftoken 128 | } 129 | }) 130 | .then(res => res.json()) 131 | .then(data => { 132 | if (data.status === 200) { 133 | getTotal(true); 134 | 135 | numbers.forEach(number => { 136 | if (number.dataset.id === id) { 137 | if (data.product_quantity > 1) { 138 | number.innerHTML = data.product_quantity; 139 | } else if (data.product_quantity === 1) { 140 | number.innerHTML = data.product_quantity; 141 | e.target.classList.add('disabled'); 142 | } 143 | } 144 | }); 145 | 146 | if (data.item_stock > 0) { 147 | availability.forEach(item => { 148 | if (item.dataset.id === e.target.dataset.id) { 149 | item.innerHTML = 'In Stock'; 150 | item.classList.remove('not_available'); 151 | item.classList.add('available'); 152 | } 153 | }) 154 | 155 | upBtns.forEach(btn => { 156 | if (btn.dataset.id === e.target.dataset.id) { 157 | btn.classList.remove('disabled'); 158 | } 159 | }); 160 | } 161 | } 162 | }) 163 | .catch(e => [ 164 | console.log(e) 165 | ]); 166 | } 167 | } 168 | }); 169 | } 170 | 171 | const removeCart = (e) => { 172 | let id = e.target.dataset.id; 173 | 174 | const formData = new FormData(); 175 | formData.append("id", id); 176 | 177 | // fetch POST request to /cart/add_to_cart/ 178 | fetch('/cart/remove_cart/', { 179 | method: 'POST', 180 | body: formData, 181 | headers: { 182 | 'X-CSRFToken': csrftoken 183 | } 184 | }) 185 | .then(res => res.json()) 186 | .then(data => { 187 | if (data.status === 200) { 188 | window.location.reload(); 189 | } 190 | }) 191 | .catch(e => [ 192 | console.log(e) 193 | ]); 194 | } 195 | 196 | addToCartBtns.forEach((btn) => { 197 | btn.addEventListener('click', function (e) { 198 | addToCart(e, false); 199 | }) 200 | }); 201 | 202 | upBtns.forEach((btn) => { 203 | btn.addEventListener('click', (e) => { 204 | addToCart(e, true); 205 | }) 206 | }); 207 | 208 | downBtns.forEach((btn) => { 209 | btn.addEventListener('click', (e) => { 210 | removeFromCart(e); 211 | }) 212 | }); 213 | 214 | deleteBtns.forEach(btn => { 215 | btn.addEventListener('click', (e) => { 216 | removeCart(e); 217 | }) 218 | }); 219 | 220 | if (window.location.pathname == '/shopping_cart/') { 221 | getTotal(true); 222 | } else { 223 | getTotal(); 224 | } 225 | }); 226 | -------------------------------------------------------------------------------- /shop/static/shop/message.js: -------------------------------------------------------------------------------- 1 | const msgContainer = document.querySelector('.msg_container'); 2 | 3 | setTimeout(() => { 4 | msgContainer.style.display = "none"; 5 | }, 3000) -------------------------------------------------------------------------------- /shop/static/shop/script.js: -------------------------------------------------------------------------------- 1 | const hamBurgerBtn = document.querySelector('.hamburger_btn'); 2 | const sideBox = document.querySelector('.side_box'); 3 | const sideBoxContent = document.querySelector('.side_box__content'); 4 | const sideBoxBackground = document.querySelector('.side_box__background'); 5 | 6 | const navBarSearch = document.querySelector('.navbar__search > form > input'); 7 | const navBarMobileSearch = document.querySelector('.navbar__search__mobile > div > form > input') 8 | 9 | hamBurgerBtn.addEventListener('click', (e) => { 10 | // side-box 11 | sideBox.classList.remove('hidden'); 12 | } 13 | ); 14 | 15 | sideBoxBackground.addEventListener('click', (e) => { 16 | sideBox.classList.add('hidden'); 17 | }) 18 | 19 | // interrelated search fields 20 | navBarSearch.addEventListener('change', (e) => { 21 | navBarMobileSearch.value = e.target.value; 22 | }); 23 | 24 | navBarMobileSearch.addEventListener('change', (e) => { 25 | navBarSearch.value = e.target.value; 26 | }); 27 | -------------------------------------------------------------------------------- /shop/static/shop/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | font-family: Arial, Helvetica, sans-serif; } 5 | 6 | body { 7 | width: 100vw; 8 | height: 100vh; 9 | background: #fff; 10 | overflow-x: hidden; } 11 | 12 | a { 13 | text-decoration: none; } 14 | 15 | nav { 16 | display: flex; 17 | align-items: center; 18 | background: #131921; 19 | padding: 0.7rem 1rem; } 20 | 21 | .container { 22 | padding: 0.7rem 2.5rem 0.7rem 2rem; 23 | margin: 0 auto; 24 | min-height: 80vh; 25 | box-sizing: border-box; } 26 | 27 | .msg_container { 28 | padding: 0rem 2.5rem 0rem 2rem; 29 | margin: 0 auto; 30 | box-sizing: border-box; } 31 | 32 | button:hover { 33 | cursor: pointer; } 34 | 35 | button:disabled { 36 | cursor: not-allowed; } 37 | 38 | .amazon_logo { 39 | width: 6.8rem; 40 | margin: 0.5rem 0rem 1.5rem 0rem; } 41 | 42 | .btn { 43 | color: #111111; 44 | padding: 0.4rem; 45 | outline: none; 46 | border-radius: 3px; 47 | font-size: 0.9rem; 48 | background: #F0BF3C; 49 | border: 1px solid #9b7714; 50 | width: 100%; } 51 | 52 | .messages { 53 | display: flex; 54 | flex-direction: column; 55 | list-style-type: none; } 56 | .messages li { 57 | padding: 0.6rem 0.8rem; 58 | background: #ddd; 59 | border-radius: 3px; 60 | border: 1px solid #cec8c8; 61 | margin-bottom: 0.6rem; 62 | margin-top: 0.6rem; } 63 | .messages .error { 64 | background: rgba(255, 0, 0, 0.5); 65 | border: 1px solid #e72020; } 66 | .messages .success { 67 | background: rgba(0, 255, 0, 0.5); 68 | border: 1px solid #0de40d; } 69 | 70 | .navbar__left { 71 | display: flex; 72 | flex: 2 2; 73 | align-items: center; } 74 | 75 | .navbar__center { 76 | display: flex; 77 | flex: 5 5; 78 | align-items: center; } 79 | 80 | .navbar__right { 81 | display: flex; 82 | flex: 3 3; 83 | align-items: center; } 84 | .navbar__right * { 85 | flex: 1 1; } 86 | 87 | .navbar__icon { 88 | color: #fff; 89 | text-align: center; 90 | cursor: pointer; 91 | margin: 0rem 0.5rem 0rem 0rem; } 92 | .navbar__icon .hamburger_btn { 93 | width: 2rem; 94 | border: 0.06rem solid #eee; 95 | border-radius: 0.1rem; 96 | padding: 0.2rem; } 97 | 98 | .navbar__logo { 99 | flex: 1 1; } 100 | .navbar__logo img { 101 | width: 6rem; 102 | height: 1.8rem; } 103 | 104 | .navbar__search { 105 | width: 100%; 106 | margin-right: 1.2rem; } 107 | .navbar__search form { 108 | display: flex; 109 | background: #fff; 110 | border-top-left-radius: 0.25rem; 111 | border-bottom-left-radius: 0.25rem; 112 | border-top-right-radius: 0.25rem; 113 | border-bottom-right-radius: 0.25rem; } 114 | .navbar__search form input { 115 | flex: 9 9; 116 | border: none; 117 | outline: none; 118 | padding: 0.5rem; 119 | font-size: 1rem; 120 | border-top-left-radius: 0.25rem; 121 | border-bottom-left-radius: 0.25rem; } 122 | .navbar__search form button { 123 | border: none; 124 | outline: none; 125 | cursor: pointer; 126 | border-top-right-radius: 0.25rem; 127 | border-bottom-right-radius: 0.25rem; 128 | padding: 0.2rem; 129 | background: #F3A847; } 130 | .navbar__search form button svg { 131 | flex: 1 1; 132 | width: 1.5rem; 133 | padding: 0.1rem 0.2rem; } 134 | 135 | .navbar__link { 136 | color: #fff; 137 | font-size: 0.9rem; 138 | font-weight: 600; 139 | text-align: right; } 140 | 141 | .navbar__cart { 142 | text-align: center; } 143 | .navbar__cart a .item_count { 144 | font-weight: 600; 145 | color: #F3A847; 146 | font-size: 1.1rem; 147 | left: 1.8rem; 148 | bottom: 1.5rem; 149 | position: relative; 150 | z-index: 99; } 151 | .navbar__cart a svg { 152 | color: #fff; 153 | width: 2.3rem; 154 | z-index: 1100; } 155 | 156 | .navbar__search__mobile { 157 | background: #131921; 158 | padding: 0rem 1rem 0.7rem 1rem; 159 | display: none; } 160 | 161 | /* side-box */ 162 | .hidden { 163 | display: none; } 164 | 165 | .side_box { 166 | width: 100vw; 167 | height: 100vh; 168 | box-sizing: border-box; 169 | z-index: 999999; } 170 | 171 | .side_box__content { 172 | width: 22rem; 173 | height: 100%; 174 | position: fixed; 175 | top: 0; 176 | left: 0; 177 | overflow-x: hidden; 178 | background: #fff; 179 | z-index: 999999; 180 | display: flex; 181 | flex-direction: column; } 182 | .side_box__content * { 183 | padding: 1rem 1.3rem; } 184 | .side_box__content .container { 185 | width: 90%; 186 | margin: 0 auto; } 187 | .side_box__content .top { 188 | background-color: #232F3E; 189 | font-size: 1.2rem; 190 | color: #fff; 191 | font-weight: 600; } 192 | .side_box__content .shop_by_category { 193 | text-transform: uppercase; 194 | font-size: 0.9rem; 195 | font-weight: 500; 196 | color: #999; } 197 | .side_box__content .category { 198 | color: #111; } 199 | .side_box__content .category:hover { 200 | background-color: #eee; } 201 | 202 | .side_box__background { 203 | background: #111; 204 | width: 100%; 205 | height: 100%; 206 | z-index: 999; 207 | position: fixed; 208 | top: 0; 209 | left: 0; 210 | opacity: 0.5; } 211 | 212 | .results { 213 | margin-bottom: 0.25rem; 214 | padding: 0.5rem 0px; 215 | border-bottom: 2px solid #DDDDDD; } 216 | 217 | .products { 218 | display: flex; 219 | flex-direction: column; 220 | margin-bottom: 1rem; } 221 | 222 | .product { 223 | display: flex; 224 | padding: 1.8rem 1rem; 225 | box-sizing: border-box; 226 | border-bottom: 2px solid #DDDDDD; } 227 | .product .product__left { 228 | width: 180px; 229 | margin-right: 1.8rem; } 230 | .product .product__left img { 231 | width: 100%; 232 | height: 150px; 233 | object-fit: contain; 234 | object-position: center; } 235 | .product .product__right { 236 | flex: 4 4; 237 | display: flex; 238 | flex-direction: column; } 239 | .product .product__right .category { 240 | font-size: 0.9rem; 241 | margin-bottom: 0.3rem; 242 | color: #0066CC; 243 | font-weight: 600; } 244 | .product .product__right .name { 245 | font-size: 1.3rem; 246 | font-weight: normal; 247 | color: #111; 248 | margin-bottom: 0.6rem; } 249 | .product .product__right .name:hover { 250 | color: #F3A847; } 251 | .product .product__right .price { 252 | font-size: 1.5rem; 253 | margin-bottom: 0.6rem; } 254 | .product .product__right .price::before { 255 | content: "$"; 256 | font-size: 0.8rem; 257 | position: relative; 258 | bottom: 0.4rem; 259 | left: 0.3rem; } 260 | .product .product__right .price::after { 261 | content: "00"; 262 | font-size: 0.8rem; 263 | position: relative; 264 | bottom: 0.4rem; 265 | right: 0.3rem; } 266 | .product .product__right .availability { 267 | font-size: 0.9rem; } 268 | 269 | .sub_total { 270 | display: flex; 271 | justify-content: flex-end; 272 | font-size: 1.3rem; 273 | margin: 1rem 8% 1rem 1rem; } 274 | .sub_total .total { 275 | margin-left: 0.3rem; } 276 | 277 | .cart_page .product__left { 278 | flex: 1 1; } 279 | .cart_page .product__middle { 280 | flex: 3 3; 281 | display: flex; 282 | flex-direction: column; } 283 | .cart_page .product__middle .name { 284 | font-size: 1.3rem; 285 | font-weight: normal; 286 | color: #0066c0; 287 | margin-bottom: 0.6rem; } 288 | .cart_page .product__middle .name:hover { 289 | color: #F3A847; } 290 | .cart_page .product__middle .availability { 291 | font-size: 0.9rem; 292 | margin-bottom: 0.8rem; } 293 | .cart_page .product__middle svg { 294 | width: 1.4rem; 295 | cursor: pointer; } 296 | .cart_page .product__middle svg:hover { 297 | color: #F0BF3C; } 298 | .cart_page .product__middle svg.disabled { 299 | color: #999; 300 | cursor: not-allowed !important; 301 | pointer-events: none; } 302 | .cart_page .product__middle .bottom { 303 | display: flex; 304 | align-items: center; } 305 | .cart_page .product__middle .bottom .quantity { 306 | display: flex; 307 | align-items: center; } 308 | .cart_page .product__middle .bottom .quantity span { 309 | margin-right: 0.4rem; } 310 | .cart_page .product__middle .bottom .quantity .count { 311 | font-size: 0.9rem; 312 | margin-right: 1rem; 313 | display: flex; 314 | flex-direction: column; 315 | align-items: center; } 316 | .cart_page .product__middle .bottom .line { 317 | color: #DDDDDD; } 318 | .cart_page .product__middle .bottom .delete { 319 | margin-left: 1rem; 320 | font-size: 0.9rem; } 321 | .cart_page .product__middle .bottom .delete .delete_btn { 322 | color: #0066CC; 323 | cursor: pointer; } 324 | .cart_page .product__right { 325 | flex: 1 1; } 326 | .cart_page .product__right .price { 327 | font-size: 1.4rem; 328 | color: #C23832; } 329 | 330 | .available { 331 | color: #28b932; } 332 | 333 | .not_available { 334 | color: #ce2727; } 335 | 336 | h4.category { 337 | margin-bottom: 0.25rem; 338 | padding: 0.5rem 0px; 339 | border-bottom: 2px solid #DDDDDD; } 340 | h4.category a { 341 | font-weight: 400; 342 | color: #0066CC; } 343 | 344 | .product_detail { 345 | margin-bottom: 1rem; 346 | border-bottom: 2px solid #DDDDDD; 347 | padding-bottom: 1.5rem; 348 | display: flex; 349 | margin-top: 1.5rem; 350 | padding-bottom: 3rem; } 351 | .product_detail .product_detail__left { 352 | align-self: center; } 353 | .product_detail .product_detail__left .image { 354 | width: 443px; 355 | max-height: 575px; 356 | object-fit: contain; 357 | object-position: center; 358 | margin-right: 2rem; } 359 | .product_detail .product_detail__right { 360 | flex: 5 5; } 361 | .product_detail .product_detail__right .category { 362 | margin-bottom: 0.6rem; } 363 | .product_detail .product_detail__right .name { 364 | margin-bottom: 0.6rem; } 365 | .product_detail .product_detail__right .price { 366 | color: #777; 367 | margin-bottom: 1.5rem; } 368 | .product_detail .product_detail__right .price span { 369 | font-size: 1.5rem; 370 | color: #C23832; } 371 | .product_detail .product_detail__right .price span::before { 372 | content: "$"; 373 | font-size: 0.8rem; 374 | position: relative; 375 | bottom: 0.4rem; 376 | left: 0.02rem; } 377 | .product_detail .product_detail__right .price span::after { 378 | content: "00"; 379 | font-size: 0.8rem; 380 | position: relative; 381 | bottom: 0.4rem; 382 | right: 0.02rem; } 383 | .product_detail .buttons { 384 | display: flex; 385 | margin-bottom: 1.5rem; } 386 | 387 | .details { 388 | margin-bottom: 1rem; } 389 | .details h4 { 390 | margin-bottom: 0.25rem; 391 | padding: 0.5rem 0px; } 392 | .details li span { 393 | font-size: 1rem; 394 | font-weight: 600; } 395 | 396 | .about h4 { 397 | margin-bottom: 0.25rem; 398 | padding: 0.5rem 0px; } 399 | .about li { 400 | margin-left: 1rem; } 401 | 402 | .add_to_cart_btn, a.go_to_cart_btn { 403 | padding: 0.4rem; 404 | outline: none; 405 | border-radius: 3px; 406 | font-size: 0.9rem; } 407 | 408 | .add_to_cart_btn { 409 | background: #F0BF3C; 410 | border: 1px solid #9b7714; } 411 | 412 | a.go_to_cart_btn { 413 | background: #E8962E; 414 | border: 1px solid #a8650d; 415 | margin-left: 1.2rem; 416 | color: #111; } 417 | 418 | .checkout_btn_div { 419 | display: flex; 420 | justify-content: center; 421 | align-items: center; 422 | margin: 2rem 0rem; } 423 | 424 | .checkout_btn { 425 | width: 20rem; 426 | text-align: center; } 427 | 428 | .similar_items { 429 | margin-bottom: 1rem; 430 | border-bottom: 2px solid #DDDDDD; 431 | padding-bottom: 1.5rem; } 432 | .similar_items h4 { 433 | font-size: 1.4rem; 434 | margin-bottom: 1.5rem; } 435 | .similar_items .items { 436 | display: flex; 437 | flex-wrap: wrap; 438 | justify-content: space-between; } 439 | .similar_items .items .item { 440 | width: 200px; 441 | display: flex; 442 | flex-direction: column; 443 | align-items: flex-start; 444 | margin-bottom: 1.5rem; } 445 | .similar_items .items .item a { 446 | color: #0066CC; } 447 | .similar_items .items .item img { 448 | max-width: 12rem; 449 | max-height: 12rem; 450 | margin-bottom: 0.6rem; } 451 | .similar_items .items .item .price { 452 | margin: 0.4rem 0rem; 453 | font-size: 1.4rem; 454 | color: #C23832; } 455 | .similar_items .items .item .price::before { 456 | content: "$"; 457 | font-size: 0.8rem; 458 | position: relative; 459 | bottom: 0.4rem; 460 | left: 0.02rem; } 461 | .similar_items .items .item .price::after { 462 | content: "00"; 463 | font-size: 0.8rem; 464 | position: relative; 465 | bottom: 0.4rem; 466 | right: 0.02rem; } 467 | 468 | .description { 469 | margin-bottom: 1rem; } 470 | .description h4 { 471 | margin-bottom: 0.25rem; 472 | padding: 0rem 0rem 0.5rem 0rem; } 473 | 474 | footer { 475 | display: flex; 476 | flex-direction: column; 477 | text-align: center; } 478 | footer .top { 479 | background: #485769; 480 | padding: 1rem 0rem; 481 | color: #FFFCFA; 482 | font-size: 0.9rem; } 483 | footer .center { 484 | display: flex; 485 | justify-content: center; 486 | background: #232F3E; 487 | padding: 3rem 0rem; } 488 | footer .center div { 489 | display: flex; } 490 | footer .center a { 491 | color: #FFFCFA; 492 | font-size: 0.9rem; 493 | margin-right: 1rem; } 494 | footer .center a:hover { 495 | text-decoration: underline; } 496 | footer .bottom { 497 | background: #131A22; 498 | font-size: 0.8rem; 499 | color: #8D9191; 500 | padding: 0.5rem; } 501 | 502 | .signup, .signin { 503 | display: flex; 504 | flex-direction: column; 505 | align-items: center; } 506 | .signup form, .signin form { 507 | display: flex; 508 | flex-direction: column; 509 | border: 1px solid #DDDDDD; 510 | border-radius: 3px; 511 | padding: 2rem 1.5rem; 512 | width: 20rem; 513 | box-sizing: border-box; } 514 | .signup form ul.errors, .signin form ul.errors { 515 | display: flex; 516 | flex-direction: column; 517 | list-style-type: none; } 518 | .signup form ul.errors li.error, .signin form ul.errors li.error { 519 | font-size: 0.9rem; 520 | color: #ff0000; 521 | margin: 0.2rem 0rem 0.8rem 0rem; } 522 | .signup form h1, .signin form h1 { 523 | font-size: 2rem; 524 | font-weight: 500; 525 | margin-bottom: 0.9rem; } 526 | .signup form p, .signin form p { 527 | width: 100%; } 528 | .signup form label, .signin form label { 529 | font-size: 0.9rem; 530 | font-weight: 700; 531 | color: #111; 532 | padding: 0rem 0rem 0.1rem 0.1rem; 533 | margin-bottom: 0.1rem; } 534 | .signup form input, .signin form input { 535 | width: 100%; 536 | margin-top: 0.2rem; 537 | margin-bottom: 0.8rem; 538 | border: 1px solid #8D9191; 539 | font-size: 1rem; 540 | padding: 0.3rem 0.4rem; 541 | border-radius: 3px; 542 | box-sizing: border-box; } 543 | .signup form input:focus, .signin form input:focus { 544 | outline-color: #F3A847; } 545 | .signup form p.agreement, .signin form p.agreement { 546 | font-size: 0.8rem; 547 | margin: 1.2rem 0rem; 548 | padding-bottom: 1.2rem; 549 | border-bottom: 1px solid #DDDDDD; } 550 | .signup form p.extras, .signin form p.extras { 551 | font-size: 0.9rem; } 552 | 553 | @media only screen and (max-width: 52rem) { 554 | .navbar__left { 555 | flex: 1 1; } 556 | 557 | .navbar__center { 558 | display: none; } 559 | 560 | .navbar__right { 561 | flex: 1 1; } 562 | 563 | .navbar__search__mobile { 564 | display: block; } 565 | 566 | .navbar__search__mobile * { 567 | display: block; } 568 | 569 | .product { 570 | padding: 1.8rem 0rem; } 571 | 572 | .product_detail { 573 | width: 100%; 574 | display: flex; 575 | flex-direction: column; 576 | align-items: center; } 577 | .product_detail .product_detail__left .image { 578 | max-width: 100%; 579 | max-height: 575px; 580 | margin: 0rem 0rem 0.6rem 0rem; } 581 | .product_detail .product_detail__right { 582 | flex: 1 1; } 583 | .product_detail .product_detail__right .category { 584 | margin-bottom: 0.6rem; } 585 | .product_detail .product_detail__right .name { 586 | margin-bottom: 0.6rem; } 587 | 588 | .buttons { 589 | justify-content: center; } } 590 | 591 | /*# sourceMappingURL=styles.css.map */ 592 | -------------------------------------------------------------------------------- /shop/templates/auth/signin.html: -------------------------------------------------------------------------------- 1 | {% extends 'base_auth.html' %} 2 | {% load static %} 3 | 4 | {% block title %} 5 | SignIn 6 | {% endblock %} 7 | 8 | {% block meta %} 9 | 10 | {% endblock %} 11 | 12 | {% block content %} 13 |
14 | 15 | 16 | 17 |
18 |

Sign-In

19 | 20 | {% csrf_token %} 21 |

22 |
23 | {{ form.email }} 24 | {% if form.email.errors %} 25 |

32 | {% endif %} 33 |

34 | 35 |

36 |
37 | {{ form.password }} 38 | {% if form.password.errors %} 39 |

46 | {% endif %} 47 |

48 | 49 |

50 | 51 |

52 | 53 |

54 | By creating an account, you agree to Amazon's Conditions of Use and Privacy Notice. 55 |

56 | 57 |

58 | New to Amazon? SignUp 59 |

60 |
61 |
62 | {% endblock %}z -------------------------------------------------------------------------------- /shop/templates/auth/signup.html: -------------------------------------------------------------------------------- 1 | {% extends 'base_auth.html' %} 2 | {% load static %} 3 | 4 | {% block title %} 5 | Signup 6 | {% endblock %} 7 | 8 | {% block meta %} 9 | 10 | {% endblock %} 11 | 12 | {% block content %} 13 |
14 | 15 | 16 | 17 |
18 |

Create account

19 | 20 | {% csrf_token %} 21 |

22 |
23 | {{ form.your_name }} 24 | {% if form.your_name.errors %} 25 |

32 | {% endif %} 33 |

34 | 35 |

36 |
37 | {{ form.email }} 38 | {% if form.email.errors %} 39 |

46 | {% endif %} 47 |

48 | 49 |

50 |
51 | {{ form.password }} 52 | {% if form.password.errors %} 53 |

60 | {% endif %} 61 |

62 | 63 |

64 |
65 | {{ form.re_enter_password }} 66 | {% if form.re_enter_password.errors %} 67 |

74 | {% endif %} 75 |

76 | 77 |

78 | 79 |

80 | 81 |

82 | By creating an account, you agree to Amazon's Conditions of Use and Privacy Notice. 83 |

84 | 85 |

86 | Already have an account? Sign-In 87 |

88 |
89 |
90 | {% endblock %} -------------------------------------------------------------------------------- /shop/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | {% block meta %} 8 | {% endblock %} 9 | 10 | Amazon.com:{% block title %} {% endblock %} 11 | 12 | 13 | 14 | 15 | 16 | 56 | 66 | 67 | 68 | 86 |
87 | {% if messages %} 88 | 95 | {% endif %} 96 |
97 | {% block content %} 98 | {% endblock %} 99 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /shop/templates/base_auth.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | {% block meta %} 8 | {% endblock %} 9 | 10 | Amazon.com:{% block title %} {% endblock %} 11 | 12 | 13 | 14 | 15 | 16 |
17 | {% if messages %} 18 | 25 | {% endif %} 26 |
27 |
28 | {% block content %} 29 | {% endblock %} 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /shop/templates/shop/checkout.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load get_image_url %} 3 | 4 | {% block title %} 5 | {% if name %} 6 | {{ name|title }} 7 | {% else %} 8 | Online Shopping for Electronics, Apparel, Computers, Books, DVDs & more 9 | {% endif %} 10 | {% endblock %} 11 | 12 | {% block meta %} 13 | 14 | {% endblock %} 15 | 16 | {% block content %} 17 |
18 |

Checkout Page

19 |
20 |

21 | Hey thank you for purchasing on Amazon.com. Your items will be delivered to your address soon! 22 |

23 |
24 |
25 |

26 | Continue Shopping 27 |

28 |
29 | {% endblock %} -------------------------------------------------------------------------------- /shop/templates/shop/detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load get_image_url %} 3 | 4 | {% block title %} 5 | {{ product.name }} 6 | {% endblock %} 7 | 8 | {% block meta %} 9 | 10 | {% endblock %} 11 | 12 | {% block content %} 13 |
14 |

Category > 15 | {{ product.category }} 16 |

17 |
18 |
19 | {{ product.name }} 20 |
21 |
22 |

23 | {{ product.name }} 24 |

25 | 26 |

Price: {{ product.price }}

27 |
28 | {% if product.stock > 0 %} 29 | 32 | {% else %} 33 | 36 | {% endif %} 37 | 38 | Go to Cart 39 | 40 |
41 | 42 |
43 |

Details

44 |
  • 45 | Mfg: {{ product.manufactured_on }} 46 |
  • 47 |
    48 | 49 | {% if product.features %} 50 |
    51 |

    About this item

    52 | {{ product.features|safe }} 53 |
    54 | {% endif %} 55 |
    56 |
    57 | 58 | 59 | {% if similar_items %} 60 |
    61 |

    Similar Items

    62 | 63 |
    64 | {% for item in similar_items %} 65 |
    66 | {{ item.name }} 67 | 68 | {{ item.name }} 69 | 70 |

    71 | {{ item.price }} 72 |

    73 | {% if item.stock > 0 %} 74 | 75 | {% else %} 76 | 77 | {% endif %} 78 |
    79 | {% endfor %} 80 |
    81 |
    82 | {% endif %} 83 | 84 | 85 |
    86 |

    Description

    87 | {{ product.description }} 88 |
    89 |
    90 | {% endblock %} -------------------------------------------------------------------------------- /shop/templates/shop/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load get_image_url %} 3 | 4 | {% block title %} 5 | {% if name %} 6 | {{ name|title }} 7 | {% else %} 8 | Online Shopping for Electronics, Apparel, Computers, Books, DVDs & more 9 | {% endif %} 10 | {% endblock %} 11 | 12 | {% block meta %} 13 | 14 | {% endblock %} 15 | 16 | {% block content %} 17 |
    18 |

    RESULTS

    19 | {% if products %} 20 |
    21 | {% for product in products %} 22 |
    23 |
    24 | {{ product.name }} 25 |
    26 |
    27 | 28 | {{ product.category }} 29 | 30 | 31 | {{ product.name }} 32 | 33 |

    34 | {{ product.price }} 35 |

    36 |

    37 | {% if product.stock > 0 %} 38 | In Stock 39 | {% else %} 40 | Out Of Stock 41 | {% endif %} 42 |

    43 |
    44 |
    45 | {% endfor %} 46 |
    47 | {% else %} 48 |

    No Products Found!

    49 | {% endif %} 50 |
    51 | {% endblock %} -------------------------------------------------------------------------------- /shop/templates/shop/shopping_cart.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load get_image_url %} 3 | 4 | {% block title %} 5 | {% if name %} 6 | {{ name|title }} 7 | {% else %} 8 | Online Shopping for Electronics, Apparel, Computers, Books, DVDs & more 9 | {% endif %} 10 | {% endblock %} 11 | 12 | {% block meta %} 13 | 14 | {% endblock %} 15 | 16 | {% block content %} 17 |
    18 |

    Shopping Cart

    19 | {% if products %} 20 |
    21 | {% for item in products %} 22 |
    23 |
    24 | {{ product.name }} 25 |
    26 |
    27 | 28 | {{ item.product.name }} 29 | 30 |

    31 | {% if item.product.stock > 0 %} 32 | In Stock 33 | {% else %} 34 | Out Of Stock 35 | {% endif %} 36 |

    37 |
    38 |
    39 | Qty: 40 |
    41 | {% if item.product.stock > 0 %} 42 | 43 | {% else %} 44 | 45 | {% endif %} 46 | 47 |
    {{ item.quantity }}
    48 | 49 | {% if item.quantity == 1 %} 50 | 51 | {% else %} 52 | 53 | {% endif %} 54 |
    55 |
    56 | 57 | | 58 | 59 |
    60 | Delete 61 |
    62 |
    63 |
    64 |
    65 |

    66 | {{ item.price }} 67 |

    68 |
    69 |
    70 | {% endfor %} 71 |
    72 | Subtotal(): 73 |
    74 |
    75 | Checkout 76 |
    77 |
    78 | {% else %} 79 |

    Your Cart is Empty!

    80 | {% endif %} 81 |
    82 | {% endblock %} -------------------------------------------------------------------------------- /shop/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-coding-pie/amazon-clone-django/0d1b22aef11493a520f95c745a416b53769b44be/shop/templatetags/__init__.py -------------------------------------------------------------------------------- /shop/templatetags/get_image_url.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | @register.simple_tag(name='get_image_url') 6 | def get_image_url(img): 7 | return img.url 8 | -------------------------------------------------------------------------------- /shop/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /shop/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | app_name = 'shop' 5 | 6 | urlpatterns = [ 7 | path('', views.home, name='home'), 8 | path('signup/', views.signup, name='signup'), 9 | path('signin/', views.signin, name='signin'), 10 | path('logout/', views.logout, name='logout'), 11 | path('product//', views.product_detail, name='detail'), 12 | path('category//', views.category, name='category'), 13 | path('search', views.search, name='search'), 14 | path('shopping_cart/', views.shopping_cart, name='shopping_cart'), 15 | path('checkout_page/', views.checkout_page, name='checkout_page'), 16 | ] -------------------------------------------------------------------------------- /shop/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, get_object_or_404, redirect 2 | from django.http import HttpResponse 3 | from .models import Product, Category 4 | from django.views.generic import ListView 5 | from django.urls import reverse 6 | from .forms import SignupForm, SignInForm 7 | from django.contrib import messages 8 | from django.contrib.auth.models import User 9 | from django.contrib.auth.hashers import check_password 10 | from django.contrib.auth import authenticate, login, logout as logout_user 11 | from django.conf import settings 12 | from cart.models import Cart, CartItem 13 | from django.contrib.auth.decorators import login_required 14 | 15 | def home(request): 16 | products = Product.objects.all() 17 | categories = Category.objects.all() 18 | return render(request, 'shop/index.html', { 19 | 'products': products, 20 | 'categories': categories 21 | }) 22 | 23 | def product_detail(request, slug): 24 | product = get_object_or_404(Product, slug=slug) 25 | categories = Category.objects.all() 26 | similar_items = Product.objects.filter(category=product.category).exclude(slug=slug) 27 | return render(request, 'shop/detail.html', { 28 | 'product': product, 29 | 'similar_items': similar_items, 30 | 'categories': categories 31 | }) 32 | 33 | def category(request, slug): 34 | products = Product.objects.filter(category__slug=slug) 35 | categories = Category.objects.all() 36 | return render(request, 'shop/index.html', { 37 | 'products': products, 38 | 'name': slug, 39 | 'categories': categories 40 | }) 41 | 42 | def search(request): 43 | query = request.GET.get('q') 44 | categories = Category.objects.all() 45 | if not query: 46 | return redirect('shop:home') 47 | 48 | # search in title, description, or features 49 | products = Product.objects.filter(category__name__icontains=query) | Product.objects.filter(name__icontains=query) | Product.objects.filter(description__icontains=query) | Product.objects.filter(features__icontains=query) 50 | 51 | return render(request, 'shop/index.html', { 52 | 'products': products, 53 | 'name': 'Search Results', 54 | 'categories': categories 55 | }) 56 | 57 | def signup(request): 58 | if request.user.is_authenticated: 59 | return redirect("shop:home") 60 | if request.method == 'POST': 61 | form = SignupForm(request.POST) 62 | 63 | if form.is_valid(): 64 | # create a new user 65 | form.save() 66 | 67 | messages.add_message(request, messages.SUCCESS, 'Your Account has been created successfully!') 68 | messages.add_message(request, messages.SUCCESS, 'Now Sign-In to continue...') 69 | return redirect("shop:signin") 70 | 71 | else: 72 | form = SignupForm() 73 | return render(request, 'auth/signup.html', { 74 | 'form': form 75 | }) 76 | 77 | def signin(request): 78 | next_url = request.GET.get('next') 79 | 80 | if request.user.is_authenticated: 81 | return redirect("shop:home") 82 | if request.method == 'POST': 83 | form = SignInForm(request.POST) 84 | if form.is_valid(): 85 | # check authentication 86 | email=form.cleaned_data.get('email') 87 | password=form.cleaned_data.get('password') 88 | if User.objects.filter(email=email): 89 | user = User.objects.filter(email=email)[0] 90 | # check password 91 | matched = check_password(password, user.password) 92 | if matched: 93 | # valid user 94 | # take cart from session and persist # it in db 95 | 96 | if not Cart.objects.filter(user=user): 97 | cart = Cart(user=user) 98 | cart.save() 99 | else: 100 | cart = Cart.objects.get(user=user) 101 | 102 | session_cart = request.session.get(settings.CART_SESSION_ID) 103 | 104 | if session_cart: 105 | for item_id, item in session_cart.items(): 106 | product = Product.objects.get(id=int(item_id)) 107 | 108 | if CartItem.objects.filter(product=product, cart=cart): 109 | item_in_cart = CartItem.objects.filter(product=product, cart=cart)[0] 110 | # then the product already # exists 111 | # so increase the quantity 112 | item_in_cart.price = float(item['price']) 113 | item_in_cart.quantity += int(item['quantity']) 114 | item_in_cart.save() 115 | else: 116 | # if item is not present in the cart, then add it 117 | new_item = CartItem(product=product, price=float(item['price']), quantity=int(item['quantity']), cart=cart) 118 | 119 | new_item.save() 120 | 121 | # destroy all items from the session 122 | del request.session[settings.CART_SESSION_ID] 123 | # login 124 | login(request, user) 125 | messages.add_message(request, messages.SUCCESS, "Signed In successfully!") 126 | return redirect(next_url) if next_url else redirect("shop:home") 127 | else: 128 | messages.add_message(request, messages.ERROR, "Email or Password doesn't match!") 129 | else: 130 | messages.add_message(request, messages.ERROR, "Email or Password doesn't match!") 131 | else: 132 | form = SignInForm() 133 | return render(request, 'auth/signin.html', { 134 | 'form': form 135 | }) 136 | 137 | def logout(request): 138 | logout_user(request) 139 | return redirect("shop:signin") 140 | 141 | @login_required() 142 | def shopping_cart(request): 143 | cart = Cart.objects.get(user=request.user) 144 | categories = Category.objects.all() 145 | products = CartItem.objects.filter(cart=cart) 146 | return render(request, "shop/shopping_cart.html", { 147 | "products": products, 148 | "name": "Shopping Cart", 149 | 'categories':categories 150 | }) 151 | 152 | @login_required 153 | def checkout_page(request): 154 | cart = Cart.objects.get(user=request.user) 155 | CartItem.objects.filter(cart=cart).delete() 156 | return render(request, "shop/checkout.html", { 157 | "name": "Checkout Page" 158 | }) --------------------------------------------------------------------------------