2016-03-24 6 views
0

Ich habe eine Form mit einer ModelMultipleChoiceField welche queryset is generated at Form instanciation. Ich möchte auch, dass die ersten drei Optionen zunächst überprüft werden. Hier ist mein Code:Vermeiden Sie doppelte Abfrage mit vorgefüllten ModelMultipleChoiceField

class DeliveryForm(forms.Form): 

    content = forms.CharField(
     label=_("Contenu"), validators=[ 
      MinLengthValidator(20), 
      MaxLengthValidator(5000), 
     ], widget=Wysiwyg) 

    sponsors = DeliverySponsorsField(
     label=_("Commanditaires"), validators=[ 
      MaxLengthValidator(3), 
     ], error_messages={ 
      'max_length': _(
       "Vous ne pouvez pas sélectionner plus de 3 commanditaires."), 
     }, queryset=None) 

    def __init__(self, *args, **kwargs): 
     quote_request = kwargs.pop('quote_request') 
     suitable_sponsors = Sponsor.objects.all().suitable_for_quote_request(
      quote_request) 

     initial = kwargs.pop('initial', None) or {} 
     if 'content' not in initial: 
      initial['content'] = quote_request.description 
     if 'sponsors' not in initial: 
      initial['sponsors'] = suitable_sponsors[:3] 

     kwargs['initial'] = initial 

     super().__init__(*args, **kwargs) 

     self.fields['sponsors'].queryset = suitable_sponsors 

DeliverySponsorsField ist eine Unterklasse von ModelMultipleChoiceField, die mir einen komplexen Widget angezeigt werden kann:

class DeliverySponsorsRenderer(CheckboxFieldRenderer): 
    outer_html = '<ul{id_attr} class="media-list">{content}</ul>' 
    inner_html = '<li class="media">[...]</li>' 

    def render(self): 
     id_ = self.attrs.get('id') 
     output = [] 
     for i, choice in enumerate(self.choices): 
      choice_value, sponsor = choice 
      widget = self.choice_input_class(self.name, self.value, 
              self.attrs.copy(), choice, i) 
      output.append({ 
       'x': sponsor.x, 'y': sponsor.y, 'z': sponsor.z, ...}) 

     content = format_html_join('\n', self.inner_html, output) 
     # I have my own `format_html_join` function that handles keyword arguments 
     return format_html(self.outer_html, 
          id_attr=format_html(' id="{}"', id_) if id_ else '', 
          content=content) 

class DeliverySponsorsWidget(CheckboxSelectMultiple): 
    renderer = DeliverySponsorsRenderer 

class DeliverySponsorsField(ModelMultipleChoiceField): 
    widget = DeliverySponsorsWidget 

    def label_from_instance(self, obj): 
     return obj 

Dies funktioniert wie ein Charme.

Nun, nicht ganz, weil die folgenden Zeilen wertet die queryset:

initial['sponsors'] = suitable_sponsors[:3] 

Und die queryset auch danach bewertet wird, um die Wahlmöglichkeiten zu generieren. . Obwohl nur eine Abfrage ausreichen würde (da suitable_sponsors[:3] eine Teilmenge von suitable_sponsors ist

habe ich versucht, mit den queryset Auswertung zu erzwingen:

# Replaced 
suitable_sponsors = Sponsor.objects.all().suitable_for_quote_request(
     quote_request) 
# with 
suitable_sponsors = list(
    Sponsor.objects.all().suitable_for_quote_request(quote_request)) 

jedoch ModelMultipleChoiceField beschwert sich über queryset keine QuerySet sein Genauer gesagt. es beschwert sich über queryset.all undefiniert:

File "/home/antoine/.venvs/aladom_v6/lib/python3.4/site-packages/django/forms/widgets.py" in get_renderer 
    763.   choices = list(chain(self.choices, choices)) 

File "/home/antoine/.venvs/aladom_v6/lib/python3.4/site-packages/django/forms/models.py" in __iter__ 
    1105.   queryset = self.queryset.all() 

Exception Type: AttributeError at /admin/quotation/requalification/141369/deliver/ 
Exception Value: 'list' object has no attribute 'all' 

Kann ich „leicht“ vermeiden abfragen der Datenbank zweimal während ich es onl nicht abfragen y einmal für diesen Fall?

+0

Wenn die zusätzliche Abfrage keine Leistungsprobleme verursacht, kann dies eine vorzeitige Optimierung sein. – Alasdair

+0

@Alasdair Sicher, deshalb bitte ich um eine "einfache" Lösung. Es geht nicht darum, eine übertriebene Maschinerie zu erstellen, um diese zusätzliche Abfrage zu vermeiden. Wenn ich es leicht vermeiden kann, gut. Wenn dies etwas exotische Arbeit erfordert, würde ich lieber die zusätzliche Abfrage behalten. –

+0

@Antoine: "Geben Sie einen Zoll und eine Meile", müssen Sie wachsam sein mit der Leistung :) dann wieder einmal müssen Sie geben –

Antwort

1

Anstatt eine Abfrage für Ihr Feld festzulegen, können Sie stattdessen eine Auswahlliste mit Werten für das Auswahlfeld angeben.

suitable_sponsors = Sponsor.objects.suitable_for_quote_request(quote_request)\ 
    .values_list('id', 'sponsor_name') 

if 'sponsors' not in initial: 
    initial['sponsors'] = [s.id for s in suitable_sponsors[:3]] 

self.fields['sponsors'].choices = suitable_sponsors 

müssen Sie möglicherweise ModelMultipleChoiceField in MultipleChoiceField ändern.

+0

Ich benutze tatsächlich eine benutzerdefinierte Unterklasse von 'ModelMultipleChoiceField', die mir ermöglicht, ein komplexes Widget mit mehr Daten als nur den Namen des Sponsors anzuzeigen. Das sieht jedoch nach einer guten Idee aus, ich denke, ich kann "MultipleChoiceField" und nicht "ModelMultipleChoiceField" ableiten. –

+0

Leider sieht es so aus, als würde die Verwendung eines 'MultipleChoiceField' oder' TypedMultipleChoiceField' mein benutzerdefiniertes Feld mehr als nötig komplexieren. –

+0

Können Sie self.fields ['Sponsoren'] versuchen. Widget.choices = geeignete_sponsoren? Ich weiß nicht, ob das eine gültige Syntax ist. –