├── cart ├── __init__.py ├── migrations │ └── __init__.py ├── models.py ├── admin.py ├── tests.py ├── apps.py ├── forms.py ├── urls.py ├── views.py ├── templates │ └── cart.html └── utils │ └── cart.py ├── orders ├── __init__.py ├── migrations │ ├── __init__.py │ └── 0001_initial.py ├── tests.py ├── admin.py ├── apps.py ├── urls.py ├── templates │ ├── checkout.html │ └── user_orders.html ├── models.py └── views.py ├── shop ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0007_alter_category_options.py │ ├── 0002_alter_product_slug.py │ ├── 0005_alter_product_slug.py │ ├── 0004_alter_product_slug_alter_product_title.py │ ├── 0003_alter_product_options_remove_product_category_and_more.py │ ├── 0006_alter_category_options_alter_product_options_and_more.py │ └── 0001_initial.py ├── tests.py ├── admin.py ├── apps.py ├── urls.py ├── templates │ ├── favorites.html │ ├── home_page.html │ ├── product_detail.html │ └── base.html ├── models.py └── views.py ├── accounts ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0003_remove_user_cart.py │ ├── 0002_user_cart.py │ ├── 0004_user_likes.py │ └── 0001_initial.py ├── tests.py ├── admin.py ├── apps.py ├── templates │ ├── password_reset_done.html │ ├── password_reset_complete.html │ ├── edit_profile.html │ ├── password_reset_confirm.html │ ├── password_reset.html │ ├── email_template.html │ ├── register.html │ ├── login.html │ └── manager_login.html ├── managers.py ├── models.py ├── forms.py ├── urls.py └── views.py ├── dashboard ├── __init__.py ├── migrations │ └── __init__.py ├── models.py ├── admin.py ├── tests.py ├── apps.py ├── templates │ ├── add_category.html │ ├── edit_product.html │ ├── add_product.html │ ├── order_detail.html │ ├── orders.html │ ├── products.html │ └── dashboard.html ├── urls.py ├── forms.py └── views.py ├── online_shop ├── __init__.py ├── asgi.py ├── wsgi.py ├── context_processors.py ├── urls.py └── settings.py ├── requirements.txt ├── manage.py ├── LICENSE ├── .gitignore └── README.md /cart/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /orders/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /shop/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /accounts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dashboard/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /online_shop/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cart/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /shop/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /accounts/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dashboard/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /orders/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cart/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /cart/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /cart/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /dashboard/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /orders/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /shop/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /accounts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /dashboard/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /dashboard/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /orders/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import User 4 | 5 | admin.site.register(User) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.4.1 2 | backports.zoneinfo==0.2.1 3 | Django==4.0 4 | django-crispy-forms==1.13.0 5 | Pillow==9.0.0 6 | sqlparse==0.4.2 7 | -------------------------------------------------------------------------------- /shop/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Category, Product 4 | 5 | admin.site.register(Category) 6 | admin.site.register(Product) 7 | -------------------------------------------------------------------------------- /cart/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CartConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'cart' 7 | -------------------------------------------------------------------------------- /shop/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ShopConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'shop' 7 | -------------------------------------------------------------------------------- /orders/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class OrdersConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'orders' 7 | -------------------------------------------------------------------------------- /accounts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'accounts' 7 | -------------------------------------------------------------------------------- /dashboard/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DashboardConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'dashboard' 7 | -------------------------------------------------------------------------------- /online_shop/asgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.asgi import get_asgi_application 4 | 5 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'online_shop.settings') 6 | 7 | application = get_asgi_application() 8 | -------------------------------------------------------------------------------- /accounts/templates/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load crispy_forms_tags %} 3 | {% block content %} 4 |
5 | An Email has been sent with instrunctions to reset your password! 6 |
7 | {% endblock %} -------------------------------------------------------------------------------- /cart/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | 4 | class QuantityForm(forms.Form): 5 | quantity = forms.IntegerField(label='', 6 | min_value=1, max_value=9, widget=forms.NumberInput( 7 | attrs={'class': 'form-control mt-1','placeholder': 'quantity'} 8 | ) 9 | ) -------------------------------------------------------------------------------- /accounts/templates/password_reset_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load crispy_forms_tags %} 3 | {% block content %} 4 |
5 | Your Password Has Been Set. 6 |
7 | Login Here 8 | {% endblock %} -------------------------------------------------------------------------------- /online_shop/wsgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.wsgi import get_wsgi_application 4 | 5 | from accounts.views import create_manager 6 | 7 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'online_shop.settings') 8 | 9 | # create user with 'manager' role 10 | create_manager() 11 | 12 | application = get_wsgi_application() 13 | -------------------------------------------------------------------------------- /dashboard/templates/add_category.html: -------------------------------------------------------------------------------- 1 | {% extends "dashboard.html" %} 2 | {% block content %} 3 | 4 |
5 | {% csrf_token %} 6 | {{ form }} 7 |
8 | 9 |
10 |
11 | 12 | {% endblock %} 13 | 14 | -------------------------------------------------------------------------------- /online_shop/context_processors.py: -------------------------------------------------------------------------------- 1 | from cart.utils.cart import Cart 2 | from shop.models import Category 3 | 4 | 5 | def return_cart(request): 6 | cart = len(list(Cart(request))) 7 | return {'cart_count': cart} 8 | 9 | 10 | def return_categories(request): 11 | categories = Category.objects.all() 12 | return {'categories': categories} -------------------------------------------------------------------------------- /cart/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from cart import views 4 | 5 | app_name = 'cart' 6 | 7 | urlpatterns = [ 8 | path('add//', views.add_to_cart, name='add_to_cart'), 9 | path('remove//', views.remove_from_cart, name='remove_from_cart'), 10 | path('list/', views.show_cart, name='show_cart'), 11 | 12 | ] -------------------------------------------------------------------------------- /dashboard/templates/edit_product.html: -------------------------------------------------------------------------------- 1 | {% extends "dashboard.html" %} 2 | {% block content %} 3 | 4 |
5 | {% csrf_token %} 6 | {{ form }} 7 |
8 | 9 |
10 |
11 | 12 | {% endblock %} -------------------------------------------------------------------------------- /dashboard/templates/add_product.html: -------------------------------------------------------------------------------- 1 | {% extends "dashboard.html" %} 2 | {% block content %} 3 | 4 |
5 | {% csrf_token %} 6 | {{ form }} 7 |
8 | 9 |
10 |
11 | 12 | {% endblock %} 13 | 14 | -------------------------------------------------------------------------------- /orders/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from orders import views 4 | 5 | app_name = "orders" 6 | 7 | urlpatterns = [ 8 | path('create', views.create_order, name='create_order'), 9 | path('list', views.user_orders, name='user_orders'), 10 | path('checkout/', views.checkout, name='checkout'), 11 | path('fake-payment/', views.fake_payment, name='pay_order') 12 | ] -------------------------------------------------------------------------------- /accounts/migrations/0003_remove_user_cart.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0 on 2022-01-07 09:06 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('accounts', '0002_user_cart'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='user', 15 | name='cart', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /shop/migrations/0007_alter_category_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0 on 2022-01-09 13:03 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('shop', '0006_alter_category_options_alter_product_options_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='category', 15 | options={}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /shop/migrations/0002_alter_product_slug.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0 on 2022-01-05 08:51 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.AlterField( 14 | model_name='product', 15 | name='slug', 16 | field=models.SlugField(max_length=200, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /shop/migrations/0005_alter_product_slug.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0 on 2022-01-06 07:52 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('shop', '0004_alter_product_slug_alter_product_title'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='product', 15 | name='slug', 16 | field=models.SlugField(), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /accounts/templates/edit_profile.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load crispy_forms_tags %} 3 | 4 | {% block content %} 5 |
6 |
7 | {% csrf_token %} 8 |

Edit Profile

9 |
10 | {{ form|crispy }} 11 |
12 | 13 |
14 |
15 |
16 | {% endblock %} -------------------------------------------------------------------------------- /accounts/templates/password_reset_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load crispy_forms_tags %} 3 | {% block content %} 4 |
5 |
6 | {% csrf_token %} 7 |

Reset Password

8 |
9 | {{ form|crispy }} 10 |
11 | 12 |
13 |
14 |
15 | {% endblock %} -------------------------------------------------------------------------------- /orders/templates/checkout.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 | 6 |
7 |

Your Order Amount is: ${{order.get_total_price}}

8 |
9 |

Do you want to continue and pay?

10 | Back To Cart 11 | Continue and pay 12 |
13 | 14 | {% endblock %} -------------------------------------------------------------------------------- /accounts/migrations/0002_user_cart.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0 on 2022-01-07 07:37 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('shop', '0005_alter_product_slug'), 10 | ('accounts', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='user', 16 | name='cart', 17 | field=models.ManyToManyField(blank=True, related_name='cart', to='shop.Product'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /accounts/migrations/0004_user_likes.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0 on 2022-01-08 06:02 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('shop', '0005_alter_product_slug'), 10 | ('accounts', '0003_remove_user_cart'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='user', 16 | name='likes', 17 | field=models.ManyToManyField(blank=True, related_name='likes', to='shop.Product'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /accounts/templates/password_reset.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load crispy_forms_tags %} 3 | {% block content %} 4 |
5 |
6 | {% csrf_token %} 7 |

Reset Password

8 |
9 |

Please enter your email that you used in your account.

10 | {{ form|crispy }} 11 |
12 | 13 |
14 |
15 |
16 | {% endblock %} -------------------------------------------------------------------------------- /dashboard/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from dashboard import views 4 | 5 | app_name = 'dashboard' 6 | 7 | urlpatterns = [ 8 | path('products', views.products, name='products'), 9 | path('products/delete/', views.delete_product, name='delete_product'), 10 | path('products/edit/', views.edit_product, name='edit_product'), 11 | path('orders', views.orders, name='orders'), 12 | path('orders/detail/', views.order_detail, name='order_detail'), 13 | path('add-product/', views.add_product, name='add_product'), 14 | path('add-category/', views.add_category, name='add_category'), 15 | ] 16 | -------------------------------------------------------------------------------- /shop/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from shop import views 4 | 5 | app_name = "shop" 6 | 7 | urlpatterns = [ 8 | path('', views.home_page, name='home_page'), 9 | path('', views.product_detail, name='product_detail'), 10 | path('add/favorites//', views.add_to_favorites, name='add_to_favorites'), 11 | path('remove/favorites//', views.remove_from_favorites, name='remove_from_favorites'), 12 | path('favorites/', views.favorites, name='favorites'), 13 | path('search/', views.search, name='search'), 14 | path('filter//', views.filter_by_category, name='filter_by_category'), 15 | ] -------------------------------------------------------------------------------- /dashboard/templates/order_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "dashboard.html" %} 2 | 3 | {% block content %} 4 | {% if order.status %} 5 |

Successful

6 | {% else %} 7 |

Cancelled

8 | {% endif %} 9 | {% for item in items %} 10 |
11 |

product: {{item.product.title}}

12 |

Price: ${{item.price}}

13 |

quantity: {{item.quantity}}

14 |
15 | {% endfor %} 16 |

Total: ${{ order.get_total_price }}

17 | 18 | {% endblock %} -------------------------------------------------------------------------------- /accounts/templates/email_template.html: -------------------------------------------------------------------------------- 1 | {% load i18n %}{% autoescape off %} 2 | {% blocktranslate %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktranslate %} 3 | 4 | {% translate "Please go to the following page and choose a new password:" %} 5 | {% block reset_link %} 6 | {{ protocol }}://{{ domain }}{% url 'accounts:password_reset_confirm' uidb64=uid token=token %} 7 | {% endblock %} 8 | {% translate 'Your username, in case you’ve forgotten:' %} {{ user.get_username }} 9 | 10 | {% translate "Thanks for using our site!" %} 11 | 12 | {% blocktranslate %}The {{ site_name }} team{% endblocktranslate %} 13 | 14 | {% endautoescape %} -------------------------------------------------------------------------------- /online_shop/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 | 7 | urlpatterns = [ 8 | path('admin/', admin.site.urls), 9 | path('', include('shop.urls', namespace='shop')), 10 | path('accounts/', include('accounts.urls', namespace='accounts')), 11 | path('cart/', include('cart.urls', namespace='cart')), 12 | path('orders/', include('orders.urls', namespace='orders')), 13 | path('dashboard/', include('dashboard.urls', namespace='dashboard')), 14 | ] 15 | 16 | 17 | if settings.DEBUG: 18 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) -------------------------------------------------------------------------------- /shop/migrations/0004_alter_product_slug_alter_product_title.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0 on 2022-01-05 09:02 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('shop', '0003_alter_product_options_remove_product_category_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='product', 15 | name='slug', 16 | field=models.SlugField(unique=True), 17 | ), 18 | migrations.AlterField( 19 | model_name='product', 20 | name='title', 21 | field=models.CharField(max_length=250), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /accounts/templates/register.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load crispy_forms_tags %} 3 | {% block content %} 4 |
5 |
6 | {% csrf_token %} 7 |

Create Account

8 |
9 | {{ form|crispy }} 10 |
11 | 12 |
13 |
14 |
15 | 16 | Already have an account? Login 17 | 18 |
19 |
20 | {% endblock %} -------------------------------------------------------------------------------- /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', 'online_shop.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 | -------------------------------------------------------------------------------- /accounts/managers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import BaseUserManager 2 | 3 | 4 | class UserManager(BaseUserManager): 5 | def create_user(self, email, full_name, password): 6 | if not email: 7 | raise ValueError('Email is required!') 8 | if not full_name: 9 | raise ValueError('full name is required!') 10 | 11 | user = self.model(email=self.normalize_email(email), full_name=full_name) 12 | user.set_password(password) 13 | user.save(using=self.db) 14 | return user 15 | 16 | def create_superuser(self, email, full_name, password): 17 | user = self.create_user(email, full_name, password) 18 | user.is_admin = True 19 | user.save(using=self.db) 20 | return user 21 | -------------------------------------------------------------------------------- /accounts/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load crispy_forms_tags %} 3 | {% block content %} 4 |
5 |
6 | {% csrf_token %} 7 |

Login

8 |
9 | {{ form|crispy }} 10 |
11 | 12 |
13 |
14 | 15 | Forgot Password? 16 | 17 |
18 |
19 |
20 | 21 | New Here? Create account 22 | 23 |
24 |
25 | {% endblock %} -------------------------------------------------------------------------------- /shop/migrations/0003_alter_product_options_remove_product_category_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0 on 2022-01-05 08:56 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 | ('shop', '0002_alter_product_slug'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterModelOptions( 15 | name='product', 16 | options={}, 17 | ), 18 | migrations.RemoveField( 19 | model_name='product', 20 | name='category', 21 | ), 22 | migrations.AddField( 23 | model_name='product', 24 | name='category', 25 | field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.CASCADE, related_name='category', to='shop.category'), 26 | preserve_default=False, 27 | ), 28 | migrations.AlterField( 29 | model_name='product', 30 | name='slug', 31 | field=models.SlugField(null=True), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /dashboard/templates/orders.html: -------------------------------------------------------------------------------- 1 | {% extends "dashboard.html" %} 2 | {% block content %} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for order in orders %} 15 | 16 | 17 | 18 | 19 | 20 | 21 | {% if order.status %} 22 | 23 | {% else %} 24 | 25 | {% endif %} 26 | 27 | 28 | 29 | {% endfor %} 30 |
#UserIdtotal pricestatus
{{ forloop.counter }}{{ order.user.full_name }}{{ order.id }}${{ order.get_total_price }}SuccessfulCanceledDetail
31 | 32 | 33 | {% endblock %} 34 | 35 | -------------------------------------------------------------------------------- /shop/templates/favorites.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 | {% if products %} 4 |

Your Favorites

5 |
6 | {% for product in products %} 7 |
8 | 9 |
10 |
{{ product.title }}
11 |

${{ product.price }}

12 | Buy Now 13 |
14 | Remove 15 |
16 | {% endfor %} 17 | {% else %} 18 |
19 |
20 |
21 |
22 |

favorites list is empty..!

23 | Back 24 |
25 |
26 |
27 | {% endif %} 28 | {% endblock %} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sajad Zarei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /accounts/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import AbstractBaseUser 3 | 4 | from .managers import UserManager 5 | from shop.models import Product 6 | 7 | 8 | class User(AbstractBaseUser): 9 | email = models.EmailField(max_length=100, unique=True) 10 | full_name = models.CharField(max_length=100) 11 | is_admin = models.BooleanField(default=False) 12 | is_active = models.BooleanField(default=True) 13 | likes = models.ManyToManyField(Product, blank=True, related_name='likes') 14 | # set a manager role for shop manager to access orders and products 15 | is_manager = models.BooleanField(default=False) 16 | 17 | objects = UserManager() 18 | 19 | USERNAME_FIELD = 'email' 20 | REQUIRED_FIELDS = ['full_name'] 21 | 22 | 23 | def __str__(self): 24 | return self.email 25 | 26 | def has_perm(self, perm, obj=None): 27 | return True 28 | 29 | def has_module_perms(self, app_label): 30 | return True 31 | 32 | @property 33 | def is_staff(self): 34 | return self.is_admin 35 | 36 | def get_likes_count(self): 37 | return self.likes.count() -------------------------------------------------------------------------------- /accounts/templates/manager_login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Dashboard 9 | 10 | 11 |
12 |
13 |
14 |
15 |
16 | {% csrf_token %} 17 |

Dashboard

18 | {{ form }} 19 | 20 |
21 |

Back to shop

22 |
23 |
24 |
25 |
26 | 27 | -------------------------------------------------------------------------------- /accounts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0 on 2022-01-04 09:16 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='User', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('password', models.CharField(max_length=128, verbose_name='password')), 19 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 20 | ('email', models.EmailField(max_length=100, unique=True)), 21 | ('full_name', models.CharField(max_length=100)), 22 | ('is_admin', models.BooleanField(default=False)), 23 | ('is_active', models.BooleanField(default=True)), 24 | ('is_manager', models.BooleanField(default=False)), 25 | ], 26 | options={ 27 | 'abstract': False, 28 | }, 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /cart/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, get_object_or_404, redirect 2 | from django.contrib import messages 3 | from django.contrib.auth.decorators import login_required 4 | 5 | from cart.utils.cart import Cart 6 | from .forms import QuantityForm 7 | from shop.models import Product 8 | 9 | 10 | @login_required 11 | def add_to_cart(request, product_id): 12 | cart = Cart(request) 13 | product = get_object_or_404(Product, id=product_id) 14 | form = QuantityForm(request.POST) 15 | if form.is_valid(): 16 | data = form.cleaned_data 17 | cart.add(product=product, quantity=data['quantity']) 18 | messages.success(request, 'Added to your cart!', 'info') 19 | return redirect('shop:product_detail', slug=product.slug) 20 | 21 | 22 | @login_required 23 | def show_cart(request): 24 | cart = Cart(request) 25 | context = {'title': 'Cart', 'cart': cart} 26 | return render(request, 'cart.html', context) 27 | 28 | 29 | @login_required 30 | def remove_from_cart(request, product_id): 31 | cart = Cart(request) 32 | product = get_object_or_404(Product, id=product_id) 33 | cart.remove(product) 34 | return redirect('cart:show_cart') -------------------------------------------------------------------------------- /orders/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from accounts.models import User 4 | from shop.models import Product 5 | 6 | 7 | class Order(models.Model): 8 | user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='orders') 9 | created = models.DateTimeField(auto_now_add=True) 10 | updated = models.DateTimeField(auto_now=True) 11 | status = models.BooleanField(default=False) 12 | 13 | class Meta: 14 | ordering = ('-created',) 15 | 16 | def __str__(self): 17 | return f"{self.user.full_name} - order id: {self.id}" 18 | 19 | @property 20 | def get_total_price(self): 21 | total = sum(item.get_cost() for item in self.items.all()) 22 | return total 23 | 24 | 25 | class OrderItem(models.Model): 26 | order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items') 27 | product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='order_items') 28 | price = models.IntegerField() 29 | quantity = models.SmallIntegerField(default=1) 30 | 31 | def __str__(self): 32 | return str(self.id) 33 | 34 | def get_cost(self): 35 | return self.price * self.quantity -------------------------------------------------------------------------------- /dashboard/templates/products.html: -------------------------------------------------------------------------------- 1 | {% extends "dashboard.html" %} 2 | 3 | {% block content %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% for product in products %} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {% endfor %} 30 |
ntitlepricecategorydate
{{ forloop.counter }}{{ product.title }}${{ product.price }}{{ product.category }}{{ product.date_created|date:"M d" }}DeleteEdit
31 | 32 | {% endblock %} -------------------------------------------------------------------------------- /orders/templates/user_orders.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 | {% if orders %} 6 | {% for order in orders %} 7 |
8 | 9 | Order Id: {{ order.id }} 10 | 11 | {% if order.status %} 12 |

