DEV Community

Kentaro0919
Kentaro0919

Posted on • Updated on

Follow Django Polls Tutorial with Masonite part 2-1/2

Github

Database setup

Database Migrations in Masonite is very nice.
You do not have to keep your information in your config files. You store the information in ".env" file and Masonite will handle. Masonite supports MySQL, Postgres, and SQLite.

#.env

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=masonite
DB_USERNAME=root
DB_PASSWORD=root
DB_LOG=True

To follow the Django tutorial we use SQLite.

#.env

DB_CONNECTION=sqlite
DB_DATABASE=masonite.db
DB_LOG=True

Creating models

In Django.

#polls/models.py
from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

Question

In Masonite models and migrations are separated.
To create a new migration you can run.

$ craft model Question -m
Model Created Successfully!
Created migration: 2019_03_20_130251_create_questions_table.py

This will create a Model named Question and call orator make:migration

app
└─── Question.py

databases/migrations
├── 2018_01_09_043202_create_users_table.py # This exist at craft install
├── 2019_03_20_130251_create_questions_table.py
└── __init__.py
#/databases/migrations/2019_03_20_130251_create_questions_table.py
from orator.migrations import Migration


class CreateQuestionsTable(Migration):

    def up(self):
        """
        Run the migrations.
        """
        with self.schema.create('questions') as table:
            table.increments('id')
            table.timestamps()

    def down(self):
        """
        Revert the migrations.
        """
        self.schema.drop('questions')

Edit this to

from orator.migrations import Migration


class CreateQuestionsTable(Migration):

    def up(self):
        """
        Run the migrations.
        """
        with self.schema.create('questions') as table:
            table.increments('id')

            table.char('question_text', 200)
            table.datetime('pub_date')

            table.timestamps()

    def down(self):
        """
        Revert the migrations.
        """
        self.schema.drop('questions')

and the model from

# app/Question.py

"""Question Model"""

from config.database import Model


class Question(Model):
    """Question Model"""
    pass

to

"""Question Model"""

from config.database import Model


class Question(Model):
    """Question Model"""

    __fillable__ = ['question_text', 'pub_date']

    __dates__ = ['pub_date']

Choice

and for choices.

$ craft model Choice  -m
Model Created Successfully!
Created migration: 2019_03_20_131816_create_choices_table.py
app
├── Choice.py
└─── Question.py

databases/migrations
├── 2018_01_09_043202_create_users_table.py
├── 2019_03_20_130251_create_questions_table.py
├── 2019_03_20_131816_create_choices_table.py
└── __init__.py

# /databases/migrations/2019_03_20_131816_create_choices_table.py
from orator.migrations import Migration


class CreateChoicesTable(Migration):

    def up(self):
        """
        Run the migrations.
        """
        with self.schema.create('choices') as table:
            table.increments('id')
            table.timestamps()

    def down(self):
        """
        Revert the migrations.
        """
        self.schema.drop('choices')

edit this to

# /databases/migrations/2019_03_20_131816_create_choices_table.py
from orator.migrations import Migration


class CreateChoicesTable(Migration):

    def up(self):
        """
        Run the migrations.
        """
        with self.schema.create('choices') as table:
            table.increments('id')

            table.char('choice_text', 200)
            table.integer('votes').default(0)

            table.integer('question_id').unsigned()
            table.foreign('question_id').references('id').on('questions').on_delete('cascade')

            table.timestamps()

    def down(self):
        """
        Revert the migrations.
        """
        self.schema.drop('choices')

#/app/Choice.py
"""Choice Model"""

from config.database import Model


class Choice(Model):
    """Choice Model"""
    pass

to

#/app/Choice.py
"""Choice Model"""

from config.database import Model


class Choice(Model):
    """Choice Model"""

    __fillable__ = ['choice_text', 'pub_date', 'question_id']

This command will make the change to the database.

$ craft migrate

