2018年9月28日 星期五

[Django] 如何將Django專案與MariaDB、mysql連線

一般Django專案會使用內建的sqlite3當作資料庫
只要安裝相關套件 Django也是可以支援mariaDB、mysql

一、環境建置
首先安裝需要的軟體,mysql和mariadb二擇一即可。
sudo apt-get update
# mysql
sudo apt-get install mysqldb-server libmysqlclient-dev
# mariadb
sudo apt-get install mariadb-server libmariadbclient-dev

sudo apt-get install python-pip python-dev
pip install mysqlclient

登入資料庫
sudo mysql
新增一個資料庫和使用者,並將資料庫的存取權限授權給使用者。
> CREATE DATABASE `djangoDB`;
> CREATE USER 'myuser' IDENTIFIED BY 'mypassword';
> GRANT ALL privileges ON `djangoDB`.* TO 'myuser'@localhost;
> FLUSH PRIVILEGES;'

完成設定後,指令exit即可離開資料庫
用新使用者的身分登入試試看設定是否正確
-p代表要輸入密碼(不加會跳error)
mysql -u myuser -p


二、Django專案設定
請記得剛剛設定的資料庫名稱和使用者帳密
打開settings.py將原本設定的sqlite3修改成mysql
# project/settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'djangoDB', # DB名稱
        'USER': 'myuser', # 使用者帳號
        'PASSWORD': 'mypassword', # 使用者密碼
        'HOST': 'localhost',
        'PORT': '3306',
        'OPTIONS': {
            'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
        },
    }
}

option如果沒有設定的話,執行migrate會出現警告 相關資料請見 https://docs.djangoproject.com/en/2.1/ref/databases/#mysql-sql-mode


三、設定生效
設定完成後就可以執行migrate
python manage.py migrate
這時就可以打開資料庫確認table有沒有建好,就大功告成囉!

[Django] 如何使外來鍵指向多個模型


[本文翻譯自Bhrigu Srivastava-Django: How to add ForeignKey to multiple models]
https://medium.com/@bhrigu/django-how-to-add-foreignkey-to-multiple-models-394596f06e84

如何用一個外來鍵(Foreign Key)和1種以上的模型(Model)進行關聯。

先假設你有兩個模型,Article和Post
class Article(models.Model):
    content = models.CharField(max_length=100)
class Post(models.Model):
    content = models.CharField(max_length=100)

現在,我們新增一個Comment的模型,且Article和Post皆與此關聯。因此,我們要如何在Comment中新增一個FK,並指向上述模型中其一。

能達到這功能的概念即為通用關係(Generic Relation),Django包含一種contenttypes的應用,你的模型和content type模型在應用中的關係,能使一個模型物件與任何你建立的模型物件之間啟用通用關係,

Comment模型的格式如下:
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType

class Comment(models.Model):
    comm = models.CharField(max_length=50)
    content_type =   models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object=GenericForeignKey('content_type', 'object_id')
(建立Generic Relations時,固定加上最後三行)

ContentType是一種模型,一個ContentType的物件儲存了關於專案中的模型資訊,提供方法來回傳他們代表的模型或進行搜尋。

因此,我們能將Comment與任何種類的模型建立關聯,儲存在content_type。(content_type回傳模型種類,object_id回傳此模型物件的id)

接下來,我們新增一個Comment的物件(instance),並將此指向一個Articel物件或Post物件。

將Comment物件指向Articel物件:
>>> art = Article.objects.get(id=1)
>>> c = Comment(content_object=art, comm='asdf')
>>> c.save()
>>> c.content_object
<Post: post1>
將Comment物件指向Post物件:
>>> pos= Post.objects.get(id=1)
>>> c= Comment(content_object=pos, comm='new comment')
>>> c.save()
>>> c.content_object
<Post: post1>

逆向Generic Relations
再來,為了取得所有與Article和Post關聯的Comment物件,我們能利用GenericRelations來達成。在Article和Post中定義一個新的欄位,在你的模型中新增一個欄位來逆向搜尋。
from django.contrib.contenttypes.fields import GenericRelation

class Article(models.Model):
    content = models.CharField(max_length=100)
    comments = GenericRelation(Comment)
class Post(models.Model):
    content = models.CharField(max_length=100)
    comments = GenericRelation(Comment)

接下來,指令model_object.comments.all()會取得所有指向這個Comment物件,舉例:art是一個Article物件;post是一個Post物件。
>>> art.comments.all()
<QuerySet [<Comment: asdf>, <Comment: test>]>
>>> pos.comments.all()
<QuerySet [<Comment: new_comment>, <Comment: test2>]>
以上就是「物件指向多個模型」你所有需要知道的基本使用方式。

請參考The contenttypes framework,能知道更多關於contenttypes的應用。

2018年9月27日 星期四

[Python] 用Selenium訂台鐵車票

