↓↓クリックして頂けると励みになります。
【53 | Firebaseメッセージ】 << 【ホーム】 >> 【55 | 配達人のマップ表示】
Django Channelsをインストールします。
コマンド
pip install channels==3.0.3
バージョンを確認します。
コマンド
pip freeze > requirements.txt
内容確認 【Desktop/crowdsource/requirements.txt】
asgiref==3.7.2 attrs==23.1.0 autobahn==23.6.2 Automat==22.10.0 beautifulsoup4==4.12.2 CacheControl==0.13.1 cachetools==5.3.1 certifi==2023.7.22 cffi==1.15.1 channels==3.0.3 chardet==3.0.4 charset-normalizer==3.2.0 constantly==15.1.0 cryptography==41.0.3 daphne==3.0.2 defusedxml==0.7.1 Django==3.2.20 django-bootstrap4==2.3.1 firebase-admin==4.4.0 google-api-core==1.34.0 google-api-python-client==2.97.0 google-auth==2.22.0 google-auth-httplib2==0.1.0 google-cloud-core==2.3.3 google-cloud-firestore==2.11.1 google-cloud-storage==2.10.0 google-crc32c==1.5.0 google-resumable-media==2.5.0 googleapis-common-protos==1.60.0 grpcio==1.57.0 grpcio-status==1.48.2 httplib2==0.22.0 hyperlink==21.0.0 idna==2.10 incremental==22.10.0 msgpack==1.0.5 oauthlib==3.2.2 paypalrestsdk==1.13.1 Pillow==10.0.0 proto-plus==1.22.3 protobuf==3.20.3 pyasn1==0.5.0 pyasn1-modules==0.3.0 pycparser==2.21 PyJWT==2.8.0 pyOpenSSL==23.2.0 pyparsing==3.1.1 python3-openid==3.2.0 pytz==2023.3 requests==2.25.0 requests-oauthlib==1.3.1 rsa==4.9 service-identity==23.1.0 six==1.16.0 social-auth-app-django==4.0.0 social-auth-core==4.4.2 soupsieve==2.5 sqlparse==0.4.4 stripe==2.55.1 Twisted==23.8.0 txaio==23.1.1 typing_extensions==4.7.1 uritemplate==4.1.1 urllib3==1.26.16 zope.interface==6.0
「crowdsource/settings.py」ファイルを編集します。
記述追加 【Desktop/crowdsource/crowdsource/settings.py】45行目
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
#'core', #削除
'bootstrap4',
'social_django',
'core.apps.CoreConfig',
'channels', #追加
]
記述追加 【Desktop/crowdsource/crowdsource/settings.py】176行目(末尾)
ASGI_APPLICATION = "crowdsource.asgi.application"
「crowdsource/asgi.py」ファイルを編集します。
記述編集 【Desktop/crowdsource/crowdsource/asgi.py】
import os from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter from django.core.asgi import get_asgi_application from crowdsource.urls import websocket_urlpatterns os.environ.setdefault("DJANGO_SETTINGS_MODULE", "crowdsource.settings") application = ProtocolTypeRouter({ "http": get_asgi_application(), "websocket": AuthMiddlewareStack( URLRouter(websocket_urlpatterns) ) })
「crowdsource/core」フォルダに「consumers.py」ファイルを新規作成します。
作成した「consumers.py」ファイルを以下のように編集します。
新規作成 【Desktop/crowdsource/core/consumers.py】
import json from channels.generic.websocket import WebsocketConsumer from . import models class JobConsumer(WebsocketConsumer): def connect(self): self.accept() def disconnect(self, close_code): pass def receive(self, text_data): text_data_json = json.loads(text_data) job = text_data_json['job'] print("Job", job)
「crowdsource/urls.py」ファイルを編集します。
記述編集 【Desktop/crowdsource/crowdsource/urls.py】
from django.contrib import admin from django.urls import path, include from django.contrib.auth import views as auth_views from django.conf import settings from django.conf.urls.static import static from django.views.generic import TemplateView from core import views, consumers from core.customer import views as customer_views from core.courier import views as courier_views, apis as courier_apis customer_urlpatters = [ path('', customer_views.home, name="home"), path('profile/', customer_views.profile_page, name="profile"), path('payment_method/', customer_views.payment_method_page, name="payment_method"), path('create_job/', customer_views.create_job_page, name="create_job"), path('jobs/current/', customer_views.current_jobs_page, name="current_jobs"), path('jobs/archived/', customer_views.archived_jobs_page, name="archived_jobs"), path('jobs/<job_id>/', customer_views.job_page, name="job"), ] courier_urlpatters = [ path('', courier_views.home, name="home"), path('jobs/available/', courier_views.available_jobs_page, name="available_jobs"), path('jobs/available/<id>/', courier_views.available_job_page, name="available_job"), path('jobs/current/', courier_views.current_job_page, name="current_job"), path('jobs/current/<id>/take_photo/', courier_views.current_job_take_photo_page, name="current_job_take_photo"), path('jobs/complete/', courier_views.job_complete_page, name="job_complete"), path('jobs/archived/', courier_views.archived_jobs_page, name="archived_jobs"), path('profile/', courier_views.profile_page, name="profile"), path('payout_method/', courier_views.payout_method_page, name="payout_method"), path('api/jobs/available/', courier_apis.available_jobs_api, name="available_jobs_api"), path('api/jobs/current/<id>/update/', courier_apis.current_job_update_api, name="current_job_update_api"), path('api/fcm-token/update/', courier_apis.fcm_token_update_api, name="fcm_token_update_api"), ] urlpatterns = [ path('admin/', admin.site.urls), path('oauth/', include('social_django.urls', namespace='social')), path('', views.home), path('sign-in/', auth_views.LoginView.as_view(template_name="sign_in.html")), path('sign-out/', auth_views.LogoutView.as_view(next_page="/")), path('sign-up/', views.sign_up), path('customer/', include((customer_urlpatters, 'customer'))), path('courier/', include((courier_urlpatters, 'courier'))), path('firebase-messaging-sw.js', (TemplateView.as_view(template_name="firebase-messaging-sw.js", content_type="application/javascript",))), ] websocket_urlpatterns = [ path('ws/jobs/<job_id>/', consumers.JobConsumer.as_asgi()) ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
「core/templates/courier/current_job.html」ファイルを編集します。
記述編集 【Desktop/crowdsource/core/templates/courier/current_job.html】
{% extends 'courier/base.html' %}
{% load static %}
{% block head %}
<script
src="https://maps.googleapis.com/maps/api/js?key={{ GOOGLE_MAP_API_KEY }}&callback=initMap&libraries=places&v=weekly"
defer></script>
<script>
var pickupLat = parseFloat(" {{ job.pickup_lat }} ");
var pickupLng = parseFloat(" {{ job.pickup_lng }} ");
var deliveryLat = parseFloat(" {{ job.delivery_lat }} ");
var deliveryLng = parseFloat(" {{ job.delivery_lng }} ");
function initMap() {
if (!document.getElementById("map")) {
return;
}
const directionsService = new google.maps.DirectionsService();
const directionsRenderer = new google.maps.DirectionsRenderer();
const map = new google.maps.Map(document.getElementById("map"), {
zoom: 7,
center: { lat: 43.062087, lng: 141.354404 },
});
directionsRenderer.setMap(map);
calculateAndDisplayRoute(map, directionsService, directionsRenderer);
}
function calculateAndDisplayRoute(map, directionsService, directionsRenderer) {
directionsService.route(
{
origin: new google.maps.LatLng(pickupLat, pickupLng),
destination: new google.maps.LatLng(deliveryLat, deliveryLng),
travelMode: google.maps.TravelMode.DRIVING,
},
(response, status) => {
if (status === "OK") {
new google.maps.DirectionsRenderer({
map: map,
directions: response,
suppressMarkers: true,
polylineOptions: {
strokeColor: "red",
strokeWeight: 5,
strokeOpacity: 0.8
}
});
var leg = response.routes[0].legs[0];
new google.maps.Marker({
position: leg.start_location,
map: map,
icon: "{% static 'img/start.png' %}"
});
new google.maps.Marker({
position: leg.end_location,
map: map,
icon: "{% static 'img/end.png' %}"
});
updateCourierPosition(map);
} else {
window.alert("Directions request failed due to " + status);
}
}
);
}
function updateCourierPosition(map) {
const jobSocket = new WebSocket(
"ws://" + window.location.host + "/ws/jobs/{{ job.id }}/"
);
navigator.geolocation.watchPosition(
pos => {
var courierPostion = new google.maps.LatLng(pos.coords.latitude, pos.coords.longitude);
if (!window.courierMarker) {
window.courierMarker = new google.maps.Marker({
position: courierPostion,
map,
icon: "{% static 'img/courier.png' %}"
});
} else {
window.courierMarker.setPosition(courierPostion);
}
map.panTo(courierPostion);
try {
jobSocket.send(JSON.stringify({
job: {
courier_lat: pos.coords.latitude,
courier_lng: pos.coords.longitude,
}
}))
} catch (error) {
console.log(error);
}
},
pos => console.log(pos))
}
</script>
<style>
#map {
flex: 1;
}
small {
font-size: 12px;
line-height: 1.2rem;
}
.card {
border: none;
}
</style>
{% endblock %}
{% block content %}
<div class="d-flex flex-column h-100" style="padding-bottom: 60px">
<div class="text-center">
<div class="btn-group mt-1 mb-1 align-item-center" role="group">
<a href="{% url 'courier:current_job' %}" class="btn btn-info">進行中の配達</a>
<a href="{% url 'courier:archived_jobs' %}" class="btn btn-outline-info">終了した配達</a>
</div>
</div>
{% if job %}
<div id="map"></div>
<div class="card">
<div class="card-body p-2">
<div class="media">
<img src="{{ job.photo.url }}" class="rounded-lg mr-3" width="50px" height="50px">
<div class="media-body">
<b>{{ job.name }}</b>
<div class="d-flex">
<div class="flex-grow-1 mr-2">
<small class="text-success">
<i class="fas fa-car"></i> <span>{{ job.distance }}</span> km
<i class="far fa-clock ml-2"></i> <span>{{ job.duration }}</span> 分
</small>
<div class="d-flex align-items-center mt-2">
<i class="fas fa-map-marker-alt"></i>
<small class="text-secondary ml-2">{{ job.pickup_address }}</small>
</div>
<div class="d-flex align-items-center mt-2">
<i class="fas fa-flag-checkered"></i>
<small class="text-secondary ml-2">{{ job.delivery_address }}</small>
</div>
</div>
<h3>{{ job.price }}</h3>円
</div>
</div>
</div>
<a href="{% url 'courier:current_job_take_photo' job.id %}" class="btn btn-block btn-info btn-md mt-3">
{% if job.status == 'picking' %}配達開始の写真を撮影{% else %}配達完了の写真を撮影{% endif %}
</a>
</div>
</div>
{% else %}
<div id="main" class="text-center">
<p>
現在、何も配達依頼を受けていません。<br/>配達依頼を選んで受けてみましょう。
</p>
</div>
{% endif %}
</div>
{% include 'courier/bottom_tabs.html' %}
{% endblock %}
ブラウザを確認します。
配達人の現在位置が表示されているページをアップデートします。
http://127.0.0.1:8000/courier/jobs/current/