#It took 1.94ms to execute the query ("SELECT * FROM sqlite_master WHERE type = #'table' AND name = ?", ['migrations'])
#It took 3.5ms to execute the query ('CREATE TABLE "migrations" ("migration" VARCHAR #NOT NULL, "batch" INTEGER NOT NULL)', None)
#It took 0.13ms to execute the query ('SELECT "migration" FROM "migrations"', [])
#It took 0.17ms to execute the query ('SELECT MAX("batch") AS aggregate FROM #"migrations"', [])
#It took 1.81ms to execute the query ('CREATE TABLE "users" ("id" INTEGER NOT NULL #PRIMARY KEY AUTOINCREMENT, "name" VARCHAR NOT NULL, "email" VARCHAR NOT NULL, #"password" VARCHAR NOT NULL, "remember_token" VARCHAR NULL, "verified_at" DATETIME #NULL, "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, "updated_at" #DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL)', None)
#It took 1.71ms to execute the query ('CREATE UNIQUE INDEX users_email_unique ON #"users" ("email")', None)
#It took 1.33ms to execute the query ('INSERT INTO "migrations" ("migration", #"batch") VALUES (?, ?)', ['2018_01_09_043202_create_users_table', 1])
#It took 1.93ms to execute the query ('CREATE TABLE "questions" ("id" INTEGER NOT #NULL PRIMARY KEY AUTOINCREMENT, "question_text" VARCHAR NOT NULL, "pub_date" #DATETIME NOT NULL, "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, #"updated_at" DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL)', None)
#It took 1.49ms to execute the query ('INSERT INTO "migrations" ("migration", #"batch") VALUES (?, ?)', ['2019_03_20_130251_create_questions_table', 1])
#It took 1.77ms to execute the query ('CREATE TABLE "choices" ("id" INTEGER NOT NULL #PRIMARY KEY AUTOINCREMENT, "choice_text" VARCHAR NOT NULL, "votes" INTEGER NOT NULL #DEFAULT \'0\', "question_id" INTEGER NOT NULL, "created_at" DATETIME DEFAULT #CURRENT_TIMESTAMP NOT NULL, "updated_at" DATETIME DEFAULT CURRENT_TIMESTAMP NOT #NULL, FOREIGN KEY("question_id") REFERENCES "questions"("id") ON DELETE cascade)', #None)
#It took 1.47ms to execute the query ('INSERT INTO "migrations" ("migration", #"batch") VALUES (?, ?)', ['2019_03_20_131816_create_choices_table', 1])
#Migration table created successfully
#[OK] Migrated 2018_01_09_043202_create_users_table
#[OK] Migrated 2019_03_20_130251_create_questions_table
#[OK] Migrated 2019_03_20_131816_create_choices_table

craft can generate a model docstring based on a table definition

$ craft model:docstring questions
"""Model Definition (generated with love by Masonite)

id: integer default: None
question_text: string(255) default: None
pub_date: datetime default: None
created_at: datetime default: CURRENT_TIMESTAMP
updated_at: datetime default: CURRENT_TIMESTAMP
"""
$ craft model:docstring choices
"""Model Definition (generated with love by Masonite)

id: integer default: None
choice_text: string(255) default: None
votes: integer default: 0
question_id: integer default: None
created_at: datetime default: CURRENT_TIMESTAMP
updated_at: datetime default: CURRENT_TIMESTAMP
"""

Playing with the API

In Dango.

$ python manage.py shell

>>> from polls.models import Choice, Question  # Import the model classes we just wrote.

# No questions are in the system yet.
>>> Question.objects.all()
<QuerySet []>

# Create a new Question.
# Support for time zones is enabled in the default settings file, so
# Django expects a datetime with tzinfo for pub_date. Use timezone.now()
# instead of datetime.datetime.now() and it will do the right thing.
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())

# Save the object into the database. You have to call save() explicitly.
>>> q.save()

# Now it has an ID.
>>> q.id
1

# Access model field values via Python attributes.
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)

# Change values by changing the attributes, then calling save().
>>> q.question_text = "What's up?"
>>> q.save()

