Do I need to use transactions in google appengine - python

update 0
My def post() code has changed dramatically because originally it was base on a digital form which included both checkboxes and text entry fields, not just text entry fields, which is the current design to be more paper-like. However, as a result I have other problems which may be solved by one of the proposed solutions, but I cannot exactly follow that proposed solution, so let me try to explain new design and the problems.
The smaller problem is the inefficiency of my implementation because in the def post() I create a distinct name for each input timeslot which is a long string <courtname><timeslotstarthour><timeslotstartminute>. In my code this name is read in a nested for loop with the following snippet [very inefficient, I imagine].
tempreservation=courtname+str(time[0])+str(time[1])
name = self.request.get('tempreservation',None)
The more serious immediate problem is that my def post() code is never read and I cannot figure out why (and maybe it wasn't being read before, either, but I had not tested that far). I wonder if the problem is that for now I want both the post and the get to "finish" the same way. The first line below is for the post() and the second is for the get().
return webapp2.redirect("/read/%s" % location_id)
self.render_template('read.html', {'courts': courts,'location': location, ... etc ...}
My new post() is as follows. Notice I have left in the code the logging.info to see if I ever get there.
class MainPageCourt(BaseHandler):
def post(self, location_id):
logging.info("in MainPageCourt post ")
startTime = self.request.get('startTime')
endTime = self.request.get('endTime')
day = self.request.get('day')
weekday = self.request.get('weekday')
nowweekday = self.request.get('nowweekday')
year = self.request.get('year')
month = self.request.get('month')
nowmonth = self.request.get('nowmonth')
courtnames = self.request.get_all('court')
for c in courtnames:
logging.info("courtname: %s " % c)
times=intervals(startTime,endTime)
for courtname in courtnames:
for time in times:
tempreservation=courtname+str(time[0])+str(time[1])
name = self.request.get('tempreservation',None)
if name:
iden = courtname
court = db.Key.from_path('Locations',location_id,'Courts', iden)
reservation = Reservations(parent=court)
reservation.name = name
reservation.starttime = time
reservation.year = year
reservation.nowmonth = int(nowmonth)
reservation.day = int(day)
reservation.nowweekday = int(nowweekday)
reservation.put()
return webapp2.redirect("/read/%s" % location_id)
Eventually I want to add checking/validating to the above get() code by comparing the existing Reservations data in the datastore with the implied new reservations, and kick out to an alert which tells the user of any potential problems which she can address.
I would also appreciate any comments on these two problems.
end of update 0
My app is for a community tennis court. I want to replace the paper sign up sheet with an online digital sheet that mimics a paper sheet. As unlikely as it seems there may be "transactional" conflicts where two tennis appointments collide. So how do I give the second appointment maker a heads up to the conflict but also give the successful party the opportunity to alter her appointment like she would on paper (with an eraser).
Each half hour is a time slot on the form. People normally sign up for multiple half hours at one time before "submitting".
So in my code within a loop I do a get_all. If any get succeeds I want to give the user control over whether to accept the put() or not. I am still thinking the put() would be an all or nothing, not selective.
So my question is, do I need to make part of the code use an explicit "transaction"?
class MainPageCourt(BaseHandler):
def post(self, location_id):
reservations = self.request.get_all('reservations')
day = self.request.get('day')
weekday = self.request.get('weekday')
nowweekday = self.request.get('nowweekday')
year = self.request.get('year')
month = self.request.get('month')
nowmonth = self.request.get('nowmonth')
if not reservations:
for r in reservations:
r=r.split()
iden = r[0]
temp = iden+' '+r[1]+' '+r[2]
court = db.Key.from_path('Locations',location_id,'Courts', iden)
reservation = Reservations(parent=court)
reservation.starttime = [int(r[1]),int(r[2])]
reservation.year = int(r[3])
reservation.nowmonth = int(r[4])
reservation.day = int(r[5])
reservation.nowweekday = int(nowweekday)
reservation.name = self.request.get(temp)
reservation.put()
return webapp2.redirect("/read/%s" % location_id)
else:
... this important code is not written, pending ...
return webapp2.redirect("/adjust/%s" % location_id)

Have a look at optimistic concurrency control:
http://en.wikipedia.org/wiki/Optimistic_concurrency_control

You can check for the availability of the time slots in a given Court, and write the corresponding Reservations child entities only if their stat_time don't conflict.
Here is how you would do it for 1 single reservation using a ancestor Query:
#ndb.transactional
def make_reservation(court_id, start_time):
court = Court(id=court_id)
existing = Reservation.query(Reservation.start_time == start_time,
ancestor=court.key).fetch(2, keys_only=True)
if len(existing):
return False, existing[0]
return True, Reservation(start_time=start_time, parent=court.key).put()
Alternativly, if you make the slot part of the Reservation id, you can remove the query and construct the Reservation entity keys to check if they already exists:
#ndb.transactional
def make_reservations(court_id, slots):
court = Court(id=court_id)
rs = [Reservation(id=s, parent=court.key) for s in slots]
existing = ndb.get_multi(r.key for r in rs)
if any(existing):
return False, existing
return True, ndb.put_multi(rs)

I think you should always use transactions, but I don't think your concerns are best addressed by transactions.
I think you should implement a two-stage reservation system - which is what you see on most shopping bags and ticketing companies.
Posting the form creates a "reservation request" , which blocks out the time(s) as "in someone else's shopping bag" for 5-15 minutes
Users must submit again on an approval screen to confirm the times. You can give them the ability to update the conflicts on that screen too, and reset the 'reservation lock' on the timeslots as long as possible.
A cronjob - or a faked one that is triggered by a request coming in at a certain window - clears out expired reservation locks and returns the times back to the pool of available slots.

Related

Short description isn't working for delete selected (delete_selected.short_description isn't changing the name)

I am currently trying to change the name of the "Delete Selected" admin action. I have already effectively override the default (so I can store some data before completely deleting it), but now I want to change the option from the vague "Deleted selected" to something more specific like "Deleted all selected registrations." Or, at least, for it to say, "Deleted selected registrations" like it did before I overwrote the function.
I have so far tried this:
delete_selected.short_description = 'Delete all selected registrations'
But the option is still "Deleted selected." Is there a way to fix this?
Here's my code:
def delete_selected(modeladmin, request, queryset):
"""
This overrides the defult deleted_selected because we want to gather the data from the registration and create a
DeletedRegistration object before we delete it.
"""
for registration in queryset:
reg = registration.get_registrant()
if registration.payment_delegation:
delegate_name = registration.payment_delegation.name
delegate_email = registration.payment_delegation.email
else:
delegate_name = None
delegate_email = None
registration_to_delete = DeletedRegistration.objects.create(
registrant_name = reg.full_name(),
registrant_email = reg.email,
registrant_phone_num = reg.phone,
delegate_name = delegate_name,
delegate_email = delegate_email,
# Filtering out people (with True) who couldn't participate in events because we are only interested in the people
# we had to reserve space and prepare materials for.
num_of_participants = registration.get_num_party_members(True),
special_event = registration.sibs_event,
)
registration.delete()
delete_selected.short_description = 'Delete all selected registrations'
edit: just tried delete_selected.list_display that didn't work either
You can't have it in the function, so I just had to tab it back one space and it worked.
example:
def delete_selected(modeladmin, request, queryset)
code
delete_selected.short_description = "preferred name"
thanks.

Django annotate model and filter for specific value

I'm trying to get into djangos annotate, but can't quite figure out how it works exactly.
I've got a function where I'd like to annotate a queryset of customers, filter them and return the number of customers
def my_func(self):
received_signatures = self.customer_set.annotate(Count('registrations').filter().count()
Now for the filter part, thats where I have a problem figuring out how to do that. The thing I'd like to filter for is my received_signatures, which is a function that is being called in my customer.py
def received_signatures(self):
signatures = [reg.brought_signature for reg in self.registrations.all() if reg.status == '1_YES']
if len(signatures):
return all(signatures)
else:
return None
brough_signature is a DB Field
So how can I annotate the queryset, filter for the received_signatures and then return a number?
Relevant Model Information:
class Customer(models.Model):
brought_signature = models.BooleanField(u'Brought Signature', default=False)
class Registration(models.Model):
brought_signature = models.BooleanField(u'Brought Signature', default=False)
status = models.CharField(u'Status', max_length=10, choices=STATUS_CHOICES, default='4_RECEIVED')
Note: A participant and a registration can have brought_signature. I have a setting in my program which allows me to either A) mark only brought_signature at my participant (which mean he brought the signature for ALL his registrations) or B) mark brought_signature for every registration he has
For this case Option B) is relevant. With my received_signatures I check if the customer has brought every signature for every registration where his status is "1_YES" and I want to count all the customers who did so and return a number (which I then use in another function for a pygal chart)
If I understand it correctly, you want to check if all the Registrations for a given Customer with status == '1_YES should have as attribute .brought_signature = True, and there should be at least such value. There are several approaches for this.
We can do this by writing it like:
received_signatures = self.customer_set.filter(
registration__status='1_YES'
).annotate(
minb=Min('registration__brought_signature')
).filter(
minb__gt=0
).count()
So what we here do is first .filter(..) on the registrations that have as status 1_YES, next we calculate for every customer a value minb that is the minimum of brought_signature of these Registrations. So in case one of the brought_signatures of the related Registrations is False (in a database that is usually 0), then Min(..) is False as well. In case all brought_signatures are True (in a database that is usually 1), then the result is 1, we can then filter on the fact that minb should thus be greater than 0.
So Customers with no Registration will not be counted, Customers with no Registration with status 1_YES, will not be counted, Customers with Registrations for which there is a Registration with status 1_YES, but with brough_signature will not be counted. Only Customers for which all Registrations that have status 1_YES (not per se all Registrations) have brough_signature = True are counted.

