Analysis of Concurrent Software Models Using Partial Order Views
Models, controllers and views
-
Upload
priestc -
Category
Technology
-
view
152 -
download
0
Transcript of Models, controllers and views
The Three line MVC application
and introducing Giotto
Table on contents
First part: Why high level code organization schemes are important
Second part: All about MVC
Third Part: Giotto!
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
Non-MVC code
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)
Why?
1. Flexibility 2. Organization
Imagine...
1 app1 django view, 9000 lines of code
Imagine...
We want to fix this. Refactor!!
step 1: 1000 functions, 9 lines each
Imagine...
step 2: 100 classes, 10 functions each
And then...
App Models class class Views class class Controllers class class
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
Models
1. Usually the biggest part of your application2. Business Logic3. Not just database tables4. Should be completely controller independent
Views
1. All about formatting output from model.2. Templates/HTML3. Serializers4. Should be independent of any controllers (templates are portable)
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
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')
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
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!"
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!!!
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')
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})
The MVC color-wheelModel
ViewController
ModelViews / Forms
Context processors
Middleware
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.
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"... """
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)
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 %}}
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}]
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.
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)
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
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 ...
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()
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
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
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>
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