简体   繁体   中英

How to serialize a Join query set from several tables in Django Rest Framework

Sometimes, frameworks make things more complicated instead of simplifying them. I would like to serialize a join like this one

queryset = Cities.objects.raw("SELECT 1 as id, cities.name as ci, states.name as s, countries.name as co FROM cities JOIN states ON states.id = cities.state_id LEFT OUTER JOIN countries ON countries.id = states.country_id WHERE cities.name = %s", [city])

or like this one, if raw queries are not recommended

city = self.request.query_params.get("cityname")

As you can see this is a reverse join. The idea is to serialize a result set like this one

0:  
name:   "Guadalajara"
state:  "Castilla La Mancha"
country:    "Spain"

1:
name: "Guadalajara"
state: "Jalisco"
coutry: "Mexico"

Two cities having the same name but belonging to different states and countries. I need this to implement a sort of autocomplete feature. This is actually pseudocode but it gives an idea about the kind of JSON result I would like to get.

I read the documentation and I searched the internet, I found nothing clear about how to do this.

I'm new to Django and I'm completely lost, this is a simple task that would be easy to do manually, but I have no idea about how to achieve this using Django Rest Framework (or any other tool from Django).

Any help would be really appreciated.

The DRF provides generic views to serialize a collection of objects using a particular model. Let's say you have a model called City like this:

from django.db import models

class City(models.Model):
    name = models.CharField()
    state = models.CharField()
    country = models.CharField()

And a serializer like this one:

from rest_framework import serializers

class CitySerializer(serializers.ModelSerializer):
    class Meta:
         model = City
         fields = ['name', 'state', 'country']

In your view you'd need to inherit from generics.ListAPIView like so:

from rest_framwork.generics import ListAPIView

class CityList(ListAPIView):
    serializer_class = CitySerializer

    def get_queryset(self):
        return City.objects.filter(
            name=self.request.query_params.get('cityname')
        )

The ListAPIView class serializes a list of City instances for you quite easily. You can find further explanations here: https://www.django-rest-framework.org/api-guide/generic-views/#listapiview

I found a solution, I don't know if it's the best solution but it worked for me and I would like to share it. If someone find a better way to do this, you are welcome to suggest changes.

My model:

""" The following 3 models deal with tables that already exist in the database """
class Countries(models.Model):
    name = models.CharField(max_length=100)
    iso3 = models.CharField(max_length=3, blank=True, null=True)
    iso2 = models.CharField(max_length=2, blank=True, null=True)
    phonecode = models.CharField(max_length=255, blank=True, null=True)
    capital = models.CharField(max_length=255, blank=True, null=True)
    currency = models.CharField(max_length=255, blank=True, null=True)
    currency_symbol = models.CharField(max_length=255, blank=True, null=True)
    tld = models.CharField(max_length=255, blank=True, null=True)
    native = models.CharField(max_length=255, blank=True, null=True)
    region = models.CharField(max_length=255, blank=True, null=True)
    subregion = models.CharField(max_length=255, blank=True, null=True)
    timezones = models.TextField(blank=True, null=True)
    translations = models.TextField(blank=True, null=True)
    latitude = models.DecimalField(max_digits=10, decimal_places=8, blank=True, null=True)
    longitude = models.DecimalField(max_digits=11, decimal_places=8, blank=True, null=True)
    emoji = models.CharField(max_length=191, blank=True, null=True)
    emojiu = models.CharField(db_column='emojiU', max_length=191, blank=True, null=True)  # Field name made lowercase.
    created_at = models.DateTimeField(blank=True, null=True)
    updated_at = models.DateTimeField()
    flag = models.IntegerField()
    wikidataid = models.CharField(db_column='wikiDataId', max_length=255, blank=True, null=True)  # Field name made lowercase.

    class Meta:
        managed = False
        db_table = 'countries'


class States(models.Model):
    name = models.CharField(max_length=255)
    country = models.ForeignKey(Countries, models.DO_NOTHING)
    country_code = models.CharField(max_length=2)
    fips_code = models.CharField(max_length=255, blank=True, null=True)
    iso2 = models.CharField(max_length=255, blank=True, null=True)
    latitude = models.DecimalField(max_digits=10, decimal_places=8, blank=True, null=True)
    longitude = models.DecimalField(max_digits=11, decimal_places=8, blank=True, null=True)
    created_at = models.DateTimeField(blank=True, null=True)
    updated_at = models.DateTimeField()
    flag = models.IntegerField()
    wikidataid = models.CharField(db_column='wikiDataId', max_length=255, blank=True, null=True)  # Field name made lowercase.

    class Meta:
        managed = False
        db_table = 'states'


class Cities(models.Model):
    name = models.CharField(max_length=255)
    state = models.ForeignKey('States', models.DO_NOTHING)
    state_code = models.CharField(max_length=255)
    country = models.ForeignKey('Countries', models.DO_NOTHING)
    country_code = models.CharField(max_length=2)
    latitude = models.DecimalField(max_digits=10, decimal_places=8)
    longitude = models.DecimalField(max_digits=11, decimal_places=8)
    created_at = models.DateTimeField()
    updated_at = models.DateTimeField()
    flag = models.IntegerField()
    wikidataid = models.CharField(db_column='wikiDataId', max_length=255, blank=True, null=True)  # Field name made lowercase.

    class Meta:
        managed = False
        db_table = 'cities'

Sure, the database comes from this project, if someone needs it https://dr5hn.github.io/countries-states-cities-database/

Here's my View

class CityViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = CitiesSerializer
    def get_queryset(self):
        city = self.request.query_params.get("cityname")
        queryset = Cities.objects.filter(name__startswith=city)
        return queryset

And finally my serializers.py

""" Fields:
'name','country','country-code','fips_code','iso2','latitude','longitude','created_at','updated_at','flag','wikidataid'
"""
class StatesSerializer(serializers.ModelSerializer):    
    class Meta:
        model = States
        fields = ['name', 'iso2']

""" Fields 
'name','iso3','iso2','phonecode','capital','currency_symbol','tld','native','region','subregion','timezones','translations','latitude','longitude','emoji','emojiu','created_at','updated_at','flag','wikidataid'
"""
class CountriesSerializer(serializers.ModelSerializer):
    class Meta:
        model = Countries
        fields = ['name', 'iso2', ]#'translations']


"""Fields:
'name','state','state_code','country','country_code','latitude','longitude','created_at','updated_at','flag','wikidataid'
"""
class CitiesSerializer(serializers.ModelSerializer):
    state = StatesSerializer(many=False)
    country = CountriesSerializer(many=False)

    class Meta:
        model = Cities
        fields = ['name', 'state', 'country', 'latitude', 'longitude']

With this code I will get the right expected JSon output. Sure it must be read by a proper forntend, like JQuery, Vue or even Vanilla JavaScript, but the object of the question is fullfilled.

If you know a better way, again, don't hesitate.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM