<<

Grok 4 Noobs.

P R Sephton 4 Noobs.

A gentle introduction to using the Grok

2 A gentle introduction to using the Grok web framework

Table of Contents:

1: What does Grok do particularly well? ... [7] 1.1: What are the nicest things about & Grok? ... [10] 2: Grok & Zope in a Nutshell ... [13] 3: The Compulsory Part about Installing Grok ... [19] 3.1: Installing Grok from Github ... [21] 3.2: Installation Notes - older projects ... [22] 4: A short introduction to the Zope Component Architecture ... [24] 4.1: The Z-Object Publishing Environment ... [29] 4.2: Interfaces compared to Abstract Bases ... [30] 4.2.1: Interfaces vs. Inheritence: A Real World Example ... [33] 4.3: Traversal and the Context ... [39] 4.4: The amazingly useful Utility ... [41] 4.5: Events Mechanism ... [42] 4.5.1: A quick megrok.rdb (SQLAlchemy) setup howto ... [44] 4.6: Extending existing objects with Annotations ... [46] 5: Designing a site, defining it's layout and implementing the code ... [47] 5.1: How to make applications with Grok ... [49] 5.1.1: Modeling data in an application ... [53] 5.1.1.1: Notes on multithreading ... [55] 5.1.1.2: The Scope of models in Traversal ... [57] 5.1.1.3: Rules of Persistence ... [58] 5.1.1.4: User Session Management ... [59] 5.1.2: Defining an Article and the Application ... [60] 5.1.3: HTML Re-use and site layout ... [62] 5.1.3.1: Macros: an alternative way to re-use HTML ... [67] 5.1.3.2: The full source for the layout.py module ... [69] 5.1.4: Providing for article addition, modification and deletion ... [72] 5.1.4.1: Another way of rendering hidden input widgets ... [77] 5.1.5: Using Viewlets and Viewlet Managers to build a Navigation Fabric ... [79] 5.1.5.1: Ordered menus from a normal grok.Container ... [83] 5.1.5.1.1: Full source for SortedArticles Module ... [88] 5.1.5.2: menu.py Full Source ... [92] 5.1.5.3: Styling menu elements as buttons or menus ... [94] 5.1.6: Site Navigation: Context sensitive menus ... [97] 5.1.7: Integrating the tinyMCE editor ... [100] 5.1.7.1: A less trivial integration for tinyMCE ... [101] 5.1.8: Adding attachments to articles ... [104] 5.1.8.1: Adding syntax highlighting ... [115] 5.1.9: Authentication, Authorisation and Access Control ... [117] 5.1.9.1: Defining erP missions ... [119] 5.1.9.2: Defining and Assigning olesR ... [121] 5.1.9.3: Installing a Pluggable Authentication Utility ... [122] 5.1.9.4: Building a user management interface ... [128] 5.1.9.5: Controlling access to views and data ... [135] 6: Adding functions and extensions ... [137] 6.1: Adding an Index Page ... [138] 6.2: Printing the site as a book ... [141] 6.3: Using Layers and Skins: How to re-skin your Grok site ... [145] 6.3.1: Using Bootstrap to fast track site development ... [146] 6.3.2: Providing a nav bar and breadcrumbs ... [148] 6.3.3: Some closing remarks ... [152] 6.4: Adding a Disqus comment section ... [153] 7: About ... [155] 8: Other Resources available on the web ... [156]

3 Grok 4 Noobs.

Grok is a web framework based upon the Zope Toolkit [1] (ZTK) and the Zope Component Architecture [2] (ZCA). Development for this framework began in 2006, with the view to making Zope-3 technology more accessible to newcomers. It achieves this quite admirably by using a mix of convention, special Grok directives, and decorators to make the task of building and maintaining web sites really simple.

A Little History

At one time Zope-2 [3] was the darling of the Python web development world. Zope-2 was based on a Through the Web (TTW) development model, which meant that developers could collaborate and program a Zope-2 installation remotely. It was fast (really fast and efficient for its time), simple, intuitive and had a large number of building blocks. To this day, Zope-2 remains popular as the basis for the -3 CMS.

Zope-3, these days called BlueBream [4], was intended to be a replacement for Zope-2, but this didn't quite work out as planned. As fast as features were added to Zope-3, they were back-ported to Zope-2 in a compatibility library called "Five" (5 = 2 + 3). This reduced and hampered motivation for adopting the newer Zope-3 in favor of Zope-2. At the same time, Ruby and Rails [5] happened which completely reimagined the way people viewed web frameworks. and other Python frameworks sprang up and quickly gained popularity as Python programmers began to realise that Python web development didn't have to be a ghastly chore. The Pyramid web framework [6] came about as an effort to make Zope technologies more accessible and less intimidating to newcomers. Grok happened at around the same time and shared many of the same motives.

Zope-2 popularised and was popularised by TTW (Through the Web) development, but Zope-3, as was the case for most of the other Python frameworks, was not a TTW framework.

The promise of TTW development was that anyone, not just developers, could develop web sites by adding new, or stringing together pre-defined web components online, often never having to write a line of actual code. Today, popular site builders such as WIX or Weebly or tools like WordPress accomplish that vision.

It was arguably the confusion surrounding what Zope actually is, which brought about all of the misunderstanding, most of the confusion and much of the criticism surrounding the product. The mistake of Zope was mostly one of branding.

Zope 4 development began more recently, and is a project to build a new TTW web platform with all the bells and whistles originally found in Zope 2. The advances made in

1. The Zope Toolkit API: http://docs.zope.org/zopetoolkit/ 2. ZCA Book: http://muthukadan.net/docs/zca.html 3. The zope-2 home page: http://zope2.zope.org/ 4. Bluebream Framework: http://bluebream.zope.org/ 5. : https://rubyonrails.org/ 6. Pyramid: https://trypyramid.com/

4 A gentle introduction to using the Grok web framework

the Zope Toolkit are brought forward into Zope 4, and the new platform contains many new and exciting features.

So why is Grok important?

Grok is quite possibly the best way to make use of the over 15 years of accumulated intellectual capital which comprises the Zope Toolkit (ZTK) libraries. It is open and accessible, and its seamless use of the Zope Component Architecture (ZCA) is intuitive and simple.

Although BlueBream exists, along with it's amazingly complete set of documentation and thoroughly tested code, the same reasons why people migrated away from Zope-3 in the first place persist today. When the configuration for components is maintained in ZCML, separate from their definition and implementation, this separation can lead to unnecessary complexity and poor maintainability. ZCML was a particularly unpopular idea, and it is unlikely that anyone would want to, nor is it entirely advisable at this stage to adopt the BlueBream framework for new projects.

Pyramid was, at least initially, also based upon the Zope Toolkit. However, Pyramid takes the approach of wrapping and hiding the component architecture (ZCA) from it's users, and there is not much recognisable left of the original architecture. It tries hard to be a generalised solution for all your needs, and succeeds at this in many ways.

Grok, on the other hand is very different. With an initial investment of a few weeks (depending on learning speed) to properly understand how to fully exploit the Zope Component Architecture (ZCA), Grok gives you a mature and well documented framework capable of delivering relatively complex products in comparatively very little time.

What's this site all about?

This site documents it's own implementation. All source code is freely available on the web [1], and may be used to recreate instances of this site or to explore concepts and ideas around the Grok web framework.

To demonstrate the flexibility, utility and extensibility of Grok, this wiki uses the built in object database (ZODB) to store editable articles which describe the site itself. From the database, we can later produce a dynamic index, or a printable book. This site may not be perfect in every way, but it does serve its purpose in showing how a Grok site may be developed modularly using pluggable and reusable components for things like authentication, layout, menus, HTML editors and even skinning an existing site using Bootstrap [2] or adding Disqus [3] for comments and feedback.

The Grok tutorial [4] does a great job at showing off the simplicity of the framework. It is unfortunately not so good at showing how to progress beyond the simple things, or how the ZCA can help you build great sites quickly and maintainably instead of being the millstone around everyone's necks they imagine it to be. The ZCA is the elephant in the

1. Source code for this site: https://github.com/prsephton/Grok4Noobs 2. Bootstrap: https://getbootstrap.com/ 3. Disqus: https://disqus.com/ 4. The Grok Tutorial: http://grok.zope.org/doc/current/tutorial.html

5 Grok 4 Noobs. room everyone is ignoring which, rather than being treated like the embarrasing relative, should be the first thing proudly introduced to newcomers.

In many ways, Grok's choice of a (very cute) cave man as an emblem is so very wrong. Whilst one understands the humour of man with a club smashing obstacles like ZCML, the overriding association created in peoples fertile imaginations is undoubtedly that Grok, like cavemen, is a relic of a prehistoric age, working with ancient libraries and technologies.

Nothing could be further from the truth.

While one might argue that pyramids are not much newer than cavemen, one can only wonder at the marketing skills of Python developers. This faux pas must undoubtedly have contributed to the mindset where Zope, the ZTK, Grok, and most of the related technologies belong to a bygone era. At least the word Django has a nice modern ring to it.

The intention of this site is to serve as a testimony to Grok and the underlying Zope Toolkit, as well as the amazing work done so selflessly by the Zope, Grok & Python developers over the years, and to demonstrate that although there are a lot of newer frameworks, newer code and architectures are not necessarily always better than older code and architectures.

6 1: What does Grok do particularly well?

1: What does Grok do particularly well?

The Grok framework is by nature component driven. It uses the Zope Component Architecture. This means that it uses the idea of Objects which implement Interfaces to make Components that provide integration and function. This is a very powerful concept for a number of reasons [1].

By way of disambiguation, we use the term Widget to refer to user interface components such as select boxes, menus and form inputs. When .js and React folks speak of developing Web Components, they refer to client-side Widgets or forms based upon TypeScript coded objects.

When talking about Components, we do not refer to widgets, but rather to the building blocks such as the server-side views and models which make up an application. Similarly, the term Interface as used here, does not refer to a user interface, but instead to a specification describing attributes and methods implemented by components. A component architecture is an application framework made up from such building blocks.

To those who have built programs using the C# or languages, the concept of an interface, or software components implementing interfaces should be quite familiar to you. Although Python does not enforce Zope interfaces, they do bring significant utility to the language.

If you as a developer are comfortable with a more formal approach to software development, and with the concepts behind component architectures, then Grok is a good match for you.

The ZODB

ZODB (The Z-Object Database) is a simple, non relational Python object database which provides persistence for your Python objects without you having to do much.

Grok seamlessly integrates with the ZODB object database [2], and for many projects this will be the only database you need. The design and maintenance overheads of relational databases are eliminated if one uses the ZODB for storage. However, one is not limited to this approach, and Grok will also integrate well through Storm [3] or SQLAlchemy [4] to any relational database of your choice. MongoDB [5] also has a wrapper [6] available.

Standard development tools

When writing Grok applications, you use the same standard text editor or IDE (eg. Eclipse / PyCharm / VS Code) you would use to create any other Python application. This means

1. Component based software engineering: https://en.wikipedia.org/wiki/Component-based_software_engineering 2. The ZODB: http://www.zodb.org/en/latest/ 3. STORM: https://storm.canonical.com/ 4. SQLAlchemy: http://www.sqlalchemy.org/ 5. MongoDB: https://www.mongodb.org/ 6. MongoDB wrapper: https://github.com/zopefoundation/mongopersist

7 Grok 4 Noobs. you get all the benefits such as syntax highlighting, introspection, command line completion, automatic documentation, version control integration with systems such as Git or SVN, and collaborative team development.

"...but isn't Grok through the web (TTW)"?

No, it is not. Grok is based on Zope 3 and the Zope Toolkit (ZTK). Zope 2 is still available for TTW development, if that us what you want to do. However, TTW has long been considered to be a "Unpythonic" way of doing things, and has many limitations.

Easy to read and maintain source code

Coding for Grok is similar in many ways to that of more popular micro frameworks such as , but with a few differences. Grok uses traversal as a means to identifying a model which represents your data, and a view which can render a response to the HTTP request, while other frameworks use explicit request routing to directly identify only the view and view arguments from which to produce a response.

Therefore, coding a Grok view is simpler in many cases since your view already has access to the model it needs to produce a response at the time it is called, and there is often no need to include database lookup code within the view.

Traversal

Traversal is a great way to build web sites. Each part in the URL can identify a model, a method, attribute or view by name. Models are stored persistently in the ZODB, and attributes or methods in models may be flagged as traversable. Views are identified at the end of the traversal chain and would apply to the most recently identified data object. The ZODB is hierarchical, like a file system or directory, so this whole process is very efficient indeed.

In the words of John Stahl (former Plone foundation president), "Data in Zope is like a flowchart. Data in other web application frameworks is like a spreadsheet."

Modularity

Because components are so modular, it is easy to extend the function of an existing site without altering unrelated code. This means that Grok is very suitable to ad hoc changes or multi-developer teams working on the same project. Python modules can be produced as self contained units to provide new functionality, and this approach greatly improves testing and maintenance of code. Conversely, some other frameworks lead to monolithic files (eg. outes.py,r configure.py, etc) or import dependency problems.

Extensibility

Since the Zope Toolkit already provides a rich selection of pluggable components, it is easy to implement some otherwise complex features. Even though there are some standard modules for most things, it is relatively easy to plug in your own version as a replacement for existing modules. For example, to implement an LDAP based authentication and plug that into the Pluggable Authentication Utility infrastructure.

8 1: What does Grok do particularly well?

Simplicity

Grok is a lot simpler to learn, use and maintain than a great number of other web development tools out there. It is (subjectively speaking)

• Far simpler than PHP both syntactically as well as regards using frameworks such as [1] . • Django implements it's own ORM which means you need to know all about relational databases to use it well. • The simple project with Django is a lot more complex to maintain than an equivalent Grok site, and the complex Django site is a lot more work than the equivalent complex Grok site. • Far easier to produce something decent in plain Grok than in Zope-2 • Way more discoverable and maintainable than a Zope 2 equivalent project

This site will try to prove the above sweeping statements.

Rich legacy of library code

There are some really great libraries available, for example schema based automatic forms generation based upon zope.formlib, or z3c.form, which make implementing a site a real pleasure. The consistent use of interfaces in the design of a site makes it adaptable for many things unforseen at the start of a project.

Based on Zope, Grok is really fast and scalable, outperforming many other web frameworks. It integrates well with WSGI, and testing or debugging a Grok application is made simple by configuring your IDE to use the specialised interpreter created as part of the based installation.

The Zope framework is very much alive largely due to the continuous use and development of commonly used libraries and features for the Plone content management system. Although Plone is based upon the Zope 2 framework, and Grok on Zope 3, many, if not all of the libraries have been back ported to work with Zope 2. The popular Pyramid framework also uses the same Zope Toolkit that Grok uses. So, although there may not be much direct activity visible for Zope 3 or Grok, under the hood the project is still very much alive and well.

There are many other nice things about developing apps with Grok, which only become clear over time through continued use of the framework.

1. Laravel: https://laravel.com/

9 Grok 4 Noobs.

1.1: What are the nicest things about Zope & Grok?

Disclaimer and Warning: The following is indiluted and unmitigated personal advocacy.

Why Grok instead of all the other frameworks out there? Why not Django or Flask? Why even Python for that matter? Why not simply adopt Node.js and have done with it all? What is it about Grok that prevents you from migrating to an alternative back-end web platform?

As a continuing user (not developer) of the Grok and Zope technology, some of the features have become essential to me, and argue strongly against my adopting alternate frameworks for personal use. What are they?

• Right up there in first place has to be the Zope component architecture. This is an amazing time saver and has many uses outside of the Grok framework. • Zope Page Templates or Chamelion for HTML templates. Being able to write page templates as plain old HTML is an absolute winner for me. • ZODB for simple sites. A built-in database from the get-go is another huge time saver. (certainly not abandoned, and actively developed and maintained) • Traversal. Once one understands how adaptive traversal can be, there can be no other way to structure a site! • Simple REST, JSON and XMLRPC (Soap) extensions. • Vocabularies - I cannot think of another framework that provides this sort of ease or flexibility for populating dropdown lists or choices. • Brevity. You can do most things with surprisingly little code. • Transparent use of SqlAlchemy, with reflected tables lets one maintain table definitions in SQL where they belong, and not in Python code. The Python classes representing these tables are automatically populated at startup- along with table dependencies. So there is no "database<->code.py synchronisaton" required when a database schema changes, and new fields become new attributes in a class without anything having to be done. Reflection is seriously the only sane way to work with relational databases. • Modularity and code organisation. My code can be organised as Python modules with no required dependencies. Because of the pluggable component oriented approach, there are no circular dependencies or imports. This leads to really simple debugging and maintenance. • You can add to the above: simple skinning, really easy access control and pluggable authentication, extensibility of existing views and rock solid performance.

As a mere user of Grok, I am allowed to have an opinion. If I were a developer of Grok/Zope, I would be required to be super-critical.

So, why not Pyramid? Does it not check all the boxes above? Why, yes, I believe it does!

Except that Pyramid is, in the immortal words of it's author, "unopinionated".

Fact is, I like opinions. Yes, I also like the freedom to choose between ZODB and SQLAlchemy, and perhaps to a lesser extent the freedom to use explicit routing rather than traversal for URL dispatch. However, I especially like the simplicity of a framework

10 1.1: What are the nicest things about Zope & Grok? telling me "this is how to do the thing unless you decide different". Consider the infamous "hello world" example in Pyramid:

from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response

def hello_world(request): return Response('Hello %(name)s!' % request.matchdict)

if __name__ == '__main__': with Configurator() as config: config.add_route('hello', '/hello/{name}') config.add_view(hello_world, route_name='hello') app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever()

Admittedly, the above is meant to describe exactly what Pyramid is doing to serve up the application, and so serves to explain the process the app server goes through in quite some detail.

It is, however, rather verbose.

Why should I care about the detail?

To build this simple app from scratch, rather than copy/paste from boilerplate code or generating from a scaffold, you need to know quite a lot of detail:

• You need to know about make_server, Configurator and Response, where to import them from and what they do, how to use them and what functions they provide. • You need to know how and where to use config.add_route(), config.add_view() and config.make_wsgi_app().

It's rather easy to imagine all that explicit configuration (view and route specification) getting out of hand for very large applications, but Pyramid has this going for it: You always know precisely what it is doing, and there is no magic.

In this regard, Pyramid is similar to most other Python frameworks.

Compare the Grok equivalent hello world:

import grok

class App(grok.Model, grok.Application): ''' This is an application called "app" '''

class Index(grok.View): ''' This is the default view for "app" '''

def render(self, who='Anonymous'): return """

Hello {}

""".format(who)

Let's compare the Grok example to Pyramid:

11 Grok 4 Noobs.

• There's a fraction of the code in the Grok example (excluding comments, 5 lines vs. 12). • There's an implicit rather than explicit URL (magic). • The view is automatically configured (magic). • The detail about the server implementation is completely hidden (magic). • You need to know to make a class deriving from grok.Application for your model. • You need to know to call your default view 'index'. • Grok says "thou shalt use a class for a view and a class for thy model". Grok has an opinion. • Need another application? write a new class deriving from grok.Application. It's a bit less simple with Pyramid.

So essentially, where there is no magic with Pyramid, there is certainly some magic in the Grok example; however, the Grok magic greatly reduces boilerplate and thus the effort needed to produce, maintain and extend the app. As your project grows, the magic provides many more features and capabilities to make your life as a developer easier.

12 2: Grok & Zope in a Nutshell

2: Grok & Zope in a Nutshell

Zope (and Grok) are not really magical, although reading some of the code may leave the unintiated wondering how the framework manages to do things so sweetly.

Here follows a concise explanation of most of the core concepts surrounding a Zope/ Grok application architecture. As understanding creeps in, some of the magic will evaporate into thin air!

Basic concepts contained in Zope 3 (Bluebream) and Grok:

1. The Zope publisher converts HTTP requests into request objects, calls Python callables (views) which return response objects, and then converts the response object into HTTP responses.

2. A URL is interpreted by the publisher to provide both a data object, and a view object. A request object is provided by the WSGI layer as shown in the diagram above.

The above simply says that "app" is the name of a Data item which is an instance of "Model", and that "appview" is the name of a view on the model. We can look up a View object using the Data object and a Request object

13 Grok 4 Noobs.

(provided by the WSGI layer) and create an HTTP Response by calling the View.

1. Data objects are run time instances of a defined Python data class (a model). 2. An application name ("app" in the above URL) is specified when a Grok application is installed by the Zope Management Interface (ZMI). A Grok application is simply a special type of Python data object (a kind of model), deriving from grok.Application. 3. The default view name, unless specified in the URL, is 'index'. 4. Data objects may be containers for other data objects, forming a hierarchy. 1. Data objects are identified through the traversal process (i.e. reading the URL) by name. 2. A data object that results from interpreting the URL is a location. A location has a name, and a parent. 3. A Model class name does not define the name of a data object! For example, if the class name of a model is Book, then the data object name is "Moby Dick" or "Freckles", not "Book". 5. Data objects may be created on demand (transient instances of a model) or persisted in a database. 6. ZODB based URL's are interpreted by traversing the ZODB hierarchial structure, and identify persistent data objects. 1. The traject [1] and megrok.traject [2] packages provide for route based dispatch where desired. 2. Whether traversal or routing is used for URL dispatch, the URL always defines a model. 3. This is different from most other web frameworks, where URL's typically identify only a view which must instantiate a model. 7. View objects are python callables which return an HTML response as text, or something that can be turned into an HTTP response. Views are instantiated using both a data object and a request object. 3. The Zope Component Architecture (ZCA) provides the hard wiring for the framework. The ZCA lets a developer write pluggable components which can be easily updated or replaced without breaking the whole.

1. The Traject package: https://pypi.python.org/pypi/traject/0.10.1 2. The megrok.traject package: https://pypi.python.org/pypi/megrok.traject

14 2: Grok & Zope in a Nutshell

4. Generic types (interfaces) can be associated with models. 1. An interface defines a model's methods and attributes. 2. A model may implement zero, one or more interfaces. 3. A component is a Python object (such as a model) which implements an Interface. So for example, "Moby Dick" in the above diagram is a component which implements an IBook interface. 4. Knowing the type for a data object enables some intelligent behaviour. 1. Automatic HTML forms 2. Validation of attribute (or field) values 3. Converting one type into another type using an adapter. 5. Interfaces may inherit from other interfaces to form a type hierarchy (or logical hierarchy). 1. For example, an integer and a float are both a type of number. 2. A model inheritence (or physical) hierarchy is not needed to implement generic behaviour, as is the case for Python ABC's. So dependency hell is neatly averted. 5. Singletons may be registered as persistent LocalUtility or non-persistent GlobalUtility objects. Typical uses for such objects would be to store user identities in a persistent database, or to contain reference data which should be initialised only once at startup. These object types are registered with and located via the ZCA. 6. Adapters are converters from one registered type into another. For example, we may need to find a search interface for a book. At another time, we might want a search interface for an address list. The search interface remains the same, but the context might change.

15 Grok 4 Noobs.

For example: class BookSearch(grok.Adapter): grok.context(IBook) grok.implements(IIndexSearch)

def __new__(self, book): return BookIndex(book) 1. The ZCA can look up and return a registered callable based on what is being adapted, what it's being adapted to, and a name. This happens very efficiently. eg. book_index = IIndexSearch(mybook) or book_index = queryAdapter(mybook, interface=IIndexSearch, name=u'') ...etc 2. This provides for numerous simple or advanced patterns, for example, 1. turn a banana object, which is a type of fruit, into an RGB colour. color = IRGB(banana) 2. take an svg XML text data element, and turn it into a rendered PNG. res = IPNGResult(mysvg) 3. take the same XML text element, and turn it into a JPEG. res = IJPGResult(mysvg) 4. convert a person object and a request object into HTML representing an edit form called 'edit'. form = getMultiAdapter([person, request], name='edit') html = form() 5. convert a request object into an authenticated session object...Adapters convert one thing into another thing. They are Python classes or callables registered with the ZCA. session = ISession(request)

7. Views are defined against either models, or interfaces.

16 2: Grok & Zope in a Nutshell

For Example: class BookIndexView(grok.View): grok.context(IBook) grok.name('index')

def render(self): return "

A book index view

"

1. A view against an interface is available to all models which implement that interface. So a cookbook and a story book (specialisations of IBook) can both share a visual search interface. 2. Traversal of a URL provides a data object (called a context). 3. Views are registered adapters which convert a data type and a request into an HTTP response. 4. Views may be rendered using a template (zpt, chameleon, genshi, jinja2 etc), or directly in Python code. 5. The main difference from routing based frameworks, is that with traversal the view already has a model instance (the context) at the time it is called. 8. Basic persistence mechanism for application registration and configuration is provided via ZODB, and this use may be used for general application data persistence as well. However, nothing prevents the use of other methods of persistence for your application (sqlite/postgres/mysql/etc). 9. Zope toolkit (ZTK) provides a set of modular extensions for easy inclusion in your app. For example: 1. zope.authentication: Add authentication for users, and access control to your views. 2. zope.catalog: Generate ZODB indexes to enable search in your ZODB based app. 3. zope.copypasteremove: CRUD on your ZODB stored items 4. zope.formlib: Automatic zope forms widgets 5. zope.schema: Some field definitions for translation in zope.formlib 6. etc. The library is quite extensive. A good place to read about the ZTK, is the bluebream documentation [1]. Another good place is the Grok documentation [2].

1. The Bluebream Documentation: http://bluebream.zope.org/doc/1.0/index.html

17 Grok 4 Noobs.

7. Many of the modules are independent, and may be used outside of Zope 3 or Grok based applications.

Differences between Grok and Zope 3:

1. Both Zope 3 and Grok make use of ZCML (Zope Configuration Markup Language) to configure the framework. However, Grok makes use of it to a much lesser extent. 1. While some may not have issue maintaining configuration in XML files apart from the Python code, many if not most developers would view this negatively. As someone recently remarked, that's not separation of concerns, that's separation of technologies! 2. Grok uses a library called martian to implement Grok directives. These directives may be used to configure the framework at load time. This greatly simplifies the task of maintaining a Zope based site. 3. What kind of configuration? In Zope, anything that needs to be registered with the ZCA is normally configured using ZCML. This includes views, adapters global and local utilities, notification messages, access control items and relation to views and so forth. Grok uses directives for all these cases. 2. Grok wraps some of the Zope 3 modules to present a friendier interface. This does not restrict in any way your access to Zope modules. 3. Some extension modules to Grok make it exceedingly simple to implement things like login pages, relational database access via SQLAlchemy, etc. 4. The Zope Management Interface (ZMI) for Zope 3 is currently friendilier and more functional than that of Grok. 5. Grok is still in the process of being fully ported to Python 3, where the ZTK is Python 3.6 compliant.

2. Grok packages: http://grok.zope.org/doc/current/packages.html

18 3: The Compulsory Part about Installing Grok

3: The Compulsory Part about Installing Grok

It is customary for any tutorial to contain an obligatory section about how to install the products being documented. Books about tech products likewise follow the same recipe. Rather than duplicate perfectly good material found elsewhere, we shall instead highlight here some of the things which are less obvious to newcomers.

Rather excellent details on how to go about installing Grok may be found on the getting started page [1] on the main Grok web site.

______

It all boils down to:

$ easy_install grokproject $ grokproject mygrok

______

This will build a directory structure containing a working web server framework and a sample application in mygrok/src/mygrok/app.py.

Assuming you used mygrok as the name of your grok project, some of the details are not necessarily intuitive to a first time user. The following comments and hints should prevent quite a bit of frustration for newcomers;

• setup.py contains the configuration for your Grok installation, including any package dependencies • buildout.cfg contains additional information about versions of packages and buildout recipes which should be installed • running bin/buildout is necessary after changing either of the above files • bin/paster serve --reload parts/etc/deploy.ini will start up a server on port 8080 in production mode • bin/paster serve --reload parts/etc/debug.ini will start up a server on port 8080 in development mode • connecting to localhost on port 8080 brings up a management interface, where you can add applications to your server • Grok finds applications by scanning the directory structure for Python classes which derive from grok.Application. • It is best to create your own applications in their own Python package directories; IOW, directories containing __init__.py • View templates (or page templates) are located in a directory called _templates in the same directory as your module. For example, with app.py, the templates are located in app_templates. • It is best to include "zope.app.schema" in your setup.py immediately when starting out. There is an outstanding problem whereby global utilities (grok.GlobalUtility) don't register themselves unless this is done. • Your setup.py includes a section for fanstatic:

