Reputation: 1038
I am working with Python 3.7.3 and Django 2.0.13.
Basically I want to show a form on my website, on which a user (=participant in model definition below) can be entered. The Django ModelForm makes this to a Choice Field automatically and shows a Dropdown with all users. I don't want to show a list of all users in the dropdown menu and want a TextInput field instead.
Code:
First, relevant part from 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"]
What I tried is to overwrite the participant input field like this:
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 (if relevant; I do not think it even gets to "form_valid", does it?)
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())
This does indeed show an TextInput Field on the website, but if I enter a username ("test") I get an error:
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.
The dropdown had the user id's in the "value" attribute of each option. Now, a string is entered. So I am not surprised that it does not work, but I am surprised that the error message says that it must be a "User" (not a user id). I tried to overwrite the "clean()" method and to use a regular form, but both without success. How should this be handled properly?
Solution:
As pointed out in the answers, I did need a "clean_participant" method, but I wrapped a try-except structure around it (and also I sticked with the forms.py change overwriting the participant field):
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
Upvotes: 0
Views: 1315
Reputation: 1234
You can add a function clean_participant that will get this input and then query the database to find the associated user. It it works then you can return the participant instance. Otherwise you need to return an error 'this participant doesn't exist'
Here is an exemple:
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")
But you need to use the default modelchoicefield and change the widget of that field to be a textinput.
Upvotes: 1
Reputation: 6598
You can override clean method for participant
field and return a user instance from it. Model form will automatically do that if you are using a choice field because form will have id to find instance. Because you are overriding field you have to somehow find related instance yourself in cleaning process. You can do it by defining clean method for participant field like this.
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)
Upvotes: 1