Djangoでショッピングカートを作りたい5

いよいよショッピングカートを作っていきます。
$ python3 manage.py startapp cart

setting.py

INSTALLED_APPS = [
    'shop',
    'search',
    'cart',
    // 省略
]
// 省略
        'DIRS': [os.path.join(BASE_DIR, 'shop', 'templates/'), os.path.join(BASE_DIR, 'search', 'templates/'), os.path.join(BASE_DIR, 'cart', 'templates/')],

cart/models.py

from django.db import models
from shop.models import Product

class Cart(models.Model):
	cart_id = models.CharField(max_length=250, blank=True)
	date_added = models.DateField(auto_now_add=True)

	class Meta:
		db_table = 'Cart'
		ordering = ['date_added']

	def __str__(self):
		return self.cart_id

class CartItem(models.Model):
	product = models.ForeignKey(Product, on_delete=models.CASCADE)
	cart = models.ForeignKey(Cart, on_delete=models.CASCADE)
	quantity = models.IntegerField()
	active = models.BooleanField(default=True)

	class Meta:
		db_table = 'CartItem'

	def sub_total(self):
		return self.product.price * self.quantity

	def __str__(self):
		return self.product.name

$ python3 manage.py makemigrations cart
$ python3 manage.py migrate

cart/urls.py

from django.urls import path
from . import views

app_name = 'cart'

urlpatterns = [
	path('', views.cart_detail, name='cart_detail'),
]

urls.py

urlpatterns = [
    path('admin/', admin.site.urls),
    path('shop/', include('shop.urls')),
    path('search/', include('search.urls')),
    path('cart/', include('cart.urls')),
]

cart/views.py
L session.session_keyでセッションの値を取得する

from django.shortcuts import render, redirect
from .models import Cart, CartItem
from django.core.exceptions import ObjectDoesNotExist

def _cart_id(request):
	cart = request.session.session_key
	if not cart:
		cart = request.session.create()
	return cart

def cart_detail(request, total=0, counter=0, cart_items = None):
	try:
		cart = Cart.objects.get(cart_id=_cart_id(request))
		cart_items = CartItem.objects.filter(cart=cart, active=True)
		for cart_item in cart_items:
			total += (cart_item.product.price * cart_item.quantity)
			counter += cart_item.quantity
	except ObjectDoesNotExist:
		pass

	return render(request, 'cart/cart.html', dict(cart_items = cart_items, total = total, counter = counter))

cart/template/cart/cart.html

{% extends "base.html" %}
{% load static %}

{% block metadescription %}
	This is the shopping cart page.. Proceed to review your items and place the order.
{% endblock %}

{% block title %}
	Cart - Various Product Store
{% endblock %}

{% block content %}
	{% if not cart_items %}
	<div>
		<div class="text-center">
			<br>
			<h1 class="text center my_title">Your shopping cart is empty</h1>
			<br>
			<p class="text-center">
				Please click <a href="{% url 'shop:all_product' %}">here</a> to continue shopping.
			</p>
		</div>
	</div>
	{% else %}
	<div>
		<div class="text-center">
			<br>
			<h1 class="text-center my_title">
				Your shopping cart
			</h1>
		</div>
		<br>
	</div>
	<div class="row mx-auto">
		<div class="col-12 col-sm-12 col-lg-6 text-center">
			<table class="table my_custom_table">
				<thread class="my_custom_thread">
					<tr>
						<th colspan="5">
							Your items
						</th>
					</tr>
				</thread>
				<tbody>
					{% for cart_item in cart_items %}
					<tr>
						<td><a href="cart_item.product.get_absolute_url"><img src="{{cart_item.product.image.url}}" alt="" class="float-left rounded custom_image"></a></td>
						<td class="text-left">
							{{cart_item.product.name}}
							<br>
							SKU: {{cart_item.product.id}}
							<br>
							Unit Price: ${{cart_item.product.price}}
							<br>
							Qty: {{cart_item.quantity}} x ${{cart_item.product.price}}
						</td>
						<td>
							${{cart_item.sub_total}}
						</td>
						{% if cart_item.quantity < cart_item.product.stock %}
						<td>
							<a href="{% url 'cart:add_cart' cart_item.product.id %}" class="custom_a"><i class="fas fa-plus-circle custom_icon"></i></a>

							<a href="" class="custom_a"><i class="fas fa-minus-circle custom_icon"></a>

							<a href="" class="custom_item"><i class="far fa-trash-alt"></a>
						</td>
						{% else %}
						<td>
							<a href="" class="custom_a"><i class="fas fa-minus-circle custom_icon"></a>

							<a href="" class="custom_item"><i class="far fa-trash-alt"></i></a>
						</td>
						<td></td>
						{% endif %}
					</tr>
					{% endfor %}
				</tbody>
			</table>
		</div>
		<div class="col-12 col-sm-12 col-lg-6 text-center">
			<table class="table my_custom_table">
				<thread class="my_custom_thead">
					<tr>
						<th>
							Checkout
						</th>
					</tr>
				</thread>
				<tbody>
					<tr>
						<td>
							Please review your shopping cart item before proceeding with the order payment.
						</td>
					</tr>
					<tr>
						<td class="text-left">
							Your total is: <strong>${{total}}</strong>
						</td>
					</tr>
				</tbody>
			</table>
			<div class="mx-auto">
				<a href="{% url 'shop:all_product' %}" class="btn-secondary btn-block my_custom_button">Continue Shopping</a>
			</div>
		</div>
	</div>
	{% endif %}
{% endblock %}

