I'm really struggling to update the quantity of a ticket. My website (project) mimics a e-commerce site which sells tickets to for music events.
我真的很难更新机票的数量。我的网站(项目)模仿了一个销售音乐活动门票的电子商务网站。
When a user buys ticket, the ticket quantity should update. Example: 100 tickets available and 5 are bought, the db should say 95 remaining. But if the tickets goes below 0 then a warning should appear "No more tickets for this event".
当用户购买票证时,票证数量应该更新。示例:100个可用票据和5个已购买,db应该说剩余95个。但如果门票低于0,则会出现“不再有此活动门票”的警告。
Can somebody help me implement this? This is the last bit I need to do then I've finished!! Cheers. Code:
有人可以帮我实现吗?这是我需要做的最后一点,然后我就完成了!!干杯。码:
The capacity in the Venue class is what I'm referring to when I say it needs to be updated.
当我说它需要更新时,Venue类的容量就是我所指的。
Models.py
class Genre(models.Model):
genre = models.CharField(max_length=30, default=None, unique=True)
def __str__(self):
return self.genre
class Meta:
ordering = [ "genre" ]
verbose_name_plural = "genres"
class CardChoices(models.Model):
payment_type = models.CharField(max_length=30, default=None, unique=True)
def __str__(self):
return self.payment_type
class Meta:
ordering = [ "payment_type" ]
verbose_name_plural = "payment types"
### Start
class Venue(models.Model):
"""The 'Venue' model represents a collection of different venues."""
name = models.CharField(max_length=30, default=None)
address = models.CharField(max_length=255, default=None)
capacity = models.PositiveIntegerField(default=0)
website = models.URLField(max_length=50, null=True)
phone_number = models.CharField(validators=[
RegexValidator(regex='^\d{11}$', message='Length has to be 11',
code='Invalid number')], blank=True, null=True, max_length=11)
#phone_number = models.IntegerField(max_length=11, unique=True, validators=[RegexValidator(regex='^\d{10}$', message='Length has to be 11', code='Invalid number')], default=None)
description = models.TextField()
slug = models.SlugField(unique=True, default=None)
# Returns the name of a category
def __str__(self):
return self.name
# Call the slugify method and update the slug field when the name is changed
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Venue, self).save(*args, **kwargs)
class Meta:
# Orders the categories using the name field in ascending order
ordering = [ "name" ]
verbose_name_plural = "venues"
class Artist(models.Model):
"""The 'Artist' model represents a collection of different artists."""
name = models.CharField(max_length=30, default=None)
description = models.CharField(max_length=255)
image = models.ImageField(upload_to='artist/images')
genre = models.ForeignKey('Genre', related_name='genres', default=None)
slug = models.SlugField(unique=True, default=None)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Artist, self).save(*args, **kwargs)
class Meta:
ordering = [ "name" ]
verbose_name_plural = "artists"
class Event(models.Model):
"""The 'Event' model represents a collection of different events."""
artists = models.ManyToManyField(Artist)
venue = models.ForeignKey(Venue)
name = models.CharField(max_length=255, default=None)
quantity = models.IntegerField()
price = models.IntegerField() # Needs to be repeated so that django-carton can convert the value (dirty hack needs fixing)
#price = PriceField('Price', currency='GBP', max_digits=10, decimal_places=2)
date = models.DateField()
start_time = models.TimeField()
end_time = models.TimeField()
curfew_time = models.TimeField()
description = models.TextField()
slug = models.SlugField(unique=True, default=None)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Event, self).save(*args, **kwargs)
class Meta:
ordering = [ "name" ]
verbose_name_plural = "events"
class UserAccount(models.Model): # needs to be associated with INDIVIDUAL purchases, a user can hold more than one purchase
user = models.OneToOneField(User) # Links user account with a Django User model instance
#event = models.ForeignKey(Event, blank=True)
name = models.CharField(max_length=30)
address = models.CharField(max_length=255)
phone_number = models.IntegerField(max_length=11, unique=True, validators=[RegexValidator(regex='^\d{10}$', message='Length has to be 11', code='Invalid number')])
email = models.EmailField(validators=[validate_email])
def __str__(self):
return self.user.email
class Purchase(models.Model): # needs to be associated with a user account
cash_only = models.BooleanField('Cash only', default=False)
payment_type = models.ForeignKey('CardChoices', related_name='Payment Types')
card_name = models.CharField(max_length=26, default=None, validators=[alphanumeric_RE]) #done
card_number = models.CharField(max_length=19, default=None) # OVERWRITTEN
security_code = models.IntegerField(max_length=3, default=None) # OVERWRITTEN
expiry_date = models.DateField(default=datetime.now) # OVERWRITTEN
date = models.DateField(editable=True, auto_now_add=True, default=datetime.now)
delivery_option = models.BooleanField('Is Collecting Ticket', default=True)
reference_number = models.CharField(max_length=LENGTH, unique=True, default=None)
temp_session_key = models.CharField(null=True, editable=False, max_length=255, default=None)
def __str__(self):
return self.card_name
def save(self, *args, **kwargs): # Thanks to workmajj for providing the logic!
"""
Upon saving, generate a code by randomly picking LENGTH number of
characters from CHARSET and concatenating them. If code has already
been used, repeat until a unique code is found, or fail after trying
MAX_TRIES number of times. (This will work reliably for even modest
values of LENGTH and MAX_TRIES, but do check for the exception.)
Discussion of method: http://*.com/questions/2076838/
"""
loop_num = 0
unique = False
while not unique:
if loop_num < MAX_TRIES:
new_code = ''
for i in range(LENGTH):
new_code += CHARSET[randrange(0, len(CHARSET))]
if not Purchase.objects.filter(reference_number=new_code):
self.reference_number = new_code
unique = True
loop_num += 1
else:
raise ValueError("Couldn't generate a unique code.")
super(Purchase, self).save(*args, **kwargs)
class Meta:
ordering = [ "date" ]
Forms.py
class ContactForm(forms.Form):
"""Form for the contact page containing relevant fields and appropriate attributes."""
name = forms.CharField(required=True, max_length=100, label='Name')
email = forms.EmailField(required=True, label='E-mail Address', validators=[validate_email])
subject = forms.CharField(required=True, label='Subject')
query = forms.CharField(required=True, widget=forms.Textarea)
class PurchaseForm(forms.ModelForm):
# Custom fields overwrite the ones in the Purchase model
payment_type = forms.ModelChoiceField(queryset=CardChoices.objects.all())
card_name = forms.CharField(widget=forms.TextInput(attrs={'placeholder': "Card Holder's Name"}))
card_number = CreditCardField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Credit / Debit card number'}))
security_code = VerificationValueField(required=True)
expiry_date = ExpiryDateField(required=True)
DELIVERY_CHOICES = (
(True, 'Collect from Venue'),
(False, 'Print Ticket'),
)
delivery_option = forms.ChoiceField(choices=DELIVERY_CHOICES, widget=forms.RadioSelect())
email_receipt = forms.BooleanField(required=False, label='Tick this box to receive an e-mail receipt')
email = forms.EmailField(required=False, label='E-mail address', validators=[validate_email])
class Meta:
model = Purchase
fields = ("payment_type", "card_name", "card_number", "security_code", "expiry_date", "delivery_option", "email_receipt", "email")
class UserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput())
class Meta:
model = User
fields = ("username", "email", "password")
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserAccount
fields = ("name", "address", "phone_number")
2 个解决方案
#1
On your view after form validations you should do something like this :
在表单验证后的视图中,您应该执行以下操作:
try:
with transaction.atomic():
x=get_number_of_tickets
venue=Venue.objects.get(id=5)
venue.capacity -= x
venue.update()
Purchase.save()
except IntegrityError:
raise Error
check the django transactions documentation for more info as its more complete: https://docs.djangoproject.com/en/1.8/topics/db/transactions/
检查django事务文档以获取更多信息,因为它更完整:https://docs.djangoproject.com/en/1.8/topics/db/transactions/
#2
You'd want to avoid race conditions. Transactions are only a first step in this.
你想要避免竞争条件。交易只是其中的第一步。
The first thing you'd want to do in your transaction is to acquire a lock on the row you're changing. This prevents that you read a value that is outdated by the time you write to the database. This can be done with an update statement, that will lock the row from further updates until the transaction is committed or rolled back:
您在事务中要做的第一件事就是获取对正在更改的行的锁定。这可以防止您在写入数据库时读取过时的值。这可以通过update语句完成,该语句将锁定行以进行进一步更新,直到提交或回滚事务:
with transaction.atomic():
Event.objects.filter(id=5).update(quantity=F('quantity') - x)
When you have the lock, and done the update, check if data integrity is still intact (i.e. the capacity isn't lower than 0). If it is, continue, otherwise, rollback the transaction by raising an exception:
当您拥有锁并完成更新时,请检查数据完整性是否仍然完好(即容量不低于0)。如果是,则继续,否则,通过引发异常来回滚事务:
event = Event.objects.get(id=5)
if event.capacity < 0:
raise ValueError("Capacity exceeded")
If there are enough places left, you'll exit the atomic()
block at this point, the transaction is committed, and other requests can acquire the lock and try to update the value. If there isn't enough capacity, the transaction is rolled back, and other requests will never have known the value has changed, as they've been waiting to get the lock.
如果剩下足够的位置,此时您将退出atomic()块,提交事务,其他请求可以获取锁并尝试更新该值。如果没有足够的容量,则事务将被回滚,而其他请求将永远不会知道值已更改,因为它们一直在等待获取锁定。
Now, to put it all together:
现在,把它们放在一起:
from django.db import transaction
from django.db.models import F
def get_tickets(event_id, num_tickets):
with transaction.atomic():
Event.objects.filter(id=event_id).update(quantity=F('quantity') - num_tickets)
if Event.objects.get(id=venue_id).quantity < 0:
raise ValueError("Capacity exceeded: less than %s tickets left." % num_tickets)
#1
On your view after form validations you should do something like this :
在表单验证后的视图中,您应该执行以下操作:
try:
with transaction.atomic():
x=get_number_of_tickets
venue=Venue.objects.get(id=5)
venue.capacity -= x
venue.update()
Purchase.save()
except IntegrityError:
raise Error
check the django transactions documentation for more info as its more complete: https://docs.djangoproject.com/en/1.8/topics/db/transactions/
检查django事务文档以获取更多信息,因为它更完整:https://docs.djangoproject.com/en/1.8/topics/db/transactions/
#2
You'd want to avoid race conditions. Transactions are only a first step in this.
你想要避免竞争条件。交易只是其中的第一步。
The first thing you'd want to do in your transaction is to acquire a lock on the row you're changing. This prevents that you read a value that is outdated by the time you write to the database. This can be done with an update statement, that will lock the row from further updates until the transaction is committed or rolled back:
您在事务中要做的第一件事就是获取对正在更改的行的锁定。这可以防止您在写入数据库时读取过时的值。这可以通过update语句完成,该语句将锁定行以进行进一步更新,直到提交或回滚事务:
with transaction.atomic():
Event.objects.filter(id=5).update(quantity=F('quantity') - x)
When you have the lock, and done the update, check if data integrity is still intact (i.e. the capacity isn't lower than 0). If it is, continue, otherwise, rollback the transaction by raising an exception:
当您拥有锁并完成更新时,请检查数据完整性是否仍然完好(即容量不低于0)。如果是,则继续,否则,通过引发异常来回滚事务:
event = Event.objects.get(id=5)
if event.capacity < 0:
raise ValueError("Capacity exceeded")
If there are enough places left, you'll exit the atomic()
block at this point, the transaction is committed, and other requests can acquire the lock and try to update the value. If there isn't enough capacity, the transaction is rolled back, and other requests will never have known the value has changed, as they've been waiting to get the lock.
如果剩下足够的位置,此时您将退出atomic()块,提交事务,其他请求可以获取锁并尝试更新该值。如果没有足够的容量,则事务将被回滚,而其他请求将永远不会知道值已更改,因为它们一直在等待获取锁定。
Now, to put it all together:
现在,把它们放在一起:
from django.db import transaction
from django.db.models import F
def get_tickets(event_id, num_tickets):
with transaction.atomic():
Event.objects.filter(id=event_id).update(quantity=F('quantity') - num_tickets)
if Event.objects.get(id=venue_id).quantity < 0:
raise ValueError("Capacity exceeded: less than %s tickets left." % num_tickets)