WHY COMMON SEARCH FIELDS NOT DISPLAYING AFTER ADDING THEM DYNAMICALLY LIKE OTHER MATERIAL FIELDS? FIX IT (TO MAKE IT ALSO ADDS DYNAMICALLY WITHOUT WRITING LABEL NAME IN VUE COMPONENT CODE) from django.db.models import Q from django.http import JsonResponse from django.shortcuts import get_object_or_404 from rest_framework.response import Response from backend.apps.core import metadata as core_metadata from backend.apps.core import views as core_views from . import models, serializers, filters class BaseMaterialViewSet(core_views.BaseReadOnlyModelViewSet): metadata_class = core_metadata.MaterialGetRequestMetadata def get_common_search_lookup(self, common_search_value): lookup = Q() for field in self.common_search_fields: lookup |= Q({f"{field}__icontains": common_search_value}) return lookup def get_queryset(self): queryset = super().get_queryset() common_search = self.request.GET.get("common_search") # Changed from common_search_query if common_search: query_lookup = self.get_common_search_lookup(common_search) queryset = queryset.filter(query_lookup) return queryset.distinct() # Added distinct() to avoid duplicates def list(self, request, *args, kwargs): queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data)
class AudioMaterialViewSet(BaseMaterialViewSet): queryset = models.AudioMaterial.objects.all() serializer_class = serializers.AudioMaterialListSerializer filterset_class = filters.AudioMaterialFilter serializer_classes = { "retrieve": serializers.AudioMaterialDetailSerializer, } common_search_fields = ["keywords", "annotation", "persons", "title"]
class ImageMaterialViewSet(BaseMaterialViewSet): queryset = models.ImageMaterial.objects.all() serializer_class = serializers.ImageMaterialListSerializer filterset_class = filters.ImageMaterialFilter serializer_classes = { "retrieve": serializers.ImageMaterialDetailSerializer, } common_search_fields = ["keywords", "annotation", "persons", "author"]
class VideoMaterialViewSet(BaseMaterialViewSet): queryset = models.VideoMaterial.objects.all() serializer_class = serializers.VideoMaterialListSerializer filterset_class = filters.VideoMaterialFilter serializer_classes = { "retrieve": serializers.VideoMaterialDetailSerializer, } common_search_fields = ["keywords", "annotation", "persons", "title", "description"]
def get_video_url(request, video_id): video = get_object_or_404(models.VideoFile, id=video_id) return JsonResponse({"url": video.file_path.url})
def get_audio_url(request, audio_id): audio = get_object_or_404(models.AudioFile, id=audio_id) return JsonResponse({"url": audio.file_path.url})
def get_image_url(request, image_id): image = get_object_or_404(models.ImageFile, id=image_id) return JsonResponse({"url": image.file_path.url}) DO IT WITHOUT METADATA CHANGING<script setup>
import { ref, watch, onMounted } from "vue";
import BaseInput from "@/components/common/BaseInput.vue";
import DateInput from "@/components/common/DateInput.vue";
import { useRoute, useRouter } from "vue-router";
import { getOptions } from "@/api/default_api_promises";

const route = useRoute();
const router = useRouter();
const commonSearchInput = ref(route.query.common_search || '');
const advancedSearchOn = ref(false);
const formData = ref({});
const metadata = ref({});
const isLoading = ref(false);

// Types of search with their labels and corresponding API endpoints
const typesOfSearch = [
  { key: 'common', label: "Общий поиск", endpoint: '/api/materials/' },
  { key: 'image', label: "Поиск фотодокументов", endpoint: '/api/image-materials/' },
  { key: 'audio', label: "Поиск фонодокументов", endpoint: '/api/audio-materials/' },
  { key: 'video', label: "Поиск киновидеодокументов", endpoint: '/api/video-materials/' }
];

const currentSearchWindow = ref("common"); // Default to common search

