Model View Controller

Fantastico framework provides quite a powerful model - view - controller implementation. Here you can find details about design decisions and how to benefit from it.

Classic approach

Usually when you want to work with models as understood by MVC pattern you have in many cases boiler plate code:

  1. Write your model class (or entity)
  2. Write a repository that provides various methods for this model class.
  3. Write a facade that works with the repository.
  4. Write a web service / page that relies on the facade.
  5. Write one or multiple views.

As this is usually a good in theory, in practice you will see that many methods from facade are converting a data transfer object to an entity and pass it down to repository.

Fantastico approach

Fantastico framework provides an alternative to this classic approach (you can still work in the old way if you really really want).

class fantastico.mvc.controller_decorators.Controller(url, method='GET', models=None, **kwargs)[source]

This class provides a decorator for magically registering methods as route handlers. This is an extremely important piece of Fantastico framework because it simplifies the way you as developer can define mapping between a method that must be executed when an http request to an url is made:

@ControllerProvider()
class BlogsController(BaseController):
    @Controller(url="/blogs/", method="GET",
                models={"Blog": "fantastico.plugins.blog.models.blog.Blog"])
    def list_blogs(self, request):
        Blog = request.models.Blog

        blogs = Blog.get_records_paged(start_record=0, end_record=5,
                               sort_expr=[ModelSort(Blog.model_cls.create_date, ModelSort.ASC,
                                          ModelSort(Blog.model_cls.title, ModelSort.DESC)],
                               filter_expr=ModelFilterAnd(
                                               ModelFilter(Blog.model_cls.id, 1, ModelFilter.GT),
                                               ModelFilter(Blog.model_cls.id, 5, ModelFilter.LT))))

        # convert blogs to desired format. E.g: json.

        return Response(blogs)

The above code assume the following:

  1. As developer you created a model called blog (this is already mapped to some sort of storage).
  2. Fantastico framework generate the facade automatically (and you never have to know anything about underlining repository).
  3. Fantastico framework takes care of data conversion.
  4. As developer you create the method that knows how to handle /blog/ url.
  5. Write your view.

You can also map multiple routes for the same controller:

Below you can find the design for MVC provided by Fantastico framework:

../_images/mvc.png
fn_handler[source]

This property retrieves the method which is executed by this controller.

classmethod get_registered_routes()[source]

This class methods retrieve all registered routes through Controller decorator.

method[source]

This property retrieves the method(s) for which this controller can be invoked. Most of the time only one value is retrieved.

models[source]

This property retrieves all the models required by this controller in order to work correctly.

url[source]

This property retrieves the url used when registering this controller.

If you want to find more details and use cases for controller read Controller section.

Model

A model is a very simple object that inherits fantastico.mvc.models.BaseModel.

In order for models to work correctly and to be injected correctly into controller you must make sure you have a valid database configuration in your settings file. By default, fantastico.settings.BasicSettings provides a usable database configuration.

# fantastico.settings.BasicSettings
@property
def database_config(self):
    return {"drivername": "mysql+mysqldb",
            "username": "fantastico",
            "password": "12345",
            "host": "localhost",
            "port": 3306,
            "database": "fantastico",
            "show_sql": True}

By default, each time a new build is generated for fantastico each environment is validated to ensure connectivity to configured database works.

There are multiple ways in how a model is used but the easiest way is to use an autogenerated model facade:

class fantastico.mvc.model_facade.ModelFacade(model_cls, session)[source]

This class provides a generic model facade factory. In order to work Fantastico base model it is recommended to use autogenerated facade objects. A facade object is binded to a given model and given database session.

count_records(filter_expr=None)[source]

This method is used for counting the number of records from underlining facade. In addition it applies the filter expressions specified (if any).

records = facade.count_records(
                           filter_expr=ModelFilterAnd(
                                           ModelFilter(Blog.id, 1, ModelFilter.GT),
                                           ModelFilter(Blog.id, 5, ModelFilter.LT)))
Parameters:filter_expr (list) – A list of fantastico.mvc.models.model_filter.ModelFilterAbstract which are applied in order.
Raises fantastico.exceptions.FantasticoDbError:
 This exception is raised whenever an exception occurs in retrieving desired dataset. The underlining session used is automatically rollbacked in order to guarantee data integrity.
create(model)[source]

This method add the given model in the database.

class PersonModel(BASEMODEL):
    __tablename__ = "persons"

    id = Column("id", Integer, autoincrement=True, primary_key=True)
    first_name = Column("first_name", String(50))
    last_name = Column("last_name", String(50))

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

facade = ModelFacade(PersonModel, fantastico.mvc.SESSION)

model = facade.new_model("John", last_name="Doe")
facade.create(model)
Returns:The newly generated primary key or the specified primary key (it might be a scalar value or a tuple).
Raises fantastico.exceptions.FantasticoDbError:
 Raised when an unhandled exception occurs. By default, session is rollback automatically so that other consumers can still work as expected.
delete(model)[source]

This method deletes a given model from database. Below you can find a simple example of how to use this:

