keithsalisbury-subtrac 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +7 -0
  3. data/Rakefile +56 -0
  4. data/VERSION.yml +4 -0
  5. data/bin/subtrac +40 -0
  6. data/lib/subtrac.rb +245 -0
  7. data/lib/subtrac/common/clients/index.wsgi +89 -0
  8. data/lib/subtrac/common/favicon.ico +0 -0
  9. data/lib/subtrac/common/images/trac/banner_bg.jpg +0 -0
  10. data/lib/subtrac/common/images/trac/bar_bg.gif +0 -0
  11. data/lib/subtrac/common/images/trac/footer_back.png +0 -0
  12. data/lib/subtrac/common/images/trac/main_bg.gif +0 -0
  13. data/lib/subtrac/common/images/trac/saint_logo_small.png +0 -0
  14. data/lib/subtrac/common/static/404.html +14 -0
  15. data/lib/subtrac/common/styles/trac.css +222 -0
  16. data/lib/subtrac/common/trac.ini +178 -0
  17. data/lib/subtrac/config/config.yml +54 -0
  18. data/lib/subtrac/passwords +1 -0
  19. data/lib/subtrac/shared/trac.ini +178 -0
  20. data/lib/subtrac/templates/location.erb +16 -0
  21. data/lib/subtrac/templates/projects/blank/svn/branches/README +0 -0
  22. data/lib/subtrac/templates/projects/blank/svn/tags/README +0 -0
  23. data/lib/subtrac/templates/projects/blank/svn/trunk/README +0 -0
  24. data/lib/subtrac/templates/projects/blank/trac/wiki/WikiStart +57 -0
  25. data/lib/subtrac/templates/projects/new/svn/trunk/trac/wiki/WikiStart +46 -0
  26. data/lib/subtrac/templates/projects/new/trac/wiki/WikiStart +23 -0
  27. data/lib/subtrac/templates/projects/trac_theme/svn/trunk/index/index.html +22 -0
  28. data/lib/subtrac/templates/projects/trac_theme/svn/trunk/templates/layout.html +56 -0
  29. data/lib/subtrac/templates/projects/trac_theme/svn/trunk/templates/site.html +27 -0
  30. data/lib/subtrac/templates/projects/trac_theme/svn/trunk/templates/theme.html +86 -0
  31. data/lib/subtrac/templates/projects/trac_theme/trac/wiki/WikiStart +4 -0
  32. data/lib/subtrac/templates/trac.erb +25 -0
  33. data/lib/subtrac/templates/vhost.erb +35 -0
  34. data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/advancedworkflow/__init__.py +0 -0
  35. data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/advancedworkflow/controller.py +419 -0
  36. data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/setup.cfg +3 -0
  37. data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/setup.py +20 -0
  38. data/lib/subtrac/trac-plugins/clientsplugin/clients/__init__.py +0 -0
  39. data/lib/subtrac/trac-plugins/clientsplugin/clients/action.py +28 -0
  40. data/lib/subtrac/trac-plugins/clientsplugin/clients/action_email.py +168 -0
  41. data/lib/subtrac/trac-plugins/clientsplugin/clients/action_zendesk_forum.py +137 -0
  42. data/lib/subtrac/trac-plugins/clientsplugin/clients/admin.py +91 -0
  43. data/lib/subtrac/trac-plugins/clientsplugin/clients/api.py +199 -0
  44. data/lib/subtrac/trac-plugins/clientsplugin/clients/client.py +105 -0
  45. data/lib/subtrac/trac-plugins/clientsplugin/clients/events.py +287 -0
  46. data/lib/subtrac/trac-plugins/clientsplugin/clients/eventsadmin.py +71 -0
  47. data/lib/subtrac/trac-plugins/clientsplugin/clients/htdocs/clients.css +4 -0
  48. data/lib/subtrac/trac-plugins/clientsplugin/clients/model.py +135 -0
  49. data/lib/subtrac/trac-plugins/clientsplugin/clients/processor.py +70 -0
  50. data/lib/subtrac/trac-plugins/clientsplugin/clients/reportmanager.py +142 -0
  51. data/lib/subtrac/trac-plugins/clientsplugin/clients/reports.py +231 -0
  52. data/lib/subtrac/trac-plugins/clientsplugin/clients/summary.py +27 -0
  53. data/lib/subtrac/trac-plugins/clientsplugin/clients/summary_milestone.py +152 -0
  54. data/lib/subtrac/trac-plugins/clientsplugin/clients/summary_ticketchanges.py +160 -0
  55. data/lib/subtrac/trac-plugins/clientsplugin/clients/templates/admin_client_events.html +124 -0
  56. data/lib/subtrac/trac-plugins/clientsplugin/clients/templates/admin_clients.html +134 -0
  57. data/lib/subtrac/trac-plugins/clientsplugin/cron/changes.xslt +132 -0
  58. data/lib/subtrac/trac-plugins/clientsplugin/cron/run-client-event +97 -0
  59. data/lib/subtrac/trac-plugins/clientsplugin/cron/summary.xslt +161 -0
  60. data/lib/subtrac/trac-plugins/clientsplugin/setup.py +43 -0
  61. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/__init__.py +4 -0
  62. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/burndownchart.py +273 -0
  63. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/hoursinplaceeditor.py +44 -0
  64. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/hoursremaining.py +36 -0
  65. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery-1.2.3.min.js +32 -0
  66. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery.jeditable.js +409 -0
  67. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery.jeditable.mini.js +30 -0
  68. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/templates/edithours.html +53 -0
  69. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/burndownchart.py +181 -0
  70. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/hoursremaining.py +66 -0
  71. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/workloadchart.py +47 -0
  72. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/utils.py +93 -0
  73. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/workloadchart.py +86 -0
  74. data/lib/subtrac/trac-plugins/estimationtoolsplugin/setup.py +20 -0
  75. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/SumRollups.js +23 -0
  76. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/adw_tracdb.py +128 -0
  77. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/git-post-receive +40 -0
  78. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/trac-post-commit.py +285 -0
  79. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/trac_billing.py +173 -0
  80. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/utils/__init__.py +0 -0
  81. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/utils/mail.py +164 -0
  82. data/lib/subtrac/trac-plugins/timingandestimationplugin/setup.py +69 -0
  83. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/__init__.py +1 -0
  84. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/api.py +292 -0
  85. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/blackmagic.py +172 -0
  86. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/dbhelper.py +178 -0
  87. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/billingplugin.css +25 -0
  88. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/field_disabler.js +6 -0
  89. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/formatDate.js +356 -0
  90. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/tip_centerwindow.js +100 -0
  91. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/tip_followscroll.js +84 -0
  92. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/wz_tooltip.js +1149 -0
  93. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/linkifyer.js +119 -0
  94. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/query.js +73 -0
  95. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/ticket.js +165 -0
  96. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/query_webui.py +28 -0
  97. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reportmanager.py +221 -0
  98. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reports.py +675 -0
  99. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reports_filter.py +150 -0
  100. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/statuses.py +25 -0
  101. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/tande_filters.py +131 -0
  102. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/templates/billing.cs +84 -0
  103. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/templates/billing.html +104 -0
  104. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_daemon.py +194 -0
  105. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_policy.py +62 -0
  106. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_webui.py +28 -0
  107. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/usermanual.py +127 -0
  108. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/webui.py +129 -0
  109. data/lib/subtrac/trac-plugins/worklogplugin/setup.py +29 -0
  110. data/lib/subtrac/trac-plugins/worklogplugin/worklog/__init__.py +1 -0
  111. data/lib/subtrac/trac-plugins/worklogplugin/worklog/api.py +187 -0
  112. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jqModal.css +40 -0
  113. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jqModal.js +67 -0
  114. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jquery.mousewheel.pack.js +12 -0
  115. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jquery.timeentry.pack.js +7 -0
  116. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/tracWorklog.js +40 -0
  117. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/ui.datepicker.css +208 -0
  118. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/ui.datepicker.js +1439 -0
  119. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/work.png +0 -0
  120. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/work.xcf +0 -0
  121. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/worklogplugin.css +80 -0
  122. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/workstart.png +0 -0
  123. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/workstop.png +0 -0
  124. data/lib/subtrac/trac-plugins/worklogplugin/worklog/manager.py +336 -0
  125. data/lib/subtrac/trac-plugins/worklogplugin/worklog/reports.py +598 -0
  126. data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog.html +45 -0
  127. data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_stop.html +70 -0
  128. data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_user.html +40 -0
  129. data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_webadminui.html +59 -0
  130. data/lib/subtrac/trac-plugins/worklogplugin/worklog/ticket_daemon.py +33 -0
  131. data/lib/subtrac/trac-plugins/worklogplugin/worklog/ticket_filter.py +153 -0
  132. data/lib/subtrac/trac-plugins/worklogplugin/worklog/timeline_hook.py +96 -0
  133. data/lib/subtrac/trac-plugins/worklogplugin/worklog/usermanual.py +29 -0
  134. data/lib/subtrac/trac-plugins/worklogplugin/worklog/util.py +31 -0
  135. data/lib/subtrac/trac-plugins/worklogplugin/worklog/webadminui.py +47 -0
  136. data/lib/subtrac/trac-plugins/worklogplugin/worklog/webui.py +174 -0
  137. data/lib/subtrac/trac-plugins/worklogplugin/worklog/xmlrpc.py +73 -0
  138. data/lib/subtrac/version.rb +4 -0
  139. metadata +191 -0
