# React x Django * [[React x Django] React Setup](/M7PPnLw4Q5yAFzGw3XoNsQ) * [[React x Django] React Router](/96BTQu6zR_yUPpsR5P6tGA) * [[React x Django] 後端環境架設](/ozxW1lbISAqnhUVzr-yNew) * [[React x Django] Django REST Framework](/o6pWjIXQRHS1GHScFb2Z_A) * [[React x Django] Fetching Data (Axios)](/MVJSODOHRK2FMaE4KRQ8FA) * [[Django] 上傳圖片](/2hy5qHDJQmeFqAuVkSt95w) * [[React x Django] Serializers 序列化](/isUMEhIISvudUKh0vo_rTA) * [[React x Django] Simple JWT](/6pYUYhOBRGmUHZ0J76bFwA) --- 後端API製作 >[[React x Django] Update Profile Endpoint](/Et_SDYq1QRKLqUePqHN22w) --- * [[React x Django] 前後端整合(上)- User Login Reducer and Action](/7uQrGG7zQ3i7knAcstJoCQ) * [[React x Django] 前後端整合(下)- User Login Screen and Functionality](/dPqXR4LaTOK9S3u6_4TVmw) * [[React x Django] 畫面整合(純顯示)](/sOdjbtedSWO2w1X6_KGL8A) * [[React x Django] 顯示登入資訊( Logout & Login Navbar)](/9FjocdUbQj6Ys9z3uPov9A) * [[React x Django] User Register](/Sa8VdhnjR2K4HVyzAizKsg) * [[React x Django] Google登入登出功能](/nom1EsZ6QXm-mOjGjA90vw) * [[React x Django] Update Profile Endpoint](/Et_SDYq1QRKLqUePqHN22w) * [[React x Django] 有外來鍵的物件/外來鍵下拉式選單](/GpPj14j3Ru6hmvja60mZ7A) --- 小 Tips * [[React x Django] Headers Config](/E9ZFwOAjTLyiRAfQjK41jA) * [[React x Django] UseEffect 渲染無窮迴圈](/uRWZV5zsQZ2Kvpf9-xjvFw) * [[React x Django] Checkbox](/d5qcAWr-R9CBsc0A9RBw9w) * [[React] React-bootstrap Datepicker](/wZW1a5OjTGWhQlpe_T1YCw) --- 參考連結: [Django ORM Cookbook](https://books.agiliq.com/projects/django-orm-cookbook/en/latest/index.html) --- ## DB 遷移 `python manage.py makemigrations` `python manage.py migrate` ## BUGFIX ### 網路傳輸問題 #### 405 POST method not allowed ` "POST /api/school/create HTTP/1.1" 405 41` 解決方式: 要POST的網址最後少一個 `/` #### AssertionError: Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` to be returned from the view, but received a `<class 'str'>` 解決方式: 要在Response 前加入 `return Response` ### 系統端問題 #### Django後端改admin密碼 解決方式: `python manage.py changepassword ME` ### 資料庫問題 #### 如果 `models.py` migrate 後還是 `No such table` 或 `No detection` ,`No migrations to apply`可以改執行 `python manage.py migrate --run-syncdb` #### 初始makemigrations記得加上`my_app`名稱才能把migration都紀錄在資料夾中 `python manage.py makemigrations <my_app>` #### 萬一migrate之後有欄位要加入,出現`no such column 'fields'` 處理方式: 先將`fields`從`models.py`中刪掉 執行 `python manage.py makemigrations` `python manage.py migrate` 如果有錯誤,多執行下面這行 `python manage.py migrate --fake` 然後繼續把`fields`再重新加回 `models.py` 執行 `python manage.py makemigrations` `python manage.py migrate` ### Serializer #### SerializerMethodField 外來鍵關聯的物件有很多欄位我們是用不到的,都傳給前端會有資料冗餘,就需要我們自己去定製序列化外來鍵物件的哪些欄位 #### DRF模型序列化器默認僅返回數據庫中已存在字段,如果想新增輸出字段,該如何操作? 例如:輸出用戶角色時,順便輸出當前角色總共有多少用戶. `models.py` ```python= class Role(models.Model): """角色表,一的一方""" name = models.CharField(max_length=30, unique=True, verbose_name=‘角色名稱‘) # 媒體運營,廣告運營,活動運營,財務,技術,唯一,必填 desc = models.CharField(max_length=100, null=True, blank=True, verbose_name=‘角色描述‘) # 非必填 class Meta: db_table = ‘tb_role‘ verbose_name = ‘角色‘ verbose_name_plural = verbose_name def __str__(self): """控制對象輸出內容""" return self.name class User(BaseModel): """用戶表,多的一方""" account = models.CharField(max_length=30, unique=True, verbose_name=‘登錄賬戶‘) # 必填,唯一 password = models.CharField(max_length=100, null=True, blank=True, default=‘888888‘, verbose_name=‘登錄密碼‘) # 非必填,默認888888,長度100是為了以後加密擴展 username = models.CharField(max_length=30, null=True, blank=True, verbose_name=‘用戶名稱‘) # 非必填 role = models.ForeignKey(Role, on_delete=models.CASCADE, related_name=‘user‘, verbose_name=‘角色‘) class Meta: db_table = ‘tb_user‘ verbose_name = ‘用戶‘ verbose_name_plural = verbose_name def __str__(self): """控制對象輸出內容""" return self.account ``` `serializer.py` ```python= class RoleModelSerializer(serializers.ModelSerializer): """角色模型序列化器""" user_count = serializers.SerializerMethodField(label=‘用戶數量‘) # 新增數據庫不存在字段用戶數量 class Meta: model = Role fields = [‘id‘, ‘name‘, ‘desc‘, ‘user_count‘] def get_user_count(self, obj): """ 返回當前角色用戶數量 固定寫法,obj代表Role實例對象,模型類配置了反向引用user代表當前角色用戶 """ number = obj.user.count() return number ``` ### 外鍵資料顯示名稱而不是id 在Serilizers調整 希望在table上不要顯示id,而是外鍵的實際名稱 ![](https://i.imgur.com/pkWGL0D.png) ```python= class MemberSerializer(serializers.ModelSerializer): family = serializers.SerializerMethodField(read_only = True) intro_by = serializers.SerializerMethodField(read_only = True) class Meta: model = Member fields = '__all__' def get_family(self, obj): family_one = obj.family serializer = MemberSerializer(family_one, many=False) return serializer.data['name'] def get_intro_by(self, obj): intro_by_one = obj.intro_by serializer = MemberSerializer(intro_by_one, many=False) return serializer.data['name'] ``` 第2行 family:為我們宣告Member其中一個欄位 `family` 第3行 intro_by:為我們宣告Member其中一個欄位 `intro_by` 在前面先行宣告代表我們後面要進行處理 第8行 get_family 及 第15行 get_intro_by 名字不能改變,程式執行時要拿family與intro_by時就會去get_family 及 get_intro_by 而models中的外鍵是對Member,這邊則拿family的id去執行Member的Serilizer找出該 id 的member,然後我們回傳的時候要Member裡name的欄位 經過這個函式處理,family這個欄位會由 id 轉成 family 的 name 顯示回去 get_intro_by也是同樣道理 ### Django ORM Self-Join ```manager = models.ForeignKey('self', on_delete=models.CASCADE)``` ### ForeignKey does not allow null values ``` The blank option is used in the form validation, and the null is used when writing to database. So you might add null=True to that field. EDIT: continue the comment Considering the two steps when saving object: Validator(controlled by blank) Database limitation(controlled by null) For default option, take IntegerField for example, default=5, blank=True, null=False, pass (1) even if you didn't assign a value(having blank=True), pass (2) because it has a default value(5) and writes 5 instead of None to DB. blank=True, null=False, which pass (1) but not (2), because it attempts to write None to DB. Thus, if you want to make a field optional, use either default=SOMETHING, blank=True, null=False or blank=True, null=True. ``` 所以,如果有Foreign Key的欄位要optional的話 改成 `blank=True, null=True` ```python= family = models.ForeignKey('self', on_delete=models.SET_NULL, blank=True, null=True,related_name="member_family_User") ``` #### Model 日期自動更新 ```python= created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) ``` #### BUG: __str__ returned non-string (type Student) ```python= def __str__(self): return self.student_name ``` serilizers.py需作修改 ```python= def __str__(self): return str(self.student_name) ``` ### Table 內排序 `models.py`中,每個table末端補上 ```python= class Meta: ordering = ('-created_at',) ``` * 記得加上 `,` ,不然會出現 `'ordering' must be a tuple or list (even if you want to order by only one field).` 錯誤 ### 多Table Join --- ### React #### 下拉式選單 ##### Uncaught Error: input is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`. ##### `Warning: Each child in an array or iterator should have a unique 'key' prop.` 在 JSX 的 { } 中用 inline 的寫法: ```javascript= {numbers.map((numbers) => <li key={numbers.toString()}>{numbers}</li> )} ``` 執行上面的例子,你可能有發現在 console 中 React 給你一個警告訊息是: Warning: Each child in an array or iterator should have a unique 'key' prop. `字面上的意思是,你需要給陣列中的每一個 React 元素一個唯一的編號 (unique key),編號是字串型態,設定在 key 這個屬性上。` 因為 React 需要知道實際上哪個元素是哪一個,React 的 Virtual DOM Diff 演算法才能確定哪些元素是需要在 DOM 中被新增的、哪些是要更新的、哪些是要被刪除的。 `React 用 key 在陣列中判斷是不是同一個元素物件。` 改成: ```javascript= const listItems = numbers.map((numbers) => <li key={numbers.toString()}>{numbers}</li> );` ``` 或 每個元素不好想出個編號,或陣列中的元素順序是固定不變的 ```javascript= const todoItems = todos.map((todo, index) => // 利用 map function 的第二個 index 參數 <li key={index}> {todo.text} </li> ); ``` 在我遇到的例子 將`<Dropdown.item></Dropdown.item>`加上 `index`, 及`key={index}` ```javascript= {schools.map((school,index) =>{ return <Dropdown.Item eventKey={[school._id,school.name] } key={index} >{school.name}</Dropdown.Item> })} ``` #### React顯示true or false `沒辦法直接以oneStud.is_end顯示結果` 需改成 ```javascript= <td>{oneStud.is_end == true ? "是":"否"}</td> ``` #### React 畫面重新整理 `history.push(redirect)`:只是跳回頁面 如果需要重新整理: `window.location.href = redirect`:不用再跳回以後按重新整理 #### Objects are not valid as a React child. If you meant to render a collection of children, use an array instead 代表傳入的東西homes為Array,沒辦法直接在前端上跑出來 需要用迭代取出來 `homes.map(home => (...))` #### TypeError: history.push is not a function ```javascript= function SettingScreen(history) ``` 要改成 ```javascript= function SettingScreen({history}) ``` #### Uncaught TypeError: Cannot read property 'push' of undefined (React-Router-Dom) 成因:因為history的傳導過程斷掉了,可能是內崁頁面所以吃不到history 解決方式: 手動給一次 ```javascript= //載入 useHistory import { useHistory } from "react-router-dom"; ``` ```javascript= //把值塞給history let history = useHistory(); ``` 可正常跳轉了 ```javascript= history.push('/asdfg') ``` >ref: [StackOverflow| uncaught-typeerror-cannot-read-property-push-of-undefined-react-router-dom](https://stackoverflow.com/questions/44009618/uncaught-typeerror-cannot-read-property-push-of-undefined-react-router-dom) ### 編輯時找不到match時會跳掉需要再按一次 將 ```javascript= if (!incomeContribute.context || incomeContribute._id != Number(incomeContributeContextId)){ history.push(redirect) } }else{ setContext(incomeContribute.context) } ``` redirect移除,只進行判斷,不做任何處理 ```javascript= if (!incomeContribute.context || incomeContribute._id != Number(incomeContributeContextId)){ //保持無動作 } }else{ setContext(incomeContribute.context) } ``` --- ### Uncaught Range Error invalid time value 原因: 因為我們的值傳入時的格式不正確 修改方式: 因為原本的值來自moment轉出來的, 所以先將拿出來的string `semester.start_date` 強轉化成moment後 再轉成Date型態 ```javascript= var start_date_for=moment(semester.start_date).toDate(); setStart_date(start_date_for) ``` ### Check Reducer is Null? 使用`{outcome} || []` 來判斷,不要使用`{outcome} === null` ```javascript= const outcomeDetailReducer = useSelector(state=>state.outcomeDetail) const {errorOutcomeDetail, loadingOutcomeDetail, outcome} = outcomeDetailReducer ... return ( ... {loadingOutcomeDetail ? <Loader /> : errorOutcomeDetail ? <Message variant='danger'>{errorOutcomeDetail}</Message> :{outcome} || []? <h1>無支出</h1> :<div> 支出項目: {outcome.name} 支出金額: {outcome.outcome_money} </div> } ... ) ``` ### React Inline Color ```javascript= <h3 style={{color:"red"}}>無資助項目</h3> ``` ### Django jwt token error. "Given token not valid for any token type, token_not_valid" 調整抓token的方式 ${userInfo.token} -> ${userInfo.access} ```javascript= const config = { headers: { 'Content-type': 'application/json', Authorization: `Bearer ${userInfo.access}` } } ```