---
# System prepended metadata

title: '[django-oscar]商品頁面新增尺寸選項'

---

# [django-oscar]商品頁面新增尺寸選項

###### django-oscar

## 1. 什麼是"option"

option讓賣家可以針對商品性質新增選項，請看以下範例：
> 衣服：尺寸、顏色（S／M／L、紅/黑/粉）
食物：辣度、口味（辣/不辣、草莓口味／巧克力口味）

有了這些選項，消費者就可以選擇特定類型加入購物車
賣家也能更善用頁面空間呈現商品

## 2. 知道什麼是option後，就來新增尺寸的option吧！

django-oscar預設是沒有任何option出現的(見下圖)


---


![](https://i.imgur.com/z8GrtgR.png =500x)
(預設的oscar-django是沒有紅框處的)



---

為了讓商品頁出現尺寸的option
我們要先到catalogue/models.py覆寫Option類別！
> 如果還沒fork要覆寫的app，記得先去fork catalogue和basket這兩個app
> 這樣子你才有catalogue/models.py可以覆寫喔
> 所有customize的第一步都是先fork要改的app！（畫重點）


```python=
#project/mainsite/catalogue/models.py
from oscar.apps.catalogue.abstract_models import AbstractOption

class Option(AbstractOption):#覆寫原本的Option

     # Option types
    TEXT = "text"
    INTEGER = "integer"
    FLOAT = "float"
    BOOLEAN = "boolean"
    DATE = "date"
    CHOICES = "choices" 

    TYPE_CHOICES = (
        (TEXT, _("Text")),
        (INTEGER, _("Integer")),
        (BOOLEAN, _("True / False")),
        (FLOAT, _("Float")),
        (DATE, _("Date")),
        (CHOICES,_("Choices")),#讓選項可以用choice的方式呈現
    )
   
    type = models.CharField(_("Type"), max_length=255, default=TEXT, choices=TYPE_CHOICES)

```


修改好程式碼後
到 `admin/catalogue/option` 的頁面按下add按鈕
應該就可以看到下拉式選單中出現了第20行打的Choices

![](https://i.imgur.com/erwF9a4.png =400x)

我們順手新增一下「尺寸」這個option吧～
![](https://i.imgur.com/TSVHKny.png =400x)




接著我們到 `admin/catalogue/Product` 隨便點一個已經新增好的商品

![](https://i.imgur.com/Xw1maHS.png =500x)

往下滑，找到`Product Options`，選擇我們剛剛新增好的「尺寸」

![](https://i.imgur.com/bNrnzel.png =380x)


接下來，因為衣服不可能永遠什麼尺寸都有，有些尺寸會比較快賣完
我們希望可以在 `Attribute` 新增尺寸這個特徵
某尺寸賣完時，我們就到 `Attribute value` 把該尺寸拿掉
而Option只要負責從 `Attribute value` 中抓值顯示就好

所以我們接下來到 `admin/catalogue/Product attribute`
新增一個名為尺寸的Attribute
（Option Group的部分選multiple options是因為尺寸有很多種）

![](https://i.imgur.com/py9AeHq.png =450x)

接下來回到 `admin/catalogue/Product`
一樣先隨便點一個商品，進入編輯頁面，到 `PRODUCT ATTRIBUTE VALUES` 的地方
應該就可以選擇有哪些尺寸了！
以這張圖片為例，這個商品就是只有S、L和XL這三種尺寸

![](https://i.imgur.com/ulGposr.png)


寫到這邊，商品頁面還沒辦法好好呈現我們的Option
我們還要再修改一下程式碼！
請回到你的程式編輯器，到basket資料夾下建一個forms.py檔案，然後把下面的程式碼放上去
（細節請見註解）

```python=
# basket/forms.py
from oscar.apps.basket.forms import AddToBasketForm, SimpleAddToBasketForm

from django.db.models import Sum
from django.forms.utils import ErrorDict
from django.utils.translation import gettext_lazy as _

from oscar.core.loading import get_model
from oscar.forms import widgets

Line = get_model('basket', 'line')
Basket = get_model('basket', 'basket')
Option = get_model('catalogue', 'Option')
Product = get_model('catalogue', 'product')
ProductAttribute = get_model('catalogue', 'ProductAttribute')
ProductAttributeValue = get_model('catalogue', 'ProductAttributeValue')


def _option_text_field(option, product):
        return forms.CharField(label=option.name, required=option.required)

def _option_integer_field(option, product):
        return forms.IntegerField(label=option.name, required=option.required)

def _option_boolean_field(option, product):
        return forms.BooleanField(label=option.name, required=option.required)

def _option_float_field(option, product):
        return forms.FloatField(label=option.name, required=option.required)

def _option_date_field(option, product):
        return forms.DateField(label=option.name, required=option.required, widget=forms.widgets.DateInput)
    
#把抓到的資料轉成tuple，供CHOICES顯示，你可以依照自己的資料狀況寫一個自己的trim方法
def trimAttribueValue(attr):
    tmp = attr.replace('尺寸: ','')
    size_list = tmp.split(', ')

    for i in range(0, len(size_list)):
        size_list[i] = (size_list[i],size_list[i])
    # print('DIY',tuple(size_list))
    size_tuple = tuple(size_list)
    return size_tuple

#重點！就是這裡讓選項跑出來的！
#記得把參數從只有option改成(option, product)
def _option_choices_field(option, product):
   #抓「尺寸」這個Attribute
    attribute = ProductAttribute.objects.filter(name='尺寸')
    #再去抓這個「product」裡的「attribute」尺寸裡面有什麼值
    product_attribute_value = ProductAttributeValue.objects.filter(attribute=attribute[0],product=product)
    #抓到資料後，處理一下，把內容轉換成tuple
    size_tuple = trimAttribueValue(str(product_attribute_value[0]))
    #將tuple設為choices的參數，就差不多大功告成了！
    return forms.ChoiceField(label=option.name, required=option.required, choices=size_tuple)

class AddToBasketForm(AddToBasketForm):
    
    OPTION_FIELD_FACTORIES = {
            Option.TEXT: _option_text_field,
            Option.INTEGER: _option_integer_field,
            Option.BOOLEAN: _option_boolean_field,
            Option.FLOAT: _option_float_field,
            Option.DATE: _option_date_field,
            Option.CHOICES: _option_choices_field,
        }

    def _add_option_field(self, product, option):

        option_field = self.OPTION_FIELD_FACTORIES.get(option.type, Option.TEXT) (option, product)#新增product這個參數
       
        self.fields[option.code] = option_field
#這個class一定要覆寫然後pass，不然系統不會抓到你上面覆寫AddToBasketForm的內容喔！
class SimpleAddToBasketForm(SimpleAddToBasketMixin, AddToBasketForm):
    pass

```


噹噹！回到商品頁面，應該就可以看到尺寸的選項出現了！
![](https://i.imgur.com/QrdTTjS.png)

選個L號的加入購物車吧！
![](https://i.imgur.com/rfzqwux.png)

大功告成！
![](https://i.imgur.com/noZfbxe.png)




---

最後附上這個專案我的github
https://github.com/Felice1004/onehalf-clothing-platform/

如有任何錯誤請不吝賜教!

© Felice Hsiao 2021


