Search for a string in all nodes with Python

def quickFindRecursive(startNodeId, searchValue)

Loops through all nodes recursively from startNodeId searching for searchValue (on both node.Name and all node.Properties.Value). It uses nodefactory and is as fast as XsltSearch. Start node id -1 = content root. Primarily intended for admin section use (PyPad), but could easily be used in a site search function aswell – then one would add conditions for protected and hidden nodes.

Returns a list of tuples with node id, property alias (or [Name]), and property value.

Example
print quickFindRecursive(-1,"Dogs")
Result
[[1045,"[Name]","Dogs"],[1054,"BodyText","I like dogs"]]

Source

def quickFindRecursive(startNodeId, searchValue):
  n = Node(startNodeId)
  r = []
  for c in n.Children:
    if c.Name==findWhat:
      r.append([c.Id, "[Name]", c.Name])
    for p in c.Properties:
      try:
        if str(p.Value).find(searchValue)>-1:
          r.append([c.Id, p.Alias, p.Value])
      except:
        pass
    r = r + quickFindRecursive(c.Id,searchValue)
  return r

PoC : Python Document and DocumentType helpers

Making Umbraco life easier with a little help of Python

I needed to copy all properties from one document type to another. The web site structure is not made with parent document types from the beginning since I had problems with that approach (mostly because of different standard values and descriptions on different document types).

Also – I like to be able to do structure by code and not only by UI. So I went for the approach creating documenttype properties by code.

C# might of course be the language of most peoples choice but I prefer the possibility for immediate response that I have with Python. I have created a “PyPad for Umbraco” which I’m using sometimes. Its an instant code window for Python in the Umbraco dev section, the only thing it does is to provide a multi line textbox and a button which executes the code in the textbox with the umbraco IronPython Engine.

So with my script in place I just want to write this code in PyPad:

dtSource = web.DocumentType.GetByAlias("oneDocTypeAlias")
dtTarget = web.DocumentType.GetByAlias("anotherDocTypeAlias")
copyDocumentTypeProperties(dtSource,dtTarget)

and the result given would be:

Added tab "Content"
Added property "Header"
Existing property "Introduction"
Added property "BodyText"

In Umbraco 4.5 code is actually being run on save in Umbraco UI code editor, so there the need of a “PyPad” there is not obvious, but my current site is running 4.0x and PyPad also gives me a possibility to print results.

Copy document content

I also need to copy document content occassionally, I want to copy all existing properties from one document to another. (With an boolean option makeChanges to make changes+save or just check for changes)

docSource = web.Document(2764)
docTarget = web.Document(19213)
copyAllPropertyValues(docSource,docTarget,True)

and the result given would be:

Changed property with alias "Header" from "Nnnno" to "Yyyyes"

Here’s the complete Python scripts

Copy document type properties

Use it at your own risk, and not on a live site until you are 100% sure it works as you’d expect!:

from umbraco.cms.businesslogic import web

def findTabCaptionById(dt, id):
  for t in dt.getVirtualTabs:
    if t.Id == id:
      return t.Caption
  return ""

def findTabByCaption(dt, caption):
  for tab in dt.getVirtualTabs:
    if tab.Caption == caption:
      return tab.Id
  return -1

def findOrAddTabByCaption(dt,caption):
  id = findTabByCaption(dt,caption)
  if id ==-1:
    print "Added tab : " + caption + "<br/>"
    return dt.AddVirtualTab(caption)
  else:
    return id

def containsPropertyTypeByAlias(dt, alias):
  for c in dt.PropertyTypes:
    if c.Alias == alias:
      return True
  return False

def copyDocumentTypeProperties(dtSource, dtTarget):
  for ptSource in dtSource.PropertyTypes:
    if not containsPropertyTypeByAlias(dtTarget,ptSource.Alias):

      dtTarget.AddPropertyType(ptSource.DataTypeDefinition, ptSource.Alias, ptSource.Name)
      ptTarget = dtTarget.getPropertyType(ptSource.Alias)
      ptTarget.Description = ptSource.Description
      ptTarget.Mandatory = ptSource.Mandatory
      ptTarget.ValidationRegExp = ptSource.ValidationRegExp

      tabIdSource = ptSource.TabId
      tabDescriptionSource = findTabCaptionById(dtSource,tabIdSource)
      tabIdTarget = findOrAddTabByCaption(dtTarget,tabDescriptionSource)
      dtTarget.SetTabOnPropertyType(ptTarget, tabIdTarget)

      dtTarget.Save()
      print "Added property : " + ptSource.Alias + "<br/>"

    else:
      print "existing : " + ptSource.Name + "<br/>"

dtSource = web.DocumentType.GetByAlias("oneDocTypeAlias")
dtTarget = web.DocumentType.GetByAlias("anotherDocTypeAlias")

copyDocumentTypeProperties(dtSource,dtTarget)

Copy document property values

Use on your own risk:

from umbraco.cms.businesslogic import web
from umbraco.cms.businesslogic import property
from umbraco import library

'''
   
   Copies an individual property value from one document to another
   with the exact same Alias and Datatypedefinition 
   
   for safety : first run with parameter printLog set to True and parameter makeChanges set to False 

'''
def changePropertyIf(docTarget, alias, dataTypeDefinitionId, newValue, printLog, makeChanges):
  if printLog: print "Looking for property alias: " + alias + "<br/>"
  for p in docTarget.getProperties:
    if p.PropertyType.Alias==alias and p.PropertyType.DataTypeDefinition.Id == dataTypeDefinitionId:
      if p.Value != newValue:
        if printLog:
          print "change from " + p.Value.ToString() + " to " + newValue.ToString() + "<br/>"
        if makeChanges:
          p.Value = newValue
          return True
      else:
        if printLog:
          print "unchanged<br/>"
      return False
  if printLog:
    print "Not found <br/>"
  return False

