diff options
Diffstat (limited to 'Bugs-Everywhere-Web/beweb')
45 files changed, 1171 insertions, 0 deletions
diff --git a/Bugs-Everywhere-Web/beweb/__init__.py b/Bugs-Everywhere-Web/beweb/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/__init__.py diff --git a/Bugs-Everywhere-Web/beweb/app.cfg b/Bugs-Everywhere-Web/beweb/app.cfg new file mode 100644 index 0000000..0035ffb --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/app.cfg @@ -0,0 +1,120 @@ +[global] +# The settings in this file should not vary depending on the deployment +# environment. devcfg.py and prodcfg.py are the locations for +# the different deployment settings. Settings in this file will +# be overridden by settings in those other files. + +# The commented out values below are the defaults + +# VIEW + +# which view (template engine) to use if one is not specified in the +# template name +# tg.defaultview = "kid" + +# kid.outputformat="html" +# kid.encoding="utf-8" + +# The sitetemplate is used for overall styling of a site that +# includes multiple TurboGears applications +# tg.sitetemplate="<packagename.templates.templatename>" + +# Allow every exposed function to be called as json, +# tg.allow_json = False + +# Set to True if you'd like all of your pages to include MochiKit +# tg.mochikit_all = False + +# VISIT TRACKING +# Each visit to your application will be assigned a unique visit ID tracked via +# a cookie sent to the visitor's browser. +# -------------- + +# Enable Visit tracking +# visit.on=False + +# Number of minutes a visit may be idle before it expires. +# visit.timeout=20 + +# The name of the cookie to transmit to the visitor's browser. +# visit.cookie.name="tg-visit" + +# Domain name to specify when setting the cookie (must begin with . according to +# RFC 2109). The default (None) should work for most cases and will default to +# the machine to which the request was made. NOTE: localhost is NEVER a valid +# value and will NOT WORK. +# visit.cookie.domain=None + +# Specific path for the cookie +# visit.cookie.path="/" + +# The name of the VisitManager plugin to use for visitor tracking. +# visit.manager="sqlobject" + + +# IDENTITY +# General configuration of the TurboGears Identity management module +# -------- + +# Switch to turn on or off the Identity management module +# identity.on=False + +# [REQUIRED] URL to which CherryPy will internally redirect when an access +# control check fails. If Identity management is turned on, a value for this +# option must be specified. +# identity.failure_url=None + +# The IdentityProvider to use -- defaults to the SqlObjectIdentityProvider which +# pulls User, Group, and Permission data out of your model database. +# identity.provider="sqlobject" + +# The names of the fields on the login form containing the visitor's user ID +# and password. In addition, the submit button is specified simply so its +# existence may be stripped out prior to passing the form data to the target +# controller. +# identity.form.user_name="user_name" +# identity.form.password="password" +# identity.form.submit="login" + +# What sources should the identity provider consider when determining the +# identity associated with a request? Comma separated list of identity sources. +# Valid sources: form, visit, http_auth +# identity.source="form,http_auth,visit" + + +# SqlObjectIdentityProvider +# Configuration options for the default IdentityProvider +# ------------------------- + +# The classes you wish to use for your Identity model. Leave these commented out +# to use the default classes for SqlObjectIdentityProvider. Or set them to the +# classes in your model. NOTE: These aren't TG_* because the TG prefix is +# reserved for classes created by TurboGears. +# identity.soprovider.model.user="beweb.model.User" +# identity.soprovider.model.group="beweb.model.Group" +# identity.soprovider.model.permission="beweb.model.Permission" + +# The password encryption algorithm used when comparing passwords against what's +# stored in the database. Valid values are 'md5' or 'sha1'. If you do not +# specify an encryption algorithm, passwords are expected to be clear text. +# +# The SqlObjectProvider *will* encrypt passwords supplied as part of your login +# form. If you set the password through the password property, like: +# my_user.password = 'secret' +# the password will be encrypted in the database, provided identity is up and +# running, or you have loaded the configuration specifying what encryption to +# use (in situations where identity may not yet be running, like tests). + +# identity.soprovider.encryption_algorithm=None + +[/static] +static_filter.on = True +static_filter.dir = "." + +[/favicon.ico] +static_filter.on = True +static_filter.file = "images/favicon.ico" + +[/] +decodingFilter.on = True +static_filter.root = '%(package_dir)s/static' diff --git a/Bugs-Everywhere-Web/beweb/config.py.example b/Bugs-Everywhere-Web/beweb/config.py.example new file mode 100644 index 0000000..8745c6d --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/config.py.example @@ -0,0 +1,10 @@ +# This is an example beweb configuration file. + +# One thing we need is a map of projects. Projects have a beweb ID, a path, +# and a display name. + +# In this example, the 'be' beweb ID is assigned the display name "Bugs +# Everywhere" and the path "/home/abentley/be" + +projects = {"be": ("Bugs Everywhere","/home/abentley/be"), + } diff --git a/Bugs-Everywhere-Web/beweb/controllers.py b/Bugs-Everywhere-Web/beweb/controllers.py new file mode 100644 index 0000000..9231030 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/controllers.py @@ -0,0 +1,229 @@ +import turbogears +from turbogears import controllers, expose, redirect, identity +import cherrypy +from libbe.bugdir import (tree_root, cmp_severity, new_bug, new_comment, + NoRootEntry) +from libbe import names +from config import projects +from prest import PrestHandler, provide_action + +def project_tree(project): + try: + return tree_root(projects[project][1]) + except KeyError: + raise Exception("Unknown project %s" % project) + +def comment_url(project, bug, comment, **kwargs): + return turbogears.url("/project/%s/bug/%s/comment/%s" % + (project, bug, comment), kwargs) + +class Comment(PrestHandler): + @provide_action("action", "New comment") + def new_comment(self, comment_data, comment, *args, **kwargs): + bug_tree = project_tree(comment_data['project']) + bug = bug_tree.get_bug(comment_data['bug']) + comment = new_comment(bug, "") + comment.content_type = "text/restructured" + comment.save() + raise cherrypy.HTTPRedirect(comment_url(comment=comment.uuid, + **comment_data)) + + @provide_action("action", "Reply") + def reply_comment(self, comment_data, comment, *args, **kwargs): + bug_tree = project_tree(comment_data['project']) + bug = bug_tree.get_bug(comment_data['bug']) + reply_comment = new_comment(bug, "") + reply_comment.in_reply_to = comment.uuid + reply_comment.save() + reply_data = dict(comment_data) + del reply_data["comment"] + raise cherrypy.HTTPRedirect(comment_url(comment=reply_comment.uuid, + **reply_data)) + + @provide_action("action", "Update") + def update(self, comment_data, comment, comment_body, *args, **kwargs): + comment.body = comment_body + comment.save() + raise cherrypy.HTTPRedirect(bug_url(comment_data['project'], + comment_data['bug'])) + + def instantiate(self, project, bug, comment): + bug_tree = project_tree(project) + bug = bug_tree.get_bug(bug) + return bug.get_comment(comment) + + def dispatch(self, comment_data, comment, *args, **kwargs): + return self.edit_comment(comment_data['project'], comment) + + @turbogears.expose(html="beweb.templates.edit_comment") + def edit_comment(self, project, comment): + return {"comment": comment, "project_id": project} + +class Bug(PrestHandler): + comment = Comment() + @turbogears.expose(html="beweb.templates.edit_bug") + def index(self, project, bug): + return {"bug": bug, "project_id": project} + + def dispatch(self, bug_data, bug, *args, **kwargs): + if bug is None: + return self.list(bug_data['project'], **kwargs) + else: + return self.index(bug_data['project'], bug) + + @turbogears.expose(html="beweb.templates.bugs") + def list(self, project, sort_by=None, show_closed=False, action=None, + search=None): + if action == "New bug": + self.new_bug() + if show_closed == "False": + show_closed = False + bug_tree = project_tree(project) + bugs = list(bug_tree.list()) + if sort_by is None: + def cmp_date(bug1, bug2): + return -cmp(bug1.time, bug2.time) + bugs.sort(cmp_date) + bugs.sort(cmp_severity) + return {"project_id" : project, + "project_name" : projects[project][0], + "bugs" : bugs, + "show_closed" : show_closed, + "search" : search, + } + + @provide_action("action", "New bug") + def new_bug(self, bug_data, bug, **kwargs): + bug = new_bug(project_tree(bug_data['project'])) + bug.save() + raise cherrypy.HTTPRedirect(bug_url(bug_data['project'], bug.uuid)) + + @provide_action("action", "Update") + def update(self, bug_data, bug, status, severity, summary, assigned, + action): + bug.status = status + bug.severity = severity + bug.summary = summary + if assigned == "": + assigned = None + bug.assigned = assigned + bug.save() + bug.rcs.precommit(bug.path) + bug.rcs.commit(bug.path, "Auto-commit") + bug.rcs.postcommit(bug.path) + raise cherrypy.HTTPRedirect(bug_list_url(bug_data["project"])) + + def instantiate(self, project, bug): + return project_tree(project).get_bug(bug) + + @provide_action("action", "New comment") + def new_comment(self, bug_data, bug, *args, **kwargs): + try: + self.update(bug_data, bug, *args, **kwargs) + except cherrypy.HTTPRedirect: + pass + return self.comment.new_comment(bug_data, comment=None, *args, + **kwargs) + + +def project_url(project_id=None): + project_url = "/project/" + if project_id is not None: + project_url += "%s/" % project_id + return turbogears.url(project_url) + +def bug_url(project_id, bug_uuid=None): + bug_url = "/project/%s/bug/" % project_id + if bug_uuid is not None: + bug_url += "%s/" % bug_uuid + return turbogears.url(bug_url) + +def bug_list_url(project_id, show_closed=False, search=None): + bug_url = "/project/%s/bug/?show_closed=%s" % (project_id, + str(show_closed)) + if search is not None: + bug_url = "%s&search=%s" % (bug_url, search) + return turbogears.url(str(bug_url)) + + +class Project(PrestHandler): + bug = Bug() + @turbogears.expose(html="beweb.templates.projects") + def dispatch(self, project_data, project, *args, **kwargs): + if project is not None: + raise cherrypy.HTTPRedirect(bug_url(project)) + else: + return {"projects": projects} + + def instantiate(self, project): + return project + + +class Root(controllers.Root): + prest = PrestHandler() + prest.project = Project() + @turbogears.expose() + def index(self): + raise cherrypy.HTTPRedirect(project_url()) + + @expose(template="beweb.templates.login") + def login(self, forward_url=None, previous_url=None, *args, **kw): + + if not identity.current.anonymous and identity.was_login_attempted(): + raise redirect(forward_url) + + forward_url=None + previous_url= cherrypy.request.path + + if identity.was_login_attempted(): + msg=_("The credentials you supplied were not correct or "\ + "did not grant access to this resource.") + elif identity.get_identity_errors(): + msg=_("You must provide your credentials before accessing "\ + "this resource.") + else: + msg=_("Please log in.") + forward_url= cherrypy.request.headers.get("Referer", "/") + cherrypy.response.status=403 + return dict(message=msg, previous_url=previous_url, logging_in=True, + original_parameters=cherrypy.request.params, + forward_url=forward_url) + + @expose() + def logout(self): + identity.current.logout() + raise redirect("/") + + @turbogears.expose('beweb.templates.about') + def about(self, *paths, **kwargs): + return {} + + @turbogears.expose() + def default(self, *args, **kwargs): + return self.prest.default(*args, **kwargs) + + def _cp_on_error(self): + import traceback, StringIO + bodyFile = StringIO.StringIO() + traceback.print_exc(file = bodyFile) + trace_text = bodyFile.getvalue() + try: + raise + except cherrypy.NotFound: + self.handle_error('Not Found', str(e), trace_text, '404 Not Found') + + except NoRootEntry, e: + self.handle_error('Project Misconfiguration', str(e), trace_text) + + except Exception, e: + self.handle_error('Internal server error', str(e), trace_text) + + def handle_error(self, heading, body, traceback=None, + status='500 Internal Server Error'): + cherrypy.response.headerMap['Status'] = status + cherrypy.response.body = [self.errorpage(heading, body, traceback)] + + + @turbogears.expose(html='beweb.templates.error') + def errorpage(self, heading, body, traceback): + return {'heading': heading, 'body': body, 'traceback': traceback} diff --git a/Bugs-Everywhere-Web/beweb/formatting.py b/Bugs-Everywhere-Web/beweb/formatting.py new file mode 100644 index 0000000..44ed849 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/formatting.py @@ -0,0 +1,73 @@ +from StringIO import StringIO + +from elementtree.ElementTree import XML +from libbe.restconvert import rest_xml + +def to_unix(text): + skip_newline = False + for ch in text: + if ch not in ('\r', '\n'): + yield ch + else: + if ch == '\n': + if skip_newline: + continue + else: + skip_newline = True + yield '\n' + + +def soft_text(text): + first_space = False + translations = {'\n': '<br />\n', '&': '&', '\x3c': '<', + '\x3e': '>'} + for ch in to_unix(text): + if ch == ' ' and first_space is True: + yield ' ' + first_space = ch in (' ') + try: + yield translations[ch] + except KeyError: + yield ch + + +def soft_pre(text): + return XML('<div style="font-family: monospace">'+ + ''.join(soft_text(text))+'</div>') + + +def get_rest_body(rest): + xml, warnings = rest_xml(StringIO(rest)) + return xml.find('{http://www.w3.org/1999/xhtml}body'), warnings + + +def comment_body_xhtml(comment): + if comment.content_type == "text/restructured": + return get_rest_body(comment.body)[0] + else: + return soft_pre(comment.body) + + +def select_among(name, options, default, display_names=None): + output = ['<select name="%s">' % name] + for option in options: + if option == default: + selected = ' selected="selected"' + else: + selected = "" + if display_names is None: + display_name = None + else: + display_name = display_names.get(option) + + if option is None: + option = "" + if display_name is None: + display_name = option + value = "" + else: + value = ' value="%s"' % option + output.append("<option%s%s>%s</option>" % (selected, value, + display_name)) + output.append("</select>") + return XML("".join(output)) diff --git a/Bugs-Everywhere-Web/beweb/model.py b/Bugs-Everywhere-Web/beweb/model.py new file mode 100644 index 0000000..e20d1ce --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/model.py @@ -0,0 +1,10 @@ +from sqlobject import * +from turbogears.database import PackageHub +# Uncomment the following line if you wish to use Identity and SO_Provider +# from turbogears.identity.soprovider import TG_User, TG_Group, TG_Permission + +hub = PackageHub("beweb") +__connection__ = hub + +# class YourDataClass(SQLObject): +# pass diff --git a/Bugs-Everywhere-Web/beweb/prest.py b/Bugs-Everywhere-Web/beweb/prest.py new file mode 100644 index 0000000..e6b7cdf --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/prest.py @@ -0,0 +1,159 @@ +from unittest import TestCase +import unittest +"""A pseudo-REST dispatching method in which only the noun comes from the path. +The action performed will depend on kwargs. +""" + +class AmbiguousAction(Exception): + def __init__(self, actions): + Exception.__init__(self, "Supplied action is ambiguous.") + self.actions = actions + + +def provide_action(name, value): + def provider(func): + func._action_desc = (name, value) + return func + return provider + +class PrestHandler(object): + def __init__(self): + object.__init__(self) + self.actions = {} + for member in (getattr(self, m) for m in dir(self)): + if not hasattr(member, '_action_desc'): + continue + name, value = member._action_desc + if name not in self.actions: + self.actions[name] = {} + self.actions[name][value] = member + + @classmethod + def add_action(klass, name, value, function): + if name not in klass.actions: + klass.actions[name] = {} + klass.actions[name][value] = function + + + def decode(self, path, data=None): + """Convert the path into a handler, a resource, data, and extra_path""" + if data is None: + data = {} + if len(path) < 2 or not (hasattr(self, path[1])): + if len(path) == 0: + resource = None + else: + resource = self.instantiate(**data) + return self, resource, data, path[1:] + if len(path) > 2: + data[path[1]] = path[2] + return getattr(self, path[1]).decode(path[2:], data) + + def default(self, *args, **kwargs): + child, resource, data, extra = self.decode([None,] + list(args)) + action = child.get_action(**kwargs) + new_args = ([data, resource]+extra) + if action is not None: + return action(*new_args, **kwargs) + else: + return child.dispatch(*new_args, **kwargs) + + def get_action(self, **kwargs): + """Return the action requested by kwargs, if any. + + Raises AmbiguousAction if more than one action matches. + """ + actions = [] + for key in kwargs: + if key in self.actions: + if kwargs[key] in self.actions[key]: + actions.append(self.actions[key][kwargs[key]]) + if len(actions) == 0: + return None + elif len(actions) == 1: + return actions[0] + else: + raise AmbiguousAction(actions) + + +class PrestTester(TestCase): + def test_decode(self): + class ProjectHandler(PrestHandler): + actions = {} + def dispatch(self, project_data, project, *args, **kwargs): + self.project_id = project_data['project'] + self.project_data = project_data + self.resource = project + self.args = args + self.kwargs = kwargs + + def instantiate(self, project): + return [project] + + @provide_action('action', 'Save') + def save(self, project_data, project, *args, **kwargs): + self.action = "save" + + @provide_action('behavior', 'Update') + def update(self, project_data, project, *args, **kwargs): + self.action = "update" + + foo = PrestHandler() + foo.project = ProjectHandler() + handler, resource, data, extra = foo.decode([None, 'project', '83', + 'bloop', 'yeah']) + assert handler is foo.project + self.assertEqual({'project': '83'}, data) + self.assertEqual(['bloop', 'yeah'], extra) + foo.default(*['project', '27', 'extra'], **{'a':'b', 'b':'97'}) + self.assertEqual(foo.project.args, ('extra',)) + self.assertEqual(foo.project.kwargs, {'a':'b', 'b':'97'}) + self.assertEqual(foo.project.project_data, {'project': '27'}) + self.assertEqual(foo.project.resource, ['27']) + foo.default(*['project', '27', 'extra'], **{'action':'Save', 'b':'97'}) + self.assertEqual(foo.project.action, 'save') + foo.default(*['project', '27', 'extra'], + **{'behavior':'Update', 'b':'97'}) + self.assertEqual(foo.project.action, 'update') + self.assertRaises(AmbiguousAction, foo.default, + *['project', '27', 'extra'], + **{'behavior':'Update', 'action':'Save', 'b':'97'}) + + class BugHandler(PrestHandler): + actions = {} + def dispatch(self, bug_data, bug, *args, **kwargs): + self.project_id = project_data['project'] + self.project_data = project_data + self.resource = project + self.args = args + self.kwargs = kwargs + + def instantiate(self, project, bug): + return [project, bug] + + @provide_action('action', 'Save') + def save(self, project_data, project, *args, **kwargs): + self.action = "save" + + @provide_action('behavior', 'Update') + def update(self, project_data, project, *args, **kwargs): + self.action = "update" + + foo.project.bug = BugHandler() + handler, resource, data, extra = foo.decode([None, 'project', '83', + 'bug', '92']) + assert handler is foo.project.bug + self.assertEqual(resource[0], '83') + self.assertEqual(resource[1], '92') + self.assertEqual([], extra) + self.assertEqual(data['project'], '83') + self.assertEqual(data['bug'], '92') + +def test(): + patchesTestSuite = unittest.makeSuite(PrestTester,'test') + runner = unittest.TextTestRunner(verbosity=0) + return runner.run(patchesTestSuite) + + +if __name__ == "__main__": + test() diff --git a/Bugs-Everywhere-Web/beweb/release.py b/Bugs-Everywhere-Web/beweb/release.py new file mode 100644 index 0000000..0232912 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/release.py @@ -0,0 +1,13 @@ +# Release information about Bugs-Everywhere-Web + +version = "1.0" + +# description = "Your plan to rule the world" +# author = "Your Name Here" +# email = "YourEmail@YourDomain" +# copyright = "Vintage 2006 - a good year indeed" + +# if it's open source, you might want to specify these +# url = "http://yourcool.site/" +# download_url = "http://yourcool.site/download" +# license = "MIT" diff --git a/Bugs-Everywhere-Web/beweb/static/css/style.css b/Bugs-Everywhere-Web/beweb/static/css/style.css new file mode 100644 index 0000000..986950f --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/css/style.css @@ -0,0 +1,104 @@ +table +{ + background-color: black; +} +td +{ + background-color: white; +} +h1 +{ + font-family: "Verdana"; + font-weight: bold; + font-size: 120%; + margin-bottom:0; + color: #990; +} + +tr.closed td +{ + background-color: #ccc; +} + +a:visited, a:link +{ + color: #990; + text-decoration: None; +} +td a:visited, td a:link +{ + display: block; +} +a:visited:hover, a:link:hover +{ + text-decoration: underline; +} +td a:visited:hover, td a:link:hover +{ + color:black; + background-color:#dda; + text-decoration: None; + display: block; +} + +body +{ + font-family: "Verdana"; + font-size:11pt; + background-color: white; +} +.comment +{ +} +.comment table +{ + background-color: transparent; +} +.comment td +{ + background-color: transparent; +} +.comment pre +{ + font-family: "Verdana"; +} +#header +{ + color: black; + font-weight: bold; + background-image: url(/static/images/half-spiral.png); + background-position: right center; + background-repeat: no-repeat; + background-color: #ff0; +} +#header ul.navoption +{ + display: block; + float: right; + margin: 0; + padding-right: 30px; +} +#header li +{ + display: inline; + margin:0; + padding:0; +} +table.insetbox +{ + margin-top: 0.5em; + margin-bottom: 0.5em; +} +.insetbox tr, .insetbox td +{ + margin: 0; + padding: 0; +} +pre.traceback +{ + font-family: Verdana, Ariel, Helvetica, sanserif; +} +tr.even td +{ + background-color: #eee; +} diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds-b.png b/Bugs-Everywhere-Web/beweb/static/images/ds-b.png Binary files differnew file mode 100644 index 0000000..790e438 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/ds-b.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds-bl.png b/Bugs-Everywhere-Web/beweb/static/images/ds-bl.png Binary files differnew file mode 100644 index 0000000..5b43259 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/ds-bl.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds-br.png b/Bugs-Everywhere-Web/beweb/static/images/ds-br.png Binary files differnew file mode 100644 index 0000000..6cfd62c --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/ds-br.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds-l.png b/Bugs-Everywhere-Web/beweb/static/images/ds-l.png Binary files differnew file mode 100644 index 0000000..a6ce3ce --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/ds-l.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds-r.png b/Bugs-Everywhere-Web/beweb/static/images/ds-r.png Binary files differnew file mode 100644 index 0000000..1ffd6f8 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/ds-r.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds-t.png b/Bugs-Everywhere-Web/beweb/static/images/ds-t.png Binary files differnew file mode 100644 index 0000000..0129b0c --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/ds-t.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds-tl.png b/Bugs-Everywhere-Web/beweb/static/images/ds-tl.png Binary files differnew file mode 100644 index 0000000..d616b77 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/ds-tl.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds-tr.png b/Bugs-Everywhere-Web/beweb/static/images/ds-tr.png Binary files differnew file mode 100644 index 0000000..18e542e --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/ds-tr.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds2-b.png b/Bugs-Everywhere-Web/beweb/static/images/ds2-b.png Binary files differnew file mode 100644 index 0000000..05a190e --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/ds2-b.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds2-r.png b/Bugs-Everywhere-Web/beweb/static/images/ds2-r.png Binary files differnew file mode 100644 index 0000000..0c3ea4c --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/ds2-r.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/favicon.ico b/Bugs-Everywhere-Web/beweb/static/images/favicon.ico Binary files differnew file mode 100644 index 0000000..339d09c --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/favicon.ico diff --git a/Bugs-Everywhere-Web/beweb/static/images/favicon.png b/Bugs-Everywhere-Web/beweb/static/images/favicon.png Binary files differnew file mode 100644 index 0000000..6dc53ee --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/favicon.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/half-spiral.png b/Bugs-Everywhere-Web/beweb/static/images/half-spiral.png Binary files differnew file mode 100644 index 0000000..cb4b56c --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/half-spiral.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/is-b.png b/Bugs-Everywhere-Web/beweb/static/images/is-b.png Binary files differnew file mode 100644 index 0000000..25d3cfa --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/is-b.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/is-bl.png b/Bugs-Everywhere-Web/beweb/static/images/is-bl.png Binary files differnew file mode 100644 index 0000000..f496223 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/is-bl.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/is-br.png b/Bugs-Everywhere-Web/beweb/static/images/is-br.png Binary files differnew file mode 100644 index 0000000..74cbd91 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/is-br.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/is-l.png b/Bugs-Everywhere-Web/beweb/static/images/is-l.png Binary files differnew file mode 100644 index 0000000..dd567fa --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/is-l.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/is-r.png b/Bugs-Everywhere-Web/beweb/static/images/is-r.png Binary files differnew file mode 100644 index 0000000..9ac4486 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/is-r.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/is-t.png b/Bugs-Everywhere-Web/beweb/static/images/is-t.png Binary files differnew file mode 100644 index 0000000..fbb06c8 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/is-t.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/is-tl.png b/Bugs-Everywhere-Web/beweb/static/images/is-tl.png Binary files differnew file mode 100644 index 0000000..9336290 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/is-tl.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/is-tr.png b/Bugs-Everywhere-Web/beweb/static/images/is-tr.png Binary files differnew file mode 100644 index 0000000..de74808 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/is-tr.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/shadows.png b/Bugs-Everywhere-Web/beweb/static/images/shadows.png Binary files differnew file mode 100644 index 0000000..9ddc676 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/shadows.png diff --git a/Bugs-Everywhere-Web/beweb/static/images/spiral.png b/Bugs-Everywhere-Web/beweb/static/images/spiral.png Binary files differnew file mode 100644 index 0000000..b4bcb1e --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/static/images/spiral.png diff --git a/Bugs-Everywhere-Web/beweb/templates/__init__.py b/Bugs-Everywhere-Web/beweb/templates/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/templates/__init__.py diff --git a/Bugs-Everywhere-Web/beweb/templates/about.kid b/Bugs-Everywhere-Web/beweb/templates/about.kid new file mode 100644 index 0000000..45d0093 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/templates/about.kid @@ -0,0 +1,23 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" + py:extends="'master.kid'"> +<head> + <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/> + <title>About Bugs Everywhere</title> +</head> + +<body> +<h1>About Bugs Everywhere</h1> +<p>Bug Everywhere was designed by Aaron Bentley and implemented by Aaron + Bentley and Oleg Romanyshyn. +</p> +<p> + Development is sponsored by + <a href="http://panoramicfeedback.com/">Panoramic Feedback</a>. +</p> +<p> + Bugs Everywhere <a href="http://panoramicfeedback.com/opensource">web site</a> +</p> +<a href="/">Project List</a> +</body> +</html> diff --git a/Bugs-Everywhere-Web/beweb/templates/bugs.kid b/Bugs-Everywhere-Web/beweb/templates/bugs.kid new file mode 100644 index 0000000..b83a593 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/templates/bugs.kid @@ -0,0 +1,49 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<?python +from libbe.cmdutil import unique_name +from beweb.controllers import bug_url, project_url, bug_list_url +from beweb.config import people +def row_class(bug, num): + if not bug.active is True: + return "closed" + elif num % 2 == 0: + return "even" + else: + return "odd" + + +def match(bug, show_closed, search): + if not show_closed and not bug.active: + return False + elif search is None: + return True + else: + return search.lower() in bug.summary.lower() +?> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" + py:extends="'master.kid'"> + +<head> + <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/> + <title>Bugs for $project_name</title> +</head> + +<body> +<h1>Bug list for ${project_name}</h1> +<table> +<tr><td>ID</td><td>Status</td><td>Severity</td><td>Assigned To</td><td>Comments</td><td>Summary</td></tr> +<div py:for="num, bug in enumerate([b for b in bugs if match(b, show_closed, search)])" py:strip="True"><tr class="${row_class(bug, num)}"><td><a href="${bug_url(project_id, bug.uuid)}">${unique_name(bug, bugs[:])}</a></td><td>${bug.status}</td><td>${bug.severity}</td><td>${people.get(bug.assigned, bug.assigned)}</td><td>${len(list(bug.iter_comment_ids()))}</td><td>${bug.summary}</td></tr> +</div> +</table> +<a href="${project_url()}">Project list</a> +<a href="${bug_list_url(project_id, not show_closed, search)}">Toggle closed</a> +<form action="${bug_list_url(project_id)}" method="post"> +<input type="submit" name="action" value="New bug"/> +</form> +<form action="${bug_list_url(project_id)}" method="get"> +<input type="hidden" name="show_closed" value="False" /> +<input name="search" /> +<input type="submit" name="action" value="Search" /> +</form> +</body> +</html> diff --git a/Bugs-Everywhere-Web/beweb/templates/edit_bug.kid b/Bugs-Everywhere-Web/beweb/templates/edit_bug.kid new file mode 100644 index 0000000..c31d660 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/templates/edit_bug.kid @@ -0,0 +1,51 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<?python +from libbe.bugdir import severity_levels, active_status, inactive_status, thread_comments +from libbe.utility import time_to_str +from beweb.controllers import bug_list_url, comment_url +from beweb.config import people +from beweb.formatting import comment_body_xhtml, select_among +?> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" + py:extends="'master.kid'"> + +<head> + <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/> + <title>Edit bug</title> +</head> + +<body> +<h1>Edit bug</h1> +<form method="post" action="."> +<table> +<tr><td>Status</td><td>Severity</td><td>Assigned To</td><td>Summary</td></tr> +<tr><td>${select_among("status", active_status+inactive_status, bug.status)}</td><td>${select_among("severity", severity_levels, bug.severity)}</td> +<td>${select_among("assigned", people.keys()+[None], bug.assigned, people)}</td><td><input name="summary" value="${bug.summary}" size="80" /></td></tr> +</table> +<div py:def="show_comment(comment, children)" class="comment"> + <insetbox> + <table> + <tr><td>From</td><td>${comment.From}</td></tr> + <tr><td>Date</td><td>${time_to_str(comment.date)}</td></tr> + </table> + <div py:content="comment_body_xhtml(comment)" py:strip="True"></div> + <a href="${comment_url(project_id, bug.uuid, comment.uuid)}">Edit</a> + <a href="${comment_url(project_id, bug.uuid, comment.uuid, + action='Reply')}">Reply</a> + </insetbox> + <div style="margin-left:20px;"> + <div py:for="child, grandchildren in children" py:strip="True"> + ${show_comment(child, grandchildren)} + </div> + </div> +</div> +<div py:for="comment, children in thread_comments(bug.list_comments())" + py:strip="True"> + ${show_comment(comment, children)} +</div> +<p><input type="submit" name="action" value="Update"/></p> +<p><input type="submit" name="action" value="New comment"/></p> +</form> +<a href="${bug_list_url(project_id)}">Bug List</a> +</body> +</html> diff --git a/Bugs-Everywhere-Web/beweb/templates/edit_comment.kid b/Bugs-Everywhere-Web/beweb/templates/edit_comment.kid new file mode 100644 index 0000000..551db9d --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/templates/edit_comment.kid @@ -0,0 +1,27 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<?python +from libbe.bugdir import severity_levels +from libbe.utility import time_to_str +from beweb.controllers import bug_list_url, bug_url +?> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" + py:extends="'master.kid'"> + +<head> + <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/> + <title>Edit comment</title> +</head> + +<body> +<h1>Edit comment</h1> +<form method="post"> +<table> + <tr><td>From</td><td>${comment.From}</td></tr> + <tr><td>Date</td><td>${time_to_str(comment.date)}</td></tr> +</table> +<insetbox><textarea rows="15" cols="80" py:content="comment.body" name="comment_body" style="border-style: none"/></insetbox> +<p><input type="submit" name="action" value="Update"/></p> +</form> +<a href="${bug_url(project_id, comment.bug.uuid)}">Up to Bug</a> +</body> +</html> diff --git a/Bugs-Everywhere-Web/beweb/templates/error.kid b/Bugs-Everywhere-Web/beweb/templates/error.kid new file mode 100644 index 0000000..bc55615 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/templates/error.kid @@ -0,0 +1,14 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" + py:extends="'master.kid'"> +<head> + <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/> + <title>BE Error: ${heading}</title> +</head> + +<body> +<h1 py:content="heading">Error heading</h1> +<div py:replace="body" >Error Body</div> +<pre py:content="traceback" class="traceback">Traceback</pre> +</body> +</html> diff --git a/Bugs-Everywhere-Web/beweb/templates/login.kid b/Bugs-Everywhere-Web/beweb/templates/login.kid new file mode 100644 index 0000000..2c150db --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/templates/login.kid @@ -0,0 +1,113 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:py="http://purl.org/kid/ns#" + py:extends="'master.kid'"> + +<head> + <meta content="text/html; charset=UTF-8" + http-equiv="content-type" py:replace="''"/> + <title>Login</title> + <style> + #loginBox + { + width: 30%; + margin: auto; + margin-top: 10%; + padding-left: 10%; + padding-right: 10%; + padding-top: 5%; + padding-bottom: 5%; + font-family: verdana; + font-size: 10px; + background-color: #eee; + border: 2px solid #ccc; + } + + #loginBox h1 + { + font-size: 42px; + font-family: "Trebuchet MS"; + margin: 0; + color: #ddd; + } + + #loginBox p + { + position: relative; + top: -1.5em; + padding-left: 4em; + font-size: 12px; + margin: 0; + color: #666; + } + + #loginBox table + { + table-layout: fixed; + border-spacing: 0; + width: 100%; + } + + #loginBox td.label + { + width: 33%; + text-align: right; + } + + #loginBox td.field + { + width: 66%; + } + + #loginBox td.field input + { + width: 100%; + } + + #loginBox td.buttons + { + text-align: right; + } + + </style> +</head> + +<body> + <div id="loginBox"> + <h1>Login</h1> + <p>${message}</p> + <form action="${previous_url}" method="POST"> + <table> + <tr> + <td class="label"> + <label for="user_name">User Name:</label> + </td> + <td class="field"> + <input type="text" id="user_name" name="user_name"/> + </td> + </tr> + <tr> + <td class="label"> + <label for="password">Password:</label> + </td> + <td class="field"> + <input type="password" id="password" name="password"/> + </td> + </tr> + <tr> + <td colspan="2" class="buttons"> + <input type="submit" value="Login"/> + </td> + </tr> + </table> + + <input py:if="forward_url" type="hidden" name="forward_url" + value="${forward_url}"/> + + <input py:for="name,value in original_parameters.items()" + type="hidden" name="${name}" value="${value}"/> + </form> + </div> +</body> +</html> diff --git a/Bugs-Everywhere-Web/beweb/templates/master.kid b/Bugs-Everywhere-Web/beweb/templates/master.kid new file mode 100644 index 0000000..54f6bad --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/templates/master.kid @@ -0,0 +1,71 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<?python import sitetemplate ?> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" py:extends="sitetemplate"> + +<head py:match="item.tag=='{http://www.w3.org/1999/xhtml}head'" py:attrs="item.items()"> + <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/> + <title py:if="False">Your title goes here</title> + <link rel="stylesheet" type="text/css" href="/static/css/style.css"/> + <meta py:replace="item[:]"/> + <style> + #pageLogin + { + font-size: 10px; + font-family: verdana; + text-align: right; + } + </style> +</head> + +<body py:match="item.tag=='{http://www.w3.org/1999/xhtml}body'" py:attrs="item.items()"> +<div id="header"><div style="float: left">b u g s e v r y w h e r e</div><ul class="navoption"><li><a href="/about/">About</a></li></ul> </div> + <div py:if="tg.config('identity.on',False) and not 'logging_in' in locals()" + id="pageLogin"> + <span py:if="tg.identity.anonymous"> + <a href="/login">Login</a> + </span> + <span py:if="not tg.identity.anonymous"> + Welcome ${tg.identity.user.displayName}. + <a href="/logout">Logout</a> + </span> + </div> + + <div py:if="tg_flash" class="flash" py:content="tg_flash"></div> + + <div py:replace="item[:]"/> + +</body> +<table py:match="item.tag=='{http://www.w3.org/1999/xhtml}insetbox'" cellspacing="0" cellpadding="0" border="0" class="insetbox"> +<tr height="19"><td background="/static/images/is-tl.png" width="19"/> + <td background="/static/images/is-t.png" /> + <td background="/static/images/is-tr.png" width="11"></td> +</tr> +<tr> + <td background="/static/images/is-l.png"/> + <td py:content="item[:]"> Hello, this is some random text</td> + <td background="/static/images/is-r.png"/> +</tr> +<tr height="11"> + <td background="/static/images/is-bl.png"/> + <td background="/static/images/is-b.png" /> + <td background="/static/images/is-br.png"/> +</tr> +</table> +<table py:match="item.tag=='{http://www.w3.org/1999/xhtml}dsbox'" cellspacing="0" cellpadding="0" border="0" class="dsbox"> +<tr height="11"><td background="/static/images/ds-tl.png" width="11"/> + <td background="/static/images/ds-t.png" /> + <td background="/static/images/ds-tr.png" width="19"></td> +</tr> +<tr> + <td background="/static/images/ds-l.png"/> + <td py:content="item[:]"> Hello, this is some random text</td> + <td background="/static/images/ds2-r.png"/> +</tr> +<tr height="19"> + <td background="/static/images/ds-bl.png"/> + <td background="/static/images/ds2-b.png" /> + <td background="/static/images/ds-br.png"/> +</tr> +</table> + +</html> diff --git a/Bugs-Everywhere-Web/beweb/templates/projects.kid b/Bugs-Everywhere-Web/beweb/templates/projects.kid new file mode 100644 index 0000000..09bde77 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/templates/projects.kid @@ -0,0 +1,32 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<?python +from libbe.bugdir import severity_levels +def select_among(name, options, default): + output = ['<select name="%s">' % name] + for option in options: + if option == default: + selected = ' selected="selected"' + else: + selected = "" + output.append("<option%s>%s</option>" % (selected, option)) + output.append("</select>") + return XML("".join(output)) +?> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" + py:extends="'master.kid'"> +<?python +project_triples = [(pn, pid, pl) for pid,(pn, pl) in projects.iteritems()] +project_triples.sort() +?> +<head> + <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/> + <title>Project List</title> +</head> + +<body> +<h1>Project List</h1> +<table> +<tr py:for="project_name, project_id, project_loc in project_triples"><td><a href="/project/${project_id}/">${project_name}</a></td></tr> +</table> +</body> +</html> diff --git a/Bugs-Everywhere-Web/beweb/templates/welcome.kid b/Bugs-Everywhere-Web/beweb/templates/welcome.kid new file mode 100644 index 0000000..0d3cf3e --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/templates/welcome.kid @@ -0,0 +1,33 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" + py:extends="'master.kid'"> + +<head> + <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/> + <title>Welcome to TurboGears</title> +</head> + +<body> + <p>Congratulations, your TurboGears application is running as of <span py:replace="now">now</span>.</p> + + <h2>Are you ready to Gear Up?</h2> + + <p>Take the following steps to dive right in:</p> + + <ol> + <li>Edit your project's model.py to create SQLObjects representing the data you're working with</li> + <li>Edit your dev.cfg file to point to the database you'll be using</li> + <li>Run "<code>tg-admin sql create</code>" to create the tables in the database</li> + <li>Edit controllers.py to add the functionality to your webapp</li> + <li>Change the master.kid template to have the headers and footers for your application.</li> + <li>Change welcome.kid (this template) or create a new one to display your data</li> + <li>Repeat steps 4-6 until done.</li> + <li><b>Profit!</b></li> + </ol> + + <p>If you haven't already, you might check out some of the <a href="http://www.turbogears.org/docs/" >documentation</a>.</p> + + <p>Thanks for using TurboGears! See you on the <a href="http://groups.google.com/group/turbogears" >mailing list</a> and the "turbogears" channel on irc.freenode.org!</p> + +</body> +</html> diff --git a/Bugs-Everywhere-Web/beweb/tests/__init__.py b/Bugs-Everywhere-Web/beweb/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/tests/__init__.py diff --git a/Bugs-Everywhere-Web/beweb/tests/test_controllers.py b/Bugs-Everywhere-Web/beweb/tests/test_controllers.py new file mode 100644 index 0000000..0c77afe --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/tests/test_controllers.py @@ -0,0 +1,16 @@ +from turbogears import testutil +from beweb.controllers import Root +import cherrypy + +cherrypy.root = Root() + +def test_method(): + "the index method should return a string called now" + import types + result = testutil.call(cherrypy.root.index) + assert type(result["now"]) == types.StringType + +def test_indextitle(): + "The mainpage should have the right title" + testutil.createRequest("/") + assert "<TITLE>Welcome to TurboGears</TITLE>" in cherrypy.response.body[0] diff --git a/Bugs-Everywhere-Web/beweb/tests/test_model.py b/Bugs-Everywhere-Web/beweb/tests/test_model.py new file mode 100644 index 0000000..5346f8b --- /dev/null +++ b/Bugs-Everywhere-Web/beweb/tests/test_model.py @@ -0,0 +1,24 @@ +# If your project uses a database, you can set up database tests +# similar to what you see below. Be sure to set the db_uri to +# an appropriate uri for your testing database. sqlite is a good +# choice for testing, because you can use an in-memory database +# which is very fast. + +from turbogears import testutil +#from beweb.model import YourDataClass +#from turbogears.identity.soprovider import TG_User + +# database.set_db_uri("sqlite:///:memory:") + +# class testTG_User(testutil.DBTest): +# def get_model(self): +# return TG_User +# +# def test_creation(self): +# "Object creation should set the name" +# obj = TG_User(userId = "creosote", +# emailAddress = "spam@python.not", +# displayName = "Mr Creosote", +# password = "Wafer-thin Mint") +# assert obj.displayName == "Mr Creosote" + |