I am making a recommendation app where a user can recommend a movie/show/book to one or more users. The form is set up so that it displays a list of all users and you can check a box next to the users you want to send the recommendation to, but I am getting this error (Field 'id' expected a number but got <Profile: user1>
) when I hit "Submit" on my form after checking the box next to the username and I'm not sure where I went wrong. Most of the other questions related to this are using ForeignKey
rather than ManyToManyField
in their model.
models.py:
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
follows = models.ManyToManyField(
'self',
related_name='followed_by',
symmetrical=False,
blank=True,
)
def __str__(self):
return self.user.username
class Recommendation(models.Model):
user = models.ForeignKey(
User, related_name="recs", on_delete=models.DO_NOTHING
)
recipients = models.ManyToManyField(
User, related_name="received_recs", symmetrical=False, blank=True
)
title = models.CharField(max_length=300)
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return (
f"{self.user} "
f"({self.created_at:%y-%m-%d %H:%M}): "
f"{self.title}"
)
views.py:
from django.shortcuts import render, redirect
from .models import Profile, Recommendation
from .forms import RecForm
def dashboard(request):
all_recs = Recommendation.objects.all()
if request.method == "POST":
form = RecForm(request.POST or None)
if form.is_valid():
rec = form.save(commit=False)
rec.user = request.user
rec.save()
form.save_m2m()
return redirect("watchthisshit:dashboard")
form = RecForm()
return render(request, "watchthisshit/dashboard.html", {
"all_recs": all_recs,
"form": form
})
forms.py:
from django import forms
from django.db.models.base import Model
from .models import Profile, Recommendation
class RecForm(forms.ModelForm):
title = forms.CharField(required=True)
description = forms.CharField(widget=forms.widgets.Textarea(
attrs={
"placeholder": "You don't have to write a description, but it would be pretty cool if you did...",
"class": "textarea is-success is-medium"
}
), label="",)
recipients = forms.ModelMultipleChoiceField(
queryset=Profile.objects.all(),
widget=forms.CheckboxSelectMultiple
)
class Meta:
model = Recommendation
fields = ["title", "description", "recipients"]
exclude = ("user", )
I thought the problem might be with how it is getting passed to ModelMultipleChoiceField
, so I tried doing the following in my forms.py:
recipients = forms.ModelMultipleChoiceField(
queryset=Profile.objects.all().values_list('id', 'user'),
widget=forms.CheckboxSelectMultiple
)
However, that just returns a tuple of (1, 1) and I can't access the name. It also doesn't actually save anything.
Simply because in your Recommendation
you set recipients
related to the User
object. But, in your form you are using the Profile
to populate your select, a minor mistake.
So, when you try to .save_m2m
it breaks down, because the field expects an User
not a Profile
, a quick fix:
forms.py
class RecForm(forms.ModelForm):
title = ...
description = ...
recipients = forms.ModelMultipleChoiceField(
queryset=User.objects.all(), widget=forms.CheckboxSelectMultiple
)
class Meta:
...
So that an User
cannot mark himself as recipient. We can override the Form __init__
method and by providing an initial value we can exclude the current user
from the QuerySet.
forms.py
class RecForm(forms.ModelForm):
title = ...
description = ...
recipients = forms.ModelMultipleChoiceField(
queryset=None, widget=forms.CheckboxSelectMultiple
)
class Meta:
...
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
current_user_id = self.initial.get('user', None)
self.fields["recipients"].queryset = User.objects.exclude(pk=current_user_id)
views.py
def dashboard(request):
all_recs = Recommendation.objects.all()
if request.method == "POST":
...
form = RecForm(initial={"user": request.user.id})
return render(
request, "watchthisshit/dashboard.html", {"all_recs": all_recs, "form": form}
)