소스 검색

add models

Староверов Данила Андреевич 1 년 전
커밋
4d1947d892
100개의 변경된 파일2740개의 추가작업 그리고 0개의 파일을 삭제
  1. BIN
      .DS_Store
  2. 17 0
      .gitignore
  3. 108 0
      README.md
  4. BIN
      SharixAdmin/.DS_Store
  5. 0 0
      SharixAdmin/__init__.py
  6. 72 0
      SharixAdmin/admin.py
  7. 65 0
      SharixAdmin/apiviews.py
  8. 7 0
      SharixAdmin/apps.py
  9. 15 0
      SharixAdmin/forms.py
  10. 139 0
      SharixAdmin/migrations/0001_initial.py
  11. 0 0
      SharixAdmin/migrations/__init__.py
  12. 26 0
      SharixAdmin/models.py
  13. 29 0
      SharixAdmin/serializer.py
  14. 6 0
      SharixAdmin/static/SharixAdmin/img/logo.svg
  15. BIN
      SharixAdmin/static/SharixAdmin/img/mc.png
  16. 4 0
      SharixAdmin/static/SharixAdmin/img/menu/arrow-right.svg
  17. 8 0
      SharixAdmin/static/SharixAdmin/img/menu/clock-history.svg
  18. 8 0
      SharixAdmin/static/SharixAdmin/img/menu/clock-history_w.svg
  19. 6 0
      SharixAdmin/static/SharixAdmin/img/menu/credit-card.svg
  20. 6 0
      SharixAdmin/static/SharixAdmin/img/menu/credit-card_w.svg
  21. 6 0
      SharixAdmin/static/SharixAdmin/img/menu/education.svg
  22. 6 0
      SharixAdmin/static/SharixAdmin/img/menu/education_w.svg
  23. 3 0
      SharixAdmin/static/SharixAdmin/img/menu/gear.svg
  24. 3 0
      SharixAdmin/static/SharixAdmin/img/menu/gear_w.svg
  25. 8 0
      SharixAdmin/static/SharixAdmin/img/menu/hdd-network.svg
  26. 8 0
      SharixAdmin/static/SharixAdmin/img/menu/hdd-network_w.svg
  27. 4 0
      SharixAdmin/static/SharixAdmin/img/menu/house.svg
  28. 4 0
      SharixAdmin/static/SharixAdmin/img/menu/house_w.svg
  29. 10 0
      SharixAdmin/static/SharixAdmin/img/menu/people.svg
  30. 10 0
      SharixAdmin/static/SharixAdmin/img/menu/people_w.svg
  31. 6 0
      SharixAdmin/static/SharixAdmin/img/menu/person.svg
  32. 6 0
      SharixAdmin/static/SharixAdmin/img/menu/person_w.svg
  33. 4 0
      SharixAdmin/static/SharixAdmin/img/menu/sotrud.svg
  34. 4 0
      SharixAdmin/static/SharixAdmin/img/menu/sotrud_w.svg
  35. 8 0
      SharixAdmin/static/SharixAdmin/img/menu/tikets.svg
  36. 8 0
      SharixAdmin/static/SharixAdmin/img/menu/tikets_w.svg
  37. BIN
      SharixAdmin/static/SharixAdmin/img/mir.png
  38. BIN
      SharixAdmin/static/SharixAdmin/img/paymo.png
  39. BIN
      SharixAdmin/static/SharixAdmin/img/schemav3.png
  40. BIN
      SharixAdmin/static/SharixAdmin/img/visa.png
  41. 73 0
      SharixAdmin/static/drf-yasg/style.css
  42. 28 0
      SharixAdmin/tables.py
  43. 33 0
      SharixAdmin/templates/SharixAdmin/auth.html
  44. 54 0
      SharixAdmin/templates/SharixAdmin/balance.html
  45. 10 0
      SharixAdmin/templates/SharixAdmin/balance_success.html
  46. 17 0
      SharixAdmin/templates/SharixAdmin/base.html
  47. 147 0
      SharixAdmin/templates/SharixAdmin/index.html
  48. 29 0
      SharixAdmin/templates/SharixAdmin/main.html
  49. 10 0
      SharixAdmin/templates/SharixAdmin/schema.html
  50. 11 0
      SharixAdmin/templates/SharixAdmin/senderform.html
  51. 9 0
      SharixAdmin/templates/SharixAdmin/test.html
  52. 25 0
      SharixAdmin/templates/SharixAdmin/trans_carried.html
  53. 15 0
      SharixAdmin/templates/SharixAdmin/transactions.html
  54. 48 0
      SharixAdmin/templates/drf-yasg/redoc.html
  55. 3 0
      SharixAdmin/tests.py
  56. 32 0
      SharixAdmin/urls.py
  57. 133 0
      SharixAdmin/views.py
  58. 3 0
      bin/webuser.sh
  59. 0 0
      core/__init__.py
  60. 29 0
      core/api_settings.py
  61. 16 0
      core/asgi.py
  62. 9 0
      core/conf_gunicorn.py
  63. 26 0
      core/config_template.py
  64. 24 0
      core/db_settings.py
  65. 178 0
      core/jazzmin_settings.py
  66. 145 0
      core/settings.py
  67. 31 0
      core/tickets_mail_settings.py
  68. 22 0
      core/urls.py
  69. 16 0
      core/wsgi.py
  70. 21 0
      install_linux.sh
  71. 5 0
      install_win.bat
  72. 22 0
      manage.py
  73. 4 0
      metaservicesynced/.gitignore
  74. 0 0
      metaservicesynced/__init__.py
  75. 3 0
      metaservicesynced/admin.py
  76. 6 0
      metaservicesynced/apps.py
  77. 46 0
      metaservicesynced/migrations/0001_initial.py
  78. 0 0
      metaservicesynced/migrations/__init__.py
  79. 96 0
      metaservicesynced/models.py
  80. 3 0
      metaservicesynced/tests.py
  81. 3 0
      metaservicesynced/views.py
  82. 57 0
      requirements.txt
  83. 4 0
      tickets/.gitignore
  84. 77 0
      tickets/README.md
  85. 0 0
      tickets/__init__.py
  86. 48 0
      tickets/admin.py
  87. 77 0
      tickets/admin_utils.py
  88. 30 0
      tickets/apiviews.py
  89. 7 0
      tickets/apps.py
  90. 22 0
      tickets/defaults.py
  91. 64 0
      tickets/forms.py
  92. 0 0
      tickets/mail/__init__.py
  93. 9 0
      tickets/mail/consumers/__init__.py
  94. 171 0
      tickets/mail/consumers/tracker.py
  95. 27 0
      tickets/mail/delivery.py
  96. 9 0
      tickets/mail/producers/__init__.py
  97. 98 0
      tickets/mail/producers/imap.py
  98. 0 0
      tickets/management/__init__.py
  99. 0 0
      tickets/management/commands/__init__.py
  100. 41 0
      tickets/management/commands/mail_worker.py

BIN
.DS_Store


+ 17 - 0
.gitignore

@@ -0,0 +1,17 @@
+# Virtualenv
+env/
+venv/
+#module
+staticfiles/
+core/config.py
+# VSCode
+.vscode
+
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+
+# Other
+*.sqlite3
+.env

+ 108 - 0
README.md

@@ -0,0 +1,108 @@
+# ShariX Open Admin
+
+Admin system implemented as a Django application.
+
+## How install?
+
+1) Download or clone repository
+```bash
+git clone http://git.sharix-app.org/ShariX_Open/sharix-open-webapp-base.git name_project
+```
+1) Set up a configuration file
+```python
+#Create file config.py with this setting or rename this file to config.py
+
+#BASE
+DEBUG=True
+SECRET_KEY='secret-key(absolutely any character)'
+ALLOWED_HOSTS = ['127.0.0.1']
+CSRF_TRUSTED_ORIGINS = []
+
+#DATABSE
+DB_NAME=None
+DB_USER=None
+DB_PASSWORD=None
+DB_HOST=None
+
+#GUNICORN
+BIND = "127.0.0.1:8000"
+WORKERS = 2
+THREADS = 4
+
+#STATIC
+from pathlib import Path
+import os
+BASE_DIR = Path(__file__).resolve().parent.parent
+STATIC_URL = '/static/'
+STATICFILES_DIRS = [BASE_DIR / "SharixAdmin/static/", BASE_DIR / "tickets/static/"]
+STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
+```
+3) Run a **install_win.bat**
+4) The system will prompt you to create a superuser
+```
+#Example
+7987654321
+admin
+pa$$w0rd
+pa$$w0rd
+```
+### Ready!
+
+## Server instalation
+1) Download or clone repository
+```bash
+git clone http://git.sharix-app.org/ShariX_Open/sharix-open-webapp-base.git name_project
+```
+2) Set up a configuration file ```nano core/config_template.py```
+```python
+#Create file config.py with this setting or rename this file to config.py
+
+#BASE
+DEBUG=True
+SECRET_KEY='secret-key(absolutely any character)'
+ALLOWED_HOSTS = ['127.0.0.1']
+CSRF_TRUSTED_ORIGINS = []
+
+#DATABSE
+DB_NAME=None
+DB_USER=None
+DB_PASSWORD=None
+DB_HOST=None
+
+#GUNICORN
+BIND = "127.0.0.1:8000"
+WORKERS = 2
+THREADS = 4
+
+#STATIC
+from pathlib import Path
+import os
+BASE_DIR = Path(__file__).resolve().parent.parent
+STATIC_URL = '/static/'
+STATICFILES_DIRS = [BASE_DIR / "SharixAdmin/static/", BASE_DIR / "tickets/static/"]
+STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
+```
+3) Run a **install_linux.sh**
+4) The system will prompt you to create a superuser
+```
+#Example
+7987654321
+admin
+pa$$w0rd
+pa$$w0rd
+```
+5) Set up the **bin/webuser.sh** file with valid paths
+```bash
+#!/bin/bash
+cd /path/to/project
+exec /path/to/project/env/bin/gunicorn core.wsgi:application -c core/conf_gunicorn.py
+```
+6) It remains to configure Nginx conf and start the daemon
+
+## Settings
+
+Optional configuration params, which can be added to your project settings:
+
+```python
+
+```

BIN
SharixAdmin/.DS_Store


+ 0 - 0
SharixAdmin/__init__.py


+ 72 - 0
SharixAdmin/admin.py

@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+from django.contrib import admin
+from django.http import HttpResponseRedirect
+from django.shortcuts import render
+from SharixAdmin.models import *
+from django import forms
+from xmpp import cli
+from django.contrib.auth.admin import UserAdmin
+import django.contrib.auth.admin as adm
+
+class MessageForm(forms.Form):
+    _selected_action = forms.CharField(widget=forms.MultipleHiddenInput)
+    message = forms.CharField(label='Message', max_length=1000)
+
+
+#@admin.action(description='Отправить сообщение на номер телефона')
+def send_phone(modeladmin, request, queryset):
+    form = None
+    #print("Hello world")
+    if 'apply' in request.POST:
+        #print("Hello world")
+        modeladmin.message_user(request, "Сообщения успешно отправлены!")
+        form = MessageForm(request.POST)
+        if form.is_valid():
+            for i in queryset:
+                if str(i.phone_number).startswith("7"):
+                    message = '{"phone":"+'+i.phone_number+'","msg":"'+request.POST['message']+'"}'      
+                else:
+                    message = '{"phone":"'+i.phone_number+'","msg":"'+request.POST['message']+'"}'
+                cli.send_message("sender", "password", "getter", message)
+                print(i)
+            print(request.POST['message'])
+            return HttpResponseRedirect(request.get_full_path())
+    else:
+        form = MessageForm(initial={'_selected_action': request.POST.getlist("_selected_action")})
+           
+    return render(request, "SharixAdmin/senderform.html", {"items":queryset, "form":form, 'title':'Отправка сообщений на номер телефона'})
+send_phone.short_description = u"Отправить сообщение на номер телефона"
+
+    
+@admin.register(SharixUser)
+class SharixUserAdmin(adm.UserAdmin):
+    
+    list_display = (
+        'username',
+        'phone_number',
+    )
+    list_filter = (
+        'last_login',
+        'is_superuser',
+        'is_staff',
+        'is_active',
+        'date_joined',
+    )
+    fieldsets = (
+        ("Главное", {'fields': ('phone_number', 'password')}),
+        ('Персональные данные', {'fields': ('username', 'email', 'first_name','last_name')}),
+        ('Разрешения', {'fields': ('is_staff', 'is_active', 'is_superuser',)}),
+        ('Прочие разрешения', {'fields': ('groups', 'user_permissions'), 'classes': ['collapse']}),
+        ('Прочее', {'fields': ('last_login', 'date_joined',)}),
+    )
+    add_fieldsets = (
+        ("Главное", {'fields': ('phone_number', 'password1', 'password2')}),
+        ('Персональные данные', {'fields': ('username', 'email', 'first_name','last_name')}),
+        ('Разрешения', {'fields': ('is_staff', 'is_active', 'is_superuser',)}),
+        ('Прочие разрешения', {'fields': ('groups', 'user_permissions'), 'classes': ['collapse']}),
+        ('Прочее', {'fields': ('last_login', 'date_joined',)}),
+    )
+    #raw_id_fields = ('groups', 'user_permissions')
+    actions = [send_phone]
+
+#admin.site.register(SharixUser, UserAdmin)

+ 65 - 0
SharixAdmin/apiviews.py

@@ -0,0 +1,65 @@
+#REST API ---------------------------
+from .serializer import *
+from rest_framework import viewsets, permissions, exceptions
+from rest_framework.authentication import TokenAuthentication
+from rest_framework.decorators import action
+from SharixAdmin.models import *
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from xmpp import cli
+from drf_yasg.views import get_schema_view
+from drf_yasg import openapi
+from drf_yasg.utils import swagger_auto_schema
+
+schema_view = get_schema_view(
+   openapi.Info(
+      title="ShariX Admin Open API",
+      default_version='v1',
+      description="REST API Documentation",
+      #terms_of_service="https://www.google.com/policies/terms/",
+      #contact=openapi.Contact(email="contact@snippets.local"),
+      #license=openapi.License(name="BSD License"),
+   ),
+   public=False,
+   permission_classes=[permissions.IsAuthenticated],
+)
+
+class SharixUserMVS(viewsets.ModelViewSet):
+    queryset = SharixUser.objects.all()
+    serializer_class = UserSerializer
+    #permission_classes = [IsOwnerOrReadOnly]
+    permission_classes = [permissions.IsAuthenticated]
+
+
+class GroupMVS(viewsets.ModelViewSet):
+    queryset = Group.objects.all()
+    serializer_class = GroupSerializer
+    permission_classes = [permissions.IsAuthenticated]
+
+    def update(self, request, *args, **kwargs):
+        #print(self.request.user.pk)
+        return super().update(request, *args, **kwargs)
+        
+    #def get_queryset(self):
+        #owner_req = self.queryset.filter(wallet=self.request.user.pk)
+        #return owner_req    
+
+    def partial_update(self, request, *args, **kwargs):
+        return super().partial_update(request, *args, **kwargs)
+
+    
+class PhoneSender(APIView):
+    """
+    Test description one
+    """
+    
+    permission_classes = [permissions.IsAdminUser, permissions.IsAuthenticated]
+    
+    @swagger_auto_schema(operation_description="Test description two")
+    def get(self, request, format=None):
+        return Response({"message": "Для отправки сообщения используйте POST-запрос"})
+    
+    @swagger_auto_schema(operation_description="Test description three")
+    def post(self, request, format=None):
+        cli.send_message("sender", "password", "getter", request.data)
+        return Response({"message": "Сообщение успешно отправлено!"})

+ 7 - 0
SharixAdmin/apps.py

@@ -0,0 +1,7 @@
+from django.apps import AppConfig
+
+
+class SharixadminConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'SharixAdmin'
+    verbose_name = "SHARIX_PLATFORM"

+ 15 - 0
SharixAdmin/forms.py

@@ -0,0 +1,15 @@
+from django.contrib.auth.forms import AuthenticationForm
+from .models import SharixUser
+from django import forms
+
+class LoginUserForm(AuthenticationForm):
+
+    password = forms.CharField(label="Пароль",
+        widget=forms.PasswordInput(attrs={'class':'form-control'}))
+
+    username = forms.CharField(label="Номер телефона",
+        widget=forms.TextInput(attrs={'class':'form-control'}))
+
+    class Meta:
+        model = SharixUser
+        fields = ['username', 'password']

+ 139 - 0
SharixAdmin/migrations/0001_initial.py

