Jose: Now finally there's quite a lot of changes
to app.py as a result of this.
But most changes that we're gonna make are simplifications
to allow to reason about the code more easily.
From now on we're gonna be dealing
with objects and their actions or methods
rather than database operations directly.
So lets go over to app.py and start making our changes.
The first thing is that down here at the very bottom
we've got this way of passing connections about.
Passing a connection that we create up here
to every function but we no longer need that because
our models are in charge of creating the connection.
So we're gonna get rid of that.
Now over here in connections.py
we are loading the connection, creating one,
so what we're gonna do is go back to app.py
and instead of using this line we're
gonna use create-connection, we do have to import that
so I'm going to do that.
Now note that we're no longer calling load_dotenv here,
so what I'm gonna do is I'm gonna
move this over to connections.py
and we're no longer going to need all of this.
For now I'm gonna get rid of this input
but we're gonna bring it back later on
so I'm just gonna copy load_dotenv
and bring it over to connections.py
and of course we do have to import it
so we're gonna import that.
That simplifies our menu quite a bit.
Notice the PyCharm is complaining that
we're not passing in the required arguments
but that's 'cause we're gonna go through
and get rid of all of these connections
that we don't need anymore.
Lets start with the top function prompt_create_poll.
Notice that prompt_create_poll is directly calling
the database.create_poll function passing in the poll_title,
owner and the list of strings each representing an option.
We now want to use our poll and option
classes to do this instead.
First things we're going to go ahead
and fix our import slightly here
and we're going to import our models.
So we're gonna do from models.option import_Option
and from models our poll import_Poll.
Now we can come back down here and start making our changes.
The first thing, when we have the title and the owner
we can create a poll and we can say
poll = Poll with title or rather poll_title and poll_owner.
Then we can save this poll to the database.
So we're gonna do poll.save.
And now we no longer need this options list,
because what we're gonna do is admittedly
a bit less efficient but just down to
how we've re-architectured our code.
While we've got a new option valid
then we're gonna do poll.add.option(new_option)
and we no longer need this call down here.
So now you can see we've simplified our code a fair bit
in exchange for losing some efficiency
when it comes to dealing with our database.
This is a trade off that you're gonna make a lot
when you're working with an ORM, for example.
And if you don't know what an ORM is,
we'll talk about that later on.
Let's take a look at list_open_polls.
At the moment it's accessing this
because it wants the polls to be coming from the database.
But now we're gonna use our model to do that
so will do polls = Poll.all,
and that is gonna go into the database
and fetch all the polls.
Note that you don't really need that
you could just put that directly in here
and that shortens you code a bit more
and I think that's still pretty reasonable.
But we can't use the destructuring here
because a poll is not a destructurable thing
so we do have to do poll,
but then you can do (poll.id) like that,
(poll.title) (poll.owner) instead.
Alternatively you could put all of this
inside the STR method of the poll object
and then you can just print the poll,
if that makes it simpler for you
then by all means you can do that.
Now let's move over to prompt_vote_poll.
Note that the first thing we're
doing here is grab a poll ID.
That means that it's very likely
the next thing we're gonna do
is get the associated poll object
so that we can interact with the database through that.
So let's do that poll = Poll.get with the (poll.id).
Now the poll options, we know that we no longer
need to access the database here like that
because we can just .options.
So when it comes to printing the poll options
we could just grab all of this and put them in there,
we don't need this variable at all.
So now we've got the printing of the poll options
is Poll.get the (poll.id) access the options of it
that goes into the database as well
and we're printing them out.
Now we get the option ID and the username
that they wanna a vote for.
And what we wanna do here is do Option.get, with (option.id)
that's gonna give us the option that they voted for
and we do .vote(username).
And we no longer need this there.
Personally I prefer this way of coding much more,
I find it more readable, more clear what's going on
but I understand that not everybody likes that like I do.
So if you don't like this way of doing things,
you prefer the databases then you can continue doing that,
but do remember that the code is gonna get
messier over time if you have a lot of different
database functions all working from the same file,
so this is a way to simplify that.
When we're calling print_poll_options,
we are now passing a list of options objects.
So we should change that and reflect it in here,
the type hinting, so we'll do List of (Option).
Note that option here is not the option tuple in
database.py, this is the option class that we've imported.
So now this should not be called poll_with_options
it should just be called options, we can iterate over them
and now instead of (option[3]) we can do (option.id),
to print the ID and instead of four we can .text.
Again, you can put this inside the STr method
of your option class if you want
and then you can print the option directly.
Let's go in to show_poll_votes,
this is gonna see its biggest change
and it's not necessarily gonna make it
a simpler piece of code,
but it is gonna make it a little more obvious
what's going on in terms of the Python code.
This is where the biggest trade-off happens
because we have a lot of joins
and database operations happening in the query
and now we're gonna have them in a Python code
which is admittedly slower,
but the gain that we get elsewhere, I think offsets this.
So what we were doing here is
we were getting the poll and the votes.
That was basically, all the things
that we needed in order to print out.
That gave us, even the percentage.
We accepted on the division by zero
to see if there were no votes cast
and then we just went through the list
returned and printed them out.
Now we have to do the same thing but with our models.
We've got a poll ID, so let's get a poll
we need it in order to interact with the database.
The options are poll.options.
And the votes_per_option, we can get
with the length of (option.votes).
Remember, option.votes is gonna give us
all the votes that are coming from
the database directly for each option,
for option in options and we're gonna
make this into a list comprehension.
Then the total number of votes
is gonna be the sum of (votes_per_option).
So now what we have here
are three very useful variables, a list of option objects,
for each object we have the number of votes cast
and then we also have the total.
So now what we wanna do is iterate
essentially over these two at the same time
the options and the votes_per_option.
So that we can match them, one to the other
the order of the elements is gonna be the same
so the first element in this list
is gonna be the number of votes
for the first element in this list.
So what we're gonna do with the try block here
we're gonna doe for option, votes in zip
of options and votes_per_option
and then what we're gonna do is calculate the percentage
that these votes are out of the total.
So we do percentage = votes / total_votes * 100
and then we're gonna print out.
So we'll do (option.text) for (votes)
or got this many votes
and that is the percentage:.2f
of the total.
Then we'll except on zero division error,
ZeroDivisionError like that
and say
No votes cast for this poll yet.
A small simplification we can do here
is we can move that over to one line,
making sure to rename this to options.
And now you can see that the code here
is slightly longer
but it is more obvious what's going on.
We've got the options, we've got the votes per options,
the total votes and then we calculate the percentage there.
However, like I mentioned earlier
it is a bit slower, a bit less efficient
that's the trade-off that we're making.
Finally randomising a pole winner changes slightly,
instead of accessing the database directly
we go through the model classes
and instead of using order by random in a database query,
we get all the voters and use random.choice,
which we're gonna import in a moment.
So here we are printing the poll options,
all we have to do here is do (Poll.get(poll.id).options),
and we can get rid of this.
And then we've got the option ID that we wanna
pick a random voter for.
What we have to do is get the votes
so we'll do votes = Option.get with (option.id).votes,
and then we say winner = random.choice out of (votes)
random.choice this is a function inside the random module
just grabs a random element from the sequence provided.
So here we're gonna give it the list of votes
and it's gonna grab one of them at random.
And then we're just gonna print
the randomly selected winner is winner zero.
Remember, votes is a list of tuples
so when we do random choice of votes
that's gonna give us a single topple.
We're still accessing zero here
not winner.name or something like that,
because we don't have a model class for votes.
Let's import the random module up here at the top
and we can go ahead and do import random
and we no longer need this division by zero error
because we're using the Python division,
We use the built in zero division error instead.
Now I know we've made a lot of changes in this video
and this has been a pretty long video
so I've got a full summary of all the changes
over in the Ebook as well as a code diff
that you can check out that has the changes.
Especially in apps.PY where they are the most complicated.
So I do recommend you do that
but am also gonna put up the code as it stands right now
in the resources section of this lecture
so you can download it and continue from here
in case you have any typos or anything like that
so you can avoid them.
But hopefully everything we've done is clear
and it makes sense why we're doing this.
Again, just for the last time
I know I've said this a few times,
this is the trade-off between performance and efficiency
by using custom database queries for everything
versus simplicity and ease of maintaining
by using the models approach.
As applications get larger and larger,
it starts to make more sense particularly
if you're working with more people
to move to a model-based approach
since that's a bit simpler to work with.
Thank you guys for joining in this video.
I hope you've enjoyed it.
Thanks for watching and I'll see you in the next one.