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 |
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 |
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 |
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 |
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 | User
8 | Id
9 | total price
10 | status
11 |
12 |
13 |
14 | {% for order in orders %}
15 |
16 |
17 | {{ forloop.counter }}
18 | {{ order.user.full_name }}
19 | {{ order.id }}
20 | ${{ order.get_total_price }}
21 | {% if order.status %}
22 | Successful
23 | {% else %}
24 | Canceled
25 | {% endif %}
26 | Detail
27 |
28 |
29 | {% endfor %}
30 |
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 |
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 | n
9 | title
10 | price
11 | category
12 | date
13 |
14 |
15 |
16 |
17 | {% for product in products %}
18 |
19 |
20 | {{ forloop.counter }}
21 | {{ product.title }}
22 | ${{ product.price }}
23 | {{ product.category }}
24 | {{ product.date_created|date:"M d" }}
25 | Delete
26 | Edit
27 |
28 |
29 | {% endfor %}
30 |
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 |
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 | product
12 | price
13 | quantity
14 | total price
15 |
16 |
17 |
18 | {% for item in cart %}
19 |
20 |
21 | {{ forloop.counter }}
22 | {{ item.product.title }}
23 | ${{ item.price }}
24 | {{ item.quantity }}
25 | ${{ item.total_price }}
26 | remove
27 |
28 |
29 | {% endfor %}
30 |
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 |
13 |
14 |
{{ product.title }}
15 |
16 |
Price: ${{ product.price }}
17 |
18 |
{{ product.description }}
19 |
20 |
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 |
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 | 
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 | 
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 |
30 | Home
31 | Shop
32 |
33 |
34 |
Categories
35 |
48 |
49 | FAQs
50 | About
51 |
52 |
53 |
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 |
--------------------------------------------------------------------------------