---
# System prepended metadata

title: 'Python, Django 實作時遇到的問題'
tags: [Django, Python, Discuss]

---

# Python, Django 實作時遇到的問題

###### tags: `Python` `Django` `Discuss`

# 2019/10/25 

### SSO Backup restore API 調整評估

#### 新 API 在哪些情況會影響舊的 Backup?
1. 更新
	可能會包含 RF, 0-10V的 bin files，
	但舊API 仍要能找到它 (不該改到 version 欄位)，
	並下載原本 Group, TB, TS 的 bin files
2. 刪除
	舊API 就看不見原本那筆備份了；
	此時舊 API 應可以再建立新的!

#### 那舊的 Backup 會對新 API 有什麼影響?

1. 若將舊版API 的備份給特別視為獨立的存在
a) 當舊的Backup 存在時，新版 API 只能建立最多 9筆 backup。
b）承a) - 反之，當舊的Backup 不存在時，新版 API 只能建立最多 10筆 backup。
c) 承b) - 使用舊版API仍可以建立一筆專屬的 backup，
只是此時新版 API 會看見 11筆備份，並無法再建立新的 backup，
除非刪除2筆以上的 backup 

2. 若沒將舊版API 的備份給特別視為獨立的存在，
可能在某些情況下在處理判定或處理上會有些麻煩，
直覺想是可能舊版 API 無法建立 backup 當上限10筆存在時
(或是舊版 backup 存在時，新版API 只能再建立 9 筆 --> 似乎沒影響)



# 2019/10/02:

Python, Django 限制字串字元

我的需求,只能輸入數字、英文字母大小寫,``-``, ``_``

Google 發現有兩種方式, 一種是使用 Regular Expression, 另一種是類似 Android ``EditText`` 使用 ``digit`` 的方式