models.py

from shop.models import Product

def add_cart(request, Product_id):
	product = Product.objects.get(id=product_id)
	try:
		cart = Cart.objects.get(cart_id=_cart_id(request))
	except Cart.DoesNotExist:
		cart = Cart.objects.create(
				cart_id = _cart_id(request)
			)
		cart.save()
	try:
		cart_item = CartItem.objects.get(product=product, cart=cart)
		cart_item.quantity += 1
		cart_item.save()
	except CartItem.DoesNotExist:
		cart_item = CartItem.objects.create(
				product = product,
				quantity = 1,
				cart = cart
			)
		cart_item.save()
	return redirect('cart:cart_detail')

urls.py

urlpatterns = [
	path('add/<int:product_id>/', views.add_cart, name='add_cart'),
	path('', views.cart_detail, name='cart_detail'),
]

product_detail.html

<a class="btn btn-secondary" href="{% url 'cart:add_cart' product.id %}">Add to Cart</a>

cart/views.py

def cart_remove(request, product_id):
	cart = Cart.objects.get(cart_id=_cart_id(request))
	product = get_object_or_404(Product, id=product_id)
	cart_item = CartItem.objects.get(product=product, cart=cart)
	if cart_item.quantity > 1:
		cart_item.quantity -= 1
		cart_item.save()
	else:
		cart_item.delete()
	return redirect('cart:cart_detail')

def full_remove(request, product_id):
	cart = Cart.objects.get(cart_id=_cart_id(request))
	product = get_object_or_404(Product, id=product_id)
	cart_item = CartItem.objects.get(product=product, cart=cart)
	cart_item.delete()
	return redirect('cart:cart_detail')

urls.py

app_name = 'cart'

urlpatterns = [
	path('add/<int:product_id>/', views.add_cart, name='add_cart'),
	path('', views.cart_detail, name='cart_detail'),
	path('remove/<int:product_id>/', views.cart_remove, name='cart_remove'),
	path('full_remove/<int:product_id>/', views.full_remove, name='full_remove')
]

cart/context_processors.py

from .models import Cart, CartItem
from .views import _cart_id

def counter(request):
	item_count = 0
	if 'admin' in request.path:
		return {}
	else:
		try:
			cart = Cart.objects.filter(cart_id=_cart_id(request))
			cart_items = CartItem.objects.all().filter(cart=cart[:1])
			for cart_item in cart_items:
				item_count += cart_item.quantity
		except Cart.DoesNotExist:
			item_count = 0
	return dict(item_count = item_count)

settings.py

            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'cart.context_processors.counter',
            ],

navbar.html

			{% if item_count > 0 %}
			<li class="nav-item">
				<a class="nav-link" href="{% url 'cart:cart_detail' %}">({{item_count}})</a>
			</li>
			{% endif %}

cart.html

						{% if cart_item.quantity < cart_item.product.stock %}
						<td>
							<a href="{% url 'cart:add_cart' cart_item.product.id %}" class="custom_a"><i class="fas fa-plus-circle custom_icon"></i></a>

							<a href="{% url 'cart:cart_remove' cart_item.product.id %}" class="custom_a"><i class="fas fa-minus-circle custom_icon"></a>

							<a href="{% url 'cart:full_remove' cart_item.product.id %}" class="custom_item"><i class="far fa-trash-alt"></a>
						</td>
						{% else %}
						<td>
							<a href="{% url 'cart:cart_remove' cart_item.product.id %}" class="custom_a"><i class="fas fa-minus-circle custom_icon"></a>

							<a href="{% url 'cart:full_remove' cart_item.product.id %}" class="custom_item"><i class="far fa-trash-alt"></i></a>
						</td>
						<td></td>
						{% endif %}

