iT邦幫忙

2022 iThome 鐵人賽

DAY 17
0

Day 17 個人化,Django 多租戶網站設定

每個租戶都會有各自的網站相關資訊,這些資訊可不能只由我們開發者才能調整,這樣當要頻繁更動資料時開發者會非常辛苦,將這些調整時會更動的內容放上管理介面讓網站管理者自行控制吧!

建立 網站設定 模型

將頁面可能會時常調整的內容定義成欄位,主要分為網站語言、網站資訊、網站樣式、首頁、商品詳細頁、更多資訊。

網站語言
語言的設定將會在之後介紹多語系的時候進行講解

網站資訊
網站名稱、聯絡電話

網站樣式
包含 網站logo、網站圖示、還有主要 css 檔案。

首頁
將上方商品類型區塊預設取前三筆資料改為讓管理者能自行選擇哪三筆

商品詳細頁
將會新增第二個版面進行選擇

更多資訊
為頁尾的社群媒體連結

# core/models.py

from django.db import models
from core import helpers

# Create your models here.
class Setting(models.Model):
    id = models.CharField('語言代碼', max_length=10, primary_key=True)
    language = models.CharField('系統語言', max_length=50)
    sitename = models.CharField(
        '系統名稱', default='', null=True, max_length=100, blank=True
    )
    logo = models.FileField(
        "logo 圖片", null=True, blank=True, upload_to=helpers.upload_handle
    )
    favicon = models.FileField(
        "favicon 圖片", null=True, blank=True, upload_to=helpers.upload_handle
    )
    style = models.FileField(
        "CSS樣式", null=True, blank=True, upload_to=helpers.upload_handle
    )
    home_type1 = models.ForeignKey(
        'products.ProductCategory', blank=True, null=True, 
        on_delete=models.RESTRICT, verbose_name='左上商品分類', related_name='home_type1_set'
    )
    home_type2 = models.ForeignKey(
        'products.ProductCategory', blank=True, null=True, 
        on_delete=models.RESTRICT, verbose_name='左下商品分類', related_name='home_type2_set'
    )
    home_type3 = models.ForeignKey(
        'products.ProductCategory', blank=True, null=True, 
        on_delete=models.RESTRICT, verbose_name='右側商品分類', related_name='home_type3_set'
    )
    detail_template = models.CharField(
        '詳細頁面版型', 
        max_length=100, 
        choices=(("Template-1", "版型一"),("Template-2", "版型二")), 
        default="Template-1"
    )
    facebook = models.CharField(
        'Facebook 連結', default='', null=True, max_length=100, blank=True
    )
    instagram = models.CharField(
        'Instagram 連結', default='', null=True, max_length=100, blank=True
    )
    twitter = models.CharField(
        'Twitter 連結', default='', null=True, max_length=100, blank=True
    )
    class Meta:
        verbose_name = '網站設定'
        verbose_name_plural = '網站設定'

    def __str__(self):
        return "%s" % (self.language)

將 core 放入 TENANT_APPS (原先沒有使用到 Model 所以沒有加入)

# main/settings.py 

TENANT_APPS = (
    'django.contrib.auth',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.admin',
    'django.contrib.sitemaps',
    'core',
    'products',
)

建立資料庫遷移檔案

docker exec --workdir /opt/app/web example_tenant_web \
python3.10 manage.py makemigrations core

Migrations for 'core':
  core/migrations/0001_initial.py
    - Create model Setting

執行資料庫遷移

docker exec --workdir /opt/app/web example_tenant_web python3.10 manage.py migrate core
=== Starting migration
Operations to perform:
  Apply all migrations: core
Running migrations:
  Applying core.0001_initial...
 OK
=== Starting migration
Operations to perform:
  Apply all migrations: core
Running migrations:
  Applying core.0001_initial...
 OK
=== Starting migration
Operations to perform:
  Apply all migrations: core
Running migrations:
  Applying core.0001_initial...
 OK

建立管理介面

這裡使用 fieldsets 設定將欄位分為組為多個區塊

# core/admin.py 

from django.contrib import admin

# Register your models here.
from core.models import Setting

class SettingAdmin(admin.ModelAdmin):
    fieldsets = [
        ('語言', {
            'fields': (
                'id',
                'language',
            )
        }),
        ('網站資訊設定', {
            'classes': (
                'tab',
                'tab-general',
            ),
            'fields': (
                'sitename',
                'phone',
            )
        }),
        ('樣式設定', {
            'classes': (
                'tab',
                'tab-style',
            ),
            'fields': (
                'logo',
                'favicon',
                'style',
            )
        }),
        ('首頁設定', {
            'classes': (
                'tab',
                'tab-home',
            ),
            'fields': (
                'home_type1',
                'home_type2',
                'home_type3',
            )
        }),
        ('商品詳細頁設定', {
            'classes': (
                'tab',
                'tab-detail',
            ),
            'fields': (
                'detail_template',
            )
        }),
        ('更多資訊設定', {
            'classes': (
                'tab',
                'tab-more',
            ),
            'fields': (
                'facebook',
                'instagram',
                'twitter',
            )
        }),
    ]

admin.site.register(Setting, SettingAdmin) 

網站設定管理介面展示:

https://ithelp.ithome.com.tw/upload/images/20220929/201516569FOSDyxu5g.png

在租戶 example01 與 example02 分別建立一筆語言代碼為 zh-hant 的資料

https://ithelp.ithome.com.tw/upload/images/20220929/2015165629mk6Ifly5.png

https://ithelp.ithome.com.tw/upload/images/20220929/20151656FOT8nUwVMB.png

成功建立好管理介面與設定資料,但要如何在使用者介面能夠使用這些資料呢?

