基於 Django 的接口管理平臺中遇到的 bugs 及處理


最近又練習了一個基於 django 的小項目,在此過程中也遇到一些小問題,最後通過查找資料和debug等辦法解決了,同時也將一些感覺比較好的bug記錄了一下。

積跬步至千里,積小流成江海。加油!

代碼提交到 Gitee
步驟 markdown


1. 虛擬環境

最好在命令行中創建虛擬環境,如果用 pycharm 可能不不小心將本地的 Python 環境作爲基礎添加進去。

D:\>python
Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> quit()

D:\>python -m venv env_testing

D:\>
D:\>cd env_testing/Scripts
D:\env_testing\Scripts>activate.bat
(env_testing) D:\env_testing\Scripts>

使用純淨的 Python 環境作爲基礎:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-rj7KRtOm-1593152839254)(other/settings_env.png)]


2. django 項目之始遷移文件

發現在通過 python manage.py runserver 啓動服務後,可以訪問:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-5I8GONJK-1593152839268)(other/django_start.png)]
但是無法訪問 admin 的頁面,報錯如下:

......
File "D:\env_testing\lib\site-packages\django\db\backends\sqlite3\base.py", line 383, in execute
    return Database.Cursor.execute(self, query, params)
django.db.utils.OperationalError: no such table: django_session
[17/Jun/2020 01:08:13] "GET /admin/ HTTP/1.1" 500 198090

需要遷移數據庫:

(env_testing) D:\django_learning\api_testing>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying sessions.0001_initial... OK

接着就可以訪問 admin 了:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zs6HXjit-1593152839287)(other/admin_page.png)]


3. mysqlclient error

manage.py@api_testing > startapp login
......
......
	File "D:\env_testing\lib\site-packages\django\db\backends\mysql\base.py", line 36, in <module>
    raise ImproperlyConfigured('mysqlclient 1.3.13 or newer is required; you have %s.' % Database.__version__)
django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.13 or newer is required; you have 0.9.3.

比較好的解決辦法(親測可用):找到引入 pymsql 的文件,然後添加版本信息,只需要一行代碼。

import pymysql

pymysql.version_info = (1, 3, 13, "final", 0) # 解決mysql版本問題報錯而添加的代碼
pymysql.install_as_MySQLdb()

原因:

  • Why do I know you are using pymysql? Because 0.9.3 is just the latest version of pymysql.
  • Why use pymysql instead of mysqlclient for the project? Because it is easier to install. pymysql does not depend on system libraries, while mysqlclient relies on a series of system libraries such as libmysqlclient-dev.
  • Why is mysqlclient difficult to install and Django still uses it by default? Because mysqlclient is faster and performs better. So if your project has high performance requirements, I suggest you remove the compatible code above and install mysqlclient in your project. If you need help during the installation of mysqlclient, please refer to this link: How to install Python MySQLdb module using pip?, and ensure libssl-dev has been installed before pip install mysqlclient.

其他方法:參考 stack overflow 上的其他建議。

ps:我特意先使用 google 搜索該問題,第一條搜索結果就可以解決該問題,而且還很簡單;然後我去百度同樣搜索,發現很多方法比較複雜,也沒有軟用,治標不治本,還浪費時間。


4. 運行時在 mysql 的 operations.py 中報錯

(env_testing) D:\django_learning\api_testing>python manage.py runserver
	......
	File "D:\env_testing\lib\site-packages\django\db\backends\mysql\features.py", line 82, in is_sql_auto_is_null_enabled
    cursor.execute('SELECT @@SQL_AUTO_IS_NULL')
  File "D:\env_testing\lib\site-packages\django\db\backends\utils.py", line 103, in execute
    sql = self.db.ops.last_executed_query(self.cursor, sql, params)
	File "D:\env_testing\lib\site-packages\django\db\backends\mysql\operations.py", line 146, in last_executed_query
    query = query.decode(errors='replace')
AttributeError: 'str' object has no attribute 'decode'

處理辦法:Go to django folder, then go to this path: django\db\backends\mysql

then go to operations.py and find query = query.decode(errors='replace'). Remove the line and put query = errors='replace'

def last_executed_query(self, cursor, sql, params):
    # With MySQLdb, cursor objects have an (undocumented) "_executed"
    # attribute where the exact query sent to the database is saved.
    # See MySQLdb/cursors.py in the source distribution.
    query = getattr(cursor, '_executed', None)
    if query is not None:
        # query = query.decode(errors='replace')  # zhuyuping modify
        query = errors = 'replace'  # zhuyuping add
    return query

5. 映射html出問題,函數不存在

NoReverseMatch at /register/
Reverse for 'login' not found. 'login' is not a valid view function or pattern name.

templates\login\register.html:

<div class="margin-top20 text-center">
    已經有賬號了? <a href={% url 'login' %}>登錄</a>
</div>

login\urls.py:

app_name = 'login'

urlpatterns = [
    path('', views.LoginView.as_view(), name='login'),
    path('forgot/', views.ForgotView.as_view(), name='forgot'),
    path('register/', views.RegisterView.as_view(), name='register'),
    path('reset/', views.ResetView.as_view(), name='reset'),
]

需要指定app,所以修改爲:

<div class="margin-top20 text-center">
    已經有賬號了? <a href={% url 'login:login' %}>登錄</a>
