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)

typescript入門

$ npm –version
$ npm init
$ sudo npm install typescript
$ echo export PATH=\$PATH:`pwd`/node_modules/.bin >> ~/.bashrc
$ source ~/.bashrc
$ tsc –version
Version 4.2.4

function hello(name: string): void {
	console.log("Hello " + name + "!");
}
let your_name: string = "Yamada";
hello(your_name);

$ tsc sample.ts
$ node sample.js
Hello Yamada!

WordPressでPluginをゼロから作りたい

Pluginのフォルダにファイルを作成し、ライセンス情報を記載する

/*
	Plugin Name: CustomIndexBanner
	Plugin URI: http://hpscript.com/blog/
	Description: indexページに表示可能なバナーの設定
	Version: 1.0.0
	Author: Hpscript
	Author URI: http://hpscript.com/blog/
	License: MIt
*/

すると、プラグインとして自動で認識される

メニュー追加

add_action('init', 'CustomIndexBanner::init');

class CustomIndexBanner {

	static function init(){
		return new self();
	}

	function __construct(){
		if(is_admin() && is_user_logged_in()){
			add_action('admin_menu', [$this, 'set_plugin_menu']);
			add_action('admin_menu', [$this, 'set_plugin_sub_menu']);
		}
	}

	function set_plugin_menu(){
		add_menu_page (
			'カスタムバナー', // ページタイトル
			'カスタムバナー', // メニュータイトル
			'manage_options', // 権限
			'custom-index-banner', // ページを開いたときのURL
			[$this, 'show_about_plugin'], // メニューに紐づく画面を描画するcallback関数
			'dashicons-format-gallery', // アイコン
			99 // 表示位置オフセット
		);
	}
	function set_plugin_sub_menu(){
		add_submenu_page(
			'custom-index-banner',
			'設定',
			'設定',
			'manage_options',
			'custom-index-banner-config',
			[$this, 'show_config_form']
		);
	}
}

	function show_about_plugin(){
		$html = "<h1>カスタムバナー</h1>";
		$html .= "<p>トップページに表示するバナーを指定できます</p>";

		echo $html;
	}

	function show_config_form(){

?>
	<h1>カスタムバナーの設定</h1>
<?php
	}
}

### フォームの作成

	const VERSION = '1.0.0';
	const PLUGIN_ID = 'custom-index-banner';
	const CREDENTIAL_ACTION = self::PLUGIN_ID . '-nonce-action';
	const CREDENTIAL_NAME = self::PLUGIN_ID . '-nonce-key';
	const PLUGIN_DB_PREFIX = self::PLUGIN_ID . '_';


	function show_config_form(){
		// wp_optionsを引っ張る
		$title = get_option(self::PLUGIN_DB_PREFIX . "_title");
?>
	<div class="wrap">
		<h1>カスタムバナーの設定</h1>

		<form action="" method='post' id="my-submenu-form">
			<?php wp_nonce_field(self::CREDENTIAL_ACTION, self::CREDENTIAL_NAME) ?>

			<p>
				<label for="title">タイトル:</label>
				<input type="text" name="title" value="<?= $title ?>"/>
			</p>

			<p><input type="submit" value="保存" class="button button-primary button-large"></p>
		</form>
	</div>
<?php
	}
}


	const CONFIG_MENU_SLIG = self::PLUGIN_ID . '-config';
	function save_config(){

		if(isset($_POST[self::CREDENTIAL_NAME]) && $_POST[self::CREDENTIAL_NAME]){
			if(check_admin_referer(self::CREDENTIAL_ACTION, self::CREDENTIAL_NAME)){

				$key =
				$title = $_POST($value['title']) ? $_POST['title'] : "";

				update_option(self::PLUGIN_DB_PREFIX . $key, $title);
				$completed_text = "設定の保存が完了しました。管理画面にログインした状態で、トップページにアクセスし変更が正しく反映されたか確認してください。";

				set_transient(self::COMPLETE_CONFIG, $completed_text, 5);

				wp_safe_redirect(menu_page_url(self::CONFIG_MENU_SLUG), false);

			}
		}
	}

なんやこれは。。。 プラグインの実装はテーマエディタではなく、プラグインのソースコードでカスタマイズすんのか。 時間かかるな。

[WordPress] MTS Simple Bookingに決済処理をカスタマイズしたいが。。。

MTS Simple Bookingのプラグインの仕組みを検証する
まずファイル構造から
$ sudo yum install tree
$ tree
.
├── css
│   ├── mtssb-admin.css
│   └── mtssb-front.css
├── image
│   ├── ajax-loader.gif
│   ├── ajax-loaderf.gif
│   ├── system-stop.png
│   └── system-tick.png
├── js
│   ├── mtssb-article-admin.js
│   ├── mtssb-booking-admin.js
│   ├── mtssb-calendar-widget.js
│   └── mtssb-schedule-admin.js
├── languages
│   ├── ja.mo
│   └── ja.po
├── mts-simple-booking.php
├── mtssb-article-admin.php
├── mtssb-article.php
├── mtssb-booking-admin.php
├── mtssb-booking-form.php
├── mtssb-booking.php
├── mtssb-calendar-admin.php
├── mtssb-calendar-widget.php
├── mtssb-front.php
├── mtssb-list-admin.php
├── mtssb-mail.php
├── mtssb-schedule-admin.php
├── mtssb-settings-admin.php
└── uninstall.php

