5: Adding Resources To Hierarchies

Multiple views per type allowing addition of content anywhere in a resource tree.

Background

We now have multiple kinds of things, but only one view per resource type. We need the ability to add things to containers, then view and edit resources.

We will use the previously mentioned concept of named views. A name is a part of the URL that appears after the resource identifier. For example:

@view_config(context=Folder, name='add_document')

...means that this URL:

http://localhost:6543/some_folder/add_document

...will match the view being configured. It's as if you have an object-oriented web with operations on resources represented by a URL.

Goals

  • Allow adding and editing content in a resource tree.

  • Create a simple form which POSTs data.

  • Create a view which takes the POST data, creates a resource, and redirects to the newly-added resource.

  • Create per-type named views.

Steps

  1. We are going to use the previous step as our starting point:

    $ cd ..; cp -r typeviews addcontent; cd addcontent
    $ $VENV/bin/python setup.py develop
    
  2. Our views in addcontent/tutorial/views.py need type-specific registrations:

     1from random import randint
     2
     3from pyramid.httpexceptions import HTTPFound
     4from pyramid.location import lineage
     5from pyramid.view import view_config
     6
     7from .resources import (
     8    Root,
     9    Folder,
    10    Document
    11    )
    12
    13
    14class TutorialViews(object):
    15    def __init__(self, context, request):
    16        self.context = context
    17        self.request = request
    18        self.parents = reversed(list(lineage(context)))
    19
    20    @view_config(renderer='templates/root.jinja2',
    21                 context=Root)
    22    def root(self):
    23        page_title = 'Quick Tutorial: Root'
    24        return dict(page_title=page_title)
    25
    26    @view_config(renderer='templates/folder.jinja2',
    27                 context=Folder)
    28    def folder(self):
    29        page_title = 'Quick Tutorial: Folder'
    30        return dict(page_title=page_title)
    31
    32    @view_config(name='add_folder', context=Folder)
    33    def add_folder(self):
    34        # Make a new Folder
    35        title = self.request.POST['folder_title']
    36        name = str(randint(0, 999999))
    37        new_folder = Folder(name, self.context, title)
    38        self.context[name] = new_folder
    39
    40        # Redirect to the new folder
    41        url = self.request.resource_url(new_folder)
    42        return HTTPFound(location=url)
    43
    44    @view_config(name='add_document', context=Folder)
    45    def add_document(self):
    46        # Make a new Document
    47        title = self.request.POST['document_title']
    48        name = str(randint(0, 999999))
    49        new_document = Document(name, self.context, title)
    50        self.context[name] = new_document
    51
    52        # Redirect to the new document
    53        url = self.request.resource_url(new_document)
    54        return HTTPFound(location=url)
    55
    56    @view_config(renderer='templates/document.jinja2',
    57                 context=Document)
    58    def document(self):
    59        page_title = 'Quick Tutorial: Document'
    60        return dict(page_title=page_title)
    
  3. Make a re-usable snippet in addcontent/tutorial/templates/addform.jinja2 for adding content:

     1<p>
     2    <form class="form-inline"
     3          action="{{ request.resource_url(context, 'add_folder') }}"
     4          method="POST">
     5        <div class="form-group">
     6            <input class="form-control" name="folder_title"
     7                   placeholder="New folder title..."/>
     8        </div>
     9        <input type="submit" class="btn" value="Add Folder"/>
    10    </form>
    11</p>
    12<p>
    13    <form class="form-inline"
    14          action="{{ request.resource_url(context, 'add_document') }}"
    15          method="POST">
    16        <div class="form-group">
    17            <input class="form-control" name="document_title"
    18                   placeholder="New document title..."/>
    19        </div>
    20        <input type="submit" class="btn" value="Add Document"/>
    21    </form>
    22</p>
    
  4. Add this snippet to addcontent/tutorial/templates/root.jinja2:

     1{% extends "templates/layout.jinja2" %}
     2{% block content %}
     3
     4    <h2>{{ context.title }}</h2>
     5    <p>The root might have some other text.</p>
     6    {% include "templates/contents.jinja2" %}
     7
     8    {% include "templates/addform.jinja2" %}
     9
    10{% endblock content %}
    
  5. Forms are needed in addcontent/tutorial/templates/folder.jinja2:

    1{% extends "templates/layout.jinja2" %}
    2{% block content %}
    3
    4    <h2>{{ context.title }}</h2>
    5    {% include "templates/contents.jinja2" %}
    6
    7    {% include "templates/addform.jinja2" %}
    8
    9{% endblock content %}
    
  6. $ $VENV/bin/nosetests should report running 4 tests.

  7. Run your Pyramid application with:

    $ $VENV/bin/pserve development.ini --reload
    
  8. Open http://localhost:6543/ in your browser.

Analysis

Our views now represent a richer system, where form data can be processed to modify content in the tree. We do this by attaching named views to resource types, giving them a natural system for object-oriented operations.

To mimic uniqueness, we randomly choose a satisfactorily large number. For true uniqueness, we would also need to check that the number does not already exist at the same level of the resource tree.

We'll start to address a couple of issues brought up in the Extra Credit below in the next step of this tutorial, 6: Storing Resources In ZODB.

Extra Credit

  1. What happens if you add folders and documents, then restart your app?

  2. What happens if you remove the pseudo-random, pseudo-unique naming convention and replace it with a fixed value?