'''
   
   Copies all property values from one document to another
   with the exact same Alias and Datatypedefinition 

   for safety first run with parameter makeChanges set to False

'''
def copyAllPropertyValues(docSource, docTarget, makeChanges):
  hasChanges = False
  for p in docSource.getProperties:
    if changePropertyIf(docTarget, p.PropertyType.Alias, p.PropertyType.DataTypeDefinition.Id, p.Value, not(makeChanges), makeChanges):
      hasChanges = True
  if makeChanges and hasChanges:
    docTarget.Save
    docTarget.Publish(docTarget.User)
    library.UpdateDocumentCache(docTarget.Id)
  return hasChanges


# Example 

docSource = web.Document(2764)
docTarget = web.Document(19213)

hasChanges = copyAllPropertyValues(docSource,docTarget,True)

if hasChanges:
  print "Target document has/had differences"
else:
  print "No changes found"

List all document types

from umbraco.cms.businesslogic import web
def printAllDocumentTypes():
  for d in web.DocumentType.GetAll:
    i = d.MasterContentType
    if i==0:
      master = ""
    else:
      master = ", Master : " + web.DocumentType(i).Alias
    print d.Text + "," + d.Alias + master

Trying out different tools for the job – Xslt / Python / Ascx

Oh, no, not him again!

Yes, well, me again. Feeling the need of ventilating my woes… If you’re looking for advice, you probably best go somewhere else. If you read my post and like to share your own good practice you are most welcome. Many real life karma (the ones that will get you a better salary and a bigger car in your next life) I will send through space to whoever give me some good advice on my path to understand how to code better and be more efficient.

Edit: I do have some advice to share… For XSLT : improve your Xslt skills, learn from others + use a XSLT/XML-editing tool. For C#: invest in an extension like ReSharper to help you code better and faster. And generally: Be active on forums (our.umbraco.org and stackoverflow).

A common scenario

I wanted to create a macro that presents vcard-style person data in a list with templated Html. A very common scenario, right?

For the macro language / method I wanted three things :

  • readability,
  • modularity and a
  • separate presentation layer (that a designer could edit, for example to reorder or change tags).

Here’s what the final markup should look like:

<div class="vcard">
    <div class="fn n"><span>Taichi</span> <span>Kaminogoya</span></div>
    <div><a class="email" href="mailto:your@mail.address" title="Mail to Taichi Kaminogoya">your@mail.address</a></div>
    <div class="tel">
        <span class="type">home</span>:
        <span class="value">+1.415.555.1212</span>
    </div>
</div>

The data is fetched from a list of persons and no field is certain to contain information. If empty then the whole tag should be left out.

First call : beginners style Xslt

Polonius:
-What do you read, my lord?
Hamlet:
– Tags, tags, tags.

With plain Xslt I made it into a working macro quite fast. Xslt has its advantages, no doubt about that.

But with all the alternatives I needed the code became very long and hard to read, filled with lots of

<xsl:choose><xsl:when test="./data [@alias]=''">...

Guru style Xslt

“Divide your Xslt into templates you should” as Yoda would have said. Yes with

<xsl:call-template name="render-item"><xsl:with-param name="css-class" select="'tel'"><xsl:with-param "... 

code got better and more readable.

But with many parameters, it was still a tag soup hard to melt, hard to find the real syntax and elements underneath all tag-names and <xsl:.

One thing that went fine was placing the render-item template in another file so I could reuse it later.

<xsl:import href="vCard-render-item.xslt"/>

Conclusion : Xslt works fine but is hard to read because of the many options and parameters. I have several similar macros running with xslt, but they are hard to maintain, small fixes sometimes takes a lot of time to do. I wanted to try out another way this time.

Edit : A real Xslt guru (Chriztian Steinmeier) gave me a great advice on this particular case, “use apply-template”. Makes sense and the syntax gets more readable.

<xsl:template match="person">
	<div class="vcard">
		<xsl:apply-templates />
	</div>
</xsl:template>
<xsl:template match="name">
	<div class="fn n">
		<span><xsl:value-of select="." /></span>
	</div>
</xsl:template>
<xsl:template match="email">
...

DLR and IronPython

It first felt like a breeze going back to familiar C/VB/Pascal-like syntax from my quite long-time Xslt-efforts. And for my vCard-task the code did get nice and readable. With a separate file for item-rendering I also achieved the goal to have that part reusable.

However I never got comfortable with the Html strings inside the code (instead of the other way around). String templates like s = “<div class='{classname}’>{value}</div>” feels half okay, but I wanted the templates be in a separate file with VS (or any other tool) for Html validation. I wish I had found a cool Python Html rendering engine. Anyone?

Also – and perhaps the biggest disadvantages with IronPython as I see it is 1) there’s almost no IronPython-activity on the Umbraco forums and 2) already using xslt, cs and vb, a fourth language just feels wrong.

Conclusion : Python is okay but Html is included in code. Also you dont see much Python in Our forums, and the syntax lacks full support in Visual Studio.

Back to .Net-User Controls

Why not choose the easiest way? Plain comfortable C#!

