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

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

Django3.2 | クラウドソーシングアプリの構築 | 30 | 配達先フォーム

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


29 | Google Map】 << 【ホーム】 >> 【31 | DistanceMatrix


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


記述追加 【Desktop/crowdsource/core/models.py】70行目

import uuid
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone

# Create your models here.
class Customer(models.Model):
  user = models.OneToOneField(User, on_delete=models.CASCADE)
  avatar = models.ImageField(upload_to='customer/avatars/', blank=True, null=True)
  phone_number = models.CharField(max_length=50, blank=True)
  stripe_customer_id = models.CharField(max_length=255, blank=True)
  stripe_payment_method_id = models.CharField(max_length=255, blank=True)
  stripe_card_last4 = models.CharField(max_length=255, blank=True)

  def __str__(self):
    return self.user.get_full_name()
  
class Category(models.Model):
  slug = models.CharField(max_length=255, unique=True)
  name = models.CharField(max_length=255)

  def __str__(self):
    return self.name

class Job(models.Model):
  SMALL_SIZE = "small"
  MEDIUM_SIZE = "medium"
  LARGE_SIZE = "large"
  SIZES = (
    (SMALL_SIZE, '小'),
    (MEDIUM_SIZE, '中'),
    (LARGE_SIZE, '大'),
  )

  CREATING_STATUS = 'creating'
  PROCESSING_STATUS = 'processing'
  PICKING_STATUS = 'picking'
  DELIVERING_STATUS = 'delivering'
  COMPLETED_STATUS = 'completed'
  CANCELED_STATUS = 'canceled'
  STATUSES = (
    (CREATING_STATUS, 'Creating'),
    (PROCESSING_STATUS, 'Processing'),
    (PICKING_STATUS, 'Picking'),
    (DELIVERING_STATUS, 'Delivering'),
    (COMPLETED_STATUS, 'Completed'),
    (CANCELED_STATUS, 'Canceled'),
  )

  # Step 1
  id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
  customer = models.ForeignKey(Customer, on_delete=models.CASCADE, verbose_name='配達依頼人')
  name = models.CharField('配達依頼名', max_length=255)
  description = models.CharField('備考', max_length=255)
  category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='カテゴリー')
  size = models.CharField('サイズ', max_length=20, choices=SIZES, default=MEDIUM_SIZE)
  quantity = models.IntegerField('数量', default=1)
  photo = models.ImageField('写真', upload_to='job/photos/')
  status = models.CharField('状態', max_length=20, choices=STATUSES, default=CREATING_STATUS)
  created_at = models.DateTimeField(default=timezone.now, verbose_name='登録日')

  # Step 2
  pickup_address = models.CharField('配達依頼人住所', max_length=255, blank=True)
  pickup_lat = models.FloatField('依頼人緯度', default=0)
  pickup_lng = models.FloatField('依頼人経度', default=0)
  pickup_name = models.CharField('依頼人氏名', max_length=255, blank=True)
  pickup_phone = models.CharField('依頼人電話番号', max_length=50, blank=True)

  # Step 3
  delivery_address = models.CharField('配達先住所', max_length=255, blank=True)
  delivery_lat = models.FloatField('配達先緯度', default=0)
  delivery_lng = models.FloatField('配達先経度',default=0)
  delivery_name = models.CharField('配達先氏名',max_length=255, blank=True)
  delivery_phone = models.CharField('配達先電話番号',max_length=50, blank=True)

  def __str__(self):
    return self.description



マイグレーションファイルを作成します。
コマンド
python manage.py makemigrations


マイグレーションを適用します。
コマンド
python manage.py migrate


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


記述編集 【Desktop/crowdsource/core/customer/forms.py】

from django import forms
from django.db import models
from django.contrib.auth.models import User

from core.models import Customer, Job

class BasicUserForm(forms.ModelForm):
  class Meta:
    model = User
    fields = ('last_name', 'first_name')

class BasicCustomerForm(forms.ModelForm):
  class Meta:
    model = Customer
    fields = ('avatar',)

class JobCreateStep1Form(forms.ModelForm):
  class Meta:
    model = Job
    fields = ('name', 'description', 'category', 'size', 'quantity', 'photo')

class JobCreateStep2Form(forms.ModelForm):
  pickup_address = forms.CharField(label='依頼人住所',required=True)
  pickup_name = forms.CharField(label='依頼人氏名', required=True)
  pickup_phone = forms.CharField(label='依頼人電話番号', required=True)

  class Meta:
    model = Job
    fields = ('pickup_name', 'pickup_phone', 'pickup_address', 'pickup_lat', 'pickup_lng')

