initial commit

This commit is contained in:
Stefan Kögl 2012-03-31 17:45:24 +02:00
commit 042b5ffd98
102 changed files with 11914 additions and 0 deletions

1
TracRendezVous/AUTHORS Normal file
View File

@ -0,0 +1 @@
Stefan Kögl

64
TracRendezVous/CHANGELOG Normal file
View File

@ -0,0 +1,64 @@
= changes for 0.3 =
== bug fixes and refactoring ==
* fixed index error for elected date
* fixed ugly date display when scheduling a RendezVous
* added missing conversion to RendezVousVote.exists()
* sorting RendezVousDates in rendezvous matrix
* renamed permissions. Now all rendezvous specific permissions have a prefix
'RendezVous_'
* renamed DateVote to RendezVousVote
* refactored begin and end into date and time parts for better usability
== new features ==
* added begin and end description to graphs
* added comments (RendezVousComment) to RendezVous
* blocking changes if RendezVous is scheduled. See comments in
[source:trunk/TracRendezVous/tracrendezvous/web_ui.py]
* reduced fatal error messages to verbose error warnings and
using more notices on success
* extented workflow
* automatic workflow status changes for "expire" and "start"
* new macro "ScheduledRendezVouses"
changes for 0.2
------------------------------------------------------------------------
r67 | hotshelf | 2009-01-12 00:54:32 +0100 (Mo, 12. Jan 2009) | 1 line
* fixing typo
------------------------------------------------------------------------
r66 | hotshelf | 2009-01-12 00:52:37 +0100 (Mo, 12. Jan 2009) | 1 line
* fixing permission 2
------------------------------------------------------------------------
r65 | hotshelf | 2009-01-12 00:47:53 +0100 (Mo, 12. Jan 2009) | 1 line
* fixing permission
------------------------------------------------------------------------
r64 | hotshelf | 2008-11-30 14:58:04 +0100 (So, 30. Nov 2008) | 1 line
* fixed type
------------------------------------------------------------------------
r63 | hotshelf | 2008-11-26 17:33:09 +0100 (Mi, 26. Nov 2008) | 1 line
* silly bug
------------------------------------------------------------------------
r62 | hotshelf | 2008-11-26 17:01:32 +0100 (Mi, 26. Nov 2008) | 1 line
* catching missing input before it get's dirty
------------------------------------------------------------------------
r61 | hotshelf | 2008-11-24 15:44:03 +0100 (Mo, 24. Nov 2008) | 1 line
* print statements
------------------------------------------------------------------------
r60 | hotshelf | 2008-11-24 15:43:28 +0100 (Mo, 24. Nov 2008) | 2 lines
* better label
* dates now have a default begin and end, which will be used as default values for new votes
------------------------------------------------------------------------
r59 | hotshelf | 2008-11-20 16:22:56 +0100 (Do, 20. Nov 2008) | 1 line
* deleted unused file
------------------------------------------------------------------------
changes for 0.1
* initial release - beginning changelog

340
TracRendezVous/LICENSE Normal file
View File

@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

8
TracRendezVous/babel.ini Normal file
View File

