分類
网站

使用 AWS S3 創建自有域名的靜態網站

對於普通流量不大的靜態站點,所需的 AWS 服務都是免費的,但是我要先提醒,AWS 的使用體驗真的很繁瑣,至少比在 VPS 中建立網站複雜多了。這裏只是大概記錄一下使用亞馬遜服務建立一個自有域名且啓用了 HTTPS 的靜態網站的步驟。

首先你要有一個亞馬遜服務的賬戶。然後在 S3 服務中建立一個 Bucket,如果你的網站是 abc.domain.ltd 那麼你的 Bucket 名字就可以叫 abc.domain.ltd 。要給予公共訪問權限,以及啓用 Static website hosting 。設置完成後在 Properties 選項卡的底部可以看到訪問這個靜態站點的 URL( 如:http://abc.domain.ltd.s3-website-us-east-1.amazonaws.com/ )。如果你往這個 Bucket 里上傳一個 index.html 文件,那麼應該可以通過 http://abc.domain.ltd.s3-website-us-east-1.amazonaws.com/index.html 訪問到。

第二步是通過 AWS Certificate Manager 申請或導入啓用 HTTPS 所需的證書。我沒有試過導入,所以這裏只介紹下申請。申請時我只填寫里二級域名,使用的 DNS 驗證方式。提交後可以看到需要添加的 CNAME DNS 記錄。如果域名在 Cloudflare 那麼直接新建一條 CNAME 記錄,填入 Host 和 Value 的值,過一小會兒就能看到證書頒發里。如果是在 Namecheap, 直接複製進去 Host 和 Value 值似乎不行,可以嘗試只輸入 Host 值的前兩部分(第二個點之前的字符,如:_904706782abb3d16301321f28db53e03.abc )以及不帶最後一個點的 Value(如:_0ba986089fff81c1b4f395a2ea75f42e.hkvuiqjoua.acm-validations.aws )。可以使用 dig 命令查看 DNS 記錄是否生效: dig _904706782abb3d16301321f28db53e03.abc.domain.ltd CNAME 如果生效了會在 ANSWER SECTION 看到前面設定的值。如果沒有生效或設置錯誤,會出現 status: NXDOMAIN 及 ANSWER: 0 字樣。

第三步是在 AWS CloudFront 里新建一個 Distribution,選擇需要的 Bucket 以及輸入要用域名 abc.domain.ltd。證書那裏選擇剛剛生成的證書。建立後會生成一個 Distribution domain name 類似d174updd62jl4k.cloudfront.net 。然後在 DNS 中再增加一條 CNAME 記錄,Host 是 abc,值是 d174updd62jl4k.cloudfront.net 。生效後就大功告成可以用 https://abc.domain.ltd 訪問了。如果你遇到 SSL_ERROR_NO_CYPHER_OVERLAP 報錯,可以嘗試在 Distribution 的 General 選項卡中找到 Alternate Domain Names 輸入框,輸入你的域名如 abc.domain.ltd ,待其生效後應該就可以了。

分類
网站

Ten plugins and ten themes for WordPress

Here is a collection of plugins and themes from Chapter 16: The Part of Tens of the book WordPress for dummies by Lisa Sabin-Wilson.

Plugins

Custom Post Type UI

For custom post types and taxonomy.

Jetpack

A suit of plugins provided by wordpress.com.

Limit Login Attempts Reloaded

Limit Login Attempts.

Cookie Notice for GDPR & CCPA

Cookie Notice for GDPR & CCPA

Yoast SEO

Search engine optimization

BackupBuddy

For backing up and transfering website. Price strats from $80.

WP Super Cache

Create static HTML files from your dynamic WordPress content.

WooCommerce

Selling product or service on your website.

Google XML Sitemap

Create a Google-compliant XML site map of your entire site and submit it to major search engines.

Sucuri Security

Scan for malware, spam, blacklisting and other security issues hidden inside your code files.

Themes

Hybrid Core

Highly customizable parent theme. It's a WordPress Framework now.

Hestia

One-page theme built for use on a small-business website.

Responsive

Nine templates built on a fluid grid system as well as webmaster toos, logo management and multilingual support.

Ashe

Suit for personal website, lifestyle blog, bakery, travel agency.Support WooCommerce.

Prefer Blog

Simple blog with built-in Auther and Contact Us block.

BlackBird

Use your own logo, analytics code, customize featured text with an easy-to-use widget, post thumbnails, header image.

Storefront

For store with WooCommerce support, custom widgets and sidebars.

Sinatra

Good for general websites like restaurants, bakeries tech startups and blog.

Nisarg

A nice clean blog theme.

Optics

A minimalist theme featuring a grid layout. Clean and Simple.

分類
网站

使用 Django Q 方便地執行耗時任務

Django Q 是一個使用 Python 多進程製作的原生 Django 任务队列、调度器和 worker 应用。它具有很多優異特性但我只是粗淺使用了如下幾點:

  • 多進程 worker 池
  • 異步任務
  • 定時任務、cron 和重複的任務
  • 把失敗和成功結果保存到數據庫或緩存
  • 自動集成到 Django Admin,就可以在後臺添加和管理任務
  • 支持 Redis, Disque, IronMQ, SQS, MongoDB 或 ORM 這麼多種隊列代理方式,最方便的當然是 ORM
  • 注意:Django Q 的任務間隔粒度是 30 秒,如果你的任務頻率或精準度要求高於 30 秒,你需要嘗試修改源碼或使用其他任務隊列

安裝 Django Q

pip install django-q
#如果要使用 cron 規則,則也要安裝
pip install croniter
#在項目的 settings.py 文件 INSTALLED_APPS 裏加入 django_q
INSTALLED_APPS = (
    # other apps
    'django_q',
)
#設置代理方式,我選擇 ORM,
#只需把下面字段也加入 settings.py

#更多設置請參見 https://django-q.readthedocs.io/en/latest/configure.html
Q_CLUSTER = {
    'name': 'djangtasks',
    'workers': 2,
    'timeout': 180,
    'retry': 200,
    'queue_limit': 50,
    'bulk': 10,
    'orm': 'default'
}

#執行數據庫遷移來創建數據庫表
python manage.py migrate
#運行 Django Q 來處理任務隊列
python manage.py qcluster

我是使用 screen 來在後臺運行 qcluster,很方便的。

使用 Django Q 在 Django 後臺執行耗時任務

# file: views.py
import datetime
from django.http import HttpResponse
from django.utils import timezone
from django_q.tasks import async_task, schedule
from django_q.models import Schedule

def a_longtime_task(arg):
    time.sleep(30)
    return arg

def scheduled_task(arg):
    time.sleep(30)
    return arg

def a_longtime_task_request(request):
    #立即在後臺執行
    async_task(a_longtime_task,'args for the function')
    return HttpResponse('The longtime task has been started.')

def another_longtime_task_request(request):
    #三分鐘後執行
    schedule('YOURAPP.views.scheduled_task',
            'args for the function',
            schedule_type=Schedule.ONCE,
            next_run=timezone.now() + datetime.timedelta(minutes=3))
    return HttpResponse('The task has been scheduled.')

在 Django Admin 佈置定時任務

這個就比較直接,比如要每天 10 點都執行上面例子中的 scheduled_task,就點擊 Admin 頁面 Scheduled tasks 旁邊的「新增」。

Func(填入完整的函數路徑):YOURAPP.views.scheduled_task
Args:'args for the function'
Schedule Type:Daily
Next Run(設置要運行的時間如):2022-08-27 10:00

最後點擊「儲存」就可以了。

同樣的,如果選擇 Cron 類型,就需要在 Cron 輸入框填入 Cron 計劃。比如在 9 點到 23 點期間,每 5,15,25,35,45,55 各執行一次:
5,15,25,35,45,55 9-23 * * *
更多 Cron 用法和組合可以參考 crontab.guru 這個網站。

等待執行的任務、失敗的任務和成功的任務都可以方便的在Admin頁面查看和操作,非常方便。

管理隊列任務與已完成任務 Schedule Task

# file: tasks.py
from django_q.models import Schedule, Task
from django.db.models import Q
#your model to be checked
from app1.models import Race
from django.utils import timezone

import datetime
import operator
from functools import reduce

# Task to delete successful old tasks
def delete_old_tasks():
    len_task_20 = 0
    len_task_gen = 0
    now = datetime.datetime.now()
    days_passed_2 = timezone.utc.localize(now - datetime.timedelta(days=2))
    days_passed_7 = timezone.utc.localize(now - datetime.timedelta(days=7))
    task_q_list = []
    task_q_list.append(Q(group__startswith='20'))
    task_q_list.append(Q(started__lt=days_passed_7))
    task_q_list.append(Q(success__exact=True))
    task_20_queryset = Task.objects.filter(reduce(operator.and_, task_q_list))
    len_task_20 = len(task_20_queryset)
    for task in task_20_queryset:
        task.delete()

    task_q_list = []
    task_q_list.append(Q(group__exact='generate_0_task'))
    task_q_list.append(Q(started__lt=days_passed_2))
    task_q_list.append(Q(success__exact=True))
    task_gen_queryset = Task.objects.filter(reduce(operator.and_, task_q_list))
    len_task_gen = len(task_gen_queryset)
    for task in task_gen_queryset:
        task.delete()

    return f'{len_task_20} checked tasks and {len_task_gen} checking tasks have been removed.'

#generate 0 task every 5 minuts
def generate_0_task():
    res=[]
    now = datetime.datetime.now()
    dto_now = timezone.utc.localize(now)
    start_datetime_6 = now + datetime.timedelta(minutes=6)
    dto_plus6 = timezone.utc.localize(start_datetime_6)
    current_races = Race.objects.filter(post_time_live__lte=dto_plus6,
                                          post_time_live__gte=dto_now)
    if len(current_races) == 0:
        res.append('no current races')
        return res
    for race in current_races:
        if race.race_conditions.find('SIMULCAST')>-1:
            res.append(str(race)+': task passed SIMULCAST')
            continue
        tasks = Schedule.objects.filter(name=str(race))
        if len(tasks)==0:
            schedule('app1.views.zero_mtp_task',
                    race.id, race.track_id, race.race_number, race.race_date, race.post_time_live.isoformat(),
                    schedule_type=Schedule.ONCE,
                    next_run=race.post_time_live,
                    name=str(race))
            res.append(str(race)+': task added'+' post time:'+str(race.post_time_live))
        else:
            res.append(str(race)+': task already added'+' post time:'+str(race.post_time_live))
    return res

本文更新於 2023/01/27。

分類
网站

Django with PostgreSQL

參考 PostgreSQL:Linux downloads (Red Hat family) 來安裝PostgreSQL,比如 Fedora 35 可以這樣:

sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/F-35-x86_64/pgdg-fedora-repo-latest.noarch.rpm
sudo dnf install -y postgresql13-server libpq-devel
sudo /usr/pgsql-13/bin/postgresql-13-setup initdb
sudo systemctl enable postgresql-13
sudo systemctl start postgresql-13

安裝完成後來初始化一個數據庫和用戶。

#使用 root 登錄 PostgreSQL
sudo -u postgres psql
#創建數據庫
CREATE DATABASE project;
#創建新用戶
CREATE USER user42 WITH PASSWORD 'password42';
#下面這三行優化是 Django 推薦的
ALTER ROLE user42 SET client_encoding TO 'utf8';
ALTER ROLE user42 SET default_transaction_isolation TO 'read committed';
ALTER ROLE user42 SET timezone TO 'Asia/Taipei';
#把數據庫授權給新用戶
GRANT ALL PRIVILEGES ON DATABASE project TO user42;
#退出數據庫
\q

要在 Django 中使用 PostgreSQL 數據庫,需要還安裝 psycopg2:

pip install Django psycopg2
#如果像我一樣在 Fedora 安裝失敗了
#可以嘗試安裝編譯好的版本
pip install Django psycopg2-binary

之後在項目的設置中把默認的 SQLite 數據庫資料改成 PostgreSQL 就可以了。更像詳細的步驟與解釋,可以參考:How To Use PostgreSQL with your Django Application on Ubuntu 20.04

刪除 Django 數據庫中的表格並重建

這似乎不是正確的回滾數據庫的操作辦法,但是也可以一試。

#從 migrations 目錄找到要刪除的數據庫變更文件,刪除掉
#進入數據庫操作程序
python manage.py dbshell
#查看並找到要刪除的表名
SELECT * FROM pg_catalog.pg_tables;
#如果是MariaDB,這樣查看表名
show tables;
#比如表名爲 task_day,則這樣刪除
DROP TABLE task_day;
#退出數據庫操作程序
exit;
#刪掉數據庫裏的 migrations
python manage.py migrate --prune task

#現在可以重新建立表格了
python manage.py makemigrations
python manage.py migrate

本文更新於 2022/10/24。

分類
Linux 程序

Django簡易搭建上傳文件

這裡使用Django+Gunicorn+Nginx的方式簡單運行一個小型webserver,實現一個簡單的上傳文件到服務器的功能(並不生成下載鏈接)。

啟動虛擬環境,安裝django和gunicorn:

pip install Django==2.0
pip install gunicorn
#進入要放置代碼的目錄並新建項目
django-admin startproject mysite
##或者在當前目錄建立項目
#django-admin startproject mysite .
cd mysite
#新建app
python manage.py startapp polls

先建立一個表格:

#polls/forms.py
from django import forms

class UploadFileForm(forms.Form):
    title = forms.CharField(label='密碼',max_length=20,widget=forms.PasswordInput)
    file = forms.FileField(label='文件',)

修改view:

#polls/views.py
import os
import subprocess
from django.core.files.storage import FileSystemStorage
from django.conf import settings
from django.shortcuts import render
from django.http import HttpResponse
from .forms import UploadFileForm

#handle file example with file
def handle_uploaded_file(f):
    with open('/file/should/be/saved/here/target.odt', 'wb+') as destination:
        for chunk in f.chunks():
            destination.write(chunk)

#another handle file example with filename
def handle_uploaded_file2(filename):
    msg=''
    try:
        targetZipFilePath = os.path.join(settings.BASE_DIR, filename)
        cmd1=subprocess.check_call(["unzip", "-o", targetZipFilePath, "-d", "/home/fred/workspace/"])
        if cmd1==0 :
            cmd2=subprocess.check_call(["cp", "-Rf", "/home/fred/workspace/dist", "/home/fred/"])
            if cmd2==0 :
                msg="deployed successfully"
            else:
                msg="error 2"
        else:
            msg="error 1"
    except:
        msg = 'error 0'
    return msg

def upload_file(request):
    if request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid() and request.POST['title']=='Secret':
#            handle_uploaded_file(request.FILES['file'])
#            return HttpResponse("上傳成功")
            myfile = request.FILES['file']
            fs = FileSystemStorage()
            filename = fs.save(myfile.name, myfile)
            uploaded_file_url = fs.url(filename)
            print(uploaded_file_url)
            res = handle_uploaded_file2(uploaded_file_url)
            return HttpResponse(res)
    else:
        form = UploadFileForm()
    return render(request, 'upload.html', {'form': form})

新建一個表格的模板:

#polls/templates/upload.html
<form enctype="multipart/form-data" action="/polls/upload/" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="上傳" />
</form>

新建一個url路由表:

#polls/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('upload/', views.upload_file, name='upload_file'),
]

