├── 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 | ![Screenshot from 2023-05-28 23-12-33](https://github.com/ZokirjonovaMuslima/drf_ecommerce/assets/122157752/9ea0339e-430e-4d10-9142-85495ad91ef4) 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 | --------------------------------------------------------------------------------