J'ai deux modèles, une machine et un périphérique
[api-66c9c5d85c-qtnrq api-app] attrs -> {}
[api-66c9c5d85c-qtnrq api-app] choices -> <django.forms.models.ModelChoiceIterator object at 0x7f6c73ad4ac8>
[api-66c9c5d85c-qtnrq api-app] is_required -> False
Maintenant dans Django Admin, dans la page de changement de machine, je veux pouvoir ajouter des références de périphérique 1-N, cela fonctionne bien et bien de la boîte avec la relation ManyToMany, voici à quoi cela ressemble dans admin (souhaité)
J'essaie d'utiliser forms.ModelMultipleChoiceField pour la sélection 1: N. J'ai déjà compris la sauvegarde de cette relation, mais alimenter la valeur initiale ne semble tout simplement pas fonctionner.
Comment j'essaie d'alimenter les valeurs initiales:
[api-857c7fc84d-rh42v api-app] empty_label -> None
[api-857c7fc84d-rh42v api-app] required -> False
[api-857c7fc84d-rh42v api-app] label -> None
[api-857c7fc84d-rh42v api-app] initial -> <QuerySet [<Device: 10126>, <Device: 10127>]>
[api-857c7fc84d-rh42v api-app] show_hidden_initial -> False
[api-857c7fc84d-rh42v api-app] help_text ->
[api-857c7fc84d-rh42v api-app] disabled -> False
[api-857c7fc84d-rh42v api-app] label_suffix -> None
[api-857c7fc84d-rh42v api-app] localize -> False
[api-857c7fc84d-rh42v api-app] widget -> <django.forms.widgets.SelectMultiple object at 0x7f7d749597f0>
[api-857c7fc84d-rh42v api-app] error_messages -> {'required': 'This field is required.', 'invalid_choice': 'Select a valid choice. %(value)s is not one of the available choices.', 'list': 'Enter a list of values.', 'invalid_pk_value': '"%(pk)s" is not a valid value.'}
[api-857c7fc84d-rh42v api-app] validators -> []
[api-857c7fc84d-rh42v api-app] _queryset -> <QuerySet [<Device: 10036>, <Device: 10135>, <Device: 10062>, <Device: 10069>, <Device: 10101>, <Device: 10139>, <Device: 10022>, <Device: 10149>, <Device: 10103>, <Device: 10146>, <Device: 10020>, <Device: 10040>, <Device: 10075>, <Device: 10123>, <Device: 10059>, <Device: 10001>, <Device: 10142>, <Device: 10148>, <Device: 10097>, <Device: 10118>, '...(remaining elements truncated)...']>
Dans le débogueur, je peux clairement que le jeu de requêtes initial n'est pas vide:
initial mais le champ dans django admin est toujours vide.
Quelqu'un sait pourquoi?
EDIT:
J'ai déjà essayé
self.fields ['devices']. Initial = self.instance.devices.all (). Values_list ('id' , flat = True)
sans chance.
EDIT2:
Journal de débogage de self.fields ['devices'] .__ dict__
class MachineForm(forms.ModelForm):
class Meta:
model = Machine
fields = '__all__'
devices = forms.ModelMultipleChoiceField(queryset=Device.objects.filter(machine=None).all(), required=False)
def __init__(self, *args, **kwargs):
super(MachineForm, self).__init__(*args, **kwargs)
if self.instance:
self.fields['devices'].initial = self.instance.devices.all()
def save(self, *args, **kwargs):
instance = super(MachineForm, self).save(commit=False)
self.fields['devices'].initial.update(machine=None)
instance.save()
self.cleaned_data['devices'].update(machine=instance)
return instance
Widget comme dict:
class Machine(models.Model):
pass
class Device(models.Model):
machine = models.ForeignKey(Machine, related_name='devices')
3 Réponses :
Vous devez passer les choix en tant que tuple, afin de convertir la requête résultante en tuple, vous devrez faire quelque chose comme ci-dessous et cela fonctionnera:
CHOICES = tuple((x.id, x.devices) for x in Device.objects.all())
class MachineForm(forms.ModelForm):
devices = forms.MultipleChoiceField(choices=CHOICES)
class Meta:
model = Machine
fields = '__all__'
Je n'ai pas de problèmes avec le jeu de requêtes, cela fonctionne bien, j'ai des problèmes avec les valeurs initiales. Et même si je le passe sous forme de tuples, cela ne fonctionne pas, donc cela ne répond malheureusement pas à ma question, en plus, x.devices n'a aucun sens car x est un objet Device
J'utilise également ModelMultipleChoiceField , pas MultipleChoiceField .
Désolé, je l'ai mal compris. Dans le cas de ModelMultipleChoiceField, vous pouvez simplement utiliser devices = forms.ModelMultipleChoiceField (Device.objects.all ()) vous n'avez pas à taper 'queryset ='
Queryset est la liste des choix possibles, c'est bien, je ne peux pas remplir l'initiale, cela signifie que les objets déjà sélectionnés ne sont pas visibles.
form = MachineForm (initial = {'abc': Device.objects.filter (pk = 2)}) marque les options comme sélectionnées, vous pouvez le découvrir en inspectant l'élément. Mais il n'est pas affiché dans le navigateur comme sélectionné, une solution pour cela peut être d'utiliser un style css comme: sélectionnez [multiple] option [sélectionné] {arrière-plan: bleu; Couleur blanche; }
Essayez plutôt de définir la propriété queryset :
if self.instance:
self.fields['devices'].queryset = self.instance.devices.all()
Bonjour, Si je fais cela, non seulement l'initiale est toujours vide, mais l'ensemble de requêtes d'appareils que je peux choisir n'est limité qu'à deux (l'ensemble de requêtes initial). Donc, initial! = Queryset, comme indiqué précédemment.
J'ai réussi à résoudre ce problème. Après avoir cherché pendant des heures dans la documentation de Django , j'ai trouvé ceci dans le code source de ModelMultipleChoiceField .
https://docs.djangoproject.com/en/2.1/_modules/django/forms/models/#ModelMultipleChoiceField
devices = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), required=False)
Le problème était que mon initial et mon jeu de requêtes étaient en disjonction, donc il échouait silencieusement quelque part.
Donc, changer
devices = forms.ModelMultipleChoiceField(queryset=Device.objects.filter(machine=None).all(), required=False)
to
def _check_values(self, value):
"""
Given a list of possible PK values, return a QuerySet of the
corresponding objects. Raise a ValidationError if a given value is
invalid (not a valid PK, not in the queryset, etc.) <- NOT IN THE QUERYSET?!
"""
key = self.to_field_name or 'pk'
# deduplicate given values to avoid creating many querysets or
# requiring the database backend deduplicate efficiently.
try:
value = frozenset(value)
except TypeError:
# list of lists isn't hashable, for example
raise ValidationError(
self.error_messages['list'],
code='list',
)
for pk in value:
try:
self.queryset.filter(**{key: pk}) # <------
except (ValueError, TypeError):
raise ValidationError(
self.error_messages['invalid_pk_value'],
code='invalid_pk_value',
params={'pk': pk},
)
qs = self.queryset.filter(**{'%s__in' % key: value})
pks = {str(getattr(o, key)) for o in qs}
for val in value:
if str(val) not in pks:
raise ValidationError(
self.error_messages['invalid_choice'],
code='invalid_choice',
params={'value': val},
)
return qs
a corrigé le problème.
Je me demande s'il s'agit d'un bogue dans Django ? Étant donné que les valeurs initiales vont toujours manquer dans l'ensemble de requêtes, du moins dans mon cas, car mon ensemble de requêtes est une collection d'appareils non attribués.
Cela fonctionne-t-il si vous utilisez les identifiants?
self.fields ['devices']. initial = self.instance.devices.values_list ('id', flat = True)@DanielRoseman Malheureusement non, j'ai oublié de mentionner que j'ai déjà essayé cela, je ne comprends tout simplement pas pourquoi il ignore l'ensemble initial.