I am following the Django Fundamentals tutorial on Code School and I've done pretty well understanding, following along, and figuring out my own issues as they arise, until this point. The tutorial builds a tic-tac-toe game.
I am trying to add the capability to make a move when it is the users turn.
My relevant code is below. I have narrowed it down to the point that I think it has something to do with using the context, because if I take it out and try the following, it will display the form. I wanted to make sure that my form was okay and it seems like it displays correctly if done this way.
if request.method == 'POST':
form = MoveForm(data=request.POST, instance=Move(game=game))
if form.is_valid():
form.save()
return redirect('tictactoe_game_detail', pk=pk)
else:
form = MoveForm()
return render(request, "tictactoe/game_do_move.html", {'form': form})
see? it displays the form, but only the form:
I want it to be displayed in the box above the submit button, but for some reason it is not there.
I am pretty confident that the form is okay. I tried this also just to see if maybe the fields weren't there or something in the context, but it shows the x, y as fields.
I have been staring at this for a day and I can't figure out what I'm doing incorrectly. I am using Django 1.8.1. (The tutorial is for 1.6, but so far it seems to be okay. Migrations instead of syncdb seems to be one of the biggest changes I noticed)
Thank you for any and all help!
The code:
class Game(models.Model):
first_player = models.ForeignKey(User, related_name="games_first_player")
second_player = models.ForeignKey(User, related_name="games_second_player")
next_to_move = models.ForeignKey(User, related_name="games_to_move")
start_time = models.DateTimeField(auto_now_add=True)
last_active = models.DateTimeField(auto_now=True)
status = models.CharField(max_length=1, default='A', choices=GAME_STATUS_CHOICES)
objects = GamesManager()
def as_board(self):
"""Return a representation of the game board as a two dimensional list,
so you can ask for the state of a square in position [y][x].
It will contain a list of lines, where eveyr line is a list of 'X', 'O', or ''.
For example, a 3x3 board position:
[['', 'X', ''],
['O', '', ''],
['X', '', '']]
"""
board = [['' for x in range(BOARD_SIZE)] for y in range(BOARD_SIZE)]
for move in self.move_set.all():
board[move.y][move.x] = FIRST_PLAYER_MOVE if move.by_first_player else SECOND_PLAYER_MOVE
return board
def last_move(self):
return self.move_set.latest()
def get_absolute_url(self):
return reverse('tictactoe_game_detail', args=[self.id])
def games_for_user(self, user):
return "none"
def is_users_move(self, user):
return self.status == 'A' and self.next_to_move == user
def __str__(self):
return "{0} vs {1}".format(self.first_player, self.second_player)
class Move(models.Model):
x = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(BOARD_SIZE-1)])
y = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(BOARD_SIZE-1)])
comment = models.CharField(max_length=300, blank=True)
game = models.ForeignKey(Game)
by_first_player = models.BooleanField(default=False)
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
get_latest_by = 'timestamp'
def player(self):
return self.game.first_player if self.by_first_player else self.game.second_player
@login_required
def game_detail(request, pk):
game = get_object_or_404(Game, pk=pk)
if game.is_users_move(request.user):
return redirect('tictactoe_game_do_move', pk=pk)
return render(request, "tictactoe/game_detail.html", {'game': game})
@login_required
def game_do_move(request, pk):
game = get_object_or_404(Game, pk=pk)
if not game.is_users_move(request.user):
raise PermissionDenied
context = {'game': game}
if request.method == 'POST':
form = MoveForm(data=request.POST, instance=Move(game=game))
context['form'] = form
if form.is_valid():
form.save()
return redirect('tictactoe_game_detail', pk=pk)
else:
context['form'] = MoveForm()
return render(request, "tictactoe/game_do_move.html", {'game': game})
{% extends "tictactoe/game_detail.html" %}
{% load staticfiles %}
{% load crispy_forms_tags %}
{% block styling %}
{{ block.super }}
<style type="text/css">
.tictactoe-cell.empty:hover {
background-color: #48CA3B;
cursor: pointer;
}
</style>
{% endblock styling %}
{% block moveform %}
{{ block.super }}
<div class="well col-md-6">
<form action="" method="post">
{% csrf_token %}
{{ form | crispy }}
<button type="submit">Submit</button>
</form>
</div>
{% endblock moveform %}
{% extends "base.html" %}
{% load staticfiles %}
{% block title %}
Tic-Tac-Toe game: {{ game.first_player }} vs {{ game.second_player }}
{% endblock title %}
{% block styling %}
{{ block.super }}
<link rel="stylesheet"
href="{% static 'bootstrap/font-awesome-4.3.0/font-awesome-4.3.0/css/font-awesome.min.css' %}">
<style type="text/css">
.tictactoe-cell {
background-color: #debb27;
}
#last-move {
background-color: #DF6E1E;
}
</style>
{% endblock styling %}
{% block content %}
<h3>Game: {{ game }}</h3>
<div class="col-sm-9">
<table class="table table-bordered" style="width: 60px; border-width: 2px">
{% for line in game.as_board %}
<tr>
{% for square in line %}
<td class="tictactoe-cell {% if not square %}empty{% endif %}"
style="width: 20px; height: 20px">
{{ square }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
{% block moveform %}{% endblock moveform %}
</div>
{% endblock content %}
class MoveForm(ModelForm):
class Meta:
model = Move
exclude = ['game', 'by_first_player', 'comment']
urlpatterns = patterns('tictactoe.views',
url(r'^invite$', 'new_invitation', name='tictactoe_invite'),
url(r'^invitation/(?P<pk>\d+)/$', 'accept_invitation', name='tictactoe_accept_invitation'),
url(r'^game/(?P<pk>\d+)/$', 'game_detail', name='tictactoe_game_detail'),
url(r'^game/(?P<pk>\d+)/do_move$', 'game_do_move', name='tictactoe_game_do_move'),
)
The problem is in views.py. In the line:
return render(request, "tictactoe/game_do_move.html", {'game': game})
the portion {'game': game}
is the context
. For any GET
request (like rendering a blank form) you aren't rendering the form
context variable.
Change that line to:
return render(request, "tictactoe/game_do_move.html", context)
and it should work. You added the blank form
object to the context variable in the lines
else:
context['form'] = MoveForm()
but without passing it to the render
method it won't appear in the template.