Flask/Python denying existing logic conditionally

I got two types of registration and can't figure out what to if by accident user selects both. Basically I want to prioritise one of the logic in case user have both the options. Following is the explanation and conditions I am trying to code.
User can register with the schools allowed to register free.
User can also register if he/she has the coupon.
If user's school is in list and user has coupon then he should be registered on behalf of university and coupon will not be used by backend.
my_school = form.university.data
waiverlist = ['A', 'B', 'C']
if my_school in waiverlist:
package = Package(
student_id=profile_data.id,
stripe_id = 'N/A For Group Subscriber',
student_email= profile_data.email,
is_active=True,
package_type='PartnerSubscription',
subscription_id='N/A For Group Subscriber'
)
dbase.session.add(package)
dbase.session.commit()
cp = Coupons.query.filter_by(coupon=Coupons.coupon).first()
if cp:
mycoupon = form.coupon.data
print mycoupon
print cp.coupon
if form.coupon.data==cp.coupon:
package = Package(
student_id=profile_data.id,
stripe_id = 'N/A For Group Subscriber',
student_email= profile_data.email,
is_active=True,
package_type='GroupSubsciption',
subscription_id='N/A For Group Subscriber'
)
dbase.session.add(package)
dbase.session.commit()
return redirect('/profile')
With above code it creates two database entries. Actually i tried with elif but couldn't make it work.
Please advise.
What about simply making the coupon check if cp and my_school not in waiverlist
Or even better make both conditional blocks of code into functions. Then call the coupon function only with a condition check that will return a call to the school function:
# I don't know what other variables you'd need, so kwargs
def register_with_coupon(school, **kwargs):
if school in waiver_list:
return register_with_school()
#insert with coupon

