Can a django formset that dynamically adds fields have persistent data? - python

I am making a formset in python/django and need to dynamically add more fields to a formset as a button is clicked. The form I'm working on is for my school asking students who they would like to disclose certain academic information to, and the button here allows them to add more fields for entering family members/people they want to disclose to.
I have the button working to the point where the extra fields show up, and you can add as many as you like. Problem is, the data that was previously entered into the already existing fields gets deleted. However, only the things in the formset get deleted. Everything else that was filled out earlier in the form stays persistent.
Is there any way to make the formset keep the data that was entered before the button was pressed?
form.py:
from django import forms
from models import Form, ParentForm, Contact
from django.core.exceptions import ValidationError
def fff (value):
if value == "":
raise ValidationError(message = 'Must choose a relation', code="a")
# Create your forms here.
class ModelForm(forms.ModelForm):
class Meta:
model = Form
exclude = ('name', 'Relation',)
class Parent(forms.Form):
name = forms.CharField()
CHOICES3 = (
("", '-------'),
("MOM", 'Mother'),
("DAD", 'Father'),
("GRAN", 'Grandparent'),
("BRO", 'Brother'),
("SIS", 'Sister'),
("AUNT", 'Aunt'),
("UNC", 'Uncle'),
("HUSB", 'Husband'),
("FRIE", 'Friend'),
("OTHE", 'Other'),
("STEP", 'Stepparent'),
)
Relation = forms.ChoiceField(required = False, widget = forms.Select, choices = CHOICES3, validators = [fff])
models.py
from django.db import models
from django import forms
from content.validation import *
from django.forms.models import modelformset_factory
class Contact(models.Model):
name = models.CharField(max_length=100)
class Form(models.Model):
CHOICES1 = (
("ACCEPT", 'I agree with the previous statement.'),
)
CHOICES2 = (
("ACADEMIC", 'Academic Records'),
("FINANCIAL", 'Financial Records'),
("BOTH", 'I would like to share both'),
("NEITHER", 'I would like to share neither'),
("OLD", "I would like to keep my old sharing settings"),
)
Please_accept = models.CharField(choices=CHOICES1, max_length=200)
Which_information_would_you_like_to_share = models.CharField(choices=CHOICES2, max_length=2000)
Full_Name_of_Student = models.CharField(max_length=100)
Carthage_ID_Number = models.IntegerField(max_length=7)
I_agree_the_above_information_is_correct_and_valid = models.BooleanField(validators=[validate_boolean])
Date = models.DateField(auto_now_add=True)
name = models.ManyToManyField(Contact, through="ParentForm")
class ParentForm(models.Model):
student_name = models.ForeignKey(Form)
name = models.ForeignKey(Contact)
CHOICES3 = (
("MOM", 'Mother'),
("DAD", 'Father'),
("GRAN", 'Grandparent'),
("BRO", 'Brother'),
("SIS", 'Sister'),
("AUNT", 'Aunt'),
("UNC", 'Uncle'),
("HUSB", 'Husband'),
("FRIE", 'Friend'),
("OTHE", 'Other'),
("STEP", 'Stepparent'),
)
Relation = models.CharField(choices=CHOICES3, max_length=200)
def __unicode__(self):
return 'name: %r, student_name: %r' % (self.name, self.student_name)
and views.py
from django.shortcuts import render
from django.http import HttpResponse
from form import ModelForm, Parent
from models import Form, ParentForm, Contact
from django.http import HttpResponseRedirect
from django.forms.formsets import formset_factory
def create(request):
ParentFormSet = formset_factory(Parent, extra=1)
if request.POST:
Parent_formset = ParentFormSet(request.POST, prefix='Parent_or_Third_Party_Name')
if 'add' in request.POST:
list=[]
for kitties in Parent_formset:
list.append({'Parent_or_Third_Party_Name-0n-ame': kitties.data['Parent_or_Third_Party_Name-0-name'], 'Parent_or_Third_Party_Name-0-Relation': kitties.data['Parent_or_Third_Party_Name-0-Relation']})
Parent_formset = ParentFormSet(prefix='Parent_or_Third_Party_Name', initial= list)
form = ModelForm(request.POST)
if form.is_valid() and Parent_formset.is_valid():
form_instance = form.save()
for f in Parent_formset:
if f.clean():
(obj, created) = ParentForm.objects.get_or_create(name=f.cleaned_data['name'], Relation=f.cleaned_data['Relation'])
return HttpResponseRedirect('http://Google.com')
else:
form = ModelForm()
Parent_formset = ParentFormSet(prefix='Parent_or_Third_Party_Name')
return render(request, 'content/design.html', {'form': form, 'Parent_formset': Parent_formset})
def submitted(request):
return render(request, 'content/design.html')
Thank you in advance!

