Django Delete Button Action for ModelFormset - django

I've got a modelformset which is shown in below:
views.py
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=('name', 'title', 'birth_date'))
if request.method == 'POST':
if "del_btn" in request.POST:
query = Author.objects.get(...).delete()
formset = AuthorFormSet(request.POST, request.FILES)
if formset.is_valid():
formset.save()
formset = AuthorFormSet(queryset=Author.objects.all())
print "yes"
else:
formset = AuthorFormSet(request.POST, request.FILES)
if formset.is_valid():
formset.save()
formset = AuthorFormSet(queryset=Author.objects.all())
else:
formset = AuthorFormSet(queryset=Author.objects.all())
return render(request, "manage_authors.html", {"formset": AuthorFormSet, })
manage_authors.html
<form method="post" action="/manage_authors.html">{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
{{ form.id }}
<ul>
{{ form.name }} {{ form.title }} {{ form.birth_date }}
<input type="submit" name="del_btn" value="Delete"/>
</ul>
{% endfor %}
<input type='submit' name="edit_btn" value='Edit / Add'/>
What query I can define so that the Delete button could work?
Now, I got problem with knowing which row must be deleted.
Thanks in advance

First I recommend registering your user model in admin.py:
admin.site.register(Author)
Django will handle the rest.
But if you want to do it with this code do like this:
<form method="post" action="/manage_authors.html">{% csrf_token %}
{{ formset.management_form }}
for form in formset %}
{{ form.id }}
<ul>
{{ form.name }} {{ form.title }} {{ form.birth_date }}
<input type="submit" name="del_btn{{ form.instance.id }}" value="Delete"/>
</ul>
{% endfor %}
<input type='submit' name="edit_btn" value='Edit / Add'/>
So that primary key of object will relate to the delete button name.
Now, in views.py:
import re, urllib
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=('name', 'title', 'birth_date'))
if request.method == 'POST':
enurl=urllib.urlencode(request.POST) # To convert POST into a string
matchobj=re.search(r'del_btn\d', enurl) # To match for e.g. del_btn1
btnname=matchobj.group() # Contains matched button name
pri_key=btname[-1] # slice the number of btn to identify primarykey
if matchobj:
query = Author.objects.get(pk=pri_key).delete()
formset = AuthorFormSet(request.POST, request.FILES)
if formset.is_valid():
formset.save()
formset = AuthorFormSet(queryset=Author.objects.all())
print "yes"
else:
formset = AuthorFormSet(request.POST, request.FILES)
if formset.is_valid():
formset.save()
formset = AuthorFormSet(queryset=Author.objects.all())
else:
formset = AuthorFormSet(queryset=Author.objects.all())
return render(request, "manage_authors.html", {"formset": AuthorFormSet, })

Related

Django form and formset are not valid

I'm trying to make a view containing one form and one formset but something does not work.
both form and formset after checking if .is_valid returns false. I do not really undersand why it is like that
def ProductCreateView(request):
context = {}
created_product = None
form = ProductForm()
if request.method == 'POST':
form = ProductForm(request.POST)
if form.is_valid():
created_product = form.save()
print("Successfully created new product: {}".format(created_product))
else:
print("form is not valid")
#print(request.POST) returns csrfmiddlewaretoken ...
#print(request.FILES) returns file : inmemoryuploadedfile ...
#print(list(request.POST.items()))
context['form'] = form
formset = ProductPhotoInlineFormset()
if request.method=='POST':
formset = ProductPhotoInlineFormset(request.POST or None, request.FILES or None, instance=created_product)
if formset.is_valid():
created_images = formset.save()
print("Successfully created new imagest: {}".format(created_images))
else:
print("formset is not valid")
context['formset'] = formset
return render(request, "Ecommerce/create_product_test.html", context)
my template - create_product_test.html
{% extends 'base.html' %}
{% block content %}
<div id="alert-box">
</div>
<div id="image-box" class="mb-3">
</div>
<div id="image-box"></div>
<div class="form-container">
<button class="btn btn-primary mt-3 not-visible" id="confirm-btn">confirm</button>
<form method="POST" enctype="multipart/form-data" action="" id="image-form">
{% csrf_token %}
<div>
{{form}}
{{formset.management_form}}
{{formset.as_p}}
</div>
</form>
</div>
{% endblock content %}
forms.py file
ProductPhotoInlineFormset = inlineformset_factory(
Product,
Photo,
fields=('file',),
form=PhotoForm,
extra=1,
)
where is the problem ?
You can find out what's wrong with the form with:
print(form.errors)
print(form.non_field_errors())
and for a formset:
print(formset.errors)
print(formset.non_form_errors())
This way, you can easily find out why the form is not valid.