なるほど、ただこれだと、ログアウトした時の処理などが入ってないから、完成には遠いな。Libraryはないのかしら?

Djangoでショッピングカートを作りたい4

### search app作成
$ python3 manage.py startapp search

setting.py

INSTALLED_APPS = [
    'shop',
    'search',
    // 省略
]

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'shop', 'templates/'), os.path.join(BASE_DIR, 'search', 'templates/')],
        // 省略
    },
]

search/urls.py

from django.urls import path
from . import views

app_name = 'search'

urlpatterns = [
	path('', views.search_result, name='search_result'),
]

urls.py

urlpatterns = [
    path('admin/', admin.site.urls),
    path('shop/', include('shop.urls')),
    path('search/', include('search.urls')),
]

search/views.py

from django.shortcuts import render
from shop.models import Product

def search_result(request):
	products = Product.objects.all()
	return render(request, 'search.html', {'products': products})

navbar.html

		<form class="form-inline my-2 my-lg-0" action="{% url 'search:search_result' %}" method="get">
			<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search" name="q">
			<button class="btn btn-secondary my-2 my-sm-0" type="submit"><i class="fas fa-search"></i></button>
		</form>

search.html

{% extends "base.html" %}
{% load static %}

{% block metadescription %}
	 We have a variety of stunning and comfy cushions. Lock for the one that suits your needs.
{% endblock %}

{% block title %}
	Search - Perfect Cushion Store
{% endblock %}

{% block content %}
<div>
	<p class="text-center my_search_text">You have searched for: <b>"{{ query }}"</b></p>
</div>
<div class="container">
	<div class="row mx-auto">
		{% for product in products %}
		<div class="my_bottom_margin col-9 col-sm-12 com-md-4 com-md-12 col-lg-4">
			<div class="card text-center" style="min-width: 18rem;">
				<a href="{{product.get_url}}"><img class="card-img-top my_image" src="{{product.image.url}}" alt="{{product.name}}"></a>
				<div class="card-body">
					<h4>{{product.name}}</h4>
					<p>${{product.price}}</p>
				</div>
			</div>
		</div>
		{% empty %}
		<div class="row mx-auto">
			<p class="text-center my_search_text">0 results found.</p>
		</div>
		{% endfor %}
	</div>
</div>
{% endblock %}

OKでしょう。

Djangoでショッピングカートを作りたい3

一覧から画像をクリックすると遷移する商品詳細ページを作成します。

shop/models.py
L reverseとは、Djangoのurlsに設定された名前をパラメータとして渡すとURLを返す。

from django.urls import reverse

class Product(models.Model):
	// 省略

	def get_url(self):
		return reverse('shop:product_detail', args=[self.slug])

shop/urls.py

app_name = 'shop'

urlpatterns = [
	// 省略
	path('<slug:product_slug/>', views.product_detail, name='product_detail'),
]

shop/views.py

def product_detail(request, product_slug):
	try:
		product = Product.objects.get(slug=product_slug)
	except Exception as e:
		raise e
	return render(request, 'shop/product_detail.html', {'product': product})

shop/templates/shop/product_detail.html

{% extends "base.html" %}
{% load static %}

{% block metadescription %}
	{{ product.description|truncatewords:155}}
{% endblock %}

{% block title %}
		{{ product.name }} - Perfect Cushion Store
{% endblock %}

{% block content %}
<div>
	<div>
		<p><a href="{% url 'shop:all_product' %}">Home</a>|<a href="{{product.get_url}}">{{product.category}}</a></p>
	</div>
	<div>
		<br>
		<div>
			<div>
				<div>
					<img src="{{product.image.url}}" alt="{{product.name}}">
				</div>
			</div>
			<div>
				<div>
					<h1>{{product.name}}</h1>
					<p>${{product.price}}</p>
					<p>Product Description</p>
					<p>{{product.description}}</p>
					{% if product.stock <= 0 %}
					<p><b>Out of Stock</b></p>
					{% else %}
					<a href="">Add to Cart</a>
					{% endif %}
				</div>
			</div>
		</div>
	</div>
</div>
{% endblock %}

shop/templates/shop/product_list.html

			<div>
				<a href="{{product.get_url}}"><img src="{{ product.image.url }}" alt="{{product.name}}"></a>
				<div>
					<h4>{{product.name}}</h4>
					<p>${{product.price}}</p>
				</div>
			</div>

