{"id":20619776,"url":"https://github.com/twtrubiks/django-field-tutorial","last_synced_at":"2025-07-06T22:34:34.986Z","repository":{"id":84518926,"uuid":"100791746","full_name":"twtrubiks/django-field-tutorial","owner":"twtrubiks","description":"Django ORM and Relationship Field  OneToOneField , ForeignKey ,ManyToManyField   📝","archived":false,"fork":false,"pushed_at":"2024-06-23T12:58:41.000Z","size":13,"stargazers_count":23,"open_issues_count":0,"forks_count":5,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-04T22:12:58.564Z","etag":null,"topics":["django","foreignkey","manytomanyfield","onetoonefield","orm","relational-model","tutorial"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/twtrubiks.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-08-19T12:05:13.000Z","updated_at":"2024-06-23T12:58:44.000Z","dependencies_parsed_at":"2024-11-16T12:12:40.276Z","dependency_job_id":"ebbf5f05-0ebd-4baa-a00e-5bc8f467d6ec","html_url":"https://github.com/twtrubiks/django-field-tutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/twtrubiks/django-field-tutorial","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango-field-tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango-field-tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango-field-tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango-field-tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/twtrubiks","download_url":"https://codeload.github.com/twtrubiks/django-field-tutorial/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango-field-tutorial/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259712369,"owners_count":22900032,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["django","foreignkey","manytomanyfield","onetoonefield","orm","relational-model","tutorial"],"created_at":"2024-11-16T12:12:29.636Z","updated_at":"2025-06-13T20:03:44.295Z","avatar_url":"https://github.com/twtrubiks.png","language":"Python","readme":"# django-field-tutorial\n\nDjango ORM and Relationship Field\n\n認識 [Django](https://www.djangoproject.com/)  **OneToOneField** , **ForeignKey** ,**ManyToManyField**  📝\n\n為什麼我會把這三個特別拿出來講呢 ？ 因為他會影響到你設計資料庫，更影響到你的整體架構。\n\n* [Youtube Tutorial - part1](https://youtu.be/b2W7aJjbbC0)\n\n* [Youtube Tutorial - OneToOneField - part2](https://youtu.be/tYV2pmpTGEU)\n\n* [Youtube Tutorial - ForeignKey - part3](https://youtu.be/1RkipG5YQO0)\n\n* [Youtube Tutorial - ManyToManyField - part4](https://youtu.be/f3YZIHUTzMg)\n\n建議對 [Django](https://github.com/django/django) 不熟悉的朋友，可以先觀看我之前寫的文章（ 先認識一下 [Django](https://github.com/django/django) ）\n\n* [Django 基本教學 - 從無到有 Django-Beginners-Guide](https://github.com/twtrubiks/django-tutorial)\n\n## 我可以從這篇學到什麼\n\n* OneToOneField\n* ForeignKey\n* ManyToManyField\n\n## 安裝套件\n\n請在 cmd ( 命令提示字元 ) 輸入以下指令\n\n```python\npip install django==4.2.3\n```\n\n如果你想要使用 PostgreSQL, 請多安裝\n\n```python\npip install psycopg2==2.9.6\n```\n\n並且修改 [settings.py](https://github.com/twtrubiks/django-field-tutorial/blob/master/django_field_tutorial/settings.py)\n\n```python\nDATABASES = {\n    'default': {\n        'ENGINE': 'django.db.backends.postgresql_psycopg2',\n        'NAME': 'postgres',\n        'USER': 'myuser',\n        'PASSWORD': 'password123',\n        'HOST': 'localhost',\n        'PORT': 5432,\n    }\n}\n```\n\n## 教學\n\n我們先透過 OneToOneField_tutorial 來認識基本的流程，所以請將 [settings.py](https://github.com/twtrubiks/django-field-tutorial/blob/master/django_field_tutorial/settings.py) 裡的 INSTALLED_APPS修改一下，修改如下\n\n```python\nINSTALLED_APPS = [\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n    'OneToOneField_tutorial',\n    # 'ForeignKey_tutorial',\n    # 'ManyToManyField_tutorial',\n]\n```\n\n在 OneToOneField_tutorial 的 [models.py](https://github.com/twtrubiks/django-field-tutorial/blob/master/OneToOneField_tutorial/models.py) 裡，有我們事先寫好的 model，\n\n我們先 makemigrations\n\n```python\npython manage.py makemigrations OneToOneField_tutorial\n```\n\n接著你應該會看到這類的文件訊息\n\n```cmd\nMigrations for 'OneToOneField_tutorial':\n  OneToOneField_tutorial\\migrations\\0001_initial.py\n    - Create model Profile\n```\n\n接著我們再透過 sqlmigrate 指令來看 migration 將會執行的 SQL，\n\n下面這行並不會馬上執行你的 SQL\n\n```cmd\npython manage.py sqlmigrate OneToOneField_tutorial 0001\n```\n\n接著你應該會看到這類的文件訊息\n\n```cmd\nBEGIN;\n--\n-- Create model Profile\n--\nCREATE TABLE \"OneToOneField_tutorial_profile\" (\"user_id\" integer NOT NULL PRIMARY KEY REFERENCES \"auth_user\" (\"id\"), \"date_of_birth\" date NULL);\nCOMMIT;\n```\n\n上面這些資訊能幫助你更了解對資料庫做了什麼事情\n\n***sqlmigrate 這個指令是可以省略的，我建議可以執行這個指令多去了解將會執行的 SQL***\n\n最後我們再 migrate\n\n( 這行才會幫你執行 SQL )\n\n```python\npython manage.py migrate\n```\n\n***不管你對 model 做了任何的 新增，修改，刪除，請記得一定要再執行 makemigrations 以及 migrate***\n\n以上是基本的流程，接著我要進入今天的主題 :grin:\n\n## OneToOneField\n\n OneToOneField 官方文件的參考\n\n[https://docs.djangoproject.com/en/4.2/topics/db/examples/one_to_one/](https://docs.djangoproject.com/en/4.2/topics/db/examples/one_to_one/)\n\n[https://docs.djangoproject.com/en/4.2/ref/models/fields/#django.db.models.OneToOneField](https://docs.djangoproject.com/en/4.2/ref/models/fields/#django.db.models.OneToOneField)\n\n在 OneToOneField_tutorial 的 [models.py](https://github.com/twtrubiks/django-field-tutorial/blob/master/OneToOneField_tutorial/models.py) 裡，有我們事先寫好的 model，\n\n [models.py](https://github.com/twtrubiks/django-field-tutorial/blob/master/OneToOneField_tutorial/models.py) 裡面的程式碼如下\n\n```python\nclass Profile(models.Model):\n    user = models.OneToOneField(\n        settings.AUTH_USER_MODEL,\n        on_delete=models.CASCADE,\n        primary_key=True\n    )\n    date_of_birth = models.DateField(blank=True, null=True)\n\n    def __str__(self):\n        return f'Profile for user {self.user.username}'\n```\n\nOneToOneField 我們最常用的時機就是擴充 ( extends )，舉個例子，\n\nDjango 的 User Model 預設已經有一些存在的 field ，但很多時候我們\n\n常常需要增加一些額外的資料，像是需要記錄使用者的生日，這時候\n\nOneToOneField 就派上用場了。建立一個 Profile 的 model，透過\n\nOneToOneField 和 User Model 建立 ***一對一 （ one-to-one ）*** 的關係。\n\n我在再透過 python console 來把玩一下，\n\n我們先建立一個 user\n\n```python\nfrom django.contrib.auth.models import User\n\n# create user\nuser = User.objects.create_user(username='user1',email='user@test.com',password='password123')\n```\n\n接著再加入 Profile\n\n```python\nfrom OneToOneField_tutorial.models import Profile\n\nimport datetime\n\n# create profile\nprofile = Profile.objects.create(user=user, date_of_birth=datetime.datetime(2017,2,3))\n```\n\n```python\nprofile.user\n\u003e \u003cUser: user1\u003e\n```\n\n也可以反查\n\n```python\n# via user get profile\nuser = User.objects.get(username='user1')\nuser.profile\n\u003e \u003cProfile: Profile for user user1\u003e\n```\n\n這邊的反查是使用預設的反查機制.\n\n也可以自己透過 `related_name` 定義反查機制, 範例如下\n\n```python\nclass Profile(models.Model):\n    user = models.OneToOneField(\n        settings.AUTH_USER_MODEL,\n        on_delete=models.CASCADE,\n        related_name=\"user_profile\",\n    )\n    pass\n```\n\n反查代碼變成如下\n\n```python\n# via user get profile\nuser = User.objects.get(username='user1')\nuser.user_profile\n\u003e \u003cProfile: Profile for user user1\u003e\n\n# 假如沒有定義 related_name=\"user_profile\", 會變成 model_name + _set\n\u003e user.profile_set\n```\n\nP.S 在 model 裡的 on_delete=models.CASCADE ，\n\n可以幫助你刪除資料時一併刪除。\n\n其他的一些例子\n\n```python\n# 找出 username 開頭是 user\nProfile.objects.filter(user__username__startswith=\"user\")\n\u003e \u003cQuerySet [\u003cProfile: Profile for user user1\u003e]\u003e\n\n# 排除 username 開頭是 user\nProfile.objects.exclude(user__username__contains=\"user\")\n\u003e \u003cQuerySet []\u003e\n```\n\n如果今天\n\n```python\n\u003e\u003e\u003e user2 = User.objects.create_user(username='user2',email='user@test.com',password='password123')\n\u003e\u003e\u003e user2.profile\nTraceback (most recent call last):\n  File \"\u003cconsole\u003e\", line 1, in \u003cmodule\u003e\n  File \"/home/twtrubiks/.pyenv/versions/test_env_39/lib/python3.9/site-packages/django/db/models/fields/related_descriptors.py\", line 492, in __get__\n    raise self.RelatedObjectDoesNotExist(\ndjango.contrib.auth.models.User.profile.RelatedObjectDoesNotExist: User has no profile.\n\n# 請使用以下的 code 修正\n\u003e\u003e\u003e from django.core.exceptions import ObjectDoesNotExist\n\u003e\u003e\u003e try:\n...     user2.profile\n... except ObjectDoesNotExist:\n...     print(\"no profile\")\n...\nno profile\n```\n\n## ForeignKey\n\nForeignKey 官方文件的參考\n\n[https://docs.djangoproject.com/en/4.2/topics/db/examples/many_to_one/](https://docs.djangoproject.com/en/4.2/topics/db/examples/many_to_one/)\n\n[https://docs.djangoproject.com/en/4.2/ref/models/fields/#django.db.models.ForeignKey](https://docs.djangoproject.com/en/4.2/ref/models/fields/#django.db.models.ForeignKey)\n\n在 ForeignKey_tutorial 的 [models.py](https://github.com/twtrubiks/django-field-tutorial/blob/master/ForeignKey_tutorial/models.py) 裡，有我們事先寫好的 model，\n\n [models.py](https://github.com/twtrubiks/django-field-tutorial/blob/master/ForeignKey_tutorial/models.py) 裡面的程式碼如下\n\n```python\n\nclass Reporter(models.Model):\n    first_name = models.CharField(max_length=30)\n    last_name = models.CharField(max_length=30)\n    email = models.EmailField()\n\n    def __str__(self):\n        return f\"{self.first_name} {self.last_name}\"\n\nclass Article(models.Model):\n    headline = models.CharField(max_length=100)\n    pub_date = models.DateField()\n    reporter = models.ForeignKey(\n        Reporter,\n        related_name='articles',\n        on_delete=models.CASCADE,\n        db_index=True # 預設為 True, 會自動幫你建立 index\n    )\n\n    # reporter = models.ForeignKey(\n    #     Reporter,\n    #     on_delete=models.CASCADE)\n\n    def __str__(self):\n        return self.headline\n\n    class Meta:\n        # ref\n        # https://docs.djangoproject.com/en/4.2/ref/models/options/#ordering\n        ordering = [\"headline\"]\n\n        # unique_together = ['headline', 'reporter']\n        # indexes = [\n        #     models.Index(name='headline_reporter_index', fields=['headline','reporter'],)\n        # ]\n\n        # index 建議使用 Meta.indexes, db_index 未來可能會棄用\n        # ref\n        # https://docs.djangoproject.com/en/4.2/ref/models/fields/#db-index\n```\n\n以上面這個例子來說，我們有一個  Reporter 記者 model 以及一個 Article 文章 model。\n\n在 Article model 中，我們定義了 ForeignKey ，因為一篇文章只屬於一個記者的，而一\n\n個記者可以寫很多篇文章，所以是屬於 ***多對一 （ many-to-one ）*** 的關係。\n\n註解掉的部份是要和大家解釋，假如你沒有定義 `related_name` 這個屬性，這樣當你需\n\n要反查回去時，你需要使用 Django model 的名稱再加上 `_set`，以範例來說，就是 `article_set`。\n\n我在再透過 python console 來把玩一下，\n\n```python\n\nfrom ForeignKey_tutorial.models import Reporter, Article\n\n# create reporter\nreporter = Reporter.objects.create(first_name='John', last_name='Smith', email='john@example.com')\n\nimport datetime\n\ndate = datetime.datetime(2017,2,3)\n\n# create article\narticle = Article.objects.create(headline=\"This is a test\", pub_date=date, reporter=reporter)\n```\n\n```python\n# via article get reporter\narticle.reporter\n\u003e \u003cReporter: John Smith\u003e\n```\n\n```python\narticle.reporter.id # 會有多餘的 query\n\u003e 1\n```\n\n```python\narticle.reporter_id # 不會有多餘的 query\n\u003e 1\n```\n\n如果想要增加效能, 可以透過 `select_related`,\n\n```python\n\u003e\u003e\u003e article = Article.objects.select_related('reporter').get(id=1) # 會有一次 query\n\u003e\u003e\u003e article.reporter.id    # 不會 query\n\u003e\u003e\u003e article.reporter.email # 不會 query\n```\n\n延伸閱讀 [透過 django 介紹 N+1 Queries Problem](https://github.com/twtrubiks/django_N_add_1_queries_problem_tutorial)\n\n也可以反查\n\n```python\n\nreporter.articles.all()\n\n# if not set related_name\n# reporter.article_set.all()\n\n\u003e \u003cQuerySet [\u003cArticle: This is a test\u003e]\u003e\n\n```\n\n上面兩個結果是一樣的，只是差在是否有設定 `related_name`\n\n反查可以透過 `prefetch_related` 改善效能,\n\n```python\n\u003e\u003e\u003e reporter = Reporter.objects.prefetch_related('articles').get(id=1)\n\u003e\u003e\u003e reporter.articles.first()\n```\n\n延伸閱讀 [透過 django 介紹 N+1 Queries Problem](https://github.com/twtrubiks/django_N_add_1_queries_problem_tutorial)\n\n透過 Reporter object，建立一個 Article object\n\n```python\n# create an article via the reporter object:\nnew_article = reporter.articles.create(headline=\"John's second story\", pub_date=date)\n\n# new_article = reporter.article_set.create(headline=\"John's second story\", pub_date=date)\n\nnew_article.reporter\n\u003e \u003cReporter: John Smith\u003e\n\n```\n\n其他的一些例子\n\n```python\n# 將 article2 的 reporter 從 John Smith -\u003e Tony Shen\nreporter2 = Reporter.objects.create(first_name='Tony', last_name='Shen', email='tony@example.com')\narticle2 = Article.objects.get(headline=\"This is a test\")\narticle2.reporter\n\u003e \u003cReporter: John Smith\u003e\nreporter2.articles.add(article2)\narticle2.reporter\n\u003e \u003cReporter: Tony Shen\u003e\n```\n\n其他的一些例子\n\n```python\nArticle.objects.filter(reporter__first_name=\"John\")\n\u003e \u003cQuerySet [\u003cArticle: John's second story\u003e, \u003cArticle: This is a test\u003e]\u003e\n\n# 列出全部 reporter id 為 1 或 2 的\nArticle.objects.filter(reporter__in=[1, 2])\n\n# 列出全部 reporter id 為 reporter 物件, 放 id 或 物件 都可以\nArticle.objects.filter(reporter__in=[reporter])\n\n# 列出全部 reporter id 為 1 或 2 的, 並且根據 headline 下去做 distinct\n# ref postgresql\n# https://docs.djangoproject.com/en/4.2/ref/models/querysets/#distinct\nArticle.objects.filter(reporter__in=[1, 2]).order_by(\"headline\").distinct(\"headline\")\n\n# reverse filter\n# ref.\n# https://docs.djangoproject.com/en/4.2/ref/models/fields/#django.db.models.ForeignKey.related_query_name\nReporter.objects.filter(articles__headline__startswith=\"This\")\n\u003e \u003cQuerySet [\u003cReporter: John Smith\u003e]\u003e\n\nReporter.objects.prefetch_related('articles').filter(articles__headline__startswith=\"This\")\n\u003e \u003cQuerySet [\u003cReporter: John Smith\u003e]\u003e\n\nreporter.articles.filter(headline__startswith=\"This\")\n\u003e \u003cQuerySet [\u003cArticle: This is a test\u003e]\u003e\n```\n\n## ManyToManyField\n\n ManyToManyField 官方文件的參考\n\n[https://docs.djangoproject.com/en/4.2/topics/db/examples/many_to_many/](https://docs.djangoproject.com/en/4.2/topics/db/examples/many_to_many/)\n\n[https://docs.djangoproject.com/en/4.2/ref/models/fields/#django.db.models.ManyToManyField](https://docs.djangoproject.com/en/4.2/ref/models/fields/#django.db.models.ManyToManyField)\n\n在 ManyToManyField 的 [models.py](https://github.com/twtrubiks/django-field-tutorial/blob/master/ManyToManyField_tutorial/models.py) 裡，有我們事先寫好的 model，\n\n [models.py](https://github.com/twtrubiks/django-field-tutorial/blob/master/ManyToManyField_tutorial/models.py) 裡面的程式碼如下\n\n```python\n\nclass Image(models.Model):\n    title = models.CharField(max_length=200)\n    description = models.TextField(blank=True)\n    created = models.DateField(auto_now_add=True)\n    users_like = models.ManyToManyField(\n        settings.AUTH_USER_MODEL,\n        related_name='images_like'\n    )\n\n```\n\n以上面這個例子來說，我們建立一個 Image model ，一張圖片可以有很多使用者喜歡，\n\n而一個使用者也可以喜歡多張圖片，所以他們是 ***多對多（ many-to-many ）*** 的關係。\n\n當你建立多對多（ many-to-many ）的關係時，你會發現被多建立一張表，這張表是用來\n\n記錄多對多的關係。\n\n註解掉的部份是要和大家解釋，假如你沒有定義 `related_name` 這個屬性，這樣當你需要\n\n反查回去時，你需要使用 Django model 的名稱再加上 `_set`，以範例來說，就是 `image_set`\n\n再透過 python console 來把玩一下，\n\n建立 user\n\n```python\n\nfrom django.contrib.auth.models import User\n\nuser1 = User.objects.create_user(username='user1',email='user@test.com',password='password123')\n\nuser2 = User.objects.create_user(username='user2',email='user2@test.com',password='password123')\n\n```\n\n```python\n\nfrom ManyToManyField_tutorial.models import Image\n\n# create image\nimage = Image.objects.create(title='img1')\n\n# add users via image\nimage.users_like.add(user1)\nimage.users_like.add(user2)\n\n# get users via image\nimage.users_like.all()\n\u003e \u003cQuerySet [\u003cUser: user1\u003e, \u003cUser: user2\u003e]\u003e\n\n# get images via users\nuser1.images_like.all()\n\u003e \u003cQuerySet [\u003cImage: Image object\u003e]\u003e\n\n```\n\n## 後記\n\n 這次介紹了 [Django](https://www.djangoproject.com/)  的 **OneToOneField** , **ForeignKey** , **ManyToManyField** ，\n\n 除了幫助大家更了解他們的關係之外，更簡單介紹適合的情境，希望能對大\n\n 家在設計架構時有幫助，這三個 Field 看似簡單，但值得深入去了解他 :satisfied:。\n\n## 執行環境\n\n* Python 3.8\n\n## Reference\n\n* [Django](https://www.djangoproject.com/)\n\n## License\n\nMIT license","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwtrubiks%2Fdjango-field-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftwtrubiks%2Fdjango-field-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwtrubiks%2Fdjango-field-tutorial/lists"}