django - promo code - coupon code - discount generator - django

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 :-)

Related

(Hidden field id) Select a valid choice. That choice is not one of the available choices. (Django)

I'm receiving this error when I try to submit two formsets. After I fill the form and click the save button, it gives the error:
(Hidden field id) Select a valid choice. That choice is not one of the available choices.
I'm trying to create dynamic form so that the user can add new sections and also new lectures inside the section when they click "Add" button. The adding new form function works well, I just have problem saving it to the database.
Views.py
def addMaterials(request, pk):
course = Course.objects.get(id=pk)
sections = CourseSection.objects.filter(course_id=pk)
materials = CourseMaterial.objects.filter(section__in=sections)
SectionFormSet = modelformset_factory(CourseSection, form=SectionForm, extra=0)
sectionformset = SectionFormSet(request.POST or None, queryset=sections)
MaterialFormSet = modelformset_factory(CourseMaterial, form=MaterialForm, extra=0)
materialformset = MaterialFormSet(request.POST or None, queryset=materials)
context = {
'course': course,
'sectionformset': sectionformset,
'materialformset': materialformset,
}
if request.method == "POST":
if all([sectionformset.is_valid() and materialformset.is_valid()]):
for sectionform in sectionformset:
section = sectionform.save(commit=False)
section.course_id = course.id
section.save()
for materialform in materialformset:
material = materialform.save(commit=False)
print(material)
material.section_id = section #section.id or section.pk also doesn't work
material.save()
return('success')
return render(request, 'courses/add_materials.html', context)
Forms.py
class SectionForm(forms.ModelForm):
class Meta:
model = CourseSection
fields = ['section_name', ]
exclude = ('course_id', )
class MaterialForm(forms.ModelForm):
class Meta:
model = CourseMaterial
fields = ['lecture_name', 'contents']
The second formset which is materialformset need the section id from the first formset hence why there is two loop in views.
Can someone help me to solve this. I'm not sure how to fix it.
This is the what I'm trying to do.
I'm new to django but I had to face with the same problem. My solution was to handle singularly each formset inside 'views.py'.
In the template.html, create a tag for each formset you have, than inside that tag put <input type="submit" name="form1">(Note that name is important and must be different with the respect of the form you are submitting).
Then in views.py, instead for writing if all([sectionformset.is_valid() and materialformset.is_valid()]), try like this:
if 'form1' in request.POST:
if sectionformset.is_valid():
sectionformset.save()
# other rows of your code
return('success')
if 'form2' in request.POST:
if materialformset.is_valid():
materialformset.save()
# etc. etc.

How to use timezones in Django Forms

