学生向けプログラミング入門 | 無料

学生向けにプログラミングを無料で解説。Java、C++、Ruby、PHP、データベース、Ruby on Rails, Python, Django

Django3.2 | クラウドソーシングアプリの構築 | 45 | 配達完了

↓↓クリックして頂けると励みになります。


44 | 配送写真アップロード】 << 【ホーム】 >> 【46 | 配達完了ページ


「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) {
 
    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);

      },
      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="#" class="btn btn-info">進行中の配達依頼</a>
        <a href="#" 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 %}



「core/templates/courier/current_job_take_photo.html」ファイルを編集します。


記述編集 【Desktop/crowdsource/core/templates/courier/current_job_take_photo.html】

{% extends 'courier/base.html' %}
{% load static %}

{% block head %}
<script src="{% static 'js/webcam-easy.min.js' %}"></script>
<style>
  body {
    background-color: black;
  }

  .btn-back {
    position: fixed;
    top: 30px;
    left: 30px;
  }

  .buttons {
    position: fixed;
    bottom: 20px;
    left: 0;
    right: 0;
    text-align: center;
  }

  #take-photo-step {
    height: 100%;
    display: flex;
    align-items: center;
  }

  video {
    width: 100%;
    height: auto;
  }

  #upload-step {
    height: 100%;
    display: none;
    align-items: center;
  }  

</style>

{% endblock %}

{% block content %}

<div id="upload-step">
    <img id="photo">
    <div class="buttons">
      <a id="retake-button" class="btn btn-light" href="javascript:void(retake_photo())">再度撮影</a>
      <a id="upload-button" class="btn btn-info" href="javascript:void(upload_photo())">
        {% if job.status == 'picking' %}配送開始の写真をアップロード{% else %}配送完了の写真をアップロード{% endif %}
      </a>
    </div>
  </div>

<div id="take-photo-step">
  <video id="webcam" autoplay playsinline></video>
  <canvas id="canvas" class="d-none"></canvas>

  <a href="{% url 'courier:current_job' %}" class="btn-back">
    <i class="fas fa-chevron-left text-light"></i>
  </a>

  <div class="buttons">
    <a href="javascript:void(take_photo())" class="btn btn-info">
        {% if job.status == 'picking' %}配送開始の写真を撮影{% else %}配送完了の写真を撮影{% endif %}
    </a>
  </div>
</div>

<script>
    const webcamElement = document.getElementById('webcam');
    const canvasElement = document.getElementById('canvas');
    const webcam = new Webcam(webcamElement, 'environment', canvasElement);
    webcam.start();
  
    function take_photo() {
      let picture = webcam.snap();
      console.log(picture);

        $("#photo").attr("src", picture);
        $("#take-photo-step").css("display", "none");
        $("#upload-step").css("display", "flex");

    }
  
    function retake_photo() {
        $("#upload-step").css("display", "none");
        $("#take-photo-step").css("display", "flex");
    }

    function upload_photo() {
    document.getElementById("canvas").toBlob(function (blob) {
      var formData = new FormData();
      var upload_name = "{% if job.status == 'picking' %}pickup{% else %}delivery{% endif %}_photo"
      formData.append(upload_name, blob, upload_name + '.png');

      fetch("{% url 'courier:current_job_update_api' job.id %}", {
        method: "POST",
        body: formData
      })
        .then(function (response) { return response.json() })
        .then(function (json) {
          if (json.success) {
            window.location.href = "{% if job.status == 'picking' %}{% url 'courier:current_job' %}{% else %}{% url 'courier:home' %}{% endif%}"
          }
        })
    })
  }    
    
  </script>

{% endblock %}



「core/courier/apis.py」ファイルを編集します。


記述編集 【Desktop/crowdsource/core/courier/apis.py】36行目

from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import csrf_exempt
from django.utils import timezone

from core.models import *

@csrf_exempt
@login_required(login_url="/courier/sign-in/")
def available_jobs_api(request):
  jobs = list(Job.objects.filter(status=Job.PROCESSING_STATUS).values())

  return JsonResponse({
    "success": True,
    "jobs": jobs
  })

@csrf_exempt
@login_required(login_url="/courier/sign-in/")
def current_job_update_api(request, id):
  job = Job.objects.filter(
    id=id,
    courier=request.user.courier,
    status__in=[
      Job.PICKING_STATUS,
      Job.DELIVERING_STATUS
    ]
  ).last()

  if job.status == Job.PICKING_STATUS:
    job.pickup_photo = request.FILES['pickup_photo']
    job.pickedup_at = timezone.now()
    job.status = Job.DELIVERING_STATUS
    job.save()

  elif job.status == Job.DELIVERING_STATUS:
    job.delivery_photo = request.FILES['delivery_photo']
    job.delivered_at = timezone.now()
    job.status = Job.COMPLETED_STATUS
    job.save()

  return JsonResponse({
    "success": True
  })



動作を確認します。
ステータスが「Picking」の状態の場合、「配送開始の写真を撮影」になっています。

ステータス「Picking」
ステータス「Picking」
配送開始の写真を撮影
配送開始の写真を撮影



配送開始の写真をアップロードすると、ステータスが「Delivering」に変わります。

配送開始の写真をアップロード
配送開始の写真をアップロード
ステータス「Delivering」
ステータス「Delivering」



配送完了の写真をアップロードすると、ステータスが「Completed」にかわります。

配送完了の写真をアップロード
配送完了の写真をアップロード
ステータス「Completed」
ステータス「Completed」



「crowdsource/media/job」フォルダに写真が格納されているのを確認してください。

写真確認
写真確認


↓↓クリックして頂けると励みになります。


44 | 配送写真アップロード】 << 【ホーム】 >> 【46 | 配達完了ページ