class JobCreateStep3Form(forms.ModelForm):
  delivery_address = forms.CharField(label='配達先住所', required=True)
  delivery_name = forms.CharField(label='配達先氏名',required=True)
  delivery_phone = forms.CharField(label='配達先電話番号',required=True)

  class Meta:
    model = Job
    fields = ('delivery_name', 'delivery_phone', 'delivery_address', 'delivery_lat', 'delivery_lng')



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


記述編集 【Desktop/crowdsource/core/customer/views.py】

import firebase_admin
from firebase_admin import credentials, auth
import stripe

from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.urls import reverse
from core.customer import forms

from django.contrib import messages
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth import update_session_auth_hash
from django.conf import settings

from core.models import *

cred = credentials.Certificate(settings.FIREBASE_ADMIN_CREDENTIAL)
firebase_admin.initialize_app(cred)

stripe.api_key = settings.STRIPE_API_SECRET_KEY

@login_required()
def home(request):
    return redirect(reverse('customer:profile'))

@login_required(login_url="/sign-in/?next=/customer/")
def profile_page(request):
    user_form = forms.BasicUserForm(instance=request.user)
    customer_form = forms.BasicCustomerForm(instance=request.user.customer)
    password_form = PasswordChangeForm(request.user)

    if request.method == "POST":
 
        if request.POST.get('action') == 'update_profile':
            user_form = forms.BasicUserForm(request.POST, instance=request.user)
            customer_form = forms.BasicCustomerForm(request.POST, request.FILES, instance=request.user.customer)

            if user_form.is_valid() and customer_form.is_valid():
                user_form.save()
                customer_form.save()

                messages.success(request, 'プロフィールが更新されました。')
                return redirect(reverse('customer:profile'))

        elif request.POST.get('action') == 'update_password':
            password_form = PasswordChangeForm(request.user, request.POST)
            if password_form.is_valid():
                user = password_form.save()
                update_session_auth_hash(request, user)

                messages.success(request, 'パスワードが更新されました。')
                return redirect(reverse('customer:profile'))

        elif request.POST.get('action') == 'update_phone':
            # Get Firebase user data
            firebase_user = auth.verify_id_token(request.POST.get('id_token'))

            request.user.customer.phone_number = firebase_user['phone_number']
            request.user.customer.save()
            messages.success(request, '電話番号が更新されました。')
            return redirect(reverse('customer:profile'))

    return render(request, 'customer/profile.html', {
        "user_form": user_form,
        "customer_form": customer_form,
        "password_form": password_form,
    })

@login_required(login_url="/sign-in/?next=/customer/")
def payment_method_page(request):
    current_customer = request.user.customer

    # Remove existing card
    if request.method == "POST":
        stripe.PaymentMethod.detach(current_customer.stripe_payment_method_id)
        current_customer.stripe_payment_method_id = ""
        current_customer.stripe_card_last4 = ""
        current_customer.save()
        return redirect(reverse('customer:payment_method'))

    # Save stripe customer infor
    if not current_customer.stripe_customer_id:
        customer = stripe.Customer.create()
        current_customer.stripe_customer_id = customer['id']
        current_customer.save()    

    # Get Stripe payment method
    stripe_payment_methods = stripe.PaymentMethod.list(
        customer = current_customer.stripe_customer_id,
        type = "card",
    )

    print(stripe_payment_methods)

    if stripe_payment_methods and len(stripe_payment_methods.data) > 0:
        payment_method = stripe_payment_methods.data[0]
        current_customer.stripe_payment_method_id = payment_method.id
        current_customer.stripe_card_last4 = payment_method.card.last4
        current_customer.save()
    else:
        current_customer.stripe_payment_method_id = ""
        current_customer.stripe_card_last4 = ""
        current_customer.save()


    if not current_customer.stripe_payment_method_id:
        intent = stripe.SetupIntent.create(
            customer = current_customer.stripe_customer_id
        )

        return render(request, 'customer/payment_method.html', {
            "client_secret": intent.client_secret,
            "STRIPE_API_PUBLIC_KEY": settings.STRIPE_API_PUBLIC_KEY,
        })
    else:
        return render(request, 'customer/payment_method.html')

