diff options
Diffstat (limited to 'docs/tutorials/wiki2/definingmodels.rst')
| -rw-r--r-- | docs/tutorials/wiki2/definingmodels.rst | 82 |
1 files changed, 43 insertions, 39 deletions
diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst index e419fdf0a..45a3167a7 100644 --- a/docs/tutorials/wiki2/definingmodels.rst +++ b/docs/tutorials/wiki2/definingmodels.rst @@ -17,22 +17,21 @@ be to define a wiki page :term:`domain model`. but this is only a convention and not a requirement. -Declaring dependencies in our ``setup.py`` file -=============================================== +Declaring dependencies in our ``pyproject.toml`` file +===================================================== The models code in our application will depend on a package which is not a dependency of the original "tutorial" application. The original "tutorial" application was generated by the cookiecutter; it doesn't know about our custom application requirements. -We need to add a dependency, the `bcrypt <https://pypi.org/project/bcrypt/>`_ package, to our ``tutorial`` -package's ``setup.py`` file by assigning this dependency to the ``requires`` -parameter in the ``setup()`` function. +We need to add a dependency, the `bcrypt <https://pypi.org/project/bcrypt/>`_ package, to our ``tutorial`` package's ``pyproject.toml`` file. +Dependencies are defined via the ``dependencies`` key in the ``[project]`` section. -Open ``tutorial/setup.py`` and edit it to look like the following by adding ``bcrypt`` and sorting the packages: +Open ``pyproject.toml`` and edit it to look like the following by adding ``bcrypt`` and sorting the packages: -.. literalinclude:: src/models/setup.py - :lines: 11-24 +.. literalinclude:: src/models/pyproject.toml + :lines: 20-33 :linenos: :lineno-match: :emphasize-lines: 3 @@ -56,7 +55,7 @@ Since a new software dependency was added, you will need to run ``pip install the newly added dependency distribution. Make sure your current working directory is the root of the project (the -directory in which ``setup.py`` lives) and execute the following command. +directory in which ``pyproject.toml`` lives) and execute the following command. On Unix: @@ -75,7 +74,7 @@ like the following. .. code-block:: text - Successfully installed bcrypt-3.2.0 cffi-1.14.4 pycparser-2.20 tutorial + Successfully installed bcrypt-4.1.2 tutorial-0.0 Remove ``mymodel.py`` @@ -96,19 +95,24 @@ Create a new file ``tutorial/models/user.py`` with the following contents: This is a very basic model for a user who can authenticate with our wiki. -We discussed briefly in the previous chapter that our models will inherit from -an SQLAlchemy :func:`sqlalchemy.ext.declarative.declarative_base`. This will -attach the model to our schema. +We discussed briefly in the previous chapter that our models will inherit from an SQLAlchemy :func:`sqlalchemy.orm.DeclarativeBase`. +This will attach the model to our schema. -As you can see, our ``User`` class has a class-level attribute -``__tablename__`` which equals the string ``users``. Our ``User`` class will -also have class-level attributes named ``id``, ``name``, ``password_hash``, -and ``role`` (all instances of :class:`sqlalchemy.schema.Column`). These will -map to columns in the ``users`` table. The ``id`` attribute will be the primary -key in the table. The ``name`` attribute will be a text column, each value of -which needs to be unique within the column. The ``password_hash`` is a nullable -text attribute that will contain a securely hashed password. Finally, the -``role`` text attribute will hold the role of the user. +As you can see, our ``User`` class has a class-level attribute ``__tablename__`` which equals the string ``users``. +Our ``User`` class will also have class-level attributes named ``id``, ``name``, ``password_hash``, and ``role``. +These attributes will map to columns in the ``users`` table. +The ``id`` attribute will be the primary key in the table. +The ``name`` attribute will be a text column, each value of which needs to be unique within the column. +The ``password_hash`` is a nullable text attribute that will contain a securely hashed password. +Finally, the ``role`` text attribute will hold the role of the user. + +.. note:: + + Read more about how SQLAlchemy defines columns in the ORM at :ref:`sqla:orm_declarative_table_config_toplevel`. + Every column has a base schema inferred from the ``Mapped[...]`` type annotation. + This can define the Python type, for which SQLAlchemy maintains default database type mappings. + It also defines whether the attribute is nullable. + Any further schema attributes like primary keys, unique keys, or overrides to the database types, can be done by also defining the ``mapped_column``. There are two helper methods that will help us later when using the user objects. The first is ``set_password`` which will take a raw password and @@ -118,10 +122,11 @@ the hashed value of the submitted password against the hashed value of the password stored in the user's record in the database. If the two hashed values match, then the submitted password is valid, and we can authenticate the user. -We hash passwords so that it is impossible to decrypt them and use them to -authenticate in the application. If we stored passwords foolishly in clear -text, then anyone with access to the database could retrieve any password to -authenticate as any user. +We hash passwords so that it is impossible to decipher them and use them to authenticate in the application. +If we stored passwords foolishly in clear text and we lost control of the database, all of our users could be compromised. + +Notice that we configured a ``created_pages`` relationship, which will make more sense after you create the ``Page`` object in the next section. +This relationship is the reverse side of the ``creator`` one-to-many relationship on the ``Page`` and allows a user object to access a list of all pages in which the user is the creator. Add ``page.py`` @@ -141,7 +146,7 @@ here is the ``creator_id`` column, which is a foreign key referencing the want to relate ``User`` objects with ``Page`` objects, we also define a ``creator`` attribute as an ORM-level mapping between the two tables. SQLAlchemy will automatically populate this value using the foreign key -referencing the user. Since the foreign key has ``nullable=False``, we are +referencing the user. Since ``creator`` attribute / foreign key is not marked as optional, we are guaranteed that an instance of ``page`` will have a corresponding ``page.creator``, which will be a ``User`` instance. @@ -190,20 +195,19 @@ Success executing these commands will generate output similar to the following. .. code-block:: text - 2021-01-07 08:00:14,550 INFO [alembic.runtime.migration:155][MainThread] Context impl SQLiteImpl. - 2021-01-07 08:00:14,551 INFO [alembic.runtime.migration:158][MainThread] Will assume non-transactional DDL. - 2021-01-07 08:00:14,553 INFO [alembic.autogenerate.compare:134][MainThread] Detected added table 'users' - 2021-01-07 08:00:14,553 INFO [alembic.autogenerate.compare:134][MainThread] Detected added table 'pages' - 2021-01-07 08:00:14,558 INFO [alembic.autogenerate.compare:622][MainThread] Detected removed index 'my_index' on 'models' - 2021-01-07 08:00:14,558 INFO [alembic.autogenerate.compare:176][MainThread] Detected removed table 'models' - Generating <somepath>/tutorial/tutorial/alembic/versions/20210107_bc9a3dead43a.py ... done + 2024-02-04 13:29:23,664 INFO [alembic.runtime.migration:216][MainThread] Context impl SQLiteImpl. + 2024-02-04 13:29:23,665 INFO [alembic.runtime.migration:219][MainThread] Will assume non-transactional DDL. + 2024-02-04 13:29:23,667 INFO [alembic.autogenerate.compare:189][MainThread] Detected added table 'users' + 2024-02-04 13:29:23,667 INFO [alembic.autogenerate.compare:189][MainThread] Detected added table 'pages' + 2024-02-04 13:29:23,672 INFO [alembic.autogenerate.compare:672][MainThread] Detected removed index 'my_index' on 'models' + 2024-02-04 13:29:23,672 INFO [alembic.autogenerate.compare:230][MainThread] Detected removed table 'models' + Generating <somepath>/tutorial/alembic/versions/20240204_07f9d6b626b2.py ... done .. code-block:: text - 2021-01-07 08:00:21,318 INFO [alembic.runtime.migration:155][MainThread] Context impl SQLiteImpl. - 2021-01-07 08:00:21,318 INFO [alembic.runtime.migration:158][MainThread] Will assume non-transactional DDL. - 2021-01-07 08:00:21,320 INFO [alembic.runtime.migration:517][MainThread] Running upgrade 90658c4a9673 -> bc9a3dead43a, use new models Page and User - + 2024-02-04 13:32:48,735 INFO [alembic.runtime.migration:216][MainThread] Context impl SQLiteImpl. + 2024-02-04 13:32:48,735 INFO [alembic.runtime.migration:219][MainThread] Will assume non-transactional DDL. + 2024-02-04 13:32:48,737 INFO [alembic.runtime.migration:622][MainThread] Running upgrade 4b6614165904 -> 07f9d6b626b2, use new models Page and User .. _wiki2_alembic_overview: @@ -235,7 +239,7 @@ command, as we did in the installation step of this tutorial. .. note:: - The command is named ``initialize_tutorial_db`` because of the mapping defined in the ``[console_scripts]`` entry point of our project's ``setup.py`` file. + The command is named ``initialize_tutorial_db`` because of the mapping defined in the ``[project.scripts]`` section of our project's ``pyproject.toml`` file. Since we've changed our model, we need to make changes to our ``initialize_db.py`` script. In particular, we'll replace our import of |