I first tried using a Web User Control and a DataList for my job.

But I first stumbled on the part where I was to build my Html with some simple conditions.

if (obj.Phone!="") <div...>

With the help of Stackoverflow i did I get a nice solution to omit tags with empty values:

<div runat="server" visible="<%# Eval("MyProp")!==%>">nnn</div>

But Evals are just not cool. Late data binding in this day and age? No help from Visual Studio finding the right names and (slightly) bad performance.

Going for the data item is better,

<%# ((Customer)Container.DataItem).CustomerName %>

or via a helper function in my case – since I had to to two casts and that got messy:

<%# person(Container.DataItem).CustomerName %>

The only drawback is that in strings we loose the IntelliSense, and passing parameters I need strings… 😮 Stackoverflow again (no solution).

Conclusion : works fine but I do miss my IntelliSense.

Edit: The Visual Studio extension ReSharper does a beautiful job with code in strings. Syntax checking, IntelliSense, go to declaration, create property stub – everything works.

Update: this is my current solution, look below.

Code blocks

Next I went to old asp-style Code blocks syntax,

<% if (obj.MyProp!=""){ %><div ... <% } %>

and was happy for a while. Nice and readable, and does the job. Markup mixed with presentation logic. Beautiful!

At least so I thought until I decided to move the item-rendering part to a separate Web User Control. A most valid decision.

Using codeblocks in both the main Web User Control and the included Web User Control just will not render. I got <%=obj.Name%> in my rendered markup. Guess that has to do with the page life cycle. Stackoverflow again.

Conclusion : looks good, but does not work nicely with a sub-control.

Code blocks and Server side sub user control

Server Controls was a new experience to me. A good part of it is that it gives a possibility to code the control is design time presentation aswell as the runtime. Plus it fits nicely in Visual Studio Toolbox.

A Server Control for the Item renderer worked fine. And it could be mixed with code blocks in the calling Web User Control. But here I was dropping in yet another technology again. Plus the Server Control only has a cs-file, so back to building Html strings…

Conclusion : works fine, but  needs string html building and adds one extra technology.

A combination with codeblocks and a helper function

Code block syntax combined with an item-rendering-function in a App_Code class:

<% foreach (person in GetPersonList()) {>%>
    <div class="vcard">
        <%=library.RenderVcardItem("n",person.FullName) %>
        <%=library.RenderVcardItem("phone",person.Phone)%>
    </div>
<%}%> 

With that I think I achieved very good readability. However the goal to have all markup editable I did not reach. I’ll leave that for the MVC future.

VB or C#?

Another thought – using code blocks with C# syntax, the brackets are quite hard to find between the % and > so I stongly consider using VB in code blocks to get better readablity:

<% For Each person In GetPersonList() >%>
    <div>
        <%=library.RenderVcardItem("n",person.FullName) %>
        <%=library.RenderVcardItem("phone",person.Phone)%>
    </div>
<% Next %> 

Update: Current solution using databinding control

Using an Ascx with a databound ListView combined with an item rendering Ascx using code blocks I actually achieved what I wanted (only with a bit of mix of techniques and some extra overhead writing properties and the function to cast the dataitem). Also with the Resharper extension I got my dear Intellisense and syntax check. Not 100% on the syntax check tho, but a good step forward.

<asp:ListView ID="ListView1" runat="server">
    <LayoutTemplate>
        <ul class="contact-list">
            <li id="itemPlaceHolder" runat="server"></li>
        </ul>
    </LayoutTemplate>
    <ItemTemplate>
        <li class="vcard">
            <uc1:DSS runat="server" Label="Name:" CssClass="fn n" Text='<%# db(Container.DataItem).Firstname +" "+ db(Container.DataItem).Lastname %>' />
            <uc1:DSS runat="server" Label="Address:" CssClass="adr" Text="<%# db(Container.DataItem).Address %>" />
            <uc1:DSS runat="server" Label="Phone:" CssClass="tel" Text="<%# db(Container.DataItem).Phone%>" />
        </li>
    </ItemTemplate>
</asp:ListView>

The item rendering Web User Control (DSS):

Properties like Text, Label and CssClass is declared in the codebehind.

<%if (Text != "")
  {%>
<div class="<%=CssClass%>">
<% if (string.IsNullOrEmpty(AHrefLink)){ %>
    <strong class="type" title="<%=SubClass %>"><%=Label %></strong>&nbsp;<span class="value"><%=Text%></span>
    <%} else {%>
    <strong class="type" title="<%=SubClass %>"><%=Label %></strong>&nbsp;<a href="<%=AHrefLink %>"><%=Text%></a>
    <%} %></div>
<%} %>

Okay with this?

Lets check my list again:

  • readability I think so Yes – but the mix of databinding and code block syntax is not perfect.
  • modularity Yes.
  • separate presentation layer Even though I doubt a HTML-designer person would be perfecly happy with this he/she is able to see and change the tags in the Ascx files, so Yes.

Thanks

Thank you for reading. And thanks to people that helped me out with this task.

Please do post a comment if you feel like it.

(All code in this post are written directly into the WP text editor, not tested and only intended to show the ideas.)

Using IronPython in Umbraco – testing some Ajax

I just did some tests to spice up pyForum with Ajax. Still without any code but Python (well Javascript ofcourse). Here’s what I did:

I setup a Ajax call on the pyForum form posts to serialize the form and send it to the macro via Rndr (see separate post about that package if you like). Rndr activates on a url call and renders the pyForumStart macro with a query string that tells the macro to only handle post data. My Python code like this:


