2020年1月26日 星期日

採用AppSeed提供的Django Admin Dashboards,為Django網站換新裝

今天是鼠年初二,在初一時,發現好東西,就是AppSeed的Django管理儀錶板(Django Admin Dashboards),下載的網址:https://appseed.us/admin-dashboards/django。除了Django外,還有Flasklask的管理面板,網址:https://appseed.us/admin-dashboards/flask-dashboard-argon。當然還有更精彩的付費管理面板,所有管理面板其網址:https://appseed.us/admin-dashboards,多達47個Django/Flask管理面板可供您下載,其中開放式源始碼的管理面板(https://appseed.us/admin-dashboards/open-source)也有33個。



我們以CoreUI為例:https://appseed.us/admin-dashboards/django-dashboard-coreui,其展示影片如下:

其開放式原始碼網址:https://github.com/app-generator/django-dashboard-coreui

1.下載程式碼
git clone https://github.com/app-generator/django-dashboard-coreui.git


2.切換工作目錄
cd django-dashboard-coreui

3.建立虛擬環境
mkvirtualenv django-dashboard-coreui

4.安裝套件
pip3 install -r requirements.txt

5.產生資料庫表單
python manage.py makemigrations
python manage.py migrate

6.執行網站
python manage.py runserver

7.開啟瀏覽器,觀看結果

8.註冊使用者帳號
發現不使直接增加使用者

9.在cmd按下Ctrl+C,建立使用者帳號
python manage.py createsuperuser
Username:demo
Email:test@gmail.com
Password:1234
Passwaord(again):1234

10.再度啟動網站
python manage.py runserver

11.輸入帳號

12.進入管理面板

現在您可以開始享受智慧生活,祝福您 鼠年 身體健康 平安順利 鈔票鼠不完。

2020年1月24日 星期五

xavierlesa / channels-asgi-mqtt測試心得

開放源碼:xavierlesa/channels-asgi-mqtt

按照說明,修改兩個地方,一個是broker位址,另一個是專案名稱。
chasgimqtt -H broker.hivemq.com -p 1883 --topic=some_topic:2 mysite.asgi:channel_layer
執行結果:

由於我們設定虛擬環境是channels-asgi-mqtt,因此要修訂\Envs\channels-asgi-mqtt\Lib\site-packages\chasgimqtt程式碼(187-191行):


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
import os
import asyncio
import functools
import logging
import time
import signal
import json

import paho.mqtt.client as mqtt

logger = logging.getLogger(__name__)

async def mqtt_send(future, channel_layer, channel, event):
    result = await channel_layer.send(channel, event)
    future.set_result(result)

async def mqtt_group_send(future, channel_layer, group, event):
    result  = await channel_layer.group_send(group, event)
    future.set_result(result)

# Solo para grupos
async def mqtt_group_add(future, channel_layer, group):
    channel_layer.channel_name = channel_layer.channel_name or await channel_layer.new_channel()
    result = await channel_layer.group_add(group, channel_layer.channel_name)
    future.set_result(result)

# Solo para grupos
async def mqtt_group_discard(future, channel_layer, group):
    result = await channel_layer.group_discard(group, channel_layer.channel_name)
    future.set_result(result)


