add pos device - fix closed quotas pagination

This commit is contained in:
2025-07-24 16:02:08 +03:30
parent f8dbe0ab29
commit 18fd27a9c4
9 changed files with 246 additions and 31 deletions

View File

@@ -62,6 +62,7 @@ ALLOWED_HOSTS = [
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'jazzmin', # noqa
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
@@ -91,6 +92,7 @@ INSTALLED_APPS = [
'drf_yasg', 'drf_yasg',
"django_celery_results", "django_celery_results",
"django_celery_beat", "django_celery_beat",
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@@ -349,3 +351,144 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = False SECURE_SSL_REDIRECT = False
SESSION_COOKIE_SECURE = False SESSION_COOKIE_SECURE = False
CSRF_COOKIE_SECURE = False CSRF_COOKIE_SECURE = False
JAZZMIN_SETTINGS = {
# title of the window (Will default to current_admin_site.site_title if absent or None)
"site_title": "Library Admin",
# Title on the login screen (19 chars max) (defaults to current_admin_site.site_header if absent or None)
"site_header": "Library",
# Title on the brand (19 chars max) (defaults to current_admin_site.site_header if absent or None)
"site_brand": "Library",
# Logo to use for your site, must be present in static files, used for brand on top left
"site_logo": "books/img/logo.png",
# Logo to use for your site, must be present in static files, used for login form logo (defaults to site_logo)
"login_logo": None,
# Logo to use for login form in dark themes (defaults to login_logo)
"login_logo_dark": None,
# CSS classes that are applied to the logo above
"site_logo_classes": "img-circle",
# Relative path to a favicon for your site, will default to site_logo if absent (ideally 32x32 px)
"site_icon": None,
# Welcome text on the login screen
"welcome_sign": "Welcome to the library",
# Copyright on the footer
"copyright": "Acme Library Ltd",
# List of model admins to search from the search bar, search bar omitted if excluded
# If you want to use a single search field you dont need to use a list, you can use a simple string
"search_model": ["auth.User", "auth.Group"],
# Field name on user model that contains avatar ImageField/URLField/Charfield or a callable that receives the user
"user_avatar": None,
############
# Top Menu #
############
# Links to put along the top menu
"topmenu_links": [
# Url that gets reversed (Permissions can be added)
{"name": "Home", "url": "admin:index", "permissions": ["auth.view_user"]},
# external url that opens in a new window (Permissions can be added)
{"name": "Support", "url": "https://github.com/farridav/django-jazzmin/issues", "new_window": True},
# model admin to link to (Permissions checked against model)
{"model": "auth.User"},
# App with dropdown menu to all its models pages (Permissions checked against models)
{"app": "books"},
],
#############
# User Menu #
#############
# Additional links to include in the user menu on the top right ("app" url type is not allowed)
"usermenu_links": [
{"name": "Support", "url": "https://github.com/farridav/django-jazzmin/issues", "new_window": True},
{"model": "auth.user"}
],
#############
# Side Menu #
#############
# Whether to display the side menu
"show_sidebar": True,
# Whether to aut expand the menu
"navigation_expanded": True,
# Hide these apps when generating side menu e.g (auth)
"hide_apps": [],
# Hide these models when generating side menu (e.g auth.user)
"hide_models": [],
# List of apps (and/or models) to base side menu ordering off of (does not need to contain all apps/models)
"order_with_respect_to": ["auth", "books", "books.author", "books.book"],
# Custom links to append to app groups, keyed on app name
"custom_links": {
"books": [{
"name": "Make Messages",
"url": "make_messages",
"icon": "fas fa-comments",
"permissions": ["books.view_book"]
}]
},
# Custom icons for side menu apps/models See https://fontawesome.com/icons?d=gallery&m=free&v=5.0.0,5.0.1,5.0.10,5.0.11,5.0.12,5.0.13,5.0.2,5.0.3,5.0.4,5.0.5,5.0.6,5.0.7,5.0.8,5.0.9,5.1.0,5.1.1,5.2.0,5.3.0,5.3.1,5.4.0,5.4.1,5.4.2,5.13.0,5.12.0,5.11.2,5.11.1,5.10.0,5.9.0,5.8.2,5.8.1,5.7.2,5.7.1,5.7.0,5.6.3,5.5.0,5.4.2
# for the full list of 5.13.0 free icon classes
"icons": {
"auth": "fas fa-users-cog",
"auth.user": "fas fa-user",
"auth.Group": "fas fa-users",
},
# Icons that are used when one is not manually specified
"default_icon_parents": "fas fa-chevron-circle-right",
"default_icon_children": "fas fa-circle",
#################
# Related Modal #
#################
# Use modals instead of popups
"related_modal_active": False,
#############
# UI Tweaks #
#############
# Relative paths to custom CSS/JS scripts (must be present in static files)
"custom_css": None,
"custom_js": None,
# Whether to link font from fonts.googleapis.com (use custom_css to supply font otherwise)
"use_google_fonts_cdn": True,
# Whether to show the UI customizer on the sidebar
"show_ui_builder": False,
###############
# Change view #
###############
# Render out the change view as a single form, or in tabs, current options are
# - single
# - horizontal_tabs (default)
# - vertical_tabs
# - collapsible
# - carousel
"changeform_format": "horizontal_tabs",
# override change forms on a per modeladmin basis
"changeform_format_overrides": {"auth.user": "collapsible", "auth.group": "vertical_tabs"},
# Add a language dropdown into the admin
# "language_chooser": True,
}

View File

@@ -24,6 +24,7 @@ from django.conf.urls import (
handler404, handler404,
) )
# handler500 = 'apps.core.error_handler.handler500' # noqa # handler500 = 'apps.core.error_handler.handler500' # noqa
urlpatterns = [ urlpatterns = [

View File

@@ -0,0 +1,20 @@
# Generated by Django 5.0 on 2025-07-24 09:07
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authorization', '0019_page_is_active_permissions_is_active'),
('pos_device', '0006_alter_posclientattribute_choices'),
]
operations = [
migrations.AddField(
model_name='providercompany',
name='user_relation',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='pos_provider', to='authorization.userrelations'),
),
]

View File

@@ -1,10 +1,17 @@
from django.db import models
from apps.core.models import BaseModel
from apps.authentication.models import Organization from apps.authentication.models import Organization
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from apps.authorization.models import UserRelations
from apps.core.models import BaseModel
from django.db import models
class ProviderCompany(BaseModel): class ProviderCompany(BaseModel):
user_relation = models.ForeignKey(
UserRelations,
related_name='pos_provider',
on_delete=models.CASCADE,
null=True
)
name_fa = models.CharField(max_length=250, null=True) name_fa = models.CharField(max_length=250, null=True)
name_en = models.CharField(max_length=250, null=True) name_en = models.CharField(max_length=250, null=True)
activation = models.BooleanField(default=False) activation = models.BooleanField(default=False)

View File

@@ -7,6 +7,7 @@ router = DefaultRouter()
router.register(r'client', client_views.POSClientViewSet, basename='client') router.register(r'client', client_views.POSClientViewSet, basename='client')
router.register(r'attributes', client_views.POSClientAttributeViewSet, basename='attributes') router.register(r'attributes', client_views.POSClientAttributeViewSet, basename='attributes')
router.register(r'provider', device_views.ProviderCompanyViewSet, basename='provider') router.register(r'provider', device_views.ProviderCompanyViewSet, basename='provider')
router.register(r'device', device_views.DeviceViewSet, basename='device')
urlpatterns = [ urlpatterns = [
path('v1/pos/', include(router.urls)) path('v1/pos/', include(router.urls))

View File

@@ -1,12 +1,16 @@
from apps.pos_device.web.api.v1.serilaizers import serializers as pos_serializer from apps.pos_device.web.api.v1.serilaizers import serializers as pos_serializer
from apps.authentication.api.v1.api import UserViewSet from apps.authentication.api.v1.api import UserViewSet
from apps.authorization.models import UserRelations
from rest_framework.exceptions import APIException from rest_framework.exceptions import APIException
from apps.pos_device import models as pos_models from apps.pos_device import models as pos_models
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.decorators import action
from common.tools import CustomOperations from common.tools import CustomOperations
from rest_framework import viewsets from rest_framework import viewsets
from django.db import transaction
from rest_framework import status from rest_framework import status
class ProviderCompanyViewSet(viewsets.ModelViewSet): # noqa class ProviderCompanyViewSet(viewsets.ModelViewSet): # noqa
queryset = pos_models.ProviderCompany.objects.all() queryset = pos_models.ProviderCompany.objects.all()
serializer_class = pos_serializer.ProviderCompanySerializer serializer_class = pos_serializer.ProviderCompanySerializer
@@ -15,21 +19,69 @@ class ProviderCompanyViewSet(viewsets.ModelViewSet): # noqa
""" custom create of provider client """ """ custom create of provider client """
try: try:
client = CustomOperations().custom_create( # creating user & relations
request=request, client = UserViewSet().create(request=request)
view=UserViewSet(),
data=request.data
)
except Exception as e:
raise APIException(detail="data is invalid", code=403)
return Response(client, status=status.HTTP_201_CREATED) if client.status_code == 201:
# create provider
serializer = self.serializer_class(data=request.data['provider'])
if serializer.is_valid():
provider = serializer.save()
provider.user_relation = UserRelations.objects.get(
user_id=client.data['id']
)
provider.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_403_FORBIDDEN)
else:
return client
except Exception as e:
raise e
class DeviceViewSet(viewsets.ModelViewSet): class DeviceViewSet(viewsets.ModelViewSet):
queryset = pos_models.Device.objects.all() queryset = pos_models.Device.objects.all()
serializer_class = pos_serializer.DeviceSerializer serializer_class = pos_serializer.DeviceSerializer
def create(self, request, *args, **kwargs):
""" Custom create of pos devices """
company = pos_models.ProviderCompany.objects.get(
user_relation__user=request.user
)
# create device
request.data.update({'company': company.id})
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_403_FORBIDDEN)
@action(
methods=['get'],
detail=False,
url_name='my_devices',
url_path='my_devices',
name='my_devices'
)
@transaction.atomic
def my_devices(self, request):
""" list of company devices """
devices = self.queryset.filter(
company__user_relation__user=request.user
)
# paginate devices
page = self.paginate_queryset(devices)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
class DeviceVersionViewSet(viewsets.ModelViewSet): class DeviceVersionViewSet(viewsets.ModelViewSet):
queryset = pos_models.DeviceVersion.objects.all() queryset = pos_models.DeviceVersion.objects.all()

View File

@@ -1,3 +1,6 @@
from django.contrib import admin from django.contrib import admin
from .models import Quota
# Register your models here. # Register your models here.
admin.site.register(Quota)

View File

@@ -268,6 +268,12 @@ class QuotaViewSet(viewsets.ModelViewSet): # noqa
return Response(status.HTTP_200_OK) return Response(status.HTTP_200_OK)
@action(
methods=['patch'],
)
def activate(self, request):
pass
@action( @action(
methods=['get'], methods=['get'],
detail=False, detail=False,
@@ -305,6 +311,7 @@ class QuotaViewSet(viewsets.ModelViewSet): # noqa
""" list of organization closed quotas """ """ list of organization closed quotas """
organization = get_organization_by_user(request.user) organization = get_organization_by_user(request.user)
# paginate queryset # paginate queryset
page = self.paginate_queryset( page = self.paginate_queryset(
self.queryset.filter( self.queryset.filter(
@@ -412,26 +419,6 @@ class QuotaViewSet(viewsets.ModelViewSet): # noqa
serializer = self.serializer_class(quotas, many=True).data serializer = self.serializer_class(quotas, many=True).data
return Response(serializer, status=status.HTTP_200_OK) return Response(serializer, status=status.HTTP_200_OK)
@action(
methods=['get'],
detail=False,
url_path='closed_quotas',
url_name='closed_quotas',
name='closed_quotas'
)
def closed_quotas(self, request):
""" get list of close quotas for organization """
quotas = quotas = self.queryset.filter(
(Q(assigned_organizations=get_organization_by_user(request.user)) |
Q(registerer_organization=get_organization_by_user(request.user))) &
Q(is_closed=True)
)
serialize = self.serializer_class(quotas, many=True).data
return Response(serialize, status=status.HTTP_200_OK)
@action( @action(
methods=['put'], methods=['put'],
detail=True, detail=True,

View File

@@ -82,3 +82,4 @@ django-celery-results
channels channels
channels_redis channels_redis
daphne daphne
django-jazzmin