###### tags: `web` # Django # [build website](https://www.youtube.com/results?search_query=django+build+website&sp=EgIQAw%253D%253D) 實在是太多只能先抓基本在看別人怎麼做的 # [直接執行不用command的方法](https://stackoverflow.com/questions/49775020/how-to-run-a-django-project-without-manage-py) # [嚴重bug](https://stackoverflow.com/questions/48822571/syntaxerror-generator-expression-must-be-parenthezised-python-manage-py-migra) 要手動修改...目前不知道怎麼解比較好可能就用新版不要用舊版本 # [github範例](https://github.com/twtrubiks/django-tutorial) # freecodecamp ## [github](https://github.com/codingforentrepreneurs/Try-Django) [Python Django Web Framework - Full Course for Beginners](https://www.youtube.com/watch?v=F5mRW0jo-U4&t=2003s&ab_channel=freeCodeCamp.org) 先看這個 [Django For Beginners - Full Tutorial](https://www.youtube.com/watch?v=sm1mokevMWk&ab_channel=TechWithTim) [Python Backend Web Development Course (with Django)](https://www.youtube.com/watch?v=jBzwzrDvZ18&t=13947s&ab_channel=freeCodeCamp.org ) [Django 3 Course - Python Web Framework (+ pandas, matplotlib, & more)](https://www.youtube.com/watch?v=04L0BbAcCpQ&ab_channel=freeCodeCamp.org ) [Vue.js Course for Beginners ](https://www.youtube.com/watch?v=FXpIoQ_rT_c&t=1225s&ab_channel=freeCodeCamp.org ) [E-commerce Website With Django and Vue Tutorial (Django Rest Framework) ](https://www.youtube.com/watch?v=Yg5zkd9nm6w) # django-template https://docs.djangoproject.com/en/2.1/ref/templates/builtins/ # restful+swagger [參考](https://zoejoyuliao.medium.com/%E7%94%A8-django-rest-framework-%E6%92%B0%E5%AF%AB-restful-api-%E4%B8%A6%E7%94%9F%E6%88%90-swagger-%E6%96%87%E6%AA%94-7cbef7c8e8d6) # ref [官方](https://docs.djangoproject.com/zh-hans/3.2/) [awesome](https://github.com/wsvincent/awesome-django) [cheat sheet](https://edu.anarcho-copy.org/Programming%20Languages/Python/Python%20CheatSheet/beginners_python_cheat_sheet_pcc_django.pdf) [admin ref](https://docs.djangoproject.com/en/2.1/ref/contrib/admin/) [官網](https://docs.djangoproject.com/en/3.2/) # [幫幫忙](https://ithelp.ithome.com.tw/users/20140594/ironman/4698) ## [Overview](https://docs.djangoproject.com/en/3.2/intro/overview/) **object-relational mapper**描述db的工具 看的出來官方也拿print 甚麼沒主意乾脆讓使用者自訂 ``` from django.db import models class Reporter(models.Model): full_name = models.CharField(max_length=70) def __str__(self): return self.full_name class Article(models.Model): pub_date = models.DateField() headline = models.CharField(max_length=200) content = models.TextField() reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE) def __str__(self): return self.headline ``` ## Install 自動創建database 這邊可以看的出來django想辦法在連結database跟物件的關聯性 ``` $ python manage.py makemigrations $ python manage.py migrate ``` ## 簡單的db用法 順帶一提id primary key是自帶的 ``` # Import the models we created from our "news" app >>> from news.models import Article, Reporter # No reporters are in the system yet. >>> Reporter.objects.all() <QuerySet []> # Create a new Reporter. >>> r = Reporter(full_name='John Smith') # Save the object into the database. You have to call save() explicitly. >>> r.save() # Now it has an ID. >>> r.id 1 # Now the new reporter is in the database. >>> Reporter.objects.all() <QuerySet [<Reporter: John Smith>]> # Fields are represented as attributes on the Python object. >>> r.full_name 'John Smith' # Django provides a rich database lookup API. >>> Reporter.objects.get(id=1) <Reporter: John Smith> >>> Reporter.objects.get(full_name__startswith='John') <Reporter: John Smith> >>> Reporter.objects.get(full_name__contains='mith') <Reporter: John Smith> >>> Reporter.objects.get(id=2) Traceback (most recent call last): ... DoesNotExist: Reporter matching query does not exist. # Create an article. >>> from datetime import date >>> a = Article(pub_date=date.today(), headline='Django is cool', ... content='Yeah.', reporter=r) >>> a.save() # Now the article is in the database. >>> Article.objects.all() <QuerySet [<Article: Django is cool>]> # Article objects get API access to related Reporter objects. >>> r = a.reporter >>> r.full_name 'John Smith' # And vice versa: Reporter objects get API access to Article objects. >>> r.article_set.all() <QuerySet [<Article: Django is cool>]> # The API follows relationships as far as you need, performing efficient # JOINs for you behind the scenes. # This finds all articles by a reporter whose name starts with "John". >>> Article.objects.filter(reporter__full_name__startswith='John') <QuerySet [<Article: Django is cool>]> # Change an object by altering its attributes and calling save(). >>> r.full_name = 'Billy Goat' >>> r.save() # Delete an object with delete(). >>> r.delete() ``` ## dynamic admin interface 像下面註冊model你才可以在後台去修改 ``` # mysite/news/models.py¶ from django.db import models class Article(models.Model): pub_date = models.DateField() headline = models.CharField(max_length=200) content = models.TextField() reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE) #mysite/news/admin.py¶ from django.contrib import admin from . import models admin.site.register(models.Article) ``` ## URL範例 範例 ``` mysite/news/urls.py¶ from django.urls import path from . import views urlpatterns = [ path('articles/<int:year>/', views.year_archive), path('articles/<int:year>/<int:month>/', views.month_archive), path('articles/<int:year>/<int:month>/<int:pk>/', views.article_detail), ] ``` URL “/articles/2005/05/39323/”, Django 相當於呼叫下方function, 因為是keyword argument所以命名要注意 news.views.article_detail(request, year=2005, month=5, pk=39323). ## views簡介 範例 ``` mysite/news/views.py¶ from django.shortcuts import render from .models import Article def year_archive(request, year): a_list = Article.objects.filter(pub_date__year=year) context = {'year': year, 'article_list': a_list} return render(request, 'news/year_archive.html', context) ``` ## templates簡介 ``` mysite/news/templates/news/year_archive.html¶ {% extends "base.html" %} {% block title %}Articles for {{ year }}{% endblock %} {% block content %} <h1>Articles for {{ year }}</h1> {% for article in article_list %} <p>{{ article.headline }}</p> <p>By {{ article.reporter.full_name }}</p> <p>Published {{ article.pub_date|date:"F j, Y" }}</p> {% endfor %} {% endblock %} ``` 這種寫法叫template flag下方是template filter template flag不知道跟 ``` {{ article.pub_date|date:"F j, Y" }} uses a Unix-style “pipe” (the “|” character). This is called a template filter, ``` [Jinja2 templates a superset of Django templates?](https://stackoverflow.com/questions/24236721/jinja2-templates-a-superset-of-django-templates) 由此可見jinja2=>flask,Django templates=>Django所以有語法上差異性存在 細節未知 static 引用的用法這邊跟flask就很不同 flask用url_for ``` mysite/templates/base.html¶ {% load static %} <html> <head> <title>{% block title %}{% endblock %}</title> </head> <body> <img src="{% static 'images/sitelogo.png' %}" alt="Logo"> {% block content %}{% endblock %} </body> </html> ``` ## 版本 感覺要快速做一點東西用python shell似乎也有挺高靈活性 ``` >>> import django >>> print(django.get_version()) 3.2 ``` # Django shortcuts 要花時間去看他分類 還有像是redirect、get_object_or_404 # [幫幫忙](https://ithelp.ithome.com.tw/articles/10199515) Django 是一個基於Python語言所寫出來的框架,跟React.js、Angular.js及Vue.js一樣, 簡化了很多我們在寫網頁的流程。 相較於後者的MVC(Model-View-Contorller)架構, Django也有屬於它的MTV(Model-Template-Views) 建議版本用2.0+ ## 建 project django-admin startproject [projectname] ``` (ithome_enve) C:\Users\User\Desktop\ithome\ithome>tree /F .. C:\USERS\USER\DESKTOP\ITHOME ├───ithome │ │ manage.py │ │ │ └───ithome │ settings.py │ urls.py │ wsgi.py │ __init__.py │ └───ithome_enve ``` * ithome/settings.py,重頭戲來了! settings掌管了整個專案的運作 * ithome/url.py router * ithome/wsgi.py,WSGI則是扮演著web server與web application之間溝通的一個介面? ## 執行 python manage.py runserver ``` (ithome_enve) C:\Users\User\Desktop\ithome\ithome>python manage.py runserver Performing system checks... System check identified no issues (0 silenced). You have 15 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. Run 'python manage.py migrate' to apply them. October 03, 2018 - 21:17:48 Django version 2.1.2, using settings 'ithome.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CTRL-BREAK. [03/Oct/2018 21:19:24] "GET / HTTP/1.1" 200 16348 ``` ## 中文化 中文介面,那麼可以到ithomo\settings.py進行修改,打開檔案搜尋LANGUAGE_CODE, ``` LANGUAGE_CODE = 'en-us' => 'zh-Hant' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True ``` ## app建立 python manage.py startapp [app_name] 下方建立welcome app 看的出來主要管理跟支線的差異性 跟flask blueprint類似? ``` (ithome_enve) C:\Users\User\Desktop\ithome\ithome>python manage.py startapp welcome (ithome_enve) C:\Users\User\Desktop\ithome\ithome>tree /F .. ithome │ ├───ithome │ │ │ settings.py │ │ │ urls.py │ │ │ wsgi.py │ │ │ __init__.py │ │ │ └───welcome │ │ admin.py │ │ apps.py │ │ models.py │ │ tests.py │ │ views.py │ │ urls.py #要自己copy一份過來 │ │ __init__.py │ │ │ └───migrations │ __init__.py ``` * admin.py : 設定資料庫呈現的模式,之後會跟models溝通 * models.py : 建構你的資料庫型態 這邊要客製化的話要自己用sql_alchemy * tests.py : 這是拿來檢查商業邏輯的地方 所以這邊是用來寫test case? * views.py : 相信你還沒有忘記,在 Day1我們這一位擔任控制者(controller)的身分, 沒錯! 它就是寫商業邏輯的地方,它會跟urls.py做呼應,並將所需傳達給前端 所以這邊應該就是撰寫response的地方 * urls.py : 它擔任著橋樑的角色,讓views.py與相對的網站做對應 * apps.py : 這裡你只要先了解,這是用來區別app的一個檔案即可 * migrations : 這資料夾裡面存放的內容,記錄著models裡面所創建的資料庫型態 不知道跟flask-migration有沒有關係當時看到就是個資料搬移的套件 welcome\views.py 這邊可以看的出來flask會import request但是這邊會使用request當作input response還不論斷定是render比較像flask的make response ``` from django.shourtcuts import render from django.http import HttpResponse def index(request): return HttpResponse("test") ``` welcome\urls.py 可以看到blue print的樣子 所以他的做法是透過app建置去劃分功能性跟區域 我在flask沒試過''這樣做法但是應該是可以設定 ``` from django.contrib import admin from django.urls import path from . import views urlpatterns = [ #name ='index' 有點不確定用途? path('',views.index,name='index') ] ``` ithome\urls.py ``` from django.contrib import admin from django.urls import path,include urlpatterns = [ #這邊應該就是類似flask的註冊 #所以urlpatterns應該是一定要正確的命名 path('welcome/',include('welcome.urls')) path('admin/',admin.site.urls) ] ``` ## database settings.py=>DATABASES ``` DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } ``` settings.py=>INSTALLED_APPS #看的出來就是安裝套件是甚麼 #可以去參考awesome django ``` INSTALLED_APPS = \[ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', \] ``` **只要將你創建的apps 放進來這裡,配合著 migrate,則它就會去監督該 apps 的 models,** 這句話不知道是指reparse還是marshal? ## migrate ``` (ithome_enve) C:\Users\User\Desktop\ithome\ithome>py 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 sessions.0001_initial... OK ``` migrate的用途這裡再強調一次, 它會去看 installed_apps裡面的設定, 並根據設定來找相對應的models,並根據我們所使用的資料庫來轉換成該資料庫的形式 ## models 用vendor來作範例 ``` (ithome_enve) C:\Users\User\Desktop\ithome\ithome>python manage.py startapp vendor (ithome_enve) C:\Users\User\Desktop\ithome\ithome>cd vendor ``` models.py ForeignKey跟sqlalchemy有87%像 ``` from django.db import models # Create your models here. class Vendor(models.Model): vendor_name = models.CharField(max_length = 20) store_name = models.CharField(max_length = 10) phone_number = models.CharField(max_length = 20) address = models.CharField(max_length = 100) class Food(models.Model): food_name = models.CharField(max_length = 30) # 食物名稱 price_name = models.DecimalField(max_digits = 3, decimal_places=0) food_vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE) ``` food_vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE) 一言以蔽之 一個攤販會賣各式各樣的食物,當它今天收店了,你就也再也吃不到它賣的每一樣食物了 **要追蹤該app,就要將它寫入 installed_apps裡面** settings.py 看不懂它命名規則 內容加上 **vendor.apps.VendorConfig** ``` INSTALLED_APPS = [ 'vendor.apps.VendorConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] ``` 建立migration還是看不等?這個步驟會產生db init的py (ithome_enve) C:\Users\User\Desktop\ithome\ithome>python manage.py makemigrations vendor ``` (ithome_enve) C:\Users\User\Desktop\ithome\ithome>python manage.py makemigrations vendor Migrations for 'vendor': vendor\migrations\0001_initial.py - Create model Food - Create model Vendor - Add field food_vendor to food ``` vendor\migrations\0001_initial.py 可以透過下方的語法operations裡面去建立db ``` from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='Food', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('food_name', models.CharField(max_length=30)), ('price_name', models.DecimalField(decimal_places=0, max_digits=3)), ], ), migrations.CreateModel( name='Vendor', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('vendor_name', models.CharField(max_length=20)), ('store_name', models.CharField(max_length=10)), ('phone_number', models.CharField(max_length=20)), ('address', models.CharField(max_length=100)), ], ), migrations.AddField( model_name='food', name='food_vendor', field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='vendor.Vendor'), ), ] ``` python manage.py migrate vendor 0001 可以透過指令去執行migrate裡面的編號他規則八成是 \d{4}_descript.py ``` Operations to perform: Apply all migrations: vendor Running migrations: Applying vendor.0001_initial... OK ``` ## INSTALLED_APPS探討 vendor.apps.VendorConfig 替換成 vendor是可以的 推薦使用 vendor.apps.VendorConfig 在 Django 1.9以前,下 python manage.py startapp [someapps]是不會自動產生 apps.py ``` INSTALLED_APPS = [ 'vendor', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] ``` vendor/apps.py ``` from django.apps import AppConfig class VendorConfig(AppConfig): name = 'vendor' ``` 我們託付給 Django 管理 apps, 它的運作模式就是去看該路徑是否有 default_app_config 這個變數 ,如果有這個變數,才會繼續去看後面所接的路徑 下面是trace過程但簡單講就是有變數繼續追直到找到繼承AppConfig的class INSTALLED_APPS 的設定 django.contrib.admin site-packages\django\contrib\admin ``` def autodiscover(): autodiscover_modules('admin', register_to=site) default_app_config = 'django.contrib.admin.apps.AdminConfig' ``` apps.AdminConfig ``` from django.apps import AppConfig from django.contrib.admin.checks import check_admin_app, check_dependencies from django.core import checks from django.utils.translation import gettext_lazy as _ class SimpleAdminConfig(AppConfig): ...省略 class AdminConfig(SimpleAdminConfig): """The default AppConfig for admin which does autodiscovery.""" def ready(self): super().ready() self.module.autodiscover() ``` 應該避免使用單純 app 的名稱 而是採用更明確的方式,在 settings.py 的 INSTALLED_APPS 加入 [someapp].apps.[SomeApps]Config ## admin 創建一個管理者(superuser) python manage.py createsuper ``` (ithome_enve) C:\Users\User\Desktop\ithome\ithome>python manage.py createsuperuser 使用者名稱 (leave blank to use 'bxchen'): ithome 電子信箱: ithome@example.com Password: Password (again): 這個密碼過短。請至少使用 8 個字元。 Bypass password validation and create user anyway? [y/N]: y Superuser created successfully. ``` host_ip/admin 就可以進入管理頁面 為了讓創建的models顯示在資料庫上,我們要去修改 admin.py 的內容 打開 vendor app 底下的 admin.py 不知道是不是只能寫admin看起來應該不用應該有import就ok但是 為了方面架構性管理應該不要犯賤亂放 ``` from django.contrib import admin from .models import Vendor, Food # Register your models here. admin.site.register(Vendor) admin.site.register(Food) ``` 後臺管理就可以直接增加刪除檔案 ## Shell 創建資料 Django便會提供我進行 Create、Retrieve、Update及Delete 的方式來操作資料庫,也就是所謂的CRUD py manage.py shell,便能夠進入 Python Shell ``` >>>from vendor.models import Food, Vendor >>>Vendor.objects.all() <QuerySet []> 兩種儲存方式 vendor = Vendor(vendor_name = "Alex", store_name="xelAshop", phone_number="09236", address='xs') vendor.save() or vendor = Vendor.objects.create(vendor_name = "Alex", store_name="xelAshop", phone_number="09236", address='xs') >>> Vendor.objects.all() <QuerySet [<Vendor: Vendor object (1)>]> >>> vendor = Vendor.objects.get(id=1) # 取得第一筆資料 >>> vendor.vendor_name # 相對應的鍵值 'Alex' >>> vendor.store_name 'xelAshop' >>> vendor.address 'xs' >>> vendor.phone_number '09236' Vendor.objects.filter(vendor_name = "Alex").update(address = 'Kao') Food.objects.get(id=1).delete() ``` ## Models & Admin 複寫改掉str repr內容 不是修改Models的資料型態,所以所們不需要進行 makemigrations 所以makemigrations應該是init db的動作 目前admin看起來只對後臺顯示管理有差 目前已知後台功能只有database CRUD有差 其他還不知道其實要看一下WORDPRESS才知道後台管理有甚麼功能 樣板更動,增加套件甚麼的 ``` class Vendor(models.Model): def __str__(self): return self.vendor_name 顯示會改變 >>> v1 = Vendor.objects.get(id=1) >>> v1 ``` django的model會自動加 sqlalchemy可能就要自己加 id = models.AutoField(primary_key=True) 但是不會顯示 ``` from django.db import models # 新增 from django.contrib import admin class Vendor(models.Model): ...中間略 # 新增 class VendorAdmin(admin.ModelAdmin): list_display = ('id', 'vendor_name') ``` 1. 新增 from django.contrib import admin 2. 新增該類別的管理者 VendorAdmin 看起來admin管理類別是透過特定寫法去做一些效果 再新增完上面的敘述之後,還要去 admin.py 進行設定 所以註冊可以model和amdin類型**綁定**註冊 ``` from django.contrib import admin # 後方新增 你所建立的 "類別名稱",這裡我的名稱是VendorAdmin from .models import Vendor, Food, VendorAdmbin # Register your models here. # 將管理者類別 VendorAdmin 填至該類別後方 admin.site.register(Vendor, VendorAdmbin) admin.site.register(Food) ``` 快速註冊方式 這個手法就是反向註冊 ``` @admin.register(Vendor) class VendorAdmin(admin.ModelAdmin): # 這裡我一併將 Vendor 類別 其它的欄位都加進來了 list_display = ['id', 'vendor_name', 'store_name', 'phone_number', 'address'] # admin.site.register(Vendor) # < 註解 ``` 這裡可以看到_meta應該是metadata所以django在管理上有很多地方跟sqlalchemy很像 ``` list_display = [field.name for field in Vendor._meta.fields] list_display = ['id', 'vendor_name', 'store_name', 'phone_number', 'address'] ``` 可以增加filter功能(不是屏蔽,是以xxx當作過濾條件) 但我覺得還是不夠靈活可以的話想要用re當filter 此時你便能透過 price_name 進行過濾的動作 ``` list_filter = ('price_name',) ``` 自製filter範例 照這個範例可以看到 完全可以寫出一個對所有東西可以用re filter的工具 但我還是希望可以直接選擇自己需要filter的key ``` # 額外 import 這個套件 from django.utils.translation import gettext_lazy as _ # 自行宣告 類別 class Morethanfifty(admin.SimpleListFilter): title = _('price') parameter_name = 'compareprice' # url最先要接的參數 def lookups(self, request, model_admin): return ( ('>50',_('>50')), # 前方對應下方'>50'(也就是url的request),第二個對應到admin顯示的文字 ('<=50',_('<=50')), ) # 定義查詢時的過濾條件 def queryset(self, request, queryset): if self.value() == '>50': return queryset.filter(price_name__gt=50) if self.value() == '<=50': return queryset.filter(price_name__lte=50) @admin.register(Food) class FoodAdmin(admin.ModelAdmin): list_display = [field.name for field in Food._meta.fields] # 將 Morethanfifty 填入 list_filter = (Morethanfifty,) ``` Django提供了許多方法來修改Admin的介面 以下只提供幾個 1. fields 2. search_fields 3. ordering fields,它的功用是限制 Admin 可以修改的欄位,如此一來可以避免發生不必要的修改 下面只有price_name可以修改其他不能,exclude是反向 fields 白名單 exclude黑名單 a in fields and a not in exclude ``` @admin.register(Food) class FoodAdmin(admin.ModelAdmin): list_display = [field.name for field in Food._meta.fields] list_filter = (morethanfourty,) fields = ['price_name'] # 顯示欄位 #exclude=['food_name', 'food_vendor'] ``` search_field 我最需要功能 search_fields = [filed_name] 如果搜尋麵包 WHERE ( food_name ILIKE '麵包' OR price_name ILIKE '麵包') ``` @admin.register(Food) class FoodAdmin(admin.ModelAdmin): list_display = [field.name for field in Food._meta.fields] list_filter = (morethanfourty,) search_fields = ('food_name','price_name') # 搜尋欄位 ``` ordering ordering = [filed_name] 但我比較想要按一個鈕可以改變排序 ``` @admin.register(Food) class FoodAdmin(admin.ModelAdmin): list_display = [field.name for field in Food._meta.fields] # list_display =['id', 'food_name'] list_filter = (morethanfourty,) search_fields = ('food_name','price_name') ordering = ('price_name',) # 價格 由小到大 排序 #ordering = ('-price_name',) # 新增 -,資料改為 由大到小 排序 ``` ## path ,ref path ,url **Django 2 以前**, 網址的寫法都是用 url() 搭配 正規表示法(regular expression) ``` from django.conf.urls import url from . import views urlpatterns = [ url(r'^articles/2003/$', views.special_case_2003), url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail), ] ``` **Django 2 之後**則用path,re_path re_path原始碼是call url所以其實也不用再用url了 反正path...就跟我想得差不多就看怎麼call而已 在強調view是放response的 只是flask我習慣view跟router放一起因為有一個decorator ``` #原本的寫法 url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), #修改後的寫法 path('articles/<int:year>/', views.year_archiv), path('articles/<int:year>/<int:month>', views.montu_archiv) ``` views 寫法 所以跟flask差異就是request類似self算是自帶參數 flaskg則是from flask import request去取資料 所以有可能他的app_context做法不同? 不過我目前沒看到他session怎麼處理的可能只是為了方便 所以request先放著 ``` # 這裡多傳入 year def year_archive(request, year): ... # 這裡多傳入 year 及 month def month_archive(request, year, month): ... ``` ## template Template 是什麼? 它其實就跟 MVC 架構的 V - view 資料夾命名重點是要取 template"s"不能少s ``` C:. │ db.sqlite3 │ manage.py ├───ithome │ │ settings.py │ │ urls.py │ │ wsgi.py │ │ __init__.py │ └───__pycache__ ├───templates #習慣放這個位置 ├───vendor │ │ admin.py │ │ apps.py │ │ models.py │ │ tests.py │ │ urls.py │ │ views.py │ │ __init__.py │ ├───migrations │ └───__pycache__ └───welcome │ admin.py │ apps.py │ models.py │ tests.py │ urls.py │ views.py │ __init__.py ├───migrations ``` ithome\settings.py=> TEMPLATES 看起來有預設的backend 其中也可以看到context processors會經過甚麼 這個可能跟debug有關但是我有看到auth 所以可能不用decorator自己去寫@require_login而是 去管理context 裡面的dirs應該就是template的來源 但是我覺得有點困惑這個setting是 ithome\settings 但view應該是不同app底下管理 ``` TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] ``` 'DIRS': ["[yourpath]/templates", 但這個寫法會換目錄掛點 settings.py => BASE_DIR BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 'DIRS': [ os.path.join(BASE_DIR, 'templates')] settings.py ``` urlpatterns = [ path('welcome/', include('welcome.urls')), path('vendor/', include('vendor.urls')), path('admin/', admin.site.urls), ] ``` vendor/urls.py ``` urlpatterns = [ path('', views.showtemplate), ] ``` vendor/urls.py ``` from django.shortcuts import render def showtemplate(request): return render(request, 'test.html') ``` 記得要在templates放test.html vendor/urls.py ``` # 修改前 urlpatterns = [ path('', views.showtemplate), ] # 修改後 urlpatterns = [ # 後方的 name 可以先忽略,目前不會用到 path('', views.vendor_index, name="vendor_index"), ] ``` vendor/views ``` from django.shortcuts import render from .models import Vendor def showtemplate(request): vendor_list = Vendor.objects.all() # 把所有 Vendor 的資料取出來 d = {'vendor_list': vendor_list} # 建立 Dict對應到Vendor的資料, return render(request, 'test.html', d) ``` test.html 就可以用jinja2引用到 ``` {{vlist.vendor_name }} ``` ## template 範本 其實base要放nav跟底層的bar 重點是jinja的繼承特性 templates\base.html ``` <!DOCTYPE html> <html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <title>{% block title %} {% endblock %}</title> </head> <body> {% block content %} {% endblock %} </body> </html> ``` templates\base.html ``` {% extends "base.html" %} {% block title %} My store {% endblock %} {% block content%} {% for vlist in vendor_list %} <h1> 店家 : {{vlist.vendor_name}} </h1> <ul> <li>店名 : {{ vlist.store_name}}</li> <li>電話 : {{ vlist.phone_number}}</li> <li>地址 : {{ vlist.address}}</li> </ul> {% endfor %} {% endblock %} ``` ## Django 插拔性 插拔性的概念就是,相同的 app 在不同 project,並透過一些額外設定,依然能夠正常的運作 project 所建立的 template 並不會跟著 app 移動,除非它原本就在 app 裡面! 所以又回到我當時的問題 template都放在一起blueprint失去他的獨立性 在 app 底下的 template,它必須額外建立該 app 名稱的資料夾 ├───vendor │ │ ...略 │ │ │ ├───templates │ │ └───vendors │ │ vendor_detail.html vendor_detail.html ``` {% extends "base.html" %} {% block title %} My store {% endblock %} {% block content%} {% for vlist in vendor_list %} <h1> 店家 : {{vlist.vendor_name}} </h1> <ul> <li>店名 : {{ vlist.store_name}}</li> <li>電話 : {{ vlist.phone_number}}</li> <li>地址 : {{ vlist.address}}</li> </ul> {% endfor %} {% endblock %} ``` ``` from django.shortcuts import render from .models import Vendor # Create your views here. def showtemplate(request): vendor_list = Vendor.objects.all() context = {'vendor_list': vendor_list} # print(vendor_list) return render(request, 'vendors/vendor_detail.html', context) ``` 在 Django 中, Template 搜尋的次序這個議題我們已經有討論過了,此時你會發現,此時多了一個順位2 所以django有搜尋的順序 你多**vendors**/vendor_detail.html只是為了方便區隔 重點是要取 template"s" ## modelform 看起來這個就是對應flask wtform的東西 有點差異modelform會直接儲存到DB用form.save() 會不會發生已經有相同名稱之類的錯誤處理要之後看看 一個validator 這邊我覺得有個東西我還沒嘗試就是form提交是一個post動作 理論上他會刷新頁面要怎麼保留form資料 我猜wtform跟modelform會處理 model : 我要使用哪一個 Model fileds : 使用 Model 的哪些欄位 ``` from django import forms from .models import Vendor, Food class VendorForm(forms.ModelForm): class Meta: model = Vendor fields = '__all__' labels = { 'vendor_name': _('攤販名稱'), 'store_name' : _('店名'), 'phone_number' : _('電話'), 'address' : _('地址'), } ``` post範例 ``` from .forms import VendorForm # 要記得 import 相對應的 Model Form 唷! # 針對 vendor_create.html def vendor_create_view(request): form = VendorForm(request.POST or None) if form.is_valid(): form.save() form = VerdorForm() # 清空 form context = { 'form' : form } return render(request, "vendors/vendor_create.html", context) ``` vendor_create.html form.as_p看起來是重點 感覺可以根據內容展開 驗證 顯示都在裡面 ``` {% extends "base.html" %} {% block title %} My store {% endblock %} {% block content%} <h2> Vendor Create</h2> <form method='POST'> {% csrf_token %} {{ form.as_p }} <input type="submit" value="save" /> </form> {% endblock %} ``` Django 提供給 forms 顯示的一種方式,目前總共有 3 種方式,分別是 而它所代表的意思便是, 我們的 form 要透過 HTML 的 \<tr> or \<p> or \<li> 來裝飾 * as_table * as_p * as_ul \{% csrf_token %} 可以避免csrf的錯誤很重要 ## Form 我們 創建Form 就像 創建Model的資料庫型態 一樣 forms.py ``` # 創建一個 Raw Form class RawVendorForm(forms.Form): vendor_name = forms.CharField() store_name = forms.CharField() phone_number = forms.CharField() # Model - Vendor class Vendor(models.Model): vendor_name = models.CharField(max_length = 20) # 攤販的名稱 store_name = models.CharField(max_length = 10) # 攤販店家的名稱 phone_number = models.CharField(max_length = 20) # 攤販的電話號碼 address = models.CharField(max_length = 100) # 攤販的地址 ``` views.py ``` from .forms import RawVendorForm # 新增 RawVendorForm # 新增 def vendor_create_view(request): form = RawVendorForm(request.POST or None) if form.is_valid(): Vendor.objects.create(**form.cleaned_data) # 新增到db form = RawVendorForm() context = { 'form' : form } return render(request, "vendors/vendor_create.html", context) ``` ModelForm 是來自 Form 的產物 Form的提交基本上都是add/update db 所以才要這樣連結 除非有一天他是做不一樣的操作再用form 上面例子只看的出來model form會做Vendor.objects.create 但是沒有update ## 動態網址 - path 應用 就PATH取用.. ``` # urls.py from django.contrib import admin from django.urls import path from . import views urlpatterns = [ path('', views.showtemplate), path('<int:id>/', views.singleVendor, name='vendor'), ] #重點名稱要正確 router設定跟function要一致 #在flask因為我都用decoraotr做所以沒感覺 def singleVendor(request, id): vendor_list = Vendor.objects.get(id=id) context = { 'vendor_list': vendor_list } return render(request, 'vendors/vendor_detail.html', context) ``` ## 網頁例外 我覺得我之前沒仔細考慮如果網頁設計不論從path還是query的變數 最後取得不到物件直接用404就好然後其他網頁做跳轉 當然如果是查詢功能像是動漫花園那樣的話就顯示沒有結果 Http404 可以raise的exception但是要在debug=False才能看到正常網頁 get_object_or_404:透過 get 取得一個物件 get_list_or_404 : 透過 filter 取得一些物件 ``` from django.http import Http404 def singleVendor(request, id): vendor_list = get_object_or_404(Vendor, id=id) # try: # vendor_list = Vendor.objects.get(id=id) # except Vendor.DoesNotExist: # raise Http404 context = { 'vendor_list': vendor_list } return render(request, 'vendors/vendor_detail.html', context) ``` settings.py 可以改debug模式 SECURITY WARNING: don't run with debug turned on in production! DEBUG = True =>False 將允許的網域加在 settings.py 的 ALLOWED_HOSTS ALLOWED_HOSTS = ['*'] ## urls name 原來jinja2可以call function???規則到底是甚麼... ``` #硬寫 <a href="/vendor/{{vlist.id}}"> <a href= {{ vlist.get_absolute_url }}> class Vendor(models.Model): def get_absolute_url(self): return f"/vendor/{self.id}/" urlspatterns 時,會有一個 name 的欄位 urlpatterns = [ path('<int:id>/', views.singleVendor, name='vendor_id'), # 這一行 ] ``` vendor_id,再將其反轉換 reverse() 其實就是flask url_for但是url_for在flask可以直接用 所以jinja2在flask/django有自己的方言? ``` from django.urls import reverse def get_absolute_url(self): #也就是使用vendor_id這個name然後kwargs是他input, #可能除了path還包含data,query等等 #也可以用args丟參數 return reverse("vendor_id", kwargs={"id": self.id}) ``` ## [命名空間](https://docs.djangoproject.com/en/2.1/topics/http/urls/#reversing-namespaced-urls) 為了避免 name 重複性的問題, 在 **/urls.py** 加上 名稱,而在 Django 裡面,我們稱作它為 app_name ``` urls.py from . import views app_name = 'vendors' # 新增 def get_absolute_url(self): return reverse("vendors:vendor_id", kwargs={"id": self.id}) ``` 命名空間 分成兩種 1. 應用命名空間 (Application Namespace) : 本名 2. 實例命名空間 (Instance Namespace) : 綽號 ``` # top-level urls.py from django.contrib import admin from django.urls import path, include from .views import test urlpatterns = [ path('admin/', admin.site.urls), # vendors app 有兩個綽號 f-vendors 及 s-vendors path('welcome/', include('vendor.urls', namespace='f-vendors')), path('vendor/', include('vendor.urls', namespace='s-vendors')), path('test/', test, name='index'), ] # vendor/urls.py from django.contrib import admin from django.urls import path from . import views app_name = 'vendors' urlpatterns = [ path('', views.showtemplate, name='index'), path('<int:id>/', views.singleVendor, name='vendor_id'), path('create', views.vendor_create_view, name='create'), path('fcreate', views.food_create_view, ), ] ``` 用url的語法可以直接查詢就跟url_for一樣 所以幹嘛要用reverse又有url... ``` <p> vendors:index :</p> {% url 'vendors:index' %}<br/> ``` 搜尋規則 =>vendors:index 1. 先找 應用命名空間(App Namespace) - 也就是我們的 app_name 2. 如果 應用命名空間底下有index網址便是 vendor/ 3. 找不到去找 預設應用命名空間(Default App Namespace) 4. 找不到去找所有綽號中最後一個取的綽號,也就是 s-vendors 5. 第一步如果找不到相符的 應用命名空間則會直接去找綽號 - 實例命名空間 個人覺得top_level直接放棄name_space比較簡單...反正本來就不能衝突 更下層給綽號name倒是怎麼樣都可以比較取得太相似就好 ## Class-Based Views (CBV) Class-Based Views 1. ListView 2. DetailView ``` # views.py from django.views.generic import ListView, DetailView # 新增 # Create your views here. # 繼承 ListView class VendorListView(ListView): model = Vendor template_name = 'vendor/vendor_list.html' # 繼承 DetailView class VendorDetail(DetailView): model = Vendor # queryset = Vendor.objects.all() template_name = 'vendor/vendor_detail.html' ``` * model :用哪個 Model,與 queryset = Vendor.objects.all()等義 * template_name:用哪個 template ``` # vendor_lst.html {% extends 'base.html' %} {% block content %} {% for vendor in object_list %} <div class=""> <h2><a href="{% url 'vendors:vendor_id' vendor.id %}">{{ vendor.store_name }}</a></h2> <p>{{ vendor.vendor_name }}</p> </div> {% endfor %} {% endblock content %} # vendor_detail.html {% extends "base.html" %} {% block title %} My store {% endblock %} {% block content%} <h1> 店家 : {{ vendor.vendor_name}} </h1> <ul> <li>店名 : {{ vendor.store_name}}</li> <li>電話 : {{ vendor.phone_number}}</li> <li>地址 : {{ vendor.address}}</li> </ul> {% endblock %} ``` 注意 * object_list:使用 for-loop 來存取 * vendor_detail.html:可以使用 object or 小寫的 Model name - vendor來存取 ``` urlpatterns = [ path('', VendorListView.as_view(), name='index'), path('<int:id>/', VendorDetailView.as_view(), name='vendor_id'), ``` * CreateView ``` from django.views.generic import CreateView class VendorCreateView(CreateView): # form_class = VendorModelForm model = Vendor fields='__all__' template_name = 'vendors/vendor_create.html' ``` ``` #vendor_create.html fields= ['vendor_name', 'store_name'] 在網頁可以存取的東西會剩這兩個 {{ form.as_p }} #urls.py path('create/', VendorCreateView.as_view(), name='create'), ``` create_view+formmodel ``` # CreateView class VendorCreateView(CreateView): form_class = VendorModelForm # model = Vendor # fields= ['vendor_name', 'store_name'] template_name = 'vendors/vendor_create.html' # modelForm class VendorModelForm(forms.ModelForm): class Meta: model = Vendor fields = '__all__' # fields = ( # 'vendor_name', # 'store_name', # 'phone_number', # 'address', # ) labels = { 'vendor_name': _('攤販名稱'), 'store_name' : _('店名'), 'phone_number' : _('電話'), 'address' : _('地址'), } ``` * UpdateView ``` class VendorUpdateView(UpdateView): form_class = VendorModelForm template_name = 'vendors/vendor_create.html' queryset = Vendor.objects.all() # 這很重要 #urls.py path('<int:pk>/update/', VendorUpdateView.as_view(), name='update'), # ``` 加上 queryset 的原因是因為 UpdateView 會呼叫 get_object() CreateView 並不會呼叫 get_object() ## cbv vs fbv fbv ``` def vendor_create_view(request): form = RawVendorForm(request.POST or None) if form.is_valid(): # 決定邏輯 Vendor.objects.create(form.cleaned_data) # 作處理 form = RawVendorForm() # return redirect('/vendor/') context = { 'form' : form } return render(request, "vendors/vendor_create.html", context) ``` * CBV : 你想要的功能在某一個CBV都提供了(e.g. ListView),而你純粹覆寫一些屬性,CBV比較適合 * FBV : 同時處理多個表單,FBV比較適合! ## auth- 登入/登出 ``` INSTALLED_APPS = [ 'django.contrib.auth', ] ``` 要使用 Django 內建的 auth 相當的簡單,只要在 urls.py ``` # 新增 urls.py 再進行修改 from django.contrib import admin from django.urls import path, include urlpatterns = [ ... 略 path('accounts/', include('django.contrib.auth.urls')), # 新增 ] ``` include後可以用下面的東西 ``` accounts/login/ [name='login'] accounts/logout/ [name='logout'] accounts/password_change/ [name='password_change'] accounts/password_change/done/ [name='password_change_done'] accounts/password_reset/ [name='password_reset'] accounts/password_reset/done/ [name='password_reset_done'] accounts/reset/<uidb64>/<token>/ [name='password_reset_confirm'] accounts/reset/done/ [name='password_reset_complete'] ``` 重要 新增這一個 template的位置及名稱是有限制的 template/registration/login.html ``` # template/registration/login.html {% extends "base.html" %} {% block title %} Login {% endblock title %} {% block content %} <form method="post" action="{% url 'login' %}"> {% csrf_token %} <table> <tr> <td>{{ form.username.label_tag }}</td> <td>{{ form.username }}</td> </tr> <tr> <td>{{ form.password.label_tag }}</td> <td>{{ form.password }}</td> </tr> </table> <input type="submit" value="login"> </form> {% endblock content %} ``` 成功後轉網址設定 ``` #settings.py LOGIN_REDIRECT_URL = '/' LOGOUT_REDIRECT_URL = '/' ``` ``` from django.contrib import admin from django.urls import path, include from .views import test from django.views.generic.base import TemplateView # 新增 urlpatterns = [ ...略 path('accounts/', include('django.contrib.auth.urls')), path('', TemplateView.as_view(template_name='home.html')) # 新增 ] ``` 透過 TemplateView 這個 CBV,我們能夠直接把 template的屬性加在後方直接使用! ``` {% extends "base.html" %} {% block title %} HomePage {% endblock %} {% block content %} {% if user.is_authenticated %} Hello, {{ user.username }}!</br> <a href="{% url 'logout' %}">Logout</a><br> {% else %} <p>You are not logged in</p> <a href="{% url 'login' %}">Login Page</a> {% endif %} {% endblock %} ``` ## 註冊 ``` from django.contrib.auth.forms import UserCreationForm # 新增 def register(request): if request.method == 'POST': form = UserCreationForm(request.POST) print("Errors", form.errors) if form.is_valid(): form.save() return redirect('') else: return render(request, 'registration/register.html', {'form':form}) else: form = UserCreationForm() context = {'form': form} return render(request, 'registration/register.html', context) # top-level urls.py urlpatterns = [ ...略 path('register/', register, name='register'), ] ``` ``` #templates/registration/register.html {% extends 'base.html' %} {% block title %} Register Page {% endblock title %} {% block content %} <form method="POST"> {% csrf_token %} {{ form.as_p }} {% for field in form %} {{ field.errors }} {% endfor %} <button type="submit">送出</button> {% endblock content %} ``` ## [第三方登入](https://ithelp.ithome.com.tw/articles/10206389) 自己看 # 問題 1. path('',views.index,name='index') name ='index' 有點不確定用途? 可以透過reverse('index')去找到他href位置 2. vendor.apps.VendorConfig 看不懂哪來的 [someapp].apps.[SomeApps]Config /1.9版以後才會自動產生app.py?所以未必每個東西都要放到 INSTALLED_APPS去管理 好處跟壞處要再看看 目前只知道migrate指令才可以認到install_apps裡面 3. 不知道admin註冊是不是要加到INSTALLED_APPS 不知道他規則是否要加入到面才有效果? 4. 奇怪flask的blue print怎麼管理static file和template 是可以指定拉不過都放一起比較方便... ``` frontend = Blueprint('frontend', __name__, template_folder='templates', static_folder='static') ``` 5. modelform的各種錯誤處理要怎麼做 像是甚麼格式錯誤已經有相同使用者之類的 if form.is_valid()應該是這句話 要怎麼對每個欄位做validator forms.Form或forms.ModelForm繼承之後 應該可以在裡面寫對欄位的規則不過這邊沒看到怎麼做 而且validator分兩種可以本地判斷跟遠端判斷的 像是email格式你可以寫在js去判斷他格式禁止他post 但是使用者存不存在只能ajax或post才能知道 這邊感覺應該是沒有本地判斷的機制全部都用post來解 可能速度會比較慢 6.settings.py跟short cut都要另外看裡面可以做的設定 7. jinja2可以call function??? ``` <a href= {{ vlist.get_absolute_url }}> ``` 8. reverse 仍然有很多問題像是 跨app傳遞資料等他命名衝突會怎樣? 要用app_name:name去呼叫 ``` reverse("vendors:vendor_id", kwargs={"id": self.id}) ``` 9. url跟reverse有啥差異 10. user.is_authenticated 這個東西可以在jinja2用不知道 可不可以用在fbv裡面