@login_required(login_url="/sign-in/?next=/customer/")
def create_job_page(request):
    current_customer = request.user.customer

    if not current_customer.stripe_payment_method_id:
        return redirect(reverse('customer:payment_method'))

    creating_job = Job.objects.filter(customer=current_customer, status=Job.CREATING_STATUS).last()
    step1_form = forms.JobCreateStep1Form(instance=creating_job)
    step2_form = forms.JobCreateStep2Form(instance=creating_job)
    step3_form = forms.JobCreateStep3Form(instance=creating_job)

    if request.method == "POST":
        if request.POST.get('step') == '1':
            step1_form = forms.JobCreateStep1Form(request.POST, request.FILES)
            if step1_form.is_valid():
                creating_job = step1_form.save(commit=False)
                creating_job.customer = current_customer
                creating_job.save()
                return redirect(reverse('customer:create_job'))

        elif request.POST.get('step') == '2':
            step2_form = forms.JobCreateStep2Form(request.POST, instance=creating_job)
            if step2_form.is_valid():
                creating_job = step2_form.save()
                return redirect(reverse('customer:create_job'))


    # 現在のステップ
    if not creating_job:
        current_step = 1
    elif creating_job.pickup_name:
        current_step = 3        
    else:
        current_step = 2
    
    return render(request, 'customer/create_job.html', {
        "job": creating_job,
        "step": current_step,
        "step1_form": step1_form,
        "step2_form": step2_form,
        "step3_form": step3_form,
    })



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


記述編集 【Desktop/crowdsource/core/templates/customer/create_job.html】

{% extends 'base.html' %}
{% load bootstrap4 %}

{% block head %}

<script
  src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCApwNw0nn5uJ76G50pXQOqmHMY5sMUNIo&callback=initMap&libraries=places&v=weekly"
  defer></script>

<style>
  #pills-tab a {
    color: black;
  }

  #pills-tab a:hover {
    color: green;
    text-decoration: none;
  }

  #pills-tab a.active {
    color: red;
  }

  #pickup-map,
  #delivery-map {
    height: 100%;
  }

</style>
{% endblock %}


