I am trying to update a choice field before it is validated in Django. The reason for this is the choice field in question value is the Id of a model that i want to update.
def fuelLogSystemOne(request):
entries = FuelLogSystemOneMod.objects.all().order_by('date')
products = productsMod.objects.all()
if request.method == 'POST':
form = forms.AddFuelLogOneForm(request.POST, request.FILES)
productid = form['product'].value()
product = productsMod.objects.get(id=productid)
product_id = request.POST.get('product')
form.fields['product'].choices = [(product.product_type, product.product_type)]
if form.is_valid():
bucketsRemoved = form['buckets_added'].value()
product.stock -= bucketsRemoved
product.save(['stock'])
instance = form.save(commit=False)
instance.staff = request.user
instance.save()
return redirect('home')
else:
form = forms.AddFuelLogOneForm()
return render(request,'systems/fuellogsystemone.html',{'form':form,'entry':entries,'products':products})
The below part is where i am trying to change the form data before it gets validated so it doesn't say 'Select a valid choice. 1 is not one of the available choices'
product_id = request.POST.get('product')
form.fields['product'].choices = [(product.product_type, product.product_type)]
But when I first submit the form it is still saying 'Select a valid choice.'
At what point does Django validate the form because I am changing the form before the is_valid() method and it still hits this error?
This should be in your form, you have to override the init method, and pass the product id during form initialization in the views
forms.py
class AddFuelLogOneForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
product = kwargs.pop('product', None)
self.fields['product'].choices = [(product.product_type, product.product_type)]
views.py
def fuelLogSystemOne(request):
entries = FuelLogSystemOneMod.objects.all().order_by('date')
products = productsMod.objects.all()
if request.method == 'POST':
product_id = request.POST.get('product')
productid = form['product'].value()
product = productsMod.objects.get(id=productid)
product_id = request.POST.get('product')
form = forms.AddFuelLogOneForm(request.POST, request.FILES, product=product)
if form.is_valid():
bucketsRemoved = form['buckets_added'].value()
product.stock -= bucketsRemoved
product.save(['stock'])
instance = form.save(commit=False)
instance.staff = request.user
instance.save()
return redirect('home')
else:
form = forms.AddFuelLogOneForm()
return render(request,'systems/fuellogsystemone.html',{'form':form,'entry':entries,'products':products})
Related
I cannot find a way in the Django documentation to pass a value between different function views. I would like to create an object in my create_player_view, capture that new object pk and pass it to scoring_view. Doing this through the form action field has been unsuccessful as no data is passing between the views. What is a better way to do this?
I want a simple behavior that takes the Match ID created in create_player_view and passes it for update/use to scoring_view.
models
class Players(models.Model):
matchID = models.AutoField(primary_key=True)
player1Name = models.CharField(max_length=10)
player2Name = models.CharField(max_length=10)
def __str__(self):
return f'{self.matchID}: {self.player1Name} vs {self.player2Name}'
class Scores(models.Model):
matchID = models.OneToOneField(Players, on_delete=models.CASCADE, related_name='match')
p1_set_1_score = models.IntegerField(default=0)
p1_set_2_score = models.IntegerField(default=0)
p1_set_3_score = models.IntegerField(default=0)
p1_set_4_score = models.IntegerField(default=0)
p2_set_1_score = models.IntegerField(default=0)
p2_set_2_score = models.IntegerField(default=0)
p2_set_3_score = models.IntegerField(default=0)
p2_set_4_score = models.IntegerField(default=0)
def __str__(self):
return f'{self.matchID}: '
views
def create_player_view(request):
"""
allows users to name two
players competing vs one another
"""
if request.method == "POST":
form = PlayerForm(request.POST)
if form.is_valid():
form.save(commit=True)
return redirect('tennis:m_score') #is it possible to pass this view created instance?
else:
message = "Form could not be completed"
return render(request, "create_player.html", {"message":message})
else:
form = PlayerForm()
return render(request, "create_player.html",
{'form': form})
def scoring_view(request):
"""
View allows user to select the participating 2 players
and record their scores per set.
"""
if request.method == "POST":
form = ScoresForm(request.POST )#, instance=player_instance)
if form.is_valid():
form.save(commit=True)
return redirect('tennis:m_results')
else:
form = ScoresForm()
return render(request, "now_playing.html", {'form':form}) #todo add filtering
forms.py
class PlayerForm(ModelForm):
class Meta:
model = Players
exclude = ('matchID',)
labels = {
'playerName':('Player 1 Name', 'Player 2 Name'),
}
class ScoresForm(ModelForm):
class Meta:
model = Scores
fields = "__all__"
###urls.py
from django.urls import path
from tennis import views
app_name="tennis"
urlpatterns = [
path('players/', views.create_player_view, name="c_pl"),
path('scoring/<new_player>/', views.scoring_view, name="m_score"),
path('summary/', views.match_summary_view, name="m_results"),
]
Edit your views:
def create_player_view(request):
""""
allows users to name two
players competing vs one another
"""
if request.method == "POST":
form = PlayerForm(request.POST)
if form.is_valid():
new_player = = form.save(commit=True)
return redirect('tennis:m_score', new_player=new_player) # If you want to pass the pk instead of the object itself write: new_player=new_player.pk
else:
message = "Form could not be completed"
return render(request, "create_player.html", {"message":message})
else:
form = PlayerForm()
return render(request, "create_player.html",
{'form': form})
def scoring_view(request, new_player):
""""
View allows user to select the participating 2 players
and record their scores per set.
"""
if request.method == "POST":
# You can use the value of new_player as your needs
data = request.POST
data['matchID'] = new_player
form = ScoresForm(data)
if form.is_valid():
form.save(commit=True)
return redirect('tennis:m_results')
else:
form = ScoresForm()
return render(request, "now_playing.html", {'form':form}) #todo add filtering
I am using a function to record the time between which the page is loaded and the user submits the data. In that form, I have created a hidden field so that the admin can see it. However, I am unable to implement my idea since I don't understand how I can submit the time to the form from views.py. If someone can suggest an easier and simpler alternative methods to achieve what I am trying to do, that would be very helpful to me as well. My code is as follows.
models.py
class Responses(models.Model):
question1 = models.TextField()
question2 = models.TextField()
question3 = models.TextField()
timespent1 = models.TextField()
forms.py
class Question1Form(forms.ModelForm):
question1 = forms.CharField()
timespent1 = forms.TimeField(widget=forms.HiddenInput())
class Meta:
model = Responses
fields = ('question1','timespent1')
views.py
def qone(request):
if request.method =="GET":
starttime = my_timer()
elif request.method == "POST":
form = Question1Form(request.POST)
if form.is_valid():
endtime = my_timer()
timespentstr = '{0} seconds'.format(endtime-starttime)
#Do something to set timespent1 field = timespentstr here
form = form.save()
else:
form = Question1Form
return render(request,'question1.html',{'form':form})
You'll need to store the start time "somewhere else" - perhaps store it in the session in the GET, and then pull it back out from there in the POST:
def qone(request):
if request.method == 'GET':
request.session['start_time'] = mytimer()
form = Question1Form()
elif request.method == 'POST':
form = Question1Form(request.POST, request.FILES)
if form.is_valid():
end_time = mytimer()
start_time = request.session.get('start_time')
time_spent = end_time - start_time
....
form.save()
return render(request, 'question1.html', {'form': form})
You could also look at passing in the start time to the form directly, and then having all of the logic in there, but that might complicate things. Perhaps passing the "time taken" to the form constructor might be the best way.
class Question1Form(forms.Form):
def __init__(self, *args, **kwargs):
self.time_taken = self.kwargs.pop('time_taken', None)
super().__init__(*args, **kwargs)
def qone(request):
if request.method == 'GET':
form = Question1Form()
request.session['start_time'] = my_timer()
elif request.method == 'POST':
form = Question1Form(
request.POST, request.FILES,
time_taken=my_timer() - request.session.pop('start_time')
)
if form.is_valid():
form.save()
# ...
Is it semantically correct in Django to fetch a Product object of provided Product id in the clean() function, or should I do this somewhere else?
forms.py
def clean_product(self):
product_id = self.cleaned_data['product']
try:
product = Product.objects.get(id=int(product_id))
except ValueError:
raise ValidationError('Invalid value')
return product
views.py
def submit_new(request):
status = 'fail'
if request.method == 'POST':
form = NewBForm(request.POST)
if form.is_valid():
...
b = form.save(commit=False)
boost.save()
status = 'success'
return JsonResponse({'status': status})
I am trying to use a ModelForm to save a model.
forms.py
class PurchaseForm(forms.ModelForm):
weight = forms.IntegerField()
class Meta:
model = Purchase
fields = ["number", "pieces"]
views.py
if request.method == "POST":
form = PurchaseForm(request.POST)
if form.is_valid():
purchase = form.save(commit=False)
purchase.contract = Contract.objects.get(number=slug)
weight = form.cleaned_data.get('weight')
if check_weight(weight, purchase.contract):
weight_type = purchase.contract.supplier.market.weights
purchase.lbs, purchase.kgs = generate_weights(weight, weight_type)
purchase.save()
In the view above, I need to prevent the model from saving if the check_weight function returns False.
This function requires some data from the related object. I'm having some trouble figuring this out. What should I do?
If I'm understood your question correctly, this would work,
from django.http import HttpResponse
def my_form_view(request):
if request.method == "POST":
form = PurchaseForm(request.POST)
if form.is_valid():
purchase = form.save(commit=False)
purchase.contract = Contract.objects.get(number=slug)
weight = form.cleaned_data.get('weight')
if check_weight(weight, purchase.contract):
weight_type = purchase.contract.supplier.market.weights
purchase.lbs, purchase.kgs = generate_weights(weight, weight_type)
purchase.save()
return HttpResponse("save success")
return HttpResponse("'check_weight' returned False")
else: # if a GET (or any other method) we'll create a blank form
form = PurchaseForm()
return render(request, 'some_html_template.html', {'form': form})
I'm having trouble in saving a m2m data, containing a 'through' class table.
I want to save all selected members (selected in the form) in the through table.
But i don't know how to initialise the 'through' table in the view.
my code:
class Classroom(models.Model):
user = models.ForeignKey(User, related_name = 'classroom_creator')
classname = models.CharField(max_length=140, unique = True)
date = models.DateTimeField(auto_now=True)
open_class = models.BooleanField(default=True)
members = models.ManyToManyField(User,related_name="list of invited members", through = 'Membership')
class Membership(models.Model):
accept = models.BooleanField(User)
date = models.DateTimeField(auto_now = True)
classroom = models.ForeignKey(Classroom, related_name = 'classroom_membership')
member = models.ForeignKey(User, related_name = 'user_membership')
and in the view:
def save_classroom(request):
classroom_instance = Classroom()
if request.method == 'POST':
form = ClassroomForm(request.POST, request.FILES, user = request.user)
if form.is_valid():
new_obj = form.save(commit=False)
new_obj.user = request.user
new_obj.save()
membership = Membership(member = HERE SELECTED ITEMS FROM FORM,classroom=new_obj)
membership.save()
How should I initialise the membership for the Membership table to be populated right?
In case of using normal m2m relation (not through intermediary table) you could replace:
membership = Membership(member = HERE SELECTED ITEMS FROM FORM,classroom=new_obj)
membership.save()
with
form.save_m2m()
But in case of using intermediary tables you need to manually handle POST data and create Membership objects with all required fields (similar problem). The most basic solution is to change your view to something like:
def save_classroom(request):
if request.method == 'POST':
form = ClassroomForm(request.POST, request.FILES)
if form.is_valid():
new_obj = form.save(commit=False)
new_obj.user = request.user
new_obj.save()
for member_id in request.POST.getlist('members'):
membership = Membership.objects.create(member_id = int(member_id), classroom = new_obj)
return HttpResponseRedirect('/')
else:
form = ClassroomForm()
return render_to_response('save_classroom.html', locals())
Note how request.POST is manipulated (.getlist). This is because post and get are QueryDict objects which has some implications (request.POST['members'] will return always one object!).
You can modify this code to get it more reliable (error handling etc.), and more verbose, eg:
member = get_object_or_404(User, pk = member_id)
membership = Membership.objects.create(member = member , classroom = new_obj)
But note that you are performing some db queries in a loop which is not a good idea in general (in terms of performance).
Like what dzida did, but use form.cleaned_data instead of request.post:
def save_classroom(request):
if request.method == 'POST':
form = ClassroomForm(request.POST, request.FILES)
if form.is_valid():
new_obj = form.save(commit=False)
new_obj.user = request.user
new_obj.save()
for member in form.cleaned_data['members'].all():
Membership.objects.create(member = member, classroom = new_obj)
return HttpResponseRedirect('/')
else:
form = ClassroomForm()
return render_to_response('save_classroom.html', locals())
You also need to consider some memberships might be deleted, so:
def save_classroom(request):
if request.method == 'POST':
form = ClassroomForm(request.POST, request.FILES)
if form.is_valid():
new_obj = form.save(commit=False)
new_obj.user = request.user
new_obj.save()
final_members = form.cleaned_data['members'].all()
initial_members = form.initial['members'].all()
# create and save new members
for member in final_members:
if member not in initial_members:
Membership.objects.create(member = member, classroom = new_obj)
# delete old members that were removed from the form
for member in initial_members:
if member not in final_members:
Membership.objects.filter(member = member, classroom = new_obj).delete()
return HttpResponseRedirect('/')
else:
form = ClassroomForm()
return render_to_response('save_classroom.html', locals())
If you use model forms (like in a generic CBV: form_class=ClassroomForm), override and put the saving logic above in the save method, something like:
ClassroomForm(forms.ModelForm):
members = ModelMultipleChoiceField(
queryset=Classroom.objects.all(),
widget=SelectMultiple
)
def save(self, commit=True):
classroom = super().save(commit=False)
if commit:
classroom.save()
if 'members' in self.changed_data:
final_members = self.cleaned_data['members'].all()
initial_members = self.initial['members']
# create and save new members
for member in final_members:
if member not in initial_members:
Membership.objects.create(member = member, classroom = new_obj)
# delete old members that were removed from the form
for member in initial_members:
if member not in final_members:
Membership.objects.filter(member = member, classroom = new_obj).delete()
return classroom
You also need to specify the classroom for the membership:
membership = Membership(member = request.user,
classroom=new_obj) #if new_obj if your classroom
membership.save()
I guess you should also remove User in accept = models.BooleanField(User). It shouldn't be necessary to set the date upon saving if you are using auto_now! But maybe `auto_now_add is more likely what you need (http://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.DateField)
This is how I did it in a generic UpdateForm class-based view (django 1.8) for a similar yet different application using the form_valid method.
def form_valid(self, form):
"""
If the form is valid, save the associated model.
"""
self.object.members.clear()
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.save()
list_of_members = form.cleaned_data['members']
ClassRoom.objects.bulk_create([
Membership(
Course=self.object,
member=member_person,
order=num)
for num, member_person in enumerate(list_of_members)
])
return super(ModelFormMixin, self).form_valid(form)