.. _config_django_tic_tac_toe: ========================== So let's get started ! ========================== Introduction ============= To start, let's clone our starter code, build our docker image, and run migrations. Edit docker-compose version: "3.3" :: git clone -b boilerplate --single-branch https://github.com/Jonathan-Adly/htmx-tictactoe.git :: ✦ ❯ git diff diff --git a/docker-compose.yml b/docker-compose.yml index 9f65e04..1766eb8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: "3.9" +version: "3.3" :: docker-compose up -d --build :: docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 33e99a10e6b4 htmx-tictactoe_web "python manage.py ru…" 11 seconds ago Up 11 seconds 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp htmx-tictactoe_web_1 23cdc5808dd3 postgres:11 "docker-entrypoint.s…" 14 minutes ago Up 11 seconds 5432/tcp htmx-tictactoe_db_1 :: docker-compose exec web python manage.py migrate :: ❯ docker-compose exec web python manage.py migrate :: Operations to perform: Apply all migrations: account, accounts, admin, auth, contenttypes, sessions, sites, socialaccount Running migrations: Applying contenttypes.0001_initial... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0001_initial... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying auth.0012_alter_user_first_name_max_length... OK Applying accounts.0001_initial... OK Applying account.0001_initial... OK Applying account.0002_email_max_length... OK Applying accounts.0002_auto_20211101_0104... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying sessions.0001_initial... OK Applying sites.0001_initial... OK Applying sites.0002_alter_domain_unique... OK Applying socialaccount.0001_initial... OK Applying socialaccount.0002_token_max_lengths... OK Applying socialaccount.0003_extra_data_default_dict... OK http://0.0.0.0:8000/accounts/signup/ ===================================== .. figure:: images/signup.png :align: center http://0.0.0.0:8000/accounts/signup/ Our starter code is a Django project with three applications. The first is an accounts application that uses django-allauth for authentications. **If you went through the previous tutorials (I highly recommend)**, it is the same. The only difference is we added a couple of new fields to our user model. To represent the "symbol" that our users would use ( "X" vs. "O"), we added a Charfield with a default of "X". The interesting part though was adding a nested ArrayField to represent our game board. ArrayField is a PostgreSQL specific field that allows us to store lists in our database. ArrayField is a PostgreSQL specific field that allows us to store lists in our database. We would represent our board with something like this:: board = [ [0,0,0], [0,0,0], [0,0,0] ] Where 0 is an empty cell, 1 would be a cell filled by "X", and 2 would be a cell filled by "O". This allows us to use any symbol we want, not just "X" and "O". Also, ArrayField data types have to be the same. So we can't have an integer and a string in the same board. When an X player picks a board cell, let's say the middle cell. Our board will change as such:: board = [[0,0,0][0,1,0][0,0,0]] Then, the computer move might change the board as follow,:: board = [[0,0,0][0,1,2][0,0,0]] And so on. There are three things we need when using an ArrayField. A base_field that represents the type of data in the list. For example, a list of [0,1,2] would have an IntegerField as its base. Another list of ["a","b","c"] would have a CharField as its base field. The base_field could be another ArrayField, giving us a nested ArrayField which we will use in our project to represent our board. We can't have an array of [1, "a"], the type of data has to stay the same If we are to give our field a default, it should be a callable or a function that returns a list. A default of [] is not recommended. Lastly, if we decide to nest our arrays, PostgreSQL requires that the arrays be rectangular. In other words, you can't have one array with three values and another with one value. accounts/models.py ==================== .. literalinclude::src/accounts/models.py :linenos: Config application ====================== The config application doesn't have any changes from our minimal boilerplate tutorial (you can read it `modern-django-boilerplate `_ (https://htmx-django.com/blog/a-minimalistic-modern-django-boilerplate)). We just added our tictactoe application to the APPLICATION_LIST and its URLS to the urls.py module config/settings.py --------------------- .. literalinclude:: ../src/config/settings.py :linenos: config/urls.py --------------------- .. literalinclude:: ../src/config/urls.py :linenos: Tic-Tac-Toe ============== Our last application has only 1 view/endpoint that returns a home template. As before, we are using a base template and extending it for use in our home. Also, we have a component directory that holds a navbar. The base template has a CSS style tag in the head. This handles our game board look and feel. It is from the React official tutorial with no changes. The "include" block renders our navbar. (if you have trouble following, I highly recommend going through the previous tutorials in that course. We already went through the step-by-step process for building those patterns). The interesting part is in the home template. Here is how we are rendering the game board. .. code-block:: django {% for i in request.user.board %}
{% for j in i %} {% endfor %}
{% endfor %} We are taking advantage of the magic of Django templates to dynamically render our game board based on the user board. The end result is a 3x3 grid with each cell containing a button(square). Something like this. .. figure:: images/tictactoe.png :align: center Now that we went over our starter code. Let's go ahead and work on our user stories. You can see the `code `_ (https://github.com/Jonathan-Adly/htmx-tictactoe/tree/boilerplate) for this part here.