@@ -0,0 +1,139 @@
+# Generated by Django 4.1.3 on 2023-03-17 10:12
+
+import django.contrib.auth.models
+import django.contrib.auth.validators
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ("auth", "0012_alter_user_first_name_max_length"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="SharixUser",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("password", models.CharField(max_length=128, verbose_name="password")),
+                (
+                    "last_login",
+                    models.DateTimeField(
+                        blank=True, null=True, verbose_name="last login"
+                    ),
+                ),
+                (
+                    "is_superuser",
+                    models.BooleanField(
+                        default=False,
+                        help_text="Designates that this user has all permissions without explicitly assigning them.",
+                        verbose_name="superuser status",
+                    ),
+                ),
+                (
+                    "username",
+                    models.CharField(
+                        error_messages={
+                            "unique": "A user with that username already exists."
+                        },
+                        help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
+                        max_length=150,
+                        unique=True,
+                        validators=[
+                            django.contrib.auth.validators.UnicodeUsernameValidator()
+                        ],
+                        verbose_name="username",
+                    ),
+                ),
+                (
+                    "first_name",
+                    models.CharField(
+                        blank=True, max_length=150, verbose_name="first name"
+                    ),
+                ),
+                (
+                    "last_name",
+                    models.CharField(
+                        blank=True, max_length=150, verbose_name="last name"
+                    ),
+                ),
+                (
+                    "email",
+                    models.EmailField(
+                        blank=True, max_length=254, verbose_name="email address"
+                    ),
+                ),
+                (
+                    "is_staff",
+                    models.BooleanField(
+                        default=False,
+                        help_text="Designates whether the user can log into this admin site.",
+                        verbose_name="staff status",
+                    ),
+                ),
+                (
+                    "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",
+                    ),
+                ),
+                (
+                    "date_joined",
+                    models.DateTimeField(
+                        default=django.utils.timezone.now, verbose_name="date joined"
+                    ),
+                ),
+                (
+                    "phone_number",
+                    models.CharField(
+                        help_text="А здесь можно добавить описание поля",
+                        max_length=20,
+                        unique=True,
+                        verbose_name="Номер телефона",
+                    ),
+                ),
+                (
+                    "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",
+                    ),
+                ),
+                (
+                    "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",
+                    ),
+                ),
+            ],
+            options={
+                "db_table": "auth_user",
+            },
+            managers=[
+                ("objects", django.contrib.auth.models.UserManager()),
+            ],
+        ),
+    ]

+ 0 - 0
SharixAdmin/migrations/__init__.py


+ 26 - 0
SharixAdmin/models.py

@@ -0,0 +1,26 @@
+from django.db import models
+from django.contrib.auth.models import AbstractUser
+from django.urls import reverse
+
+
+class SharixUser(AbstractUser):
+    """
+    Пользователь - здесь находиться описание сущности!
+    """
+    #pk = models.BigAutoField(help_text="А здесь можно добавить описание поля")
+    phone_number = models.CharField(max_length=20, unique=True, blank=False, verbose_name='Номер телефона', help_text="А здесь можно добавить описание поля")
+
+    USERNAME_FIELD = 'phone_number'
+    REQUIRED_FIELDS = ['username']
+
+    @property
+    def full_name(self):
+        if self.first_name == "" or self.last_login  == "":
+            return self.username
+        else:
+            return f"{self.first_name} {self.last_name}"
+        
+    class Meta:
+        db_table = "auth_user"
+
+# Create your models here.

+ 29 - 0
SharixAdmin/serializer.py

@@ -0,0 +1,29 @@
+from rest_framework import serializers
+#from rest_framework.exceptions import ValidationError
+#from django.contrib.auth.models import User
+from .models import *
+from django.contrib.auth.models import *
+
+class UserSerializer(serializers.ModelSerializer):
+    full_name = serializers.ReadOnlyField()
+    group_name = serializers.ReadOnlyField(source="groups.name")
+    class Meta:
+        model = SharixUser
+        exclude = ['password', 'id']
+        read_only_fields = ['username', 'phone_number']
+        extra_kwargs = {
+            'first_name': {'write_only': True},
+            'last_name':{'write_only':True}
+        }
+    def validate(self, attrs):
+        return super().validate(attrs)
+
+class GroupToUserSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = GroupManager
+        fields = "__all__"
+    
+class GroupSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Group
+        fields = ("id", "name")

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 6 - 0
SharixAdmin/static/SharixAdmin/img/logo.svg


BIN
SharixAdmin/static/SharixAdmin/img/mc.png


+ 4 - 0
SharixAdmin/static/SharixAdmin/img/menu/arrow-right.svg

@@ -0,0 +1,4 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M1 8C1 7.72386 1.22386 7.5 1.5 7.5L13.2929 7.5L10.1464 4.35355C9.95118 4.15829 9.95118 3.84171 10.1464 3.64645C10.3417 3.45118 10.6583 3.45118 10.8536 3.64645L14.8536 7.64645C15.0488 7.84171 15.0488 8.15829 14.8536 8.35355L10.8536 12.3536C10.6583 12.5488 10.3417 12.5488 10.1464 12.3536C9.95118 12.1583 9.95118 11.8417 10.1464 11.6464L13.2929 8.5H1.5C1.22386 8.5 1 8.27614 1 8Z" 
+fill="#0081ff"/>
+</svg>

+ 8 - 0
SharixAdmin/static/SharixAdmin/img/menu/clock-history.svg

@@ -0,0 +1,8 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8.51496 1.01896C8.34401 1.00635 8.17225 1 8 1V0C8.19685 0 8.39314 0.00726199 8.58852 0.0216722L8.51496 1.01896ZM10.5193 1.46905C10.1985 1.34533 9.86912 1.2454 9.53371 1.17008L9.75282 0.194382C10.1361 0.280463 10.5126 0.394665 10.8792 0.536055L10.5193 1.46905ZM11.889 2.17971C11.7458 2.08402 11.5994 1.99388 11.4503 1.90939L11.9432 1.0393C12.1136 1.13586 12.2809 1.23888 12.4446 1.34824C12.6082 1.4576 12.7674 1.5727 12.9219 1.69322L12.3066 2.48158C12.1715 2.37612 12.0322 2.27541 11.889 2.17971ZM13.7231 3.96934C13.5252 3.68829 13.3068 3.42218 13.0697 3.17321L13.794 2.48368C14.0649 2.76821 14.3145 3.07233 14.5407 3.39353L13.7231 3.96934ZM14.4672 5.32122C14.4012 5.16208 14.3296 5.00583 14.2526 4.85271L15.1458 4.40311C15.2339 4.5781 15.3157 4.75667 15.391 4.93853C15.4664 5.12039 15.5348 5.30453 15.5962 5.49054L14.6467 5.80423C14.5929 5.64147 14.5331 5.48035 14.4672 5.32122ZM14.9979 7.82822C14.9895 7.48455 14.9557 7.14197 14.8969 6.80326L15.8822 6.63231C15.9494 7.01939 15.988 7.41092 15.9976 7.80367L14.9979 7.82822ZM14.8655 9.36563C14.8991 9.1967 14.9264 9.02699 14.9474 8.85687L15.9398 8.97929C15.9159 9.17372 15.8847 9.36766 15.8463 9.56072C15.8079 9.75378 15.7625 9.94489 15.7102 10.1337L14.7464 9.867C14.7922 9.70179 14.8319 9.53457 14.8655 9.36563ZM13.914 11.745C14.0979 11.4546 14.2602 11.151 14.3995 10.8367L15.3137 11.2419C15.1545 11.6011 14.969 11.9481 14.7588 12.28L13.914 11.745ZM12.9497 12.9497C13.0715 12.828 13.1885 12.702 13.3005 12.5722L14.0577 13.2254C13.9297 13.3737 13.796 13.5177 13.6569 13.6569L12.9497 12.9497Z" 
+fill="#0081ff"/>
+<path d="M8 1C6.84885 1 5.71545 1.2839 4.70022 1.82655C3.68499 2.3692 2.81926 3.15386 2.17971 4.11101C1.54017 5.06816 1.14654 6.16827 1.03371 7.31388C0.920876 8.45949 1.09232 9.61525 1.53285 10.6788C1.97337 11.7423 2.66939 12.6808 3.55925 13.4111C4.44911 14.1414 5.50533 14.6409 6.63437 14.8655C7.76341 15.0901 8.93041 15.0327 10.032 14.6986C11.1336 14.3644 12.1358 13.7637 12.9497 12.9497L13.6569 13.6569C12.7266 14.5871 11.5812 15.2736 10.3223 15.6555C9.06332 16.0374 7.72961 16.1029 6.43928 15.8463C5.14895 15.5896 3.94183 15.0187 2.92486 14.1841C1.90788 13.3495 1.11243 12.2769 0.608966 11.0615C0.105504 9.846 -0.0904279 8.52514 0.0385242 7.21586C0.167476 5.90659 0.617333 4.64933 1.34825 3.55544C2.07916 2.46155 3.06857 1.5648 4.22883 0.94463C5.38909 0.324457 6.68439 0 8 0V1Z" 
+fill="#0081ff"/>
+<path d="M7.5 3C7.77614 3 8 3.22386 8 3.5V8.70984L11.2481 10.5659C11.4878 10.7029 11.5711 11.0083 11.4341 11.2481C11.2971 11.4878 10.9917 11.5711 10.7519 11.4341L7.25193 9.43412C7.09615 9.3451 7 9.17943 7 9V3.5C7 3.22386 7.22386 3 7.5 3Z" 
+fill="#0081ff"/>
+</svg>

+ 8 - 0
SharixAdmin/static/SharixAdmin/img/menu/clock-history_w.svg

@@ -0,0 +1,8 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8.51496 1.01896C8.34401 1.00635 8.17225 1 8 1V0C8.19685 0 8.39314 0.00726199 8.58852 0.0216722L8.51496 1.01896ZM10.5193 1.46905C10.1985 1.34533 9.86912 1.2454 9.53371 1.17008L9.75282 0.194382C10.1361 0.280463 10.5126 0.394665 10.8792 0.536055L10.5193 1.46905ZM11.889 2.17971C11.7458 2.08402 11.5994 1.99388 11.4503 1.90939L11.9432 1.0393C12.1136 1.13586 12.2809 1.23888 12.4446 1.34824C12.6082 1.4576 12.7674 1.5727 12.9219 1.69322L12.3066 2.48158C12.1715 2.37612 12.0322 2.27541 11.889 2.17971ZM13.7231 3.96934C13.5252 3.68829 13.3068 3.42218 13.0697 3.17321L13.794 2.48368C14.0649 2.76821 14.3145 3.07233 14.5407 3.39353L13.7231 3.96934ZM14.4672 5.32122C14.4012 5.16208 14.3296 5.00583 14.2526 4.85271L15.1458 4.40311C15.2339 4.5781 15.3157 4.75667 15.391 4.93853C15.4664 5.12039 15.5348 5.30453 15.5962 5.49054L14.6467 5.80423C14.5929 5.64147 14.5331 5.48035 14.4672 5.32122ZM14.9979 7.82822C14.9895 7.48455 14.9557 7.14197 14.8969 6.80326L15.8822 6.63231C15.9494 7.01939 15.988 7.41092 15.9976 7.80367L14.9979 7.82822ZM14.8655 9.36563C14.8991 9.1967 14.9264 9.02699 14.9474 8.85687L15.9398 8.97929C15.9159 9.17372 15.8847 9.36766 15.8463 9.56072C15.8079 9.75378 15.7625 9.94489 15.7102 10.1337L14.7464 9.867C14.7922 9.70179 14.8319 9.53457 14.8655 9.36563ZM13.914 11.745C14.0979 11.4546 14.2602 11.151 14.3995 10.8367L15.3137 11.2419C15.1545 11.6011 14.969 11.9481 14.7588 12.28L13.914 11.745ZM12.9497 12.9497C13.0715 12.828 13.1885 12.702 13.3005 12.5722L14.0577 13.2254C13.9297 13.3737 13.796 13.5177 13.6569 13.6569L12.9497 12.9497Z" 
+fill="white"/>
+<path d="M8 1C6.84885 1 5.71545 1.2839 4.70022 1.82655C3.68499 2.3692 2.81926 3.15386 2.17971 4.11101C1.54017 5.06816 1.14654 6.16827 1.03371 7.31388C0.920876 8.45949 1.09232 9.61525 1.53285 10.6788C1.97337 11.7423 2.66939 12.6808 3.55925 13.4111C4.44911 14.1414 5.50533 14.6409 6.63437 14.8655C7.76341 15.0901 8.93041 15.0327 10.032 14.6986C11.1336 14.3644 12.1358 13.7637 12.9497 12.9497L13.6569 13.6569C12.7266 14.5871 11.5812 15.2736 10.3223 15.6555C9.06332 16.0374 7.72961 16.1029 6.43928 15.8463C5.14895 15.5896 3.94183 15.0187 2.92486 14.1841C1.90788 13.3495 1.11243 12.2769 0.608966 11.0615C0.105504 9.846 -0.0904279 8.52514 0.0385242 7.21586C0.167476 5.90659 0.617333 4.64933 1.34825 3.55544C2.07916 2.46155 3.06857 1.5648 4.22883 0.94463C5.38909 0.324457 6.68439 0 8 0V1Z" 
+fill="white"/>
+<path d="M7.5 3C7.77614 3 8 3.22386 8 3.5V8.70984L11.2481 10.5659C11.4878 10.7029 11.5711 11.0083 11.4341 11.2481C11.2971 11.4878 10.9917 11.5711 10.7519 11.4341L7.25193 9.43412C7.09615 9.3451 7 9.17943 7 9V3.5C7 3.22386 7.22386 3 7.5 3Z" 
+fill="white"/>
+</svg>

+ 6 - 0
SharixAdmin/static/SharixAdmin/img/menu/credit-card.svg

@@ -0,0 +1,6 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M11 5.5C11 5.22386 11.2239 5 11.5 5H13.5C13.7761 5 14 5.22386 14 5.5V6.5C14 6.77614 13.7761 7 13.5 7H11.5C11.2239 7 11 6.77614 11 6.5V5.5Z" 
+fill="#0081ff"/>
+<path d="M2 2C0.895431 2 0 2.89543 0 4V12C0 13.1046 0.895431 14 2 14H14C15.1046 14 16 13.1046 16 12V4C16 2.89543 15.1046 2 14 2H2ZM15 4V9H1V4C1 3.44772 1.44772 3 2 3H14C14.5523 3 15 3.44772 15 4ZM14 13H2C1.44772 13 1 12.5523 1 12V11H15V12C15 12.5523 14.5523 13 14 13Z" 
+fill="#0081ff"/>
+</svg>

+ 6 - 0
SharixAdmin/static/SharixAdmin/img/menu/credit-card_w.svg

@@ -0,0 +1,6 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M11 5.5C11 5.22386 11.2239 5 11.5 5H13.5C13.7761 5 14 5.22386 14 5.5V6.5C14 6.77614 13.7761 7 13.5 7H11.5C11.2239 7 11 6.77614 11 6.5V5.5Z" 
+fill="white"/>
+<path d="M2 2C0.895431 2 0 2.89543 0 4V12C0 13.1046 0.895431 14 2 14H14C15.1046 14 16 13.1046 16 12V4C16 2.89543 15.1046 2 14 2H2ZM15 4V9H1V4C1 3.44772 1.44772 3 2 3H14C14.5523 3 15 3.44772 15 4ZM14 13H2C1.44772 13 1 12.5523 1 12V11H15V12C15 12.5523 14.5523 13 14 13Z" 
+fill="white"/>
+</svg>

+ 6 - 0
SharixAdmin/static/SharixAdmin/img/menu/education.svg

@@ -0,0 +1,6 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8.21144 2.04691C8.07742 1.98436 7.92258 1.98436 7.78856 2.04691L0.288558 5.54691C0.107914 5.63121 -0.00539504 5.81476 0.000197994 6.01403C0.005791 6.2133 0.129217 6.3902 0.314306 6.46424L7.81431 9.46424C7.93351 9.51192 8.06649 9.51192 8.1857 9.46424L14 7.13852V13C13.4477 13 13 13.4477 13 14V16H16V14C16 13.4477 15.5523 13 15 13V6.73852L15.6857 6.46424C15.8708 6.3902 15.9942 6.2133 15.9998 6.01403C16.0054 5.81476 15.8921 5.63121 15.7114 5.54691L8.21144 2.04691ZM8 8.46148L1.75802 5.96469L8 3.05176L14.242 5.96469L8 8.46148Z" 
+fill="#0081ff"/>
+<path d="M4.17556 9.03184C4.04549 8.98306 3.90098 8.99059 3.77669 9.05264C3.6524 9.11468 3.55952 9.22564 3.52032 9.35892L3.02032 11.0589C2.94756 11.3063 3.07488 11.5685 3.31431 11.6642L7.81431 13.4642C7.93351 13.5119 8.06649 13.5119 8.1857 13.4642L12.6857 11.6642C12.9251 11.5685 13.0524 11.3063 12.9797 11.0589L12.4797 9.35892C12.4405 9.22564 12.3476 9.11468 12.2233 9.05264C12.099 8.99059 11.9545 8.98306 11.8244 9.03184L8 10.466L4.17556 9.03184ZM4.10803 10.9047L4.32795 10.157L7.82444 11.4682C7.93763 11.5106 8.06237 11.5106 8.17556 11.4682L11.6721 10.157L11.892 10.9047L8 12.4615L4.10803 10.9047Z" 
+fill="#0081ff"/>
+</svg>