@ -0,0 +1,8 @@
# Extraction from Python source files
[python: **.py]
# Extraction from Genshi HTML and text templates
[genshi: **/templates/**.html]
ignore_tags = script,style
include_attrs = alt title summary
[genshi: **/templates/**.txt]
template_class = genshi.template:TextTemplate

View File

@ -0,0 +1,19 @@
[epydoc]
modules: tracrendezvous
verbosity: 1
parse: yes
introspect: yes
output: html
simple-term: no
target: docs/apidocs/
sourcecode: no
graph: all
dotpath: /usr/bin/dot
graph: all
dotpath: /usr/bin/dot
graph-font: Helvetica
graph-font-size: 10

47
TracRendezVous/setup.cfg Normal file
View File

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
[extract_messages]
add_comments = TRANSLATOR:
copyright_holder = Stefan Koegl
msgid_bugs_address = hotshelf@ctdo.de
output_file = tracrendezvous/locale/messages.pot
keywords = _ ngettext:1,2 N_ tag_
[init_catalog]
input_file = tracrendezvous/locale/messages.pot
output_dir = tracrendezvous/locale
[compile_catalog]
directory = tracrendezvous/locale
[update_catalog]
input_file = tracrendezvous/locale/messages.pot
output_dir = tracrendezvous/locale
[extract_messages_js]
add_comments = TRANSLATOR:
copyright_holder = Stefan Koegl
msgid_bugs_address = hotshelf@ctdo.de
output_file = tracrendezvous/locale/messages-js.pot
keywords = _ ngettext:1,2 N_
mapping_file = messages-js.cfg
[init_catalog_js]
domain = tracrendezvous-js
input_file = tracrendezvous/locale/messages-js.pot
output_dir = tracrendezvous/locale
[compile_catalog_js]
domain = tracrendezvous-js
directory = tracrendezvous/locale
[update_catalog_js]
domain = tracrendezvous-js
input_file = tracrendezvous/locale/messages-js.pot
output_dir = tracrendezvous/locale
[generate_messages_js]
domain = tracrendezvous-js
input_dir = tracrendezvous/locale
output_dir = tracrendezvous/htdocs/tracrendezvous

92
TracRendezVous/setup.py Normal file
View File

@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-
import os, sys
from setuptools import find_packages, setup
from babel.messages import frontend as babel
from distutils.cmd import Command
from trac.util.dist import get_l10n_js_cmdclass
commands = {'compile_catalog': babel.compile_catalog,
'extract_messages': babel.extract_messages,
'init_catalog': babel.init_catalog,
'update_catalog': babel.update_catalog}
commands.update(get_l10n_js_cmdclass())
try:
from epydoc import cli
except ImportError:
print 'epydoc not installed, skipping API documentation target.'
else:
class build_apidoc(Command):
description = 'Builds the api documentation'
user_options = []
boolean_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
epydoc_conf = os.path.join('docs', 'epydoc.conf')
old_argv = sys.argv[1:]
sys.argv[1:] = [
'--check',
'-v',
'--config=%s' % epydoc_conf,
'--no-private']
try:
cli.cli()
except:
pass
finally:
sys.argv[1:] = old_argv
commands['build_apidoc'] = build_apidoc
setup(
name='TracRendezVous',
version='0.3',
packages=find_packages(),
install_requires = {
#'PIL': ['Imaging>=1.1.6']
},
zip_safe=False,
entry_points = """
[trac.plugins]
tracrendezvous.location.web_ui = tracrendezvous.location.web_ui
tracrendezvous.event.web_ui = tracrendezvous.event.web_ui
tracrendezvous.rendezvous.web_ui = tracrendezvous.rendezvous.web_ui
""",
cmdclass = commands,
message_extractors = {'tracrendezvous': [
('**.py', 'python', None),
('**/templates/**.html', 'genshi', None),
('**/templates/**.txt', 'genshi', {
'template_class': 'genshi.template:TextTemplate'
})
],
},
package_data={
'' : ['templates/*'],
'tracrendezvous': [
'htdocs/css/*.css',
'htdocs/script/*.js',
'htdocs/images/*',
'locale/*/LC_MESSAGES/*.mo', 'htdocs/tracrendezvous/*.js'
],
'tracrendezvous.location': ['htdocs/css/*.css','htdocs/script/*.js','htdocs/images/*'],
'tracrendezvous.rendezvous': ['*.ttf','htdocs/css/*.css','htdocs/script/*.js','htdocs/images/*'],
'tracrendezvous.event': [ 'htdocs/css/*.css', 'htdocs/script/*.js', 'htdocs/images/*']},
author = "Stefan Kögl",
author_email = "skoegl@online.de",
description = "a plugin for meeting dates syndication and event calendar with ical export",
license = "GPL",
keywords = "rendezvous, dates, teaming, syndication, calendar",
url = "http://trac.ctdo.de/dev/", # project home page, if any
#requires=["Imaging (>=1.1.6)"],
test_suite = 'tracrendezvous.test.suite'
)

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
from trac.core import Component, implements, TracError
from trac.web.chrome import ITemplateProvider
class RendezVousBase(Component):
implements(ITemplateProvider)
# ITemplateProvider methods
# Used to add the plugin's templates and htdocs
def get_templates_dirs(self):
from pkg_resources import resource_filename
return [resource_filename(__name__, 'templates')]
def get_htdocs_dirs(self):
"""Return a list of directories with static resources (such as style
sheets, images, etc.)
Each item in the list must be a `(prefix, abspath)` tuple. The
`prefix` part defines the path in the URL that requests to these
resources are prefixed with.
The `abspath` is the absolute path to the directory containing the
resources on the local file system.
"""
from pkg_resources import resource_filename
return [('hw', resource_filename(__name__, 'htdocs'))]

View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from tracrendezvous.event.web_ui import *
from tracrendezvous.event.model import *
from tracrendezvous.event.macros import *

View File

@ -0,0 +1,200 @@
# -*- coding: utf-8 -*-
from os.path import join
from trac.admin import IAdminPanelProvider
from trac.core import *
from trac.web.chrome import add_stylesheet
from tracrendezvous.rendezvous.api import RendezVousSystem
from trac.util.translation import _
from model import RendezVousType, TypePermission, RendezVousDate
from ctdotools.utils import validate_id, update_votes_graph, date_cmp
__all__ = ['ComponentRendezVousGeneral', 'ComponentRendezVousTypes']
def get_actions(myactions):
actions = []
for action in myactions:
if isinstance(action, tuple):
actions.append(action[0])
else:
actions.append(action)
return actions
class RendezVousAdminPanel(Component):
implements(IAdminPanelProvider)
abstract = True
# IAdminPanelProvider methods
def get_admin_panels(self, req):
if 'RENDEZVOUS_ADMIN' in req.perm:
yield ('rendezvous', 'RendezVous System', self._type, self._label[1])
def render_admin_panel(self, req, cat, page, rendezvous):
req.perm.require('RENDEZVOUS_ADMIN')
# Trap AssertionErrors and convert them to TracErrors
try:
return self._render_admin_panel(req, cat, page, rendezvous)
except AssertionError, e:
raise TracError(e)
class ComponentRendezVousGeneral(RendezVousAdminPanel):
_type = 'rendezvous'
_label = ('rendezvous', 'General')
def _render_admin_panel(self, req, cat, page, rendezvous):
add_stylesheet (req, 'hw/css/rendezvous.css')
if req.method == "POST":
if req.args.has_key("show_vote_graph"):
self.config.set("rendezvous", "show_vote_graph", True)
else:
self.config.set("rendezvous", "show_vote_graph", False)
if req.args.has_key("show_location_map"):
self.config.set("rendezvous", "show_location_map", True)
else:
self.config.set("rendezvous", "show_location_map", False)
if req.args.has_key("max_description_length"):
tmp = int(req.args["max_description_length"])
self.config.set("rendezvous", "max_description_length", tmp)
if req.args.has_key("max_votes_per_date"):
tmp = int(req.args["max_votes_per_date"])
self.config.set("rendezvous", "max_votes_per_date", tmp)
if req.args.has_key("max_dates_per_rendezvous"):
tmp = int(req.args["max_dates_per_rendezvous"])
self.config.set("rendezvous", "max_dates_per_rendezvous", tmp)
if req.args.has_key("graph_size_x"):
tmp = int(req.args["graph_size_x"])
self.config.set("rendezvous", "graph_size_x", tmp)
update = True
update = False
if req.args.has_key("graph_size_y"):
tmp = int(req.args["graph_size_y"])
self.config.set("rendezvous", "graph_size_y", tmp)
update = True
if req.args.has_key("default_vote_time_start"):
tmp = req.args["default_vote_time_start"]
self.config.set("rendezvous", "default_vote_time_start", tmp)
if req.args.has_key("default_vote_time_end"):
tmp = req.args["default_vote_time_end"]
self.config.set("rendezvous", "default_vote_time_end", tmp)
if req.args.has_key("default_rendezvous_type"):
tmp = req.args["default_rendezvous_type"]
self.config.set("rendezvous", "default_rendezvous_type", tmp)
self.config.save()
if update:
path = join(self.env.path, "htdocs", "rendezvous_graphs")
size = [self.config.getint("rendezvous", "graph_size_x"),
self.config.getint("rendezvous", "graph_size_y")]
dates = RendezVousDate.fetch_all(self.env)
for date in dates:
if date.votes:
update_votes_graph(date.votes, path, size)
data = {"show_vote_graph" : self.config.getbool("rendezvous", "show_vote_graph"),
"show_location_map" : self.config.getbool("rendezvous", "show_location_map"),
"max_description_length" : self.config.getint("rendezvous", "max_description_length"),
"max_votes_per_date" : self.config.getint("rendezvous", "max_votes_per_date"),
"max_dates_per_rendezvous" : self.config.getint("rendezvous", "max_dates_per_rendezvous"),
"graph_size_x" : self.config.getint("rendezvous", "graph_size_x"),
"graph_size_y" : self.config.getint("rendezvous", "graph_size_y"),
"default_vote_time_start" : self.config.get("rendezvous", "default_vote_time_start"),
"default_vote_tTime_end" : self.config.get("rendezvous", "default_vote_time_end")}
return "admin_general.html", data
class ComponentRendezVousTypes(RendezVousAdminPanel):
_type = 'types'
_label = ('types', 'Types')
def _render_admin_panel(self, req, cat, page, rendezvoustype):
add_stylesheet (req, 'hw/css/rendezvous.css')
data = {}
myPermissions = get_actions(RendezVousSystem._actions)
if req.method == "POST":
rtype = req.args.get("rtype")
permission = req.args.get("permission")
if rtype and rtype.isupper():
raise TracError(_('All upper-cased tokens are reserved for '
'permission names'))
#if not rtype:
#raise TracError(_('Unknown RendezVousType'))
if permission and permission not in myPermissions:
raise TracError(_('Unknown permission'))
if req.args.get("add") and rtype and permission:
rtype = RendezVousType.fetch_one(self.env, name=rtype)
if rtype.has_permission(permission):
raise TracError(_('permission already granted to RendezVousType'))
tPerm = TypePermission(self.env, rtype.type_id, permission)
self.validate_type_permission(tPerm)
tPerm.commit()
req.redirect(req.href.admin(cat, page))
elif req.args.has_key("add") and rtype:
realType = RendezVousType.fetch_one(self.env, name=rtype)
if realType:
raise TracError(_('RendezVousType already exists'))
realType = RendezVousType(self.env, 0, rtype)
self.validate_rendezvous_type(realType)
realType.commit()
req.redirect(req.href.admin(cat, page))
elif req.args.has_key("save") and req.args.has_key("rsel"):
req.perm.require('RENDEZVOUS_ADMIN')
rsel = req.args.get('rsel')
rsel = isinstance(rsel, list) and rsel or [rsel]
for key in rsel:
type_id = int(key)
rtype = RendezVousType.fetch_one(self.env, type_id)
if not rtype:
raise TracError(_('Unknown RendezVousType'))
rtype.delete()
req.redirect(req.href.admin(cat, page))
elif req.args.has_key("save") and req.args.has_key("sel"):
req.perm.require('RENDEZVOUS_ADMIN')
sel = req.args.get('sel')
sel = isinstance(sel, list) and sel or [sel]
for key in sel:
rtype, permission = key.split(":")
rtype_id = int(rtype)
if permission and permission not in myPermissions:
raise TracError(_('Unknown type permission relation'))
typePermission = TypePermission.fetch_one(self.env, rtype_id, permission)
if typePermission:
typePermission.delete()
req.redirect(req.href.admin(cat, page))
elif req.args.has_key("save") and req.args.has_key("default"):
default = int(req.args["default"])
self.config.set("rendezvous", "default_rendezvous_type", default)
self.config.save()
data.update({"default_rendezvous_type" : self.config.getint("rendezvous", "default_rendezvous_type"),
"rendezVousTypes" : RendezVousType.fetch_all(self.env),
"actions" : myPermissions})
return "admin_types.html", data
def validate_rendezvous_type(self, typ):
if type(typ.type_id) != int:
raise TypeError("RendezVousType.validate() wrong type")
if type(typ.name) != unicode:
raise TypeError("RendezVousType.validate() wrong type")
def validate_type_permission(self, mytype):
if type(mytype.type_id) != int:
raise TypeError("TypePermission.__init__(): expected type int, got '%s'" % type(mytype.type_id))
if type(mytype.permission) != unicode:
raise TypeError("TypePermission.__init__() expected type 'unicode', got '%s'" % type(mytype.permission))
validate_id(mytype.type_id)

View File

@ -0,0 +1,158 @@
#main, #content {margin:0 !important;padding:0 !important;left:0;}
fieldset {-khtml-border-radius: 5px;-webkit-border-radius: 20px;-moz-border-radius: 20px;padding-bottom:10px;margin:0 !important;}
fieldset legend {margin-left:15px;}
span.edit {margin-left:5px;}
#new_event {width:50em;margin:auto;}
#new_event fieldset {text-align:left;}
#recurrence {padding:0 !important;width:45em;margin:auto;}
#recurrency-freq, #recurrency-timeframe, #recurrency-exceptions {border:1px outset #000 !important;}
#recurrency-exceptions table {border-collapse: collapse;}
#recurrency-exceptions td {vertical-align:top;}
#recurrency-exceptions td.left {border-right:1px solid #000;}
ul#exceptions {padding:5px;list-style-type:none;}
ul#exceptions li {padding:5px;margin:5px;background:#ccc;}
#event-overview {width:35em; margin:auto;}
table.upcoming
{
border-spacing:0px;
border-collapse: collapse;
margin:auto;
text-align:center;
color:#000;
font-size:70% !important;
margin-bottom:10px;
}
table.upcoming :link, table.upcoming a {color:#a00;}
table.upcoming h1, table.upcoming h2 {color:#000 !important;font-size:100%;font-family:serif;}
div#content h1, div#content h2 {text-align:center;}
.event-item {margin-bottom:1em; overflow: hidden;}
.event-item h2 {margin:0;padding:0;padding-top:0.5em;}
.event-intern
{
padding:0;
margin:0;
background:#e4d6b0;
margin-bottom: -2000px;
padding-bottom: 2000px;
}
.recurring, .unique
{
width:2em;
float:left;
margin-bottom: -2000px;
padding-bottom: 2000px;
font-family:monospace;
text-align:center;
font-weight:bold;
}
.recurring { background:#f00;}
.unique { background:#ccc;}
td.upcoming, td.upcoming-event
{
background:#e4d6b0;
padding:5px;
border-top:1px solid #fff;
border-bottom:1px solid #fff;
border-right:1px solid #fff;
min-height:2em;
vertical-align:top;
text-align:center;
}
td.upcoming-event {background:#ffedbc;}
td.upcoming:hover, td.upcoming-event:hover {background:#ffffee;}
td.upcoming-event table {padding:0;margin:0;}
table.upcoming thead th, table.upcoming tfoot th {
border-right:1px solid #fff !important;
background:#000;
color:#fff;
font-size:80%;
font-weight: bold;
padding-top:10px;
padding-bottom:10px;
padding-left:5px;
vertical-align: bottom;
}
table.upcoming tfoot th {border:0 !important;}
table.upcoming thead th.last, table.upcoming thead th:last-child
{
border-top-right-radius:5px;
-moz-border-radius-topright: 20px;
padding-right:10px;
}
table.upcoming thead th.first, table.upcoming thead th:first-child
{
border-top-left-radius:20px;
-khtml-border-radius-topleft: 5px;
-webkit-border-top-left-radius: 20px;
-moz-border-radius-topleft: 20px;
background:#000;
color:#fff;
padding-left:10px;
}
table.upcoming tfoot th.first, table.upcoming tfoot th:first-child
{
border-bottom-left-radius:20px;
-khtml-border-radius-bottomleft: 5px;
-webkit-border-bottom-left-radius: 20px;
-moz-border-radius-bottomleft: 20px;
background:#000;
color:#fff;
padding-left:10px;
}
table.upcoming tfoot th.last, table.upcoming tfoot th:last-child
{
border-bottom-right-radius:20px;
-khtml-border-radius-bottomright: 5px;
-webkit-border-bottom-right-radius: 20px;
-moz-border-radius-bottomright: 20px;
padding-right:10px;
}
td.day
{
font-weight:bold;
font-family:sans-serif;
padding:5px 10px;
margin:0px;
border-right:1px solid #000;
border-bottom:1px solid #000;
min-height:2em;
}
td.daytext
{
background:#000;
color:#fff;
font-weight:bold;
font-family:sans-serif;
font-size:80%;
padding:2px 10px 5px 10px;
margin:0px;
border-right:1px solid #fff !important;
border-bottom:1px solid #fff;
min-height:2em;
vertical-align:top;
}
#altlinks li a.ical
{
background-image: url("../images/ical_icon.jpg");
padding-left: 45px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 B

View File

@ -0,0 +1,267 @@
# -*- coding: utf-8 -*-
import time, calendar
from datetime import datetime, date, timedelta
from cStringIO import StringIO
from trac.wiki.api import WikiSystem
from trac.wiki.macros import WikiMacroBase
from trac.util import *
from trac.web.chrome import add_stylesheet
from trac.web import Href
from trac.util.datefmt import utc, to_timestamp
from trac.resource import get_resource_url
from tracrendezvous.event.model import Event
from ctdotools.utils import get_tz
__all__ = ['RendezVousesCalendarMacro',]
class EventHeaderMacro(WikiMacroBase):
def expand_macro(self, formatter, name, content):
try:
e_id = int(content)
except ValueError:
return ""
event = Event.fetch_one(self.env, e_id, show_all=True, days=60)
if not event or not event.periodic():
return ""
rows = []
rows.append("""<style type="text/css">
div.eventheader {text-align:center; min-width:5em; min-height:20em; float:right; clear:both; background:#ffedbc;}
a.existing_occurence, a.existing-occurence:visited {color:#00f;}
</style>""")
session_tzname, selected_tz = get_tz(formatter.req.session.get('tz', self.env.config.get("trac", "default_timezone") or None))
rows.append("<tr><td>%s</td></tr>" % event.rrules_explained)
if hasattr(event, "followups"):
l = []
for ev in event.followups:
local_time_begin = selected_tz.fromutc(ev.time_begin)
local_time_end = selected_tz.fromutc(ev.time_end)
link_label = "%s %s - %s %s" % (local_time_begin.strftime("%d.%m.%Y %H:%M"), local_time_begin.tzinfo.tzname(None), local_time_end.strftime("%d.%m.%Y %H:%M"), local_time_end.tzinfo.tzname(None))
link = "events/%s/%s" % (event.e_id, ev.time_begin.strftime("%Y-%m-%d"))
if WikiSystem(self.env).has_page(link):
row = '<li><a class="existing-occurence" href="%s">%s</a></li>' % (formatter.href.wiki(link) or formatter.href.event("createpage", event.e_id, ev.time_begin.strftime("%Y-%m-%d")), link_label)
else:
row = '<li><a href="%s">%s</a></li>' % (formatter.href.event("createpage", event.e_id, ev.time_begin.strftime("%Y-%m-%d")), link_label)
l.append(row)
rows.append("<tr><td><ul>%s</ul></td></tr>" % "".join(l))
return """<div class="eventheader"><h2><a href="%s">%s</a></h2><table class="eventheader"><tbody>%s<tr><td></td></tr></tbody></table></div>""" % (formatter.href.event(event.e_id), event.name, "".join(rows))
class RendezVousesCalendarMacro(WikiMacroBase):
"""Inserts a small calendar with scheduled RendezVouses with optional locations
constraint
Examples:
{{{
[[WikiCalendar]]
[[WikiCalendar(location1,location2,foo_location)]]
}}}
"""
def expand_macro(self, formatter, name, content):
today = time.localtime()
http_param_year = formatter.req.args.get('year', '')
http_param_month = formatter.req.args.get('month', '')
if content:
args = content.split(',')
else:
args = []
if http_param_year == "":
# not clicked on a prev or next button
if len(args) >= 1 and args[0] <> "*":
# year given in macro parameters
year = int(args[0])
else:
# use current year
year = today.tm_year
else:
# year in http params (clicked by user) overrides everything
year = int(http_param_year)
if http_param_month == "":
# not clicked on a prev or next button
if len(args) >= 2 and args[1] <> "*":
# month given in macro parameters
month = int(args[1])
else:
# use current month
month = today.tm_mon
else:
# month in http params (clicked by user) overrides everything
month = int(http_param_month)
wiki_page_format = "%Y-%m-%d"
if len(args) >= 4:
wiki_page_format = args[3]
curr_day = None
if year == today.tm_year and month == today.tm_mon:
curr_day = today.tm_mday
thispageURL = Href(get_resource_url(self.env, formatter.resource, formatter.href))
# for the prev/next navigation links
prevMonth = month-1
prevYear = year
nextMonth = month+1
nextYear = year
# check for year change (KISS version)
if prevMonth == 0:
prevMonth = 12
prevYear -= 1
if nextMonth == 13:
nextMonth = 1
nextYear += 1
# 9-tuple for use with time.* functions requiring a struct_time
mydate = [0] * 8 + [-1] # AS: breaks Python 2.4
# building the output
buff = []
buff.append(u'''\
<style type="text/css">
<!--
div#wiki-calendar-block {margin:auto; display:inline-block; border:2px solid #000; padding:0; -khtml-border-radius: 17px;-moz-border-radius: 17px;}
table.wiki-calendar {margin:0px}
table.wiki-calendar caption {font-size: 120%; white-space: nowrap;}
table.wiki-calendar caption a {display: inline; margin: 0; border: 0; padding: 0; background-color: transparent; color: #b00; text-decoration: none;}
table.wiki-calendar caption a.prev {padding-right: 5px;}
table.wiki-calendar caption a.next {padding-left: 5px;}
table.wiki-calendar caption a:hover {background-color: #eee;background:transparent;border:0;}
table.wiki-calendar th {border: none; border-bottom: 2px solid #000; text-align: center; font-weight: bold;}
table.wiki-calendar td {border: none; text-align: center;padding:10px 10px;wrap:nowrap;}
table.wiki-calendar td.day {min-width: 2em; height: 100%; margin: 0; border: 2px solid #eee; background-color: #fff; color: #888; text-decoration: none; -khtml-border-radius: 17px;-moz-border-radius: 17px;}
table.wiki-calendar td.active {border-color: #eee; background:#0f0; color: #000;}
table.wiki-calendar td.active:hover {border-color: #eee; background:#afa; color: #000;}
table.wiki-calendar td.today {border-color: #b77 !important;}
table.wiki-calendar ul, table.wiki-calendar li {margin:0;padding:0;}
table.wiki-calendar td.adjacent_month {background-color: #333;}
table.wiki-calendar td.adjacent_month:hover {background-color: #333;color:#888}
table.wiki-calendar td.collision {background:#800;}
table.wiki-calendar a.byday {min-width:100px; border:1px solid #eee;padding:0px 10px; margin:0; font-weight:bold; -khtml-border-radius: 17px;-moz-border-radius: 17px;}
table.wiki-calendar a.byday:hover {background:#ccc;}
table.wiki-calendar td.adjacent_month a.byday {color: #ddd !important;}
table.wiki-calendar td.adjacent_month a.byday:hover {color: #000 !important;}
table.wiki-calendar :link,table.wiki-calendar :visited {color: #000 !important;font-size:1.2em;wrap:nowrap;}
//-->
</style>
<div id="wiki-calendar-block">
<table class="wiki-calendar"><caption>
''')
import locale
encoding = locale.getlocale()[1]
# prev year link
mydate[0:2] = [year-1, month]
mydate_label = time.strftime('%B %Y', tuple(mydate))
if encoding:
mydate_label = mydate_label.decode(encoding)
buff.append(u'<a class="prev" href="%s" title="%s">&lt;&lt;</a>' % (
thispageURL(month=month, year=year-1),
mydate_label
))
# prev month link
mydate[0:2] = [prevYear, prevMonth]
mydate_label = time.strftime('%B %Y', tuple(mydate))
if encoding:
mydate_label = mydate_label.decode(encoding)
buff.append(u'<a class="prev" href="%s" title="%s">&lt;</a>' % (
thispageURL(month=prevMonth, year=prevYear),
mydate_label
))
# the caption
mydate[0:2] = [year, month]
mydate_label = time.strftime('%B %Y', tuple(mydate))
if encoding:
mydate_label = mydate_label.decode(encoding)
buff.append(mydate_label)
# next month link
mydate[0:2] = [nextYear, nextMonth]
mydate_label = time.strftime('%B %Y', tuple(mydate))
if encoding:
mydate_label = mydate_label.decode(encoding)
buff.append(u'<a class="next" href="%s" title="%s">&gt;</a>' % (
thispageURL(month=nextMonth, year=nextYear),
mydate_label))
# next year link
mydate[0:2] = [year+1, month]
mydate_label = time.strftime('%B %Y', tuple(mydate))
if encoding:
mydate_label = mydate_label.decode(encoding)
buff.append(u'<a class="next" href="%s" title="%s">&gt;&gt;</a>' % (
thispageURL(month=month, year=year+1),
mydate))
buff.append(u'</caption>\n<thead>\n<tr>')
for day in calendar.weekheader(2).split():
buff.append(u'<th scope="col">%s</th>' % day)
buff.append(u'</tr>\n</thead>\n<tbody>')
last_week_prev_month = calendar.monthcalendar(prevYear, prevMonth)[-1];
first_week_next_month = calendar.monthcalendar(nextYear, nextMonth)[0];
w = -1
db = self.env.get_db_cnx()
cursor = db.cursor()
day_list = []
foo,last_day = calendar.monthrange(year, month)
start_dt = datetime(year, month, 1, tzinfo=utc)
end_dt = datetime(year, month, last_day, 23, 59, tzinfo=utc)
rts = Event.fetch_by_period_dict(self.env, start_dt, end_dt)
session_tzname, selected_tz = get_tz(formatter.req.session.get('tz', self.env.config.get("trac", "default_timezone") or None))
def _f(t):
assert type(t) == Event
real_begin = selected_tz.fromutc(t.time_begin)
real_end = selected_tz.fromutc(t.time_end)
return u'<li><a href="%s" title="%s - %s %s">%s:<br/>%s</a></li>' % (formatter.href.event(t.e_id), real_begin.strftime("%d.%m.%Y %H:%M"), real_end.strftime("%d.%m.%Y %H:%M"), real_begin.tzinfo.tzname(None), unicode(t.location.name), unicode(t.name))
for week in calendar.monthcalendar(year, month):
buff.append(u'\n<tr>')
w = w+1
d = -1
for day in week:
d = d+1
# calc date and update CSS classes
mydate[0:3] = [year, month, day]
classes = u'day'
title = u''
if not day:
classes += u' adjacent_month'
if w == 0:
day = last_week_prev_month[d]
mydate[0:3] = [prevYear, prevMonth, day]
else:
day = first_week_next_month[d]
mydate[0:3] = [nextYear, nextMonth, day]
else:
if day == curr_day:
classes += u' today'
title += u"Heute:"
wiki = time.strftime(wiki_page_format, tuple(mydate))
actual_date = date(mydate[0], mydate[1], mydate[2])
if rts and rts.has_key(actual_date):
rt = rts[actual_date]
for t in rt:
if actual_date.day != t.time_end.day:
tomorrow = actual_date + timedelta(1)
rts[tomorrow].append(t)
text = u"".join([_f(t) for t in rt])
classes += u" active"
title += u" %d Termin(e)" % len(rt)
daylink = unicode(formatter.href.event("by-day", "%d-%d-%d" % tuple(mydate[:3])))
#text = text.encode("utf8")
buff.append(u'\n<td class="%s"><a class="byday" href="%s" title="%s">%s</a><br/><ul>%s</ul></td>' % (classes.strip(), daylink, title, day, text))
else:
title += u"Noch keine Termine"
daylink = formatter.href.event("new", "%d-%d-%d" % tuple(mydate[:3]))
buff.append(u'\n<td class="%s"><a class="byday" href="%s" title="%s">%s</a></td>' % (classes, daylink, title, day))
buff.append(u'\n</tr>')
buff.append(u'\n</tbody>\n</table></div>\n')
table = u"".join(buff)
return table

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,66 @@
<!DOCTYPE htm
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="layout.html" />
<head>
<script type="text/javascript" src="${chrome.htdocs_location}js/wikitoolbar.js"></script>
<script type="text/javascript">
/* <![CDATA[ */
jQuery(document).ready(function($) {
$("#name").focus();
$("#date_begin").datepicker({"dateFormat" : "dd.mm.yy"});
$("#date_end").datepicker({"dateFormat" : "dd.mm.yy"});
$("#until_date").datepicker({"dateFormat" : "dd.mm.yy"});
});
/* ]]> */
</script>
<title>${event.e_id and 'Edit Event' or 'Add Event'}</title>
</head>
<body>
<div id="content">
<form name="new_event" id='new_event' uri="" method="post" mime-type="text/plain" action="">
<input py:if="event.e_id != 0" type="hidden" name="event_id" value="${event.e_id}"/>
<fieldset id="properties">
<legend>${event.e_id and 'Edit Event' or 'Add Event'}</legend>
<table class="event-wizard" py:with="mydt = selected_tz.fromutc(event.time_begin);dt2 = selected_tz.fromutc(event.time_end)">
<tr><th><label for="name">Title:</label></th><td><input id="name" type="text" size="60" maxlength="200" name="name" value="${event.name}"/></td></tr>
<tr><th><label for="time_begin">Date begin:</label></th><td><input id="date_begin" type="text" size="10" maxlength="10" name="date_begin" value="${mydt.strftime('%d.%m.%Y')}"/>
<input id="time_begin" type="text" size="5" maxlength="5" name="time_begin" value="${mydt.strftime('%H:%M')}"/> ${mydt.tzinfo.tzname(None)}</td>
</tr>
<tr><th><label for="time_begin">Date end:</label></th><td><input id="date_end" type="text" size="10" maxlength="10" name="date_end" value="${dt2.strftime('%d.%m.%Y')}"/>
<input id="time_end" type="text" size="5" maxlength="5" name="time_end" value="${dt2.strftime('%H:%M')}"/> ${dt2.tzinfo.tzname(None)}</td>
</tr>
<tr><th><label for="location">Locations:</label></th>
<td><select id="location" name="location_id" size="1">
<option py:for="location in locations" selected="${location.location_id == event.location_id and 'checked' or None}" value="${location.location_id}">${location.name} :${location.coordinate_str()}</option>
</select>
<a py:if="'LOCATION_MODIFY' in perm" href="${href.location(from_='event', id=event.e_id)}">edit/search locations</a></td>
</tr>
<tr><td></td><td py:choose="" test=""><a py:when="event.e_id" class="buttonlike" href="${href.event('recurrency', event.e_id)}">show recurrency options</a>
<input py:otherwise="" type="checkbox" id="show_recurrency" name="show_recurrency"/><label for="show_recurrency">show recurrency options</label></td>
</tr>
</table>
</fieldset>
<fieldset>
<legend >Tags</legend>
<div>
<input id="tags" type="text" size="70" maxlength="70" name="tags" value="${event.tags}"/>
</div>
</fieldset>
<div class="mybuttons">
<input type="reset" value="Reset"/>
<py:choose test="">
<py:when test="event.event_id != 0">
<input type="submit" name="edit" value="${event.e_id == 0 and 'add' or 'edit'}"/>
<input py:if="'EVENTS_DELETE' in perm and event.e_id != 0" type="submit" name="delete" value="delete"/>
</py:when>
<input py:otherwise="" type="submit" name="add" value="add"/>
</py:choose>
</div>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="layout.html" />
<xi:include href="event_display.html" />
<head>
<title>${title}</title>
</head>
<body>
<div id="content" class="event">
<py:choose test="">
<py:when test="table">
<h1>${title}</h1>
<table class="upcoming">
<thead><tr><th py:for="h in headers">${h}</th></tr></thead>
<tfoot><tr><th py:for="h in headers">&nbsp;</th></tr></tfoot>
<tbody>
<tr py:for="ix,row in enumerate(table)" class="upcoming">
<td class="daytext">${selected_tz.fromutc(row[0]).strftime(format)}</td>
<py:for each="eventlist in row[1:]">
<td py:if="eventlist != False" class="${isinstance(eventlist, list) and 'upcoming-event' or 'upcoming'}" rowspan="${eventlist.rowspan and eventlist.rowspan or None}">
<py:if test="eventlist != True">
<py:for each="event in eventlist">
${render_event(event, True)}
</py:for></py:if></td></py:for>
</tr>
</tbody>
</table>
</py:when>
<py:otherwise>
<h2>${_("No events in this time frame. Create here")} <a href="${href.event('new', now.strftime('%Y-%m-%d'))}">${_(" a new event")}</a>&nbsp;!</h2>
</py:otherwise>
</py:choose>
</div>
</body>
</html>

View File

@ -0,0 +1,30 @@
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<py:def function="render_event(event, with_day=False)">
<h2>${event.name}<span py:if="'EVENTS_MODIFY' in perm" class="edit"><a style="color:#f00;" href="${href.event('edit', event.e_id)}">edit</a></span></h2>
<table py:with="dt = selected_tz.fromutc(event.time_begin);dt2 = selected_tz.fromutc(event.time_end)">
<tr>
<td >${with_day and dt.strftime('%Y.%m.%d')} ${dt.strftime('%H:%M')} - ${with_day and dt2.strftime('%Y.%m.%d')} ${dt2.strftime('%H:%M')}</td>
<!-- <td >${dt.strftime('%H:%M')} ${dt.tzinfo.tzname(None)} - ${dt2.strftime('%H:%M')} ${dt2.tzinfo.tzname(None)}</td> -->
</tr>
<tr py:if="event.periodic()">
<td headers="h_tags">${event.rrule and event.rrule.explain() or None}</td>
</tr>
<tr py:if="event.tags">
<td headers="h_tags">${event.tags and event.tags or None}</td>
</tr>
<py:if test="event.location and event.location.lat != None">
<tr>
<td headers="h_location">${event.location.name}</td>
</tr>
<tr py:if="event.location">
<td headers="h_coordinates"><a href="${'http://www.openstreetmap.org/index.html?mlat=%s&amp;mlon=%s&amp;zoom=15&amp;layers=B00TTT' % (event.location.lat, event.location.lon)}">${event.location.coordinate_str()}</a></td>
</tr>
</py:if>
<tr>
<td headers="wiki_link"><a href="${href.wiki(event.wikipage)}">wiki page</a></td>
</tr>
</table>
</py:def>
</html>

View File

@ -0,0 +1,66 @@
<!DOCTYPE htm
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="layout.html" />
<head>
<script type="text/javascript" src="${chrome.htdocs_location}js/wikitoolbar.js"></script>
<script type="text/javascript">
/* <![CDATA[ */
jQuery(document).ready(function($) {
$("#name").focus();
$("#date_begin").datepicker({"dateFormat" : "dd.mm.yy", altField: '#date_end'});
$("#date_end").datepicker({"dateFormat" : "dd.mm.yy"});
$("#until_date").datepicker({"dateFormat" : "dd.mm.yy"});
});
/* ]]> */
</script>
<title>${event.e_id and 'Edit Event' or 'Add Event'}</title>
</head>
<body>
<div id="content">
<form name="new_event" id='new_event' uri="" method="post" mime-type="text/plain" action="">
<input py:if="event.e_id != 0" type="hidden" name="event_id" value="${event.e_id}"/>
<fieldset id="properties">
<legend>${event.e_id and 'Edit Event' or 'Add Event'}</legend>
<table class="event-wizard" py:with="mydt = selected_tz.fromutc(event.time_begin);dt2 = selected_tz.fromutc(event.time_end)">
<tr><th><label for="name">Title:</label></th><td><input id="name" type="text" size="60" maxlength="200" name="name" value="${event.name}"/></td></tr>
<tr><th><label for="time_begin">Date begin:</label></th><td><input id="date_begin" type="text" size="10" maxlength="10" name="date_begin" value="${mydt.strftime('%d.%m.%Y')}"/>
<input id="time_begin" type="text" size="5" maxlength="5" name="time_begin" value="${mydt.strftime('%H:%M')}"/> ${mydt.tzinfo.tzname(None)}</td>
</tr>
<tr><th><label for="time_begin">Date end:</label></th><td><input id="date_end" type="text" size="10" maxlength="10" name="date_end" value="${dt2.strftime('%d.%m.%Y')}"/>
<input id="time_end" type="text" size="5" maxlength="5" name="time_end" value="${dt2.strftime('%H:%M')}"/> ${dt2.tzinfo.tzname(None)}</td>
</tr>
<tr><th><label for="location">Locations:</label></th>
<td><select id="location" name="location_id" size="1">
<option py:for="location in locations" selected="${location.location_id == event.location_id and 'checked' or None}" value="${location.location_id}">${location.name} :${location.coordinate_str()}</option>
</select>
<a py:if="'LOCATION_MODIFY' in perm" href="${href.location(from_='event', id=event.e_id)}">edit/search locations</a></td>
</tr>
<tr><td></td><td py:choose="" test=""><a py:when="event.e_id" class="buttonlike" href="${href.event('recurrency', event.e_id)}">show recurrency options</a>
<input py:otherwise="" type="checkbox" id="show_recurrency" name="show_recurrency"/><label for="show_recurrency">show recurrency options</label></td>
</tr>
</table>
</fieldset>
<fieldset>
<legend >Tags</legend>
<div>
<input id="tags" type="text" size="70" maxlength="70" name="tags" value="${event.tags}"/>
</div>
</fieldset>
<div class="mybuttons">
<input type="reset" value="Reset"/>
<py:choose test="">
<py:when test="event.event_id != 0">
<input type="submit" name="edit" value="${event.e_id == 0 and 'add' or 'edit'}"/>
<input py:if="'EVENTS_DELETE' in perm and event.e_id != 0" type="submit" name="delete" value="delete"/>
</py:when>
<input py:otherwise="" type="submit" name="add" value="add"/>
</py:choose>
</div>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,28 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="layout.html" />
<xi:include href="event_display.html" />
<head>
<title>${title}</title>
</head>
<body>
<div id="content" class="event">
<div id="event-overview">
<h1>${title}</h1>
<div py:for="ix,event in enumerate(events)"
id="event:${event.e_id}" class="event-item">
<py:choose test=""><div py:when="event.periodic() or event.initial_e_id==True" class="recurring">C<br/>I<br/>R<br/>C<br/>U<br/>L<br/>A<br/>R</div>
<div py:otherwise="" class="unique">S<br/>I<br/>N<br/>G<br/>U<br/>L<br/>A<br/>R</div>
</py:choose>
<div class="event-intern">
${render_event(event, True)}
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,45 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:i18n="http://genshi.edgewall.org/i18n"
i18n:domain="tracrendezvous">
<xi:include href="layout.html" />
<xi:include href="event_display.html" />
<head>
<title>${title}</title>
</head>
<body>
<div id="content">
<py:choose test="">
<py:when test="table">
<h1>${title}</h1>
<h2>${title2}</h2>
<table class="upcoming">
<thead><tr><th py:for="h in headers">${h}</th></tr></thead>
<tfoot><tr><th py:for="h in headers">&nbsp;</th></tr></tfoot>
<tbody>
<tr py:for="ix,row in enumerate(table)" class="upcoming">
<td class="daytext"><a href="${href.event('by-day', row[0].strftime('%Y-%m-%d'))}">${row[0].strftime(format)}</a></td>
<py:for each="eventlist in row[1:]">
<td py:if="eventlist != False" class="${isinstance(eventlist, list) and 'upcoming-event' or 'upcoming'}" rowspan="${eventlist.rowspan and eventlist.rowspan or None}">
<py:if test="eventlist != True">
<py:for each="event in eventlist">
${render_event(event)}
</py:for>
</py:if>
</td>
</py:for>
</tr>
</tbody>
</table>
</py:when>
<py:otherwise>
<h2>${_("No events in this time frame. Create here")} <a href="${href.event('new', now.strftime('%Y-%m-%d'))}">${_(" a new event")}</a>&nbsp;!</h2>
</py:otherwise>
</py:choose>
</div>
</body>
</html>

View File

@ -0,0 +1,33 @@
BEGIN:VCALENDAR
PRODID://TracRendezVous-0.3 by Stefan Kögl //DE
VERSION:2.0
CALSCALE:GREGORIAN
CAL-ADDRESS:${abs_href.events("upcoming")}
METHOD:PUBLISH
X-WR-CALNAME:${calname}
X-WR-TIMEZONE:UTC
X-WR-CALDESC:${caldesc}
{% for event in events %}\
BEGIN:VEVENT
UID:${"%s-%d@%s" % (event.time_begin.strftime("%Y%m%dT%H%M%SZ"), event.e_id, abs_href())}
CREATED:${event.time_created.strftime("%Y%m%dT%H%M%SZ")}
DTSTAMP:${stamp}
LAST-MODIFIED:${event.time_modified.strftime("%Y%m%dT%H%M%SZ")}
SUMMARY:${event.name.replace(",", "\,")}
LOCATION:${event.location.name.replace(",", "\,")}
GEO:${event.location.lat};${event.location.lon}
URI:${abs_href.events(event.e_id)}
DESCRIPTION:more information:${abs_href.events(event.e_id)}
CLASS:PUBLIC
DTSTART:${event.time_begin.strftime("%Y%m%dT%H%M%SZ")}
DTEND:${event.time_end.strftime("%Y%m%dT%H%M%SZ")}
{% for rrule in event.rrules %}\
${rrule}
{% end %}\
{% for alarm in event.alarms %}\
${alarm}
{% end %}\
TRANSP:OPAQUE
END:VEVENT
{% end %}\
END:VCALENDAR

View File

@ -0,0 +1,141 @@
<!DOCTYPE htm
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="layout.html" />
<head>
<script type="text/javascript" src="${chrome.htdocs_location}js/wikitoolbar.js"></script>
<script type="text/javascript">
/* <![CDATA[ */
function toggleStatus() {
if ($('#is_periodic').is(':checked')) {
$('#recurrency-freq :input').removeAttr('disabled');
$('#recurrency-exceptions :input').removeAttr('disabled');
$('#recurrency-timeframe :input').removeAttr('disabled');
} else {
$('#recurrency-freq :input').attr('disabled', true);
$('#recurrency-exceptions :input').attr('disabled', true);
$('#recurrency-timeframe :input').attr('disabled', true);
}
}
jQuery(document).ready(function($) {
$("#until_date").datepicker({"dateFormat" : "dd.mm.yy"});
var ix = 2;
switch(${freq}){
case 0:
case 1:
case 2:
ix = 0;
break;
case 3:
case 4:
ix = 1;
break;
case 5:
ix = 2;
break;
case 6:
ix = 3;
break;
}
$("#accordion").accordion({header: "h3"}).accordion('activate', ix);
$("#exception_name").datepicker({"dateFormat" : "dd.mm.yy"});
toggleStatus();
});
/* ]]> */
</script>
<title>${'Edit Recurrency'}</title>
</head>
<body>
<div id="content">
<h1>Recurrency Options for event '${event.name}'</h1>
<h2 py:with="dt = selected_tz.fromutc(event.time_begin);
dt2 = selected_tz.fromutc(event.time_end)">${dt.strftime("%A, %d.%m.%Y %H:%M")} ${dt.tzinfo.tzname(None)} - ${dt2.strftime("%A, %d.%m.%Y %H:%M")} ${dt2.tzinfo.tzname(None)}</h2>
<form name="recurrence" id='recurrence' uri="" method="post" mime-type="text/plain" action="">
<fieldset>
<div class="mybuttons">
<input type="checkbox" id="is_periodic" name="is_periodic" value="${event.is_periodic}" checked="${event.is_periodic and 'checked' or None}" onchange="toggleStatus()"/><label for="is_periodic"> Repeat this event</label>
</div>
<fieldset id="recurrency-freq"><legend>Recurrency frequency</legend>
<div id="accordion">
<div>
<h3><a href="#">Yearly</a></h3>
<table>
<tr><td>Repeat every <input type="text" size="3" maxlength="3" name="yearinterval" value="${period.interval}"/> year(s)</td></tr>
<tr><td><input type="radio" name="freq" value="0" checked="${freq == 0 and 'checked' or None}"/><label for="monthday-yearly-0">Repeat on day</label>
<input type="text" size="3" maxlength="3" id="monthday-yearly-0" name="monthday-yearly-0" value="${period.bymonthday}"/>
<label for="monthname-yearly-0">in</label>
<select name="monthname-yearly-0"><option py:for="ix, x in enumerate(month_names)" value="${ix}" selected="${period.bymonth == ix and 'selected' or None}">${x}</option></select></td>
</tr>
<tr><td><input type="radio" name="freq" value="1" checked="${freq == 1 and 'checked' or None}"/><label for="dayocc-yearly-1">Repeat on</label>
<select id="dayocc-yearly-1" name="dayocc-yearly-1"><option py:for="ix, x in enumerate(weekday_names)" value="${ix}" selected="${period.byweekdayocc == ix and 'selected' or None}">${x}</option></select>
<select name="weekday-yearly-1"><option py:for="ix, x in enumerate(day_names)" value="${ix}" selected="${period.byweekday == ix and 'selected' or None}">${x}</option></select>&nbsp;in
<select name="monthname-yearly-1"><option py:for="ix, x in enumerate(month_names)" value="${ix}" selected="${period.bymonth == ix and 'selected' or None}">${x} </option></select></td>
</tr>
<tr><td><input type="radio" name="freq" value="2" checked="${freq == 2 and 'checked' or None}"/><label for="yearday">Repeat on day no.</label><input type="text" size="3" maxlength="3" id="yearday" name="yearday" value="${period.byyearday}"/><label for="yearday">of the year</label></td></tr>
</table>
</div>
<div>
<h3><a href="#">Monthly</a></h3>
<div>
<table class="event-wizard">
<tr><td><label for="monthinterval">Repeat every</label>
<input type="text" size="3" maxlength="3" id="monthinterval" name="monthinterval" value="${period.interval}"/>
<label for="monthinterval">month(s)</label></td></tr>
<tr><td><input type="radio" name="freq" value="3" checked="${freq == 3 and 'checked' or None}"/><label for="monthday-monthly-0">Repeat on</label>
<select id="monthday-monthly-0" name="monthday-monthly-0"><option py:for="ix, x in enumerate(monthday_names)" value="${ix}">${x}</option></select>&nbsp;day of the month</td></tr>
<tr><td><input type="radio" name="freq" value="4" checked="${freq == 4 and 'checked' or None}"/><label for="dayocc-monthly-1">Repeat on</label>
<select id="dayocc-monthly-1" name="dayocc-monthly-1"><option py:for="ix, x in enumerate(weekday_names)" value="${ix}" selected="${period.byweekdayocc == ix and 'selected' or None}">${x}</option></select>
<select name="weekday-monthly-1"><option py:for="ix, x in enumerate(day_names)" value="${ix}" selected="${ix in period.byweekday and 'selected' or None}">${x}</option></select></td></tr>
</table>
</div>
</div>
<div>
<h3><a href="#">Weekly</a></h3>
<div>
<table class="event-wizard">
<tr><td><input type="radio" name="freq" value="5" checked="${freq == 5 and 'checked' or None}"/>Repeat every <input type="text" size="3" maxlength="3" name="weekinterval" value="${period.interval}"/> week(s)</td></tr>
<tr>
<td>
<py:for each="ix, x in enumerate(day_names)"><input type="checkbox" py:with="myid='weekday:%d' % ix" id="{$myid}" name="weekday-weekly" value="${ix}" checked="${ix in period.byweekday and 'selected' or None}"/><label for="{$myid}">${x}</label></py:for></td></tr>
</table>
</div>
</div>
<div>
<h3><a href="#">Daily</a></h3>
<div>
<table class="event-wizard">
<tr><td><input type="radio" name="freq" value="6" checked="${freq == 6 and 'checked' or None}"/> Repeat every <input type="text" size="3" maxlength="3" name="dayinterval" value="${period.interval}"/> day(s)</td></tr>
</table>
</div>
</div>
</div>
</fieldset>
<fieldset id="recurrency-timeframe"><legend>Recurrency Timeframe</legend>
<table class="event-wizard">
<tr><td><label for="repeatframe2">Finish after </label><input id="count" type="text" size="3" maxlength="3" name="count" value="${period.count}"/> Repetitions</td></tr>
<tr><td><label for="repeatframe3">End on:</label><input id="until_date" type="text" size="10" maxlength="10" name="until_date" value="${period.until and period.until.strftime('%d.%m.%Y') or None}"/></td>
</tr>
</table>
</fieldset>
<fieldset id="recurrency-exceptions"><legend>Recurrency Exceptions</legend>
<table class="event-wizard">
<tr><td class="left"><input type="text" id="exception_name" name="exception-name" size="10" value="${exception_name}"/></td>
<td rowspan="4"><ul id="exceptions">
<li py:for="exception in exceptions"><input type="checkbox" name="exception:${exception.erd_id}"/>&nbsp;${exception.erd_datetime.strftime('%d.%m.%Y')}</li></ul>
</td></tr>
<tr><td class="left"><input type="submit" name="exception-add" value="add"/></td></tr>
<tr><td class="left"><input type="submit" name="exception-edit" value="edit"/></td></tr>
<tr><td class="left"><input type="submit" name="exception-delete" value="del"/></td></tr>
</table>
</fieldset>
<div class="mybuttons">
<input type="submit" name="save" value="save"/>
</div>
</fieldset>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,35 @@
<?xml version="1.0"?>
<rss version="2.0" xmlns:py="http://genshi.edgewall.org/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<channel>
<title>${project.name}: #${title}</title>
<link>${abs_href.event("upcoming")}</link>
<description>Die kommenden Termine im und Rund um den 'CTDO'</description>
<language>de-de</language>
<image py:if="chrome.logo.src">
<title>$project.name</title>
<url>${abs_href.chrome(chrome.logo.src)}</url>
<link>${abs_href.chrome(chrome.logo.src)}</link>
</image>
<generator>TracRendezvous</generator>
<item py:for="key,value in events">
<pubDate>${http_date(today)}</pubDate>
<title>$event.name</title>
<link>${abs_href.events(event.srv_id)}</link>
<guid isPermaLink="false">${abs_href.events(event.srv_id)}</guid>
<description>
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;When:&lt;/strong&gt; &lt;em&gt;${event.time_begin.strftime("%d.%m.%Y %Z")} - ${event.time_end.strftime("%d.%m.%Y %Z")}&lt;/em&gt;&lt;/li&gt;
<py:if test="event.location">&lt;li&gt;&lt;strong&gt;Location:&lt;/strong&gt; &lt;em&gt;$event.location.name&lt;/em&gt;&lt;/li&gt;</py:if>
<py:if test="event.type_name">&lt;li&gt;&lt;strong&gt;Type:&lt;/strong&gt; &lt;em&gt;$event.type_name&lt;/em&gt;&lt;/li&gt;</py:if>
<py:if test="event.tags">&lt;li&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;em&gt;$event.tags&lt;/em&gt;&lt;/li&gt;</py:if>
<py:if test="event.attendees">&lt;li&gt;&lt;strong&gt;Attendees:&lt;/strong&gt; &lt;em&gt;$event.attendees&lt;/em&gt;&lt;/li&gt;</py:if>
&lt;/ul&gt;
</description>
<category>Upcoming Events</category>
</item>
</channel>
</rss>

View File

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
import doctest
import unittest
from tracrendezvous.event.tests import model
def suite():
suite = unittest.TestSuite()
suite.addTest(model.suite())
return suite
if __name__ == '__main__':
import sys
if '--skip-functional-tests' in sys.argv:
sys.argv.remove('--skip-functional-tests')
INCLUDE_FUNCTIONAL_TESTS = False
unittest.main(defaultTest='suite')

View File

@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
import unittest
from datetime import datetime, timedelta
from trac.util.datefmt import utc, to_timestamp
from trac.test import EnvironmentStub, Mock
from trac.search.api import *
from tracrendezvous.event.model import Event, EventModelProvider
from tracrendezvous.location.model import ItemLocation, LocationModelProvider
class EventTestCase(unittest.TestCase):
def setUp(self):
self.env = EnvironmentStub(default_data=True, enable=['trac.*', 'tracrendezvous.*'])
l = EventModelProvider(self.env).environment_created()
l2 = LocationModelProvider(self.env).environment_created()
loc1 = ItemLocation(self.env, 0, u"CTDO, Langer August", "N", 51, 31, 39.4, "E", 7, 27, 53.8, 51.527611, 7.464922)
loc1.commit()
self.created = datetime.now(utc)
self.created = self.created.replace(microsecond=0)
self.time_begin = datetime(2009, 3, 1, 17, 30, tzinfo=utc)
self.time_end = datetime(2009, 3, 1, 22, 30, tzinfo=utc)
loc1 = Event(self.env, 0, "name", self.created, self.created, self.time_begin, self.time_end, 1, initial_e_id=0, tags="a b c", attendees="a b c", is_periodic=True, wikipage="foobar")
loc1.commit()
def test_1_commit(self):
self.env.get_db_cnx().cursor().execute("select name from events;").fetchall()
def test_2_fetch_one(self):
event = Event.fetch_one(self.env, 1)
self.assertEqual(1, event.e_id)
self.assertEqual(u"name", event.name)
self.assertEqual(self.created, event.time_created)
self.assertEqual(self.created, event.time_modified)
self.assertEqual(self.time_begin, event.time_begin)
self.assertEqual(self.time_begin, event.time_begin)
self.assertEqual(self.time_end, event.time_end)
self.assertEqual(1, event.location_id)
self.assertEqual(0, event.initial_e_id)
self.assertEqual("a b c", event.tags)
self.assertEqual("a b c", event.attendees)
self.assertEqual(True, event.is_periodic)
self.assertEqual("foobar", event.wikipage)
def test_3_exists(self):
self.assertEqual(True, Event.exists(self.env, 1, self.time_begin, self.time_end))
self.assertEqual(False, Event.exists(self.env, 1, datetime(2009,4,1,17,20,tzinfo=utc), datetime(2009,3,1,17,20,tzinfo=utc)))
self.assertEqual(False, Event.exists(self.env, 1, datetime(2009,3,1,17,20,tzinfo=utc), datetime(2009,4,1,17,20,tzinfo=utc)))
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(EventTestCase, 'test'))
return suite
if __name__ == '__main__':
unittest.main(defaultTest='suite')