{% block content %}
<div class="container mt-4">
  <div class="row">
    <!-- 左側 -->
    <div class="col-lg-4">
      <div class="card">
        <div class="card-header">
          配達依頼の概要
        </div>
        <div class="card-body">
          {% if not job %}
            <p>配達依頼があればここに表示されます。</p>
          {% else %}

          {% if step > 1 %}
            <h4>{{ job.name }}</h4>
            <span>数量:{{ job.quantity }}</span><br />
            <span>大きさ:{{ job.get_size_display }}</span>
          {% endif %}

          {% if step > 2 %}
            <hr />
            <p class="text-secondary"><small><b>荷物受取先</b></small></p>
            依頼人氏名:<h4>{{ job.pickup_name }}</h4>
            <span>住所:{{ job.pickup_address }}</span><br />
          {% endif %}

          {% endif %}
        </div>
      </div>
    </div>

    <!-- 右側 -->
    <div class="col-lg-8">

        <!-- Step tabs -->
        <div class="card mb-5">
            <div class="card-body">
                
                <ul class="nav nav-pills nav-justified align-items-center mb-3" id="pills-tab" role="tablist">
                    <li class="nav-item" role="presentation">
                      <a class="{% if step == 1 %}active{% endif %}" id="pills-info-tab" data-toggle="pill" href="#pills-info"
                      role="tab" aria-controls="pills-info" aria-selected="true">配達依頼内容</a>
                    </li>
                    <i class="fas fa-chevron-right"></i>
                    <li class="nav-item" role="presentation">
                      <a class="{% if step == 2 %}active{% endif %}" id="pills-pickup-tab" data-toggle="pill"
                      href="#pills-pickup" role="tab" aria-controls="pills-pickup" aria-selected="false">荷物受取先</a>
                    </li>
                    <i class="fas fa-chevron-right"></i>
                    <li class="nav-item" role="presentation">
                      <a class="{% if step == 3 %}active{% endif %}" id="pills-delivery-tab" data-toggle="pill"
                      href="#pills-delivery" role="tab" aria-controls="pills-delivery" aria-selected="false">配達先</a>
                    </li>
                    <i class="fas fa-chevron-right"></i>
                    <li class="nav-item" role="presentation">
                      <a class="{% if step == 4 %}active{% endif %}" id="pills-payment-tab" data-toggle="pill"
                      href="#pills-payment" role="tab" aria-controls="pills-payment" aria-selected="false">支払い</a>
                    </li>
                </ul>
            </div>
        </div>

      <!-- Step forms -->
      <b>配達依頼作成</b>
      <div class="tab-content" id="pills-tabContent">

         <!-- Step 1 -->
        <div class="tab-pane fade {% if step == 1 %}show active{% endif %}" id="pills-info" role="tabpanel" aria-labelledby="pills-info-tab">
          <h1>配達依頼内容</h1>
         
          <form method="POST" enctype="multipart/form-data">
            <b class="text-secondary">情報</b><br />
            <div class="card bg-white mt-2 mb-5">
              <div class="card-body">
                {% csrf_token %}
                {% bootstrap_form step1_form %}
              </div>
            </div>
            <input type="hidden" name="step" value="1">
            <button type="submit" class="btn btn-info">保存して続ける</button>
          </form>

        </div>

        <!-- Step 2 -->
        <div class="tab-pane fade {% if step == 2 %}show active{% endif %}" id="pills-pickup" role="tabpanel" aria-labelledby="pills-info-tab">
            <h1>荷物受取先</h1>

            <form method="POST" enctype="multipart/form-data">
              <b class="text-secondary">情報</b><br />
              <div class="card bg-white mt-2 mb-5">
                <div class="card-body">

                  <div class="row">
                    <div class="col-lg-8">
                      {% csrf_token %}
                      {% bootstrap_form step2_form exclude='pickup_lat, pickup_lng' %}
                      <input hidden id="pickup_lat" name="pickup_lat" value="{{ job.pickup_lat }}" />
                      <input hidden id="pickup_lng" name="pickup_lng" value="{{ job.pickup_lng }}">
                    </div>
                    <div class="col-lg-4">
                      <div id="pickup-map"></div>
                      <div id="pickup-infowindow-content">
                        <img src="" width="16" height="16" id="pickup-place-icon" />
                        <span id="pickup-place-name" class="title"></span><br />
                        <span id="pickup-place-address"></span>
                      </div>
                    </div>
                  </div>

                </div>
              </div>
              <input type="hidden" name="step" value="2">
              <button type="button" class="btn btn-outline-info"
                onclick="$('#pills-info-tab').tab('show');">戻る</button>
              <button type="submit" class="btn btn-info">保存して続ける</button>
            </form>

        </div>

        <!-- Step 3 -->
        <div class="tab-pane fade {% if step == 3 %}show active{% endif %}" id="pills-delivery" role="tabpanel" aria-labelledby="pills-info-tab">
            <h1>配達先</h1>

            <form method="POST" enctype="multipart/form-data">
              <b class="text-secondary">情報</b><br />
              <div class="card bg-white mt-2 mb-5">
                <div class="card-body">
  
                  <div class="row">
                    <div class="col-lg-8">
                      {% csrf_token %}
                      {% bootstrap_form step3_form exclude='delivery_lat, delivery_lng' %}
                      <input hidden id="delivery_lat" name="delivery_lat" value="{{ job.delivery_lat }}" />
                      <input hidden id="delivery_lng" name="delivery_lng" value="{{ job.delivery_lng }}">
                    </div>
                    <div class="col-lg-4">
                      <div id="delivery-map"></div>
                      <div id="delivery-infowindow-content">
                        <img src="" width="16" height="16" id="delivery-place-icon" />
                        <span id="delivery-place-name" class="title"></span><br />
                        <span id="delivery-place-address"></span>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <input type="hidden" name="step" value="3">
              <button type="button" class="btn btn-outline-info"
                onclick="$('#pills-info-tab').tab('show');">戻る</button>
              <button type="submit" class="btn btn-info">保存して続ける</button>
            </form>
            
        </div>

        <!-- Step 4 -->
        <div class="tab-pane fade {% if step == 4 %}show active{% endif %}" id="pills-payment" role="tabpanel" aria-labelledby="pills-info-tab">
          <h1>支払い</h1>
        </div>

    </div>
  </div>
</div>

