问题
I have a form whose queryset depends on request.user, and whose initial value depends on a session key. The primary models are User (slight modification of default User model) and Account, with a many-to-many relationship between them. The form allows a User to change the Account that he/she is viewing, and that choice must persist as the User navigates the site. The form works fine when created in a single view and passed to a single template, but I want the form to appear in the top navigation bar so that the User can change Accounts from anywhere.
Here is the form:
class ChangeAccountContextForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
self.current_account_id = kwargs.pop('account_id')
super(ChangeAccountContextForm, self).__init__(*args, **kwargs)
self.fields['account_choices'].queryset = self.user.accounts.all()
try:
self.fields['account_choices'].initial = Account.objects.get(id=self.current_account_id)
except(Account.DoesNotExist):
self.fields['account_choices'].initial = None
#queryset and initial are set to None, because they are assigned dynamically in the constructor (see above)
account_choices = forms.ModelChoiceField(queryset=None, initial=None, label='Account:', widget=forms.Select(attrs={'onChange':'this.form.submit()', 'class': 'custom-select mr-sm-2 ml-2'}), required=True )
class Meta:
model = User
fields = ['account_choices']
And here is the existing view where the form is used:
@login_required
def welcome_view(request):
user = request.user
context = {}
accounts = user.accounts.all().order_by('account_name')
context['accounts'] = accounts
context['num_accounts'] = len(accounts)
try:
account_id = request.session['current_account_id']
except (KeyError):
account_id = None
if request.method == 'POST':
form = ChangeAccountContextForm(request.POST, user=user, account_id=account_id)
context['form'] = form
if form.is_valid():
new_account_context = form.cleaned_data['account_choices']
request.session['current_account_name'] = new_account_context.account_name
request.session['current_account_id'] = new_account_context.id
else:
form = ChangeAccountContextForm(user=user, account_id=account_id)
context['form'] = form
return render(request, 'welcome.html', context)
(The session keys, incidentally, are set when the User logs in.)
Given the dependence on request.user and variables stored in the session, I'm not sure how to include the form on every page without reconstructing the form in every view as show above. That would work, I suppose, but I'm sure there must be a more DRY approach.
回答1:
This is a partial answer to my own question. I'm adding it here because I think the documentation for adding a form to a custom template tag is lacking.
1.) Create folder called templatetags (with __init__.py, of course) and add account_context_form.py (or whatever name makes sense for you). Mine looks like:
from django import template
from userauth.forms import ChangeAccountContextForm
register = template.Library()
@register.inclusion_tag('account_context_form.html')
def account_context_form(**kwargs):
user = kwargs['user']
account_id = kwargs['account_id']
form = ChangeAccountContextForm(user=user, account_id=account_id)
return {'account_context_form' : form }
- create a html file that you're passing to your tag and save to your templates folder. In my case, 'account_context_form.html'
<form class="form-inline" action="{{ request.path }}" method="POST">{% csrf_token %}
{{ account_context_form }}
</form>
- in base_generic.html (or where ever make sense for you), call the tag like so:
{% load account_context_form %}
{% account_context_form user=request.user account_id=request.session.current_account_id %}
As for how to actually handle the POST request, see my question (and my answer) here: How to handle a POST request from a Django form loaded via a custom template tag?
来源:https://stackoverflow.com/questions/60660373/how-to-place-a-django-form-in-a-nav-bar-so-that-it-appears-on-every-page