View File

@ -0,0 +1,999 @@
# -*- coding: utf-8 -*-
import re, math
from datetime import datetime, timedelta, time
from os import mkdir
from os.path import join
from re import match, sub
from collections import defaultdict
from sqlite3 import IntegrityError
from operator import attrgetter
from trac.config import *
from trac.mimeview.api import Mimeview, IContentConverter, Context
from trac.core import Component, implements, TracError
from trac.resource import IResourceManager, Resource, get_resource_url
from trac.perm import PermissionError, IPermissionRequestor
from trac.search import ISearchSource, search_to_sql, shorten_result
from trac.util import get_reporter_id, Ranges
from trac.util.text import to_unicode
from trac.util.html import html
from trac.util.datefmt import get_timezone, utc, format_time, localtz
#from trac.util.translation import _, add_domain
from trac.web.chrome import INavigationContributor, ITemplateProvider, add_stylesheet, add_warning, add_notice, add_ctxtnav, add_script, add_link, Chrome
from trac.web import IRequestHandler
from trac.wiki import IWikiSyntaxProvider
from dateutil import rrule
from genshi.builder import tag
from genshi.template import TemplateLoader, NewTextTemplate
from tractags.api import TagSystem, ITagProvider
from tracrendezvous.location.model import ItemLocation
from tracrendezvous.event.model import Event, EventRRule, EventRDate
#from tracrendezvous.event.parse_ical import parse_ical
from ctdotools.utils import gen_wiki_page, validate_id, time_parse, datetime_parse, date_parse, get_tz, local_to_utc
from trac.util.translation import domain_functions
add_domain, _, tag_ = domain_functions('tracrendezvous', ('add_domain', '_', 'tag_'))
import tracrendezvous
__all__ = ['EventModule','EventTagProvider']
class EventTagProvider(Component):
"""A tag provider using Events tag field as sources of tags.
"""
implements(ITagProvider)
# ITagProvider methods
def get_taggable_realm(self):
return 'event'
def get_tagged_resources(self, req, tags):
split_into_tags = TagSystem(self.env).split_into_tags
db = self.env.get_db_cnx()
cursor = db.cursor()
args = []
sql = "SELECT * FROM (SELECT e_id, tags, COALESCE(tags, '') as fields FROM events)"
constraints = []
if tags:
constraints.append(
"(" + ' OR '.join(["fields LIKE %s" for t in tags]) + ")")
args += ['%' + t + '%' for t in tags]
else:
constraints.append("fields != ''")
if constraints:
sql += " WHERE %s" % " AND ".join(constraints)
sql += " ORDER BY e_id"
self.env.log.debug(sql)
cursor.execute(sql, args)
for row in cursor:
id, ttags = row[0], ' '.join([f for f in row[1:-1] if f])
event_tags = split_into_tags(ttags)
tags = set([to_unicode(x) for x in tags])
if (not tags or event_tags.intersection(tags)):
yield Resource('event', id), event_tags
def get_resource_tags(self, req, resource):
if 'EVENTS_VIEW' not in req.perm(resource):
return
event = Event.fetch_one(self.env, resource.id)
tags = self.__event_tags(event)
return tags
def set_resource_tags(self, req, resource, tags):
req.perm.require('EVENTS_MODIFY', resource)
split_into_tags = TagSystem(self.env).split_into_tags
event = Event.fetch_one(self.env, resource.id)
all_ = self.__event_tags(event)
tags.difference_update(all_.difference(event.tags))
event.tags = u' '.join(sorted(map(to_unicode, tags)))
event.update()
def remove_resource_tags(self, req, resource):
req.perm.require('EVENTS_MODIFY', resource)
event = Event.fetch_one(self.env, resource.id)
event.tags = None
event.update()
# Private methods
def __event_tags(self, rendezvous):
return TagSystem(self.env).split_into_tags(rendezvous.tags)
class EventModule(Component):
'''The web ui frontend for the event management system'''
Option("event", "upcoming_name", _(u"Upcoming Events"))
Option("event", "upcoming_desc", _(u"Hier erfährst Du direkt, was in den nächsten 6 Monaten im Chaostreff los ist."))
Option("event", "wiki_content", _(u"Feel free to fill this outer space with content descibing the actual event.[[BR]]If the event is recurrent, describe the overall topics or similarities here and put the date dependant stuff into the wikipages you find via the links on the right side."))
implements(IRequestHandler,
IResourceManager,
IWikiSyntaxProvider,
IContentConverter,
IPermissionRequestor,
INavigationContributor,
ISearchSource,
ITemplateProvider)
m1 = re.compile(r'^/event/(\d+)$')
m2 = re.compile(r'^/event/edit/(\d+)$')
m3 = re.compile(r'^/event/recurrency/(\d+)$')
m4 = re.compile(r'^/event/by-day/(\d{4})-(\d{1,2})-(\d{1,2})$')
m5 = re.compile(r'^/event/new/(\d{4})-(\d{1,2})-(\d{1,2})$')
m6 = re.compile(r'^/event/createpage/(\d+)/(\d{4})-(\d{1,2})-(\d{1,2})$')
def __init__(self, parent=None):
from pkg_resources import resource_filename
locale_dir = resource_filename(tracrendezvous.__name__, 'locale')
add_domain(self.env.path, locale_dir)
# INavigationContributor methods
def get_active_navigation_item(self, req):
return 'event'
def get_navigation_items(self, req):
if "EVENTS_VIEW" in req.perm:
yield ('mainnav', 'event', html.A(_('Upcoming Events'), href=req.href.event("upcoming")))
def get_permission_actions(self):
'''returns all permissions this component provides'''
return ['EVENTS_ADD', 'EVENTS_DELETE', 'EVENTS_MODIFY', 'EVENTS_VIEW',
('EVENTS_ADMIN',
('EVENTS_ADD', 'EVENTS_DELETE', 'EVENTS_MODIFY', 'EVENTS_VIEW'))]
def match_request(self, req):
self.env.log.debug("EventModule.match_request %s\n" % req)
key = req.path_info
simple_paths = ("/event/upcoming", '/event', "/event/new")
if key in simple_paths:
return True
m = self.m1.match(key)
if m:
req.args["e_id"] = int(m.group(1))
return True
m = self.m2.match(key)
if m:
req.args["e_id"] = int(m.group(1))
return True
m = self.m3.match(key)
if m:
req.args["e_id"] = int(m.group(1))
return True
m = self.m4.match(key)
if m:
req.args['arg_date'] = datetime(int(m.group(1)),int(m.group(2)),int(m.group(3)), 17, tzinfo=utc)
return True
m = self.m5.match(key)
if m:
req.args['arg_date'] = datetime(int(m.group(1)),int(m.group(2)),int(m.group(3)), 17, tzinfo=utc)
return True
m = self.m6.match(key)
if m:
req.args["e_id"] = int(m.group(1))
req.args['arg_date'] = datetime(int(m.group(2)),int(m.group(3)),int(m.group(4)), tzinfo=utc)
return True
return False
# ITemplateProvider methods
def get_templates_dirs(self):
from pkg_resources import resource_filename
return [resource_filename(__name__, 'templates')]
def get_htdocs_dirs(self):
"""Return a list of directories with static resources (such as style
sheets, images, etc.)
Each item in the list must be a `(prefix, abspath)` tuple. The
`prefix` part defines the path in the URL that requests to these
resources are prefixed with.
The `abspath` is the absolute path to the directory containing the
resources on the local file system.
"""
from pkg_resources import resource_filename
return [('hw', resource_filename(__name__, 'htdocs')),
('parent', resource_filename(tracrendezvous.__name__, 'htdocs'))]
def process_request(self, req):
query = req.path_info
if "/event/upcoming" == query:
return self.__display_upcoming_events(req)
elif "/event/by-day" in query:
return self.__display_events_by_day(req)
elif "/event/new" in query:
return self.__process_event(req)
elif "/event/recurrency" in query:
return self.__process_recurrency(req)
elif "/event/edit" in query:
return self.__process_event(req)
elif "/event/createpage" in query:
return self.__create_wiki_page(req)
elif "/event" in query:
return self.__display_events(req)
else:
return self.__display_upcoming_events(req)
# IContentConverter methods
def get_supported_conversions(self):
#yield ('rss', _('RSS Feed'), 'xml',
#'tracrendezvous.Event', 'application/rss+xml', 8)
yield ('ical', _('Ical Feed'), 'ics',
'tracrendezvous.Event', 'text/calendar', 8)
def convert_content(self, req, mimetype, cal, key):
#if key == 'rss':
#return self._export_upcoming_events_rss(req, ticket)
if key == 'ical':
return self.__export_upcoming_events_ical(req, cal)
# IResourceManager methods
def get_resource_realms(self):
yield 'event'
def get_resource_description(self, resource, format=None, context=None,
**kwargs):
if format == 'compact':
return 'Event #%s' % resource.id
from tracrendezvous.event.model import Event
event = Event.fetch_one(self.env, resource.id)
# TODO: really UTC?
return "Event #%d - %s (%s UTC - %s UTC)" % (event.e_id, event.name, event.time_begin, event.time_end)
# ISearchSource methods
def get_search_filters(self, req):
if 'EVENTS_VIEW' in req.perm:
yield ('event', _('Events'))
def get_search_results(self, req, terms, filters):
if not 'event' in filters:
return
db = self.env.get_db_cnx()
sql_query, args = search_to_sql(db, ['e1.name','e1.author'],
terms)
cursor = db.cursor()
cursor.execute("SELECT e1.e_id, e1.name, e1.author, e1.time_modified "
"FROM events e1 "
"WHERE " + sql_query, args)
event_realm = Resource('event')
for e_id, name, author, ts in cursor:
event = event_realm(id=e_id)
yield (get_resource_url(self.env, event, req.href),
name,
datetime.fromtimestamp(ts, utc), author,
_("Click the link"))
# IWikiSyntaxProvider methods
def get_wiki_syntax(self):
return []
def get_link_resolvers(self):
yield ('event', self._format_event_link)
# private methods
def __process_recurrency(self, req):
req.perm.require("EVENTS_MODIFY")
e_id = req.args["e_id"]
add_ctxtnav(req, _('Back to') + _('Overview'), req.href.event())
add_ctxtnav(req, _('Back to') + _('Event') + ' #%s' % e_id, req.href.event('edit', e_id))
add_stylesheet (req, 'hw/css/ui.all.css')
add_stylesheet (req, 'hw/css/event.css')
add_script (req, 'hw/scripts/jquery-ui-1.6.custom.min.js')
if req.session.has_key("edited"):
#add_notice(req, tag.p(_("Recurrency rule created successfully. Back to "), tag.a(_("Event"), href=req.href.event(event.e_id))))
del req.session["edited"]
req.session.save()
if req.session.has_key("added"):
#add_notice(req, tag.p(_("Recurrency rule created successfully. Back to "), tag.a(_("Event"), href=req.href.event(event.e_id))))
del req.session["added"]
req.session.save()
if req.session.has_key("event_added"):
add_notice(req, tag.p(_("Event created successfully.") + _("Back to "), tag.a(_("Event"), href=req.href.event(event.e_id))))
del req.session["event_added"]
req.session.save()
if req.session.has_key("deleted"):
#add_notice(req, tag.p(_("Recurrency rule deleted successfully. Back to "), tag.a(_("Event"), href=req.href.event(event.e_id))))
del req.session["deleted"]
req.session.save()
session_tzname, selected_tz = get_tz(req.session.get('tz', self.env.config.get("trac", "default_timezone") or None))
event = Event.fetch_one(self.env, e_id)
if not event:
raise TracError(_("Event not found"))
try:
period = EventRRule.fetch_by_event(self.env, e_id)[0]
exists = True
except IndexError:
period = EventRRule(self.env, e_id=event.e_id)
exists = False
freq = 0
if req.method == "POST":
if req.args.has_key("save"):
is_valid = True
is_periodic = req.args.has_key("is_periodic")
if event.is_periodic and not is_periodic:
EventRRule.delete(self.env, event.e_id)
EventRDate.delete(self.env, event.e_id)
event.is_periodic = False
event.update()
req.redirect(req.href.event("recurrency", event.e_id))
elif not event.is_periodic and is_periodic:
event.is_periodic = True
event.update()
def get_bymonth(req, name, period):
try:
period.bymonth = int(req.args[name])
if not (0 <= period.bymonth < 12):
raise Exception()
except Exception:
add_warning(req, _("Wrong Value for 'month'."))
raise
def get_bymonthday(req, name, period):
try:
period.bymonthday = int(req.args[name])
if not (-33 <= period.bymonthday < 32):
raise Exception()
except Exception, e:
add_warning(req, _("Wrong Value for 'monthday'."))
raise
def get_byweekday(req, name, period):
try:
period.byweekday = set((int(req.args[name]),))
if filter(lambda x: not (0 <= x < 7), period.byweekday):
raise Exception()
except Exception, e:
add_warning(req, _("Wrong Value for 'weekday'."))
raise
def get_byweekdayocc(req, name, period):
byweekdayocc = req.args[name]
if byweekdayocc:
try:
period.byweekdayocc = int(byweekdayocc)
except Exception, e:
add_warning(req, _("Wrong Value for 'weekday occurence'."))
raise
else:
period.byweekdayocc = None
def get_interval(req, name, period):
try:
period.interval = int(req.args[name])
if not (0 <= period.interval < 1000):
raise Exception()
except Exception, e:
add_warning(req, _("Wrong Value for interval"))
raise
# std parameters
count = req.args["count"]
if count:
try:
period.count = int(count)
except Exception, e:
is_valid = False
add_warning(req, _("Wrong Value for count"))
else:
period.count = None
until_date = req.args["until_date"]
if until_date:
try:
until = date_parse(until_date)
period.until = datetime.combine(until, time(12,tzinfo=utc))
except Exception, e:
add_warning(req, _("Wrong Value for until_date or until_time"))
raise
else:
period.until = None
# selective parameters
try:
freq = int(req.args["freq"])
except KeyError:
add_warning(req, _("Please select one of the recurrency types"))
is_valid = False
if freq == 0:
period.freq = rrule.YEARLY
period.byweekday = list()
period.byweekdayocc = None
period.byyearday = None
try:
get_interval(req, "yearinterval", period)
get_bymonthday(req, "monthday-yearly-0", period)
get_bymonth(req, "monthname-yearly-0", period)
except Exception:
is_valid = False
elif freq == 1:
period.freq = rrule.YEARLY
get_interval(req, "yearinterval", period)
period.bymonthday = None
period.byyearday = None
try:
get_byweekdayocc(req, "dayocc-yearly-1", period)
get_byweekday(req, "weekday-yearly-1", period)
get_bymonth(req, "monthname-yearly-1", period)
except Exception:
is_valid = False
elif freq == 2:
period.freq = rrule.YEARLY
try:
get_interval(req, "yearinterval", period)
except Exception:
is_valid = False
period.bymonthday = None
period.bymonth = None
period.byweekday = list()
period.byweekdayocc = None
try:
period.byyearday = int(req.args["yearday"])
if not (0 <= period.byyearday < 366):
raise Exception()
except Exception, e:
add_warning(req, _("Wrong Value for 'weekday'."))
is_valid = False
elif freq == 3:
period.freq = rrule.MONTHLY
try:
get_interval(req, "monthinterval", period)
get_bymonthday(req, "monthday-monthly-0", period)
except Exception:
is_valid = False
period.byweekday = None
period.byweekdayocc = None
period.byyearday = None
period.bymonth = None
elif freq == 4:
period.freq = rrule.MONTHLY
period.byyearday = None
period.bymonth = None
period.bymonthday = None
try:
get_interval(req, "monthinterval", period)
get_byweekdayocc(req, "dayocc-monthly-1", period)
get_byweekday(req, "weekday-monthly-1", period)
except Exception:
is_valid = False
elif freq == 5:
period.freq = rrule.WEEKLY
try:
get_interval(req, "weekinterval", period)
except Exception:
is_valid = False
period.byweekday = list()
period.byweekdayocc = None
period.byyearday = None
period.bymonth = None
period.bymonthday = None
try:
weekdays = set(map(int, req.args["weekday-weekly"]))
if any(map(lambda x: not (0 <= x < 7), weekdays)):
is_valid = False
if weekdays != period.byweekday:
period.byweekday = weekdays
except KeyError:
add_warning(req, _("Any days selected."))
is_valid = False
elif freq == 6:
period.freq = rrule.DAILY
try:
get_interval(req, "dayinterval", period)
except Exception:
is_valid = False
period.byweekday = None
period.byweekdayocc = None
period.byyearday = None
period.bymonth = None
period.bymonthday = None
if is_valid:
if exists:
period.update()
else:
period.commit()
req.redirect(req.href.event("recurrency", e_id))
elif req.args.has_key("exception-add"):
try:
exception = datetime_parse("%s 12:00" % req.args["exception-name"], selected_tz)
exception = exception.replace(hour=event.time_begin.hour, minute=event.time_begin.minute)
except ValueError:
add_warning(req, _("Wrong recurrency exception date format"))
req.redirect(req.href.event("recurrency", e_id))
rdate = EventRDate(self.env, 0, event.e_id, True, exception)
rdate.commit()
req.session["added"] = True
req.redirect(req.href.event("recurrency", e_id))
elif req.args.has_key("exception-edit"):
for k in req.args.iterkeys():
try:
marker, erd_id = k.split(":", 1)
except Exception,e:
continue
try:
erd_id = int(erd_id)
validate_id(erd_id)
except ValueError,e:
continue
if marker != "exception":
continue
rdate = EventRDate.fetch_one(self.env, erd_id)
try:
exception = datetime_parse("%s 12:00" % req.args["exception-name"], selected_tz)
exception = exception.replace(hour=event.time_begin.hour, minute=event.time_begin.minute)
except KeyError:
add_warning(req, _("You have to specify a valid recurrency exception date for that operation!"))
req.redirect(req.href.event("recurrency", e_id))
if exception:
rdate.erd_datetime = exception
rdate.update()
req.session["edited"] = True
req.redirect(req.href.event("recurrency", e_id))
req.redirect(req.href.event("recurrency", e_id))
elif req.args.has_key("exception-delete"):
for k in req.args.iterkeys():
try:
marker, erd_id = k.split(":", 1)
except Exception,e:
continue
try:
erd_id = int(erd_id)
validate_id(erd_id)
except ValueError,e:
continue
if marker != "exception":
continue
EventRDate.delete(self.env, erd_id)
req.session["deleted"] = True
req.redirect(req.href.event("recurrency", e_id))
else:
if period.bymonthday!=None and period.bymonth!=None:
freq = 0
elif period.byweekday and period.bymonth!=None:
freq = 1
elif period.byyearday!=None:
freq = 2
elif period.bymonthday!=None:
freq = 3
elif period.byweekdayocc!=None and period.byweekday:
freq = 4
elif period.freq == rrule.WEEKLY:
freq = 5
elif period.freq == rrule.DAILY:
freq = 6
exceptions = EventRDate.fetch_by_event(self.env, event.e_id)
return "recur_edit.html", {
"event" : event,
"freq" : freq,
"period" : period,
"session_tzname" : session_tzname,
"selected_tz" : selected_tz,
"exceptions" : exceptions,
"default_location" : self.config.getint("location", "default_location"),
"locations" : ItemLocation.fetch_all(self.env),
"month_names" : EventRRule.month_names,
"day_names" : EventRRule.day_names,
"weekday_names" : EventRRule.weekday_names,
"monthday_names" : EventRRule.monthday_names}, None
def __process_event(self, req):
'''process add, change and delete rendezvous'''
if 'EVENTS_ADD' not in req.perm and 'EVENTS_MODIFY' not in req.perm and 'EVENTS_DELETE' not in req.perm:
raise PermissionError()
add_stylesheet (req, 'hw/css/event.css')
add_stylesheet (req, 'hw/css/ui.all.css')
add_script (req, 'hw/scripts/jquery-ui-1.6.custom.min.js')
# set information after redirects
if req.session.has_key("edited"):
add_notice(req, tag.p(_("Event edited successfully. Back to "), tag.a(_("Overview"), href=req.href.event())))
del req.session["edited"]
req.session.save()
if req.session.has_key("added"):
add_notice(req, tag.p(_("Event created successfully. Back to "), tag.a(_("Overview"), href=req.href.event())))
del req.session["added"]
req.session.save()
if req.session.has_key("deleted"):
add_notice(req, tag.p(_("Event deleted successfully. Back to "), tag.a(_("Overview"), href=req.href.event())))
del req.session["deleted"]
req.session.save()
myenv = self.env
date_now = datetime.now(utc)
session_tzname, selected_tz = get_tz(req.session.get('tz', self.config.get("trac", "default_timezone") or None))
if req.args.has_key("e_id"):
event = Event.fetch_one(myenv, req.args["e_id"])
if not event:
raise TracError(_("Event not found"))
add_ctxtnav(req, _('Events Overview'), req.href.event())
add_ctxtnav(req, _('Upcoming Events'), req.href.event("upcoming"))
add_ctxtnav(req, _('Add New Event'), req.href.event("new"))
else:
try:
pi = req.path_info
crap, new_date = pi.split("new/", 1)
year, month, day = re.match("(\d{4})-(\d{1,2})-(\d{1,2})", new_date).groups()
new_date = datetime(int(year), int(month), int(day), 17, tzinfo=utc)
except ValueError:
new_date = date_now
event = Event(myenv, 0, "", req.authname, date_now, date_now, new_date, new_date + timedelta(0,10800), 1)
add_ctxtnav(req, _('Events Overview'), req.href.event())
add_ctxtnav(req, _('Upcoming Events'), req.href.event("upcoming"))
if req.method == "POST":
if req.args.has_key("delete"):
req.perm.require("EVENTS_DELETE")
EventRRule.delete(myenv, event.e_id)
EventRDate.delete(myenv, event.e_id)
Event.delete(myenv, event.e_id)
db = myenv.get_db_cnx()
cursor = db.cursor()
# TODO: that is hackish. find out how to use trac arg formatting
# of "select * from foo where bar like 'baz%';"
like = "delete from wiki where name like 'events/%s/%%'" % int(event.e_id)
cursor.execute(like)
db.commit()
req.session["deleted"] = True
req.redirect(req.href.event("new"))
is_valid = True
event.name = req.args.get("name", None)
try:
event.old_time_begin = event.time_begin
event.time_begin = datetime_parse("%s %s" % (req.args["date_begin"], req.args["time_begin"]), selected_tz)
except Exception:
add_warning(req, _("Wrong format in 'Date begin'."))
is_valid = False
try:
event.old_time_end = event.time_end
event.time_end = datetime_parse("%s %s" % (req.args["date_end"], req.args["time_end"]), selected_tz)
except Exception, e:
add_warning(req, _("Wrong format in 'Date end'."))
is_valid = False
try:
event.location_id = int(req.args["location_id"])
location = ItemLocation.fetch_one(myenv, event.location_id)
except Exception, e:
add_warning(req, _("Could not find location"))
is_valid = False
tags = req.args.get("tags", None)
if tags:
event.tags = sub("[,;.:]", " ", tags)
attendees = req.args.get("attendees", None)
if attendees:
attendees = sub("[,;.:]", " ", attendees)
if is_valid:
if not req.args.has_key("e_id"):
req.perm.require("EVENTS_ADD")
try:
self.__validate_event(event)
event.commit()
except Exception, e:
add_warning(req, str(e))
raise
event.wikipage = "events/%d" % event.e_id
event.update()
wikicontent = u" = %s =\n[[EventHeader(%d)]]\n\n%s" % (event.name, event.e_id, self.config.get("event", "wiki_content"))
try:
gen_wiki_page(myenv, req.authname, event.wikipage, wikicontent, req.remote_addr)
except IntegrityError, e:
add_warning(req, _("Wikipage already exists."))
if req.args.has_key("show_recurrency"):
req.redirect(req.href.event("recurrency", event.e_id))
req.session["event_added"] = True
req.session.save()
if req.args.has_key("show_recurrency"):
req.session["added"] = True
req.session.save()
req.redirect(req.href.event("edit", event.e_id))
else:
req.perm.require("EVENTS_MODIFY")
try:
self.__validate_event(event)
except Exception, e:
add_warning(req, str(e))
raise
else:
event.time_modified = datetime.now(utc)
event.update()
req.session["edited"] = True
req.session.save()
req.redirect(req.href.event("edit", event.e_id))
return "event_edit.html", {"event" : event,
"session_tzname" : session_tzname,
"selected_tz" : selected_tz,
"default_location" : self.config.getint("location", "default_location"),
"locations" : ItemLocation.fetch_all(myenv)}, None
def __display_events(self, req):
'''process add,change,delete actions'''
add_stylesheet (req, 'hw/css/event.css')
if "EVENTS_ADD" in req.perm:
add_ctxtnav(req, _('Add New Event'), req.href.event("new"))
add_ctxtnav(req, _('Upcoming Events'), req.href.event("upcoming"))
if req.args.has_key("e_id"):
events = [Event.fetch_one(self.env, req.args["e_id"], show_next=True)]
title = _("Event Details")
else:
events = Event.fetch_all_with_rrule(self.env)
events = sorted(events, key=attrgetter("time_begin"))
title = _("Event Overview")
session_tzname, selected_tz = get_tz(req.session.get('tz', self.env.config.get("trac", "default_timezone") or None))
return "event_list.html", {"events" : events, "title" : title, "session_tzname" : session_tzname, "selected_tz" : selected_tz}, None
def __display_upcoming_events(self, req):
if req.args.get("format") == 'ical':
events = Event.fetch_as_ical(self.env)
Mimeview(self.env).send_converted(req, 'tracrendezvous.Event', events, "ical", _("CTDO: Upcoming Events"))
add_stylesheet (req, 'hw/css/event.css')
if req.locale is not None:
add_script(req, 'parent/tracrendezvous/%s.js' % req.locale)
if "EVENTS_ADD" in req.perm:
add_ctxtnav(req, _('Add New Event'), req.href.event("new"))
add_ctxtnav(req, _('Events Overview'), req.href.event())
for conversion in Mimeview(self.env).get_supported_conversions("tracrendezvous.Event"):
conversion_href = req.href.event("upcoming", format=conversion[0])
add_link(req, 'alternate', conversion_href, conversion[1], conversion[3], conversion[0])
session_tzname, selected_tz = get_tz(req.session.get('tz', self.env.config.get("trac", "default_timezone") or None))
n = datetime.now(utc)
n = n.replace(hour=0, minute=0, second=0, microsecond=0)
end = n + timedelta(183)
end = end.replace(hour=23, minute=0, second=59, microsecond=999)
table, headers = self.__get_upcoming_table(n, end)
#ical_file = file("/home/hotshelf/icalout.ics")
#ical = parse_ical(self.env, ical_file)
return "events.html", {"table" : table, "headers" : headers, "session_tzname" : session_tzname, "format" : "%a, %d.%m.%Y", "selected_tz" : selected_tz, "title" : _("Upcoming Events for"), "title2" : "%s - %s" % (n.strftime('%A, %d.%m.%Y %H:%M'), end.strftime('%A, %d.%m.%Y %H:%M')), "now" : n, "end" : end}, None
def __display_events_by_day(self, req):
add_stylesheet (req, 'hw/css/event.css')
add_ctxtnav(req, _('Back to Upcoming Events'), req.href.event("upcoming"))
session_tzname, selected_tz = get_tz(req.session.get('tz', self.env.config.get("trac", "default_timezone") or None))
# do datetime calc only in utc !!!
n = title = req.args["arg_date"]
n = datetime(n.year, n.month, n.day, tzinfo=utc)
#n = local_to_utc(n, selected_tz)
e = n + timedelta(1)
if "EVENTS_ADD" in req.perm:
add_ctxtnav(req, _('Add New Event'), req.href.event("new", title.strftime("%Y-%m-%d")))
table, headers = self.__get_day_table(n, e)
return "event_details.html", {"table" : table, "headers" : headers, "format" : "%H:%M", "session_tzname" : session_tzname, "selected_tz" : selected_tz, "title" : _("Details of %s") % (title.strftime('%A, %d.%m.%Y'),), "now" : n, "end" : e}, None
def __export_upcoming_events_rss(self, req, foo):
now = datetime.now(utc)
now.replace(hour=0,second=0,microsecond=0)
end = now + timedelta(183)
end.replace(hour=23,second=59,microsecond=999)
events = Event.fetch_all(self.env)
data = {"events" : sorted(events, key=attrgetter("time_begin")), "today" : datetime.now(utc)}
output = Chrome(self.env).render_template(req, 'upcoming_events.rss', data,
'application/rss+xml')
return output, 'application/rss+xml'
def __export_upcoming_events_ical(self, req, events):
data = {"stamp" : datetime.now(utc).strftime("%Y%m%dT%H%M%SZ"),
"events" : sorted(events, key=attrgetter("time_begin")),
"today" : datetime.now(utc),
"calname" : self.env.config.get("events", "upcoming_name"),
"caldesc" : self.env.config.get("events", "upcoming_desc")}
ch = Chrome(self.env)
ds = ch.get_all_templates_dirs()
data = ch.populate_data(req, data)
templates = TemplateLoader(ds, auto_reload=self.env.config.getbool('trac', 'auto_reload'), variable_lookup='lenient')
template = templates.load("ical.txt", cls=NewTextTemplate)
try:
stream = template.generate(**data)
output = stream.render()
return output, 'text/calendar'
except Exception, e:
# give some hints when hitting a Genshi unicode error
if isinstance(e, UnicodeError):
pos = self._stream_location(stream)
if pos:
location = "'%s', line %s, char %s" % pos
else:
location = _("(unknown template location)")
raise TracError(_("Genshi %(error)s error while rendering "
"template %(location)s",
error=e.__class__.__name__,
location=location))
raise
def __validate_event(self, event):
if not event.name:
raise TypeError(_("EventValidationError: Title is empty"))
if event.time_end <= event.time_begin:
raise TypeError(_("EventValidationError: end time before or equal begin time"))
def __get_upcoming_table(self, timerange_begin, timerange_end):
# ongoing events goes in here referenced by column index
res = Event.fetch_by_period_dict(self.env, timerange_begin, timerange_end)
ongoing = {} # column index -> event
table = []
timerange_begin = timerange_begin.date()
timerange_end = timerange_end.date()
dt = timedelta(1)
column_count = 0
def get_next(d):
count = 1
while 1:
if not d.has_key(count):
return count
count += 1
while timerange_begin < timerange_end:
row = {0 : timerange_begin}
done = []
for key,event in ongoing.iteritems():
if event.time_end.date() < timerange_begin:
done.append(key)
for i in done:
del ongoing[i]
for key in ongoing.iterkeys():
row[key] = False
events = res[timerange_begin]
for event in events:
dt2 = event.time_end - event.time_begin
dt2_days = dt2.days + 1
if dt2.seconds > 43200:
dt2_days += 1
slot = get_next(ongoing)
event.rowspan = dt2_days
ongoing[slot] = event
row[slot] = [event,]
column_count = max(column_count, len(ongoing))
table.append(row)
timerange_begin += dt
table2 = []
mbase = [True for x in xrange(column_count+1)]
for row in table:
myrow = mbase[:]
for key,value in row.iteritems():
myrow[key] = value
table2.append(myrow)
mbase = ["" for x in xrange(column_count+1)]
mbase[0] = _("date")
return table2, mbase
def __create_wiki_page(self, req):
'''Programatically create a new wiki page tailored to a given occurence of an event
- one date of a recurrence set.
'''
req.perm.require("WIKI_CREATE")
dt = req.args["arg_date"]
e_id = req.args["e_id"]
wikipage = "events/%d/%s" % (e_id, dt.strftime("%Y-%m-%d"))
event = Event.fetch_one(self.env, e_id)
wikicontent = u" = %s =\n[[EventHeader(%d)]]\n\n%s" % (event.name, event.e_id, self.env.config.get("event", "wiki_content", ""))
try:
gen_wiki_page(self.env, req.authname, wikipage, wikicontent, req.remote_addr)
except IntegrityError, e:
pass
req.redirect(req.href.wiki(wikipage))
def __get_day_table(self, timerange_begin, timerange_end, dt=timedelta(0, 1800)):
''' Calculates and arranges events during a day period which can be defined
by start datetime 'timerange_begin' and end datetime 'timerange_end'.
The resolution of the "table" prepared for html or other UIs can be
controlled by timedelta 'dt'.
The first column contains the time slots, additional columns contain
destinct locations.
It returns a row list of cells.'''
class UList(list):
def ___init__(self):
list.__init__(self)
self.rowspan = None
events = Event.fetch_by_period_list(self.env, timerange_begin, timerange_end)
if not events:
return None, None
timerange_begin = min(events, key=lambda x:x.time_begin).time_begin
timerange_end = max(events, key=lambda x:x.time_end).time_end
# now we calcutate the value of previous "slot" beginning as datetime to beautify
# the slot time beginnings. Try to uncomment the next few lines and reload to see
# what happens to the table without;-)
dts = dt.days * 86400 + dt.seconds
tbs = ((timerange_begin.hour*3600+timerange_begin.minute*60)/dts)*dts
minutes, seconds = divmod(tbs, 60)
hours, minutes = divmod(minutes, 60)
timerange_begin = timerange_begin.replace(hour=hours, minute=minutes)
#timerange_end += dt # we want one slot after the last event ends
column_count = 1 # column 0 is always timerange_begin
locmap = dict() # location_id -> column index
ongoing = dict() # column index of actual used column -> eventlist
locs = dict() # column index -> location name
slotcount = 0
table = []
done = []
while timerange_begin < timerange_end:
row = {0 : timerange_begin}
for k,e in done:
events.remove(e)
done = []
slot_end = timerange_begin + dt
for event in events:
if event.time_begin >= timerange_begin and event.time_begin < slot_end:
if not locmap.has_key(event.location_id):
index = column_count
column_count += 1
locmap[event.location_id] = index
locs[event.location_id] = event.location.name
else:
index = locmap[event.location_id]
if not hasattr(event, "rowspan_begin"):
# event starts in that timeslot
event.rowspan_begin = slotcount
if not row.has_key(index):
row[index] = UList()
if ongoing.has_key(index):
ongoing[index][0]+=1
refc, event_list = ongoing[index]
else:
event_list = row[index]
ongoing[index] = [1, event_list]
event_list.append(event)
if event.time_end <= slot_end:
# event ends
index = locmap[event.location_id]
done.append((locmap[event.location_id], event))
event.rowspan_end = slotcount+1
refc, el = ongoing[index]
if refc<=1:
del ongoing[index]
else:
refc-=1
ongoing[index] = [refc, el]
if hasattr(event, "rowspan_begin") and event.time_begin < timerange_begin and event.time_end > timerange_begin:
# marking cell as occupied due to longer lasting event than time slot length
index = locmap[event.location_id]
row[index] = False
table.append(row)
timerange_begin += dt
slotcount += 1
final_table = []
header = [True for x in xrange(column_count)]
for row in table:
trow = header[:]
for index, item in row.iteritems():
trow[index] = item
if item and isinstance(item, UList):
rowspan_begin = min(item, key=lambda x:x.rowspan_begin).rowspan_begin
rowspan_end = max(item, key=lambda x:x.rowspan_end).rowspan_end
item.rowspan = rowspan_end - rowspan_begin
final_table.append(trow)
for location_id, index in locmap.iteritems():
header[index] = locs[location_id]
header[0] = _("Start")
return final_table, header
def _format_event_link(self, formatter, ns, target, label, fullmatch=None):
link, params, fragment = formatter.split_link(target)
r = Ranges(link)
if len(r) == 1:
num = r.a
validate_id(num)
cursor = formatter.db.cursor()
cursor.execute("SELECT name,time_begin,time_end "
"FROM events WHERE e_id=%s", (num,))
session_tzname, selected_tz = get_tz(formatter.req.session.get("tz", self.env.config.get("trac", "default_timezone") or None))
for name, time_begin, time_end in cursor:
time_begin = selected_tz.fromutc(datetime.fromtimestamp(time_begin, utc))
time_end = selected_tz.fromutc(datetime.fromtimestamp(time_end, utc))
title = "%s (%s - %s %s)" % (name,
time_begin.strftime('%d.%m.%Y %H:%M'),
time_end.strftime('%d.%m.%Y %H:%M'), time_begin.tzinfo.tzname(None))
if label == link:
label = title
href = formatter.href.event(num)
return tag.a(label, title=title, href=href)
return tag.a(label, class_='missing event')