requestAjax = library.RequestQueryString("ajax")
requestId = library.RequestQueryString("id")

if requestAjax:
    if requestAjax == "getthreads":
        getlist = ForumThreadsHtml(int(requestId))
        print getlist
    if requestAjax == "post":
        post = HandleFormPost(None)
        print post
else:
    print ForumStartView()

For my Javascript I added this string Html template:


includeJavascriptTemplate ="""
<script type="text/javascript">
function getForumThreads()
{
id = jQuery('#forumpost-id').val();
url = "http://www.programenta.se/rndr?sc=fsp&ajax=getthreads&id=" + id + "&rnd=" + Math.floor(Math.random()*10000000);
jQuery.ajax({
url: url,
type: 'GET',
success: function (data) {
jQuery('#forumthreads').html(data);
},
error: function (msg, url, line) {
alert(msg); }
} );

}
function getDataFromMyMacro()
{

url = "http://www.programenta.se/rndr?sc=fsp&ajax=post";
data = jQuery('#forumpost').serialize();
jQuery.ajax({
url: url,
data : data,
type: 'POST',
success: function (data) {
getForumThreads();
jQuery('#forumNewThreadForm').html(data)},
error: function (msg, url, line) {
alert(msg); }
} );

}
</script>

"""

A bit slow at first

When I tried out the Ajax-version of the form I was a bit disappointed at first, since each Ajax post took 2 X 0.4 seconds or something. But no surprise actually since every call to the web server do take some tenths of a second. So I changed from two Ajax calls to one. Of course it’s indeed how Ajax should be handled. Dont ever call twice if you can call once.

I remade the code a bit so I have one call – and it makes Ajax posting better.

Have a look at the current demo.

Some nice message and perhaps a fade would make the thing look even better.

Conclusion and progress

Yes, this “Ajax over Rndr + Python” method seem to be fast enough and worth taking a step further. So my next post will be on writing a complete Ajaxified version of pyForum. Including the packaged code. So keep watching this space.

Using IronPython in Umbraco – tools and links

Python syntax

The official place to go for Python info is www.python.org

For a compressed Synax description I really like codesyntax.netfirms.com/lang-python.htm (great one!).

Editing Python files : IronPython tools for Visual Studio

It’s very conveniant to be able to edit Python files in the Umbraco UI code editor. For longer coding sessions however – we soon long for the comfy Visual Studio editor. But can VS do Python? Not by default, but there’s a very good add in avaliable: IronPython Tools for Visual Studio. http://ironpython.net/tools/ Install it and you edit your Python files in VS (almost) as if you were editing C# / VB.Net. (The tools is really a part of full IronPython installation so you’ll get the whole cake with the piece.)

A quick nice way to edit your Umbraco site Python scripts:

  1. Create a new Web application in Visual Studio.
  2. Add references to current versions of umbraco.dll. cms.dll and businesslogic.dll.
  3. Add a /python folder.
  4. Start adding and editing py-files to that /python folder.

Tada! You have Python syntax check and highlight plus Umbraco classes intellisense at your fingertips – and some Python intellisense aswell. You can of course extend your app with with a /xslt folder for xslts and /usercontrols for ascx’es if you like.

Using IronPython in Umbraco – pyForum part 3

This is the third post in this series and in this we’ll quickly finish up stuff to a point where we have a actual working forum. Well – in a very primitive form.

You can look at and try a running demo here. Later version avaliable here (at a faster server).

At the bottom of this post you’ll find a complete package with templates, document types, macros and scripts – installable at your own Umbraco 4.52+ installation for you to play with.

A macro for viewing a thread and it’s comments

We have the pyForumStart script for our start page that lists our threads and can show and handle a form for adding new threads. Now we also need one for our threads – with a form for new posts. It’s almost exact the same function as pyFormuStart – so we copy that one and just change the template strings, and some minor details in the code.

Going a bit further we could make one or two helper functions instead of our duplicate code, but I leave that for future improvements.

Here’s the complete pyForumThread script:


#
# Imports
#

from umbraco import library
from umbraco.presentation.nodeFactory import Node

#
# We need  System.Web.HttpContext.Current.
#  System.Web should really be a standard reference for the python engine
#

import clr
clr.AddReference('System.Web')
from System.Web.HttpContext import Current

# Add python path
import  sys
sys.path.append(Current.Server.MapPath('~/python'))

from pyForumHelpers  import *

requestType = Current.Request.RequestType

#
# Html Templates
#

threadTemplate  = """

 <div  class="forumthread">
 <h1>{subject}</h1>
 <p>{text}</p>
 <p>By:  <strong>{userName}</strong> {date}</p>
 </div>
 {forumPosts}
 {forumPostForm}
 <p><a  href='{parentLink}'>Back to forum start</a></p>
 """

forumPostTemplate  = """
 <div  class="forumpost">
 <h2>{subject}</h2>
 <p>{text}</p>
 <p>By:  <strong>{userName}</strong> {date}</p>
 </div>
 """

forumPostShowFormTemplate = "<p><a  href='?new=1'>Add a new post</a></p>"