Timezones in Django...
I am not sure why this is so difficult, but I am stumped.
I have a form that is overwriting the UTC dateTime in the database with the localtime of the user. I can't seem to figure out what is causing this.
my settings.py timezone settings look like:
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'America/Toronto'
USE_I18N = True
USE_L10N = False
USE_TZ = True
I am in Winnipeg, my server is hosted in Toronto. My users can be anywhere.
I have a modelfield for each user that is t_zone = models.CharField(max_length=50, default = "America/Winnipeg",) which users can change themselves.
with respect to this model:
class Build(models.Model):
PSScustomer = models.ForeignKey(Customer, on_delete=models.CASCADE)
buildStart = models.DateTimeField(null=True, blank=True)
...
I create a new entry in the DB using view logic like:
...
now = timezone.now()
newBuild = Build(author=machine,
PSScustomer = userCustomer,
buildStart = now,
status = "building",
addedBy = (request.user.first_name + ' ' +request.user.last_name),
...
)
newBuild.save()
buildStart is saved to the database in UTC, and everything is working as expected. When I change a user's timezone in a view with timezone.activate(pytz.timezone(self.request.user.t_zone)) it will display the UTC time in their respective timezone.
All is good (I think) so far.
Here is where things go sideways:
When I want a user to change buildStart in a form, I can't seem to get the form to save the date to the DB in UTC. It will save to the DB in whatever timezone the user has selected as their own.
Using this form:
class EditBuild_building(forms.ModelForm):
buildStart = forms.DateTimeField(input_formats = ['%Y-%m-%dT%H:%M'],widget = forms.DateTimeInput(attrs={'type': 'datetime-local','class': 'form-control'},format='%Y-%m-%dT%H:%M'), label = "Build Start Time")
def __init__(self, *args, **kwargs):# for ensuring fields are not left empty
super(EditBuild_building, self).__init__(*args, **kwargs)
self.fields['buildDescrip'].required = True
class Meta:
model = Build
fields = ['buildDescrip', 'buildStart','buildLength'...]
labels = {
'buildDescrip': ('Build Description'),
'buildStart': ('Build Start Time'),
...
}
widgets = {'buildDescrip': forms.TextInput(attrs={'class': 'required'}),
and this view:
class BuildUpdateView_Building(LoginRequiredMixin,UpdateView):
model = Build
form_class = EditBuild_building
template_name = 'build_edit_building.html'
login_url = 'login'
def get(self, request, *args, **kwargs):
proceed = True
try:
instance = Build.objects.get(id = (self.kwargs['pk']))
except:
return HttpResponse("<h2 style = 'margin:2em;'>This build is no longer available it has been deleted, please please return to dashboard</h2>")
if instance.buildActive == False:
proceed = False
if instance.deleted == True:
proceed = False
#all appears to be well, process request
if proceed == True:
form = self.form_class(instance=instance)
timezone.activate(pytz.timezone(self.request.user.t_zone))
customer = self.request.user.PSScustomer
choices = [(item.id, (str(item.first_name) + ' ' + str(item.last_name))) for item in CustomUser.objects.filter(isDevice=False, PSScustomer = customer)]
choices.insert(0, ('', 'Unconfirmed'))
form.fields['buildStrategyBy'].choices = choices
form.fields['buildProgrammedBy'].choices = choices
form.fields['operator'].choices = choices
form.fields['powder'].queryset = Powder.objects.filter(PSScustomer = customer)
context = {}
context['buildID'] = self.kwargs['pk']
context['build'] = Build.objects.get(id = (self.kwargs['pk']))
return render(request, self.template_name, {'form': form, 'context': context})
else:
return HttpResponse("<h2 style = 'margin:2em;'>This build is no longer editable here, or has been deleted, please return to dashboard</h2>")
def form_valid(self, form):
timezone.activate(pytz.timezone(self.request.user.t_zone))
proceed = True
try:
instance = Build.objects.get(id = (self.kwargs['pk']))
except:
return HttpResponse("<h2 style = 'margin:2em;'>This build is no longer available it has been deleted, please please return to dashboard</h2>")
if instance.buildActive == False:
proceed = False
if instance.deleted == True:
proceed = False
#all appears to be well, process request
if proceed == True:
form.instance.editedBy = (self.request.user.first_name)+ " " +(self.request.user.last_name)
form.instance.editedDate = timezone.now()
print('edited date ' + str(form.instance.editedDate))
form.instance.reviewed = True
next = self.request.POST['next'] #grabs prev url from form template
form.save()
build = Build.objects.get(id = self.kwargs['pk'])
if build.buildLength >0:
anticipated_end = build.buildStart + (timedelta(hours = float(build.buildLength)))
print(anticipated_end)
else:
anticipated_end = None
build.anticipatedEnd = anticipated_end
build.save()
build_thres_updater(self.kwargs['pk'])#this is function above, it updates threshold alarm counts on the build
return HttpResponseRedirect(next) #returns to this page after valid form submission
else:
return HttpResponse("<h2 style = 'margin:2em;'>This build is no longer available it has been deleted, please please return to dashboard</h2>")
When I open this form, the date and time of buildStart are displayed in my Winnipeg timezone, so Django converted from UTC to my timezone, perfect, but when I submit this form, the date in the DB has been altered from UTC to Winnipeg Time. Why is this?
I have tried to convert the submitted time to UTC in the form_valid function, but this does not seem like the right approach. What am I missing here?
I simply want to store all times as UTC, but display them in the user's timezone in forms/pages.
EDIT
When I remove timezone.activate(pytz.timezone(self.request.user.t_zone)) from both get and form_valid, UTC is preserved in the DB which is great. But the time displayed on the form is now in the default TIME_ZONE in settings.py. I just need this to be in the user's timezone....
EDIT 2
I also tried to add:
{% load tz %}
{% timezone "America/Winnipeg" %}
{{form}}
{% endtimezone %}
Which displayed the time on the form correctly, but then when the form submits, it will again remove 1 hour from the UTC time in the DB.
If I change template to:
{% load tz %}
{% timezone "Europe/Paris" %}
{{form}}
{% endtimezone %}
The time will be displayed in local Paris time. When I submit the form, it will write this Paris time to the DB in UTC+2. So, in summary:
Time record was created was 11:40 Winnipeg time, which writes
16:40 UTC to database, perfect
I access the form template, and time is displayed as local Paris time, 6:40pm, which is also what I would expect.
I submit form without changing any fields.
Record has been updated with the time as 22:40, which is UTC + 6 hours.
What is happening here!?
Put simply: your activate() call in form_valid() comes too late to affect the form field, so the incoming datetime gets interpreted in the default timezone—which in your case is America/Toronto—before being converted to UTC and saved to the database. Hence the apparent time shift.
The documentation doesn't really specify when you need to call activate(). Presumably, though, it has to come before Django converts the string value in the request to the aware Python datetime in the form dictionary (or vice versa when sending a datetime). By the time form_valid() is called, the dictionary of field values is already populated with the Python datetime object.
The most common place to put activate() is in middleware (as in this example from the documentation), since that ensures that it comes before any view processing. Alternatively, if using generic class-based views like you are, you could put it in dispatch().

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]).