首先說明Selenium如何抓到指定的欄位
假設有個HTML原始碼
<input id="my_name" type="text" name="fname"/>
代表可以透過id my_name來填入資料,寫成:
input = browser.find_element_by_xpath("//input[@id='my_name']")
input.send_keys('Amy')

就可以在指定的欄位中填入你的名字
但如果剛好網站不像這樣這麼單純的話
還有一個簡單的方法可以取得xpath

檢視原始碼找到元件,點原始碼按右鍵→Copy→Copy XPath
再到程式碼中填上就可以了


訂購車票程式範例
from selenium import webdriver
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException, WebDriverException
import time

if __name__ == '__main__':
    try:
        browser = webdriver.Chrome()
        browser.get('http://railway.hinet.net/Foreign/TW/etno1.html')

        person_id = browser.find_element_by_xpath("//input[@id='person_id']")
        person_id.send_keys('A123456789') # 身分證字號

        date = browser.find_element_by_xpath("//select[@id='getin_date']")
        date.send_keys('2018/09/27') # 日期

        from_station = Select(browser.find_element_by_id('from_station'))
        from_station.select_by_value('100') # 起站代碼
        to_station = Select(browser.find_element_by_id("to_station"))
        to_station.select_by_value('149') # 到站代碼

        train_no = browser.find_element_by_xpath("//input[@id='train_no']")
        train_no.send_keys('181') # 車次

        browser.find_element_by_css_selector('button.btn.btn-primary').click()

        rand = browser.find_element_by_xpath("//input[@id='randInput']")
        input_str = input('請輸入圖形中的英數字: ') # [!!] 手動輸入圖形驗證碼
        rand.send_keys(input_str)

        browser.find_element_by_css_selector('button.btn.btn-primary').click()

        print('close brower after 10s...')
        time.sleep(10)
        browser.close()
    except NoSuchElementException as e:
        print(e)
    except WebDriverException as e:
        print(e)
其中幾個參數請根據情形修改
(1) 身分證字號
(2) 乘車日期(格式YYYY/MM/DD)
(3) 起站代碼、到站代碼
(4) 車次 代碼請參考
http://railway.hinet.net/Foreign/TW/etno1.html

圖形驗證碼其實就是防止自動程式搶購
因此這部分需要自己看圖輸入答案才會真正完成訂購
雖然如此能自動填寫資料還是可以節省不少時間
程式未針對資料錯誤進行防呆(像是車次不存在、代碼填錯)
資料正確的情況下都能成功訂購一般車次

如果是普悠瑪的話
因為還要填人數所以不太一樣
需要的話記得要修改一下才適用

2018年8月23日 星期四

[Python] Selenium操作Firefox


安裝selenium
pip install selenium==3.14.0

安裝geckodriver
# 取得合適的版本
wget https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-linux64.tar.gz
# 解壓縮
tar zxvf geckodriver-v0.21.0-linux64.tar.gz
sudo mv geckodriver /usr/bin

設定firefox環境參數
export DISPLAY=:0.0

現在可以來測試selenium效果,打開瀏覽器到google,截圖後存檔。若執行成功應該會在同一目錄下產生google擷取畫面。
from selenium import webdriver
browser = webdriver.Firefox()
browser.get('https://www.google.com')
browser.save_screenshot('screenshot_google.png')
browser.close()

2018年8月13日 星期一

[Vue.js] 在ubuntu安裝 Vue.js

方法一、下載原始碼
# install
weget http://vuejs.org/js/vue.min.js

方法二、npm安裝
npm install vue
npm install --global vue-cli

# create a new project
vue init webpack my-project
執行這個專案
npm install
npm run dev
打開網頁 http://localhost:8080可以看到預設vue的頁面

2018年8月6日 星期一

[django] 將Django專案部署到Heroku

首先安裝相關套件
sudo apt-get install snapd
# 安裝heroku cli(Linunx)
# https://devcenter.heroku.com/articles/heroku-cli#download-and-install
sudo snap install heroku --classic

# 安裝django-heroku
pip install django-heroku
# 安裝gunicorn
pip install gunicorn # 安裝git
sudo apt install git

若成功安裝就可以登入Heroku開始建立專案囉! 尚未有Heroku帳號的,請按註冊Heroku會員。
# 登入Heroku
heroku login

先建立一個空白的專案
mkdir heroku-python
cd heroku-python
# create project
django-admin startproject mysite .
打開mysite/settings.py,先將IP加到ALLOWED_HOSTS,'*'是允許所有的Hostname,比較偷懶的方式。再來加入heroku的套件,套用這個檔案內的設定。
# mysite/settings.py
import django_heroku

ALLOWED_HOSTS = ['*']
...

# end of file
django_heroku.settings(locals())
為了讓Heroku知道專案需要的環境,有兩個必要的檔案Procfilerequirements.txt必須手動加入,請注意Procfile沒有副檔名。
# Procfile
web: gunicorn mysite.wsgi

requirements.txt可以透過pip freeze自動生成,內容可以參考下面。
# requirements.txt
Django==2.1
django-heroku==0.3.1
gunicorn==19.9.0