Django - formset with crispy forms - missing management form data

I'm trying to render this form set:
ProductFormSet = modelformset_factory(
model=Product,
fields='__all__',
extra=5,
)
class ProductFormSetHelper(FormHelper):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_tag = False
self.layout = Layout(
Row(
'productCategory',
'name',
'measurement_unit',
)
)
self.render_required_fields = True
with this view:
def product_create(request):
helper = ProductFormSetHelper()
context = {
'helper': helper,
'title': 'Nuovi Prodotti',
}
if request.method == 'GET':
formset = ProductFormSet(queryset=Product.objects.none())
context['formset'] = formset
elif request.method == 'POST':
formset = ProductFormSet(request.POST)
context['formset'] = formset
if formset.is_valid():
formset.save()
messages.success(request, 'Prodotti aggiunti correttamente', fail_silently=True)
return HttpResponseRedirect(reverse('warehouse:products_list'))
else:
return render(request, 'warehouse/product_create.html', context)
return render(request, 'warehouse/product_create.html', context)
and this template:
{% extends "masterpage.html" %}
{% load static %}
{% block headTitle %}
<title>Nuovo Prodotto</title>
{% endblock %}
{% block contentHead %}
{% endblock %}
{% block contentBody %}
{% load document_tags %}
{% load custom_tags %}
{% load crispy_forms_tags %}
<FORM method="POST" autocomplete="off">
<div class="alert alert-info">
{{ title }}
</div>
{{ formset.management_form }}
{% crispy formset helper %}
<input type="submit" class="btn btn-primary margin-left" value="SALVA">
</FORM>
{% endblock %}
Problem is that when I submit the form I get the: ValidationError: management form data are missing! First of all, using crispy forms the management data should be included, second even if I explicitly call with the tag, I still get the ValidationError.
in Every forum I searched online, everyone was missing the management form tag, so I have no clue on what could be wrong....
Any idea?
thank you very much
C
in your Post method you didnt include the "queryset"
formset = ProductFormSet(queryset=Product.objects.WHATEVER_YOU_CHOOSE, request.POST)
and for crispy forms
{% load crispy_forms_tags %}
<form method="post">
{% csrf_token %}
{{ formset|crispy }}
{{ formset.management_form }}
<input type="submit" class="btn btn-primary margin-left" value="SALVA">
</form>
and because you are not working in get_context_data fucntion, just keep it simple
context = {'formset':formset}
Exemple from the docs
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
if request.method == "POST":
formset = AuthorFormSet(
request.POST, request.FILES,
queryset=Author.objects.filter(name__startswith='O'),
)
if formset.is_valid():
formset.save()
# Do something.
else:
formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
return render(request, 'manage_authors.html', {'formset': formset})
Hope it can help others. Since I'm having troubles configuring the thousand separator, I tried to change the input.html widget in /templates/django/forms/widgets by adding the |intcomma filter.
This altered the management form data ( one field is set to 1000 by default ), thus corrupting the whole form.

Django booleanfield modelform