一つ一つ理解してカスタマイズするより前に、プラグインそのものを理解しないとあかんね。

[WordPress] MTS Simple Bookingを使う

MTS Simple Bookingのhpで、Dowloadページからzipファイルをダウンロードします。
http://mtssb.mt-systems.jp/downloadp/

そして、Wordpress管理画面のプラグインページでPluginのアップロードフォームから先ほどダウンロードしたzipファイルをuploadします。

すると、管理画面上に予約システムが表示されたことがわかります。

続いてスタートガイドを参考に各種設定をしていきます。

### 予約システム各種設定
「各種設定」のページで予約機能の設定をしていきます。
予約パラメータ、施設情報、予約メール、その他のタブメニューがあります。
施設情報は予約メールに記載される内容を書きます。
予約メールには、入力項目の設定と、メール後文には「施設情報」のタブで入力した変数を入力する。

### 予約品目
予約条件設定で、件数、人数などの条件を設定できる。時間割(スタートタイム)、制約タイプなども併せて設定する。
edit時のパラメータ post=${num} をコピーしておく。

### スケジュール登録
予約システムのスケジュールに表示する

### フロントページへの登録
固定ページにbooking-formとスラックを名付ける
併せて、booking-thanksの予約完了ページを作成する

### 管理画面側
予約リストで予約状況を確認できる。
ほう、中々凄い

パラメータのutm=1618826400はlinuxtimeですね。
問題はどうやってクレジットカード決済を導入するかだな。。
mtssb-booking-form.phpでbooking-formの処理を行なっているけど、submitせずに、決済画面にパラメータも一緒に飛ばしたい。

[python3] フォームのデータを受信し表示

index.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
</head>
<body>
	<h1>翻訳したい言語を入力してください</h1>
	<form method="POST" action="result.py">
		<label>テキスト:</label>
		<textarea name="text"></textarea>
		<button type="submit">送信</button>
	</form>
</body>
</html>

result.py

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import cgi
import cgitb
import sys

cgitb.enable()

form = cgi.FieldStorage()

print("Content-Type: text/html; charset=UTF-8")
print("")

if "text" not in form:
	print("<h1>Erro!</h1>")
	print("<br>")
	print("テキストを入力してください!")
	print("<a href='/'><button type='submit'>戻る</button></a>")
	sys.exit()

text = form.getvalue("text")
print(text)

ん。。。なんか上手くいかんな。

[CentOS8]ビルトインサーバにアクセス出来ない時

vagrant & CentOS8でビルトインサーバを起動します。

$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) …

なんでやねん。

Vagrantifle

  config.vm.network "private_network", ip: "192.168.34.10"

プロキシの設定等はしていない。どうやらfirewallが怪しい、ということで
$ sudo firewall-cmd –list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0 eth1
sources:
services: cockpit dhcpv6-client ssh
ports:
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:

あ、やっぱり。http, 8000がアクセス出来ません。

$ sudo firewall-cmd –add-service=http –zone=public –permanent
$ sudo firewall-cmd –add-port=8000/tcp –zone=public –permanent
$ firewall-cmd –reload

出来ました。アホみたいなミスでした。
CentOS8だとデフォルトでfirewallがonになってるんですね。そういえば、sakuraのvpsなどもポートフィルタリングがデフォルトでonになってます。クワバラクワバラ

[python3] googletransで日本語->英語に翻訳する

$ sudo pip3 install googletrans
Successfully installed chardet-3.0.4 contextvars-2.4 googletrans-3.0.0 h11-0.9.0 h2-3.2.0 hpack-3.0.0 hstspreload-2020.12.22 httpcore-0.9.1 httpx-0.13.3 hyperframe-5.2.0 immutables-0.15 rfc3986-1.4.0 sniffio-1.2.0

# -*- coding: utf-8 -*-
from googletrans import Translator
translator = Translator()

translation = translator.translate("こんにちは", src='ja', dest="en")
print(translation.text)

$ python3 app.py
AttributeError: ‘NoneType’ object has no attribute ‘group’

どうやら3.0.0ではなく、4系が動いているとのこと
$ sudo pip3 uninstall googletrans
$ sudo pip3 install googletrans==4.0.0-rc1

# -*- coding: utf-8 -*-
from googletrans import Translator
translator = Translator()

translation = translator.translate("h音楽アーティストやレコードレーベルが保有する楽曲を NFT 化し世界中に販売", src='ja', dest='en')
print(translation.text)

$ python3 app.py
Music artists and songs owned by record labels NFT and sell worldwide

なかなか凄いな
formからpostして翻訳して返却したいな