.. _intro-tutorial01: ===================================== Writing your first Wilson App, part 1 ===================================== Welcome to Wilson! To get started, let's take a look at what's involved in setting up a basic blogging application. This assumes you already have :ref:`Wilson installed `. You can tell that Wilson is installed by running node and typing ``require('wilson')``. If that command returns a large object literal, then you're in the clear. Creating a project ================== Wilson is designed from the bottom up to feel like writing Django code. It borrows heavily from Django, not least of all in it's concept of "projects" and "applications". To start, ``cd`` into a directory where you'd like to start your Wilson project, and run ``wilson core:startproject mysite``. This will create a ``mysite`` directory in your current directory. It should contain:: mysite/ index.js urls.js settings.js manage These files are: * :file:`index.js`: Declares this directory as a module (similar to ``__init__.py`` in Python). * :file:`urls.js`: A stub for the base routes for your project. * :file:`settings.js`: Base settings for your project. * :file:`manage`: A shortcut to the ``wilson`` command that automatically uses the local ``settings.js``. Running your project -------------------- Now that you've created a base project, it might be worthwhile to check that it worked. Run ``mysite/manage core:runserver 8000`` to run your project as a Node.js server. When you open the ``http://localhost:8000`` in your browser, you should see a familiar welcome page (NOT IMPLEMENTED YET). Create an app ------------- Run ``mysite/manage core:startapp blog``. This should create a directory called ``blog`` containing:: blog/ views.js urls.js index.js models.js These files are: * :file:`views.js`: A file containing your view functions. * :file:`urls.js`: A file defining url patterns for your app. * :file:`models.js`: A file containing model definitions. * :file:`index.js`: The main file of the application -- this defines the linkage between your app and external applications, as well describing default settings for your app. Now hold on a minute before we get too far into this -- Wilson clearly borrows the idea of apps from Django. Why would we need an ``index.js`` file? In Django, your models are autodiscovered once you add that app into the list of ``INSTALLED_APPS``, and you can easily hook up urls by using ``include`` and pointing them at a urls file that exists on your ``PYTHONPATH``. Isn't it a little boilerplate-y to have to manually describe your app to the outside world? Application, and Application Instances -------------------------------------- In short, no. Wilson puts control over the apps you leverage in your site firmly back in your hands. This means that apps must provide your project with the following: * ``provides``: A one-word description of what this app provides to other apps -- for instance, "auth". * ``models``: An object literal of models that the application provides -- mapping ``name`` to ``model``. * ``external_apps``: An object literal of ``external_app_label`` to ``finding function``. For instance, you can select the primary application that provides ``auth`` by saying ``myauth:primary('auth')``. * ``urls``: If your app provides URLs, they should be supplied here. * ``template_directories``: if your app has template directories, they can be added here and will be automatically added to your template library's ``path``. * ``settings``: An object literal of settings values for your app. These can be overridden in a project's ``settings.js``, but provide useful defaults. * ``middleware``: An object literal mapping ``middleware_class_name`` to ``middleware_object``. Wilson requires application authors to provide this information because, until an app is instantiated -- that is, included in the object literal ``INSTALLED_APPS``, it has no meaning. Models cannot be imported from a base app, since Wilson does not know how you intend to reference it. Your view functions will usually be called as a method on the current application instance. That means that usable models are accessible as follows:: // for local models: var Entry = this.models.Entry; // for external dependencies var User = this.externals.auth.models.User; The main result is that you can safely install one application many times, and override it's external dependencies within your project's settings. Which is pretty cool. Sorry for the tangent --------------------- Getting back to it, let's install your application into your project. In settings.js, add:: 'myblog':app('blog'), to:: INSTALLED_APPS = { 'core':apps.usePrimary('wilson/core'), 'sessions':apps.usePrimary('wilson/contrib/sessions'), 'auth':apps.usePrimary('wilson/contrib/auth'), 'myblog':apps.use('blog'), // <--- our newly install blog! }; We've just added an instance of your blog app to this project's installed apps. The ``apps.use(...)`` portion of this plainly includes the blog app as a normal app with no special attributes. If you use ``apps.usePrimary(...)`` this app instance will be marked as the primary instance the app. For now, that doesn't matter too much, but it comes in handy when defining your application's external dependencies. And in your urls.js, add:: exports.patterns = routes('' , app('^/myblog/', 'myblog') ) This usage of ``app(...)`` denotes that we should include the url patterns from the app instance named "myblog", and all views within should execute in that app instance's context. And now your app is installed -- though it won't do much good as is, since there are no views! Open up ``blog/urls.js`` and it should look like this:: var escaperoute = require('escaperoute'), routes = escaperoute.routes, url = escaperoute.url, surl = escaperoute.surl; exports.patterns = routes('blog/views' //, url('list/$', 'list_view', 'blog-list-view') ); Edit the file to add a view:: exports.patterns = routes('blog/views' , url('list/$', 'list_view', 'list_view') , surl('([:w:d\\-_]+)/$', 'detail_view', 'detail_view') ); The first argument to ``routes`` defines where these views should be imported from (a la Django). The next two arguments define routes to named views. ``surl`` and ``url`` are fairly interchangeable -- ``surl`` allows you to write regexen using ``:`` as the special character instead of ``\``. The first argument is the regex to match, the second is either a string name of a function to be imported, or an actual callable. The last argument is the name of the view for reverse matching purposes. And open up ``views.js``, and edit it to look like so:: exports.detail_view = function(request, slug) { getObjectOr404(request)(this.models.Entry, {'slug':slug}, function(entry) { renderToResponse(request)('blog/entry_detail.html', { 'entry':entry }); }); }; exports.list_view = function(request) { this.models.Entry.objects.filter({'public':true}).all(function(objects, error) { if(error) { // this will trigger the error handling middleware request.attemptContinue([error]); } else { renderToResponse(request)('blog/entry_list.html', { 'entry_list':objects }); } }); }; This is slightly more complex than a Django ``views.py``. As you can see, arguments from the routes are appended to the view function when it is called -- hence, ``detail_view``'s ``slug`` will contain the match from the url. ``getObjectOr404`` takes a single argument -- ``request``, and returns a function that takes three arguments -- ``resource``, ``filter``, and ``callback``. If no matching object is found the request is immediately ended with a 404. Otherwise it is passed as the first argument to the ``callback`` function, where we take it and render it to a template. ``renderToResponse`` works similarly to ``getObjectOr404`` in that it takes a request and returns a function. This function's arguments are ``template_name`` and ``context_dict``. In list view, we query the ``Entry`` resource for all public entries, and provide it with a callback that takes ``object_list`` and ``error``. If there is an error, we pass it to the request and ask the request to ``attemptContinue``.When objects are passed into ``request.attemptContinue``, they are treated as errors and will trigger the ``processException`` middleware. Defining our models ------------------- Just like the divide between ``Application`` and ``ApplicationInstance``, models are divided into ``Models`` and ``Resources``. Models are the platonic ideal of Resources -- they define how a Resource should be created. To create a resource, they need the context of an ``ApplicationInstance``. To be more explicit: ``Model``'s cannot be queried, filtered, or used in any way, until they are transformed into ``Resource``'s. When operating within the context of an application instance, your resources are available to you via ``this.models.`` and ``this.externals..models.`` -- these can be used to create, read, update, and delete actual database rows. Let's define our Entry model in ``models.js``:: var reverse = wilson.urls.reverse; exports.Entry = models.model({ 'public':models.BooleanField(), 'title':models.CharField({max_length:255}), 'tease':models.CharField({max_length:255}), 'slug':models.CharField({max_length:40, unique:true}), 'body':models.TextField(), 'published':models.DateTimeField({default:function() { return new Date(); }}), 'author':models.ForeignKey(models.dep('ourauth', 'User'), {'related_name':'entry_set'}), 'toString':function() { return this.title; }, 'getAbsoluteURL':function() { return reverse([this._meta.app_name, 'detail_view'].join(':'), [ this.slug ]); } Meta:{ 'ordering':'-created' } }); This should look somewhat familiar if you're coming from Django. Kwargs are passed into the fields using object literals, the Meta class is present, and any function attached to the model -- like ``toString`` -- will always be executed in the context of the model instance. The ``author`` ForeignKey takes a model or dependency for its first argument. In this case, we depend upon an external app named ``ourauth``, and we want to use the ``User`` model from that app instance. We need to add ``ourauth`` as an external in our ``app.js`` file:: 'external_apps':{ 'ourauth':primary('auth'), } Should do the trick. This says that we want the primary app instance that provides the tag ``auth``, and we want to use it as ``ourauth``. Creating our database --------------------- Run ``createdb mysite``, and then add the following to your ``settings.js`` file:: var pieshop = require('pieshop'); pieshop.settings.set_addon('backend', 'postpie.backends:PostgresBackend'); pieshop.settings.set_addon('transport', 'postpie.transports:PostgresTransport'); pieshop.settings.set_value('DB_HOST', 'localhost'); pieshop.settings.set_value('DB_NAME', 'mysite'); We're stating that we're using the local database named ``mysite``, and that we want to use the ``PostgresBackend`` as our backend and ``PostgresTransport`` as our transport. Now we can run:: ./manage core:syncdb | psql mysite To sync our database.