I don't know how to display formset in django - django

I don't know how to display formset individually.
I understand how to display all at once.
But I tried some model field names, but it didn't work.
#views
class UserEdit(generic.UpdateView):
model = User
form_class = forms.UserUpdateForm
template_name = 'accounts/accounts_edit.html'
success_url = reverse_lazy('accounts:edit')
def get_object(self):
return get_object_or_404(User, pk=self.request.user.user_id)
#forms
class ProfileUpdateForm(forms.ModelForm):
class Meta:
model = profile
fields = ('first_name','last_name','birthday')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs['class'] = 'form-control'
ProfileFormSet = inlineformset_factory(User,profile,form=ProfileUpdateForm,extra=0)
class UserUpdateForm(mixins.ModelFormWithFormSetMixin,forms.ModelForm):
#Userモデルにprofileモデルを入れる
formset_class = ProfileFormSet
class Meta:
model = User
fields = ('username','email',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs['class'] = 'form-control'
#template
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form.username.label_tag }}
{{ form.username }}
{{ form.email.label_tag }}
{{ form.email }}
{{ form.formset }}
<button type="submit">Submit</button>
</form>
I want to specify and display instead of the method of displaying at once with formset.
Postscript
Mixins are created for data verification and data storage.
I want to display the details of formset display in template.
However, I only know how to display it at once, like {{form.formset}}.
Ideally I want to display it individually like {{form.formset.first_name}}
#mixins
class ModelFormWithFormSetMixin:
def __init__(self, *args, **kwargs):
super(ModelFormWithFormSetMixin, self).__init__(*args, **kwargs)
self.formset = self.formset_class(
instance=self.instance,
data=self.data if self.is_bound else None,
)
def is_valid(self):
return super(ModelFormWithFormSetMixin, self).is_valid() and self.formset.is_valid()
def save(self, commit=True):
saved_instance = super(ModelFormWithFormSetMixin, self).save(commit)
self.formset.save(commit)
return saved_instance

A formset is a set of forms so you have to loop through them {% for subform in form.formset%}{{subform.first_name}}{%endfor%}
Dirkgroten told me in the comments.
Thank you.

Related

How to save multiple dynamically created forms in django

I am trying to create a page where various data corresponding to mutliple models can be input by the user, and to have an option to dynamically add additional forms. I have been attempting to use htmx for this and am able to dynamically add forms, however when I save it is only the last entered form that is saved. I haven't used formsets as this wont integrate well with htmx https://justdjango.com/blog/dynamic-forms-in-django-htmx#django-formsets-vs-htmx.
Code below any suggestion as to how to get all the dynamically created forms to be save would be most appreciated!
models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=50)
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
forms.py
from django import forms
from .models import Book, Author
from crispy_forms.helper import FormHelper
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
fields = ['name']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_id = 'id-CaseForm'
self.helper.form_class = 'blueForms'
self.helper.form_method = 'post'
self.helper.form_tag = False
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = ('title',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.form_tag = False
self.helper.form_show_labels = False
views.py
class create_book(TemplateView):
template_name = 'create_book.html'
def get(self, *args, **kwargs):
author_form = AuthorForm
book_form = BookForm
return self.render_to_response(
{'book_form': BookForm,
"author_form": AuthorForm,}
)
def post(self, *args, **kwargs):
author_form = AuthorForm(data=self.request.POST)
book_form = BookForm(data=self.request.POST)
if author_form.is_valid():
author = author_form.save()
if book_form.is_valid():
book = book_form.save(commit=False)
book.author = author
book.save()
def create_book_form(request):
form = BookForm()
context = {
"form": form
}
return render(request, "partials/book_form.html", context)
urls.py
urlpatterns = [
path('create_book/', create_book.as_view(), name='create-book'),
path('htmx/create-book-form/', create_book_form, name='create-book-form'),]
create_book.html
{% extends "base.html" %}
{% block content %}
{% load crispy_forms_tags %}
<form class="blueForms" id="id-CaseForm" method="post" >
{% crispy author_form %}
{% crispy book_form %}
<button type="button" hx-get="{% url 'create-book-form' %}" hx-target="#bookforms" hx-swap="beforeend">
Add book form
</button>
<div id="bookforms"></div>
</form>
partials/book_form.html
{% load crispy_forms_tags %}
<div hx-target="this" hx-swap="outerHTML">
{% csrf_token %}
{% crispy form %}
{{ form.management_form }}
</div>
A quick Google finds me this module:
https://github.com/adamchainz/django-htmx
It seems someone may have already done this for you my friend :)