Successful

13 | {% else %} 14 |

Cancelled

15 | {% endif %} 16 |
17 | {% for item in order.items.all %} 18 |
19 |

product: {{item.product.title}}

20 |

Price: ${{item.price}}

21 |

quantity: {{item.quantity}}

22 |
23 | {% endfor %} 24 | Total price: ${{ order.get_total_price }} 25 |
26 | {% endfor %} 27 | {% else %} 28 |
29 |
30 |
31 |
32 |

Orders list is empty..!

33 | Back 34 |
35 |
36 |
37 | {% endif %} 38 | 39 | {% endblock %} -------------------------------------------------------------------------------- /shop/migrations/0006_alter_category_options_alter_product_options_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0 on 2022-01-08 11:26 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 | ('shop', '0005_alter_product_slug'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterModelOptions( 15 | name='category', 16 | options={'ordering': ('title',)}, 17 | ), 18 | migrations.AlterModelOptions( 19 | name='product', 20 | options={'ordering': ('-date_created',)}, 21 | ), 22 | migrations.AddField( 23 | model_name='category', 24 | name='is_sub', 25 | field=models.BooleanField(default=False), 26 | ), 27 | migrations.AddField( 28 | model_name='category', 29 | name='slug', 30 | field=models.SlugField(default=1, max_length=200, unique=True), 31 | preserve_default=False, 32 | ), 33 | migrations.AddField( 34 | model_name='category', 35 | name='sub_category', 36 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sub_categories', to='shop.category'), 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /shop/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0 on 2022-01-05 08:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Category', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('title', models.CharField(max_length=200)), 19 | ], 20 | ), 21 | migrations.CreateModel( 22 | name='Product', 23 | fields=[ 24 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 25 | ('image', models.ImageField(upload_to='products')), 26 | ('title', models.CharField(max_length=200)), 27 | ('description', models.TextField()), 28 | ('price', models.IntegerField()), 29 | ('date_created', models.DateTimeField(auto_now_add=True)), 30 | ('slug', models.SlugField(max_length=200)), 31 | ('category', models.ManyToManyField(related_name='category', to='shop.Category')), 32 | ], 33 | options={ 34 | 'ordering': ('title',), 35 | }, 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /dashboard/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.forms import ModelForm 3 | 4 | from shop.models import Product, Category 5 | 6 | 7 | class AddProductForm(ModelForm): 8 | class Meta: 9 | model = Product 10 | fields = ['category', 'image', 'title','description', 'price'] 11 | 12 | def __init__(self, *args, **kwargs): 13 | super(AddProductForm, self).__init__(*args, **kwargs) 14 | for visible in self.visible_fields(): 15 | visible.field.widget.attrs['class'] = 'form-control' 16 | 17 | 18 | class AddCategoryForm(ModelForm): 19 | class Meta: 20 | model = Category 21 | fields = ['title', 'sub_category', 'is_sub'] 22 | 23 | 24 | def __init__(self, *args, **kwargs): 25 | super(AddCategoryForm, self).__init__(*args, **kwargs) 26 | self.fields['is_sub'].widget.attrs['class'] = 'form-check-input' 27 | self.fields['sub_category'].widget.attrs['class'] = 'form-control' 28 | self.fields['title'].widget.attrs['class'] = 'form-control' 29 | 30 | 31 | class EditProductForm(ModelForm): 32 | class Meta: 33 | model = Product 34 | fields = ['category', 'image', 'title','description', 'price'] 35 | 36 | def __init__(self, *args, **kwargs): 37 | super(EditProductForm, self).__init__(*args, **kwargs) 38 | for visible in self.visible_fields(): 39 | visible.field.widget.attrs['class'] = 'form-control' -------------------------------------------------------------------------------- /orders/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect, get_object_or_404 2 | from django.contrib.auth.decorators import login_required 3 | 4 | from django.views.decorators.http import require_POST 5 | from django.utils import timezone 6 | 7 | from .models import Order, OrderItem 8 | from cart.utils.cart import Cart 9 | 10 | 11 | @login_required 12 | def create_order(request): 13 | cart = Cart(request) 14 | order = Order.objects.create(user=request.user) 15 | for item in cart: 16 | OrderItem.objects.create( 17 | order=order, product=item['product'], 18 | price=item['price'], quantity=item['quantity'] 19 | ) 20 | return redirect('orders:pay_order', order_id=order.id) 21 | 22 | 23 | @login_required 24 | def checkout(request, order_id): 25 | order = get_object_or_404(Order, id=order_id) 26 | context = {'title':'Checkout' ,'order':order} 27 | return render(request, 'checkout.html', context) 28 | 29 | 30 | @login_required 31 | def fake_payment(request, order_id): 32 | cart = Cart(request) 33 | cart.clear() 34 | order = get_object_or_404(Order, id=order_id) 35 | order.status = True 36 | order.save() 37 | return redirect('orders:user_orders') 38 | 39 | 40 | @login_required 41 | def user_orders(request): 42 | orders = request.user.orders.all() 43 | context = {'title':'Orders', 'orders': orders} 44 | return render(request, 'user_orders.html', context) -------------------------------------------------------------------------------- /accounts/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from .models import User 4 | 5 | 6 | class UserLoginForm(forms.Form): 7 | email = forms.EmailField( 8 | widget=forms.EmailInput( 9 | attrs={'class': 'form-control', 'placeholder': 'email'} 10 | ) 11 | ) 12 | password = forms.CharField( 13 | widget=forms.PasswordInput( 14 | attrs={'class': 'form-control', 'placeholder': 'password'} 15 | ) 16 | ) 17 | 18 | 19 | class UserRegistrationForm(forms.Form): 20 | email = forms.EmailField( 21 | widget=forms.EmailInput( 22 | attrs={'class': 'form-control', 'placeholder': 'email'} 23 | ) 24 | ) 25 | full_name = forms.CharField( 26 | widget=forms.TextInput( 27 | attrs={'class': 'form-control', 'placeholder': 'full name'} 28 | ) 29 | ) 30 | password = forms.CharField( 31 | widget=forms.PasswordInput( 32 | attrs={'class': 'form-control', 'placeholder': 'password'} 33 | ) 34 | ) 35 | 36 | 37 | class ManagerLoginForm(forms.Form): 38 | email = forms.EmailField( 39 | widget=forms.EmailInput( 40 | attrs={'class': 'form-control', 'placeholder': 'email'} 41 | ) 42 | ) 43 | password = forms.CharField( 44 | widget=forms.PasswordInput( 45 | attrs={'class': 'form-control', 'placeholder': 'password'} 46 | ) 47 | ) 48 | 49 | 50 | class EditProfileForm(forms.ModelForm): 51 | class Meta: 52 | model = User 53 | fields = ['full_name', 'email'] 54 | -------------------------------------------------------------------------------- /cart/templates/cart.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 | {% if cart_count != 0 %} 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for item in cart %} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {% endfor %} 30 |
productpricequantitytotal price
{{ forloop.counter }}{{ item.product.title }}${{ item.price }}{{ item.quantity }}${{ item.total_price }}remove
31 |
32 | Checkout 33 |

