2015-05-07 3 views
0

Я действительно пытаюсь обновить количество билетов. Мой сайт (проект) имитирует сайт электронной коммерции, который продает билеты на музыкальные мероприятия.Как обновить количество? Django

Когда пользователь покупает билет, количество билетов должно обновляться. Пример: 100 билетов доступны и 5 куплены, db должен сказать 95 оставшихся. Но если билеты идут ниже 0, тогда должно появиться предупреждение «Больше билетов на это мероприятие».

Может ли кто-нибудь помочь мне реализовать это? Это последний бит, который мне нужно сделать, и я закончил! Приветствия. Код:

Емкость в классе 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://stackoverflow.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") 
+0

Чтобы быть честным, это зависит whather пользователь может приобрести более, что 1 билет с одной и той же операции или нет. Если пользователь может купить только один билет за раз, а 2 пользователя пытаются купить последний билет, тогда вы должны начать резервирование последнего билета для пользователя, который сначала начал транзакцию с истечением срока действия сессии. Если пользователь покупает билет, то нет больше билетов должно быть доступно. –

+0

Если пользователь может купить только один билет, вы должны разрешить только максимум, чтобы сказать «5» в момент транзакции, и он должен быть зарезервирован таким же образом. Вы можете выпустить билеты без конкурса после окончания сессии. –

+0

Количество доступных билетов должно быть жестко запрограммировано или сохранено в файле или в поле базы данных. Допустим, есть 100 билетов.В вашем коде должна быть атомная транзакция, которая сохранит покупку и вычитает количество билетов до тех пор, пока они будут доступны. В случае IntegrityError он должен откатить изменения –

ответ

0

На ваш взгляд, после формы валидаций вы должны сделать что-то вроде этого:

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 

Проверьте django transa ctions для получения дополнительной информации: https://docs.djangoproject.com/en/1.8/topics/db/transactions/

+0

В какую форму вы относитесь? – user4584967

0

Вы хотите избежать условий гонки. Транзакции - это только первый шаг в этом.

Первое, что вы хотели бы сделать в своей транзакции, - это получить блокировку в строке, которую вы меняете. Это предотвращает чтение устаревшего значения, когда вы пишете в базу данных. Это может быть сделано с помощью оператора обновления, который заблокирует строку из дальнейших обновлений, пока транзакция не будет зафиксирована или откат:

with transaction.atomic(): 
    Event.objects.filter(id=5).update(quantity=F('quantity') - x) 

Если у вас есть замок, и сделал обновление, проверьте целостность данных по-прежнему (т. е. емкость не ниже 0). Если, по-прежнему, в противном случае, откатить транзакцию, вызывая исключение:

event = Event.objects.get(id=5) 
if event.capacity < 0: 
    raise ValueError("Capacity exceeded") 

Если есть осталось достаточно места, вы будете выйти из atomic() блок в этой точке, то сделка совершается, и другие запросы могут приобретите блокировку и попробуйте обновить значение. Если емкость недостаточна, транзакция откатывается назад, а другие запросы никогда не узнают, что значение изменилось, поскольку они ожидали получить блокировку.

Теперь, чтобы поставить все это вместе:

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) 
+0

В какой файл входит этот код? @knbk – user4584967

+0

Я бы предположил, что это метод на модели, но на самом деле ограничений нет. Чтобы получить цитату из документов из контекста: «Это ваш шанс действительно выставлять напоказ свой индивидуализм». – knbk

+0

@knbk Будет ли он работать с mongoDB, который не поддерживает транзакции? –