英文:
How to use choices of a Select field of a form, when defined in the view in Django?
问题
我有一个与选择输入相关的表单问题。此输入用于从列表中选择用户。该列表应仅包含当前使用的组中的用户。
我找到了一个解决方案,但我对表单定义中的def __init__
部分的工作原理并不完全了解(我不完全理解这部分的工作方式)。当然,我的代码没有起作用:我得到了正确的表单,并且我需要的选项,但是如果提交数据,它不会保存在数据库中。
我已经能够检查表单是否有效(它无效),错误如下:
类别 - 请选择一个有效的选项。该选择不是可用选项之一。
(对于用户字段,我有相同的错误)。我在这方面无法找到方法,所以如果你能帮忙的话,将不胜感激!
我的模型:
class Group(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class User(AbstractUser):
groups = models.ManyToManyField(Group)
current_group = models.ForeignKey(Group, on_delete=models.SET_NULL,blank=True,null=True,related_name="current_group")
class Category(models.Model):
name = models.CharField(max_length=100)
groups = models.ManyToManyField(Group)
def __str__(self):
return self.name
class Expenses(models.Model):
date = models.DateTimeField()
amount = models.DecimalField(decimal_places=2, max_digits=12)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
comment = models.CharField(max_length=500)
def __str__(self):
return self.amount
我的表单:
class CreateExpenseForm(forms.ModelForm):
class Meta:
model = Expenses
fields = ['date', 'amount', 'category', 'user','comment']
widgets={'date':DateInput(attrs={'placeholder':'Date', 'class':'form-input', 'type':'date'}),
'amount':TextInput(attrs={'placeholder':'Amount', 'class':'form-input', 'type':'text'}),
'category':Select(attrs={'placeholder':'Category', 'class':'form-select', 'type':'text'}),
'user':Select(attrs={'placeholder':'User', 'class':'form-select', 'type':'text'}),
'comment':TextInput(attrs={'placeholder':'Comment', 'class':'form-input', 'type':'text'}),}
def __init__(self, *args, **kwargs):
user_choices = kwargs.pop('user_choices', None)
category_choices = kwargs.pop('category_choices', None)
super().__init__(*args, **kwargs)
if user_choices:
self.fields['user'].choices = user_choices
if category_choices:
self.fields['category'].choices = category_choices
我的视图:
def SummaryView(request):
createExpenseForm = CreateExpenseForm(user_choices=[(user.username, user.username) for user in request.user.current_group.user_set.all()],
category_choices=[(category.name, category.name) for category in request.user.current_group.category_set.all()])
if request.method == "POST":
if 'createExpense' in request.POST:
createExpenseForm = CreateExpenseForm(user_choices=[(user.username, user.username) for user in request.user.current_group.user_set.all()],
category_choices=[(category.name, category.name) for category in request.user.current_group.category_set.all()],
data=request.POST)
if createExpenseForm.is_valid():
expense = createExpenseForm.save(commit=False)
expense.group = request.user.current_group
expense.save()
else:
messages.success(request, "Error!")
context = {'createExpenseForm':createExpenseForm}
return render(request, 'app/summary.html', context)
希望这对你有所帮助!
英文:
I have an issue with a form with a Select input. This input is used to select a user in a list. The list should only contain user from the group currently used.
I found a solution, however I am not completely sure of what am I doing in the form definition (I do not fully understand how works the def __init__
part). And of course, my code is not working : I obtain the right form, with the choices I need, however if a submit data, it's not saved in the database.
I have been able to check if the form is valid (it is not), the error is the following :
> Category - Select a valid choice. That choice is not one of the available choices.
(I have the same error for the user field). I can't find my way in this, so if you can help, would be very appreciated!
My models:
class Group(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class User(AbstractUser):
groups = models.ManyToManyField(Group)
current_group = models.ForeignKey(Group, on_delete=models.SET_NULL,blank = True , null = True, related_name="current_group")
class Category(models.Model):
name = models.CharField(max_length=100)
groups = models.ManyToManyField(Group)
def __str__(self):
return self.name
class Expanses(models.Model):
date = models.DateTimeField()
amount = models.DecimalField(decimal_places=2, max_digits=12)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
comment = models.CharField(max_length=500)
def __str__(self):
return self.amount
My form:
class CreateExpanseForm(forms.ModelForm):
class Meta:
model = Expanses
fields = ['date', 'amount', 'category', 'user','comment']
widgets={'date':DateInput(attrs={'placeholder':'Date', 'class':'form-input', 'type':'date'}),
'amount':TextInput(attrs={'placeholder':'Amount', 'class':'form-input', 'type':'text'}),
'category':Select(attrs={'placeholder':'Category', 'class':'form-select', 'type':'text'}),
'user':Select(attrs={'placeholder':'user', 'class':'form-select', 'type':'text'}),
'comment':TextInput(attrs={'placeholder':'comment', 'class':'form-input', 'type':'text'}),}
def __init__(self, *args, **kwargs):
user_choices = kwargs.pop('user_choices', None)
category_choices = kwargs.pop('category_choices', None)
super().__init__(*args, **kwargs)
if user_choices:
self.fields['user'].choices = user_choices
if category_choices:
self.fields['category'].choices = category_choices
My view:
def SummaryView(request):
createExpanseForm = CreateExpanseForm(user_choices = [(user.username, user.username) for user in request.user.current_group.user_set.all()],
category_choices = [(category.name, category.name) for category in request.user.current_group.category_set.all()])
if request.method == "POST":
if 'createExpanse' in request.POST:
createExpanseForm = CreateExpanseForm(user_choices = [(user.username, user.username) for user in request.user.current_group.user_set.all()],
category_choices = [(category.name, category.name) for category in request.user.current_group.category_set.all()],
data=request.POST)
if createExpanseForm.is_valid():
expanse = createExpanseForm.save()
if expanse is not None:
expanse.group = request.user.current_group
expanse.save()
else:
messages.success(request, "Error!")
context = {'createExpanseForm':createExpanseForm}
return render(request, 'app/summary.html', context)
答案1
得分: 1
The form field of a ForeignKey
is a ModelChoiceField
, this needs the primary key of the item to work with, not the name of a category. You thus try to do too much yourself, and thus invalidated the data of the form field.
I suggest that you let the form handle it and specify the queryset when necessary:
class CreateExpanseForm(forms.ModelForm):
def __init__(self, *args, user=None, **kwargs):
super().__init__(*args, **kwargs)
if user is not None:
self.fields['user'].queryset = user.current_group.user_set.all()
self.fields['category'].queryset = user.current_group.category_set.all()
class Meta:
model = Expanses
fields = ['date', 'amount', 'category', 'user', 'comment']
widgets = {
'date': DateInput(
attrs={
'placeholder': 'Date',
'class': 'form-input',
'type': 'date',
}
),
'amount': TextInput(
attrs={
'placeholder': 'Amount',
'class': 'form-input',
'type': 'text',
}
),
'category': Select(
attrs={
'placeholder': 'Category',
'class': 'form-select',
'type': 'text',
}
),
'user': Select(
attrs={
'placeholder': 'user',
'class': 'form-select',
'type': 'text',
}
),
'comment': TextInput(
attrs={
'placeholder': 'comment',
'class': 'form-input',
'type': 'text',
}
),
}
In the view you can then just pass the logged in user:
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
@login_required
def summary_view(request):
form = CreateExpanseForm(user=request.user)
if request.method == 'POST':
if 'createExpanse' in request.POST:
form = CreateExpanseForm(
request.POST, request.FILES, user=request.user
)
if form.is_valid():
form.instance.group = request.user.current_group
expanse = form.save()
return redirect('name-of-some-view')
context = {'createExpanseForm': form}
return render(request, 'app/summary.html', context)
Note: Functions are normally written in snake_case, not PascalCase, therefore it is advisable to rename your function to
summary_view
, notSummaryView
.
Note: You can limit views to authenticated users with the
@login_required
decorator.
Note: In case of a successful POST request, you should make a
redirect
to implement the Post/Redirect/Get pattern. This avoids that you make the same POST request when the user refreshes the browser.
英文:
The form field of a ForeignKey
is a ModelChoiceField
<sup>[Django-doc]</sup>, this neecds the primary key of the item to work with, not the name of a category. You thus try to do too much yourself, and thus invalidated the data of the form field.
I suggest that you let the form handle it and specify the queryset when necessary:
<pre><code>class CreateExpanseForm(forms.ModelForm):
def init(self, *args, user=None, **kwargs):
super().init(*args, **kwargs)
if user is not None:
self.fields['user']<b>.queryset</b> = user.current_group.user_set.all()
self.fields[
'category'
]<b>.queryset</b> = user.current_group.category_set.all()
class Meta:
model = Expanses
fields = ['date', 'amount', 'category', 'user', 'comment']
widgets = {
'date': DateInput(
attrs={
'placeholder': 'Date',
'class': 'form-input',
'type': 'date',
}
),
'amount': TextInput(
attrs={
'placeholder': 'Amount',
'class': 'form-input',
'type': 'text',
}
),
'category': Select(
attrs={
'placeholder': 'Category',
'class': 'form-select',
'type': 'text',
}
),
'user': Select(
attrs={
'placeholder': 'user',
'class': 'form-select',
'type': 'text',
}
),
'comment': TextInput(
attrs={
'placeholder': 'comment',
'class': 'form-input',
'type': 'text',
}
),
}</code></pre>
In the view you can then just pass the logged in user:
<pre><code>from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
@login_required
def summary_view(request):
form = CreateExpanseForm(<b>user=request.user</b>)
if request.method == 'POST':
if 'createExpanse' in request.POST:
form = CreateExpanseForm(
request.POST, request.FILES, <b>user=request.user</b>
)
if form.is_valid():
form<b>.instance.group = request.user.current_group</b>
expanse = createExpanseForm.save()
return redirect('<em>name-of-some-view</em>')
context = {'createExpanseForm': form}
return render(request, 'app/summary.html', context)</code></pre>
> Note: Functions are normally written in snake_case, not PascalCase, therefore it is
> advisable to rename your function to summary_view
, not <s>SummaryView
</s>.
> Note: You can limit views to a view to authenticated users with the
> @login_required
decorator <sup>[Django-doc]</sup>.
> Note: In case of a successful POST request, you should make a redirect
> <sup>[Django-doc]</sup>
> to implement the Post/Redirect/Get pattern <sup>[wiki]</sup>.
> This avoids that you make the same POST request when the user refreshes the
> browser.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论