Total: ${{ cart.get_total_price }}

34 |
35 | {% else %} 36 |
37 |
38 |

cart is empty..!

39 | Back 40 |
41 | {% endif %} 42 |
43 | 44 | {% endblock %} -------------------------------------------------------------------------------- /shop/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.urls import reverse 3 | from django.template.defaultfilters import slugify 4 | 5 | 6 | class Category(models.Model): 7 | title = models.CharField(max_length=200) 8 | sub_category = models.ForeignKey( 9 | 'self', on_delete=models.CASCADE, 10 | related_name='sub_categories', null=True, blank=True 11 | ) 12 | is_sub = models.BooleanField(default=False) 13 | slug = models.SlugField(max_length=200, unique=True) 14 | 15 | def __str__(self): 16 | return self.title 17 | 18 | def get_absolute_url(self): 19 | return reverse('shop:product_detail', kwargs={'slug':self.slug}) 20 | 21 | def save(self, *args, **kwargs): # new 22 | self.slug = slugify(self.title) 23 | return super().save(*args, **kwargs) 24 | 25 | 26 | class Product(models.Model): 27 | category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='category') 28 | image = models.ImageField(upload_to='products') 29 | title = models.CharField(max_length=250) 30 | description = models.TextField() 31 | price = models.IntegerField() 32 | date_created = models.DateTimeField(auto_now_add=True) 33 | slug = models.SlugField(unique=True) 34 | 35 | class Meta: 36 | ordering = ('-date_created',) 37 | 38 | def __str__(self): 39 | return self.slug 40 | 41 | def get_absolute_url(self): 42 | return reverse('shop:product_detail', kwargs={'slug':self.slug}) 43 | 44 | def save(self, *args, **kwargs): 45 | self.slug = slugify(self.title) 46 | return super().save(*args, **kwargs) -------------------------------------------------------------------------------- /cart/utils/cart.py: -------------------------------------------------------------------------------- 1 | from shop.models import Product 2 | 3 | CART_SESSION_ID = 'cart' 4 | 5 | 6 | class Cart: 7 | def __init__(self, request): 8 | self.session = request.session 9 | self.cart = self.add_cart_session() 10 | 11 | def __iter__(self): 12 | product_ids = self.cart.keys() 13 | products = Product.objects.filter(id__in=product_ids) 14 | cart = self.cart.copy() 15 | for product in products: 16 | cart[str(product.id)]['product'] = product 17 | for item in cart.values(): 18 | item['total_price'] = int(item['price']) * int(item['quantity']) 19 | yield item 20 | 21 | def add_cart_session(self): 22 | cart = self.session.get(CART_SESSION_ID) 23 | if not cart: 24 | cart = self.session[CART_SESSION_ID] = {} 25 | return cart 26 | 27 | def add(self, product, quantity): 28 | product_id = str(product.id) 29 | 30 | if product_id not in self.cart: 31 | self.cart[product_id] = {'quantity': 0, 'price': str(product.price)} 32 | 33 | self.cart.get(product_id)['quantity'] += quantity 34 | self.save() 35 | 36 | def remove(self, product): 37 | product_id = str(product.id) 38 | if product_id in self.cart: 39 | del self.cart[product_id] 40 | self.save() 41 | 42 | def save(self): 43 | self.session.modified = True 44 | 45 | def get_total_price(self): 46 | return sum(int(item['price']) * item['quantity'] for item in self.cart.values()) 47 | 48 | def clear(self): 49 | del self.session[CART_SESSION_ID] 50 | self.save() -------------------------------------------------------------------------------- /accounts/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, reverse_lazy 2 | from django.contrib.auth import views as auth_views 3 | 4 | from accounts import views 5 | 6 | 7 | app_name = 'accounts' 8 | 9 | urlpatterns = [ 10 | path('register/', views.user_register, name='user_register'), 11 | path('login/', views.user_login, name='user_login'), 12 | path('login/manager/', views.manager_login, name='manager_login'), 13 | path('logout/', views.user_logout, name='user_logout'), 14 | path('profile/edit', views.edit_profile, name='edit_profile'), 15 | path( 16 | 'password-reset/', 17 | auth_views.PasswordResetView.as_view( 18 | template_name='password_reset.html', 19 | success_url=reverse_lazy('accounts:password_reset_done'), 20 | email_template_name='email_template.html' 21 | ), 22 | name='password_reset' 23 | ), 24 | path( 25 | 'password-reset/done', 26 | auth_views.PasswordResetDoneView.as_view( 27 | template_name='password_reset_done.html', 28 | ), 29 | name='password_reset_done' 30 | ), 31 | path( 32 | 'password-reset-confirm///', 33 | auth_views.PasswordResetConfirmView.as_view( 34 | template_name='password_reset_confirm.html', 35 | success_url=reverse_lazy('accounts:password_reset_complete'), 36 | ), 37 | name='password_reset_confirm' 38 | ), 39 | path( 40 | 'password-reset-complete/', 41 | auth_views.PasswordResetCompleteView.as_view( 42 | template_name='password_reset_complete.html', 43 | ), 44 | name='password_reset_complete' 45 | ), 46 | ] 47 | 48 | -------------------------------------------------------------------------------- /orders/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0 on 2022-01-08 08:06 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 | ('accounts', '0004_user_likes'), 13 | ('shop', '0005_alter_product_slug'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Order', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('created', models.DateTimeField(auto_now_add=True)), 22 | ('updated', models.DateTimeField(auto_now=True)), 23 | ('status', models.BooleanField(default=False)), 24 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='accounts.user')), 25 | ], 26 | options={ 27 | 'ordering': ('-created',), 28 | }, 29 | ), 30 | migrations.CreateModel( 31 | name='OrderItem', 32 | fields=[ 33 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 34 | ('price', models.IntegerField()), 35 | ('quantity', models.SmallIntegerField(default=1)), 36 | ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='orders.order')), 37 | ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='order_items', to='shop.product')), 38 | ], 39 | ), 40 | ] 41 | -------------------------------------------------------------------------------- /shop/templates/home_page.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 | {% if products %} 5 | {% for product in products.object_list %} 6 |
7 | 8 |
9 |
{{ product.title }}
10 |

