Django : Aggregate Sum works on view only after a refresh - django

I have an Invoice model in Django that has multiple Line models.(line items that have a title, unit price , and qty)
I have a 'addline' view that allows to add item lines to the invoice.
The view also displays all current invoice lines and a calculated sum of the total price for all line items.
when I submit a new line item, the view refreshes to the same page and the line item appears properly, but the total (totalservices or totalgoods) of line items is not updated .
It becomes updated when i refresh the page manually , or when i add another line -with the previous line total.
here is my relevant view
def addline(request, id):
form = AddLineForm(request.POST or None )
invoice = get_object_or_404(Invoice, id = id)
linelist = Line.objects.filter(invoice = id).order_by('created_at')
servicelines = linelist.filter(line_type = "S")
goodslines = linelist.filter(line_type = "G")
totalservice = servicelines.aggregate(Sum('line_total'))['line_total__sum']
print servicelines.aggregate(Sum('line_total'))
totalgoods = goodslines.aggregate(Sum('line_total'))['line_total__sum']
if form.is_valid():
instance = form.save(commit=False) #do schtuff with data
instance.invoice = invoice
instance.line_total = instance.unit_price *instance.qty
#print instance.line_total
if form.cleaned_data.get('overwrite'):
invoice.invoiced_service = totalservice or 0 #or zero to prevent fuss if list is empty
invoice.invoiced_goods = totalgoods or 0
invoice.save()
form.save()
form = AddLineForm()
context = {'inv': invoice, 'form': form, 'lines':linelist, 'goods': goodslines, 'services': servicelines ,'totalservice' : totalservice, 'totalgoods':totalgoods }
return render(request,'testpaper.html', context)
Thanks in advance, I'm not sure what could be the problem. Maybe that the Sum is lazy and not evaluated ?
*edited to reflect actual view

Found the problem : the 'if form.is_valid 'block that ends in form.save() needed to be before the block that calculates lines , otherwise lines are calculated before current item has been saved .

Related

How do I tackle this issue in my Guess The Flag Django web app

