from django.shortcuts import render, redirect from django.contrib import messages from django.http import JsonResponse, HttpResponse from django.views.decorators.http import require_GET from django.contrib.admin.views.decorators import staff_member_required from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator from django.views import View from django.views.generic import TemplateView, FormView, UpdateView, DeleteView, CreateView from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin from django.contrib.auth import logout from django.forms import inlineformset_factory, modelformset_factory from django.db import models from django.urls import reverse_lazy from django.contrib.gis.geos import Point import pandas as pd from .utils import ( fill_data_from_df, add_satellite_list, get_points_from_csv, get_vch_load_from_html, compare_and_link_vch_load, kub_report ) from mapsapp.utils import parse_transponders_from_json, parse_transponders_from_xml from .forms import ( LoadExcelData, LoadCsvData, UploadFileForm, VchLinkForm, UploadVchLoad, NewEventForm, ObjItemForm, ParameterForm, GeoForm ) from .models import ObjItem from .clusters import get_clusters from io import BytesIO from datetime import datetime class AddSatellitesView(LoginRequiredMixin, View): def get(self, request): add_satellite_list() return redirect('home') # class AddTranspondersView(View): # def get(self, request): # try: # parse_transponders_from_json(BASE_DIR / "transponders.json") # except FileNotFoundError: # print("Файл не найден") # return redirect('home') class AddTranspondersView(LoginRequiredMixin, FormView): template_name = 'mainapp/transponders_upload.html' form_class = UploadFileForm def form_valid(self, form): uploaded_file = self.request.FILES['file'] try: content = uploaded_file.read() parse_transponders_from_xml(BytesIO(content)) messages.success(self.request, "Файл успешно обработан") except ValueError as e: messages.error(self.request, f"Ошибка при чтении таблиц: {e}") except Exception as e: messages.error(self.request, f"Неизвестная ошибка: {e}") return redirect('add_trans') def form_invalid(self, form): messages.error(self.request, "Форма заполнена некорректно.") return super().form_invalid(form) from django.views.generic import View class ActionsPageView(View): def get(self, request): if request.user.is_authenticated: return render(request, 'mainapp/actions.html') else: return render(request, 'mainapp/login_required.html') class HomePageView(View): def get(self, request): if request.user.is_authenticated: # Redirect to objitem list if authenticated return redirect('objitem_list') else: return render(request, 'mainapp/login_required.html') class LoadExcelDataView(LoginRequiredMixin, FormView): template_name = 'mainapp/add_data_from_excel.html' form_class = LoadExcelData def form_valid(self, form): uploaded_file = self.request.FILES['file'] selected_sat = form.cleaned_data['sat_choice'] number = form.cleaned_data['number_input'] try: import io df = pd.read_excel(io.BytesIO(uploaded_file.read())) if number > 0: df = df.head(number) result = fill_data_from_df(df, selected_sat, self.request.user.customuser) messages.success(self.request, f"Данные успешно загружены! Обработано строк: {result}") return redirect('load_excel_data') except Exception as e: messages.error(self.request, f"Ошибка при обработке файла: {str(e)}") return redirect('load_excel_data') def form_invalid(self, form): messages.error(self.request, "Форма заполнена некорректно.") return super().form_invalid(form) from django.views.generic import View from django.core.paginator import Paginator from django.db.models import Prefetch from .models import Satellite, ObjItem, Parameter, Geo class GetLocationsView(LoginRequiredMixin, View): def get(self, request, sat_id): locations = ObjItem.objects.filter(parameters_obj__id_satellite=sat_id) if not locations: return JsonResponse({'error': 'Объектов не найдено'}, status=400) features = [] for loc in locations: param = loc.parameters_obj.get() features.append({ "type": "Feature", "geometry": { "type": "Point", "coordinates": [loc.geo_obj.coords[0], loc.geo_obj.coords[1]] }, "properties": { "pol": param.polarization.name, "freq": param.frequency*1000000, "name": f"{loc.name}", "id": loc.geo_obj.id } }) return JsonResponse({ "type": "FeatureCollection", "features": features }) class LoadCsvDataView(LoginRequiredMixin, FormView): template_name = 'mainapp/add_data_from_csv.html' form_class = LoadCsvData def form_valid(self, form): uploaded_file = self.request.FILES['file'] try: # Read the file content and pass it directly to the function content = uploaded_file.read() if isinstance(content, bytes): content = content.decode('utf-8') get_points_from_csv(content, self.request.user.customuser) messages.success(self.request, f"Данные успешно загружены!") return redirect('load_csv_data') except Exception as e: messages.error(self.request, f"Ошибка при обработке файла: {str(e)}") return redirect('load_csv_data') def form_invalid(self, form): messages.error(self.request, "Форма заполнена некорректно.") return super().form_invalid(form) from collections import defaultdict @method_decorator(staff_member_required, name='dispatch') class ShowMapView(UserPassesTestMixin, View): def test_func(self): return self.request.user.is_staff def get(self, request): ids = request.GET.get('ids', '') points = [] if ids: id_list = [int(x) for x in ids.split(',') if x.isdigit()] locations = ObjItem.objects.filter(id__in=id_list).prefetch_related( 'parameters_obj__id_satellite', 'parameters_obj__polarization', 'parameters_obj__modulation', 'parameters_obj__standard', 'geo_obj' ) for obj in locations: param = obj.parameters_obj.get() points.append({ 'name': f"{obj.name}", 'freq': f"{param.frequency} [{param.freq_range}] МГц", 'point': (obj.geo_obj.coords.x, obj.geo_obj.coords.y) }) else: return redirect('admin') grouped = defaultdict(list) for p in points: grouped[p["name"]].append({ 'point': p["point"], 'frequency': p["freq"] }) groups = [ { "name": name, "points": coords_list } for name, coords_list in grouped.items() ] context = { 'groups': groups, } return render(request, 'admin/map_custom.html', context) class ClusterTestView(LoginRequiredMixin, View): def get(self, request): objs = ObjItem.objects.filter(name__icontains="! Astra 4A 12654,040 [1,962] МГц H") coords = [] for obj in objs: if obj.geo_obj and obj.geo_obj.coords: coords.append((obj.geo_obj.coords.coords[1], obj.geo_obj.coords.coords[0])) get_clusters(coords) return JsonResponse({"success": "ок"}) def custom_logout(request): logout(request) return redirect('home') class UploadVchLoadView(LoginRequiredMixin, FormView): template_name = 'mainapp/upload_html.html' form_class = UploadVchLoad def form_valid(self, form): selected_sat = form.cleaned_data['sat_choice'] uploaded_file = self.request.FILES['file'] try: get_vch_load_from_html(uploaded_file, selected_sat) messages.success(self.request, "Файл успешно обработан") except ValueError as e: messages.error(self.request, f"Ошибка при чтении таблиц: {e}") except Exception as e: messages.error(self.request, f"Неизвестная ошибка: {e}") return redirect('vch_load') def form_invalid(self, form): messages.error(self.request, "Форма заполнена некорректно.") return super().form_invalid(form) class LinkVchSigmaView(LoginRequiredMixin, FormView): template_name = 'mainapp/link_vch.html' form_class = VchLinkForm def form_valid(self, form): freq = form.cleaned_data['value1'] freq_range = form.cleaned_data['value2'] # ku_range = float(form.cleaned_data['ku_range']) sat_id = form.cleaned_data['sat_choice'] # print(freq, freq_range, ku_range, sat_id.pk) count_all, link_count = compare_and_link_vch_load(sat_id, freq, freq_range, 1) messages.success(self.request, f"Привязано {link_count} из {count_all} объектов") return redirect('link_vch_sigma') def form_invalid(self, form): return self.render_to_response(self.get_context_data(form=form)) class ProcessKubsatView(LoginRequiredMixin, FormView): template_name = 'mainapp/process_kubsat.html' form_class = NewEventForm def form_valid(self, form): # selected_sat = form.cleaned_data['sat_choice'] # selected_pol = form.cleaned_data['pol_choice'] uploaded_file = self.request.FILES['file'] try: content = uploaded_file.read() df = kub_report(BytesIO(content)) output = BytesIO() with pd.ExcelWriter(output, engine='openpyxl') as writer: df.to_excel(writer, index=False, sheet_name='Результат') output.seek(0) response = HttpResponse( output.getvalue(), content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ) response['Content-Disposition'] = f'attachment; filename="kubsat_report.xlsx"' messages.success(self.request, "Событие успешно обработано!") return response except Exception as e: messages.error(self.request, f"Ошибка при обработке файла: {str(e)}") return redirect('kubsat_excel') # return redirect('kubsat_excel') def form_invalid(self, form): messages.error(self.request, "Форма заполнена некорректно.") return super().form_invalid(form) from django.contrib.auth.mixins import LoginRequiredMixin class ObjItemListView(LoginRequiredMixin, View): def get(self, request): satellites = Satellite.objects.filter(parameters__objitems__isnull=False).distinct().order_by('name') # Get selected satellite from query parameters selected_sat_id = request.GET.get('satellite_id') page_number = request.GET.get('page', 1) items_per_page = request.GET.get('items_per_page', '50') # Get filter parameters freq_min = request.GET.get('freq_min') freq_max = request.GET.get('freq_max') range_min = request.GET.get('range_min') range_max = request.GET.get('range_max') snr_min = request.GET.get('snr_min') snr_max = request.GET.get('snr_max') bod_min = request.GET.get('bod_min') bod_max = request.GET.get('bod_max') search_query = request.GET.get('search') selected_modulations = request.GET.getlist('modulation') selected_polarizations = request.GET.getlist('polarization') selected_satellites = request.GET.getlist('satellite_id') has_kupsat = request.GET.get('has_kupsat') has_valid = request.GET.get('has_valid') try: items_per_page = int(items_per_page) except ValueError: items_per_page = 50 # Only filter objects by selected satellite if provided (no data shown by default) objects = ObjItem.objects.none() # Initially empty if selected_satellites or selected_sat_id: # Handle single satellite from old parameter or multiple from new parameter if selected_sat_id and not selected_satellites: # For backward compatibility - if only single satellite parameter is provided try: selected_sat_id_single = int(selected_sat_id) selected_satellites = [selected_sat_id_single] except ValueError: selected_satellites = [] # Start with the basic filter if any satellites are selected if selected_satellites: # Start with the basic filter - optimized with prefetch_related for all related objects objects = ObjItem.objects.select_related( 'geo_obj', 'updated_by__user', 'created_by__user', ).prefetch_related( 'parameters_obj__id_satellite', 'parameters_obj__polarization', 'parameters_obj__modulation', 'parameters_obj__standard' ).filter(parameters_obj__id_satellite_id__in=selected_satellites) else: # If no satellites are selected, start with all objects objects = ObjItem.objects.select_related( 'geo_obj', 'updated_by__user', 'created_by__user', ).prefetch_related( 'parameters_obj__id_satellite', 'parameters_obj__polarization', 'parameters_obj__modulation', 'parameters_obj__standard' ) # Apply additional filters # Frequency filter if freq_min is not None and freq_min.strip() != '': try: freq_min_val = float(freq_min) objects = objects.filter(parameters_obj__frequency__gte=freq_min_val) except ValueError: pass if freq_max is not None and freq_max.strip() != '': try: freq_max_val = float(freq_max) objects = objects.filter(parameters_obj__frequency__lte=freq_max_val) except ValueError: pass # Range filter if range_min is not None and range_min.strip() != '': try: range_min_val = float(range_min) objects = objects.filter(parameters_obj__freq_range__gte=range_min_val) except ValueError: pass if range_max is not None and range_max.strip() != '': try: range_max_val = float(range_max) objects = objects.filter(parameters_obj__freq_range__lte=range_max_val) except ValueError: pass # SNR filter if snr_min is not None and snr_min.strip() != '': try: snr_min_val = float(snr_min) objects = objects.filter(parameters_obj__snr__gte=snr_min_val) except ValueError: pass if snr_max is not None and snr_max.strip() != '': try: snr_max_val = float(snr_max) objects = objects.filter(parameters_obj__snr__lte=snr_max_val) except ValueError: pass # Symbol rate filter if bod_min is not None and bod_min.strip() != '': try: bod_min_val = float(bod_min) objects = objects.filter(parameters_obj__bod_velocity__gte=bod_min_val) except ValueError: pass if bod_max is not None and bod_max.strip() != '': try: bod_max_val = float(bod_max) objects = objects.filter(parameters_obj__bod_velocity__lte=bod_max_val) except ValueError: pass # Modulation filter if selected_modulations: objects = objects.filter(parameters_obj__modulation__id__in=selected_modulations) # Polarization filter if selected_polarizations: objects = objects.filter(parameters_obj__polarization__id__in=selected_polarizations) # Kupsat coords filter if has_kupsat == '1': # has coords objects = objects.filter(geo_obj__coords_kupsat__isnull=False) elif has_kupsat == '0': # no coords objects = objects.filter(geo_obj__coords_kupsat__isnull=True) # Valid coords filter if has_valid == '1': # has coords objects = objects.filter(geo_obj__coords_valid__isnull=False) elif has_valid == '0': # no coords objects = objects.filter(geo_obj__coords_valid__isnull=True) # Add search functionality - search only in name and location fields to avoid spatial lookup errors if search_query: search_query = search_query.strip() if search_query: # Search in name and location fields to match displayed text objects = objects.filter( models.Q(name__icontains=search_query) | models.Q(geo_obj__location__icontains=search_query) ) else: selected_sat_id = None # Add pagination paginator = Paginator(objects, items_per_page) page_obj = paginator.get_page(page_number) # Prepare the data to include calculated fields for the template processed_objects = [] for obj in page_obj: # Get the first parameter using the prefetched relation to avoid additional queries param = None if hasattr(obj, 'parameters_obj') and obj.parameters_obj.all(): # Get parameters from the prefetched queryset without triggering new query param_list = list(obj.parameters_obj.all()) if param_list: param = param_list[0] # Get first parameter without additional query # Process geo coordinates geo_coords = "-" kupsat_coords = "-" valid_coords = "-" distance_geo_kup = "-" distance_geo_valid = "-" distance_kup_valid = "-" if obj.geo_obj: # Format geo coordinates if obj.geo_obj.coords: longitude = obj.geo_obj.coords.coords[0] latitude = obj.geo_obj.coords.coords[1] lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W" lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S" geo_coords = f"{lat} {lon}" # Format kupsat coordinates if obj.geo_obj.coords_kupsat: longitude = obj.geo_obj.coords_kupsat.coords[0] latitude = obj.geo_obj.coords_kupsat.coords[1] lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W" lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S" kupsat_coords = f"{lat} {lon}" elif obj.geo_obj.coords_kupsat is not None: kupsat_coords = "-" # Format valid coordinates if obj.geo_obj.coords_valid: longitude = obj.geo_obj.coords_valid.coords[0] latitude = obj.geo_obj.coords_valid.coords[1] lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W" lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S" valid_coords = f"{lat} {lon}" elif obj.geo_obj.coords_valid is not None: valid_coords = "-" # Format distances if obj.geo_obj.distance_coords_kup is not None: distance_geo_kup = f"{obj.geo_obj.distance_coords_kup:.3f}" if obj.geo_obj.distance_coords_valid is not None: distance_geo_valid = f"{obj.geo_obj.distance_coords_valid:.3f}" if obj.geo_obj.distance_kup_valid is not None: distance_kup_valid = f"{obj.geo_obj.distance_kup_valid:.3f}" # Extract related object data to avoid additional queries in template satellite_name = "-" frequency = "-" freq_range = "-" polarization_name = "-" bod_velocity = "-" modulation_name = "-" snr = "-" if param: # Get satellite data directly to avoid additional query if hasattr(param, 'id_satellite') and param.id_satellite: satellite_name = param.id_satellite.name if hasattr(param.id_satellite, 'name') else "-" # Get parameter values directly frequency = f"{param.frequency:.3f}" if param.frequency is not None else "-" freq_range = f"{param.freq_range:.3f}" if param.freq_range is not None else "-" bod_velocity = f"{param.bod_velocity:.0f}" if param.bod_velocity is not None else "-" snr = f"{param.snr:.0f}" if param.snr is not None else "-" # Get polarization name directly to avoid additional query if hasattr(param, 'polarization') and param.polarization: polarization_name = param.polarization.name if hasattr(param.polarization, 'name') else "-" # Get modulation name directly to avoid additional query if hasattr(param, 'modulation') and param.modulation: modulation_name = param.modulation.name if hasattr(param.modulation, 'name') else "-" processed_objects.append({ 'id': obj.id, 'name': obj.name or "-", 'satellite_name': satellite_name, 'frequency': frequency, 'freq_range': freq_range, 'polarization': polarization_name, 'bod_velocity': bod_velocity, 'modulation': modulation_name, 'snr': snr, 'geo_coords': geo_coords, 'kupsat_coords': kupsat_coords, 'valid_coords': valid_coords, 'distance_geo_kup': distance_geo_kup, 'distance_geo_valid': distance_geo_valid, 'distance_kup_valid': distance_kup_valid, 'updated_by': obj.updated_by if obj.updated_by else '-', 'obj': obj }) # Get all modulations and polarizations for filter dropdowns from .models import Modulation, Polarization modulations = Modulation.objects.all() polarizations = Polarization.objects.all() context = { 'satellites': satellites, 'selected_satellite_id': selected_sat_id, 'page_obj': page_obj, 'processed_objects': processed_objects, 'items_per_page': items_per_page, 'available_items_per_page': [50, 100, 500, 1000], # Filter values 'freq_min': freq_min, 'freq_max': freq_max, 'range_min': range_min, 'range_max': range_max, 'snr_min': snr_min, 'snr_max': snr_max, 'bod_min': bod_min, 'bod_max': bod_max, 'search_query': search_query, 'selected_modulations': [int(x) for x in selected_modulations if x.isdigit()], 'selected_polarizations': [int(x) for x in selected_polarizations if x.isdigit()], 'selected_satellites': [int(x) for x in selected_satellites if x.isdigit()], 'has_kupsat': has_kupsat, 'has_valid': has_valid, # For filter dropdowns 'modulations': modulations, 'polarizations': polarizations, # Enable full width layout 'full_width_page': True, } return render(request, 'mainapp/objitem_list.html', context) class ObjItemUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView): model = ObjItem form_class = ObjItemForm template_name = 'mainapp/objitem_form.html' success_url = reverse_lazy('home') def test_func(self): return self.request.user.customuser.role in ['admin', 'moderator'] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Добавляем контекст для карты context['LEAFLET_CONFIG'] = { 'DEFAULT_CENTER': (55.75, 37.62), 'DEFAULT_ZOOM': 5, } # Остальной контекст остается без изменений ParameterFormSet = modelformset_factory( Parameter, form=ParameterForm, extra=0, can_delete=True ) if self.object: parameter_queryset = self.object.parameters_obj.all() context['parameter_forms'] = ParameterFormSet( queryset=parameter_queryset, prefix='parameters' ) if hasattr(self.object, 'geo_obj'): context['geo_form'] = GeoForm(instance=self.object.geo_obj, prefix='geo') else: context['geo_form'] = GeoForm(prefix='geo') else: context['parameter_forms'] = ParameterFormSet( queryset=Parameter.objects.none(), prefix='parameters' ) context['geo_form'] = GeoForm(prefix='geo') return context def form_valid(self, form): context = self.get_context_data() parameter_forms = context['parameter_forms'] geo_form = context['geo_form'] # Сохраняем основной объект self.object = form.save(commit=False) self.object.updated_by = self.request.user.customuser self.object.save() # Сохраняем связанные параметры if parameter_forms.is_valid(): instances = parameter_forms.save(commit=False) for instance in instances: instance.save() instance.objitems.set([self.object]) # Сохраняем геоданные geo_instance = None if hasattr(self.object, 'geo_obj'): geo_instance = self.object.geo_obj # Создаем или обновляем гео-объект if geo_instance is None: geo_instance = Geo(objitem=self.object) # Обновляем поля из geo_form if geo_form.is_valid(): geo_instance.location = geo_form.cleaned_data['location'] geo_instance.comment = geo_form.cleaned_data['comment'] geo_instance.is_average = geo_form.cleaned_data['is_average'] # Обрабатываем координаты геолокации geo_longitude = self.request.POST.get('geo_longitude') geo_latitude = self.request.POST.get('geo_latitude') if geo_longitude and geo_latitude: geo_instance.coords = Point(float(geo_longitude), float(geo_latitude)) # Обрабатываем координаты Кубсата kupsat_longitude = self.request.POST.get('kupsat_longitude') kupsat_latitude = self.request.POST.get('kupsat_latitude') if kupsat_longitude and kupsat_latitude: geo_instance.coords_kupsat = Point(float(kupsat_longitude), float(kupsat_latitude)) # Обрабатываем координаты оперативников valid_longitude = self.request.POST.get('valid_longitude') valid_latitude = self.request.POST.get('valid_latitude') if valid_longitude and valid_latitude: geo_instance.coords_valid = Point(float(valid_longitude), float(valid_latitude)) # Обрабатываем дату/время timestamp_date = self.request.POST.get('timestamp_date') timestamp_time = self.request.POST.get('timestamp_time') if timestamp_date and timestamp_time: naive_datetime = datetime.strptime(f"{timestamp_date} {timestamp_time}", "%Y-%m-%d %H:%M") geo_instance.timestamp = naive_datetime geo_instance.save() messages.success(self.request, 'Объект успешно сохранён!') return super().form_valid(form) class ObjItemCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView): model = ObjItem form_class = ObjItemForm template_name = 'mainapp/objitem_form.html' success_url = reverse_lazy('home') def test_func(self): return self.request.user.customuser.role in ['admin', 'moderator'] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) ParameterFormSet = modelformset_factory( Parameter, form=ParameterForm, extra=1, can_delete=True ) context['parameter_forms'] = ParameterFormSet( queryset=Parameter.objects.none(), prefix='parameters' ) context['geo_form'] = GeoForm(prefix='geo') return context def form_valid(self, form): context = self.get_context_data() parameter_forms = context['parameter_forms'] geo_form = context['geo_form'] # Сохраняем основной объект self.object = form.save(commit=False) self.object.created_by = self.request.user.customuser self.object.updated_by = self.request.user.customuser self.object.save() # Сохраняем связанные параметры if parameter_forms.is_valid(): instances = parameter_forms.save(commit=False) for instance in instances: instance.save() instance.objitems.add(self.object) # Создаем гео-объект geo_instance = Geo(objitem=self.object) # Обновляем поля из geo_form if geo_form.is_valid(): geo_instance.location = geo_form.cleaned_data['location'] geo_instance.comment = geo_form.cleaned_data['comment'] geo_instance.is_average = geo_form.cleaned_data['is_average'] # Обрабатываем координаты геолокации geo_longitude = self.request.POST.get('geo_longitude') geo_latitude = self.request.POST.get('geo_latitude') if geo_longitude and geo_latitude: geo_instance.coords = Point(float(geo_longitude), float(geo_latitude)) # Обрабатываем координаты Кубсата kupsat_longitude = self.request.POST.get('kupsat_longitude') kupsat_latitude = self.request.POST.get('kupsat_latitude') if kupsat_longitude and kupsat_latitude: geo_instance.coords_kupsat = Point(float(kupsat_longitude), float(kupsat_latitude)) # Обрабатываем координаты оперативников valid_longitude = self.request.POST.get('valid_longitude') valid_latitude = self.request.POST.get('valid_latitude') if valid_longitude and valid_latitude: geo_instance.coords_valid = Point(float(valid_longitude), float(valid_latitude)) # Обрабатываем дату/время timestamp_date = self.request.POST.get('timestamp_date') timestamp_time = self.request.POST.get('timestamp_time') if timestamp_date and timestamp_time: naive_datetime = datetime.strptime(f"{timestamp_date} {timestamp_time}", "%Y-%m-%d %H:%M") geo_instance.timestamp = naive_datetime geo_instance.save() messages.success(self.request, 'Объект успешно создан!') return super().form_valid(form) class ObjItemDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView): model = ObjItem template_name = 'mainapp/objitem_confirm_delete.html' success_url = reverse_lazy('home') def test_func(self): return self.request.user.customuser.role in ['admin', 'moderator'] def delete(self, request, *args, **kwargs): messages.success(self.request, 'Объект успешно удалён!') return super().delete(request, *args, **kwargs)