<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 initMapByType(type, initLat, initLng) {
    const map = new google.maps.Map(document.getElementById(type + "-map"), {
      center: { lat: initLat || 43.062087, lng: initLng || 141.354404 },
      zoom: 12,
    });

    if (initLat && initLng) {
      new google.maps.Marker({
        position: new google.maps.LatLng(initLat, initLng),
        map: map,
      })
    }    

    const input = document.getElementById("id_" + type + "_address");
    const autocomplete = new google.maps.places.Autocomplete(input);
    // Bind the map's bounds (viewport) property to the autocomplete object,
    // so that the autocomplete requests use the current map bounds for the
    // bounds option in the request.
    autocomplete.bindTo("bounds", map);
    // Set the data fields to return when the user selects a place.
    autocomplete.setFields(["address_components", "geometry", "icon", "name"]);
    const infowindow = new google.maps.InfoWindow();
    const infowindowContent = document.getElementById(type + "-infowindow-content");
    infowindow.setContent(infowindowContent);
    const marker = new google.maps.Marker({
      map,
      anchorPoint: new google.maps.Point(0, -29),
    });
    autocomplete.addListener("place_changed", () => {
      infowindow.close();
      marker.setVisible(false);
      const place = autocomplete.getPlace();

      if (!place.geometry) {
        // User entered the name of a Place that was not suggested and
        // pressed the Enter key, or the Place Details request failed.
        window.alert("表示できる場所はありません: '" + place.name + "'");
        return;
      }

      // If the place has a geometry, then present it on a map.
      if (place.geometry.viewport) {
        map.fitBounds(place.geometry.viewport);
      } else {
        map.setCenter(place.geometry.location);
        map.setZoom(17); // Why 17? Because it looks good.
      }
      marker.setPosition(place.geometry.location);
      marker.setVisible(true);
      let address = "";

      if (place.address_components) {
        address = [
          (place.address_components[0] &&
            place.address_components[0].short_name) ||
          "",
          (place.address_components[1] &&
            place.address_components[1].short_name) ||
          "",
          (place.address_components[2] &&
            place.address_components[2].short_name) ||
          "",
        ].join(" ");
      }
      infowindowContent.children[type + "-place-icon"].src = place.icon;
      infowindowContent.children[type + "-place-name"].textContent = place.name;
      infowindowContent.children[type + "-place-address"].textContent = address;
      infowindow.open(map, marker);

      $("#" + type + "_lat").val(place.geometry.location.lat());
      $("#" + type + "_lng").val(place.geometry.location.lng());
    });
  }

  function initMap() {
    initMapByType("pickup", pickupLat, pickupLng);
    initMapByType("delivery", deliveryLat, deliveryLng);
  }

</script>

{% endblock %}



配達ページが表示されるようになりました。

配達ページ表示
配達ページ表示



保存して次のステップに進めるようにします。


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


記述編集 【Desktop/crowdsource/core/customer/views.py】

import firebase_admin
from firebase_admin import credentials, auth
import stripe

from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.urls import reverse
from core.customer import forms

from django.contrib import messages
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth import update_session_auth_hash
from django.conf import settings

from core.models import *

cred = credentials.Certificate(settings.FIREBASE_ADMIN_CREDENTIAL)
firebase_admin.initialize_app(cred)

stripe.api_key = settings.STRIPE_API_SECRET_KEY

@login_required()
def home(request):
    return redirect(reverse('customer:profile'))

@login_required(login_url="/sign-in/?next=/customer/")
def profile_page(request):
    user_form = forms.BasicUserForm(instance=request.user)
    customer_form = forms.BasicCustomerForm(instance=request.user.customer)
    password_form = PasswordChangeForm(request.user)

    if request.method == "POST":
 
        if request.POST.get('action') == 'update_profile':
            user_form = forms.BasicUserForm(request.POST, instance=request.user)
            customer_form = forms.BasicCustomerForm(request.POST, request.FILES, instance=request.user.customer)

            if user_form.is_valid() and customer_form.is_valid():
                user_form.save()
                customer_form.save()

                messages.success(request, 'プロフィールが更新されました。')
                return redirect(reverse('customer:profile'))

        elif request.POST.get('action') == 'update_password':
            password_form = PasswordChangeForm(request.user, request.POST)
            if password_form.is_valid():
                user = password_form.save()
                update_session_auth_hash(request, user)

                messages.success(request, 'パスワードが更新されました。')
                return redirect(reverse('customer:profile'))

        elif request.POST.get('action') == 'update_phone':
            # Get Firebase user data
            firebase_user = auth.verify_id_token(request.POST.get('id_token'))

            request.user.customer.phone_number = firebase_user['phone_number']
            request.user.customer.save()
            messages.success(request, '電話番号が更新されました。')
            return redirect(reverse('customer:profile'))

    return render(request, 'customer/profile.html', {
        "user_form": user_form,
        "customer_form": customer_form,
        "password_form": password_form,
    })