View File

@ -0,0 +1,5 @@
p.help { color:#666666; margin-top:1em; margin-right:0.5em; margin-bottom:0.5em; margin-left:0.5em; }
.error{color:#ff0000;}
.rendezvous .main {width:82%;margin-right:1%;padding-right:1%;}
#rendezvous-main {text-align:left;}
#content {text-align:center;}

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

View File

@ -0,0 +1,2 @@
// Generated messages javascript file from compiled MO file
babel.Translations.load({"domain":"tracrendezvous-js","locale":"de","messages":{},"plural_expr":"(n != 1)"}).install();

View File

@ -0,0 +1,2 @@
// Generated messages javascript file from compiled MO file
babel.Translations.load({"domain":"tracrendezvous-js","locale":"de_DE","messages":{},"plural_expr":"(n != 1)"}).install();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
# German translations for TracRendezVous.
# Copyright (C) 2010 Stefan Koegl
# This file is distributed under the same license as the TracRendezVous
# project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
#
msgid ""
msgstr ""
"Project-Id-Version: TracRendezVous 0.3\n"
"Report-Msgid-Bugs-To: hotshelf@ctdo.de\n"
"POT-Creation-Date: 2010-08-03 04:59+0200\n"
"PO-Revision-Date: 2010-08-03 04:59+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: de <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.5\n"

View File

@ -0,0 +1,20 @@
# Translations template for TracRendezVous.
# Copyright (C) 2010 Stefan Koegl
# This file is distributed under the same license as the TracRendezVous
# project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: TracRendezVous 0.3\n"
"Report-Msgid-Bugs-To: hotshelf@ctdo.de\n"
"POT-Creation-Date: 2010-08-03 04:59+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.5\n"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from tracrendezvous.location.web_ui import *
from tracrendezvous.location.model import *

View File

@ -0,0 +1,18 @@
#main, #content {margin:0 !important;padding:0 !important;left:0;}
/* form.location {margin:auto;width:600px;text-align:center;} */
#location, #new-location, #location-search {display:inline-block;}
/* #new-location {clear:right;} */
/* #location {float:left;} */
a.location,a.location:visited,a.location:hover
{
color:#bb0000;
border-bottom-width:1px;
border-bottom-style:dotted;
border-bottom-color:#ff0000;
cursor:pointer;
}
div.hint {font-family:cursive;background:#aaa;color:#000 !important;}
div.hint h3 {font-size:110%;color:#000;padding:1em;}

View File

@ -0,0 +1,43 @@
html,body {
background-color: #000000;
height: 100%;
width: 100%;
margin: 1%; padding: 0;
font-family: Verdana, Arial;
font-size: 1em;
overflow: hidden;
color: #ffffff;
}
a {
color: #ffff00;
text-decoration: none;
}
a:hover {
color: #ffff00;
text-decoration: underline;
}
#header {
font-family: Verdana, Arial;
font-size: 1em;
overflow: hidden;
color: #ffffff;
}
#map {
height: 86%;
width: 96%;
padding: 0; margin: 0;
}
#content {
font-size: 1em;
}
#osm {
font-size: 0.7em;
font-style: italic;
margin-bottom: 20px;
}

View File

@ -0,0 +1,63 @@
function jumpTo(lon, lat, zoom) {
var x = Lon2Merc(lon);
var y = Lat2Merc(lat);
map.setCenter(new OpenLayers.LonLat(x, y), zoom);
return false;
}
function Lon2Merc(lon) {
return 20037508.34 * lon / 180;
}
function Lat2Merc(lat) {
var PI = 3.14159265358979323846;
lat = Math.log(Math.tan( (90 + lat) * PI / 360)) / (PI / 180);
return 20037508.34 * lat / 180;
}
function addMarker(layer, lon, lat, popupContentHTML) {
var ll = new OpenLayers.LonLat(Lon2Merc(lon), Lat2Merc(lat));
var feature = new OpenLayers.Feature(layer, ll);
feature.closeBox = true;
feature.popupClass = OpenLayers.Class(OpenLayers.Popup.FramedCloud, {minSize: new OpenLayers.Size(300, 180) } );
feature.data.popupContentHTML = popupContentHTML;
feature.data.overflow = "hidden";
var marker = new OpenLayers.Marker(ll);
marker.feature = feature;
var markerClick = function(evt) {
if (this.popup == null) {
this.popup = this.createPopup(this.closeBox);
map.addPopup(this.popup);
this.popup.show();
} else {
this.popup.toggle();
}
OpenLayers.Event.stop(evt);
};
marker.events.register("mousedown", feature, markerClick);
layer.addMarker(marker);
map.addPopup(feature.createPopup(feature.closeBox));
}
function getCycleTileURL(bounds) {
var res = this.map.getResolution();
var x = Math.round((bounds.left - this.maxExtent.left) / (res * this.tileSize.w));
var y = Math.round((this.maxExtent.top - bounds.top) / (res * this.tileSize.h));
var z = this.map.getZoom();
var limit = Math.pow(2, z);
if (y < 0 || y >= limit)
{
return null;
}
else
{
x = ((x % limit) + limit) % limit;
return this.url + z + "/" + x + "/" + y + "." + this.type;
}
}

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
import httplib
from trac.util.text import unicode_urlencode
from genshi import escape
try:
import xml.etree.ElementTree as ET
except:
try:
import elementtree.ElementTree as ET
except:
import celementtree.ElementTree as ET
def search_location(query):
conn = httplib.HTTPConnection("gazetteer.openstreetmap.org")
conn.request(u"GET", u"/namefinder/search.xml?%s" % unicode_urlencode({u'find': query}))
xmlData = conn.getresponse()
tree = ET.parse(xmlData)
return tree.findall("named")

View File

@ -0,0 +1,193 @@
# -*- coding: utf-8 -*-
from trac.core import *
from trac.env import IEnvironmentSetupParticipant
from trac.db import Table, Column, Index
from trac.search.api import search_to_sql
from ctdotools.utils import validate_id
__all__ = ['LocationModelProvider', 'ItemLocation']
class ItemLocation(object):
def __init__(self, env, location_id=0, name=None, lat_side=None, lat_deg=None, lat_min=None, lat_sec=None, lon_side=None, lon_deg=None, lon_min=None, lon_sec=None, lat=None, lon=None):
self.env = env
self.location_id = location_id
self.name = name
self.lat_side = lat_side
self.lat_deg = lat_deg
self.lat_min = lat_min
self.lat_sec = lat_sec
self.lon_side = lon_side
self.lon_deg = lon_deg
self.lon_min = lon_min
self.lon_sec = lon_sec
self.lat = lat
self.lon = lon
@staticmethod
def fetch_all(env):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("SELECT * FROM item_location")
rows = cursor.fetchall()
if not rows:
return []
res = []
for row in rows:
res.append(ItemLocation(env, *row))
return res
@staticmethod
def fetch_one(env, location_id):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("SELECT * FROM item_location WHERE location_id=%s",
(location_id,))
row = cursor.fetchone()
if not row:
return None
return ItemLocation(env, *row)
@staticmethod
def fetch_by_name(env, name):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("SELECT * FROM item_location WHERE name=%s",
(name,))
row = cursor.fetchone()
if not row:
return None
return ItemLocation(env, *row)
@staticmethod
def search_one(env, name):
db = env.get_db_cnx()
cursor = db.cursor()
sql, params = search_to_sql(db, ["name",], [name,])
cursor.execute("SELECT * FROM item_location WHERE " + sql, params)
row = cursor.fetchone()
if not row:
return None
return ItemLocation(env, *row)
@staticmethod
def exists(env, name):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""SELECT *
FROM 'item_location'
WHERE name=%s""", (name,))
row = cursor.fetchone()
return row != None
def commit(self):
db = self.env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""INSERT INTO item_location
(name,lat_side,lat_deg,lat_min,lat_sec,lon_side,lon_deg,lon_min,lon_sec,lat,lon)
VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)""", (self.name, self.lat_side, self.lat_deg, self.lat_min, self.lat_sec, self.lon_side, self.lon_deg, self.lon_min, self.lon_sec, self.lat, self.lon))
db.commit()
self.location_id = db.get_last_id(cursor, 'item_location')
def update(self):
db = self.env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""UPDATE item_location
SET name =%s,
lat_side=%s,
lat_deg=%s,
lat_min=%s,
lat_sec=%s,
lon_side=%s,
lon_deg=%s,
lon_min=%s,
lon_sec=%s,
lat=%s,
lon=%s
WHERE location_id=%s""", (self.name, self.lat_side, self.lat_deg, self.lat_min, self.lat_sec, self.lon_side, self.lon_deg, self.lon_min, self.lon_sec, self.lat, self.lon, self.location_id))
db.commit()
@staticmethod
def delete(env, location_id):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("DELETE FROM item_location WHERE location_id=%s", (location_id,))
db.commit()
def coordinate_str(self):
if self.lat == None:
return u""
return u"%s%d°%d'%.5f\" %s%d°%d'%.5f\""%(self.lat_side, self.lat_deg, self.lat_min, self.lat_sec, self.lon_side, self.lon_deg, self.lon_min, self.lon_sec)
def __str__(self):
return "<ItemLocation: %d %s>" % (self.location_id, self.name)
class LocationModelProvider(Component):
implements(IEnvironmentSetupParticipant)
def environment_created(self):
self._create_models(self.env.get_db_cnx())
def environment_needs_upgrade(self, db):
"""First version - nothing to migrate, but possibly to create.
"""
cursor = db.cursor()
try:
cursor.execute("select count(*) from item_location;")
cursor.fetchone()
return False
except:
db.rollback()
return True
def upgrade_environment(self, db):
""" nothing to do here for now
"""
self._create_models(db)
def _create_models(self, db):
"""Called when a new Trac environment is created."""
db_backend = None
try:
from trac.db import DatabaseManager
db_backend, _ = DatabaseManager(self.env)._get_connector()
except ImportError:
db_backend = self.env.get_db_cnx()
cursor = db.cursor()
t = Table('item_location', key='location_id')[
Column('location_id', auto_increment=True),
Column('name'),
Column('lat_side'),
Column('lat_deg', type='int'),
Column('lat_min', type='int'),
Column('lat_sec', type='real'),
Column('lon_side'),
Column('lon_deg', type='int'),
Column('lon_min', type='int'),
Column('lon_sec', type='real'),
Column('lat', type='real'),
Column('lon', type='real'),
Index(['name'])]
for stmt in db_backend.to_sql(t):
self.env.log.debug(stmt)
try:
cursor.execute(stmt)
db.commit()
except Exception, e:
self.env.log.warning(str(e))
db.rollback()
#LOCATION_DATA = (
#(u"CTDO, Langer August", "N", 51, 31, 39.4, "E", 7, 27, 53.8, 51.527611, 7.464922),
#(u"WILA, Langer August", "N", 51, 31, 39.4, "E", 7, 27, 53.8, 51.527611, 7.464922))
#cursor.executemany("""INSERT INTO 'item_location'
#(name,lat_side,lat_deg,lat_min,lat_sec,lon_side,lon_deg,lon_min,lon_sec, lat, lon)
#VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)""", LOCATION_DATA)
#db.commit()

View File

@ -0,0 +1,169 @@
<!DOCTYPE htm
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="layout.html" />
<head>
<script type="text/javascript">
// <![CDATA[
function setLocation()
{
for (i = 0; i < document.mylocation.location_sel.length; ++i)
if (document.mylocation.location_sel.options[i].selected == true)
{
document.mylocation.location_name.value = document.mylocation.location_sel.options[i].value;
document.mylocation.coordinates.value = document.getElementById("nr_coord_" + i.toString()).value;
}
}
function createIframe(lat, lon)
{
var iframe;
try
{
iframe = document.getElementById("mapframe");
iframe.parentNode.removeChild(iframe);
}
catch(e)
{
}
if (document.createElement && (iframe =
document.createElement("iframe")))
{
var blonstart= lon - 0.00928;
var blonend= lon + 0.011276;
var blatstart = lat - 0.0033;
var bladend = lat + 0.0109;
iframe.name = iframe.id = "mapframe";
iframe.width = "100%";
iframe.height = "600px";
iframe.src = "http://www.openstreetmap.org/export/embed.html?bbox=" + blonstart.toString() + "," + blatstart.toString() + "," + blonend.toString() + "," + bladend.toString() + "&marker=" + lat + "," + lon + "&zoom=16";
document.getElementById("location-results").appendChild(iframe);
}
}
var map;
var layer_mapnik;
var layer_tah;
var layer_markers;
function drawmap() {
// Popup und Popuptext mit evtl. Grafik
var popuptext="<font color=\"black\"><b>Thomas Heiles<br>Stra&szlig;e 123<br>54290 Trier</b><p><img src=\"test.jpg\" width=\"180\" height=\"113\"></p></font>";
OpenLayers.Lang.setCode('de');
// Position und Zoomstufe der Karte
var lon = 6.641389;
var lat = 49.756667;
var zoom = 7;
map = new OpenLayers.Map('map', {
projection: new OpenLayers.Projection("EPSG:900913"),
displayProjection: new OpenLayers.Projection("EPSG:4326"),
controls: [
new OpenLayers.Control.MouseDefaults(),
new OpenLayers.Control.LayerSwitcher(),
new OpenLayers.Control.PanZoomBar()],
maxExtent:
new OpenLayers.Bounds(-20037508.34,-20037508.34,
20037508.34, 20037508.34),
numZoomLevels: 18,
maxResolution: 156543,
units: 'meters'
});
layer_mapnik = new OpenLayers.Layer.OSM.Mapnik("Mapnik");
layer_markers = new OpenLayers.Layer.Markers("Address", { projection: new OpenLayers.Projection("EPSG:4326"),
visibility: true, displayInLayerSwitcher: false });
map.addLayers([layer_mapnik, layer_markers]);
jumpTo(lon, lat, zoom);
// Position des Markers
addMarker(layer_markers, 6.641389, 49.756667, popuptext);
}
jQuery(document).ready(function($) {
$("#nr_location_search").get(0).focus();
drawmap();
});
// ]]>
</script>
<title>Locations</title>
</head>
<body>
<div id="content">
<form name="mylocation" class="location" uri="" method="post" mime-type="text/plain" action="">
<input type="hidden" value="${rendezvous_id}"/>
<fieldset id="location">
<legend>Known Locations</legend>
<table class="listing">
<thead><tr><th>Name</th><th>Coordinates</th><th py:if="'LOCATION_MODIFY' in perm">Default</th><th py:if="'LOCATION_DELETE' in perm">Delete</th></tr></thead>
<tr py:for="location in locations">
<td><input name="name:${location.location_id}" type="text" size="30" maxlength="100" value="${location.name}"/></td>
<td><input name="location:${location.location_id}" type="text" size="32" maxlength="32" value="${location.coordinate_str()}"/></td>
<py:if test="'LOCATION_MODIFY' in perm">
<td><input type="radio" name="default" value="${location.location_id}" checked="${default_location == location.location_id and 'checked' or None}"/></td>
<td><input name="delete:${location.location_id}" type="checkbox" /></td>
</py:if>
</tr>
</table>
<p class="help">Change or delete existing locations.</p>
<div class="mybuttons">
<input type="reset" name="reset"/>
<input type="submit" name="savelocations" value="Save Changes"/>
</div>
</fieldset>
</form>
<form name="newlocation" class="location" uri="" method="post" mime-type="text/plain" action="">
<fieldset id="new-location">
<legend>New Location</legend>
<table class="rendezvous-wizard">
<tr><th><label for="nr_location_text">Name:</label></th><td><input id="nr_location_text" type="text" size="24" maxlength="25" name="location_name" value=""/></td></tr>
<tr><th><label for="nr_location_text">Coordinates:</label></th><td><input id="nr_coordinates_text" type="text" size="32" maxlength="32" name="coordinates" value=""/></td></tr>
</table>
<div class="mybuttons">
<input type="reset" name="reset"/>
<input type="submit" name="addlocation" value="Add Location"/>
</div>
<div class="hint">
<h3>Allowed coordinates formats:</h3>
<ul>
<li>DD format = 'lat,lon', e.g 53.235235,6.235235</li>
<li>DMS Format = 'N|Wdd°mm'ss" E|Wdddd°mm'ss", e.g N51°31'39.40000" E7°27'53.7200"</li>
</ul>
</div>
</fieldset>
</form>
<form name="search_location" class="location" uri="" method="post" mime-type="text/plain" action="">
<fieldset id="location-search">
<legend>Location Search</legend>
<div>
<label for="nr_location_search">Location Search:</label>
<input id="nr_location_search" type="text" size="40" maxlength="40" name="location_search"/>
<input type="submit" name="search" value="Search"/>
</div>
<p class="help">e.g "central station, cityname"</p>
<div py:if="location_results" id="location-results">
<h3>Search Results</h3>
<table>
<tr py:for="rx, result in enumerate(location_results)" >
<td>${result.attrib["info"]} <a class="location" onclick="createIframe(${result.attrib['lat']},${result.attrib['lon']});">${result.attrib["name"]}</a> ${result.attrib["is_in"]}
<py:with vars="nears = result.find('nearestplaces')">
<py:if test="nears">,&nbsp;
<py:with vars="places = nears.findall('named')">
<py:for each="place in places">${place.attrib["distance"]} km away of <b>${place.attrib["name"]}</b></py:for>
</py:with>
</py:if>
</py:with>
</td>
</tr>
</table>
</div>
</fieldset>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
import doctest
import unittest
from tracrendezvous.location.tests import model
def suite():
suite = unittest.TestSuite()
suite.addTest(model.suite())
return suite
if __name__ == '__main__':
import sys
if '--skip-functional-tests' in sys.argv:
sys.argv.remove('--skip-functional-tests')
INCLUDE_FUNCTIONAL_TESTS = False
unittest.main(defaultTest='suite')

View File

@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
import os.path
import shutil
import tempfile
import unittest
from trac.test import EnvironmentStub, Mock
from trac.search.api import *
from tracrendezvous.location.model import ItemLocation, LocationModelProvider
class ItemLocationTestCase(unittest.TestCase):
def setUp(self):
#self.basedir = os.path.realpath(tempfile.mkdtemp())
self.env = EnvironmentStub(default_data=True, enable=['trac.*', 'tracrendezvous.location.*'])
l = LocationModelProvider(self.env).environment_created()
loc1 = ItemLocation(self.env, 0, u"CTDO, Langer August", "N", 51, 31, 39.4, "E", 7, 27, 53.8, 51.527611, 7.464922)
loc1.commit()
loc2 = ItemLocation(self.env, 0, u"WILA, Langer August", "N", 51, 31, 39.4, "E", 7, 27, 53.8, 51.527611, 7.464922)
loc2.commit()
def test_1_commit(self):
self.env.get_db_cnx().cursor().execute("select name from item_location;").fetchall()
def test_2_fetch_one(self):
loc1 = ItemLocation.fetch_one(self.env, 1)
self.assertEqual(1, loc1.location_id)
self.assertEqual(u"CTDO, Langer August", loc1.name)
self.assertEqual(u"N", loc1.lat_side)
self.assertEqual(51, loc1.lat_deg)
self.assertEqual(31, loc1.lat_min)
self.assertEqual(39.4, loc1.lat_sec)
self.assertEqual("E", loc1.lon_side)
self.assertEqual(7, loc1.lon_deg)
self.assertEqual(27, loc1.lon_min)
self.assertEqual(53.8, loc1.lon_sec)
self.assertEqual(51.527611, loc1.lat)
self.assertEqual(7.464922, loc1.lon)
def test_3_search_one(self):
loc2 = ItemLocation.search_one(self.env, u"WILA")
self.assertEqual(2, loc2.location_id)
self.assertEqual(u"WILA, Langer August", loc2.name)
self.assertEqual(u"N", loc2.lat_side)
self.assertEqual(51, loc2.lat_deg)
self.assertEqual(31, loc2.lat_min)
self.assertEqual(39.4, loc2.lat_sec)
self.assertEqual("E", loc2.lon_side)
self.assertEqual(7, loc2.lon_deg)
self.assertEqual(27, loc2.lon_min)
self.assertEqual(53.8, loc2.lon_sec)
self.assertEqual(51.527611, loc2.lat)
self.assertEqual(7.464922, loc2.lon)
def test_4_fetch_all(self):
locs = ItemLocation.fetch_all(self.env)
self.assertEqual(1, locs[0].location_id)
self.assertEqual(u"CTDO, Langer August", locs[0].name)
self.assertEqual(u"N", locs[0].lat_side)
self.assertEqual(51, locs[0].lat_deg)
self.assertEqual(31, locs[0].lat_min)
self.assertEqual(39.4, locs[0].lat_sec)
self.assertEqual("E", locs[0].lon_side)
self.assertEqual(7, locs[0].lon_deg)
self.assertEqual(27, locs[0].lon_min)
self.assertEqual(53.8, locs[0].lon_sec)
self.assertEqual(51.527611, locs[0].lat)
self.assertEqual(7.464922, locs[0].lon)
self.assertEqual(2, locs[1].location_id)
self.assertEqual(u"WILA, Langer August", locs[1].name)
self.assertEqual(u"N", locs[1].lat_side)
self.assertEqual(51, locs[1].lat_deg)
self.assertEqual(31, locs[1].lat_min)
self.assertEqual(39.4, locs[1].lat_sec)
self.assertEqual("E", locs[1].lon_side)
self.assertEqual(7, locs[1].lon_deg)
self.assertEqual(27, locs[1].lon_min)
self.assertEqual(53.8, locs[1].lon_sec)
self.assertEqual(51.527611, locs[1].lat)
self.assertEqual(7.464922, locs[1].lon)
def test_5_exists(self):
self.assertEqual(True, ItemLocation.exists(self.env, u"WILA, Langer August"))
self.assertEqual(False, ItemLocation.exists(self.env, u"WILA"))
def test_6_update(self):
loc = ItemLocation.fetch_one(self.env, 1)
loc.name = "foo"
loc.lat_side = "Q"
loc.lat_deg = 1
loc.lat_min = 1
loc.lat_sec = 1
loc.lon_side = "Q"
loc.lon_deg = 1
loc.lon_min = 1
loc.lon_sec = 1
loc.update()
self.assertEqual(loc.lat_side , "Q")
self.assertEqual(loc.lat_deg , 1)
self.assertEqual(loc.lat_min , 1)
self.assertEqual(loc.lat_sec , 1)
self.assertEqual(loc.lon_side , "Q")
self.assertEqual(loc.lon_deg , 1)
self.assertEqual(loc.lon_min , 1)
self.assertEqual(loc.lon_sec , 1)
def test_7_delete(self):
ItemLocation.delete(self.env, 1)
self.assertEqual(None, ItemLocation.fetch_one(self.env, 1))
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(ItemLocationTestCase, 'test'))
return suite
if __name__ == '__main__':
unittest.main(defaultTest='suite')