# objects.all() displays all the questions in the database.
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>

In Masonite, we can load our application with the craft tinker command.

$craft tinker
#Masonite Python 3.7.2 Console
#This interactive console has the following things imported:
#    container as 'app'

#Type `exit()` to exit.

>>> from app.Question import Question

>>> Question.all()
It took 1.37ms to execute the query ('SELECT * FROM "questions"', [])
#<orator.orm.collection.Collection object at 0x106967e80>

>>> import datetime

>>> q = Question.create(question_text="What's new?", pub_date=datetime.datetime.now())
#It took 350.62ms to execute the query ('INSERT INTO "questions" ("pub_date", #"question_text") VALUES (?, ?)', [datetime.datetime(2019, 3, 21, 6, 36, 20, #45979), "What's new?"])

>>> q.save()
#True
>>> q.id
#1

>>> q.pub_date
#datetime.datetime(2019, 3, 21, 6, 36, 20, 45979)

>>> q.question_text = "What's up?"
>>> q.save()
#It took 3.67ms to execute the query ('UPDATE "questions" SET "question_text" = ?, #"updated_at" = ? WHERE "id" = ?', ["What's up?", '2019-03-20 21:38:01.456334', 1])
#True

>>> Question.all()
#It took 0.15ms to execute the query ('SELECT * FROM "questions"', [])
#<orator.orm.collection.Collection object at 0x10c88c9e8>

In Dango.

#polls/models.py

from django.db import models

class Question(models.Model):
    # ...
    def __str__(self):
        return self.question_text

class Choice(models.Model):
    # ...
    def __str__(self):
        return self.choice_text

In Masonite

#
"""Question Model"""
import datetime

from config.database import Model


class Question(Model):
    """Question Model"""
    __fillable__ = ['question_text', 'pub_date']

    def __repr__(self):
        return self.question_text

"""Choice Model"""

from config.database import Model


class Choice(Model):
    """Choice Model"""
    __fillable__ = ['question_id', 'choice_text', 'votes']

    def __repr__(self):
        return self.choice_text

In Django

#
import datetime

from django.db import models
from django.utils import timezone


class Question(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

In Masonite

#
"""Question Model"""
import datetime

from config.database import Model


class Question(Model):
    """Question Model"""
    __fillable__ = ['question_text', 'pub_date']
    __dates__ = ['pub_date']

    def __repr__(self):
        return self.question_text

    def was_published_recently(self):
        return self.pub_date >= datetime.datetime.now() - datetime.timedelta(days=1)

In Dango.

>>> from polls.models import Choice, Question

# Make sure our __str__() addition worked.
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>

# Django provides a rich database lookup API that's entirely driven by
# keyword arguments.
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith='What')
<QuerySet [<Question: What's up?>]>

# Get the question that was published this year.
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>

# Request an ID that doesn't exist, this will raise an exception.
>>> Question.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: Question matching query does not exist.

# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to Question.objects.get(id=1).
>>> Question.objects.get(pk=1)
<Question: What's up?>

# Make sure our custom method worked.
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True

# Give the Question a couple of Choices. The create call constructs a new
# Choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates
# a set to hold the "other side" of a ForeignKey relation
# (e.g. a question's choice) which can be accessed via the API.
>>> q = Question.objects.get(pk=1)

# Display any choices from the related object set -- none so far.
>>> q.choice_set.all()
<QuerySet []>

# Create three choices.
>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0)
#<Choice: The sky>

>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)

# Choice objects have API access to their related Question objects.
>>> c.question
#<Question: What's up?>

# And vice versa: Question objects get access to Choice objects.

>>> q.choice_set.all()
#<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>

>>> q.choice_set.count()
#3

# The API automatically follows relationships as far as you need.
# Use double underscores to separate relationships.
# This works as many levels deep as you want; there's no limit.
# Find all Choices for any question whose pub_date is in this year
# (reusing the 'current_year' variable we created above).