I've had trouble with dynamically adding fields in Django before and this stackoverflow question helped me:
dynamically add field to a form
To be honest, I'm not entirely sure what you mean by "persistent" in your case - are the values of your forms being removed as you add inputs? Are you sure it isn't something with your JS?

A coworker of mine finally figured it out. Here is the revised views.py:
from django.shortcuts import render
from django.http import HttpResponse
from form import ModelForm, Parent
from models import Form, ParentForm, Contact
from django.http import HttpResponseRedirect
from django.forms.formsets import formset_factory
def create(request):
ParentFormSet = formset_factory(Parent, extra=1)
boolean = False
if request.POST:
Parent_formset = ParentFormSet(request.POST, prefix='Parent_or_Third_Party_Name')
if 'add' in request.POST:
boolean = True
list=[]
for i in range(0,int(Parent_formset.data['Parent_or_Third_Party_Name-TOTAL_FORMS'])):
list.append({'name': Parent_formset.data['Parent_or_Third_Party_Name-%s-name' % (i)], 'Relation': Parent_formset.data['Parent_or_Third_Party_Name-%s-Relation' % (i)]})
Parent_formset = ParentFormSet(prefix='Parent_or_Third_Party_Name', initial= list)
form = ModelForm(request.POST)
if form.is_valid() and Parent_formset.is_valid():
form_instance = form.save()
for f in Parent_formset:
if f.clean():
(contobj, created) = Contact.objects.get_or_create(name=f.cleaned_data['name'])
(obj, created) = ParentForm.objects.get_or_create(student_name=form_instance, name=contobj, Relation=f.cleaned_data['Relation'])
return HttpResponseRedirect('http://Google.com')
else:
form = ModelForm()
Parent_formset = ParentFormSet(prefix='Parent_or_Third_Party_Name')
return render(request, 'content/design.html', {'form': form, 'Parent_formset': Parent_formset, 'boolean':boolean})
def submitted(request):
return render(request, 'content/design.html')
Thank you for your input, those of you who answered :)

I was once trying to do something like this, and was directed to django-crispy-forms by a man much wiser than I. I never finished the project so I can't offer more help than that, but it could be a starting point.

If your formset does not show the input you made before that means it does not see model's queryset. Add queryset to formset arguments to resolve this. For example:
formset = SomeModelFormset(queryset=SomeModel.objects.filter(arg_x=x))

Related

model form doesn't render form again when form is not valid

