I have used an InLineFormSet to display all of the records that relate to a particular timesheet. The user can add many new forms on the page, I've used ajax and html to append an empty existing_form on the bottom with new IDs. However saving this is a problem.... because the newly added forms do not have timesheet_id assigned yet, they don't save as part of existing_forms.
I am trying to put an init method together to assign this when the form is created, hopefully solving this problem. (?)
1) Do I have to save the record before it will become part of existing_formset?
2) I have excluded the timesheet_id from the TimeForm.... this means I assume form.timesheet_id = var will not work... and I would have to use obj.timesheet_id... but can I do this in the init of a form?? very confused.
3) Is it easier to use a new formset for newly added forms and use modelformset_factory.....
View:
class CustomInlineFormSet(BaseInlineFormSet):
def clean(self):
super(CustomInlineFormSet, self).clean()
timesheet = TimeSheet.objects.get(pk=timesheet_id)
for form in self.forms:
form.empty_permitted = True
def __init__(self, request, *args, **kwargs):
super(CustomInlineFormSet, self).__init__(*args,**kwargs)
# the following won't work because it's excluded from the form?
# self.fields['timesheet_id'] = forms....... oh no
timesheet_fromview = request.session.get('timesheet')
print timesheet_fromview
for form in self.forms:
obj = form.save(commit=False)
obj.timesheet_id = timesheet_fromview
try:
del request.session['timesheet']
print "session deleted"
except KeyError:
pass
print "Key error raised"
def timesheet(request, timesheet_id):
timesheet = TimeSheet.objects.get(pk=timesheet_id)
TimeInlineFormSet = inlineformset_factory(TimeSheet, Time, exclude=('timesheet_id',), extra=0, formset=CustomInlineFormSet)
if request.method == 'POST':
# instance is not yet timesheet.... no foreign key in new fields so doesn't do anything
existing_formset = TimeInlineFormSet(request.POST, request.FILES, instance=timesheet)
for thetime in existing_formset.forms:
# obj = thetime.save(commit=False)
# obj.timesheet_id = timesheet
# obj.save()
# print obj
if thetime.is_valid():
print "existing time is valid"
thetime.save()
else:
"existing time is not valid"
context = {
"timesheet": timesheet,
"existing_formset": existing_formset,
}
return render(request, 'tande/timesheet.html', context)
else:
print "method is not post"
existing_formset = TimeInlineFormSet(instance=timesheet)
new_timeformset = NewTimeFormSet()
request.session['timesheet'] = timesheet
context = {
"timesheet": timesheet,
"existing_formset": existing_formset,
}
return render(request, 'tande/timesheet.html', context)
I've approached this by using two separate formsets in my views, one inline for existing records and one modelformset for new additions. I then save the two separately with a field that is True when the record is a new addition, and False when it is in the existing formset:
class Time(models.Model):
project_id = models.ForeignKey(Project, null=True)
date_worked = models.DateField(null=True, blank=True)
hours = models.CharField(max_length=1)
description = models.CharField(max_length=150)
timesheet_id = models.ForeignKey(TimeSheet, null=True)
add_row = models.CharField(max_length=10, default=True, editable=False)
def __unicode__ (self):
return self.description
The view saves the two formsets separately:
def timesheet(request, timesheet_id):
timesheet = TimeSheet.objects.get(pk=timesheet_id)
TimeInlineFormSet = inlineformset_factory(TimeSheet, Time, exclude=('timesheet_id',), extra=0, formset=CustomInlineFormSet)
NewTimeFormSet = modelformset_factory(Time, form=TimeForm, formset=RequiredFormSet)
if request.method == 'POST':
existing_formset = TimeInlineFormSet(request.POST, request.FILES, instance=timesheet)
newtime_formset = NewTimeFormSet(request.POST, request.FILES)
for orange in newtime_formset:
obj = orange.save(commit=False)
if obj.add_row == "True":
obj.timesheet_id = timesheet
obj.add_row = "False"
obj.save()
for thetime in existing_formset.forms:
if thetime.is_valid():
thetime.save()
existing_formset = TimeInlineFormSet(instance=timesheet)
newtime_formset = NewTimeFormSet()
context = {
"timesheet": timesheet,
"existing_formset": existing_formset,
"newtime_formset": newtime_formset,
}
return render(request, 'tande/timesheet.html', context)
Related
I use django-cities-light for a travel website and I would like to filter the cities in the fields ville_de_depart and ville_destination in the newBookingForm by trip.depart and trip.destination.
I tried to pass the trip object in the instance of newBookingForm. I override the __init__ and I took the value of the depart and destination, I succeeded in filtering the fields but I could no longer save the newBooking, the view redirect to the alltrip page with no error but no new booking is added to the database.
I tried to replace the trip by the slug which is the same value as the id and it shows me this error
'int' object has no attribute '_meta'
models.py
class trip(models.Model):
depart = models.ForeignKey(default='',to=Country,on_delete=models.CASCADE,related_name='depart')
destination = models.ForeignKey(default='',to=Country,on_delete=models.CASCADE)
date_de_depart = models.DateField(default='')
prix_kg = models.PositiveIntegerField(default='')
collecte = models.BooleanField(default=False,null=False,help_text='' )
creation_date = models.DateTimeField(auto_now=True)
author = models.ForeignKey(to=settings.AUTH_USER_MODEL,on_delete=models.CASCADE,default='')
slug = models.SlugField(max_length=100, default='' )
#jouter slug
def save(self, *args , **kwargs):
super(trip, self).save(*args , **kwargs)
if not self.slug:
self.slug = self.id
self.save()
def __str__(self):
return f'{self.id} {self.author} '
class Booking(models.Model):
trip = models.ForeignKey(trip,on_delete=models.CASCADE, default='',)
author = models.ForeignKey(to=settings.AUTH_USER_MODEL,on_delete=models.CASCADE,default='')
creation_date = models.DateTimeField(auto_now=True)
ville_de_depart = models.ForeignKey(City,on_delete=models.CASCADE,default='')
slug = models.SlugField(max_length=100, default='' )
# ville_depart = models.ForeignKey(default='',to=City,on_delete=models.CASCADE,related_name='ville_dep')
sender_phone = PhoneNumberField(blank=True)
receiver_phone = PhoneNumberField()
ville_destination = models.ForeignKey(default='',to=City,on_delete=models.CASCADE,related_name='ville_dest')
#jouter slug
def save(self, *args , **kwargs):
super(Booking, self).save(*args , **kwargs)
if not self.slug:
self.slug = self.id
self.save()
def __str__(self):
return str(self.trip.author)
views.py
def detailsTrip(request, slug):
trip = get_object_or_404(models.trip,slug=slug)
auth = trip.author
bookingForm = newBookingForm(instance=slug)
context = {'trip': trip, 'auth': auth, 'form': bookingForm}
if request.method == 'POST':
form = newBookingForm(request.POST , instance=slug )
if request.user.is_authenticated:
if form.is_valid():
trip = get_object_or_404(models.trip,slug=slug)
Booking = form.save(commit=False)
Booking.trip_id= trip.id
Booking.author_id = request.user.id
Booking = form.save()
return redirect('/alltrips')
else:
trip = get_object_or_404(models.trip,slug=slug)
auth = trip.author
bookingForm = newBookingForm()
context = {'trip': trip, 'auth': auth, 'form': bookingForm}
return render(request, 'detailstrip.html', context)
else:
return render (request, 'notFound.html')
return render(request,'detailstrip.html', context , )
forms.py
class newBookingForm(forms.ModelForm,):
def __init__(self,*args,**kwargs):
# capture the instance : Slug
slug = kwargs.get('instance')
# capture current trip
Trip = get_object_or_404(trip, id=slug)
# Filter cities field by the instance : trip.depart / trip.destination
super(newBookingForm, self).__init__(*args,**kwargs)
self.fields['ville_de_depart'].queryset = City.objects.filter(country_id=Trip.depart)
self.fields['ville_destination'].queryset = City.objects.filter(country_id=Trip.destination)
class Meta:
model = Booking
fields = ['ville_de_depart','ville_destination']
exclude = ['sender_phone','receiver_phone']
solution
#view.py
def detailsTrip(request, slug):
trip = get_object_or_404(models.trip,slug=slug)
auth = trip.author
#add the trip to the form
bookingForm = newBookingForm(trip=trip)
context = {'trip': trip, 'auth': auth, 'form': bookingForm}
if request.method == 'POST':
#add the object trip to the form here too
form = newBookingForm(trip,request.POST )
if request.user.is_authenticated:
if form.is_valid():
trip = get_object_or_404(models.trip,slug=slug)
Booking = form.save(commit=False)
Booking.trip_id= trip.id
Booking.author_id = request.user.id
Booking = form.save()
return redirect('/alltrips')
else:
trip = get_object_or_404(models.trip,slug=slug)
auth = trip.author
bookingForm = newBookingForm()
context = {'trip': trip, 'auth': auth, 'form': bookingForm}
return render(request, 'detailstrip.html', context)
else:
return render (request, 'notFound.html')
return render(request,'detailstrip.html', context , )
form.py
class newBookingForm(forms.ModelForm,):
class Meta:
model = Booking
fields = ['ville_de_depart','ville_destination']
exclude = ['sender_phone','receiver_phone']
def __init__(self,trip,*args,**kwargs):
# Filter cities field by trip.depart / trip.destination
super(newBookingForm, self).__init__(*args,**kwargs)
self.fields['ville_de_depart'].queryset = City.objects.filter(country=trip.depart)
self.fields['ville_destination'].queryset = City.objects.filter(country=trip.destination)
As long as you use many-to-many fields, you need to call form.save_m2m() after saving the model instance. or you can just call form.save() without commit=False keyword argument.
Here are some quotes from Django documentation:
This save() method accepts an optional commit keyword argument, which accepts either True or False. If you call save() with commit=False, then it will return an object that hasn’t yet been saved to the database.
Another side effect of using commit=False is seen when your model has a many-to-many relation with another model. If your model has a many-to-many relation and you specify commit=False when you save a form, Django cannot immediately save the form data for the many-to-many relation.
read this documentation about the save method
I'm doing a edit form for some data. I'm having problem to pass information to . As you can see in my view, I pass the data to form using "initial" dictionary.
VIEWS.PY
#login_required
def project_detail(request, project_id):
if request.method == 'POST':
project = get_object_or_404(Project, pk=project_id)
form = ProjectDetailForm(project_id, request.POST, instance = project)
if form.is_valid():
instance = form.save(commit=False)
instance.client = Project.objects.get(pk=project_id).client
form.save()
messages.success(request,'Projeto modificado')
return redirect('projects')
else:
messages.error(request,'Ocorreu um erro!')
else:
project = get_object_or_404(Project, pk=project_id)
form = ProjectDetailForm(project_id, initial={'modal':project.modal,
'culture':project.culture,
'owner':project.owner,
'value':project.value,
'final_date':project.final_date,
'text':project.text,
'status':project.status,
'farm':project.farm.values()})
return render(request,'project_detail.html',{'form':form})
But doing this, the data is not displaied in . Thinking here, ManyToManyField saves data in lists. I tried iterate this field but still not working and I guess thats not the best way to do this.
MODELS.PY
class Project(models.Model):
modal_types = [('CUSTEIO AGRÍCOLA','Custeio Agrícola'),('CUSTEIO PECUÁRIO','Custeio Pecuário'),('INVESTIMENTO AGRÍCOLA','Investimento Agrícola'),('INVESTIMENTO PECUÁRIO','Investimento Pecuário'),('FGPP','FGPP')]
status_opts = [('Análise','Análise'),('Desenvolvimento','Desenvolvimento'),('Processamento','Processamento'),('Liberação','Liberação'),('Finalizado','Finalizado'),('Cancelado','Cancelado'),('Suspenso','Suspenso')]
farm = models.ManyToManyField(Farm, related_name='farm_name',verbose_name='Propriedade beneficiada')
client = models.ForeignKey(Clients, on_delete=models.CASCADE, related_name='project_client',default=None,null=True, verbose_name='Cliente')
owner = models.ForeignKey(Owner, on_delete=models.CASCADE, related_name='project_bidder',default=None,null=True, verbose_name='Proponente')
warranty = models.ManyToManyField(Farm, related_name='project_warranty',default=None, verbose_name='Propriedade de garantia')
modal = models.CharField(max_length=100,default=None,choices=modal_types, null=True, verbose_name='Tipo')
culture = models.CharField(max_length=50,null=True, verbose_name='Cultura')
status = models.CharField(max_length=50,null=True, verbose_name='Status', choices=status_opts)
created_date = models.DateField(null=True, verbose_name='Data de criação')
value = models.FloatField(max_length=10,null=True, verbose_name='Valor financiado')
final_date = models.DateField(default=None,null=True, verbose_name='Fim do contrato')
text = models.TextField(default=None,null=True, verbose_name='Observações')
forms.py
class ProjectDetailForm(ModelForm):
class Meta:
model = Project
fields = ['status','owner', 'farm', 'warranty', 'modal', 'culture', 'value','final_date','text']
def __init__(self, project_id, *args, **kwargs):
client_id = Project.objects.get(pk=project_id).client
super(ProjectDetailForm,self).__init__(*args,**kwargs)
self.fields['value'].required = False
self.fields['final_date'].required = False
self.fields['text'].required = False
self.fields['farm'].queryset = Farm.objects.filter(client=client_id)
self.fields['warranty'].queryset = Farm.objects.filter(client=client_id)
for field_name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'
Here all the fields with information, but in "select" nothing is selected, despite having data in the database
Someone can help me?
Why are you passing 'project_id' into your form class instance? Try changing this:
form = ProjectDetailForm(project_id, request.POST, instance = project)
to this:
form = ProjectDetailForm(request.POST, instance = project)
and see if it helps. Also in your form initialization, I'm not sure you're using the "initial=" values dictionary correctly. Initial values are typically defaults applicable to the "create" view, not database records that it sounds like you want to see in an update view. I think you want to pass in the instance of your database record there, something like:
else:
project = get_object_or_404(Project, pk=project_id)
form = ProjectDetailForm(instance=project)
Also, you really don't need to write project object query twice in this view. You can do something like:
def project_detail(request, project_id):
project = get_object_or_404(Project, pk=project_id) # query this once here
if request.method == 'POST':
form = ProjectDetailForm(request.POST, instance=project)
if form.is_valid():
instance = form.save(commit=False)
instance.client = Project.objects.get(pk=project_id).client
form.save()
messages.success(request,'Projeto modificado')
return redirect('projects')
else:
messages.error(request,'Ocorreu um erro!')
# you probably want a redirect here as well
else:
form = ProjectDetailForm(instance=project)
return render(request,'project_detail.html',{'form':form})
Finally, if you're trying to limit the choices from your ManyToMany field in the user's form, you can do so with something like this:
class ProjectDetailForm(forms.ModelForm):
class Meta:
model = YourModelName
fields = ['farm']
farm = forms.ModelMultipleChoiceField(
queryset=Farm.objects.filter(some_field=some_criteria).order_by('some_field'),
widget=forms.CheckboxSelectMultiple)
More info about widgets on form classes here in Django docs.
Say, I have a view function that creates a Vacancy object on saving the form, and it has the field company that, if the form is valid, has to be assigned with value of field name, that is on the other model named EmployerProfile, how can I do that? I have company as a foreign key on my model.
My models
class EmployerProfile(models.Model):
name = models.CharField(max_length = 64)
description = models.TextField()
username = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete = models.CASCADE)
class Vacancy(models.Model):
name = models.CharField(max_length = 64)
competences = models.CharField(max_length = 32)
salary = models.DecimalField(decimal_places = 2, max_digits = 10)
description = models.TextField(null = True)
company = models.ForeignKey(EmployerProfile, on_delete = models.CASCADE)
featured = models.BooleanField(default = False)
My view
#login_required(login_url = 'login')
def vacancyAddView(request):
if request.method == 'POST':
form = VacancyAddForm(request.POST)
if form.is_valid():
form.save()
return redirect('profile')
else:
form = VacancyAddForm()
context = {
'form':form
}
return render(request, "addVacancy.html", context)
My form
class VacancyAddForm(forms.ModelForm):
class Meta:
model = Vacancy
fields = [
'name',
'competences',
'description',
'salary',
]
P.S. I have tried adding this piece of code to my view, rigth after form.is_valid():
obj = EmployerProfile.objects.get(username = request.user)
form.instance.company = obj.name
but it didn't work, it returned the ValueError with text "Vacancy.company" must be a "EmployerProfile" instance.
P.P.S. I also tried to override the save method in my model like this
def save(self, *args, **kwargs):
self.company = self.EmployerProfile.name
super(Vacancy, self).save(*args, **kwargs)
but it didn't work either.
You can't do what you want. Your Vacancy model defines company as a foreign key, but you're trying to set it as a string.
class Vacancy(models.Model):
company = models.ForeignKey(EmployerProfile, on_delete = models.CASCADE)
However, here's what your view should change to be to work with your models as you've defined them currently.
#login_required(login_url = 'login')
def vacancyAddView(request):
if request.method == 'POST':
form = VacancyAddForm(request.POST)
if form.is_valid():
# Set the company
form.instance.company = request.user.employerprofile
form.save()
return redirect('profile')
else:
form = VacancyAddForm()
context = {
'form':form
}
return render(request, "addVacancy.html", context)
The basic issue here is that you can't set a ForeignKey field to be a string. If you want the user to be able to set the Company from the form, you have a couple of choices. One is to take what you have now in the form and use it to filter, e.g. something like:
company = EmployerProfile.objects.filter(name__iexact=form.cleaned_data['company'])
form.instance.company = company
Another would be to change the form so that instead of a text input, company is a ModelChoiceField, or a regular ChoiceField populated with the company choices available to the user.
In either case the end goal will be to ensure that the company field contains a foreign key to an EmployerProfile object, rather than the string name of a company.
I'm doing a multi step form where everything is saved at the end. In my models I have a m2m checkbox field and I'm using django Sessions to grab the forms datas to show it on the final step.
The issue is that the m2m field (checkboxes) is not saved when I submit the final form.
Here is my views file :
views.py
def step1(request):
initial={'name': request.session.get('name', None), 'checkbox': request.session.get('checkbox', (False,))} #cookies
form = FormOneForm(request.POST or None, initial=initial)
if request.method == 'POST':
if form.is_valid():
request.session['name'] = form.cleaned_data['name']
request.session['checkbox'] = form.cleaned_data.get('checkbox')
return HttpResponseRedirect(reverse('step2'))
return render(request, 'step1.html', {'form': form})
def step2(request):
form = FormTwoForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
formtwo = form.save(commit=False)
formone2 = FormOne.objects.create(checkbox=request.session.get('checkbox')) #error is here
formone = FormOne.objects.create(name=request.session['name'])
formtwo.owner = formone
formtwo.save()
formone2.save_m2m()
return HttpResponseRedirect(reverse('step3'))
return render(request, 'step2.html', {'form': form})
models.py
class Font(models.Model):
font_name = models.CharField(max_length=100)
font_family = models.CharField(max_length=100)
font_link = models.CharField(max_length=100)
...
class FormOne(models.Model):
name = models.CharField(max_length=40)
checkbox = models.ManyToManyField(Font, blank=True)
...
class FormTwo(models.Model):
owner = models.ForeignKey(FormOne)
name = models.CharField(max_length=40)
...
this code gives me this error :
'checkbox' is an invalid keyword argument for this function
How can I achieve what I am trying to realise ?
Try to save object first:
formone2 = FormOne.objects.create(name=request.session['name'])
formone2.checkbox.add(request.session.get('checkbox')
The problem is that you need to save object before use Many-to-many relations. See docs:
You can’t associate it with a Publication until it’s been saved
I have an inlineformset based off a very simple Model form. I'd like a way to add a choiceset to one of the fields of the form, but I want to do it from the views.py file as the choiceset is generated dynamically. Is this possible? Python2.7, Django1.5
models.py
class RoleForm(ModelForm):
class Meta:
model = Role
fields = ['role_type', 'user', 'item']
views.py
def new(request):
''' Create a new application '''
user = request.user
associatedRoleTypes = getAssociatedRoleTypes(Application)
#^^^this returns a queryset, and is what I want model_choices to be equal to
RoleFormSetFactory = inlineformset_factory(Application, Role, can_delete=False, form=RoleForm)
if request.method == 'POST':
applicationForm = ApplicationForm(request.POST)
if applicationForm.is_valid():
new_application = applicationForm.save()
roleInlineFormSet = RoleFormSetFactory(request.POST, instance = new_application)
if roleInlineFormSet.is_valid():
roleInlineFormSet.save()
else:
print roleInlineFormSet.errors
return redirect(reverse('index'))
else:
applicationForm = ApplicationForm()
roleInlineFormSet = RoleFormSetFactory()
return render(request, 'who/editapp.html',
{'roleInlineFormSet': roleInlineFormSet,
'applicationForm': applicationForm
})