async function loadMetadata() {
  try {
    isLoading.value = true;
    const searchTypes = ['image', 'audio', 'video'];
    
    for (const type of searchTypes) {
      const searchType = typesOfSearch.find(t => t.key === type);
      if (searchType) {
        const { data } = await getOptions({ url: searchType.endpoint })();
        metadata.value[type] = formatFields(data.actions.POST || data.actions.list);
      }
    }
  } catch (error) {
    console.error('Error loading metadata:', error);
  } finally {
    isLoading.value = false;
  }
}

function formatFields(listData) {
  if (!listData) return [];
  
  const excludeFields = ['id', 'media_files'];
  const formattedFields = Object.entries(listData)
    .filter(([key]) => !excludeFields.includes(key))
    .map(([key, value]) => ({
      name: key,
      placeholder: value.label || key,
      type: determineFieldType(key, value)
    }));

  // Group fields into rows of two
  return formattedFields.reduce((rows, field, index) => {
    if (index % 2 === 0) {
      rows.push([field]);
    } else {
      rows[rows.length - 1].push(field);
    }
    return rows;
  }, []);
}

function determineFieldType(key, value) {
  if (key.includes('date')) return 'date';
  if (value.type === 'datetime') return 'date';
  return 'text';
}

function resetFormData() {
  formData.value = {};
}

function buildSearchQuery() {
  const query = {
    page: 1,
    page_size: 10
  };

  // Add common search if present
  if (commonSearchInput.value?.trim()) {
    query.common_search = commonSearchInput.value.trim();
  }

  // Add advanced search parameters if enabled
  if (advancedSearchOn.value) {
    query.material_type = currentSearchWindow.value;

    // Process form data
    Object.entries(formData.value).forEach(([key, value]) => {
      if (value) {
        if (key.includes('date_from') || key.includes('date_after')) {
          query.date_from = value;
        } else if (key.includes('date_to') || key.includes('date_before')) {
          query.date_to = value;
        } else {
          query[`${key}__icontains`] = value;
        }
      }
    });
  }

  return query;
}

async function handleSubmit() {
  const query = buildSearchQuery();
  if (Object.keys(query).length > 2) { // More than just page and page_size
    await router.push({ name: "search", query });
  }
}

function scrollToOneThirdHeight() {
  const height = window.innerHeight;
  window.scrollTo({
    top: height / 3,
    behavior: 'smooth'
  });
}

// Watchers
watch(advancedSearchOn, (newVal) => {
  if (newVal) {
    scrollToOneThirdHeight();
  } else {
    resetFormData();
  }
});

watch(currentSearchWindow, () => {
  resetFormData();
});

// Initialize component
onMounted(async () => {
  await loadMetadata();
  
  // Initialize form data from URL query parameters
  if (route.query) {
    Object.entries(route.query).forEach(([key, value]) => {
      if (key.endsWith('__icontains')) {
        formData.value[key.replace('__icontains', '')] = value;
      } else if (key === 'date_from') {
        formData.value.date_from = value;
      } else if (key === 'date_to') {
        formData.value.date_to = value;
      }
    });
  }
});
</script>