+ 6 - 0
SharixAdmin/static/SharixAdmin/img/menu/education_w.svg

@@ -0,0 +1,6 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8.21144 2.04691C8.07742 1.98436 7.92258 1.98436 7.78856 2.04691L0.288558 5.54691C0.107914 5.63121 -0.00539504 5.81476 0.000197994 6.01403C0.005791 6.2133 0.129217 6.3902 0.314306 6.46424L7.81431 9.46424C7.93351 9.51192 8.06649 9.51192 8.1857 9.46424L14 7.13852V13C13.4477 13 13 13.4477 13 14V16H16V14C16 13.4477 15.5523 13 15 13V6.73852L15.6857 6.46424C15.8708 6.3902 15.9942 6.2133 15.9998 6.01403C16.0054 5.81476 15.8921 5.63121 15.7114 5.54691L8.21144 2.04691ZM8 8.46148L1.75802 5.96469L8 3.05176L14.242 5.96469L8 8.46148Z" 
+fill="white"/>
+<path d="M4.17556 9.03184C4.04549 8.98306 3.90098 8.99059 3.77669 9.05264C3.6524 9.11468 3.55952 9.22564 3.52032 9.35892L3.02032 11.0589C2.94756 11.3063 3.07488 11.5685 3.31431 11.6642L7.81431 13.4642C7.93351 13.5119 8.06649 13.5119 8.1857 13.4642L12.6857 11.6642C12.9251 11.5685 13.0524 11.3063 12.9797 11.0589L12.4797 9.35892C12.4405 9.22564 12.3476 9.11468 12.2233 9.05264C12.099 8.99059 11.9545 8.98306 11.8244 9.03184L8 10.466L4.17556 9.03184ZM4.10803 10.9047L4.32795 10.157L7.82444 11.4682C7.93763 11.5106 8.06237 11.5106 8.17556 11.4682L11.6721 10.157L11.892 10.9047L8 12.4615L4.10803 10.9047Z" 
+fill="white"/>
+</svg>

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 3 - 0
SharixAdmin/static/SharixAdmin/img/menu/gear.svg


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 3 - 0
SharixAdmin/static/SharixAdmin/img/menu/gear_w.svg


+ 8 - 0
SharixAdmin/static/SharixAdmin/img/menu/hdd-network.svg

@@ -0,0 +1,8 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M4.5 5C4.77614 5 5 4.77614 5 4.5C5 4.22386 4.77614 4 4.5 4C4.22386 4 4 4.22386 4 4.5C4 4.77614 4.22386 5 4.5 5Z" 
+fill="#0081ff"/>
+<path d="M3 4.5C3 4.77614 2.77614 5 2.5 5C2.22386 5 2 4.77614 2 4.5C2 4.22386 2.22386 4 2.5 4C2.77614 4 3 4.22386 3 4.5Z" 
+fill="#0081ff"/>
+<path d="M0 4C0 2.89543 0.895431 2 2 2H14C15.1046 2 16 2.89543 16 4V5C16 6.10457 15.1046 7 14 7H8.5V10C9.32843 10 10 10.6716 10 11.5H15.5C15.7761 11.5 16 11.7239 16 12C16 12.2761 15.7761 12.5 15.5 12.5H10C10 13.3284 9.32843 14 8.5 14H7.5C6.67157 14 6 13.3284 6 12.5H0.5C0.223858 12.5 0 12.2761 0 12C0 11.7239 0.223858 11.5 0.5 11.5H6C6 10.6716 6.67157 10 7.5 10V7H2C0.895431 7 0 6.10457 0 5V4ZM1 4V5C1 5.55228 1.44772 6 2 6H14C14.5523 6 15 5.55228 15 5V4C15 3.44772 14.5523 3 14 3H2C1.44772 3 1 3.44772 1 4ZM7 11.5V12.5C7 12.7761 7.22386 13 7.5 13H8.5C8.77614 13 9 12.7761 9 12.5V11.5C9 11.2239 8.77614 11 8.5 11H7.5C7.22386 11 7 11.2239 7 11.5Z" 
+fill="#0081ff"/>
+</svg>

+ 8 - 0
SharixAdmin/static/SharixAdmin/img/menu/hdd-network_w.svg

@@ -0,0 +1,8 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M4.5 5C4.77614 5 5 4.77614 5 4.5C5 4.22386 4.77614 4 4.5 4C4.22386 4 4 4.22386 4 4.5C4 4.77614 4.22386 5 4.5 5Z" 
+fill="white"/>
+<path d="M3 4.5C3 4.77614 2.77614 5 2.5 5C2.22386 5 2 4.77614 2 4.5C2 4.22386 2.22386 4 2.5 4C2.77614 4 3 4.22386 3 4.5Z" 
+fill="white"/>
+<path d="M0 4C0 2.89543 0.895431 2 2 2H14C15.1046 2 16 2.89543 16 4V5C16 6.10457 15.1046 7 14 7H8.5V10C9.32843 10 10 10.6716 10 11.5H15.5C15.7761 11.5 16 11.7239 16 12C16 12.2761 15.7761 12.5 15.5 12.5H10C10 13.3284 9.32843 14 8.5 14H7.5C6.67157 14 6 13.3284 6 12.5H0.5C0.223858 12.5 0 12.2761 0 12C0 11.7239 0.223858 11.5 0.5 11.5H6C6 10.6716 6.67157 10 7.5 10V7H2C0.895431 7 0 6.10457 0 5V4ZM1 4V5C1 5.55228 1.44772 6 2 6H14C14.5523 6 15 5.55228 15 5V4C15 3.44772 14.5523 3 14 3H2C1.44772 3 1 3.44772 1 4ZM7 11.5V12.5C7 12.7761 7.22386 13 7.5 13H8.5C8.77614 13 9 12.7761 9 12.5V11.5C9 11.2239 8.77614 11 8.5 11H7.5C7.22386 11 7 11.2239 7 11.5Z" 
+fill="white"/>
+</svg>

+ 4 - 0
SharixAdmin/static/SharixAdmin/img/menu/house.svg

@@ -0,0 +1,4 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8.70711 1.50005C8.31658 1.10952 7.68342 1.10952 7.29289 1.50005L0.646447 8.14649C0.451184 8.34176 0.451184 8.65834 0.646447 8.8536C0.841709 9.04886 1.15829 9.04886 1.35355 8.8536L2 8.20715V13.5C2 14.3285 2.67157 15 3.5 15H12.5C13.3284 15 14 14.3285 14 13.5V8.20715L14.6464 8.8536C14.8417 9.04886 15.1583 9.04886 15.3536 8.8536C15.5488 8.65834 15.5488 8.34176 15.3536 8.14649L13 5.79294V2.50005C13 2.2239 12.7761 2.00005 12.5 2.00005H11.5C11.2239 2.00005 11 2.2239 11 2.50005V3.79294L8.70711 1.50005ZM13 7.20715L8 2.20715L3 7.20715V13.5C3 13.7762 3.22386 14 3.5 14H12.5C12.7761 14 13 13.7762 13 13.5V7.20715Z" 
+fill="#0081ff"/>
+</svg>

+ 4 - 0
SharixAdmin/static/SharixAdmin/img/menu/house_w.svg

@@ -0,0 +1,4 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8.70711 1.50005C8.31658 1.10952 7.68342 1.10952 7.29289 1.50005L0.646447 8.14649C0.451184 8.34176 0.451184 8.65834 0.646447 8.8536C0.841709 9.04886 1.15829 9.04886 1.35355 8.8536L2 8.20715V13.5C2 14.3285 2.67157 15 3.5 15H12.5C13.3284 15 14 14.3285 14 13.5V8.20715L14.6464 8.8536C14.8417 9.04886 15.1583 9.04886 15.3536 8.8536C15.5488 8.65834 15.5488 8.34176 15.3536 8.14649L13 5.79294V2.50005C13 2.2239 12.7761 2.00005 12.5 2.00005H11.5C11.2239 2.00005 11 2.2239 11 2.50005V3.79294L8.70711 1.50005ZM13 7.20715L8 2.20715L3 7.20715V13.5C3 13.7762 3.22386 14 3.5 14H12.5C12.7761 14 13 13.7762 13 13.5V7.20715Z" 
+fill="white"/>
+</svg>

+ 10 - 0
SharixAdmin/static/SharixAdmin/img/menu/people.svg

@@ -0,0 +1,10 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M15 14C15 14 16 14 16 13C16 12 15 9 11 9C7 9 6 12 6 13C6 14 7 14 7 14H15ZM7.02235 13C7.01888 12.9996 7.01403 12.999 7.00815 12.998C7.00538 12.9975 7.00266 12.997 7.00001 12.9965C7.00146 12.7325 7.16687 11.9669 7.75926 11.2758C8.31334 10.6294 9.28269 10 11 10C12.7173 10 13.6867 10.6294 14.2407 11.2758C14.8331 11.9669 14.9985 12.7325 15 12.9965C14.9973 12.997 14.9946 12.9975 14.9919 12.998C14.986 12.999 14.9811 12.9996 14.9777 13H7.02235Z" 
+fill="#0081ff"/>
+<path d="M11 7C12.1046 7 13 6.10457 13 5C13 3.89543 12.1046 3 11 3C9.89543 3 9 3.89543 9 5C9 6.10457 9.89543 7 11 7ZM14 5C14 6.65685 12.6569 8 11 8C9.34315 8 8 6.65685 8 5C8 3.34315 9.34315 2 11 2C12.6569 2 14 3.34315 14 5Z" 
+fill="#0081ff"/>
+<path d="M6.93593 9.27996C6.56813 9.16232 6.15954 9.07679 5.70628 9.03306C5.48195 9.01141 5.24668 9 5 9C1 9 0 12 0 13C0 13.6667 0.333333 14 1 14H5.21636C5.07556 13.7159 5 13.3791 5 13C5 11.9897 5.37724 10.958 6.08982 10.0962C6.33327 9.80174 6.61587 9.52713 6.93593 9.27996ZM4.92004 10.0005C4.32256 10.9136 4 11.9547 4 13H1C1 12.7393 1.16424 11.97 1.75926 11.2758C2.30468 10.6395 3.25249 10.0197 4.92004 10.0005Z" 
+fill="#0081ff"/>
+<path d="M1.5 5.5C1.5 3.84315 2.84315 2.5 4.5 2.5C6.15685 2.5 7.5 3.84315 7.5 5.5C7.5 7.15685 6.15685 8.5 4.5 8.5C2.84315 8.5 1.5 7.15685 1.5 5.5ZM4.5 3.5C3.39543 3.5 2.5 4.39543 2.5 5.5C2.5 6.60457 3.39543 7.5 4.5 7.5C5.60457 7.5 6.5 6.60457 6.5 5.5C6.5 4.39543 5.60457 3.5 4.5 3.5Z" 
+fill="#0081ff"/>
+</svg>

+ 10 - 0
SharixAdmin/static/SharixAdmin/img/menu/people_w.svg

@@ -0,0 +1,10 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M15 14C15 14 16 14 16 13C16 12 15 9 11 9C7 9 6 12 6 13C6 14 7 14 7 14H15ZM7.02235 13C7.01888 12.9996 7.01403 12.999 7.00815 12.998C7.00538 12.9975 7.00266 12.997 7.00001 12.9965C7.00146 12.7325 7.16687 11.9669 7.75926 11.2758C8.31334 10.6294 9.28269 10 11 10C12.7173 10 13.6867 10.6294 14.2407 11.2758C14.8331 11.9669 14.9985 12.7325 15 12.9965C14.9973 12.997 14.9946 12.9975 14.9919 12.998C14.986 12.999 14.9811 12.9996 14.9777 13H7.02235Z" 
+fill="white"/>
+<path d="M11 7C12.1046 7 13 6.10457 13 5C13 3.89543 12.1046 3 11 3C9.89543 3 9 3.89543 9 5C9 6.10457 9.89543 7 11 7ZM14 5C14 6.65685 12.6569 8 11 8C9.34315 8 8 6.65685 8 5C8 3.34315 9.34315 2 11 2C12.6569 2 14 3.34315 14 5Z" 
+fill="white"/>
+<path d="M6.93593 9.27996C6.56813 9.16232 6.15954 9.07679 5.70628 9.03306C5.48195 9.01141 5.24668 9 5 9C1 9 0 12 0 13C0 13.6667 0.333333 14 1 14H5.21636C5.07556 13.7159 5 13.3791 5 13C5 11.9897 5.37724 10.958 6.08982 10.0962C6.33327 9.80174 6.61587 9.52713 6.93593 9.27996ZM4.92004 10.0005C4.32256 10.9136 4 11.9547 4 13H1C1 12.7393 1.16424 11.97 1.75926 11.2758C2.30468 10.6395 3.25249 10.0197 4.92004 10.0005Z" 
+fill="white"/>
+<path d="M1.5 5.5C1.5 3.84315 2.84315 2.5 4.5 2.5C6.15685 2.5 7.5 3.84315 7.5 5.5C7.5 7.15685 6.15685 8.5 4.5 8.5C2.84315 8.5 1.5 7.15685 1.5 5.5ZM4.5 3.5C3.39543 3.5 2.5 4.39543 2.5 5.5C2.5 6.60457 3.39543 7.5 4.5 7.5C5.60457 7.5 6.5 6.60457 6.5 5.5C6.5 4.39543 5.60457 3.5 4.5 3.5Z" 
+fill="white"/>
+</svg>

+ 6 - 0
SharixAdmin/static/SharixAdmin/img/menu/person.svg

@@ -0,0 +1,6 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8 8C9.65685 8 11 6.65685 11 5C11 3.34315 9.65685 2 8 2C6.34315 2 5 3.34315 5 5C5 6.65685 6.34315 8 8 8ZM10 5C10 6.10457 9.10457 7 8 7C6.89543 7 6 6.10457 6 5C6 3.89543 6.89543 3 8 3C9.10457 3 10 3.89543 10 5Z" 
+fill="#0081ff"/>
+<path d="M14 13C14 14 13 14 13 14H3C3 14 2 14 2 13C2 12 3 9 8 9C13 9 14 12 14 13ZM13 12.9965C12.9986 12.7497 12.8462 12.0104 12.1679 11.3321C11.5156 10.6798 10.2891 10 7.99999 10C5.71088 10 4.48435 10.6798 3.8321 11.3321C3.15375 12.0104 3.00142 12.7497 3 12.9965H13Z" 
+fill="#0081ff"/>
+</svg>

+ 6 - 0
SharixAdmin/static/SharixAdmin/img/menu/person_w.svg

@@ -0,0 +1,6 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8 8C9.65685 8 11 6.65685 11 5C11 3.34315 9.65685 2 8 2C6.34315 2 5 3.34315 5 5C5 6.65685 6.34315 8 8 8ZM10 5C10 6.10457 9.10457 7 8 7C6.89543 7 6 6.10457 6 5C6 3.89543 6.89543 3 8 3C9.10457 3 10 3.89543 10 5Z" 
+fill="white"/>
+<path d="M14 13C14 14 13 14 13 14H3C3 14 2 14 2 13C2 12 3 9 8 9C13 9 14 12 14 13ZM13 12.9965C12.9986 12.7497 12.8462 12.0104 12.1679 11.3321C11.5156 10.6798 10.2891 10 7.99999 10C5.71088 10 4.48435 10.6798 3.8321 11.3321C3.15375 12.0104 3.00142 12.7497 3 12.9965H13Z" 
+fill="white"/>
+</svg>

+ 4 - 0
SharixAdmin/static/SharixAdmin/img/menu/sotrud.svg

@@ -0,0 +1,4 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M6.5 1C5.67173 1 5 1.67117 5 2.49976V3H1.5C0.671573 3 0 3.67157 0 4.5V12.5C0 13.3284 0.671573 14 1.5 14H14.5C15.3284 14 16 13.3284 16 12.5V4.5C16 3.67157 15.3284 3 14.5 3H11V2.49976C11 1.67117 10.3283 1 9.5 1H6.5ZM6.5 2H9.5C9.7763 2 10 2.22463 10 2.50061V3H6V2.49976C6 2.22377 6.2237 2 6.5 2ZM8.38649 8.91441L15 7.15081V12.5C15 12.7761 14.7761 13 14.5 13H1.5C1.22386 13 1 12.7761 1 12.5V7.15081L7.61351 8.91441C7.86674 8.98194 8.13326 8.98194 8.38649 8.91441ZM1.5 4H14.5C14.7761 4 15 4.22386 15 4.5V6.11586L8.12883 7.94817C8.04442 7.97068 7.95558 7.97068 7.87117 7.94817L1 6.11586V4.5C1 4.22386 1.22386 4 1.5 4Z" 
+fill="#0081ff"/>
+</svg>

