Как заменить поле выбора в Django ModelForm на TextInput - PullRequest
0 голосов
/ 28 апреля 2019

Я работаю с Python 3.7.3 и Django 2.0.13.

По сути, я хочу показать на своем веб-сайте форму, в которую можно ввести пользователя (= участника в определении модели ниже). Django ModelForm автоматически делает это в поле выбора и показывает раскрывающийся список со всеми пользователями. Я не хочу показывать список всех пользователей в выпадающем меню и хочу вместо этого поле TextInput.

Код:

Во-первых, соответствующая часть от models.py:

class Invite(models.Model):
    game        = models.ForeignKey(Game, on_delete=models.CASCADE)

    host        = models.ForeignKey(User, on_delete=models.CASCADE, related_name = "invites_as_host")
    participant = models.ForeignKey(User, on_delete=models.CASCADE, related_name = "invites_as_participant")

    accepted = models.BooleanField(blank = True, default = False)
    declined = models.BooleanField(blank = True, default = False)

    date_created = models.DateTimeField(auto_now_add=True)
    date_edited  = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = ["game", "host", "participant"]

forms.py:

class GameInviteNewForm(forms.ModelForm):
    class Meta:
        model = Invite
        fields = ["participant"]

Я пытался переписать поле ввода участника следующим образом:

class GameInviteNewForm(forms.ModelForm):

    participant = forms.CharField(
                label=_("User to invite"), 
                max_length=100,
                widget = forms.TextInput
            )

    class Meta:
        model = Invite
        fields = ["participant"]

views.py (если уместно; я не думаю, что он даже доходит до "form_valid", не так ли?)

class GameInviteNewView(LoginRequiredMixin, UserIsLeaderMixin, FormView):
    form_class = GameInviteNewForm 

    template_name = "app/game/new_invite.html"

    pk_url_kwarg  = "game_id"


    def get_success_url(self):
        return reverse_lazy("app:game_invite", kwargs={
            "game_id": self.kwargs['game_id']
        })


    def form_valid(self, form):
        participant = form.save(commit=False)

        participant = User.objects.get(username=participant.name)
        host = User.objects.get(username=self.request.user.username)
        game = Game.objects.get(id=self.kwargs['game_id'])

        invite.participant_id = participant.id
        invite.host_id = host.id
        invite.game_id = game.id

        invite.save()

        return redirect(self.get_success_url())

Это действительно показывает поле TextInput на веб-сайте, но если я ввожу имя пользователя («test»), я получаю ошибку:

Internal Server Error: /app/game/invite/7
Traceback (most recent call last):
  File "/home/dremet/anaconda3/envs/django/lib/python3.7/site-packages/django/core/handlers/exception.py", line 35, in inner
    response = get_response(request)
  File "/home/dremet/anaconda3/envs/django/lib/python3.7/site-packages/django/core/handlers/base.py", line 128, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/home/dremet/anaconda3/envs/django/lib/python3.7/site-packages/django/core/handlers/base.py", line 126, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/dremet/anaconda3/envs/django/lib/python3.7/contextlib.py", line 74, in inner
    return func(*args, **kwds)
  File "/home/dremet/anaconda3/envs/django/lib/python3.7/site-packages/django/views/generic/base.py", line 69, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/dremet/anaconda3/envs/django/lib/python3.7/site-packages/django/contrib/auth/mixins.py", line 52, in dispatch
    return super().dispatch(request, *args, **kwargs)
  File "/home/dremet/anaconda3/envs/django/lib/python3.7/site-packages/django/contrib/auth/mixins.py", line 109, in dispatch
    return super().dispatch(request, *args, **kwargs)
  File "/home/dremet/anaconda3/envs/django/lib/python3.7/site-packages/django/views/generic/base.py", line 89, in dispatch
    return handler(request, *args, **kwargs)
  File "/home/dremet/anaconda3/envs/django/lib/python3.7/site-packages/django/views/generic/edit.py", line 141, in post
    if form.is_valid():
  File "/home/dremet/anaconda3/envs/django/lib/python3.7/site-packages/django/forms/forms.py", line 179, in is_valid
    return self.is_bound and not self.errors
  File "/home/dremet/anaconda3/envs/django/lib/python3.7/site-packages/django/forms/forms.py", line 174, in errors
    self.full_clean()
  File "/home/dremet/anaconda3/envs/django/lib/python3.7/site-packages/django/forms/forms.py", line 378, in full_clean
    self._post_clean()
  File "/home/dremet/anaconda3/envs/django/lib/python3.7/site-packages/django/forms/models.py", line 396, in _post_clean
    self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
  File "/home/dremet/anaconda3/envs/django/lib/python3.7/site-packages/django/forms/models.py", line 60, in construct_instance
    f.save_form_data(instance, cleaned_data[f.name])
  File "/home/dremet/anaconda3/envs/django/lib/python3.7/site-packages/django/db/models/fields/__init__.py", line 838, in save_form_data
    setattr(instance, self.name, data)
  File "/home/dremet/anaconda3/envs/django/lib/python3.7/site-packages/django/db/models/fields/related_descriptors.py", line 197, in __set__
    self.field.remote_field.model._meta.object_name,
ValueError: Cannot assign "'test'": "Invite.participant" must be a "User" instance.

В раскрывающемся списке указаны идентификаторы пользователя в атрибуте «value» каждой опции. Теперь строка введена. Поэтому я не удивлен, что это не работает, но я удивлен, что в сообщении об ошибке говорится, что это должен быть «Пользователь» (не идентификатор пользователя). Я пытался переписать метод "clean ()" и использовать обычную форму, но оба безуспешно. Как это правильно обрабатывать?


Решение:

Как указывалось в ответах, мне нужен был метод "clean_participant", но я обернул вокруг него структуру try-Кроме (а также придерживался изменения forms.py, перезаписывая поле участника):

    def clean_participant(self):

        participant_string = self.cleaned_data['participant']

        try:
            participant = User.objects.get(username=participant_string)
        except User.DoesNotExist:
            raise forms.ValidationError("User does not exist.")

        return participant

Ответы [ 2 ]

1 голос
/ 28 апреля 2019

Вы можете добавить функцию clean_participant, которая будет получать этот ввод, а затем запрашивать базу данных, чтобы найти ассоциированного пользователя. Если все работает, вы можете вернуть экземпляр участника. В противном случае вам нужно вернуть ошибку «этот участник не существует»

Вот пример:

def clean_participant(self):
    participant = self.cleaned_data.get('participant')
    q = User.objects.get(username= participant)
    if q:
        return q
    raise forms.ValidationError("this participant doesn't exist")

Но вам нужно использовать поле выбора модели по умолчанию и изменить виджет этого поля на текстовый.

1 голос
/ 28 апреля 2019

Вы можете переопределить метод очистки для поля participant и вернуть из него экземпляр пользователя. Модель формы автоматически сделает это, если вы используете поле выбора, потому что форма будет иметь идентификатор для поиска экземпляра. Поскольку вы переопределяете поле, вы должны как-то самостоятельно найти связанный экземпляр в процессе очистки. Вы можете сделать это, определив метод очистки для поля участника следующим образом.

class GameInviteNewForm(forms.ModelForm):
    class Meta:
        model = Invite
        fields = ["participant"]

    def clean_participant(self):
        # you have to return a user instance from here some how use filtering logic you want
        participant = self.cleaned_data['participant']
        # just an example handle exceptions and other stuff
        return User.objects.get(username=participant)
...