So I'm making a to-do list and I made a booleanfield modelform which has attribute "complete". I want that user to check it when it's complete and I tried wriring if task.complete == True cross out the item and it didn't work(it only worked when I checked it from the admin panel). Then I tried form.complete instead of task.complete and it doesn't do anything.
models:
class Task(models.Model):
title = models.CharField(max_length=200)
complete = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
forms:
from .models import *
class TaskForm(forms.ModelForm):
title = forms.CharField(widget= forms.TextInput(attrs={'placeholder':'Add new task...'}))
class Meta:
model = Task
fields = '__all__'
html:
<div class="main-container">
<form method="POST" action="/">
{% csrf_token %}
<input type="submit"/>
{{ form.title }}
</form>
{% for task in tasks %}
{{ form.complete }}
{% if form.complete == True %}
<h1><strike>{{task.title}}</strike></h1>
{% else %}
<h1>{{task.title}}</h1>
{% endif %}
{% endfor %}
</div>
views:
def index(request):
tasks = Task.objects.order_by('-created')
form = TaskForm()
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
form.save()
return redirect('/')
context = {
'tasks': tasks,
'form': form,
}
return render(request, 'to-do.html', context)
There are some problems with your code I don't know how to explain. Try this. It should work.
<div class="main-container">
<form method="POST" action="/"> # create new task
{% csrf_token %}
{{ form.title }}
<input type="submit" name="new-task"/>
</form>
<form method="post"> # update task status
{% csrf_token %}
{% for task in tasks %}
{% if task.complete %}
<input type="checkbox" name="if_completed" checked value="{{ task.pk }}">
<h1><strike>{{task.title}}</strike></h1>
{% else %}
<input type="checkbox" name="if_completed" value="{{ task.pk }}">
<h1>{{task.title}}</h1>
{% endif %}
{% endfor %}
<input type="submit" name="update-task"/>
</form>
</div>
view.py (Only for form, add your other code with it)
from django.http import HttpResponseRedirect
from django.urls import reverse
def index(request):
tasks = Task.objects.order_by('-created')
form = TaskForm()
if request.method == 'POST':
if 'new-task' in request.POST:
form = TaskForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('home')) # replace home with url name where you want to redirect
elif 'update-task' in request.POST:
task_pk = request.POST.getlist("if_completed")
for i in task_pk:
Task.objects.filter(pk=i).update(complete=True) # I have replace pk with i here
return HttpResponseRedirect(reverse('home')) # replace home with url name where you want to redirect
context = {
'tasks': tasks,
'form': form,
}
return render(request, 'to-do.html', context)
in forms.py
class TaskForm(forms.ModelForm):
class Meta:
model = Task
fields = ('title',)
widgets = {
'title': forms.TextInput(attrs={'placeholder':'Add new task...'})
}
This should work. There may be be some error because of typos or indentation. Let me know if you get any issue
def index(request):
tasks = Task.objects.order_by('-created')
form = TaskForm()
context = {
'tasks': tasks,
'form': form,
}
if request.method == 'POST':
if 'new-task' in request.POST:
form = TaskForm(request.POST)
if form.is_valid():
form.save()
elif 'update-task' in request.POST:
task_pk = request.POST.getlist("if_completed")
for i in task_pk:
Task.objects.filter(pk=pk).update(complete=True)
return render(request, 'to-do.html', context)

Django: Create an editable form for each instance within a queryset all in one page?