static/css/custom.css

.nav-item {
	letter-spacing: .2em;
	font-size: 14px;
	text-transform: uppercase;
}

.dropdown-item {
	font-size: 14px;
	letter-spacing: .2em;
	text-transform: uppercase;
}

/* google font */
body {
	font-family: 'Roboto', sans-serif;
}
.my_footer {
	background-color: #f8f9fa;
	height: 60px;
}

.my_footer p {
	padding-top: 20px;
	font-size: 14px;
}

base.html

	<link rel="stylesheet" href="{% static 'css/custom.css' %}">
	<link href="https://fonts.googleapis.com/css?family=Roboto&display=swap" rel="stylesheet">

スタイリングをしていきます。

orange, bananaを追加します。

### ページネーション
views.py

from django.core.paginator import Paginator, EmptyPage, InvalidPage
def all_products(request):
	products = Product.valid_objects.all()

	paginator = Paginator(products_list, 3)
	try:
		page = int(request.GET.get('page','1'))
	except:
		page = 1

	try:
		products = paginator.page(page)
	except (EmptyPage, InvalidPage):
		products = paginator.page(paginator.num_pages)
		
	return render(request, 'shop/product_list.html',{'products':products})

product_list.html

<div class="row">
	<div class="mx-auto">
		{% if products.paginator.num_pages > 1%}
		<hr>
		<div class="text-center">
			{% for pg in products.paginator.page_range %}
			<a href="?page={{pg}}" class="btn btn-light btn-sm {% if products.number == pg %}active{% endif %}">{{pg}}</a>
			{% endfor %}
		</div>
		{% endif %}
	</div>
</div>

商品数が多い場合は使えないけど、こういう書き方があるんやな

Djangoでショッピングカートを作りたい2

テンプレートを作成していく

template/base.html
L テンプレートで変数使用時は{% block title %}{% endblock %} と書く
L includeは include ‘header.html’

{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="description" content="{% block metadescription %}{% endblock %}">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  <title>{% block title %}{% endblock %}</title>
</head>
<body>
	<div>
		{% include 'header.html' %}
		{% include 'navbar.html' %}
		{% block content %}
		{% endblock %}
	</div>
		{% include 'footer.html' %}
		    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>
</html>

template/header.html

{% load staticfiles %}
<header>
	<center>
		<img src="{% static 'img/logo.png' %}" alt="Perfect Cushion Store">
	</center>
</header>

template/navbar.html

<nav>
	<ul>
		<li><a href="{% url 'shop:all_product' %}">All Products</a></li>
		<li>Your Cart()</li>
	</ul>
</nav>

template/footer.html

<div>
	<p>© shoppingcart, with Django</p>
</div>

template/shop/product_list.html
L entends “base.html” でテンプレートを呼び出す

{% entends "base.html" %}
{% load staticfiles %}

{% block metadescription %}
	{% if category %}
{{ category.description|truncatewords:155}}
	{% else %}
		welecome to the cushion store where you can buy comfy and awesome cushions.
	{% endif %}
{% endblock %}

{% block title %}
	{% if category %}
{{ category.name }} - Perfect Cushion Store
	{% else %}
		See Our Cushion Collection - Perfect Cushion Store
	{% endif %}
{% endblock %}

{% block content %}
<div>
	<img src="{% static 'img/banner.jpg' %}" alt="Our Products Collection">
</div>
<br>
<div>
	<h1>Our Products Collection</h1>
	<p>Finding the perfect cushion for your room can add to the levels of comfort and sense of style throughout your home.</p>
</div>

<div>
	<div>
		{% for product in products %}
		<div>
			<div>
				<a href=""><img src="{{ product.image.url }}" alt="{{product.name}}"></a>
				<div>
					<h4>{{product.name}}</h4>
					<p>${{product.price}}</p>
				</div>
			</div>
		</div>
		{% endfor %}
	</div>
</div>
{% endblock %}

shop/admin.py

from django.contrib import admin
from .models import Friend, Category, Product

admin.site.register(Friend)

class CategoryAdmin(admin.ModelAdmin):
	list_display = ['name', 'slug']
	prepopulated_field = {'slug':('name',)}
admin.site.register(Category, CategoryAdmin)

class ProductAdmin(admin.ModelAdmin):
	list_display = ['name','price', 'stock', 'available', 'created', 'updated']
	list_editable = ['price', 'stock', 'available']
	prepopulated_field = {'slug':('name',)}
	list_per_page = 20
admin.site.register(Product, ProductAdmin)

settings.py

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
    )
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'static', 'media')