Is it possible to pass a specific value to a django formset?

So I've been playing with Django formsets for about a week and I'm about ready to throw out Django altogether. Just kidding. Kinda. :). I have the standard inline formset implementation working finally as I documented here. How do I properly implement Django formsets with a CreateView ( Class Based View )? Hope it helps someone. Now I'm trying to leverage this code by trying to pass a specific queryset to it as shown below.
class PlayerFormSet(PlayerFormSet,BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(PlayerFormSet, self).__init__(*args, **kwargs)
self.queryset = Player.objects.filter(team_pk=20)
As shown above, if I hard code a value, no worries it works to populate my formset. Although hardcoding this value is of little value to the implementation. I have researched form_kwargs, played around with numerous implementations as shown throughout SO, about passing a value but nothing I try works other than the code above for one team obviously and I can't for the life of me figure out how to do something like:
class PlayerFormSet(PlayerFormSet,BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(PlayerFormSet, self).__init__(*args, **kwargs)
self.queryset = Player.objects.filter(self.object.team_pk)
Or...
class PlayerFormSet(PlayerFormSet,BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(PlayerFormSet, self).__init__(*args, **kwargs)
self.queryset = Player.objects.filter(self.instance.team_pk)
I know the pk, it can clearly be passed here in a hardcoded fashion...And I have the value accessible in my view. I just can't figure out how to pass it to the formset to get the proper queryset. Is this even possible? This seems to be way more challenging than it should be. Or of course it could be me. Thanks in advance for any ideas.
Update for more thorough analysis.
My Models....
class Team(models.Model):
team_name = models.CharField(null=True)
class Player(models.Model):
player_name = models.CharField(null=True)
team_pk = models.IntegerField(null=True)
team = models.ForeignKey("Team",null=True,on_delete=models.CASCADE)
forms.py
PlayerFormSet = inlineformset_factory(Team, Player, extra=0, fields=['player_name',])
class PlayerFormSet(PlayerFormSet,BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(PlayerFormSet, self).__init__(*args, **kwargs)
self.queryset = Player.objects.filter(team_pk=20)
views.py
class UpdateTeamView(LoginRequiredMixin,CreateView):
model = Team
form_class = TeamForm
template_name = 'update_team.html'
def get_context_data(self, **kwargs):
context = super(UpdateTeamView, self).get_context_data(**kwargs)
dropdown = self.request.GET.get("dropdown", None)
queryset = Player.objects.filter(team_pk=dropdown)
player_form = PlayerFormSet(queryset=queryset)
context['player_form'] = player_form
return context
def form_valid(self, form, player_form):
self.object = form.save()
player_form.instance = self.object
player_form.save()
def form_invalid(self, form, player_form):
return self.render_to_response(
self.get_context_data(form=form,
player_form=player_form,
))
def post(self, request, *args, **kwargs):
if "cancel" in request.POST:
return HttpResponseRedirect(reverse('Team:team_main_menu'))
else:
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
player_form = PlayerFormSet(self.request.POST)
if (form.is_valid() and player_form.is_valid()):
return self.form_valid(form, player_form)
else:
return self.form_invalid(form, player_form)
My Template...
<form method="POST" enctype="multipart/form-data" id="forms">
{% csrf_token %}
{{ player_form.management_form }}
{{ player_form.non_form_errors }}
{% for hidden in player_form.management_form %}
{{ hidden }}
{% endfor %}
{% for form in player_form.forms %}
{{ form.id }}
<div class="inline {{ player_form.prefix }}">
<div class="leftwidth22">
<div class="width52">
<h2 class="floatright23">Player Name - </h2>
</div>
</div>
<div class="rightwidth53">
<h2 class="width70">
{{ form.player_name }}
</h2>
</div>
{% if player_form.non_form_errors %}
<h3 class="spacer12">
{{ player_form.non_form_errors }}
</h3>
{% endif %}
{% if form.player_name.errors %}
<h3 class="spacer12">
{{ form.player_name.errors }}
</h3>
{% endif %}
{% endfor %}
</form>
If this is the only view using the inlineformset then you can ditch the BaseInlineFormset override and just give the queryset to the formset call in the view.
Because this is an inlineformset it can take an instance of the parent (instead of a queryset of the objects themselves).
def get_context_data(self, **kwargs):
context = super(UpdateTeamView, self).get_context_data(**kwargs)
dropdown = self.request.GET.get("dropdown", None)
instance = Team.objects.get(pk=dropdown)
player_form = PlayerFormSet(instance=instance)
context['player_form'] = player_form
return context
Now the get portion should work. The post and form_valid need fixing too. You don't need to check the form, as all forms will be validated by the formset. Try:
def form_valid(self, player_form):
player_form.save()
return HttpResponseRedirect('/')
def form_invalid(self, player_form):
return self.render_to_response(self.get_context_data(player_form=player_form))
def post(self, request, *args, **kwargs):
if "cancel" in request.POST:
return HttpResponseRedirect(reverse('Team:team_main_menu'))
else:
instance = Team.objects.get(team_pk=dropdown)
player_form = PlayerFormSet(self.request.POST,instance=instance)
if player_form.is_valid():
return self.form_valid(player_form)
else:
return self.form_invalid(player_form)

Django web app not redirecting and not posting

I an not sure where is the problem as I press submit button, it went through with no error shown. It was supposed to update the database with the data filled in form then redirect them back to 'search' page (option.html).
models.py:
OptionChoice = (
('A','A'),
('B','B'),
('C','C'),
)
class OptionPlan(models.Model):
option = models.CharField(max_length=200, choices=OptionChoice, default="DEFAULT", blank=True)
...
updated = models.DateField(max_length=20, null=True)
updatedBy = models.CharField(max_length=10, null=True)
urls.py:
app_name = 'Benefits'
urlpatterns = [
path('simple_upload', views.simple_upload, name='simple_upload'),
#path('search', views.search, name='search'),
path('search/', FilterView.as_view(filterset_class=BenefitsFilter, template_name='Benefits/option.html'), name='search'),
path('OptionUpdate/<int:id>', views.OptionUpdate.as_view(), name='OptionUpdate')
]
views.py:
def search(request):
option = OptionPlan.objects.get_queryset()
option_filter = BenefitsFilter(request.GET, queryset=option)
return render(request, 'Benefits/option.html', {'filter':option_filter})
class OptionUpdate(UpdateView):
model = OptionPlan
fields =[
'option',
...
'cb_updatedBy',
'cb_updated',
]
template_name = 'Benefits/OptionUpdate.html'
slug_field = 'id'
slug_url_kwarg = 'id'
def form_valid(self, request, obj, form, change):
OptionPlan = form.save(commit=False)
if OptionPlan.option and 'option' in form.changed_data:
OptionPlan.updatedBy = str(self.request.user)
OptionPlan.updated = timezone.localtime(timezone.now())
OptionPlan.save()
return redirect('Benefits:search')
optionUpdate.html:
{% if user.is_authenticated %}
<div style="margin-left:100px;margin-right:100px;">
<form method="POST">
<div class="row">
<div class="col">
<h2 class="alert alert-primary">...</h2>
{% csrf_token %}
<div class="row">
<div class="col-4" style="font-size:30px;">
{{ form.option|as_crispy_field }}
</div>
</div>
...
<div class="col">
<h2 class="alert alert-success">...</h2>
...
<div class="col" style="font-size:30px;">
{{ form.cb_remarks|as_crispy_field }}
</div>
</div>
<button type="submit" class="btn btn-primary btn-block">2020 Option Form Update</button>
</div>
</form>
</div>
{% else %}
Thank you so much for helping!
Edit:
What version of Django are you using? A cursory look at generic UpdateView* from Django v1.3 through v2.2 use this same post method:
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
form = self.get_form()
if form.is_valid(): <--- you passed here so your form is valid
return self.form_valid(form) <-- you overwrote this method (problem area)
else:
return self.form_invalid(form)
*get familiar with that website if you are using class based views, its a life saver.
I am not sure how your form_valid method is being called with four variables: request, obj, form, change when it only expects one variable.
I typically split this logic apart and would make a separate form:
forms.py
class OptionPlanUpdateForm(forms.ModelForm):
class Meta:
model = OptionPlan
fields = [
'option',
'cb_updatedBy',
...
'cb_updated',
]
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
def save(self, **kwargs):
option_plan = super().save(commit=False)
if 'option' in self.changed_data:
option_plan.updatedBy = str(self.user)
option_plan.updated = timezone.localtime(timezone.now())
option_plan.save()
return option_plan
Since we have a form we have to update our view to handle the form. We pass the user to the form through the get_form_kwargs method.
class OptionUpdate(UpdateView):
form_class = OptionPlanUpdateForm
model = OptionPlan
slug_field = 'id'
slug_url_kwarg = 'id'
template_name = 'Benefits/OptionUpdate.html'
success_url = reverse_lazy('Benefits:search')
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
***To test for errors in your form you can do the following:
def post(self, request, *args, **kwargs):
form = self.form_class(self.request.POST)
print(form.errors)
return super().post(request, *args, **kwargs)
If I were to assume, the form is throwing some type of error that you aren't printing nor handling. Try checking if any form errors exists to begin with.

Django UserForm edit no username

I made a custom user interface for user. I can create the user and edit with no problem except that when in edit form, the previous user doesn't show (nor does the passwor1, but that's ok I guess).
I don't know what I'm missing. It's a silly thing maybe, but I want it to be displayed.
The form:
class UserForm(UserCreationForm):
def __init__(self, *args, **kwargs):
super(UserForm, self).__init__(*args, **kwargs)
self.fields['username'].widget = TextInput(attrs = {'class': 'form-control',})
self.fields['password1'].widget = PasswordInput(attrs = {'class': 'form-control',})
self.fields['password2'].widget = PasswordInput(attrs = {'class': 'form-control',})
class Meta:
model = User
fields = ['username', 'password1', 'password2']
The view:
class UserUpdateView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView):
model = User
form_class = UserForm
template_name = 'security/user_create.html'
success_message = "El usuario fue editado exitosamente."
permission_required = ('user.can_update')
def get_success_url(self, **kwargs):
context = super(UserUpdateView, self).get_context_data(**kwargs)
person_id = self.kwargs['person_id']
return reverse('people:person-detail', args = [person_id])
def get_context_data(self, **kwargs):
context = super(UserUpdateView, self).get_context_data(**kwargs)
context['person'] = Person.objects.get(pk = self.kwargs['person_id'])
context['form_user'] = self.form_class
return context
The template:
<div class="form-group">
<div class="col-sm-9">
{{ form_user.username }}
</div>
</div>
Thanks!
You shouldn't be defining or using form_user. That's a reference to the form class; the instantiated form, including the initial data, is already passed to the template as form.
UpdateView allows you to just put {{ form }} in the template without defining the form in the view.
For the form to work, you are going to need something like this:
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Update" />
</form>

Django : How to exclude form field if the user is staff?

How to exclude form fields if the user is not staff ? I tried this but didn't work , giving an error :
global name 'user' is not defined
class PostForm(ModelForm):
class Meta:
model = Photo
exclude = ['author','featured','published']
def __init__(self, *args, **kwargs):
published = kwargs.pop('published', None)
super(PostForm, self).__init__(*args, **kwargs)
if not user.is_staff:
del self.fields['published']
view.py
def addpost(request):
if request.method == 'POST':
form = PostForm(request.POST,request.FILES,user=request.user)
if form.is_valid():
post = form.save(False)
post.author = request.user
form.save()
return HttpResponseRedirect(reverse('insight.content.views.index', ))
else:
form = PostForm(user=request.user)
ispost = True
return render_to_response('form_add_place.html', {'form': form,'ispost':ispost},context_instance=RequestContext(request))
This can be achieved in the template when rendering the form. It will need to allow null values or have a default value in the model definition or alternatively have its validation overridden:
<form method="post">{% csrf_token %}
{% if request.user.is_staff %}
<p>{{ form.published }}</p>
{% endif %}
<p>{{ form.author }}</p>
<!-- ... your other fields -->
</form>
Similarly you can check for is_superuser or check permissions, see the docs: https://docs.djangoproject.com/en/dev/topics/auth/default/#permissions
You need to pass it the user instance from your request - the model form doesn't have access to it.
my_form = PostForm(user=request.user)
Then, in your __init__:
def __init__(self, *args, **kwargs):
published = kwargs.pop('published', None)
user = kwargs.pop('user', None)
super(PostForm, self).__init__(*args, **kwargs)
if not user.is_staff:
del self.fields['published']