NINI Franchise — Django Backend
Har bir faylni "Nusxa olish" tugmasini bosib nusxalang, keyin kompyuteringizda tegishli papkaga joylashtiring.
Papka strukturasi (avval shu papkalarni yarating)
nini-backend/
├── manage.py
├── requirements.txt
├── .env
├── nini_project/
│ ├── __init__.py (bo'sh fayl)
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── core/
│ ├── __init__.py (bo'sh fayl)
│ ├── models.py
│ ├── views.py
│ ├── urls.py
│ └── admin.py
├── static/
│ └── js/
│ └── backend-calls.js
└── templates/
├── AdminIndex.html (asl faylingiz + o'zgartirish)
└── TvIndex.html (asl faylingiz + o'zgartirish)
Django>=4.2,<5.0
djangorestframework>=3.14
django-cors-headers>=4.3
psycopg2-binary>=2.9
gunicorn>=21.2
python-dotenv>=1.0
whitenoise>=6.5
DEBUG=True
SECRET_KEY=nini-franchise-maxfiy-kalit-bu-yerni-ozgartiring-2024
DATABASE_URL=postgres://nini_user:parol123@localhost:5432/nini_db
ALLOWED_HOSTS=localhost,127.0.0.1
#!/usr/bin/env python
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nini_project.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError("Django topilmadi. pip install -r requirements.txt buyrug'ini bajaring.") from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main() import os
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.getenv('SECRET_KEY', 'fallback-secret-key-for-dev')
DEBUG = os.getenv('DEBUG', 'True').lower() in ('true', '1', 'yes')
ALLOWED_HOSTS = [h.strip() for h in os.getenv('ALLOWED_HOSTS', 'localhost,127.0.0.1').split(',')]
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'corsheaders',
'core',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'corsheaders.middleware.CorsMiddleware',
'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',
]
ROOT_URLCONF = 'nini_project.urls'
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',
],
},
},
]
WSGI_APPLICATION = 'nini_project.wsgi.application'
# --- DATABASE ---
db_url = os.getenv('DATABASE_URL', '')
if db_url.startswith('postgres'):
import re
m = re.match(r'postgres(?:ql)?://([^:]+):([^@]+)@([^:]+):(\d+)/(.+)', db_url)
if m:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'USER': m.group(1),
'PASSWORD': m.group(2),
'HOST': m.group(3),
'PORT': m.group(4),
'NAME': m.group(5),
}
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
AUTH_PASSWORD_VALIDATORS = []
LANGUAGE_CODE = 'uz'
TIME_ZONE = 'Asia/Tashkent'
USE_I18N = True
USE_TZ = True
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [BASE_DIR / 'static']
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
CORS_ALLOW_ALL_ORIGINS = DEBUG
CORS_ALLOWED_ORIGINS = [
'http://localhost:3000',
'http://127.0.0.1:3000',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],
}
SESSION_COOKIE_AGE = 6 * 60 * 60
SESSION_ENGINE = 'django.contrib.sessions.backends.db' from django.contrib import admin
from django.urls import path, include
from core import views
urlpatterns = [
path('admin/', admin.site.urls),
# Sahifalar (HTML)
path('', views.admin_page, name='admin_page'),
path('tv/', views.tv_page, name='tv_page'),
# API endpointlar
path('api/', include('core.urls')),
] import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nini_project.settings')
application = get_wsgi_application() from django.db import models
# ============================================================
# 1. FILIAL — har bir filial (markaz)
# ============================================================
class Filial(models.Model):
STATUS_CHOICES = [('Faol', 'Faol'), ('Arxiv', 'Arxiv')]
nomi = models.CharField(max_length=200, unique=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='Faol')
class Meta:
verbose_name_plural = 'Filiallar'
ordering = ['nomi']
def __str__(self):
return self.nomi
# ============================================================
# 2. SOZLAMALAR — muassasa nomi va rahbar (bitta qator)
# ============================================================
class Sozlamalar(models.Model):
nomi = models.CharField(max_length=300, blank=True, default='')
rahbar = models.CharField(max_length=300, blank=True, default='')
class Meta:
verbose_name_plural = 'Sozlamalar'
def __str__(self):
return self.nomi or 'Sozlamalar'
# ============================================================
# 3. LAVOZIM — xodim lavozimlari
# ============================================================
class Lavozim(models.Model):
nomi = models.CharField(max_length=200)
filial = models.CharField(max_length=200, blank=True, default='')
class Meta:
verbose_name_plural = 'Lavozimlar'
def __str__(self):
return self.nomi
# ============================================================
# 4. FOYDALANUVCHI — tarbiyachilar (xodimlar)
# ============================================================
class Foydalanuvchi(models.Model):
xodim_id = models.CharField(max_length=50, unique=True)
tarbiyachi = models.CharField(max_length=300)
guruh = models.CharField(max_length=200)
filial = models.CharField(max_length=200, blank=True, default='')
lavozim = models.CharField(max_length=200, blank=True, default='')
class Meta:
verbose_name_plural = 'Foydalanuvchilar'
def __str__(self):
return f"{self.tarbiyachi} ({self.guruh})"
# ============================================================
# 5. TARBIYALANUVCHI — bolalar (o'quvchilar)
# ============================================================
class Tarbiyalanuvchi(models.Model):
STATUS_CHOICES = [('Faol', 'Faol'), ('Arxiv', 'Arxiv')]
fio = models.CharField(max_length=300)
filial = models.CharField(max_length=200, blank=True, default='')
telefon = models.CharField(max_length=100, blank=True, default='')
guruh = models.CharField(max_length=200)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='Faol')
ketgan_sana = models.CharField(max_length=20, blank=True, default='')
sabab = models.CharField(max_length=500, blank=True, default='')
class Meta:
verbose_name_plural = 'Tarbiyalanuvchilar'
def __str__(self):
return f"{self.fio} ({self.guruh})"
# ============================================================
# 6. FAN — guruhga biriktirilgan fanlar
# ============================================================
class Fan(models.Model):
tarbiyachi = models.CharField(max_length=300)
fan = models.CharField(max_length=200)
guruh = models.CharField(max_length=200, blank=True, default='')
filial = models.CharField(max_length=200, blank=True, default='')
class Meta:
verbose_name_plural = 'Fanlar'
def __str__(self):
return f"{self.fan} — {self.tarbiyachi}"
# ============================================================
# 7. TEST SOZLAMALARI — fan uchun ball nisbati
# ============================================================
class TestSozlama(models.Model):
fan = models.CharField(max_length=200, unique=True)
nisbat = models.FloatField(default=1)
class Meta:
verbose_name_plural = 'Test sozlamalari'
def __str__(self):
return f"{self.fan} (x{self.nisbat})"
# ============================================================
# 8. JURNAL — kunlik davomat va o'zlashtirish
# ============================================================
class Jurnal(models.Model):
sana = models.CharField(max_length=20)
guruh = models.CharField(max_length=200)
fan = models.CharField(max_length=200)
bola_fio = models.CharField(max_length=300)
davomat = models.CharField(max_length=50)
ozlashtirish = models.CharField(max_length=100, blank=True, default='')
qayta_aloqa = models.CharField(max_length=100, blank=True, default='')
test_ball = models.FloatField(null=True, blank=True)
class Meta:
verbose_name_plural = 'Jurnal'
indexes = [
models.Index(fields=['guruh', 'sana']),
models.Index(fields=['bola_fio', 'guruh']),
]
def __str__(self):
return f"{self.sana} | {self.bola_fio} | {self.fan}"
# ============================================================
# 9. DASTUR — o'quv rejasi (mavzular)
# ============================================================
class Dastur(models.Model):
tarbiyachi = models.CharField(max_length=300)
guruh = models.CharField(max_length=200)
fan = models.CharField(max_length=200)
mavzu = models.CharField(max_length=500)
sana = models.CharField(max_length=20)
filial = models.CharField(max_length=200, blank=True, default='')
class Meta:
verbose_name_plural = 'Dastur'
indexes = [
models.Index(fields=['guruh', 'sana']),
]
def __str__(self):
return f"{self.sana} | {self.guruh} | {self.mavzu}"
# ============================================================
# 10. KPI SOZLAMALARI — KPI mezonlari
# ============================================================
class KpiSetting(models.Model):
STATUS_CHOICES = [('Faol', 'Faol'), ('Arxiv', 'Arxiv')]
kpi_id = models.CharField(max_length=100, unique=True)
lavozim = models.CharField(max_length=200)
kpi_nomi = models.CharField(max_length=300)
maks_foiz = models.IntegerField(default=100)
baholovchi = models.CharField(max_length=100, default='Admin')
bosh_sana = models.CharField(max_length=20)
tug_sana = models.CharField(max_length=20)
variantlar = models.TextField(default='[]')
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='Faol')
class Meta:
verbose_name_plural = 'KPI Sozlamalari'
def __str__(self):
return f"{self.lavozim} — {self.kpi_nomi}"
# ============================================================
# 11. KPI RECORDS — xodimlar KPI baholari
# ============================================================
class KpiRecord(models.Model):
sana = models.CharField(max_length=30)
xodim_id = models.CharField(max_length=50)
guruh = models.CharField(max_length=200)
kpi_id = models.CharField(max_length=100)
olingan_foiz = models.IntegerField(default=0)
izoh = models.TextField(blank=True, default='')
admin = models.CharField(max_length=200, blank=True, default='')
class Meta:
verbose_name_plural = 'KPI Yozuvlari'
def __str__(self):
return f"{self.xodim_id} | {self.kpi_id} | {self.olingan_foiz}%"
# ============================================================
# 12. XODIM DAVOMAT — xodimlar ish grafigi
# ============================================================
class XodimDavomat(models.Model):
sana = models.CharField(max_length=20)
xodim_id = models.CharField(max_length=50)
xodim_fio = models.CharField(max_length=300)
guruh = models.CharField(max_length=200)
holat = models.CharField(max_length=50)
kelgan_vaqt = models.CharField(max_length=20, blank=True, default='')
ketgan_vaqt = models.CharField(max_length=20, blank=True, default='')
izoh = models.TextField(blank=True, default='')
filial = models.CharField(max_length=200, blank=True, default='')
class Meta:
verbose_name_plural = 'Xodim davomati'
def __str__(self):
return f"{self.sana} | {self.xodim_fio}"
# ============================================================
# 13. ADMIN — login qiluvchi adminlar
# ============================================================
class AdminUser(models.Model):
login = models.CharField(max_length=100, unique=True)
parol = models.CharField(max_length=100)
filial = models.CharField(max_length=200, blank=True, default='')
class Meta:
verbose_name_plural = 'Adminlar'
def __str__(self):
return f"{self.login} ({self.filial})" import json
import uuid
from datetime import datetime
from django.shortcuts import render
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST, require_GET
from .models import (
Filial, Sozlamalar, Lavozim, Foydalanuvchi, Tarbiyalanuvchi,
Fan, TestSozlama, Jurnal, Dastur, KpiSetting, KpiRecord,
XodimDavomat, AdminUser,
)
# =====================================================
# SESSION XOTIRASI (oddiy dict, productionda Redis ishlating)
# =====================================================
_sessions = {}
def _get_body(request):
try:
return json.loads(request.body)
except (json.JSONDecodeError, ValueError):
return {}
def _now_str():
return datetime.now().strftime('%d.%m.%Y %H:%M')
def _today_str():
return datetime.now().strftime('%d.%m.%Y')
# =====================================================
# SAHIFALAR (HTML template'lar)
# =====================================================
def admin_page(request):
return render(request, 'AdminIndex.html')
def tv_page(request):
return render(request, 'TvIndex.html')
# =====================================================
# AUTH — Login / Session / Logout
# =====================================================
@csrf_exempt
@require_POST
def api_login(request):
data = _get_body(request)
login = data.get('login', '').strip()
password = data.get('password', '').strip()
if not login or not password:
return JsonResponse({'success': False, 'message': 'Login va parolni kiriting'})
try:
admin = AdminUser.objects.get(login=login, parol=password)
except AdminUser.DoesNotExist:
return JsonResponse({'success': False, 'message': 'Login yoki parol xato!'})
token = str(uuid.uuid4())
_sessions[token] = login
return JsonResponse({
'success': True,
'filial': admin.filial,
'sessionToken': token,
})
@csrf_exempt
@require_POST
def api_check_session(request):
data = _get_body(request)
token = data.get('token', '')
if not token or token not in _sessions:
return JsonResponse({'valid': False})
login = _sessions[token]
try:
admin = AdminUser.objects.get(login=login)
return JsonResponse({
'valid': True,
'filial': admin.filial,
'login': login,
})
except AdminUser.DoesNotExist:
return JsonResponse({'valid': False})
@csrf_exempt
@require_POST
def api_logout(request):
data = _get_body(request)
token = data.get('token', '')
_sessions.pop(token, None)
return JsonResponse({'success': True})
# =====================================================
# MA'LUMOT OLISH — Core Data (lazy loading)
# =====================================================
@require_GET
def api_core_data(request):
try:
filiallar = list(Filial.objects.values('nomi', 'status'))
fanlar = list(Fan.objects.values('tarbiyachi', 'fan', 'guruh', 'filial'))
test_sozlamalari = list(TestSozlama.objects.values('fan', 'nisbat'))
foydalanuvchilar = []
for f in Foydalanuvchi.objects.all():
foydalanuvchilar.append({
'id': f.xodim_id, 'tarbiyachi': f.tarbiyachi,
'guruh': f.guruh, 'filial': f.filial, 'lavozim': f.lavozim,
})
bolalar = []
for b in Tarbiyalanuvchi.objects.all():
bolalar.append({
'fio': b.fio, 'filial': b.filial, 'telefon': b.telefon,
'guruh': b.guruh, 'status': b.status,
'ketganSana': b.ketgan_sana, 'sabab': b.sabab,
})
soz = Sozlamalar.objects.first()
sozlamalar = {'nomi': soz.nomi if soz else '', 'rahbar': soz.rahbar if soz else ''}
lavozimlar = list(Lavozim.objects.values('nomi', 'filial'))
kpi_settings = []
for k in KpiSetting.objects.all():
kpi_settings.append({
'id': k.kpi_id, 'lavozim': k.lavozim, 'kpi_nomi': k.kpi_nomi,
'maks_foiz': k.maks_foiz, 'baholovchi': k.baholovchi,
'bosh_sana': k.bosh_sana, 'tug_sana': k.tug_sana,
'variantlar': k.variantlar, 'status': k.status,
})
kpi_records = []
for r in KpiRecord.objects.all():
kpi_records.append({
'sana': r.sana, 'xodim_id': r.xodim_id, 'guruh': r.guruh,
'kpi_id': r.kpi_id, 'olingan_foiz': r.olingan_foiz,
'izoh': r.izoh, 'admin': r.admin,
})
return JsonResponse({
'success': True,
'data': {
'filiallar': filiallar, 'fanlar': fanlar,
'testSozlamalari': test_sozlamalari,
'foydalanuvchilar': foydalanuvchilar, 'bolalar': bolalar,
'sozlamalar': sozlamalar, 'lavozimlar': lavozimlar,
'kpiSettings': kpi_settings, 'kpiRecords': kpi_records,
'jurnal': [], 'dastur': [],
}
})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@require_GET
def api_jurnal_data(request):
try:
jurnal = []
for j in Jurnal.objects.all():
jurnal.append({
'sana': j.sana, 'guruh': j.guruh, 'fan': j.fan,
'bolaFio': j.bola_fio, 'davomat': j.davomat,
'ozlashtirish': j.ozlashtirish, 'qaytaAloqa': j.qayta_aloqa,
'testBall': j.test_ball,
})
return JsonResponse({'success': True, 'jurnal': jurnal})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@require_GET
def api_dastur_data(request):
try:
dastur = []
for d in Dastur.objects.all():
dastur.append({
'tarbiyachi': d.tarbiyachi, 'guruh': d.guruh,
'fan': d.fan, 'mavzu': d.mavzu, 'sana': d.sana, 'filial': d.filial,
})
return JsonResponse({'success': True, 'dastur': dastur})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@require_GET
def api_all_data(request):
try:
core = api_core_data(request)
core_data = json.loads(core.content)
if not core_data.get('success'):
return JsonResponse(core_data)
result = core_data['data']
for j in Jurnal.objects.all():
result['jurnal'].append({
'sana': j.sana, 'guruh': j.guruh, 'fan': j.fan,
'bolaFio': j.bola_fio, 'davomat': j.davomat,
'ozlashtirish': j.ozlashtirish, 'qaytaAloqa': j.qayta_aloqa,
'testBall': j.test_ball,
})
for d in Dastur.objects.all():
result['dastur'].append({
'tarbiyachi': d.tarbiyachi, 'guruh': d.guruh,
'fan': d.fan, 'mavzu': d.mavzu, 'sana': d.sana, 'filial': d.filial,
})
return JsonResponse({'success': True, 'data': result})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@require_GET
def api_tv_data(request):
try:
filiallar = list(Filial.objects.filter(status='Faol').values_list('nomi', flat=True))
bolalar = [{'fio': b.fio, 'filial': b.filial, 'telefon': b.telefon, 'guruh': b.guruh, 'status': b.status, 'ketganSana': b.ketgan_sana, 'sabab': b.sabab} for b in Tarbiyalanuvchi.objects.all()]
foydalanuvchilar = [{'id': f.xodim_id, 'tarbiyachi': f.tarbiyachi, 'guruh': f.guruh, 'filial': f.filial} for f in Foydalanuvchi.objects.all()]
test_sozlamalari = list(TestSozlama.objects.values('fan', 'nisbat'))
jurnal = [{'sana': j.sana, 'guruh': j.guruh, 'fan': j.fan, 'bolaFio': j.bola_fio, 'davomat': j.davomat, 'ozlashtirish': j.ozlashtirish, 'qaytaAloqa': j.qayta_aloqa, 'testBall': j.test_ball} for j in Jurnal.objects.all()]
dastur = [{'tarbiyachi': d.tarbiyachi, 'guruh': d.guruh, 'fan': d.fan, 'mavzu': d.mavzu, 'sana': d.sana, 'filial': d.filial} for d in Dastur.objects.all()]
soz = Sozlamalar.objects.first()
sozlamalar = {'nomi': soz.nomi if soz else '', 'rahbar': soz.rahbar if soz else ''}
return JsonResponse({'success': True, 'data': {
'filiallar': filiallar, 'bolalar': bolalar,
'foydalanuvchilar': foydalanuvchilar, 'testSozlamalari': test_sozlamalari,
'jurnal': jurnal, 'dastur': dastur, 'sozlamalar': sozlamalar,
}})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
# =====================================================
# CRUD — Sozlamalar, Fanlar, Test, Foyd, Lavozim, Dastur, Filial
# =====================================================
@csrf_exempt
@require_POST
def api_save_settings(request):
data = _get_body(request)
soz, _ = Sozlamalar.objects.get_or_create(pk=1)
soz.nomi = data.get('nomi', '')
soz.rahbar = data.get('rahbar', '')
soz.save()
return JsonResponse({'success': True, 'message': 'Sozlamalar muvaffaqiyatli saqlandi!'})
@csrf_exempt
@require_POST
def api_modify_fan(request):
data = _get_body(request)
action, old, new = data.get('action', 'add'), data.get('oldData'), data.get('newData')
if action == 'add' and new:
Fan.objects.create(tarbiyachi=new['tarbiyachi'], fan=new['fan'], guruh=new.get('guruh', ''), filial=new.get('filial', ''))
return JsonResponse({'message': "Fan qo'shildi!"})
if action == 'edit' and old and new:
f = Fan.objects.filter(tarbiyachi=old['tarbiyachi'], fan=old['fan'], guruh=old.get('guruh', '')).first()
if f:
f.tarbiyachi, f.fan, f.guruh, f.filial = new['tarbiyachi'], new['fan'], new.get('guruh', ''), new.get('filial', '')
f.save()
return JsonResponse({'message': 'Tahrirlandi!'})
return JsonResponse({'message': 'Topilmadi!'})
if action == 'delete' and old:
Fan.objects.filter(tarbiyachi=old['tarbiyachi'], fan=old['fan'], guruh=old.get('guruh', '')).delete()
return JsonResponse({'message': "O'chirildi!"})
return JsonResponse({'message': "Noto'g'ri so'rov"})
@csrf_exempt
@require_POST
def api_modify_test(request):
data = _get_body(request)
action, old_fan, new = data.get('action', 'add'), data.get('oldFan'), data.get('newData')
if action == 'add' and new:
TestSozlama.objects.update_or_create(fan=new['fan'], defaults={'nisbat': float(new.get('nisbat', 1))})
return JsonResponse({'message': "Test sozlamasi qo'shildi!"})
if action == 'edit' and old_fan and new:
try:
t = TestSozlama.objects.get(fan=old_fan)
t.fan, t.nisbat = new['fan'], float(new.get('nisbat', 1))
t.save()
return JsonResponse({'message': 'Tahrirlandi!'})
except TestSozlama.DoesNotExist:
return JsonResponse({'message': 'Topilmadi!'})
if action == 'delete' and old_fan:
TestSozlama.objects.filter(fan=old_fan).delete()
return JsonResponse({'message': "O'chirildi!"})
return JsonResponse({'message': "Xato so'rov"})
@csrf_exempt
@require_POST
def api_modify_foyd(request):
data = _get_body(request)
action, old_id, new = data.get('action', 'add'), data.get('oldId'), data.get('newData')
if action == 'add' and new:
Foydalanuvchi.objects.create(xodim_id=new['id'], tarbiyachi=new['tarbiyachi'], guruh=new['guruh'], filial=new.get('filial', ''), lavozim=new.get('lavozim', ''))
return JsonResponse({'message': "Foydalanuvchi qo'shildi!"})
if action == 'edit' and old_id and new:
try:
f = Foydalanuvchi.objects.get(xodim_id=old_id)
f.xodim_id, f.tarbiyachi, f.guruh = new['id'], new['tarbiyachi'], new['guruh']
f.filial, f.lavozim = new.get('filial', ''), new.get('lavozim', '')
f.save()
return JsonResponse({'message': 'Tahrirlandi!'})
except Foydalanuvchi.DoesNotExist:
return JsonResponse({'message': 'Topilmadi!'})
if action == 'delete' and old_id:
Foydalanuvchi.objects.filter(xodim_id=old_id).delete()
return JsonResponse({'message': "O'chirildi!"})
return JsonResponse({'message': "Xato so'rov"})
@csrf_exempt
@require_POST
def api_modify_lavozim(request):
data = _get_body(request)
action, new = data.get('action', 'add'), data.get('newData')
if action == 'add' and new:
Lavozim.objects.create(nomi=new['nomi'], filial=new.get('filial', ''))
return JsonResponse({'message': "Lavozim qo'shildi!"})
if action == 'delete':
Lavozim.objects.filter(nomi=data.get('oldNomi', '')).delete()
return JsonResponse({'message': "Lavozim o'chirildi!"})
return JsonResponse({'message': "Xato so'rov"})
@csrf_exempt
@require_POST
def api_modify_dastur(request):
data = _get_body(request)
action, old, new = data.get('action', 'add'), data.get('oldData'), data.get('newData')
if action == 'add' and new:
Dastur.objects.create(tarbiyachi=new['tarbiyachi'], guruh=new['guruh'], fan=new['fan'], mavzu=new['mavzu'], sana=new['sana'], filial=new.get('filial', ''))
return JsonResponse({'message': "Mavzu qo'shildi!"})
if action == 'edit' and old and new:
d = Dastur.objects.filter(tarbiyachi=old['tarbiyachi'], guruh=old['guruh'], fan=old['fan'], mavzu=old['mavzu'], sana=old['sana']).first()
if d:
d.tarbiyachi, d.guruh, d.fan = new['tarbiyachi'], new['guruh'], new['fan']
d.mavzu, d.sana, d.filial = new['mavzu'], new['sana'], new.get('filial', '')
d.save()
return JsonResponse({'message': 'Tahrirlandi!'})
return JsonResponse({'message': 'Topilmadi!'})
if action == 'delete' and old:
Dastur.objects.filter(tarbiyachi=old['tarbiyachi'], guruh=old['guruh'], fan=old['fan'], mavzu=old['mavzu'], sana=old['sana']).delete()
return JsonResponse({'message': "O'chirildi!"})
return JsonResponse({'message': "Xato so'rov"})
@csrf_exempt
@require_POST
def api_modify_filial(request):
data = _get_body(request)
action, filial_name = data.get('action'), data.get('filialName')
if not filial_name:
return JsonResponse({'success': False, 'message': 'Filial nomi kerak'})
try:
fil = Filial.objects.get(nomi=filial_name)
except Filial.DoesNotExist:
return JsonResponse({'success': False, 'message': 'Filial topilmadi'})
if action == 'arxiv':
fil.status = 'Arxiv'; fil.save()
return JsonResponse({'success': True, 'message': f'{filial_name} arxivlandi!'})
elif action == 'restore':
fil.status = 'Faol'; fil.save()
return JsonResponse({'success': True, 'message': f'{filial_name} qayta tiklandi!'})
return JsonResponse({'success': False, 'message': "Noto'g'ri harakat"})
# =====================================================
# KPI
# =====================================================
@csrf_exempt
@require_POST
def api_save_kpi_setting(request):
data = _get_body(request)
action = data.get('action')
if action == 'add':
kpi_id = f"KPI_{int(datetime.now().timestamp() * 1000)}"
KpiSetting.objects.create(kpi_id=kpi_id, lavozim=data.get('lavozim', ''), kpi_nomi=data.get('kpi_nomi', ''), maks_foiz=int(data.get('maks_foiz', 0)), baholovchi=data.get('baholovchi', 'Admin'), bosh_sana=data.get('bosh_sana', ''), tug_sana=data.get('tug_sana', ''), variantlar=data.get('variantlar', '[]'), status='Faol')
return JsonResponse({'success': True, 'message': 'KPI muvaffaqiyatli saqlandi!'})
if action == 'delete':
KpiSetting.objects.filter(kpi_id=data.get('id', '')).delete()
return JsonResponse({'success': True, 'message': "KPI o'chirildi!"})
return JsonResponse({'success': False, 'message': 'Harakat aniqlanmadi'})
@csrf_exempt
@require_POST
def api_save_kpi_record(request):
data = _get_body(request)
action = data.get('action')
xodim_id, kpi_id = str(data.get('xodim_id', '')), str(data.get('kpi_id', ''))
if action == 'delete':
KpiRecord.objects.filter(xodim_id=xodim_id, kpi_id=kpi_id).delete()
return JsonResponse({'success': True, 'message': "Baho o'chirib tashlandi!"})
record, created = KpiRecord.objects.update_or_create(xodim_id=xodim_id, kpi_id=kpi_id, defaults={
'sana': data.get('sana_full', _today_str()), 'guruh': data.get('guruh', ''),
'olingan_foiz': int(data.get('olingan_foiz', 0)), 'izoh': data.get('izoh', ''),
'admin': f"{data.get('admin', '')} ({_now_str()})",
})
return JsonResponse({'success': True, 'message': 'Baholash yangilandi!' if not created else 'Muvaffaqiyatli baholandi!'})
@csrf_exempt
@require_POST
def api_mark_qayta_aloqa(request):
data = _get_body(request)
ts = _now_str()
record = Jurnal.objects.filter(sana=data.get('sana', ''), guruh=data.get('guruh', ''), fan=data.get('fan', ''), bola_fio=data.get('bolaFio', '')).order_by('-id').first()
if record:
record.qayta_aloqa = ts; record.save()
return JsonResponse({'success': True, 'vaqt': ts})
return JsonResponse({'success': False, 'message': "Yo'qlama topilmadi!"})
@csrf_exempt
@require_POST
def api_save_xodim_davomat(request):
data = _get_body(request)
record, created = XodimDavomat.objects.update_or_create(sana=data.get('sana', ''), xodim_id=data.get('xodim_id', ''), defaults={
'xodim_fio': data.get('xodim_fio', ''), 'guruh': data.get('guruh', ''),
'holat': data.get('holat', ''), 'kelgan_vaqt': data.get('kelgan_vaqt', ''),
'ketgan_vaqt': data.get('ketgan_vaqt', ''), 'izoh': data.get('izoh', ''), 'filial': data.get('filial', ''),
})
return JsonResponse({'success': True, 'message': 'Yangilandi!' if not created else 'Saqlandi!'})
@require_GET
def api_xodim_davomat_data(request):
try:
result = [{'sana': x.sana, 'xodim_id': x.xodim_id, 'xodim_fio': x.xodim_fio, 'guruh': x.guruh, 'holat': x.holat, 'kelgan_vaqt': x.kelgan_vaqt, 'ketgan_vaqt': x.ketgan_vaqt, 'izoh': x.izoh, 'filial': x.filial} for x in XodimDavomat.objects.all()]
return JsonResponse({'success': True, 'data': result})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)}) from django.urls import path
from . import views
urlpatterns = [
path('login/', views.api_login, name='api_login'),
path('check-session/', views.api_check_session, name='api_check_session'),
path('logout/', views.api_logout, name='api_logout'),
path('core-data/', views.api_core_data, name='api_core_data'),
path('jurnal-data/', views.api_jurnal_data, name='api_jurnal_data'),
path('dastur-data/', views.api_dastur_data, name='api_dastur_data'),
path('all-data/', views.api_all_data, name='api_all_data'),
path('tv-data/', views.api_tv_data, name='api_tv_data'),
path('save-settings/', views.api_save_settings, name='api_save_settings'),
path('modify-fan/', views.api_modify_fan, name='api_modify_fan'),
path('modify-test/', views.api_modify_test, name='api_modify_test'),
path('modify-foyd/', views.api_modify_foyd, name='api_modify_foyd'),
path('modify-lavozim/', views.api_modify_lavozim, name='api_modify_lavozim'),
path('modify-dastur/', views.api_modify_dastur, name='api_modify_dastur'),
path('modify-filial/', views.api_modify_filial, name='api_modify_filial'),
path('save-kpi-setting/', views.api_save_kpi_setting, name='api_save_kpi_setting'),
path('save-kpi-record/', views.api_save_kpi_record, name='api_save_kpi_record'),
path('mark-qayta-aloqa/', views.api_mark_qayta_aloqa, name='api_mark_qayta_aloqa'),
path('save-xodim-davomat/', views.api_save_xodim_davomat, name='api_save_xodim_davomat'),
path('xodim-davomat-data/', views.api_xodim_davomat_data, name='api_xodim_davomat_data'),
] from django.contrib import admin
from .models import (
Filial, Sozlamalar, Lavozim, Foydalanuvchi, Tarbiyalanuvchi,
Fan, TestSozlama, Jurnal, Dastur, KpiSetting, KpiRecord,
XodimDavomat, AdminUser,
)
@admin.register(Filial)
class FilialAdmin(admin.ModelAdmin):
list_display = ('nomi', 'status')
list_filter = ('status',)
@admin.register(Sozlamalar)
class SozlamalarAdmin(admin.ModelAdmin):
list_display = ('nomi', 'rahbar')
@admin.register(Lavozim)
class LavozimAdmin(admin.ModelAdmin):
list_display = ('nomi', 'filial')
@admin.register(Foydalanuvchi)
class FoydalanuvchiAdmin(admin.ModelAdmin):
list_display = ('xodim_id', 'tarbiyachi', 'guruh', 'filial', 'lavozim')
list_filter = ('filial', 'guruh', 'lavozim')
search_fields = ('tarbiyachi', 'xodim_id')
@admin.register(Tarbiyalanuvchi)
class TarbiyalanuvchiAdmin(admin.ModelAdmin):
list_display = ('fio', 'guruh', 'filial', 'status', 'telefon')
list_filter = ('status', 'filial', 'guruh')
search_fields = ('fio', 'telefon')
@admin.register(Fan)
class FanAdmin(admin.ModelAdmin):
list_display = ('fan', 'tarbiyachi', 'guruh', 'filial')
list_filter = ('guruh',)
@admin.register(TestSozlama)
class TestSozlamaAdmin(admin.ModelAdmin):
list_display = ('fan', 'nisbat')
@admin.register(Jurnal)
class JurnalAdmin(admin.ModelAdmin):
list_display = ('sana', 'guruh', 'fan', 'bola_fio', 'davomat', 'ozlashtirish')
list_filter = ('guruh', 'fan', 'davomat')
search_fields = ('bola_fio',)
@admin.register(Dastur)
class DasturAdmin(admin.ModelAdmin):
list_display = ('sana', 'guruh', 'fan', 'tarbiyachi', 'mavzu')
list_filter = ('guruh', 'fan')
search_fields = ('mavzu',)
@admin.register(KpiSetting)
class KpiSettingAdmin(admin.ModelAdmin):
list_display = ('kpi_id', 'lavozim', 'kpi_nomi', 'maks_foiz', 'status')
list_filter = ('lavozim', 'status')
@admin.register(KpiRecord)
class KpiRecordAdmin(admin.ModelAdmin):
list_display = ('sana', 'xodim_id', 'kpi_id', 'olingan_foiz')
list_filter = ('kpi_id',)
@admin.register(XodimDavomat)
class XodimDavomatAdmin(admin.ModelAdmin):
list_display = ('sana', 'xodim_fio', 'guruh', 'holat')
list_filter = ('holat', 'guruh')
@admin.register(AdminUser)
class AdminUserAdmin(admin.ModelAdmin):
list_display = ('login', 'filial') /**
* NINI FRANCHISE — Django Backend uchun JavaScript
* google.script.run o'rniga fetch() ishlatadi.
* AdminIndex.html dagi <script> ichiga qo'shiladi yoki alohida yuklanadi.
*/
function apiGet(url) {
return fetch('/api/' + url).then(function(r) { return r.json(); });
}
function apiPost(url, body) {
return fetch('/api/' + url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
}).then(function(r) { return r.json(); });
}
function tryRestoreSession() {
var token = sessionStorage.getItem('nini_session');
if (!token) return;
document.getElementById('loginMsg').innerText = "Sessiya tiklanmoqda...";
apiPost('check-session/', { token: token }).then(function(res) {
if (res.valid) {
sessionToken = token; currentAdmin = res.login; myFilial = res.filial;
if (!myFilial || myFilial.trim() === '' || myFilial.toLowerCase() === 'barchasi' || myFilial.toLowerCase() === 'super admin') { isSuperAdmin = true; myFilial = ''; } else { isSuperAdmin = false; }
document.getElementById('loginScreen').classList.add('hidden');
document.getElementById('mainScreen').classList.remove('hidden');
fetchCoreData();
} else { sessionStorage.removeItem('nini_session'); document.getElementById('loginMsg').innerText = ''; }
});
}
function login() {
var log = document.getElementById('adminLogin').value, pass = document.getElementById('adminPass').value;
if (!log || !pass) return;
document.getElementById('loginMsg').innerText = "Tizimga ulanilmoqda...";
currentAdmin = log;
apiPost('login/', { login: log, password: pass }).then(function(res) {
if (res.success) {
myFilial = res.filial; sessionToken = res.sessionToken;
sessionStorage.setItem('nini_session', sessionToken);
if (!myFilial || myFilial.trim() === '' || myFilial.toLowerCase() === 'barchasi' || myFilial.toLowerCase() === 'super admin') { isSuperAdmin = true; myFilial = ''; } else { isSuperAdmin = false; }
document.getElementById('loginScreen').classList.add('hidden');
document.getElementById('mainScreen').classList.remove('hidden');
fetchCoreData();
} else { document.getElementById('loginMsg').innerText = res.message; }
});
}
function logout() {
if (sessionToken) apiPost('logout/', { token: sessionToken });
sessionStorage.removeItem('nini_session'); sessionStorage.removeItem('nini_filters'); location.reload();
}
function fetchCoreData() {
showLoad(true);
apiGet('core-data/').then(function(r) {
if (!r.success) { alert("Xato: " + r.error); showLoad(false); return; }
rawData = r.data; rawData.jurnal = rawData.jurnal || []; rawData.dastur = rawData.dastur || [];
setupFilialSelector(); populateDropdowns(); restoreFilters(); renderCoreViews(); showLoad(false);
fetchJurnalData(); fetchDasturDataAsync();
}).catch(function(e) { alert("Xatolik: " + e.message); showLoad(false); });
}
function fetchJurnalData() {
apiGet('jurnal-data/').then(function(r) { if (r.success) { rawData.jurnal = r.jurnal; jurnalLoaded = true; renderJurnalViews(); } });
}
function fetchDasturDataAsync() {
apiGet('dastur-data/').then(function(r) { if (r.success) { rawData.dastur = r.dastur; dasturLoaded = true; renderDasturViews(); } });
}
function fetchAllData() {
showLoad(true);
apiGet('all-data/').then(function(r) {
if (!r.success) { alert("Xato: " + r.error); showLoad(false); return; }
rawData = r.data; jurnalLoaded = true; dasturLoaded = true;
populateDropdowns(); renderAllData(); showLoad(false);
}).catch(function(e) { alert("Xatolik: " + e.message); showLoad(false); });
}
function setQaytaAloqa(btn, sana, guruh, fan, fio) {
btn.disabled = true; btn.innerText = "Kuting...";
apiPost('mark-qayta-aloqa/', { sana: sana, guruh: guruh, fan: fan, bolaFio: fio }).then(function(r) {
if (r.success) { btn.outerHTML = '<span class="status-badge bg-grey">' + r.vaqt + '</span>'; fetchAllData(); }
else { alert(r.message); btn.disabled = false; btn.innerText = "Belgilash"; }
});
}
function saveSettingsFrontend() {
var n = gv('setBogchaNomi'), r = gv('setRahbar');
var btn = document.getElementById('btnSaveSettings'), msg = document.getElementById('msgSettings');
btn.disabled = true; btn.innerText = "Kuting...";
apiPost('save-settings/', { nomi: n, rahbar: r }).then(function(res) {
btn.disabled = false; btn.innerText = "Saqlash"; msg.innerText = res.message;
msg.style.color = res.success ? "var(--success-text)" : "var(--danger-text)";
if (res.success && rawData.sozlamalar) { rawData.sozlamalar.nomi = n; rawData.sozlamalar.rahbar = r; }
setTimeout(function() { msg.innerText = ''; }, 3000);
});
}
function saveModal() {
var btn = document.getElementById('modalSaveBtn'); btn.disabled = true; btn.innerText = "Kuting...";
if (modalConfig.type === 'test') {
apiPost('modify-test/', { action: modalConfig.action, oldFan: modalConfig.oldData ? modalConfig.oldData.fan : null, newData: { fan: gv('mTestFan'), nisbat: gv('mTestNisbat') } }).then(function(r) { alert(r.message); fetchAllData(); closeModal(); btn.disabled = false; btn.innerText = "Saqlash"; });
} else if (modalConfig.type === 'fan') {
apiPost('modify-fan/', { action: modalConfig.action, oldData: modalConfig.oldData, newData: { tarbiyachi: gv('mFanTarbiyachi'), fan: gv('mFanNomi'), guruh: stateFanGuruh, filial: myFilial } }).then(function(r) { alert(r.message); fetchAllData(); closeModal(); btn.disabled = false; btn.innerText = "Saqlash"; });
} else if (modalConfig.type === 'foyd') {
apiPost('modify-foyd/', { action: modalConfig.action, oldId: modalConfig.oldData ? modalConfig.oldData.id : null, newData: { id: gv('mFoydId'), tarbiyachi: gv('mFoydTarbiyachi'), guruh: gv('mFoydGuruh'), filial: myFilial, lavozim: gv('mFoydLavozim') } }).then(function(r) { alert(r.message); fetchAllData(); closeModal(); btn.disabled = false; btn.innerText = "Saqlash"; });
} else if (modalConfig.type === 'lavozim') {
apiPost('modify-lavozim/', { action: 'add', oldNomi: null, newData: { nomi: gv('mLavNomi'), filial: myFilial } }).then(function(r) { alert(r.message); fetchAllData(); closeModal(); btn.disabled = false; btn.innerText = "Saqlash"; });
}
}
function delRec(type, data) {
if (!confirm("O'chirasizmi?")) return;
var endpoint = type === 'test' ? 'modify-test/' : type === 'fan' ? 'modify-fan/' : type === 'foyd' ? 'modify-foyd/' : 'modify-lavozim/';
var body = type === 'test' ? { action: 'delete', oldFan: data.fan } : type === 'fan' ? { action: 'delete', oldData: data } : type === 'foyd' ? { action: 'delete', oldId: data.id } : { action: 'delete', oldNomi: data.nomi };
apiPost(endpoint, body).then(function(r) { alert(r.message); fetchAllData(); });
}
function saveDasturModal() {
var nd = { guruh: gv('dmGuruh'), fan: gv('dmFan'), tarbiyachi: gv('dmTarbiyachi'), mavzu: gv('dmMavzu'), sana: gv('dmSana'), filial: myFilial };
if (!nd.guruh || !nd.fan || !nd.mavzu || !nd.sana) return alert("To'ldiring!");
var btn = document.getElementById('dasturModalSaveBtn'); btn.disabled = true; btn.innerText = "Kuting...";
apiPost('modify-dastur/', { action: dasturModalConfig.action, oldData: dasturModalConfig.oldData, newData: nd }).then(function(r) { alert(r.message); closeDasturModal(); btn.disabled = false; btn.innerText = "Saqlash"; fetchAllData(); });
}
function deleteDasturItem(data) {
if (!confirm("O'chirasizmi?")) return;
apiPost('modify-dastur/', { action: 'delete', oldData: data }).then(function(r) { alert(r.message); fetchAllData(); });
}
function filialAction(action, filialName) {
if (!confirm(action === 'arxiv' ? filialName + " ni arxivlaysizmi?" : "Tiklaysizmi?")) return;
showLoad(true);
apiPost('modify-filial/', { action: action, filialName: filialName }).then(function(r) { alert(r.message); fetchAllData(); });
}
function saveKpiSetting() {
var l = gv('kAddLavozim'), n = gv('kAddNomi'), b = gv('kAddBaholovchi'), bs = gv('kAddBoshSana'), ts = gv('kAddTugSana');
if (!n || !bs || !ts || !l) return alert("To'ldiring!");
var vr = document.querySelectorAll('.variant-row'); if (vr.length === 0) return alert("Variant qo'shing!");
var va = [], mf = 0;
for (var i = 0; i < vr.length; i++) { var fv = parseInt(vr[i].querySelector('.v-foiz').value) || 0; var nv = vr[i].querySelector('.v-nom').value.trim(); if (!nv) return alert("Nom to'ldiring!"); if (fv > mf) mf = fv; va.push({ foiz: fv, nom: nv }); }
showLoad(true); document.getElementById('kpiAddModal').classList.add('hidden');
apiPost('save-kpi-setting/', { action: 'add', lavozim: l, kpi_nomi: n, baholovchi: b, bosh_sana: bs, tug_sana: ts, maks_foiz: mf, variantlar: JSON.stringify(va) }).then(function(r) { alert(r.message); fetchAllData(); });
}
function deleteKpi(id) {
if (!confirm("O'chirasizmi?")) return; showLoad(true);
apiPost('save-kpi-setting/', { action: 'delete', id: id }).then(function(r) { fetchAllData(); });
}
function submitKpiEval() {
if (currentSelectedFoiz === null) return alert("Variantni tanlang!");
var btn = document.getElementById('evalSaveBtn'); btn.disabled = true; btn.innerText = 'Kuting...';
apiPost('save-kpi-record/', { sana_full: getTodayStr(), xodim_id: currentEvalData.xodim_id, guruh: currentEvalData.guruh, kpi_id: currentEvalData.kpi_id, olingan_foiz: Number(currentSelectedFoiz), izoh: gv('evalModalIzoh').trim(), admin: currentAdmin }).then(function(r) { btn.disabled = false; btn.innerText = 'Saqlash'; document.getElementById('kpiEvalModal').classList.add('hidden'); fetchAllData(); });
}
function deleteKpiEval() {
if (!confirm("O'chirasizmi?")) return;
var btn = document.getElementById('evalClearBtn'); btn.disabled = true; btn.innerText = 'Kuting...';
apiPost('save-kpi-record/', { action: 'delete', xodim_id: currentEvalData.xodim_id, kpi_id: currentEvalData.kpi_id }).then(function(r) { btn.disabled = false; btn.innerText = 'Tozalash'; document.getElementById('kpiEvalModal').classList.add('hidden'); fetchAllData(); });
}