Using yield with multiple ndb.get_multi_async

I am trying to improve efficiency of my current query from appengine datastore. Currently, I am using a synchronous method:
class Hospital(ndb.Model):
name = ndb.StringProperty()
buildings= ndb.KeyProperty(kind=Building,repeated=True)
class Building(ndb.Model):
name = ndb.StringProperty()
rooms= ndb.KeyProperty(kind=Room,repeated=True)
class Room(ndb.Model):
name = ndb.StringProperty()
beds = ndb.KeyProperty(kind=Bed,repeated=True)
class Bed(ndb.Model):
name = ndb.StringProperty()
.....
Currently I go through stupidly:
currhosp = ndb.Key(urlsafe=valid_hosp_key).get()
nbuilds = ndb.get_multi(currhosp.buildings)
for b in nbuilds:
rms = ndb.get_multi(b.rooms)
for r in rms:
bds = ndb.get_multi(r.beds)
for b in bds:
do something with b object
I would like to transform this into a much faster query using get_multi_async
My difficulty is in how I can do this?
Any ideas?
Best
Jon
using the given structures above, it is possible, and was confirmed that you can solve this with a set of tasklets. It is a SIGNIFICANT speed up over the iterative method.
#ndb.tasklet
def get_bed_info(bed_key):
bed_info = {}
bed = yield bed_key.get_async()
format and store bed information into bed_info
raise ndb.Return(bed_info)
#nbd.tasklet
def get_room_info(room_key):
room_info = {}
room = yield room_key.get_async()
beds = yield map(get_bed_info,room.beds)
store room info in room_info
room_info["beds"] = beds
raise ndb.Return(room_info)
#ndb.tasklet
def get_building_info(build_key):
build_info = {}
building = yield build_key.get_async()
rooms = yield map(get_room_info,building.rooms)
store building info in build_info
build_info["rooms"] = rooms
raise ndb.Return(build_info)
#ndb.toplevel
def get_hospital_buildings(hospital_object):
buildings = yield map(get_building_info,hospital_object.buildings)
raise ndb.Return(buildings)
Now comes the main call from the hospital function where you have the hospital object (hosp).
hosp_info = {}
buildings = get_hospital_buildings(hospital_obj)
store hospital info in hosp_info
hosp_info["buildings"] = buildings
return hosp_info
There you go! It is incredibly efficient and lets the schedule complete all the information in the fastest possible manner within the GAE backbone.
You can do something with query.map(). See https://developers.google.com/appengine/docs/python/ndb/async#tasklets and https://developers.google.com/appengine/docs/python/ndb/queryclass#Query_map
Its impossible.
Your 2nd query (ndb.get_multi(b.rooms)) depends on the result of your first query.
So pulling it async dosnt work, as at this point the (first) result of the first query has to be avaiable anyway.
NDB does something like that in the background (it allready buffers the next items of ndb.get_multi(currhosp.buildings) while you process the first result).
However, you could use denormalization, i.e. keeping a big table with one entry per Building-Room-Bed pair, and pull your results from that table.
If you have more reads than writes to this table, this will get you a massive speed improvement (1 DB read, instead of 3).