urls.py

from django.contrib import admin
from django.urls import path, include
import shop.views as shop

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('shop/', include('shop.urls')),
]

if settings.DEBUG:
	urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
	urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

static/img に、apple.jpeg, banner.jpeg, logo.jpeg を入れます。
admin画面からデータを入力して、確認します。
http://192.168.33.10:8000/shop/all

おおお、なんか感動した。乾いた感動だけど。

Djangoでショッピングカートを作りたい1

Vagrant, Amazon Linux2, dbはmysql8系を使います。

$ python3 –version
Python 3.7.9
$ pip3 –version
pip 21.0.1 from /usr/local/lib/python3.7/site-packages/pip (python 3.7)
$ pip3 install Django
$ pip3 install PyMySQL

### プロジェクト作成
$ django-admin startproject shoppingcart
$ cd shoppingcart
$ tree
.
├── manage.py
└── shoppingcart
├── __init__.py
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py

1 directory, 6 files

setting.py

ALLOWED_HOSTS = ["192.168.33.10"]
// 省略

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'shoppingcart',
        'USER': 'hoge',
        'PASSWORD': 'fuga',
        'HOST': 'localhost',
        'PORT': '3306',
    }
    # 'default': {
    #     'ENGINE': 'django.db.backends.sqlite3',
    #     'NAME': BASE_DIR / 'db.sqlite3',
    # }
}

mysql> create database shoppingcart;
Query OK, 1 row affected (0.03 sec)

__init__.py

import pymysql
pymysql.install_as_MySQLdb()

initpyでimportしないと、mysqlclientをインストールしたかと聞かれるので注意が必要

$ python3 manage.py runserver 192.168.33.10:8000
$ python3 manage.py startapp shop

settings.py