class Server(object):
    def __init__(self, channel, host, port, username=None, password=None, 
            client_id=None, topics_subscription=None, mqtt_channel_name = None, 
            mqtt_channel_sub=None, mqtt_channel_pub=None):

        self.channel = channel
        self.host = host
        self.port = port
        self.client_id = client_id
        self.client = mqtt.Client(client_id=self.client_id, userdata={
            "server": self,
            "channel": self.channel,
            "host": self.host,
            "port": self.port,
        })
        self.username = username
        self.password = password
        self.client.on_connect = self._on_connect
        self.client.on_disconnect = self._on_disconnect
        self.client.on_message = self._on_message

        self.topics_subscription = topics_subscription or [("#", 2),]
        assert isinstance(self.topics_subscription, list), "Topic subscription must be a list with (topic, qos)"

        self.mqtt_channel_name = mqtt_channel_name or "mqtt"
        self.mqtt_channel_pub = mqtt_channel_pub or "mqtt.pub"
        self.mqtt_channel_sub = mqtt_channel_sub or "mqtt.sub"


    def _on_connect(self, client, userdata, flags, rc):
        logger.info("Connected with status {}".format(rc))
        client.subscribe(self.topics_subscription)


    def _on_disconnect(self, client, userdata, rc):
        logger.info("Disconnected")
        if not self.stop:
            j = 3
            for i in range(j):
                logger.info("Trying to reconnect")
                try:
                    client.reconnect()
                    logger.info("Reconnected")
                    break
                except Exception as e:
                    if i < j:
                        logger.warn(e)
                        time.sleep(1)
                        continue
                    else:
                        raise

    def _mqtt_send_got_result(self, future):
        logger.debug("Sending message to MQTT channel, with result\r\n%s", future.result())

    def _on_message(self, client, userdata, message):
        logger.debug("Received message from topic {}".format(message.topic))
        payload = message.payload.decode("utf-8")

        try:
            payload = json.loads(payload)
        except:
            logger.debug("Payload is nos a JSON Serializable\r\n%s", payload)
            pass

        msg = {
            "topic": message.topic,
            "payload": payload,
            "qos": message.qos,
            "host": userdata["host"],
            "port": userdata["port"],
        }

        try:

            future = asyncio.Future()
            asyncio.ensure_future(
                    mqtt_send(
                        future, 
                        self.channel, 
                        self.mqtt_channel_name,
                        {
                            "type": self.mqtt_channel_sub,
                            "text": msg
                        })
                )

            future.add_done_callback(self._mqtt_send_got_result)

            
        except Exception as e:
            logger.error("Cannot send message {}".format(msg))
            logger.exception(e)


    async def client_pool_start(self):
        # Loop para recibir mensajes MQTT
        if self.username:
            self.client.username_pw_set(username=self.username, password=self.password)
        
        self.client.connect(self.host, self.port)

        logger.info("Starting loop")

        while True:
            self.client.loop(0.1)
            #logger.debug("Restarting loop")
            await asyncio.sleep(0.1)


    def _mqtt_receive(self, msg):
        """
        Recibe un mensaje desde el Channel `mqtt.pub` y lo envia al broker MQTT
        """

        # Solo nos interesan los messages del channel asociado al mqtt_channel_pub
        if msg['type'] == self.mqtt_channel_pub:

            payload = msg['text']

            if not isinstance(payload, dict):
                payload = json.loads(payload)

            logger.info("Recibe un menssage con payload: %s", msg)
            self.client.publish(
                    payload['topic'], 
                    payload['payload'], 
                    qos=payload.get('qos', 2), 
                    retain=False)


    async def client_pool_message(self):
        logger.info("Loop de recepción de messages")

        while True:
            logger.info("Espera recibir un message desde el channel %s", self.mqtt_channel_name)
            result = await self.channel.receive(self.mqtt_channel_name)
            self._mqtt_receive(result)
            await asyncio.sleep(0.1)
            

    def stop_server(self, signum):
        logger.info("Received signal {}, terminating".format(signum))
        self.stop = True
        for task in asyncio.Task.all_tasks():
            task.cancel()
        self.loop.stop()


    def run(self):
        self.stop = False
        loop = asyncio.get_event_loop()
        self.loop = loop

        #for signame in ('SIGINT', 'SIGTERM'):
        #    loop.add_signal_handler(
        #            getattr(signal, signame),
        #            functools.partial(self.stop_server, signame)
        #        )

        print("Event loop running forever, press Ctrl+C to interrupt.")
        print("pid %s: send SIGINT or SIGTERM to exit." % os.getpid())


        tasks = asyncio.gather(*[
                asyncio.ensure_future(self.client_pool_start()),
                asyncio.ensure_future(self.client_pool_message()),
            ])

        asyncio.wait(tasks)

        try:
            loop.run_forever()
        finally:
            loop.run_until_complete(loop.shutdown_asyncgens())            
            loop.close()
        
        self.client.disconnect()

再執行完就可以正常工作:
chasgimqtt -H broker.hivemq.com -p 1883 --topic=some_topic:2 mysite.asgi:channel_layer
執行結果:
另外要再執行下列命令。
manage.py runworker mqtt

並利用MQTTBox來進行測試,測試結果如上圖。

接下來,在mqtt_app/tasks.py程式中增加一小段程式,理由是我們發現原程式無法送出MQTT訊息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from __future__ import absolute_import, unicode_literals

from celery.schedules import crontab
from celery import shared_task

