如何在Django视图中定义表单的Select字段的选项时使用它们?

huangapple go评论101阅读模式
英文:

How to use choices of a Select field of a form, when defined in the view in Django?

问题

我有一个与选择输入相关的表单问题。此输入用于从列表中选择用户。该列表应仅包含当前使用的组中的用户。

我找到了一个解决方案,但我对表单定义中的def __init__部分的工作原理并不完全了解(我不完全理解这部分的工作方式)。当然,我的代码没有起作用:我得到了正确的表单,并且我需要的选项,但是如果提交数据,它不会保存在数据库中。

我已经能够检查表单是否有效(它无效),错误如下:

类别 - 请选择一个有效的选项。该选择不是可用选项之一。

(对于用户字段,我有相同的错误)。我在这方面无法找到方法,所以如果你能帮忙的话,将不胜感激!

我的模型:

  1. class Group(models.Model):
  2. name = models.CharField(max_length=100)
  3. def __str__(self):
  4. return self.name
  5. class User(AbstractUser):
  6. groups = models.ManyToManyField(Group)
  7. current_group = models.ForeignKey(Group, on_delete=models.SET_NULL,blank=True,null=True,related_name="current_group")
  8. class Category(models.Model):
  9. name = models.CharField(max_length=100)
  10. groups = models.ManyToManyField(Group)
  11. def __str__(self):
  12. return self.name
  13. class Expenses(models.Model):
  14. date = models.DateTimeField()
  15. amount = models.DecimalField(decimal_places=2, max_digits=12)
  16. category = models.ForeignKey(Category, on_delete=models.CASCADE)
  17. user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
  18. group = models.ForeignKey(Group, on_delete=models.CASCADE)
  19. comment = models.CharField(max_length=500)
  20. def __str__(self):
  21. return self.amount

我的表单:

  1. class CreateExpenseForm(forms.ModelForm):
  2. class Meta:
  3. model = Expenses
  4. fields = ['date', 'amount', 'category', 'user','comment']
  5. widgets={'date':DateInput(attrs={'placeholder':'Date', 'class':'form-input', 'type':'date'}),
  6. 'amount':TextInput(attrs={'placeholder':'Amount', 'class':'form-input', 'type':'text'}),
  7. 'category':Select(attrs={'placeholder':'Category', 'class':'form-select', 'type':'text'}),
  8. 'user':Select(attrs={'placeholder':'User', 'class':'form-select', 'type':'text'}),
  9. 'comment':TextInput(attrs={'placeholder':'Comment', 'class':'form-input', 'type':'text'}),}
  10. def __init__(self, *args, **kwargs):
  11. user_choices = kwargs.pop('user_choices', None)
  12. category_choices = kwargs.pop('category_choices', None)
  13. super().__init__(*args, **kwargs)
  14. if user_choices:
  15. self.fields['user'].choices = user_choices
  16. if category_choices:
  17. self.fields['category'].choices = category_choices

我的视图:

  1. def SummaryView(request):
  2. createExpenseForm = CreateExpenseForm(user_choices=[(user.username, user.username) for user in request.user.current_group.user_set.all()],
  3. category_choices=[(category.name, category.name) for category in request.user.current_group.category_set.all()])
  4. if request.method == "POST":
  5. if 'createExpense' in request.POST:
  6. createExpenseForm = CreateExpenseForm(user_choices=[(user.username, user.username) for user in request.user.current_group.user_set.all()],
  7. category_choices=[(category.name, category.name) for category in request.user.current_group.category_set.all()],
  8. data=request.POST)
  9. if createExpenseForm.is_valid():
  10. expense = createExpenseForm.save(commit=False)
  11. expense.group = request.user.current_group
  12. expense.save()
  13. else:
  14. messages.success(request, "Error!")
  15. context = {'createExpenseForm':createExpenseForm}
  16. 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:

  1. class Group(models.Model):
  2. name = models.CharField(max_length=100)
  3. def __str__(self):
  4. return self.name
  5. class User(AbstractUser):
  6. groups = models.ManyToManyField(Group)
  7. current_group = models.ForeignKey(Group, on_delete=models.SET_NULL,blank = True , null = True, related_name="current_group")
  8. class Category(models.Model):
  9. name = models.CharField(max_length=100)
  10. groups = models.ManyToManyField(Group)
  11. def __str__(self):
  12. return self.name
  13. class Expanses(models.Model):
  14. date = models.DateTimeField()
  15. amount = models.DecimalField(decimal_places=2, max_digits=12)
  16. category = models.ForeignKey(Category, on_delete=models.CASCADE)
  17. user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
  18. group = models.ForeignKey(Group, on_delete=models.CASCADE)
  19. comment = models.CharField(max_length=500)
  20. def __str__(self):
  21. return self.amount

My form:

  1. class CreateExpanseForm(forms.ModelForm):
  2. class Meta:
  3. model = Expanses
  4. fields = ['date', 'amount', 'category', 'user','comment']
  5. widgets={'date':DateInput(attrs={'placeholder':'Date', 'class':'form-input', 'type':'date'}),
  6. 'amount':TextInput(attrs={'placeholder':'Amount', 'class':'form-input', 'type':'text'}),
  7. 'category':Select(attrs={'placeholder':'Category', 'class':'form-select', 'type':'text'}),
  8. 'user':Select(attrs={'placeholder':'user', 'class':'form-select', 'type':'text'}),
  9. 'comment':TextInput(attrs={'placeholder':'comment', 'class':'form-input', 'type':'text'}),}
  10. def __init__(self, *args, **kwargs):
  11. user_choices = kwargs.pop('user_choices', None)
  12. category_choices = kwargs.pop('category_choices', None)
  13. super().__init__(*args, **kwargs)
  14. if user_choices:
  15. self.fields['user'].choices = user_choices
  16. if category_choices:
  17. self.fields['category'].choices = category_choices