+ 4 - 0
SharixAdmin/static/SharixAdmin/img/menu/sotrud_w.svg

@@ -0,0 +1,4 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M6.5 1C5.67173 1 5 1.67117 5 2.49976V3H1.5C0.671573 3 0 3.67157 0 4.5V12.5C0 13.3284 0.671573 14 1.5 14H14.5C15.3284 14 16 13.3284 16 12.5V4.5C16 3.67157 15.3284 3 14.5 3H11V2.49976C11 1.67117 10.3283 1 9.5 1H6.5ZM6.5 2H9.5C9.7763 2 10 2.22463 10 2.50061V3H6V2.49976C6 2.22377 6.2237 2 6.5 2ZM8.38649 8.91441L15 7.15081V12.5C15 12.7761 14.7761 13 14.5 13H1.5C1.22386 13 1 12.7761 1 12.5V7.15081L7.61351 8.91441C7.86674 8.98194 8.13326 8.98194 8.38649 8.91441ZM1.5 4H14.5C14.7761 4 15 4.22386 15 4.5V6.11586L8.12883 7.94817C8.04442 7.97068 7.95558 7.97068 7.87117 7.94817L1 6.11586V4.5C1 4.22386 1.22386 4 1.5 4Z" 
+fill="white"/>
+</svg>

+ 8 - 0
SharixAdmin/static/SharixAdmin/img/menu/tikets.svg

@@ -0,0 +1,8 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M9.5 0C9.77614 0 10 0.223858 10 0.5C10 0.776142 10.2239 1 10.5 1C10.7761 1 11 1.22386 11 1.5V2C11 2.27614 10.7761 2.5 10.5 2.5H5.5C5.22386 2.5 5 2.27614 5 2V1.5C5 1.22386 5.22386 1 5.5 1C5.77614 1 6 0.776142 6 0.5C6 0.223858 6.22386 0 6.5 0H9.5Z" 
+fill="#0081ff"/>
+<path d="M3 2.5C3 2.22386 3.22386 2 3.5 2H4C4.27614 2 4.5 1.77614 4.5 1.5C4.5 1.22386 4.27614 1 4 1H3.5C2.67157 1 2 1.67157 2 2.5V14.5C2 15.3284 2.67157 16 3.5 16H12.5C13.3284 16 14 15.3284 14 14.5V2.5C14 1.67157 13.3284 1 12.5 1H12C11.7239 1 11.5 1.22386 11.5 1.5C11.5 1.77614 11.7239 2 12 2H12.5C12.7761 2 13 2.22386 13 2.5V14.5C13 14.7761 12.7761 15 12.5 15H3.5C3.22386 15 3 14.7761 3 14.5V2.5Z" 
+fill="#0081ff"/>
+<path d="M10.8536 7.85355C11.0488 7.65829 11.0488 7.34171 10.8536 7.14645C10.6583 6.95118 10.3417 6.95118 10.1464 7.14645L7.5 9.79289L6.35355 8.64645C6.15829 8.45118 5.84171 8.45118 5.64645 8.64645C5.45118 8.84171 5.45118 9.15829 5.64645 9.35355L7.14645 10.8536C7.24021 10.9473 7.36739 11 7.5 11C7.63261 11 7.75979 10.9473 7.85355 10.8536L10.8536 7.85355Z" 
+fill="#0081ff"/>
+</svg>

+ 8 - 0
SharixAdmin/static/SharixAdmin/img/menu/tikets_w.svg

@@ -0,0 +1,8 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M9.5 0C9.77614 0 10 0.223858 10 0.5C10 0.776142 10.2239 1 10.5 1C10.7761 1 11 1.22386 11 1.5V2C11 2.27614 10.7761 2.5 10.5 2.5H5.5C5.22386 2.5 5 2.27614 5 2V1.5C5 1.22386 5.22386 1 5.5 1C5.77614 1 6 0.776142 6 0.5C6 0.223858 6.22386 0 6.5 0H9.5Z" 
+fill="white"/>
+<path d="M3 2.5C3 2.22386 3.22386 2 3.5 2H4C4.27614 2 4.5 1.77614 4.5 1.5C4.5 1.22386 4.27614 1 4 1H3.5C2.67157 1 2 1.67157 2 2.5V14.5C2 15.3284 2.67157 16 3.5 16H12.5C13.3284 16 14 15.3284 14 14.5V2.5C14 1.67157 13.3284 1 12.5 1H12C11.7239 1 11.5 1.22386 11.5 1.5C11.5 1.77614 11.7239 2 12 2H12.5C12.7761 2 13 2.22386 13 2.5V14.5C13 14.7761 12.7761 15 12.5 15H3.5C3.22386 15 3 14.7761 3 14.5V2.5Z" 
+fill="white"/>
+<path d="M10.8536 7.85355C11.0488 7.65829 11.0488 7.34171 10.8536 7.14645C10.6583 6.95118 10.3417 6.95118 10.1464 7.14645L7.5 9.79289L6.35355 8.64645C6.15829 8.45118 5.84171 8.45118 5.64645 8.64645C5.45118 8.84171 5.45118 9.15829 5.64645 9.35355L7.14645 10.8536C7.24021 10.9473 7.36739 11 7.5 11C7.63261 11 7.75979 10.9473 7.85355 10.8536L10.8536 7.85355Z" 
+fill="white"/>
+</svg>

BIN
SharixAdmin/static/SharixAdmin/img/mir.png


BIN
SharixAdmin/static/SharixAdmin/img/paymo.png


BIN
SharixAdmin/static/SharixAdmin/img/schemav3.png


BIN
SharixAdmin/static/SharixAdmin/img/visa.png


+ 73 - 0
SharixAdmin/static/drf-yasg/style.css

@@ -0,0 +1,73 @@
+html {
+    box-sizing: border-box;
+    overflow: -moz-scrollbars-vertical;
+    overflow-y: scroll;
+}
+
+*,
+*:before,
+*:after {
+    box-sizing: inherit;
+}
+
+body {
+    margin: 0;
+    padding: 0;
+}
+
+body.swagger-body {
+    background: #fafafa;
+}
+
+.hidden {
+    display: none;
+}
+
+#django-session-auth > div {
+    display: inline-block;
+}
+
+#django-session-auth .btn.authorize {
+    padding: 10px 23px;
+}
+
+#django-session-auth .btn.authorize a {
+    color: #49cc90;
+    text-decoration: none;
+}
+
+#django-session-auth .hello {
+    margin-right: 5px;
+}
+
+#django-session-auth .hello .django-session {
+    font-weight: bold;
+}
+
+.label {
+    display: inline;
+    padding: .2em .6em .3em;
+    font-weight: 700;
+    line-height: 1;
+    color: #fff;
+    text-align: center;
+    white-space: nowrap;
+    vertical-align: baseline;
+    border-radius: .25em;
+}
+
+.label-primary {
+    background-color: #337ab7;
+}
+
+.divider {
+    margin-right: 8px;
+    background: #16222c44;
+    width: 2px;
+}
+
+svg.swagger-defs {
+    position: absolute;
+    width: 0;
+    height: 0;
+}

+ 28 - 0
SharixAdmin/tables.py

@@ -0,0 +1,28 @@
+import django_tables2 as tables
+from .models import *
+from django.utils.html import format_html
+
+class TransactionsWalletTable(tables.Table):
+    # id = tables.Column(order_by=True)
+    id = tables.Column(verbose_name='#', orderable=False,                                       attrs={"td":{"width":"5%"}})
+    wallet = tables.Column(verbose_name='Владелец', orderable=False,                            attrs={"td":{"width":"15%"}})
+    name_operation = tables.Column(verbose_name='Услуга', attrs={'th':{'scope':'col'},          "td":{"width":"20%"}}) 
+    price = tables.Column(verbose_name='Баллы', attrs={"class":"row",                           "td":{"width":"10%"}})
+    date_operation = tables.Column(verbose_name='Дата оформления',                              attrs={"td":{"width":"30%"}})
+    is_carried_out = tables.BooleanColumn(verbose_name='Статус', orderable=False, yesno="Успешно,Не успешно", attrs={"td":{"width":"20%"}})
+    
+    class Meta:
+        #model = TransactionsWallets
+        attrs = {"class": "table table-striped"}
+        exclude = ("balance_before", 
+                   "amount", 
+                   "metaservice_id", 
+                   "transaction_type",
+                   "doc_num",
+                   "service_id")
+
+    def render_name_operation(self, value, record):
+        return format_html("<a href='{}'>{}</a>", record.get_absolute_url(), value)
+        
+
+    

+ 33 - 0
SharixAdmin/templates/SharixAdmin/auth.html

@@ -0,0 +1,33 @@
+{% extends 'SharixAdmin/base.html' %}
+{% load static %}
+
+{% block content %}
+<div class="text-center" style="margin-top: 150px;">
+    <div class="form-signin" style="width: 100%; max-width:330px; margin: auto; display:block;">
+        <form class="m-2 p-2" method="post">
+            <img class="mb-4" src="{% static 'SharixAdmin/img/logo.svg' %}" alt="" width="72" >
+            <h1 class="h3 mb-3 fw-normal">Авторизуйтесь</h1>
+            {% csrf_token %}
+            
+            {% for item in form %}
+                <div class="form-floating my-3">
+                    {{item}}
+                    <label for="{{ item.id_for_label }}">{{ item.label }}</label>
+                </div>
+                {% if item.errors %}
+                    <div class="alert alert-danger" role="alert">
+                        {{ item.errors }}
+                    </div>
+                {% endif %}
+            {% endfor %}
+            {% if form.non_field_errors %}
+                <div class="alert alert-danger" role="alert">
+                    Пожалуйста, введите правильные Номер телефона и пароль.
+                </div>
+            {% endif %}
+            <button class="w-100 btn btn-lg" type="submit" style="background-color: #479FF8; color: white;">Войти</button>
+            <p class="mt-5 mb-3 text-muted">© 2022</p>
+          </form>
+    </div>
+</div>
+{% endblock %}

+ 54 - 0
SharixAdmin/templates/SharixAdmin/balance.html

@@ -0,0 +1,54 @@
+{% extends 'SharixAdmin/index.html' %}
+{% load static %}
+
+{% block contenthome %}
+    <h1 >{{ title }}</h1>
+    <div class="container" style="overflow: auto; width: 100%; height: 650px;">
+        <div class="row" >
+            <div class="col-12 text-center" >
+                <br>
+                <p>Доступна оплата картой и по реквизитам. При оплате заказа банковской картой, 
+                    <br> обработка платежа происходит на странице платежного провайдера PAYMO.</p>
+                <br>
+                <p>Вам будет необходимо ввести данные Вашей банковской карты: МИР; VISA International; 
+                    <br> Mastercard Worldwide.</p>
+                <br>
+                <p>Карты только 3D-Secure.</p>
+                <form method="post" >
+                    {% csrf_token %}
+                    <div class="form-floating mb-3 w-50 m-auto">
+                        <input type="number" class="form-control" name="price" placeholder="Введите необходимую сумму" step="100">
+                        <label for="floatingInput">Введите необходимую сумму</label>
+                    </div>
+                    <br>
+                    <p>После пополнения, средства можно использовать для оплаты услуг дочерних сервисов.</p>
+                    <br>
+                    <div>
+                        <img src="{% static 'SharixAdmin/img/visa.png' %}" alt="">
+                        <img src="{% static 'SharixAdmin/img/mc.png' %}" alt="">
+                        <img src="{% static 'SharixAdmin/img/mir.png' %}" alt="">
+                        <br>
+                        <img src="{% static 'SharixAdmin/img/paymo.png' %}" alt="">
+                    </div>
+                    <button class="btn btn-primary mt-5 mb-3">Пополнить</button>
+                    <br>
+                    <a href="" >Назад</a>
+                </form>
+            </div>
+            <div class="col-12 mt-5">
+                <h3>Реквизиты</h3>
+                <div class="p-5" style="border-radius: 15px; border: solid 1px gray;">
+                    <p>Юридический адрес:<b>199155, город Санкт-Петербург, Железноводская улица, дом 32 литер д, помещение 22-н офис 1-8</b></p> 
+                    <p>Электронная почта: <b>info@sharix-app.org</b></p>
+                    <p>ИНН: <b>7801685119</b></p>
+                    <p>Банк: <b>Московский филиал АКБ МКПБ (АО)</b></p>
+                    <p>Бик: <b>044525610</b></p>
+                    <p>Расчетный счет: <b>40702810800400000025</b></p>
+                    <p>Корреспондентский счет: <b>30101810645250000610</b></p>
+                </div>
+            </div>
+        </div>
+    </div>
+    
+    
+{% endblock contenthome %}

+ 10 - 0
SharixAdmin/templates/SharixAdmin/balance_success.html

@@ -0,0 +1,10 @@
+{% extends 'SharixAdmin/index.html' %}
+
+{% block contenthome %}
+    <h1 >{{ title }}</h1>
+    <div class="container text-center" style="width: 100%; height: 650px; margin-top: 250px;">
+        <h3>{{ msg }}</h3> 
+    </div>
+    
+    
+{% endblock contenthome %}

+ 17 - 0
SharixAdmin/templates/SharixAdmin/base.html

@@ -0,0 +1,17 @@
+{% load static %}
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <link rel="shortcut icon" href="{% static 'SharixAdmin/img/logo.svg' %}"/>
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
+    <title>{{ title }}</title>
+</head>
+<body style="min-width: 1050px;">
+    {% block content %}
+    {% endblock %}
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
+</body>
+</html>

+ 147 - 0
SharixAdmin/templates/SharixAdmin/index.html