from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
import paho.mqtt.publish as publish

channel_layer = get_channel_layer()

@shared_task(bind=True)
def mqtt_test(self, msg):
    print(f"Celery task say: \"{msg}\"")
    # publish a message then disconnect.
    
    host = "broker.hivemq.com"
    topic = "some_topic"

    # If broker asks user/password.
    auth = {'username': "", 'password': ""}

    # If broker asks client ID.
    client_id = ""

    publish.single(topic, payload=msg+"123", qos=1, hostname=host)
    

    

    async_to_sync(channel_layer.send)('mqtt', {
        'type': 'mqtt_pub',
        'text': {
            'topic': 'some_topic', 
            'payload': f"{msg} - {self.request.id}"
        }
    })
    
    

測試我們加開3個cmd,分別執行下列指令:
redis-server
celery -A mysite worker --pool=solo -l info
celery -A mysite beat -l info



    我們故意在我們程式中加上"123",可以發現僅有加上"123"的"hello"訊號可以送到MQTTBOX。

2020年1月23日 星期四

採用Python、Django、Channels、MQTT、Celery等技術打造網站背景執行的服務


問題:
在生活中,我們經常地使用網站查資料,因此大多數的網站設計都比較偏向於前端設計,提供很美的人機介面,然而網站需要背景執行來和物聯網設備連接。


1.建構虛擬環境
mkvirtualenv channels-agsi-mqtt

2. pip install django

3.建立專案
django-admin startproject mysite

4. 安裝 channels_redis
pip install channels_redis

mysite.asgi.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
"""
ASGI config for mysite project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
"""

import os
import django
from channels.routing import get_default_application
from channels.layers import get_channel_layer

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
django.setup()

# Application
application = get_default_application()

# Layers
channel_layer = get_channel_layer()

mysite/__init__.py
1
2
3
4
5
6
7
from __future__ import absolute_import, unicode_literals

# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app

__all__ = ('celery_app',)

mysite/celery.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
import mqtt_app.client

# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')

print("celery")
app = Celery('mysite', broker='redis://localhost:6379')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.conf.beat_schedule = {
    'msg-every-3-seconds': {
        'task': 'mqtt_app.tasks.handles',
        'schedule': 3,
        'args': ('南投好山好水', )
    },
}

# Load task modules from all registered Django app configs.
app.autodiscover_tasks()

mqtt_app/client.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# client.py
import paho.mqtt.client as mqtt

def on_message(client, userdata, msg):
    print(msg.topic,msg.payload)
    
def on_connect(client, userdata, flags, rc):
    print("connect")
    client.subscribe("nkut/test")

client = mqtt.Client('admin')

client.on_message = on_message
client.on_connect = on_connect

client.loop_start()
client.connect('broker.hivemq.com', 1883, 60)

#client.loop_forever()

mqtt_app/tasks.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from __future__ import absolute_import, unicode_literals

from celery.schedules import crontab
from celery import shared_task

from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
import paho.mqtt.publish as publish

channel_layer = get_channel_layer()

@shared_task(bind=True)
def handles(self, msg):
    print(f"Celery task say: \"{msg}\"")
    # publish a message then disconnect.
    host = "broker.hivemq.com"
    topic = "tw/rocksaying"

    # If broker asks user/password.
    auth = {'username': "", 'password': ""}

    # If broker asks client ID.
    client_id = ""

    publish.single(topic, payload=msg, qos=1, hostname=host)

測試
redis-server

celery -A mysite worker --pool=solo -l info
celery -A mysite beat -l info








參考資料:

Django Channels
Using Celery and MQTT to launch long running tasks with feedback to the user

Asynchronous Tasks With Django and Celery

Celery User Guide

How to use celery in mqtt


2020年1月19日 星期日

好玩有趣的 Bokeh用 Python 來設計一個圖+兩個下拉式選單

請先閱讀前一篇文章:好玩有趣的 Bokeh用 Python 來設計網頁互動視覺化

問題:一個圖+兩個下拉式選單,例如第一個下拉是11月和12月,第二個下拉是品牌A和品牌B,共4種數據。

程式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.models.widgets import Select
from bokeh.io import curdoc
from bokeh.layouts import column, row

import pandas as pd