My view:

  1. def SummaryView(request):
  2. createExpanseForm = CreateExpanseForm(user_choices = [(user.username, user.username) for user in request.user.current_group.user_set.all()],
  3. category_choices = [(category.name, category.name) for category in request.user.current_group.category_set.all()])
  4. if request.method == "POST":
  5. if 'createExpanse' in request.POST:
  6. createExpanseForm = CreateExpanseForm(user_choices = [(user.username, user.username) for user in request.user.current_group.user_set.all()],
  7. category_choices = [(category.name, category.name) for category in request.user.current_group.category_set.all()],
  8. data=request.POST)
  9. if createExpanseForm.is_valid():
  10. expanse = createExpanseForm.save()
  11. if expanse is not None:
  12. expanse.group = request.user.current_group
  13. expanse.save()
  14. else:
  15. messages.success(request, "Error!")
  16. context = {'createExpanseForm':createExpanseForm}
  17. 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:

  1. class CreateExpanseForm(forms.ModelForm):
  2. def __init__(self, *args, user=None, **kwargs):
  3. super().__init__(*args, **kwargs)
  4. if user is not None:
  5. self.fields['user'].queryset = user.current_group.user_set.all()
  6. self.fields['category'].queryset = user.current_group.category_set.all()
  7. class Meta:
  8. model = Expanses
  9. fields = ['date', 'amount', 'category', 'user', 'comment']
  10. widgets = {
  11. 'date': DateInput(
  12. attrs={
  13. 'placeholder': 'Date',
  14. 'class': 'form-input',
  15. 'type': 'date',
  16. }
  17. ),
  18. 'amount': TextInput(
  19. attrs={
  20. 'placeholder': 'Amount',
  21. 'class': 'form-input',
  22. 'type': 'text',
  23. }
  24. ),
  25. 'category': Select(
  26. attrs={
  27. 'placeholder': 'Category',
  28. 'class': 'form-select',
  29. 'type': 'text',
  30. }
  31. ),
  32. 'user': Select(
  33. attrs={
  34. 'placeholder': 'user',
  35. 'class': 'form-select',
  36. 'type': 'text',
  37. }
  38. ),
  39. 'comment': TextInput(
  40. attrs={
  41. 'placeholder': 'comment',
  42. 'class': 'form-input',
  43. 'type': 'text',
  44. }
  45. ),
  46. }

In the view you can then just pass the logged in user:

  1. from django.contrib.auth.decorators import login_required
  2. from django.shortcuts import redirect
  3. @login_required
  4. def summary_view(request):
  5. form = CreateExpanseForm(user=request.user)
  6. if request.method == 'POST':
  7. if 'createExpanse' in request.POST:
  8. form = CreateExpanseForm(
  9. request.POST, request.FILES, user=request.user
  10. )
  11. if form.is_valid():
  12. form.instance.group = request.user.current_group
  13. expanse = form.save()
  14. return redirect('name-of-some-view')
  15. context = {'createExpanseForm': form}
  16. 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, not SummaryView.

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&nbsp;<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()

  1. class Meta:
  2. model = Expanses
  3. fields = [&#39;date&#39;, &#39;amount&#39;, &#39;category&#39;, &#39;user&#39;, &#39;comment&#39;]
  4. widgets = {
  5. &#39;date&#39;: DateInput(
  6. attrs={
  7. &#39;placeholder&#39;: &#39;Date&#39;,
  8. &#39;class&#39;: &#39;form-input&#39;,
  9. &#39;type&#39;: &#39;date&#39;,
  10. }
  11. ),
  12. &#39;amount&#39;: TextInput(
  13. attrs={
  14. &#39;placeholder&#39;: &#39;Amount&#39;,
  15. &#39;class&#39;: &#39;form-input&#39;,
  16. &#39;type&#39;: &#39;text&#39;,
  17. }
  18. ),
  19. &#39;category&#39;: Select(
  20. attrs={
  21. &#39;placeholder&#39;: &#39;Category&#39;,
  22. &#39;class&#39;: &#39;form-select&#39;,
  23. &#39;type&#39;: &#39;text&#39;,
  24. }
  25. ),
  26. &#39;user&#39;: Select(
  27. attrs={
  28. &#39;placeholder&#39;: &#39;user&#39;,
  29. &#39;class&#39;: &#39;form-select&#39;,
  30. &#39;type&#39;: &#39;text&#39;,
  31. }
  32. ),
  33. &#39;comment&#39;: TextInput(
  34. attrs={
  35. &#39;placeholder&#39;: &#39;comment&#39;,
  36. &#39;class&#39;: &#39;form-input&#39;,
  37. &#39;type&#39;: &#39;text&#39;,
  38. }
  39. ),
  40. }&lt;/code&gt;&lt;/pre&gt;

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>')

  1. context = {&#39;createExpanseForm&#39;: form}
  2. return render(request, &#39;app/summary.html&#39;, context)&lt;/code&gt;&lt;/pre&gt;

> 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&nbsp;<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&nbsp;<sup>[wiki]</sup>.
> This avoids that you make the same POST request when the user refreshes the
> browser.

huangapple
  • 本文由 发表于 2023年5月22日 01:41:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/76301163.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定