@login_required(login_url="/sign-in/?next=/customer/")
def payment_method_page(request):
    current_customer = request.user.customer

    # Remove existing card
    if request.method == "POST":
        stripe.PaymentMethod.detach(current_customer.stripe_payment_method_id)
        current_customer.stripe_payment_method_id = ""
        current_customer.stripe_card_last4 = ""
        current_customer.save()
        return redirect(reverse('customer:payment_method'))

    # Save stripe customer infor
    if not current_customer.stripe_customer_id:
        customer = stripe.Customer.create()
        current_customer.stripe_customer_id = customer['id']
        current_customer.save()    

    # Get Stripe payment method
    stripe_payment_methods = stripe.PaymentMethod.list(
        customer = current_customer.stripe_customer_id,
        type = "card",
    )

    print(stripe_payment_methods)

    if stripe_payment_methods and len(stripe_payment_methods.data) > 0:
        payment_method = stripe_payment_methods.data[0]
        current_customer.stripe_payment_method_id = payment_method.id
        current_customer.stripe_card_last4 = payment_method.card.last4
        current_customer.save()
    else:
        current_customer.stripe_payment_method_id = ""
        current_customer.stripe_card_last4 = ""
        current_customer.save()


    if not current_customer.stripe_payment_method_id:
        intent = stripe.SetupIntent.create(
            customer = current_customer.stripe_customer_id
        )

        return render(request, 'customer/payment_method.html', {
            "client_secret": intent.client_secret,
            "STRIPE_API_PUBLIC_KEY": settings.STRIPE_API_PUBLIC_KEY,
        })
    else:
        return render(request, 'customer/payment_method.html')

@login_required(login_url="/sign-in/?next=/customer/")
def create_job_page(request):
    current_customer = request.user.customer

    if not current_customer.stripe_payment_method_id:
        return redirect(reverse('customer:payment_method'))

    creating_job = Job.objects.filter(customer=current_customer, status=Job.CREATING_STATUS).last()
    step1_form = forms.JobCreateStep1Form(instance=creating_job)
    step2_form = forms.JobCreateStep2Form(instance=creating_job)
    step3_form = forms.JobCreateStep3Form(instance=creating_job)

    if request.method == "POST":
        if request.POST.get('step') == '1':
            step1_form = forms.JobCreateStep1Form(request.POST, request.FILES)
            if step1_form.is_valid():
                creating_job = step1_form.save(commit=False)
                creating_job.customer = current_customer
                creating_job.save()
                return redirect(reverse('customer:create_job'))

        elif request.POST.get('step') == '2':
            step2_form = forms.JobCreateStep2Form(request.POST, instance=creating_job)
            if step2_form.is_valid():
                creating_job = step2_form.save()
                return redirect(reverse('customer:create_job'))

        elif request.POST.get('step') == '3':
            step3_form = forms.JobCreateStep3Form(request.POST, instance=creating_job)
            if step3_form.is_valid():
                creating_job = step3_form.save()
                
    # 現在のステップ
    if not creating_job:
        current_step = 1
    elif creating_job.delivery_name:
        current_step = 4
    elif creating_job.pickup_name:
        current_step = 3        
    else:
        current_step = 2
    
    return render(request, 'customer/create_job.html', {
        "job": creating_job,
        "step": current_step,
        "step1_form": step1_form,
        "step2_form": step2_form,
        "step3_form": step3_form,
    })



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


記述編集 【Desktop/crowdsource/core/templates/customer/create_job.html】

{% extends 'base.html' %}
{% load bootstrap4 %}

{% block head %}

<script
  src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCApwNw0nn5uJ76G50pXQOqmHMY&callback=initMap&libraries=places&v=weekly"
  defer></script>

<style>
  #pills-tab a {
    color: black;
  }

  #pills-tab a:hover {
    color: green;
    text-decoration: none;
  }

  #pills-tab a.active {
    color: red;
  }

  #pickup-map,
  #delivery-map {
    height: 100%;
  }

</style>
{% endblock %}