#圖的資料
d1 = {'time': [1,2,3,4], 'y': [2,4,6,8]}
d2 = {'time': [1,2,3,4,5], 'y': [2,1,1,8,22]}
d3 = {'time': [1,2,3,4], 'y': [8,6,4,2]}
d4 = {'time': [1,2,3,4,5], 'y': [22,8,1,1,2]}
df1 = pd.DataFrame(data=d1, )
df2 = pd.DataFrame(data=d2, )
df3 = pd.DataFrame(data=d3, )
df4 = pd.DataFrame(data=d4, )

#設定數據資料來源
source = ColumnDataSource(df1 )
source.data = df1

#配圖
p = figure()
r = p.vbar(x='time', top='y', width=1,
         source = source)

#第一個下拉選單
select = Select(title="月份",  options=['11月', '12月'])

#第二個下拉選單
select2 = Select(title="品牌",  options=['品牌 A', '品牌 B'])

#為下拉選單置預設值
select.value, select2.value = '11月', '品牌 A'

#第一個下拉選單的callback
def update_plot(attrname, old, new):
    newSource = df1
    if select.value == '11月' and select2.value == '品牌 A':
        newSource = df1
    if select.value == '11月' and select2.value == '品牌 B':
        newSource = df2
    if select.value == '12月' and select2.value == '品牌 A':
        newSource = df3
    if select.value == '12月' and select2.value == '品牌 B':
        newSource = df4
    source.data =  newSource 

#第一個下拉選單動作
select.on_change('value', update_plot)
#第二個下拉選單動作
select2.on_change('value', update_plot)
#把表單放入網頁的佈局
layout = column(row([select, select2], width=400), p)
curdoc().add_root(layout)

以上程式儲存在test.py
執行指令為
bokeh serve --show test.py

執行畫面:


2020年1月18日 星期六

好玩有趣的 Bokeh用 Python 來設計網頁互動視覺化

安裝 Bokeh以及簡易的範例請參考這篇文章:Python 上前端!利用 Bokeh 與 Python 製作網頁互動視覺化

背景:來自一位Nantou.py社員的問題,經由Nantou.py大家庭,大家相互討論,以共學方式共同解決該問題。

LINE群組:https://line.me/R/ti/g/YX6mi3AUqc
臉書群組:https://www.facebook.com/groups/Nantou.py/

問題:能否把兩個下拉式表單放在同一畫面,並能操作。

程式碼:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, output_file, show, output_notebook, gridplot
from bokeh.models.widgets import Select
from bokeh.io import curdoc
from bokeh.layouts import column, row

import pandas as pd

#第一個圖的資料
d1 = {'time': [1,2,3,4], 'y': [2,4,6,8]}
d2 = {'time': [1,2,3,4,5], 'y': [2,1,1,8,22]}
df1 = pd.DataFrame(data=d1, )
df2 = pd.DataFrame(data=d2, )

source = ColumnDataSource(df1 )

p = figure()
r = p.vbar(x='time', top='y', width=1,
         source = source)

#第一個下拉選單
select = Select(title="monthly csv-s",  options=['d1', 'd2'])

#第一個下拉選單的callback
def update_plot(attrname, old, new):
    if select.value == 'd1':
        newSource = df1
    if select.value == 'd2':
        newSource = df2
    source.data =  newSource 

#第一個下拉選單動作
select.on_change('value', update_plot)
layout1 = column(row(select, width=400), p)
#curdoc().add_root(layout)

#第二個圖的資料
d3 = {'time': [1,2,3,4], 'y': [8,6,4,2]}
d4 = {'time': [1,2,3,4,5], 'y': [22,8,1,1,2]}
df3 = pd.DataFrame(data=d3, )
df4 = pd.DataFrame(data=d4, )

source2 = ColumnDataSource(df3 )

p2 = figure()
r1 = p2.vbar(x='time', top='y', width=1,
         source = source2)

#第二個下拉選單
select2 = Select(title="monthly csv-s2",  options=['d3', 'd4'])

#第二個下拉選單的callback
def update_plot2(attrname, old, new):
    if select2.value == 'd3':
        newSource2 = df3
    if select2.value == 'd4':
        newSource2 = df4
    source2.data =  newSource2 

#第二個下拉選單動作
select2.on_change('value', update_plot2)
layout2 = column(row(select2, width=400), p2)

#把第一個圖表和第二個圖表組合起來
layout3 = gridplot([[layout1, layout2]], toolbar_location=None)