View File

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
from re import compile as re_compile
from re import match
from decimal import Decimal, Context, getcontext
from datetime import date, time, datetime, timedelta
from os.path import join, dirname
from os.path import exists as path_exists
from os import mkdir
from sys import maxint
from trac.util import Ranges
from trac.wiki import WikiPage, WikiSystem
from trac.util.datefmt import utc, to_timestamp, localtz, format_time, get_timezone, timezone
from PIL import Image, ImageDraw, ImageFont
__all__ = ["validate_dd_coordinates", "validate_dms_coordinates", "convert_dms_2_dd", "convert_dd_2_dms"]
class ValidationError(ValueError):
def __str__(self):
return "ValidationError: value out of bounds!"
def validate_dms_coordinates(value):
mytest=re_compile(u"(N|S)(\d{1,2})°(\d{1,2})'(\d{1,2}\.\d{1,5})\" (E|W)(\d{1,3})°(\d{1,2})'(\d{1,2}\.\d{1,5})\"$")
m = mytest.match(value)
if not m:
raise ValueError(u"validate_rendezvous(): coordinates have wrong format:")
groups = m.groups()
if 0 > groups[1] > 90.0:
raise ValueError(u"validate_rendezvous(): lat not valid:")
if 0 > groups[5] > 180.0:
raise ValueError(u"validate_rendezvous(): lon not valid:")
return groups
def validate_dd_coordinates(value):
mytest=re_compile(u"(-?\d{1,3}\.\d{1,8}),(-?\d{1,3}\.\d{1,8})$")
m = mytest.match(value)
if not m:
raise ValueError(u"validate_rendezvous(): coordinates have wrong format:")
lat, lon = m.groups()
if 0 > lat > 90.0:
raise ValueError(u"validate_rendezvous(): lat not valid:")
if 0 > lon > 180.0:
raise ValueError(u"validate_rendezvous(): lon not valid:")
return lat, lon
def convert_dms_2_dd(ns,a,b,c,ew,d,e,f):
getcontext().prec = 20
r1 = Decimal(a) + Decimal(b) / 60 + Decimal(str(c))/3600
r2 = Decimal(d) + Decimal(e) / 60 + Decimal(str(f))/3600
if ns == u"S":
r1=-r1
if ew == u"W":
r2=-r2
a = round(r1,6)
b = round(r2,6)
return a, b
def convert_dd_2_dms(lat, lon):
def convert(value, pos, neg):
getcontext().prec = 32
dValue = Decimal(value)
tValue = dValue.as_tuple()
valueDir = tValue[0] and neg or pos
# extracting full degrees, keep in mind we want an int
degValue = Decimal((0, tValue[1][:tValue[2]], 0))
# extracting minutes as int
tMinValueRemainder = (Decimal((0, tValue[1][tValue[2]:], tValue[2])) * 60).as_tuple()
minValue = Decimal((0, tMinValueRemainder[1][:tMinValueRemainder[2]], 0))
# extracting sec, we want the remaining seconds as float
secValueRemainder = Decimal((0, tMinValueRemainder[1][tMinValueRemainder[2]:], tMinValueRemainder[2]))
secValueRemainder = secValueRemainder * 60
return valueDir, int(degValue), int(minValue), float(secValueRemainder)
return convert(lat, "N", "S") + convert(lon, "E", "W")

View File

@ -0,0 +1,236 @@
# -*- coding: utf-8 -*-
from datetime import datetime, timedelta
from os import mkdir
from os.path import join
from re import match, sub
from collections import defaultdict
from sqlite3 import IntegrityError
from operator import attrgetter
from trac.config import *
from trac.core import Component, implements, TracError
from trac.perm import PermissionError, IPermissionRequestor
from trac.resource import Resource, get_resource_url, get_resource_name
from trac.util import get_reporter_id
from trac.util.text import to_unicode
from trac.util.datefmt import utc, get_timezone, localtz, timezone
from trac.util.translation import _
from trac.util.html import html
from trac.web.chrome import INavigationContributor, ITemplateProvider, add_stylesheet, add_warning, add_notice, add_ctxtnav, add_script
from trac.web import IRequestHandler
from genshi.builder import tag
from tracrendezvous.location.loc_xml import *
from tracrendezvous.location.model import *
from tracrendezvous.location.utils import *
__all__ = ['LocationModule',]
class LocationModule(Component):
'''The web ui frontend for the location management system'''
implements(INavigationContributor,
IRequestHandler,
IPermissionRequestor,
ITemplateProvider)
# INavigationContributor methods
def get_active_navigation_item(self, req):
return 'location'
def get_navigation_items(self, req):
if "LOCATION_VIEW" in req.perm:
yield ('mainnav', 'location', html.A('Locations', href=req.href.location()))
# IPermissionRequestor methods
def get_permission_actions(self):
'''returns all permissions this component provides'''
return ['LOCATION_VIEW', 'LOCATION_ADD', 'LOCATION_DELETE', 'LOCATION_MODIFY',
('LOCATION_ADMIN', ('LOCATION_VIEW', 'LOCATION_ADD', 'LOCATION_DELETE', 'LOCATION_MODIFY'))]
def match_request(self, req):
self.env.log.debug("LocationModule.match_request %r\n" % req.__dict__)
res = req.path_info == "/location"
self.env.log.debug("LocationModule.match_request res = %r\n" % res)
return res
def process_request(self, req):
self.env.log.debug("LocationModule.process_request %r\n" % req.__dict__)
'''process add,change,delete actions'''
req.perm.require("LOCATION_VIEW")
#add_stylesheet(req, 'hw/css/base.css')
add_stylesheet (req, 'hw/css/location.css')
#add_stylesheet (req, 'hw/css/map.css')
add_script(req, 'http://www.openlayers.org/api/OpenLayers.js')
add_script(req, 'http://www.openstreetmap.org/openlayers/OpenStreetMap.js')
add_script(req, 'hw/script/tom.js')
data = {"results": []}
if req.args.has_key("from") and req.args.has_key("id"):
req.session["from"] = req.args["from"]
req.session["id"] = req.args["id"]
req.session.save()
if req.session.has_key("edited"):
try:
from_resource = req.session.get("from")
resource_id = int(req.session.get("id"))
resource = Resource(from_resource, id=resource_id)
link = get_resource_url(self.env, resource, req.href)
add_notice(req, tag.p("Location edited successfully. Back to " , tag.a(get_resource_description(self.env, resource, "summary"), href=link)))
del req.session["from"]
del req.session["id"]
except Exception, e:
add_notice(req, _("Location edited successfully."))
del req.session["edited"]
req.session.save()
if req.session.has_key("added"):
try:
from_resource = req.args.get("from")
resource_id = int(req.args.get("id"))
resource = Resource(from_resource, id=resource_id)
link = get_resource_url(from_resource, resource_id)
add_notice(req, tag.p(_("Location created successfully. Back to ") % resource, tag.a(resource, href=link)))
except Exception:
add_notice(req, _("Location created successfully."))
del req.session["added"]
req.session.save()
if req.method == "POST":
if req.args.has_key("location_search"):
query = unicode(req.args["location_search"])
results = search_location(query)
data["location_results"] = results
if req.args.has_key("savelocations"):
req.perm.require("LOCATION_MODIFY")
deleted = []
locations = {}
changed = {}
if req.args.has_key("default"):
default_location = self.config.getint("rendezvous", "default_location")
default = int(req.args["default"])
if default_location != default:
default_location = default
self.config.set("rendezvous", "default_location", default)
self.config.save()
for key in req.args:
kind = location_id = None
try:
kind, location_id = key.split(":", 1)
except ValueError:
continue
location_id = int(location_id)
if location_id in deleted:
continue
if not locations.has_key(location_id):
location = ItemLocation.fetch_one(self.env, location_id)
if not location:
add_warning(req, "Could not find ItemLocation with location_id '%d'" % location_id)
continue
locations[location_id] = location
if kind == "delete":
req.perm.require("LOCATION_DELETE")
locations[location_id].delete()
deleted.append(location_id)
del locations[location_id]
elif kind == "name":
name = req.args[key]
if not name:
add_warning(req, "location name must be specified for ItemLocation '%d'" % location_id)
continue
if name != locations[location_id].name:
locations[location_id].name = name
changed[location_id] = True
elif kind == "location":
coordinates = req.args[key]
if coordinates:
try:
lat, lon = validate_dd_coordinates(coordinates)
lat_side, lat_deg, lat_min, lat_sec, lon_side, lon_deg, lon_min, lon_sec = convert_dd_2_dms(lat, lon)
except ValueError:
try:
lat_side, lat_deg, lat_min, lat_sec, lon_side, lon_deg, lon_min, lon_sec = validate_dms_coordinates(coordinates)
lat, lon = convert_dms_2_dd(lat_side, lat_deg, lat_min, lat_sec, lon_side, lon_deg, lon_min, lon_sec)
except ValueError:
add_warning(req, "coordinates have wrong format")
continue
if lat != location.lat:
location.lat = lat
changed[location_id] = True
if lon != location.lon:
location.lon = lon
changed[location_id] = True
if lat_side != location.lat_side:
location.lat_side = lat_side
changed[location_id] = True
if lat_deg != location.lat_deg:
location.lat_deg = lat_deg
changed[location_id] = True
if lat_min != location.lat_min:
location.lat_min = lat_min
changed[location_id] = True
if lat_sec != location.lat_sec:
location.lat_sec= lat_sec
changed[location_id] = True
if lon_side != location.lon_side:
location.lon_side = lon_side
changed[location_id] = True
if lon_deg != location.lon_deg:
location.lon_deg = lon_deg
changed[location_id] = True
if lon_min != location.lon_min:
location.lon_min = lon_min
changed[location_id] = True
if lon_sec != location.lon_sec:
location.lon_sec = lon_sec
changed[location_id] = True
done=True
for dvi in changed:
if dvi not in deleted:
try:
locations[dvi].update()
except Exception, err:
add_warning(req, str(err))
done=False
continue
if done:
req.session["edited"] = True
req.session.save()
req.redirect(req.href.location())
if req.args.has_key("addlocation") and req.args.has_key("location_name"):
req.perm.require("LOCATION_ADD")
rl = ItemLocation(self.env, name=req.args["location_name"])
is_valid = True
if not rl.name:
add_warning(req, "Coordinate name is empty")
is_valid = False
if req.args.has_key("coordinates"):
coordinates = unicode(req.args["coordinates"])
if coordinates:
try:
rl.lat, rl.lon = validate_dd_coordinates(coordinates)
rl.lat_side, rl.lat_deg, rl.lat_min, rl.lat_sec, rl.lon_side, rl.lon_deg, rl.lon_min, rl.lon_sec = convert_dd_2_dms(rl.lat, rl.lon)
except ValueError:
try:
rl.lat_side, rl.lat_deg, rl.lat_min, rl.lat_sec, rl.lon_side, rl.lon_deg, rl.lon_min, rl.lon_sec = validate_dms_coordinates(coordinates)
rl.lat, rl.lon = convert_dms_2_dd(rl.lat_side, rl.lat_deg, rl.lat_min, rl.lat_sec, rl.lon_side, rl.lon_deg, rl.lon_min, rl.lon_sec)
except ValueError:
add_warning(req, "coordinates have wrong format")
is_valid = False
if is_valid:
rl.commit()
req.session["added"] = True
req.session.save()
req.redirect(req.href.location())
data.update({"results" : None,
"default_location" : self.config.getint("rendezvous", "default_location"),
"locations" : ItemLocation.fetch_all(self.env)})
return 'location.html', data, None
# ITemplateProvider methods
def get_templates_dirs(self):
from pkg_resources import resource_filename
return [resource_filename(__name__, 'templates')]
def get_htdocs_dirs(self):
from pkg_resources import resource_filename
return [('hw', resource_filename(__name__, 'htdocs'))]

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
import workflow
import macros
import api

View File

@ -0,0 +1,201 @@
# -*- coding: utf-8 -*-
from os.path import join
from trac.admin import IAdminPanelProvider
from trac.core import *
from trac.web.chrome import add_stylesheet
from api import RendezVousSystem
from trac.util.translation import _
from model import RendezVousType, TypePermission, RendezVousDate
from ctdotools.utils import validate_id
from tracrendezvous.rendezvous.utils import *
__all__ = ['ComponentRendezVousGeneral', 'ComponentRendezVousTypes']
def get_actions(myactions):
actions = []
for action in myactions:
if isinstance(action, tuple):
actions.append(action[0])
else:
actions.append(action)
return actions
class RendezVousAdminPanel(Component):
implements(IAdminPanelProvider)
abstract = True
# IAdminPanelProvider methods
def get_admin_panels(self, req):
if 'RENDEZVOUS_ADMIN' in req.perm:
yield ('rendezvous', 'RendezVous System', self._type, self._label[1])
def render_admin_panel(self, req, cat, page, rendezvous):
req.perm.require('RENDEZVOUS_ADMIN')
# Trap AssertionErrors and convert them to TracErrors
try:
return self._render_admin_panel(req, cat, page, rendezvous)
except AssertionError, e:
raise TracError(e)
class ComponentRendezVousGeneral(RendezVousAdminPanel):
_type = 'rendezvous'
_label = ('rendezvous', 'General')
def _render_admin_panel(self, req, cat, page, rendezvous):
add_stylesheet (req, 'hw/css/rendezvous.css')
if req.method == "POST":
if req.args.has_key("show_vote_graph"):
self.config.set("rendezvous", "show_vote_graph", True)
else:
self.config.set("rendezvous", "show_vote_graph", False)
if req.args.has_key("show_location_map"):
self.config.set("rendezvous", "show_location_map", True)
else:
self.config.set("rendezvous", "show_location_map", False)
if req.args.has_key("max_description_length"):
tmp = int(req.args["max_description_length"])
self.config.set("rendezvous", "max_description_length", tmp)
if req.args.has_key("max_votes_per_date"):
tmp = int(req.args["max_votes_per_date"])
self.config.set("rendezvous", "max_votes_per_date", tmp)
if req.args.has_key("max_dates_per_rendezvous"):
tmp = int(req.args["max_dates_per_rendezvous"])
self.config.set("rendezvous", "max_dates_per_rendezvous", tmp)
if req.args.has_key("graph_size_x"):
tmp = int(req.args["graph_size_x"])
self.config.set("rendezvous", "graph_size_x", tmp)
update = True
update = False
if req.args.has_key("graph_size_y"):
tmp = int(req.args["graph_size_y"])
self.config.set("rendezvous", "graph_size_y", tmp)
update = True
if req.args.has_key("default_vote_time_start"):
tmp = req.args["default_vote_time_start"]
self.config.set("rendezvous", "default_vote_time_start", tmp)
if req.args.has_key("default_vote_time_end"):
tmp = req.args["default_vote_time_end"]
self.config.set("rendezvous", "default_vote_time_end", tmp)
if req.args.has_key("default_rendezvous_type"):
tmp = req.args["default_rendezvous_type"]
self.config.set("rendezvous", "default_rendezvous_type", tmp)
self.config.save()
if update:
path = join(self.env.path, "htdocs", "rendezvous_graphs")
size = [self.config.getint("rendezvous", "graph_size_x"),
self.config.getint("rendezvous", "graph_size_y")]
dates = RendezVousDate.fetch_all(self.env)
for date in dates:
if date.votes:
update_votes_graph(date.votes, path, size)
data = {"show_vote_graph" : self.config.getbool("rendezvous", "show_vote_graph"),
"show_location_map" : self.config.getbool("rendezvous", "show_location_map"),
"max_description_length" : self.config.getint("rendezvous", "max_description_length"),
"max_votes_per_date" : self.config.getint("rendezvous", "max_votes_per_date"),
"max_dates_per_rendezvous" : self.config.getint("rendezvous", "max_dates_per_rendezvous"),
"graph_size_x" : self.config.getint("rendezvous", "graph_size_x"),
"graph_size_y" : self.config.getint("rendezvous", "graph_size_y"),
"default_vote_time_start" : self.config.get("rendezvous", "default_vote_time_start"),
"default_vote_tTime_end" : self.config.get("rendezvous", "default_vote_time_end")}
return "admin_general.html", data
class ComponentRendezVousTypes(RendezVousAdminPanel):
_type = 'types'
_label = ('types', 'Types')
def _render_admin_panel(self, req, cat, page, rendezvoustype):
add_stylesheet (req, 'hw/css/rendezvous.css')
data = {}
myPermissions = get_actions(RendezVousSystem._actions)
if req.method == "POST":
rtype = req.args.get("rtype")
permission = req.args.get("permission")
if rtype and rtype.isupper():
raise TracError(_('All upper-cased tokens are reserved for '
'permission names'))
#if not rtype:
#raise TracError(_('Unknown RendezVousType'))
if permission and permission not in myPermissions:
raise TracError(_('Unknown permission'))
if req.args.get("add") and rtype and permission:
rtype = RendezVousType.fetch_one(self.env, name=rtype)
if rtype.has_permission(permission):
raise TracError(_('permission already granted to RendezVousType'))
tPerm = TypePermission(self.env, rtype.type_id, permission)
self.validate_type_permission(tPerm)
tPerm.commit()
req.redirect(req.href.admin(cat, page))
elif req.args.has_key("add") and rtype:
realType = RendezVousType.fetch_one(self.env, name=rtype)
if realType:
raise TracError(_('RendezVousType already exists'))
realType = RendezVousType(self.env, 0, rtype)
self.validate_rendezvous_type(realType)
realType.commit()
req.redirect(req.href.admin(cat, page))
elif req.args.has_key("save") and req.args.has_key("rsel"):
req.perm.require('RENDEZVOUS_ADMIN')
rsel = req.args.get('rsel')
rsel = isinstance(rsel, list) and rsel or [rsel]
for key in rsel:
type_id = int(key)
rtype = RendezVousType.fetch_one(self.env, type_id)
if not rtype:
raise TracError(_('Unknown RendezVousType'))
rtype.delete()
req.redirect(req.href.admin(cat, page))
elif req.args.has_key("save") and req.args.has_key("sel"):
req.perm.require('RENDEZVOUS_ADMIN')
sel = req.args.get('sel')
sel = isinstance(sel, list) and sel or [sel]
for key in sel:
rtype, permission = key.split(":")
rtype_id = int(rtype)
if permission and permission not in myPermissions:
raise TracError(_('Unknown type permission relation'))
typePermission = TypePermission.fetch_one(self.env, rtype_id, permission)
if typePermission:
typePermission.delete()
req.redirect(req.href.admin(cat, page))
elif req.args.has_key("save") and req.args.has_key("default"):
default = int(req.args["default"])
self.config.set("rendezvous", "default_rendezvous_type", default)
self.config.save()
data.update({"default_rendezvous_type" : self.config.getint("rendezvous", "default_rendezvous_type"),
"rendezVousTypes" : RendezVousType.fetch_all(self.env),
"actions" : myPermissions})
return "admin_types.html", data
def validate_rendezvous_type(self, typ):
if type(typ.type_id) != int:
raise TypeError("RendezVousType.validate() wrong type")
if type(typ.name) != unicode:
raise TypeError("RendezVousType.validate() wrong type")
def validate_type_permission(self, mytype):
if type(mytype.type_id) != int:
raise TypeError("TypePermission.__init__(): expected type int, got '%s'" % type(mytype.type_id))
if type(mytype.permission) != unicode:
raise TypeError("TypePermission.__init__() expected type 'unicode', got '%s'" % type(mytype.permission))
validate_id(mytype.type_id)

View File

@ -0,0 +1,191 @@
# -*- coding: utf-8 -*-
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License version 2 as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this library; see the file COPYING.LIB. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
#
# ---
# Copyright (C) 2008, hotshelf <skoegl@online.de>
#
''' the api of the rendezvous system'''
from trac.resource import IResourceManager
from trac.config import ExtensionOption
from trac.core import Interface, Component, implements
from trac.perm import IPermissionRequestor
from trac.util import Ranges
from trac.util.datefmt import get_timezone, utc, format_time, localtz
from trac.wiki import IWikiSyntaxProvider
from genshi.builder import tag
from ctdotools.utils import validate_id, gen_wiki_page
from tracrendezvous.location.model import ItemLocation
from tracrendezvous.event.model import Event
from model import *
from datetime import datetime
__all__ = ['IRendezVousActionController', 'RendezVousSystem']
class IRendezVousActionController(Interface):
"""Extension point interface for components willing to participate
in the rendezvous workflow.
This is mainly about controlling the changes to the rendezvous ''status'',
though not restricted to it.
"""
def get_rendezvous_actions(req, rendezvous):
"""Return an iterable of `(weight, action)` tuples corresponding to
the actions that are contributed by this component.
That list may vary given the current state of the rendezvous and the
actual request parameter.
`action` is a key used to identify that particular action.
(note that 'history' and 'diff' are reserved and should not be used
by plugins)
The actions will be presented on the page in descending order of the
integer weight. The first action in the list is used as the default
action.
When in doubt, use a weight of 0."""
def get_all_status():
"""Returns an iterable of all the possible values for the ''status''
field this action controller knows about.
This will be used to populate the query options and the like.
It is assumed that the initial status of a rendezvous is 'new' and
the terminal status of a rendezvous is 'closed'.
"""
def render_rendezvous_action_control(req, rendezvous, action):
"""Return a tuple in the form of `(label, control, hint)`
`label` is a short text that will be used when listing the action,
`control` is the markup for the action control and `hint` should
explain what will happen if this action is taken.
This method will only be called if the controller claimed to handle
the given `action` in the call to `get_rendezvous_actions`.
Note that the radio button for the action has an `id` of
`"action_%s" % action`. Any `id`s used in `control` need to be made
unique. The method used in the default ITicketActionController is to
use `"action_%s_something" % action`.
"""
def change_rendezvous_workflow(req, rendezvous, action):
"""Change workflow
"""
def current_rendezvous():
"""Return an iterable of current, active rendezvouses."""
def past_rendezvous():
"""Return an iterable of past rendezvouses."""
class RendezVousSystem(Component):
"""base mathods of the rendezvous system"""
implements(IPermissionRequestor,
IResourceManager,
IWikiSyntaxProvider)
workflow_controller = ExtensionOption('rendezvous', 'workflow',
IRendezVousActionController, 'RendezVousWorkflow',
"""Ordered list of workflow controllers to use for rendezvous actions.""")
_actions = ['RENDEZVOUS_ADD', 'RENDEZVOUS_DELETE', 'RENDEZVOUS_MODIFY',
'RENDEZVOUS_VIEW', 'RENDEZVOUS_COMMENT_ADD', 'RENDEZVOUS_COMMENT_VIEW',
'RENDEZVOUS_DATE_ADD', 'RENDEZVOUS_DATE_DELETE', 'RENDEZVOUS_DATE_MODIFY',
'RENDEZVOUS_DATE_VIEW', 'RENDEZVOUS_VOTE_ADD', 'RENDEZVOUS_VOTE_DELETE',
'RENDEZVOUS_VOTE_MODIFY', 'RENDEZVOUS_VOTE_VIEW', 'RENDEZVOUS_VOTE_GRAPH_VIEW',
'RENDEZVOUS_VOTE_VIEW_OTHERS', 'RENDEZVOUS_LOCATION_VIEW', 'RENDEZVOUS_LOCATION_ADD',
'RENDEZVOUS_LOCATION_DELETE', 'RENDEZVOUS_LOCATION_MODIFY',
('RENDEZVOUS_ADMIN',
('RENDEZVOUS_ADD', 'RENDEZVOUS_DELETE', 'RENDEZVOUS_MODIFY', 'RENDEZVOUS_VIEW',
'RENDEZVOUS_VIEW', 'RENDEZVOUS_COMMENT_ADD', 'RENDEZVOUS_DATE_ADD',
'RENDEZVOUS_DATE_DELETE', 'RENDEZVOUS_DATE_MODIFY', 'RENDEZVOUS_DATE_VIEW',
'RENDEZVOUS_VOTE_ADD', 'RENDEZVOUS_VOTE_DELETE', 'RENDEZVOUS_VOTE_MODIFY',
'RENDEZVOUS_VOTE_GRAPH_VIEW', 'RENDEZVOUS_VOTE_VIEW_OTHERS',
'RENDEZVOUS_LOCATION_ADD', 'RENDEZVOUS_LOCATION_DELETE', 'RENDEZVOUS_LOCATION_MODIFY'))]
# workflow stuff
def get_available_actions(self, req, rendezvous):
"""Returns a sorted list of available actions"""
# The list should not have duplicates.
actions = {}
weighted_actions = self.workflow_controller.get_rendezvous_actions(req, rendezvous)
for weight, action in weighted_actions:
if action in actions:
actions[action] = max(actions[action], weight)
else:
actions[action] = weight
all_weighted_actions = [(weight, action) for action, weight in
actions.items()]
return [x[1] for x in sorted(all_weighted_actions, reverse=True)]
def get_all_status(self):
"""Returns a sorted list of all the states all of the action
controllers know about."""
valid_states = set()
valid_states.update(self.workflow_controller.get_all_status())
return sorted(valid_states)
# IWikiSyntaxProvider methods
def get_wiki_syntax(self):
return []
def get_link_resolvers(self):
yield ('rendezvous', self._format_link)
def _format_link(self, formatter, ns, target, label, fullmatch=None):
link, params, fragment = formatter.split_link(target)
r = Ranges(link)
if len(r) == 1:
num = r.a
rendezvous = formatter.resource("rendezvous", num)
validate_id(num)
cursor = formatter.db.cursor()
cursor.execute("SELECT name,status "
"FROM rendezvous WHERE rendezvous_id=%s", (num,))
for name, status in cursor:
title = "rendezvous #%d: %s (%s)" % (num, name, status)
if label == link:
label = title
href = formatter.href.rendezvous(num)
return tag.a(label, title=title, href=href)
return tag.a(label, class_='missing rendezvous')
# IPermissionRequestor methods
def get_permission_actions(self):
'''returns all permissions this component provides'''
return self._actions
# IResourceManager methods
def get_resource_realms(self):
yield 'rendezvous'
def get_resource_description(self, resource, format=None, context=None,
**kwargs):
if format == 'compact':
return 'RendezVous #%s' % resource.id
elif format == 'summary':
from tracrendezvous.model import RendezVous
rendezvous = RendezVous.fetch_one(self.env, resource.id)
return "RendezVous #%d - %s (%s)" % (rendezvous.rendezvous_id, rendezvous.name, rendezvous.status)

View File