INSTALLED_APPS = [
    'shop',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

### テストコード作成
$ pip3 install pytest-django

$ touch pytest.ini
$ rm shop/tests.py
$ mkdir shop/tests
$ touch shop/tests/test_views.py

pytest.ini

[pytest]
DJANGO_SETTINGS_MODULE = shoppingcart.settings
python_classes = *Test
python_functions = test_*
python_files = tests.py test_*.py *_tests.py
 
norecursedirs = static templates env

shop/tests/test_views.py

from django.test import TestCase

class ViewTest(TestCase):

	def test_first_page(self):
		response = self.client.get('/')
		assert response.status_code == 200

$ pytest -l -v -s shoppingcart && flake8
============================= test session starts ==============================
platform linux — Python 3.8.5, pytest-6.2.3, py-1.10.0, pluggy-0.13.0 — /usr/bin/python3
cachedir: .pytest_cache
django: settings: shoppingcart.settings (from ini)
rootdir: /home/vagrant/prd/dev/shoppingcart, configfile: pytest.ini
plugins: django-4.2.0
collected 0 items

============================ no tests ran in 0.05s =============================

model

class Category(models.Model):
	name = models.CharField(max_length=250, unique=True)
	slug = models.SlugField(max_length=250, unique=True)
	description = models.ImageField(upload_to='category', blank=True)

	class Meta:
		ordering = ('name',)
		verbose_name = 'category'
		verbose_name_plural = 'categories'

	def __str__(self):
		return '{}'.format(self.name)

$ python3 manage.py makemigrations shop
$ python3 manage.py migrate

class Product(models.Model):
	name = models.CharField(max_length=250, unique=True)
	slug = models.SlugField(max_length=250, unique=True)
	description = models.TextField(blank=True)
	category = models.ForeignKey(Category, on_delete=models.CASCADE)
	price = models.DecimalField(max_digits=10, decimal_places=2)
	image = models.ImageField(upload_to='product', blank=True)
	stock = models.IntegerField()
	available = models.BooleanField(default=True)
	created = models.DateTimeField(auto_now_add=True)

	class Meta:
		ordering = ('name',)
		verbose_name = 'product'
		verbose_name_plural = 'products'

	def __str__(self):
		return '{}'.format(self.name)

[aws ec2] Ubuntu20.04 x Apache2 x Django3.0 x MySQL8系 環境構築

結論から言うと、ec2 x Djangoでapache2 or Nginxを使う場合、仮想環境を作らないと、Djangoのパスが通らずに、「ModuleNotFoundError: No module named ‘django’」とエラーになる。開発環境では、manage.py runserverで確認すれば良かったが、ec2にデプロイしてapache2もしくはNginxを通す場合は、仮想環境の構築が必須。
ec2でapache2もしくはNginxを構築する前に、uWSGIの基礎を学んでから構築した方が、理解が深まるし、トラブルシューティングしやすくなる。
それを省略したので、結局2日かかった orz…

以下が構築手順。Ubuntu20.04のインスタンスが出来ている状態から始める。

### 1.インスタンスログイン
$ ssh ubuntu@${public ip} -i ~/.ssh/*.pem
$ sudo apt update
$ sudo apt upgrade
$ sudo apt install python3-pip

### 2.MySQL8系インストールとdjango用のdb作成
$ sudo apt install mysql-client-core-8.0
$ sudo apt-get update
$ sudo apt install mysql-server
$ sudo service mysql start
$ sudo mysql_secure_installation
$ sudo mysql -u root -p
mysql>set global validate_password.length=6;
mysql>set global validate_password.policy=LOW;
mysql>CREATE USER ‘admin’@’%’ IDENTIFIED BY ‘hogehoge’;
mysql>GRANT ALL PRIVILEGES ON *.* TO ‘admin’@’%’ WITH GRANT OPTION;
mysql>FLUSH PRIVILEGES;
mysql>create database hoge;

### 3.apacheインストール
$ sudo apt update
$ sudo apt install apache2
$ sudo ufw app list
$ sudo ufw allow ‘Apache Full’
$ sudo ufw status
$ sudo systemctl status apache2

### 4.git clone
// 所有権
$ sudo chown ubuntu /home
$ git clone https://github.com/hoge/hoge.git
$ cd hoge

#### 5.django商用設定
settings.py

DEBUG = False
ALLOWED_HOSTS = ['*']  #もしくはEC2のIP
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
migration

// migration
$ python3 manage.py makemigrations hoges
$ python3 manage.py migrate

### 6. 仮想環境構築(!!重要!!)
$ sudo apt install -y python3-wheel python3-venv python3-dev
$ python3 -m venv env
$ source env/bin/activate
$ pip install wheel
// 各種ライブラリインストール 省略
$ apt-get install apache2-dev
$ sudo pip3 install mod_wsgi
$ mod_wsgi-express module-config
LoadModule wsgi_module “/usr/local/lib/python3.8/dist-packages/mod_wsgi/server/mod_wsgi-py38.cpython-38-x86_64-linux-gnu.so”
WSGIPythonHome “/usr”
$ python3 manage.py collectstatic
$ deactivate

### 7. Apache設定
$ sudo vi /etc/apache2/sites-available/django.conf

LoadModule wsgi_module /usr/local/lib/python3.8/dist-packages/mod_wsgi/server/mod_wsgi-py38.cpython-38-x86_64-linux-gnu.so

WSGIPythonHome /usr
WSGIScriptAlias / /home/ubuntu/hoge/hoge/wsgi.py
WSGIPythonPath /home/ubuntu/hoge:/home/ubuntu/hoge/env/lib/python3.8/site-packages

<Directory /home/ubuntu/hoge/hoge>
  <Files wsgi.py>
    Require all granted
  </Files>
</Directory>

Alias /static/ /home/ubuntu/hoge/hoge/
<Directory /home/ubuntu/hoge/static>
  Require all granted
</Directory>

$ sudo a2dissite 000-default
$ sudo a2ensite django
$ sudo systemctl restart apache2
$ sudo systemctl enable apache2

### 8.挙動確認
EC2のpublic IPを叩く

### 9.AMI作成
– インスタンスのバックアップ

お疲れ様でした。

ちなみにこれ、仮想環境構築をすっ飛ばしてインスタンス作成を5〜6回ぐらいやり直してapacheのエラーログ見て悶絶してた。まあ、ここを乗り切ればハードルは一気に下がりますね。

[uWSGI] DjangoをUbuntu+Nginx+uWSGIの構成で動かしたい

### uWSGIとは?
-WSGIは仕様で、uWSGIは既存のWebサーバに機能を追加
-WebサーバにNginx, WSGIコンテナにuWSGI(Nginxとは別プロセス)
-Apache2, Nginx, cherokee, lighttpdに対応
-gunicornというWSGIも有名
-Apacheは同時接続数が極端に多くなると対応できないが、Nginxはレスポンスが早い

### Django構築
$ django-admin startproject testSite
$ python3 manage.py startapp testapp

settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'testapp',
]
ALLOWED_HOSTS = ['*']

views.py

from django.http import HttpResponse
# Create your views here.

def hello(request):
	return HttpResponse("hello, Nginx!")

urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('', views.hello, name='hello'),
]
urlpatterns = [
    path('admin/', admin.site.urls),
    path('testapp/', include('testapp.urls')),
]

### UbuntuにNginx
$ sudo apt update
$ apt show nginx
Package: nginx
Version: 1.18.0-0ubuntu1
$ sudo apt install nginx
$ nginx -v

### djangoでuWSGI
$ python3 manage.py runserver 192.168.33.10:8000

$ uwsgi –http :8000 –module testSite.wsgi

testApp/uwsgi_params

uwsgi_param  QUERY_STRING       $query_string;
uwsgi_param  REQUEST_METHOD     $request_method;
uwsgi_param  CONTENT_TYPE       $content_type;
uwsgi_param  CONTENT_LENGTH     $content_length;
 
uwsgi_param  REQUEST_URI        $request_uri;
uwsgi_param  PATH_INFO          $document_uri;
uwsgi_param  DOCUMENT_ROOT      $document_root;
uwsgi_param  SERVER_PROTOCOL    $server_protocol;
uwsgi_param  REQUEST_SCHEME     $scheme;
uwsgi_param  HTTPS              $https if_not_empty;
 
uwsgi_param  REMOTE_ADDR        $remote_addr;
uwsgi_param  REMOTE_PORT        $remote_port;
uwsgi_param  SERVER_PORT        $server_port;
uwsgi_param  SERVER_NAME        $server_name;

testApp/testSite_nginx.conf

upstream django {
	server 192.168.33.10:8001;
}

server {
	listen 8000;
	server_name 192.168.33.10;
	charset utf-8;

	location /static {
		alias /home/vagrant/other/testSite/static;
	}

	location / {
		uwsgi_pass django;
		include /home/vagrant/other/testSite/testSite/uwsgi_params;
	}
}

$ sudo ln -s /home/vagrant/other/testSite/testSite/testSite_nginx.conf /etc/nginx/sites-enabled/

setting.py

STATIC_ROOT = os.path.join(BASE_DIR, "static/")

$ python3 manage.py collectstatic

$ sudo nginx
failed (Result: exit-code)

$ sudo lsof -i:80
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 75758 root 6u IPv4 87712 0t0 TCP *:http (LISTEN)
nginx 75758 root 7u IPv6 87713 0t0 TCP *:http (LISTEN)
nginx 75759 www-data 6u IPv4 87712 0t0 TCP *:http (LISTEN)
nginx 75759 www-data 7u IPv6 87713 0t0 TCP *:http (LISTEN)
nginx 75760 www-data 6u IPv4 87712 0t0 TCP *:http (LISTEN)
nginx 75760 www-data 7u IPv6 87713 0t0 TCP *:http (LISTEN)

$ uwsgi –socket :8001 –module testSite.wsgi buffer-size=32768

うーん、ちょっとよくわからんな。。

[aws ec2]ubuntu20.04にningxを入れて起動させたい

1. SSHログイン
ssh ubuntu@* -i ~/.ssh/*.pem

2. apt update
$ sudo apt update
$ sudo apt upgrade

3. mysql
$ sudo apt install mysql-client-core-8.0
$ sudo apt-get update
$ sudo apt install mysql-server
$ mysqld –version
$ sudo service mysql start
$ sudo mysql_secure_installation
$ sudo mysql -u root -p
mysql> create database hanbai;

4. Nginx install
$ sudo apt install nginx

5. 仮想環境作成
$ sudo apt install python3-venv python3-pip python3-dev
$ sudo chown ubuntu /home
$ git clone https://github.com/*/*.git
$ cd hanbai
$ ls
README.md db.sqlite3 hanbai manage.py sales
$ python3 -m venv vdjango

$ . vdjango/bin/activate
(vdjango)$ pip3 install Django==3.0.4
(vdjango)$ pip3 install uwsgi

$ sudo ufw allow 80

6. Nginx設定
$ cd /etc/nginx/conf.d
$ sudo vi project.conf

server{
    listen 80;
    server_name ${publicIp};

    location / {
        proxy_pass http://127.0.0.1:8000;
    }
}

7. iniファイル
$ ls
README.md db.sqlite3 hanbai manage.py sales vdjango
$ vim django.ini

[uwsgi]
module          =  project.wsgi:application
master          =  true
pidfile         =  django.uwsgi.pid
enable-threads  = true
http            =  127.0.0.1:8000
processes       =  5
harakiri        =  50
max-requests    =  5000
vacuum          =  true
home            =  vdjango
daemonize       =  django.uwsgi.log

8. library install & migration
// 省略

9. Nginxとuwsgi起動
$ sudo service nginx start
$ sudo apt-get install -y uwsgi
$ sudo apt install uwsgi-plugin-python3

うーん、
$ sudo python3 manage.py runserver 0.0.0.0:8000 で8000ポート開けても動くんだけど、なんか違うんだよな。。

[aws ec2]Apache2を入れるが、Internal Server Error

### apache2 インストール
$ sudo apt update
$ sudo apt install apache2
$ sudo ufw app list
$ sudo ufw allow ‘Apache Full’
$ sudo ufw status
$ sudo systemctl status apache2
$ hostname -I

### mod_wsgi
$ apt-get install apache2-dev
$ pip3 install mod_wsgi

### settings.py

ALLOWED_HOSTS = ['*'] 

$ mod_wsgi-express module-config
LoadModule wsgi_module “/home/ubuntu/.local/lib/python3.8/site-packages/mod_wsgi/server/mod_wsgi-py38.cpython-38-x86_64-linux-gnu.so”
WSGIPythonHome “/usr”

### migrate
$ python3 manage.py makemigrations sales
$ python3 manage.py migrate

### apache設定
sudo vi /etc/apache2/sites-available/000-default.conf

LoadModule wsgi_module "/usr/lib/apache2/modules/mod_wsgi-py38.cpython-38-x86_64-linux-gnu.so"
WSGIScriptAlias / /var/www/hanbai/hanbai/wsgi.py
WSGIPythonHome "/usr"
WSGIPythonPath "/var/www/hanbai"

<VirtualHost *:80>
        ServerAdmin webmaster@localhost
        # DocumentRoot /var/www/hanbai

        # WSGIScriptAlias / /var/www/hanbai/hanbai/wsgi.py
        # WSGIPythonPath /var/www/hanbai/        
        <Directory /var/www/hanbai/hanbai/>
            <Files wsgi.py>
                Order deny,allow
                AllowOverride None
                require all granted
            </Files>
        </Directory>
</VirtualHost>

$ sudo /etc/init.d/apache2 restart

$ /var/log/apache2/error.log
[client 59.126.236.35:46554] from django.core.wsgi import get_wsgi_application
[Wed Oct 28 23:46:17.546723 2020] [wsgi:error] [pid 10444:tid 139970643724032] [client 59.126.236.35:46554] ModuleNotFoundError: No module named ‘django’

$ pip3 freeze | grep wsgi
mod-wsgi==4.7.1

WSGIPythonPath /var/www/hanbai:/home/ubuntu/.local/lib/python3.8/site-packages

何故だ。。Djangoをインストールした場所が悪かった?
もう一回やるか。。

[Django3.0]send_mailでメール送信

ログイン時にユーザ名を忘れたユーザがいたら、メールフォームにemailアドレスを入力してもらって、auth_usersにメールアドレスがあれば、そのメールアドレスにユーザ名を送信したい。パスワードは送信しない。

ログイン画面

メール入力画面

公式ドキュメント: send email

SMTPは開発環境なので、mailtrapを使います。
EMAIL_BACKEND = ‘django.core.mail.backends.console.EmailBackend’とすれば、コマンドラインに表示されます。

settings.py

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'

EMAIL_HOST = 'smtp.mailtrap.io'
EMAIL_PORT = 587
EMAIL_HOST_USER = '******'
EMAIL_HOST_PASSWORD = '******'
EMAIL_USE_TLS = True

### views.py
auth_userテーブルから、一致するemailがあるか確認し、あれば、そのメールアドレスにusernameを送信する。
送信はsend_mailを使用する。
本文の改行は”\n”

from django.core.mail import send_mail

def username_send(request):
	if(request.method == 'POST'):
		data = User.objects.filter(email=request.POST['email']).first()
		if data is not None:
			subject = "【Hanai】ユーザ名のお知らせ"
			message = 'お問い合わせありがとうございます。\nログインに必要なユーザ名をお知らせします。\n\nユーザ名:' + data.username + '\n\n\n※本メールは販売管理システムの\n送信専用のメールアドレスから自動送信されています。\nご返信いただいても返信できませんのでご了承ください。\n本サービスに心上がりがない場合など、お問い合わせください。\n\nHanbai'
			from_email = 'master@hanbai.com'
			recipient_list = [ data.email ]
			send_mail(subject, message, from_email, recipient_list) 
		else:
			params = {
				'message': 'メールアドレスの登録がありません。'
			}
			return render(request, 'sales/username_forget.html', params)
	return render(request, 'sales/username_send.html')

メール受信

OK! 大分来た
さあ、この調子で続けてラズパイとopenCVやります。