#安排畫面
curdoc().add_root(layout3)

以上程式儲存在test.py
執行指令為
bokeh serve --show test.py

執行畫面:

2020年1月4日 星期六

#將整個django專案,移植到另一個地方。



#目的
將複製來的myproj專案(請下載練習),移植到新專案myproj01中。

(因為複製來的django專案,並不能直接使用。)

#步驟摘要:
(本例環境是python 3.7.4)
(請自行建立虛擬環境, 啟動虛擬環境後,安裝django)

1. 建立專案myproj01,
   建立myapp(取名myproj專案的app同名,也叫myapp),
   建立資料庫檔案

2. myproj中有,而myproj01裡沒有的檔案和資料夾,複製過去。

3. 以下檔案,從myproj複製到myproj01,直接覆蓋。
    a. settings.py和ruls.py
        (其中settings.py的內容裡,還要將myproj取代成myproj01,應該有3個地方)

    b. models.py、admin.py和views.py。

    c. db.sqlite3

4. 啟動server,python manage.py runserver 8000,然後用瀏覽器打開127.0.0.1:8000應該就可以看到內容了。

5. 如果要上傳到heroku伺服器,請參考 這裡

#操作
  • 新建步驟
1. django-admin startproject myproj01
2. cd myproj01
3. python manage.py startapp myapp
4. python manage.py migrate

  • 新建專案的資料夾結構如下
myproj01
│  db.sqlite3
│  manage.py
│ 
├─myapp
│  │  admin.py
│  │  apps.py
│  │  models.py
│  │  tests.py
│  │  views.py
│  │  __init__.py
│  │ 
│  └─migrations
│          __init__.py
│         
└─myproj01
    │  asgi.py
    │  settings.py
    │  urls.py
    │  wsgi.py
    │  __init__.py
    │ 
    └─__pycache__
            settings.cpython-37.pyc
            urls.cpython-37.pyc
            __init__.cpython-37.pyc
           
  • 剩下的步驟直接參考上面的「步驟摘要2~4」說明

2020年1月3日 星期五

sqlite(四) 在django中使用ORM指令

#前言
  • db browser或是django的後台,都能對資料表內容做編輯,
  • django網頁裡,是使用ORM(Object Relational Mapping)的指令來操作sqlite資料表
舉例如下,
Create: table_name.objects.create()
Read: table_name.objects.all(),  db.objects.get(), db.objects.filter()
Update: data_obj.update()
Delete: data_obj.delete()
              data_obj.save()

#網站:練習CRUD(點選功能後,請按GO)


#程式
  • 各頁面說明:
index:首頁
page6是查詢
page7是新增,page9是新增後的結果。
page8是修改,page10是修改後的結果。
page5是刪除,page11是刪除後的結果。

models.py的主要內容
from django.db import models
# 資料表結構
class gasSite(models.Model):
    gassiteName = models.CharField(max_length=20)
    gassiteAddress = models.TextField(max_length=100)
    gassiteOwner = models.TextField(max_length=10)
    gassiteTelephone = models.CharField(max_length=20)

urls.py的內容
from django.contrib import admin
from django.urls import path
from myapp.views import index,page1,page5,page6,page7,page8,page9,page10,page11

urlpatterns = [
    path('admin/', admin.site.urls),
    path('',index),
    path('1/',page1),
    path('5/',page5),
    path('6/',page6),
    path('7/',page7),
    path('8/',page8),
    path('9/',page9),
    path('10/',page10),
    path('11/',page11),
]

views.py的內容
from django.shortcuts import render
# 本程式的資料表
from myapp.models import gasSite

#test測試用
def searchOne(request):
    theResult=gasSite.objects.get(id=3)
    print(theResult)
    return render(request,"list1.html",locals())
#test測試用
def showdata(request):
    dbData=gasSite.objects.all()
    return render(request,"index.html",locals())

#HOME PAGE
def index(request):
    return render(request,"index.html")

#no use後來沒用上了
def page1(request):
    pass
    return render(request,"page1.html",locals())
   
#展示delete的頁面
def page5(request):
    dbData=gasSite.objects.all()
    return render(request,"page5.html",locals())

#列出資料表所有內容
def page6(request):
    dbData=gasSite.objects.all()
    return render(request,"page6.html",locals())

#展示新增資料的頁面
def page7(request):
    pass
    return render(request,"page7.html",locals())
   