class PersonModel(BASEMODEL):
    __tablename__ = "persons"

    id = Column("id", Integer, autoincrement=True, primary_key=True)
    first_name = Column("first_name", String(50))
    last_name = Column("last_name", String(50))

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

facade = ModelFacade(PersonModel, fantastico.mvc.SESSION)
model = facade.find_by_pk({PersonModel.id: 1})
facade.delete(model)
Raises fantastico.exceptions.FantasticoDbError:
 Raised when an unhandled exception occurs. By default, session is rollback automatically so that other consumers can still work as expected.
find_by_pk(pk_values)[source]

This method returns the entity which matches the given primary key values.

class PersonModel(BASEMODEL):
    __tablename__ = "persons"

    id = Column("id", Integer, autoincrement=True, primary_key=True)
    first_name = Column("first_name", String(50))
    last_name = Column("last_name", String(50))

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

facade = ModelFacade(PersonModel, fantastico.mvc.SESSION)
model = facade.find_by_pk({PersonModel.id: 1})
get_records_paged(start_record, end_record, filter_expr=None, sort_expr=None)[source]

This method retrieves all records matching the given filters sorted by the given expression.

records = facade.get_records_paged(start_record=0, end_record=5,
                           sort_expr=[ModelSort(Blog.create_date, ModelSort.ASC,
                                      ModelSort(Blog.title, ModelSort.DESC)],
                           filter_expr=ModelFilterAnd(
                                           ModelFilter(Blog.id, 1, ModelFilter.GT),
                                           ModelFilter(Blog.id, 5, ModelFilter.LT))))
Parameters:
Returns:

A list of matching records strongly converted to underlining model.

Raises fantastico.exceptions.FantasticoDbError:
 

This exception is raised whenever an exception occurs in retrieving desired dataset. The underlining session used is automatically rollbacked in order to guarantee data integrity.

model_cls[source]

This property holds the model based on which this facade is built.

model_pk_cols[source]

This property returns the model primary key columns as defined in the model cls.

new_model(*args, **kwargs)[source]

This method is used to obtain an instance of the underlining model. Below you can find a very simple example:

class PersonModel(BASEMODEL):
    __tablename__ = "persons"

    id = Column("id", Integer, autoincrement=True, primary_key=True)
    first_name = Column("first_name", String(50))
    last_name = Column("last_name", String(50))

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

facade = ModelFacade(PersonModel, fantastico.mvc.SESSION)

model = facade.new_model("John", last_name="Doe")
Parameters:
  • args (list) – A list of positional arguments we want to pass to underlining model constructor.
  • kwargs (dict) – A dictionary containing named parameters we want to pass to underlining model constructor.
Returns:

A BASEMODEL instance if everything is ok.

session[source]

This property returns the current sqlalchemy session used to access database.

update(model)[source]

This method updates an existing model from the database based on primary key.

class PersonModel(BASEMODEL):
    __tablename__ = "persons"

    id = Column("id", Integer, autoincrement=True, primary_key=True)
    first_name = Column("first_name", String(50))
    last_name = Column("last_name", String(50))

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

facade = ModelFacade(PersonModel, fantastico.mvc.SESSION)

model = facade.new_model("John", last_name="Doe")
model.id = 5
facade.update(model)
Raises:

If you are using the Fantastico MVC support you don’t need to manually create a model facade instance because fantastico.mvc.controller_decorators.Controller injects defined models automatically.

View