@@ -0,0 +1,147 @@
+{% extends 'SharixAdmin/base.html' %}
+{% load static %}
+
+{% block content %}
+<input type="checkbox" name="" style="display: none;" checked id="hideMenuCheckBox">
+<div class="container-fluid">
+    <div class="row">
+        <div class="col-3" id="leftmainpage">
+            <div class="d-flex flex-column p-2" style="width: 100%; min-width: 80px; height: 800px;  border-radius: 15px; box-shadow: 4px 5px 40px #cfcfcf; margin-top: 50px;">
+                <a class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-decoration-none" 
+                onclick="barMenuHide()" 
+                style="cursor: pointer;">
+                  <img src="{% static 'SharixAdmin/img/menu/arrow-right.svg' %}" 
+                  style="width: 30px; height: 30px; rotate: 180deg; color: #0081ff; transition-duration: 0.4s;" 
+                  class="mx-3 my-2" id="row-bar-menu" alt="">
+                  <span class="fs-4" id="sharix-menu-row">ShariX Menu</span>
+                </a>
+                <hr>
+                <ul class="nav nav-pills flex-column mb-auto">
+                  {% for item in menu %}
+                  {% if item.link == url_path %}
+                  <li class="nav-item">
+                    <a class="nav-link active" >
+                      {% with 'SharixAdmin/img/menu/'|add:item.sel|add:'_w.svg' as image_static %}
+                        <img  src="{% static image_static %}" alt="" style="width: 30px; height: 30px; "> 
+                      {% endwith %}
+                      
+                      <span class="hidemenu">{{ item.title }}</span>
+                    </a>
+                  </li>
+                  {% elif item.link == 'tickets' %}
+                  <li class="nav-item">
+                    <a href="{% url 'tickets:lists' %}" class="nav-link" >
+                      {% with 'SharixAdmin/img/menu/'|add:item.sel|add:'.svg' as image_static %}
+                        <img  src="{% static image_static %}" alt="" style="width: 30px; height: 30px;"> 
+                      {% endwith %}
+                      <span class="hidemenu">{{ item.title }}</span>
+                    </a>
+                  </li>
+                  {% elif item.link == 'course' %}
+                  <li class="nav-item">
+                    <a href="http://study.reversea.net/" class="nav-link" >
+                      {% with 'SharixAdmin/img/menu/'|add:item.sel|add:'.svg' as image_static %}
+                        <img  src="{% static image_static %}" alt="" style="width: 30px; height: 30px;"> 
+                      {% endwith %}
+                      <span class="hidemenu">{{ item.title }}</span>
+                    </a>
+                  </li>
+                  {% else %}
+                  <li class="nav-item">
+                    <a href="{% url item.link %}" class="nav-link" >
+                      {% with 'SharixAdmin/img/menu/'|add:item.sel|add:'.svg' as image_static %}
+                        <img  src="{% static image_static %}" alt="" style="width: 30px; height: 30px; "> 
+                      {% endwith %}
+                      <span class="hidemenu">{{ item.title }}</span>
+                    </a>
+                  </li>
+                  {% endif%}
+                  {% endfor %}
+                </ul>
+                <hr>
+                <div class="dropdown">
+                  <a href="#" class="d-flex align-items-center link-dark text-decoration-none dropdown-toggle" id="dropdownUser2" data-bs-toggle="dropdown" aria-expanded="false">
+                    <img alt="" width="32" height="32" class="rounded-circle me-2">
+                    <strong id="user-name-exit">{{ request.user.username }}</strong>
+                  </a>
+                  <ul class="dropdown-menu text-small shadow" aria-labelledby="dropdownUser2">
+                    <li><a class="dropdown-item" href="#">Контакты</a></li>
+                    <li><a class="dropdown-item" href="#">Условия использования</a></li>
+                    <li><a class="dropdown-item" href="#">Политика конфиденциальноти</a></li>
+                    <li><hr class="dropdown-divider"></li>
+                    <li><a class="btn btn-danger mx-2" href="{% url 'logoutweb' %}">Выйти</a></li>
+                  </ul>
+                </div>
+              </div>      
+        </div>
+        <div class="col-9" id="rightmainpage">
+            <div class="d-flex flex-column p-4" 
+            style="
+            width: 100%; 
+            height: 800px; 
+            border-radius: 15px; 
+            background-color:white;
+            box-shadow: 4px 5px 40px #cfcfcf; 
+            margin-top: 50px;">
+            {% block contenthome %}
+            {% endblock contenthome%}
+        </div>
+            
+        </div>
+    </div>
+</div>
+
+<script>
+  hidemenu = document.getElementsByClassName('hidemenu')
+  shMenuRow = document.getElementById('sharix-menu-row')
+  usNameExit = document.getElementById('user-name-exit')
+  rightmainpage = document.getElementById('rightmainpage')
+  leftmainpage = document.getElementById('leftmainpage')
+  hideCheck = document.getElementById('hideMenuCheckBox')
+  rowBarMenu = document.getElementById('row-bar-menu')
+
+  function show(){
+    rowBarMenu.style.transform = 'rotate(0deg)';
+    shMenuRow.style.display = "inline"
+      usNameExit.style.display = "inline"
+      leftmainpage.style.maxWidth = "unset"
+      leftmainpage.classList.remove("col-1")
+      leftmainpage.classList.add("col-3")
+      rightmainpage.classList.remove("col-11")
+      rightmainpage.classList.add("col-9")
+      for(var i = 0; i < hidemenu.length; i++){
+       hidemenu[i].style.display = "inline"; // depending on what you're doing
+      }
+  }
+  function hide(){
+    rowBarMenu.style.transform = 'rotate(180deg)';
+    shMenuRow.style.display = "none"
+    usNameExit.style.display = "none"
+    leftmainpage.style.maxWidth = "100px"
+    leftmainpage.classList.remove("col-3")
+    leftmainpage.classList.add("col-1")
+    rightmainpage.classList.remove("col-9")
+    rightmainpage.classList.add("col-11")
+    for(var i = 0; i < hidemenu.length; i++){
+      hidemenu[i].style.display = "none"; // depending on what you're doing
+    }
+  }
+  function barMenuHide(){
+    if(hideCheck.checked == true){
+      hideCheck.checked = false
+      hide()
+    } else {
+      hideCheck.checked = true
+      show()
+    }
+  }
+  if(hideCheck.checked == true){
+      show()
+    } else {
+      hide()
+    }
+  
+  
+</script>
+
+{% endblock %}

+ 29 - 0
SharixAdmin/templates/SharixAdmin/main.html

@@ -0,0 +1,29 @@
+{% extends 'SharixAdmin/index.html' %}
+
+{% block contenthome %}
+    <h1 >{{ title }}</h1>
+    <div class="container" style="padding: 2px;">
+        <div class="row fs-6">
+            
+            <div class="col-6 ">
+                <ul class="list-group">
+                    <li class="list-group-item light"><strong>Документация</strong> </li>
+                    <li class="list-group-item"><a  href="https://wiki.sharix-app.org/doku.php/sharix/legal/politika_konfidencialnosti_platformy_sharix">Политика конфиденциальности</a></li>
+                    <li class="list-group-item"><a  href="https://wiki.sharix-app.org/doku.php/sharix/legal/pravila_okazanija_uslug">Правила оказания услуг</a></li>
+                    <li class="list-group-item"><a  href="https://wiki.sharix-app.org/doku.php/sharix/legal/porjadok_okazanija_uslug">Порядок оказания услуг</a></li>
+                    <li class="list-group-item"><a  href="https://wiki.sharix-app.org/doku.php/sharix/legal/perechen_uslug_platformy">Перечень услуг</a></li>
+                    <li class="list-group-item"><a  href="{% url 'schemav1' %}">Схема 1</a> / <a  href="{% url 'schemav2' %}">Схема 2</a> / <a  href="{% url 'schema' %}">Схема 3</a> / <a  href="{% url 'schema-redoc' %}">API Docs</a></li>
+                
+                  </ul>
+            </div>
+            <div class="col-6 text-end">
+                <p>Пользователя: <b>{{ request.user.username }}</b></p>
+                <p >Телефон: <b>{{ phone }}</b></p>
+                <p >Дата регистрации: <b>{{ request.user.date_joined }}</b> </p>
+                <p >Баланс: <b>{{ wallet.balance }} баллов</b></p>
+                <a href="{% url 'balance' %}" class="btn btn-success">Купить баллы</a>
+            </div>
+        </div>
+    </div>
+    
+{% endblock contenthome %}

+ 10 - 0
SharixAdmin/templates/SharixAdmin/schema.html

@@ -0,0 +1,10 @@
+{% extends 'SharixAdmin/base.html' %}
+{% load static %}
+
+{% block content %}
+    
+     <div class="container-fluid" style="width: 100%;" >
+        <img src="{% static 'SharixAdmin/img/schemav3.png' %}" /> 
+    </div> 
+    
+{% endblock content %}

+ 11 - 0
SharixAdmin/templates/SharixAdmin/senderform.html

@@ -0,0 +1,11 @@
+{% extends "admin/base_site.html" %}
+
+{% block content %}
+<form action="" method="post">{% csrf_token %}
+    {{ form }}
+    <p>Сообщение будет отправлена следующим пользователям:</p>
+    <ul>{{ items|unordered_list }}</ul>
+    <input type="hidden" name="action" value="send_phone" />
+    <input type="submit" name="apply" value="Отправить" />
+</form>
+{% endblock %}

+ 9 - 0
SharixAdmin/templates/SharixAdmin/test.html

@@ -0,0 +1,9 @@
+{% extends 'SharixAdmin/index.html' %}
+
+{% block contenthome %}
+    <h1 >{{ title }}</h1>
+    <div class="container text-center" style="width: 100%; height: 650px; margin-top: 250px;">
+        <h3>Данные страницы находиться в разработке ;)</h3> 
+    </div>
+    
+{% endblock contenthome %}

+ 25 - 0
SharixAdmin/templates/SharixAdmin/trans_carried.html

@@ -0,0 +1,25 @@
+{% extends 'SharixAdmin/index.html' %}
+
+{% block contenthome %}
+    <h1 >{{ title }}</h1>
+    <h3 >Ползователь: {{ request.user.username }}</h3>
+    <h3 >Баланс: {{ wallet.balance }} р.</h3>
+    {% if balance_err %}
+    <div class="alert alert-danger" role="alert">
+        {{ balance_err }}
+    </div>
+    {% endif %}
+    <ul class="list-group">
+        <li class="list-group-item active">{{ model.name_operation }}</li>
+        <li class="list-group-item">Цена: {{ model.price }}</li>
+        <li class="list-group-item">Валделец: {{ model.wallet.user.username }}</li>
+        <li class="list-group-item">Дата услуги: {{ model.date_operation }}</li>
+        <li class="list-group-item">Проведено: {{ model.is_carried_out }}</li>
+    </ul>
+    {% if not model.is_carried_out %}
+    <form method="post">
+        {% csrf_token %}
+        <button class="btn btn-success my-3" type="submit">Провести</button>
+    </form>
+    {% endif %}
+{% endblock contenthome %}

+ 15 - 0
SharixAdmin/templates/SharixAdmin/transactions.html

@@ -0,0 +1,15 @@
+{% extends 'SharixAdmin/index.html' %}
+{% load render_table from django_tables2 %}
+{% block contenthome %}
+    <h1 >{{ title }}</h1>
+    <h3 >Ползователь: {{ request.user.username }}</h3>
+    <h3 >Баланс: {{ wallet.balance }} баллов</h3>
+    <form class="d-flex" method="post">
+        {% csrf_token %}
+        <input class="form-control me-2" name="search" type="search" placeholder="Search" aria-label="Search">
+        <button class="btn btn-outline-success" type="submit">Search</button>
+      </form>
+      {% comment %} {% render_table  table %} {% endcomment %}
+      
+    
+{% endblock contenthome %}

+ 48 - 0
SharixAdmin/templates/drf-yasg/redoc.html