{% block content %}
<div class="container mt-4">
  <div class="row">
    <!-- 左側 -->
    <div class="col-lg-4">
      <div class="card">
        <div class="card-header">
          配達依頼の概要
        </div>
        <div class="card-body">
          {% if not job %}
            <p>配達依頼があればここに表示されます。</p>
          {% else %}

            {% if step > 1 %}
              <h4>{{ job.name }}</h4>
              <span>数量:{{ job.quantity }}</span><br />
              <span>大きさ:{{ job.get_size_display }}</span>
            {% endif %}

            {% if step > 2 %}
              <hr />
              <p class="text-secondary"><small><b>荷物受取先</b></small></p>
              依頼人氏名:<h4>{{ job.pickup_name }}</h4>
              <span>住所:{{ job.pickup_address }}</span><br />
            {% endif %}

            {% if step > 3 %}
            <hr />
            <p class="text-secondary"><small><b>配達先</b></small></p>
            <h4>{{ job.delivery_name }}</h4>
            <span>{{ job.delivery_address }}</span><br />
            {% endif %}

          {% endif %}
        </div>
      </div>
    </div>

    <!-- 右側 -->
    <div class="col-lg-8">

        <!-- Step tabs -->
        <div class="card mb-5">
            <div class="card-body">
                
                <ul class="nav nav-pills nav-justified align-items-center mb-3" id="pills-tab" role="tablist">
                    <li class="nav-item" role="presentation">
                      <a class="{% if step == 1 %}active{% endif %}" id="pills-info-tab" data-toggle="pill" href="#pills-info"
                      role="tab" aria-controls="pills-info" aria-selected="true">配達依頼内容</a>
                    </li>
                    <i class="fas fa-chevron-right"></i>
                    <li class="nav-item" role="presentation">
                      <a class="{% if step == 2 %}active{% endif %}" id="pills-pickup-tab" data-toggle="pill"
                      href="#pills-pickup" role="tab" aria-controls="pills-pickup" aria-selected="false">荷物受取先</a>
                    </li>
                    <i class="fas fa-chevron-right"></i>
                    <li class="nav-item" role="presentation">
                      <a class="{% if step == 3 %}active{% endif %}" id="pills-delivery-tab" data-toggle="pill"
                      href="#pills-delivery" role="tab" aria-controls="pills-delivery" aria-selected="false">配達先</a>
                    </li>
                    <i class="fas fa-chevron-right"></i>
                    <li class="nav-item" role="presentation">
                      <a class="{% if step == 4 %}active{% endif %}" id="pills-payment-tab" data-toggle="pill"
                      href="#pills-payment" role="tab" aria-controls="pills-payment" aria-selected="false">支払い</a>
                    </li>
                </ul>
            </div>
        </div>

      <!-- Step forms -->
      <b>配達依頼作成</b>
      <div class="tab-content" id="pills-tabContent">

         <!-- Step 1 -->
        <div class="tab-pane fade {% if step == 1 %}show active{% endif %}" id="pills-info" role="tabpanel" aria-labelledby="pills-info-tab">
          <h1>配達依頼内容</h1>
         
          <form method="POST" enctype="multipart/form-data">
            <b class="text-secondary">情報</b><br />
            <div class="card bg-white mt-2 mb-5">
              <div class="card-body">
                {% csrf_token %}
                {% bootstrap_form step1_form %}
              </div>
            </div>
            <input type="hidden" name="step" value="1">
            <button type="submit" class="btn btn-info">保存して続ける</button>
          </form>

        </div>

        <!-- Step 2 -->
        <div class="tab-pane fade {% if step == 2 %}show active{% endif %}" id="pills-pickup" role="tabpanel" aria-labelledby="pills-info-tab">
            <h1>荷物受取先</h1>

            <form method="POST" enctype="multipart/form-data">
              <b class="text-secondary">情報</b><br />
              <div class="card bg-white mt-2 mb-5">
                <div class="card-body">

                  <div class="row">
                    <div class="col-lg-8">
                      {% csrf_token %}
                      {% bootstrap_form step2_form exclude='pickup_lat, pickup_lng' %}
                      <input hidden id="pickup_lat" name="pickup_lat" value="{{ job.pickup_lat }}" />
                      <input hidden id="pickup_lng" name="pickup_lng" value="{{ job.pickup_lng }}">
                    </div>
                    <div class="col-lg-4">
                      <div id="pickup-map"></div>
                      <div id="pickup-infowindow-content">
                        <img src="" width="16" height="16" id="pickup-place-icon" />
                        <span id="pickup-place-name" class="title"></span><br />
                        <span id="pickup-place-address"></span>
                      </div>
                    </div>
                  </div>

                </div>
              </div>
              <input type="hidden" name="step" value="2">
              <button type="button" class="btn btn-outline-info"
                onclick="$('#pills-info-tab').tab('show');">戻る</button>
              <button type="submit" class="btn btn-info">保存して続ける</button>
            </form>

        </div>

        <!-- Step 3 -->
        <div class="tab-pane fade {% if step == 3 %}show active{% endif %}" id="pills-delivery" role="tabpanel" aria-labelledby="pills-info-tab">
            <h1>配達先</h1>

            <form method="POST" enctype="multipart/form-data">
              <b class="text-secondary">情報</b><br />
              <div class="card bg-white mt-2 mb-5">
                <div class="card-body">
  
                  <div class="row">
                    <div class="col-lg-8">
                      {% csrf_token %}
                      {% bootstrap_form step3_form exclude='delivery_lat, delivery_lng' %}
                      <input hidden id="delivery_lat" name="delivery_lat" value="{{ job.delivery_lat }}" />
                      <input hidden id="delivery_lng" name="delivery_lng" value="{{ job.delivery_lng }}">
                    </div>
                    <div class="col-lg-4">
                      <div id="delivery-map"></div>
                      <div id="delivery-infowindow-content">
                        <img src="" width="16" height="16" id="delivery-place-icon" />
                        <span id="delivery-place-name" class="title"></span><br />
                        <span id="delivery-place-address"></span>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <input type="hidden" name="step" value="3">
              <button type="button" class="btn btn-outline-info"
                onclick="$('#pills-info-tab').tab('show');">戻る</button>
              <button type="submit" class="btn btn-info">保存して続ける</button>
            </form>
            
        </div>

        <!-- Step 4 -->
        <div class="tab-pane fade {% if step == 4 %}show active{% endif %}" id="pills-payment" role="tabpanel" aria-labelledby="pills-info-tab">
          <h1>支払い</h1>
        </div>

    </div>
  </div>
