موقعیت جغرافیایی با drf, django و postgres

اولین مقاله ای هست که مینویسم پس اگه موردی داشت حتما بهم گوش زد کنید :) خوشحال میشم بازخورد هاتون رو بشنوم.توی این مقاله به صورت خلاصه یه اپلیکیشن با پشتیبانی از موقعیت جغرافیایی, نقشه گرافیکی در django admin و ملزومات rest api رو شرح میدم.فرض من بر اینه که شما با پایتون, django و postgres آشنایی دارین به همین خاطر سعی کردم فقط نکات جدید و کلیدی در رابطه با موضوع مقاله رو اینجا بیان کنم. اگه آشنایی ندارید یا دوست دارید یه مقاله خوب و کامل در این رابطه بخونید مقاله سایت Digital Ocean میتونه کمکتون کنه.Spatial Database:یه دیتا بیس که برای ذخیره و بازیابی اطلاعات محیط جغرافیایی (Point, Line, Polygon) ایجاد شده. البته postgres به صورت عادی این امکان رو نداره و باید یه افزونه بهش اضافه بشه که در ادامه با هم انجام میدیم.قبل از اینکه افزونه postgres رو بهش اضافه کنیم, چند مورد باید روی سیستم نصب بشه. این بسته ها به صورت مستقیم یا غیر مستقیم توسط کتابخانه های جغرافیایی استفاده می شن.sudo apt-get install binutils libproj-dev gdal-binمتاسفانه یا خوشبختانه من فقط نصب debian/ubuntu رو میدونم برای mac و ویندوز به سایت مرجع رجوع شود. :) اگه نیازی به اطلاعات بیشتر برای نصب داشتید به این لینک برید.حالا باید افزونه postgis رو به postgres اضافه کنیم برای این کار به دیتابیس مد نظرتون برید و افزونه postgis رو اضافه کنید.sudo su postgres psql <db name> CREATE EXTENSION postgis; تا اینجا یکم قضیه دواپزی بود بریم قسمت دولوپری :). برای راحتی خودم :)) model رو ساده میزینم با فرض اینکه میخایم فقط یه نقطه مختصات جغرافیایی ذخیره بشه. model میشه یه همچین چیزی:from django.contrib.gis.db import models class Location(models.Model): location_point = models.PointField(null=True, blank=True) # other fields ... بعد از اینکه makemigration و migrate رو زدین(اسم app رو هم به settingsاصافه کنید). برای قسمت ادمین هم باید این کد ها رو داشته باشیم. با فرض اینکه اسم app ما location هست.from django.contrib import admin from location.models import Location # Suppose location is the name of app :)@admin.register(Location) class LocationAdmin(admin.ModelAdmin): fields = ( 'location_point', )خب حالا ببینیم ادمین django چه شکلیاس.نقشه پیش فرض django adminبچه ها اون گربهه ایران ماست :)) این نقشه پیش فرض django هست ولی نقشه جذابی نیست چون شهر یا خیابان رو نشون نمیده. برای ساپورت باید از یه app django استفاده کنیم بین چندتایی که وجود داشت من django-location-field رو پیشنهاد میکنم. استفاده از این app هم خیلی راحته, اول اون رو در محیط پروژه نصب میکنیم.pip install django-location-fieldدر ادامه location_field.apps.DefaultConfig رو به INSTALLED_APPS در قسمت settings پروژه اضافه میکنیم. باید یه سری تنظیمات هم یه فایل settings اضافه کنیم. این افزونه google maps رو هم ساپورت میکنه اما چون google maps api پولیه از open street map استفاده میکنیمLOCATION_FIELD = { 'map.provider': 'openstreetmap', 'map.zoom': 15, 'search.provider': 'google', 'search.suffix': '', # # Google # 'provider.google.api': '//maps.google.com/maps/api/js?sensor=false', # 'provider.google.api_key': '<INSERT GOOGLE API KEY>', # 'provider.google.api_libraries': '', # 'provider.google.map.type': 'ROADMAP', # # # Mapbox # 'provider.mapbox.access_token': '', # 'provider.mapbox.max_zoom': 18, # 'provider.mapbox.id': 'mapbox.streets', # OpenStreetMap 'provider.openstreetmap.max_zoom': 16, # # misc # 'resources.root_path': LOCATION_FIELD_PATH, # 'resources.media': { # 'js': ( # LOCATION_FIELD_PATH + '/js/form.js', # ), # }, } من قسمت هایی که استفاده نکردم رو کامنت کردم و گذاشتم شاید کسی بخواد استفاده کنه... حالا باید توی Location model فیلد location_point رو تغییر بدیم با استفاده از این کد ها:from location_field.models.spatial import LocationFieldclass Location(models.Model): location_point = LocationField(based_fields=['city'], zoom=7, default=Point(51.67, 32.65), null=True, blank=True)یه نکته ای این app یه نوع فیلد دیگه هم داره به اسم PlainLocationField که به همین منظور استفاده میشه با این تفاوت که مختصات رو توی دیتا بیس string ذخیره میکنه, به نظر من گزینه جذابی نیست ولی میتونه به درد اونایی بخوره که به هر دلیلی نمیخان افزونه postgis رو اضافه کنن.حالا اگه ادمین رو دوباره ببینیمنقشه جدید در django adminبرای اضافه کردن rest api به فرمت json از django rest framework استفاده میکنیم. چون توضیحات توی سایتش کامله و خارج از بحث ما میشه من فقط قسمت مربوط به serializer رو اینجا می نویسم!import json from django.contrib.gis.geos import Point from rest_framework import serializers from location.models import Location class LocationSerializer(serializers.Serializer): location = serializers.ListField(child=serializers.DecimalField(max_digits=7, decimal_places=5), max_length=2, min_length=2) def create(self, validated_data): validated_data['location_point'] = Point(validated_data.pop('location')) location = Location(**validated_data) location.save() return location def update(self, instance, validated_data): if validated_data.get('location'): instance.location_point = Point(validated_data.pop('location')) return instance def to_representation(self, instance): instance.location = json.loads(instance.location_point.geojson)['coordinates'] instance = super().to_representation(instance) return instanceاین serialzier یه لیست دو تایی از اعداد اعشاری میگیره که همون latitude , longitude هستن اما باید برعکس قرار بگیرن! (به صورت long, lat ارسال بشن) این قسمتش رو خودم مطمین نیستم اما فکر میکنم ذخیره سازی براساس محور مختصات هست که در اون x, y وجود داره برای همین برعکس عرض جغرافیایی و طول جغرافیایی میشن(اگه اشتباهه و کسی درستش رو میدونه بگه تا اصلاح کنم این قسمت رو). خروجی rest اینجوری میشه&quotlocation&quot: [&quot51.67000&quot,&quot32.65000&quot]و قاعدتا برای ساخت و آپدیت هم همینجوری باید ارسال بشه. انتخاب نام location برای serializer و location_point برای model هم به این دلیله که موقع create و update فیلد point که در دیتابیس هست به لیست اعداد توی rest api تبدیل بشه. اگه روش بهتری وجود داره گوش زد کنید:).