You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:如何在Django Rest Framework(PostgreSQL)与Angular 11项目中实现多字段多值筛选功能

Hey there! Let's break down how to implement this multi-value filtering feature for your Django DRF + Angular 11 project. I’ve dealt with similar scenarios before, so here’s a step-by-step approach that’ll work smoothly with your PostgreSQL backend.

Backend Implementation (Django REST Framework + PostgreSQL)

First, we’ll use the django-filter library—it’s the most straightforward way to handle complex filtering in DRF, especially for many-to-many fields.

1. Install & Configure django-filter

Start by installing the package:

pip install django-filter

Then add it to your settings.py to enable it in DRF:

INSTALLED_APPS = [
    # ... your existing apps
    'django_filters',
]

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend',
        # Keep any other backends you’re using (like search/sort)
    ],
}

2. Create a FilterSet for Project Model

This class will define how we filter on your many-to-many fields. We’ll support two common scenarios:

  • Any match: Projects that have at least one of the selected values (e.g., C# OR Java)
  • All match: Projects that have all selected values (e.g., C# AND Java)

Here’s the code for the filter:

from django_filters import rest_framework as filters
from .models import Project, Domain, Environment, ToolsAndTechnology

class ProjectFilter(filters.FilterSet):
    # Option 1: Filter for projects with ANY of the selected tools (default behavior)
    tools = filters.ModelMultipleChoiceFilter(
        field_name='tools_and_technology__id',
        queryset=ToolsAndTechnology.objects.all(),
        lookup_expr='in',
        label='Tool IDs (any match)'
    )
    
    # Option 2: Filter for projects with ALL selected tools (custom method)
    # Uncomment this if you need "all match" instead of "any match"
    # tools = filters.ModelMultipleChoiceFilter(
    #     queryset=ToolsAndTechnology.objects.all(),
    #     method='filter_all_tools',
    #     label='Tool IDs (all required)'
    # )

    domains = filters.ModelMultipleChoiceFilter(
        field_name='domain__id',
        queryset=Domain.objects.all(),
        lookup_expr='in',
        label='Domain IDs (any match)'
    )

    environments = filters.ModelMultipleChoiceFilter(
        field_name='environment__id',
        queryset=Environment.objects.all(),
        lookup_expr='in',
        label='Environment IDs (any match)'
    )

    # Custom method for "all match" on tools
    def filter_all_tools(self, queryset, name, value):
        for tool in value:
            queryset = queryset.filter(tools_and_technology=tool)
        return queryset

    class Meta:
        model = Project
        fields = ['tools', 'domains', 'environments']

3. Update Your ViewSet

Use the ProjectFilter in your viewset, and optimize queries with prefetch_related to avoid N+1 database hits (critical for performance with many-to-many fields):

from rest_framework import viewsets
from .models import Project
from .serializers import ProjectSerializer
from .filters import ProjectFilter

class ProjectViewSet(viewsets.ReadOnlyModelViewSet):
    # Prefetch related many-to-many fields to optimize performance
    queryset = Project.objects.all().prefetch_related(
        'domain', 'environment', 'tools_and_technology'
    )
    serializer_class = ProjectSerializer
    filterset_class = ProjectFilter

4. Update Your Serializer

Make sure your serializer includes the related fields so frontend can display them:

from rest_framework import serializers
from .models import Project, Domain, Environment, ToolsAndTechnology

class DomainSerializer(serializers.ModelSerializer):
    class Meta:
        model = Domain
        fields = ['id', 'name'] # Adjust fields to match your Domain model

class EnvironmentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Environment
        fields = ['id', 'name']

class ToolsAndTechnologySerializer(serializers.ModelSerializer):
    class Meta:
        model = ToolsAndTechnology
        fields = ['id', 'name']

class ProjectSerializer(serializers.ModelSerializer):
    domain = DomainSerializer(many=True)
    environment = EnvironmentSerializer(many=True)
    tools_and_technology = ToolsAndTechnologySerializer(many=True)

    class Meta:
        model = Project
        fields = ['id', 'title', 'domain', 'environment', 'tools_and_technology', 'photo']
Frontend Implementation (Angular 11)

Now let’s build the Angular side to send filter parameters to the backend and display results.

1. Create a Project Service

This service will handle HTTP requests to fetch filtered projects:

import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Project } from './project.model';

@Injectable({ providedIn: 'root' })
export class ProjectService {
  private apiUrl = '/api/projects/'; // Adjust to your backend API endpoint

  constructor(private http: HttpClient) {}

  getProjects(filters: { tools?: number[], domains?: number[], environments?: number[] }): Observable<Project[]> {
    let params = new HttpParams();

    // Append multiple values for each filter (e.g., ?tools=1&tools=2)
    if (filters.tools?.length) {
      filters.tools.forEach(id => params = params.append('tools', id.toString()));
    }
    if (filters.domains?.length) {
      filters.domains.forEach(id => params = params.append('domains', id.toString()));
    }
    if (filters.environments?.length) {
      filters.environments.forEach(id => params = params.append('environments', id.toString()));
    }

    return this.http.get<Project[]>(this.apiUrl, { params });
  }

  // Add methods to fetch tool/domain/environment options for dropdowns
  getTools(): Observable<ToolsAndTechnology[]> {
    return this.http.get<ToolsAndTechnology[]>('/api/tools/');
  }
  // Repeat similar methods for domains and environments
}

2. Build the Filter Component

Create a component with multi-select dropdowns for each filter, and a button to apply filters:

import { Component, OnInit } from '@angular/core';
import { ProjectService } from './project.service';
import { Project, ToolsAndTechnology, Domain, Environment } from './project.model';

@Component({
  selector: 'app-project-filter',
  templateUrl: './project-filter.component.html',
  styleUrls: ['./project-filter.component.css']
})
export class ProjectFilterComponent implements OnInit {
  selectedTools: number[] = [];
  selectedDomains: number[] = [];
  selectedEnvironments: number[] = [];
  projects: Project[] = [];
  tools: ToolsAndTechnology[] = [];
  domains: Domain[] = [];
  environments: Environment[] = [];

  constructor(private projectService: ProjectService) {}

  ngOnInit(): void {
    // Fetch options for dropdowns on component load
    this.projectService.getTools().subscribe(tools => this.tools = tools);
    this.projectService.getDomains().subscribe(domains => this.domains = domains);
    this.projectService.getEnvironments().subscribe(envs => this.environments = envs);
  }

  applyFilters(): void {
    this.projectService.getProjects({
      tools: this.selectedTools,
      domains: this.selectedDomains,
      environments: this.selectedEnvironments
    }).subscribe(projects => {
      this.projects = projects;
    });
  }
}

3. Component Template

Add the HTML for dropdowns and project display:

<div class="filter-panel">
  <div class="filter-group">
    <label>Tools & Technology</label>
    <select multiple [(ngModel)]="selectedTools" class="multi-select">
      <option *ngFor="let tool of tools" [value]="tool.id">{{ tool.name }}</option>
    </select>
  </div>

  <div class="filter-group">
    <label>Domains</label>
    <select multiple [(ngModel)]="selectedDomains" class="multi-select">
      <option *ngFor="let domain of domains" [value]="domain.id">{{ domain.name }}</option>
    </select>
  </div>

  <div class="filter-group">
    <label>Environments</label>
    <select multiple [(ngModel)]="selectedEnvironments" class="multi-select">
      <option *ngFor="let env of environments" [value]="env.id">{{ env.name }}</option>
    </select>
  </div>

  <button (click)="applyFilters()" class="filter-btn">Apply Filters</button>
</div>

<div class="projects-grid">
  <div *ngFor="let project of projects" class="project-card">
    <h3>{{ project.title }}</h3>
    <p><strong>Domains:</strong> {{ project.domain.map(d => d.name).join(', ') }}</p>
    <p><strong>Tools:</strong> {{ project.tools_and_technology.map(t => t.name).join(', ') }}</p>
    <p><strong>Environments:</strong> {{ project.environment.map(e => e.name).join(', ') }}</p>
    <img *ngIf="project.photo" [src]="project.photo" alt="{{ project.title }} thumbnail" class="project-photo">
  </div>
</div>
Quick Optimization Tips
  • Backend: prefetch_related is key here—it tells PostgreSQL to fetch all related data in a single query instead of one per project.
  • Frontend: Add debouncing if you want to auto-apply filters as the user selects options (use rxjs debounceTime to avoid excessive API calls).
  • PostgreSQL: Your many-to-many join tables already have indexes on foreign keys (thanks to Django), so no extra index setup is needed for good performance.

内容的提问来源于stack exchange,提问作者Md Sabbir Hossain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 09:17:43