ターミナルで配達人の位置が表示されるのを確認します。
WebSocket HANDSHAKING /ws/jobs/3d8f6ddc-caca-45b0-b39a-3a1422efbb96/ [127.0.0.1:56598]
WebSocket CONNECT /ws/jobs/3d8f6ddc-caca-45b0-b39a-3a1422efbb96/ [127.0.0.1:56598]
Job {'courier_lat': 4338, 'courier_lng': 1492}
Job {'courier_lat': 496, 'courier_lng': 1496974}
引き続き「core/consumers.py」ファイルを編集します。
記述編集 【Desktop/crowdsource/core/consumers.py】18行目
import json from channels.generic.websocket import WebsocketConsumer from . import models class JobConsumer(WebsocketConsumer): def connect(self): self.accept() def disconnect(self, close_code): pass def receive(self, text_data): text_data_json = json.loads(text_data) job = text_data_json['job'] # print("Job", job) if job.get('courier_lat') and job.get('courier_lng'): self.scope['user'].courier.lat = job['courier_lat'] self.scope['user'].courier.lng = job['courier_lng'] self.scope['user'].courier.save()
配達人ページを更新して、管理サイトを確認します。
配達人の位置がデータベースに格納されているのがわかります。

↓↓クリックして頂けると励みになります。
【53 | Firebaseメッセージ】 << 【ホーム】 >> 【55 | 配達人のマップ表示】