Models, controllers and views

36
The Three line MVC application and introducing Giotto

Transcript of Models, controllers and views

Page 1: Models, controllers and views

The Three line MVC application

and introducing Giotto

Page 2: Models, controllers and views

Table on contents

First part: Why high level code organization schemes are important

Second part: All about MVC

Third Part: Giotto!

Page 3: Models, controllers and views

My experiences

1. Started web development in 20072. Wrote code without any kind of architectural pattern at all3. This was very frustrating, but I didn't know any better4. Realized it's taking too long to get stuff fixed and its not fun5. Learned first hand that not using MVC is a pain

Page 4: Models, controllers and views

Non-MVC code

Page 5: Models, controllers and views

Flightlogg.in'

View: HTML/JavascriptController: Standard HTTP GET/POSTModel: Flight Storage and flight data analysis.

Was originally PHP (non-MVC)Now is django (mostly MVC)

Page 6: Models, controllers and views

Why?

1. Flexibility 2. Organization

Page 7: Models, controllers and views

Imagine...

1 app1 django view, 9000 lines of code

Page 8: Models, controllers and views

Imagine...

We want to fix this. Refactor!!

step 1: 1000 functions, 9 lines each

Page 9: Models, controllers and views

Imagine...

step 2: 100 classes, 10 functions each

Page 10: Models, controllers and views

And then...

App Models class class Views class class Controllers class class

Page 11: Models, controllers and views
Page 12: Models, controllers and views

Overview

1. Models - The application2. Controllers - The interface a. ties your application (model) to the outside world3. Views - The presentation of the output to the user

Page 13: Models, controllers and views

Models

1. Usually the biggest part of your application2. Business Logic3. Not just database tables4. Should be completely controller independent

Page 14: Models, controllers and views

Views

1. All about formatting output from model.2. Templates/HTML3. Serializers4. Should be independent of any controllers (templates are portable)

Page 15: Models, controllers and views

Controllers

1. How the data gets to and from the user2. Apache, nginx, varnish, django middleware, mod_wsgi are all technically part of the controller.3. What goes into my controller? a. High level Model code b. High level View code c. final interface level operations

Page 16: Models, controllers and views

An example controller

def new_flight_controller(request): total_time = request.POST['total_time'] landings = request.POST['landings'] user = request.user flight = Flight.new_flight(user, total_time, landings) try: flight.save() except: raise HttpResponseError("invalid flight data") return render_to_response( context={'flights': Flight.objects.filter(request.user)} template='flights.html')

Page 17: Models, controllers and views

An example controller

def new_flight_controller(request): total_time = request.POST['total_time'] landings = request.POST['landings'] user = request.user flight = Flight.new_flight(user, total_time, landings) try: flight.save() except: raise HttpResponseError("invalid flight data") return render_to_response( context={'flights': Flight.objects.filter(request.user)} template='flights.html')

Interface operations

High level model code

High level view code

Page 18: Models, controllers and views

Another example controllerclass NewFlightCommand(BaseCommand): option_list = BaseCommand.option_list + ( make_option('--total_time', '-t', dest='total_time'), make_option('--landings', '-l', dest='landings'), make_option('--user', '-u', dest='user') ) def handle(self, *args, **options): flight = Flight.new_flight(**options) try: flight.save() except: print "Invalid flight data" print "Flight saved!"

Page 19: Models, controllers and views

Don't put non-controller code inside a controller!def to_decimal(input): """ >>> to_decimal('3:30') 3.5 >>> to_decimal('3.5') 3.5 >>> to_decimal('3:12') 3.2 """

This is not a controller function! Not high level model code, not high level view code, and not interface specific!!!

Page 20: Models, controllers and views

This code is not controller code!

def controller(request): total_time = to_decimal(request.GET['total_time']) # bad! landings = request.GET['landings'] user = request.user flight = Flight.new_flight(user, total_time, landings) try: flight.save() except: raise HttpResponseError("invalid flight data") return render_to_response( context={'flights': Flight.objects.filter(request.user)} template='flights.html')

Page 21: Models, controllers and views

The three line MVC application!

def mini_controller(request): return {total_time: request.GET['total_time'], landings: request.GET['landings'], user: request.user}

def new_flight(request): args = mini_controller(request) flight = Flight.new_flight(*args).save() return render_to_response('view_flight.html', {'flight': flight})

Page 22: Models, controllers and views

The MVC color-wheelModel

ViewController

ModelViews / Forms

Context processors

Middleware

Page 23: Models, controllers and views

ModelViews

1. Projection of a Model (subclass) intended for use in a set of views2. Atomic elements that should not hinder the 'real' view's ability to do its job.

Page 24: Models, controllers and views

ModelViews

