如何在Django模型中使用抽象父类进行过滤或查询?
Hey there! Let's break down how to make this work for your Django DRF setup. Since you're using an abstract PrimaryAddress model with subclasses like MedicalStorePrimaryAddress (and more coming), here's a step-by-step approach to query all related entities filtered by city, latitude/longitude, and distance:
Because abstract models don't create their own database tables, each subclass (like MedicalStorePrimaryAddress, HospitalPrimaryAddress) lives in its own table. To query across all of them, you'll need to union their querysets:
from .models import MedicalStorePrimaryAddress, HospitalPrimaryAddress # Union all address subclass querysets combined_addresses = MedicalStorePrimaryAddress.objects.all() | HospitalPrimaryAddress.objects.all()
As you add more subclasses later, just extend this union with the new model's queryset.
Filtering by city is straightforward—just chain a filter() call to the combined queryset. In a DRF view, you'll typically pull the city from request query parameters:
def get_queryset(self): combined_addresses = MedicalStorePrimaryAddress.objects.all() | HospitalPrimaryAddress.objects.all() # Filter by city if provided in query params (e.g., ?city=NewYork) city = self.request.query_params.get('city') if city: combined_addresses = combined_addresses.filter(city__iexact=city) return combined_addresses
To filter by distance from a given latitude/longitude, you can use a custom database function to calculate the Haversine distance (works with MySQL and PostgreSQL with minor tweaks). First, define the function:
from django.db.models import F, Func, FloatField class Haversine(Func): function = 'HAVERSINE' output_field = FloatField()
Then, in your view, add distance filtering using the user-provided lat/lon:
def get_queryset(self): combined_addresses = MedicalStorePrimaryAddress.objects.all() | HospitalPrimaryAddress.objects.all() # City filter (as before) city = self.request.query_params.get('city') if city: combined_addresses = combined_addresses.filter(city__iexact=city) # Distance filter (e.g., ?latitude=40.7128&longitude=-74.0060&distance=10) lat = self.request.query_params.get('latitude') lon = self.request.query_params.get('longitude') max_distance = self.request.query_params.get('distance', 10) # Default to 10km if lat and lon: try: lat_float = float(lat) lon_float = float(lon) max_distance_float = float(max_distance) combined_addresses = combined_addresses.annotate( distance=Haversine(F('latitude'), F('longitude'), lat_float, lon_float) ).filter(distance__lte=max_distance_float) except ValueError: # Handle invalid number inputs gracefully pass return combined_addresses
Note: For PostgreSQL, you might need to use ST_Distance instead of HAVERSINE (you'll need PostGIS installed for that). Adjust the Haversine function accordingly if you're using a different database.
Since your queryset includes multiple address subclasses, you'll need a serializer that can handle different model types. You can either build a custom serializer or use the django-rest-polymorphic package for simplicity.
Custom Polymorphic Serializer
from rest_framework import serializers from .models import MedicalStorePrimaryAddress, HospitalPrimaryAddress class MedicalStoreAddressSerializer(serializers.ModelSerializer): class Meta: model = MedicalStorePrimaryAddress fields = '__all__' class HospitalAddressSerializer(serializers.ModelSerializer): class Meta: model = HospitalPrimaryAddress fields = '__all__' class AddressSerializer(serializers.Serializer): def to_representation(self, instance): if isinstance(instance, MedicalStorePrimaryAddress): return MedicalStoreAddressSerializer(instance).data elif isinstance(instance, HospitalPrimaryAddress): return HospitalAddressSerializer(instance).data # Add new subclass serializers here as you create them return super().to_representation(instance)
Using django-rest-polymorphic
Install the package first, then define a polymorphic serializer:
pip install django-rest-polymorphic
from rest_polymorphic.serializers import PolymorphicSerializer from .models import PrimaryAddress, MedicalStorePrimaryAddress, HospitalPrimaryAddress class AddressPolymorphicSerializer(PolymorphicSerializer): model_serializer_mapping = { MedicalStorePrimaryAddress: MedicalStoreAddressSerializer, HospitalPrimaryAddress: HospitalAddressSerializer, # Add more mappings as you add subclasses }
If you find unioning querysets cumbersome long-term, consider changing PrimaryAddress from an abstract model to a concrete one, then use multi-table inheritance for subclasses. This lets you query directly from the parent model:
class PrimaryAddress(models.Model): latitude = models.FloatField() longitude = models.FloatField() city = models.CharField(max_length=100) # Other shared fields # Remove abstract = True class MedicalStorePrimaryAddress(PrimaryAddress): # Medical store-specific fields store_id = models.ForeignKey(MedicalStore, on_delete=models.CASCADE)
Now you can query PrimaryAddress.objects.all() and filter directly, then use select_related to fetch subclass data if needed.
内容的提问来源于stack exchange,提问作者Maverick