>>> Choice.objects.filter(question__pub_date__year=current_year)
#<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>

# Let's delete one of the choices. Use delete() for that.

>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()

In Masonite

>>> from app.Question import Question
>>> from app.Choice import Choice

>>> Question.all()
#It took 0.14ms to execute the query ('SELECT * FROM "questions"', [])
#<orator.orm.collection.Collection object at 0x10c990d30>

>>> Question.find(1) #Question.find(id=1) works the same
#It took 0.26ms to execute the query ('SELECT * FROM "questions" WHERE #"questions"."id" = ? LIMIT 1', [1])
#What's up?


Question.where_raw('question_text LIKE ?', ["What%"]).get()
#It took 2.68ms to execute the query ('SELECT * FROM "questions" WHERE question_text LIKE ?', ['What%'])
#<orator.orm.collection.Collection object at 0x10c9aa9e8>

>>> import datetime
>>> current_year = datetime.datetime.now().year



>>> Question.find(id=2).get()
#It took 0.09ms to execute the query ('SELECT * FROM "questions" WHERE #"questions"."id" = ? LIMIT 1', [2])
#Traceback (most recent call last):
#  File "<console>", line 1, in <module>
#AttributeError: 'NoneType' object has no attribute 'get'

To handle the relationships
We have to edit our model like

"""Question Model"""
import datetime
from config.database import Model
from orator.orm import has_many


class Question(Model):
    """Question Model"""

    __fillable__ = ['question_text', 'pub_date']

    __dates__ = ['pub_date']

    def __repr__(self):
        return self.question_text

    def was_published_recently(self):
        return self.pub_date >= datetime.datetime.now() - datetime.timedelta(days=1)

    @has_many
    def choice(self):
        from app.Choice import Choice
        return Choice

Then we can handle the relationship.

>>> q = Question.find(id=1)
#It took 0.1ms to execute the query ('SELECT * FROM "questions" WHERE "questions"."id" = ? LIMIT 1', [1])

>>> q
#What's up?

>>> q.was_published_recently()
#True

>>> q.choice().get()
#It took 47.28ms to execute the query ('SELECT * FROM "choices" WHERE #"choices"."question_id" = ?', [1])
#<orator.orm.collection.Collection object at 0x10da62908>


>>> q.choice().create(choice_text='Not much', votes=0)
#It took 44.38ms to execute the query ('INSERT INTO "choices" ("choice_text", #"created_at", "question_id", "updated_at") VALUES (?, ?, ?, ?)', ['Not much', #'2019-03-20 22:16:52.997981', 1, '2019-03-20 22:16:52.997981'])
#Not much

>>> q.choice().create(choice_text='The sky', votes=0)
#It took 3.53ms to execute the query ('INSERT INTO "choices" ("choice_text", #"created_at", "question_id", "updated_at") VALUES (?, ?, ?, ?)', ['The sky', #'2019-03-20 22:17:11.626357', 1, '2019-03-20 22:17:11.626357'])
#The sky

>>> q.choice().create(choice_text='Just hacking again', votes=0)
#It took 295.54ms to execute the query ('INSERT INTO "choices" ("choice_text", #"created_at", "question_id", "updated_at") VALUES (?, ?, ?, ?)', ['Just #hacking again', '2019-03-20 22:17:47.761326', 1, '2019-03-20 #22:17:47.761326'])
#Just hacking again


>>> q.choice().count()
#It took 0.17ms to execute the query ('SELECT COUNT(*) AS aggregate FROM #"choices" WHERE "choices"."question_id" = ?', [1])
#3

>>> import datetime
>>> current_year = datetime.datetime.now().year

TODO

find some way to do

# The API automatically follows relationships as far as you need.
# Use double underscores to separate relationships.
# This works as many levels deep as you want; there's no limit.
# Find all Choices for any question whose pub_date is in this year
# (reusing the 'current_year' variable we created above).
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>

# Let's delete one of the choices. Use delete() for that.
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()

Discussion (0)