@ -0,0 +1,264 @@
p.help
{
color:#666666;
margin-top:1em;
margin-right:0.5em;
margin-bottom:0.5em;
margin-left:0.5em;
}
.error{color:#ff0000;}
div.overview-set
{
display:inline-block;
margin:5px;
min-width:20em;
border:1px solid #000;
-moz-border-radius:17px;
-webkit-border-radius:17px;
}
a.voting{color:black;}
li a.overview
{
font-size:14pt;
font-weight:bold;
border-bottom-style:none;
color:#000;
font-size:14pt;
-moz-border-radius:17px;
-webkit-border-radius:17px;
padding:5px;
}
li a.overview:hover{color:#ffffaa;background-color:#333300;border-bottom-style:none;}
ul.overview-set{list-style-type:none;}
a.location,a.location:visited,a.location:hover
{
color:#bb0000;
border-bottom-width:1px;
border-bottom-style:dotted;
border-bottom-color:#ff0000;
cursor:pointer;
}
.rendezvous .main
{
width:82%;
margin-right:1%;
padding-right:1%;
}
#rendezvous-main
{
text-align:left;
}
#rendezvous-details.table
{
border-top:1px solid Gray;
border-bottom:1px solid Gray;
margin-bottom:10px;
padding:10px;
}
table.rendezvous
{
clear:both;
border-top:1px solid #dd9;
border-collapse:separate;
table-layout:fixed;
width:100%;
}
td.rendezvous-voted
{
height:30px;
margin:10px;
background:green;
color:black;
}
td.rendezvous-notvoted
{
height:30px;
margin:10px;
background:red;
color:black;
}
td.rendezvous-notelected
{
margin:10px;
background:gray;
color:black;
}
div.rendezvous-item
{
border:1px outset #996;
background:#ffd;
margin:10px;
width:300px;
}
.rendezvous-header
{
color:#005500;
position:relative;
top:10px;
right:10px;
float:right;
}
.rendezvous-header:link,.rendezvous-header:visited
{
text-decoration:none;
border-bottom-style:none;
font-size:22pt;
font-family:serif;
}
.rendezvous-stats
{
border-top:1px double #000;
}
#rendezvous-details
{
min-width:400px;
margin-bottom:1em;
margin-top:1em;
padding:.5em 1em;
padding-left:10px;
padding-right:10px;
padding-top:10px;
}
.rendezvous-wizard th
{
text-align:left;
}
table.properties th{color:#666633;text-align:left;width:20%;}
div.description{border:1px outset #996;background:#dfd;padding-left:10px;padding-right:10px;}
div.newrendezvous{border:1px outset #996;background:#ffffdd;width:200px;padding:20px;}
div.graph{border:1px outset #996;background:#ffd;padding:10px;}
.scheduled{color:#ff0000;font-weight:bold;text-decoration:blink;}
.comment{border-bottom:1px solid #000000;margin-bottom:10px;padding-top:1em;}
comment h1
{
font-style:italic;
font-size:12pt;
font-weight:bold;
padding:10px;
margin-bottom:0.7em;
}
.comment h2
{
font-style:italic;
font-size:10pt;
font-weight:bold;
padding:10px;
margin-bottom:0.7em;
}
comment h3
{
font-style:italic;
font-size:8pt;
font-weight:bold;
padding:10px;
margin-bottom:0.2em;
margin-bottom:0.5em;
}
.comment-header
{
color:#005500;
position:relative;
top:10px;
right:10px;
float:right;
}
#content {text-align:center;}
#new_event
{
margin:auto;
}
#new_event fieldset {text-align:left;}
.event-item
{
border-bottom:1px solid #ccc;
border-left:1px solid #ccc;
border-right:1px solid #ccc;
margin:0px;
padding:10px 5px;
}
.event-item.first
{
border-top:1px solid #ccc;
}
.event-item h2 {margin-top:0;}
table.upcoming
{
border:1px solid #000 !important;
border-spacing:0px;
border-collapse: collapse;
margin:auto;
text-align:left;
}
tr.upcoming-even
{
margin:0px;
padding:0px;
background:#e4d6b0;
}
tr.upcoming
{
padding:0px;
background:#ffedbc;
}
td.upcoming
{
min-width:20em;
padding:5px;
border-bottom:1px solid #000;
border-right:1px solid #000;
height:10em;
}
td.upcoming:hover, td.upcoming-even:hover
{
background:#ffffee;
}
td.day
{
font-size:200%;
font-weight:bold;
font-family:sans-serif;
padding:5px 10px;
margin:0px;
border-right:1px solid #000;
border-bottom:1px solid #000;
min-height:5em;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@ -0,0 +1,63 @@
function jumpTo(lon, lat, zoom) {
var x = Lon2Merc(lon);
var y = Lat2Merc(lat);
map.setCenter(new OpenLayers.LonLat(x, y), zoom);
return false;
}
function Lon2Merc(lon) {
return 20037508.34 * lon / 180;
}
function Lat2Merc(lat) {
var PI = 3.14159265358979323846;
lat = Math.log(Math.tan( (90 + lat) * PI / 360)) / (PI / 180);
return 20037508.34 * lat / 180;
}
function addMarker(layer, lon, lat, popupContentHTML) {
var ll = new OpenLayers.LonLat(Lon2Merc(lon), Lat2Merc(lat));
var feature = new OpenLayers.Feature(layer, ll);
feature.closeBox = true;
feature.popupClass = OpenLayers.Class(OpenLayers.Popup.FramedCloud, {minSize: new OpenLayers.Size(300, 180) } );
feature.data.popupContentHTML = popupContentHTML;
feature.data.overflow = "hidden";
var marker = new OpenLayers.Marker(ll);
marker.feature = feature;
var markerClick = function(evt) {
if (this.popup == null) {
this.popup = this.createPopup(this.closeBox);
map.addPopup(this.popup);
this.popup.show();
} else {
this.popup.toggle();
}
OpenLayers.Event.stop(evt);
};
marker.events.register("mousedown", feature, markerClick);
layer.addMarker(marker);
map.addPopup(feature.createPopup(feature.closeBox));
}
function getCycleTileURL(bounds) {
var res = this.map.getResolution();
var x = Math.round((bounds.left - this.maxExtent.left) / (res * this.tileSize.w));
var y = Math.round((this.maxExtent.top - bounds.top) / (res * this.tileSize.h));
var z = this.map.getZoom();
var limit = Math.pow(2, z);
if (y < 0 || y >= limit)
{
return null;
}
else
{
x = ((x % limit) + limit) % limit;
return this.url + z + "/" + x + "/" + y + "." + this.type;
}
}

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
from admin import *
from api import *
from macros import *
from model import *
from rendezvous_tag import *
from web_ui import *
from workflow import *

Binary file not shown.

View File

@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
from genshi.builder import tag
from trac.util.translation import _
from trac.wiki.macros import WikiMacroBase
from tracrendezvous.rendezvous import api
from tracrendezvous.rendezvous import model
__all__ = ['ExpiredRendezVousesMacro', 'CanceledRendezVousesMacro', 'ScheduledRendezVousesMacro']
class ExpiredRendezVousesMacro(WikiMacroBase):
"""Renders an overview of canceled or expired !RendezVouses"""
revision = "$Rev: 186 $"
def expand_macro(self, formatter, name, content):
if 'RENDEZVOUS_VIEW' not in formatter.perm:
return ""
uperm = model.RendezVousTypePermissionSystem(self.env)
controller = api.RendezVousSystem(self.env).workflow_controller
ls = controller.expired_rendezvouses()
lsr = [tag.li(tag.a("%s: (%s)" % (i.name, i.status), href=formatter.href.rendezvous(i.rendezvous_id)))
for i in ls
if 'RENDEZVOUS_ADMIN' in formatter.perm or uperm.check_user_type_permissions(formatter.req.authname, i.type_id)]
if lsr:
return tag.div([tag.ul(lsr)])
return None
class CanceledRendezVousesMacro(WikiMacroBase):
"""Renders an overview of canceled or expired !RendezVouses"""
revision = "$Rev: 186 $"
def expand_macro(self, formatter, name, content):
if 'RENDEZVOUS_VIEW' not in formatter.perm:
return ""
uperm = model.RendezVousTypePermissionSystem(self.env)
controller = api.RendezVousSystem(self.env).workflow_controller
ls = controller.canceled_rendezvouses()
lsr = [tag.li(tag.a("%s: (%s)" % (i.name, i.status), href=formatter.href.rendezvous(i.rendezvous_id)))
for i in ls
if 'RENDEZVOUS_ADMIN' in formatter.perm or uperm.check_user_type_permissions(formatter.req.authname, i.type_id)]
if lsr:
return tag.div([tag.ul(lsr)])
return None
class CurrentRendezVousesMacro(WikiMacroBase):
"""Renders an overview of current !RendezVouses"""
revision = "$Rev:$"
def expand_macro(self, formatter, name, content):
if 'RENDEZVOUS_VIEW' not in formatter.perm:
return ""
uperm = model.RendezVousTypePermissionSystem(self.env)
controller = api.RendezVousSystem(self.env).workflow_controller
ls = controller.voting_rendezvouses()
lsr = [tag.li(tag.a("%s: (%s)" % (i.name, i.status), href=formatter.href.rendezvous(i.rendezvous_id)))
for i in ls
if 'RENDEZVOUS_ADMIN' in formatter.perm or uperm.check_user_type_permissions(formatter.req.authname, i.type_id)]
if lsr:
return tag.div([tag.ul(lsr)])
return None
class ScheduledRendezVousesMacro(WikiMacroBase):
"""Renders an overview of scheduled !RendezVouses"""
revision = "$Rev: 186 $"
def expand_macro(self, formatter, name, content):
if 'RENDEZVOUS_VIEW' not in formatter.perm:
return ""
uperm = model.RendezVousTypePermissionSystem(self.env)
controller = api.RendezVousSystem(self.env).workflow_controller
rendezvouses = controller.scheduled_rendezvouses(check=True)
lsr = []
for i in rendezvouses:
if i.elected and ('RENDEZVOUS_ADMIN' in formatter.perm or uperm.check_user_type_permissions(formatter.req.authname, i.type_id)):
lsr.append(tag.li(tag.a("%s: %s - %s" % (i.name, i.get_date(i.elected).time_begin.strftime('%Y.%m.%d %H:%M'), i.get_date(i.elected).time_end.strftime('%Y.%m.%d %H:%M')), href=formatter.href.rendezvous(i.rendezvous_id))))
if lsr:
return tag.div([tag.ul(lsr, style="list-style-image:url(%s)" % formatter.href("/chrome/hw/images/selected.png"))])
return None

View File

@ -0,0 +1,850 @@
# -*- coding: utf-8 -*-
from trac.core import *
from trac.perm import PermissionSystem
from trac.db import Table, Column, Index
from trac.db.util import sql_escape_percent
from datetime import datetime, timedelta
from trac.util.datefmt import utc, to_timestamp
from trac.util.text import to_unicode
from trac.env import IEnvironmentSetupParticipant
from ctdotools.utils import validate_id, gen_wiki_page
from dateutil.rrule import *
from collections import defaultdict
from tracrendezvous.location.model import ItemLocation as RendezVousLocation
from tracrendezvous.rendezvous import api
__all__ = ['RendezVous', 'RendezVousComment', 'RendezVousType', 'RendezVousDate',
'RendezVousVote', 'TypePermission', 'RendezVousModelProvider',
'RendezVousTypePermissionSystem']
class RendezVousVote(object):
def __init__(self, env, vote_id, date_id, user, email, time_created, time_begin, time_end):
self.env = env
self.vote_id = vote_id
self.date_id = date_id
self.user = to_unicode(user)
self.email = to_unicode(email)
self.time_created = time_created
self.time_begin = time_begin
self.time_end = time_end
@staticmethod
def fetch_one(env, vote_id):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""SELECT *
FROM rendezvous_vote
WHERE vote_id=%s""", (vote_id,))
row = cursor.fetchone()
if not row:
return None
vote_id, date_id, user, email, time_created, time_begin, time_end = row
return RendezVousVote(env, vote_id, date_id, user, email, datetime.fromtimestamp(time_created, utc), datetime.fromtimestamp(time_begin, utc), datetime.fromtimestamp(time_end, utc))
@staticmethod
def exists(env, date_id, user, time_begin, time_end):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""SELECT COUNT(*) FROM rendezvous_vote
WHERE date_id=%s
and user=%s
and time_begin=%s
and time_end=%s""", (date_id, user, to_timestamp(time_begin), to_timestamp(time_end)))
row = cursor.fetchone()
return row[0] > 0
@staticmethod
def fetch_by_date(env, date_id, userName=None):
db = env.get_db_cnx()
cursor = db.cursor()
if userName == None:
cursor.execute("""SELECT * FROM rendezvous_vote
WHERE date_id=%s
ORDER BY user""", (date_id,))
else:
cursor.execute("""SELECT * FROM rendezvous_vote
WHERE date_id=%s and user=%s
ORDER BY user;""", (date_id, userName))
res = []
for row in cursor:
vote_id, date_id, user, email, time_created, time_begin, time_end = row
res.append(RendezVousVote(env, vote_id, date_id, user, email, datetime.fromtimestamp(time_created, utc), datetime.fromtimestamp(time_begin, utc), datetime.fromtimestamp(time_end, utc)))
return res
def commit(self):
db = self.env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""INSERT INTO rendezvous_vote
(date_id, user, email, time_created, time_begin, time_end)
VALUES(%s, %s, %s, %s, %s, %s)""", (
self.date_id,
self.user,
self.email,
to_timestamp(self.time_created),
to_timestamp(self.time_begin),
to_timestamp(self.time_end)))
db.commit()
self.vote_id = db.get_last_id(cursor, 'rendezvous_vote')
@staticmethod
def delete(env, vote_id):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""DELETE FROM rendezvous_vote
WHERE vote_id =%s;""", (vote_id,))
db.commit()
@staticmethod
def delete_by_date(env, date_id):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""DELETE FROM rendezvous_vote
WHERE date_id =%s;""", (date_id,))
db.commit()
def update(self):
db = self.env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""UPDATE rendezvous_vote
SET user=%s,
email=%s,
time_created=%s,
time_begin=%s,
time_end=%s
WHERE vote_id=%s;""", (self.user,
self.email,
to_timestamp(self.time_created),
to_timestamp(self.time_begin),
to_timestamp(self.time_end),
self.vote_id))
db.commit()
def __str__(self):
return "<RendezVousVote: %d, %d, %s, %s, %s, %s, %s" %(self.vote_id, self.date_id, self.user, self.email, self.time_created, self.time_begin, self.time_end)
def timedelta_2_string(t):
return "%d,%d,%d" % (t.days, t.seconds,t.microseconds)
def string2_timedelta(t):
return timedelta(*map(int, "1,2,3".split(",",3)))
class RendezVousDate(object):
def __init__(self, env, date_id, rendezvous_id, author, email, time_created, time_begin, time_end, elected, fetch_votes=False):
self.env = env
self.date_id = date_id
self.rendezvous_id = rendezvous_id
self.author = to_unicode(author)
self.email = to_unicode(email)
self.time_created = time_created
self.time_begin = time_begin
self.time_end = time_end
self.elected = elected
if fetch_votes:
self.votes = RendezVousVote.fetch_by_date(env, self.date_id)
@staticmethod
def fetch_one(env, date_id, fetch_votes=True):
db = env.get_db_cnx()
cursor = db.cursor()
if int(date_id) > 0:
cursor.execute("""SELECT *
FROM rendezvous_date
WHERE date_id=%s""", (date_id,) )
row = cursor.fetchone()
if not row:
return None
date_id, rendezvous_id, author, email, time_created, time_begin, time_end, elected = row
return RendezVousDate(env, date_id, rendezvous_id, author, email, datetime.fromtimestamp(time_created, utc), datetime.fromtimestamp(time_begin, utc), datetime.fromtimestamp(time_end, utc), elected, fetch_votes)
@staticmethod
def exists(env, ts_begin, ts_end):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("SELECT date_id "
"FROM rendezvous_date "
"WHERE time_begin=%s AND time_end = %s",
(to_timestamp(ts_begin),
to_timestamp(ts_end)))
rows = cursor.fetchall()
return bool(rows)
@staticmethod
def fetch_by_rendezvous(env, rendezvous_id, fetch_votes=True):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""SELECT *
FROM rendezvous_date
WHERE rendezvous_id=%s;""", (rendezvous_id,) )
rows = cursor.fetchall()
res = []
for row in rows:
date_id, rendezvous_id, author, email, time_created, time_begin, time_end, elected= row
res.append(RendezVousDate(env, date_id, rendezvous_id, author, email, datetime.fromtimestamp(time_created, utc), datetime.fromtimestamp(time_begin, utc), datetime.fromtimestamp(time_end, utc), elected, fetch_votes))
return res
@staticmethod
def fetch_all(env, fetch_votes=True):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("SELECT * FROM rendezvous_date;")
rows = cursor.fetchall()
res = []
for row in rows:
date_id, rendezvous_id, author, email, time_created, time_begin, time_end, elected = row
res.append(RendezVousDate(env, date_id, rendezvous_id, author, email, datetime.fromtimestamp(time_created, utc), datetime.fromtimestamp(time_begin, utc), datetime.fromtimestamp(time_end, utc), elected, fetch_votes))
return res
def get_vote_count(self, authname=None):
db = self.env.get_db_cnx()
cursor = db.cursor()
if authname:
cursor.execute("""SELECT COUNT(rendezvous_date.date_id) from rendezvous_date INNER JOIN rendezvous_vote ON
rendezvous_date.date_id=rendezvous_vote.date_id where rendezvous_date.date_id=%s and rendezvous_vote.user=%s;""", (self.date_id, authname))
else:
cursor.execute("SELECT COUNT(rendezvous_date.date_id) from rendezvous_date INNER JOIN rendezvous_vote "
"ON rendezvous_date.date_id=rendezvous_vote.date_id "
"where rendezvous_date.date_id=%s;", (self.date_id,))
row = cursor.fetchone()
if row:
return row[0]
return 0
def commit(self, conn=None):
db = conn and conn or self.env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""INSERT INTO rendezvous_date
(rendezvous_id, author, email, time_created, time_begin, time_end, elected)
VALUES(%s,%s,%s,%s,%s,%s,%s)""", (
self.rendezvous_id,
self.author,
self.email,
to_timestamp(self.time_created),
to_timestamp(self.time_begin),
to_timestamp(self.time_end),
self.elected))
db.commit()
self.date_id = db.get_last_id(cursor, 'rendezvous_date')
@staticmethod
def delete(env, date_id):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("DELETE FROM rendezvous_date WHERE date_id=%s", (date_id,))
db.commit()
@staticmethod
def delete_by_rendezvous(env, rendezvous_id):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""DELETE FROM rendezvous_date
WHERE rendezvous_id=%s;""", (rendezvous_id,) )
db.commit()
def update(self, conn=None):
db = conn and conn or self.env.get_db_cnx()
cursor = db.cursor()
try:
cursor.execute("""UPDATE rendezvous_date
SET rendezvous_id=%s,
author=%s,
email=%s,
time_created=%s,
time_begin=%s,
time_end=%s,
elected=%s
WHERE date_id=%s""", (self.rendezvous_id,
self.author,
self.email,
to_timestamp(self.time_created),
to_timestamp(self.time_begin),
to_timestamp(self.time_end),
self.elected,
self.date_id))
if not conn:
db.commit()
except Exception:
pass
def __str__(self):
return "<RendezVousDate: %d, %d, %s, %s, %s, %s, %s, %s>" % (self.date_id, self.rendezvous_id, self.author, self.email, str(self.time_created), str(self.time_begin), str(self.time_end), self.elected)
class RendezVousComment(object):
def __init__(self, env, comment_id, rendezvous_id, author, comment, time_created):
self.env = env
self.comment_id = comment_id
self.rendezvous_id = rendezvous_id
self.author = to_unicode(author)
self.comment = to_unicode(comment)
self.time_created = time_created
@staticmethod
def fetch_one(env, comment_id):
db = env.get_db_cnx()
cursor = db.cursor()
if int(comment_id) > 0:
cursor.execute("SELECT * "
"FROM rendezvous_comment "
"WHERE comment_id=%s", (comment_id,) )
row = cursor.fetchone()
if not row:
return None
comment_id, rendezvous_id, author, comment, time_created = row
return RendezVousComment(env, comment_id, rendezvous_id, author, comment, datetime.fromtimestamp(time_created, utc))
@staticmethod
def fetch_by_rendezvous(env, rendezvous_id):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""SELECT *
FROM rendezvous_comment
WHERE rendezvous_id=%s;""", (rendezvous_id,) )
rows = cursor.fetchall()
res = []
for row in rows:
comment_id, rendezvous_id, author, comment, time_created = row
res.append(RendezVousComment(env, comment_id, rendezvous_id, author, comment, datetime.fromtimestamp(time_created, utc)))
return res
@staticmethod
def fetch_all(env):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("SELECT * FROM rendezvous_comment;")
rows = cursor.fetchall()
res = []
for row in rows:
comment_id, rendezvous_id, author, comment, time_created = row
res.append(RendezVousComment(env, comment_id, rendezvous_id, author, comment, datetime.fromtimestamp(time_created, utc)))
return res
def commit(self):
db = self.env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""INSERT INTO rendezvous_comment
(rendezvous_id, author, comment, time_created)
VALUES(%s,%s,%s,%s)""", (
self.rendezvous_id,
self.author,
self.comment,
to_timestamp(self.time_created)))
db.commit()
self.comment_id = db.get_last_id(cursor, 'rendezvous_comment')
@staticmethod
def delete(env, comment_id):
db = env.get_db_cnx()
RendezVousVote.delete_by_date(env, comment_id)
cursor = db.cursor()
cursor.execute("DELETE FROM rendezvous_comment WHERE comment_id=%s", (comment_id,))
db.commit()
@staticmethod
def delete_by_rendezvous(env, rendezvous_id):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("DELETE FROM rendezvous_comment WHERE rendezvous_id=%s", (rendezvous_id,))
db.commit()
def update(self):
db = self.env.get_db_cnx()
cursor = db.cursor()
try:
cursor.execute("""UPDATE rendezvous_comment
SET rendezvous_id=%s,
author=%s,
comment=%s,
time_created=%s
WHERE comment_id=%s""", (self.rendezvous_id,
self.author,
self.comment,
to_timestamp(self.time_created),
self.comment_id))
db.commit()
except Exception:
pass
def __str__(self):
return "<RendezVousComment: %d, %d, %s, %s, %s>" % (self.comment_id, self.rendezvous_id, self.author, self.comment, str(self.time_created))
class RendezVousType(object):
def __init__(self, env, type_id, name):
self.env = env
self.type_id = type_id
self.name = to_unicode(name)
self.typePermissions = TypePermission.fetch(env, type_id)
@staticmethod
def fetch_one(env, type_id=None, name=None):
db = env.get_db_cnx()
cursor = db.cursor()
if type_id and type_id > 0:
validate_id(int(type_id))
cursor.execute("""SELECT *
FROM rendezvous_type
WHERE type_id=%s""", (type_id,))
else:
cursor.execute("""SELECT *
FROM rendezvous_type
WHERE name=%s""", (name,))
row = cursor.fetchone()
if row:
return RendezVousType(env, row[0], row[1])
return None
@staticmethod
def fetch_all(env):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("SELECT * FROM rendezvous_type")
rows = cursor.fetchall()
if not rows:
return []
res = []
for row in rows:
res.append(RendezVousType(env, row[0], row[1]))
return res
def commit(self):
db = self.env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""INSERT INTO rendezvous_type
(name)
VALUES(%s)""", (self.name,))
db.commit()
self.type_id = db.get_last_id(cursor, 'rendezvous_type')
def has_permission(self, permission):
for i in self.typePermissions:
if i.permission == permission:
return True
return False
def delete(self):
db = self.env.get_db_cnx()
cursor = db.cursor()
TypePermission.delete_by_type(self.env, self.type_id)
cursor.execute("DELETE FROM rendezvous_type WHERE type_id=%s", (self.type_id,))
db.commit()
def update(self):
db = self.env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""UPDATE rendezvous_time
SET name=%s
WHERE type_id=%s""", (self.name, self.type_id))
db.commit()
class TypePermission(object):
def __init__(self, env, type_id, permission):
self.env = env
self.type_id = type_id
self.permission = permission
@staticmethod
def fetch(env, type_id):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""SELECT *
FROM rendezvous_type_to_permission
WHERE type_id=%s""", (type_id,) )
rows = cursor.fetchall()
if not rows:
return []
res = []
for row in rows:
res.append(TypePermission(env, row[0], row[1]))
return res
@staticmethod
def fetch_one(env, type_id, permission):
db = env.get_db_cnx()
cursor = db.cursor()
if int(type_id) > 0:
cursor.execute("""SELECT *
FROM rendezvous_type_to_permission
WHERE type_id=%s AND permission=%s""", (type_id, permission))
row = cursor.fetchone()
if row:
return TypePermission(env, row[0], row[1])
return None
@staticmethod
def delete_by_type(env, type_id):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("DELETE FROM rendezvous_type_to_permission WHERE type_id=%s", (type_id,))
db.commit()
def commit(self):
db = self.env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""INSERT INTO rendezvous_type_to_permission
(type_id, permission)
VALUES(%s,%s)""", (self.type_id, self.permission))
db.commit()
def delete(self):
db = self.env.get_db_cnx()
cursor = db.cursor()
cursor.execute("DELETE FROM rendezvous_type_to_permission WHERE type_id=%s AND permission=%s", (self.type_id, self.permission))
db.commit()
def __str__(self):
return "<TypePermission: %d %s>" % (self.type_id, self.permission)
class RendezVous(object):
def __init__(self, env, fetch_dates, rendezvous_id, name, author, email, description, time_created, schedule_deadline, min_votes, type_id, status, location_id, is_date_fixed, tags):
self.env = env
self.rendezvous_id = rendezvous_id
self.name = to_unicode(name)
self.author = to_unicode(author)
self.email = to_unicode(email)
self.description = to_unicode(description)
self.time_created = time_created
self.schedule_deadline = schedule_deadline
self.min_votes = min_votes
self.type_id = type_id
t = RendezVousType.fetch_one(self.env, type_id)
self.type_name = t and t.name or None
self.status = to_unicode(status)
self.location_id = location_id
self.is_date_fixed = is_date_fixed
self.dates = []
self.tags = tags
self.elected = 0
if fetch_dates:
self.dates = RendezVousDate.fetch_by_rendezvous(env, self.rendezvous_id)
for i in self.dates:
if i.elected:
self.elected = i.date_id
@staticmethod
def fetch_one(env, rid=None, name=None, fetch_dates=False):
db = env.get_db_cnx()
cursor = db.cursor()
rendezvous_id=0
if rid:
rendezvous_id = int(rid)
validate_id(rendezvous_id)
cursor.execute("""SELECT *
FROM rendezvous
WHERE rendezvous_id=%s""", (rendezvous_id,))
if name:
myname = unicode(name)
cursor.execute("""SELECT *
FROM rendezvous
WHERE name=%s""", name)
row = cursor.fetchone()
if not row:
return None
rendezvous_id, name, author, email, description, time_created, schedule_deadline, min_votes, type_id, status, location_id, is_date_fixed, tags = row
return RendezVous(env, fetch_dates, rendezvous_id, name, author, email, description,
datetime.fromtimestamp(time_created, utc), datetime.fromtimestamp(schedule_deadline, utc), min_votes, type_id, status, location_id, is_date_fixed, tags)
@staticmethod
def _fetch_some(env, fetch_dates, query, *args):
db = env.get_db_cnx()
cursor = db.cursor()
if args:
cursor.execute(query, args)
else:
cursor.execute(query)
rows = cursor.fetchall()
if not rows:
return []
res = []
for row in rows:
rendezvous_id, name, author, email, description, time_created, schedule_deadline, min_votes, type_id, status, location_id, is_date_fixed, tags = row
res.append(
RendezVous(env, fetch_dates, rendezvous_id, name, author,
email, description, datetime.fromtimestamp(time_created, utc), datetime.fromtimestamp(schedule_deadline, utc),
min_votes, type_id, status, location_id, is_date_fixed, tags))
return res
def get_date(self, date_id):
for i in self.dates:
if i.date_id == date_id:
return i
raise ValueError("RendezVousDate not found in RendezVous")
@staticmethod
def fetch_all(env, fetch_dates=False, sort=None):
if not sort:
return RendezVous._fetch_some(env, fetch_dates, "SELECT * FROM rendezvous;")
return RendezVous._fetch_some(env, fetch_dates, "SELECT * FROM rendezvous ORDER BY name")
@staticmethod
def my_rendezvous(env, name):
return RendezVous._fetch_some(env, False, "SELECT * FROM rendezvous where author = %s;", name)
@staticmethod
def exists(env, rendezvous_id=0):
db = env.get_db_cnx()
cursor = db.cursor()
if int(rendezvous_id) <= 0:
return False
cursor.execute("""SELECT *
FROM 'rendezvous'
WHERE rendezvous_id=%s""", (rendezvous_id,))
row = cursor.fetchone()
return row != None
def has_voted(self, authname=None):
for date in self.dates:
for vote in date.votes:
if vote.user == authname:
return True
return False
def has_votes(self):
for date in self.dates:
if date.votes:
return True
return False
def commit(self):
db = self.env.get_db_cnx()
t = datetime.now(utc)
cursor = db.cursor()
cursor.execute( "INSERT INTO rendezvous "
"(name,author,email,description,time_created,schedule_deadline,min_votes,type_id,status,location_id,is_date_fixed,tags) "
"VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
(self.name, self.author, self.email, self.description, to_timestamp(t),
to_timestamp(self.schedule_deadline), self.min_votes, self.type_id,
self.status, self.location_id, self.is_date_fixed, self.tags))
db.commit()
self.rendezvous_id = db.get_last_id(cursor, 'rendezvous')
@staticmethod
def delete(env, rendezvous_id):
db = env.get_db_cnx()
cursor = db.cursor()
cursor.execute("DELETE FROM rendezvous WHERE rendezvous_id = %s", (rendezvous_id,))
db.commit()
def update(self):
db = self.env.get_db_cnx()
cursor = db.cursor()
cursor.execute("UPDATE rendezvous " \
"SET name =%s, " \
"author=%s, " \
"email=%s, " \
"description=%s, " \
"time_created=%s, " \
"schedule_deadline=%s, " \
"min_votes=%s, " \
"type_id=%s, " \
"status=%s, " \
"location_id=%s, " \
"is_date_fixed=%s, " \
"tags=%s " \
"WHERE rendezvous_id=%s", (self.name, self.author, self.email,
self.description, to_timestamp(self.time_created), to_timestamp(self.schedule_deadline),
self.min_votes, self.type_id, self.status, self.location_id, self.is_date_fixed, self.tags, self.rendezvous_id))
db.commit()
def __str__(self):
return "<RendezVous: %d, %s, %s, %s, %s>" % (self.rendezvous_id, self.name, self.author, self.email, str(self.time_created))
class RendezVousModelProvider(Component):
implements(IEnvironmentSetupParticipant)
SCHEMA = [
# rendezvous system
Table('rendezvous', key='rendezvous_id')[
Column('rendezvous_id', auto_increment=True),
Column('name'),
Column('author'),
Column('email'),
Column('description'),
Column('time_created', type='int'),
Column('schedule_deadline', type='int'),
Column('min_votes', type='int'),
Column('type_id', type='int'),
Column('status'),
Column('location_id', type='int'),
Column('is_date_fixed', type='int'),
Column('tags'),
Index(['name']),
Index(['status']) ],
Table('rendezvous_comment', key='comment_id')[
Column('comment_id', auto_increment=True),
Column('rendezvous_id', type='int'),
Column('author'),
Column('comment'),
Column('time_created', type='int')],
# an user's spare time frame
Table('rendezvous_date', key='id')[
Column('date_id', auto_increment=True),
Column('rendezvous_id', type='int'),
Column('author'),
Column('email'),
Column('time_created', type='int'),
Column('time_begin', type='int'),
Column('time_end', type='int'),
Column('elected', type='int')],
Table('rendezvous_type', key=["type_id"])[
Column("type_id", auto_increment=True),
Column("name"),
Index(["name"])],
Table('rendezvous_type_to_permission', key=["type_id", "permission"])[
Column("type_id", type="int"),
Column("permission")],
# user's votings for a date with date and length of time frame
Table('rendezvous_vote', key=['vote_id'])[
Column('vote_id', auto_increment=True),
Column('date_id', type='int'),
Column('user'),
Column('email'),
Column('time_created', type='int'),
Column('time_begin', type='int'),
Column('time_end', type='int'),
Index(['time_begin']),
Index(['time_end'])]]
RendezVousDateTrigger = "CREATE TRIGGER fkd_date_rendezvous_id " \
"BEFORE DELETE ON rendezvous " \
"FOR EACH ROW BEGIN " \
"DELETE from rendezvous_date WHERE rendezvous_id = OLD.rendezvous_id; " \
"END;"
RendezVousCommentTrigger = "CREATE TRIGGER fkd_comment_rendezvous_id " \
"BEFORE DELETE ON rendezvous " \
"FOR EACH ROW BEGIN " \
"DELETE from rendezvous_comment WHERE rendezvous_id = OLD.rendezvous_id; " \
"END;"
RendezVousVoteTrigger = "CREATE TRIGGER fkd_vote_date_id " \
"BEFORE DELETE ON rendezvous_date " \
"FOR EACH ROW BEGIN " \
"DELETE from rendezvous_vote WHERE date_id = OLD.date_id; " \
"END;"
TYPE_DATA = (
(u'public',),
(u'admin',),
(u'Offizieller Treff',),
(u'Topic Treff',))
LOCATION_DATA = (
(u"CTDO, Langer August", "N", 51, 31, 39.4, "E", 7, 27, 53.8, 51.527611, 7.464922),
(u"WILA, Langer August", "N", 51, 31, 39.4, "E", 7, 27, 53.8, 51.527611, 7.464922))
TYPE_PERMISSIONS_DATA = (
(1, u'RENDEZVOUS_VIEW'),
(2, u'RENDEZVOUS_ADMIN'))
def environment_created(self):
if not "rendezvous" in self.config.sections():
data = {"graph_size_x" : 1024,
"graph_size_y" : 300,
"max_dates_per_rendezvous" : 99,
"max_description_length" : 1024,
"max_votes_per_date" : 99,
"show_location_map" : True,
"show_vote_graph" : True}
for k, v in data.iteritems():
self.config.set("rendezvous", k, v)
self.config.save()
self._create_models(self.env.get_db_cnx())
def environment_needs_upgrade(self, db):
"""First version - nothing to migrate, but possibly to create.
"""
cursor = db.cursor()
try:
cursor.execute("select count(*) from rendezvous")
cursor.fetchone()
cursor.execute("select count(*) from rendezvous_date")
cursor.fetchone()
cursor.execute("select count(*) from rendezvous_comment")
cursor.fetchone()
cursor.execute("select count(*) from rendezvous_type")
cursor.fetchone()
cursor.execute("select count(*) from rendezvous_type_to_permission")
cursor.fetchone()
cursor.execute("select count(*) from rendezvous_vote")
cursor.fetchone()
return False
except:
db.rollback()
return True
def upgrade_environment(self, db):
""" nothing to do here for now
"""
self._create_models(db)
def _create_models(self, db):
"""Called when a new Trac environment is created."""
db_backend = None
try:
from trac.db import DatabaseManager
db_backend, _ = DatabaseManager(self.env)._get_connector()
except ImportError:
db_backend = self.env.get_db_cnx()
cursor = db.cursor()
for table in self.SCHEMA:
for stmt in db_backend.to_sql(table):
self.env.log.debug(stmt)
try:
cursor.execute(stmt)
db.commit()
except Exception, e:
self.env.log.warning(str(e))
db.rollback()
cursor.execute(self.RendezVousCommentTrigger)
cursor.execute(self.RendezVousDateTrigger)
cursor.execute(self.RendezVousVoteTrigger)
db.commit()
#try:
cursor.executemany("""INSERT INTO 'rendezvous_type'
(name)
VALUES(%s)""", self.TYPE_DATA)
db.commit()
class RendezVousTypePermissionSystem(Component):
def check_user_type_permissions(self, user, type_id=None, name=None):
ps = PermissionSystem(self.env).get_user_permissions(user)
if 'RENDEZVOUS_ADMIN' in ps:
return True
t = RendezVousType.fetch_one(self.env, type_id=type_id, name=name)
if not t:
return False
db = self.env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""SELECT permission
FROM rendezvous_type_to_permission
WHERE type_id=%s""", (t.type_id,))
rows = cursor.fetchall()
for p in rows:
if not ps.has_key(p[0]):
return False
return True

View File

@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
from trac import __version__
from trac.core import *
from trac.config import *
from trac.notification import NotifyEmail
from trac.util import md5
from trac.util.datefmt import to_timestamp
from trac.util.text import CRLF, wrap, to_unicode, obfuscate_email_address
from genshi.template.text import TextTemplate
from tracrendezvous.model import RendezVous, RendezVousLocation
class RendezVousNotificationSystem(Component):
always_notify_workflow_change = BoolOption('rendezvous', 'always_notify_workflow_changes',
'true',
"""Always send notifications to all authors, voters in the rendezvous on workflow changes""")
ticket_subject_template = Option('rendezvous', 'ticket_subject_template',
'$prefix #$rendezvous.rendezvous_id: $summary',
"""A Genshi text template snippet used to get the notification subject.
(since 0.3)""")
class RendezVousSchedulingNotifyEmail(NotifyEmail):
"""Notification for a scheduled rendezvous.
"""
template_name = "rendezvous_notify_email.txt"
rendezvous = None
modtime = 0
from_email = 'trac+rendezvous@localhost'
COLS = 75
ical_fmt = "%Y%m%dT%H%M00Z"
def __init__(self, env):
NotifyEmail.__init__(self, env)
def notify(self, editor, rendezvous, is_new=False, modtime=None):
self.editor = editor
self.rendezvous = rendezvous
self.modtime = modtime
self.is_new = is_new
changes_body = ''
changes_descr = ''
self.rendezvous.link = self.env.abs_href.rendezvous(rendezvous.rendezvous_id)
self.rendezvous_description = wrap(
self.rendezvous.description, self.COLS,
initial_indent=' ', subsequent_indent=' ', linesep=CRLF)
subject = self.format_subj("RendezVous evolved to %s")
if not is_new:
subject = 'Re: ' + subject
self.data.update({
'rendezvous_props': self.format_props(),
'rendezvous_body_hdr': self.format_hdr(),
'subject': subject,
'rendezvous': rendezvous,
'changes_body': changes_body,
'changes_descr': changes_descr
})
NotifyEmail.notify(self, rendezvous.rendezvous_id, subject)
def format_props(self):
rendezvous = self.rendezvous
text = ""
text+= "%s: %s\n" % ("Name".center(12), rendezvous.name)
text+= "%s: %s\n" % ("Author".center(12), rendezvous.author)
text+= "%s: %s\n" % ("Created on".center(12), rendezvous.time_created)
location = RendezVousLocation.fetch_one(self.env, rendezvous.location_id)
text+= "%s: %s\n" % ("Location".center(12), location and location.name or "")
text+= "%s: %s\n" % ("Coordinates".center(12), location and location.coordinate_str() or "")
return text
def get_recipients(self, rendezvous_id):
def parse_email(txt):
return filter(lambda x: '@' in x, txt.replace(',', ' ').split())
r = RendezVous.fetch_one(self.env, rendezvous_id, fetch_dates=True)
recipients = []
if r:
tmp = r.email
if tmp:
emails = parse_email(tmp)
recipients.extend(emails)
tmp = r.dates[r.elected].email
if tmp:
emails = parse_email(tmp)
recipients.extend(emails)
return recipients, []
def format_hdr(self):
return '#%s: %s' % (self.rendezvous.rendezvous_id, wrap(self.rendezvous.description,
self.COLS, linesep=CRLF))
def format_subj(self, summary):
template = self.config.get('rendezvous','rendezvous_subject_template')
template = TextTemplate(template.encode('utf8'))
prefix = self.config.get('notification', 'smtp_subject_prefix')
if prefix == '__default__':
prefix = '[%s]' % self.config.get('project', 'name')
data = {
'prefix': prefix,
'summary': summary,
'rendezvous': self.rendezvous,
'env': self.env,
}
return template.generate(**data).render('text', encoding=None).strip()
def get_message_id(self, rcpt, modtime=None):
"""Generate a predictable, but sufficiently unique message ID."""
s = '%s.%08d.%d.%s' % (self.config.get('project', 'url'),
int(self.rendezvous.rendezvous_id), to_timestamp(modtime),
rcpt.encode('ascii', 'ignore'))
dig = md5(s).hexdigest()
host = self.from_email[self.from_email.find('@') + 1:]
msgid = '<%03d.%s@%s>' % (len(s), dig, host)
return msgid
def send(self, torcpts, ccrcpts):
dest = self.editor or 'anonymous'
hdrs = {}
hdrs['Message-ID'] = self.get_message_id(dest, self.modtime)
hdrs['X-Trac-Ticket-ID'] = str(self.rendezvous.rendezvous_id)
hdrs['X-Trac-Ticket-URL'] = self.rendezvous.link
if not self.is_new:
msgid = self.get_message_id(dest)
hdrs['In-Reply-To'] = msgid
hdrs['References'] = msgid
NotifyEmail.send(self, torcpts, ccrcpts, hdrs)

View File

@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
import re
from trac.core import *
from tractags.api import TagSystem, ITagProvider
from trac.util.text import to_unicode
from trac.util.compat import set, sorted
from trac.config import *
from trac.resource import Resource
from model import RendezVous
__all__ = ['RendezVousTagProvider',]
class RendezVousTagProvider(Component):
"""A tag provider using rendezvous tag field as sources of tags.
"""
implements(ITagProvider)
ignore_closed_rendezvous = BoolOption('tags', 'ignore_closed_rendezvous', True,
'Do not collect tags from closed rendezvous.')
# ITagProvider methods
def get_taggable_realm(self):
return 'rendezvous'
def get_tagged_resources(self, req, tags):
if 'RENDEZVOUS_VIEW' not in req.perm:
return
split_into_tags = TagSystem(self.env).split_into_tags
db = self.env.get_db_cnx()
cursor = db.cursor()
args = []
ignore = ''
if self.ignore_closed_rendezvous:
ignore = " WHERE status != 'closed'"
sql = "SELECT * FROM (SELECT rendezvous_id, tags, COALESCE(tags, '') as fields FROM rendezvous%s)" % ignore
constraints = []
if tags:
constraints.append(
"(" + ' OR '.join(["fields LIKE %s" for t in tags]) + ")")
args += ['%' + t + '%' for t in tags]
else:
constraints.append("fields != ''")
if constraints:
sql += " WHERE " + " AND ".join(constraints)
sql += " ORDER BY rendezvous_id"
self.env.log.debug(sql)
cursor.execute(sql, args)
for row in cursor:
id, ttags = row[0], ' '.join([f for f in row[1:-1] if f])
rendezvous_tags = split_into_tags(ttags)
tags = set([to_unicode(x) for x in tags])
if (not tags or rendezvous_tags.intersection(tags)):
yield Resource('rendezvous', id), rendezvous_tags
def get_resource_tags(self, req, resource):
if 'RENDEZVOUS_VIEW' not in req.perm(resource):
return
rendezvous = RendezVous.fetch_one(self.env, resource.id)
tags = self._rendezvous_tags(rendezvous)
return tags
def set_resource_tags(self, req, resource, tags):
#req.perm.require('RENDEZVOUS_MODIFY', resource)
split_into_tags = TagSystem(self.env).split_into_tags
rendezvous = RendezVous.fetch_one(self.env, resource.id)
all = self._rendezvous_tags(rendezvous)
tags.difference_update(all.difference(rendezvous.tags))
rendezvous.tags = u' '.join(sorted(map(to_unicode, tags)))
rendezvous.update()
def remove_resource_tags(self, req, resource):
req.perm.require('RENDEZVOUS_MODIFY', resource)
rendezvous = RendezVous.fetch_one(self.env, resource.id)
rendezvous.tags = None
rendezvous.update()
# Private methods
def _rendezvous_tags(self, rendezvous):
return TagSystem(self.env).split_into_tags(rendezvous.tags)

View File

@ -0,0 +1,57 @@
<!DOCTYPE htm
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="admin.html" />
<head>
<title>RendezVous General Settings</title>
</head>
<body>
<h2>General Settings</h2>
<form class='mod' id="modbasic" action="" method="post">
<fieldset>
<legend>RendezVous</legend>
<div class="field">
<label>max description length:<br/>
<input type="text" name="max_description_length" value="${max_description_length}"/>
</label>
</div>
<div class="field">
<label>max votes per date:<br/>
<input type="text" name="max_votes_per_date" value="${max_votes_per_date}"/>
</label>
</div>
<div class="field">
<label>max dates per rendezvous:<br/>
<input type="text" name="max_dates_per_rendezvous" value="${max_dates_per_rendezvous}"/>
</label>
</div>
<div class="field">
<label>graph width:<br/>
<input type="text" name="graph_size_x" value="${graph_size_x}"/>
</label>
</div>
<div class="field">
<label>graph height:<br/>
<input type="text" name="graph_size_y" value="${graph_size_y}"/>
</label>
</div>
<div class="field">
<label>show vote graph:<br/>
<input type="checkbox" name="show_vote_graph" checked="${show_vote_graph and 'checked' or None}"/>
</label>
</div>
<div class="field">
<label>show location map:<br/>
<input type="checkbox" name="show_location_map" checked="${show_location_map and 'checked' or None}"/>
</label>
</div>
</fieldset>
<div class="buttons">
<input type="submit" value="Apply changes" />
</div>
</form>
</body>
</html>

View File

@ -0,0 +1,38 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:py="http://genshi.edgewall.org/">
<xi:include href="admin.html" />
<head>
<title>$label_plural</title>
<script type="text/javascript">
/* <![CDATA[ */
/* ]]> */
</script>
</head>
<body>
<h2>Overview Configuration</h2>
<form id="overview" name="overview" method="post" action="">
<fieldset>
<legend>Overview Configuration</legend>
<div class="overview-row" py:for="l in all_status">
<div class="overview-set">
${l[0]}
<input type="text" id="cat_${l[1]}" name="${l[0]}" size="3" maxlength="3" value="${l[1]}"/><span py:if="len(l) == 4" class="error">wrong position - should be ${l[3]}!</span>
<input type="checkbox" name="show" value="${l[0]}" checked="${l[2] and 'checked' or None}"/>
</div>
</div>
<p class="help">
help
</p>
<div class="buttons">
<input type="submit" name="save" value="Save" />
</div>
</fieldset>
</form>
</body>
</html>

View File

@ -0,0 +1,95 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:py="http://genshi.edgewall.org/">
<xi:include href="admin.html" />
<head>
<title>$label_plural</title>
</head>
<body>
<h2>Manage RendezVousTypes</h2>
<py:if test="'RENDEZVOUS_ADMIN' in perm">
<form id="addperm" class="addnew" method="post" action="">
<fieldset>
<legend>Add Type:</legend>
<table>
<tr class="field">
<th><label for="gp_subject">RendezVous Type:</label></th>
<td><input id="gp_subject" type="text" name="rtype" /></td>
</tr>
</table>
<p class="help">
Create a new RendezVous Type. Note that <em>RendezVousType</em> names can't be all upper-case,
as that is reserved for permission names.
</p>
<div class="buttons">
<input type="submit" name="add" value=" Add " />
</div>
</fieldset>
</form>
<form id="addsubj" class="addnew" method="post" action="">
<fieldset>
<legend>Grant Permission To Type:</legend>
<table>
<tr class="field">
<th><label for="rtype">RendezVousType:</label></th>
<td><select id="rtype" name="rtype">
<option py:for="rtype in rendezVousTypes">${rtype.name}</option>
</select>
</td>
</tr>
<tr class="field">
<th><label for="permission">Permission:</label></th>
<td><select id="permission" name="permission">
<option py:for="action in sorted(actions)">${action}</option>
</select>
</td>
</tr>
</table>
<p class="help">
Grant permission to a RendezVousType.
</p>
<div class="buttons">
<input type="submit" name="add" value=" Add " />
</div>
</fieldset>
</form>
</py:if>
<form method="post" action="">
<table class="listing" id="permlist">
<thead>
<tr><th>RendezVous Type</th><th>Default</th><th>Permission</th><th>delete</th></tr>
</thead>
<tbody>
<tr py:for="idx, rtype in enumerate(rendezVousTypes)"
class="${idx % 2 and 'odd' or 'even'}">
<td>${rtype.name}</td>
<td><input type="radio" name="default" value="${rtype.type_id}" checked="${rtype.type_id == default_rendezvous_type and 'checked' or None}" /></td>
<td>
<py:for each="typePerm in rtype.typePermissions">
<div>
<input type="checkbox" id="${typePerm.type_id}:${typePerm.permission}" name="sel" value="${typePerm.type_id}:${typePerm.permission}" />
<label for="${typePerm.type_id}:${typePerm.permission}">${typePerm.permission}</label>
</div>
</py:for>
</td>
<td><input type="checkbox" id="delete:${rtype.type_id}" name="rsel" value="${rtype.type_id}"/></td>
</tr>
</tbody>
</table>
<div class="buttons">
<input type="submit" name="save" value="Save Changes" />
</div>
</form>
<p class="help">
Note that <em>RendezVousType</em> names can't be all upper-case,
as that is reserved for permission names.
</p>
</body>
</html>

View File

@ -0,0 +1,106 @@
<!DOCTYPE htm
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="layout.html" />
<head>
<script type="text/javascript">
jQuery(document).ready(function($) {
$("input[name^='date_begin']").datepicker({"dateFormat" : "dd.mm.yy", altField: "#date_end"});
$("input[name^='date_end']").datepicker({"dateFormat" : "dd.mm.yy"});
});
</script>
<title>Dates</title>
</head>
<body>
<div id="content" py:if="rendezvous">
<div id='rendezvous-main'>
<py:choose>
<py:when test="'RENDEZVOUS_DATE_MODIFY' in perm or 'RENDEZVOUS_DATE_ADD' in perm or 'RENDEZVOUS_DATE_DELETE' in perm">
<form py:if="len(dates) > 0" class='votes' method="post" action="">
<fieldset >
<legend>Dates for ${rendezvous.name}</legend>
<input type="hidden" name="item" value="${rendezvous.rendezvous_id}"/>
<table class="listing">
<thead>
<tr>
<th>author</th>
<th>email</th>
<th>date begin</th>
<th>time begin</th>
<th>date end</th>
<th>time end</th>
<th>delete</th>
</tr>
</thead>
<py:for each="date in dates">
<tr py:with="dt = selected_tz.fromutc(date.time_begin);
dt2 = selected_tz.fromutc(date.time_end)">
<td>${date.author}</td>
<td><input type="text" size="25" name="email:${date.date_id}" value="${date.email}"/></td>
<td><input type="text" size="10" maxlength="10" name="date_begin:${date.date_id}" value="${date.time_begin.strftime('%d.%m.%Y')}"/></td>
<td><input type="text" size="5" maxlength="5" name="time_begin:${date.date_id}" value="${dt.strftime('%H:%M')}"/> ${dt.tzinfo.tzname(None)}</td>
<td><input type="text" size="10" maxlength="10" id="date_end" name="date_end:${date.date_id}" value="${date.time_end.strftime('%d.%m.%Y')}"/></td>
<td><input type="text" size="5" maxlength="5" name="time_end:${date.date_id}" value="${dt2.strftime('%H:%M')}"/> ${dt2.tzinfo.tzname(None)}</td>
<td><input type="checkbox" name="delete" value="${date.date_id}"/></td>
</tr>
</py:for>
</table>
</fieldset>
<p class="help">Change or delete votes for an existing rendezvous date.</p>
<div class="mybuttons">
<input type="reset" name="reset"/>
<input type="submit" name="savedates" value="Save Changes"/>
</div>
</form>
<form class='rendezvous-wizard' method="post" mime-type="text/plain">
<input type="hidden" name="rendezvous_id" value="$rendezvous.rendezvous_id"/>
<fieldset id="properties">
<legend>New Date</legend>
<table class="rendezvous-wizard" py:with="dt = selected_tz.fromutc(new_date.time_begin);
dt2 = selected_tz.fromutc(new_date.time_end)">
<tr><th><label for="nd_email">Email:</label></th><td><input id="nd_email" type="text" size="24" maxlength="25" name="email"/></td></tr>
<tr><th><label for="nd_date_begin">Date begin:</label></th><td><input id="nd_date_begin" type="text" size="10" maxlength="10" name="date_begin" value="${new_date.time_begin.strftime('%d.%m.%Y')}"/>
<input id="nd_time_begin" type="text" size="5" maxlength="5" name="time_begin" value="${dt.strftime('%H:%M')}"/> ${dt.tzinfo.tzname(None)}</td></tr>
<tr><th><label for="date_end">Date end:</label></th><td><input id="date_end" type="text" size="10" maxlength="10" name="date_end" value="${new_date.time_end.strftime('%d.%m.%Y')}"/>
<input id="nd_time_end" type="text" size="5" maxlength="5" name="time_end" value="${dt2.strftime('%H:%M')}"/> ${dt2.tzinfo.tzname(None)}</td></tr>
<tr><th><label for="nd_autovoting">Vote that for me!</label></th><td><input id="nd_autovoting" type="checkbox" checked="checked" name="autovoting"/></td></tr>
</table>
</fieldset>
<div class="mybuttons">
<input type="reset" name="reset"/>
<input type="submit" name="newdate" value=" Add " />
</div>
</form>
</py:when>
<h3>Allowed date/time formats:</h3>
<ul>
<li>yyyymmdd</li>
<li>yyyy.mm.dd</li>
</ul>
<py:otherwise>
<h2>Dates for ${rendezvous.name}</h2>
<table class="listing">
<thead>
<tr>
<th>author</th>
<th>day</th>
<th>day part</th>
</tr>
</thead>
<tbody>
<tr py:for="date in dates">
<td>${date.author}</td>
<td>${date.time_begin.strftime('%d.%m.%Y %H:%M')}</td>
<td>${date.time_end.strftime('%d.%m.%Y %H:%M')}</td>
</tr>
</tbody>
</table>
</py:otherwise>
</py:choose>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,126 @@
<!DOCTYPE htm
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="layout.html" />
<head>
<script type="text/javascript">
/* <![CDATA[ */
jQuery(document).ready(function($) {$("#nr_location_search").get(0).focus()});
function setLocation()
{
for (i = 0; i < document.mylocation.location_sel.length; ++i)
if (document.mylocation.location_sel.options[i].selected == true)
{
document.mylocation.location_name.value = document.mylocation.location_sel.options[i].value;
document.mylocation.coordinates.value = document.getElementById("nr_coord_" + i.toString()).value;
}
}
function createIframe(lat, lon)
{
var iframe;
try
{
iframe = document.getElementById("mapframe");
iframe.parentNode.removeChild(iframe);
}
catch(e)
{
}
if (document.createElement && (iframe =
document.createElement("iframe")))
{
var blonstart= lon - 0.00928;
var blonend= lon + 0.011276;
var blatstart = lat - 0.0033;
var bladend = lat + 0.0109;
iframe.name = iframe.id = "mapframe";
iframe.width = "100%";
iframe.height = "600px";
iframe.src = "http://www.openstreetmap.org/export/embed.html?bbox=" + blonstart.toString() + "," + blatstart.toString() + "," + blonend.toString() + "," + bladend.toString() + "&marker=" + lat + "," + lon + "&zoom=16";
document.getElementById("location-results").appendChild(iframe);
}
}
/* ]]> */
</script>
<title>Locations</title>
</head>
<body>
<div id="content">
<div id='rendezvous-main'>
<form name="mylocation" class='rendezvous-wizard' uri="" method="post" mime-type="text/plain" action="">
<input type="hidden" value="${rendezvous_id}"/>
<fieldset id="location">
<legend>Known Locations</legend>
<table class="listing">
<thead><tr><th>Name</th><th>Coordinates</th><th py:if="'RENDEZVOUS_LOCATION_MODIFY' in perm">Default</th><th py:if="'RENDEZVOUS_LOCATION_DELETE' in perm">Delete</th></tr></thead>
<tr py:for="location in locations">
<td><input name="name:${location.location_id}" type="text" size="40" maxlength="40" value="${location.name}"/></td>
<td><input name="location:${location.location_id}" type="text" size="32" maxlength="32" value="${location.coordinate_str()}"/></td>
<py:if test="'RENDEZVOUS_LOCATION_MODIFY' in perm">
<td><input type="radio" name="default" value="${location.location_id}" checked="${default_location == location.location_id and 'checked' or None}"/></td>
<td><input name="delete:${location.location_id}" type="checkbox" /></td>
</py:if>
</tr>
</table>
<p class="help">Change or delete existing locations.</p>
<div class="mybuttons">
<input type="reset" name="reset"/>
<input type="submit" name="savelocations" value="Save Changes"/>
</div>
</fieldset>
</form>
<form name="newlocation" class='rendezvous-wizard' uri="" method="post" mime-type="text/plain" action="">
<fieldset id="new location">
<legend>New Location</legend>
<table class="rendezvous-wizard">
<tr><th><label for="nr_location_text">Name:</label></th><td><input id="nr_location_text" type="text" size="24" maxlength="25" name="location_name" value=""/></td></tr>
<tr><th><label for="nr_location_text">Coordinates:</label></th><td><input id="nr_coordinates_text" type="text" size="32" maxlength="32" name="coordinates" value=""/></td></tr>
</table>
<div class="mybuttons">
<input type="reset" name="reset"/>
<input type="submit" name="addlocation" value="Add Location"/>
</div>
<p class="hint">
<h3>Allowed coordinates formats:</h3>
<ul>
<li>DD format = 'lat,lon', e.g 53.235235,6.235235</li>
<li>DMS Format = 'N|Wdd°mm'ss" E|Wdddd°mm'ss", e.g N51°31'39.40000" E7°27'53.7200"</li>
</ul>
</p>
</fieldset>
</form>
<form name="search_location" class='rendezvous-wizard' uri="" method="post" mime-type="text/plain" action="">
<fieldset id="location-search">
<legend>Location Search</legend>
<div>
<label for="nr_location_search">Location Search:</label>
<input id="nr_location_search" type="text" size="40" maxlength="40" name="location_search"/>
<input type="submit" name="search" value="Search"/>
</div>
<p class="help">e.g "central station, cityname"</p>
<div py:if="location_results" id="location-results">
<h3>Search Results</h3>
<table>
<tr py:for="rx, result in enumerate(location_results)" >
<td>${result.attrib["info"]} <a class="location" onclick="createIframe(${result.attrib['lat']},${result.attrib['lon']});">${result.attrib["name"]}</a> ${result.attrib["is_in"]}
<py:with vars="nears = result.find('nearestplaces')">
<py:if test="nears">,&nbsp;
<py:with vars="places = nears.findall('named')">
<py:for each="place in places">${place.attrib["distance"]} km away of <b>${place.attrib["name"]}</b></py:for>
</py:with>
</py:if>
</py:with>
</td>
</tr>
</table>
</div>
</fieldset>
</form>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,57 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="layout.html" />
<head>
<title>RendezVous</title>
<script type="text/javascript" src="${chrome.htdocs_location}js/wikitoolbar.js"></script>
</head>
<body>
<div id="content" class="rendezvous">
<!-- <div class="overview-content"> -->
<!-- <py:for each="ix, section in enumerate(all_rendezvouses.iteritems())"> -->
<div class="overview-set">
<table>
<tr><td><table><tr><td align="center"><img src="${href.chrome('/hw/images/new.png')}" border="3"/></td></tr><tr><td align="center"><h2>new</h2></td></tr></table></td>
<td><ul class="overview-set"><li py:for="item in all_rendezvouses['new']"><a class="overview" href="${href.rendezvous(item[0])}">${item[1]}</a></li></ul></td></tr>
</table>
</div>
<div class="overview-set">
<table>
<tr><td><table><tr><td align="center"><img src="${href.chrome('/hw/images/voting.png')}" border="3"/></td></tr><tr><td align="center"><h2>voting</h2></td></tr></table></td>
<td><ul class="overview-set"><li py:for="item in all_rendezvouses['voting']"><a class="overview" href="${href.rendezvous(item[0])}">${item[1]}</a></li></ul></td></tr>
</table>
</div>
<div class="overview-set">
<table>
<tr><td><table><tr><td align="center"><img src="${href.chrome('/hw/images/canceled.png')}" border="3"/></td></tr><tr><td align="center"><h2>canceled</h2></td></tr></table></td>
<td><ul class="overview-set"><li py:for="item in all_rendezvouses['canceled']"><a class="overview" href="${href.rendezvous(item[0])}">${item[1]}</a></li></ul></td></tr>
</table>
</div>
<div class="overview-set">
<table>
<tr><td><table><tr><td align="center"><img src="${href.chrome('/hw/images/expired.png')}" border="3"/></td></tr><tr><td align="center"><h2>expired</h2></td></tr></table></td>
<td><ul class="overview-set"><li py:for="item in all_rendezvouses['expired']"><a class="overview" href="${href.rendezvous(item[0])}">${item[1]}</a></li></ul></td></tr>
</table>
</div>
<!--<div class="overview-set">
<table>
<tr><td><table><tr><td align="center"><img src="${href.chrome('/hw/images/%s' % icons[section[0]])}" border="3"/></td></tr><tr><td align="center"><h2>${section[0]}</h2></td></tr></table></td>
<td><ul class="overview-set"><li py:for="item in section[1]"><a class="overview" href="${href.rendezvous(item[0])}">${item[1]}</a></li></ul></td></tr>
</table>
</div>-->
<!--<div class="overview-set">
<table>
<tr><td><table><tr><td align="center"><img src="${href.chrome('/hw/images/%s' % icons[section[0]])}" border="3"/></td></tr><tr><td align="center"><h2>${section[0]}</h2></td></tr></table></td>
<td><ul class="overview-set"><li py:for="item in section[1]"><a class="overview" href="${href.rendezvous(item[0])}">${item[1]}</a></li></ul></td></tr>
</table>
</div>-->
<!-- </py:for> -->
<!-- </div> -->
</div>
</body>
</html>

View File

@ -0,0 +1,149 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="layout.html" />
<head>
<title>RendezVous</title>
<script type="text/javascript" src="${chrome.htdocs_location}js/wikitoolbar.js"></script>
</head>
<body>
<div id="content">
<div id="rendezvous-main">
<div id="rendezvous-details">
<h1>RendezVous #${rendezvous.rendezvous_id}<span style="color:#ff0000">&nbsp;(${rendezvous.status})</span></h1>
<span class="rendezvous-header">Created on ${rendezvous.time_created.strftime('%d.%m.%Y %H:%M')} by ${rendezvous.author}</span>
<h3>${rendezvous.name}</h3><span></span>
<table class="properties" cellspacing="10px">
<tr>
<th id="h_type">Type</th>
<td headers="h_type">${rendezvous.type_name}</td>
</tr>
<tr py:if="rendezvous.min_votes > 0">
<th id="h_minvotes">Min. Votes</th>
<td headers="h_minvotes">${rendezvous.min_votes}</td>
</tr>
<tr>
<th id="h_tags">Tagged with</th>
<td headers="h_tags">${rendezvous.tags and rendezvous.tags or None}</td>
</tr>
<tr>
<th id="h_location">Location</th>
<td headers="h_location">${location.name}</td>
</tr>
<tr py:if="location and location.lat != None">
<th id="h_coordinates">Coordinates</th>
<td headers="h_coordinates"><a href="${'http://www.openstreetmap.org/index.html?mlat=%s&amp;mlon=%s&amp;zoom=15&amp;layers=B00TTT' % (location.lat, location.lon)}">${location.coordinate_str()}</a></td>
</tr>
<tr py:if="show_location_map and location.lat != None"><td colspan="4"><iframe width="100%" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="${'http://www.openstreetmap.org/export/embed.html?bbox=%f,%f,%f,%f&amp;layer=mapnik&amp;marker=%s,%s&amp;zoom=16' % (location.lon-0.00928, location.lat-0.0033, location.lon+0.011276, location.lat+0.0109, location.lat, location.lon)}" style="border: 1px solid black"></iframe></td></tr>
</table>
</div>
<div py:if="rendezvous.description">
<h3>Description</h3>
${wiki_to_html(context, rendezvous.description, escape_newlines=preserve_newlines)}
</div>
<h3>Votings</h3>
<table class="rendezvous-matrix">
<thead>
<tr class="rendezvous-matrix"><th class="rendezvous-matrix">Date proposals -></th>
<py:for each="date in rendezvous.dates">
<th class="rendezvous-matrix" py:with="dt_begin = selected_tz.fromutc(date.time_begin); dt_end = selected_tz.fromutc(date.time_end)">
<div py:if="date.elected" class="scheduled"><img src="${href.chrome('/hw/images/selected.png')}"/>scheduled</div>
<a py:strip="('RENDEZVOUS_DATE_VIEW' not in perm and date.author != authname) or 'RENDEZVOUS_ADMIN' not in perm" href="${href.date(rendezvous.rendezvous_id)}">${dt_begin.strftime('%d.%m.%Y %H:%M')} ${dt_begin.tzinfo.tzname(None)} - ${dt_end.strftime('%d.%m.%Y %H:%M')} ${dt_end.tzinfo.tzname(None)}</a>
</th>
</py:for>
<th class="rendezvous-matrix" py:if="'RENDEZVOUS_DATE_ADD' in perm and not rendezvous.elected">
<a title="create a new date for rendezvous #${rendezvous.rendezvous_id}" href="${href.date(rendezvous.rendezvous_id)}">new date</a>
</th>
</tr>
</thead>
<tfoot>
<tr class="rendezvous-stats">
<td class="rendezvous-stats">VoteCount</td>
<py:for each="date in rendezvous.dates">
<td class="rendezvous-stats">${date.get_vote_count()}</td>
</py:for>
</tr>
</tfoot>
<tbody>
<py:for each="row in table">
<tr class="rendezvous-matrix">
<td align="center" class="rendezvous-details">${row[0]}</td>
<py:for each="col in row[1:]" py:with="ismyrow = row[0] == req.authname">
<py:choose test="">
<td py:when="rendezvous.elected and not rendezvous.get_date(col[1]).elected" align="center" class="rendezvous-notelected"/>
<py:otherwise test=""><td align="center" class="${not col[0] and 'rendezvous-notvoted' or 'rendezvous-voted'}">
<py:if test="'RENDEZVOUS_VOTE_VIEW' in perm and ismyrow">
<py:choose>
<py:when test="not col[0] and 'RENDEZVOUS_VOTE_ADD' in perm">
<a class="voting" title="quick and dirty voting for the date" href="${href.quickvote(col[1])}">vote</a>&nbsp;&nbsp;
<a class="voting" title="advanced voting operations" href="${href.vote(col[1])}">...advanced</a>
</py:when>
<a py:when="'RENDEZVOUS_VOTE_MODIFY' in perm" class="voting" href="${href.vote(col[1])}">edit</a>
</py:choose>
</py:if>
</td>
</py:otherwise>
</py:choose>
</py:for>
</tr>
</py:for>
<tr class="rendezvous-details" py:if="not voted and len(rendezvous.dates) > 0 and 'RENDEZVOUS_VOTE_ADD' in perm" action="${urlbase}/rendezvous/addvote">
<td class="rendezvous-details"><input type="text" id="n" size="16" maxlength="15" name="newuser" value="${authname}"/></td>
<py:for each="date in rendezvous.dates">
<td align="center" class="rendezvous-details">
<a class="voting" title="quick and dirty voting for the date" href="${href.quickvote(date.date_id)}">vote</a>&nbsp;&nbsp;
<a class="voting" title="advanced voting operations" href="${href.vote(date.date_id)}">...advanced</a>
</td>
</py:for>
</tr>
</tbody>
</table>
<div py:if="has_votes and show_vote_graph == True and 'RENDEZVOUS_VOTE_GRAPH_VIEW' in perm">
<h2>Vote Distribution By RendezVousDate</h2>
<div py:if="date.votes" py:for="date in rendezvous.dates">
<img src="${href.chrome('site/rendezvous_graphs/date%s.png'%date.date_id)}" border="3" width="100%"/>
</div>
</div>
<div>
<py:if test="'RENDEZVOUS_COMMENT_VIEW' in perm">
<py:if test="comments">
<h3>Comments</h3>
<div py:for="comment in comments" class="comment">
<span class="comment-header">added on ${comment.time_created.strftime('%d.%m.%Y %H:%M')} by ${comment.author} <a py:if="authname == comment.author or 'RENDEZVOUS_ADMIN' in perm" href="${href.rendezvous(rendezvous.rendezvous_id, 'comment', comment.comment_id)}#edit">edit</a></span>
${wiki_to_html(context, comment.comment, escape_newlines=preserve_newlines)}
</div>
</py:if>
<h3 id="edit">New Comment</h3>
<div>
<py:if test="preview_comment">
<h3 style="color:#ff0000;">Preview!</h3>
<div class="graph">
<div class="comment">
<span class="comment-header">added on ${preview_comment.time_created.strftime('%d.%m.%Y %H:%M')} by ${preview_comment.author}</span>
${wiki_to_html(context, preview_comment.comment, escape_newlines=preserve_newlines)}
</div>
</div>
</py:if>
<form name="my_comment" action="#edit" method="post" mime-type="text/plain">
<input py:if="preview_comment and preview_comment.comment_id > 0" type="hidden" name="comment_id" value="${preview_comment.comment_id}"/>
<textarea id="new_comment" class="wikitext" rows="20" cols="100" name="comment" py:content="preview_comment and preview_comment.comment or None"></textarea>
<div class="buttons">
<input type="submit" name="preview" value="Preview"/>
<input type="submit" name="save" value="Save"/>
<input type="submit" name="delete" value="Delete"/>
</div>
<b>Note:</b> See <a href="${href.wiki('WikiFormatting')}">WikiFormatting</a> and
<a href="${href.wiki('TracWiki')}">TracWiki</a> for help on editing wiki content.
</form>
</div>
</py:if>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,93 @@
<!DOCTYPE htm
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="layout.html" />
<head>
<script type="text/javascript" src="${chrome.htdocs_location}js/wikitoolbar.js"></script>
<script type="text/javascript">
/* <![CDATA[ */
jQuery(document).ready(function($) {
/* only enable control elements for the currently selected action */
var actions = $("#action input[name='action']");
function updateActionFields()
{
actions.each(function() {
$(this).siblings().find("*[@id]").enable($(this).checked());
$(this).siblings().filter("*[@id]").enable($(this).checked());
})
}
actions.click(updateActionFields);
updateActionFields();
$("#schedule_deadline").datepicker({"dateFormat" : "dd.mm.yy"});
});
/* ]]> */
</script>
<title>${title}</title>
</head>
<body>
<div id="content">
<div id='rendezvous-main'>
<form name="new_rendezvous" class='rendezvous-wizard' uri="" method="post" mime-type="text/plain" action="">
<input py:if="rendezvous.rendezvous_id != 0" type="hidden" name="rendezvous_id" value="${rendezvous.rendezvous_id}"/>
<fieldset id="properties">
<legend>${title}</legend>
<table class="rendezvous-wizard" py:with="dt = selected_tz.fromutc(rendezvous.schedule_deadline)">
<tr><th><label for="name">Title</label></th><td><input id="name" type="text" size="50" maxlength="50" name="name" value="${rendezvous.name}"/></td></tr>
<tr><th><label for="author">Author</label></th><td>
<py:choose test="">
<input py:when="not rendezvous.rendezvous_id or 'RENDEZVOUS_ADMIN' in perm" id="author" type="text" size="50" maxlength="50" name="author" value="${rendezvous.rendezvous_id and rendezvous.author or authname}"/>
<py:otherwise>${rendezvous.author}</py:otherwise>
</py:choose>
</td></tr>
<tr><th><label for="email">Email</label></th><td><input id="email" type="text" size="50" maxlength="100" name="email" value="${rendezvous.email}"/></td></tr>
<tr><th><label for="rtype">Type</label></th><td><select id="rtype" name="type_name" size="1"><option py:for="rtype in types" selected="${rtype.type_id == rendezvous.type_id and 'checked' or None}">${rtype.name}</option></select></td></tr>
<tr><th><label for="min_votes">Minimum votes</label></th>
<td><input id="min_votes" type="input" name="min_votes" size="4" maxlength="4" value="${rendezvous.min_votes}" /></td>
</tr>
<tr><th><label for="schedule_deadline">Voting deadline</label></th>
<td><input id="schedule_deadline" type="input" name="schedule_deadline_date" size="10" maxlength="10" value="${dt.strftime('%d.%m.%Y')}" />
<input id="schedule_deadline_time" type="input" size="5" maxlength="5" name="schedule_deadline_time" value="${dt.strftime('%H:%M')}" /> ${dt.tzinfo.tzname(None)}</td>
</tr>
<tr><th><label for="location">Locations</label></th>
<td><select id="location" name="location" size="1">
<option py:for="location in locations" selected="${location.location_id == rendezvous.location_id and 'checked' or None}">${location.name} :${location.coordinate_str()}</option>
</select>
<a py:if="'RENDEZVOUS_LOCATION_MODIFY' in perm" href="${href.location(location and location.location_id or 0)}">edit/search locations</a></td>
</tr>
<tr><th><label for="description">Description</label></th><td><textarea id="description" class="wikitext" rows="20" cols="100" name="description" py:content="rendezvous.description"></textarea></td></tr>
</table>
</fieldset>
<!--! Workflow support -->
<fieldset py:if="rendezvous.rendezvous_id != 0" id="action">
<legend>${_("Workflow")}</legend>
<div py:for="key, label, controls, hints in action_controls">
<input type="radio" id="action_$key" name="action" value="$key" checked="${cstatus == key and 'checked' or None}" />
<label for="action_$key">$label</label>
$controls
<span class="hint" py:for="hint in hints">$hint</span>
</div>
</fieldset>
<fieldset>
<legend >Tags</legend>
<div>
<input id="tags" type="text" size="70" maxlength="70" name="tags" value="${rendezvous.tags}"/>
</div>
</fieldset>
<div class="mybuttons">
<input type="reset" value="Reset"/>
<py:choose test="">
<py:when test="rendezvous.rendezvous_id != 0">
<input type="submit" name="edit" value="edit"/>
<input type="submit" name="delete" value="delete"/>
</py:when>
<input py:otherwise="" type="submit" name="add" value="add"/>
</py:choose>
</div>
</form>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,2 @@
$rendezvous_body_hdr

View File

@ -0,0 +1,129 @@
<!DOCTYPE htm
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="layout.html" />
<head>
<script type="text/javascript">
jQuery(document).ready(function($) {
$("#rdate_begin").datepicker({"dateFormat" : "dd.mm.yy"});
$("#rdate_end").datepicker({"dateFormat" : "dd.mm.yy"});
$("#time_begin\:\\S*").datepicker({"dateFormat" : "dd.mm.yy"});
$("#time_end\:\\S*").datepicker({"dateFormat" : "dd.mm.yy"});
});
</script>
<title>Votes</title>
</head>
<body>
<div id="content">
<div id='rendezvous-main'>
<form py:with="canDelete = 'RENDEZVOUS_VOTE_DELETE' in perm; canModify = 'RENDEZVOUS_VOTE_MODIFY' in perm" py:if="votes" class='votes' method="post" action="">
<fieldset>
<py:choose test="">
<legend py:when="'RENDEZVOUS_VOTE_VIEW_OTHERS' in perm">All votes for ${rdate.time_begin.strftime('%x')}</legend>
<legend py:otherwise="">Votes for ${rdate.time_begin.strftime('%x')} made by $authname</legend>
</py:choose>
<table class="listing">
<thead>
<tr>
<py:if test="canModify">
<th>user</th>
<th>email</th>
<th>begin</th>
<th>end</th>
</py:if>
<th py:if="canDelete">delete</th>
</tr>
</thead>
<tbody py:choose="">
<tr py:when="canModify and canDelete" py:for="vote in votes" py:with="dt = selected_tz.fromutc(vote.time_begin);dt2 = selected_tz.fromutc(vote.time_end)">
<td>${vote.user}</td>
<td><input type="text" size="40" name="email:${vote.vote_id}" value="${vote.email}"/></td>
<td><input type="text" size="8" id="time_begin:${vote.vote_id}" name="date_begin:${vote.vote_id}" value="${dt.strftime('%d.%m.%Y')}" />
<input type="text" size="5" name="time_begin:${vote.vote_id}" value="${dt.strftime('%H:%M')}"/> ${dt.tzinfo.tzname(None)}</td>
<td><input type="text" size="8" id="time_end:${vote.vote_id}" name="date_end:${vote.vote_id}" value="${dt2.strftime('%d.%m.%Y')}" />
<input type="text" size="5" name="time_end:${vote.vote_id}" value="${dt2.strftime('%H:%M')}"/> ${dt2.tzinfo.tzname(None)}</td>
<td><input type="checkbox" name="delete" value="${vote.vote_id}" /></td>
</tr>
<tr py:when="not canModify and canDelete" py:for="vote in votes" py:with="dt = selected_tz.fromutc(vote.time_begin);dt2 = selected_tz.fromutc(vote.time_end)">
<td>${vote.user}</td>
<td>${vote.mail}</td>
<td>${dt.strftime('%d.%m.%Y')}
${dt.strftime('%H:%M')} ${dt.tzinfo.tzname(None)}</td>
<td>${dt2.strftime('%d.%m.%Y')}
${dt2.strftime('%H:%M')} ${dt2.tzinfo.tzname(None)}</td>
<td><input type="checkbox" name="delete" value="${vote.vote_id}"/></td>
</tr>
<tr py:when="canModify and not canDelete" py:for="vote in votes" py:with="dt = selected_tz.fromutc(vote.time_begin);dt2 = selected_tz.fromutc(vote.time_end)">
<td><input type="text" size="40" name="email:${vote.vote_id}" value="${vote.email}" /></td>
<td><input type="text" size="8" id="time_begin:${vote.vote_id}" name="date_begin:${vote.vote_id}" value="${dt.strftime('%d.%m.%Y')}" />
<input type="text" size="5" name="time_begin:${vote.vote_id}" value="${dt.strftime('%H:%M')}"/> ${dt.tzinfo.tzname(None)}</td>
<td><input type="text" size="8" id="time_end:${vote.vote_id}" name="date_end:${vote.vote_id}" value="${dt2.strftime('%d.%m.%Y')}" />
<input type="text" size="5" name="time_end:${vote.vote_id}" value="${dt2.strftime('%H:%M')}"/> ${dt2.tzinfo.tzname(None)}</td>
</tr>
<tr py:otherwise="" py:for="vote in votes">
<td>${vote.user}</td>
<td>${vote.time_begin.strftime('%d.%m.%Y')}</td>
<td>${dt.strftime('%H:%M')} ${dt.tzinfo.tzname(None)}</td>
<td>${dt2.strftime('%d.%m.%Y')}</td>
<td>${dt2.strftime('%H:%M')} ${dt2.tzinfo.tzname(None)}</td>
</tr>
</tbody>
</table>
</fieldset>
<div class="mybuttons">
<input type="reset" name="reset"/>
<input type="submit" name="savevotes" value="Save Changes"/>
</div>
</form>
<p class="help">Change or delete votes for an existing rendezvous date.</p>
<form py:if="'RENDEZVOUS_VOTE_ADD' in perm" action="" method="post" mime-type="text/plain">
<fieldset>
<legend>Add new vote for ${rdate.time_begin.strftime("%x")}:</legend>
<table>
<tr class="field">
<th><label for="dv_user">User:</label></th>
<td><input id="dv_user" type="text" name="user" value="${authname}"/></td>
</tr>
<tr class="field">
<th><label for="dv_email">Email:</label></th>
<td><input id="dv_email" type="text" name="email" /></td>
</tr>
<tr class="field" py:with="dt = selected_tz.fromutc(rdate.time_begin)">
<th><label for="dv_date_begin">Date Begin:</label></th>
<td><input id="rdate_begin" type="text" name="date_begin" value="${dt.strftime('%d.%m.%Y')}"/>
<input type="text" name="time_begin" value="${dt.strftime('%H:%M')}"/> ${dt.tzinfo.tzname(None)}</td>
</tr>
<tr class="field" py:with="dt = selected_tz.fromutc(rdate.time_end)">
<th><label for="dv_date_end">Date End:</label></th>
<td><input id="rdate_end" type="text" name="date_end" value="${dt.strftime('%d.%m.%Y')}"/>
<input type="text" name="time_end" value="${dt.strftime('%H:%M')}"/> ${dt.tzinfo.tzname(None)}</td>
</tr>
</table>
<div class="mybuttons">
<input type="submit" name="add" value="Add" />
</div>
</fieldset>
</form>
<p class="help">Add a vote to an existing rendezvous date.</p>
</div>
<h3>Allowed date/time formats:</h3>
<ul>
<li>time:
<ul>
<li>'hhMM'</li>
<li>'hh:MM'</li>
</ul>
</li>
<li>date:
<ul>
<li>'yyyymmdd'</li>
<li>'yyyy.mm.dd'</li>
</ul>
</li>
</ul>
</div>
</body>
</html>

View File

@ -0,0 +1,204 @@
# -*- coding: utf-8 -*-
from re import compile as re_compile
from re import match
from decimal import Decimal, Context, getcontext
from datetime import date, time, datetime, timedelta
from os.path import join, dirname
from os.path import exists as path_exists
from os import mkdir
from sys import maxint
from trac.util import Ranges
from trac.wiki import WikiPage, WikiSystem
from trac.util.datefmt import utc, to_timestamp, localtz, format_time, get_timezone, timezone
from PIL import Image, ImageDraw, ImageFont
__all__ = ["check_date_collision", "check_vote_collision","update_votes_graph"]
class ValidationError(ValueError):
def __str__(self):
return "ValidationError: value out of bounds!"
class VoteCollisionError(ValueError):
def __str__(self):
return "VoteCollisionError: that vote collides with another one from you!"
def check_vote_collision(newvote, votes):
for vote in votes:
if newvote.vote_id == vote.vote_id:
continue
if vote.time_begin < newvote.time_begin < vote.time_end:
raise VoteCollisionError
if vote.time_begin < newvote.time_end < vote.time_end:
raise VoteCollisionError
if newvote.time_begin < vote.time_begin < newvote.time_end:
raise VoteCollisionError
if newvote.time_begin < vote.time_end < newvote.time_end:
raise VoteCollisionError
def check_date_collision(newdate, dates):
for date in dates:
if newdate.date_id == date.date_id:
continue
if date.time_begin < newdate.time_begin < date.time_end:
raise VoteCollisionError
if date.time_begin < newdate.time_end < date.time_end:
raise VoteCollisionError
if newdate.time_begin < date.time_begin < newdate.time_end:
raise VoteCollisionError
if newdate.time_begin < date.time_end < newdate.time_end:
raise VoteCollisionError
def colorGen(steps):
r = 255
g = 0
b = 0
rsteps=512/steps
for ry in xrange(255):
g+=rsteps
g = max(255,g)
yield r,g,b
for yg in xrange(255, 0, -1):
r-=rsteps
r = min(0,r)
yield r,g,b
def create_graph(gname, votes, path, real_mindate, maxdate, size, selected_tz):
""" I wanna be a better gantt diagram - oh yeah ...
Render an diagram image of all votes users made. The
x-axis is the timeline beginning with the hour of the first vote as origin and
the hour after the last vote.
"""
import math
def trange(start, end, vhours, deltaT, width):
pixels = 0
units = pixelPerHour
scaledDeltaTime = timedelta(0,3600,0)
scaleFactor = 1
if vhours > 10:
scaleFactor = math.ceil(vhours / 10.0)
scaledDeltaTime *= int(scaleFactor)
units *= scaleFactor
e = end.replace(minute=0,second=0, microsecond=0)+timedelta(0,3600)
while start <= e:
yield pixels, (format_time(start, '%d', tzinfo=selected_tz), format_time(start, '%H:%M', tzinfo=selected_tz))
start += scaledDeltaTime
pixels += units
deltaT = maxdate - real_mindate
mindate = real_mindate.replace(minute=0, second=0, microsecond=0)
steps = len(votes)
if steps <= 0:
return
hours = int(deltaT.days * 24.0 + deltaT.seconds / 3600.0)
vhours = hours + 2
font = None
fontpath = join(dirname(__file__), "luxisr.ttf")
xBase = 0
pixelPerHour = (size[0]-xBase) / vhours
hOffset = mindate.hour
halfHour = pixelPerHour / 2
user2Row = {}
rowCount=0
for d in xrange(steps):
user=votes[d].user
if not user2Row.has_key(user):
user2Row[user] = rowCount
rowCount+=1
fontSize = 22
deltaHeightPerVote = fontSize*2
size[1] = (rowCount + 2) * deltaHeightPerVote
chart = Image.new("RGBA", size, (230, 230, 230, 255))
chartDraw = ImageDraw.Draw(chart)
yBase = size[1]-fontSize*2
font = ImageFont.truetype(fontpath, fontSize)
timefont = ImageFont.truetype(fontpath, 13)
del rowCount
for d in xrange(steps):
user=votes[d].user
n = user2Row[user]
i = votes[d].time_begin
j = votes[d].time_end
tmp=i-mindate
tmp=tmp.days * 24.0 + tmp.seconds / 3600.0
x1 = tmp * pixelPerHour + xBase
tmp=j - mindate
tmp=tmp.days * 24 + tmp.seconds / 3600.0
x2 = tmp * pixelPerHour + xBase
y1 = yBase - (n + 1) * deltaHeightPerVote
y2 = y1 + deltaHeightPerVote
chartDraw.rectangle((x1, y1, x2, y2), fill=(161, 235, 255))
chartDraw.line((x1, y1, x1, yBase), fill=(150, 150, 150))
chartDraw.line((x2, y1, x2, yBase), fill=(150, 150, 150))
chartDraw.line((xBase, y1, size[0], y1), fill=(150, 150, 150))
chartDraw.line((xBase, y2, size[0], y2), fill=(150, 150, 150))
chartDraw.text((xBase, y1 + deltaHeightPerVote/4), votes[d].user, font=font, fill=(0,0,0))
chartDraw.line((0, yBase, size[0], yBase), fill = (0, 0, 0, 255), width=2)
fontBaseLine = size[1] - 30
#chartDraw.text((xBase, fontSize/2), gname, font=font, fill=(0, 0, 250))
for xMajor, ts in trange(mindate, maxdate, vhours, deltaT, size[0] - xBase):
chartDraw.text((xMajor + xBase, fontBaseLine), ts[0], font=timefont, fill=(0, 0, 0))
chartDraw.text((xMajor + xBase, fontBaseLine + 15), ts[1], font=timefont, fill=(0, 0, 0))
chartDraw.line((xMajor + xBase, size[1] - fontSize, xMajor + xBase, yBase), fill=(0, 0, 0, 255), width=2)
chart.save(join(path, "date%d.png" % votes[0].date_id), "PNG")
def update_votes_graph(gname, dvotes, path, size, selected_tz):
if dvotes:
if not path_exists(path):
mkdir(path, 0755)
votes = sorted(dvotes, date_cmp)
matchCount, mindate, maxdate = date_stats(votes)
create_graph(gname, votes, path, mindate, maxdate, size, selected_tz)
def date_cmp(a,b):
if a.time_begin < b.time_begin:
return -1
elif a.time_begin > b.time_begin:
return 1
else:
return 0
def date_stats(votes):
""" I'm expecting a list of votes sorted by time_begin.
I'm returning a tuple of 3 values
* a list with match counts
* the minimum date in the votes
* the maximum date in the votes
"""
if len(votes) == 0:
return None, None, None
maxdate = datetime(1, 1, 1, tzinfo=utc)
mindate = votes[0].time_begin
ic=0
matchCount = [0 for i in xrange(len(votes))]
for vote in votes:
if vote.time_end > maxdate:
maxdate = vote.time_end
jc=0
for voteB in votes:
if voteB.time_begin < vote.time_end:
matchCount[ic] += 1
matchCount[jc] += 1
jc+=1
ic+=1
return matchCount, mindate, maxdate
def sortVotesPerUser(votes, users):
myvotes = [False for i in users]
for i in votes:
myvotes[users[i.user]] = i
def sortedUsers(users):
myusers = ["" for i in users]
for i in users:
myusers[users[i]] = i

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More