</div>

<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 initMapByType(type, initLat, initLng) {
    const map = new google.maps.Map(document.getElementById(type + "-map"), {
      center: { lat: initLat || 43.062087, lng: initLng || 141.354404 },
      zoom: 12,
    });

    if (initLat && initLng) {
      new google.maps.Marker({
        position: new google.maps.LatLng(initLat, initLng),
        map: map,
      })
    }    

    const input = document.getElementById("id_" + type + "_address");
    const autocomplete = new google.maps.places.Autocomplete(input);
    // Bind the map's bounds (viewport) property to the autocomplete object,
    // so that the autocomplete requests use the current map bounds for the
    // bounds option in the request.
    autocomplete.bindTo("bounds", map);
    // Set the data fields to return when the user selects a place.
    autocomplete.setFields(["address_components", "geometry", "icon", "name"]);
    const infowindow = new google.maps.InfoWindow();
    const infowindowContent = document.getElementById(type + "-infowindow-content");
    infowindow.setContent(infowindowContent);
    const marker = new google.maps.Marker({
      map,
      anchorPoint: new google.maps.Point(0, -29),
    });
    autocomplete.addListener("place_changed", () => {
      infowindow.close();
      marker.setVisible(false);
      const place = autocomplete.getPlace();

      if (!place.geometry) {
        // User entered the name of a Place that was not suggested and
        // pressed the Enter key, or the Place Details request failed.
        window.alert("表示できる場所はありません: '" + place.name + "'");
        return;
      }

      // If the place has a geometry, then present it on a map.
      if (place.geometry.viewport) {
        map.fitBounds(place.geometry.viewport);
      } else {
        map.setCenter(place.geometry.location);
        map.setZoom(17); // Why 17? Because it looks good.
      }
      marker.setPosition(place.geometry.location);
      marker.setVisible(true);
      let address = "";

      if (place.address_components) {
        address = [
          (place.address_components[0] &&
            place.address_components[0].short_name) ||
          "",
          (place.address_components[1] &&
            place.address_components[1].short_name) ||
          "",
          (place.address_components[2] &&
            place.address_components[2].short_name) ||
          "",
        ].join(" ");
      }
      infowindowContent.children[type + "-place-icon"].src = place.icon;
      infowindowContent.children[type + "-place-name"].textContent = place.name;
      infowindowContent.children[type + "-place-address"].textContent = address;
      infowindow.open(map, marker);

      $("#" + type + "_lat").val(place.geometry.location.lat());
      $("#" + type + "_lng").val(place.geometry.location.lng());
    });
  }

  function initMap() {
    initMapByType("pickup", pickupLat, pickupLng);
    initMapByType("delivery", deliveryLat, deliveryLng);
  }

</script>

{% endblock %}



保存して次のステップに移動できるようになりました。
http://127.0.0.1:8000/customer/create_job/

保存して次のステップへ
保存して次のステップへ


管理サイト確認
管理サイト確認


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


29 | Google Map】 << 【ホーム】 >> 【31 | DistanceMatrix