使用 templatetags 自定義標籤

Django Template Language 本身已經有許多模板標籤,而想要擁有自定義的標籤就需要使用到 templatetags。
在 core 應用程式目錄底下建立一個 templatetags 目錄,並在其中再建立空白的 __init__.py 檔案與 core_tag.py 檔案。
以下為 core 應用程式目錄架構:

.
├── admin.py
├── apps.py
├── helpers.py
├── __init__.py
├── migrations
│   ├── 0001_initial.py
│   └── __init__.py
├── models.py
├── templatetags
│   ├── core_tag.py
│   └── __init__.py
├── tests.py
└── views.py

core_tag.py 檔案中定義一個 get_setting 函數來返回網站設定的資料。
由於網站設定會在每個頁面中使用,如果取每個欄位的資料都傳欄位名稱進函數溝通資料庫取值會有很多重複的查詢,因此我們將整個網站設定轉換成一張 setting_dict 字典表,這樣每個頁面只要查詢一次即可。
而因為 Foreignkey 的欄位預設是顯示關聯 id,這裡重新取得完整後轉換為字典後加入 setting_dict ,再將更新後的 setting_dict 返回。

# core/templatetags/core_tag.py

from django import template
register = template.Library()

from core.models import Setting

@register.simple_tag
def get_setting():
    setting = Setting.objects.get(id='zh-hant')
    setting_dict = Setting.objects.get(id='zh-hant').__dict__
    setting_dict['home_type1'] = setting.home_type1.__dict__ if setting.home_type1 else None
    setting_dict['home_type2'] = setting.home_type2.__dict__ if setting.home_type2 else None
    setting_dict['home_type3'] = setting.home_type3.__dict__ if setting.home_type3 else None
    return setting_dict

模板使用自定義標籤

core_tag.py 寫好函數後,就可以在模板使用 load 語法載入自定義標籤,標籤的名稱為檔案名稱 core_tag。

在基底頁面使用 load 語法載入自定義標籤 {% load core_tag %}, 而要使用函數也是用 {% %} 將函數名稱包起來 {% get_setting %}。
在這裡使用 DTL 的 as 語法再將返回值存入 DTL 模板變數 get_setting 方便後續使用字典。

<!-- products/templates/base.html -->
{% load core_tag %}
{% get_setting as get_setting %}

基底頁面使用 . 語法取得 sitename

<!-- products/templates/base.html -->
<!-- ... -->

<text id="sitename">
    <tspan x="108.94" y="325">{{ get_setting.sitename }}</tspan>
</text>

<!-- ... -->

基底頁面使用 . 語法取得頁尾更多資訊

<!-- products/templates/base.html -->
<!-- ... -->

<ul class="social-media">
    <li>
        <a href="{{ get_setting.facebook }}" target="_blank">
            <i class="tf-ion-social-facebook"></i>
        </a>
    </li>
    <li>
        <a href="{{ get_setting.instagram }}" target="_blank">
            <i class="tf-ion-social-instagram"></i>
        </a>
    </li>
    <li>
        <a href="{{ get_setting.twitter }}" target="_blank">
            <i class="tf-ion-social-twitter"></i>
        </a>
    </li>
</ul>

<!-- ... -->

首頁取得三筆網站設定定義的商品分類

<!-- products/templates/products/home.html -->
<!-- ... -->

<section class="product-category section">
    <div class="container">
        <div class="row">
            <div class="col-md-12">
                <div class="title text-center">
                    <h2>以商品分類尋找屬於您的商品</h2>
                </div>
            </div>
            <div class="col-md-6">
                <div class="category-box">
                    <a href="#!">
                        {% if get_setting.home_type1.image %}
                            <img src="{% get_media_prefix %}{{ get_setting.home_type1.image }}" alt="" />
                        {% endif %}
                        <div class="content">
                            <h3 style="color: white;">{{ get_setting.home_type1.name }}</h3>
                            <p style="color: white;">{{ get_setting.home_type1.description }}</p>
                        </div>
                    </a>	
                </div>
                <div class="category-box">
                    <a href="#!">
                        {% if get_setting.home_type2.image %}
                            <img src="{% get_media_prefix %}{{ get_setting.home_type2.image }}" alt="" />
                        {% endif %}
                        <div class="content">
                            <h3 style="color: white;">{{ get_setting.home_type2.name }}</h3>
                            <p style="color: white;">{{ get_setting.home_type2.description }}</p>
                        </div>
                    </a>	
                </div>
            </div>
            <div class="col-md-6">
                <div class="category-box category-box-2">
                    <a href="#!">
                        {% if get_setting.home_type3.image %}
                            <img src="{% get_media_prefix %}{{ get_setting.home_type3.image }}" alt="" />
                        {% endif %}
                        <div class="content">
                            <h3 style="color: white;">{{ get_setting.home_type3.name }}</h3>
                            <p style="color: white;">{{ get_setting.home_type3.description }}</p>
                        </div>
                    </a>
                </div>
            </div>
        </div>
    </div>
</section>

<!-- ... -->

租戶 example01

https://ithelp.ithome.com.tw/upload/images/20220929/20151656lAEroLjX5c.png

租戶 example02

https://ithelp.ithome.com.tw/upload/images/20220929/20151656odQbAMak0l.png

個人化設定完成!

今天完成了多租戶個人化的網站資訊,下一回將進行網站樣式與版面的切換調整『個性風格,自定義樣式版面』。


上一篇
Day 16 佈置房間, 將資料傳入 Template
下一篇
Day 18 個性風格,自定義樣式版面
系列文
全能住宅改造王,Django 多租戶架構的應用 —— 實作一個電商網站30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言