</div>

同理,還有類似的寫法也都是報這個錯誤。


6. register頁面註冊成功後跳轉到login頁面,但是url還是register

class RegisterView(View):
    def get(self, request):
        return render(request, 'login/register.html')

    def post(self, request):
        user_register_form = RegisterForm(data=request.POST)
        if user_register_form.is_valid():
            username = user_register_form.cleaned_data.get('username')
            password = user_register_form.cleaned_data.get('password')
            email = user_register_form.cleaned_data.get('email')
            if not request.POST.get('aggree'):
                return translate2json(errno=ResCode.AGGREE, errmsg=error_map[ResCode.AGGREE])
            new_user = User.objects.create(username=username, password=password, email=email)
            new_user.save()
            return render(request, 'login/index.html')
        else:
            return HttpResponse("註冊輸入有誤,請重新輸入~")

在註冊頁面,註冊一個用戶:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cWf9CKIx-1593152839301)(other/register_test_page.png)]
提交註冊數據後,跳轉到登錄頁面:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-IMi8aQ5v-1593152839324)(other/register_login_page.png)]
如果直接填寫信息登錄,會出現錯誤,原因是當前網頁的 url 並不是真正的登錄(login)頁面,所以提交的數據不能通過本應該是 login 的路由來發出 get 請求。仔細看頁面的路由,依然是 register。

此時填寫正確的註冊過的用戶數據,會返回註冊過程的一些數據。(由於剛剛在註冊頁面註冊了信息,數據被寫入數據庫,然後再在當前的假登錄【實際還是註冊的頁面】進行登錄,就相當於用原來的數據重新註冊一次,當然這樣是不允許的,在後端代碼中就防止該行爲出現而拋出異常。)

解決辦法:使用 redirect 來重定向到真正的登錄頁面。

# return render(request, 'login/index.html')
return redirect('login:login')

從該定位該 bug 以及解決可知,以後凡是涉及頁面跳轉的,不僅要考慮前端頁面,還有考慮跳轉後的路由 url 是否與頁面匹配,防止出現類似的問題。


同樣的,在登錄頁面進行登錄後進入首頁,但是路由 url 並沒有改變,刷新 url 後又回到了登錄界面,顯然是不合理的。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zGj7IOYy-1593152839329)(other/login_homepage.png)]
涉及的相關代碼:

class LoginView(View):
    def get(self, request):
        return render(request, 'login/index.html')

    def post(self, request):
        try:
            post_data = request.POST
            if not post_data:
                return translate2json(errno=ResCode.PARAMERR, errmsg="參數爲空,請輸入")
            user_key = ["email", "password", "remember"]
            data_dict = {}
            for key in user_key:
                data_dict[key] = post_data.get(key)
        except Exception as e:
            logging.info("錯誤信息:\n{}".format(e))
            return translate2json(errno=ResCode.UNKOWNERR, errmsg=error_map[ResCode.UNKOWNERR])
        login_form = LoginForm(data=data_dict, request=request)
        if login_form.is_valid():
            return render(request, 'home/index.html')  # 問題原因

將 render 改爲重定向:

# login\views.py
if login_form.is_valid():
    # return render(request, 'home/index.html')
    return redirect(reverse('index')) 
# apiwork\urls.py
urlpatterns = [
    path('index/', views.ApiView.as_view(), name='index')
]
# apiwork\views.py
class ApiView(View):
    def get(self, request):
        return render(request, 'home/index.html')

7. as_view()

路由中調用視圖函數 as_view 函數後面需要加括號。

urlpatterns = [
    path('index/', views.ApiView.as_view, name='index')
]

view.py是這樣的:

class ApiView(View):
    def get(self, request):
        return render(request, 'home/index.html')

當前端發起 request 請求時,會報這樣的錯誤:

TypeError at /apiwork/index/

as_view() takes 1 positional argument but 2 were given
Request Method: GET
Request URL: http://127.0.0.1:8000/apiwork/index/
Django Version: 2.2
Exception Type: TypeError
Exception Value: as_view() takes 1 positional argument but 2 were given
urlpatterns = [
    path('index/', views.ApiView.as_view(), name='index')
]

8. 前端返回數據有問題

見:前後端數據交互調試


9. 小結

通過這個小小的項目的學習練習,也發現自己在前端知識方面存在很多不懂的地方,需要好好彌補一下。不過由於現在練習的兩個項目都是前後端不分離,這已經是落後的潮流了,以後的趨勢是前後端分離,所以準備學一下目前大火的 Vue,正好也有一個前後端分離的項目可以用來練習。

很多模型以及設計方面的東西還是不太理解的,只是照葫蘆畫瓢,不過相比以前剛剛接觸 django 來說,已經是進步很多了。有些 bug 的處理沒有很好地辦法,有的可以通過刪庫後重新遷移文件來解決,但是實際場景中肯定是不可以這樣做的,也就是說很多 bug 的解決沒有找到本質的原因、沒有用最佳的方法解決,需要慢慢練習、向他人學習。

從一個 django 項目的練習中,可以發現哪些東西都要惡補,記錄一下,加入到後續的學習計劃中。

  • HTTP
  • 數據庫
  • django 的官方文檔
  • 前端,Vue
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章