├── apps
├── __init__.py
├── tests.py
├── apps.py
├── permissions.py
├── admin.py
├── tasks.py
├── urls.py
├── serializers.py
├── models.py
└── views.py
├── accounts
├── __init__.py
├── models.py
├── admin.py
├── tests.py
├── apps.py
├── urls.py
├── serializers.py
├── views.py
└── services.py
├── root
├── __init__.py
├── asgi.py
├── wsgi.py
├── celery.py
├── urls.py
└── settings.py
├── TODO
├── manage.py
├── .github
└── workflows
│ └── django.yml
├── requirements.txt
└── README.md
/apps/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/accounts/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/accounts/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 |
--------------------------------------------------------------------------------
/apps/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/accounts/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | # Register your models here.
4 |
--------------------------------------------------------------------------------
/accounts/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/root/__init__.py:
--------------------------------------------------------------------------------
1 | from .celery import app as celery_app
2 |
3 | __all__ = ('celery_app', )
--------------------------------------------------------------------------------
/apps/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class AppsConfig(AppConfig):
5 | default_auto_field = 'django.db.models.BigAutoField'
6 | name = 'apps'
7 |
--------------------------------------------------------------------------------
/accounts/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class AuthConfig(AppConfig):
5 | default_auto_field = 'django.db.models.BigAutoField'
6 | name = 'accounts'
7 |
--------------------------------------------------------------------------------
/accounts/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from accounts.views import RegisterAPIView, LogoutAPIView
4 |
5 | urlpatterns = [
6 | path('register/', RegisterAPIView.as_view(), name='register'),
7 | path('logout/', LogoutAPIView.as_view(), name='logout'),
8 |
9 | ]
10 |
--------------------------------------------------------------------------------
/accounts/serializers.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import User
2 | from rest_framework.serializers import ModelSerializer
3 |
4 |
5 | class UserSerializer(ModelSerializer):
6 | class Meta:
7 | model = User
8 | fields = ('first_name', 'last_name', 'email', 'username')
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/apps/permissions.py:
--------------------------------------------------------------------------------
1 | from rest_framework.permissions import BasePermission, SAFE_METHODS
2 |
3 |
4 | class IsAuthenticatedOrReadOnly2(BasePermission):
5 |
6 | def has_permission(self, request, view):
7 | if request.method in SAFE_METHODS:
8 | return True
9 | else:
10 | return False
--------------------------------------------------------------------------------
/root/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for root project.
3 |
4 | It exposes the ASGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.asgi import get_asgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'root.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/root/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for root project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'root.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | Product , Product Detail, Update, Popular product, New product ✅
2 | Filter: price, category, color ✅
3 | Login, Register, Logout ✅
4 | Blog list, Blog detail ✅
5 | Order by product ✅
6 |
7 | News send to user_email with celery ✅
8 | Email sender with celer ✅
9 | Subscribe: email ✅
10 | Shopping Card ✅
11 | Pagination ✅
12 | WishList ✅
13 |
14 | Search ✅
15 | Comment ✅
16 | Category ✅
17 | Last new ✅
18 | New collection ✅
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/root/celery.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from celery import Celery
4 |
5 | # Set the default Django settings module for the 'celery' program.
6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'root.settings')
7 |
8 | app = Celery('root')
9 |
10 | # Using a string here means the worker doesn't have to serialize
11 | # the configuration object to child processes.
12 | # - namespace='CELERY' means all celery-related configuration keys
13 | # should have a `CELERY_` prefix.
14 | app.config_from_object('django.conf:settings', namespace='CELERY')
15 |
16 | # Load task modules from all registered Django apps.
17 | app.autodiscover_tasks()
18 |
19 |
20 | @app.task(bind=True)
21 | def debug_task(self):
22 | print(f'Request: {self.request!r}')
23 |
--------------------------------------------------------------------------------
/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', 'root.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 |
--------------------------------------------------------------------------------
/.github/workflows/django.yml:
--------------------------------------------------------------------------------
1 | name: Django CI
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 | strategy:
14 | max-parallel: 4
15 | matrix:
16 | python-version: [3.7, 3.8, 3.9]
17 |
18 | steps:
19 | - uses: actions/checkout@v3
20 | - name: Set up Python ${{ matrix.python-version }}
21 | uses: actions/setup-python@v3
22 | with:
23 | python-version: ${{ matrix.python-version }}
24 | - name: Install Dependencies
25 | run: |
26 | python -m pip install --upgrade pip
27 | pip install -r requirements.txt
28 | - name: Run Tests
29 | run: |
30 | python manage.py test
31 |
--------------------------------------------------------------------------------
/apps/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from .models import Category, Product, New, ProductImage
4 | from .tasks import send_email_customer, send_email_customer1
5 |
6 |
7 | class CategoryAdmin(admin.ModelAdmin):
8 | list_display = ('id', 'name', 'parent')
9 |
10 |
11 | admin.site.register(Category, CategoryAdmin)
12 |
13 |
14 | class NewAdmin(admin.ModelAdmin):
15 | list_display = ('title',)
16 |
17 | def save_model(self, request, obj, form, change):
18 | super().save_model(request, obj, form, change)
19 | send_email_customer1.apply_async(args=[self, request, obj, form])
20 |
21 |
22 | admin.site.register(New, NewAdmin)
23 |
24 |
25 | class ProductAdmin(admin.ModelAdmin):
26 | list_display = ('title', 'price')
27 |
28 |
29 | admin.site.register(Product, ProductAdmin)
30 |
31 |
32 | class ProductImageAdmin(admin.ModelAdmin):
33 | list_display = ['image', 'product']
34 |
35 |
36 | admin.site.register(ProductImage, ProductImageAdmin)
37 |
--------------------------------------------------------------------------------
/apps/tasks.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from celery import shared_task
4 | from django.core.mail import send_mail
5 | from django.shortcuts import get_object_or_404
6 |
7 | from apps.models import Subscribe, New
8 |
9 |
10 | # Send email
11 | @shared_task
12 | def send_email_customer(email, message, name, phone):
13 | print('Sending message')
14 | msg = f'''
15 | {email}, {message}, {name}, {phone}
16 | '''
17 | print(msg)
18 | send_mail(
19 | subject="Hello",
20 | message=msg,
21 | from_email=os.getenv("EMAIL_HOST_USER"),
22 | recipient_list=[email],
23 | fail_silently=False,
24 | )
25 |
26 |
27 | # Send new user_email
28 | @shared_task()
29 | def send_email_customer1(pk):
30 | new = get_object_or_404(New, id=pk)
31 | for i in Subscribe.objects.all():
32 | print('sending message')
33 | send_mail(
34 | subject=i.pk,
35 | # message=new,
36 | from_email=os.getenv("EMAIL_HOST_USER"),
37 | recipient_list=[i.email],
38 | fail_silently=False,
39 | )
40 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | amqp==5.1.1
2 | asgiref==3.6.0
3 | async-timeout==4.0.2
4 | billiard==3.6.4.0
5 | celery==5.2.7
6 | certifi==2023.5.7
7 | cffi==1.15.1
8 | charset-normalizer==3.1.0
9 | click==8.1.3
10 | click-didyoumean==0.3.0
11 | click-plugins==1.1.1
12 | click-repl==0.2.0
13 | coreapi==2.3.3
14 | coreschema==0.0.4
15 | cryptography==40.0.2
16 | Django==4.2.1
17 | django-celery-results==2.5.1
18 | django-filter==23.2
19 | django-js-asset==2.0.0
20 | django-mptt==0.14.0
21 | django-reviews==1.2.1
22 | djangorestframework==3.14.0
23 | djangorestframework-simplejwt==5.2.2
24 | drf-yasg==1.21.5
25 | idna==3.4
26 | inflection==0.5.1
27 | itypes==1.2.0
28 | Jinja2==3.1.2
29 | kombu==5.2.4
30 | MarkupSafe==2.1.2
31 | packaging==23.1
32 | Pillow==9.5.0
33 | prompt-toolkit==3.0.38
34 | psycopg2-binary==2.9.6
35 | pycparser==2.21
36 | Pygments==2.15.1
37 | PyJWT==2.7.0
38 | python-dotenv==1.0.0
39 | pytz==2023.3
40 | redis==4.5.5
41 | requests==2.30.0
42 | ruamel.yaml==0.17.26
43 | ruamel.yaml.clib==0.2.7
44 | six==1.16.0
45 | sqlparse==0.4.4
46 | uritemplate==4.1.1
47 | urllib3==2.0.2
48 | vine==5.0.0
49 | wcwidth==0.2.6
50 |
--------------------------------------------------------------------------------
/accounts/views.py:
--------------------------------------------------------------------------------
1 | from rest_framework.permissions import IsAuthenticated
2 | from rest_framework.views import APIView
3 | from rest_framework.response import Response
4 | from rest_framework_simplejwt.tokens import RefreshToken
5 |
6 | from accounts.services import register_service, reset_password_service, reset_password_confirm_service
7 |
8 |
9 | # Register API
10 | class RegisterAPIView(APIView):
11 | def post(self, request):
12 | response = register_service(request.data)
13 | if response['success']:
14 | return Response(status=201)
15 | return Response(response, status=405)
16 |
17 |
18 | # Reset Password API
19 |
20 | class ResetPasswordAPIView(APIView):
21 | def post(self, request):
22 | responce = reset_password_service(request)
23 | if responce['success']:
24 | return Response({'message': 'sent'})
25 | return Response(responce, status=404)
26 |
27 |
28 | # Reset Password Confirm API
29 |
30 | class PasswordResetConfirmAPIView(APIView):
31 |
32 | def post(self, request, token, uuid):
33 | response = reset_password_confirm_service(request, token, uuid)
34 | if response['success']:
35 | return Response({'message': 'Password changed'})
36 | return Response(response, status=400)
37 |
38 |
39 | # Logout API
40 |
41 | class LogoutAPIView(APIView):
42 | permission_classes = (IsAuthenticated,)
43 |
44 | def post(self, request):
45 | token = RefreshToken(request.user)
46 | token.blacklist()
47 | return Response(status=200)
48 |
49 | # User Order API
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Ecommerce Rest API
2 |
3 |
4 |
5 | An Ecommerce Store Backend to Sell Products. Built with Python/Django Rest Framework.
6 |
7 |
8 | ## Table of Contents
9 |
10 | - [Introduction](#introduction)
11 | - [Features](#features)
12 | - [Installation Process](#installation-process)
13 | - [Feedback](#feedback)
14 |
15 | ## Introduction
16 |
17 | The DRF-Ecommerce-API offers API endpoints that enable the sale of tangible goods.
18 |
19 | ## Features
20 |
21 | Several functionalities are available through this application:
22 |
23 | * Admin can create/update/delete Product, Category
24 | * Admin can create/update/delete Product Detail
25 | * Authenticated Users can make POST requests to Product Category & Product Detail
26 | * Unauthenticated Users can only make GET requests to Product Category & Product Detail
27 | * Users can SignUp to be authorized
28 |
29 | ## Installation Process
30 |
31 |
32 | 1. Create a Virtual Environment `virtualenv projectenv`
33 | 2. Go To environment Directory `cd projectenv/apps`
34 | 3. Activate Virtual Environment `activate`
35 | 4. Install Required Package `pip install -r requirements.txt`
36 | 5. Migrate Database `python manage.py migrate`
37 | 6. Finally Run The Project `python manage.py runserver`
38 |
39 |
40 |
41 |
42 | ## Feedback
43 |
44 | Feel free to send me feedback on [file an issue](https://github.com/ZokirjonovaMuslima/drf_ecommerce/issues/new). If you wish to contribute, please feel free to do so!
45 |
46 |
47 |
48 |
49 | 
50 |
--------------------------------------------------------------------------------
/root/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import path, include, re_path
3 | from drf_yasg import openapi
4 | from drf_yasg.views import get_schema_view
5 | from rest_framework import permissions
6 | from rest_framework_simplejwt.views import TokenRefreshView, TokenObtainPairView
7 |
8 | from accounts.views import ResetPasswordAPIView, PasswordResetConfirmAPIView
9 |
10 | schema_view = get_schema_view(
11 | openapi.Info(
12 | title="Snippets API",
13 | default_version='v1',
14 | description="Test description",
15 | terms_of_service="https://www.google.com/policies/terms/",
16 | contact=openapi.Contact(email="contact@snippets.local"),
17 | license=openapi.License(name="BSD License"),
18 | ),
19 | public=True,
20 | permission_classes=[permissions.AllowAny],
21 | )
22 | urlpatterns = [
23 | path('', include('apps.urls')),
24 | path('admin/', admin.site.urls),
25 | path('accounts/', include('accounts.urls')),
26 | path('api-auth/', include('rest_framework.urls')),
27 | re_path(r'^swagger(?P\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
28 | re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
29 | re_path(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
30 |
31 | # JWT
32 | path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
33 | path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
34 | path('api/password/reset/', ResetPasswordAPIView.as_view(), name='password-reset'),
35 | path('api/password/reset///', PasswordResetConfirmAPIView.as_view(),
36 | name='password-reset-confirm'),
37 | ]
38 |
--------------------------------------------------------------------------------
/apps/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from .views import ProductListCreateAPIView, \
4 | SearchAPIView, NewCollectionAPIView, BlogListAPIView, AddCardAPIView, UserCardAPIView, DeleteCardAPIView, \
5 | BlogDetailUpdateAPIView, WishListCreateAPIView, SubscriptionCreateView, ProductPopularListAPIView, \
6 | ProductDetailRetrieveAPIView, ProductRetrieveUpdateDestroyAPIView, CommentCreateAPIView, CategoryCreateAPIView, \
7 | NewsListCreateAPIView, WishlistRetrieveDestroyAPIView, SendMailAPIView, SendNewAPIView
8 |
9 | urlpatterns = [
10 | path('news/', NewsListCreateAPIView.as_view(), name='news'),
11 | path('search/', SearchAPIView.as_view(), name='search'),
12 | path('product/', ProductListCreateAPIView.as_view(), name='product'),
13 | path('product_detail//', ProductDetailRetrieveAPIView.as_view(), name='product_detail'),
14 | path('popular_product/', ProductPopularListAPIView.as_view(), name='popular_product'),
15 | path('product-update-delete/', ProductRetrieveUpdateDestroyAPIView.as_view(),
16 | name='products_update_delete'),
17 | path('comment/', CommentCreateAPIView.as_view(), name='comment'),
18 | path('category/', CategoryCreateAPIView.as_view(), name='category'),
19 | path('collection/', NewCollectionAPIView.as_view(), name='collection'),
20 | path('blog_list//', BlogListAPIView.as_view(), name='blog'),
21 | path('blog_detail/', BlogDetailUpdateAPIView.as_view(), name='blog_detail'),
22 | path('add_card/', AddCardAPIView.as_view(), name='card'),
23 | path('user_card/', UserCardAPIView.as_view(), name='user_card'),
24 | path('delete_card/', DeleteCardAPIView.as_view(), name='delete_card'),
25 | path('wishlist/', WishListCreateAPIView.as_view(), name='wishlist_create'),
26 | path('wishlist//', WishlistRetrieveDestroyAPIView.as_view(), name='wishlist_delete'),
27 | path('send_mail', SendMailAPIView.as_view(), name='send_mail'),
28 | path('send_new', SendNewAPIView.as_view(), name='send_new'),
29 | path('subscriptions/', SubscriptionCreateView.as_view(), name='subscription_create'),
30 |
31 | ]
32 |
--------------------------------------------------------------------------------
/accounts/services.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import User
2 | from django.contrib.auth.tokens import default_token_generator
3 | from django.contrib.sites.shortcuts import get_current_site
4 | from django.core.mail import send_mail
5 | from django.utils.encoding import force_bytes, force_str
6 | from django.utils.http import urlsafe_base64_encode
7 |
8 | from accounts.serializers import UserSerializer
9 |
10 |
11 | def register_service(request_data):
12 | password1 = request_data.get('password1')
13 | password2 = request_data.get('password2')
14 | email = request_data.get('email')
15 | username = request_data.get('username')
16 | if password1 == password2:
17 | if User.objects.filter(username=username).exists():
18 | return {'success': False, 'error': 'This username already exists!'}
19 | if User.objects.filter(email=email).exists():
20 | return {'success': False, 'error': 'This email already exists!'}
21 | serializer = UserSerializer(data=request_data)
22 | serializer.is_valid(raise_exception=True)
23 | serializer.save()
24 |
25 | user = User.objects.get(username=username)
26 | user.set_password(password1)
27 | user.save()
28 | else:
29 | return {'success': False, 'error': 'Passwords are not same!'}
30 | return {'success': True, 'error': ''}
31 |
32 |
33 | def reset_password_service(request):
34 | try:
35 | email = request.data.get('email')
36 | user = User.objects.get(email=email)
37 |
38 | token = default_token_generator.make_token(user)
39 | uid = urlsafe_base64_encode(force_bytes(user.pk))
40 |
41 | current_site = get_current_site(request)
42 | subject = 'Rest Password'
43 | msg = f'''
44 | User: {email}
45 | url: {current_site.domain}/{token}/{uid}
46 | '''
47 | send_mail(subject, msg, 'no-reply@gmail.com', [email])
48 | except Exception as e:
49 | return {'success': False, 'error': f'{e}'}
50 | return {'success': True, 'error': ''}
51 |
52 |
53 | def reset_password_confirm_service(request, token, uid):
54 | id_ = force_str(urlsafe_base64_encode(uid))
55 | user = User.objects.get(pk=id_)
56 |
57 | if not default_token_generator.check_token(user, token):
58 | return {'success': False, 'error': 'Invalid token!'}
59 | user.set_password(request.data.get('password1'))
60 | user.save()
61 | return {'success': True, 'error': ''}
62 |
--------------------------------------------------------------------------------
/apps/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework.fields import EmailField, CharField, IntegerField
2 | from rest_framework.serializers import ModelSerializer, Serializer
3 |
4 | from .models import ProductImage, Product, Comment, Category, New, Card, Wishlist, Subscribe
5 |
6 |
7 | class ProductImageModelSerializer(ModelSerializer):
8 | class Meta:
9 | model = ProductImage
10 | fields = '__all__'
11 |
12 |
13 | class ProductModelSerializer(ModelSerializer):
14 | images = ProductImageModelSerializer(many=True, read_only=True)
15 |
16 | class Meta:
17 | model = Product
18 | fields = '__all__'
19 |
20 |
21 | class CommentModelSerializer(ModelSerializer):
22 | class Meta:
23 | model = Comment
24 | fields = '__all__'
25 |
26 |
27 | class CategoryModelSerializer(ModelSerializer):
28 | class Meta:
29 | model = Category
30 | fields = '__all__'
31 |
32 |
33 | class NewModelSerializer(ModelSerializer):
34 | class Meta:
35 | model = New
36 | exclude = ()
37 |
38 |
39 | class NewCollectionSerializer(ModelSerializer):
40 | class Meta:
41 | model = Product
42 | exclude = ()
43 |
44 |
45 | class BlogDetailSerializer(ModelSerializer):
46 | class Meta:
47 | model = New
48 | fields = '__all__'
49 |
50 |
51 | class BlogListSerializer(ModelSerializer):
52 | class Meta:
53 | model = New
54 | fields = ('title', 'description')
55 |
56 |
57 | class WishListSerializer(ModelSerializer):
58 | class Meta:
59 | model = Wishlist
60 | fields = '__all__'
61 |
62 |
63 | class SubscriptionSerializer(ModelSerializer):
64 | class Meta:
65 | model = Subscribe
66 | fields = '__all__'
67 |
68 |
69 | class SearchSerializer(ModelSerializer):
70 | class Meta:
71 | model = Product
72 | fields = ('title',)
73 |
74 |
75 | class ProductSerializerForCard(ModelSerializer):
76 | class Meta:
77 | model = Product
78 | fields = ('title', 'price', 'category')
79 |
80 |
81 | class CardSerializer(ModelSerializer):
82 | class Meta:
83 | model = Card
84 | fields = ('product', 'quantity', 'user', 'date')
85 |
86 |
87 | class CardDetailSerializer(ModelSerializer):
88 | product = ProductSerializerForCard()
89 |
90 | class Meta:
91 | model = Card
92 | fields = ('product', 'quantity')
93 |
94 |
95 | class SendNewSerializer(Serializer):
96 | pk = IntegerField()
97 |
98 |
99 | class SendEmailSerializer(Serializer):
100 | message = CharField(max_length=500)
101 | name = CharField(max_length=100)
102 | phone = CharField(max_length=55)
103 | email = EmailField()
104 |
--------------------------------------------------------------------------------
/apps/models.py:
--------------------------------------------------------------------------------
1 | from django.db.models import Model, TextField, CharField, IntegerField, ForeignKey, ImageField, CASCADE, TextChoices, \
2 | DateTimeField, EmailField
3 |
4 | from django.utils.translation import gettext_lazy as _
5 | from mptt.fields import TreeForeignKey
6 | from mptt.models import MPTTModel
7 |
8 |
9 | class Product(Model):
10 | title = CharField(max_length=255)
11 | price = IntegerField()
12 | description = TextField(blank=True, null=True)
13 | discount = IntegerField(blank=True, null=True)
14 |
15 | class Availability(TextChoices):
16 | IN_STOCK = 'in stock', 'In stock'
17 | OUT_OF_STOCK = 'out of stock', 'Out of stock'
18 |
19 | availability = CharField(max_length=15, choices=Availability.choices)
20 | quantity = IntegerField()
21 | category = ForeignKey('apps.Category', on_delete=CASCADE)
22 | created_at = DateTimeField(auto_now_add=True)
23 | views = IntegerField(default=0)
24 | option = ForeignKey('apps.Option', CASCADE, null=True)
25 |
26 | def __str__(self):
27 | return self.title
28 |
29 |
30 | class ProductImage(Model):
31 | product = ForeignKey('apps.Product', CASCADE)
32 | image = ImageField(upload_to='products/images/')
33 |
34 | def __str__(self):
35 | return self.product.title
36 |
37 | class Meta:
38 | verbose_name = _('product image')
39 | verbose_name_plural = _('product images')
40 |
41 |
42 | class Comment(Model):
43 | author = ForeignKey('auth.User', CASCADE)
44 | product = ForeignKey('apps.Product', CASCADE)
45 | job = CharField(max_length=255)
46 | text = TextField()
47 | image = ImageField(upload_to='comment/images/')
48 |
49 |
50 | class Category(MPTTModel, Model):
51 | name = CharField(max_length=255)
52 | parent = TreeForeignKey('self', CASCADE, 'children', null=True, blank=True)
53 |
54 | def __str__(self):
55 | return self.name
56 |
57 |
58 | class New(Model):
59 | title = CharField(max_length=255)
60 | description = TextField()
61 | comment = ForeignKey('apps.Comment', on_delete=CASCADE)
62 | image = ImageField(upload_to='news/')
63 | created_at = DateTimeField(auto_now_add=True)
64 | user = ForeignKey('auth.User', default='auth.User', on_delete=CASCADE)
65 |
66 |
67 | class Card(Model):
68 | product = ForeignKey('apps.Product', CASCADE)
69 | quantity = IntegerField(default=1)
70 | user = ForeignKey('auth.User', CASCADE)
71 | date = DateTimeField(auto_now_add=True)
72 |
73 |
74 | class Wishlist(Model):
75 | product = ForeignKey('apps.Product', CASCADE)
76 | user = ForeignKey('auth.User', CASCADE)
77 | created_at = DateTimeField(auto_now=True)
78 |
79 |
80 | class Subscribe(Model):
81 | email = EmailField()
82 | subscribe_at = DateTimeField(auto_now_add=True)
83 |
84 |
85 | class Option(Model):
86 | brand = CharField(max_length=50),
87 | season = CharField(max_length=50),
88 | color = CharField(max_length=50),
89 | fit = CharField(max_length=50),
90 | size = CharField(5)
91 |
--------------------------------------------------------------------------------
/root/settings.py:
--------------------------------------------------------------------------------
1 | import os
2 | from pathlib import Path
3 |
4 | from dotenv import load_dotenv
5 |
6 | BASE_DIR = Path(__file__).resolve().parent.parent
7 |
8 | SECRET_KEY = 'django-insecure-l*qm976&$dry)d2-h(^0zl$zu$wyl%t=xda6!8vyl^zjdrj$p*'
9 |
10 | DEBUG = True
11 |
12 | load_dotenv()
13 |
14 | ALLOWED_HOSTS = ['*']
15 |
16 | INSTALLED_APPS = [
17 | 'django.contrib.admin',
18 | 'django.contrib.auth',
19 | 'django.contrib.contenttypes',
20 | 'django.contrib.sessions',
21 | 'django.contrib.messages',
22 | 'django.contrib.staticfiles',
23 | 'apps.apps.AppsConfig',
24 | 'django_celery_results',
25 | 'accounts',
26 | 'rest_framework',
27 | 'rest_framework_simplejwt',
28 | 'rest_framework_simplejwt.token_blacklist',
29 | 'drf_yasg',
30 | 'mptt',
31 | 'django_filters',
32 |
33 | ]
34 |
35 | MIDDLEWARE = [
36 | 'django.middleware.security.SecurityMiddleware',
37 | 'django.contrib.sessions.middleware.SessionMiddleware',
38 | 'django.middleware.common.CommonMiddleware',
39 | 'django.middleware.csrf.CsrfViewMiddleware',
40 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
41 | 'django.contrib.messages.middleware.MessageMiddleware',
42 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
43 | ]
44 |
45 | ROOT_URLCONF = 'root.urls'
46 |
47 | TEMPLATES = [
48 | {
49 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
50 | 'DIRS': [BASE_DIR / 'templates']
51 | ,
52 | 'APP_DIRS': True,
53 | 'OPTIONS': {
54 | 'context_processors': [
55 | 'django.template.context_processors.debug',
56 | 'django.template.context_processors.request',
57 | 'django.contrib.auth.context_processors.auth',
58 | 'django.contrib.messages.context_processors.messages',
59 | ],
60 | },
61 | },
62 | ]
63 |
64 | WSGI_APPLICATION = 'root.wsgi.application'
65 |
66 | DATABASES = {
67 | 'default': {
68 | 'ENGINE': 'django.db.backends.postgresql_psycopg2',
69 | 'NAME': os.getenv('DB_NAME'),
70 | 'USER': os.getenv('DB_USER'),
71 | 'PASSWORD': os.getenv('DB_PASSWORD'),
72 | 'HOST': os.getenv('DB_HOST'),
73 | 'PORT': os.getenv('DB_PORT')
74 | }
75 | }
76 |
77 | # AUTH_PASSWORD_VALIDATORS = [
78 | # {
79 | # 'NAME': 'django.contrib.accounts.password_validation.UserAttributeSimilarityValidator',
80 | # },
81 | # {
82 | # 'NAME': 'django.contrib.accounts.password_validation.MinimumLengthValidator',
83 | # },
84 | # {
85 | # 'NAME': 'django.contrib.accounts.password_validation.CommonPasswordValidator',
86 | # },
87 | # {
88 | # 'NAME': 'django.contrib.accounts.password_validation.NumericPasswordValidator',
89 | # },
90 | # ]
91 |
92 | LANGUAGE_CODE = 'en-us'
93 |
94 | TIME_ZONE = 'UTC'
95 |
96 | USE_I18N = True
97 |
98 | USE_TZ = True
99 |
100 | STATIC_URL = 'static/'
101 | STATIC_ROOT = os.path.join(BASE_DIR / 'static')
102 |
103 | MEDIA_URL = 'media/'
104 | MEDIA_ROOT = os.path.join(BASE_DIR / 'media')
105 |
106 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
107 |
108 | REST_FRAMEWORK = {
109 | 'DEFAULT_FILTER_BACKENDS': [
110 | 'django_filters.rest_framework.DjangoFilterBackend'
111 | ],
112 | 'DEFAULT_AUTHENTICATION_CLASSES': (
113 | 'rest_framework_simplejwt.authentication.JWTAuthentication',
114 | ),
115 | 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
116 | 'PAGE_SIZE': 10
117 | }
118 |
119 | # Email Config
120 | EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
121 | EMAIL_HOST = os.getenv('EMAIL_HOST')
122 | EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER')
123 | EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD')
124 | EMAIL_PORT = os.getenv('EMAIL_PORT')
125 | EMAIL_USE_TLS = True
126 |
127 | # Celery Configuration Options
128 | CELERY_TIMEZONE = "Asia/Tashkent"
129 | CELERY_BROKER_URL = 'redis://localhost:6379/0'
130 |
131 | CELERY_RESULT_BACKEND = 'django-db'
132 | CELERY_CACHE_BACKEND = 'django-cache'
133 |
134 | CELERY_BROKER_TRANSPORT_OPTIONS = {'visibility_timeout': 3600}
135 |
136 | CELERY_ACCEPT_CONTENT = ['json']
137 | CELERY_RESULT_SERIALIZER = 'json'
138 | CELERY_TASK_SERIALIZER = 'json'
139 | CELERY_TASK_TRACK_STARTED = True
140 | CELERY_TASK_TIME_LIMIT = 30 * 60
141 |
--------------------------------------------------------------------------------
/apps/views.py:
--------------------------------------------------------------------------------
1 | from datetime import timedelta
2 |
3 | from django.db.models import Q
4 | from django.utils import timezone
5 | from django_filters.rest_framework import DjangoFilterBackend
6 | from drf_yasg.utils import swagger_auto_schema
7 | from rest_framework.filters import OrderingFilter
8 | from rest_framework.generics import ListCreateAPIView, UpdateAPIView, CreateAPIView, ListAPIView, \
9 | RetrieveUpdateDestroyAPIView, RetrieveDestroyAPIView, RetrieveAPIView
10 | from rest_framework.permissions import IsAuthenticated
11 | from rest_framework.response import Response
12 | from rest_framework.views import APIView
13 |
14 | from apps.models import Product, Comment, Category, New, Card, Wishlist, Subscribe
15 | from apps.serializers import ProductModelSerializer, CommentModelSerializer, CategoryModelSerializer, \
16 | NewModelSerializer, NewCollectionSerializer, BlogListSerializer, SearchSerializer, CardSerializer, \
17 | CardDetailSerializer, WishListSerializer, SendEmailSerializer, SubscriptionSerializer, SendNewSerializer
18 | from apps.tasks import send_email_customer
19 |
20 |
21 | # ProductDetail
22 | class ProductDetailRetrieveAPIView(RetrieveAPIView):
23 | queryset = Product.objects.all()
24 | serializer_class = ProductModelSerializer
25 |
26 |
27 | # Create
28 | class ProductListCreateAPIView(ListCreateAPIView):
29 | queryset = Product.objects.all()
30 | serializer_class = ProductModelSerializer
31 | filter_backends = (DjangoFilterBackend, OrderingFilter)
32 | filterset_fields = ('category', 'price', 'option__color')
33 |
34 | # filterset_class = ProductFilter
35 |
36 |
37 | # Update and Delete
38 | class ProductRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView):
39 | queryset = Product.objects.all()[:9]
40 | serializer_class = ProductModelSerializer
41 |
42 |
43 | # Comment
44 | class CommentCreateAPIView(CreateAPIView):
45 | queryset = Comment.objects.all()
46 | serializer_class = CommentModelSerializer
47 |
48 |
49 | # Subscription
50 | class SubscriptionCreateView(CreateAPIView):
51 | queryset = Subscribe.objects.all()
52 | serializer_class = SubscriptionSerializer
53 |
54 |
55 | # Category
56 | class CategoryCreateAPIView(CreateAPIView):
57 | queryset = Category.objects.all()
58 | serializer_class = CategoryModelSerializer
59 |
60 |
61 | # Last news
62 | class NewsListCreateAPIView(ListCreateAPIView):
63 | queryset = New.objects.all()
64 | serializer_class = NewModelSerializer
65 | filter_backends = (DjangoFilterBackend, OrderingFilter)
66 |
67 |
68 | # Blog List
69 | class BlogListAPIView(ListAPIView):
70 | queryset = New.objects.all()
71 | serializer_class = BlogListSerializer
72 |
73 |
74 | # Blog Detail
75 | class BlogDetailUpdateAPIView(UpdateAPIView):
76 | queryset = New.objects.all()
77 | serializer_class = ProductModelSerializer
78 |
79 |
80 | # WishList create
81 |
82 | class WishListCreateAPIView(CreateAPIView):
83 | queryset = Wishlist.objects.all()
84 | serializer_class = WishListSerializer
85 |
86 |
87 | # WishList Delete
88 | class WishlistRetrieveDestroyAPIView(RetrieveDestroyAPIView):
89 | queryset = Wishlist.objects.all()
90 | serializer_class = WishListSerializer
91 |
92 |
93 | # Search
94 | class SearchAPIView(APIView):
95 |
96 | @swagger_auto_schema(query_serializer=SearchSerializer)
97 | def get(self, request):
98 | q = request.GET.get('title', None)
99 | print(q)
100 | posts = Product.objects.filter(title__iexact=q)
101 | serializer = ProductModelSerializer(posts, many=True)
102 | return Response(serializer.data)
103 |
104 |
105 | # Popular product
106 | class ProductPopularListAPIView(ListAPIView):
107 | queryset = Product.objects.all()
108 | serializer_class = ProductModelSerializer
109 |
110 | def get_queryset(self):
111 | query = super().get_queryset().order_by('-views')
112 | return query
113 |
114 |
115 | # New Collection
116 | class NewCollectionAPIView(APIView):
117 | def get(self, request):
118 | expiry_date = timezone.now() - timedelta(days=5)
119 | products = Product.objects.filter(Q(created_at__gte=expiry_date) & Q(created_at__lte=timezone.now()))
120 | serializer = NewCollectionSerializer(products, many=True)
121 |
122 | return Response(serializer.data, status=200)
123 |
124 |
125 | # Shopping Card
126 | class AddCardAPIView(APIView):
127 | permission_classes = (IsAuthenticated,)
128 |
129 | def post(self, request):
130 | request.data._mutable = True
131 | request.data['user'] = request.user.id
132 | serializer = CardSerializer(data=request.data)
133 | serializer.is_valid(raise_exception=True)
134 | serializer.save()
135 |
136 | return Response(status=201)
137 |
138 |
139 | class UserCardAPIView(APIView):
140 | permission_classes = (IsAuthenticated,)
141 |
142 | def get(self, request):
143 | user_product = Card.objects.filter(user=request.user)
144 | serializer = CardDetailSerializer(user_product, many=True)
145 | summ = 0
146 | for element in serializer.data:
147 | summ += element['product']['price'] * element['quantity']
148 | data = {
149 | 'data': serializer.data,
150 | 'summ': summ
151 | }
152 | return Response(data)
153 |
154 |
155 | class DeleteCardAPIView(APIView):
156 | permission_classes = (IsAuthenticated,)
157 |
158 | def post(self, request, pk):
159 | try:
160 | Card.objects.get(Q(pk=pk), Q(user=request.user)).delete()
161 | except Card.DoesNotExist:
162 | return Response({'message': 'This product does not exist'})
163 | return Response(status=204)
164 |
165 |
166 | # Celery SendMail
167 |
168 | class SendMailAPIView(APIView):
169 | def post(self, request):
170 | try:
171 | serializer = SendEmailSerializer(data=request.data)
172 | serializer.is_valid(raise_exception=True)
173 | email = serializer.validated_data.get('email')
174 | message = serializer.validated_data.get('message')
175 | name = serializer.validated_data.get('name')
176 | phone = serializer.validated_data.get('phone')
177 |
178 | send_email_customer.delay(email, message, name, phone)
179 | except Exception as e:
180 | return Response({'success': False, 'message': str(e)})
181 |
182 | return Response({'success': True, 'message': 'Email sent!'})
183 |
184 |
185 | # Send news to user_email
186 | class SendNewAPIView(APIView):
187 | def post(self, request):
188 | try:
189 | serializer = SendNewSerializer(data=request.data)
190 | serializer.is_valid(raise_exception=True)
191 | pk = serializer.validated_data.get('pk')
192 |
193 | send_email_customer.delay(pk)
194 | except Exception as e:
195 | return Response({'success': False, 'message': str(e)})
196 |
197 | return Response({'success': True, 'message': 'Email sent!'})
198 |
--------------------------------------------------------------------------------