@@ -0,0 +1,48 @@
+{% load static %}
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>{% block title %}{{ title }}{% endblock %}</title>
+
+    {% block extra_head %}
+        {# -- Add any extra HTML heads tags here - except scripts and styles -- #}
+    {% endblock %}
+
+    {% block favicon %}
+        {# -- Maybe replace the favicon -- #}
+        <link rel="icon" type="image/png" href="{% static 'SharixAdmin/img/logo.svg' %}"/>
+    {% endblock %}
+
+    {% block main_styles %}
+        <link rel="stylesheet" type="text/css" href="{% static 'SharixAdmin/style.css' %}"/>
+    {% endblock %}
+    {% block extra_styles %}
+        {# -- Add any additional CSS scripts here -- #}
+    {% endblock %}
+</head>
+<body>
+
+{% block extra_body %}
+    {# -- Add any header/body markup here (rendered BEFORE the swagger-ui/redoc element) -- #}
+{% endblock %}
+
+<div id="redoc-placeholder"></div>
+
+{% block footer %}
+    {# -- Add any footer markup here (rendered AFTER the swagger-ui/redoc element) -- #}
+{% endblock %}
+
+<script id="redoc-settings" type="application/json">{{ redoc_settings | safe }}</script>
+
+{% block main_scripts %}
+    <script src="{% static 'drf-yasg/insQ.min.js' %}"></script>
+    <script src="{% static 'drf-yasg/redoc-init.js' %}"></script>
+    <script src="{% static 'drf-yasg/redoc/redoc.min.js' %}"></script>
+{% endblock %}
+{% block extra_scripts %}
+    {# -- Add any additional scripts here -- #}
+{% endblock %}
+</body>
+</html>

+ 3 - 0
SharixAdmin/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 32 - 0
SharixAdmin/urls.py

@@ -0,0 +1,32 @@
+from django.urls import path, include, re_path
+from .views import *
+from .apiviews import *
+from rest_framework import routers
+from django_spaghetti.views import Plate
+from schema_graph.views import Schema
+from django.contrib.auth.decorators import login_required
+router = routers.SimpleRouter()
+router.register(r'sharix-users', SharixUserMVS)
+router.register(r'group', GroupMVS)
+
+urlpatterns = [
+    #path('auth/', LoginSharix.as_view(), name='auth'),
+    path('accounts/login/', LoginSharix.as_view(), name='authweb'),
+    path('', index, name='home'),
+    path('transactions/', transactions, name='trans'),
+    path('transactions/<int:trans_id>/', trans_id, name='transid'),
+    path('logout/', logout_view, name='logoutweb'),
+    path('balance/', balance, name='balance'),
+    path('test/', testPage, name='test-page'),
+    
+    #path('v1/auth/', include('djoser.urls')),
+    path('auth/', include('djoser.urls.authtoken'), name='auth'),
+    path('platform/api/', include(router.urls), name="sharix-api"),
+    path('senderphone/', PhoneSender.as_view()),
+    #schemas
+    path('schemav1/', login_required(Schema.as_view()), name='schemav1'),
+    path('schemav2/', login_required(Plate.as_view()),  name='schemav2'),
+    path('schemav3/', schema_v3, name='schema'),
+
+    re_path(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
+]

+ 133 - 0
SharixAdmin/views.py

@@ -0,0 +1,133 @@
+
+import json
+from django.shortcuts import render
+from django.http import HttpResponseRedirect, HttpResponse
+from django.contrib.auth.views import LoginView
+from django.urls import reverse_lazy, resolve, reverse
+from django.contrib.auth.decorators import login_required
+from .forms import *
+from SharixAdmin.models import *
+from django.contrib.auth import logout
+from django.db.models import Q
+from .tables import *
+from django import template
+# Create your views here.
+
+
+@login_required
+def index(request):
+
+    ph_num = str(request.user.phone_number)
+    convert_ph_num = f"+{ph_num[:1]} ({ph_num[1:4]}) {ph_num[4:7]}-{ph_num[7:9]}-{ph_num[9:11]}"
+    #print(convert_ph_num)
+    context = get_context(request, {
+        'title':'Главная/баланс',
+        'phone':convert_ph_num
+    })
+    
+    return render(request, 'SharixAdmin/main.html', context)
+
+@login_required
+def transactions(request):
+    
+    context = get_context(request, {
+        'title':'История платежей',
+        })
+        
+    return render(request, 'SharixAdmin/transactions.html', context)
+
+@login_required
+def trans_id(request, trans_id):
+    
+    
+    
+    context = get_context(request, {
+        'title':'Услуга'
+        })
+    return render(request, 'SharixAdmin/trans_carried.html', context)
+
+@login_required
+def balance(request):
+    context = get_context(request, {
+        'title':'Пополнить баланс'
+        })
+    if request.method == 'POST':
+        if float(request.POST['price']) > 0:
+            context = get_context(request, {
+                'title':'Пополнить баланс',
+                'msg':'Оплата прошла успешно ;)'
+                })
+            return render(request, "SharixAdmin/balance_success.html", context)
+        else:
+            context = get_context(request, {
+                'title':'Пополнить баланс',
+                'msg':'Оплата не прошла ;('
+                })
+            return render(request, "SharixAdmin/balance_success.html", context)
+
+    
+    return render(request, "SharixAdmin/balance.html", context)
+
+
+def logout_view(request):
+    logout(request)
+    return HttpResponseRedirect(reverse('authweb'))
+
+class LoginSharix(LoginView):
+    form_class = LoginUserForm
+    template_name = 'SharixAdmin/auth.html'
+
+    
+    def get_success_url(self):
+        print(self.request.GET['next'])
+        
+        return reverse_lazy('home')
+
+def testPage(request):
+    context = get_context(request, {
+        'title':'Страница в разработке'
+        })
+    return render(request, "SharixAdmin/test.html", context)
+
+
+
+
+menu = [
+    {'title':'Главная/баланс',          'link':'home', 'sel':'house'},
+    {'title':'Платежная информация',    'link':'test-page', 'sel':'credit-card'},
+    {'title':'История платежей',        'link':'trans','sel':'clock-history'},
+    {'title':'Курсы',                   'link':'course', 'sel':'education'},
+    {'title':'Личная информация',       'link':'test-page', 'sel':'person'},
+    {'title':'Управление сервисами',    'link':'test-page', 'sel':'hdd-network'},
+    {'title':'Мои связи',               'link':'test-page', 'sel':'people'},
+    {'title':'Сотрудничество',          'link':'test-page', 'sel':'sotrud'},
+    {'title':'Техподдержка',            'link':'test-page', 'sel':'gear'},
+    {'title':'Мои заявки',              'link':'tickets', 'sel':'tikets'},
+]
+
+def get_context(request, page_context) -> dict:
+    base_context = {
+        "title":page_context['title'],
+        'url_path':resolve(request.path_info).url_name,
+        'menu':menu
+    }
+    context = dict(list(base_context.items()) + list(page_context.items()))
+    return context
+
+
+
+
+#Shema views
+@login_required
+def schema_v3(request):
+    
+    return render(request, "SharixAdmin/schema.html")
+
+
+
+
+
+    
+
+
+

+ 3 - 0
bin/webuser.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+cd /path/to/project
+exec /path/to/project/env/bin/gunicorn core.wsgi:application -c core/conf_gunicorn.py

+ 0 - 0
core/__init__.py


+ 29 - 0
core/api_settings.py

@@ -0,0 +1,29 @@
+REST_FRAMEWORK = {
+    # Use Django's standard `django.contrib.auth` permissions,
+    # or allow read-only access for unauthenticated users.
+    'DEFAULT_RENDERER_CLASSES':[
+        'rest_framework.renderers.JSONRenderer',
+        'rest_framework.renderers.BrowsableAPIRenderer',
+    ],
+    'DEFAULT_PERMISSION_CLASSES': [
+        #'rest_framework.permissions.AllowAny',
+        #'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
+    ],
+    'DEFAULT_AUTHENTICATION_CLASSES': (
+        'rest_framework.authentication.TokenAuthentication',
+        'rest_framework.authentication.BasicAuthentication',
+        'rest_framework.authentication.SessionAuthentication',
+    ),
+}
+
+SPAGHETTI_SAUCE = {
+    'apps': ['auth', 'SharixAdmin', 
+             'tickets', 'admin', 
+             'flatpages', 'sessions', 'sites', 'metaservicesynced'],
+    'show_fields': False,
+    'show_proxy':True,
+}
+GRAPH_MODELS = {
+  'all_applications': True,
+  'group_models': True,
+}

+ 16 - 0
core/asgi.py

@@ -0,0 +1,16 @@
+"""
+ASGI config for core project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')
+
+application = get_asgi_application()

+ 9 - 0
core/conf_gunicorn.py

@@ -0,0 +1,9 @@
+#bind = "0.0.0.0:8001"
+import core.config as conf
+bind = conf.BIND
+workers = conf.WORKERS
+worker_class = "sync"
+threads = conf.THREADS
+timeout = 30
+max_requests = 1000
+capture_output = True

+ 26 - 0
core/config_template.py

@@ -0,0 +1,26 @@
+#Create file config.py with this setting or rename this file to config.py
+
+#BASE
+DEBUG=True
+SECRET_KEY='secret-key(absolutely any character)'
+ALLOWED_HOSTS = ['127.0.0.1']
+CSRF_TRUSTED_ORIGINS = []
+
+#DATABSE
+DB_NAME=None
+DB_USER=None
+DB_PASSWORD=None
+DB_HOST=None
+
+#GUNICORN
+BIND = "127.0.0.1:8000"
+WORKERS = 2
+THREADS = 4
+
+#STATIC
+from pathlib import Path
+import os
+BASE_DIR = Path(__file__).resolve().parent.parent
+STATIC_URL = '/static/'
+STATICFILES_DIRS = [BASE_DIR / "SharixAdmin/static/", BASE_DIR / "tickets/static/"]
+STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")

+ 24 - 0
core/db_settings.py

@@ -0,0 +1,24 @@
+from pathlib import Path
+import os
+import core.config as conf
+
+BASE_DIR = Path(__file__).resolve().parent.parent
+if conf.DB_NAME is None:
+    DATABASES = {
+        'default': {
+            'ENGINE': 'django.db.backends.sqlite3',
+            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+        }
+    }
+else:
+    DATABASES = {
+        'default': {
+            'ENGINE': 'django.db.backends.postgresql',
+            'NAME': conf.DB_NAME,
+            'USER': conf.DB_USER,
+            'PASSWORD': conf.DB_PASSWORD,
+            'HOST': conf.DB_HOST,
+            'PORT': '5432',
+        }
+    }
+

+ 178 - 0
core/jazzmin_settings.py

@@ -0,0 +1,178 @@
+JAZZMIN_SETTINGS = {
+    # title of the window (Will default to current_admin_site.site_title if absent or None)
+    "site_title": "ShariX Admin",
+
+    # Title on the login screen (19 chars max) (defaults to current_admin_site.site_header if absent or None)
+    "site_header": "ShariX Platform",
+
+    # Title on the brand (19 chars max) (defaults to current_admin_site.site_header if absent or None)
+    "site_brand": "ShariX Platform",
+
+    # Logo to use for your site, must be present in static files, used for brand on top left
+    "site_logo": "SharixAdmin/img/logo.svg",
+
+    # 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 ShariX Admin",
+
+    # 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": ["SharixAdmin.SharixUser"],
+
+    # Field name on user model that contains avatar ImageField/URLField/Charfield or a callable that receives the user
+    "user_avatar": "",
+
+    ############
+    # Top Menu #
+    ############
+
+    # Links to put along the top menu
+    "topmenu_links": [
+
+        # Url that gets reversed (Permissions can be added)
+        {"name": "Главная",  "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": "SharixAdmin.SharixUser"},
+
+        # App with dropdown menu to all its models pages (Permissions checked against models)
+        {"app": "tickets"},
+    ],
+
+    #############
+    # 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": "SharixAdmin.SharixUser"}
+    # ],
+
+    # #############
+    # # 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": {
+    #     "tickets": [{
+    #         "name": "Make Messages", 
+    #         "url": "make_messages", 
+    #         "icon": "fas fa-comments",
+    #         "permissions": ["tickets.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",
+        "SharixAdmin": "fas fa-users-cog",
+        "SharixAdmin.SharixUser": "fas fa-user",
+        "tickets.Task": "fas fa-check",
+        "tickets.TaskList": "fas fa-list",
+        "tickets.Comment": "fas fa-comment",
+        "tickets.Attachment": "fas fa-file",
+    },
+    # # 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": True,
+
+    ###############
+    # 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": {"SharixAdmin.SharixUser": "collapsible", "auth.group": "vertical_tabs"},
+    # Add a language dropdown into the admin
+    #"language_chooser": True,
+}
+JAZZMIN_UI_TWEAKS = {
+    "navbar_small_text": True,
+    "footer_small_text": True,
+    "body_small_text": False,
+    "brand_small_text": True,
+    "brand_colour": "navbar-light",
+    "accent": "accent-navy",
+    "navbar": "navbar-navy navbar-dark",
+    "no_navbar_border": False,
+    "navbar_fixed": False,
+    "layout_boxed": False,
+    "footer_fixed": False,
+    "sidebar_fixed": True,
+    "sidebar": "sidebar-light-navy",
+    "sidebar_nav_small_text": False,
+    "sidebar_disable_expand": False,
+    "sidebar_nav_child_indent": False,
+    "sidebar_nav_compact_style": True,
+    "sidebar_nav_legacy_style": False,
+    "sidebar_nav_flat_style": False,
+    "theme": "default",
+    "dark_mode_theme": None,
+    "button_classes": {
+        "primary": "btn-outline-primary",
+        "secondary": "btn-outline-secondary",
+        "info": "btn-outline-info",
+        "warning": "btn-outline-warning",
+        "danger": "btn-outline-danger",
+        "success": "btn-outline-success"
+    },
+    "actions_sticky_top": False
+}

+ 145 - 0
core/settings.py

@@ -0,0 +1,145 @@
+from pathlib import Path
+import os
+from core.db_settings import *
+from core.api_settings import *
+from core.tickets_mail_settings import *
+from core.jazzmin_settings import *
+import core.config as conf
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = conf.SECRET_KEY
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = conf.DEBUG
+
+ALLOWED_HOSTS = conf.ALLOWED_HOSTS
+CSRF_TRUSTED_ORIGINS = []
+
+# Application definition
+INSTALLED_APPS = [
+    'jazzmin',
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'SharixAdmin.apps.SharixadminConfig',
+    'tickets.apps.ticketsConfig',
+    'metaservicesynced.apps.MetaservicesyncedConfig',
+    'django_tables2',
+    "django.contrib.sites",
+    "django.contrib.flatpages",
+    "django.contrib.admindocs",
+    "django_extensions",
+    'rest_framework',
+    'rest_framework.authtoken',
+    'djoser',
+    'schema_graph',
+    'drf_yasg',
+    'django_spaghetti',
+]
+
+MIDDLEWARE = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+    'django.contrib.admindocs.middleware.XViewMiddleware',
+]
+
+ROOT_URLCONF = 'core.urls'
+
+#SESSION_EXPIRE_AT_BROWSER_CLOSE = True
+#SESSION_SECURITY_WARN_AFTER = 5
+#SESSION_SECURITY_EXPIRE_AFTER = 12
+INTERNAL_IPS = [
+    '127.0.0.1',
+]
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [
+            BASE_DIR / "templates"
+        ],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+
+                "django.template.context_processors.i18n",
+                "django.template.context_processors.media",
+                "django.template.context_processors.static",
+                "django.template.context_processors.tz",
+            ],
+            'libraries': {
+                'custom_tags':'tickets.template_tags.custom_tags'
+            }
+        },
+    },
+]
+
+
+WSGI_APPLICATION = 'core.wsgi.application'
+
+# Password validation
+# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+# Internationalization
+# https://docs.djangoproject.com/en/4.1/topics/i18n/
+
+LANGUAGE_CODE = 'ru'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_TZ = True
+
+SITE_ID = 1
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/4.1/howto/static-files/
+
+STATIC_URL = conf.STATIC_URL
+STATICFILES_DIRS = conf.STATICFILES_DIRS
+STATIC_ROOT = conf.STATIC_ROOT
+# Uploaded media
+MEDIA_ROOT = os.path.join(BASE_DIR, "media")
+MEDIA_URL = "/media/"
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+AUTH_USER_MODEL = 'SharixAdmin.SharixUser'
+
+
+
+
+
+

+ 31 - 0
core/tickets_mail_settings.py

@@ -0,0 +1,31 @@
+#LOGIN_REDIRECT_URL = "tickets:lists"
+#LOGIN_URL = "/accounts/login/"
+#LOGOUT_REDIRECT_URL = "/accounts/login/"
+# tickets-specific settings
+TICKETS_STAFF_ONLY = False
+TICKETS_DEFAULT_LIST_SLUG = 'tickets'
+TICKETS_DEFAULT_ASSIGNEE = None
+TICKETS_PUBLIC_SUBMIT_REDIRECT = '/'
+
+from tickets.mail.producers import imap_producer
+from tickets.mail.consumers import tracker_consumer
+from tickets.mail.delivery import smtp_backend, console_backend
+
+# email notifications configuration
+# each task list can get its own delivery method
+IS_SEND_EMAIL = False
+# TICKETS_MAIL_BACKENDS = {
+#     # mail-queue is the name of the task list, not the worker name
+#     "mail-queue": smtp_backend(
+#         host="*********",
+#         port=465,
+#         use_ssl=True,
+#         username="*********",
+#         password="*********",
+#         # used as the From field when sending notifications.
+#         # a username might be prepended later on
+#         from_address="*******",
+#         # additionnal headers
+#         headers={}
+#     ),
+# }

+ 22 - 0
core/urls.py

@@ -0,0 +1,22 @@
+from django.contrib import admin
+from django.conf import settings
+from django.urls import path, include
+from django.conf.urls.static import static
+
+
+
+urlpatterns = (
+    [
+    path('admin/doc/', include('django.contrib.admindocs.urls')),
+    path('admin/', admin.site.urls),
+    path('', include('SharixAdmin.urls')),
+    path('tickets/', include("tickets.urls"), name='tickets'),
+    
+
+] 
++ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
++ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+)
+
+if settings.DEBUG:
+    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

+ 16 - 0
core/wsgi.py

@@ -0,0 +1,16 @@
+"""
+WSGI config for core project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')
+
+application = get_wsgi_application()

+ 21 - 0
install_linux.sh

@@ -0,0 +1,21 @@
+#!/bin/sh
+
+git clone -b tickets_module http://git.sharix-app.org/ShariX_Open/sharix-open-tickets.git tickets
+
+git clone -b metasynced_module http://git.sharix-app.org/ShariX_Open/sharix-open-backend.git metaservicesynced
+
+python3 -m venv env
+
+source env/bin/activate
+
+pip install -r requirements.txt
+
+python3 manage.py makemigrations
+
+python3 manage.py migrate
+
+python3 manage.py collectstatic
+
+python3 manage.py createsuperuser
+
+deactivate

+ 5 - 0
install_win.bat

@@ -0,0 +1,5 @@
+@echo off
+git clone -b tickets_module http://git.sharix-app.org/ShariX_Open/sharix-open-tickets.git tickets
+git clone -b metasynced_module http://git.sharix-app.org/ShariX_Open/sharix-open-backend.git metaservicesynced
+python -m venv env
+.\env\Scripts\activate && pip install -r requirements.txt && python manage.py makemigrations && python manage.py migrate && python manage.py createsuperuser && python manage.py runserver

+ 22 - 0
manage.py

@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+    """Run administrative tasks."""
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError as exc:
+        raise ImportError(
+            "Couldn't import Django. Are you sure it's installed and "
+            "available on your PYTHONPATH environment variable? Did you "
+            "forget to activate a virtual environment?"
+        ) from exc
+    execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+    main()

+ 4 - 0
metaservicesynced/.gitignore

@@ -0,0 +1,4 @@
+# Python
+__pycache__/
+*.py[cod]
+*$py.class

+ 0 - 0
metaservicesynced/__init__.py


+ 3 - 0
metaservicesynced/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 6 - 0
metaservicesynced/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class MetaservicesyncedConfig(AppConfig):
+    default_auto_field = "django.db.models.BigAutoField"
+    name = "metaservicesynced"

+ 46 - 0
metaservicesynced/migrations/0001_initial.py

@@ -0,0 +1,46 @@
+# Generated by Django 4.1.3 on 2023-03-28 09:31
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ("tickets", "0001_initial"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="Documents",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("check_date", models.DateTimeField()),
+                ("check_level", models.IntegerField()),
+                ("expire_date", models.DateTimeField()),
+                ("id_metaservice", models.BigIntegerField()),
+                ("requirements", models.CharField(max_length=150)),
+                ("status", models.CharField(max_length=150)),
+                (
+                    "ticket_status",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.DO_NOTHING,
+                        to="tickets.task",
+                    ),
+                ),
+            ],
+            options={
+                "db_table": "documents",
+            },
+        ),
+    ]

+ 0 - 0
metaservicesynced/migrations/__init__.py


+ 96 - 0
metaservicesynced/models.py

@@ -0,0 +1,96 @@
+from django.db import models
+from tickets.models import Task
+from SharixAdmin.models import SharixUser
+# Create your models here.
+
+# Валя: Provider, ServiceType, Permissions
+# Виталий: Orders, Client, Relationship
+# Данила: Service, Company, Resource
+
+class Company(models.Model):
+    legal_name = models.CharField(max_length=150, help_text="настоящее имя юридического лица")
+    repr_id = models.ForeignKey(SharixUser, on_delete=models.DO_NOTHING, help_text="уникальный идентификатор представителя компании. Это обязательно пользователь-провайдер определенного типа. То есть нельзя назначить ответственного, который не может быть ответственным.")
+    inn = models.CharField(max_length=10, unique=True, help_text="ИНН компании")
+    kpp = models.CharField(max_length=9,  help_text="КПП компании")
+    ogrn = models.CharField(max_length=13, help_text="ОГРН компании")
+    bank_name = models.CharField(max_length=150, help_text="Название банка с расчетным счетом")
+    bik = models.CharField(max_length=9, help_text="БИК компании")
+    ks = models.CharField(max_length=50, help_text="Корреспондентский счёт (счёт, открываемый банковской организацией в подразделении самого банка)")
+    rs = models.CharField(max_length=50, help_text="Расчетный счет")
+    address = models.CharField(max_length=150, help_text="Юридический адрес")
+    requirements = models.CharField(max_length=150, help_text="код необходимого для того, чтобы ресурс мог стать активным")
+    status = models.CharField(max_length=150, help_text="статус обработки заявки в системе заявок")
+    ticket_status = models.ForeignKey(Task, on_delete=models.DO_NOTHING, help_text="id последнего актуального тикета, касающийся статуса. Если он меняет статус на закрытый - вызывается проверка, которая смотрит, нет ли другого открытого по пользователю.")
+    id_metaservice = models.BigIntegerField(null=True, help_text="уникальный идентификатор мета-сервиса, необходимый для синхронизации данных. Если при синхронизации возникает конфликт (несовместимость) с другим сервисом, предлагается или форсировать изменения везде (если возможно), либо is_global выставляется как false.")
+    is_global = models.CharField(max_length=1, help_text="доступны ли документы для хранения в глобальном сервисе/нужна синхронизация")
+    is_visible = models.CharField(max_length=1, help_text="доступна ли информация о наличии документов для планирования в цепочке с другими услугами в глобальном сервисе")
+    
+    class Meta:
+            db_table = "company"
+
+class Resource(models.Model):
+    """
+    Resource/Список ресурсов – автомобили/дома/объекты сервиса
+    """
+    type_id = models.CharField(max_length=10, help_text="идентификатор ресурса")
+    user_id = models.ForeignKey(SharixUser, on_delete=models.DO_NOTHING, help_text="уникальный идентификатор ответственного")
+    requirements = models.CharField(max_length=150, help_text="код необходимого для того, чтобы ресурс мог стать активным")
+    status = models.CharField(max_length=150, help_text="статус обработки заявки в системе заявок")
+    ticket_status = models.ForeignKey(Task, on_delete=models.DO_NOTHING, help_text="id последнего актуального тикета, касающийся статуса. Если он меняет статус на закрытый - вызывается проверка, которая смотрит, нет ли другого открытого по пользователю.")
+    id_metaservice = models.BigIntegerField(null=True, help_text="уникальный идентификатор мета-сервиса, необходимый для синхронизации данных. Если при синхронизации возникает конфликт (несовместимость) с другим сервисом, предлагается или форсировать изменения везде (если возможно), либо is_global выставляется как false.")
+    is_global = models.CharField(max_length=1, help_text="доступны ли документы для хранения в глобальном сервисе/нужна синхронизация")
+    is_visible = models.CharField(max_length=1, help_text="доступна ли информация о наличии документов для планирования в цепочке с другими услугами в глобальном сервисе")
+    
+    class Meta:
+        db_table = "resource"
+class Service(models.Model):
+    """
+    service - спецификация услуги каждого конкретного поставщика 
+    (например, в рамках сервиса многие могут предоставлять услуги перевозки, 
+    но конкретный шаблон с конкретным тарифом относится к отдельному перевозчику)
+    """
+
+    servicetype_id = models.ForeignKey("ServiceType", on_delete=models.DO_NOTHING, help_text="тип оказываемой услуги по классификатору услуг сервиса")
+    id_provider = models.ForeignKey("Provider",on_delete=models.DO_NOTHING, help_text="идентификатор поставщика услуг")
+    resource_id = models.ForeignKey(Resource, on_delete=models.DO_NOTHING, null=True ,help_text="ответственный за ресурс(не всегда)")
+    requirements = models.CharField(max_length=150, help_text="код необходимого для того, чтобы ресурс мог стать активным")
+    id_metaservice = models.BigIntegerField(null=True, help_text="уникальный идентификатор мета-сервиса, необходимый для синхронизации данных. Если при синхронизации возникает конфликт (несовместимость) с другим сервисом, предлагается или форсировать изменения везде (если возможно), либо is_global выставляется как false.")
+    price_alg = models.CharField(max_length=100, help_text="шаблон алгоритма расчета цены для оказываемой услуги")
+    price_km = models.DecimalField(max_digits=9, decimal_places=2, help_text="значение параметра стоимости 1км данного поставщика для данного шаблона услуги")
+    price_min =  models.DecimalField(max_digits=9, decimal_places=2, help_text="значение параметра стоимости 1мин данного поставщика для данного шаблона услуги")
+    price_amount =  models.DecimalField(max_digits=9, decimal_places=2, help_text="значение параметра стоимости 1 услуги данного поставщика для данного шаблона услуги")
+    service_status = models.CharField(max_length=150, help_text="статус спецификации типа услуги")
+    status = models.CharField(max_length=150, help_text="статус обработки заявки в системе заявок")
+    ticket_status = models.ForeignKey(Task, on_delete=models.DO_NOTHING, help_text="id последнего актуального тикета, касающийся статуса. Если он меняет статус на закрытый - вызывается проверка, которая смотрит, нет ли другого открытого по пользователю.")
+    is_global = models.CharField(max_length=1, help_text="доступны ли документы для хранения в глобальном сервисе/нужна синхронизация")
+    is_visible = models.CharField(max_length=1, help_text="доступна ли информация о наличии документов для планирования в цепочке с другими услугами в глобальном сервисе")
+    
+    class Meta:
+        db_table = "service"
+
+class Documents(models.Model):
+    """
+    Documents - это одна таблица со всеми документами.
+    Вообще в концепции предполагалось, что таких таблиц должно быть много под каждый тип для удобства поиска. 
+    То есть отдельно таблица с паспортами, отдельно с правами, отдельно с какими-нибудь разрешениями и так далее. 
+    Что пока непонятно - документов может быть много разных.
+    """
+
+    check_date = models.DateTimeField(help_text="timestamp проверки")
+    check_level = models.IntegerField(help_text="информация об уровне проверки. Документ может быть проверен как платформой, так и мета-сервисом, так и партнером мета-сервиса, а может быть и никем (просто загружен). Указывается, так как достоверность проверки разная. Документ, проверенный только на низком уровне, не принимается во внимание как имеющийся до прохождения более высокоуровневой проверки. Информацию об уровнях проверки можно посмотреть по словарю Requirements. В данной таблице хранится информация о наиболее высоком уровне проверки.")
+    expire_date = models.DateTimeField(null=True, help_text="срок окончания действия документа.")
+    id_metaservice = models.BigIntegerField(null=True, help_text="уникальный идентификатор мета-сервиса, необходимый для синхронизации данных. Если при синхронизации возникает конфликт (несовместимость) с другим сервисом, предлагается или форсировать изменения везде (если возможно), либо is_global выставляется как false.")
+    requirements = models.CharField(max_length=150)
+    status = models.CharField(max_length=150, help_text="активность на основе системы заявок")
+    ticket_status = models.ForeignKey(Task, on_delete=models.DO_NOTHING, help_text="id последнего актуального тикета, касающийся статуса. Если он меняет статус на закрытый - вызывается проверка, которая смотрит, нет ли другого открытого по пользователю.")
+    datalink = models.TextField(blank=True, help_text="адрес фактического размещения на физическом носителе, если информация настолько велика, что не может храниться внутри БД.")
+    doc_type = models.CharField(help_text="тип документа (паспорт/паспорт 1 страница и т д) в соответствии с классификатором типов документов (см описание в Requirements)")
+    user_id = models.ForeignKey(SharixUser, on_delete=models.DO_NOTHING, help_text="уникальный идентификатор пользователя (конкретного клиентского аккаунта) являющегося владельцем данного документа")
+    company_id = models.ForeignKey("Company", on_delete=models.DO_NOTHING, null=True, help_text="идентификатор компании, к которой относится документ, если таковая есть (может не быть)")
+    is_global = models.CharField(max_length=1, help_text="доступны ли документы для хранения в глобальном сервисе/нужна синхронизация")
+    is_visible = models.CharField(max_length=1, help_text="доступна ли информация о наличии документов для планирования в цепочке с другими услугами в глобальном сервисе")
+    checked_by = models.ForeignKey(SharixUser, on_delete=models.DO_NOTHING, null=True, help_text="userid проверившего")
+
+    class Meta:
+        db_table = "documents"
+

+ 3 - 0
metaservicesynced/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 3 - 0
metaservicesynced/views.py

@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.

+ 57 - 0
requirements.txt

@@ -0,0 +1,57 @@
+aniso8601==9.0.1
+asgiref==3.5.2
+attrs==22.2.0
+bleach==5.0.1
+certifi==2022.12.7
+cffi==1.15.1
+charset-normalizer==3.0.1
+coreapi==2.3.3
+coreschema==0.0.4
+cryptography==39.0.0
+defusedxml==0.7.1
+Django==4.1.3
+django-extensions==3.2.1
+django-jazzmin==2.6.0
+django-schema-graph==2.2.1
+django-spaghetti-and-meatballs==0.4.2
+django-tables2==2.4.1
+django-templated-mail==1.1.1
+djangorestframework==3.14.0
+djangorestframework-simplejwt==4.8.0
+djoser==2.1.0
+docutils==0.19
+drf-yasg==1.21.5
+graphene==3.2.1
+graphql-core==3.2.3
+graphql-relay==3.2.0
+gunicorn==20.1.0
+html2text==2020.1.16
+idna==3.4
+inflection==0.5.1
+itypes==1.2.0
+Jinja2==3.1.2
+MarkupSafe==2.1.2
+oauthlib==3.2.2
+packaging==23.0
+promise==2.3
+psycopg2-binary==2.9.5
+pycparser==2.21
+pydot==1.4.2
+PyJWT==2.6.0
+pyparsing==3.0.9
+python3-openid==3.2.0
+pytz==2022.7.1
+requests==2.28.2
+requests-oauthlib==1.3.1
+ruamel.yaml==0.17.21
+ruamel.yaml.clib==0.2.7
+six==1.16.0
+social-auth-app-django==4.0.0
+social-auth-core==4.3.0
+sqlparse==0.4.3
+text-unidecode==1.3
+tzdata==2022.6
+uritemplate==4.1.1
+urllib3==1.26.14
+webencodings==0.5.1
+xmpppy==0.7.1

+ 4 - 0
tickets/.gitignore

@@ -0,0 +1,4 @@
+# Python
+__pycache__/
+*.py[cod]
+*$py.class

+ 77 - 0
tickets/README.md

@@ -0,0 +1,77 @@
+# ShariX Open Tickets
+
+Ticketing system implemented as a Django application.
+
+## Installation
+
+1) Download or clone repository
+
+```bash
+git clone -b tickets_module http://git.sharix-app.org/ShariX_Open/sharix-open-tickets.git tickets
+```
+
+2) Install required dependencies in project settings
+
+```python
+INSTALLED_APPS = [
+    ...
+    'tickets.apps.ticketsConfig',
+    'django.contrib.sites',
+    ...
+]
+```
+
+3) Delete the migration files in the **migrations** folder (everything except __init__.py)
+
+4) Install required libraries (don't forget to install and activate virtual space)
+
+```powershell
+pip install -r tickets/requirements.txt
+```
+
+Start test the server:
+
+```bash
+python manage.py runserver 8000
+```
+
+If the port has not been selected, it is 8000 by default. If selected port is busy, use another one (for example, try increasing port number by 1 until the server starts). A link to the website should appear in the terminal.
+
+## Settings
+
+Optional configuration params, which can be added to your project settings:
+
+```python
+# Restrict access to ALL tickets lists/views to `is_staff` users.
+# If False or unset, all users can see all views (but more granular permissions are still enforced
+# within views, such as requiring staff for adding and deleting lists).
+TICKETS_STAFF_ONLY = True
+
+# If you use the "public" ticket filing option, to whom should these tickets be assigned?
+# Must be a valid username in your system. If unset, unassigned tickets go to "Anyone."
+TICKETS_DEFAULT_ASSIGNEE = 'johndoe'
+
+# If you use the "public" ticket filing option, to which list should these tickets be saved?
+# Defaults to first list found, which is probably not what you want!
+TICKETS_DEFAULT_LIST_SLUG = 'tickets'
+
+# If you use the "public" ticket filing option, to which *named URL* should the user be
+# redirected after submitting? (since they can't see the rest of the ticket system).
+# Defaults to "/"
+TICKETS_PUBLIC_SUBMIT_REDIRECT = 'dashboard'
+
+# Enable or disable file attachments on Tasks
+# Optionally limit list of allowed filetypes
+TICKETS_ALLOW_FILE_ATTACHMENTS = True
+TICKETS_ALLOWED_FILE_ATTACHMENTS = [".jpg", ".gif", ".csv", ".pdf", ".zip"]
+TICKETS_MAXIMUM_ATTACHMENT_SIZE = 5000000  # In bytes
+
+# Additional classes the comment body should hold.
+# Adding "text-monospace" makes comment monospace
+TICKETS_COMMENT_CLASSES = []
+
+# The following two settings are relevant only if you want TICKETS to track a support mailbox -
+# see Mail Tracking below.
+TICKETS_MAIL_BACKENDS
+TICKETS_MAIL_TRACKERS
+```

+ 0 - 0
tickets/__init__.py


+ 48 - 0
tickets/admin.py

@@ -0,0 +1,48 @@
+from django.contrib import admin
+from tickets.models import Attachment, Comment, Task, TaskList, TicketType
+from tickets.admin_utils import *
+from django.contrib.auth.models import Group
+from django.contrib.auth.admin import GroupAdmin
+
+
+@admin.register(Task)
+class TaskAdmin(admin.ModelAdmin):
+    list_display = ("title", "task_list", "status", "priority", "due_date")
+    list_filter = ("task_list",)
+    ordering = ("priority",)
+    search_fields = ("title",)
+
+@admin.register(TaskList)
+class TaskListAdmin(admin.ModelAdmin):
+    actions = [add_default_tickets_list_admin]
+
+@admin.register(TicketType)
+class TicketTypeAdmin(admin.ModelAdmin):
+    actions = [add_default_ticketstype_admin]
+
+admin.site.unregister(Group)
+@admin.register(Group)
+class GroupsAdmin(admin.ModelAdmin):
+    actions = [add_default_group_admin]
+    search_fields = ("name",)
+    ordering = ("name",)
+    filter_horizontal = ("permissions",)
+
+    def formfield_for_manytomany(self, db_field, request=None, **kwargs):
+        if db_field.name == "permissions":
+            qs = kwargs.get("queryset", db_field.remote_field.model.objects)
+            kwargs["queryset"] = qs.select_related("content_type")
+        return super().formfield_for_manytomany(db_field, request=request, **kwargs)
+
+class CommentAdmin(admin.ModelAdmin):
+    list_display = ("author", "date", "snippet")
+
+
+class AttachmentAdmin(admin.ModelAdmin):
+    list_display = ("task", "added_by", "timestamp", "file")
+    autocomplete_fields = ["added_by", "task"]
+
+
+admin.site.register(Comment, CommentAdmin)
+admin.site.add_action(export_to_csv)
+admin.site.register(Attachment)

+ 77 - 0
tickets/admin_utils.py

@@ -0,0 +1,77 @@
+from tickets.models import *
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.auth.models import Group, Permission
+from django.http import HttpResponse
+import csv
+
+def add_perm(group, permissions, contenttype):
+    tct = ContentType.objects.get_for_model(contenttype)
+    for i in permissions:
+        group.permissions.add(Permission.objects.get(codename=i, content_type=tct))
+
+def export_to_csv(modeladmin, request, queryset):
+    meta = modeladmin.model._meta
+    field_names = [field.name for field in meta.fields]
+    #field_show_names = [field.verbose_name for field in meta.fields]
+    response = HttpResponse(content_type='text/csv')
+    response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta)
+    writer = csv.writer(response)
+
+    writer.writerow(field_names)
+    #writer.writerow(field_show_names)
+    for obj in queryset:
+        row = writer.writerow([getattr(obj, field) for field in field_names])
+
+    return response
+
+def add_default_ticketstype():
+    if not TicketType.objects.filter(name="SERVICE_REQUEST"):
+        TicketType.objects.create(name="SERVICE_REQUEST", life_cycle="210-211-251,211-212-220-238-249,212-221-229-238-249,221-222-238-249,220-211-238-249,229-211-251,222-231-238-249,231-241-238-249,238-231-239-211-212-221-220-222-249,239-231-239-211-212-221-220")
+        TicketType.objects.create(name="ST_REQUEST", life_cycle="111-121-149-159,110-121-149-159,121-131-149-159,131-141-149,141-151-110,149-151-110,159,151")
+        TicketType.objects.create(name="NEG_REQUEST", life_cycle="420-421-459,421,459")
+        TicketType.objects.create(name="ACCESS_REQUEST", life_cycle="320-321-359,321,359")
+
+def add_default_group():
+    if not Group.objects.filter(name="meta-user"):
+        #Meta user
+        group = Group.objects.create(name="meta-user")
+        add_perm(group, ['add_task', 'change_task', 'view_task'], Task)
+        #Platform admin
+        group = Group.objects.create(name="platform-admin")
+        add_perm(group, ['add_attachment', 'view_attachment'], Attachment)
+        add_perm(group, [ 'add_comment', 'view_comment'], Comment)
+        add_perm(group, [ 'add_task', 'change_task', 'view_task'], Task)
+        group = Group.objects.create(name="platform-supervisor")
+        add_perm(group, ['add_attachment', 'view_attachment'], Attachment)
+        add_perm(group, [ 'add_comment', 'view_comment'], Comment)
+        add_perm(group, [ 'add_task', 'change_task', 'view_task'], Task)
+        group = Group.objects.create(name="platform-support")
+        add_perm(group, [ 'add_comment', 'view_comment'], Comment)
+        add_perm(group, [ 'add_task', 'change_task', 'view_task'], Task)
+        group = Group.objects.create(name="platform-techsupport")
+        add_perm(group, ['add_attachment', 'view_attachment'], Attachment)
+        add_perm(group, [ 'add_comment', 'view_comment'], Comment)
+        add_perm(group, [ 'add_task', 'change_task', 'view_task'], Task)
+
+def add_default_tickets_list(request):
+    if not TaskList.objects.filter(name="Заявки клиентов"):
+        if not Group.objects.filter(name="TestGroup"):
+            group = Group.objects.create(name="TestGroup")
+            group.user_set.add(request.user)
+        else:
+            group = Group.objects.get(pk=1)
+        TaskList.objects.create(name="Заявки клиентов", slug="customer-applications", group=group)
+
+def add_default_ticketstype_admin(modeladmin, request, queryset):
+    add_default_ticketstype()
+
+def add_default_group_admin(modeladmin, request, queryset):
+    add_default_group()
+
+def add_default_tickets_list_admin(modeladmin, request, queryset):
+    add_default_tickets_list(request)
+
+add_default_tickets_list_admin.short_description = "Создать значения по умолчанию"
+add_default_ticketstype_admin.short_description = "Создать значения по умолчанию"
+add_default_group_admin.short_description = "Создать значения по умолчанию"
+export_to_csv.short_description = "Экспортировать в CSV"