#展示修改資料的頁面
def page8(request):
    dbData=gasSite.objects.all()
    return render(request,"page8.html",locals())
   
#展示新增資料完成的結果
def page9(request):
    if request.method=="POST":
        gname=request.POST['gname']
        gaddress=request.POST['gaddress']
        gowner=request.POST['gowner']
        gtelephone=request.POST['gtelephone']
    data1=gasSite.objects.create(gassiteName=gname,gassiteAddress=gaddress,gassiteOwner=gowner,gassiteTelephone=gtelephone)
    data1.save()
    #取出資料庫的最後一筆,用filter,避免有重複的name
    newdata=gasSite.objects.filter(gassiteName=gname)
    newdata=newdata[len(newdata)-1]
    return render(request,"page9.html",locals())
   
#展示修改資料後的結果
def page10(request):
    if request.method=="POST":
        gid=request.POST['gid']
        gname=request.POST['gname']
        gaddress=request.POST['gaddress']
        gowner=request.POST['gowner']
        gtelephone=request.POST['gtelephone']
    data1=gasSite.objects.filter(id=gid)
    data1.update(gassiteName=gname,gassiteAddress=gaddress,gassiteOwner=gowner,gassiteTelephone=gtelephone)
    return render(request,"page10.html",locals())
   

#展示刪除後的結果
def page11(request):
    if request.method=="POST":
        kid=request.POST['hvalue']
        data1=gasSite.objects.get(id=kid)
        data1.delete()
    return render(request,"page11.html",locals())


 

2020年1月2日 星期四

sqlite(三) 在django中建立

#前言
  • django的內定資料庫是sqlite
  • django處理資料庫的方式
    • 建立或修改資料庫,用migrate
    • 建立資料表,用models.py
    • 操作資料表,用admin.py設定。
  • 程式操作資料庫,使用ORM(Object Relational Mapping),而非SQL語言。
  • 版本python 3.7.4


#操作:djago的建立
  • 建立虛擬環境
python -m venv myenv

  • 安裝django
pip install django

  • 建立django project(sqliteproj) 
django-admin startproject sqliteporj

  • 進入sqliteproj資料夾,建立app(myapp)
python manage.py startapp myapp

註:這裡是根目錄,現在裡面有
專案目錄,sqliteproj
app目錄,myapp
一個檔案manage.py

  • 進入專案目錄sqliteproj
編輯settings.py,將myapp加入
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp',
]
#操作:資料庫和資料表的建立
  • 進入myapp資料夾,建立資料表結構。
編輯models.py
  1. #models.py的內容
  2. from django.db import models

  3. # 建立資料表gassite的結構
  4. class gassite(models.Model):
  5.     gassiteName = models.CharField(max_length=20)
  6.     gassiteAddress = models.TextField(max_length=100)
  7.     gassiteOwner = models.TextField(max_length=10)
  8.     gassiteTelephone = models.CharField(max_length=20)
  9. #
  10. #資料是宜蘭加油站
  11. #欄位有,加油站名稱,地址,所有人,電話。
編輯 admin.py, 登錄資料表,供django後台管理。
from django.contrib import admin
from myapp.models import *
admin.site.register(gassite)


  • 回到根目錄
產生出db.sqlite3資料庫
python manage.py migrate

建立資料表實體(目前沒有資料)
python manage.py makemigrations myapp
python manage.py migrate myapp

(下載安裝)dbbrowser觀察,資料表名稱是myapp_gassite(實際名稱)

也可以登入django後台來觀察,資料表名稱是gassite(django環境下使用的名稱)
在根目錄,建立管理帳號
python manage.py createsuperuser
(依提示,填入帳號,密碼,email可以enter跳過。)
啟動server
python manage.py runserver 8000
開瀏覽器,用剛才的帳號登入後台,就可以看到gassite了。
http://127.0.0.1:8000/admin

  • 從網路匯入加油站的json資料
下載createdata.py到根目錄中執行,輸入資料庫名稱(db.sqlite3),資料表名稱(myapp_gassite),完成資料匯入。
python createdata.py

用dbbroser觀察內容,可以立即看到明細。

如果用django後台,只能看到gassite object(61), ...gassite object(1)等61筆資訊。必須個別點進去,才能看到明細。 如果要顯示更多欄位,作法請參考 這裡