├── order ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0002_initial.py │ └── 0001_initial.py ├── views.py ├── urls.py ├── tests.py ├── admin.py ├── apps.py └── models.py ├── product ├── urls.py ├── views.py ├── __init__.py ├── migrations │ ├── __init__.py │ └── 0001_initial.py ├── tests.py ├── apps.py ├── admin.py └── models.py ├── user ├── __init__.py ├── migrations │ ├── __init__.py │ └── 0001_initial.py ├── urls.py ├── tests.py ├── views.py ├── apps.py ├── models.py ├── managers.py ├── forms.py └── admin.py ├── utils ├── __init__.py ├── validators.py └── pagination.py ├── client_side ├── __init__.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py ├── admin.py ├── apps.py ├── urls.py ├── serializers.py └── views.py ├── onlineshop ├── __init__.py ├── asgi.py ├── wsgi.py ├── urls.py └── settings.py ├── db.sqlite3 ├── requirements.txt ├── manage.py ├── README.md └── .gitignore /order/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /product/urls.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /product/views.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /user/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client_side/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /onlineshop/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /product/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /order/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /user/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /product/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client_side/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /order/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | -------------------------------------------------------------------------------- /user/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | 4 | urlpatterns = [] -------------------------------------------------------------------------------- /order/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | 4 | urlpatterns = [] 5 | -------------------------------------------------------------------------------- /order/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /product/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /user/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /user/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /client_side/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /client_side/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fatmeakr/django_admin_customizing/HEAD/db.sqlite3 -------------------------------------------------------------------------------- /client_side/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /order/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from.models import Order 3 | 4 | admin.site.register(Order) 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.5.0 2 | backports.zoneinfo==0.2.1 3 | Django==4.0.4 4 | django-filter==21.1 5 | djangorestframework==3.13.1 6 | pytz==2022.1 7 | sqlparse==0.4.2 8 | -------------------------------------------------------------------------------- /user/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UserConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'user' 7 | -------------------------------------------------------------------------------- /order/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class OrderConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'order' 7 | -------------------------------------------------------------------------------- /product/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ProductConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'product' 7 | -------------------------------------------------------------------------------- /client_side/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ClientSideConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'client_side' 7 | -------------------------------------------------------------------------------- /utils/validators.py: -------------------------------------------------------------------------------- 1 | from django.core.validators import RegexValidator 2 | 3 | mobile_number_validator = RegexValidator( 4 | regex=r'^09\d{9}$', 5 | message="Mobile number format is incorrect", 6 | ) -------------------------------------------------------------------------------- /utils/pagination.py: -------------------------------------------------------------------------------- 1 | from rest_framework.pagination import PageNumberPagination 2 | 3 | 4 | class StandardResultsSetPagination(PageNumberPagination): 5 | page_size = 10 6 | page_size_query_param = "page_size" 7 | max_page_size = 1000 8 | -------------------------------------------------------------------------------- /product/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Product, ProductBrnd, ProductCategory, ProductPrice 3 | 4 | admin.site.register(Product) 5 | admin.site.register(ProductBrnd) 6 | admin.site.register(ProductCategory) 7 | admin.site.register(ProductPrice) -------------------------------------------------------------------------------- /client_side/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from rest_framework.routers import DefaultRouter 3 | from .views import ProductViewSet, OrderViewSet 4 | 5 | router = DefaultRouter() 6 | 7 | router.register("order", OrderViewSet, basename="order") 8 | router.register("product", ProductViewSet, basename="product") 9 | 10 | urlpatterns = [ 11 | path("", include(router.urls)), 12 | ] 13 | -------------------------------------------------------------------------------- /onlineshop/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for onlineshop project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.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', 'onlineshop.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /onlineshop/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for onlineshop project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.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', 'onlineshop.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /user/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import AbstractUser 3 | from utils.validators import mobile_number_validator 4 | from .managers import CustomUserManager 5 | 6 | 7 | class User(AbstractUser): 8 | username = None 9 | password = models.CharField(max_length=128, null=True, blank=True) 10 | mobile_number = models.CharField(max_length=13, unique=True, validators=[mobile_number_validator]) 11 | 12 | objects = CustomUserManager() 13 | USERNAME_FIELD = "mobile_number" 14 | 15 | def __str__(self): 16 | return f"{self.mobile_number}" 17 | 18 | def __repr__(self): 19 | return self.__str__() 20 | -------------------------------------------------------------------------------- /client_side/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework.serializers import ModelSerializer 2 | from product.models import Product 3 | from order.models import Order 4 | 5 | 6 | class ProductSerializer(ModelSerializer): 7 | class Meta: 8 | model = Product 9 | fields = "__all__" 10 | 11 | 12 | class OrderSerializer(ModelSerializer): 13 | class Meta: 14 | model = Order 15 | fields = "__all__" 16 | read_only_fields = ["id", "user", "status"] 17 | 18 | def create(self, validated_data): 19 | user = self.context["request"].user 20 | instance = Order.objects.create(user=user, **validated_data) 21 | return instance 22 | -------------------------------------------------------------------------------- /order/migrations/0002_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-24 12:53 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('order', '0001_initial'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name='order', 20 | name='user', 21 | field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='user_orders', to=settings.AUTH_USER_MODEL), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /order/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Order(models.Model): 5 | class OrderStatusType(models.IntegerChoices): 6 | Pending = 0, "Pending" 7 | Paied = 1, "Paied" 8 | Failed = 2, "Failed" 9 | 10 | status = models.PositiveSmallIntegerField(choices=OrderStatusType.choices, default=0, null=True) 11 | user = models.ForeignKey( 12 | to="user.User", on_delete=models.DO_NOTHING, related_name="user_orders" 13 | ) 14 | product = models.OneToOneField( 15 | to="product.Product", on_delete=models.DO_NOTHING, related_name="order" 16 | ) 17 | 18 | def __str__(self): 19 | return f"{self.user}" 20 | 21 | def __repr__(self): 22 | return self.__str__() 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', 'onlineshop.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 | -------------------------------------------------------------------------------- /order/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-24 12:53 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 | ('product', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Order', 18 | fields=[ 19 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('status', models.PositiveSmallIntegerField(choices=[(0, 'Pending'), (1, 'Paied'), (2, 'Failed')], default=0, null=True)), 21 | ('product', models.OneToOneField(on_delete=django.db.models.deletion.DO_NOTHING, related_name='order', to='product.product')), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django_admin_customizing 2 | This is a project by django and django rest framework with some small models for a tiny online shop and I concentrated on django admin customizing. 3 | 4 | Basically database should not be with repositories and it should be in .gitignore file, but as a small project , to be able to test the staff users 5 | Permission and superuser's , database is attached to the project. 6 | 7 | For testing the accessibility of super users , ‍‍‍‍create a super user via python manage.py createsuperuser. 8 | And to have staff users you can manually add an staff user , log in and test the staff's permissions. 9 | 10 | A group is created manually in database, called 'staff' that determines the accessibility of staff users. 11 | This project is a minimal one ,so it does not develope programmatically. 12 | But in future, it can. 13 | 14 | And at the end, you can test the APIs via token authentication. 15 | -------------------------------------------------------------------------------- /user/managers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.base_user import BaseUserManager 2 | 3 | 4 | class CustomUserManager(BaseUserManager): 5 | def create_user(self, mobile_number, password, **extra_fields): 6 | 7 | if not mobile_number: 8 | raise ValueError("please enter your mobile number.") 9 | user = self.model(mobile_number=mobile_number, **extra_fields) 10 | user.set_password(password) 11 | user.save() 12 | return user 13 | 14 | def create_superuser(self, mobile_number, password, **extra_fields): 15 | 16 | extra_fields.setdefault("is_staff", True) 17 | extra_fields.setdefault("is_superuser", True) 18 | extra_fields.setdefault("is_active", True) 19 | 20 | if extra_fields.get("is_staff") is not True: 21 | raise ValueError("Superuser must have is_staff=True.") 22 | if extra_fields.get("is_superuser") is not True: 23 | raise ValueError("Superuser must have is_superuser=True.") 24 | return self.create_user(mobile_number, password, **extra_fields) 25 | -------------------------------------------------------------------------------- /user/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth import get_user_model 3 | from django.contrib.auth.forms import ReadOnlyPasswordHashField, UserCreationForm 4 | from utils.validators import mobile_number_validator 5 | 6 | UserModel = get_user_model() 7 | 8 | 9 | class UpdateUserForm(forms.ModelForm): 10 | password = ReadOnlyPasswordHashField( 11 | label="Password", 12 | help_text="To change your password " 'use this form.', 13 | ) 14 | 15 | class Meta: 16 | model = UserModel 17 | fields = "__all__" 18 | 19 | 20 | class CustomUserCreationForm(UserCreationForm): 21 | mobile_number = forms.CharField( 22 | label="Mobile", help_text="Mobile number must be entered in the format: '09999999999'.", validators=[mobile_number_validator], required=True 23 | ) 24 | 25 | def __init__(self, *args, **kwargs): 26 | super(CustomUserCreationForm, self).__init__(*args, **kwargs) 27 | self.fields["password1"].help_text = None 28 | del self.fields["password2"] 29 | -------------------------------------------------------------------------------- /client_side/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import mixins 2 | from rest_framework.filters import SearchFilter 3 | from rest_framework.permissions import IsAuthenticated 4 | from rest_framework.viewsets import GenericViewSet, ModelViewSet 5 | from django_filters.rest_framework import DjangoFilterBackend 6 | from utils.pagination import StandardResultsSetPagination 7 | from order.models import Order 8 | from product.models import Product 9 | from .serializers import OrderSerializer, ProductSerializer 10 | 11 | 12 | class ProductViewSet(mixins.RetrieveModelMixin, 13 | mixins.ListModelMixin, 14 | GenericViewSet): 15 | queryset = Product.objects.all() 16 | serializer_class = ProductSerializer 17 | permission_classes = [IsAuthenticated] 18 | pagination_class = StandardResultsSetPagination 19 | filterset_fields = ["color", "category"] 20 | filter_backends = [DjangoFilterBackend, SearchFilter] 21 | search_fields = ["name", "category"] 22 | 23 | 24 | class OrderViewSet(ModelViewSet): 25 | queryset = Order.objects.all() 26 | serializer_class = OrderSerializer 27 | permission_classes = [IsAuthenticated] 28 | -------------------------------------------------------------------------------- /onlineshop/urls.py: -------------------------------------------------------------------------------- 1 | """onlineshop URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.conf import settings 17 | from django.conf.urls.static import static 18 | from django.contrib import admin 19 | from django.urls import include, path 20 | 21 | 22 | urlpatterns = [ 23 | path('admin/', admin.site.urls), 24 | path('', include('client_side.urls')), 25 | ] 26 | 27 | if settings.DEBUG: 28 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 29 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 30 | -------------------------------------------------------------------------------- /product/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class ProductCategory(models.Model): 5 | name = models.CharField(verbose_name="نام", max_length=100) 6 | 7 | def __str__(self): 8 | return f"{self.name}" 9 | 10 | def __repr__(self): 11 | return self.__str__() 12 | 13 | 14 | class ProductBrnd(models.Model): 15 | name = models.CharField(verbose_name="نام", max_length=100) 16 | 17 | def __str__(self): 18 | return f"{self.name}" 19 | 20 | def __repr__(self): 21 | return self.__str__() 22 | 23 | 24 | class ProductPrice(models.Model): 25 | class UnitType(models.IntegerChoices): 26 | Toman = 0, "Toman" 27 | Dollar = 1, "Dollar" 28 | Uro = 2, "Uro" 29 | 30 | price = models.PositiveIntegerField(verbose_name="مقدار عددی قیمت هر کالا") 31 | unit = models.IntegerField(choices=UnitType.choices) 32 | 33 | def __str__(self): 34 | return f"{self.price} - {self.unit}" 35 | 36 | def __repr__(self): 37 | return self.__str__() 38 | 39 | 40 | class Product(models.Model): 41 | class Colors(models.IntegerChoices): 42 | Blue = 0, "Blue" 43 | Green = 1, "Green" 44 | Gold = 2, "Gold" 45 | 46 | name = models.CharField(verbose_name="نام", max_length=100) 47 | color = models.IntegerField(choices=Colors.choices) 48 | prices = models.ManyToManyField(to=ProductPrice, related_name="prices_products", blank=True) 49 | brand = models.ForeignKey( 50 | to=ProductBrnd, on_delete=models.CASCADE, related_name="brnad_products" 51 | ) 52 | category = models.ForeignKey( 53 | to=ProductCategory, on_delete=models.CASCADE, related_name="category_products" 54 | ) 55 | 56 | def __str__(self): 57 | return f"{self.name}" 58 | 59 | def __repr__(self): 60 | return self.__str__() 61 | -------------------------------------------------------------------------------- /product/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-24 12:53 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='ProductBrnd', 17 | fields=[ 18 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('name', models.CharField(max_length=100, verbose_name='نام')), 20 | ], 21 | ), 22 | migrations.CreateModel( 23 | name='ProductCategory', 24 | fields=[ 25 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 26 | ('name', models.CharField(max_length=100, verbose_name='نام')), 27 | ], 28 | ), 29 | migrations.CreateModel( 30 | name='ProductPrice', 31 | fields=[ 32 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 33 | ('price', models.PositiveIntegerField(verbose_name='مقدار عددی قیمت هر کالا')), 34 | ('unit', models.IntegerField(choices=[(0, 'Toman'), (1, 'Dollar'), (2, 'Uro')])), 35 | ], 36 | ), 37 | migrations.CreateModel( 38 | name='Product', 39 | fields=[ 40 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 41 | ('name', models.CharField(max_length=100, verbose_name='نام')), 42 | ('color', models.IntegerField(choices=[(0, 'Blue'), (1, 'Green'), (2, 'Gold')])), 43 | ('brand', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='brnad_products', to='product.productbrnd')), 44 | ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='category_products', to='product.productcategory')), 45 | ('prices', models.ManyToManyField(blank=True, related_name='prices_products', to='product.productprice')), 46 | ], 47 | ), 48 | ] 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | #*.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 88 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 89 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 90 | # install all needed dependencies. 91 | #Pipfile.lock 92 | 93 | # celery beat schedule file 94 | celerybeat-schedule 95 | 96 | # SageMath parsed files 97 | *.sage.py 98 | 99 | # Environments 100 | .env 101 | .venv 102 | env/ 103 | venv/ 104 | ENV/ 105 | env.bak/ 106 | venv.bak/ 107 | 108 | # Spyder project settings 109 | .spyderproject 110 | .spyproject 111 | 112 | # Rope project settings 113 | .ropeproject 114 | 115 | # mkdocs documentation 116 | /site 117 | 118 | # mypy 119 | .mypy_cache/ 120 | .dmypy.json 121 | dmypy.json 122 | 123 | # Pyre type checker 124 | .pyre/ 125 | 126 | .idea/ 127 | 128 | static/ 129 | media/ 130 | 131 | -------------------------------------------------------------------------------- /user/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-24 12:53 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | import django.utils.timezone 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('auth', '0012_alter_user_first_name_max_length'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='User', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 22 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 23 | ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), 24 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 25 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), 26 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 27 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 28 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 29 | ('password', models.CharField(blank=True, max_length=128, null=True)), 30 | ('mobile_number', models.CharField(max_length=13, unique=True, validators=[django.core.validators.RegexValidator(message='Mobile number format is incorrect', regex='^09\\d{9}$')])), 31 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), 32 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), 33 | ], 34 | options={ 35 | 'verbose_name': 'user', 36 | 'verbose_name_plural': 'users', 37 | 'abstract': False, 38 | }, 39 | ), 40 | migrations.CreateModel( 41 | name='CustomerProxy', 42 | fields=[ 43 | ], 44 | options={ 45 | 'verbose_name': 'customer', 46 | 'verbose_name_plural': 'customers', 47 | 'proxy': True, 48 | 'indexes': [], 49 | 'constraints': [], 50 | }, 51 | bases=('user.user',), 52 | ), 53 | migrations.CreateModel( 54 | name='StaffUserProxy', 55 | fields=[ 56 | ], 57 | options={ 58 | 'verbose_name': 'staff', 59 | 'verbose_name_plural': 'staffs', 60 | 'proxy': True, 61 | 'indexes': [], 62 | 'constraints': [], 63 | }, 64 | bases=('user.user',), 65 | ), 66 | migrations.CreateModel( 67 | name='SuperUserProxy', 68 | fields=[ 69 | ], 70 | options={ 71 | 'verbose_name': 'super admin', 72 | 'verbose_name_plural': 'super admins', 73 | 'proxy': True, 74 | 'indexes': [], 75 | 'constraints': [], 76 | }, 77 | bases=('user.user',), 78 | ), 79 | ] 80 | -------------------------------------------------------------------------------- /onlineshop/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for onlineshop project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.8. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | from pathlib import Path 15 | 16 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 17 | BASE_DIR = Path(__file__).resolve().parent.parent 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = 'django-insecure-b6((6%&=8v=6h8ihr8kv7tgi738*#^^p_=r2_4%i7f*vf@jvet' 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = [] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = [ 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | 'rest_framework', 42 | 'rest_framework.authtoken', 43 | 'django_filters', 44 | 'user.apps.UserConfig', 45 | 'product.apps.ProductConfig', 46 | 'order.apps.OrderConfig', 47 | ] 48 | 49 | MIDDLEWARE = [ 50 | 'django.middleware.security.SecurityMiddleware', 51 | 'django.contrib.sessions.middleware.SessionMiddleware', 52 | 'django.middleware.common.CommonMiddleware', 53 | 'django.middleware.csrf.CsrfViewMiddleware', 54 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 55 | 'django.contrib.messages.middleware.MessageMiddleware', 56 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 57 | ] 58 | 59 | ROOT_URLCONF = 'onlineshop.urls' 60 | 61 | TEMPLATES = [ 62 | { 63 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 64 | 'DIRS': [], 65 | 'APP_DIRS': True, 66 | 'OPTIONS': { 67 | 'context_processors': [ 68 | 'django.template.context_processors.debug', 69 | 'django.template.context_processors.request', 70 | 'django.contrib.auth.context_processors.auth', 71 | 'django.contrib.messages.context_processors.messages', 72 | ], 73 | }, 74 | }, 75 | ] 76 | 77 | WSGI_APPLICATION = 'onlineshop.wsgi.application' 78 | 79 | 80 | REST_FRAMEWORK = { 81 | 'DEFAULT_AUTHENTICATION_CLASSES': [ 82 | 'rest_framework.authentication.SessionAuthentication', 83 | 'rest_framework.authentication.TokenAuthentication', 84 | ], 85 | 'DEFAULT_FILTER_BACKENDS': [ 86 | 'django_filters.rest_framework.DjangoFilterBackend', 87 | 'rest_framework.filters.SearchFilter', 88 | ], 89 | } 90 | 91 | # Database 92 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 93 | 94 | DATABASES = { 95 | 'default': { 96 | 'ENGINE': 'django.db.backends.sqlite3', 97 | 'NAME': BASE_DIR / 'db.sqlite3', 98 | } 99 | } 100 | 101 | 102 | # Password validation 103 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 104 | 105 | AUTH_PASSWORD_VALIDATORS = [ 106 | { 107 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 108 | }, 109 | { 110 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 111 | }, 112 | { 113 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 114 | }, 115 | { 116 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 117 | }, 118 | ] 119 | 120 | # Internationalization 121 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 122 | 123 | LANGUAGE_CODE = 'en-us' 124 | 125 | TIME_ZONE = 'Asia/Tehran' 126 | 127 | USE_I18N = True 128 | 129 | USE_L10N = True 130 | 131 | USE_TZ = True 132 | 133 | AUTH_USER_MODEL = 'user.User' 134 | 135 | # Static files (CSS, JavaScript, Images) 136 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 137 | 138 | STATIC_URL = '/static/' 139 | STATIC_ROOT = 'static/' 140 | 141 | MEDIA_URL = '/media/' 142 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 143 | 144 | # Default primary key field type 145 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 146 | 147 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 148 | -------------------------------------------------------------------------------- /user/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth import get_user_model 3 | from django.contrib.auth.admin import UserAdmin as BaseUserAdmin 4 | from django.core.exceptions import ObjectDoesNotExist 5 | from django.contrib.auth.models import Group 6 | from .forms import UpdateUserForm, CustomUserCreationForm 7 | 8 | UserModel = get_user_model() 9 | 10 | 11 | class CustomerProxy(UserModel): 12 | class Meta: 13 | proxy = True 14 | verbose_name = "customer" 15 | verbose_name_plural = "customers" 16 | 17 | 18 | @admin.register(CustomerProxy) 19 | class CustomerAdmin(BaseUserAdmin): 20 | ordering = ("-id",) 21 | list_display = ("mobile_number", "is_active", "is_staff", "is_superuser") 22 | form = UpdateUserForm 23 | add_form = CustomUserCreationForm 24 | fieldsets = ( 25 | ( 26 | "General Info", 27 | { 28 | "fields": ( 29 | "first_name", 30 | "last_name", 31 | "mobile_number", 32 | "password", 33 | ) 34 | }, 35 | ), 36 | ) 37 | 38 | add_fieldsets = ( 39 | ( 40 | "General Info", 41 | {"fields": (("first_name",), "last_name", "mobile_number")}, 42 | ), 43 | ("Password Setting", {"fields": ((("password1")),)}), 44 | ) 45 | 46 | def get_queryset(self, request): 47 | queryset = super(CustomerAdmin, self).get_queryset(request) 48 | if not self.has_view_or_change_permission(request): 49 | queryset = queryset.none() 50 | return queryset.filter( 51 | is_superuser=False, 52 | is_staff=False, 53 | is_active=True, 54 | ) 55 | 56 | 57 | class SuperUserProxy(UserModel): 58 | class Meta: 59 | proxy = True 60 | verbose_name = "super admin" 61 | verbose_name_plural = "super admins" 62 | 63 | 64 | @admin.register(SuperUserProxy) 65 | class SuperUserAdmin(BaseUserAdmin): 66 | ordering = ("-id",) 67 | search_fields = ["mobile_number", "first_name", "last_name"] 68 | list_filter = [] 69 | list_display = ("mobile_number", "is_active", "is_staff", "is_superuser") 70 | list_display_links = ("mobile_number", "is_active", "is_staff", "is_superuser") 71 | form = UpdateUserForm 72 | add_form = CustomUserCreationForm 73 | fieldsets = ( 74 | ( 75 | "General Info", 76 | { 77 | "fields": ( 78 | ("first_name",), 79 | "last_name", 80 | "mobile_number", 81 | "password", 82 | ) 83 | }, 84 | ), 85 | ) 86 | add_fieldsets = ( 87 | ( 88 | "General Info", 89 | {"fields": (("first_name",), "last_name", "mobile_number")}, 90 | ), 91 | ("Password Setting", {"fields": ((("password1")),)}), 92 | ) 93 | 94 | def save_model(self, request, obj, form, change): 95 | obj.is_staff = True 96 | obj.is_superuser = True 97 | super(SuperUserAdmin, self).save_model(request, obj, form, change) 98 | 99 | def get_queryset(self, request): 100 | queryset = super(SuperUserAdmin, self).get_queryset(request) 101 | if not self.has_view_or_change_permission(request): 102 | queryset = queryset.none() 103 | return queryset.filter( 104 | is_superuser=True, 105 | is_staff=True, 106 | is_active=True, 107 | ) 108 | 109 | 110 | class StaffUserProxy(UserModel): 111 | class Meta: 112 | proxy = True 113 | verbose_name = "staff" 114 | verbose_name_plural = "staffs" 115 | 116 | 117 | @admin.register(StaffUserProxy) 118 | class StaffUserAdmin(BaseUserAdmin): 119 | ordering = ("-id",) 120 | search_fields = ["mobile_number", "first_name", "last_name"] 121 | list_filter = [] 122 | list_display = ("mobile_number", "is_active", "is_staff", "is_superuser") 123 | list_display_links = ("mobile_number", "is_active", "is_staff", "is_superuser") 124 | form = UpdateUserForm 125 | add_form = CustomUserCreationForm 126 | fieldsets = ( 127 | ( 128 | "General Info", 129 | { 130 | "fields": ( 131 | ("first_name",), 132 | "last_name", 133 | "mobile_number", 134 | "password", 135 | ) 136 | }, 137 | ), 138 | ) 139 | add_fieldsets = ( 140 | ( 141 | "General Info", 142 | {"fields": (("first_name",), "last_name", "mobile_number")}, 143 | ), 144 | ("Password Setting", {"fields": ((("password1")),)}), 145 | ) 146 | 147 | def save_model(self, request, obj, form, change): 148 | obj.is_staff = True 149 | super(StaffUserAdmin, self).save_model(request, obj, form, change) 150 | try: 151 | staff_group = Group.objects.get(name="staff") 152 | obj.groups.add(staff_group) 153 | except ObjectDoesNotExist: 154 | pass 155 | 156 | def get_queryset(self, request): 157 | queryset = super(StaffUserAdmin, self).get_queryset(request) 158 | if not self.has_view_or_change_permission(request): 159 | queryset = queryset.none() 160 | return queryset.filter( 161 | is_superuser=False, 162 | is_staff=True, 163 | is_active=True, 164 | ) 165 | 166 | def has_change_permission(self, request, obj=None): 167 | base_change_permission = super(StaffUserAdmin, self).has_change_permission(request, obj=None) 168 | if base_change_permission: 169 | if bool(request.user == obj or request.user.is_superuser): 170 | return True 171 | return False 172 | --------------------------------------------------------------------------------