I'm working on my first django project that is a sports betting game.
Here are my models:
class Game(models.Model):
home_team = models.CharField(max_length=200)
away_team = models.CharField(max_length=200)
home_goals = models.IntegerField(default=None)
away_goals = models.IntegerField(default=None)
class Bet(models.Model):
gameid = models.ForeignKey(Game, on_delete=models.CASCADE)
userid = models.ForeignKey(User, on_delete=models.CASCADE)
home_goals = models.IntegerField()
away_goals = models.IntegerField()
score = models.IntegerField(default=None, null=True)
First I create a game instance with null values in goals fields, then users make their bets. Once the game is over, I update game goals fields. Now I need to assign points for each user like this:
WHEN bet.home_goals = game.home_goals AND bet.away_goals = game.away_goals THEN 2
WHEN game.home_goals > game.away_goals AND bet.home_goals > bet.away_goals THEN 1
WHEN game.home_goals < game.away_goals AND bet.home_goals < bet.away_goals THEN 1
WHEN bet.home_goals = bet.away_goals AND game.home_goals = game.away_goals THEN 1
ELSE 0
It seems that I should create a POST_SAVE singal to update Bet.score for each user based on update of Game.home_goals and Game.away_goals? But I have no idea how to do this
I would recommend staying away from Signals. Generally speaking, you should use Signals when:
In your case, only the Bet
model is interested in the Game
save/change event. You have direct access to the Game
class.
I'm saying that because signals tend to "hide" code/business logic of your application, making the maintenance harder (because it's not immediately obvious you have some code being executed).
For me it looks like a job for a regular view, where you would add the score of the game and "close" it. Maybe an extra field (could be a BooleanField
or DateTimeField
) to indicate that the Game
is over.
See an example below:
forms.py
from .models import Game
from django import forms
from django.db import transaction
class GameForm(forms.ModelForm):
class Meta:
model = Game
fields = ('home_goals', 'away_goals')
# do everything inside the same database transaction to make sure the data is consistent
@transaction.atomic
def save(self):
game = super().save(commit=True)
for bet in game.bet_set.all():
if bet.home_goals == game.home_goals and bet.away_goals == game.away_goals:
bet.score = 2
elif <build_your_logic_here>:
bet.score = 1
else:
bet.score = 0
bet.save()
return game
views.py
from django.shortcuts import redirect
from .forms import GameForm
def end_game(request, game_id):
game = Game.objects.get(pk=game_id)
if request.method == 'POST':
form = GameForm(request.POST, instance=game)
if form.is_valid():
form.save()
return redirect('/gameboard/') # add here the relevant url where to send the user
else:
form = GameForm(instance=game)
return render(request, 'game_form.html', {'form': form})
If for some reason, the score change event happened from multiple points (that is, the model was updated by different parts of your application), in your case the best option would be overriding the save()
method, like this:
models.py
class Game(models.Model):
home_team = models.CharField(max_length=200)
away_team = models.CharField(max_length=200)
home_goals = models.IntegerField(default=None)
away_goals = models.IntegerField(default=None)
def save(self, *args, **kwargs):
# call the save method
super().save(*args, **kwargs)
# execute your extra logic
for bet in self.bet_set.all():
if bet.home_goals == self.home_goals and bet.away_goals == self.away_goals:
bet.score = 2
# rest of the if/else logic
bet.save()
This would be a similar implementation than a Signal, but I would say more explicit. As I mentioned, I do not think this is the best implementation for your problem. This could potentially slow down your application, because this for loop would be executed every time you save a Game
instance.
But, if you want to learn more about Signals, I've wrote a blog post about it: How to Create Django Signals.