* [python – Djangoのフォームフィールドに英数字のみを含める方法](https://codeday.me/jp/qa/20190215/242681.html)

* [How can I make a Django form field contain only alphanumeric characters](https://stackoverflow.com/questions/17165147/how-can-i-make-a-django-form-field-contain-only-alphanumeric-characters)

* [django form field regex validation for alphanumeric characters [duplicate]](https://stackoverflow.com/questions/29865320/django-form-field-regex-validation-for-alphanumeric-characters)

### Regular Expression

```
# RegexValidator alphanumeric, dash, underline, 
alphanumeric = RegexValidator(r'^[0-9a-zA-Z]*$', 'Only alphanumeric characters are allowed.')

name = models.CharField(max_length=50, blank=True, null=True, validators=[alphanumeric])
email = models.EmailField(max_length=50, unique=True, validators=[alphanumeric])
```

### 字元集合比對

```
import string

def validate(password):
    letters = set(string.ascii_letters)
    digits = set(string.digits)
    pwd = set(password)
    return not (pwd.isdisjoint(letters) or pwd.isdisjoint(digits))

print(validate('123123'))    # False
print(validate('asdfsdf'))   # False
print(validate('12312sfdf')) # True
```


### 實戰結果

經過測試後，發現 __字元集合比對__ 會有邏輯漏洞，無法測試出混合的情況，
所以後來還是改用 Regular Expression

```
def verify_comment(comment):
    # punctuation = r"""-_"""
    # alphanumeric = string.ascii_letters + string.digits + punctuation
    # alphanumeric_set = set(alphanumeric)
    # comment_set = set(comment)
    # letters = set(string.ascii_letters)
    # digits = set(string.digits)
    # punctuations = set(punctuation)
    # result1 = comment_set.isdisjoint(letters)
    # result2 = comment_set.isdisjoint(digits)
    # result3 = comment_set.isdisjoint(punctuations)
    # result4 = comment_set.isdisjoint(alphanumeric_set)
    pattern = "^[a-zA-Z0-9-_]*$"
    # result5 = re.match(pattern, comment)
    # log_wrapper('result1: {}, result2: {}, result3: {}, result4: {}, result5: {}'
    #             .format(result1, result2, result3, result4, result5))
    # log_wrapper('type of result5: {}'.format(type(result5)))
    result = re.match(pattern, comment)
    log_wrapper('result of re.match: {}'.format(result))
    return result is not None
```  

---

# 2019/09/16

## 新舊版本共存的議題

看過有人的設計，是在每次呼叫 API時，就順便夾帶一些參數；
例如目前的API 版本，AES 字串，與手機OS，型號等；
這樣的好處，一者是可以分辨使用者是使用什麼OS，手機型號，供除錯用。
一者是當需求有出現重大改變時 (Table 新增欄位或關聯所有改變)，
但使用者的 APP 可能不會更新的情況下，
因此，為了支援新舊的 APP 都可以使用 API，這種有夾帶 API 版本，
可以讓 API 可以依不同版的APP，做差異性處理。


另外也有看見別人的 API，例如 Github，或衣芙，他們API的路徑，是 ``domain/type/version/path``

其中，``domain/type/``可以合併或分開:
* 分開:
```
api.github.com/release/
api.github.com/develop/
```
* 合併:
```
api.github.com/
api.develop.github.com/
```
* version:
``v1, v2, v3``

* path:
依 API 接口的目的來區分，例如登入，會員，商品:
```
login/
members/
products/
```

---

## 我的處置

因為我一開始沒想到會要同時支援新舊版本，且為了讓程式控制方便，
並因應新增 Tables 與現有 Tables 之間的關聯，
與人討論後，決定採取新的 API 接口存取新的 Tables的方式，
讓現有 API 維持使用現有的 Tables 與 business logic。
這樣也可以降低一些 case 判斷的成本與錯誤風險。

但我想，我採用這種方式，和我一開始規劃 DB scheme 也有關係
就是 API 接口存取的主要 Table，是以 Foreign key 方式關聯到其它副 Tables，
所以新的需求對我而言，是新增額外的副 Tables，然後新增另一張主要的 Tables，
使其以 Foreign key 方式與原本的副 Tables 和新增的 Tables 建立關聯。
然後新的 API 接口只會在判斷條件與 DB 存取上與現有的 API 接口所有不同，
對此，我可以將原本的 business logic 放在一個新的 abstract base class中，
並定義相關的 abstract functions 來給 subclasses實作。

### 2019/10/14

API 新舊相容性問題，還必須考量新版 API 可以存取或更新舊的 Backup; 
反過來，即使使用新的 API 進行更新，舊版 API 仍要能正常存取或更新 Backup。 
基於這個考量，Table 以擴充欄位的方式來調整比較適合，然後再進行交叉測試。


## 刪除議題

之前也有討論過 Hard/ Soft Delete 的議題
與人討論的重點在於，是否這個 Table 的資料被刪除了會有什麼重大影響?
通常，與使用者本上有重大的關聯，可能還會用到，就建議使用 Soft Delete。
若是暫存，可以再被產生，或忘了也沒差的，那就使用 Hard Delete，
如此一來可以節省 DB 的空間，這樣 Query 的效率也會提昇!

---

# 2019/08/29:

## 1. 想仿 RX 用 chain 的方式來執行，定義下列 3 個 ``class``

```
class IeConversionFunction:
    class Meta:
        abstract = True

    # abstract
    def do_convert(self, input_data):
        """
        return output
        """
        raise NotImplementedError("do_convert() must be overridden.")


class IeDataHolder:
    def __init__(self, kept_data):
        self.kept_data = kept_data


class IeChain:
    def __init__(self, data):
        self.ie_data_holder = IeDataHolder(data)

    def map(self, conversion_function):
        output_data = conversion_function.do_convert(conversion_function, self.ie_data_holder.kept_data)
        return IeChain(output_data)

    def on_complete(self):
        return self.ie_data_holder.kept_data
```

## 2. Django Rest Framework 的 ``APIView`` Subclass 中執行

```
response_json = IeChain(user_drf_request_tuple) \
    .map(self.ParametersVerificationTask) \
    .map(self.UserFilesSaveTask) \
    .on_complete()
```

其中，``ParametersVerificationTask`` 與 ``UserFilesSaveTask`` 
為 2 個 inner class，皆承 ``IeConversionFunction``

上述程式片段可以正常執行，但值得留意的是，
``ParametersVerificationTask`` 與 ``UserFilesSaveTask`` 都沒被初始化，
卻可以直接 access 它的 ``do_convert()`` 方法!!

但這樣的方式有一個缺點: 
若 ``ParametersVerificationTask`` 與 ``UserFilesSaveTask`` 
有想要定義其它 methods 並想要引用時，
因為這兩個 classes 沒被初始化 (沒有 instances)，
所以內部的 properties, methods 都無法利用 ``self`` 來存取

有試過改成
```
response_json = IeChain(user_drf_request_tuple) \
    .map(self.ParametersVerificationTask()) \
    .map(self.UserFilesSaveTask \
    .on_complete()
```

但當要執行 ``map()`` 時就報錯:

```
TypeError('do_convert() takes 2 positional arguments but 3 were given')
```

目前的方式是改為正統方式:
```
task1 = self.ParametersVerificationTask()
task2 = self.UserFilesSaveTask()
backup_request_tuple = task1.do_convert(user_drf_request_tuple)
response_json = task2.do_convert(backup_request_tuple)
```

## 1. 順便看了 inner class 與 outter class 之間的關係:

Outter class 可存取 inner class
但 inner class 不能存取 Outter class
https://www.datacamp.com/community/tutorials/inner-classes-python



---

有人更建議：將每次 Request + Response 的結果保存 (寫入DB 或暫存於 File System)，
這樣在除錯時，方便釐清到底是後端程式的問題，或是 Client 端送錯資料。