forumPostFormTemplate ="""

 <h2>Add a new  post</h2>

 <form action="#" method="post" id="forumpost">

 <label  for="forumpost-subject">Subject</label><br/>
 <input  type="text" id="forumpost-subject"  name="forumpost-subject"/><br/>

 <label  for="forumpost-text">Text</label><br/>
 <textarea  cols=80 rows=4 id="forumpost-text"  name="forumpost-text"></textarea><br/>

 <label  for="forumpost-user">User (name)</label><br/>
 <input  type="text" id="forumpost-user" name="forumpost-user"/><br/>

 <input type="submit" id="forumpost-submit"  name="forumpost-submit" value="Submit"/>

 </forum>

"""

forumPostMessageTemplate = "<p>Thank  you for your post!</p>"
forumPostErrorMessageTemplate = "<p>Not valid!</p>"

#
# Helper functions
#

def HandleFormPost(id):
    retval = ""
    requestForm = Current.Request.Form
    subject = requestForm["forumpost-subject"]
    text = requestForm["forumpost-text"]
    user = requestForm["forumpost-user"]

    properties = {}
    properties["richtext"]=text
    properties["username"]=user

    if subject:
        create = DocumentCreate("pyForumPost",id,subject,properties)
        retval = forumPostMessageTemplate
        return retval

#
# Main
#

def ForumThreadView():

    def HandleForumForm():
        if requestType=="GET":
            requestNew  = library.RequestQueryString("new")
            if requestNew:
                return forumPostFormTemplate
            else:
                return forumPostShowFormTemplate

        if requestType=="POST":
            return HandleFormPost(currentPage.Id)

    def ForumPostsHtml():
        retval = ""
        for c in Node(currentPage.Id).Children:
            subject  = c.Name
            text = NodePropertyValue(c,"richtext")
            userName = NodePropertyValue(c,"username")
            date = c.CreateDate
            retval += forumPostTemplate.format(subject=subject,text=text,userName=userName, date = date)

        return retval

    forumFormHtml  = HandleForumForm()

    subject = currentPage.Name
    text = NodePropertyValue(currentPage,"richtext")
    userName = NodePropertyValue(currentPage,"username")
    date = currentPage.CreateDate
    parentLink = ""
    if currentPage.Parent:
        parentLink = library.NiceUrl(currentPage.Parent.Id)

    return threadTemplate.format(subject=subject,text=text,userName=userName,forumPosts=ForumPostsHtml(),forumPostForm = forumFormHtml, date=date, parentLink= parentLink)

print ForumThreadView()

I’ve added a link to the parent page “parentLink”. And the sharp eyed see a small fix in the child loop:

        for c in Node(currentPage.Id).Children:

In the first version we had:

        for c in currentPage.Children:

The difference is that the currentPage is created when the umbraco renderer activates the script. And the new child post will be created at a later point, so currentPage wont know about it. The call to Node(Id) however will get the current node data at the time of the call. So to get our new post we need to use that one.

The package

As promised : here’s an installable package of pyForum with all functions up to this point.

The next steps

I’ve written the first three posts now and we are at a point where we have something almost usable. Here’s some future progress ideas:

  • Dress up the forum with a style sheet. Something nice can be made just with a few CSS rules. Also the html string templates are very easy to change if necessary.
  • Remove the user name textbox and put the logged in user name there instead. If we build upon a site with existing membership that would be a piece of cake.
  • Add Javascript for form validation. Also a quick fix using some of the many libraries out there. jQuery validator for example. Inserting some Javascript calls is just a matter of entering the Html code in the document templates.
  • Add some Ajax calls for posting and retrieving the results to spice up the forum. Would not be a biggie either.
  • Add email-notifications. Using membership email addresses and .net messaging makes that easy.
  • Make it possible to edit threads and posts.

I will continue with more posts on this subject, and I indend to make pyForum a usable package in due time. However I don’t know when that will be.

I hope you have found theese first three parts informative and that you’ve been a bit inspired to dive further into IronPython with Umbraco.

Comments, suggestions and questions are most welcome.

Happy coding! / Jonas

Using IronPython in Umbraco – pyForum part 2

Continuing with our pyForum. In this post we will create a possibility for our users to add threads with a standard Html form.

More Html templates for our form

First we add four more Html template strings,

  • a link that the user clicks to show the form,
  • the actual form,
  • a thank you messsage to show after the form has been submitted and
  • a error message to show if the form data is not valid.

For now we target all users, with or without Javascript enabled browsers. For that reason we use a simple querystring ?new=1 to activate our form. Later on we will spice up our forum with Javascript and Ajax.

forumShowNewThreadFormTemplate ="<p><a href='?new=1'>Add a new thread</a></p>

forumNewThreadFormTemplate ="""
<h2>Add a new thread</h2>
<form action="#" method="post" id="forumpost">
<label for="forumpost-subject">Subject</label><br/>
<input type="text" id="forumpost-subject" name="forumpost-subject"/><br/>
<label for="forumpost-text">Text</label><br/>
<textarea cols=80 rows=4 id="forumpost-text" name="forumpost-text"></textarea><br/>
<label for="forumpost-user">User (name)</label><br/>
<input type="text" id="forumpost-user" name="forumpost-user"/><br/>
<input type="submit" id="forumpost-submit" name="forumpost-submit" value="Submit"/>
</forum>
"""
forumNewThreadPostMessageTemplate = "<p>Thank you for creating a new thread!</p>"
forumNewThreadPostErrorMessageTemplate = "<p>Not valid!</p>"

These new Html strings should also have a place in our original full view, so we add a place holder for them in our forumStartTemplate:

forumStartTemplate = """
 <div  class="forumstart">
 <h1>{forumname}</h1>
 </div>
 <ul>
 {forumThreads}
 </ul>
 {forumNewThreadForm}

 """