${{ product.price }}

11 | Buy Now 12 |
13 |
14 | {% endfor %} 15 | 16 |
17 |
18 | 29 |
30 |
31 | {% else %} 32 |
33 |
34 |
35 |
36 |

No Products..!

37 |
38 |
39 |
40 | {% endif %} 41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /shop/templates/product_detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 | 5 |
6 |
7 | 8 |
9 | 10 |
11 | categories: {{product.category}} 12 |
13 | 14 |

{{ product.title }}

15 | 16 |

Price: ${{ product.price }}

17 | 18 |
{{ product.description }}
19 | 20 |
21 | {% csrf_token %} 22 | {{ form }} 23 | 24 |
25 |
26 | {% if favorites == 'remove' %} 27 | {{favorites}} from favorites 28 | {% else %} 29 | add to {{favorites}} 30 | {% endif %} 31 |
32 | 33 |
34 | 35 |
36 |
37 | 38 |
39 |

Related Products:


40 | {% for p in related_products %} 41 | 42 | {% if p != product %} 43 |
44 | 45 |
46 |
{{ p.title }}
47 |

${{ p.price }}

48 | Buy Now 49 |
50 |
51 | {% endif %} 52 | {% endfor %} 53 |
54 | {% endblock %} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | *.jpg 7 | *.png 8 | *.webp 9 | *.gif 10 | # C extensions 11 | *.so 12 | .idea/ 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | pip-wheel-metadata/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | -------------------------------------------------------------------------------- /dashboard/templates/dashboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{title}} 9 | 10 | 11 |
12 |
13 | 14 |
15 |