+ 30 - 0
tickets/apiviews.py

@@ -0,0 +1,30 @@
+from tickets.serializer import *
+from rest_framework import viewsets, permissions, exceptions
+from rest_framework.authentication import TokenAuthentication
+from rest_framework.decorators import action
+from tickets.models import *
+
+class TaskMVS(viewsets.ModelViewSet):
+    queryset = Task.objects.all()
+    serializer_class = TaskSerializer
+    permission_classes = [permissions.IsAuthenticated]
+
+class TaskListMVS(viewsets.ModelViewSet):
+    queryset = TaskList.objects.all()
+    serializer_class = TaskListSerializer
+    permission_classes = [permissions.IsAuthenticated]
+
+class TicketTypeMVS(viewsets.ModelViewSet):
+    queryset = TicketType.objects.all()
+    serializer_class = TicketTypeSerializer
+    permission_classes = [permissions.IsAuthenticated]
+
+class CommentMVS(viewsets.ModelViewSet):
+    queryset = Comment.objects.all()
+    serializer_class = CommentSerializer
+    permission_classes = [permissions.IsAuthenticated]
+
+class AttachmentMVS(viewsets.ModelViewSet):
+    queryset = Attachment.objects.all()
+    serializer_class = AttachmentSerializer
+    permission_classes = [permissions.IsAuthenticated]