Handle user interaction

We have three states of our page. First the regular GET request shall show the page with the Add new thread link. Second, we show our form when the user clicks that link, and the request querystring becomes new=1. And third, we handle the form post and show a confirmation after the user submits the form data.

Current HttpContext

To get the RequestType and some other Context information we need the current httpcontext object, since System.Web is not referenced to from start we need to reference it and then import the System.Web.HttpContext:

import clr
clr.AddReference('System.Web')
from System.Web.HttpContext import Current
requestType = Current.Request.RequestType

Here’s what our handling code looks like, if the requestType is GET we just return the appropriate template string, on POST however we call a function that will handle the form data:

def HandleForumForm():
    if requestType=="GET":
        requestNew = library.RequestQueryString("new")
        if requestNew:
            return forumNewThreadFormTemplate
        else:
            return forumShowNewThreadFormTemplate
    if requestType=="POST":
        return HandleFormPost(currentPage.Id)

The HandleFormPost function contains the following code:

def HandleFormPost(id):
    retval = ""
    requestForm = Current.Request.Form
    subject = requestForm["forumpost-subject"]
    text = requestForm["forumpost-text"]
    user = requestForm["forumpost-user"]
    properties = {}
    properties["richtext"]=text
    properties["user"]=user
    # a very simple form validation:
    isValid = True
    if not subject: isValid=False
    if not user: isValid=False
    if not text: isValid=False
    if isValid:
        create = DocumentCreate("pyForumThread",id,subject,properties)
        retval = forumNewThreadPostMessageTemplate
    else:
        retval = forumNewThreadPostErrorMessageTemplate
    return retval

Note the standard calls to get data from Request Form fields. After those we construct a dictionary (much like a hashtable) which we will send to a DocumentCreate function. The DocumentCreate function uses Umbraco Api functions to create a new document using the parameters, nodetypealias, parent node id, name of the document and a list of property values. The Umbraco Api is well documented in other places.

Here are DocumentCreate:

def DocumentCreate(nodeTypeAlias, pageId, name, properties):
    dt = DocumentType.GetByAlias(nodeTypeAlias)
    author = User(0)
    documentId = int(pageId)
    documentName = name
    doc = Document.MakeNew(documentName, dt, author, documentId)
    #
    # Loop through document properties matching items in the properties dictionary.
    #
    if properties:
        for prop in doc.getProperties:
            alias = prop.PropertyType.Alias
            if alias in properties:
                doc.getProperty(alias).Value = properties[alias]
    doc.Publish(author)
    library.UpdateDocumentCache(doc.Id)
    return doc.Id

The “if properties” statement evaluates to True if properties != null, we could use the more explicit “if not properties is None”. See also http://boodebr.org/main/python/tourist/none-empty-nothing

The pyForumHelpers module

DocumentCreate is a function that we will use from other Python macro scripts aswell. For that reason we create another file for it, pyForumHelpers.py, and we import all functions from it into our scripts with the statement:

from pyForumHelpers import *

A workaround for current version of Umbraco (4.5.2)

Well – we should be able to do import that if we had the /Python folder in our Python engine list of paths. That’s an issue in the current Umbraco version, we need to write some lines to add that path first:

import clr
clr.AddReference('System.Web')
from System.Web.HttpContext import Current
#  Add python path
import sys
sys.path.append(Current.Server.MapPath('~/python'))
# now we are able to import our module:
from pyForumHelpers import *

We’ll include another helper function in FormHelpers as well – one that gives us a property value from a Node, or a empty string if the property does not exist. Also we need a list of imports to be able to use the code in DocumentCreate. So the complete ForumHelpers.py will look like this:

#
# Imports
#
import clr
clr.AddReference('cms')
clr.AddReference('umbraco')
clr.AddReference('businesslogic')
from umbraco import library
from umbraco.BusinessLogic import User
from umbraco.cms import businesslogic
from umbraco.cms.businesslogic.web  import DocumentType
from umbraco.cms.businesslogic.web import Document
clr.AddReference('System.Web')
from System.Web.HttpContext import Current

def NodePropertyValue(node, propertyname):
    prop = node.GetProperty(propertyname)
    if prop : return prop.Value
    else : return ""
def DocumentCreate(nodeTypeAlias, pageId, name, properties):
    dt = DocumentType.GetByAlias(nodeTypeAlias)
    author = User(0)
    documentId = int(pageId)
    documentName = name
    doc = Document.MakeNew(documentName, dt, author, documentId)
    #
    # Loop through document properties matching items in the properties dictionary.
    #
    if properties:
        for prop in doc.getProperties:
            alias = prop.PropertyType.Alias
            if alias in properties:
                doc.getProperty(alias).Value = properties[alias]
    doc.Publish(author)
    library.UpdateDocumentCache(doc.Id)
    return doc.Id

End of current session

Now we have almost all code we need to lists our threads and give the users a possibility to add new ones. In the next part I will finish up stuff, including listing and handling the threads with comments(posts). It will also include a full package with the (very) basic but working forum. Part 3.

Using IronPython in Umbraco – pyForum part 1

This is the first in a series of blog posts about IronPython in Umbraco.

I find IronPython (I’ll just write Python further on) to be a great middle way between Xslt and C# in Umbraco. It combines the ease of creating dynamic html right in the Umbraco UI with the power of a modern oo programming language that has access to .net and umbraco dll’s.