{{ title }}

16 |
17 | 18 | {% if messages %} 19 | {% for message in messages %} 20 |
{{ message }}
21 | {% endfor %} 22 | {% endif %} 23 | {% block content %}{% endblock %} 24 |
25 |
26 | 27 |
28 |

Menu

29 | 30 |
  • Products
  • 31 |
    32 | 33 |
  • Add New Product
  • 34 |
    35 | 36 |
  • Add New Category
  • 37 |
    38 | 39 |
  • Orders
  • 40 |
    41 |
    42 | 43 |
  • Back To shop
  • 44 |
    45 | 46 |
  • Logout
  • 47 |
    48 |
    You logged in as: {{ request.user.email }}
    49 |
    50 |
    51 |
    52 | 53 | -------------------------------------------------------------------------------- /shop/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect, get_object_or_404 2 | from django.contrib.auth.decorators import login_required 3 | from django.contrib import messages 4 | from django.core.paginator import Paginator 5 | 6 | from shop.models import Product, Category 7 | from cart.forms import QuantityForm 8 | 9 | 10 | def paginat(request, list_objects): 11 | p = Paginator(list_objects, 20) 12 | page_number = request.GET.get('page') 13 | try: 14 | page_obj = p.get_page(page_number) 15 | except PageNotAnInteger: 16 | page_obj = p.page(1) 17 | except EmptyPage: 18 | page_obj = p.page(p.num_pages) 19 | return page_obj 20 | 21 | 22 | def home_page(request): 23 | products = Product.objects.all() 24 | context = {'products': paginat(request ,products)} 25 | return render(request, 'home_page.html', context) 26 | 27 | 28 | def product_detail(request, slug): 29 | form = QuantityForm() 30 | product = get_object_or_404(Product, slug=slug) 31 | related_products = Product.objects.filter(category=product.category).all()[:5] 32 | context = { 33 | 'title':product.title, 34 | 'product':product, 35 | 'form':form, 36 | 'favorites':'favorites', 37 | 'related_products':related_products 38 | } 39 | if request.user.likes.filter(id=product.id).first(): 40 | context['favorites'] = 'remove' 41 | return render(request, 'product_detail.html', context) 42 | 43 | 44 | @login_required 45 | def add_to_favorites(request, product_id): 46 | product = get_object_or_404(Product, id=product_id) 47 | request.user.likes.add(product) 48 | return redirect('shop:product_detail', slug=product.slug) 49 | 50 | 51 | @login_required 52 | def remove_from_favorites(request, product_id): 53 | product = get_object_or_404(Product, id=product_id) 54 | request.user.likes.remove(product) 55 | return redirect('shop:favorites') 56 | 57 | 58 | @login_required 59 | def favorites(request): 60 | products = request.user.likes.all() 61 | context = {'title':'Favorites', 'products':products} 62 | return render(request, 'favorites.html', context) 63 | 64 | 65 | def search(request): 66 | query = request.GET.get('q') 67 | products = Product.objects.filter(title__icontains=query).all() 68 | context = {'products': paginat(request ,products)} 69 | return render(request, 'home_page.html', context) 70 | 71 | 72 | def filter_by_category(request, slug): 73 | """when user clicks on parent category 74 | we want to show all products in its sub-categories too 75 | """ 76 | result = [] 77 | category = Category.objects.filter(slug=slug).first() 78 | [result.append(product) \ 79 | for product in Product.objects.filter(category=category.id).all()] 80 | # check if category is parent then get all sub-categories 81 | if not category.is_sub: 82 | sub_categories = category.sub_categories.all() 83 | # get all sub-categories products 84 | for category in sub_categories: 85 | [result.append(product) \ 86 | for product in Product.objects.filter(category=category).all()] 87 | context = {'products': paginat(request ,result)} 88 | return render(request, 'home_page.html', context) 89 | -------------------------------------------------------------------------------- /dashboard/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect, get_object_or_404 2 | from django.contrib import messages 3 | from django.contrib.auth.decorators import login_required 4 | from django.contrib.auth.decorators import user_passes_test 5 | from django.http import Http404 6 | 7 | from shop.models import Product 8 | from accounts.models import User 9 | from orders.models import Order, OrderItem 10 | from .forms import AddProductForm, AddCategoryForm, EditProductForm 11 | 12 | 13 | def is_manager(user): 14 | try: 15 | if not user.is_manager: 16 | raise Http404 17 | return True 18 | except: 19 | raise Http404 20 | 21 | 22 | @user_passes_test(is_manager) 23 | @login_required 24 | def products(request): 25 | products = Product.objects.all() 26 | context = {'title':'Products' ,'products':products} 27 | return render(request, 'products.html', context) 28 | 29 | 30 | @user_passes_test(is_manager) 31 | @login_required 32 | def add_product(request): 33 | if request.method == 'POST': 34 | form = AddProductForm(request.POST, request.FILES) 35 | if form.is_valid(): 36 | form.save() 37 | messages.success(request, 'Product added Successfuly!') 38 | return redirect('dashboard:add_product') 39 | else: 40 | form = AddProductForm() 41 | context = {'title':'Add Product', 'form':form} 42 | return render(request, 'add_product.html', context) 43 | 44 | 45 | @user_passes_test(is_manager) 46 | @login_required 47 | def delete_product(request, id): 48 | product = Product.objects.filter(id=id).delete() 49 | messages.success(request, 'product has been deleted!', 'success') 50 | return redirect('dashboard:products') 51 | 52 | 53 | @user_passes_test(is_manager) 54 | @login_required 55 | def edit_product(request, id): 56 | product = get_object_or_404(Product, id=id) 57 | if request.method == 'POST': 58 | form = EditProductForm(request.POST, request.FILES, instance=product) 59 | if form.is_valid(): 60 | form.save() 61 | messages.success(request, 'Product has been updated', 'success') 62 | return redirect('dashboard:products') 63 | else: 64 | form = EditProductForm(instance=product) 65 | context = {'title': 'Edit Product', 'form':form} 66 | return render(request, 'edit_product.html', context) 67 | 68 | 69 | @user_passes_test(is_manager) 70 | @login_required 71 | def add_category(request): 72 | if request.method == 'POST': 73 | form = AddCategoryForm(request.POST) 74 | if form.is_valid(): 75 | form.save() 76 | messages.success(request, 'Category added Successfuly!') 77 | return redirect('dashboard:add_category') 78 | else: 79 | form = AddCategoryForm() 80 | context = {'title':'Add Category', 'form':form} 81 | return render(request, 'add_category.html', context) 82 | 83 | 84 | @user_passes_test(is_manager) 85 | @login_required 86 | def orders(request): 87 | orders = Order.objects.all() 88 | context = {'title':'Orders', 'orders':orders} 89 | return render(request, 'orders.html', context) 90 | 91 | 92 | @user_passes_test(is_manager) 93 | @login_required 94 | def order_detail(request, id): 95 | order = Order.objects.filter(id=id).first() 96 | items = OrderItem.objects.filter(order=order).all() 97 | context = {'title':'order detail', 'items':items, 'order':order} 98 | return render(request, 'order_detail.html', context) -------------------------------------------------------------------------------- /accounts/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect 2 | from django.contrib import messages 3 | from django.contrib.auth import authenticate, login, logout 4 | 5 | from .forms import UserRegistrationForm, UserLoginForm, ManagerLoginForm, EditProfileForm 6 | from accounts.models import User 7 | 8 | 9 | def create_manager(): 10 | """ 11 | to execute once on startup: 12 | this function will call in online_shop/urls.py 13 | """ 14 | if not User.objects.filter(email="manager@example.com").first(): 15 | user = User.objects.create_user( 16 | "manager@example.com", 'shop manager' ,'managerpass1234' 17 | ) 18 | # give this user manager role 19 | user.is_manager = True 20 | user.save() 21 | 22 | 23 | def manager_login(request): 24 | if request.method == 'POST': 25 | form = ManagerLoginForm(request.POST) 26 | if form.is_valid(): 27 | data = form.cleaned_data 28 | user = authenticate( 29 | request, email=data['email'], password=data['password'] 30 | ) 31 | if user is not None and user.is_manager: 32 | login(request, user) 33 | return redirect('dashboard:products') 34 | else: 35 | messages.error( 36 | request, 'username or password is wrong', 'danger' 37 | ) 38 | return redirect('accounts:manager_login') 39 | else: 40 | form = ManagerLoginForm() 41 | context = {'form': form} 42 | return render(request, 'manager_login.html', context) 43 | 44 | 45 | def user_register(request): 46 | if request.method == 'POST': 47 | form = UserRegistrationForm(request.POST) 48 | if form.is_valid(): 49 | data = form.cleaned_data 50 | user = User.objects.create_user( 51 | data['email'], data['full_name'], data['password'] 52 | ) 53 | return redirect('accounts:user_login') 54 | else: 55 | form = UserRegistrationForm() 56 | context = {'title':'Signup', 'form':form} 57 | return render(request, 'register.html', context) 58 | 59 | 60 | def user_login(request): 61 | if request.method == 'POST': 62 | form = UserLoginForm(request.POST) 63 | if form.is_valid(): 64 | data = form.cleaned_data 65 | user = authenticate( 66 | request, email=data['email'], password=data['password'] 67 | ) 68 | if user is not None: 69 | login(request, user) 70 | return redirect('shop:home_page') 71 | else: 72 | messages.error( 73 | request, 'username or password is wrong', 'danger' 74 | ) 75 | return redirect('accounts:user_login') 76 | else: 77 | form = UserLoginForm() 78 | context = {'title':'Login', 'form': form} 79 | return render(request, 'login.html', context) 80 | 81 | 82 | def user_logout(request): 83 | logout(request) 84 | return redirect('accounts:user_login') 85 | 86 | 87 | def edit_profile(request): 88 | form = EditProfileForm(request.POST, instance=request.user) 89 | if form.is_valid(): 90 | form.save() 91 | messages.success(request, 'Your profile has been updated', 'success') 92 | return redirect('accounts:edit_profile') 93 | else: 94 | form = EditProfileForm(instance=request.user) 95 | context = {'title':'Edit Profile', 'form':form} 96 | return render(request, 'edit_profile.html', context) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Online Shop: Manage Products and Orders with Django 2 | ![Screenshot 2022-01-09 at 17-04-55 Django Online Shop](https://user-images.githubusercontent.com/71011395/148684469-79bfdb07-efa0-4dde-ad76-1f3277f833e6.png) 3 | 4 | 5 | This project is a simple but usable online shop written with Django. The app provides a custom dashboard to manage products and orders. Users can like a product, add it to the cart, and proceed to checkout. Order processing is supported, but the payment is handled using a fake pay system. 6 | 7 | [Preview](#app-preview) 8 | 9 | ## Features 10 | 11 | There are two types of users in this app: regular users and managers. 12 | 13 | ### Available to the Users: 14 | 15 | - **Cart**: Users can manage items in their cart. 16 | - **Edit Personal Information**: Users can update their personal details. 17 | - **Orders**: Users can view their order history. 18 | - **Favorites**: Users can like and save their favorite products. 19 | - **Reset Password**: Users can reset their password using their registered email. 20 | 21 | ### Available to the Managers: 22 | 23 | Managers can access all the features available to regular users, along with additional capabilities, through the custom dashboard accessible at [http://127.0.0.1:8000/accounts/login/manager](http://127.0.0.1:8000/accounts/login/manager). 24 | 25 | - **Add Product**: Managers can add new products to the shop. 26 | - **Edit and Delete Product**: Managers can modify or remove existing products. 27 | - **Add New Category**: Managers have the ability to create new categories for products. 28 | - **Access to Orders**: Managers can view and manage all orders and order items. 29 | 30 | ## Manager Dashboard Access 31 | 32 | To access the custom dashboard for managers, please use the following credentials: 33 | 34 | - Email: manager@example.com 35 | - Password: managerpass1234 36 | 37 | ## Technologies Used 38 | 39 | - Python 3 40 | - Django 41 | - Bootstrap 42 | - SQLite3 database 43 | 44 | ## How to Run the Application 45 | 46 | 1. Clone or download the project to your local machine. 47 | 2. Change directory to the "online-shop-django" folder. 48 | 3. Ensure that you have Python 3, pip, and virtualenv installed on your machine. 49 | 4. Create a virtual environment using the following command: 50 | - For Mac and Linux: `python3 -m venv venv` 51 | - For Windows: `python -m venv venv` 52 | 5. Activate the virtual environment: 53 | - For Mac and Linux: `source venv/bin/activate` 54 | - For Windows: `venv\scripts\activate` 55 | 6. Install the application requirements by running: `pip install -r requirements.txt` 56 | 7. Migrate the database by executing: `python manage.py migrate` 57 | 8. Start the server: `python manage.py runserver` 58 | 9. You should now be able to access the application by visiting: [http://127.0.0.1:8000/](http://127.0.0.1:8000/) 59 | 60 | ## How to Contribute 61 | 62 | I welcome contributions to enhance and customize this project. If you would like to contribute, please follow these steps: 63 | 64 | 1. Fork the repository on GitHub. 65 | 2. Create a new branch with a descriptive name for your feature or bug fix. 66 | 3. Make the necessary changes in your branch. 67 | 4. Test your changes thoroughly. 68 | 5. Commit your changes and push them to your forked repository. 69 | 6. Submit a pull request, clearly describing the changes you have made. 70 | 71 | ## License 72 | 73 | released under the [MIT License](LICENSE). Feel free to use, modify, and distribute it as per the terms of the license. 74 | 75 | Feel free to explore, contribute, and customize this according to your needs! 76 | 77 | 78 | ## App Preview 79 | ![Peek 2022-01-09 19-15](https://user-images.githubusercontent.com/71011395/148689722-6ceacc8f-81b7-48e0-a258-9d4e543d1e7c.gif) 80 | -------------------------------------------------------------------------------- /online_shop/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 5 | BASE_DIR = Path(__file__).resolve().parent.parent 6 | 7 | 8 | # Quick-start development settings - unsuitable for production 9 | # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ 10 | 11 | # SECURITY WARNING: keep the secret key used in production secret! 12 | SECRET_KEY = 'django-insecure-kr1p-zj6!vss$(xd2f7vk8nw*3g@-ao92zzg8^@u!mj(l#s)+i' 13 | 14 | # SECURITY WARNING: don't run with debug turned on in production! 15 | DEBUG = True 16 | 17 | ALLOWED_HOSTS = [] 18 | 19 | 20 | # Application definition 21 | 22 | INSTALLED_APPS = [ 23 | 'django.contrib.admin', 24 | 'django.contrib.auth', 25 | 'django.contrib.contenttypes', 26 | 'django.contrib.sessions', 27 | 'django.contrib.messages', 28 | 'django.contrib.staticfiles', 29 | 'crispy_forms', 30 | 'accounts.apps.AccountsConfig', 31 | 'cart.apps.CartConfig', 32 | 'orders.apps.OrdersConfig', 33 | 'shop.apps.ShopConfig', 34 | 'dashboard.apps.DashboardConfig', 35 | ] 36 | 37 | MIDDLEWARE = [ 38 | 'django.middleware.security.SecurityMiddleware', 39 | 'django.contrib.sessions.middleware.SessionMiddleware', 40 | 'django.middleware.common.CommonMiddleware', 41 | 'django.middleware.csrf.CsrfViewMiddleware', 42 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 43 | 'django.contrib.messages.middleware.MessageMiddleware', 44 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 45 | ] 46 | 47 | ROOT_URLCONF = 'online_shop.urls' 48 | 49 | TEMPLATES = [ 50 | { 51 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 52 | 'DIRS': [], 53 | 'APP_DIRS': True, 54 | 'OPTIONS': { 55 | 'context_processors': [ 56 | 'django.template.context_processors.debug', 57 | 'django.template.context_processors.request', 58 | 'django.contrib.auth.context_processors.auth', 59 | 'django.contrib.messages.context_processors.messages', 60 | 'online_shop.context_processors.return_cart', 61 | 'online_shop.context_processors.return_categories', 62 | ], 63 | }, 64 | }, 65 | ] 66 | 67 | WSGI_APPLICATION = 'online_shop.wsgi.application' 68 | 69 | 70 | # Database 71 | # https://docs.djangoproject.com/en/4.0/ref/settings/#databases 72 | 73 | DATABASES = { 74 | 'default': { 75 | 'ENGINE': 'django.db.backends.sqlite3', 76 | 'NAME': BASE_DIR / 'db.sqlite3', 77 | } 78 | } 79 | 80 | AUTH_USER_MODEL = 'accounts.User' 81 | 82 | # Password validation 83 | # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators 84 | 85 | AUTH_PASSWORD_VALIDATORS = [ 86 | { 87 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 88 | }, 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 91 | }, 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 97 | }, 98 | ] 99 | 100 | 101 | # Internationalization 102 | # https://docs.djangoproject.com/en/4.0/topics/i18n/ 103 | 104 | LANGUAGE_CODE = 'en-us' 105 | 106 | TIME_ZONE = 'UTC' 107 | 108 | USE_I18N = True 109 | 110 | USE_TZ = True 111 | 112 | 113 | # Static files (CSS, JavaScript, Images) 114 | # https://docs.djangoproject.com/en/4.0/howto/static-files/ 115 | 116 | STATIC_URL = 'static/' 117 | 118 | # directory that we want to store uploaded files 119 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 120 | MEDIA_URL = '/media/' 121 | 122 | # Default primary key field type 123 | # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field 124 | 125 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 126 | 127 | 128 | CRISPY_TEMPLATE_PACK = 'bootstrap4' 129 | 130 | LOGIN_URL = 'accounts:user_login' 131 | 132 | 133 | EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' 134 | EMAIL_HOST = 'smtp.gmail.com' 135 | EMAIL_PORT = 587 136 | EMAIL_USE_SSL = False 137 | EMAIL_USE_TLS = True 138 | EMAIL_HOST_USER = 'username@example.com' 139 | EMAIL_HOST_PASSWORD = 'your-password' -------------------------------------------------------------------------------- /shop/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% if title %} {{ title }} {% else %} Django Online Shop {% endif %} 13 | 14 | 15 |
    16 |
    17 |
    18 | 19 | {% if request.user.is_authenticated %} 20 | {{ cart_count }} 21 | {% endif %} 22 | 23 | 24 | {% if request.user.is_authenticated %} 25 | {{ request.user.get_likes_count}} 26 | {% endif %} 27 | 28 | 29 | 52 | 53 |
    54 | 55 |
    56 | {% if request.user.is_authenticated %} 57 | 58 | 68 | {% else %} 69 | 70 |
    71 | Login 72 | Sign-up 73 |
    74 | {% endif %} 75 |
    76 |
    77 |
    78 |
    79 |
    80 | 81 | {% if messages %} 82 | {% for message in messages %} 83 |
    {{ message }}
    84 | {% endfor %} 85 | {% endif %} 86 | 87 | {% block content %}{% endblock %} 88 |
    89 |
    90 | 91 | 92 | 93 | 94 | --------------------------------------------------------------------------------