接著,新增一個Heroku專案,一種由系統自動產生,一種直接在create後面加上你想要的名稱。 之後這個專案網址是[project_name].herokuapp.com,因此如果顯示"Name [project-name] is already taken",可能是你想要的名稱已經先被佔用了。
# 由Heroku自動產生專案名稱
heroku create
# 使用者指定專案名稱
heroku create [project_name]
執行heroku create自動產生的結果,可以參考下面,intense-river-54924就是Heroku隨機產生的專案名稱。
Creating app... done, ⬢ intense-river-54924
https://intense-river-54924.herokuapp.com/ | https://git.heroku.com/intense-river-54924.git
新增一個 git repository
git init
git add .
git commit -m "initial commit"

# 設定遠端伺服器/設定專案的上傳目標
heroku git:remote -a intense-river-54924
# 確認是否設定成功
git remote -v
執行git remote -v的顯示範例
heroku https://git.heroku.com/intense-river-54924.git (fetch)
heroku https://git.heroku.com/intense-river-54924.git (push)

因為staticfiles設定要調整,所以先取消執行collectstatic的部分,才不會有錯誤喔!
# 設定參數
# heroku config:set [key]=[value]
heroku config:set DISABLE_COLLECTSTATIC=1
設定完成後就上傳至遠端Heroku的伺服器
# git push [remote-name] [branch-name]
git push heroku master
執行網站,此時就可以打開瀏覽器查看網頁囉!
heroku ps:scale web=1

專案名稱是intense-river-54924,只要打開https://intense-river-54924.herokuapp.com/,就可以看到結果囉!如果不記得專案名稱,可以透過指令顯示執行網站url。
heroku open


部署到這邊告一段落,底下有些指令可以參考使用,像是有錯誤無法正常顯示網站,可以查看Log來排除錯誤。
heroku logs --tail
heroku logs --tail --app [HEROKU_APP_NAME]
其他Django的指令執行範例,不是本地端而是在遠端伺服器執行。
heroku run python mysite/manage.py migrate
heroku run python mysite/manage.py createsuperuser
在編輯你的專案時,小幅度的修改可以透過以下指令在本地端執行,確定沒有問題時再推到遠端去。
# Procfile is required.
# 效果如同 "python manage.py runserver 0.0.0.0:5000"
heroku local
當你想要執行已有的專案時,可以自遠端伺服器取得專案
heroku git:clone -a [project-name]

參考資料:
https://devcenter.heroku.com/articles/getting-started-with-python

2018年8月5日 星期日

連線VMware VM


在Windows環境下安裝VMware,如果要從windows連到VMware的linux虛擬主機,首先請確定VM->Settings,網路的設定為NAT。


接者請安裝以下套件。
# install
sudo apt-get install ssh
sudo apt-get install openssh-server

查詢指令ifconfig得到VM的IP,即可連線。

2018年7月24日 星期二

安裝node js v8.9.4

前置作業
# install
apt-get update
sudo apt-get install build-essential libssl-dev
curl https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | sh
source ~/.profile
安裝
# install
nvm install v8.9.4
確認安裝版本
# check
node -v

2018年3月21日 星期三

[django] django-widget-tweaks 設定Form widget套件

請擇一方式完成django-widget-tweaks安裝
[方法一] pip
# 利用pip安裝
pip install django-widget-tweaks

[方法二] 原始碼安裝
# 選定安裝的版本,將原始碼下載到當前目錄
wget --no-check-certificate https://github.com/jazzband/django-widget-tweaks/archive/1.4.2.tar.gz
# 解壓縮
tar zvxf 1.4.2.tar.gz
cd django-widget-tweaks-1.4.2/
python setup.py install

完成安裝後,使用套件要在settings.py裡面include
# project/settings.py
INSTALLED_APPS = (
    ...
    'widget_tweaks',
)

在Template裡面可以這樣設定,這邊套用了bootstrap的form style。
# templates/index.html
{% load widget_tweaks %}

<form method="post">
  {% csrf_token %}
  {% for field in form %}<br />
    <div class="form-group">
      <label for="{{ field.id_for_label }}">{{ field.label }} :</label>
      {% render_field field placeholder=field.help_text class+="form-control" %}
    </div>
  {% endfor %}
  <button class="btn btn-primary" type="sumbit">儲存</button>
</form>

其他的運用方式,可以參考 這裡,了解更多!

參考資料:
https://github.com/jazzband/django-widget-tweaks

2018年3月13日 星期二

[HTML] 上傳媒體(圖片、影片、聲音)

透過accept這個attr來設定上傳檔案的類型,以及資料來源,可用手機測試實作效果。
<input accept="video/*;capture=camcorder" />
<input accept="audio/*;capture=microphone" />
<input accept="image/*" /> 限制檔案為影像
<input accept="image/*;capture=camera" /> 限制檔案為影像,資料來源為相機
<input accept="image/*" multiple="" type="file" />