To have something fun to deal with in this posts we’re going to create a basic forum. Without a singe line of Xlst, C# or VB.Net. Only Python.

Disclaimers : I’m no expert in Python. Far from it – I just started some week ago. This is written as I learn and experiment myself. Also, English is not my first language, and I sometimes stumble using it. So some grammar and spelling errors will wait ahead. Comments and improvement suggestions are most welcome.

In the third post in this series you find a link to a demo and a complete pyForum package of the version up to the point of part 3. (New version on the demo running here.)

Umbraco building blocks for our pyForum

The forum need a simple structure within Umbraco to be able to store and show some forum content. The document structure with some threads and posts will look like this:

Site home
--Forum start
----First thread
------Answer 1 (post)
------Answer 2 (post)
------Answer 3 (post)
----Second thread
------Answer 1 (post)
----Third thread

Document types

We store data in the Umbraco document structure with the help of three very simple document types.

pyForumStart is the container for our forum. Under it we store threads (pyForumThread) and on the third level we have posts (pyForumPost). I choose to separate thread and post data types, mostly because I like them to appear differently in Umbraco UI.

pyForumStart
--pyForumThread
----pyForumPost

pyForumStart is just a empty document type that we use for the forum name, allowing document type pyForumThread as child.

pyForumThread and pyForumPost have two properties to begin with, richtext (rich text editor data type) which contains the actual post text and username (text). pyForumThread allow pyForumPost as a child document type. (For simplicity I dont use hierarchy data types here.)

Document templates

We make use of three document templates from the start. pyForumMaster which is the topmost master template, then we have pyForumStart and pyForumThread.

pyForumMaster
--pyForumStart
----pyForumThread

pyForumStart just has basic html-structure plus a content place holder for our child master pages. In our pyForumStart template we have a macro that lists titles of all existing threads with links. And in pyForumThread a macro that shows thread and posts under it.

Enter Python