1. Getting Started with Grok: http://grok.zope.org/about/download

19 Grok 4 Noobs.

entry_points={ 'fanstatic.libraries': [ 'mygrok = mygrok.resource:library', ... ] } which should be modified for each package directory from where you want to include static resources. There is more documentation on the Grok site [1] about how to use fanstatic, but its a little hard to find. Also look at the grokproject documentation [2], and at fanstatics home page [3]. • buildout.cfg has a include-site-packages = false setting which may be set true if you want your local Grok instance to access site-wide packages. • The easiest way to set up your IDE is to set the interpreter to ~/mygrok/bin/ python_console. This ensures that all of the buildout eggs are available to your IDE so that syntax highlighting and introspection works properly.

1. Fanstatic integration: http://grok.zope.org/doc/community/view_generation/fanstatic_resources.html 2. GrokProject at pypi: https://pypi.python.org/pypi/grokproject 3. Fanstatic: http://www.fanstatic.org/en/0.14/integration.html

20 3.1: Installing Grok from Github

3.1: Installing Grok from Github

An alternative to installing grokproject via the cheeseshop, is to install from the github repo source. install git (sudo apt-get install git)

1. git clone https://github.com/zopefoundation/grokproject.git 2. cd grokproject 3. python ./bootstrap.py 4. bin/buildout 5. bin/grokproject eg. bin/grokproject ~/myproject 6. You will be prompted for a superuser name and password

21 Grok 4 Noobs.

3.2: Installation Notes - older projects

A guide to upgrading older projects

Existing projects based on bin/buildout may be broken due to older grokproject(s) still referring to the subversion source at http://grok.zope.org. This may be fixed by changing the line:

extends = http://grok.zope.org/releaseinfo/1.5.5/versions.cfg

in buildout.cfg to

extends = https://raw.githubusercontent.com/zopefoundation/ grokproject/master/versions/1.5.5/versions.cfg

PyPI recently (Nov. 2017) dropped support for HTTP, and all connections must use the HTTPS protocol. This means that the version of bootstrap.py/buildout you may be using can no longer work correctly, and must be upgraded. To do this, download bootstrap.py:

wget https://raw.githubusercontent.com/buildout/buildout/master/ bootstrap/bootstrap.py and run python ./bootstrap.py . This should update buildout to the current version.

The next step is to edit your buildout.cfg, and change all occurrences of z3c.recipe.script to zc.recipe.egg. Also remove the line buildout.dumppickedversions, which has been deprecated. Then run bin/buildout, which should bring your source eggs up to date.

One slight problem, is that the interpreter installed by the zc.recipe.egg is not as nice s the previous one, and cannot be used as an interpreter for IDE installations such as Eclipse, Comodo or PyCharm. To fix this, make a directory src/[project]/scripts and add __init__.py and interpreter.py. interpreter.py looks like this:

import os, sys

def main(): os.environ['PYTHONPATH'] = ':'.join(sys.path) os.execve(sys.executable, sys.argv, os.environ)

Now edit your buildout.cfg, and add a part called 'console' under parts:

parts = app ... console ...

First remove the existing line referring to python-console, and then define the section for console as follows:

[console] recipe = zc.recipe.egg:scripts

22 3.2: Installation Notes - older projects

eggs = xxx entry-points = python-console=xxx.scripts.interpreter:main where xxx is your project name.

When you run bin/buildout, the new python-console installed will be usable from an IDE.

23 Grok 4 Noobs.

4: A short introduction to the Zope Component Architecture

The Zope Component Architecture [1] is, loosely speaking, a registry for components. These components are defined in terms of their associated Interfaces [2]. The reason why this is useful, is that the registry provides an easy and fast lookup to find implementations of various interfaces, and a way to specify relationships between components of the system being developed.

Where is the ZCA used?

The ZCA is used wherever a pluggable interface is needed. For example, views on data models: to define a view, it is firstegister r ed with the ZCA. When the Grok framework receives a request, the request URL maps to a data model, and an associated view is looked up by name in the site registry. Similarly, event handlers may be registered with the ZCA and an appropriate event handler located later, using the site registry.

Note that this behaviour is very different indeed from most other web frameworks, which use "routing" to directly identify a view. Routing does not usually identify a model, and it's up to the view to locate the needed data. Zope (and Grok) works differently, using "traversal" to identify both the data model and the associated view, and this can greatly simplify code.

What are the advantages of this approach?

In a nutshell, efficiency, simplicity, scalability and flexibility. By using a common and uniform way to glue bits and pieces of the framework together, the way these things fit together is formalised. Because any component may be easily replaced by another by simply registering the new component with the ZCA, the entire framework becomes pluggable and extensible.

A further distinct advantage is separation of concerns. By building your component to meet the specification of a public interface, none of the implementation detail beyond the interface itself needs to be exposed. On a practical level, this means that your source code is better organised, better documented and more managable.

There are sure to be those readers who have run into circular dependancy problems while coding web apps. The use of global or local utities and adapters completely circumvents this problem. We will show how views may be shared across multiple URL's with the greatest of ease using the ZCA.

1. The ZCA: http://docs.zope.org/zope.component/narr.html 2. Zope Interfaces: http://bluebream.zope.org/doc/1.0/manual/interface.html

24 4: A short introduction to the Zope Component Architecture

Are there any caveats to using the ZCA?

Sure there are. No architecture is without faults. One problem is that one can easily get carried away. Not every function needs to be a pluggable utility or registered with the site registry. It is quite easy to end up adding rather than reducing complexity (AKA "Death by Abstraction").

This is an easy mistake to make for developers new to Grok; as they start getting familiar with and excited about concepts like utilities and adapters, they immediately try to apply the concepts to everything they do (its so cool, right?).

There is also quite a steep learning curve and conceptual hill to climb before one can become really productive. One first needs to understand the types of components supported by the ZCA and where best to use them. There are a huge number of components in the Zope Toolkit, and an awful lot to learn about.

What types of components are defined by the ZCA?

There are two basic types of components: Utilities, and Adapters. Other component types are specialisations of these.

Utilities

Utilities are simple plug points identified by an interface. For example, the ZTK (zope.schema.vocabulary) defines IVocabularyFactory as a kind of utility. When you define an instance of this interface, you give it a name- for example "fruit". The implementation of the factory returns a list of terms, each being a tuple consisting of a value, a token and a title. For example, ('orange', str(0), u'An Orange'). Later, when the framework needs a list of "fruit" options, perhaps whilst rendering options for a select tag, it can look for an IVocabularyFactory utility with a name of "fruit".

class Fruit(grok.GlobalUtility) grok.implements(IVocabularyFactory) grok.name('fruit')

def __call__(self, context): typesOfFruit = [('orange', u'An Orange'), ('', u"A Pear"), ('banana', u"A Banana")] terms = [SimpleVocabulary.createTerm(value, str(token), title) for token, (val, title) in enumerate(typesOfFruit)] return SimpleVocabulary(terms)

Of course we can have all sorts of vocabularies defined and registered, but since they all implement the same interface, we can treat them in precisely the same way. So, for example, we can have a component which renders a vocabulary as a dropdown list, and that component will work for all vocabularies. Another component might render vocabularies as a horizontal radio button list.

Utilities are singletons. For global utilities, there is only ever one instance of the object. Local utilities are object instances installed within a site manager, and will be specifically available for the currently active site.

25 Grok 4 Noobs.

Python class advisors

You will notice an unfamiliar syntax being used to specify that the Fruit class implements the IVocabularyFactory interface.

grok.implements(IVocabularyFactory) grok.name('fruit')

Such directives are Grok's way of removing the need for registering utilities and views in ZCML (Zope Configuration Markup Language). Instead of maintaining configuration outside of Python, it becomes part of specifying your classes. Another way Grok manages the ZCA configuration is through decorators, although this usage may be minimal.

There are a good number of such Grok directives registered, and the way they work is based upon some trickery implemented in the zope.interface.advice module. You can read more about class advisors in a rather old blog [1] on the subject.

NOTE: Decorators are often used by other Python frameworks for route configuration. ZCML for configuration is eminiscentr of the "old way" for ASP route configuration, while decorators are similar to the "new way" for configuring ASP routes. This similarity is misleading. Zope & Grok do not configure routes in a comparable way using decorators, but may use decorators to configure Zope adapters and utilities.

Adapters

Adapters are things which make one interface look like another. Another way of saying it, is that an Adapter is a Python callable which takes one thing as an argument, and returns another kind of thing.

A MultiAdapter is a Python callable which... wait for it ... takes more than one kind of thing and turns it into another kind of thing.

The concept is so simple as to be almost stupid. This may be the reason some programmers think the ZCA is complicated: they keep saying to themselves "it can't be that simple!" and overthinking it.

The difference though, is that the ZCA provides a way to register such callable objects, by name if necessary, and to find them later according to argument type and/or name. That is where the power and utility of the ZCA comes from.

The ZCA documentation [2] goes on about UK wall sockets and USA wall sockets and a physical adapter that makes a UK socket look like a USA one. Good enough as analogies go.

In the case of the ZCA, ZTK and Grok, a real world example may be better than an analogy.

1. Class advisors: https://blogs.gnome.org/jamesh/2005/09/08/python-class-advisors/ 2. Zope Component Architecture: http://docs.zope.org/zope.component/socketexample.html

26 4: A short introduction to the Zope Component Architecture

A Zope schema is a kind of Interface which defines fields as attributes. These fields have a type such as Int, Text, Boolean etc. Schemas may be used to automatically generate add, edit or view forms for classes which implement the schema. So for example,

class ImySchema(Interface): name = TextLine(title=u'Name:') surname = TextLine(title=u'Surname:') ...

We can then define a model which implements the schema:

class MyModel(grok.Model): grok.implements(ImySchema) ...

A default view for this model could be a form:

class Index(grok.EditForm): grok.context(MyModel)

Now when we navigate to an instance of MyModel, we will see an automatically generated view of the fields in the schema.

Yes, it really is that simple.

But what if...

The model above implements ImySchema,and because ImySchema defines the name and surname fields, the view can automatically generate a form for editing an object of that type.

There are other approches as well. Sometimes a model does not directly implement the interface you need, but an object can be derived from the model instance which does. For example:

class MyModel(grok.Model):

def getObj(self): return db.ANewObjectInstanceOfImySchema() ...

Then we could use an adapter to make MyModel actually look like an ImySchema:

class MyModelSchema(grok.Adapter): grok.context(MyModel) grok.implements(ImySchema) name = u'' surname = u''

27 Grok 4 Noobs.

def __init__(self, context): super(MyModelSchema, self).__init__(context) ob = context.getObj() self.name, self.surname = ob.name, ob.surname

The above adapter adapts MyModel, which means that the context argument in the __init__(self, context) is an instance of MyModel. The adapter calls context.getObj() to retrieve an instance of the ImySchema, and then replaces it's own attributes with the ones from the model. The adapter implements ImySchema by defining the name and surname attributes. One could define these attributes as python properties in order to handle updates/changes to these fields.

A more transparent way for the adapter to work would be to implement __new__() instead of __init__()- a factory.

class MyModelSchema(grok.Adapter): grok.context(MyModel) grok.implements(ImySchema)

def __new__(cls, context): return context.getObj()

Doing it this way exposes the object (as returned by context.getObj()) directly to the view, where in the previous example the view would have seen an instance of the adapter class. There is a subtle difference.

If we then define Index() as follows:

class Index(grok.DisplayForm): grok.context(MyModel) form_fields = grok.Fields(ImySchema) the framework will recognise that the fields defined don't match the context, and will search for a suitable adapter which can make a MyModel instance look like an ImySchema. If it finds such an adapter, the form fields will be mapped to the attributes of whichever object the adapter returns.

28 4.1: The Z-Object Publishing Environment

4.1: The Z-Object Publishing Environment

The origins of Zope lie in something they called Bobo. This was something apparently dreamed up on a flight by someone who was conversant with the terminology at the time (aka buzz words) but less so with technology. Which just goes to show that you don't need to be a technologist to come up with good technologies.

That was then, and this is now. Jim Fulton (the bloke on the plane) [1] has since learned a few things, and is now respected as one of the leading technologists behind Zope, and a Python thought leader of note.

