Files
ACORE/core/views.py
T
lokses faa06b9f82 Security hardening: fix open redirect, API auth, MIME validation, PII exposure
- core/views.py: fix open redirect in login — validate next_url with
  url_has_allowed_host_and_scheme before redirecting
- visit_manager/api/views.py: add explicit permission_classes/
  authentication_classes on all 3 API views; add MIME magic-byte
  validation + Pillow verify for photo uploads; validate employee_id/
  location_id/date query params; restrict photo access to is_staff,
  own record, or direct manager
- employees/serializers.py: split into EmployeeSerializer (public
  fields only) and EmployeeDetailSerializer (PII fields, for HR/admin)
- mattermost_service.py: sanitize sensitive fields (password, token)
  before logging; log only error message not full response body
- settings/base.py: add SESSION/CSRF cookie HttpOnly + SameSite flags;
  set file upload size limits (15MB); remove BasicAuthentication from
  DRF (sends credentials in plaintext)
2026-05-31 18:05:54 +03:00

201 lines
7.3 KiB
Python

from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.utils.http import url_has_allowed_host_and_scheme
from django.conf import settings
from apps.employees.models.base import Employee
from apps.locations.models.pharmacies import Pharmacy
from core.models.directories import Directory
import json
# Create your views here.
def is_htmx_request(request):
"""Check if request is from HTMX"""
return request.headers.get('HX-Request') == 'true'
@login_required
def index_view(request):
return render(request, 'index.html')
@login_required
def dashboard_view(request):
if is_htmx_request(request):
return render(request, 'pages/dashboard.html')
return render(request, 'index.html', {'page_template': 'pages/dashboard.html'})
@login_required
def employees_view(request):
location_type = request.GET.get('location_type', '')
organization_id = request.GET.get('organization_id', '')
pharmacy_id = request.GET.get('pharmacy_id', '')
employees = Employee.objects.select_related(
'department', 'post', 'user_pharmacy', 'user_organization',
'user_pharmacy__organization',
).order_by('last_name', 'first_name')
if location_type == 'office':
employees = employees.filter(user_location_type='office')
elif location_type == 'pharmacy':
employees = employees.filter(user_location_type='pharmacy')
if organization_id:
employees = employees.filter(user_pharmacy__organization_id=organization_id)
if pharmacy_id:
employees = employees.filter(user_pharmacy_id=pharmacy_id)
active_count = employees.filter(is_active=True).count()
organizations = Directory.objects.filter(
directory_type__code='organization', is_active=True
).order_by('name')
pharmacies_qs = (
Pharmacy.objects
.select_related('organization')
.filter(is_active=True)
.order_by('organization__name', 'pharmacy_number')
)
pharmacies_by_org = {}
for ph in pharmacies_qs:
key = ph.organization_id
if key not in pharmacies_by_org:
pharmacies_by_org[key] = []
pharmacies_by_org[key].append({
'id': ph.id,
'label': f"Аптека №{ph.pharmacy_number} — {ph.address}",
})
current_pharmacies = (
pharmacies_qs.filter(organization_id=organization_id)
if organization_id else pharmacies_qs
)
context = {
'employees': employees,
'active_count': active_count,
'organizations': organizations,
'current_pharmacies': current_pharmacies,
'pharmacies_by_org': pharmacies_by_org,
'filter_location_type': location_type,
'filter_organization_id': organization_id,
'filter_pharmacy_id': pharmacy_id,
}
if is_htmx_request(request):
return render(request, 'pages/employees.html', context)
return render(request, 'index.html', {'page_template': 'pages/employees.html', **context})
def build_region_tree(region_nodes):
allowed_types = {'region', 'district', 'city', 'city_district'}
tree = []
def lookup_node(list_nodes, node_id):
for item in list_nodes:
if item['id'] == node_id:
return item
return None
for region in region_nodes:
ancestors = [node for node in list(region.get_ancestors()) if node.directory_type.code in allowed_types]
parent_list = tree
for ancestor in ancestors:
existing = lookup_node(parent_list, ancestor.id)
if existing is None:
existing = {
'id': ancestor.id,
'name': ancestor.name,
'children': []
}
parent_list.append(existing)
parent_list = existing['children']
return tree
@login_required
def pharmacies_view(request):
pharmacies = Pharmacy.objects.select_related('organization', 'location_region', 'head_pharmacist').order_by('organization__name', 'pharmacy_number')
active_count = pharmacies.filter(is_active=True).count()
# Get unique regions for filter
regions = pharmacies.values_list('location_region__name', flat=True).distinct().order_by('location_region__name')
region_ids = sorted({rid for rid in pharmacies.values_list('location_region_id', flat=True).distinct() if rid})
region_nodes = Directory.objects.filter(id__in=region_ids).select_related('parent', 'directory_type')
region_tree = build_region_tree(region_nodes)
context = {
'pharmacies': pharmacies,
'active_count': active_count,
'regions': regions,
'region_tree_json': json.dumps(region_tree, ensure_ascii=False),
}
if is_htmx_request(request):
return render(request, 'pages/pharmacies.html', context)
return render(request, 'index.html', {'page_template': 'pages/pharmacies.html', **context})
@login_required
def calendar_view(request):
if is_htmx_request(request):
return render(request, 'pages/calendar.html')
return render(request, 'index.html', {'page_template': 'pages/calendar.html'})
@login_required
def timetrack_view(request):
if is_htmx_request(request):
return render(request, 'pages/timetrack.html')
return render(request, 'index.html', {'page_template': 'pages/timetrack.html'})
@login_required
def reports_view(request):
if is_htmx_request(request):
return render(request, 'pages/reports.html')
return render(request, 'index.html', {'page_template': 'pages/reports.html'})
@login_required
def documents_view(request):
if is_htmx_request(request):
return render(request, 'pages/documents.html')
return render(request, 'index.html', {'page_template': 'pages/documents.html'})
@login_required
def recruiting_view(request):
if is_htmx_request(request):
return render(request, 'pages/recruiting.html')
return render(request, 'index.html', {'page_template': 'pages/recruiting.html'})
@login_required
def structure_view(request):
if is_htmx_request(request):
return render(request, 'pages/structure.html')
return render(request, 'index.html', {'page_template': 'pages/structure.html'})
@login_required
def settings_view(request):
if is_htmx_request(request):
return render(request, 'pages/settings.html')
return render(request, 'index.html', {'page_template': 'pages/settings.html'})
def login_view(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
next_url = request.GET.get('next') or request.POST.get('next') or '/'
if not url_has_allowed_host_and_scheme(next_url, allowed_hosts=settings.ALLOWED_HOSTS):
next_url = '/'
return redirect(next_url)
return render(request, 'login.html', {'login_error': 'Неверный логин или пароль'})
return render(request, 'login.html')
def logout_view(request):
logout(request)
return redirect('login')
def modal_add_employee_view(request):
return render(request, 'partials/modal-add-employee.html')