Now to our Python code. You can find info of the basic syntax in zillion places on the net. I will try to explain just something of the special things with python – assuming you have knowledge of other languages (c#, javascript, vb.net …).

In Umbraco we find our Python files in the Developer section under Scripting Files. To make them useful in templates we use regular Umbraco macros, just as when we write Xslts.

The Hello World sample would look like this in Python:

print "Hello World!"

Some very brief information about Python syntax

Like I said before you find information about Pyhon on many places. The official place to go is www.python.org , for a compressed list with the most important information I really like codesyntax.netfirms.com/lang-python.htm (great one!). Here’s a minimal list of what you need to know about the language.

Variables are strongly typed but you don’t declare them.

Huh? Yeah, they simply get their type from the first value they get. It’s called duck typing. “If it walks and talks like a duck…” yada yada, google on that.

stringvariable = "Name"
numericvariable = 3333

Python don’t need semicolons at the end of each line (but they are allowed).
Code blocks are made just with indentation. So no need of brackets. Also don’t use parenthesis to enclose if-expressions:

x = 2
y = 3
if x > y:
    print "x is greater than y"
elif x==y:
    print "equal they are"
else:
    print "y is greater than x"

A basic for-loop:

for c in currentPage.Children:
    print c.Name

The statement print sends a string to the output stream, in Umbraco that is our rendered macro.

A function looks like this:

def NodeName(node):
    return node.Name

And a function call looks like this:

currentPageNodeName = NodeName(currentPage)

A recommendation for a Python script structure for use in Umbraco

The simplest Python file can consist of just a few lines of code and thats it. But when we build something a little more complex it is very good to lean on some kind of structure. After some struggling I have found myself comfortable ordering things this way :

  1. Imports – for external dll’s and python files
  2. Template htmls – string variables containing html with place holders for dynamic content
  3. Helper functions
  4. Main function
  5. And finally just a statement that prints out the result of the main function

Start coding our pyForum

Using the mentioned file structure, we can start looking at the code for our forum. First we write the code for our pyForumStart-macro, which will only print a header and a list of the threads with links. Like this:

Py Forum Demo – part 1

Template htmls (part 2 of our Python script)

The templates are simply string variables containing html code and place holders:

forumStartTemplate = """
 <div>
   <h1>{forumname}</h1>
 </div>
 <ul>
   {forumThreads}
 </ul>

 """

forumThreadTemplate = "<li><a href='{threadlink}'>{subject}</a></li>"

The triple quote is the Python way of handling multi-line strings. {variable}s is the place holders for content. In our main function we will use the built in python string method .format() to replace our place holders with content.

Main (part 4 and 5 of our Python script)

Here’s the main part of our pyForumStart macro script:

def ForumStartView():
   def ForumThreadsHtml():
       retval = ""
       for c in currentPage.Children:
           threadlink = library.NiceUrl(c.Id)
           subject = c.Name
           retval += forumThreadTemplate.format(subject=subject,threadlink = threadlink )
       return retval

   forumName = currentPage.Name

   return forumStartTemplate.format(forumname=forumName, forumThreads=ForumThreadsHtml())

print ForumStartView()

I hope it’s pretty self explanatory. There’s a function constructing the main html string for our forum start view. Enclosed in that function we have another function building the list of threads html. Also note the .format() – method mentioned earlier.

currentPage

Note the currentPage object, which Umbraco gracefully gives us to play with. It’s a umbraco.presentation.nodeFactory.Node of the current page. We can use that object just as if we were in C#, for example:

print currentPage.Name
print str(currentPage.Id)
print currentPage.GetProperty("propertyname").Value
for c in currentPage.Children:
    print c.Name
if currentPage.Parent:
    print currentPage.Parent.Name

Imports and helper functions (part 1 and 3 of our Python script)

We need to include a import to be able to use the umbraco.library – which we use to get nice url strings for our threads:

from umbraco import library

Umbraco being the namespace and library the class we import.

We don’t use any helper function just yet.

The complete code for this blog post

#
# Imports
#

from umbraco import library

#
# Html Templates
#

forumStartTemplate = """
 <div>
   <h1>{forumname}</h1>
 </div>
 <ul>
   {forumThreads}
 </ul>

 """

forumThreadTemplate = "<li><a href='{threadlink}'>{subject}</a></li>"</pre>

#
# Main function and call
#

def ForumStartView():

   def ForumThreadsHtml():
       retval = ""
       for c in currentPage.Children:
           threadlink = library.NiceUrl(c.Id)
           subject = c.Name
           retval += forumThreadTemplate.format(subject=subject,threadlink = threadlink )
       return retval

   forumName = currentPage.Name

   return forumStartTemplate.format(forumname=forumName, forumThreads=ForumThreadsHtml())

print ForumStartView()

Thats it for today

That’s it for the first post. In the next post we’ll add a form and code to handle the post backs. Part 2.

Introducing rndr – a package for macro testing and ajax calls

Rndr is a (Umbraco 4.5+) package that basically makes whatever macro callable from a url. The response will be the exact output from the macro.

The requesting url is constructed this way (in debug-mode):

http://www.mysite.com/rndr?macroalias=mymacro&pid=1100

And the result will be from mymacro (using a basic test macro), for example:

Hello world from page "My page"

So you send your macro alias and your page id with the query string, and you will get back the result of your macro. Adding the page id is necessary since macros can use the currentPage object. But you can define a default page id as we will see later.

Calling a macro that way makes it easy to visualize for example IronPython macros. You can test run your macro immediately after you saved it for the first time, no need of including it in a template or a page.

There’s no magic under the hood. The package only consists of a template that calls a macro which in turn renders the requested macro. The rendering macro takes care of the request string with the options that comes with it.

The package is avaliable from here http://our.umbraco.org/projects/website-utilities/rndr-url-macro-renderer (vote for it if you like it, thanks!)

Can we use this on a live site?

For a live site you would not like to open up for all macros to be callable. Also the url can be pretty long and unnecessary explicit. Therefore there’s a shortcut-option. For example:

http://www.mysite.com/rndr?sc=mm

or

http://www.mysite.com/rndr?sc=mm&pid=1100

Where sc is a shortcut for a specific macro – you define your own shortcuts to whatever macro you choose. That also makes it safer, since you only define shortcuts for the macros you like to be callable.

What’s the use of this again?

The practical uses are mainly macro testing and ajax calling. As for ajax calling, you can easily do a macro that will put out some information taken a parameter or two from the url (or from the macro parameters) and return a xml or whatever string for your javascript to parse:

function getDataFromMyMacro(id)
 {
    url = "http://www.mysite.com/rndr?sc=gg&amp;id=" + id;
    jQuery.ajax({
    url: url,
    type: 'POST',
    success: function (data) {
     alert(data); },
    error: function (msg, url, line) {
     alert(msg); }
    } );

 }

Many people use built in Base functionality for this, but I find it very convenient to have the ajax calls to get data from Xslt or IronPython code as well.

Macro parameters?

Yes you can also add macro parameters if you like:

http://www.mysite.com/rndr?macroalias=mymacro&amp;pageid=1100&x=magic&y=132187

or

http://www.mysite.com/rndr?sc=mm&x=magic&y=132187

They will be sent to your macro as they should.

How do I define what macros I like to use in this way?

If you run in debug mode you can call all macros with the standard request string (macroalias=sss&pageid=nnn). Otherwise you add shortcuts this way:

debugmode = False # set to True to allow all macros to be called

# define shortcuts:
shortcuts={}
shortcuts["mm"] = ["mymacro",1100] # including a default page id
shortcuts["dd"] = ["helloworld"]

The above setting would make those two requests valid:

http://www.mysite.com/rndr?sc=mm
http://www.mysite.com/rndr?sc=dd&pid=1100

Where do I set that options, and what’s that strange syntax?

The macro is a IronPython script, called RenderMacro, so after you’ve installed the package you find the code file in Scripting Files folder (Developer section in Umbraco UI).

Testing with included rndrtest macro

The package comes with a sample macro called rndrtest with an added shortcut “test”. It simply prints “hello from {page name}”. You can try it out with this call (where pid is a published page id):

http://www.mysite.com/rndr?sc=test&pid=1100
hello from mypage

Try it with a macro parameter + get a quick dive into python

To try the macro parameter option do this :

Open up the macro rndrtest from Macros, and add a parameter called myparam, type = text.

Open upp the script rndrtest.py from Scripting Files, change it so it looks like this:

print "hello from " + currentPage.Name
# check if parameter exists, and if so - print the value
if "myparam" in globals(): print myparam

Save, then test run it:

http://www.mysite.com/rndr?sc=test&pid=1100&myparam=testing-testing
hello from mypage
testing-testing

I like to use my own url, not rndr

Sure – you can call it whatever you like. Just change the name of the rndr-template.

I want to test my Ascx macros

Sure, enclose the macro call with <form id=”aspnet” runat=”server”> … </form> in the rndr-template.