@@ -0,0 +1,3 @@
1
+ [egg_info]
2
+ tag_build = dev
3
+ tag_svn_revision = true
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env python
2
+
3
+ from setuptools import setup, find_packages
4
+
5
+ setup(
6
+ name='AdvancedTicketWorkflowPlugin',
7
+ version='0.10',
8
+ author = 'Eli Carter',
9
+ author_email = 'elicarter@retracile.net',
10
+ license='BSD',
11
+ description = 'Advanced workflow operations Trac plugin',
12
+ long_description = 'Provides more advanced workflow operations',
13
+ url = 'http://trac-hacks.org/wiki/AdvancedTicketWorkflowPlugin',
14
+
15
+ packages = find_packages(),
16
+ package_data = {},
17
+ entry_points = {'trac.plugins':['advancedworkflow.controller = advancedworkflow.controller']},
18
+ install_requires = [],
19
+ #zip_safe = False,
20
+ )
@@ -0,0 +1,28 @@
1
+ from lxml import etree
2
+ from trac.core import *
3
+
4
+ class IClientActionProvider(Interface):
5
+ """Extension point interface for components that define their own way
6
+ to act on a given client summary (IClientSummaryProvider.get_summary
7
+ """
8
+
9
+ def get_name():
10
+ """Return the name of the action (for use in UI)
11
+ """
12
+
13
+ def get_description():
14
+ """Return the description of the action (for use in UI)
15
+ """
16
+
17
+ def options(client=None):
18
+ """The options for this action
19
+ If the client option is None then these options are defined as being per instance.
20
+ """
21
+
22
+ def init(event, client):
23
+ """Todo
24
+ """
25
+
26
+ def perform(req, summary):
27
+ """Perform the action. This must return an etree object
28
+ """
@@ -0,0 +1,168 @@
1
+ import re
2
+ import os
3
+ import sys
4
+ import locale
5
+ import time
6
+ import codecs
7
+ from datetime import datetime
8
+ from StringIO import StringIO
9
+
10
+ from trac import __version__
11
+ from trac.core import *
12
+ from trac.env import open_environment
13
+ from trac.util.datefmt import format_date, to_datetime
14
+ from trac.wiki import wiki_to_html
15
+ from genshi import escape
16
+
17
+ from lxml import etree
18
+ from clients.action import IClientActionProvider
19
+
20
+
21
+ class ClientActionEmail(Component):
22
+ implements(IClientActionProvider)
23
+
24
+ client = None
25
+ debug = False
26
+
27
+ def get_name(self):
28
+ return "Send Email"
29
+
30
+ def get_description(self):
31
+ return "Send an email to a certain list of addresses"
32
+
33
+ def options(self, client=None):
34
+ if client is None:
35
+ yield {'name': 'XSLT', 'description': 'Formatting XSLT to convert the summary to an email', 'type': 'large'}
36
+ yield {'name': 'Subject', 'description': 'Email subject (use %s to replace the active client name)', 'type': 'medium'}
37
+ else:
38
+ yield {'name': 'Email Addresses', 'description': 'Comma separated list of email addresses', 'type': 'medium'}
39
+
40
+
41
+ def init(self, event, client):
42
+ self.client = client
43
+ if not event.action_options.has_key('XSLT') or not event.action_options['XSLT']['value']:
44
+ return False
45
+ try:
46
+ self.transform = etree.XSLT(etree.fromstring(str(event.action_options['XSLT']['value'])))
47
+ except:
48
+ self.env.log.error("Error: Cannot load/parse stylesheet")
49
+ return False
50
+
51
+ if not event.action_client_options.has_key('Email Addresses') or not event.action_client_options['Email Addresses']['value']:
52
+ return False
53
+
54
+ self.emails = []
55
+ for email in event.action_client_options['Email Addresses']['value'].replace(',', ' ').split(' '):
56
+ if '' != email.strip():
57
+ self.emails.append(email.strip())
58
+
59
+ if not self.emails:
60
+ return False
61
+
62
+ if not event.action_options.has_key('Subject') or not event.action_options['Subject']['value']:
63
+ self.subject = 'Ticket Summary for %s'
64
+ else:
65
+ self.subject = event.action_options['Subject']['value']
66
+
67
+ if self.subject.find('%s') >= 0:
68
+ self.subject = self.subject % (client,)
69
+
70
+ return True
71
+
72
+
73
+ def perform(self, req, summary):
74
+ if summary is None:
75
+ return False
76
+ self.config = self.env.config
77
+ self.encoding = 'utf-8'
78
+ subject = self.subject
79
+
80
+ if not self.config.getbool('notification', 'smtp_enabled'):
81
+ return False
82
+ smtp_server = self.config['notification'].get('smtp_server')
83
+ smtp_port = self.config['notification'].getint('smtp_port')
84
+ from_email = self.config['notification'].get('smtp_from')
85
+ from_name = self.config['notification'].get('smtp_from_name')
86
+ replyto_email = self.config['notification'].get('smtp_replyto')
87
+ from_email = from_email or replyto_email
88
+ if not from_email:
89
+ return False
90
+
91
+ # Authentication info (optional)
92
+ user_name = self.config['notification'].get('smtp_user')
93
+ password = self.config['notification'].get('smtp_password')
94
+
95
+ # Thanks to the author of this recipe:
96
+ # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/473810
97
+
98
+ from email.MIMEMultipart import MIMEMultipart
99
+ from email.MIMEText import MIMEText
100
+ from email.MIMEImage import MIMEImage
101
+ from email.Charset import add_charset, SHORTEST
102
+ add_charset( 'utf-8', SHORTEST, None, None )
103
+
104
+ projname = self.config.get('project', 'name')
105
+
106
+ # Create the root message and fill in the from, to, and subject headers
107
+ msg_root = MIMEMultipart('alternative')
108
+ msg_root['To'] = str(', ').join(self.emails)
109
+
110
+ msg_root['X-Mailer'] = 'ClientsPlugin for Trac'
111
+ msg_root['X-Trac-Version'] = __version__
112
+ msg_root['X-Trac-Project'] = projname
113
+ msg_root['Precedence'] = 'bulk'
114
+ msg_root['Auto-Submitted'] = 'auto-generated'
115
+ msg_root['Subject'] = subject
116
+ msg_root['From'] = '%s <%s>' % (from_name or projname, from_email)
117
+ msg_root['Reply-To'] = replyto_email
118
+ msg_root.preamble = 'This is a multi-part message in MIME format.'
119
+
120
+ view = 'plain'
121
+ arg = "'%s'" % view
122
+ result = self.transform(summary, view=arg)
123
+ msg_text = MIMEText(str(result), view, self.encoding)
124
+ msg_root.attach(msg_text)
125
+
126
+ msg_related = MIMEMultipart('related')
127
+ msg_root.attach(msg_related)
128
+
129
+ view = 'html'
130
+ arg = "'%s'" % view
131
+ result = self.transform(summary, view=arg)
132
+ #file = open('/tmp/send-client-email.html', 'w')
133
+ #file.write(str(result))
134
+ #file.close()
135
+
136
+ msg_text = MIMEText(str(result), view, self.encoding)
137
+ msg_related.attach(msg_text)
138
+
139
+ # Handle image embedding...
140
+ view = 'images'
141
+ arg = "'%s'" % view
142
+ result = self.transform(summary, view=arg)
143
+ if result:
144
+ images = result.getroot()
145
+ if images:
146
+ for img in images:
147
+ if 'img' != img.tag:
148
+ continue
149
+ if not img.get('id') or not img.get('src'):
150
+ continue
151
+
152
+ fp = open(img.get('src'), 'rb')
153
+ if not fp:
154
+ continue
155
+ msg_img = MIMEImage(fp.read())
156
+ fp.close()
157
+ msg_img.add_header('Content-ID', '<%s>' % img.get('id'))
158
+ msg_related.attach(msg_img)
159
+
160
+ # Send the email
161
+ import smtplib
162
+ smtp = smtplib.SMTP() #smtp_server, smtp_port)
163
+ if False and user_name:
164
+ smtp.login(user_name, password)
165
+ smtp.connect()
166
+ smtp.sendmail(from_email, self.emails, msg_root.as_string())
167
+ smtp.quit()
168
+ return True
@@ -0,0 +1,137 @@
1
+ import re
2
+ import os
3
+ import sys
4
+ import locale
5
+ import time
6
+ import codecs
7
+ import httplib
8
+ import urlparse
9
+ from datetime import datetime
10
+ from StringIO import StringIO
11
+
12
+ from trac.core import *
13
+ from trac.env import open_environment
14
+ from trac.util.datefmt import format_date, to_datetime
15
+ from trac.wiki import wiki_to_html
16
+ from genshi import escape
17
+
18
+ from lxml import etree
19
+ from clients.action import IClientActionProvider
20
+
21
+
22
+ class ClientActionZendesk(Component):
23
+ implements(IClientActionProvider)
24
+
25
+ client = None
26
+ debug = False
27
+
28
+ def get_name(self):
29
+ return "Post to Zendesk"
30
+
31
+ def get_description(self):
32
+ return "Post the summary to a Zendesk forum topic"
33
+
34
+ def options(self, client=None):
35
+ if client is None:
36
+ yield {'name': 'XSLT', 'description': 'Formatting XSLT to convert the summary to a Zendesk compatible post', 'type': 'large'}
37
+ yield {'name': 'Username', 'description': 'Zendesk username', 'type': 'medium'}
38
+ yield {'name': 'Password', 'description': 'Zendesk password', 'type': 'medium'}
39
+ yield {'name': 'Method', 'description': 'Interaction Method', 'type': 'list', 'vals': ['POST', 'PUT']}
40
+ else:
41
+ yield {'name': 'Zendesk URI', 'description': 'Zendesk Forum REST URI', 'type': 'medium'}
42
+
43
+
44
+ def init(self, event, client):
45
+ self.client = client
46
+ if not event.action_options.has_key('XSLT') or not event.action_options['XSLT']['value']:
47
+ return False
48
+ try:
49
+ self.transform = etree.XSLT(etree.fromstring(str(event.action_options['XSLT']['value'])))
50
+ except:
51
+ print "Error: Cannot load/parse stylesheet"
52
+ return False
53
+
54
+ if not event.action_options.has_key('Username') or not event.action_options['Username']['value']:
55
+ return False
56
+ self.username = event.action_options['Username']['value']
57
+
58
+ if not event.action_options.has_key('Password') or not event.action_options['Password']['value']:
59
+ return False
60
+ self.password = event.action_options['Password']['value']
61
+
62
+ if not event.action_options.has_key('Method') or not event.action_options['Method']['value']:
63
+ return False
64
+ self.method = event.action_options['Method']['value']
65
+
66
+ if not event.action_client_options.has_key('Zendesk URI') or not event.action_client_options['Zendesk URI']['value']:
67
+ return False
68
+ self.uri = event.action_client_options['Zendesk URI']['value']
69
+
70
+ return True
71
+
72
+
73
+ def perform(self, req, summary):
74
+ def parseuri(uri):
75
+ """Parse URI, return (host, port, path) tuple.
76
+
77
+ >>> parseuri('http://example.org/testing?somequery#frag')
78
+ ('example.org', 80, '/testing?somequery')
79
+ >>> parseuri('http://example.net:8080/test.html')
80
+ ('example.net', 8080, '/test.html')
81
+ """
82
+
83
+ scheme, netplace, path, query, fragid = urlparse.urlsplit(uri)
84
+
85
+ if ':' in netplace:
86
+ host, port = netplace.split(':', 2)
87
+ port = int(port)
88
+ else: host, port = netplace, 80
89
+
90
+ if query: path += '?' + query
91
+
92
+ return host, port, path
93
+
94
+
95
+
96
+ if summary is None:
97
+ return False
98
+
99
+ result = self.transform(summary)
100
+
101
+ username = self.username
102
+ password = self.password
103
+ uri = self.uri
104
+
105
+ host, port, path = parseuri(uri)
106
+
107
+ okay = set([200, 201, 204])
108
+
109
+ import base64
110
+ userpass = username + ':' + password
111
+ userpass = base64.encodestring(userpass).strip()
112
+ authorization = 'Basic ' + userpass
113
+
114
+ # Attempt to HTTP PUT the data
115
+ h = httplib.HTTPConnection(host, port)
116
+
117
+ data = str(result)
118
+ h.putrequest('PUT', path)
119
+
120
+ h.putheader('User-Agent', 'Trac/1.0')
121
+ h.putheader('Accept', 'application/xml')
122
+ h.putheader('Content-Type', 'text/xml')
123
+ h.putheader('Authorization', authorization)
124
+ h.putheader('Content-Length', len(data))
125
+ h.endheaders()
126
+
127
+ h.send(str(data))
128
+
129
+ resp = h.getresponse()
130
+ status = resp.status # an int
131
+
132
+ # Got a response, now decide how to act upon it
133
+ if status not in okay:
134
+ print 'Got "%s %s"' % (status, resp.reason)
135
+ return False
136
+
137
+ return True
@@ -0,0 +1,91 @@
1
+ from trac.core import *
2
+ from trac.perm import PermissionSystem
3
+ #from trac.ticket.model import AbstractEnum
4
+ #from trac.ticket.admin import AbstractEnumAdminPage
5
+ from trac.ticket.admin import TicketAdminPanel
6
+
7
+
8
+ from clients.model import Client
9
+ from clients.events import ClientEvent
10
+ from trac.util.datefmt import utc, parse_date, get_date_format_hint, \
11
+ get_datetime_format_hint
12
+ from trac.web.chrome import add_link, add_script
13
+
14
+ class ClientAdminPanel(TicketAdminPanel):
15
+
16
+ _type = 'clients'
17
+ _label = ('Client', 'Clients')
18
+
19
+ # TicketAdminPanel methods
20
+ def _render_admin_panel(self, req, cat, page, client):
21
+ # Detail view?
22
+ if client:
23
+ clnt = Client(self.env, client)
24
+ events = ClientEvent.select(self.env, client)
25
+ if req.method == 'POST':
26
+ if req.args.get('save'):
27
+ clnt.name = req.args.get('name')
28
+ clnt.description = req.args.get('description')
29
+ clnt.changes_list = req.args.get('changes_list')
30
+ clnt.changes_period = req.args.get('changes_period')
31
+ clnt.summary_list = req.args.get('summary_list')
32
+ clnt.summary_period = req.args.get('summary_period')
33
+ clnt.default_rate = req.args.get('default_rate')
34
+ clnt.currency = req.args.get('currency')
35
+ clnt.update()
36
+
37
+ db = self.env.get_db_cnx()
38
+ for clev in events:
39
+ for option in clev.summary_client_options:
40
+ arg = 'summary-option-%s-%s' % (clev.md5, clev.summary_client_options[option]['md5'])
41
+ clev.summary_client_options[option]['value'] = req.args.get(arg)
42
+ for option in clev.action_client_options:
43
+ arg = 'action-option-%s-%s' % (clev.md5, clev.action_client_options[option]['md5'])
44
+ clev.action_client_options[option]['value'] = req.args.get(arg)
45
+ clev.update_options(client, db)
46
+ db.commit()
47
+
48
+ req.redirect(req.href.admin(cat, page))
49
+ elif req.args.get('cancel'):
50
+ req.redirect(req.href.admin(cat, page))
51
+
52
+ add_script(req, 'common/js/wikitoolbar.js')
53
+ data = {'view': 'detail', 'client': clnt, 'events': events}
54
+
55
+ else:
56
+ if req.method == 'POST':
57
+ # Add Client
58
+ if req.args.get('add') and req.args.get('name'):
59
+ clnt = Client(self.env)
60
+ clnt.name = req.args.get('name')
61
+ clnt.insert()
62
+ req.redirect(req.href.admin(cat, page))
63
+
64
+ # Remove clients
65
+ elif req.args.get('remove') and req.args.get('sel'):
66
+ sel = req.args.get('sel')
67
+ sel = isinstance(sel, list) and sel or [sel]
68
+ if not sel:
69
+ raise TracError('No client selected')
70
+ db = self.env.get_db_cnx()
71
+ for name in sel:
72
+ clnt = Client(self.env, name, db=db)
73
+ clnt.delete(db=db)
74
+ db.commit()
75
+ req.redirect(req.href.admin(cat, page))
76
+
77
+ # Set default client
78
+ elif req.args.get('apply'):
79
+ if req.args.get('default'):
80
+ name = req.args.get('default')
81
+ self.log.info('Setting default client to %s', name)
82
+ self.config.set('ticket', 'default_client', name)
83
+ self.config.save()
84
+ req.redirect(req.href.admin(cat, page))
85
+
86
+ default = self.config.get('ticket', 'default_client')
87
+ data = {'view': 'list',
88
+ 'clients': Client.select(self.env),
89
+ 'default': default}
90
+
91
+ return 'admin_clients.html', data