I'm completely new to Django and I'm trying to build this guess the flag web game with it. In main page when someone presses the 'play' button, he's sent to a page where a list of 4 randomly selected countries from the DB is generated, and only of one these 4 countries is the actual answer.
Here's the code from views.py in my App directory :
context = {}
context['submit'] = None
context['countries_list'] = None
score = []
score.clear()
context['score'] = 0
def play(request):
len_score = len(score)
countries = Country.objects.all()
real_choice = None
if request.POST:
get_guess = request.POST.get('guess')
print(request.POST)
if str(get_guess).casefold() == str(context['submit']).casefold():
score.append(1)
else:
score.clear()
len_score = len(score)
choices = random.sample(tuple(countries),4)
real_choice = random.choice(choices)
context['countries_list'] = choices
context['submit'] = real_choice
context['score'] = len_score
return render (request, 'base/play.html', context)
Everything works as expected when there's only one person playing, or the site is opened in only one tab.
The issue here is one someone else opens the site or it's opened in more than one tab, the score gets reset and a new list of random countries is generated for all users, so your guess will never be right!
How do I go about to solve this? Again, I'm completely new to this so I'm left clueless here.
The best way to store short term, per user information is by session variables. This way you can maintain the user's individual settings. So, something like:
def play(request):
len_score = len(score)
countries = Country.objects.all()
real_choice = None
if request.POST:
get_guess = request.POST.get('guess')
print(request.POST)
#Compare guess to session variable
#(using .get() which returns none rather than error if variable not found)
if str(get_guess).casefold() == str(request.session.get('real_choice').casefold():
score.append(1)
else:
score.clear()
len_score = len(score)
choices = random.sample(tuple(countries),4)
real_choice = random.choice(choices)
#Store answer in session variable
request.session['real_choice'] = real_choice
context['countries_list'] = choices
context['submit'] = real_choice
context['score'] = len_score
return render (request, 'base/play.html', context)

Form fields are empty when created using instance = instance

I have two models prodcut_prices and WrongPrice.
In WrongPrice the user can correct report wrong prices - when the price is reported it should also be updated in product_price.
My problem is, even though I instantiate product_price at the very beginning as instance_productprice, all of its required fields returns the "this field has to be filled out" error.
How come those field are not set when im using the instance instance_productprice = product_prices.objects.filter(id=pk)[0] ? Note, that all fields in product_prices are always non-empty since they are being pulled from the product_price model, which is handled in another view, thus that is not the issue.
def wrong_price(request,pk):
#Get the current price object
instance_productprice = product_prices.objects.filter(id=pk)[0]
#Get different values
wrong_link = instance_productprice.link
img_url = instance_productprice.image_url
wrong_price = instance_productprice.last_price
domain = instance_productprice.domain
# Create instances
instance_wrongprice = WrongPrice(
link=wrong_link,
correct_price=wrong_price,
domain = domain)
if request.method == "POST":
form_wrong_price = wrong_price_form(request.POST,instance=instance_wrongprice)
# Update values in product_prices
form_product_price = product_prices_form(request.POST,instance=instance_productprice)
form_product_price.instance.start_price = form_wrong_price.instance.correct_price
form_product_price.instance.last_price = form_wrong_price.instance.correct_price
if form_wrong_price.is_valid() & form_product_price.is_valid():
form_wrong_price.save()
form_product_price.save()
messages.success(request, "Thanks")
return redirect("my_page")
else:
messages.error(request, form_product_price.errors) # Throws empty-field errors,
messages.error(request, form_wrong_price.errors)
return redirect("wrong-price", pk=pk)
else:
form_wrong_price = wrong_price_form(instance=instance_wrongprice)
return render(request, "my_app/wrong_price.html",context={"form":form_wrong_price,"info":{"image_url":img_url}})
I am bit confused about how you implemented it. You have passed a instance of WrongPrice price through the form, which is unnecessary, you could have used initial:
wrong_values = dict(
link=wrong_link,
correct_price=wrong_price,
domain = domain
)
form_wrong_price = wrong_price_form(initial= wrong_values)
Then you are adding values to product_prices_form from instance of form_wrong_price. I don't see why you need a form again here. You can simple use:
form_wrong_price = wrong_price_form(request.POST, initial= wrong_values)
if form_wrong_price.is_valid():
instance = form_wrong_price.save()
instance_productprice.start_price = instance.correct_price
instance_productprice.last_price = instance.correct_price
instance_productprice.save()
Finally, please use PascalCase when defining class names. And you can get the product prices by product_prices.objects.get(id=pk)(instead of filter()[0]).

Why does the render_template keep on showing the old value of the flask form?

I've been searching for an answer for hours. I apologise if I missed something.
I'm using the same form multiple times in order to add rows to my database.
Every time I check an excel file to pre-fill some of the wtforms StringFields with known information that the user may want to change.
The thing is: I change the form.whatever.data and when printing it, it shows the new value. But when I render the template it keeps showing the old value.
I tried to do form.hours_estimate.data = "" before assigning it a new value just in case but it didn't work.
I will attach here the route I'm talking about. The important bit is after # Get form ready for next service. If there's more info needed please let me know.
Thank you very much.
#coordinator_bp.route("/coordinator/generate-order/<string:pev>", methods=['GET', 'POST'])
#login_required
def generate_order_services(pev):
if not (current_user.is_coordinator or current_user.is_manager):
return redirect(url_for('public.home'))
# Get the excel URL
f = open("./app/database/datafile", 'r')
filepath = f.read()
f.close()
error = None
if GenerateServicesForm().submit1.data and GenerateServicesForm().validate():
# First screen submit (validate the data -> first Service introduction)
form = FillServiceForm()
next_service_row = get_next_service_row(filepath)
if next_service_row is None:
excel_info = excel_get_pev(filepath)
error = "Excel error. Service code not found. If you get this error please report the exact way you did it."
return render_template('coordinator/get_pev_form.html', form=GetPevForm(), error=error, info=excel_info)
service_info = get_service_info(filepath, next_service_row)
service_code = service_info[0]
start_date = service_info[1]
time_estimate = service_info[2]
objects = AssemblyType.get_all()
assembly_types = []
for assembly_type in objects:
assembly_types.append(assembly_type.type)
form.service_code.data = service_code
form.start_date.data = start_date
form.hours_estimate.data = time_estimate
return render_template('coordinator/fill_service_form.html', form=form, error=error, assembly_types=assembly_types)
if FillServiceForm().submit2.data:
if not FillServiceForm().validate():
objects = AssemblyType.get_all()
assembly_types = []
for assembly_type in objects:
assembly_types.append(assembly_type.type)
return render_template('coordinator/fill_service_form.html', form=FillServiceForm(), error=error,
assembly_types=assembly_types)
# Service screen submits
# Here we save the data of the last submit and ready the next one or end the generation process
# Ready the form
form = FillServiceForm()
next_service_row = get_next_service_row(filepath)
if next_service_row is None:
excel_info = excel_get_pev(filepath)
error = "Excel error. Service code not found. If you get this error please report the exact way you did it."
return render_template('coordinator/get_pev_form.html', form=GetPevForm(), error=error, info=excel_info)
service_info = get_service_info(filepath, next_service_row)
service_code = service_info[0]
form.service_code.data = service_code
# create the service (this deletes the service code from the excel)
service = create_service(form, filepath)
if isinstance(service,str):
return render_template('coordinator/fill_service_form.html', form=form, error=service)
# Get next service
next_service_row = get_next_service_row(filepath)
if next_service_row is None:
# This means there is no more services pending
return "ALL DONE"
else:
# Get form ready for next service
service_info = get_service_info(filepath, next_service_row)
service_code = service_info[0]
start_date = service_info[1]
time_estimate = service_info[2]
print("time_estimate")
print(time_estimate) # I get the new value.
objects = AssemblyType.get_all()
assembly_types = []
for assembly_type in objects:
assembly_types.append(assembly_type.type)
form.service_code.data = service_code
form.start_date.data = start_date
form.hours_estimate.data = time_estimate
print(form.hours_estimate.data) # Here I get the new value. Everything should be fine.
# In the html, the old value keeps on popping.
return render_template('coordinator/fill_service_form.html', form=form, error=error,
assembly_types=assembly_types)
number_of_services = excel_get_services(filepath=filepath, selected_pev=pev)
# Get the number of the first excel row of the selected pev
first_row = excel_get_row(filepath, pev)
if first_row is None:
excel_info = excel_get_pev(filepath)
error = "Excel error. PEV not found. If you get this error please report the exact way you did it."
return render_template('coordinator/get_pev_form.html', form=GetPevForm(), error=error, info=excel_info)
service_code = []
start_date = []
time_estimate_code = []
quantity = []
# Open the excel
wb = load_workbook(filepath)
# grab the active worksheet
ws = wb.active
for idx in range(number_of_services):
# Append the data to the lists
service_code.append(ws.cell(row=first_row+idx, column=12).value)
start_date.append(str(ws.cell(row=first_row + idx, column=5).value)[:10])
time_estimate_code.append(ws.cell(row=first_row+idx, column=7).value)
quantity.append(ws.cell(row=first_row + idx, column=9).value)
wb.close()
return render_template('coordinator/generate_services_form.html',
form=GenerateServicesForm(),
pev=pev,
service_code=service_code,
start_date=start_date,
time_estimate_code=time_estimate_code,
quantity=quantity)
Well I found a workarround: I send the data outside the form like this:
return render_template('coordinator/fill_service_form.html', form=form, error=error,
assembly_types=assembly_types,
service_code=service_code,
start_date=start_date,
time_estimate=time_estimate)
And replace the jinja form for this:
<input class="form-control" placeholder="2021-04-23" name="start_date" type="text" value="{{start_date}}">
I'm still using the form (name= the form field name) and at the same time I input the value externally.
I hope this helps somebody.

Django pagination while objects are being added

I've got a website that shows photos that are always being added and people are seeing duplicates between pages on the home page (last added photos)
I'm not entirely sure how to approach this problem but this is basically whats happening:
Home page displays latest 20 photos [0:20]
User scrolls (meanwhile photos are being added to the db
User loads next page (through ajax)
Page displays photos [20:40]
User sees duplicate photos because the photos added to the top of the list pushed them down into the next page
What is the best way to solve this problem? I think I need to somehow cache the queryset on the users session maybe? I don't know much about caches really so a step-by-step explanation would be invaluable
here is the function that gets a new page of images:
def get_images_paginated(query, origins, page_num):
args = None
queryset = Image.objects.all().exclude(hidden=True).exclude(tags__isnull=True)
per_page = 20
page_num = int(page_num)
if origins:
origins = [Q(origin=origin) for origin in origins]
args = reduce(operator.or_, origins)
queryset = queryset.filter(args)
if query:
images = watson.filter(queryset, query)
else:
images = watson.filter(queryset, query).order_by('-id')
amount = images.count()
images = images.prefetch_related('tags')[(per_page*page_num)-per_page:per_page*page_num]
return images, amount
the view that uses the function:
def get_images_ajax(request):
if not request.is_ajax():
return render(request, 'home.html')
query = request.POST.get('query')
origins = request.POST.getlist('origin')
page_num = request.POST.get('page')
images, amount = get_images_paginated(query, origins, page_num)
pages = int(math.ceil(amount / 20))
if int(page_num) >= pages:
last_page = True;
else:
last_page = False;
context = {
'images':images,
'last_page':last_page,
}
return render(request, '_images.html', context)
One approach you could take is to send the oldest ID that the client currently has (i.e., the ID of the last item in the list currently) in the AJAX request, and then make sure you only query older IDs.
So get_images_paginated is modified as follows:
def get_images_paginated(query, origins, page_num, last_id=None):
args = None
queryset = Image.objects.all().exclude(hidden=True).exclude(tags__isnull=True)
if last_id is not None:
queryset = queryset.filter(id__lt=last_id)
...
You would need to send the last ID in your AJAX request, and pass this from your view function to get_images_paginated:
def get_images_ajax(request):
if not request.is_ajax():
return render(request, 'home.html')
query = request.POST.get('query')
origins = request.POST.getlist('origin')
page_num = request.POST.get('page')
# Get last ID. Note you probably need to do some type casting here.
last_id = request.POST.get('last_id', None)
images, amount = get_images_paginated(query, origins, page_num, last_id)
...
As #doniyor says you should use Django's built in pagination in conjunction with this logic.

django - promo code - coupon code - discount generator

I am attempting to create a promo code for a shopping cart that I already have. I want it to be simple, such as typing 100off to get $100 off, I am getting a error saying "global name 'PromoCode' is not defined".
models.py
class PromoCode(ModelForm):
code = models.FloatField(max_length=15)
discount = models.FloatField(max_length=15)
views.py
def addtocart(request, prod_id):
if (request.method == 'POST'):
form = CartForm(request.POST)
if form.is_valid():
newComment = form.save()
newComment.session = request.session.session_key[:20]
newComment.save()
return HttpResponseRedirect('/products/' + str(newComment.product.id))
else:
form = CartForm( {'name':'Your Name', 'session':'message', 'product':prod_id} )
return render_to_response('Products/comment.html', {'form': form, 'prod_id': prod_id})
def delItem(request, prod_id):
addtocart = get_object_or_404(Cart, pk = prod_id)
prod_id = addtocart.product.id
addtocart.delete()
return HttpResponseRedirect('/userHistory/')
def userHistory(request):
promo = PromoCode.objects.filter(code = code_from_the_form)
userCart = Cart.objects.filter(session = request.session.session_key[:20])
totalCost = 0
for item in userCart:
print item
totalCost += item.quantity * item.product.prodPrice * 1.06
return render_to_response('Products/history.html', {'userCart':userCart, 'totalCost' : totalCost})
Add a PromoCode model with two fields: code and discount. You can then add a couple of promo codes in the admin.
In the form, just add a promo code field and upon submit, check if the code matches any of your PromoCode objects and apply the discount.
(And perhaps a bit of javascript to check the code on the fly. And I'd add some checks here and there to make sure your discount is well between 0 and 1 ("0.5") if you just want to multiply and well between 0 and 100 if it is a percentage. Just make sure you cannot make a mistake with it, that would be my fear if I'd have to implement it :-)