<template>
  <div class="home">
    <div id="datepicker-container" />
    <div class="wrapper wrapper__indent">
      <!-- Main Search Box -->
      <div class="search-box">
        <base-input
          v-model="commonSearchInput"
          class="main-search-input"
          placeholder="Поиск..."
          @keyup.enter="handleSubmit"
        />
        <v-btn
          @click="handleSubmit"
          :loading="isLoading"
          variant="outlined"
          class="search-button"
        >
          <v-icon size="28" class="search-icon" color="primary">mdi-magnify</v-icon>
        </v-btn>
      </div>

      <!-- Advanced Search Toggle -->
      <div class="search-switch">
        <v-switch
          v-model="advancedSearchOn"
          label="Расширенный поиск"
          color="primary-blue"
        />
      </div>

      <!-- Advanced Search Section -->
      <div v-if="advancedSearchOn" class="advanced-search-box">
        <v-window
          v-model="currentSearchWindow"
          show-arrows
          :continuous="true"
          class="search-window-container"
        >
          <!-- Navigation Arrows -->
          <template v-slot:prev="{ props }">
            <button @click="props.onClick" class="arrow-button prev-arrow">
              <v-icon size="36" color="primary">mdi-chevron-left</v-icon>
            </button>
          </template>

          <template v-slot:next="{ props }">
            <button @click="props.onClick" class="arrow-button next-arrow">
              <v-icon size="36" color="primary">mdi-chevron-right</v-icon>
            </button>
          </template>

          <!-- Search Windows -->
          <v-window-item
            v-for="type in typesOfSearch"
            :key="type.key"
            :value="type.key"
          >
            <v-card class="d-flex justify-center">
              <div class="search-window">
                <p class="window-title">{{ type.label }}</p>
                <v-divider class="mb-4" />

                <template v-if="metadata[type.key]">
                  <div
                    v-for="(row, rowIndex) in metadata[type.key]"
                    :key="rowIndex"
                    class="search-row"
                  >
                    <template v-for="field in row" :key="field.name">
                      <date-input
                        v-if="field.type === 'date'"
                        v-model="formData[field.name]"
                        :placeholder="field.placeholder"
                        class="search-input"
                      />
                      <base-input
                        v-else
                        v-model="formData[field.name]"
                        :placeholder="field.placeholder"
                        class="search-input"
                      />
                    </template>
                  </div>
                </template>
              </div>
            </v-card>
          </v-window-item>
        </v-window>
      </div>

      <router-view :key="route.fullPath" />
    </div>
  </div>
</template>

<style lang="scss" scoped>
.home {
  padding: 20px;
}

.search-box {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-bottom: 20px;
  gap: 10px;
}

.main-search-input {
  width: 400px;
  padding: 24px;
}

.search-button {
  height: 60px;
  width: 60px;
  border: 1px solid var(--v-border-color);
  transition: all 0.3s ease;

  &:hover {
    background-color: var(--v-primary-base);
    .search-icon {
      color: white !important;
    }
  }
}

.search-icon {
  transition: color 0.3s ease;
}

.search-switch {
  display: flex;
  justify-content: center;
  margin-bottom: 20px;
}

.advanced-search-box {
  display: flex;
  justify-content: center;
  position: relative;
}

.search-window-container {
  width: 1000px;
  position: relative;
}

.search-window {
  width: 800px;
  padding: 24px;
  margin: 0 auto;
}

.window-title {
  font-size: 18px;
  color: var(--v-primary-base);
  margin-bottom: 16px;
  font-weight: 500;
}

.search-row {
  display: flex;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 16px;
}

.search-input {
  flex: 1;
  min-width: 0;
}

.arrow-button {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: transparent;
  border: none;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.3s ease;
  z-index: 1;
  padding: 8px;
  border-radius: 50%;

  &:hover {
    background-color: rgba(0, 0, 0, 0.05);
  }

  &.prev-arrow {
    left: 10px;
  }

  &.next-arrow {
    right: 10px;
  }
}

.advanced-search-box:hover .arrow-button {
  opacity: 1;
}

@media (max-width: 1024px) {
  .search-window-container {
    width: 100%;
  }

  .search-window {
    width: 90%;
  }

  .search-row {
    flex-direction: column;
    gap: 10px;
  }
}

@media (max-width: 600px) {
  .main-search-input {
    width: 100%;
  }

  .search-box {
    flex-direction: column;
  }

  .search-button {
    width: 100%;
  }
}
</style>from django_filters import rest_framework as filters
from django.db.models import Q
from .models import ImageMaterial, AudioMaterial, VideoMaterial

class BaseMaterialFilter(filters.FilterSet):
    """Base filter class with common fields for all material types"""
    common_search = filters.CharFilter(method='filter_common_search', label='Общий поиск')
    date_from = filters.DateFilter(field_name='creation_date', lookup_expr='gte', label='Дата от')
    date_to = filters.DateFilter(field_name='creation_date', lookup_expr='lte', label='Дата до')
    archive_number = filters.CharFilter(field_name='archive_number', lookup_expr='icontains', label='Архивный номер')

    def filter_common_search(self, queryset, name, value):
        if not value:
            return queryset
        return queryset.filter(self.get_common_search_lookup(value)).distinct()

    def get_common_search_lookup(self, value):
        """Override in child classes to define common search fields"""
        raise NotImplementedError

class ImageMaterialFilter(BaseMaterialFilter):
    keywords = filters.CharFilter(field_name='keywords', lookup_expr='icontains', label='Ключевые слова')
    persons = filters.CharFilter(field_name='persons', lookup_expr='icontains', label='Персоналии')
    creation_place = filters.CharFilter(field_name='creation_place', lookup_expr='icontains', label='Место съёмки')
    author = filters.CharFilter(field_name='author', lookup_expr='icontains', label='Автор фотографии')
    annotation = filters.CharFilter(field_name='annotation', lookup_expr='icontains', label='Аннотация')

    class Meta:
        model = ImageMaterial
        fields = ['keywords', 'persons', 'creation_place', 'author', 'annotation']

    def get_common_search_lookup(self, value):
        return Q(keywords__icontains=value) | \
               Q(annotation__icontains=value) | \
               Q(persons__icontains=value) | \
               Q(author__icontains=value)

class AudioMaterialFilter(BaseMaterialFilter):
    title = filters.CharFilter(field_name='title', lookup_expr='icontains', label='Название')
    keywords = filters.CharFilter(field_name='keywords', lookup_expr='icontains', label='Ключевые слова')
    annotation = filters.CharFilter(field_name='annotation', lookup_expr='icontains', label='Аннотация')
    text_author = filters.CharFilter(field_name='text_author', lookup_expr='icontains', label='Автор текста')
    performer = filters.CharFilter(field_name='performer', lookup_expr='icontains', label='Исполнитель')
    persons = filters.CharFilter(field_name='persons', lookup_expr='icontains', label='Персоналии')
    music_author = filters.CharFilter(field_name='music_author', lookup_expr='icontains', label='Автор музыки')
    creation_place = filters.CharFilter(field_name='creation_place', lookup_expr='icontains', label='Место записи')

    class Meta:
        model = AudioMaterial
        fields = ['title', 'keywords', 'annotation', 'text_author', 'performer',
                 'persons', 'music_author', 'creation_place']

    def get_common_search_lookup(self, value):
        return Q(keywords__icontains=value) | \
               Q(annotation__icontains=value) | \
               Q(persons__icontains=value) | \
               Q(title__icontains=value)

class VideoMaterialFilter(BaseMaterialFilter):
    # Video-specific fields
    title = filters.CharFilter(field_name='title', lookup_expr='icontains', label='Название документа')
    keywords = filters.CharFilter(field_name='keywords', lookup_expr='icontains', label='Ключевые слова')
    annotation = filters.CharFilter(field_name='annotation', lookup_expr='icontains', label='Аннотация')
    persons = filters.CharFilter(field_name='persons', lookup_expr='icontains', label='Персоналии')
    roles = filters.CharFilter(field_name='roles', lookup_expr='icontains', label='Роли')
    director = filters.CharFilter(field_name='director', lookup_expr='icontains', label='Режиссер')
    cameraman = filters.CharFilter(field_name='cameraman', lookup_expr='icontains', label='Оператор')
    composer = filters.CharFilter(field_name='composer', lookup_expr='icontains', label='Композитор')
    studio = filters.CharFilter(field_name='studio', lookup_expr='icontains', label='Студия')

    class Meta:
        model = VideoMaterial
        fields = ['title', 'keywords', 'annotation', 'persons', 'roles',
                 'director', 'cameraman', 'composer', 'studio']

    def get_common_search_lookup(self, value):
        return (Q(keywords__icontains=value) |
                Q(annotation__icontains=value) |
                Q(persons__icontains=value) |
                Q(title__icontains=value) |
                Q(description__icontains=value)) DO IT WITHOUT METADATA CHANGING AND WRITING LABEL NAMES IN VUE COMPONENY