Bobo was based on the concept of an Object Request Broker (yes, that's right, as in CORBA or COM/DCOM). Except that in the case of Bobo, the object was a Python object, and the request was an HTTP request. You can see where this is leading.

A typical HTTP request would go:

http://some.or.other.site/object-a/object-b/method-a?arg1=val&arg2=val

This would be translated into a lookup in object-a for object-b, and a call to object- b.method-a(arg1, arg2).

Simple enough as things go. Remember that at the time of Bobo (1996), the hierarchical ZoDB did not yet exist (**). It is astounding how complicated that simple idea became over the following decade. You can always trust open source developers to take a basic concept and make the most of it.

** Nor did REST, although XMLRPC (soap) was already a specification.

1. Jim Fulton: https://github.com/jimfulton

29 Grok 4 Noobs.

4.2: Interfaces compared to Abstract Bases

An Interface in Java or C++ is the fundamental concept which enables one to define components. An interface is meant to describe the component, including any attributes or methods the component might implement. It is not meant to actually implement anything at all, nor is it to be able to be instantiated as an object. In these languages, an interface provides the hooks to interact with the object without needing to know the implementation detail.

The concept of an interface pervades Python already, although there's no formal definition of the concept in Python. For example:

• A "Generator" is a thing which returns a sequence • A "Sequence" is a thing which implements a __iter__() method and a next() method • A "List" is something that has a __getitem__, __setitem__ and __len__ method, etc. • A "List" is a specialisation of a more generic hypothetical "Container" type.

So, if you are a Python developer, how would you know which methods are available for such types? Of course, Python has some marvellous support for introspection: dir(obj) or help(obj) is generally all you need.

The use of ABC's in other languages

C++ implements interfaces as Abstract Base Classes (ABC's), where methods declared are pure virtual methods. This defines what the in-memory object should look like and defines the elativer addresses for methods in the virtual method table (or vtable). An implementation of the interface subclasses the abstract base class, thus inheriting the same vtable, and allows the defined virtual methods to be called by an external program while completely hiding the implementation. This mechanism is the basis for the COM protocol and various other component frameworks.

Java has a similar concept, but uses a specific Interface language construct, or Abstract classes to accomplish the same thing.

Python ABC's

Python is different. It is a dynamic language, and nothing about objects is hidden. Because of this it is unlikely that Python components would ever need to be consumed by external programs in the same way C++ components can be. However, this does not mean that components are not useful as a way to modularise and reuse packaged python modules from within a Python program.

ABC's were added to standard Python in PEP 3119 [1]. The basis for this PEP was that it was hard in some instances to be able to tell the type of a python object by examining it's attributes. The example given is to determine whether something is a "mutable sequence container". One might look for a __getitem__ attribute, or test isinstance(object, list), but that is not conclusive. PEP3119 provides a way to overload the isinstance and issubclass functions.

1. PEP 3119: https://www.python.org/dev/peps/pep-3119/

30 4.2: Interfaces compared to Abstract Bases

So Python ABC's are in no way similar to C++ ABC's or Java Interfaces, and are more aimed at solving some introspection limitations with the Python language than they are aimed at providing a way to implement pluggable components.

Even so, one can use Python ABC's to make a base class which gives an error if one attempts to instantiate it, and one may define subclasses which give errors if all the abstract methods are not implemented. Nevertheless, it is hard to see the point of all this since this is Python and not C++, and mimicing C++ behaviour with regard instantiation does not automatically provide the benefits one might associate with C++ or Java interfaces.

In other words, if pluggable Python components is what you are after, ABC's only provide a small part of an answer.

Zope Interfaces

Zope components (2001) preceded PEP 3119 (2007) by a wide margin. They do not try to mimic the behaviour of C++ or Java interfaces. Instead, Zope Interfaces focus on the advantages of components as they would pertain to a Python environment, and implement the infrastructure required to enable component oriented development.

So, for example, if a class advertises that it implements a certain Zope Interface, but does not actually define the methods or attributes declared in the interface definition, nothing at all happens; no exception, no errors, nothing.

Why should it matter after all? One might argue that an interface is a contract, and if a component says that it implements the contract but does not, awful dire things may happen. So what? This is Python! You are in control of the code and you have full introspection of all the objects at any stage. Nothing is hidden. Implementing a check that a component implements it's contract(s) is just unnecessary overhead.

If you insist, and don't mind the overhead, Zope does provide zope.component.verify.verifyclass() which can ensure that an ordinary Python class does indeed implement the interfaces it says it does.

More important is the ability to easily replace one component with another, or to extend (adapt) existing components, or to find which components implement which contracts, or to build component factories, or to implement publish/subscribe notifications...

These are the important bits about component oriented architectures which the ZCA implements, and which are much more important in practice than an artificial way of implementing contracts that check for validity.

In a nutshell

Although one might draw a superficial comparison between Zope Interfaces and ABC's, they are fundamentally different. Take a look at the following graphic:

31 Grok 4 Noobs.

Clearly, where ABC's depends intrinsically on the inheritence mechanism, Zope Interfaces do not. ABC's are more of an "is a" relationship, while Interfaces are more of a "has a", or rather "implements a" relationship, and so not interfere with normal inheritance. Although having said that, if a subclass derives from a superclass which implements a Zope interface, the subclass will also implement the same. There is thus no loss of flexiblity, and ABC's have no benefit over Interfaces.

Where with ABC's one may test whether isinstance(myObject, super), the ZCA lets one test whether myInterface.providedBy(myObject). Where ABC's allow issubclass(myClass, super), Interfaces allow myInterface.implementedBy(myClass).

In practice, being adjacent rather than a part of the inheritence hierarchy, the ZCA allows for a great deal of flexibility regarding one's design. A fact which becomes evident particularly when reviewing and refactoring for modularity.

32 4.2.1: Interfaces vs. Inheritence: A Real World Example

4.2.1: Interfaces vs. Inheritence: A Real World Example

When one thinks about data hierarchies, or in other words, information systems where elements may be related to one another in a hierarchical way, a programmer will often think of expressing that relationship by using an inheritence tree.

Python supports multiple inheritence for objects, and this is often used as "mixins"; one can add all of the attributes and methods of a superclass to your derived class by simple inclusion. So, within limits it may be possible to express the relationship "A is a kind of B, and is also a type of C" through multiple inheritence.

The ZODB naturally stores information hierarchically, but that hierarchy is not an inheritance structure, and is rather a parent->child relationship. The type of parent in a ZODB structure does not predicate the type of the child.

Hierarchies by Inheritence

Let's consider the following hierarchy:

In summary, a publication has a title, date and one or more authors. An article is a publication with a few additional attributes. A book is also a kind of publication having a table of contents and ISBN. And so forth.

Normally, a relational database might map the above types into a set of tables. One might then write some Python classes to map onto the structure, for example:

class Publication(object): title = "" date = "" authors = []

33 Grok 4 Noobs.

class Pamphlet(Publication): ''' a kind of publication that is handed out to people '''

class Book(Publication): ''' a kind of publication that one can buy off the shelf ''' contents = {} # Section to page mapping ISBN = ""

class Biography(Book): ''' A book about yourself or someone else '''

class Novel(Book): ''' A story book ''' foreword = ""

...

Later, the programmer might map some views onto the classes (IOW the data model) to produce forms for data entry, or to produce reports. Some web frameworks use class inheritence as a mechanism to define a full set of attributes in order to provide the fields for an automatically generated entry form. For example, due to inheritance, we know that a Novel object has a title, a list of authors, a publication date, a table of contents, an ISBN and a foreword section.

To update the database with a Novel object, the Publication table would first be updated, then the Book table, then the Novel table using data entered in the form. Pretty good so far. Using an object relational mapper (for example Django ORM, STORM or SqlAlchemy), the task of creating or updating records in the database can be greatly simplified. Django takes the approach of directly defining class attributes as schema fields, so that a field can simultaneously describe a form user interface as well as a database table schema field.

Things would break down rather badly if a database schema were to diverge, and no longer match the class hierarchy. So, in the case where a class hierarchy is used to map a database schema, the two would always have to be in synchrony.

Zope Schemas

Zope & Grok do things rather differently.

A Zope Schema is defined as a set of schema fields, collected into a Zope Interface. For example, one might define:

from zope import schema, component

class IAuthor(component.Interface): name = schema.TextLine(title=u'Name', description=u'Author Name') surname = schema.TextLine(title=u'Surname', description=u'Author Surname') country = schema.TextLine(title=u'Country', description=u'Country of Origin') dob = schema.Date(title=u'Date of Birth', description=u'Author birth date')

class IPublication(component.Interface): title = schema.TextLine(title=u'Title:', description=u'The title of the publication') authors = schema.List(title=u'Author(s)', description=u'A list of authors', value_type=schema.Object(schema=IAuthor), required=False) date = schema.Date(title=u'Date:', description=u'Date of Publication')

class IBook(IPublication): contents = schema.Dict(title=u'Contents', description=u'Table of Contents', key_type=schema.TextLine(title=u'Section Header'),

34 4.2.1: Interfaces vs. Inheritence: A Real World Example

value_type=schema.Int(title=u'Page Number')) isbn = schema.TextLine(title=u'ISBN', description=u'ISBN')

class INovel(IBook): foreword = schema.Text(title=u'Foreword', description=u'Foreword')

In summary, a Zope schema defines the fields contained in an entity such as a Novel, and inheritence may be used to help define that schema interface.

However, just defining a schema does not in any way describe or constrain the structure of your data model. We could for example declare a Novel as a single discrete Python class, such as:

class Novel(object): grok.implements(INovel) title = u'' author = [] date = u'' contents = {} isbn = u'' foreword = u''

If we were to do this, the plain old data class Novel is now associated with the INovel interface, and also any parent interfaces:

> INovel.implementedBy(Novel) true > IBook.implementedBy(Novel) true > IPublication.implementedBy(Novel) true

Since various HTML fom elements are already associated with Zope schema fields via form libraries such as zope.formlib or z3c.forms, it is possible to automatically generate entry forms, add forms or display forms just from the zope schema. This is similar in some ways to the approach taken by other web frameworks, except that the zope.component.Interface is completely abstracted from the data storage format.

To allow instances of Novel to persist in a ZODB database, we can derive the class from a grok.Model, which in turn derives from persistent.persistent, which is required for objects stored in ZODB.

ZODB storage

Recalling that in our schema everything (other than IAuthor) derives from an IPublication, we could feasibly store all data for our publications in a single ZODB folder. The different Publication specialisations are similar to files of different types in the same file system folder. How these publications are treated could simply be a function of the type.

For example, imagine an application with the following simple ZODB structure:

35 Grok 4 Noobs.

Where app, authors and publications are all containers, this would provide automatic URL's for http://[server]/app, http://[server]/authors and http://[server]/app/publications. In code, this might look something like this:

import grok

class Authors(grok.Container): ''' A container for authors ''' grok.implements(IAuthor) # for an automatic add form

class Publications(grok.Container): ''' A container for publications '''

class App(grok.Application, grok.Container): ''' Our main application gets installed by the management interface '''

def __init__(self): super(App, self).__init__() self['authors'] = Authors() self['publications'] = Publications()

Looking at the container App()['publications'], there is no implied restriction as to what might be stored the container. The trick though, is handling (creating, listing, updating and viewing) data items intelligently. One might imagine an application which must be able to list all publications regardless of type, but also create new books. When creating a Novel, there should be entry fields specific to novels not present for other book types. There are various approaches to accomplishing this, and no approach would be "wrong".

One approach is to add sub-containers for Book, Article and Pamphlet to the Publications container. One might then add Biography, Novel and Reference sub- containers to Book, and so on. This has many advantages:

• The list of items in each container are kept relatively small, adding to efficiency • Similar view and model code could be used to represent items in containers • URL traversal implicitly selects the publication type • It's easy to add new items to each category since you already know the type from its container

On the other hand this has some disadvantages too, when compared to a single Publications folder which contains publications of various types:

• It's harder to generate a list of all books for example, or all publications in one long sorted list. It would involve listing each sub-container in turn. • It is more difficult to find an arbitrary publication without first knowing it's type (and therefore it's hierarchy).

36 4.2.1: Interfaces vs. Inheritence: A Real World Example

• The data storage structure may constrain or influence the application flow (a very bad idea): eg. instead of create biography, novel or reference it becomes ◦ create new ▪ publication: book, pamphlet or article ▪ book: biography, novel or reference

A rule of thumb is to think of ZODB as a data storage system for objects. While containers generally store items of related classes, it is not required that they do so. However, structuring one's storage in a logical way aids classification and later searches to find content. ZODB can store both structured as well as unstructured content.

Compare a persistent object storage container, which may easily store items of varying types, to a relational database table which always stores the same data columns- although generic views might be accomplished through unused columns which contain null data. Persistent objects may have logic (i.e. methods) as well as data attributes, and while relational databases may have stored functions, it is not the same thing. Modelling data relationally does have some nice advantages where it comes to foreign key import constraint rules, triggers, queries, joins and numerous other things, for which there are no direct equivalents in object databases. For example, ensuring uniqueness in an object database might involve an event handler which checks against an index for prior existence; that is extra work which is unnecessary when using relational databases.

Relational mappings

Typically, one will find elationalr databases do not easily map to object oriented designs.

Other than when working with object-relational databases such as PostgreSQL, a translation of our schema to a set of related tables does not provide us with a mechanism for inheritance. Rather, the fact that a Publication has a list of Author is described throuh a foreign key relationship between the two tables, and Book and Publication are related in the same way.

Rather than using inheritance to describe the fact that a Book is a Publication with an ISBN and table of contents, or that a Novel is a Book with a foreword, it is perhaps a better approach, at least from the point of view of relational databases, to describe a Novel as having a Book, and a Book as having a Publication. This changes our schema slightly:

from zope import schema, component

class IAuthor(component.Interface): name = component.TextLine(title=u'Name', description=u'Author Name') surname = component.TextLine(title=u'Surname', description=u'Author Surname') country = component.TextLine(title=u'Country', description=u'Country of Origin') dob = component.Date(title=u'Date of Birth', description=u'Author birth date')

class IPublication(component.Interface): title = schema.TextLine(title=u'Title:', description=u'The title of the publication') authors = schema.List(title=u'Author(s)', description=u'A list of authors', value_type=schema.Object(schema=IAuthor), required=False) date = schema.Date(title=u'Date:', description=u'Date of Publication')

37 Grok 4 Noobs.

class IBook(component.Interface): publication = schema.Object(title=u'Publication', schema=IPublication) contents = schema.Dict(title=u'Contents', description=u'Table of Contents', key_type=schema.TextLine(title=u'Section Header'), value_type=schema.Int(title=u'Page Number')) isbn = schema.TextLine(title=u'ISBN', description=u'ISBN')

class INovel(component.Interface): book = schema.Object(title=u'Book', schema=IBook) foreword = schema.Text(title=u'Foreword', description=u'Foreword')

If our data were stored in an RDBMS such as Postgres, MySql or even SqlLite, one could define database tables as follows:

import grok from megrok import rdb

class Author(rdb.Model): grok.Implements(IAuthor) rdb.metadata(metadata) rdb.reflected()

class Publication(rdb.Model): grok.implements(IPublication) rdb.metadata(metadata) rdb.reflected() authors = rdb.relation('Author', backref='publication')

class Book(rdb.Model): grok.implements(IBook) rdb.metadata(metadata) rdb.reflected() publication = rdb.relation('Publication', backref='book')

class Novel(rdb.Model): grok.implements(INovel) rdb.metadata(metadata) rdb.reflected() book = rdb.relation('Book', backref='novel')

Now assuming the schema were implemented in the database, the attributes would be automatically read in through reflection, and elationsr would be defined as described in the database.

So the Publication class would have fields defined for attributes title, author, and date, and also because Book refers to Publication specifying a 'book' back reference, there will also be a Publication.book attribute containing a list of books. These attributes map directly to the database, so updating an attribute will result in the appropriate SQL instructions to accomplish the change.

Using Object-relational mappings might seem like a step backward when compared to a true object database such as ZODB, but there are many advantages- not least being the ability to access your database from code written outside of Python. It is always possible to extend your mapped classes with your own methods and even non-persistent attributes, and so one gains much flexibility and portability by using an ORM such as SqlAlchemy.

38 4.3: Traversal and the Context

4.3: Traversal and the Context

The attribute variable called self.context in Adapters refers to the object instance being adapted. Generally, self implements an instance of what it is being adapted to, although self may be a factory instead.

Since grok.View is an adapter, and forms are specialisations of grok.View, form view objects have a self.context.

The grok.context(cls) directive configures a grok.view (or adapter) to adapt objects of the type cls.

Persistent Store

The ZODB (Zope Object Database) is a persistent store of pickled python objects. This store is hierarchical, having a root to the tree, and each component in the tree potentially containing other persistent components.

Traversal is a way of mapping a URL to a specific resource in this hierarchy. As one would traverse the path in a file system by visiting each node in turn, one may intrepret a URL in the same way to eventually reach a specific esourr ce in the ZODB hierarchy. A resource identified in this way may be processed to produce one or more data views, and is said to form the Context for the view or views.

It must be repeated here that traversal can also traverse attributes, objects in other databases, or even through objects which don't persist at all.

So, in the documentation for the ZTK or Grok, whenever one finds a eferr ence to a context in the definition of views, adapters and other classes, the context is invariably an object against which an operation, such as view or form generation is being performed.

The grok.context directive

In the Bluebream platform, the views defined against a given context would need to be configured for a site using the ZCML markup language. For example:

The above ZCML might be used to describe and configure the following view:

from zope.formlib import form from zope.formlib import DisplayForm from zope.site.interfaces import IFolder

class HelloWorld(DisplayForm):

form_fields = form.Fields(IHelloWorld)

39 Grok 4 Noobs.

def subFolderIds(self): for name, subobj in self.context.items(): if IFolder.providedBy(subobj): yield name

Grok, of course eliminates this need, and does so through use of the grok.context() directive, and some conventions:

class myModel(grok.Model): ...

class myView(grok.View): grok.context(myModel) ...

The use of implicit convention eliminates a lot of specification and improves readability.

By convention, the name of the view is the lower case class name, and the template will be the view name with the extention '.pt' or '.cpt'. Default permission is public, unless another view in the module specifies a different permission, in which case an explicit grok.require('zope.Public') directive should be used. Grok goes a bit further as well, by stating the convention that if the model is the only one defined in the source file, the views in the same source file are automatically configured for the defined model, and it is unnecessary to use an explicit grok.context(...) directive.

Where else are contexts used?

Typically, wherever adapters or multi-adapters occur, you will find a context. Views are actually adapters, which are instantiated from a model instance and browser request, and export a render method which returns a result that may be adapted to an IResult interface. The context here is the model that is being adapted.

Automatic form fields (formlib.field) are also models which likewise have views associated with them called widgets. So widgets as produced by the formlib library also have contexts, and render HTML snippets.

Viewlets are also adapters which adapt a model, a request, a view and a viewlet manager. They typically return snippets of HTML. grok.JSON, grok.REST, grok.XMLRPX also all operate against a context.

40 4.4: The amazingly useful Utility

4.4: The amazingly useful Utility

It's really easy to miss why a utility is so useful in a web application. Why not just use a function, or perhaps a object factory instead?

Process flow for web applications is initiated by a request. For Grok and Zope, the request is served by a handler function in the module zope.publisher.publish called (wait for it...) publish. This traverses the data path, locates and instantiates the data object which is the context for the request, and calls the appropriate views/adapter objects or callables for the context. When the HTML (or other) content is returned by the view, the whole stack is unwound again.

It is simply not efficient, in many cases, to repeat the entire build up and tear down for each and every request. That's why we have a web framework, and don't just use CGI.

Imagine for example having to open and close a database connection for every request!

Oh, sorry, is that what your current framework does?

Utilities to the rescue!

Utilities are singleton objects registered with the component framework. The lookup and instantiation for these objects is highly optimised. There are two kinds of utilities: global, and local.

A global utility provides a specific interface, and is initialised once only, at registration time, whenever the application is run. Because the initialisation is not repeated every time it is accessed, a utility can be implemented in a very efficient way. Global utilities are differentiated by name or by interface.

It is also easy to replace standard behaviour by overriding the implementation of a global utility, since there can only be one utility registered at a time for a given interface and name; you just plug in your own component in the interface slot.

Much of the behaviour of global utilities applies to local utilities, except that local utilities are only ever initialised once, at the time they are created and installed. Within the Grok framework, multiple applications (or sites) can be active at any time, and local utilities are specific to, and the utility object stored persistently within a particular site. A good example for a local utility would be an authentication database containing user account and authorization data.

Grok sites are always instances of grok.Application objects (or more accurately implementors of the ISITE interface) stored inside the ZODB tree for a given framework instance.

Both global and local utilities are easily (and very quickly) found by either a call to zope.component.getUtility() or zope.component.queryUtility().

41 Grok 4 Noobs.

4.5: Events Mechanism

Sometimes there is a need to perform actions based on other actions which might occur, such as a need to update a search index whenever an item is added to a container object.

The ZCA event mechanism provides a way to do precisely that. By listening for "add" events to a container, and having inserts to that container trigger an "add" event, one can easily extend the behaviour of the container.

Zope already provides a number of events for standard components, for example:

zope.component.interfaces.IRegistrationEvent zope.component.interfaces.IAdapterRegistration zope.component.interfaces.IHandlerRegistration zope.component.interfaces.ISubscriptionAdapterRegistration zope.component.interfaces.IUtilityRegistration and it is easy enough to add your own.

from zope.component.interfaces import IObjectEvent, ObjectEvent

class IMyProcessingDoneEvent(IObjectEvent): ''' An event to trigger after processing '''

class MyProcessingDoneEvent(ObjectEvent): grok.implements(IMyProcessingDoneEvent)

class MyApp(grok.Application, grok.Container):

def do_index(self, ev): ''' Do some indexing ''' ... grok.notify(MyProcessingDoneEvent(self))

@grok.subscribe(MyApp, grok.IObjectAddedEvent) def newItem(app, event): ''' A new item was added ''' app.do_index(event)

@grok.subscribe(MyApp, IMyProcessingDoneEvent) def processingDone(app, event): ''' Do stuff after processing is finished''' ...

Another useful way to use an event is to deal with asynchronicity; for example, when configuring an xtere nal resource which you know will take some time, and you do not want to hang up the thread waiting for the resource to become available:

from megrok import rdb

...

metadata = rdb.MetaData()

@grok.subscribe(IEngineCreatedEvent)

42 4.5: Events Mechanism

def create_engine_created(event): rdb.setupDatabase(metadata)

For a fuller example of setting up SQLAlchemy, see here.

43 Grok 4 Noobs.

4.5.1: A quick megrok.rdb (SQLAlchemy) setup howto

The following is a quick howto on setting up SQLAlchemy connections using megrok.rdb

import grok from megrok import rdb from z3c.saconfig import (EngineFactory, GloballyScopedSession) from z3c.saconfig.interfaces import (IEngineFactory, IScopedSession, IEngineCreatedEvent) DSN = "postgresql://user@localhost:5432/mydb"

engine_factory = EngineFactory(DSN, =False) grok.global_utility(engine_factory, provides=IEngineFactory, direct=True)

scoped_session = GloballyScopedSession() grok.global_utility(scoped_session, provides=IScopedSession, direct=True)

metadata = rdb.MetaData()

@grok.subscribe(IEngineCreatedEvent) def create_engine_created(event): rdb.setupDatabase(metadata) # To generate database create SQL # rdb.setupDatabaseSkipCreate(metadata) # To skip database creation commands

A database engine factory is first installed as a global utility. It is important to note that the initialisation of a global utility is never repeated after initial setup, regardless of how many times it is used.

Second, a global session factory is created, and also installed as a gobal utility.

Essentially, our defining global utilities in this manner allows other parts of the code (in this case the megrok.rdb module) to easily and quickly locate our pluggable components. This means that our own modules can retrieve a session really easily by simply calling rdb.Session().

Why use SQLAlchemy?

SQLAlchemy is a generic Object Relational Mapper (ORM) for Python. There are many reasons for using an ORM, the most obvious and frequently quoted being immunity against SQL injection attacks, and late binding for performance gains. In addition, the ability to work with your mapped table objects as if they were just persistent Python objects saves a huge amount of work.

Django comes with it's own built in ORM, and Django programmers must define table models in Python code, specifying column types and table relationships for each table. In effect, the Django program owns the database schema. The down sides to maintaining the schema in Python are many, not least being the continued re-synchronisation required every time a table definition changes. Inability to use stored database functions, and other restrictions on database features, sometimes preventing an optimal database are also concerns. Such issues are not limited to Django ORM, and these problems are easily demonstrated in other ORM implementations as well.

One particular feature of SQLAlchemy, namely the ability to build mapped Python classes by introspecting a database neatly sidesteps these problems. We call this "table reflection". Instead of maintaining the schema in Python, which is really poorly suited to the task, one can maintain the schema in SQL and have the Python program re-read the

44 4.5.1: A quick megrok.rdb (SQLAlchemy) setup howto schema definition upon startup. This eliminates the need for manual synchronisation, and simultaneously allows for ad-hoc database changes such as column addition/ removal, referential integrity changes or stored function changes, often without needing an application restart.

Even though Grok/Zope already comes with an object database, requirements for portability and accessibility or some of the more advanced features offered by a modern RDBMS may indicate use of databases other than ZODB. In such cases, SQLAlchemy really shines.

How does zope.schema fit into things with SQLAlchemy and mapped tables?

Schemas as defined using zope.schema fields are not directly related to SQL database schemas, although in theory one might at least partially derive a zope.schema interface from an RDBMS table. Details such as a minimum and maximum value for integer fields, or specialised fields such as password fields are not normally seen in relational table specifications. Such detail is specific to producing or validating HTML forms, and make little sense when defining tables.

One may gain the best of both worlds by defining a zope.schema interface, and then declaring that a mapped table class implements the interface. That will allow automatic form generation from the mapped table class, together will all the bells and whistles a zope.schema gives, such as automatic server side updating and validation or field type- specific widget enderingr and behaviour.

megrok.rdb defines all of the plumbing one would normally need to declare mapped classes with or without reflection, column types, queries and methods.

45 Grok 4 Noobs.

4.6: Extending existing objects with Annotations

Where events provide a way to extend functionality of existing components, annotations provide a seamless way to extend the data stored (in ZoDB) with instances of components.

For example, a zope.security.interfaces.IPrincipal interface provides an id, a title and a description. It may be that you would like to add some additional account based information such as firstname, surname and email address:

from zope.security.interfaces import IPrincipal from zope.interface import Interface, Attribute

class IAccount(Interface): firstname = Attribute("First Name") surname = Attribute("Surname") email = Attribute("Enail")

class Principal(grok.Model) grok.implements(IPrincipal) ...

class Account(grok.Annotation): grok.implements(IAccount) firstname = '' surname = '' email = ''

joe = Principal() account = Account(joe) account.firstname = "Joseph" ...

This way the original IPrincipal interface does not have to be altered every time an application has some specific data it needs to add for a user.

46 5: Designing a site, defining it's layout and implementing the code

5: Designing a site, defining it's layout and implementing the code

Grok 4 Noobs is, as previously mentioned, a site which documents itself. This documentation is a narrative - it tells a story - and does not always discuss API's in detail. There are rather a lot of API and other documents available on the web, to which we will refer as needed. By doing this we hope to add some value rather than detract from what is already abundantly available.

The full source for this site is available on GitHub [1], and licensed under the GPL v2. Installation instructions are included, and the best way to use this site is in conjunction with the source.

This site makes use of the prince utility if available to convert itself into a printable booklet. You can access this by clicking on the "Make Book" button if it is shown; if prince is not installed, the button will not show.

There is a prescribed norm for writing tutorial or descriptive documents. The idea is to start with small easily understood building blocks and work your way up to eventually implementing a small site or product. Common wisdom is that by introducing concepts first, one can build upon existing knowlege almost like building a tower, eventually introducing more meaty material.

One problem with this approach is that it is often tedious and it is very easy to lose sight of big picture concepts. By definition, one is xpectede to understand the sum of preceding pages completely in order to understand this shiny new concept presented at the end. This cumulative cognitive overload can become an inhibitor to learning. While noobs might find it fascinating, advanced audiences merely find it boring, and in theory, all noobs may advance.

In the case of the documentation presented here, the idea is to describe and present a fully formed concept, and then discuss the detail as necessary. Show people the tower, and then discuss how the beams rest on and support one another, how useful the struts are, or how windows make the most of the sunlight.

So, for example, we will present a functional module, explain what it is supposed to accomplish, and then dig down while there is still information to impart. One might refer to this as a top-down rather than the traditional bottom-up approach to writing documents.

1. Source for this site: https://github.com/prsephton/Grok4Noobs

47 Grok 4 Noobs.

The narrative style of this document is closer to what may be found in a book than a web site. Each section (or chapter) may be quite lengthy, and discusses the detailed implementation for modules that make up this site.

48 5.1: How to make applications with Grok

5.1: How to make applications with Grok

Any class deriving from grok.Application may be added via the Zope Management Interface (ZMI) to the grok application server. You may even add multiple instances of the same application. For instance, this application is a wiki (sort of). If we wanted to run multiple wiki's with different url's we could simply create as many instances of our application as we wanted.

ZMI & the Site Manager

The core of the Grok application server is the Zope Management Interface. Persistence for the ZMI is provided by a single ZODB database.

Each application registered with the ZMI and served by the Zope application server, is represented by a site manager. Site managers implement the ISite interface. IApplication is a superset of ISite, and grok.Application implements the IApplication interface. The Zope Management Interface recognises application candidates for registration because they implement IApplication.

Each site manager instance is the root of a persistent object store for a Grok application. The site manager provides a ZCA registry for local utilities and local adapters.

A basic application

Revisiting the simple hello world application we presented earlier, we can see that it consists of a model, and a view. An instance of the model may be stored as an object in the Zope application server, and given a name. That name then becomes the base URL to access the application from a browser. The default view name is 'index', so simply navigating to the URL will call the render() method of the view, and produce the web page.

import grok

class App(grok.Model, grok.Application): ''' This is an application called "app" '''

class Index(grok.View): ''' This is the default view for "app" '''

def render(self, who='Anonymous'): return """

Hello {}

""".format(who)

In the class App, the grok.Model (or grok.Container if you like) implements the IPersistent interface, allowing the object instance to be persistently stored in ZODB, and the grok.Application implements IApplication and ISite, effectively becoming a Grok installable application and site manager for the application.

Traversal and Views

When the Zope publisher finds an object by traversing the URL path and mapping it to a hierarchy defined either in code or by the ZODB, the publisher ends up with either an object, or an object and a view name. For example, the URL http://mysite/myapp/bob might end up with an application 'myapp' and view name 'bob', or it may end up with an

49 Grok 4 Noobs.

object 'bob' if it is defined. Where there might be confusion, eg both an object in myapp named 'bob' as well as a view for myapp named 'bob', the publisher will return the object 'bob' unlesss the url is specified as http://mysite/myapp/@@bob, in which case the publisher explicitly looks for a view 'bob'.

If through traversal, the publisher cannot identify a view name, the view name defaults to 'index'. Thus, if http://mysite/myapp/bob identifies an object 'bob', we assume the view name to be 'index'.

To locate and instantiate a view object, the publisher tries to look up a view named 'index' for object bob. If found, the publisher calls the view, and then if IResult is not already provided, tries to adapt the response to an IResult. The final esponser is then returned.

Views are implemented as adapters, which adapt an (object and an IRequest) to an IView. IBrowserView, IFTPView, IHTTPView and IXMLRPCView are all specialisations of IView. Normal Grok views implement the IBrowserView interface.

View Templates

If we were to include all of our HTML in Python render() methods as shown above, things would quickly become unmanageable. For one thing, syntax highlighting of simple strings becomes impossible in editors, and there is no support for syntax checking or error reporting.

So a better way is to define your code in a template. While Grok supports various template types, one is generally better off using Chameleon or Zope Page Templates (ZPT) since those formats are effectively just HTML/XHTML with attribute markup.

Hello

For obvious reasons, one cannot have both a render() method and a template defined for the same view. The above ZPT template should be self explanitory. It produces "Hello World", or if 'who' is defined in the URL as "Bob", produces "Hello Bob".

The TAL namespace contains numerous definitions, allowing all of the constructs one might find useful in producing HTML. There is a ZPT language reference provided here [1]. Chameleon is very similar [2], but differs in some syntactical aspects and is compiled rather than interpreted at runtime for speed.

Variables such as request (the view request object), context (the model instance) and user (the authenticated user object) are automatically available to view templates and views. Other automatic view variables include view (the view object itself) static (the static resources folder view) or dynamic variables, for example a loop state container. User defined variables may be created using tal:define="varname1 value1; varname2

1. Zope Page Templates: https://docs.zope.org/zope2/zope2book/AppendixC.html 2. Chameleon Page Templates: https://chameleon.readthedocs.io/en/latest/

50 5.1: How to make applications with Grok value2[; ...]" statements, or in Python code from within the view class itself by either defining view attributes and eferr ring to them as view/ in the template, or by overriding the namespace() method in the view class. The namespace() method returns a dict which exports names and values to the template.

Initialising a view is done by overriding the update() method for views. As with the render() method, any arguments passed to the URL will appear as named arguments to the view.update() method, which is always called prior to render().

Zope Schemas zope.schema introduces several interesting definitions, such as zope.schema.TextLine, zope.schema.Choice, zope.schema.Bool, zope.schema.List, etc. These are all classes which derive from zope.schema.Field, and the intention of a field is to provide a data type which contains descriptive information and constraints, and can be checked for validity against those constraints. For example, a zope.schema.Int may be restricted to range min=1, max=10.

Rather than the field object itself containing the data for the field, a field may be bound to another objects attribute by name, so an ordinary attribute which stores a text string may be mapped to a schema.TextLine having a max length of 20, or a schema.Password field for xample.e

To demonstrate, consider the following:

from zope import schema from zope.component import Interface

class ILoginForm(Interface): ''' Our login form implements login and password schema fields. ''' login = schema.BytesLine(title=u'Username', required=True) password = schema.Password(title=u'Password', required=True)

A schema.BytesLine restricts the content to just bytes (or in other words ASCII text if you will). We will get back to schema.password momentarily. Given the above interface, we could define a model to match:

class Account(grok.Model): grok.implements(ILoginForm) login = "" password = u""

def __init__(login="", password=u""): self.login, self.password = login, password

Now, since we specify that the ILoginForm is implemented by Account, the login and password fields are described by the schema, and we can use zope.schema.getSchemaValidationErrors(ILoginForm, ob) to check compliance. Automatic compliance checks can be added by defining attributes as

from zope.schema.fieldproperty import FieldProperty login = FieldProperty(ILoginForm['login'])

51 Grok 4 Noobs.

This will check the login attribute every time it is assigned a new value, and raise a ValidationError if it cannot be validated.

Views on Fields

Just as the zope publisher looks up a view for an object and then calls it to produce HTML, we can do the same for any object of our own, and that includes schema fields. Thus, a zope.schema.TextLine can have a view which returns a snippet of HTML which renders an HTML input element wrapped in some formatting fluff. Such views, as defined against schema fields are called widgets, and form the basis for automatic form generation.

Although zope.schema.Password is derived from zope.schema.TextLine, it's default widget renders as an HTML password input rather than as a textline input.

A grok.AddForm, for example, is a view which collects all the schema elements and in turn looks up widget views for each field, and then renders them one after the other inside a table embedded in an HTML form element. When processing the form request, it maps incoming form data to their corresponding fields and updates the model attributes corresponding to the schema field definitions.

So, we can have an AddForm, an EditForm and a DeleteForm, which require little or no work to produce directly from Python schema-mapped classes. grok.formlib, which wraps zope.formlib, is one library which provides widgets. It is reasonably trivial to extend existing widgets or produce one's own. Deriving a new field type and then defining a new widget for your new field is a good way to support multiple input methods for the same basic kind of data field. For example, one might render radio boxes either horizontally or vertically by simply switching out a CSS class and doing some styling.

So, for example were we to define:

class Login(grok.AddForm): grok.context(Interface) grok.require('zope.Public')

@grok.action('login') def handle_login(self, **data): ''' Use the data to log in '''

the result might look a little like this:

52 5.1.1: Modeling data in an application

5.1.1: Modeling data in an application

Models in Grok, are classes which define the attributes and methods of objects identified at run time to serve as context for HTTP requests. To properly understand the nature of models, one must realise that were one to declare:

class Article(grok.Model): implements(IArticle) title = u'My title' ...

...that the class definition only specifies what an Article instance should look like. It's just a class (or type) declaration which implements something that looks like an IArticle.

At run time, while processing requests, we are dealing with instances of Article, which now provide an IArticle interface. The Zope publisher is aware of the objects registered in the ZCA registry, and can process a URL to identify an object instance and a method (or view on the object).

The data associated with an Article instance may originate from a number of sources. If the Article is derived from persistent.persistent, (for example, a grok.Model vs an object) then the Article instance may be stored in the ZoDB. Alternately, the Article instance could represent a row in a relational database table, or the Article instance could be created dynamically (eg. myArticle=Article()).

Dynamically created data models instances are called transient. Such data lives only as long as the request does unless persisted in one or another database.

Finding Views

If an Article instance was retrieved from the ZoDB, then it is most likely (but not always) retrieved as a result of the Zope publisher traversing the URL to locate the model. At the same time, the publisher would identify a view, either by name from the URL, or use the default view named 'index'. Views in Grok are also classes registered with the ZCA:

class Index(grok.View) grok.context(Article) ...

The view is registered with the ZCA object registry as an adapter by name ('index'), and having object interfaces (Article) and what the view is instantiated from (an IBrowserRequest). So, when processing a Request, the Zope publisher can easily find the 'index' view for an IArticle and instantiate the view with an instance of Article as context.

Specifically, a view is looked up by the Zope publisher using a function

zope.component.getMultiAdapter((Article, Request), name='index')

53 Grok 4 Noobs.

The view is registered with the component registry when Grok parses the module and finds a declaration for the class Index(grok.View) with an appropriate context.

54 5.1.1.1: Notes on multithreading

5.1.1.1: Notes on multithreading

Zope, and hence Grok, is a multithreaded server. This means that several requests may be served concurrently at any given time. Where it comes to persistent data stored in the ZODB, it is possible that two different threads might modify the same ZODB object at the same time.

If this happens, we say there has been a conflict error. To handle this, Zope allows the one thread to succeed, and for the other thread, raises a ConflictError.

Handling Conflicts with ZoDB

The Zope 2 book [1] demonstrates how to handle ZoDB conflicts with the following code, copied for your convenience:

class Counter(Persistent):

self.count = 0

def hit(self): self.count = self.count + 1

def _p_resolveConflict(self, oldState, savedState, newState):

# Figure out how each state is different: savedDiff= savedState['count'] - oldState['count'] newDiff= newState['count']- oldState['count']

# Apply both sets of changes to old state: oldState['count'] = oldState['count'] + savedDiff + newDiff

return oldState

One would very seldom need to do this.

The _p_resolveconflict() method defined for the persistent class will be called if it exists, passing the original state prior to any changes, the saved state containing the changes made by the other thread, and the new state containing changes made by this thread. The return value should be the merged state between savedState and newState.

Other conflicts

While Zope will handle most conflicts automatically, it is not able to handle concurrent access to external resources, and some libraries which depend on global objects or singletons might have difficulties.

For example, the MatPlotLib Python library which produces various graphs of sterling quality is not thread safe. Operations which use such resources should be serialised by means of a semaphore or similar mechanism.

1. Zope 2 book on concurrency: http://docs.zope.org/zope2/zdgbook/ZODBPersistentComponents.html#transactions-and- persistent-objects

55 Grok 4 Noobs.

Another way to deal with concurrency issues is by means of an event queue, which is a construct quite well supported by the Zope Component Architecture.

56 5.1.1.2: The Scope of models in Traversal

5.1.1.2: The Scope of models in Traversal

Who can see changes made to objects?

Scope, in this instance, refers to what data a visitor to a web site can see. Do all users view the same data in a model? Do users of the same browser view the same data, or is data private and specific to each individual user?

The scope of a model completely depends on what is returned as a result of traversal.

Any objects stored in the ZoDB are global in scope- which is to say that if an object is located and instantiated through the ZoDB the same object (or a representation of it) is returned regardless of who requests it.

A parallel may be drawn to a Python object which represents a row in a table of a relational database (eg. SQLAlchemy). When instantiated, the information in the row is copied into such an object, and when the object must refresh the database from any changes to it's state, the changed database values are updated from the object. ZoDB objects work exactly like that.

However, it may be that a traversal path might return different objects depending on something specific like a cookie, session or user data.

For example, a browser session cookie can ensure that a given server side object will be specific to a browser. Remember that although a browser is generally only ever used by only one person, this might not always be the case. The same browser used by two different people cannot differentiate between the people. This is the case for the "basic auth" protocol.

If a person is authenticated by providing credentials and a shared secret, a traversal path such as

"/myserver/user/name" might be interpreted by a traverser so as to return a different user object for each authenticated person. In such a case, the scope of the data object, or model instance, would be specific to each user.

57 Grok 4 Noobs.

5.1.1.3: Rules of Persistence

The ZoDB is an object database which transparently makes any changes to objects in memory persist between sessions and between individual runs of your application. A ZoDB object literally looks like any other object except that any changes made to it are long lived. To accomplish this magic, one must follow a couple of rules.

Conditions for persistent objects

In order for a Zope (ZoDB) object to be persitent, it must fulfill the following conditions:

• It must be pickleable • It must not have any normal attributes starting with a _p_ • Attributes starting with a _v_ are not persisted (volatile) • Any changes to mutable attributes such as dicts or lists must be flagged by setting the _p_changed attribute to True. • A persistent class cannot have a __del__() method • Persistent classes derive from Persistence.Persistent.

Other databases

In the case where a relational database or alternative store is used for permanent storage, different rules will apply for marshalling or instantiating objects from the database.

58 5.1.1.4: User Session Management

5.1.1.4: User Session Management

Sessions are virtual environments provided by some web frameworks for each browser connection. Changes to session data persists between individual requests made by the browser. Typically the amount of session data can impact on the scalability of the otherwise stateless web model, so use of a session to persist information must be done with discretion.

Managing browser or user specific data

Zope provides built in cookie based session management with automatic expiry of session data after a period, normally 20 minutes. It is important to note that the session is specific to the browser, and not specific to a person who might use the browser.

It is an extremely simple matter to retrieve the a session:

class MyView(grok.View): def update(self): sn = ISession(self.request) self.session = sn[MyAppIdentifier]

The session can be treated like a dict, and any information stored therein will be persisted for the length of the session.

User data

NB: Sessions identify a Browser Session, not a user. Imagine many users using the same browser instance after one another (eg. at a library), but identifying themselves individually to a web application.

User data, as opposed to Session data, depends on an identity being associated with the user. Such information may be easily persisted indefinitely in the ZoDB, or temporarily within a session.

A good strategy for this, is to associate user identities with a serial number, and then to maintain a "users" container with keys being the user serial number converted to text.

59 Grok 4 Noobs.

5.1.2: Defining an Article and the Application

Our application, being a wiki, consists of articles. The way we would like to structure things, is for an article also to contain other articles. Thus, when one is viewing one page, one should be able to navigate to any pages contained by the current one, or to the parent. One should also be able to navigate back and forth through sibling pages- siblings are pages contained by the current article's parent.

By the above specification, we know that an article is a container (grok.Container). The application itself is also an article. We define the interface for an article as follows:

class IArticle(Interface): title = TextLine(title=u'Page Title:', description=u'The title of the displayed page') navTitle = TextLine(title=u'Nav Title:', description=u'A title for the navigation area') text = Text(title=u'Display Text:', description=u'Text to display on this page')

We can see that an article contains a title, a navigation title and text. As previously discussed, the attributes are instances of schema field classes, which may be mapped to fields and widgets in automatically generated forms.

Now, we also know that articles will be containers, and that applies both to the application itself as well as any contained articles. So it's a good idea to capture that fact in code:

class ArticleContainer(grok.Container): ''' An article container is a generic container (a dict-like object) which also provides a sorter for the articles it contains ''' grok.traversable('attachments') grok.traversable('sorter')

attachments = None def sorter(self): ''' Find an adapter which adapts self to an IArticleSorter, and return an instance ''' return IArticleSorter(self)

We are already getting ahead of ourselves here with the definition of ArticleContainer.attachments; we shall discuss the IArticleSorter interface and adapter in due course. In the above, we are saying that traversing to an 'article/sorter' will return an instance of an IArticleSorter.

For the moment, what is contained in attachments is still unknown, but if we assume it to be a list of attachments, this would not be far from the truth.

As for our application and contained articles, the difference is that the application has no parent and is at the root of the site, where every other kind of article has a parent:

class NoobsArticle(ArticleContainer): ''' Contains the detail for a navigable article in the site ''' grok.implements(IArticle, ILayout) title = u'' navTitle = u'' text = u''

60 5.1.2: Defining an Article and the Application

class Grok4Noobs(grok.Application, ArticleContainer): ''' The Grok4Noobs application is a content wiki-like application which contains examples explaining how to use the Grok framework for trivial and non-trivial applications. ''' grok.implements(IArticle, ILayout) title = u'A gentle introduction to using the Grok web framework' navTitle = u'Introduction' text = u''

We can see how both the Grok4Noobs application as well as the contained NoobsArticles are instances of ArticleContainer. They also both implement IArticle and ILayout interfaces, thus inheriting the behaviour associated with those interfaces such as views, menus, viewlets and so forth.

As there is an index view defined for ILayout, our above classes also get the index view. This view, as we have already discussed, will render providers (viewlet managers) for the various parts of the web page such as masthead, navigation or content. This means that we can easily include the article content by defining a viewlet manager for an IArticle:

class TextViewlet(grok.Viewlet): ''' Render the article content ''' grok.context(IArticle) grok.viewletmanager(Content)

The template for TextViewlet will decide what should go into the Content slot when rendering an IArticle.

61 Grok 4 Noobs.

5.1.3: HTML Re-use and site layout

In general, most sites on the web follow a relatively prescriptive layout.

Sometimes the menu is on the right, or below the masthead. Somethimes one has more than one navigation area. Often, since screens are so wide these days and this impacts on readability, one will have a "Side Matter" area to the right, and perhaps a footer area below the content:

As you can see, we chose to implement a masthead, left side menu and footer.

What are Marker Interfaces?

This is the part that many people (especially noobs) generally miss about Grok & Zope. I missed it myself when starting with Grok, leading to endless confusion and frustration.

So listen up.

The idea of traversal is to identify a data resource by walking a tree of objects by name, mapping names from the URL. Then, depending on what is identified, we can either render a default view (index) for that resource, or another named view, or traverse further into the hierarchy. But what exactly is the type (or class) of the data item we identified by means of traversal?

Well, interfaces add a whole new amazing aspect to the idea of "duck typing". Call it "duck typing by proxy":

class ILayout(Interface): ''' This is an interface for the main site layout

All a model has to do to inherit the Layout view, is to say that it implements this interface. '''

The above is what is called a "Marker" interface. It is declared as a ZCA Interface, but it does not specify what must be done to implement it. Consequently, any model

62 5.1.3: HTML Re-use and site layout component may state that it implements the interface without having to do anything special.

class MyModel(grok.Model): ''' A model which must be rendered using the site layout ''' grok.implements(ILayout) ....

We can also use the marker interface in grok.View implementations. If we do this, the view will be rendered for contexts which implement the marker interface. So, for example, we may define a view called 'index' (the default view) with a context of ILayout, and this view will apply to each and every model which implements ILayout:

class Layout(grok.View): grok.context(ILayout) grok.name('index') ...

Looking at that graphically, if we have for example Model1 and Model2 which both implement an ILayout interface, then an Index view (or Layout View, or whatever) defined with an ILayout context will become available and render for both Model1 and Model2.

In other words, we have a simple way to define a single view which applies to models we define in our application, simply by stating that the model implements (quacks like) a particular interface. This is great as far as things go, but this is far from the end of our story.

63 Grok 4 Noobs.

Page Templates:

Views in Grok are often rendered by means of Zope Page Templates [1] (zpt). Templates are closer to HTML syntax than python code, and are much easier to develop and maintain than writing all of the HTML as Python strings. Although Grok supports a variery of templating languages, this site sticks to plain old ZPT. ZPT and Chameleon (a compiled and optimised ZPT) [2] are similar in many respects in that they are attribute based and valid HTML in their own right.

Consider the following Zope Page Template (layout.pt):

</head> <body i18n:domain="camibox"> <div class='MastHead rounded-top' tal:content='structure provider:masthead' /> <div class='ContentWrapper'> <div class='ContentArea'> <div class='Navigation' tal:content='structure provider:navigation' /> <div class='SideBar' tal:content='structure provider:sidebar' /> <div class='ContentBox'> <div class='Content' tal:content='structure provider:content' /> </div> </div> </div> <div class='Footer' tal:content='structure provider:footer' /> </body> </html></p><p>One will notice immediately that the above is in fact an HTML document, but with a few rather strange looking attributes. These all start with the xml namespace tal: and may be explained in the order in which the occur in the above template file:</p><p>• tal:content="context/title | context/description | string:Untitled": context is an automatic variable in the template namespace, as are view/ viewlet as applicable and request. The meaning of context should be clear from prior discussions, and the content attribute simply tells the template to replace the content of the HTML tag with the value from context.title. If that does not exist, then to use context/description. If that fails too, then use the string "Untitled". • tal:content='structure provider:x', where x ∈ [masthead, navigation, sidebar, content, footer]. This also replaces content within a tag, but this time with what appears to be a "provider". The structure specifier tells the template language to include the content from the provider verbatim and not to try to escape the HTML prior to inclusion.</p><p>1. ZPT Reference: http://bluebream.zope.org/doc/1.0/manual/zpt.html 2. Chameleon: http://chameleon.readthedocs.org/en/latest/reference.html</p><p>64 5.1.3: HTML Re-use and site layout</p><p>• <base tal:attributes="href python:view.url(context, '')" /> includes a 'base' tag for every page, which allows relative URL's to function in an unambiguous way. The tal:attributes attribute allows for the definition of attributes for the tag which are based upon data from the application. In this instance, the view.url() method is called for the current context in order to produce the base URL. Without the base tag, the browser will use the current url to determine a relative URL. The problem is that if the URL contains a trailing slash, the base of the relative link will be different from the case where it does not. For example, relative='pq/rs'</p><p> url='http://ab/cd/' result='http://ab/cd/pq/rs'</p><p> url='http://ab/cd' result='http://ab/pq/rs'</p><p>So what then, is a "provider"? The short answer, is that a provider is a grok.ViewletManager. There is a longer answer, but to discuss it would serve only to distract.</p><p>Viewlet Managers:</p><p>Viewlet managers can be seen as pluggable areas of your rendered view. In our case, we define a viewlet manager for each area that we want to fill differently depending on context.</p><p>You will find the following code which defines viewlet managers for each discrete region of our site :</p><p># We specify the renderable areas of the page as viewlet managers. # These areas will always render for instances of ILayout class MastHead(grok.ViewletManager): grok.context(ILayout)</p><p> class Navigation(grok.ViewletManager): grok.context(ILayout)</p><p> class Content(grok.ViewletManager): grok.context(ILayout)</p><p> class SideBar(grok.ViewletManager): grok.context(ILayout)</p><p> class Footer(grok.ViewletManager): grok.context(ILayout)</p><p>Viewlet managers are called whenever they are asked to render their content. In the code above, we see that they will render for a context which implements the ILayout interface. Fair enough, since the layout.pt page template implements the renderer for the Layout view which also renders for instances of ILayout. This is quite important though, since we could have specified an ancestor interface (eg. Interface) from which ILayout derives, and then the viewlet manager would have had wider scope.</p><p>65 Grok 4 Noobs.</p><p>Viewlets:</p><p>Of course, viewlet managers render that which they manage. Namely, viewlets. The generic masthead viewlet is defined as follows:</p><p> class MastHeadViewlet(grok.Viewlet): ''' Render layout masthead ''' grok.context(ILayout) grok.viewletmanager(MastHead)</p><p>As with views, viewlets generally render through means of page templates. The mastheadviewlet.pt template looks like this:</p><p><div> <div style="float:right" tal:content="structure provider:authsection" /> <div style="float:left"> <h1>Grok 4 Noobs</h1> <h4 class='indent' tal:content='context/title' /> </div> <div style="float:none; clear: both" /> </div></p><p>Of course, everyone should by now know exactly what it produces. (hint: look at the top of this page).</p><p>Model-specific content:</p><p>To complete the picture, take a look again at our graphic depicting two models, Model 1 and Model 2, both implementing ILayout. That meant we could have an automatic index view for both models, and a generic masthead that renders different titles depending on which model was identified in the URL.</p><p>But what if we wanted to do completely different HTML for say, the content section? Model1 could be an index, and Model2 could be an article, and rendering content for each differs completely!</p><p>We can do this by using a specific context: grok.context(Model1) or grok.context(Model2) in a viewlet class will allow one to write two completely different viewlets which differentiate between Model1 and Model2. In this way, the page becomes adaptive to the kind of data which is the context at the time the page is rendered.</p><p>66 5.1.3.1: Macros: an alternative way to re-use HTML</p><p>5.1.3.1: Macros: an alternative way to re-use HTML</p><p>Zope Page Template macros are an alternative to viewlets for re-using HTML templates, although either macros and viewlets may be used depending on circumstance. Those who have used other frameworks such as Django or PHP might find this method more familiar than Viewlets and Viewlet Managers for site layout.</p><p>One or more named macros may be defined in a page template file. These macros may then be later used inside any other view, by referring to the macro from within the view page template.</p><p>For example, if we were to create a file, macros.pt as follows:</p><p><div class="header" metal:define-macro="header"> <h2 tal:content="context/title" /> </div></p><p><div class="footer" metal:define-macro="footer"> <p>Grok 4 Noobs</p> </div></p><p>We could later refer to these macros in another page template like this:</p><p><html> <head></head> <body> <div use-macro="path/to/macros/header" /> <stuff /> <div use-macro="path/to/macros/footer" /> </body> </html></p><p>Now, if the same macro is used in multiple view page templates we have only one place to change the header or footer definitions. Thats nice.</p><p>But there's more!</p><p>A system of slots lets the template fill in bits from the calling template. In the above example, each view has to fill in the <head /> prolog and a lot of unnecessary boilerplate code. So let's see if we can use macros and slots to make the layout more generic.</p><p>First, let us define our macros.pt file as follows:</p><p><html define-macro="base_page"> <head></head> <body> <div class="header"> <h2 tal:content="context/title" /> </div></p><p><stuff define-slot="content_area" /></p><p><div class="footer"> <p>Grok 4 Noobs</p> </div> </body> </html></p><p>67 Grok 4 Noobs. then we could simplify the definition of our view page template right down to using the macro and filling in the content_area slot:</p><p><html metal:use-macro="path/to/macros/base_page"> <stuff metal:fill-slot="content_area" /> </html> which would produce HTML functionally equivalent to the first example.</p><p>This demonstrates how the system of macros and slots in Zope Page Templates can be used as the basis for site layout. Some would find this simpler than using viewlets, but remember that unlike macros, which can fill just one slot at a time, many viewlets can "fill" the same "slot" (viewlet manager), and so can be quite a bit more flexible than macros.</p><p>68 5.1.3.2: The full source for the layout.py module</p><p>5.1.3.2: The full source for the layout.py module</p><p>The layout.py module source</p><p>This module defined ILayout, which is a marker for a generic site layout view. Models can inherit the view by implementing ILayout.</p><p> import grok from resource import style, favicon, tinymce, textdivs from interfaces import Interface</p><p> class ILayout(Interface): ''' This is an interface for the main site layout</p><p>All a model has to do to inherit the Layout view, is to say that it implements this interface.</p><p>The Layout view defines a basic HTML document shell consisting of a number of parts; the viewlet managers below are place holders for your site implementation to fill in the blanks. What goes in those sections will depend on the context. '''</p><p># We specify the renderable areas of the page as viewlet managers. # These areas will always render for instances of ILayout class MastHead(grok.ViewletManager): grok.context(ILayout)</p><p> class AuthSection(grok.ViewletManager): grok.context(ILayout)</p><p> class Navigation(grok.ViewletManager): grok.context(ILayout)</p><p> class Content(grok.ViewletManager): grok.context(ILayout)</p><p> class SideBar(grok.ViewletManager): grok.context(ILayout)</p><p> class Footer(grok.ViewletManager): grok.context(ILayout)</p><p># We define a few viewlets which always render for specific areas # of the page. These render because the template for the Layout # view (below) does tal:content='structure provider:masthead' etc. # The appropriate viewletmanager collects up all the viewlets # registered for itself and renders them. No magic involved. class MastHeadViewlet(grok.Viewlet): ''' Render layout masthead ''' grok.context(ILayout) grok.viewletmanager(MastHead)</p><p> class FooterViewlet(grok.Viewlet): ''' Render the layout footer ''' grok.context(ILayout) grok.viewletmanager(Footer)</p><p># Finally, the page layout itself is a view which renders the html # skeleton. It also includes any resources such as css and <a href="/tags/JavaScript/" rel="tag">javascript</a> # which would be required by the content. class Layout(grok.View): ''' Renders the base HTML page layout for the site. Since for this site, editing, adding and deleting are going to be common actions, we provide for these actions as</p><p>69 Grok 4 Noobs.</p><p>URL arguments. Another approach would have been to use traversers, and make the operations part of the URL itself. ''' grok.context(ILayout) grok.name('index') grok.require('zope.Public')</p><p> editing = False adding = False deleting = False viewing = True</p><p> def update(self, edit=None, add=None, delete=None, nomce=None): self.editing = edit is not None self.adding = add is not None self.deleting = delete is not None self.viewing = not (self.editing or self.adding or self.deleting) style.need() favicon.need() if nomce is None: # Switch includes or omits tinyMCE tinymce.need() textdivs.need()</p><p> layout.pt is the main site layout page template</p><p>Note now the page template defined slots for the various viewlet managers, to be filled by the specific model implementations for the associated viewlets.</p><p><!doctype html> <html itemscope="itemscope" itemtype="http://schema.org/WebPage" xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal" xmlns:i18n="http://xml.zope.org/namespaces/i18n" lang="en"> <head> <meta content="IE=9" http-equiv="X-UA-Compatible" /> <meta content="text/html; charset=UTF-8" http-equiv="content-type" /> <meta name="viewport" content="width=device-width user-scalable=no" /> <title tal:content="context/title | context/description | string:Untitled" /> </head> <body i18n:domain="camibox"> <div class='MastHead rounded-top' tal:content='structure provider:masthead' /> <div class='ContentWrapper'> <div class='ContentArea'> <div class='Navigation' tal:content='structure provider:navigation' /> <div class='SideBar' tal:content='structure provider:sidebar' /> <div class='ContentBox'> <div class='Content' tal:content='structure provider:content' /> </div> </div> </div> <div class='Footer' tal:content='structure provider:footer' /> </body> </html></p><p>We only define the header and footer viewlets in layout.py.</p><p> mastheadviewlet.pt is the template for the masthead viewlet:</p><p><div> <div style="float:right" tal:content="structure provider:authsection" /> <div style="float:left"> <h1>Grok 4 Noobs</h1> <h4 class='indent' tal:content='context/title' /> </div> <div style="float:none; clear: both" /> </div></p><p>70 5.1.3.2: The full source for the layout.py module footerviewlet.pt is the page template for the footer viewlet:</p><p><em>Grok 4 Noobs</em>; A site built using <a href="http://grok.zope.org/doc/current/index.html"> Grok </a></p><p>71 Grok 4 Noobs.</p><p>5.1.4: Providing for article addition, modification and deletion</p><p>Recalling the implementation of the index view for ILayout, one will remember the way it sets a mode depending on request variables which may have been part of the request.</p><p> class Layout(grok.View): ''' Renders the base HTML page layout for the site. Since for this site, editing, adding and deleting are going to be common actions, we provide for these actions as URL arguments. Another approach would have been to use traversers, and make the operations part of the URL itself. ''' grok.context(ILayout) grok.name('index')</p><p> editing = False adding = False deleting = False viewing = True</p><p> def update(self, edit=None, add=None, delete=None, nomce=None): self.editing = edit is not None self.adding = add is not None self.deleting = delete is not None self.viewing = not (self.editing or self.adding or self.deleting) style.need() favicon.need() if nomce is None: # Switch includes or omits tinyMCE tinymce.need() textdivs.need()</p><p>Note how the view sets instance attributes for self.editing, self.adding, self.deleting and self.viewing. This is important when we look at the Content viewlet manager for an article:</p><p> class TextViewlet(grok.Viewlet): ''' Render the article content ''' grok.context(IArticle) grok.viewletmanager(Content)</p><p>...and the corresponding template...</p><p><div class='centered' tal:condition='viewlet/view/editing' tal:content='structure context/@@edit' /> <div class='centered' tal:condition='viewlet/view/adding' tal:content='structure context/@@add' /> <div class='centered' tal:condition='viewlet/view/deleting' tal:content='structure context/@@delete' /> <div class='vscroll text' tal:condition='viewlet/view/viewing' tal:content="structure context/text" />h</p><p>The template refers to the state attributes for the view, and decides which div to render. While there are numerous other approaches that would work as well, this one is simple and coincidentally manages to demonstrate conditional code in page templates. The tal:content="structure context/@@edit" and similar lines are effectively path expressions which render the views called edit, add or delete for the current context. So this demonstrates how to render other views within a viewlet.</p><p>72 5.1.4: Providing for article addition, modification and deletion</p><p>Overriding Formlib</p><p>The edit, add or delete views under discussion are forms which will be automatically generated by the grok.formlib library, which uses fields derived from schema definitions to generate input widgets.</p><p>The default template for a grok.EditForm and grok.AddForm assumes that the HTML will be viewed as an independent document, and so generates the html, head and body tags. This is both unnecessary and unwanted.</p><p>Also, there is a need to persist the state variable (such as 'edit') which was passed as a request variable to the index view. If we do not include this variable as a hidden input in our rendered form, subsequent form submission would post data which never reaches the form handler. Why? because the view mode would never get set, and the wrong view would be rendered without the correct mode.</p><p>So the above two needs suggest that we create our own specialisation of a grok.EditForm and grok.AddForm to deal with these issues. We can find appropriate code to do this in forms.py</p><p>""" A small shim over grok.formlib forms to add a default hidden() method and replace the default form template """ import grok</p><p> class EditForm(grok.EditForm): grok.template('edit') grok.baseclass()</p><p> def hidden(self): return []</p><p> class AddForm(grok.AddForm): grok.template('edit') grok.baseclass()</p><p> def hidden(self): return []</p><p>These two classes use grok.baseclass() to ensure that Grok does not try to configure the them. Both EditForm and AddForm use a replacement template, namely edit.pt.</p><p><form tal:attributes="id view/formId | nothing; action view/getURL | request/URL" method="post" class="edit-form" enctype="multipart/form-data"></p><p><tal:loop tal:repeat="field view/hidden"> <input type='hidden' tal:attributes="name field/name; value field/value"/> </tal:loop></p><p><fieldset class="form-fieldset"></p><p><legend i18n:translate="" tal:condition="view/label" tal:content="view/label">Label</legend></p><p><div class="form-status" tal:define="status view/status" tal:condition="status"></p><p><div i18n:translate="" tal:content="view/status"> Form status summary </div></p><p>73 Grok 4 Noobs.</p><p><ul class="errors" tal:condition="view/errors"> <li tal:repeat="error view/error_views"> <span tal:replace="structure error">Error Type</span> </li> </ul> </div></p><p><div class="form-fields"> <table> <tal:block repeat="widget view/widgets"> <tr class="form-widget"> <td class="label" tal:define="hint widget/hint"> <label tal:condition="python:hint" tal:attributes="for widget/name; title hint"> <span class="required" tal:condition="widget/required" >*</span><span i18n:translate="" tal:content="widget/label">label</span> </label> <label tal:condition="python:not hint" tal:attributes="for widget/name"> <span class="required" tal:condition="widget/required" >*</span><span i18n:translate="" tal:content="widget/label">label</span> </label> </td> <td class="field"> <div class="widget" tal:content="structure widget"> <input type="text" /> </div> <div class="error" tal:condition="widget/error"> <span tal:replace="structure widget/error">error</span> </div> <div class="widgetDisplay" tal:condition="widget/display | nothing" tal:content="structure widget/display"> </div> </td> </tr> </tal:block> </table> </div> <input type='hidden' id='camefrom' name='camefrom' tal:condition='view/referrer | nothing' tal:attributes='value view/referrer' /> <div id="actionsView"> <span class="actionButtons" tal:condition="view/availableActions"> <input tal:repeat="action view/actions" tal:replace="structure action/render" /> </span> </div> </fieldset> </form></p><p>Changing the default form template is not the only way to produce a hidden input inside a form. Another approach which one might have followed is to define a new schema field type and add the field to the model itself.</p><p>CRUD Forms</p><p>The actual forms used to add or edit articles for our wiki application derive from the base classes defined above.</p><p>First, the Add Form:</p><p>74 5.1.4: Providing for article addition, modification and deletion</p><p> class Add(forms.AddForm): ''' Renders the article add form, which is similar to the edit form ''' grok.context(IArticle) form_fields = grok.Fields(IArticle).omit('text')</p><p> def hidden(self): # persists the request 'add' form variable return [dict(name='add', value='')]</p><p> def setUpWidgets(self, ignore_request=False): super(Add, self).setUpWidgets(ignore_request) self.widgets['title'].displayWidth=50 self.widgets['navTitle'].displayWidth=12</p><p>@grok.action(u'Add this page') def addItem(self, **data): ntitle = quote_plus(data['navTitle']) item = self.context[ntitle] = NoobsArticle() self.applyData(item, **data) self.redirect(self.url(self.context))</p><p>The Edit Form:</p><p> class Edit(forms.EditForm): ''' Renders the article editor. This includes the tinyMCE HTML editor ''' grok.context(IArticle)</p><p> def hidden(self): # persists the request 'edit' form variable return [dict(name='edit', value='')]</p><p> def setUpWidgets(self, ignore_request=False): super(Edit, self).setUpWidgets(ignore_request) self.widgets['title'].displayWidth=50 self.widgets['navTitle'].displayWidth=12</p><p>@grok.action(u'Change this page') def changeItem(self, **data): title = data['navTitle'] if self.context.navTitle==title: self.applyData(self.context, **data) self.redirect(self.url(self.context)) else: # to change it we must create new and remove old ntitle = quote_plus(title) self.context.__parent__[ntitle] = self.context navTitle = quote_plus(self.context.navTitle) del self.context.__parent__[navTitle]</p><p> self.applyData(self.context, **data) self.redirect(self.url(self.context.__parent__[ntitle])) and finally, the delete form:</p><p> class Delete(forms.EditForm): ''' A Delete confirmation form and action ''' grok.context(NoobsArticle) # for normal articles form_fields = grok.AutoFields(NoobsArticle).omit('text')</p><p> def setUpWidgets(self, ignore_request=False): self.form_fields['title'].field.readonly = True self.form_fields['navTitle'].field.readonly = True super(Delete, self).setUpWidgets(ignore_request) self.form_fields['title'].field.readonly = False self.form_fields['navTitle'].field.readonly = False</p><p> def hidden(self): # persists the request 'delete' form variable return [dict(name='delete', value='')]</p><p>75 Grok 4 Noobs.</p><p>@grok.action(u'Delete this page (cannot be undone)') def delPage(self, **_data): title = self.context.navTitle try: ntitle = quote_plus(title) parent = self.context.__parent__ del self.context.__parent__[ntitle] self.redirect(self.url(parent)) except Exception, e: raise e</p><p>With the Add form, we omit the text from the form, since the article editor also supports the abilty to add resources such as graphics or syntax highlighted text. The problem with this, is that the article does not exist until after it is created, and so can contain no links to resources within itself.</p><p>In each of the forms, a method caled hidden() returns a dict containing the list of names and values for inclusion of state as hidden inputs in the rendered form.</p><p>The Delete form, like the Add form does not include text. In addition, it changes the fields to ead-onlyr by defining the SetUpWidgets method, and restores the readonly flag to False after calling the superclass SetUpWidgets method. This causes the widget to render as text rather than as an entry box.</p><p>Note: The use of SetUpWidgets is not the only way to control field properties, although with AutoFields it may be. If instead, one uses the grok.Fields() call, one may do grok.Fields(Interface, for_display=True).omit(...) . The difference is that grok.AutoFields() examines the model for all interfaces and all fields required by those interfaces, where grok.Fields() is more of a manual thing.</p><p>76 5.1.4.1: Another way of rendering hidden input widgets</p><p>5.1.4.1: Another way of rendering hidden input widgets</p><p>Changing the form template to include hidden input widgets is a decent way to do the job, but there's another way to accomplish the same goal by using the generic form templates and schema fields. This approach could be better in some circumstances.</p><p>To make a hidden input, i.e. <input type="hidden" .../>, we can derive a new widget from the existing formlib.widget.SimpleInputWidget, which as a hidden() method that renders the field as a hidden input element. We can then derive a new schema field from the existing zope.schema.Text or zope.schema.TextLine field types. We can then tie the new schema type to the widget by defining an adapter.</p><p>#______# The code below defines a new HiddenText schema field type and associates a widget # with it that renders as a hidden input type. # A HiddenText schema field may be used to persist state in forms between the server # and the browser. import grok from zope.schema import TextLine from zope.publisher.browser import IBrowserRequest from zope.formlib.interfaces import IInputWidget from zope.formlib.widget import SimpleInputWidget</p><p>#______class HiddenText(TextLine): ''' We register an adapter for this new schema field which renders a hidden text field '''</p><p>#______class HiddenTextWidget(SimpleInputWidget): ''' Our hidden text widget is easily derived from SimpleInputWidget. We override the __call__ to render as a hidden input ''' def _toFieldValue(self, aInput): return unicode(aInput)</p><p> def __call__(self): return self.hidden()</p><p>#______class HiddenTextAdapter(grok.MultiAdapter): ''' Define an adapter for our schema field which selects our hidden text widget ''' grok.adapts(HiddenText, IBrowserRequest) grok.implements(IInputWidget)</p><p> def __new__(cls, field, request): return HiddenTextWidget(field, request)</p><p>To use this schema field type in a form, remember to set the title as an empty string and the required argument to False:</p><p> from hiddentext import HiddenText</p><p> class mySchema(Interface): hidden = HiddenText(title=u"", description=u"", required=False) ...</p><p> class myModel(grok.Model): grok.implements(mySchema) hidden = "My persisted data"</p><p> class myForm(grok.Form):</p><p>77 Grok 4 Noobs.</p><p> grok.context(myModel) ....</p><p>That's as easy as it gets.</p><p>One of the amazing things about Grok and other frameworks that use the ZCA is the ease with which new behaviour may be implemented, or existing behaviour redefined.</p><p>78 5.1.5: Using Viewlets and Viewlet Managers to build a Navigation Fabric</p><p>5.1.5: Using Viewlets and Viewlet Managers to build a Navigation Fabric</p><p>So far our site has defined pluggable areas for masthead, navigation, footer, sitematter and content. These areas are filled by enderingr their respective viewlet managers. The masthead and footer areas already contain content as defined by viewlets in the layout.pt source code.</p><p>Navigation and content have yet to be defined, and this brings us to the page template menu.pt.</p><p>We begin by defining a Menu(grok.Viewlet), and tell it to render into the Navigation slot for instances of ILayout:</p><p> class Menu(grok.Viewlet): grok.viewletmanager(Navigation) # Render into the Navigation area grok.context(ILayout) # for all instances of ILayout</p><p>The corresponding template lives in menu.pt:</p><p><div class='MenuHeader' tal:condition="context/navTitle | nothing"> <h5 tal:content="context/navTitle" /> </div></p><p><div class='buttons' tal:condition="context/navTitle | nothing"> <a tal:attributes="href python:viewlet.view.url(context,data={'edit':1})">Edit</a> <a tal:attributes="href python:viewlet.view.url(context,data={'add':1})">Add</a> <a tal:attributes="href python:viewlet.view.url(context)">Cancel</a> </div></p><p><ul class='menu'> <div tal:replace='structure provider:menuitems' /> </ul></p><p>The result may be seen on the left, the template renders two div's and an unordered list; The first contains the navTitle for the current context. Although.it is conditional on the context actually having a navTitle, we expect this to always be the case. The second div contains three anchors, clearly containing links to edit, add or cancel operations (only shown if you are an authenticated user). The unorderd list element renders the content of a new provider called 'menuitems'.</p><p>So let us take a look at the MenuItems viewlet manager:</p><p> class MenuItems(grok.ViewletManager): grok.context(ILayout) # This will be a list of <li /> elements</p><p>We can see that it renders for contexts which implement the ILayout interface.</p><p>In order to greatly reduce the amount of boilerplate code we would otherwise have, we can define a generic MenuItem which we then may use as a base class for ad-hoc menu items we may wish to later add. A grok.View or grok.Viewlet may use the grok.baseclass() directive to ensure that the class is treated as a base class and does not register itself with the ZCA.</p><p>79 Grok 4 Noobs.</p><p> class MenuItem(grok.Viewlet): ''' A base class for ad-hoc navigation menu items. ''' grok.viewletmanager(MenuItems) # Render into the MenuItems area grok.context(ILayout) # for all instances of ILayout grok.baseclass() # This is not a concrete class</p><p> title = u'' link = u'' mclass = '' dclass = '' mtitle = '' image = None</p><p> def condition(self): return True</p><p> def selected(self): return self.title == getattr(self.context, 'navTitle', None)</p><p> def href(self): return self.view.url(self.link)</p><p> def render(self): if self.condition(): img = '' if self.image is not None: img = '''<img src="{}" />'''.format(self.image)</p><p> return ("""<li class="menuItem%s%s" title="%s"><a href="%s">%s%s</a></li>""" % (' selected' if self.selected() else '', ' %s'%self.mclass if len(self.mclass) else '', self.mtitle, self.href(), img, self.title)) else: return ''</p><p>For the first time we have an instance of a view or viewlet which does not have an associated page template. The MenuItem class defines a render() method, which renders the whole of the viewlet <li /> tag content. You cannot have both a render method and a page template for the same view or viewlet (a view may have only one way of rendering).</p><p>One or two things should be explained about the above code;</p><p>First, menu items are selectively rendered depending on the condition() method. A subclass may override this method, for example use information available in the context to decide whether or not to render.</p><p>Second, the grok.view.url(argument) call may take several kinds of argument. Text is interpreted as relative to the current view url, if a grok.Model or grok.Container is the argument, the absolute url to the object is returned.</p><p>Third, the selected() method returns True if the menu title matches the navTitle of the context. That is to say, if the current URL has identified a model which has the same navTitle as the currently rendered menu item's title, we give the <li /> tag a selected class. This is not immediately useful, but in conjunction with CSS, we can style the menu item differently depending on the current state of navigation.</p><p> mclass is an extra css class for the menu item, and mtitle is a title for the <li /> tag which is displayed when hovering over the item. title is displayed as a menu</p><p>80 5.1.5: Using Viewlets and Viewlet Managers to build a Navigation Fabric</p><p> title, and link is interpreted by the href() method as discussed. If an image is specified, it will be enderr ed in a 20x20 area to the left of the menu title.</p><p>In order to move ahead with our discussion here, we will first need to introduce the IArticle interface. We don't need to know an awful lot about it yet, other than that there is one, and that it represents an article in our wiki. The reason we introduce IArticle here, is that IArticle is a container which contains other IArticle's, and the navigation menu is generated automatically from the content. An IArticle also has a title, a navTitle, and text:</p><p> class IArticle(Interface): ''' Identifies an article, and defines a data schema for it ''' title = TextLine(title=u'Page Title:', description=u'The title of the displayed page') navTitle = TextLine(title=u'Nav Title:', description=u'A title for the navigation area') text = Text(title=u'Display Text:', description=u'Text to display on this page')</p><p>In the above class definition, the TextLine and Text classes are defined in zope.schema, and such schema classes are the basis for server-side validation and automatic form generation in Grok. This will be discussed in more detail later.</p><p>We define a ContainerMenu viewlet which renders into the MenuItems slot whenever the context is an IArticle:</p><p> class ContainerMenu(grok.Viewlet): ''' Render the items contained inside self.context. The container menu must always be rendered in the same order ''' grok.viewletmanager(MenuItems) # Render into the MenuItems area grok.context(IArticle) # for all instances of ILayout</p><p> def sortedItems(self): sorter = IArticleSorter(self.context) return sorter.sortedItems()</p><p>The corresponding containermenu.pt page template loops through the containermenu.sorteditems() and renders a <li /> item tag for each one:</p><p><li class="indent" tal:repeat='item viewlet/sortedItems'> <a tal:attributes="href python:viewlet.view.url(item)" tal:content="item/navTitle" /> </li></p><p>Which brings us to IArticleSorter. Grok defines two kinds of containers; the general grok.Container looks like and quacks like a dict. The grok.OrderedContainer sort of looks like and quacks like a dict which always appends to the end, and has an updateOrder() method which can be used to change the order of items maintained. Performance of grok.OrderedContainer is not as good as the grok.Container, due to the need to maintain the sorted order.</p><p>Containers in a hierarchical structure may contain other containers, and this forms the basis for site hierarchies in the ZODB, Zope, and Grok, and the ability to locate resources through traversal in that hierarchy.</p><p>We decided to use a normal grok.Container and implement our own sorting extension, mostly in order to demonstrate extensibility but also because we wanted a bit more</p><p>81 Grok 4 Noobs. flexibility than a grok.OrderedContainer would have given us out of the box. The IArticleSorter interface is defined as follows:</p><p> class IArticleSorter(Interface): ''' This defines an interface into an article sorter. We use this approach rather than using grok.OrderedContainer for articles, in order to demonstrate the extensibility of the grok web framework. ''' def sortedItems(self): ''' Returns a sorted list of articles, and maintains sorting order'''</p><p>The line...</p><p> sorter = IArticleSorter(self.context) in the listing for the ContainerMenu viewlet finds an adapter which can turn a self.context, which we know to be an instance of IArticle, into an IArticleSorter instance. This adapter implementation may be found in sortedarticles.py, and this will lead us kicking and screaming to the next part of our narrative discussion.</p><p>82 5.1.5.1: Ordered menus from a normal grok.Container</p><p>5.1.5.1: Ordered menus from a normal grok.Container</p><p>Our journey continues with adding the ability to support a fixed order for menu items which are derived from a container which looks, and quacks, like a dict. We also want to support the ability to change the maintained order of articles.</p><p>Since we know that the list of contained articles are not going to grow very large (there's a practical limit), it does not make a great deal of sense to expend much effort in permanently storing and maintaining lists of articles or article references. Instead, directly producing a new list and sorting the list every time we want to produce a sorted list of articles is hardly going to impact performance, while greatly reducing complexity and possibility for error.</p><p>The way we will remember the order of articles, is to store a persistent attribute called 'order' with each IArticle.</p><p>As we have already seen, our sorter interface looks like this:</p><p> class IArticleSorter(Interface): ''' This defines an interface into an article sorter. We use this approach rather than using grok.OrderedContainer for articles, in order to demonstrate the extensibility of the grok web framework. ''' def sortedItems(self): ''' Returns a sorted list of articles, and maintains sorting order''' return []</p><p>We can see that this specifies that the interface should define a method returning a list of sorted articles.</p><p>An adapter which returns an instance of IArticleSorter when given an instance of an IArticle, is defined as:</p><p> class LayoutArticleSorter(grok.Adapter): ''' Adapts an IArticle and returns an IArticleSorter. Uses a factory pattern to return an instance of a Sorter. Since the Sorter implements ILayout, the site's index view will be rendered as the default view for such objects. This means that our viewlet below will be called for the Content area. ''' grok.context(IArticle) grok.implements(IArticleSorter)</p><p> def __new__(cls, context): return Sorter(context)</p><p>As you can see, this does nothing more than define a factory which spews out instances of the Sorter() class. So what is the Sorter() class, and what does it do? At the very least we know it must implement a method called sortedItems() which according to the interface definition, eturr ns a list of sorted articles:</p><p> class Sorter(grok.Model): ''' Implements an order for items in a normal grok.Container. Not terribly efficient since it sorts every time we extract the items, but there won't really be that many items anyway, and this demonstrates a transient object. ''' grok.implements(IArticleSorter, ILayout)</p><p>83 Grok 4 Noobs.</p><p> grok.provides(IArticleSorter) title = u'Ordering the navigation menu'</p><p> def __init__(self, context): self.context = context def renumber(self, items): prev = None for i, ob in enumerate(items): if prev is not None: # Simultaneously build a doubly linked list prev.next = ob.navTitle # while assigning an object order ob.prev = prev.navTitle else: ob.prev = None ob.next = None ob.order = i # Re-assign order numbers prev = ob def itemKey(self, value): return getattr(value, 'order', 9999) # by default insert items at the end def sortedItems(self): items = [v for v in self.context.values()] items.sort(key=lambda value: self.itemKey(value)) self.renumber(items) # We ensure a renumber of ordered items every time we render return items</p><p>Whoops! Information Overload!</p><p>What is that grok.implements(IArticleSorter, ILayout) and grok.provides(IArticleSorter)???</p><p>We are simply telling the ZCA that a Sorter model implements both IArticleSorter and ILayout interfaces. That is why the Sorter model also defines a title, since the site index view requires it (as per the ILayout interface definition).</p><p>As the ZCA now has two interfaces defined for Sorter, one cannot tell which one of the two the model provides. Hence the need for grok.provides(IArticleSorter).</p><p>Other than a few shenanigans in the renumber() method, the rest of the Sorter class implementation should be self-explanitory.</p><p>The renumber() method is passed a sorted list of [references to] instances of IArticle, as contained in their parent container. We traverse this list in order, and assign sequential numbers to the order attribute. At the same time, we assign other attributes for prev, next, being the navTitle values for the prior and next article in the list respectively. This will simplify implementing menu items for the prior or next items in the list while rendering a given article.</p><p>So this is why a simple call to sorter=IArticleSorter(context) can return an instance of Sorter for the context, and subsequent calls to sorter.sortedItems() will return the needed list of items- whilst as an added benefit ensuring consistency in things like prev/ next links.</p><p>Ok, so what happens if we traverse the site and end up on an instance of a Sorter?</p><p>Since Sorter implements ILayout, the default view, being index, will be rendered. This means that viewlets for the Masthead, Footer, Navigation and Content areas will be rendered (if they exist) using the Sorter as a context.</p><p>84 5.1.5.1: Ordered menus from a normal grok.Container</p><p>To render the Sorter Content, we define a viewlet to enderr the list of contained IArticle instances:</p><p> class ReOrderViewlet(grok.Viewlet): ''' Renders an interface for re-ordering items in the IArticle container ''' grok.context(IArticleSorter) grok.viewletmanager(Content)</p><p> items = [] def update(self): ordermenu.need() # Include Javascript self.items = self.context.sortedItems() # Get the items</p><p>For viewlets and views, the update() function will be called prior to the render() function or template. This means that one may perform any processing needed that the template might need later. In this case, we have an obscure looking ordermenu.need() and an assignment to a class variable containing the sorted items.</p><p>The ordermenu is defined in resource.py as:</p><p> from fanstatic import Library, Resource</p><p> library = Library('mygrok.grok4noobs', 'static') favicon = Resource(library, 'favicon.ico') style = Resource(library, 'style.css') ... <a href="/tags/JQuery/" rel="tag">jquery</a> = Resource(library, 'lib/jquery-1.11.1.min.js') ordermenu = Resource(library, 'ordermenu.js', depends=[jquery]) ...</p><p>In the Installation section we described how static resources are served through the fanstatic library. The definition of esourr ces and their dependencies is generally found in the resource.py file, although there is nothing that enforces that module name. To include a resource - in this case ordermenu.js - in the <head /> tag of the site, simply include in the update() function for the view or viewlet, a line such as ordermenu.need().</p><p>Our template for ReOrderViewlet is found in reorderviewlet.pt:</p><p><div> <ul class="menu"> <li class="menuItem movable" tal:repeat="item viewlet/items"> <span class='navTitle' tal:content="item/navTitle" /> - <span tal:content="item/title" /> </li> </ul> </div></p><p>After styling, the sorter renders as a list of items something like this:</p><p>For navigation buttons, we define the following two classes:</p><p> class SorterBackLink(MenuItem): grok.context(IArticleSorter) grok.order(0) title = u'Back to article' link = u'..' mclass = 'nav buttons'</p><p>85 Grok 4 Noobs.</p><p> class SorterAccept(MenuItem): grok.context(IArticleSorter) grok.order(1) title = u'Accept this menu order' link = u'' mclass = 'setItemOrder buttons'</p><p> which adds two menu items to the sorter. The first navigates back to the parent article, while the other effectively navigates back to the same URL that got us to the sorter in the first place. What???</p><p>Take a closer look at the mclass = 'setItemOrder ...' line. The trick is there. A small javascript function in ordermenu.js traps and takes care of handling the button click, and performs the actual sorting function. The operation of the javascript function is described in the comments of ordermenu.js:</p><p>//______// Javascript to move and re-order menu items. // Initial state, are a bunch of ul.menu > li.movable items. // When we click on one of them, we remove the .movable from all of them, and set // the clicked one to li.moving. The items above we set to li.aboveme, and the // items below, we set to li.belowme. When an li.aboveme is clicked, we move the // li.moving item to be above the clicked element. The opposite for li.belowme. // When the li.moving item has moved, we remove aboveme, belowme and moving, and // set all the classes back to movable.</p><p>$(document).ready(function(){ $('li.menuItem.setItemOrder').on('click', function(clickEvent){ var setOrder = []; clickEvent.preventDefault(); $('span.navTitle', $('li.menuItem.movable')).each(function(){ setOrder.push($(this).text()); });</p><p>$('<div />').load('setOrder', {'new_order':JSON.stringify(setOrder)}, function(response, status, xhr){ console.log('response is ' + response); if (status != 'error') { document.location.href = '../'; } else { alert(response); } }); });</p><p>$('ul.menu').on('click', '> li.movable, >li.aboveMe, >li.belowMe', function(){ var parent = $(this).parent(); var siblings = parent.children();</p><p> function resetState(){ siblings = parent.children(); siblings.removeClass('moving').addClass('movable'); siblings.removeClass('aboveMe').removeClass('belowMe'); }</p><p> if ($(this).hasClass('movable')) { var toMove = $(this); var idx = toMove.index();</p><p> siblings.removeClass('movable'); toMove.addClass('moving');</p><p> if (idx > 0) { siblings.slice(0, idx).addClass('aboveMe'); } if (idx < siblings.length) {</p><p>86 5.1.5.1: Ordered menus from a normal grok.Container</p><p> siblings.slice(idx+1).addClass('belowMe'); } } else { var toMove = $('li.moving', parent); if ($(this).hasClass('aboveMe')) { toMove.remove(); $(this).before(toMove); resetState(); } if ($(this).hasClass('belowMe')) { toMove.remove(); $(this).after(toMove); resetState(); } } }); });</p><p>So, to be able to reorder a list of <li /> elements, we give them a class of 'movable'. When we click an 'li.movable', we change the state of the clicked item to 'moving'; the siblings above get an aboveme class, and those below get a belowme class. Clicking aboveme or belowme items completes the move operation. The rest is done with css.</p><p>The line $('<div />').load('setOrder', {'new_order':JSON.stringify(setOrder)}, function(response, status, xhr)... is the important bit which actually updates the item order. The new order is simply an array of navTitle values retrieved from the list items themselves, and already in the now required order. This code does a JSON call to the server, calling the setOrder(new_order) function. At the server side, we implement the function within a grok.JSON class:</p><p> class OrderSetter(grok.JSON): ''' Any method declared below becomes a JSON callable ''' grok.context(IArticleSorter)</p><p> def setOrder(self, new_order=None): ''' Accepts an array of navTitle elements in the required order. Normally, we would not have to use JSON.stringify and simplejson.loads on arguments, but array arguments get names which are not compatible with Python. ''' from simplejson import loads from urllib import quote_plus</p><p> if new_order is not None: new_order = loads(new_order) # Unescape stringified json container = self.context.context for nth, navTitle in enumerate(new_order): container[quote_plus(navTitle)].order = nth return 'ok'</p><p>As stated in the comments, one would not generally need to use JSON.stringify/ simplejson.loads() to deal with arguments. However, the fact that arrays are passed with incompatible names makes argument transparency not really doable in Python.</p><p>87 Grok 4 Noobs.</p><p>5.1.5.1.1: Full source for SortedArticles Module</p><p>Sortedarticles.pt module:</p><p>''' This module deals with ordering articles and managing article order. Grok does include a OrderedContainer type which could have formed a better basis for our IArticle implementation, but the code below demonstrates numerous features quite well. ''' import grok</p><p> from interfaces import IArticle, IArticleSorter from layout import ILayout, Content from resource import ordermenu from menu import MenuItem</p><p> class SorterBackLink(MenuItem): grok.context(IArticleSorter) grok.order(0) title = u'Back to article' link = u'..' mclass = 'nav buttons'</p><p> class SorterAccept(MenuItem): grok.context(IArticleSorter) grok.order(1) title = u'Accept this menu order' link = u'' mclass = 'setItemOrder buttons'</p><p> class Sorter(grok.Model): ''' Implements an order for items in a normal grok.Container. Not terribly efficient since it sorts every time we extract the items, but there won't really be that many items anyway, and this demonstrates a transient object. ''' grok.implements(IArticleSorter, ILayout) grok.provides(IArticleSorter) title = u'Ordering the navigation menu'</p><p> def __init__(self, context): self.context = context def renumber(self, items): prev = None for i, ob in enumerate(items): if prev is not None: # Simultaneously build a doubly linked list prev.next = ob.navTitle # while assigning an object order ob.prev = prev.navTitle else: ob.prev = None ob.next = None ob.order = i # Re-assign order numbers prev = ob def itemKey(self, value): return getattr(value, 'order', 9999) # by default insert items at the end def sortedItems(self): items = [v for v in self.context.values()] items.sort(key=lambda value: self.itemKey(value)) self.renumber(items) # We ensure a renumber of ordered items every time we render return items</p><p> class LayoutArticleSorter(grok.Adapter): ''' Adapts an IArticle and returns an IArticleSorter. Uses a factory pattern to return an instance of a Sorter. Since the Sorter implements ILayout, the site's index view will be rendered as the default view for such objects. This means that our viewlet below will be called for the Content area. ''' grok.context(IArticle) grok.implements(IArticleSorter)</p><p>88 5.1.5.1.1: Full source for SortedArticles Module</p><p> def __new__(cls, context): return Sorter(context)</p><p> class ReOrderViewlet(grok.Viewlet): ''' Renders an interface for re-ordering items in the IArticle container ''' grok.context(IArticleSorter) grok.viewletmanager(Content)</p><p> items = [] def update(self): ordermenu.need() # Include Javascript self.items = self.context.sortedItems() # Get the items</p><p> class OrderSetter(grok.JSON): ''' Any method declared below becomes a JSON callable ''' grok.context(IArticleSorter)</p><p> def setOrder(self, new_order=None): ''' Accepts an array of navTitle elements in the required order. Normally, we would not have to use JSON.stringify and simplejson.loads on arguments, but array arguments get names which are not compatible with Python. ''' from simplejson import loads from urllib import quote_plus</p><p> if new_order is not None: new_order = loads(new_order) # Unescape stringified json container = self.context.context for nth, navTitle in enumerate(new_order): container[quote_plus(navTitle)].order = nth return 'ok'</p><p>The ReOrderArticle viewlet page template:</p><p><div> <ul class="menu"> <li class="menuItem movable" tal:repeat="item viewlet/items"> <span class='navTitle' tal:content="item/navTitle" /> - <span tal:content="item/title" /> </li> </ul> </div></p><p>The ordermenu.js source:</p><p>//______// Javascript to move and re-order menu items. // Initial state, are a bunch of ul.menu > li.movable items. // When we click on one of them, we remove the .movable from all of them, and set // the clicked one to li.moving. The items above we set to li.aboveme, and the // items below, we set to li.belowme. When an li.aboveme is clicked, we move the // li.moving item to be above the clicked element. The opposite for li.belowme. // When the li.moving item has moved, we remove aboveme, belowme and moving, and // set all the classes back to movable.</p><p>$(document).ready(function(){ $('li.menuItem.setItemOrder').on('click', function(clickEvent){ var setOrder = []; clickEvent.preventDefault(); $('span.navTitle', $('li.menuItem.movable')).each(function(){ setOrder.push($(this).text()); });</p><p>$('<div />').load('setOrder', {'new_order':JSON.stringify(setOrder)},</p><p>89 Grok 4 Noobs.</p><p> function(response, status, xhr){ console.log('response is ' + response); if (status != 'error') { document.location.href = '../'; } else { alert(response); } }); });</p><p>$('ul.menu').on('click', '> li.movable, >li.aboveMe, >li.belowMe', function(){ var parent = $(this).parent(); var siblings = parent.children();</p><p> function resetState(){ siblings = parent.children(); siblings.removeClass('moving').addClass('movable'); siblings.removeClass('aboveMe').removeClass('belowMe'); }</p><p> if ($(this).hasClass('movable')) { var toMove = $(this); var idx = toMove.index();</p><p> siblings.removeClass('movable'); toMove.addClass('moving');</p><p> if (idx > 0) { siblings.slice(0, idx).addClass('aboveMe'); } if (idx < siblings.length) { siblings.slice(idx+1).addClass('belowMe'); } } else { var toMove = $('li.moving', parent); if ($(this).hasClass('aboveMe')) { toMove.remove(); $(this).before(toMove); resetState(); } if ($(this).hasClass('belowMe')) { toMove.remove(); $(this).after(toMove); resetState(); } } }); });</p><p>The CSS styling for the sort interface(ordermenu.css):</p><p>/* css to support moving menu items around */ li.menuItem.movable, li.menuItem.aboveMe, li.menuItem.belowMe { margin:2px 0; border:1px solid gray; border-top-color:darkgray; border-left-color:darkgray; cursor:pointer; padding:1px 0.5em; }</p><p> li.menuItem.movable:hover { background-color: #BAEACA; }</p><p> li.menuItem.aboveMe:hover, li.menuItem.belowMe:hover { cursor:move; }</p><p> li.menuItem.moving {</p><p>90 5.1.5.1.1: Full source for SortedArticles Module</p><p> background-color: #CACAEA; } li.menuItem.aboveMe:hover { border-top:2px solid black; margin-top:0px; } li.menuItem.belowMe:hover { border-bottom:2px solid black; margin-bottom:0px; }</p><p>91 Grok 4 Noobs.</p><p>5.1.5.2: menu.py Full Source</p><p>The following source listing is for menu.py:</p><p> import grok from layout import ILayout, Navigation from interfaces import IArticle, IArticleSorter</p><p> class Menu(grok.Viewlet): grok.viewletmanager(Navigation) # Render into the Navigation area grok.context(ILayout) # for all instances of ILayout</p><p> def isEditable(self): if not hasattr(self.context, 'navTitle'): return False if self.context.navTitle is None: return False i = self.request.interaction if not i.checkPermission('gfn.editing', self.context): return False return True</p><p> class MenuItems(grok.ViewletManager): grok.context(ILayout) # This will be a list of <li /> elements</p><p> class MenuItem(grok.Viewlet): ''' A base class for ad-hoc navigation menu items. ''' grok.viewletmanager(MenuItems) # Render into the MenuItems area grok.context(ILayout) # for all instances of ILayout grok.baseclass() # This is not a concrete class</p><p> title = u'' link = u'' mclass = '' dclass = '' mtitle = '' image = None</p><p> def condition(self): return True</p><p> def selected(self): return self.title == getattr(self.context, 'navTitle', None)</p><p> def href(self): return self.view.url(self.link)</p><p> def render(self): if self.condition(): img = '' if self.image is not None: img = '''<img src="{}" />'''.format(self.image)</p><p> return ("""<li class="menuItem%s%s" title="%s"><a href="%s">%s%s</a></li>""" % (' selected' if self.selected() else '', ' %s'%self.mclass if len(self.mclass) else '', self.mtitle, self.href(), img, self.title)) else: return ''</p><p> class ContainerMenu(grok.Viewlet): ''' Render the items contained inside self.context. The container menu must always be rendered in the same order ''' grok.viewletmanager(MenuItems) # Render into the MenuItems area grok.context(IArticle) # for all instances of ILayout</p><p>92 5.1.5.2: menu.py Full Source</p><p> def sortedItems(self): sorter = IArticleSorter(self.context) return sorter.sortedItems()</p><p>Menu page template (menu.pt)</p><p><div class='MenuHeader' tal:condition="context/navTitle | nothing"> <h5 tal:content="context/navTitle" /> </div></p><p><div class='buttons' tal:condition="context/navTitle | nothing"> <a tal:attributes="href python:viewlet.view.url(context,data={'edit':1})">Edit</a> <a tal:attributes="href python:viewlet.view.url(context,data={'add':1})">Add</a> <a tal:attributes="href python:viewlet.view.url(context)">Cancel</a> </div></p><p><ul class='menu'> <div tal:replace='structure provider:menuitems' /> </ul></p><p>Container Menu Page template (containermenu.pt):</p><p><li class="indent" tal:repeat='item viewlet/sortedItems'> <a tal:attributes="href python:viewlet.view.url(item)" tal:content="item/navTitle" /> </li></p><p>93 Grok 4 Noobs.</p><p>5.1.5.3: Styling menu elements as buttons or menus</p><p>Generation of HTML is one thing, but styling is entirely another. As long as the HTML is semantically correct, we should be able to make it look like whatever we need it to. Without styling, the menu to the left appears like this (if you don't have edit permissions, the links for edit, add, cancel, delete and managing attachments will be missing):</p><p>The unstyled menu is still functional, but does not display as well as the styled one.</p><p>We would like to style menu items either as buttons, or as clickable menu items. Sometimes we even want to mix the two. The following stylesheet is what makes a set of divs, anchors and list items look like proper buttons and menus as may be seen to the left:</p><p>/* Styling menu items as buttons */ ul.menu >li { display:block; width:auto; list-style: none; line-height: 1.2em; background-color: #c0c0d0; margin:auto; }</p><p> ul.menu >li.nav { background:transparent; background-color: lightgray; }</p><p> div.buttons { padding:3px; margin:3px auto; text-shadow: 0px 1px 1px #ABA99E; }</p><p> ul.menu { border:1px solid gray; border-top-color:darkgray; border-left-color:darkgray; -webkit-border-radius: 5px; -khtml-border-radius: 5px; -moz-border-radius: 5px; -ms-border-radius: 5px; -o-border-radius: 5px; border-radius: 5px;</p><p> display:block; width: auto;</p><p>94 5.1.5.3: Styling menu elements as buttons or menus</p><p> color: black; margin:-2px; padding:2px; margin:10px 5px;</p><p> padding:3px; text-align:center; overflow:hidden; text-shadow: 0px 1px 1px #ABA99E; } ul.menu > li > a { display:block; padding:3px; text-decoration:none; color:navy; } ul.menu > li > a:hover { font-weight:bold; } div.buttons > a, ul.menu > li.buttons > a { -webkit-border-radius: 5px; -khtml-border-radius: 5px; -moz-border-radius: 5px; -ms-border-radius: 5px; -o-border-radius: 5px; border-radius: 5px;</p><p> border:1px solid gray; border-top-color:darkgray; border-left-color:darkgray; background-color: #EAFAEA; cursor:pointer;</p><p> margin: auto; padding:1px 0.5em; min-height:1.4em; line-height:1.2em; overflow:hidden;</p><p> font-weight:normal; text-decoration:none;</p><p> color:navy; } div.buttons > a, ul.menu > li.buttons > a { background-color: #daeada; filter: progid:DXImageTransform.Microsoft.gradient(endColorstr='#CADACA', startColorstr='#EAFFEA'); /* for IE 6, 7*/ ms-filter: "progid:DXImageTransform.Microsoft.gradient(endColorstr='#CADACA', startColorstr='#EAFFEA'')"; /* for IE 8,9*/ background-image: -moz-linear-gradient(bottom, #CADACA, #EAFFEA); background-image: -webkit-linear-gradient(bottom, #CADACA, #EAFFEA); background-image: -ms-linear-gradient(bottom, #CADACA, #EAFFEA); background-image: -o-linear-gradient(bottom, #CADACA, #EAFFEA); background-image: linear-gradient(bottom, #CADACA, #EAFFEA); } div.buttons > a:hover, ul.menu > li.buttons > a:hover { background-color: #CAFACA; cursor:pointer; filter: progid:DXImageTransform.Microsoft.gradient(endColorstr='#AAEAAA', ' startColorstr='#CAFACA'); /* for IE 6, 7 */ ms-filter: "progid:DXImageTransform.Microsoft.gradient(endColorstr='#AAEAAA', startColorstr='#CAFACA')"; /* for IE 8, 9 */ background-image: -moz-linear-gradient(bottom, #AAEAAA, #CAFACA); background-image: -webkit-linear-gradient(bottom, #AAEAAA, #CAFACA); background-image: -ms-linear-gradient(bottom, #AAEAAA, #CAFACA); background-image: -o-linear-gradient(bottom, #AAEAAA, #CAFACA);</p><p>95 Grok 4 Noobs.</p><p> background-image: linear-gradient(bottom, #AAEAAA, #CAFACA); }</p><p> ul.menu > li.buttons > a > img { display:block; float:left; height:20px; }</p><p>96 5.1.6: Site Navigation: Context sensitive menus</p><p>5.1.6: Site Navigation: Context sensitive menus</p><p>With all the prep work done for navigation, adding a menu is simplicity itself. Remember that the site index view will render the Navigation provider (viewlet manager) which in turn renders the viewlets for the current context. So basically, the viewlets are sensitive to context, and for IArticles, the context is either an instance of a Grok4Noobs application, or a NoobsArticle.</p><p>For NoobsArticle, we want a button which navigates up a level toward the site root. The single instance of Grok4Noobs is the root, so we don't want the PreviousLevel viewlet rendered for that class.</p><p> class PreviousLevelMenuEntry(MenuItem): ''' A menu item for articles with parent articles. IOW NoobsArticle ''' grok.context(NoobsArticle) grok.order(-4) title = mtitle = u'Up a level' link = u'..' mclass = 'nav buttons' def condition(self): self.title = self.context.__parent__.navTitle self.image = self.static['up.png'] return True</p><p>The grok.order() call manages the placement of the menu items. This one is at the top of the list. The title gets displayed on the button. The link is simple text in this case for traversing up a level. mclass is used to style the button, and condition() decides whether or not to display the button.</p><p>Then we want to render button links to the previous and next pages, which are siblings of the current article.</p><p> class PrevMenuEntry(MenuItem): ''' A menu item for previous articles in the list ''' grok.context(NoobsArticle) grok.order(-3) title = mtitle = u'Prev Page' mclass = 'nav buttons' @property def link(self): title = quote_plus(self.context.prev) return self.context.__parent__[title] def condition(self): if getattr(self.context, 'prev', None) is not None: self.title = self.context.prev self.image = self.static['left.png'] return True</p><p>Here, link is defined as a property, returning the object representing the previous sibling. Of course, the button is conditional depending on there being an actual sibling.</p><p>We do the same thing for the next sibling:</p><p> class NextMenuEntry(MenuItem): ''' A menu item for articles next in the list '''</p><p>97 Grok 4 Noobs.</p><p> grok.context(NoobsArticle) grok.order(-2) title = mtitle = u'Next Page' mclass = 'nav buttons' @property def link(self): title = quote_plus(self.context.next) return self.context.__parent__[title] def condition(self): if getattr(self.context, 'next', None) is not None: self.title = self.context.next self.image = self.static['right.png'] return True</p><p>Then, for articles which contain more than just one child article, we want to add a button which navigates to the sorter. The sorter has already been discussed in quite some detail:</p><p> class SorterLink(MenuItem): ''' A conditional menu item which shows for articles with more than one child ''' grok.context(IArticle) grok.order(-1) title = u'Change menu order' link = u'sorter' mclass = 'nav buttons'</p><p> def condition(self): return len(self.context) > 1</p><p>Not really a part of the general navigation, and again only for NoobsArticles which are contained in other articles, we want to support a delete button. This button renders itself right at the top of the navigation area:</p><p> class DeleteButton(grok.Viewlet): ''' Renders a delete button into the navigation area ''' grok.viewletmanager(Navigation) # Render into the Navigation area grok.context(NoobsArticle) # for normal articles</p><p>The DeleteButton() viewlet has an associated template which accomplishes the actual rendering:</p><p><div class='buttons'> <a tal:attributes="href python:viewlet.view.url(context,data={'delete':1})"> Delete this page </a> </div></p><p>The edit, add and cancel buttons are already rendered by the generic viewlet defined for the Menu viewlet (in menu.py) as discussed previously:</p><p> class Menu(grok.Viewlet): grok.viewletmanager(Navigation) # Render into the Navigation area grok.context(ILayout) # for all instances of ILayout</p><p> and the full menu template as displayed in the Navigation area is duplicated here for your reference:</p><p>98 5.1.6: Site Navigation: Context sensitive menus</p><p><div class='MenuHeader' tal:condition="context/navTitle | nothing"> <h5 tal:content="context/navTitle" /> </div></p><p><div class='buttons' tal:condition="context/navTitle | nothing"> <a tal:attributes="href python:viewlet.view.url(context,data={'edit':1})">Edit</a> <a tal:attributes="href python:viewlet.view.url(context,data={'add':1})">Add</a> <a tal:attributes="href python:viewlet.view.url(context)">Cancel</a> </div></p><p><ul class='menu'> <div tal:replace='structure provider:menuitems' /> </ul></p><p>99 Grok 4 Noobs.</p><p>5.1.7: Integrating the tinyMCE editor</p><p>The trivial case for integrating tinyMCE is not very complicated. After downloading and placing the tinyMCE library in grok4noobs/static/libs/tinyMCE, we need to include the tinyMCE javascript in the site view. This is done only when the edit mode is active for the site layout view. Before we can get there, we need to create the appropriate fanstatic libraries:</p><p> from fanstatic import Library, Resource</p><p> library = Library('mygrok.grok4noobs', 'static') ... tinymcelib = Resource(library, 'lib/tinymce/js/tinymce/tinymce.min.js') tinymce = Resource(library, 'lib/tinymce/init.js', depends=[tinymcelib])</p><p>After this, we can tell our view to include the library (layout.pt):</p><p> from resource import tinymce</p><p> class Layout(grok.View): ...</p><p> def update(self): ... if nomce is None: # Switch includes or omits tinyMCE tinymce.need()</p><p>This includes the javascript for the tinyMCE editor in the headers for the HTML document. The default initialisation associates the inline HTML editor with a textarea.</p><p>A less trivial example</p><p>We want our editor to do a number of things over and above the default. For example, we want to integrate a source highlighter so that we get highlighted text in our text blocks, and we also want to add pictures. We also want to bring up an edit box when someone clicks on a source text box, so that it becomes impossible to mess with the syntax highlighting. All of this is going to need a couple more libraries, and some javascript programming of our own.</p><p>You can read all about it here.</p><p>100 5.1.7.1: A less trivial integration for tinyMCE</p><p>5.1.7.1: A less trivial integration for tinyMCE</p><p>We want our editor to do a number of things over and above the default. For example, we want to integrate a source highlighter so that we get highlighted text in our text blocks, and we also want to add pictures. We also want to bring up an edit box when someone clicks on a source text box, so that it becomes impossible to accidentally mess with the syntax highlighting. All of this is going to need a couple more libraries, and some javascript programming of our own.</p><p>TinyMCE is a pluggable editor. To extend tinyMCE, you write some Javascript, add your plugin and resources to a specific place in the tinyMCE folder hierarchy, and tell the initialisation script about your plugin. This lets one add menu items or buttons to the editor navigation, and alter the editor's behaviour.</p><p>Assuming the tinyMCE distribution is unpacked at static/lib/tinyMCE, the initialisation script will generally be found in that directory too, and it is called init.js. In our case, init.js looks like this:</p><p> tinymce.init({ selector: "textarea", content_css: "/fanstatic/mygrok.grok4noobs/mceStyle.css", plugins: ["lists", "charmap", "paste", "gfncode", "textcolor", "link", "visualblocks", "table"], toolbar: 'undo redo | gfncode | link | styleselect | bold italic underline | ' + 'alignleft aligncenter alignright alignjustify | ' + 'bullist numlist outdent indent | forecolor backcolor', extended_valid_elements: "div[class|id|title|lang|style|align|onclick|name|src]", style_formats_merge: true, style_formats: [ { title: "Images", selector: 'img', items: [ { title: 'Normal', styles: {'float': 'none', 'margin': '10px', }}, { title: 'Float Left', styles: {'float': 'left', 'margin': '0 10px 0 10px'}}, { title: 'Float Right', styles: {'float': 'right','margin': '0 0 10px 10px'}}, ] }, ], });</p><p>Without going into too much detail, this initialises the editor with a bunch of options, and attaches the editor to instances of textarea.</p><p>The two interesting options, are</p><p>1. The gfncode plugin is one we wrote ourselves to handle attachments, and 2. Our css for the editor is in /fanstatic/mygrok.grok4noobs/mceStyle.css, which according to mygrok/setup.py, translates to grok4noobs/static/mceStyle.css. Using fanstatic in this way bypasses the automatic versioning, so browsers will cache such css files until told not to. Note: this path may need to be manually adapted to point to the correct place.</p><p>Our plugin displays an attachment edit form inside a dialog, lets one add an attachment either by uploading a file, or by typing in some text. The attachment submits the edit form using ajax, and if the response validates the request, will close the dialog. If the response was an error, the dialog remains visible.</p><p>101 Grok 4 Noobs.</p><p>For consistency, the dialog uses JQuery and JQuery-ui. To submit the form, the plugin uses JQuery-form, which properly handles file uploads over AJAX. So the dependency list for our tinyMCE grows quite radically:</p><p> from fanstatic import Library, Resource library = Library('mygrok.grok4noobs', 'static') ... jquery = Resource(library, 'lib/jquery-1.11.1.min.js') jqueryform = Resource(library, 'lib/jquery.form.min.js', depends=[jquery]) jqueryuicss= Resource(library, 'lib/jquery-ui-1.11.1/jquery-ui.min.css') jqueryui = Resource(library, 'lib/jquery-ui-1.11.1/jquery-ui.min.js', depends=[jquery, jqueryuicss]) tinymcelib = Resource(library, 'lib/tinymce/js/tinymce/tinymce.min.js', depends=[jqueryform, jqueryui]) ... tinymcegfn = Resource(library, 'tinymce.gfn.plugin.js', depends=[tinymcelib, jquery]) tinymce = Resource(library, 'lib/tinymce/init.js', depends=[tinymcelib, tinymcegfn])</p><p>Our plugin is called tinymce.gfn.plugin.js. It starts with an instruction to add itself into the plugin manager for tinyMCE:</p><p> tinymce.PluginManager.add('gfncode', function(editor, url) {...}</p><p>Event handlers are defined by the plugin to handle all sorts of things, but in particular, the init() method is trapped to perform all initialisation and override the click event. Calls to the addMenuItem() and addButton() methods add instructions to the menu and buttonbar to launch our dialog.</p><p>Here is the full code for our plugin:</p><p> tinymce.PluginManager.add('gfncode', function(editor, url) {</p><p> function addOrEdit(url) { var url = url || 'attach'; var title = 'Modify Attachment';</p><p> if (url=='attach') title = 'Insert Attachment';</p><p>$('<div />').load(url, function(r, a , b) {</p><p> var dlg = $(this).dialog({ title: title, modal: true, width: 'auto', resizable: false, });</p><p> function responseHandler(responseText, textStatus, xhr, $form){ if (textStatus=='error') { // retry- comms or other error in submission alert('Could not reach the server; Please try again.'); } else if (responseText.length) { if ($('div.form-status', $(responseText)).length) { // form had errors in submission $(form).html($(responseText)); } else { // Everything is great tinymce.activeEditor.selection.setContent(responseText); dlg.remove(); } } else { // Cancelled dlg.remove(); } }</p><p> var form = $('form', this); $(form).ajaxForm({ success: responseHandler,</p><p>102 5.1.7.1: A less trivial integration for tinyMCE</p><p>}); }); }</p><p> editor.addCommand('gfncodeCmd', addOrEdit);</p><p> editor.addButton( 'gfncode', { title: 'Add Attachment', image: url+'/highlight.jpg', cmd: 'gfncodeCmd', });</p><p> editor.addMenuItem('gfncode', { text: 'Add Attachment', image: url+'/highlight.jpg', cmd: 'gfncodeCmd', context: 'insert', });</p><p> editor.on('init', function(initEv){ $(editor.getBody()).on("click", "div.attachment", function(clickEv){ clickEv.preventDefault(); var src = $(this).attr("src") + '/modify'; editor.selection.select(this); addOrEdit(src); }); }); });</p><p> editor.addCommand() is a very useful shorthand for defining editor commands. The same command is then referenced by name when defining the menu and toolbar additions. editor.on('init', ...) is an event handler which is called when the initialisation is complete. This means that the editor window exists, and lets us trap the click event on highlighted text to edit our attachment.</p><p>The Python source code to handle attachments is one of the longest modules we have in terms of lines of code, and we discuss the module here.</p><p>103 Grok 4 Noobs.</p><p>5.1.8: Adding attachments to articles</p><p>If an article comprises HTML text, then adding things like graphics or external content is not something which can be merged with the HTML document and stored as a unit in the database. It becomes essential to be able to store and reference resources like that seperate from the article itself, and to facilitate linking back from the article to the stored resource.</p><p>We do want to store graphics and linked text in the ZoDB rather than as file system based resources, as this makes distribution of wiki content possible as a single integrated unit.</p><p>Defining attachments</p><p>We define an attachment as some content, either raw bytes or editable text, having a specific type or format, a name and a description. We envisage an article being able to contain any number of attachments, and refer to these attachments from within the article itself. Text attachments shall be rendered directly inline within a div element and not as an iframe, as the overheads associated with iframes is in this case unnecessary. Images shall be referenced from <img /> tags.</p><p>Looking at attachments then, the following zope.schema interface defines an attachment:</p><p> class IAttachment(Interface): """ Schema for resources which we intend to store alongside articles """ name = DottedName(title=u'Resource Name:', description=u'The name of the resource being defined') description = TextLine(title=u'Description', description=u'A description for the resource') fmt = Choice(title=u'Format:', description=u'The format of the source', vocabulary='gfn.resourceTypes', required=True, default='python') fdata = Bytes(title=u'Upload file:', description=u'Upload source', required=False) text = Text(title=u'or type Text:', description=u'Source Text', required=False)</p><p>@invariant def file_or_text(self): if self.fdata or self.text: return True raise Invalid('You must either upload the source or type it in')</p><p>First, take a look at the IAttachment.fmt attribute in the above. This is defined as a schema.Choice instance, having a vocabulary of 'gfn.resourceTypes'. A vocabulary is a global utility registered with the ZCA and implementing an IVocabularyFactory with the associated name. While there are a few ways to populate Choice fields, including specifying a list of options directly, vocabularies are well worth learning about. They are immensely powerful, and provide the means to implementing dynamic options with the greatest of ease.</p><p>Our definition for 'gfn.resourceTypes' looks like this:</p><p> class ResourceTypes(grok.GlobalUtility): grok.implements(IVocabularyFactory) grok.name('gfn.resourceTypes')</p><p>104 5.1.8: Adding attachments to articles</p><p> formats = [ ('image', u'A png, gif or jpeg image'), ('html', u'HTML Source'), ('javascript', u'Javascript Code'), ('python', u'Python Sources'), ('css', u'CSS Source')]</p><p> def __call__(self, context): self.context = context terms = [] for value, title in self.formats: token = str(value) terms.append(SimpleVocabulary.createTerm(value,token,title)) return SimpleVocabulary(terms)</p><p>An important side note: For a grok.GlobalUtility to work as described above, your setup.py for the site must include the grok.app.schema package. This is a known outstanding issue.</p><p>You will notice that the factory __call__() dunder method takes a context as argument. This will be the view context for the form at the time the view is rendered, and the context may be used to decide which vocabulary items to return. A vocabulary Term consists of a value, a token and a title. The value is what we get back as a field value when a user chooses the associated choice in the form, and may be any Python object. The title is what the user sees in the list of choices. The token is what is used as an <option /> tag name. There is thus no limit to the complexity of the object identified and returned by a schema.Choice field.</p><p>Our IAttachment also defines a fdata = Bytes(title=u'Upload file:'...). The default widget generated by the formlib library for an object of type schema,Bytes, is a <file /> upload entry box. We can simply treat this attribute as a raw byte string.</p><p>The schema.TextLine attributes are reasonably self evident, and are ordinary Python unicode strings.</p><p>Although both the text and fdata attributes are defined as optional fields, we have defined an invariant on these two fields to enforce that either one or the other attribute is present during validation of the schema. An invariant is a rule which returns True if the rule succeeds, or raises an exception otherwise.</p><p>A container for attachments</p><p>Now, having defined our attachment, we also need to define a bin which holds attachments. This bin can then be associated with articles so that we can retrieve our attachments on demand.</p><p> class IAttachments(Interface): """ A bin holding attachments """</p><p>We can then define a model which implements IAttachments as a normal grok.Container:</p><p> class Attachments(grok.Container): ''' Holds things which implement IAttachment '''</p><p>105 Grok 4 Noobs.</p><p> grok.implements(IAttachments, ILayout) title = u'Managing Attachments'</p><p>As you can see, our Attachments class also implements ILayout, so we know that if we traverse to an instance of an Attachments class, we will get the site index rendered for free; this implies that the Content viewlet manager will be called correctly to fill in that part of the site view.</p><p> class ListAttachments(grok.Viewlet): ''' Displays a list of attachments and allows edit/delete on each item ''' grok.context(IAttachments) grok.viewletmanager(Content)</p><p> def update(self): popups.need()</p><p>Here, the popups resource refers to some javascript defined in resource.py as:</p><p> popups = Resource(library, 'popups.js', depends=[jqueryui])</p><p>It is a simple wrapper for displaying forms within a JQuery-UI dialog box and handling the response properly to ensure correct form submission.</p><p>$(document).ready(function(){ $('div.popup').on('click', function(e){ e.preventDefault(); var href = $('a', this).attr('href'); var title = $(this).attr('title'); $('<div />').load(href, function(r, a , b) { $(this).dialog({ title: title, modal: true, width: 'auto', resizable: false, }); }); }); });</p><p>An attachment manager UI</p><p>The Content viewlet for an Attachment context renders the following page template:</p><p><table class="attachmentsTable"> <thead> <tr> <th>Name</th> <th>Description</th> <th>Type</th> <th /> <th /> </tr> </thead> <tbody> <tal:loop tal:repeat="v context/values"> <tr tal:define="path python:viewlet.view.url(v)"> <td tal:content="v/name" /> <td tal:content="v/description" /> <td tal:content="v/fmt" /> <td class='buttons'></p><p>106 5.1.8: Adding attachments to articles</p><p><div tal:attributes="title string:Editing: ${v/description}" class="popup buttons"> <a tal:attributes="href string:${path}/edit">Edit</a> </div> </td> <td class='buttons'> <div tal:attributes="title string:Deleting: ${v/description}" class="popup buttons"> <a tal:attributes=" href string:${path}/delete">Delete</a> </div> </td> </tr> </tal:loop> </tbody> </table></p><p>This renders a simple table listing the defined attachments for the page, together with links to change or delete an attachment. For example:</p><p>Name Description Type Attachments Attachments python Edit Delete container Content viewlet Attachments.content python Edit Delete for Attachments An attachment attachment.schema python Edit Delete schema</p><p> gfn.resourceTypes Our vocabulary python Edit Delete</p><p>IAttachments iattachments python Edit Delete bin ListAttachments listattachments.pt python Edit Delete Template Popups popups.js javascript Edit Delete javascript</p><p>When an edit or delete link is clicked, the popups.js traps the click and instead of following the link, pops up the dialog with the edit form or delete confirmation.</p><p>For example,</p><p>107 Grok 4 Noobs.</p><p>Navigation menus for the attachment manager</p><p>Of course, normal article navigation does not apply when editing resources for an article, and we provide appropriate buttons to be rendered in the Navigation provider area:</p><p> class BackButton(MenuItem): grok.context(IAttachments) grok.order(0) title = u'Back to article' mclass = 'buttons' @property def link(self): return self.context.__parent__</p><p> and add an attachment manager link into the menu for an IArticle, conditional on their being attachments to manage of course:</p><p> class ManageAttachments(MenuItem): grok.context(IArticle) grok.order(-1) title = u'Manage Attachments' link = u'attachments' mclass = 'buttons'</p><p> def condition(self): a = self.context.attachments return a is not None and len(a)</p><p>Kinds of attachment</p><p>We define two types of attachment that we want to add to articles; these are: Images and Source Text. We know that they are attachments because they implement the IAttachment interface:</p><p> class Image(grok.Model): ''' An attachment being an image ''' grok.implements(IAttachment)</p><p> def __init__(self, name, description, fmt, fdata, text): self.name = name</p><p>108 5.1.8: Adding attachments to articles</p><p> self.description = description self.fmt = fmt self.fdata = fdata self.text = text def link(self): return '''<img src="attachments/%s" />''' % self.name</p><p>The link() method returns an <img /> tag with a src attribute that will load the appropriate attachment as an image. The Source() class is a little different:</p><p> class Source(grok.Model): ''' An attachment being a text item ''' grok.implements(IAttachment)</p><p> def __init__(self, name, description, fmt, fdata, text): self.name = name self.description = description self.fmt = fmt self.fdata = fdata self.text = text if fdata: self.text = fdata.replace('\r\n', '\n')</p><p> def link(self): return ('''<div class='attachment' name="%s" src="attachments/%s" />''' % (self.name, self.name))</p><p>This assumes that the data content will be text, and expressly sets the text attribute to the content of fdata if file data was uploaded. The link() method returns a div of class 'attachment' with appropriate name and src attributes. TinyMCE does not generally allow non-standard attribute tags such as a div with a src. The list of valid attributes is specified in the editor's init.js script.</p><p>Adding new attachments</p><p>To create a new attachment for an article, we need an adapter. This adapter should create a new instance of IAttachment, given an instance of an IArticle:</p><p> class ArticleAttachment(grok.Adapter): ''' An adapter which creates an IAttachment for an IArticle ''' grok.context(IArticle) grok.implements(IAttachment)</p><p> name = None description = None fmt = u'python' text = u'' fdata = None</p><p>Notice that the above adapter does not implement a factory which spews out instances of IAttachment when it is called, but is rather itself an instance of an IAttachment. The default __init__() dunder will assign self.context to the context of the adapter. i.e. self.context will be an instance of IArticle as identified through traversal. Having the above adapter, we can now create a form for an article which uses the input fields of an IAttachment, when presented with a traversed and identified IArticle instance.</p><p> class AddAttachment(grok.EditForm): grok.context(IArticle)</p><p>109 Grok 4 Noobs.</p><p> form_fields = grok.Fields(IAttachment) # Use the ArticleAttachment adapter grok.name('attach')</p><p> def setUpWidgets(self, ignore_request=False): super(AddAttachment, self).setUpWidgets(ignore_request) self.widgets['text'].cssClass = 'pre'</p><p> def update(self): from resource import style style.need()</p><p>@grok.action(u'Add this resource') def addResource(self, **data): if self.context.attachments is None: attachments = self.context.attachments = Attachments() else: attachments = self.context.attachments name = data['name'] if name in attachments: del attachments[name] if data['fmt']=='image': src = attachments[name] = Image(**data) return src.link() else: src = attachments[name] = Source(**data) content = getMultiAdapter((src, self.request), name='index') return content()</p><p>@grok.action(u'Cancel action', validator=lambda *_a, **_k: {}) def cancel(self): return ''</p><p>By specifying grok.Fields(IAttachment) for a grok.context(IArticle), we tell the form to use the adapter we prepared when initialising the context for the fields.</p><p>The name of the form view is simply 'attach'. So navigating to an article and then appending '/attach' to the urls would bring up this add form.</p><p>A grok.EditForm default template includes the HTML header and renders a complete HTML document. This we know will be displayed in a popup dialog. Unless we include the style in the update() function here [style.need()], the form will not be displayed in a way consistent with the rest of our user interface.</p><p>We ensure that the textarea which represents the text attribute of an IAttachment is rendered as preformatted text, by adding in the 'pre' css class for the text attribute in setUpWidgets().</p><p>Notice also the replacement of the validator in the cancel action. This prevents the Cancel submission from generating validation errors.</p><p>All the real work happens in addResource(). Here, if the article does not already have an attribute called 'attachments', we create an instance of the model Attachments() and assign it to the attribute. Remember that Attachments() is a grok.Container.</p><p>If an attachment by the specified name already exists, we first delete it as we will want to replace it with our new attachment. Then, we branch depending on whether the attachment is an image or source.</p><p>If the new attachment is an image, we assign a new instance of Image() to the attachment, and return a link to the image.</p><p>110 5.1.8: Adding attachments to articles</p><p>If, on the other hand, the attachment is source text, we assign a new instance of Source() and return the default view for a Source() model.</p><p>To make attachments traversable as a URL, we define the attribute as traversable in the ArticleContainer base class:</p><p> class ArticleContainer(grok.Container): .... attachments = None grok.traversable('attachments')</p><p>This means that if we traverse to an article, and then add the text '/attachments' to the url, we will get back an instance of the Attachments() model, or an error if there are no attachments defined.</p><p>Views on an IAttachment</p><p>Operations performed typically on attachments are to either change the attachment or to delete it. The delete operation is rather generic and trivially implemented, but the operation of changing attachments has some rather subtle issues which we will cover in some detail. First, deleting an attachment uses the form:</p><p> class DeleteAttachment(grok.EditForm): grok.context(IAttachment) grok.name('delete') form_fields = grok.Fields(IAttachment).omit('fdata', 'text')</p><p>@grok.action(u'Yes, Delete this attachment') def Delete(self, **data): attachments = self.context.__parent__ name = data['name'] if name in attachments: del attachments[name] self.redirect(self.url(attachments))</p><p>@grok.action(u'No, get me out of here', validator=lambda *_a, **_k: {}) def cancel(self): attachments = self.context.__parent__ self.redirect(self.url(attachments))</p><p>As is normal behaviour in handling submitted forms, the view redirects the browser to the default view for the parent after performing the updates.</p><p>The edit operation is quite similar. However, while this updates the attachment perfectly, you will recall that the actual article does not contain a reference to the attachment, but instead contains a syntax-highlighted copy of the attachment. This is to allow syntax highlighting to work consistently (you should not highlight already highlighted text). It is necessary therefore to update the content of the article itself after performing an edit operation.</p><p> class EditAttachment(grok.EditForm): grok.context(IAttachment) grok.name('edit') camefrom = None</p><p> def update(self, camefrom = None): self.camefrom = camefrom or self.url(self.context.__parent__)</p><p>@grok.action(u'Update') def Change(self, **data):</p><p>111 Grok 4 Noobs.</p><p> attachments = self.context.__parent__ name = data['name'] if name in attachments: del attachments[name] if data['fmt']=='image': attachments[name] = Image(**data) else: attachments[name] = Source(**data) self.redirect(self.camefrom)</p><p>@grok.action(u'Cancel action', validator=lambda *_a, **_k: {}) def cancel(self): self.redirect(self.camefrom)</p><p>We can ensure that the syntax highlighted attachments are the latest by doing an AJAX call for each instance of a div.attachment we find each time we eloadr the article:</p><p>$(document).ready(function(){ $('div.attachment').on('click', function(e){ e.preventDefault(); }); $('div.attachment').each(function(){ var attach = $(this); $("<div />").load(attach.attr('src')+'/highlight', function(){ attach.html($(this).html()); }); }); });</p><p>Since we also don't want to be able to click on text divs from the display view, the above script also prevents the default click event from propagating.</p><p>The above javascript is not included in the iframe that houses the tinyMCE editor, so there we will have to deal with things differently. The following template (showsource.pt) renders a Source() attachment:</p><p><div tal:attributes='class string:attachment mceNonEditable; src string:attachments/${context/name}; name context/name' tal:content="structure view/html" /></p><p>From within the editor, we cannot rely on $(document).ready() since we dont keep on reloading the document as we edit it. Instead, we need a simpler way to retrieve the highlighted text, so that our tinyMCE plugin can replace the content directly.</p><p>For this, we provide two additional views: ModifySource, and ModifyImage. ModifySource allows one to change the attachment type to an Image(), but ModifyImage does not let you go back. This not really a problem as it's really easy to delete and recreate attachments.</p><p> class ModifySource(grok.EditForm): grok.context(Source) grok.name('modify')</p><p> def setUpWidgets(self, ignore_request=False): super(ModifySource, self).setUpWidgets(ignore_request) self.widgets['text'].cssClass = 'pre'</p><p>@grok.action(u'Update') def Change(self, **data): attachments = self.context.__parent__ name = data['name']</p><p>112 5.1.8: Adding attachments to articles</p><p> if name in attachments: del attachments[name] if data['fmt']=='image': src = attachments[name] = Image(**data) else: src = attachments[name] = Source(**data) content = getMultiAdapter((src, self.request), name='index') return content()</p><p>@grok.action(u'Cancel action', validator=lambda *_a, **_k: {}) def cancel(self, **data): attachments = self.context.__parent__ name = self.context.name src = attachments[name] content = getMultiAdapter((src, self.request), name='index') return content()</p><p>As may be seen, the 'modify' view is specific to an instance of the Source() class here, and instead of a redirect to the parent page, this form returns the content of the attachment directly. It does so by finding the MultiAdapter which is called index, and has an attachment and BrowserRequest as context and argument. MultiAdapters with this form are views, so in short, we expect to find a view on the Source() attachment. Calling the adapter renders and returns the result of the view.</p><p> class ShowSource(grok.View): grok.context(Source) grok.name('index') html = ''</p><p> def update(self): self.html = self.context.highlight() # Set highlighted text style.need() textStyle.need()</p><p>The corresponding page template for a Source() view is as follows:</p><p><div tal:attributes='class string:attachment mceNonEditable; src string:attachments/${context/name}; name context/name' tal:content="structure view/html" /></p><p>The template returns the div.attribute tag and fills it in with the highlighted text.</p><p>Images work just like Source, with a few minor differences. Images are much simpler for one thing. The modify form for an image tailors things to suit. It does not allow an option to type in text or specify a format at all. Instead of returning content itself, it returns an <img /> tag with a link to content:</p><p> class ModifyImage(grok.EditForm): grok.context(Image) grok.name('modify')</p><p> form_fields = grok.AutoFields(Image).omit('text', 'fmt')</p><p>@grok.action(u'Update') def Change(self, **data): attachments = self.context.__parent__ name = data['name'] if name in attachments: del attachments[name] src = attachments[name] = Image(**data) return src.link()</p><p>@grok.action(u'Cancel action', validator=lambda *_a, **_k: {})</p><p>113 Grok 4 Noobs.</p><p> def cancel(self, **data): attachments = self.context.__parent__ name = self.context.name src = attachments[name] return src.link()</p><p>The view on an image has a render() method which returns the binary image data directly:</p><p> class ShowImage(grok.View): grok.context(Image) grok.name('index') def render(self): return self.context.fdata or self.context.text</p><p>In this case we are being very specific about the index view for an image versus an index view for source text. Grok lets us be as general or as specific as we need to be.</p><p>Wrapping it up</p><p>The addition and management of attachments turns out to be quite a complex thing to do, and the source for this module is long relative to other modules. It demonstrates admirably however, the way the component framework can be used to extend existing components, and provide pluggability. Subsequent support for other kinds of attachment for example, might be added with little trouble.</p><p>This chapter refers quite often to a syntax highlighter. We talk about it, and show where we use it, but what is it? The following section will explain in more detail.</p><p>114 5.1.8.1: Adding syntax highlighting</p><p>5.1.8.1: Adding syntax highlighting</p><p>There are two main options available for highlighting text on a web site. The first is to have the server perform the highlighting by using some or other external library, and the second option is to use client side highlighting via a Javascript app.</p><p>We chose server side for a variety of reasons. Firstly we wanted to demonstrate tinyMCE integration, and server side processing provides us with an ideal opportunity to do so. Secondly, the overheads involved in client side highlighters may not be worth it, and Third we really liked the approach of the Pygments highlighter which is also Python and has a really snug fit with what we wanted.</p><p>Many wiki type sites or documentation sites use some form of markdown, for example Wikipedia's WikiEditor [1] to produce HTML documents. While these work quite well, we think the WYSIWYG approach of tinyMCE is really elegant and lowers the barrier to entry for producing HTML really fast.</p><p>There are numerous other popular visual editors available which we might have used, such as the CKEditor [2] (formarly FCKEditor) or Wikipedia's new visual editor [3] which is to replace WikiEditor. TinyMCE is simple to integrate and does the job.</p><p>Source highlighter implementation</p><p>Looking at attachments.Source() implementation, the highlighter is called in the following method:</p><p> def highlight(self): pygments = getUtility(ISourceHighlight, name="Pygments") return pygments(self.text, self.fmt) # Returns highlighted text</p><p>Clearly, the variable pygments is being set to the utility exporting an ISourceHighlight interface with the name 'Pygments'. The utility is then called with the text and the required format specifier.</p><p>The idea behind utilities, is that we can rather easily add alternative highlighters with the same interface by merely changing the name. This provides Grok with an astonishing amount of pluggability.</p><p>The utility we find is defined in the colourise.py source file as follows:</p><p> import pygments</p><p> class Pygments(grok.GlobalUtility): """ Implements a source code highlighter as a utility """ grok.implements(ISourceHighlight) grok.name('Pygments') def __call__(self, data, fmt='python'): """ A highlighted version of the data is returned """</p><p>1. WikiPedia markup editor: https://www.mediawiki.org/wiki/Extension:WikiEditor 2. The CKEditor: http://ckeditor.com/ 3. Wikipedias new visual editor: https://en.wikipedia.org/wiki/VisualEditor</p><p>115 Grok 4 Noobs.</p><p> lexer = pygments.get_lexer_by_name(fmt) formatter = pygments.get_formatter_by_name('html') return pygments.highlight(data, lexer, formatter)</p><p>Another global utility defines the list of available syntax highlighter formats as a vocabulary. Vocabularies are rather powerful constructs which are well worth the effort spent learning about them.</p><p> class PygmentFormats(grok.GlobalUtility): """ The supported formats for our <pygmentize> based conversions *** NOTE *** Unsure why, but vocabularies defined like this need zope.app.schema in setup.py """ grok.implements(IVocabularyFactory) grok.name('gfn.pygmentFormats')</p><p> formats = [('html', u'HTML Source'), ('javascript', u'Javascript Code'), ('python', u'Python Sources'), ('css', u'CSS Source'), ('xml', u'Generic XML Source')]</p><p> def __call__(self, context): self.context = context terms = [] for value, title in self.formats: token = str(value) terms.append(SimpleVocabulary.createTerm(value,token,title)) return SimpleVocabulary(terms)</p><p>The above vocabulary is specified as a source in the 'fmt' attribute for the IAttachment interface schema class.</p><p>116 5.1.9: Authentication, Authorisation and Access Control</p><p>5.1.9: Authentication, Authorisation and Access Control</p><p>There are three primary concepts involved in online security, namely Authentication, Authorisation, and Access Control.</p><p>• Authentication is concerned about verifying the authenticity of the person, ensuring that he is who he says he is. • Authorisation is the process of allowing a person access to certain features or information • Access Control is about making sure that the person can only access the application features and information he is entitled to.</p><p>So, to authenticate someone, one must first have a way of internally representing the identity of that person. Having authenticated the person, one must then have a way of allocating entitlements to the person- that's authorisation. Having authorised someone for certain content, one also needs a way to control access to that content.</p><p>In Grok, the identity of one who may be authenticated is called a Principal.</p><p>In zope.pluggableauth.plugins, Grok provides a number of plugins for it's pluggable authentication utility (PAU) mechanism.</p><p>This mechanism allows one to implement a site specific authentication database and method. For example, using PAU we can access external SQL databases for login names and passwords, or authenticate against LDAP. The plugins provided out of the box are as follows:</p><p>1. ftpplugins.FTPCredentialsPlugin - A credentials extractor for FTP requests 2. generic.NoChallengeCredentialsPlugin - Used to prevent a challenge for credentials from happening 3. groupfolder.GroupFolder - A group folder allowing for groups of principals 4. httpplugins.HTTPBasicAuthCredentialsPlugin - Extract credentials using HTTP Basic Auth protocol 5. idpicker.IdPicker - A helper class that adds a number to identities to make them unique 6. principalfolder.PrincipalFolder - A persistent ZODB store and authenticator for principals. 7. session.SessionCredentialsPlugin - A session cookie based credentials extractor</p><p>From the above, the two most popular way of getting user credentials, is by using HTTP Basic Auth, or by using session based credentials.</p><p>117 Grok 4 Noobs.</p><p>The easiset way to add a database of users to your project, is to use a PrincipalFolder. This class already implements everything you need to store and authenticate principals.</p><p>Comparing Basic Auth to Session based credentials</p><p>Basic Auth is a standard where the browser stores user credentials, and responds directly to a challenge from the server by either popping up a login/password dialog or responding immediately to the challenge without user intervention.</p><p>Session based credentials makes use of a session cookie to associate credentials with the user as long as the session remains active.</p><p>The main disadvantages of each, is that one cannot easily log out of a Basic Auth session, while cookie based sessions expire forcing the user to log in after a period of inactivity.</p><p>The main advantages are that for Basic Auth, one only needs to log in once, while for sessions credentials, it is easy to support multiple users from within the same browser.</p><p>Session based credentials authenticate the user, while Basic Auth effectively authenticates the browser. Each method has it's place.</p><p>118 5.1.9.1: Defining ermissionsP</p><p>5.1.9.1: Defining Permissions</p><p>We start by deciding what we want to protect. For example, the ability to add or edit articles, or delete them, or the ability to backup and restore the database. Then we visualise different roles for these functions. For example, an Administrator may be able to back up and restore, while an Editor may be able to add, edit or delete articles.</p><p>To make things simple, we might just say that an Editor has access to the editing feature, while the Administrator's function is administering. Viewers may have the viewing function.</p><p>It is more natural to imagine that the Administrator role might have access to the administering, editing and viewing capabilities, while an Editor has access to both editing and viewing features. A Viewer would only be capable of viewing.</p><p>We can tell Grok what we want in the file called permissions.py:</p><p> import grok from zope.schema.vocabulary import SimpleVocabulary from zope.schema.interfaces import IVocabularyFactory</p><p>#______# Permissions defined class Administering(grok.Permission): grok.name('gfn.administering')</p><p> class Authenticated(grok.Permission): grok.name('gfn.authenticated')</p><p> class Editing(grok.Permission): grok.name('gfn.editing')</p><p> class Viewing(grok.Permission): grok.name('gfn.viewing')</p><p>#______# Roles defined class Administrator(grok.Role): grok.name('gfn.Administrator') grok.title(u'Administrator') grok.permissions(Authenticated, Administering, Editing, Viewing)</p><p> class Editor(grok.Role): grok.name('gfn.Editor') grok.title(u'Editor') grok.permissions(Authenticated, Editing, Viewing)</p><p> class Visitor(grok.Role): grok.name('gfn.Visitor') grok.title(u'Visitor') grok.permissions(Authenticated, Viewing)</p><p>At the end of permissions.py, we define a vocabulary which makes it easy for us to list the available roles:</p><p>#______# A vocabulary for our defined roles class Roles(grok.GlobalUtility): grok.name('gfn.AccountRoles') grok.implements(IVocabularyFactory)</p><p>119 Grok 4 Noobs.</p><p> def __call__(self, context): terms = [] for role in [Administrator, Editor, Visitor]: name = role.__dict__['grokcore.component.directive.name'] title = role.__dict__['grokcore.component.directive.title'] terms.append(SimpleVocabulary.createTerm(name, name, title)) return SimpleVocabulary(terms)</p><p>This vocabulary can also be used as the source for automatic forms components, as we will see later on.</p><p>120 5.1.9.2: Defining and Assigning Roles</p><p>5.1.9.2: Defining and Assigning Roles</p><p>Having defined oles,r in our case being Administrator, Editor or Viewer, we can assign one or more of these roles to principals as we create them in our PrincipalsFolder. For example, we might say "Bob is an Editor, Sue is an Administrator, and Pete is an Administrator as well as an Editor".</p><p>The way this is done, is by using the sites Role Manager:</p><p> roleMgr = IPrincipalRoleManager(grok.getSite()) uid = self.getIdByLogin('admin') roleMgr.assignRoleToPrincipal('gfn.Administrator', 'gfn.'+uid)</p><p>In other words, we say that principal 'gfn.'+uid is a 'gfn.Administrator'</p><p>We can assign as many roles to principals as is needed. Note that we use a prefix of 'gfn.' before identities like principals and roles. The reason being that by doing so we can keep the name space separate from other projects. We shall see later where prefixes are specified for the plugins.</p><p>121 Grok 4 Noobs.</p><p>5.1.9.3: Installing a Pluggable Authentication Utility</p><p>In our wiki example, we decided on using a session based credentials extraction together with a PrincipalsFolder. Since the credentials extractor needs no persistent storage, we install it as a global utility.</p><p> import grok from grok4noobs import Grok4Noobs from zope.pluggableauth.plugins.session import SessionCredentialsPlugin</p><p> class CredentialsPlugin(grok.GlobalUtility, SessionCredentialsPlugin): ''' Define the credentials plugin and challenge form fields ''' grok.provides(ICredentialsPlugin) grok.name('credentials')</p><p> loginpagename = 'login' loginfield = 'form.login' passwordfield = 'form.password'</p><p>The PrincipalsFolder on the other hand needs persistence, so we create a local utility for that.</p><p> from zope.pluggableauth.plugins.principalfolder import PrincipalFolder, InternalPrincipal from zope.pluggableauth.interfaces import IAuthenticatorPlugin from zope.securitypolicy.interfaces import IPrincipalRoleManager</p><p> class AuthenticatorPlugin(PrincipalFolder, grok.LocalUtility): ''' The Zope toolkit provides a folder based authenticator plugin called PrincipalFolder. We build a PAU plugin as a local utility based on the PrincipalFolder, which already implements IAuthenticatorPlugin. To use an external authenticator, eg. LDAP, one would implement the IAuthenticatorPlugin interface directly. ''' grok.provides(IAuthenticatorPlugin) grok.name('users')</p><p> def __init__(self, *args, **kwargs): ''' Create an administrator with default login and password ''' super(AuthenticatorPlugin, self).__init__(*args, **kwargs)</p><p> su = InternalPrincipal(login='admin', password='Admin', title=u'Administrator', description=u'The SuperUser') self['admin'] = su</p><p> roleMgr = IPrincipalRoleManager(grok.getSite()) for uid, _setting in roleMgr.getPrincipalsForRole('gfn.Administrator'): roleMgr.unsetRoleForPrincipal('gfn.Administrator', 'gfn.'+uid)</p><p> uid = self.getIdByLogin('admin') roleMgr.assignRoleToPrincipal('gfn.Administrator', 'gfn.'+uid)</p><p>Local utilities need to be registered with the local site. In case you are wondering, the site is the same thing as your application. grok.Application implements an ISite. The hierarchy in the ZODB looks something like this:</p><p>122 5.1.9.3: Installing a Pluggable Authentication Utility</p><p>App 1 is a container (if it subclasses a grok.Container) and so it can contain other grok.Model or grok.Container instances. Containers, of course, look and quack like dicts and so can contain other traversable elements. Whether the site is a container or model, as with all Python objects it can also have attributes. One of the attributes of sites like App 1 will be "_sm". This attribute contains the site manager for the site, accessible via:</p><p> site = grok.getSite() sm = site.getSiteManager()</p><p>The site manager can be used to register local components such as utilities or adapters. Such components are persistently stored inside the ZODB.</p><p>We built an adapter with the interface ISiteLocalInstaller which adapts an ISite to simplify the task of registering local utilities, and the source may be found in sitelocal.py:</p><p>#______# A small module to allow registration of local utilities after the site has been created # and added via the management UI import grok from zope.component import Interface from zope.location.interfaces import ISite</p><p> class ISiteLocalInstaller(Interface): ''' Describes the registration interface ''' def unregisterUtility(self, provided, name=None, name_in_container=None): ''' Unregister a local utility ''' def registerUtility(self, factory, provided, name=None, name_in_container=None, setup=None): ''' Register a local utility '''</p><p> class SiteLocalInstaller(grok.Adapter): ''' An adapter which adapts an ISite (or grok.Application) to return a local utility installer ''' grok.context(ISite) grok.implements(ISiteLocalInstaller)</p><p> def unregisterUtility(self, provided, name='', name_in_container=None): ''' Unregister a local utility from the site ''' if name_in_container is None: if name and len(name): name_in_container = name else: raise Exception(u'We need either a name, or a name_in_container to unregister local components')</p><p>123 Grok 4 Noobs.</p><p> sm = self.context.getSiteManager()</p><p> util = sm.queryUtility(provided, name=name) if util is not None: print 'delete %s' % util sm.unregisterUtility(provided=provided) del util</p><p> def registerUtility(self, factory, provided, name='', name_in_container=None, setup=None): ''' Register a new local utility with the site. If it already exists we remove the old one first ''' if name_in_container is None: if name and len(name): name_in_container = name else: raise Exception(u'We need either a name or a name_in_container to register local components')</p><p> sm = self.context.getSiteManager() old = sm.queryUtility(provided, name=name) if old is not None: sm.unregisterUtility(component=old, provided=provided) del old</p><p> if name_in_container in sm: del sm[name_in_container] try: obj = factory() except: obj = factory</p><p> sm[name_in_container] = obj sm.registerUtility(obj, provided=provided, name=name) if setup: setup(obj)</p><p>You can see how first the site is retrieved, then the site manager, and finally the new utility is registered with the site manager. Please note the difference between the name and name_in_container arguments; the name argument is the utility name, used in functions like getUtility() and queryUtility() to retrieve the utility by interface and name. It is important that if a name is not specified, the default name will be an empty string.</p><p>Querying a registered utility without specifying a name will not retrieve a named utility, and retrieving a utility while specifying a name will not match a registered utility with an empty name.</p><p>When using queryUtility() where there is both a local and global utility registered with matching names, the local utility will be returned. This lets us override the default global IAuthentication utility for our application by defining our own local utility and registering it with the site manager.</p><p> class PluggableAuthenticatorPlugin(PluggableAuthentication, grok.LocalUtility): ''' The Pluggable Authentication Utility mechanism provided by the Zope Toolkit is very flexible. It allows registration of utilities which retrieve credentials from the request, or provide authentication. One way to use it, is to use component lookup via the ZCA by name. Another way is to simply include instances of the plugins directly inside the PluggableAuthentication, as it is a persistent container in it's own right. ''' grok.provides(IAuthentication) grok.name('pau')</p><p> def __init__(self, *args, **kwargs):</p><p>124 5.1.9.3: Installing a Pluggable Authentication Utility</p><p> super(PluggableAuthenticatorPlugin, self).__init__(*args, **kwargs) self.credentialsPlugins = ['credentials'] # Name of utility for ICredentialsPlugin self.authenticatorPlugins = ['users'] # Name of utility for IAuthenticatorPlugin self.prefix = 'gfn.'</p><p>The 'gfn.' prefix is defined here to ensure that principals will have unique identities.</p><p>The usual way to register local utilities in Grok is to use the grok.local_utility() directive inside your application class definition. However, this has two problems:</p><p>• It only works when the application is first installed. You cannot use it to install local utilities after the fact. • The module implementing the application would have to import the class definitions of the local utilities being defined. This may lead to circular imports and dependency issues.</p><p>In our case, we install our local utilities if they don't exist at the time we are extracting credentials from our request:</p><p> class ILoginForm(Interface): ''' Our login form implements login and password schema fields. ''' login = schema.BytesLine(title=u'Username', required=True) password = schema.Password(title=u'Password', required=True)</p><p> class Login(forms.AddForm): ''' This is our login form. It will render when and where we need it. ''' grok.context(Interface) grok.require('zope.Public')</p><p> form_fields = grok.Fields(ILoginForm)</p><p>@grok.action('login') def handle_login(self, **data): ''' If the authentication plugins are not yet installed, install them ''' pau = queryUtility(IAuthentication) if pau is None or type(pau) is not PluggableAuthenticatorPlugin: installer = ISiteLocalInstaller(grok.getSite())</p><p> installer.registerUtility(PluggableAuthenticatorPlugin, provided=IAuthentication, name_in_container='pau')</p><p> installer.registerUtility(AuthenticatorPlugin, provided=IAuthenticatorPlugin, name='users') pau = queryUtility(IAuthentication) if pau is not None: pau.authenticate(self.request) self.redirect(self.url(self.context, data=data))</p><p>Our credentials are extracted from the 'login' view as specified in our credentials plugin. If the local utilities do not yet exist, we use the event of a 'login' action to check and install the two local utilities. The first is an IAuthentication utility, being our instance of a PluggableAuthentication with an empty string as a name. The second utility is our PrincipalsFolder which exports the interface for an IAuthenticatorPlugin.</p><p>If we try to access a protected view for which we do not have access, Grok will render the login page instead, so that the user can enter a name and password. This behaviour</p><p>125 Grok 4 Noobs.</p><p> sounds like a good idea, and can be for simple sites. In our case we prefer to have a login view displayed for unauthenticated users, and a welcome message displayed for those who are authenticated. To do this, we need to ensure that we never try to visit a page which is protected, which we can do with relative ease by protecting viewlets. Protected viewlets do not try to render anything at all unless the user has access to that content.</p><p>To render our welcome message or login prompt depending on login status, we define the following viewlet:</p><p> class Status(grok.Viewlet): ''' Renders the login form in Authentication area for layout ''' grok.context(ILayout) grok.viewletmanager(AuthSection) grok.require('zope.Public')</p><p> def loggedIn(self): ''' Tries to authenticate if not already authenticated. Returns status. ''' if not isLoggedIn(self.request): auth = queryUtility(IAuthentication) if auth is not None: auth.authenticate(self.request) return isLoggedIn(self.request)</p><p> def greeting(self): ''' Returns a greeting depending on the time of day ''' from datetime import datetime as dt hour = dt.now().hour tod = "Morning" if hour < 12 else "Afternoon" if hour < 18 else "Evening" return "Good %s, %s" % (tod, self.request.principal.title)</p><p> def zopeLogin(self): ''' Zope management by default uses a Basic Auth login with a 'zope' prefix ''' if self.loggedIn(): ns = self.request.principal.id.split('.') if len(ns) > 1: ns = ns[0] if ns=='zope': return True return False</p><p> def logoutLink(self): ''' If this is a Basic Auth login, redirect to challenge site, otherwise to our own logout view ''' if self.zopeLogin(): site = self.view.url("/").split("//")[1].split("/")[0] return "http://log:out@%s/." % site else: return self.view.url(self.context, "logout")</p><p>The matching page template for this viewlet is in auth_templates/status.pt:</p><p><div> <div tal:condition="viewlet/loggedIn"> <p tal:content="viewlet/greeting" /> <a tal:attributes="href viewlet/logoutLink"><button>Logout</button></a> </div> <div tal:condition="not:viewlet/loggedIn" tal:content="structure context/@@login" /> </div></p><p>The condition viewlet/loggedIn triggers the actual authenticate() process which attempts to authenticate the request. If the request contains the login form, this will succeed for valid credentials, providing the greeting instead of the login page.</p><p>126 5.1.9.3: Installing a Pluggable Authentication Utility</p><p>Note that the logoutLink() points either at a Basic Auth logout or a logout view depending on whether the ZMI is logged in or not. The only way to log out of an HTML Basic Auth connection, is to click "cancel" when presented with the credentials challenge form.</p><p>The helper function which determines whether or not we are logged in, is simple:</p><p> def isLoggedIn(request): ''' Convenience function tells us if we are logged in ''' return not IUnauthenticatedPrincipal.providedBy(request.principal)</p><p>The view which logs out from a session based authenticated connection, is also quite simple:</p><p> class Logout(grok.View): ''' We can call this view to log out from the cookie based login session. ''' grok.context(Interface) grok.require('zope.Public')</p><p> def update(self): if isLoggedIn(self.request): auth = queryUtility(IAuthentication) ILogout(auth).logout(self.request)</p><p> def render(self): self.redirect(self.url(self.context))</p><p>Since Logout uses a generic Interface as a context, it can be used to log out for any model or container.</p><p>127 Grok 4 Noobs.</p><p>5.1.9.4: Building a user management interface</p><p>A user manager for our site boils down to writing an editor for the PrincipalFolder we used as the basis of our IAuthenticatorPlugin. Such an editor should be able to add or remove users, or change the details associated with the principal, for example, change the roles associated with a given principal.</p><p>The code for this may be found in users.py.</p><p>Let us start with the interface for the editor component we are building. We know that the PrincipalFolder implements the IInternalPrincipalContainer interface, which is a container for items which conform to IInternalPrincipal.</p><p>We may envisage our editor as implementing ILayout, being the general layout of this site, and defining a content area and header area which we can fill in with the guts of our editor. The editor we might define as a simple list followed by a search box:</p><p> class IUsers(Interface): """ A user management interface """ users = List(title=u'Accounts', required=True, value_type=Object(title=u"User", schema=IAccount)) search = TextLine(title=u'Search:', required=False, default=u'')</p><p> def nItems(self): ''' Returns the number of items in the complete search result '''</p><p> def fromItem(self, setPos=None): ''' Optionally sets and returns the position from which to display items '''</p><p>We see that the list in the interface uses a value type of IAccount, which we define below:</p><p> class IAccount(Interface): login = TextLine(title=u'Login: ', description=u'A login/user Name') title = TextLine(title=u"Title: ", description=u"Provides a title for the principal.", required=False) password = Password(title=u"Password: ", description=u"The password for the principal.", required=False) roles = HSet(title=u'Roles: ', value_type=Choice(title=u'Role', vocabulary=u'gfn.AccountRoles', description=u'The kind role the user plays', default='gfn.Visitor'), default=set())</p><p>The only non-standard or odd thing about this is the HSet type. This type does not appear in zope.schema, although it otherwise behaves like a schema.Set:</p><p> class HSet(Set): ''' Marker class for a horizontal set '''</p><p>Now, when rendering a schema.Set element, zope.formlib does a queryMultiAdapter((Set, SimpleVocabulary, IBrowserRequest), IInputWidget)</p><p>128 5.1.9.4: Building a user management interface to determine the type of widget to render. Knowing this, we can override default behaviour and define our own widget rather simply by implementing an adapter:</p><p> from zope.formlib.itemswidgets import MultiCheckBoxWidget from zope.formlib.interfaces import IInputWidget from zope.publisher.browser import IBrowserRequest from zope.schema.vocabulary import SimpleVocabulary</p><p> class HSetVocabularyWidget(grok.MultiAdapter): ''' Left to it's own devices, this a set displays a dropdown selection box with multi-select capability (control-LMB to choose multiple values.) Rather than this, we would like the widget for Set fields to be multiple check boxes, one for each value in the set. This MultiAdapter overrides the original widget with the new one. ''' grok.adapts(HSet, SimpleVocabulary, IBrowserRequest) grok.provides(IInputWidget)</p><p> def __new__(cls, context, vocab, request): w=MultiCheckBoxWidget(context,vocab,request) w.orientation = 'horizontal' return w</p><p>This lets us change the way a schema.Set is rendered, and possibly even add a bit of styling to it.</p><p>We define our Account model, which implements an IAccount, by declaring the attributes found in IAccount, and giving them values. We also provide a few helper methods for populating the IAccount.roles appropriately for the given principal:</p><p> class Account(grok.Model): grok.implements(IAccount) login = u'' password = u'' title = u'' roles = set('gfn.Visitor')</p><p> def rolesFromAccount(self): ''' Populate the managed roles for this principal from self.roles ''' roleMgr = IPrincipalRoleManager(grok.getSite()) if self.login == 'admin': self.roles.add('gfn.Administrator') for rid, _setting in roleMgr.getRolesForPrincipal('gfn.'+self.login): roleMgr.unsetRoleForPrincipal(rid, 'gfn.'+self.login) for role in self.roles: roleMgr.assignRoleToPrincipal(role, 'gfn.'+self.login)</p><p> def accountFromRoles(self, login): ''' Populate self.roles by querying the role manager ''' roleMgr = IPrincipalRoleManager(grok.getSite()) for rid, setting in roleMgr.getRolesForPrincipal('gfn.'+login): if setting.getName() == 'Allow': self.roles.add(rid)</p><p> def __init__(self, user=None): self.user = user if user: self.login = user.login self.password = user.password self.title = user.title self.roles = set() self.accountFromRoles(self.login)</p><p>Of course, we don't start with an IAccount, instead we have an IInternalPrincipal. So to make an IAccount from an IInternalPrincipal, we need an adapter:</p><p>129 Grok 4 Noobs.</p><p> class InternalPrincipalAccount(grok.Adapter): grok.context(IInternalPrincipal) grok.implements(IAccount)</p><p> def __new__(cls, principal): return Account(principal)</p><p>The Users() model, which implements the IUsers interface, initialises itself from a IInternalPrincipalContainer, keeping a reference to the container and working directly on that. Users() also keeps a list of Account objects which mirror the container items, but only for a subset of the container items as specified by BATCH_SIZE (10 items).</p><p>The do_search() method populates this short list of items, which are the only ones displayed in the editor.</p><p> class Users(grok.Model): """ The list of users matching the _search filter is cached in _accounts. We limit the number of cached users to BATCH_SIZE, also the number of users actually displayed at any time. The page offset is counted from <pos>. The principals folder for the site looks somewhat like a dict, and is passed as an argument when instantiating this object. We keep a reference in <principals>. The users property setter takes a look at the cached list and compares the new list. From this it determines the set of deleted, inserted or changed items, allowing us to update several principals details at once. """ grok.implements(ILayout, IUsers) title = u'User Management' principals = {} pos = 0 _accounts = [] _search = None</p><p> def __init__(self, principals): self.principals = principals self.do_search()</p><p> def do_search(self): p = self.principals gen = p.search({'search':self.search or ''}, start=self.pos, batch_size=BATCH_SIZE) self._accounts = [IAccount(p[p.principalInfo(i).login]) for i in gen]</p><p>The 'search' attribute is implemented in the Users() class as a property, which mirrors the _search attribute:</p><p>@property def search(self): return self._search @search.setter def search(self, value): if self._search != value: self._search = value self.do_search()</p><p>This approach makes it easy to detect when the search specification has changed.</p><p>The users attribute is also implemented as a property. The read property simply returns the self._accounts attribute, but the users.setter is where the magic is. The incoming list of accounts is compared to the self._accounts, and we use set arithmetic to determine the set of added, deleted or altered records. For the deleted set, we then delete the corresponding container items. For the new elements we add appropriate</p><p>130 5.1.9.4: Building a user management interface</p><p>InternalPrincipal items to the container. For the changed items, we alter the record in the container directly.</p><p>@property def users(self): return self._accounts @users.setter def users(self, values): vdict = {account.login:account for account in values} a = set([account.login for account in self._accounts]) v = set([account.login for account in values]) deleted = a.difference(v) added = v.difference(a) updated = a.intersection(v) p = self.principals</p><p> for user in deleted: del p[user] for user in added: account = vdict[user] if account.login: account = vdict[user] p[user] = InternalPrincipal(login=account.login, title=account.title, password=account.password) account.rolesFromAccount()</p><p> for user in updated: u = p[user] account = vdict[user] if account.login and u.login != account.login: u.login=account.login u.title=account.title or '' if account.password: u.password=account.password account.rolesFromAccount() self.do_search()</p><p>When all the changes have been made, we repeat the last search to update our internal list of 10 (BATCH_SIZE) elements. nItems is expected to hold the total length of our internal list, and fromItem is the offset into our result set from which we start displaying items. nItems is again a property, but fromItems() is a method.</p><p>@property def nItems(self): ''' Returns the number of items in the complete search result ''' return len(list(self.principals.search({'search':self._search or ''})))</p><p> def fromItem(self, setPos=None): ''' Optionally sets and returns the position from which to display items ''' if setPos: self.pos = setPos return self.pos</p><p>To make a Users() instance from a IInternalPrincipalContainer, we need an adapter:</p><p> class PrincipalUsers(grok.Adapter): """ Adapts our PrincipalFolder to a user management interface """ grok.context(IInternalPrincipalContainer) grok.implements(IUsers)</p><p> def __new__(cls, principals): return Users(principals)</p><p>131 Grok 4 Noobs.</p><p>This adapter means that even though we have traversed to an intance of IInternalPrincipalContainer, our content viewlet which is based upon a Users model will work just fine. When we redirect to the editor, we determine the new context for our editor by using context=IUser(oldContext). Isn't ZCA magic just great?</p><p>...and to render the editor in the content area, we need a viewlet:</p><p> class EditPrincipals(grok.Viewlet): """ Renders the user management interface within the Content Area """ grok.context(Users) grok.require(gfn.Administering) grok.viewletmanager(Content)</p><p> with a page template to match:</p><p><div tal:content="structure context/@@editprincipalform" /></p><p>The above template simply renders the EditPrincipalForm form inside the content area.</p><p>Now for the display. We use grok.formlib to generate an entirely automatic form for the IUsers interface. Since the IUsers.users field is a schema.List of schema.Object, we use the formlib.ListSequenceWidget as a custom widget which holds formlib.ObjectWidget items. The way to do this is to use a formlib.widget.CustomWidgetFactory:</p><p> from zope.formlib.widget import CustomWidgetFactory from zope.formlib.objectwidget import ObjectWidget from zope.formlib.sequencewidget import ListSequenceWidget</p><p> def AccountWidget(): ''' An ObjectWidget produces a 'sub-form' for an object, in this case an Account. If our data field happens to be a list of accounts, we can wrap the account widget in a list sequence widget. The CustomWidgetFactory produces widgets by calling the appropriate factory with the configured arguments. ''' # Show accounts in the list as object widgets (sub-forms) ow = CustomWidgetFactory(ObjectWidget, Account) return CustomWidgetFactory(ListSequenceWidget, subwidget=ow)</p><p>Now the new AccountWidget can be set inside our automatic formlib form as follows:</p><p> class EditPrincipalForm(grok.EditForm): ''' A form that allows creation, editing and deletion of principals. ''' grok.context(Users) # view available as URL: 'appname/editprincipal'</p><p> grok.require(gfn.Administering) # Permission requirement form_fields = grok.Fields(IUsers) # Present a search form form_fields['users'].custom_widget = AccountWidget() # The current list of principals ...</p><p>We then add a bunch of actions to the form, rendered as buttons. These navigate through the virtual list of items, search the container or apply changes to the list:</p><p>@grok.action(u"Search") def search(self, **data): self.context.search = data['search']</p><p>132 5.1.9.4: Building a user management interface</p><p>@grok.action(u"Apply") def apply(self, **data): self.applyData(self.context, **data)</p><p>@grok.action(u"First Page") def firstPage(self, **data): self.context.fromItem(0)</p><p>@grok.action(u"Next Page") def nextPage(self, **data): if self.context.fromItem() + BATCH_SIZE < self.context.nItems(): self.context.fromItem(self.context.fromItem()+BATCH_SIZE)</p><p>@grok.action(u"Prev Page") def prevPage(self, **data): if self.context.fromItem() - BATCH_SIZE >= 0: self.context.fromItem(self.context.fromItem()-BATCH_SIZE) else: self.context.fromItem(0)</p><p>@grok.action(u"Last Page") def lastPage(self, **data): n = self.context.fromItem() / BATCH_SIZE if n % BATCH_SIZE == 0: n -= 1 if n < 0: n = 0 self.context.fromItem(n * BATCH_SIZE)</p><p>To make the form look good, we use a bit of css, as defined in the update() method of the form:</p><p> def update(self): rc.tabular.need()</p><p>The tabularform.css included by the rc.tabular.need() looks like this:</p><p> form { width:100% } form > table { width: 100%; -webkit-border-top-left-radius: 20px; -khtml-border-top-left-radius: 20px; -moz-border-top-left-radius: 20px; -ms-border-top-left-radius: 20px; -o-border-top-left-radius: 20px; border-top-left-radius: 20px; }</p><p> form > table fieldset {color:blue; border:none} form > table fieldset > legend {display:none}</p><p> form > table.form-fields {border:1px solid blue} form > table.form-fields td.fieldname{display:none} form > table.form-fields tr:first-child td.label span{display:none} form > table.form-fields li:nth-child(even) {background:#cecefe} form > table.form-fields tr:nth-child(even) {background:#cecefe} form > table.form-fields div.row {float:left} form > table.form-fields div.row input {border:none; border-bottom:1px dashed black; background:lightgreen} form > table.form-fields div.row div {float:left} form > table.form-fields div.row label {margin-left:5px; margin-right:2px} form > table.listing input {background:none}</p><p>The form renders automaticallly as:</p><p>133 Grok 4 Noobs.</p><p>The rest of the editor is just plugging in the navigation buttons and so forth.</p><p> class BackButtonMenuEntry(MenuItem): ''' A menu item for articles with parent articles. IOW NoobsArticle ''' grok.context(Users) title = u'Back to Main' link = u'..' mclass = 'nav buttons'</p><p> class UsersButtonMenuEntry(MenuItem): ''' A menu item for articles with parent articles. IOW NoobsArticle ''' grok.context(ISiteRoot) grok.require(gfn.Administering) grok.order(-4) title = u'Manage Users' link = u'/users' mclass = 'nav buttons'</p><p>In order to link the /users URL to the site, we add the following to the grok4noobs.Grok4Noobs class:</p><p> grok.traversable('users') def users(self): sm = self.getSiteManager() if 'users' in sm: return IUsers(sm['users'])</p><p>134 5.1.9.5: Controlling access to views and data</p><p>5.1.9.5: Controlling access to views and data</p><p>Access control in Grok is extremely easy.</p><p>When defining a view or viewlet, one may protect it by specifying the grok.requires() directive. grok.requires() may take as an argument either text, or a grok.Permission class.</p><p>All permission strings by convention start with a prefix; the basic zope permissions have a 'zope.' prefix, while our wiki uses the 'gfn.' prefix. For example, the Administering permission has a text equivalent of 'gfn.administering'.</p><p>When protecting viewlets, those viewlets which are not allowed for the current principal are simply not rendered. For protected views on the other hand, the security mechanism will render the login page instead of the view, and after accepting credentials will redirect to the original page.</p><p>For views or viewlets which should be accessable to anybody, one should use the permission 'zope.Public':</p><p> class Status(grok.Viewlet): ''' Renders the login form in Authentication area for layout ''' grok.context(ILayout) grok.viewletmanager(AuthSection) grok.require('zope.Public') ...</p><p>For views which should be resticted, specify the needed permission:</p><p> import permissions as gfn</p><p> class EditPrincipalForm(grok.EditForm): ''' A form that allows creation, editing and deletion of principals. ''' grok.context(Users) # view available as URL: 'appname/editprincipal' grok.require(gfn.Administering) # Permission requirement</p><p>...which is equivalent to:</p><p> class EditPrincipalForm(grok.EditForm): ''' A form that allows creation, editing and deletion of principals. ''' grok.context(Users) # view available as URL: 'appname/editprincipal' grok.require('gfn.administering') # Permission requirement</p><p>To manually check a permission for the current user, one may use a request.interaction.checkPermission. For examples:</p><p> def isEditable(self): i = self.request.interaction if not i.checkPermission('gfn.editing', self.context): return False return True</p><p>135 Grok 4 Noobs.</p><p>136 6: Adding functions and extensions</p><p>6: Adding functions and extensions</p><p>The ease with which a site can be extended speaks volumes about maintainability. One's ability to maintain a site or extend it without impacting on concurrent development or having to make large changes to existing code ddetermines one's exposure to risk and has a direct cost implication.</p><p>137 Grok 4 Noobs.</p><p>6.1: Adding an Index Page</p><p>We can generate an index page from the content stored in our wiki. This provides an alternative navigation, and a summary of the site.</p><p>We envisage the index being reachable from the root site menu. We can add support at a later time for reaching the index regardless of which article is being read.</p><p>Source files here are indexpage.py, indexcontent.pt and indexof.pt</p><p>The index page should show the site layout, including masthead, navigation and footer, so we start by defining a model IndexPage.</p><p> class IndexPage(grok.Model): ''' Return an index page for the site ''' grok.implements(ILayout) title = u'Site Index' navTitle = u'Index' text = u''</p><p>The Content section for an ILayout is rendered through a viewlet, and we define a viewlet called IndexContent for this purpose:</p><p> class IndexContent(grok.Viewlet): ''' This populates the 'Content' section with the index ''' grok.context(IndexPage) grok.require('zope.Public') grok.viewletmanager(Content)</p><p> def namespace(self): return dict(site=self.context.__parent__)</p><p>The namespace() method makes a template variable called site available within the template, and the definition in indexcontent.pt refers to this variable:</p><p><div class='IndexPage' style='margin:2em'> <ul class="sectionItems" tal:content="structure site/@@indexof"></ul> </div></p><p>This fills the sectionItems container with list items from an indexof view on the site.</p><p>The IndexOf view produces a list item for each contained IArticle, and each of those list items may have a <ul> container containing sub items. These sub items are rendered using a recursive call to IndexOf.</p><p><li class='IndexEntry' style='list-style-type:none'> <span tal:content="view/articleNumber" /> <a tal:content="context/title" tal:attributes="href python:view.url(context)" /> <ul class="sectionItem" tal:repeat="ctx view/sortedItems"> <div tal:replace="structure ctx/@@indexof"> section text </div> </ul> </li></p><p>138 6.1: Adding an Index Page</p><p>The view/articleNumber method above depends on the fact that to reach a given node in the hierarchy, one would already have had to visit each parent along the way. So the new article number becomes the parents article number with the current nodes order appended. That number is then stored temporarily with the node for use by any children.</p><p>The sortedItems() method uses the IArticleSorted interface discussed prevously to produce an ordered list of articles.</p><p> class IndexOf(grok.View): ''' Produces an index entry from the current article ''' grok.context(IArticle) grok.require('zope.Public')</p><p> def articleNumber(self): order = getattr(self.context, "order", None) if order is None: self.context.section = "" else: order = int(order) + 1 parent = getattr(self.context, "__parent__", None) if parent and len(parent.section): section = "{}.{}".format(parent.section, order) else: section = "{}".format(order) self.context.section = section return section + ": " return ""</p><p> def sortedItems(self): sorter = IArticleSorter(self.context) return sorter.sortedItems()</p><p>To produce the index page given the ISiteRoot, we define a view called indexpage. This creates an IndexPage() instance, and uses view = component.getMultiAdapter((page, request), name='index') to instantiate the ILayout view. We then simply call the view to produce the site index, and return the HTML.</p><p> class PageView(grok.View): grok.context(ISiteRoot) grok.name('indexpage') grok.require('zope.Public')</p><p> def render(self): page = IndexPage() page = location.located(page, self.context, 'indexpage') view = component.getMultiAdapter((page, self.request), name='index') return view()</p><p>The location.located() method above sets the __parent__ and __name__ attributes for the page, which is necessary for view.url() to produce a valid url.</p><p>To add the index to the menu for all pages other than just the first, we have only a few changes. The IndexButton definition changes to use a context of IArticle rather than ISiteRoot, and we alter PageView as follows:</p><p> class PageView(grok.View): grok.context(IArticle) ... def render(self):</p><p>139 Grok 4 Noobs.</p><p> ctx = self.context while not ISiteRoot.providedBy(ctx): ctx = ctx.__parent__ page = IndexPage() page = location.located(page, ctx, 'indexpage') view = component.getMultiAdapter((page, self.request), name='index') return view()</p><p> class IndexButton(UtilItem): ''' A menu item to navigate to the index ''' grok.context(IArticle)</p><p>140 6.2: Printing the site as a book</p><p>6.2: Printing the site as a book</p><p>The organisation of this wiki is a bit odd, one might say not as simple as might be found on other wikis. It's too deep one might argue; a wiki does not need to branch forever. And one may be right.</p><p>The reason why we did it this way, is that the navigation of the site almost forces one to build documentation in a structured manner. So, looking at one topic, one is not presented with the entire list of siblings in the navigation; rather, one can see the previous and next sibling, and the parent item. Looking at this page, one can see that the parent is "Application", and the topic "Printing a Book" has one sibling called "Doing Backups". This is similar behaviour to a book, where one may page forward or back, or go to the index.</p><p>This approach directs the focus of the author to the topic at hand, and if there are further topics, related to the current one, lets the author create sub topics. The effect is almost like a live Mind Map. When traversed in ordered depth first, the site tree produces a single document where topics are close to each other and follow each other, much the way a book is presented.</p><p>All of this makes for a pretty good printable text.</p><p>Plugging in Prince</p><p>The best converter to PDF from HTML available seems to be a commercial procuct called PrinceXML [1]. This converter is also free for non-commercial use, thanks to the creators of that product. This site does not depend on the converter, but will use it if it is present.</p><p> try: # Figure if we have prince (http://www.princexml.com) installed has_prince = subprocess.call(['prince', '--version']) >= 0 except: has_prince = False</p><p>If the utility is present after startup, then the site will render a "Make Book" button when one navigates to the root of the site.</p><p> class MkBookButton(MenuItem): ''' A menu item that turns the site into a book ''' grok.context(ISiteRoot) grok.require('zope.Public') grok.order(-9) title = u'Create Book' link = 'mkbook' mclass = 'nav buttons'</p><p> def condition(self): # Depends on the 'prince' app being installed return has_prince</p><p>1. PrinceXML: http://www.princexml.com</p><p>141 Grok 4 Noobs.</p><p>Generating content for the book</p><p>As mentioned, we need to start at the site root, and traverse the content in ordered depth first order, visiting each node in the tree and emitting the HTML associated with each node as we go. This produces a single document which may be processed and turned into a PDF document.</p><p>We start by defining a view called mkbook for the main site:</p><p> class MkBook(grok.View): ''' Turn the content of this site into a book ''' grok.context(ISiteRoot) grok.require('zope.Public')</p><p> def render(self): url = self.url(self.context, name='fullpagehtml') try: result = subprocess.check_output(['prince', url, '-o', '-']) except: return</p><p> response = self.request.response response.setHeader('content-type', 'application/pdf') response.addHeader('content-disposition', 'inline;filename="gfn.pdf"') return result</p><p>This renders the whole site to a single document, uses Prince to convert the document to a PDF (using the FullPageHtml view), and returns the PDF document to the browser.</p><p>The FullPageHtml View is defined only for ISiteRoot, being the first node in our tree. However, this view kicks off the process with the first call to a ecursiver view called PageSimpleHTML</p><p> class FullPageHTML(grok.View): ''' Return the site as a single HTML page ''' grok.context(ISiteRoot) grok.require('zope.Public')</p><p> def update(self): style.need() textLight.need()</p><p>The detail is in the page template for FullPageHtml, which renders a full HTML page:</p><p><!doctype html> <html itemscope="itemscope" itemtype="http://schema.org/WebPage" xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal" xmlns:i18n="http://xml.zope.org/namespaces/i18n" lang="en"> <head> ...<snipped for the sake of brevity /> </head> <body i18n:domain="camibox" class="plainText" > <h1 class='bookTitle'>Grok 4 Noobs.</h1></p><p><div class="sectionItems" tal:content="structure context/@@pagesimplehtml"></div> </body> </html></p><p>142 6.2: Printing the site as a book</p><p>As one can see, the view template includes the content of the PageSimpleHTML view. This turns out to be the whole site.</p><p>PageSimpleHTML visits each node in the tree, appending the text of each node to the same output. It defines two convenience methods; sortedContent() returns the ordered list of contained items for the article, and articleContent() returns the HTML content for the article.</p><p> class PageSimpleHTML(grok.View): ''' Render this IArticle as a simple page, then do the same for each of the sub-articles ''' grok.context(IArticle) grok.require('zope.Public')</p><p> def articleNumber(self): order = getattr(self.context, "order", None) if order is None: self.context.section = "" else: order = int(order) + 1 parent = getattr(self.context, "__parent__", None) if parent and len(parent.section): section = "{}.{}".format(parent.section, order) else: section = "{}".format(order) self.context.section = section return section + ": " return ""</p><p> def articleContent(self): baseUrl = self.url(self.context) + "/" text = self.context.text if self.context.attachments is not None: for a in self.context.attachments: st = 'attachments/{}'.format(a) text = text.replace(st, baseUrl+st) return text</p><p> def sortedItems(self): sorter = IArticleSorter(self.context) return sorter.sortedItems()</p><p>The articleNumber() method creates a label for each IArticle node as we visit it. The very first node (the introduction) does not get a section label. This is normal behaviour for books. Subsequent nodes use the parent's label and append their own order (as determined by sorter.sortedItems()) to produce a new section label.</p><p>What articleContent() is doing, is to replace the relative links to image attachments (the src attribute) with absolute links. Normally when visiting a page in the site, it is sufficient to useelative r links, and for the sake of backups and restoring backups, it is also a good thing to store them that way in the article. However, this would not work for a single page, as the links are not relative to the initial model.</p><p>The page template for the view is again relatively straightforward;</p><p><h2 class='aTitle'> <span tal:content="view/articleNumber" /> <span tal:replace="context/title" /> </h2> <div class='Content' tal:replace='structure view/articleContent' /> <div class="sectionItem" tal:repeat="ctx view/sortedItems"></p><p>143 Grok 4 Noobs.</p><p><div tal:replace="structure ctx/@@pagesimplehtml"> section text </div> </div></p><p>Now, when the MkBook view is called for the ISiteRoot, the view produces a full PDF document from the single HTML document that resulted from the FullPageHTML view.</p><p>The only other little details around this relate to the specific CSS equirr ed to tell Prince how to format the documents. The bits we use are:</p><p> body.plainText pre { background-color: #FAFAFA; color: #000010; max-height:none; font-size:8.5pt; font-stretch: narrower; width:95%; margin: 0 2%; padding: 0.5em; } which overrides some defaults to make the background and font better for printed text, and</p><p>@page:first { @top { content: normal } @bottom-right { content: normal } }</p><p>@page:left { @top { content: string(booktitle) } @bottom-left { content: counter(page) } }</p><p>@page:right { @top { content: string(chaptertitle) } @bottom-right { content: counter(page) } }</p><p> h1.bookTitle { string-set: booktitle content(); }</p><p> h2.aTitle { string-set: chaptertitle content(); page-break-before: always; page-break-after: avoid; }</p><p>...which fills in page numbers and headers for each page in the book.</p><p>The produced document contains the Prince logo on the first page since this is the non commercial version, and frankly given the quality of the document, I would not have minded their logo appearing on every other page too. I certainly hope someone will use Prince commercially as a result of advertisement by this site.</p><p>144 6.3: Using Layers and Skins: How to re-skin your Grok site</p><p>6.3: Using Layers and Skins: How to re-skin your Grok site</p><p>Grok has full built in support for re-skinning a site. A skin may be as simple as using a different CSS, but at the other end of the scale may be used to completely replace site content. Reality lies somewhere between these extremes.</p><p>Skins are typically used when you want to present a slightly different view on your site. Generally, the url for a view containing a skin, would be something like:</p><p> http://address:port/++skin++skin_name/[rest of url]</p><p>If you are reading this on your own installation of the Grok 4 Noobs distribution, you can try accessing a skinned version of your site by inserting the /++skin++bootstrap/ skin name just after the localhost:8080. eg.</p><p> http://localhost:8080/++skin++bootstrap/gfn/Extending+GFN/Layers+and+Skins/ [1]</p><p>If you are accessing it via the aptrackers.com host, try instead:</p><p> http://gfn.aptrackers.com/gfn-bs/Extending+GFN/Layers+and+Skins/ [2]</p><p>If you do nothing in particular about skins, Grok will create a default one for you with a blank name. So a Grok app will actually always use a skin, even if it may not be evident.</p><p>The way you specify in code that your view is to use a particular skin, is to associate the view with a layer, and to in turn associate the layer with a skin. Like this:</p><p> class LayerClass(grok.IDefaultBrowserLayer): grok.skin('skin_name')</p><p> class MyView(grok.View): grok.layer(LayerClass) ...</p><p>Note that the LayerClass is actually an Interface, since it subclasses the IDefaultBrowserLayer interface. Since the default layer for Grok views is an IDefaultBrowserLayer, the new skin inherits all of the views which already use the default. So the new layer extends the set of components already registered for the default, but any views that explicitly state the new layer will not be available to the default layer.</p><p>1. Local Server Access: http://localhost:8080/++skin++bootstrap/gfn/Extending+GFN/Layers+and+Skins/ 2. Bootstrap Site: http://gfn.aptrackers.com/gfn-bs/Extending+GFN/Layers+and+Skins/</p><p>145 Grok 4 Noobs.</p><p>6.3.1: Using Bootstrap to fast track site development</p><p>A few enterprising folks got together and built a default set of CSS, Javascript and HTML scaffolding templates with the view to making web development a lot easier. They then released the above to the public domain [1] under the name bootstrap. This toolset has become increasingly popular, to the extent that websites developed with bootstrap are recognisably similar.</p><p>The easiest way to dive into bootstrap, is to find a site based on it which advertises the template as being freely distributable, and save the site from within your browser. Any CSS, Javascript or external resources will be saved along with the site. One can then embark on altering the HTML to do what you need it to. This process saves a huge amount of time and effort.</p><p>For example, to kick things off for our skinning project, we visited BootWatch [2], selected the readable theme, and went "File, Save". Of course, you are certain to get a number of unwanted bits and pieces which need to be removed, like for example the Google Analytics script.</p><p>Reducing the HTML</p><p>After removing the unneeded parts and some editing, we were left with an HTML index file which we saved to a page template which we called bootstrap_templates/ index.pt.</p><p><!DOCTYPE html> <html lang="en"><head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta charset="utf-8"> <title tal:content="context/title">Site Title

Crumbs
Site Content

Footer

1. Bootstrap: http://getbootstrap.com/ 2. BootWatch: http://bootswatch.com

146 6.3.1: Using Bootstrap to fast track site development

We had to rename the resources folder in our static area to 'static/bootstrap/'. Note that our CSS and javascript is loaded directly from that folder, producing a faithful replica of the original theme.

For our navigation bar, we are rendering 'structure context/@@navbar' which is clearly a view. For our breadcrumbs, we render 'structure context/@@breadcrumbs', another view. However, take a look at where we render the 'structure provider:content' and 'structure provider:footer', which are existing viewlet managers which have an IArticle as context. The point is, you do not need to rebuild your site completely just to do a skinning exercise.

The Python part

Corresponding to the index.pt page template, we defined a Grok view class in the 'bootstrap.py' module:

class Index(grok.View): ''' Renders the main site index ''' grok.context(IArticle) grok.layer(Bootstrap)

editing = False # content viewlet manager needs these flags adding = False deleting = False viewing = True

def update(self): # require 'light' text box styling textLight.need()

Note that this view defines a grok.layer(Bootstrap). The layer itself is what defines the skin we are to use, and is defined as another Python class:

class Bootstrap(grok.IDefaultBrowserLayer): ''' Defines the 'bootstrap' layer ''' grok.skin('bootstrap')

This means that we can reference the Index view by including the skin in the URL when we access our site:

http://localhost:8080/++skin++bootstrap/gfn

When setting up apache or nginx in front of the site, one would need to take the skin name into account for the proxy or rewrite rule - it is all completely transparent to the user when your site is accessed via the Internet.

147 Grok 4 Noobs.

6.3.2: Providing a nav bar and breadcrumbs

Nav items in bootstrap follow a specific format; All items in the bar are instances of

If there is a dropdown menu list, then the block element is a