Buying many products at once from a webshop

It's quite simple to program just one product to get sold via my payment system (api.payson.se) but buying many products at the same time in various amounts posed trouble for me since it was not implemented and I didn't have a good idea how to do it. Now I have a solution that I just put together which works but the modelling and control flow is kind of very quick and dirty and I wonder whether this is even acceptable or should need a rewrite. The system now behaves so that I can enter the shop (step 1) and enter the amounts for the products I want to buy
Then if I press Buy ("Köp") my Python calculates the sum correctly and this works whatever combination of amounts and products I have saying which the total is and this page could also list the specification but that is not implemented yet:
The total sum is Swedish currency is correct and it has written an order to my datastore with status "unpaid" and containing which products are ordered and what amount for every product in the datastore:
The user can then either cancel the purchase or go on and actually pay through the payment system api.payson.se:
So all I need to do is listen to the response from Payson and update the status of the orders that get paid. But my solution does not look very clean and I wonder if I can go on with code like that, the data model is two stringlists, one with the amounts and one with which product (Item ID) since that was the easiest way I could solve it but it is then not directly accessible and only from the lists. Is there a better data model I can use?
The code that does the handling is slightly messy and could use a better data model and a better algorithm than just strings and lists:
class ShopHandler(NewBaseHandler):
#user_required
def get(self):
user = \
auth_models.User.get_by_id(long(self.auth.get_user_by_session()['user_id'
]))
self.render_jinja('shop.htm', items=Item.recent(), user=user)
return ''
#user_required
def post(self, command):
user = \
auth_models.User.get_by_id(long(self.auth.get_user_by_session()['user_id'
]))
logging.info('in shophandler http post item id'+self.request.get('item'))
items = [ self.request.get('items[1]'),self.request.get('items[2]'),self.request.get('items[3]'),self.request.get('items[4]'),self.request.get('items[5]'),self.request.get('items[6]'),self.request.get('items[7]'),self.request.get('items[8]') ]
amounts = [ self.request.get('amounts[1]'),self.request.get('amounts[2]'),self.request.get('amounts[3]'),self.request.get('amounts[4]'),self.request.get('amounts[5]'),self.request.get('amounts[6]'),self.request.get('amounts[7]'),self.request.get('amounts[8]') ]
total = 0
total = int(self.request.get('amounts[1]'))* long(Item.get_by_id(long(self.request.get('items[1]'))).price_fraction()) if self.request.get('amounts[1]') else total
total = total + int(self.request.get('amounts[2]'))* long(Item.get_by_id(long(self.request.get('items[2]'))).price_fraction()) if self.request.get('amounts[2]') else total
total = total + int(self.request.get('amounts[3]'))* long(Item.get_by_id(long(self.request.get('items[3]'))).price_fraction()) if self.request.get('amounts[3]') else total
total = total + int(self.request.get('amounts[4]'))* long(Item.get_by_id(long(self.request.get('items[4]'))).price_fraction()) if self.request.get('amounts[4]') else total
total = total + int(self.request.get('amounts[5]'))* long(Item.get_by_id(long(self.request.get('items[5]'))).price_fraction()) if self.request.get('amounts[5]') else total
total = total + int(self.request.get('amounts[6]'))* long(Item.get_by_id(long(self.request.get('items[6]'))).price_fraction()) if self.request.get('amounts[6]') else total
total = total + int(self.request.get('amounts[7]'))* long(Item.get_by_id(long(self.request.get('items[7]'))).price_fraction()) if self.request.get('amounts[7]') else total
total = total + int(self.request.get('amounts[8]'))* long(Item.get_by_id(long(self.request.get('items[8]'))).price_fraction()) if self.request.get('amounts[8]') else total
logging.info('total:'+str(total))
trimmed = str(total)+',00'
order = model.Order(status='UNPAID')
order.items = items
order.amounts = amounts
order.put()
logging.info('order was written')
ExtraCost = 0
GuaranteeOffered = 2
OkUrl = 'http://' + self.request.host + r'/paysonreceive/'
Key = '3110fb33-6122-4032-b25a-329b430de6b6'
text = 'niklasro#gmail.com' + ':' + str(trimmed) + ':' + str(ExtraCost) \
+ ':' + OkUrl + ':' + str(GuaranteeOffered) + Key
m = hashlib.md5()
BuyerEmail = user.email
AgentID = 11366
self.render_jinja('order.htm', order=order, user=user, total=total, Generated_MD5_Hash_Value = hashlib.md5(text).hexdigest(), BuyerEmail=user.email, Description='Bnano Webshop', trimmed=trimmed, OkUrl=OkUrl, BuyerFirstName=user.firstname, BuyerLastName=user.lastname)
My model for the order, where not all fields are used, is
class Order(db.Model):
'''a transaction'''
item = db.ReferenceProperty(Item)
items = db.StringListProperty()
amounts = db.StringListProperty()
owner = db.UserProperty()
purchaser = db.UserProperty()
created = db.DateTimeProperty(auto_now_add=True)
status = db.StringProperty( choices=( 'NEW', 'CREATED', 'ERROR', 'CANCELLED', 'RETURNED', 'COMPLETED', 'UNPAID', 'PAID' ) )
status_detail = db.StringProperty()
reference = db.StringProperty()
secret = db.StringProperty() # to verify return_url
debug_request = db.TextProperty()
debug_response = db.TextProperty()
paykey = db.StringProperty()
shipping = db.TextProperty()
And the model for a product ie an item is
class Item(db.Model):
'''an item for sale'''
owner = db.UserProperty() #optional
created = db.DateTimeProperty(auto_now_add=True)
title = db.StringProperty(required=True)
price = db.IntegerProperty() # cents / fractions, use price_decimal to get price in dollar / wholes
image = db.BlobProperty()
enabled = db.BooleanProperty(default=True)
silver = db.IntegerProperty() #number of silver
def price_dollars( self ):
return self.price / 100.0
def price_fraction( self ):
return self.price / 100.0
def price_silver( self ): #number of silvers an item "is worth"
return self.silver / 1000.000
def price_decimal( self ):
return decimal.Decimal( str( self.price / 100.0 ) )
def price_display( self ):
return str(self.price_fraction()).replace('.',',')
#staticmethod
def recent():
return Item.all().filter( "enabled =", True ).order('-created').fetch(10)
I think you now have an idea what's going on and that this kind of works towards the user but the code is not looking good. Do you think I can leave the code like this and go on and keep this "solution" or must I do a rewrite to make it more proper? There are only 8 products in the store and with this solution it becomes difficult to add a new Item for sale since then I must reprogram the script which is not perfect.
Could you comment or answer, I'd be very glad to get some feedback about this quick and dirty solution to my use case.
Thank you
Update
I did a rewrite to allow for adding new products and the following seems better than the previous:
class ShopHandler(NewBaseHandler):
#user_required
def get(self):
user = \
auth_models.User.get_by_id(long(self.auth.get_user_by_session()['user_id'
]))
self.render_jinja('shop.htm', items=Item.recent(), user=user)
return ''
#user_required
def post(self, command):
user = \
auth_models.User.get_by_id(long(self.auth.get_user_by_session()['user_id'
]))
logging.info('in shophandler http post')
total = 0
order = model.Order(status='UNPAID')
for item in self.request.POST:
amount = self.request.POST[item]
logging.info('item:'+str(item))
purchase = Item.get_by_id(long(item))
order.items.append(purchase.key())
order.amounts.append(int(amount))
order.put()
price = purchase.price_fraction()
logging.info('amount:'+str(amount))
logging.info('product price:'+str(price))
total = total + price*int(amount)
logging.info('total:'+str(total))
order.total = str(total)
order.put()
trimmed = str(total).replace('.',',') + '0'
ExtraCost = 0
GuaranteeOffered = 2
OkUrl = 'http://' + self.request.host + r'/paysonreceive/'
Key = '6230fb54-7842-3456-b43a-349b340de3b8'
text = 'niklasro#gmail.com' + ':' + str(trimmed) + ':' \
+ str(ExtraCost) + ':' + OkUrl + ':' \
+ str(GuaranteeOffered) + Key
m = hashlib.md5()
BuyerEmail = user.email # if user.email else user.auth_id[0]
AgentID = 11366
self.render_jinja(
'order.htm',
order=order,
user=user,
total=total,
Generated_MD5_Hash_Value=hashlib.md5(text).hexdigest(),
BuyerEmail=user.email,
Description='Bnano Webshop',
trimmed=trimmed,
OkUrl=OkUrl,
BuyerFirstName=user.firstname,
BuyerLastName=user.lastname,
)
Man, this is a really strange code. If you will want to add new items in you shop you must rewrite you shop's script.
At the first unlink your items from interface, you must send POST request to controller with your items ids and quantity, i don know how work gae request object, but it must be like that:
from your order page make POST request with dict of items which really need {"item_id":"qnt"}.
When in the controller you can fetch all objects like:
for item, qnt in request.POST:
{do something with each item, for example where you can sum total}
and etc
Don't link controllers with your interfaces directly. You must write more abstraction code, if you want make really flexible app.
I'm going to try to focus on one very obvious problem with your code, but there are lots of problems with it that I'm not going to get into. My advice is to stop right now. You're implementing a web-based payment system. You really should leave that to people with more skills and experience. "Web-based" is a pretty difficult thing to get right whilst ensuring security, but an online payment system is the sort of thing that well-paid consultants with decades of experience are well-paid for, and they still manage to get it wrong pretty often. You're opening yourself up to a lot of legal liability.
If you're still dead set on it, please read The Python Tutorial cover to cover, possibly several times. Python is a very different language to whatever classical OOP language you're mentally cramming into it. After that, at least leaf through the other documentation. If you're having trouble with these, pick up an O'Reilly book on Python; approaching it from another angle should help. After you done all this (and maybe at the same time), write as much code as you can that is not going to get you sued into oblivion if you do it wrong. Then maybe you can write an order/payment system.
I'm sorry if this sounds harsh, but the world doesn't need any more shoddy web stores; 1999 took care of that for us.
Anyway, on to your code :D When you write something repetitive and copy-pasted like this:
items = [ self.request.get('items[1]'),self.request.get('items[2]'),self.request.get('items[3]'),self.request.get('items[4]'),self.request.get('items[5]'),self.request.get('items[6]'),self.request.get('items[7]'),self.request.get('items[8]') ]
You should be thinking to yourself, "Wait a second! Repetitive task are exactly what computers are designed to do." You could get your text editor to do it (see Vim Macros), but concise (but not too concise ;) code is always better than long code, since you make it faster to maintain, less prone to programmer error, and easier to debug, not to mention the amount of time you save not copying and pasting, so let's improve the code.
Here's how I would revise this in Python (advanced programmers do this in their heads, or just skip to the end):
#1. with a for loop
MAX_ITEMS = 8
items = []
for i in range(MAX_ITEMS):
items.append(self.request.get('items[{}]'.format(i + 1))
#2. with a list comprehension
MAX_ITEMS = 8
items = [self.request.get('items[{}]'.format(i + 1)) for i in range(MAX_ITEMS)]
Actually, having a limit to the number of items is rather amateurish and will only frustrate your users. You can fix it like this:
items = []
i = 0
while True:
try:
items.append(self.request[i + 1]) #attempt to get the next item
except IndexError as exc: #but if it fails...
break #we must be at the last one
i += 1
I think this is the way you should leave it for now because it's clear but not repetitive. However, you could shorten it even further using functions from the itertools module.
A few quick tips:
Avoid string concatenation, especially where user-supplied strings and especially especially when user-supplied string from over the web are concerned. Use str.format and "%d" % (5,) modulus string formatting. BONUS: You don't have to convert everything to strings!
Get those constants (e.g., ExtraCost = 2) out of the middle and put them somewhere safe (at the top of the module, or in a special file in the package)
You trust the user way too much: At for item in self.request.POST:, you're assuming everything in the request is going to be an item, and you do zero validation.
Please, please, please. Never turn off autocomplete. I really don't know why that attribute exists, except to annoy.

Categories