class HTMLFlight(Flight): def as_tr(self): """ >>> HTMLFlight.objects.get(pk=234321).as_tr() '<tr id="flight_234321"><td class="total_time">3.5</td>... """

class JSONFlight(Flight): def as_json(self): """ >>> JSONFlight.objects.get(pk=56216).as_json() '{id: 56216, plane: {tailnumber: "N63NE", type: "SA-227"... """

Page 25: Models, controllers and views

ModelView

def list_flights_controller(request, format): if format == 'json': return JSONFlight, 'flights.json' elif format == 'html': return HTMLFlight, 'flights.html'

def list_flights(request, format): Flight, view = list_flights_controller(request, format) flights = Flight.objects.filter(user=request.user) return render_to_response({'flights': flights}, view)

Page 26: Models, controllers and views

ModelView

flights.html: <table class="whatever"> {{ Flight.header }} {% for flight in flights %} {{ flight.as_tr }} {% endfor %} </table>flights.json:{user: {{ request.user }}, flights: {% for flight in flights %} {{ flight.as_json }}, {% endfor %}}

Page 27: Models, controllers and views

Good models are easy to test

class BaseFlightFailedTest(object): exc = Flight.InvalidFlightData def test(self): for kwargs in self.kwarg_set: self.assertRaises(Flight.new_flight(**kwargs), self.exc)

class TotalGreatestTest(TestCase, BaseFlightFailedTest): exc = Flight.TotalMustBeGreatest kwarg_set = [{'total_time': '3:50', 'pic': 9.6}, {'total_time': 1.0, 'night': 2.3}]

class NoNightTime(TestCase, BaseFlightFailedTest) kwarg_set = [{'total_time': 1, 'night': 0, 'night_landings': 5}]

Page 28: Models, controllers and views

Tips:

1. Don't pass request objects into the model a. It couples your model to HTTP requests b. Models should only work with raw data

2. Try to avoid putting business logic into a controller a. It makes it hard to reuse models

3. Pass in only one model object to a view. a. if you have trouble doing this, your models may be wrong b. helps keep the templates reusable.

4. Make an attempt to re-write all your controllers to be exactly 3 lines.

Page 29: Models, controllers and views

Giotto!

- New python web development framework!!- Absolutely nothing has been started yet.- Doesn't let you violate MVC.- There should be one-- and preferably only one --obvious way to do it.- "MV" framework. (micro controllers)- Completely automatic urls- plugins for features- plugins for controller backends. (commandline, http-get, etc)

Page 30: Models, controllers and views

Giotto Feature

@interfaces('http-get', 'commandline')class ShowFlightsForUser(Feature): """ Show flights for a given user """ controller = {'user': Input.data.user} model = Flight.objects.show_for_user view = ShowFlights

url for feature: {% http-get ShowFlightsForUser.html 59 %} ->

Logbook.com/flights/ShowFlightsForUser.html?user=59

Page 31: Models, controllers and views

Giotto Interfaces

@interfaces('http-put', 'commandline')class NewFlight(Feature): """ Create a new flight """ controller = {'user': Input.data.user, 'total_time': Input.data... model = Logbook.Flight.create view = SingleFlight

Using the feature: $ ./giotto.py logbook NewFlight --user=chris --total_time=3 ...or PUT /new_flight.json HTTP/1.1 user=chris&total_time=3 ...

Page 32: Models, controllers and views

Giotto Models

class Manager(models.Manager) def show_for_user(self, user): return self.filter(user=user)

def create(self, *args, **kwargs): # logic goes here return Flight(**kwargs)

class Flight(models.Model): attribute = models.Field() objects = Manager()

Page 33: Models, controllers and views

Accessing features

command line: $ ./giotto.py app feature format [args]

http-get POST app.com/feature.format HTTP/1.1 [args]

sms text "feature format [args]" to 3558526

The controller backend handles transporting data to/from the user

Page 34: Models, controllers and views

Controllers handle everything for you

@interfaces('http-get', 'commandline')class ShowRouteForFlight(Feature): """ Get the route for a single flight """ controller = Flight.id model = Flight.route view = SingleRouteView

Page 35: Models, controllers and views

Giotto Views

Take only a single model instance as the only context (obj)

Views can return anything, as long as the controller backend knows how to handle it.

templates make links to application features:

<a href="{% url_get ShowFlightsForUser obj.user %}"> see {{ obj.user }}'s flights!</a>

Page 36: Models, controllers and views

Giotto Views

class SingleRouteView(View): def png(self, route): "Given a route, return as a png image" return image_file

def kml(self, route): return kml_string

def html(self, route): return jinja2.render({'obj': route}, 'route.html')

{% http-get RouteForFlight.kml 36426 %}{% http-get RouteForFlight.html 36426 %}giotto logbook RouteForFlight png 36426 | file.png