I added Min and Max Value validators to my Django model and then when I enter a value out of this range, instead of rendering form again,it raises a value error.
ValueError: The view cars_form.views.rental_review didn't return an HttpResponse object. It returned None instead.
[14/Jan/2023 21:44:50] "POST /cars_form/rental_review/ HTTP/1.1" 500 74599
It should be HTTP/1.1" 200
This is my Code
models.py:
from django.db import models
from django.core.validators import MinValueValidator,MaxValueValidator
class Review(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
stars = models.IntegerField(validators=[MinValueValidator(1),MaxValueValidator(5)])
forms.py :
from django import forms
from .models import Review
class ReviewForm(forms.ModelForm):
class Meta:
model = Review
fields = "__all__"
views.py:
from django.shortcuts import render,redirect
from django.urls import reverse
from .forms import ReviewForm
def rental_review(request):
if request.method=='POST':
form = ReviewForm(request.POST)
if form.is_valid():
form.save()
return redirect(reverse('cars_form:thank_you')) # my template
else:
form = ReviewForm() # making an object
return render(request,'cars_form/rental_review.html',context={'form':form})
if the form is not valid it must go on else statement but it doesn't and idk why.
Try this,
def rental_review(request):
if request.method=='POST':
form = ReviewForm(request.POST)
if form.is_valid():
form.save()
return redirect(reverse('cars_form:thank_you')) # my template
else:
form = ReviewForm() # making an object
return render(request,'cars_form/rental_review.html',context{'form':form})
I have changed indentation of the last line

I'm trying to create user specific access to my todolist website with Django

I'm getting an error after I make migrations in the command line. Please help
When I try to create a new todo list I get this error:
AttributeError at /create/
'AnonymousUser' object has no attribute 'todolist'
-views.py
from django.shortcuts import render
from django.http import HttpResponse, HttpResponseRedirect
from .models import ToDoList, Item
from .forms import CreateNewList
# Create your views here.
def index(response, id):
ls = ToDoList.objects.get(id=id)
if ls in response.user.todolist.all():
if response.method == "POST":
if response.POST.get("save"):
for item in ls.item_set.all():
if response.POST.get("c" + str(item.id)) == "clicked":
item.complete = True
else:
item.complete = False
item.save()
elif response.POST.get("newItem"):
txt = response.POST.get("new")
if len(txt) > 2:
ls.item_set.create(text=txt, complete=False)
else:
print("invalid")
return render(response, "main/list.html", {"ls":ls})
return render(response, "main/home.html", {})
def home(response):
return render(response, "main/home.html", {})
def create(response):
if response.method == "POST":
form = CreateNewList(response.POST)
if form.is_valid():
n = form.cleaned_data["name"]
t = ToDoList(name=n)
t.save()
response.user.todolist.add(t)
return HttpResponseRedirect("/%i" %t.id)
else:
form = CreateNewList()
return render(response, "main/create.html", {"form":form})
def view(response):
return render(response, "main/view.html", {})
-models.py
from django.db import models
from django.contrib.auth.models import User
class ToDoList(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="todolist", null=True)
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Item(models.Model):
todolist = models.ForeignKey(ToDoList, on_delete=models.CASCADE)
text = models.CharField(max_length=300)
complete = models.BooleanField()
def __str__(self):
return self.text
I used this command:
python manage.py makemigrations
python manage.py migrate
I used the tutorial from TechWithTim(link: https://www.youtube.com/watch?v=sm1mokevMWk&t=7589s) to do this project. It works fine for him but not for me.
Someone, please tell me where I went wrong.
Request is passed to views. response is what is returned from views, not the other way around(although can name request in different ways, but it's better to do it meaningfully).
def create(request):
ForeignKey creates a one-to-many relationship. The model in which this field is declared is considered secondary, and the model that is declared in this field is considered primary. Description here:
By using the primary model(ToDoList), you can get the data associated with it from the secondary model(Item). To do this, a special property (object) with the name secondary model_set is created in the primary model by default. In our case: item_set.
Note that the property is in lower case(the property is created with a small letter). You can also define it on your own using related_name (in this case, it will be the way you write it, without the _set prefix).
In the view, I first get data from the primary model based on the secondary(aaa.todolist that is, referring to the field of the secondary model, I get the name of the primary model, since the primary class returns the name. And then I get user through the name).
Then I use the primary to get the data of the secondary model (in the secondary model, I took only the first value). All received data is displayed on the page.
def create(request):
aaa = Item.objects.get(id=1)
name = aaa.todolist
user = aaa.todolist.user
bbb = ToDoList.objects.get(id=1)
fff = bbb.item_set.all()[0]#takes the first element from the QuerySet .
print(bbb.item_set.all())
return HttpResponse(f"""
<p>ferst name: {name}</p>
<p>ferst user: {user}</p>
<p>second todolist: {fff.todolist}</p>
<p>second text: {fff.text}</p>
<p>second complete: {fff.complete}</p>
""")
urls.py
urlpatterns = [
path("create/", create, name="create"),
]

Django passing user ID to filter models

I have trying to filter objects in a model to avoid people putting events in their calendars which over lap. I found the below link which helped (Django form field clean to check if entered date is in a stored range). It has left me with another issue that it blocks any other user from having an event at the same time.
My Question is, can I pass the user id number to filter the queryset? it works if I manually input my user ID number?
Code below with my last attempt?
model.py
from django.db import models
from django.contrib.auth.models import User
class Event(models.Model):
manage = models.ForeignKey(User, on_delete=models.CASCADE, default=None)
title = models.CharField(max_length=200, default='free')
description = models.TextField()
start_time = models.DateTimeField()
end_time = models.DateTimeField()
def __str__(self):
return self.title + " - " + str(self.start_time) + " - " + str(self.end_time)
Form.py
from django import forms
from django.forms import ModelForm, DateInput
from calendar_app.models import Event
from django.contrib.auth.models import User
class EventForm(ModelForm):
class Meta:
model = Event
# datetime-local is a HTML5 input type, format to make date time show on fields
widgets = {
'start_time': DateInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%dT%H:%M'),
'end_time': DateInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%dT%H:%M'),
}
fields = ['title','description','start_time','end_time']
def __init__(self, *args, **kwargs):
super(EventForm, self).__init__(*args, **kwargs)
# input_formats parses HTML5 datetime-local input to datetime field
self.fields['start_time'].input_formats = ('%Y-%m-%dT%H:%M',)
self.fields['end_time'].input_formats = ('%Y-%m-%dT%H:%M',)
def clean(self):
form_start_time = self.cleaned_data.get('start_time')
form_end_time = self.cleaned_data.get('end_time')
form_manage = self.cleaned_data.get('manage')
between = Event.objects.filter(manage=form_manage, start_time__gte=form_start_time, end_time__lte=form_end_time)
if between:
raise forms.ValidationError('Already Calendar entry for this time')
super(EventForm,self).clean()
views.py
def event(request, event_id=None):
instance = Event()
if event_id:
instance = get_object_or_404(Event, pk=event_id)
else:
instance = Event()
form = EventForm(request.POST or None, instance=instance)
if request.POST and form.is_valid():
instance.manage = request.user
form.save()
return HttpResponseRedirect(reverse('calendar_app:calendar'))
return render(request, 'event.html', {'form': form})
You can make use of the manage_id of the Event object wrapped in the form:
class EventForm(ModelForm):
# …
def clean(self):
form_start_time = self.cleaned_data.get('start_time')
form_end_time = self.cleaned_data.get('end_time')
between = Event.objects.filter(
manage_id=self.instance.manage_id,
start_time__gte=form_start_time,
end_time__lte=form_end_time
)
if between.exists():
raise forms.ValidationError('Already Calendar entry for this time')
super().clean()
of course this only works if the Event alreadh has a manage_id, but that is not a problem, since we can add the primary key of the user if necessary. It is probably also safer to filter on the manage_id in the get_object_or_404 to prevent users from editing each others events:
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
#login_required
def event(request, event_id=None):
instance = Event()
if event_id:
instance = get_object_or_404(Event, pk=event_id, manage_id=request.user.pk)
else:
instance = Event(manage_id=request.user.pk)
if request.method == 'POST':
form = EventForm(request.POST, request.FILES, instance=instance)
if form.is_valid():
form.save()
return redirect('calendar_app:calendar')
else:
form = EventForm(instance=instance)
return render(request, 'event.html', {'form': form})
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.

Checking if input from a form is in table django

The goal is to take in input from a user and see if that input matches information in a table in a database. If there is a match, delete that information from the table. If there isn't a match, clear out the form and say that there isn't a match.
Here is the views file.
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import CheckoutForm, CheckoutRegForm
from .models import Checkout
from books.models import Addbook
def checkout(request):
if request.method == 'POST':
form = CheckoutForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, f'The book has been checked out.')
return redirect('front-page')
else:
form = CheckoutForm()
return render(request, 'checkout/checkout.html', {'form': form})
def checkin(request):
if request.method == 'POST':
form = CheckoutRegForm(request.POST)
if form.is_valid():
title = form.cleaned_data['title']
member_id = form.cleaned_data['member_id']
if title in Checkout.objects.all():
Checkout.objects.filter(title = title).delete()
messages.success(request, f'The book has been checked in.')
return redirect('front-page')
else:
messages.error(request, f'Error: This book is not in the Checkout table.')
return redirect('checkin')
else:
form = CheckoutRegForm()
return render(request, 'checkout/checkin.html', {'form': form})
From my understanding, forms in Django collect user information and store it in a dictionary 'form.cleaned_data'. I tired multiple ways to set up that if statement to see if there is a match with the input from the user and information in the Checkout table. But no matter how I set up the if statement, it seems to always go to the 'else' part whenever I test it out. So basically even when I type a title that I know is in the checkout table, it doesn't get deleted and it gives me the error message of "This book is not in the Checkout table".
Here is my forms.py
from .models import Checkout
from django.forms import ModelForm
from django import forms
class CheckoutForm(ModelForm):
# member_id = forms.IntegerField()
class Meta:
model = Checkout
fields = [
'title',
'member_id',
]
class CheckoutRegForm(forms.Form):
member_id = forms.IntegerField()
title = forms.CharField(max_length = 1000)
# date_checkout = forms.DateTimeField(auto_now_add = True)
Ignore the comments. Here is the modules file if you need that.
from django.db import models
class Checkout(models.Model):
member_id = models.IntegerField(null = True)
title = models.CharField(max_length = 1000)
date_checkout = models.DateTimeField(auto_now_add = True)
Thanks for any help. If you need any other information or not really sure what I'm asking, just let me know.
Your problem is in
if title in Checkout.objects.all():
Checkout.objects.all() will return list of objects, when U compare string data from your form. So it's must be something like that:
for object in Checkout.objects.all():
if object.title == title:
do something...

Enter text immediately into box for autocomplete

I have a formset with django-autocomplete-light on the full_name field. I want to be able to TAB into the box and type. Currently, however, if I tab into the box, I can't type, it's like a ChoiceField, but if I click on it (or spacebar) it opens up the dropdown menu and I can type into the area below the menu I clicked on, as though I was typing in the first choice option.
from functools import partial, wraps
from django.urls import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django import forms
from extra_views import ModelFormSetView, InlineFormSetView, InlineFormSet, CreateWithInlinesView
from dal import autocomplete, forward
from .models import Record, Ledger, Person
from pdb import set_trace as st
# Create your views here.
class NameAutocomplete(autocomplete.Select2ListView):
def get_list(self):
return self.forwarded.get('full_name_choices', [])
def create_form_class(persons):
full_name_choices = [p.full_name for p in persons]
class RecordForm(forms.ModelForm):
full_name = autocomplete.Select2ListChoiceField(
widget=autocomplete.ListSelect2(
url='name_autocomplete',
forward=[
forward.Const(
full_name_choices, 'full_name_choices'
)
]
)
)
class Meta:
model = Record
fields = ['score', 'full_name', ]
return RecordForm
def create_records(request, pk):
ledger_id = pk
ledger = Ledger.objects.get(pk=ledger_id)
persons = Person.objects.filter(ledger_id=ledger_id)
RecordInlineFormSet = forms.inlineformset_factory(
Ledger,
Record,
can_delete=False,
form=create_form_class(persons),
extra=len(persons),
)
if request.method == 'POST':
formset = RecordInlineFormSet(
request.POST,
instance=ledger,
)
if formset.is_valid():
formset.save()
return HttpResponseRedirect('admin')
else:
formset = RecordInlineFormSet(
instance=ledger,
queryset=Person.objects.none(),
)
return render(
request,
'app/ledger_detail.html',
{'formset': formset},
)

Categories