Sorry if this is too much code, but I believe it is all relevant to the question at hand.
Long story short, on my series_detail page, all episodes belonging to each series is shown, as well as forms to add a new episode or edit an existing one.
The edit episode form, however, requires an instance, which always returns the very first object of the episodes queryset. This is presumably because of the .first(), but I used this since you can only have one object passed as an instance.
What I am trying to achieve is:
after showing the edit modal next to each episode, show the instance of each episode instead of only the first episode.
save only that episode's instance after the form is filled
achieve this without redirecting to an edit page
models.py
class Series(models.Model):
name = models.CharField(max_length=100)
class Episode(models.Model):
series = models.ForeignKey(Series, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
episode_no = models.IntegerField(null=True)
description = models.TextField(max_length=500)
image = models.ImageField(upload_to='pics/episodes',)
forms.py
class EpisodeForm(forms.ModelForm):
name = forms.CharField()
description = forms.CharField(widget=forms.Textarea, required=False)
episode_no = forms.IntegerField()
class Meta:
model = Episode
fields = ['name', 'description', 'episode_no' ,'image']
views.py
def series_detail(request, pk):
try:
series = Series.objects.get(pk=pk)
except:
return render(request, 'error_404.html')
episodes = Episode.objects.filter(series=series).first()
if request.method == 'POST':
if 'addepisodeform' in request.POST:
e_form = EpisodeForm(request.POST, request.FILES, prefix='addepisode')
e_form.instance.series = Series.objects.get(pk=pk)
if e_form.is_valid():
e_form.save()
return redirect('series_detail', pk=pk)
messages.success(request, 'Episode was created')
else:
return redirect('series_detail', pk=pk)
messages.error(request, 'Episode was not created')
elif 'editepisodeform' in request.POST:
edit_e_form = EpisodeForm(request.POST, request.FILES, instance=episodes, prefix='editepisode')
edit_e_form.instance.series = Series.objects.get(pk=pk)
if edit_e_form.is_valid():
edit_e_form.save()
return redirect('series_detail', pk=pk)
messages.success(request, 'Episode was updated')
else:
return redirect('series_detail', pk=pk)
messages.error(request, 'Episode was not updated')
else:
e_form = EpisodeForm(prefix='addepisode')
edit_e_form = EpisodeForm(instance=episodes, prefix='editepisode')
context = {
'episodes': episodes,
'e_form': e_form,
'edit_e_form': edit_e_form
}
return render(request, 'series/series_detail.html', context)
def delete_episode(request, pk1, pk2):
try:
series = Series.objects.get(pk=pk1)
except:
return render(request, 'error_404.html')
try:
episode = Episode.objects.get(series=series, episode_no=pk2)
except:
return render(request, 'error_404.html')
episode.delete()
return redirect('series_detail', pk=pk1)
urls.py
path('series/<int:pk>/', views.series_detail, name='series_detail'),
path('series/<int:pk1>/episode/<int:pk2>/delete/', views.delete_episode, name='delete_episode'),
series_detail.html
<button type="submit" name="addepisodeform">
Add Episode
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ e_form }}
</form>
</button>
{% for episode in episodes %}
{{ episode.name }}
{{ episode.description}}
<img src="{{ episode.image.url }}" height="125px" width="300px" style="object-fit: cover;">
<button type="submit" name="editepisodeform">
Edit Episode
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ edit_e_form }}
</form>
</button>
{% endfor %}
Okay, so it turns out that formsets were the way to go after all. Thanks to Willem Van Onsem's answer, I decided to go that route after all and it worked like a charm.
A form can only edit one instance, but with formsets, I was able to not only edit each episode rather than just the first instance, but even create a new object and delete multiple objects at the same time!
views.py
#import the formset
from django.forms import modelformset_factory, inlineformset_factory
#formset
EpisodeFormset = inlineformset_factory(Series, Episode, fields=('name', 'episode_no', 'description', 'image'), can_delete=True, extra=1)
#post call
if request.method == 'POST':
if 'editepisodeform' in request.POST:
formset = EpisodeFormset(request.POST, request.FILES, instance=series, prefix='editepisode')
if formset.is_valid():
formset.save()
return redirect('series_detail', pk=pk)
else:
return redirect('series_detail', pk=pk)
else:
formset = EpisodeFormset(instance=series)
series_detail.html
<div>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<button type="submit" class="btn btn-primary" name="editepisodeform">Edit</button>
{{ form.as_p }}
{% for episode in episodes %}
{% if episode.episode_no == form.episode_no.value %}
Episode: {{ episode.episode_no }} <br>
Name: {{ episode.name }} <br>
<img src="{{ episode.image.url }}" height="125px" width="300px" style="object-fit: cover;"> <br> {% endif %}
{% endif %}
{% endfor %}
{% endfor %}
</form>
</div>

How do I use two lists in one Django for tag?

I have two letters that I would like to show in my template at the same time. How can I do it in the template the fastest and easiest way?
Where set_1 = A, B, C, D,
ser_2 = result from my django queryset
Is there something like the below? Any help will be appreciated
{% for b in set_1 and a in set_2 %}
<p>{{ b }} - {{ a }}</p>
{% endfor %}
EDIT:
How can I use this when my queryset returns a list of fields in my form, as in the view below?
views.py
def account(request):
data_now = datetime.datetime.now().strftime("%Y-%m-%d")
test = Time.objects.filter(day_time__day_name='Monday')
#my form
TimeFormSet = modelformset_factory(Time, fields=('free_or_no',), labels={'free_or_no': '*odznacz jeżeli zajęte',})
if request.method == "POST":
formset = TimeFormSet(
request.POST,
queryset=Time.objects.filter(day_time__day_name='Monday'),
)
if formset.is_valid():
formset.save()
return HttpResponseRedirect(reverse('app:account'))
else:
formset = TimeFormSet(queryset=Time.objects.filter(day_time__day_name='Monday'))
list_form = zip(formset, test)
context = {'data_now': data_now, 'time_edit_form': formset, 'test': test, 'list_form': list_form}
return render(request, 'account.html', context)
If I do everything as in your link, my browser returns an error:
IntegrityError at /account/
NOT NULL constraint failed: app_time.time_equivalent
html file
<form action="." method="post">
{% csrf_token %}
{{ time_edit_form.management_form }}
{% for item1, item2 in list_form %}
<p>{{item2}} {{item1}}</p>
{% endfor %}
<button type="submit" class="btn btn-block btn-primary"> ZapiszXXX</button>
</form>