Django: Math on Models

I need to do some math against a value and I'm very confused about F(). I've read through the docs and searched for examples but I'm missing some fundamentals. Could you help with a solution and give some pointers about how to make sense of this? My attempts below are commented out. I'm just trying to convert Mb to GB. If I could get a 2 decimal value that would be be really wonderful.
class DatastoreInfo(models.Model):
[ ... ]
total_capacity = models.IntegerField(db_column='Total_Capacity', blank=True, null=True)
[ ... ]
class Meta:
managed = False
db_table = 'Datastore_Info'
def ecsdatastores(request):
form = myForm()
results = {}
if request.method == 'POST':
if 'listxyz' in request.POST:
form = myForm(request.POST)
taf = form['taf'].value()
results = DatastoreInfo.objects.filter(f_hostname=c_name)
# results = DatastoreInfo.objects.filter(f_hostname=c_name, total_capacity=F('total_capacity') / 1000)
# results = DatastoreInfo.objects.update(total_capacity=F('total_capacity') / 1000).filter(f_hostname=c_name)
return render(request, 'dpre/datastoreinfo.html', {'form': form , 'results': results})
You almost are there. You need to organize your query so first you filter down to the applicable rows, and then you apply the update with the F function as the value.
# First take your filtered rows
results = DatastoreInfo.objects.filter(f_hostname=c_name)
# Then, on the filtered queryset, you can apply the update.
results.update(total_capacity=F('total_capacity') / 1000)
You can see this is just like the reporter example from the Django F Expression Documentation.
reporter = Reporters.objects.filter(name='Tintin')
reporter.update(stories_filed=F('stories_filed') + 1)
Edit: As a side note you may want to divide by 1024, not 1000, if converting from GB to MB.

Django : Aggregate Sum works on view only after a refresh

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 .