2016-12-27 68 views
0

簡短版本:是否可以定義一組Django在呈現表單時應該使用的默認css類?如何設置Django中所有內置表單小部件的默認css類

長版本: 上下文如下:我想使用w3.css框架中定義的所有我的表單(http://www.w3schools.com/w3css/default.asp)的css類。我已經看到在Django中可以在表單類定義或表單呈現中這樣做,但是它要求在這兩種情況下都明確聲明所有表單字段。這意味着我放棄了ModelForms的自動錶單生成的所有好處。我想獲得如下內容:

  1. 定義某處(例如在設置文件中)表單字段/窗口小部件和css類之間的默認映射,例如, 'textinput': 'my_default_css_class_for_text_inputs'
  2. 默認情況下,對於窗體的所有自動生成和渲染,將使用(1)中定義的默認css類,而不對現有窗體類進行修改或對其進行最小修改。對於特定窗體,我可以重載默認值與其他值

據我瞭解,這種行爲是不可能在django。脆皮形式包似乎朝着這個方向發展,但似乎不僅僅是這一點,而且我不確定我是否想要所有額外的複雜性(我在這裏仍然是一個新手)。另一種方法是使用JavaScript在客戶端添加類。對我來說,這看起來像一個醜陋的壞習慣。

任何人都可以證實我對這個問題的理解,並指出我優雅的解決方案,如果有的話?

謝謝!

喬納森

回答

0

我已經成功地找到了答案,以我的問題,我在這裏張貼爲後人。對於標籤類,我從herehere(來自user2732686的回答)獲得了一些靈感。第一個鏈接建議在運行時重新定義BoundField類的label_tag方法。這是一個比第二個鏈接中建議的解決方案更詳細的解決方案,但是這是以項目範圍內的黑客爲代價的,我不建議這樣做。在這裏,我按照標籤的第二個鏈接中的建議,遵循Django的subclassing狂熱。

在項目settings.py,添加:

# Default css classes for widgets and labels 
DEFAULT_CSS = { 
      'error': 'w3-panel w3-red',  # displayed in the label 
      'errorlist': 'w3-padding-8 w3-red', # encloses the error list 
      'required': 'w3-text-indigo',  # used in the label and label + input enclosing box. NB: w3-validate only works if the input precedes the label! 
      'label': 'w3-label', 
      'Textarea': 'w3-input w3-border', 
      'TextInput': 'w3-input w3-border', 
      'Select': 'w3-select w3-border', 
      } 

注意:除了4個,在必須Django的插件名稱相匹配。

在你forms.py(或其他地方),地址:

from django.forms import ModelForm, inlineformset_factory, Form, BoundField 
from django.forms.utils import ErrorList 
from django.utils.html import format_html, force_text 
from django.conf import settings 

class CustErrorList(ErrorList): 
    # custom error list format to use defcss 
    def __str__(self): 
     return self.as_div() 
    def as_div(self): 
     if not self: 
      return '' 
     return format_html('<div class="{}">{}</div>', 
          settings.DEFAULT_CSS['errorlist'], 
          ' '.join([ force_text(e) for e in self ]) 
          ) 

class CustBoundField(BoundField): 
    # overload label_tag to include default css classes for labels 
    def label_tag(self, contents=None, attrs=None, label_suffix=None): 
     newcssclass = settings.DEFAULT_CSS['label'] 
     if attrs is None: 
      attrs = {} 
     elif 'class' in attrs: 
      newcssclass = ' '.join([ attrs['class'], newcssclass ]) # NB: order has no impact here (but it does in the style sheet) 
     attrs.update({ 'class': newcssclass }) 
     # return the output of the original method with the modified attrs 
     return super(CustBoundField, self).label_tag(contents, attrs, label_suffix) 

def custinit(self, subclass, *args, **kwargs): 
    # overload Form or ModelForm inits, to use default CSS classes for widgets 
    super(subclass, self).__init__(*args, **kwargs) 
    self.error_class = CustErrorList # change the default error class 

    # Loop on fields and add css classes 
    # Warning: must loop on fields, not on boundfields, otherwise inline_formsets break 
    for field in self.fields.values():    
     thiswidget = field.widget 
     if thiswidget .is_hidden: 
      continue 
     newcssclass = settings.DEFAULT_CSS[ thiswidget.__class__.__name__ ] 
     thisattrs = thiswidget.attrs 
     if 'class' in thisattrs: 
      newcssclass = ' '.join([ thisattrs['class'], newcssclass ]) # NB: order has no impact here (but it does in the style sheet) 
     thisattrs.update({ 'class': newcssclass }) 

def custgetitem(self, name): 
    # Overload of Form getitem to use the custom BoundField with 
    # css-classed labels. Everything here is just a copy of django's version, 
    # apart from the call to CustBoundField 
    try: 
     field = self.fields[name] 
    except KeyError: 
     raise KeyError(
      "Key '%s' not found in '%s'. Choices are: %s." % (
       name, 
       self.__class__.__name__, 
       ', '.join(sorted(f for f in self.fields)), 
      ) 
     ) 
    if name not in self._bound_fields_cache: 
     self._bound_fields_cache[name] = CustBoundField(self, field, name) 
     # In the original version, field.get_bound_field is called, but 
     # this method only calls BoundField. It is much easier to 
     # subclass BoundField and call it directly here 
    return self._bound_fields_cache[name]   

class DefaultCssModelForm(ModelForm): 
    # Defines the new reference ModelForm, with default css classes 
    error_css_class = settings.DEFAULT_CSS['error'] 
    required_css_class = settings.DEFAULT_CSS['required'] 

    def __init__(self, *args, **kwargs): 
     custinit(self, DefaultCssModelForm, *args, **kwargs) 

    def __getitem__(self, name): 
     return custgetitem(self, name) 

class DefaultCssForm(Form): 
    # Defines the new reference Form, with default css classes 

    error_css_class = settings.DEFAULT_CSS['error'] 
    required_css_class = settings.DEFAULT_CSS['required'] 

    def __init__(self, *args, **kwargs): 
     custinit(self, DefaultCssForm, *args, **kwargs) 

    def __getitem__(self, name): 
     return custgetitem(self, name) 

注:您的項目名稱替換<MY_PROJECT>

然後,你只需繼承DefaultCssModelFormDefaultCssForm代替ModelFormForm當定義你的表格時。對於formsets,請使用這些類作爲基類。舉例說明:

class MyForm(DefaultCssModelForm): 
    class Meta: 
     model = MyModel 
     fields = '__all__' 

MyFormSet = inlineformset_factory(..., ..., form=DefaultCssModelForm, ...)