修改項目路由:

#mysite/urls.py
from django.contrib import admin
from django.urls import include,path

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

修改項目設置:

#mysite/settings.py
INSTALLED_APPS = [    
    'polls.apps.PollsConfig',
#    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
#    'django.contrib.messages',
#    'django.contrib.staticfiles',
]
LANGUAGE_CODE = 'zh-Hant'
TIME_ZONE = 'Asia/Taipei'

然後在項目目錄(最上層)運行gunicorn就可以訪問了:

gunicorn mysite.wsgi --bind 127.0.0.1:3040

nginx中增加如下server即可在外網訪問了(鏈接應該是http://YourPublicIP:8081/polls/upload/):

server {
        listen 8081;
        server_name 127.0.0.1;
        charset utf-8;
        keepalive_timeout 60s;
        #access_log logs/django2a.access.log combined if=$loggable;
        
        location / {
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_redirect off;
    
            if (!-f $request_filename) {
                proxy_pass http://127.0.0.1:3040;
                break;
            }
    }
}

CentOS6+Django2+MySql

yum install MySQL-python
pip install mysqlclient
#如果import MySQLdb提示無libmysqlclient.so.18
#則建立軟連接如下
ln -s /usr/local/mysql/lib/libmysqlclient.so.18 /usr/lib64/libmysqlclient.so.18
分類
网站

使用 UpUp 讓網站離線可用

UpUp 是個很小(2.5 KB)的 Javascript 庫,但卻可以方便的實現網站的離線訪問。我之前有一個小項目,PHP 的後端和頁面,另外藉助 Apeche Cordova 用 HTML 實現了安卓的客戶端以供離線使用。最近用 Django 重新寫了,藉助 UpUp 的離線頁面,我不用爲了離線使用而再去生成一個安卓應用。

一般的網站,要使用 UpUp 是非常簡單的,只需要在網站的頂層文件夾引入對應的庫即可,但是 Django 的靜態文件一般是放在 static 下的,要想在項目根目錄下提供它們,可以這樣設置。

#項目的 url.py 文件
from django.urls import path
...
from . import views

urlpatterns = [
    ...
    path('upup.min.js', cache_page(60 * 60 * 48)(TemplateView.as_view(template_name="site/upup.min.js", 
  content_type='application/javascript', )), name='upup.min.js'),
    path('upup.sw.min.js', cache_page(60 * 60 * 48)(TemplateView.as_view(template_name="site/upup.sw.min.js", 
  content_type='application/javascript', )), name='upup.sw.min.js'),
]

然後將 upup.min.js 和 upup.sw.min.js 放在項目的模板目錄裏,比如 mysite/site/templates/site/ 中。

最後在 HTML 頁面中引用並設置需要離線的文件即可。

...
<script type="text/javascript" src="{% url 'upup.min.js' %}"></script>
<script type="text/javascript" src="{% static 'site/js/jquery-3.6.0.min.js' %}"></script>
<script>
UpUp.start({
  'content-url': '/site/',
  'assets': ['/static/site/js/jquery-3.6.0.min.js',
   '/static/site/js/site.js', '/static/site/css/site.css']

});
</script>

離線站點調試的過程中遇到的另外一個問題是,離線站點必須是 HTTPS 類型的加密頁面,但是本地配置 HTTPS 又略嫌繁瑣,其實只要再 Chrome 裏添加例外(chrome://flags/#enable-site-per-process)即可。火狐我還不知道要怎麼添加,最近安卓火狐的一系列更新都不盡人意,實在是有些令人擔憂。

分類
說說

210521

非常遺憾,本站似乎無法從國內直接打開了。

#天威寬帶
$ curl https://ft.shaman.eu.org/
curl: (28) Failed to connect to ft.shaman.eu.org port 443: Connection timed out

#移動網絡(2G 和 4G)
$ curl https://ft.shaman.eu.org/
curl: (7) Couldn't connect to server

通過測速網站的結果可以看到,國內的 DNS 服務可以正確解析出本站的 Cloudflare IP,但是無法打開頁面。國外則正常。