A view can be a simple html plain file or html + jinja2 enriched support. You can read more about Jinja2 here [http://jinja.pocoo.org/docs/]. Usually, if you need some logical block statements in your view (if, for, ...) it is easier to use jinja 2 template engine. The good news is that you can easily embed jinja 2 markup in your views and it will be rendered automatically.

Controller

A controller is the brain; it actually combines a model execute some business logic and pass data to the desired view that needs to be rendered. In some cases you don’t really need view in order to provide the logic you want:

  • A REST Web service.
  • A RSS feed provider.
  • A file download service

Though writing REST services does not require a view, you can load external text templates that might be useful for assembling the response:

  • An invoice generator service
  • An xml file that must be filled with product data
  • A vCard [http://en.wikipedia.org/wiki/VCard]. export service.

If you want to read a small tutorial and to start coding very fast on Fantastico MVC read MVC How to. Controller API is documented fantastico.mvc.controller_decorator.Controller.

class fantastico.mvc.controller_registrator.ControllerRouteLoader(settings_facade=<class 'fantastico.settings.SettingsFacade'>, scanned_folder=None, ignore_prefix=None)[source]

This class provides a route loader that is capable of scanning the disk and registering only the routes that contain a controller decorator in them. This happens when Fantastico servers starts. In standard configuration it ignores tests subfolder as well as test_* / itest_* modules.

load_routes()[source]

This method is used for loading all routes that are mapped through fantastico.mvc.controller_decorators.Controller decorator.

scanned_folders[source]

This property returns the currently scanned folder from where mvc routes are collected.

class fantastico.mvc.base_controller.BaseController(settings_facade)[source]

This class provides common methods useful for every concrete controller. Even if no type checking is done in Fantastico it is recommended that every controller implementation inherits this class.

curr_request[source]

This property returns the current http request being processed.

get_component_folder()[source]

This method is used to retrieve the component folder name under which this controller is defined.

load_template(tpl_name, model_data=None, get_template=<function get_template at 0x1ff4408>, enable_global_folder=False)[source]

This method is responsible for loading a template from disk and render it using the given model data.

@ControllerProvider()
class TestController(BaseController):
    @Controller(url="/simple/test/hello", method="GET")
    def say_hello(self, request):
        return Response(self.load_template("/hello.html"))

The above snippet will search for hello.html into component folder/views/.

Available filters

class fantastico.mvc.models.model_filter.ModelFilterAbstract[source]

This is the base class that defines the contract a model filter must follow. A model filter is a class that decouples sqlalchemy framework from Fantastico MVC. This is required because in the future we might want to change the ORM that powers Fantastico without breaking all existing code.

../_images/mvc_filters.png

For seeing how to implement filters (probably you won’t need to do this) see some existing filters:

build(query)[source]

This method is used for appending the current filter to the query using sqlalchemy specific language.

get_expression()[source]

This method is used for retrieving native sqlalchemy expression held by this filter.

class fantastico.mvc.models.model_filter_compound.ModelFilterCompound(operation, *args)[source]

This class provides the api for compounding ModelFilter objects into a specified sql alchemy operation.

build(query)[source]

This method transform the current compound statement into an sql alchemy filter.

get_expression()[source]

This method transforms calculates sqlalchemy expression held by this filter.

model_filters[source]

This property returns all ModelFilter instances being compound.

class fantastico.mvc.models.model_filter.ModelFilter(column, ref_value, operation)[source]

This class provides a model filter wrapper used to dynamically transform an operation to sql alchemy filter statements. You can see below how to use it:

id_gt_filter = ModelFilter(PersonModel.id, 1, ModelFilter.GT)
build(query)[source]

This method appends the current filter to a query object.

column[source]

This property holds the column used in the current filter.

get_expression()[source]

Method used to return the underlining sqlalchemy exception held by this filter.

static get_supported_operations()[source]

This method returns all supported operations for model filter. For now only the following operations are supported:

  • GT - greater than comparison
  • GE - greater or equals than comparison
  • EQ - equals comparison
  • LE - less or equals than comparison
  • LT - less than comparison
  • LIKE - like comparison
  • IN - in comparison.
operation[source]

This property holds the operation used in the current filter.

ref_value[source]

This property holds the reference value used in the current filter.

class fantastico.mvc.models.model_filter_compound.ModelFilterAnd(*args)[source]

This class provides a compound filter that allows and conditions against models. Below you can find a simple example:

id_gt_filter = ModelFilter(PersonModel.id, 1, ModelFilter.GT)
id_lt_filter = ModelFilter(PersonModel.id, 5, ModelFilter.LT)
name_like_filter = ModelFilter(PersonModel.name, '%%john%%', ModelFilter.LIKE)

complex_condition = ModelFilterAnd(id_gt_filter, id_lt_filter, name_like_filter)
class fantastico.mvc.models.model_filter_compound.ModelFilterOr(*args)[source]

This class provides a compound filter that allows or conditions against models. Below you can find a simple example:

id_gt_filter = ModelFilter(PersonModel.id, 1, ModelFilter.GT)
id_lt_filter = ModelFilter(PersonModel.id, 5, ModelFilter.LT)
name_like_filter = ModelFilter(PersonModel.name, '%%john%%', ModelFilter.LIKE)

complex_condition = ModelFilterOr(id_gt_filter, id_lt_filter, name_like_filter)
class fantastico.mvc.models.model_sort.ModelSort(column, sort_dir=None)[source]

This class provides a filter that knows how to sort rows from a query result set. It is extremely easy to use:

id_sort_asc = ModelSort(PersonModel.id, ModelSort.ASC)
build(query)[source]

This method appends sort_by clause to the given query.

column[source]

This property holds the column we are currently sorting.

get_expression()[source]

This method returns the sqlalchemy expression held by this filter.

get_supported_sort_dirs()[source]

This method returns all supported sort directions. Currently only ASC / DESC directions are supported.

sort_dir[source]

This property holds the sort direction we are currently using.

Database session management

We all know database session management is painful and adds a lot of boiler plate code. In fantastico you don’t need to manage database session by yourself. There is a dedicated middleware which automatically ensures there is an active session ready to be used:

class fantastico.middleware.model_session_middleware.ModelSessionMiddleware(app, settings_facade=<class 'fantastico.settings.SettingsFacade'>)[source]

This class is responsible for managing database connections across requests. It also takes care of connection data pools. By default, the middleware is automatically configured to open a connection. If you don’t need mvc (really improbable but still) you simply need to change your project active settings profile. You can read more on fantastico.settings.BasicSettings