+ 7 - 0
tickets/apps.py

@@ -0,0 +1,7 @@
+from django.apps import AppConfig
+
+
+class ticketsConfig(AppConfig):
+    default_auto_field = "django.db.models.BigAutoField"
+    name = "tickets"
+    verbose_name = "TICKETS"

+ 22 - 0
tickets/defaults.py

@@ -0,0 +1,22 @@
+from django.conf import settings
+
+hash = {
+    "TICKETS_ALLOW_FILE_ATTACHMENTS": True,
+    "TICKETS_COMMENT_CLASSES": [],
+    "TICKETS_DEFAULT_ASSIGNEE": None,
+    "TICKETS_LIMIT_FILE_ATTACHMENTS": [".jpg", ".gif", ".png", ".csv", ".pdf", ".zip"],
+    "TICKETS_MAXIMUM_ATTACHMENT_SIZE": 5000000,
+    "TICKETS_PUBLIC_SUBMIT_REDIRECT": "/",
+    "TICKETS_STAFF_ONLY": True,
+}
+
+
+def defaults(key: str):
+    """Try to get a setting from project settings.
+    If empty or doesn't exist, fall back to a value from defaults hash."""
+
+    if hasattr(settings, key):
+        val = getattr(settings, key)
+    else:
+        val = hash.get(key)
+    return val

+ 64 - 0
tickets/forms.py

@@ -0,0 +1,64 @@
+from django import forms
+from django.contrib.auth.models import Group
+from django.forms import ModelForm
+from tickets.models import Task, TaskList, TicketType
+from django.conf import settings
+from SharixAdmin.models import SharixUser
+
+
+class ListCreateForm(ModelForm):
+    def __init__(self, user, *args, **kwargs):
+        super(ListCreateForm, self).__init__(*args, **kwargs)
+        self.fields["group"].queryset = Group.objects.filter(user=user)
+        self.fields["group"].widget.attrs = {
+            "id": "id_group",
+            "class": "custom-select mb-3",
+            "name": "group",
+        }
+        self.fields["name"].widget.attrs = {
+            "id": "id_name",
+            "class": "form-control",
+            "name": "group",
+        }
+
+    class Meta:
+        model = TaskList
+        exclude = ["slug"]
+
+
+class TicketForm(ModelForm):
+    def __init__(self, user, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        task_list = kwargs.get("initial").get("task_list")
+        members = task_list.group.user_set.all()
+        #print(user)
+        #print(task_list.group)
+        #members = SharixUser.objects.filter(groups__name=task_list.group)
+        #print(members)
+        self.fields["assigned_to"].queryset = members
+        self.fields["assigned_to"].label_from_instance = lambda obj: "%s (%s)" % (
+            obj.get_full_name(),
+            obj.username,
+        )
+        self.fields["assigned_to"].widget.attrs = {"class": "custom-select"}
+        self.fields["type"].widget.attrs = {"class": "custom-select"}
+        self.fields["title"].widget.attrs = {"class": "form-control"}
+        self.fields["note"].widget.attrs = {"class": "form-control"}
+        
+        if kwargs.get("initial").get("type_disabled") == True:
+            self.fields["type"].disabled = True
+
+    due_date = forms.DateField(widget=forms.DateInput(attrs={"type": "date", "class": "form-control"}))
+
+    def clean_created_by(self):
+        """Keep the existing created_by regardless of anything coming from the submitted form.
+        If creating a new task, then created_by will be None, but we set it before saving."""
+        return self.instance.created_by
+
+    class Meta:
+        model = Task
+        fields = ["type", "title", "note", "due_date", "assigned_to", "created_by"]
+
+
+class SearchForm(forms.Form):
+    q = forms.CharField(widget=forms.widgets.TextInput(attrs={"size": 35}))

+ 0 - 0
tickets/mail/__init__.py


+ 9 - 0
tickets/mail/consumers/__init__.py

@@ -0,0 +1,9 @@
+def tracker_consumer(**kwargs):
+    def tracker_factory(producer):
+        # the import needs to be delayed until call to enable
+        # using the wrapper in the django settings
+        from .tracker import tracker_consumer
+
+        return tracker_consumer(producer, **kwargs)
+
+    return tracker_factory

+ 171 - 0
tickets/mail/consumers/tracker.py

@@ -0,0 +1,171 @@
+import re
+import logging
+
+from email.charset import Charset as EMailCharset
+from django.db import transaction
+from django.db.models import Count
+from django.contrib.auth import get_user_model
+from django.conf import settings
+from html2text import html2text
+from email.utils import parseaddr
+from tickets.models import Comment, Task, TaskList
+
+logger = logging.getLogger(__name__)
+
+
+def part_decode(message):
+    charset = ("ascii", "ignore")
+    email_charset = message.get_content_charset()
+    if email_charset:
+        charset = (EMailCharset(email_charset).input_charset,)
+
+    body = message.get_payload(decode=True)
+    return body.decode(*charset)
+
+
+def message_find_mime(message, mime_type):
+    for submessage in message.walk():
+        if submessage.get_content_type() == mime_type:
+            return submessage
+    return None
+
+
+def message_text(message):
+    text_part = message_find_mime(message, "text/plain")
+    if text_part is not None:
+        return part_decode(text_part)
+
+    html_part = message_find_mime(message, "text/html")
+    if html_part is not None:
+        return html2text(part_decode(html_part))
+
+    # tickets: find something smart to do when no text if found
+    return ""
+
+
+def format_task_title(format_string, message):
+    return format_string.format(subject=message["subject"], author=message["from"])
+
+
+DJANGO_TICKETS_THREAD = re.compile(r"<thread-(\d+)@django-tickets>")
+
+
+def parse_references(task_list, references):
+    related_messages = []
+    answer_thread = None
+    for related_message in references.split():
+        logger.info("checking reference: %r", related_message)
+        match = re.match(DJANGO_TICKETS_THREAD, related_message)
+        if match is None:
+            related_messages.append(related_message)
+            continue
+
+        thread_id = int(match.group(1))
+        new_answer_thread = Task.objects.filter(task_list=task_list, pk=thread_id).first()
+        if new_answer_thread is not None:
+            answer_thread = new_answer_thread
+
+    if answer_thread is None:
+        logger.info("no answer thread found in references")
+    else:
+        logger.info("found an answer thread: %s", str(answer_thread))
+    return related_messages, answer_thread
+
+
+def insert_message(task_list, message, priority, task_title_format):
+    if "message-id" not in message:
+        logger.warning("missing message id, ignoring message")
+        return
+
+    if "from" not in message:
+        logger.warning('missing "From" header, ignoring message')
+        return
+
+    if "subject" not in message:
+        logger.warning('missing "Subject" header, ignoring message')
+        return
+
+    logger.info(
+        "received message:\t"
+        f"[Subject: {message['subject']}]\t"
+        f"[Message-ID: {message['message-id']}]\t"
+        f"[References: {message['references']}]\t"
+        f"[To: {message['to']}]\t"
+        f"[From: {message['from']}]"
+    )
+
+    # Due to limitations in MySQL wrt unique_together and TextField (grrr),
+    # we must use a CharField rather than TextField for message_id.
+    # In the unlikeley event that we get a VERY long inbound
+    # message_id, truncate it to the max_length of a MySQL CharField.
+    original_message_id = message["message-id"]
+    message_id = (
+        (original_message_id[:252] + "...")
+        if len(original_message_id) > 255
+        else original_message_id
+    )
+    message_from = message["from"]
+    text = message_text(message)
+
+    related_messages, answer_thread = parse_references(task_list, message.get("references", ""))
+
+    # find the most relevant task to add a comment on.
+    # among tasks in the selected task list, find the task having the
+    # most email comments the current message references
+    best_task = (
+        Task.objects.filter(task_list=task_list, comment__email_message_id__in=related_messages)
+        .annotate(num_comments=Count("comment"))
+        .order_by("-num_comments")
+        .only("id")
+        .first()
+    )
+
+    # if no related comment is found but a thread message-id
+    # (generated by django-tickets) could be found, use it
+    if best_task is None and answer_thread is not None:
+        best_task = answer_thread
+
+    with transaction.atomic():
+        if best_task is None:
+            best_task = Task.objects.create(
+                priority=priority,
+                title=format_task_title(task_title_format, message),
+                task_list=task_list,
+                created_by=match_user(message_from),
+            )
+        logger.info("using task: %r", best_task)
+
+        comment, comment_created = Comment.objects.get_or_create(
+            task=best_task,
+            email_message_id=message_id,
+            defaults={"email_from": message_from, "body": text},
+            author=match_user(message_from), # tickets: Write test for this
+        )
+        logger.info("created comment: %r", comment)
+
+
+def tracker_consumer(
+    producer, group=None, task_list_slug=None, priority=1, task_title_format="[MAIL] {subject}"
+):
+    task_list = TaskList.objects.get(group__name=group, slug=task_list_slug)
+    for message in producer:
+        try:
+            insert_message(task_list, message, priority, task_title_format)
+        except Exception:
+            # ignore exceptions during insertion, in order to avoid
+            logger.exception("got exception while inserting message")
+
+
+def match_user(email):
+    """ This function takes an email and checks for a registered user."""
+
+    if not settings.TICKETS_MAIL_USER_MAPPER:
+        user = None
+    else:
+        try:
+            # Find the first user that matches the email
+            user = get_user_model().objects.get(email=parseaddr(email)[1])
+        except get_user_model().DoesNotExist:
+            user = None
+
+    return user

+ 27 - 0
tickets/mail/delivery.py

@@ -0,0 +1,27 @@
+import importlib
+
+
+def _declare_backend(backend_path):
+    backend_path = backend_path.split(".")
+    backend_module_name = ".".join(backend_path[:-1])
+    class_name = backend_path[-1]
+
+    def backend(*args, headers={}, from_address=None, **kwargs):
+        def _backend():
+            backend_module = importlib.import_module(backend_module_name)
+            backend = getattr(backend_module, class_name)
+            return backend(*args, **kwargs)
+
+        if from_address is None:
+            raise ValueError("missing from_address")
+
+        _backend.from_address = from_address
+        _backend.headers = headers
+        return _backend
+
+    return backend
+
+
+smtp_backend = _declare_backend("django.core.mail.backends.smtp.EmailBackend")
+console_backend = _declare_backend("django.core.mail.backends.console.EmailBackend")
+locmem_backend = _declare_backend("django.core.mail.backends.locmem.EmailBackend")

+ 9 - 0
tickets/mail/producers/__init__.py

@@ -0,0 +1,9 @@
+def imap_producer(**kwargs):
+    def imap_producer_factory():
+        # the import needs to be delayed until call to enable
+        # using the wrapper in the django settings
+        from .imap import imap_producer
+
+        return imap_producer(**kwargs)
+
+    return imap_producer_factory

+ 98 - 0
tickets/mail/producers/imap.py

@@ -0,0 +1,98 @@
+import email
+import email.parser
+import imaplib
+import logging
+import time
+
+from email.policy import default
+from contextlib import contextmanager
+
+logger = logging.getLogger(__name__)
+
+
+def imap_check(command_tuple):
+    status, ids = command_tuple
+    assert status == "OK", ids
+
+
+@contextmanager
+def imap_connect(host, port, username, password):
+    conn = imaplib.IMAP4_SSL(host=host, port=port)
+    conn.login(username, password)
+    imap_check(conn.list())
+    try:
+        yield conn
+    finally:
+        conn.close()
+
+
+def parse_message(message):
+    for response_part in message:
+        if not isinstance(response_part, tuple):
+            continue
+
+        message_metadata, message_content = response_part
+        email_parser = email.parser.BytesFeedParser(policy=default)
+        email_parser.feed(message_content)
+        return email_parser.close()
+
+
+def search_message(conn, *filters):
+    status, message_ids = conn.search(None, *filters)
+    for message_id in message_ids[0].split():
+        status, message = conn.fetch(message_id, "(RFC822)")
+        yield message_id, parse_message(message)
+
+
+def imap_producer(
+    process_all=False,
+    preserve=False,
+    host=None,
+    port=993,
+    username=None,
+    password=None,
+    nap_duration=1,
+    input_folder="INBOX",
+):
+    logger.debug("starting IMAP worker")
+    imap_filter = "(ALL)" if process_all else "(UNSEEN)"
+
+    def process_batch():
+        logger.debug("starting to process batch")
+        # reconnect each time to avoid repeated failures due to a lost connection
+        with imap_connect(host, port, username, password) as conn:
+            # select the requested folder
+            imap_check(conn.select(input_folder, readonly=False))
+
+            try:
+                for message_uid, message in search_message(conn, imap_filter):
+                    logger.info(f"received message {message_uid}")
+                    try:
+                        yield message
+                    except Exception:
+                        logger.exception(f"something went wrong while processing {message_uid}")
+                        raise
+
+                    if not preserve:
+                        # tag the message for deletion
+                        conn.store(message_uid, "+FLAGS", "\\Deleted")
+                else:
+                    logger.debug("did not receive any message")
+            finally:
+                if not preserve:
+                    # flush deleted messages
+                    conn.expunge()
+
+    while True:
+        try:
+            yield from process_batch()
+        except (GeneratorExit, KeyboardInterrupt):
+            # the generator was closed, due to the consumer
+            # breaking out of the loop, or an exception occuring
+            raise
+        except Exception:
+            logger.exception("mail fetching went wrong, retrying")
+
+        # sleep to avoid using too much resources
+        # tickets: get notified when a new message arrives
+        time.sleep(nap_duration)

+ 0 - 0
tickets/management/__init__.py


+ 0 - 0
tickets/management/commands/__init__.py


+ 41 - 0
tickets/management/commands/mail_worker.py

@@ -0,0 +1,41 @@
+import logging
+import socket
+import sys
+
+from django.core.management.base import BaseCommand
+from django.conf import settings
+
+logger = logging.getLogger(__name__)
+
+
+DEFAULT_IMAP_TIMEOUT = 20
+
+
+class Command(BaseCommand):
+    help = "Starts a mail worker"
+
+    def add_arguments(self, parser):
+        parser.add_argument("--imap_timeout", type=int, default=30)
+        parser.add_argument("worker_name")
+
+    def handle(self, *args, **options):
+        if not hasattr(settings, "TICKETS_MAIL_TRACKERS"):
+            logger.error("missing TICKETS_MAIL_TRACKERS setting")
+            sys.exit(1)
+
+        worker_name = options["worker_name"]
+        tracker = settings.TICKETS_MAIL_TRACKERS.get(worker_name, None)
+        if tracker is None:
+            logger.error("couldn't find configuration for %r in TICKETS_MAIL_TRACKERS", worker_name)
+            sys.exit(1)
+
+        # set the default socket timeout (imaplib doesn't enable configuring it)
+        timeout = options["imap_timeout"]
+        if timeout:
+            socket.setdefaulttimeout(timeout)
+
+        # run the mail polling loop
+        producer = tracker["producer"]
+        consumer = tracker["consumer"]
+
+        consumer(producer())

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.