2012-12-16 15 views
8

Come posso raggruppare le caselle di controllo prodotte da CheckboxSelectMultiple da un modello correlato?Raggruppato CheckboxSelectMultiple nel modello Django

Ciò è meglio dimostrato dall'esempio.

models.py:

class FeatureCategory(models.Model): 
    name = models.CharField(max_length=30) 

class Feature(models.Model): 
    name = models.CharField(max_length=30) 
    category = models.ForeignKey(FeatureCategory) 

class Widget(models.Model): 
    name = models.CharField(max_length=30) 
    features = models.ManyToManyField(Feature, blank=True) 

forms.py:

class WidgetForm(forms.ModelForm): 
    features = forms.ModelMultipleChoiceField(
     queryset=Feature.objects.all(), 
     widget=forms.CheckboxSelectMultiple, 
     required=False 
    ) 
    class Meta: 
     model = Widget 

views.py:

def edit_widget(request): 
    form = WidgetForm() 
    return render(request, 'template.html', {'form': form}) 

template.html:

{{ form.as_p }} 

Quanto sopra produce il seguente risultato:

[] Widget 1 
[] Widget 2 
[] Widget 3 
[] Widget 1 
[] Widget 2 

Nei desidero appartiene alla funzione caselle per essere raggruppati per categoria feature (sulla base del ForeignKey):

Category 1: 
    [] Widget 1 
    [] Widget 2 
    [] Widget 3 

Category 2: 
    [] Widget 1 
    [] Widget 2 

Come posso ottenere questo risultato? Ho provato a utilizzare il tag modello {% regroup %} senza alcun risultato.

Qualche consiglio molto apprezzato.

Grazie.

risposta

14

È necessario scrivere il widget personalizzato CheckboxSelectMultiple. Usando il snippet ho provato a rendere il campo CheckboxSelectMultiple iterabile aggiungendo il category_name come attributo nel campo attrs. In modo che io possa utilizzare il tag regroup nel modello in seguito.

Il codice seguente viene modificato dallo snippet in base alle proprie esigenze, ovviamente questo codice può essere reso più pulito e più generico, ma in questo momento non è generico.

forms.py

from django import forms 
from django.forms import Widget 
from django.forms.widgets import SubWidget 
from django.forms.util import flatatt 
from django.utils.html import conditional_escape 
from django.utils.encoding import StrAndUnicode, force_unicode 
from django.utils.safestring import mark_safe 

from itertools import chain 
import ast 

from mysite.models import Widget as wid # your model name is conflicted with django.forms.Widget 
from mysite.models import Feature 

class CheckboxInput(SubWidget): 
    """ 
    An object used by CheckboxRenderer that represents a single 
    <input type='checkbox'>. 
    """ 
    def __init__(self, name, value, attrs, choice, index): 
     self.name, self.value = name, value 
     self.attrs = attrs 
     self.choice_value = force_unicode(choice[1]) 
     self.choice_label = force_unicode(choice[2]) 

     self.attrs.update({'cat_name': choice[0]}) 

     self.index = index 

    def __unicode__(self): 
     return self.render() 

    def render(self, name=None, value=None, attrs=None, choices=()): 
     name = name or self.name 
     value = value or self.value 
     attrs = attrs or self.attrs 

     if 'id' in self.attrs: 
      label_for = ' for="%s_%s"' % (self.attrs['id'], self.index) 
     else: 
      label_for = '' 
     choice_label = conditional_escape(force_unicode(self.choice_label)) 
     return mark_safe(u'<label%s>%s %s</label>' % (label_for, self.tag(), choice_label)) 

    def is_checked(self): 
     return self.choice_value in self.value 

    def tag(self): 
     if 'id' in self.attrs: 
      self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index) 
     final_attrs = dict(self.attrs, type='checkbox', name=self.name, value=self.choice_value) 
     if self.is_checked(): 
      final_attrs['checked'] = 'checked' 
     return mark_safe(u'<input%s />' % flatatt(final_attrs)) 

class CheckboxRenderer(StrAndUnicode): 
    def __init__(self, name, value, attrs, choices): 
     self.name, self.value, self.attrs = name, value, attrs 
     self.choices = choices 

    def __iter__(self): 
     for i, choice in enumerate(self.choices): 
      yield CheckboxInput(self.name, self.value, self.attrs.copy(), choice, i) 

    def __getitem__(self, idx): 
     choice = self.choices[idx] # Let the IndexError propogate 
     return CheckboxInput(self.name, self.value, self.attrs.copy(), choice, idx) 

    def __unicode__(self): 
     return self.render() 

    def render(self): 
     """Outputs a <ul> for this set of checkbox fields.""" 
     return mark_safe(u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' 
       % force_unicode(w) for w in self])) 

class CheckboxSelectMultipleIter(forms.CheckboxSelectMultiple): 
    """ 
    Checkbox multi select field that enables iteration of each checkbox 
    Similar to django.forms.widgets.RadioSelect 
    """ 
    renderer = CheckboxRenderer 

    def __init__(self, *args, **kwargs): 
     # Override the default renderer if we were passed one. 
     renderer = kwargs.pop('renderer', None) 
     if renderer: 
      self.renderer = renderer 
     super(CheckboxSelectMultipleIter, self).__init__(*args, **kwargs) 

    def subwidgets(self, name, value, attrs=None, choices=()): 
     for widget in self.get_renderer(name, value, attrs, choices): 
      yield widget 

    def get_renderer(self, name, value, attrs=None, choices=()): 
     """Returns an instance of the renderer.""" 

     choices_ = [ast.literal_eval(i[1]).iteritems() for i in self.choices] 
     choices_ = [(a[1], b[1], c[1]) for a, b, c in choices_] 

     if value is None: value = '' 
     str_values = set([force_unicode(v) for v in value]) # Normalize to string. 
     if attrs is None: 
      attrs = {} 
     if 'id' not in attrs: 
      attrs['id'] = name 
     final_attrs = self.build_attrs(attrs) 
     choices = list(chain(choices_, choices)) 
     return self.renderer(name, str_values, final_attrs, choices) 

    def render(self, name, value, attrs=None, choices=()): 
     return self.get_renderer(name, value, attrs, choices).render() 

    def id_for_label(self, id_): 
     if id_: 
      id_ += '_0' 
     return id_ 

class WidgetForm(forms.ModelForm): 
    features = forms.ModelMultipleChoiceField(
     queryset=Feature.objects.all().values('id', 'name', 'category__name'), 
     widget=CheckboxSelectMultipleIter, 
     required=False 
    ) 
    class Meta: 
     model = wid 

Poi nel modello:

{% for field in form %} 
{% if field.name == 'features' %} 
    {% regroup field by attrs.cat_name as list %} 

    <ul> 
    {% for el in list %} 
     <li>{{el.grouper}} 
     <ul> 
      {% for e in el.list %} 
       {{e}} <br /> 
      {% endfor %} 
     </ul> 
     </li> 
    {% endfor %} 
    </ul> 
{% else %} 
    {{field.label}}: {{field}} 
{% endif %} 

{% endfor %} 

Risultati: ho aggiunto nome paesi nella categoria tavolo, e le città nella Tabella caratteristiche in modo di modello che è stato in grado di riorganizzarsi le città (caratteristiche) secondo il paese (categoria)

enter image description here

+0

Speravo che ci sarebbe stato un modo integrato, ma questo fa esattamente quello che stavo cercando. Grazie. – gjb

+0

Benvenuto :) –

+0

Questo codice funziona ancora? Ho appena provato a usarlo e mi sono perso nei messaggi di errore relativi a ast. – stickwithjosh

Problemi correlati