##// END OF EJS Templates
Merge with 6aa7db1c083a1384ebff5c2bb3c943a035bb310d - celery branch
marcink -
r499:ca41d544 merge rhodecode-0.0.0.8.3 default
parent child Browse files
Show More
@@ -0,0 +1,74 b''
1 # List of modules to import when celery starts.
2 import sys
3 import os
4 import ConfigParser
5 root = os.getcwd()
6
7 PYLONS_CONFIG_NAME = 'development.ini'
8
9 sys.path.append(root)
10 config = ConfigParser.ConfigParser({'here':root})
11 config.read('%s/%s' % (root, PYLONS_CONFIG_NAME))
12 PYLONS_CONFIG = config
13
14 CELERY_IMPORTS = ("pylons_app.lib.celerylib.tasks",)
15
16 ## Result store settings.
17 CELERY_RESULT_BACKEND = "database"
18 CELERY_RESULT_DBURI = dict(config.items('app:main'))['sqlalchemy.db1.url']
19 CELERY_RESULT_SERIALIZER = 'json'
20
21
22 BROKER_CONNECTION_MAX_RETRIES = 30
23
24 ## Broker settings.
25 BROKER_HOST = "localhost"
26 BROKER_PORT = 5672
27 BROKER_VHOST = "rabbitmqhost"
28 BROKER_USER = "rabbitmq"
29 BROKER_PASSWORD = "qweqwe"
30
31 ## Worker settings
32 ## If you're doing mostly I/O you can have more processes,
33 ## but if mostly spending CPU, try to keep it close to the
34 ## number of CPUs on your machine. If not set, the number of CPUs/cores
35 ## available will be used.
36 CELERYD_CONCURRENCY = 2
37 # CELERYD_LOG_FILE = "celeryd.log"
38 CELERYD_LOG_LEVEL = "DEBUG"
39 CELERYD_MAX_TASKS_PER_CHILD = 1
40
41 #Tasks will never be sent to the queue, but executed locally instead.
42 CELERY_ALWAYS_EAGER = False
43
44 #===============================================================================
45 # EMAIL SETTINGS
46 #===============================================================================
47 pylons_email_config = dict(config.items('DEFAULT'))
48
49 CELERY_SEND_TASK_ERROR_EMAILS = True
50
51 #List of (name, email_address) tuples for the admins that should receive error e-mails.
52 ADMINS = [('Administrator', pylons_email_config.get('email_to'))]
53
54 #The e-mail address this worker sends e-mails from. Default is "celery@localhost".
55 SERVER_EMAIL = pylons_email_config.get('error_email_from')
56
57 #The mail server to use. Default is "localhost".
58 MAIL_HOST = pylons_email_config.get('smtp_server')
59
60 #Username (if required) to log on to the mail server with.
61 MAIL_HOST_USER = pylons_email_config.get('smtp_username')
62
63 #Password (if required) to log on to the mail server with.
64 MAIL_HOST_PASSWORD = pylons_email_config.get('smtp_password')
65
66 MAIL_PORT = pylons_email_config.get('smtp_port')
67
68
69 #===============================================================================
70 # INSTRUCTIONS FOR RABBITMQ
71 #===============================================================================
72 # rabbitmqctl add_user rabbitmq qweqwe
73 # rabbitmqctl add_vhost rabbitmqhost
74 # rabbitmqctl set_permissions -p rabbitmqhost rabbitmq ".*" ".*" ".*"
@@ -0,0 +1,66 b''
1 from pylons_app.lib.pidlock import DaemonLock, LockHeld
2 from vcs.utils.lazy import LazyProperty
3 from decorator import decorator
4 import logging
5 import os
6 import sys
7 import traceback
8 from hashlib import md5
9 log = logging.getLogger(__name__)
10
11 class ResultWrapper(object):
12 def __init__(self, task):
13 self.task = task
14
15 @LazyProperty
16 def result(self):
17 return self.task
18
19 def run_task(task, *args, **kwargs):
20 try:
21 t = task.delay(*args, **kwargs)
22 log.info('running task %s', t.task_id)
23 return t
24 except Exception, e:
25 print e
26 if e.errno == 111:
27 log.debug('Unnable to connect. Sync execution')
28 else:
29 log.error(traceback.format_exc())
30 #pure sync version
31 return ResultWrapper(task(*args, **kwargs))
32
33
34 class LockTask(object):
35 """LockTask decorator"""
36
37 def __init__(self, func):
38 self.func = func
39
40 def __call__(self, func):
41 return decorator(self.__wrapper, func)
42
43 def __wrapper(self, func, *fargs, **fkwargs):
44 params = []
45 params.extend(fargs)
46 params.extend(fkwargs.values())
47 lockkey = 'task_%s' % \
48 md5(str(self.func) + '-' + '-'.join(map(str, params))).hexdigest()
49 log.info('running task with lockkey %s', lockkey)
50 try:
51 l = DaemonLock(lockkey)
52 return func(*fargs, **fkwargs)
53 l.release()
54 except LockHeld:
55 log.info('LockHeld')
56 return 'Task with key %s already running' % lockkey
57
58
59
60
61
62
63
64
65
66
@@ -0,0 +1,270 b''
1 from celery.decorators import task
2 from celery.task.sets import subtask
3 from celeryconfig import PYLONS_CONFIG as config
4 from pylons.i18n.translation import _
5 from pylons_app.lib.celerylib import run_task, LockTask
6 from pylons_app.lib.helpers import person
7 from pylons_app.lib.smtp_mailer import SmtpMailer
8 from pylons_app.lib.utils import OrderedDict
9 from operator import itemgetter
10 from vcs.backends.hg import MercurialRepository
11 from time import mktime
12 import traceback
13 import json
14
15 __all__ = ['whoosh_index', 'get_commits_stats',
16 'reset_user_password', 'send_email']
17
18 def get_session():
19 from sqlalchemy import engine_from_config
20 from sqlalchemy.orm import sessionmaker, scoped_session
21 engine = engine_from_config(dict(config.items('app:main')), 'sqlalchemy.db1.')
22 sa = scoped_session(sessionmaker(bind=engine))
23 return sa
24
25 def get_hg_settings():
26 from pylons_app.model.db import HgAppSettings
27 try:
28 sa = get_session()
29 ret = sa.query(HgAppSettings).all()
30 finally:
31 sa.remove()
32
33 if not ret:
34 raise Exception('Could not get application settings !')
35 settings = {}
36 for each in ret:
37 settings['hg_app_' + each.app_settings_name] = each.app_settings_value
38
39 return settings
40
41 def get_hg_ui_settings():
42 from pylons_app.model.db import HgAppUi
43 try:
44 sa = get_session()
45 ret = sa.query(HgAppUi).all()
46 finally:
47 sa.remove()
48
49 if not ret:
50 raise Exception('Could not get application ui settings !')
51 settings = {}
52 for each in ret:
53 k = each.ui_key
54 v = each.ui_value
55 if k == '/':
56 k = 'root_path'
57
58 if k.find('.') != -1:
59 k = k.replace('.', '_')
60
61 if each.ui_section == 'hooks':
62 v = each.ui_active
63
64 settings[each.ui_section + '_' + k] = v
65
66 return settings
67
68 @task
69 def whoosh_index(repo_location, full_index):
70 log = whoosh_index.get_logger()
71 from pylons_app.lib.pidlock import DaemonLock
72 from pylons_app.lib.indexers.daemon import WhooshIndexingDaemon, LockHeld
73 try:
74 l = DaemonLock()
75 WhooshIndexingDaemon(repo_location=repo_location)\
76 .run(full_index=full_index)
77 l.release()
78 return 'Done'
79 except LockHeld:
80 log.info('LockHeld')
81 return 'LockHeld'
82
83
84 @task
85 @LockTask('get_commits_stats')
86 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
87 author_key_cleaner = lambda k: person(k).replace('"', "") #for js data compatibilty
88
89 from pylons_app.model.db import Statistics, Repository
90 log = get_commits_stats.get_logger()
91 commits_by_day_author_aggregate = {}
92 commits_by_day_aggregate = {}
93 repos_path = get_hg_ui_settings()['paths_root_path'].replace('*', '')
94 repo = MercurialRepository(repos_path + repo_name)
95
96 skip_date_limit = True
97 parse_limit = 350 #limit for single task changeset parsing
98 last_rev = 0
99 last_cs = None
100 timegetter = itemgetter('time')
101
102 sa = get_session()
103
104 dbrepo = sa.query(Repository)\
105 .filter(Repository.repo_name == repo_name).scalar()
106 cur_stats = sa.query(Statistics)\
107 .filter(Statistics.repository == dbrepo).scalar()
108 if cur_stats:
109 last_rev = cur_stats.stat_on_revision
110
111 if last_rev == repo.revisions[-1]:
112 #pass silently without any work
113 return True
114
115 if cur_stats:
116 commits_by_day_aggregate = OrderedDict(
117 json.loads(
118 cur_stats.commit_activity_combined))
119 commits_by_day_author_aggregate = json.loads(cur_stats.commit_activity)
120
121 for cnt, rev in enumerate(repo.revisions[last_rev:]):
122 last_cs = cs = repo.get_changeset(rev)
123 k = '%s-%s-%s' % (cs.date.timetuple()[0], cs.date.timetuple()[1],
124 cs.date.timetuple()[2])
125 timetupple = [int(x) for x in k.split('-')]
126 timetupple.extend([0 for _ in xrange(6)])
127 k = mktime(timetupple)
128 if commits_by_day_author_aggregate.has_key(author_key_cleaner(cs.author)):
129 try:
130 l = [timegetter(x) for x in commits_by_day_author_aggregate\
131 [author_key_cleaner(cs.author)]['data']]
132 time_pos = l.index(k)
133 except ValueError:
134 time_pos = False
135
136 if time_pos >= 0 and time_pos is not False:
137
138 datadict = commits_by_day_author_aggregate\
139 [author_key_cleaner(cs.author)]['data'][time_pos]
140
141 datadict["commits"] += 1
142 datadict["added"] += len(cs.added)
143 datadict["changed"] += len(cs.changed)
144 datadict["removed"] += len(cs.removed)
145 #print datadict
146
147 else:
148 #print 'ELSE !!!!'
149 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
150
151 datadict = {"time":k,
152 "commits":1,
153 "added":len(cs.added),
154 "changed":len(cs.changed),
155 "removed":len(cs.removed),
156 }
157 commits_by_day_author_aggregate\
158 [author_key_cleaner(cs.author)]['data'].append(datadict)
159
160 else:
161 #print k, 'nokey ADDING'
162 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
163 commits_by_day_author_aggregate[author_key_cleaner(cs.author)] = {
164 "label":author_key_cleaner(cs.author),
165 "data":[{"time":k,
166 "commits":1,
167 "added":len(cs.added),
168 "changed":len(cs.changed),
169 "removed":len(cs.removed),
170 }],
171 "schema":["commits"],
172 }
173
174 # #gather all data by day
175 if commits_by_day_aggregate.has_key(k):
176 commits_by_day_aggregate[k] += 1
177 else:
178 commits_by_day_aggregate[k] = 1
179
180 if cnt >= parse_limit:
181 #don't fetch to much data since we can freeze application
182 break
183
184 overview_data = []
185 for k, v in commits_by_day_aggregate.items():
186 overview_data.append([k, v])
187 overview_data = sorted(overview_data, key=itemgetter(0))
188
189 if not commits_by_day_author_aggregate:
190 commits_by_day_author_aggregate[author_key_cleaner(repo.contact)] = {
191 "label":author_key_cleaner(repo.contact),
192 "data":[0, 1],
193 "schema":["commits"],
194 }
195
196 stats = cur_stats if cur_stats else Statistics()
197 stats.commit_activity = json.dumps(commits_by_day_author_aggregate)
198 stats.commit_activity_combined = json.dumps(overview_data)
199 stats.repository = dbrepo
200 stats.stat_on_revision = last_cs.revision
201 stats.languages = json.dumps({'_TOTAL_':0, '':0})
202
203 try:
204 sa.add(stats)
205 sa.commit()
206 except:
207 log.error(traceback.format_exc())
208 sa.rollback()
209 return False
210
211 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
212
213 return True
214
215 @task
216 def reset_user_password(user_email):
217 log = reset_user_password.get_logger()
218 from pylons_app.lib import auth
219 from pylons_app.model.db import User
220
221 try:
222 try:
223 sa = get_session()
224 user = sa.query(User).filter(User.email == user_email).scalar()
225 new_passwd = auth.PasswordGenerator().gen_password(8,
226 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
227 if user:
228 user.password = auth.get_crypt_password(new_passwd)
229 sa.add(user)
230 sa.commit()
231 log.info('change password for %s', user_email)
232 if new_passwd is None:
233 raise Exception('unable to generate new password')
234
235 except:
236 log.error(traceback.format_exc())
237 sa.rollback()
238
239 run_task(send_email, user_email,
240 "Your new hg-app password",
241 'Your new hg-app password:%s' % (new_passwd))
242 log.info('send new password mail to %s', user_email)
243
244
245 except:
246 log.error('Failed to update user password')
247 log.error(traceback.format_exc())
248 return True
249
250 @task
251 def send_email(recipients, subject, body):
252 log = send_email.get_logger()
253 email_config = dict(config.items('DEFAULT'))
254 mail_from = email_config.get('app_email_from')
255 user = email_config.get('smtp_username')
256 passwd = email_config.get('smtp_password')
257 mail_server = email_config.get('smtp_server')
258 mail_port = email_config.get('smtp_port')
259 tls = email_config.get('smtp_use_tls')
260 ssl = False
261
262 try:
263 m = SmtpMailer(mail_from, user, passwd, mail_server,
264 mail_port, ssl, tls)
265 m.send(recipients, subject, body)
266 except:
267 log.error('Mail sending failed')
268 log.error(traceback.format_exc())
269 return False
270 return True
@@ -0,0 +1,118 b''
1 import logging
2 import smtplib
3 import mimetypes
4 from email.mime.multipart import MIMEMultipart
5 from email.mime.image import MIMEImage
6 from email.mime.audio import MIMEAudio
7 from email.mime.base import MIMEBase
8 from email.mime.text import MIMEText
9 from email.utils import formatdate
10 from email import encoders
11
12 class SmtpMailer(object):
13 """simple smtp mailer class
14
15 mailer = SmtpMailer(mail_from, user, passwd, mail_server, mail_port, ssl, tls)
16 mailer.send(recipients, subject, body, attachment_files)
17
18 :param recipients might be a list of string or single string
19 :param attachment_files is a dict of {filename:location}
20 it tries to guess the mimetype and attach the file
21 """
22
23 def __init__(self, mail_from, user, passwd, mail_server,
24 mail_port=None, ssl=False, tls=False):
25
26 self.mail_from = mail_from
27 self.mail_server = mail_server
28 self.mail_port = mail_port
29 self.user = user
30 self.passwd = passwd
31 self.ssl = ssl
32 self.tls = tls
33 self.debug = False
34
35 def send(self, recipients=[], subject='', body='', attachment_files={}):
36
37 if isinstance(recipients, basestring):
38 recipients = [recipients]
39 if self.ssl:
40 smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
41 else:
42 smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
43
44 if self.tls:
45 smtp_serv.starttls()
46
47 if self.debug:
48 smtp_serv.set_debuglevel(1)
49
50 smtp_serv.ehlo("mailer")
51
52 #if server requires authorization you must provide login and password
53 smtp_serv.login(self.user, self.passwd)
54
55 date_ = formatdate(localtime=True)
56 msg = MIMEMultipart()
57 msg['From'] = self.mail_from
58 msg['To'] = ','.join(recipients)
59 msg['Date'] = date_
60 msg['Subject'] = subject
61 msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
62
63 msg.attach(MIMEText(body))
64
65 if attachment_files:
66 self.__atach_files(msg, attachment_files)
67
68 smtp_serv.sendmail(self.mail_from, recipients, msg.as_string())
69 logging.info('MAIL SEND TO: %s' % recipients)
70 smtp_serv.quit()
71
72
73 def __atach_files(self, msg, attachment_files):
74 if isinstance(attachment_files, dict):
75 for f_name, msg_file in attachment_files.items():
76 ctype, encoding = mimetypes.guess_type(f_name)
77 logging.info("guessing file %s type based on %s" , ctype, f_name)
78 if ctype is None or encoding is not None:
79 # No guess could be made, or the file is encoded (compressed), so
80 # use a generic bag-of-bits type.
81 ctype = 'application/octet-stream'
82 maintype, subtype = ctype.split('/', 1)
83 if maintype == 'text':
84 # Note: we should handle calculating the charset
85 file_part = MIMEText(self.get_content(msg_file),
86 _subtype=subtype)
87 elif maintype == 'image':
88 file_part = MIMEImage(self.get_content(msg_file),
89 _subtype=subtype)
90 elif maintype == 'audio':
91 file_part = MIMEAudio(self.get_content(msg_file),
92 _subtype=subtype)
93 else:
94 file_part = MIMEBase(maintype, subtype)
95 file_part.set_payload(self.get_content(msg_file))
96 # Encode the payload using Base64
97 encoders.encode_base64(msg)
98 # Set the filename parameter
99 file_part.add_header('Content-Disposition', 'attachment',
100 filename=f_name)
101 file_part.add_header('Content-Type', ctype, name=f_name)
102 msg.attach(file_part)
103 else:
104 raise Exception('Attachment files should be'
105 'a dict in format {"filename":"filepath"}')
106
107 def get_content(self, msg_file):
108 '''
109 Get content based on type, if content is a string do open first
110 else just read because it's a probably open file object
111 @param msg_file:
112 '''
113 if isinstance(msg_file, str):
114 return open(msg_file, "rb").read()
115 else:
116 #just for safe seek to 0
117 msg_file.seek(0)
118 return msg_file.read()
@@ -0,0 +1,267 b''
1 """caching_query.py
2
3 Represent persistence structures which allow the usage of
4 Beaker caching with SQLAlchemy.
5
6 The three new concepts introduced here are:
7
8 * CachingQuery - a Query subclass that caches and
9 retrieves results in/from Beaker.
10 * FromCache - a query option that establishes caching
11 parameters on a Query
12 * RelationshipCache - a variant of FromCache which is specific
13 to a query invoked during a lazy load.
14 * _params_from_query - extracts value parameters from
15 a Query.
16
17 The rest of what's here are standard SQLAlchemy and
18 Beaker constructs.
19
20 """
21 from sqlalchemy.orm.interfaces import MapperOption
22 from sqlalchemy.orm.query import Query
23 from sqlalchemy.sql import visitors
24
25 class CachingQuery(Query):
26 """A Query subclass which optionally loads full results from a Beaker
27 cache region.
28
29 The CachingQuery stores additional state that allows it to consult
30 a Beaker cache before accessing the database:
31
32 * A "region", which is a cache region argument passed to a
33 Beaker CacheManager, specifies a particular cache configuration
34 (including backend implementation, expiration times, etc.)
35 * A "namespace", which is a qualifying name that identifies a
36 group of keys within the cache. A query that filters on a name
37 might use the name "by_name", a query that filters on a date range
38 to a joined table might use the name "related_date_range".
39
40 When the above state is present, a Beaker cache is retrieved.
41
42 The "namespace" name is first concatenated with
43 a string composed of the individual entities and columns the Query
44 requests, i.e. such as ``Query(User.id, User.name)``.
45
46 The Beaker cache is then loaded from the cache manager based
47 on the region and composed namespace. The key within the cache
48 itself is then constructed against the bind parameters specified
49 by this query, which are usually literals defined in the
50 WHERE clause.
51
52 The FromCache and RelationshipCache mapper options below represent
53 the "public" method of configuring this state upon the CachingQuery.
54
55 """
56
57 def __init__(self, manager, *args, **kw):
58 self.cache_manager = manager
59 Query.__init__(self, *args, **kw)
60
61 def __iter__(self):
62 """override __iter__ to pull results from Beaker
63 if particular attributes have been configured.
64
65 Note that this approach does *not* detach the loaded objects from
66 the current session. If the cache backend is an in-process cache
67 (like "memory") and lives beyond the scope of the current session's
68 transaction, those objects may be expired. The method here can be
69 modified to first expunge() each loaded item from the current
70 session before returning the list of items, so that the items
71 in the cache are not the same ones in the current Session.
72
73 """
74 if hasattr(self, '_cache_parameters'):
75 return self.get_value(createfunc=lambda: list(Query.__iter__(self)))
76 else:
77 return Query.__iter__(self)
78
79 def invalidate(self):
80 """Invalidate the value represented by this Query."""
81
82 cache, cache_key = _get_cache_parameters(self)
83 cache.remove(cache_key)
84
85 def get_value(self, merge=True, createfunc=None):
86 """Return the value from the cache for this query.
87
88 Raise KeyError if no value present and no
89 createfunc specified.
90
91 """
92 cache, cache_key = _get_cache_parameters(self)
93 ret = cache.get_value(cache_key, createfunc=createfunc)
94 if merge:
95 ret = self.merge_result(ret, load=False)
96 return ret
97
98 def set_value(self, value):
99 """Set the value in the cache for this query."""
100
101 cache, cache_key = _get_cache_parameters(self)
102 cache.put(cache_key, value)
103
104 def query_callable(manager):
105 def query(*arg, **kw):
106 return CachingQuery(manager, *arg, **kw)
107 return query
108
109 def _get_cache_parameters(query):
110 """For a query with cache_region and cache_namespace configured,
111 return the correspoinding Cache instance and cache key, based
112 on this query's current criterion and parameter values.
113
114 """
115 if not hasattr(query, '_cache_parameters'):
116 raise ValueError("This Query does not have caching parameters configured.")
117
118 region, namespace, cache_key = query._cache_parameters
119
120 namespace = _namespace_from_query(namespace, query)
121
122 if cache_key is None:
123 # cache key - the value arguments from this query's parameters.
124 args = _params_from_query(query)
125 cache_key = " ".join([str(x) for x in args])
126
127 # get cache
128 cache = query.cache_manager.get_cache_region(namespace, region)
129
130 # optional - hash the cache_key too for consistent length
131 # import uuid
132 # cache_key= str(uuid.uuid5(uuid.NAMESPACE_DNS, cache_key))
133
134 return cache, cache_key
135
136 def _namespace_from_query(namespace, query):
137 # cache namespace - the token handed in by the
138 # option + class we're querying against
139 namespace = " ".join([namespace] + [str(x) for x in query._entities])
140
141 # memcached wants this
142 namespace = namespace.replace(' ', '_')
143
144 return namespace
145
146 def _set_cache_parameters(query, region, namespace, cache_key):
147
148 if hasattr(query, '_cache_parameters'):
149 region, namespace, cache_key = query._cache_parameters
150 raise ValueError("This query is already configured "
151 "for region %r namespace %r" %
152 (region, namespace)
153 )
154 query._cache_parameters = region, namespace, cache_key
155
156 class FromCache(MapperOption):
157 """Specifies that a Query should load results from a cache."""
158
159 propagate_to_loaders = False
160
161 def __init__(self, region, namespace, cache_key=None):
162 """Construct a new FromCache.
163
164 :param region: the cache region. Should be a
165 region configured in the Beaker CacheManager.
166
167 :param namespace: the cache namespace. Should
168 be a name uniquely describing the target Query's
169 lexical structure.
170
171 :param cache_key: optional. A string cache key
172 that will serve as the key to the query. Use this
173 if your query has a huge amount of parameters (such
174 as when using in_()) which correspond more simply to
175 some other identifier.
176
177 """
178 self.region = region
179 self.namespace = namespace
180 self.cache_key = cache_key
181
182 def process_query(self, query):
183 """Process a Query during normal loading operation."""
184
185 _set_cache_parameters(query, self.region, self.namespace, self.cache_key)
186
187 class RelationshipCache(MapperOption):
188 """Specifies that a Query as called within a "lazy load"
189 should load results from a cache."""
190
191 propagate_to_loaders = True
192
193 def __init__(self, region, namespace, attribute):
194 """Construct a new RelationshipCache.
195
196 :param region: the cache region. Should be a
197 region configured in the Beaker CacheManager.
198
199 :param namespace: the cache namespace. Should
200 be a name uniquely describing the target Query's
201 lexical structure.
202
203 :param attribute: A Class.attribute which
204 indicates a particular class relationship() whose
205 lazy loader should be pulled from the cache.
206
207 """
208 self.region = region
209 self.namespace = namespace
210 self._relationship_options = {
211 (attribute.property.parent.class_, attribute.property.key) : self
212 }
213
214 def process_query_conditionally(self, query):
215 """Process a Query that is used within a lazy loader.
216
217 (the process_query_conditionally() method is a SQLAlchemy
218 hook invoked only within lazyload.)
219
220 """
221 if query._current_path:
222 mapper, key = query._current_path[-2:]
223
224 for cls in mapper.class_.__mro__:
225 if (cls, key) in self._relationship_options:
226 relationship_option = self._relationship_options[(cls, key)]
227 _set_cache_parameters(
228 query,
229 relationship_option.region,
230 relationship_option.namespace,
231 None)
232
233 def and_(self, option):
234 """Chain another RelationshipCache option to this one.
235
236 While many RelationshipCache objects can be specified on a single
237 Query separately, chaining them together allows for a more efficient
238 lookup during load.
239
240 """
241 self._relationship_options.update(option._relationship_options)
242 return self
243
244
245 def _params_from_query(query):
246 """Pull the bind parameter values from a query.
247
248 This takes into account any scalar attribute bindparam set up.
249
250 E.g. params_from_query(query.filter(Cls.foo==5).filter(Cls.bar==7)))
251 would return [5, 7].
252
253 """
254 v = []
255 def visit_bindparam(bind):
256 value = query._params.get(bind.key, bind.value)
257
258 # lazyloader may dig a callable in here, intended
259 # to late-evaluate params after autoflush is called.
260 # convert to a scalar value.
261 if callable(value):
262 value = value()
263
264 v.append(value)
265 if query._criterion is not None:
266 visitors.traverse(query._criterion, {}, {'bindparam':visit_bindparam})
267 return v
@@ -0,0 +1,54 b''
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
4 <head>
5 <title>${_('Reset You password to hg-app')}</title>
6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 <link rel="icon" href="/images/hgicon.png" type="image/png" />
8 <meta name="robots" content="index, nofollow"/>
9
10 <!-- stylesheets -->
11 <link rel="stylesheet" type="text/css" href="/css/reset.css" />
12 <link rel="stylesheet" type="text/css" href="/css/style.css" media="screen" />
13 <link id="color" rel="stylesheet" type="text/css" href="/css/colors/blue.css" />
14
15 <!-- scripts -->
16
17 </head>
18 <body>
19 <div id="register">
20
21 <div class="title">
22 <h5>${_('Reset You password to hg-app')}</h5>
23 <div class="corner tl"></div>
24 <div class="corner tr"></div>
25 </div>
26 <div class="inner">
27 ${h.form(url('password_reset'))}
28 <div class="form">
29 <!-- fields -->
30 <div class="fields">
31
32 <div class="field">
33 <div class="label">
34 <label for="email">${_('Email address')}:</label>
35 </div>
36 <div class="input">
37 ${h.text('email')}
38 </div>
39 </div>
40
41 <div class="buttons">
42 <div class="nohighlight">
43 ${h.submit('send','Reset my password',class_="ui-button ui-widget ui-state-default ui-corner-all")}
44 <div class="activation_msg">${_('Your new password will be send to matching email address')}</div>
45 </div>
46 </div>
47 </div>
48 </div>
49 ${h.end_form()}
50 </div>
51 </div>
52 </body>
53 </html>
54
@@ -1,61 +1,69 b''
1 1 -------------------------------------
2 2 Pylons based replacement for hgwebdir
3 3 -------------------------------------
4 4
5 5 Fully customizable, with authentication, permissions. Based on vcs library.
6 6
7 7 **Overview**
8 8
9 9 - has it's own middleware to handle mercurial protocol request each request can
10 10 be logged and authenticated + threaded performance unlikely to hgweb
11 11 - full permissions per project read/write/admin access even on mercurial request
12 12 - mako templates let's you cusmotize look and feel of application.
13 13 - diffs annotations and source code all colored by pygments.
14 - mercurial branch graph and yui-flot powered graphs
14 - mercurial branch graph and yui-flot powered graphs with zooming
15 15 - admin interface for performing user/permission managments as well as repository
16 16 managment.
17 - full text search of source codes with indexing daemons using whoosh
18 (no external search servers required all in one application)
19 - async tasks for speed and performance using celery (works without them too)
17 20 - Additional settings for mercurial web, (hooks editable from admin
18 21 panel !) also manage paths, archive, remote messages
19 22 - backup scripts can do backup of whole app and send it over scp to desired location
20 23 - setup project descriptions and info inside built in db for easy, non
21 24 file-system operations
22 25 - added cache with invalidation on push/repo managment for high performance and
23 26 always upto date data.
24 27 - rss / atom feeds, gravatar support
25 28 - based on pylons 1.0 / sqlalchemy 0.6
26 29
27 30 **Incoming**
28 31
29 32 - code review based on hg-review (when it's stable)
30 - git support (when vcs can handle it)
31 - full text search of source codes with indexing daemons using whoosh
32 (no external search servers required all in one application)
33 - manage hg ui() per repo, add hooks settings, per repo, and not globally
34 - other cools stuff that i can figure out
33 - git support (when vcs can handle it - almost there !)
34 - commit based wikis
35 - in server forks
36 - clonning from remote repositories into hg-app
37 - other cools stuff that i can figure out (or You can help me figure out)
35 38
36 39 .. note::
37 40 This software is still in beta mode.
38 41 I don't guarantee that it'll work correctly.
39 42
40 43
41 44 -------------
42 45 Installation
43 46 -------------
44 47 .. note::
45 48 I recomend to install tip version of vcs while the app is in beta mode.
46 49
47 50
48 51 - create new virtualenv and activate it - highly recommend that you use separate
49 52 virtual-env for whole application
50 - download hg app from default (not demo) branch from bitbucket and run
53 - download hg app from default branch from bitbucket and run
51 54 'python setup.py install' this will install all required dependencies needed
52 55 - run paster setup-app production.ini it should create all needed tables
53 and an admin account.
56 and an admin account make sure You specify correct path to repositories.
54 57 - remember that the given path for mercurial repositories must be write
55 58 accessible for the application
56 59 - run paster serve development.ini - or you can use manage-hg_app script.
57 60 the app should be available at the 127.0.0.1:5000
58 61 - use admin account you created to login.
59 62 - default permissions on each repository is read, and owner is admin. So remember
60 63 to update these.
64 - in order to use full power of async tasks, You must install message broker
65 preferrably rabbitmq and start celeryd daemon. The app should gain some speed
66 than. For installation instructions
67 You can visit: http://ask.github.com/celery/getting-started/index.html. All
68 needed configs are inside hg-app ie. celeryconfig.py
61 69 No newline at end of file
@@ -1,155 +1,160 b''
1 1 ################################################################################
2 2 ################################################################################
3 # pylons_app - Pylons environment configuration #
3 # hg-app - Pylons environment configuration #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
9 9 debug = true
10 ############################################
11 ## Uncomment and replace with the address ##
12 ## which should receive any error reports ##
13 ############################################
10 ################################################################################
11 ## Uncomment and replace with the address which should receive ##
12 ## any error reports after application crash ##
13 ## Additionally those settings will be used by hg-app mailing system ##
14 ################################################################################
14 15 #email_to = admin@localhost
16 #error_email_from = paste_error@localhost
17 #app_email_from = hg-app-noreply@localhost
18 #error_message =
19
15 20 #smtp_server = mail.server.com
16 #error_email_from = paste_error@localhost
17 21 #smtp_username =
18 22 #smtp_password =
19 #error_message = 'mercurial crash !'
23 #smtp_port =
24 #smtp_use_tls =
20 25
21 26 [server:main]
22 27 ##nr of threads to spawn
23 28 threadpool_workers = 5
24 29
25 30 ##max request before
26 threadpool_max_requests = 2
31 threadpool_max_requests = 6
27 32
28 33 ##option to use threads of process
29 use_threadpool = true
34 use_threadpool = false
30 35
31 36 use = egg:Paste#http
32 37 host = 127.0.0.1
33 38 port = 5000
34 39
35 40 [app:main]
36 41 use = egg:pylons_app
37 42 full_stack = true
38 43 static_files = true
39 44 lang=en
40 45 cache_dir = %(here)s/data
41 46
42 47 ####################################
43 48 ### BEAKER CACHE ####
44 49 ####################################
45 50 beaker.cache.data_dir=/%(here)s/data/cache/data
46 51 beaker.cache.lock_dir=/%(here)s/data/cache/lock
47 52 beaker.cache.regions=super_short_term,short_term,long_term
48 53 beaker.cache.long_term.type=memory
49 54 beaker.cache.long_term.expire=36000
50 55 beaker.cache.short_term.type=memory
51 56 beaker.cache.short_term.expire=60
52 57 beaker.cache.super_short_term.type=memory
53 58 beaker.cache.super_short_term.expire=10
54 59
55 60 ####################################
56 61 ### BEAKER SESSION ####
57 62 ####################################
58 63 ## Type of storage used for the session, current types are
59 ## “dbm”, “file”, “memcached”, “database”, and “memory”.
64 ## "dbm", "file", "memcached", "database", and "memory".
60 65 ## The storage uses the Container API
61 66 ##that is also used by the cache system.
62 67 beaker.session.type = file
63 68
64 69 beaker.session.key = hg-app
65 70 beaker.session.secret = g654dcno0-9873jhgfreyu
66 71 beaker.session.timeout = 36000
67 72
68 73 ##auto save the session to not to use .save()
69 74 beaker.session.auto = False
70 75
71 76 ##true exire at browser close
72 77 #beaker.session.cookie_expires = 3600
73 78
74 79
75 80 ################################################################################
76 81 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
77 82 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
78 83 ## execute malicious code after an exception is raised. ##
79 84 ################################################################################
80 85 #set debug = false
81 86
82 87 ##################################
83 88 ### LOGVIEW CONFIG ###
84 89 ##################################
85 90 logview.sqlalchemy = #faa
86 91 logview.pylons.templating = #bfb
87 92 logview.pylons.util = #eee
88 93
89 94 #########################################################
90 95 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
91 96 #########################################################
92 97 sqlalchemy.db1.url = sqlite:///%(here)s/hg_app.db
93 98 #sqlalchemy.db1.echo = False
94 99 #sqlalchemy.db1.pool_recycle = 3600
95 100 sqlalchemy.convert_unicode = true
96 101
97 102 ################################
98 103 ### LOGGING CONFIGURATION ####
99 104 ################################
100 105 [loggers]
101 106 keys = root, routes, pylons_app, sqlalchemy
102 107
103 108 [handlers]
104 109 keys = console
105 110
106 111 [formatters]
107 112 keys = generic,color_formatter
108 113
109 114 #############
110 115 ## LOGGERS ##
111 116 #############
112 117 [logger_root]
113 118 level = NOTSET
114 119 handlers = console
115 120
116 121 [logger_routes]
117 122 level = DEBUG
118 123 handlers = console
119 124 qualname = routes.middleware
120 125 # "level = DEBUG" logs the route matched and routing variables.
121 126
122 127 [logger_pylons_app]
123 128 level = DEBUG
124 129 handlers = console
125 130 qualname = pylons_app
126 131 propagate = 0
127 132
128 133 [logger_sqlalchemy]
129 134 level = ERROR
130 135 handlers = console
131 136 qualname = sqlalchemy.engine
132 137 propagate = 0
133 138
134 139 ##############
135 140 ## HANDLERS ##
136 141 ##############
137 142
138 143 [handler_console]
139 144 class = StreamHandler
140 145 args = (sys.stderr,)
141 146 level = NOTSET
142 147 formatter = color_formatter
143 148
144 149 ################
145 150 ## FORMATTERS ##
146 151 ################
147 152
148 153 [formatter_generic]
149 154 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
150 155 datefmt = %Y-%m-%d %H:%M:%S
151 156
152 157 [formatter_color_formatter]
153 158 class=pylons_app.lib.colored_formatter.ColorFormatter
154 159 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
155 160 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
@@ -1,155 +1,160 b''
1 1 ################################################################################
2 2 ################################################################################
3 # pylons_app - Pylons environment configuration #
3 # hg-app - Pylons environment configuration #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
9 9 debug = true
10 ############################################
11 ## Uncomment and replace with the address ##
12 ## which should receive any error reports ##
13 ############################################
10 ################################################################################
11 ## Uncomment and replace with the address which should receive ##
12 ## any error reports after application crash ##
13 ## Additionally those settings will be used by hg-app mailing system ##
14 ################################################################################
14 15 #email_to = admin@localhost
16 #error_email_from = paste_error@localhost
17 #app_email_from = hg-app-noreply@localhost
18 #error_message =
19
15 20 #smtp_server = mail.server.com
16 #error_email_from = paste_error@localhost
17 21 #smtp_username =
18 22 #smtp_password =
19 #error_message = 'mercurial crash !'
23 #smtp_port =
24 #smtp_use_tls = false
20 25
21 26 [server:main]
22 27 ##nr of threads to spawn
23 28 threadpool_workers = 5
24 29
25 ##max request before
30 ##max request before thread respawn
26 31 threadpool_max_requests = 2
27 32
28 33 ##option to use threads of process
29 34 use_threadpool = true
30 35
31 36 use = egg:Paste#http
32 37 host = 127.0.0.1
33 38 port = 8001
34 39
35 40 [app:main]
36 41 use = egg:pylons_app
37 42 full_stack = true
38 43 static_files = false
39 44 lang=en
40 45 cache_dir = %(here)s/data
41 46
42 47 ####################################
43 48 ### BEAKER CACHE ####
44 49 ####################################
45 50 beaker.cache.data_dir=/%(here)s/data/cache/data
46 51 beaker.cache.lock_dir=/%(here)s/data/cache/lock
47 52 beaker.cache.regions=super_short_term,short_term,long_term
48 53 beaker.cache.long_term.type=memory
49 54 beaker.cache.long_term.expire=36000
50 55 beaker.cache.short_term.type=memory
51 56 beaker.cache.short_term.expire=60
52 57 beaker.cache.super_short_term.type=memory
53 58 beaker.cache.super_short_term.expire=10
54 59
55 60 ####################################
56 61 ### BEAKER SESSION ####
57 62 ####################################
58 63 ## Type of storage used for the session, current types are
59 64 ## dbm, file, memcached, database, and memory.
60 65 ## The storage uses the Container API
61 66 ##that is also used by the cache system.
62 67 beaker.session.type = file
63 68
64 69 beaker.session.key = hg-app
65 70 beaker.session.secret = g654dcno0-9873jhgfreyu
66 71 beaker.session.timeout = 36000
67 72
68 73 ##auto save the session to not to use .save()
69 74 beaker.session.auto = False
70 75
71 76 ##true exire at browser close
72 77 #beaker.session.cookie_expires = 3600
73 78
74 79
75 80 ################################################################################
76 81 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
77 82 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
78 83 ## execute malicious code after an exception is raised. ##
79 84 ################################################################################
80 85 set debug = false
81 86
82 87 ##################################
83 88 ### LOGVIEW CONFIG ###
84 89 ##################################
85 90 logview.sqlalchemy = #faa
86 91 logview.pylons.templating = #bfb
87 92 logview.pylons.util = #eee
88 93
89 94 #########################################################
90 95 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
91 96 #########################################################
92 97 sqlalchemy.db1.url = sqlite:///%(here)s/hg_app.db
93 98 #sqlalchemy.db1.echo = False
94 99 #sqlalchemy.db1.pool_recycle = 3600
95 100 sqlalchemy.convert_unicode = true
96 101
97 102 ################################
98 103 ### LOGGING CONFIGURATION ####
99 104 ################################
100 105 [loggers]
101 106 keys = root, routes, pylons_app, sqlalchemy
102 107
103 108 [handlers]
104 109 keys = console
105 110
106 111 [formatters]
107 112 keys = generic,color_formatter
108 113
109 114 #############
110 115 ## LOGGERS ##
111 116 #############
112 117 [logger_root]
113 118 level = INFO
114 119 handlers = console
115 120
116 121 [logger_routes]
117 122 level = INFO
118 123 handlers = console
119 124 qualname = routes.middleware
120 125 # "level = DEBUG" logs the route matched and routing variables.
121 126
122 127 [logger_pylons_app]
123 128 level = DEBUG
124 129 handlers = console
125 130 qualname = pylons_app
126 131 propagate = 0
127 132
128 133 [logger_sqlalchemy]
129 134 level = ERROR
130 135 handlers = console
131 136 qualname = sqlalchemy.engine
132 137 propagate = 0
133 138
134 139 ##############
135 140 ## HANDLERS ##
136 141 ##############
137 142
138 143 [handler_console]
139 144 class = StreamHandler
140 145 args = (sys.stderr,)
141 146 level = NOTSET
142 147 formatter = color_formatter
143 148
144 149 ################
145 150 ## FORMATTERS ##
146 151 ################
147 152
148 153 [formatter_generic]
149 154 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
150 155 datefmt = %Y-%m-%d %H:%M:%S
151 156
152 157 [formatter_color_formatter]
153 158 class=pylons_app.lib.colored_formatter.ColorFormatter
154 159 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
155 160 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
@@ -1,34 +1,35 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Hg app, a web based mercurial repository managment based on pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 9, 2010
22 22 Hg app, a web based mercurial repository managment based on pylons
23 versioning implementation: http://semver.org/
23 24 @author: marcink
24 25 """
25 26
26 VERSION = (0, 8, 2, 'beta')
27 VERSION = (0, 8, 3, 'beta')
27 28
28 29 __version__ = '.'.join((str(each) for each in VERSION[:4]))
29 30
30 31 def get_version():
31 32 """
32 33 Returns shorter version (digit parts only) as string.
33 34 """
34 35 return '.'.join((str(each) for each in VERSION[:3]))
@@ -1,156 +1,161 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # hg-app - Pylons environment configuration #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
9 9 debug = true
10 ############################################
11 ## Uncomment and replace with the address ##
12 ## which should receive any error reports ##
13 ############################################
10 ################################################################################
11 ## Uncomment and replace with the address which should receive ##
12 ## any error reports after application crash ##
13 ## Additionally those settings will be used by hg-app mailing system ##
14 ################################################################################
14 15 #email_to = admin@localhost
16 #error_email_from = paste_error@localhost
17 #app_email_from = hg-app-noreply@localhost
18 #error_message =
19
15 20 #smtp_server = mail.server.com
16 #error_email_from = paste_error@localhost
17 21 #smtp_username =
18 22 #smtp_password =
19 #error_message = 'hp-app crash !'
23 #smtp_port =
24 #smtp_use_tls = false
20 25
21 26 [server:main]
22 27 ##nr of threads to spawn
23 28 threadpool_workers = 5
24 29
25 30 ##max request before thread respawn
26 31 threadpool_max_requests = 2
27 32
28 33 ##option to use threads of process
29 34 use_threadpool = true
30 35
31 36 use = egg:Paste#http
32 37 host = 127.0.0.1
33 38 port = 8001
34 39
35 40 [app:main]
36 41 use = egg:pylons_app
37 42 full_stack = true
38 43 static_files = false
39 44 lang=en
40 45 cache_dir = %(here)s/data
41 46 app_instance_uuid = ${app_instance_uuid}
42 47
43 48 ####################################
44 49 ### BEAKER CACHE ####
45 50 ####################################
46 51 beaker.cache.data_dir=/%(here)s/data/cache/data
47 52 beaker.cache.lock_dir=/%(here)s/data/cache/lock
48 53 beaker.cache.regions=super_short_term,short_term,long_term
49 54 beaker.cache.long_term.type=memory
50 55 beaker.cache.long_term.expire=36000
51 56 beaker.cache.short_term.type=memory
52 57 beaker.cache.short_term.expire=60
53 58 beaker.cache.super_short_term.type=memory
54 59 beaker.cache.super_short_term.expire=10
55 60
56 61 ####################################
57 62 ### BEAKER SESSION ####
58 63 ####################################
59 64 ## Type of storage used for the session, current types are
60 65 ## dbm, file, memcached, database, and memory.
61 66 ## The storage uses the Container API
62 67 ##that is also used by the cache system.
63 68 beaker.session.type = file
64 69
65 70 beaker.session.key = hg-app
66 71 beaker.session.secret = ${app_instance_secret}
67 72 beaker.session.timeout = 36000
68 73
69 74 ##auto save the session to not to use .save()
70 75 beaker.session.auto = False
71 76
72 77 ##true exire at browser close
73 78 #beaker.session.cookie_expires = 3600
74 79
75 80
76 81 ################################################################################
77 82 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
78 83 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
79 84 ## execute malicious code after an exception is raised. ##
80 85 ################################################################################
81 86 set debug = false
82 87
83 88 ##################################
84 89 ### LOGVIEW CONFIG ###
85 90 ##################################
86 91 logview.sqlalchemy = #faa
87 92 logview.pylons.templating = #bfb
88 93 logview.pylons.util = #eee
89 94
90 95 #########################################################
91 96 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
92 97 #########################################################
93 98 sqlalchemy.db1.url = sqlite:///%(here)s/hg_app.db
94 99 #sqlalchemy.db1.echo = False
95 100 #sqlalchemy.db1.pool_recycle = 3600
96 101 sqlalchemy.convert_unicode = true
97 102
98 103 ################################
99 104 ### LOGGING CONFIGURATION ####
100 105 ################################
101 106 [loggers]
102 107 keys = root, routes, pylons_app, sqlalchemy
103 108
104 109 [handlers]
105 110 keys = console
106 111
107 112 [formatters]
108 113 keys = generic,color_formatter
109 114
110 115 #############
111 116 ## LOGGERS ##
112 117 #############
113 118 [logger_root]
114 119 level = INFO
115 120 handlers = console
116 121
117 122 [logger_routes]
118 123 level = INFO
119 124 handlers = console
120 125 qualname = routes.middleware
121 126 # "level = DEBUG" logs the route matched and routing variables.
122 127
123 128 [logger_pylons_app]
124 129 level = DEBUG
125 130 handlers = console
126 131 qualname = pylons_app
127 132 propagate = 0
128 133
129 134 [logger_sqlalchemy]
130 135 level = ERROR
131 136 handlers = console
132 137 qualname = sqlalchemy.engine
133 138 propagate = 0
134 139
135 140 ##############
136 141 ## HANDLERS ##
137 142 ##############
138 143
139 144 [handler_console]
140 145 class = StreamHandler
141 146 args = (sys.stderr,)
142 147 level = NOTSET
143 148 formatter = color_formatter
144 149
145 150 ################
146 151 ## FORMATTERS ##
147 152 ################
148 153
149 154 [formatter_generic]
150 155 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
151 156 datefmt = %Y-%m-%d %H:%M:%S
152 157
153 158 [formatter_color_formatter]
154 159 class=pylons_app.lib.colored_formatter.ColorFormatter
155 160 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
156 161 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
@@ -1,74 +1,79 b''
1 1 """Pylons environment configuration"""
2 2 from mako.lookup import TemplateLookup
3 3 from pylons.configuration import PylonsConfig
4 4 from pylons.error import handle_mako_error
5 5 from pylons_app.config.routing import make_map
6 6 from pylons_app.lib.auth import set_available_permissions, set_base_path
7 7 from pylons_app.lib.utils import repo2db_mapper, make_ui, set_hg_app_config
8 8 from pylons_app.model import init_model
9 9 from pylons_app.model.hg_model import _get_repos_cached_initial
10 10 from sqlalchemy import engine_from_config
11 11 import logging
12 12 import os
13 13 import pylons_app.lib.app_globals as app_globals
14 14 import pylons_app.lib.helpers
15 15
16 16 log = logging.getLogger(__name__)
17 17
18 18 def load_environment(global_conf, app_conf, initial=False):
19 19 """Configure the Pylons environment via the ``pylons.config``
20 20 object
21 21 """
22 22 config = PylonsConfig()
23 23
24 24 # Pylons paths
25 25 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
26 26 paths = dict(root=root,
27 27 controllers=os.path.join(root, 'controllers'),
28 28 static_files=os.path.join(root, 'public'),
29 29 templates=[os.path.join(root, 'templates')])
30 30
31 31 # Initialize config with the basic options
32 32 config.init_app(global_conf, app_conf, package='pylons_app', paths=paths)
33 33
34 34 config['routes.map'] = make_map(config)
35 35 config['pylons.app_globals'] = app_globals.Globals(config)
36 36 config['pylons.h'] = pylons_app.lib.helpers
37 37
38 38 # Setup cache object as early as possible
39 39 import pylons
40 40 pylons.cache._push_object(config['pylons.app_globals'].cache)
41 41
42 42 # Create the Mako TemplateLookup, with the default auto-escaping
43 43 config['pylons.app_globals'].mako_lookup = TemplateLookup(
44 44 directories=paths['templates'],
45 45 error_handler=handle_mako_error,
46 46 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
47 47 input_encoding='utf-8', default_filters=['escape'],
48 48 imports=['from webhelpers.html import escape'])
49 49
50 50 #sets the c attribute access when don't existing attribute are accessed
51 51 config['pylons.strict_tmpl_context'] = True
52 test = os.path.split(config['__file__'])[-1] == 'tests.ini'
52 test = os.path.split(config['__file__'])[-1] == 'test.ini'
53 if test:
54 from pylons_app.lib.utils import create_test_env, create_test_index
55 create_test_env('/tmp', config)
56 create_test_index('/tmp/*', True)
57
53 58 #MULTIPLE DB configs
54 59 # Setup the SQLAlchemy database engine
55 60 if config['debug'] and not test:
56 61 #use query time debugging.
57 62 from pylons_app.lib.timerproxy import TimerProxy
58 63 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.',
59 64 proxy=TimerProxy())
60 65 else:
61 66 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
62 67
63 68 init_model(sa_engine_db1)
64 69 #init baseui
65 70 config['pylons.app_globals'].baseui = make_ui('db')
66 71
67 72 repo2db_mapper(_get_repos_cached_initial(config['pylons.app_globals'], initial))
68 73 set_available_permissions(config)
69 74 set_base_path(config)
70 75 set_hg_app_config(config)
71 76 # CONFIGURATION OPTIONS HERE (note: all config options will override
72 77 # any Pylons config options)
73 78
74 79 return config
@@ -1,167 +1,171 b''
1 1 """Routes configuration
2 2
3 3 The more specific and detailed routes should be defined first so they
4 4 may take precedent over the more generic routes. For more information
5 5 refer to the routes manual at http://routes.groovie.org/docs/
6 6 """
7 7 from __future__ import with_statement
8 8 from routes import Mapper
9 9 from pylons_app.lib.utils import check_repo_fast as cr
10 10
11 11 def make_map(config):
12 12 """Create, configure and return the routes Mapper"""
13 13 map = Mapper(directory=config['pylons.paths']['controllers'],
14 14 always_scan=config['debug'])
15 15 map.minimization = False
16 16 map.explicit = False
17 17
18 18 # The ErrorController route (handles 404/500 error pages); it should
19 19 # likely stay at the top, ensuring it can always be resolved
20 20 map.connect('/error/{action}', controller='error')
21 21 map.connect('/error/{action}/{id}', controller='error')
22 22
23 23 # CUSTOM ROUTES HERE
24 24 map.connect('hg_home', '/', controller='hg', action='index')
25 25
26 26 def check_repo(environ, match_dict):
27 27 """
28 28 check for valid repository for proper 404 handling
29 29 @param environ:
30 30 @param match_dict:
31 31 """
32 32 repo_name = match_dict.get('repo_name')
33 33 return not cr(repo_name, config['base_path'])
34 34
35 35 #REST REPO MAP
36 36 with map.submapper(path_prefix='/_admin', controller='admin/repos') as m:
37 37 m.connect("repos", "/repos",
38 38 action="create", conditions=dict(method=["POST"]))
39 39 m.connect("repos", "/repos",
40 40 action="index", conditions=dict(method=["GET"]))
41 41 m.connect("formatted_repos", "/repos.{format}",
42 42 action="index",
43 43 conditions=dict(method=["GET"]))
44 44 m.connect("new_repo", "/repos/new",
45 45 action="new", conditions=dict(method=["GET"]))
46 46 m.connect("formatted_new_repo", "/repos/new.{format}",
47 47 action="new", conditions=dict(method=["GET"]))
48 48 m.connect("/repos/{repo_name:.*}",
49 49 action="update", conditions=dict(method=["PUT"],
50 50 function=check_repo))
51 51 m.connect("/repos/{repo_name:.*}",
52 52 action="delete", conditions=dict(method=["DELETE"],
53 53 function=check_repo))
54 54 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
55 55 action="edit", conditions=dict(method=["GET"],
56 56 function=check_repo))
57 57 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
58 58 action="edit", conditions=dict(method=["GET"],
59 59 function=check_repo))
60 60 m.connect("repo", "/repos/{repo_name:.*}",
61 61 action="show", conditions=dict(method=["GET"],
62 62 function=check_repo))
63 63 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
64 64 action="show", conditions=dict(method=["GET"],
65 65 function=check_repo))
66 66 #ajax delete repo perm user
67 67 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
68 68 action="delete_perm_user", conditions=dict(method=["DELETE"],
69 69 function=check_repo))
70 70
71 71 map.resource('user', 'users', controller='admin/users', path_prefix='/_admin')
72 72 map.resource('permission', 'permissions', controller='admin/permissions', path_prefix='/_admin')
73 73
74 74 #REST SETTINGS MAP
75 75 with map.submapper(path_prefix='/_admin', controller='admin/settings') as m:
76 76 m.connect("admin_settings", "/settings",
77 77 action="create", conditions=dict(method=["POST"]))
78 78 m.connect("admin_settings", "/settings",
79 79 action="index", conditions=dict(method=["GET"]))
80 80 m.connect("formatted_admin_settings", "/settings.{format}",
81 81 action="index", conditions=dict(method=["GET"]))
82 82 m.connect("admin_new_setting", "/settings/new",
83 83 action="new", conditions=dict(method=["GET"]))
84 84 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
85 85 action="new", conditions=dict(method=["GET"]))
86 86 m.connect("/settings/{setting_id}",
87 87 action="update", conditions=dict(method=["PUT"]))
88 88 m.connect("/settings/{setting_id}",
89 89 action="delete", conditions=dict(method=["DELETE"]))
90 90 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
91 91 action="edit", conditions=dict(method=["GET"]))
92 92 m.connect("formatted_admin_edit_setting", "/settings/{setting_id}.{format}/edit",
93 93 action="edit", conditions=dict(method=["GET"]))
94 94 m.connect("admin_setting", "/settings/{setting_id}",
95 95 action="show", conditions=dict(method=["GET"]))
96 96 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
97 97 action="show", conditions=dict(method=["GET"]))
98 98 m.connect("admin_settings_my_account", "/my_account",
99 99 action="my_account", conditions=dict(method=["GET"]))
100 100 m.connect("admin_settings_my_account_update", "/my_account_update",
101 101 action="my_account_update", conditions=dict(method=["PUT"]))
102 102 m.connect("admin_settings_create_repository", "/create_repository",
103 103 action="create_repository", conditions=dict(method=["GET"]))
104 104
105 105 #ADMIN
106 106 with map.submapper(path_prefix='/_admin', controller='admin/admin') as m:
107 107 m.connect('admin_home', '', action='index')#main page
108 108 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
109 109 action='add_repo')
110 110 #SEARCH
111 111 map.connect('search', '/_admin/search', controller='search')
112 112
113 #LOGIN/LOGOUT
113 #LOGIN/LOGOUT/REGISTER/SIGN IN
114 114 map.connect('login_home', '/_admin/login', controller='login')
115 115 map.connect('logout_home', '/_admin/logout', controller='login', action='logout')
116 116 map.connect('register', '/_admin/register', controller='login', action='register')
117 map.connect('reset_password', '/_admin/password_reset', controller='login', action='password_reset')
117 118
118 119 #FEEDS
119 120 map.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
120 121 controller='feed', action='rss',
121 122 conditions=dict(function=check_repo))
122 123 map.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
123 124 controller='feed', action='atom',
124 125 conditions=dict(function=check_repo))
125 126
126 127
127 128 #OTHERS
128 129 map.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
129 130 controller='changeset', revision='tip',
130 131 conditions=dict(function=check_repo))
131 132 map.connect('raw_changeset_home', '/{repo_name:.*}/raw-changeset/{revision}',
132 133 controller='changeset',action='raw_changeset', revision='tip',
133 134 conditions=dict(function=check_repo))
134 135 map.connect('summary_home', '/{repo_name:.*}/summary',
135 136 controller='summary', conditions=dict(function=check_repo))
136 137 map.connect('shortlog_home', '/{repo_name:.*}/shortlog',
137 138 controller='shortlog', conditions=dict(function=check_repo))
138 139 map.connect('branches_home', '/{repo_name:.*}/branches',
139 140 controller='branches', conditions=dict(function=check_repo))
140 141 map.connect('tags_home', '/{repo_name:.*}/tags',
141 142 controller='tags', conditions=dict(function=check_repo))
142 143 map.connect('changelog_home', '/{repo_name:.*}/changelog',
143 144 controller='changelog', conditions=dict(function=check_repo))
144 145 map.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
145 146 controller='files', revision='tip', f_path='',
146 147 conditions=dict(function=check_repo))
147 148 map.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
148 149 controller='files', action='diff', revision='tip', f_path='',
149 150 conditions=dict(function=check_repo))
150 map.connect('files_raw_home', '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
151 map.connect('files_rawfile_home', '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
151 152 controller='files', action='rawfile', revision='tip', f_path='',
152 153 conditions=dict(function=check_repo))
154 map.connect('files_raw_home', '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
155 controller='files', action='raw', revision='tip', f_path='',
156 conditions=dict(function=check_repo))
153 157 map.connect('files_annotate_home', '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
154 158 controller='files', action='annotate', revision='tip', f_path='',
155 159 conditions=dict(function=check_repo))
156 160 map.connect('files_archive_home', '/{repo_name:.*}/archive/{revision}/{fileformat}',
157 161 controller='files', action='archivefile', revision='tip',
158 162 conditions=dict(function=check_repo))
159 163 map.connect('repo_settings_update', '/{repo_name:.*}/settings',
160 164 controller='settings', action="update",
161 165 conditions=dict(method=["PUT"], function=check_repo))
162 166 map.connect('repo_settings_home', '/{repo_name:.*}/settings',
163 167 controller='settings', action='index',
164 168 conditions=dict(function=check_repo))
165 169
166 170
167 171 return map
@@ -1,286 +1,298 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # settings controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on July 14, 2010
22 22 settings controller for pylons
23 23 @author: marcink
24 24 """
25 25 from formencode import htmlfill
26 26 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
27 27 config
28 28 from pylons.controllers.util import abort, redirect
29 29 from pylons.i18n.translation import _
30 30 from pylons_app.lib import helpers as h
31 31 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator, \
32 32 HasPermissionAnyDecorator
33 33 from pylons_app.lib.base import BaseController, render
34 34 from pylons_app.lib.utils import repo2db_mapper, invalidate_cache, \
35 35 set_hg_app_config, get_hg_settings, get_hg_ui_settings, make_ui
36 36 from pylons_app.model.db import User, UserLog, HgAppSettings, HgAppUi
37 37 from pylons_app.model.forms import UserForm, ApplicationSettingsForm, \
38 38 ApplicationUiSettingsForm
39 39 from pylons_app.model.hg_model import HgModel
40 40 from pylons_app.model.user_model import UserModel
41 from pylons_app.lib.celerylib import tasks, run_task
41 42 import formencode
42 43 import logging
43 44 import traceback
44 45
45 46 log = logging.getLogger(__name__)
46 47
47 48
48 49 class SettingsController(BaseController):
49 50 """REST Controller styled on the Atom Publishing Protocol"""
50 51 # To properly map this controller, ensure your config/routing.py
51 52 # file has a resource setup:
52 53 # map.resource('setting', 'settings', controller='admin/settings',
53 54 # path_prefix='/admin', name_prefix='admin_')
54 55
55 56
56 57 @LoginRequired()
57 58 def __before__(self):
58 59 c.admin_user = session.get('admin_user')
59 60 c.admin_username = session.get('admin_username')
60 61 super(SettingsController, self).__before__()
61 62
62 63
63 64 @HasPermissionAllDecorator('hg.admin')
64 65 def index(self, format='html'):
65 66 """GET /admin/settings: All items in the collection"""
66 67 # url('admin_settings')
67 68
68 69 defaults = get_hg_settings()
69 70 defaults.update(get_hg_ui_settings())
70 71 return htmlfill.render(
71 72 render('admin/settings/settings.html'),
72 73 defaults=defaults,
73 74 encoding="UTF-8",
74 75 force_defaults=False
75 76 )
76 77
77 78 @HasPermissionAllDecorator('hg.admin')
78 79 def create(self):
79 80 """POST /admin/settings: Create a new item"""
80 81 # url('admin_settings')
81 82
82 83 @HasPermissionAllDecorator('hg.admin')
83 84 def new(self, format='html'):
84 85 """GET /admin/settings/new: Form to create a new item"""
85 86 # url('admin_new_setting')
86 87
87 88 @HasPermissionAllDecorator('hg.admin')
88 89 def update(self, setting_id):
89 90 """PUT /admin/settings/setting_id: Update an existing item"""
90 91 # Forms posted to this method should contain a hidden field:
91 92 # <input type="hidden" name="_method" value="PUT" />
92 93 # Or using helpers:
93 94 # h.form(url('admin_setting', setting_id=ID),
94 95 # method='put')
95 96 # url('admin_setting', setting_id=ID)
96 97 if setting_id == 'mapping':
97 98 rm_obsolete = request.POST.get('destroy', False)
98 99 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
99 100
100 101 initial = HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
101 102 repo2db_mapper(initial, rm_obsolete)
102 103 invalidate_cache('cached_repo_list')
103 104 h.flash(_('Repositories sucessfully rescanned'), category='success')
104 105
106 if setting_id == 'whoosh':
107 repo_location = get_hg_ui_settings()['paths_root_path']
108 full_index = request.POST.get('full_index', False)
109 task = run_task(tasks.whoosh_index, repo_location, full_index)
110
111 h.flash(_('Whoosh reindex task scheduled'), category='success')
105 112 if setting_id == 'global':
106 113
107 114 application_form = ApplicationSettingsForm()()
108 115 try:
109 116 form_result = application_form.to_python(dict(request.POST))
110 117
111 118 try:
112 119 hgsettings1 = self.sa.query(HgAppSettings)\
113 120 .filter(HgAppSettings.app_settings_name == 'title').one()
114 121 hgsettings1.app_settings_value = form_result['hg_app_title']
115 122
116 123 hgsettings2 = self.sa.query(HgAppSettings)\
117 124 .filter(HgAppSettings.app_settings_name == 'realm').one()
118 125 hgsettings2.app_settings_value = form_result['hg_app_realm']
119 126
120 127
121 128 self.sa.add(hgsettings1)
122 129 self.sa.add(hgsettings2)
123 130 self.sa.commit()
124 131 set_hg_app_config(config)
125 132 h.flash(_('Updated application settings'),
126 133 category='success')
127 134
128 135 except:
129 136 log.error(traceback.format_exc())
130 137 h.flash(_('error occured during updating application settings'),
131 138 category='error')
132 139
133 140 self.sa.rollback()
134 141
135 142
136 143 except formencode.Invalid as errors:
137 144 return htmlfill.render(
138 145 render('admin/settings/settings.html'),
139 146 defaults=errors.value,
140 147 errors=errors.error_dict or {},
141 148 prefix_error=False,
142 149 encoding="UTF-8")
143 150
144 151 if setting_id == 'mercurial':
145 152 application_form = ApplicationUiSettingsForm()()
146 153 try:
147 154 form_result = application_form.to_python(dict(request.POST))
148 155
149 156 try:
150 157
151 158 hgsettings1 = self.sa.query(HgAppUi)\
152 159 .filter(HgAppUi.ui_key == 'push_ssl').one()
153 160 hgsettings1.ui_value = form_result['web_push_ssl']
154 161
155 162 hgsettings2 = self.sa.query(HgAppUi)\
156 163 .filter(HgAppUi.ui_key == '/').one()
157 164 hgsettings2.ui_value = form_result['paths_root_path']
158 165
159 166
160 167 #HOOKS
161 168 hgsettings3 = self.sa.query(HgAppUi)\
162 169 .filter(HgAppUi.ui_key == 'changegroup.update').one()
163 170 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
164 171
165 172 hgsettings4 = self.sa.query(HgAppUi)\
166 173 .filter(HgAppUi.ui_key == 'changegroup.repo_size').one()
167 174 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
168 175
169 176
170 177
171 178
172 179 self.sa.add(hgsettings1)
173 180 self.sa.add(hgsettings2)
174 181 self.sa.add(hgsettings3)
175 182 self.sa.add(hgsettings4)
176 183 self.sa.commit()
177 184
178 185 h.flash(_('Updated mercurial settings'),
179 186 category='success')
180 187
181 188 except:
182 189 log.error(traceback.format_exc())
183 190 h.flash(_('error occured during updating application settings'),
184 191 category='error')
185 192
186 193 self.sa.rollback()
187 194
188 195
189 196 except formencode.Invalid as errors:
190 197 return htmlfill.render(
191 198 render('admin/settings/settings.html'),
192 199 defaults=errors.value,
193 200 errors=errors.error_dict or {},
194 201 prefix_error=False,
195 202 encoding="UTF-8")
196 203
197 204
198 205
199 206 return redirect(url('admin_settings'))
200 207
201 208 @HasPermissionAllDecorator('hg.admin')
202 209 def delete(self, setting_id):
203 210 """DELETE /admin/settings/setting_id: Delete an existing item"""
204 211 # Forms posted to this method should contain a hidden field:
205 212 # <input type="hidden" name="_method" value="DELETE" />
206 213 # Or using helpers:
207 214 # h.form(url('admin_setting', setting_id=ID),
208 215 # method='delete')
209 216 # url('admin_setting', setting_id=ID)
210 217
211 218 @HasPermissionAllDecorator('hg.admin')
212 219 def show(self, setting_id, format='html'):
213 220 """GET /admin/settings/setting_id: Show a specific item"""
214 221 # url('admin_setting', setting_id=ID)
215 222
216 223 @HasPermissionAllDecorator('hg.admin')
217 224 def edit(self, setting_id, format='html'):
218 225 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
219 226 # url('admin_edit_setting', setting_id=ID)
220 227
221 228
222 229 def my_account(self):
223 230 """
224 231 GET /_admin/my_account Displays info about my account
225 232 """
226 233 # url('admin_settings_my_account')
227 234 c.user = self.sa.query(User).get(c.hg_app_user.user_id)
228 235 c.user_repos = []
229 236 for repo in c.cached_repo_list.values():
230 237 if repo.dbrepo.user.username == c.user.username:
231 238 c.user_repos.append(repo)
232 239
233 240 if c.user.username == 'default':
234 241 h.flash(_("You can't edit this user since it's"
235 242 " crucial for entire application"), category='warning')
236 243 return redirect(url('users'))
237 244
238 245 defaults = c.user.__dict__
239 246 return htmlfill.render(
240 247 render('admin/users/user_edit_my_account.html'),
241 248 defaults=defaults,
242 249 encoding="UTF-8",
243 250 force_defaults=False
244 251 )
245 252
246 253 def my_account_update(self):
247 254 """PUT /_admin/my_account_update: Update an existing item"""
248 255 # Forms posted to this method should contain a hidden field:
249 256 # <input type="hidden" name="_method" value="PUT" />
250 257 # Or using helpers:
251 258 # h.form(url('admin_settings_my_account_update'),
252 259 # method='put')
253 260 # url('admin_settings_my_account_update', id=ID)
254 261 user_model = UserModel()
255 262 uid = c.hg_app_user.user_id
256 _form = UserForm(edit=True, old_data={'user_id':uid})()
263 _form = UserForm(edit=True, old_data={'user_id':uid,
264 'email':c.hg_app_user.email})()
257 265 form_result = {}
258 266 try:
259 267 form_result = _form.to_python(dict(request.POST))
260 268 user_model.update_my_account(uid, form_result)
261 269 h.flash(_('Your account was updated succesfully'),
262 270 category='success')
263 271
264 272 except formencode.Invalid as errors:
265 #c.user = self.sa.query(User).get(c.hg_app_user.user_id)
273 c.user = self.sa.query(User).get(c.hg_app_user.user_id)
274 c.user_repos = []
275 for repo in c.cached_repo_list.values():
276 if repo.dbrepo.user.username == c.user.username:
277 c.user_repos.append(repo)
266 278 return htmlfill.render(
267 279 render('admin/users/user_edit_my_account.html'),
268 280 defaults=errors.value,
269 281 errors=errors.error_dict or {},
270 282 prefix_error=False,
271 283 encoding="UTF-8")
272 284 except Exception:
273 285 log.error(traceback.format_exc())
274 286 h.flash(_('error occured during update of user %s') \
275 287 % form_result.get('username'), category='error')
276 288
277 289 return redirect(url('my_account'))
278 290
279 291 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
280 292 def create_repository(self):
281 293 """GET /_admin/create_repository: Form to create a new item"""
282 294 new_repo = request.GET.get('repo', '')
283 295 c.new_repo = h.repo_name_slug(new_repo)
284 296
285 297 return render('admin/repos/repo_add_create_repository.html')
286 298
@@ -1,162 +1,166 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # users controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 4, 2010
22 22 users controller for pylons
23 23 @author: marcink
24 24 """
25 25
26 26 from formencode import htmlfill
27 27 from pylons import request, session, tmpl_context as c, url
28 28 from pylons.controllers.util import abort, redirect
29 29 from pylons.i18n.translation import _
30 30 from pylons_app.lib import helpers as h
31 31 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
32 32 from pylons_app.lib.base import BaseController, render
33 33 from pylons_app.model.db import User, UserLog
34 34 from pylons_app.model.forms import UserForm
35 35 from pylons_app.model.user_model import UserModel, DefaultUserException
36 36 import formencode
37 37 import logging
38 38 import traceback
39 39
40 40 log = logging.getLogger(__name__)
41 41
42 42 class UsersController(BaseController):
43 43 """REST Controller styled on the Atom Publishing Protocol"""
44 44 # To properly map this controller, ensure your config/routing.py
45 45 # file has a resource setup:
46 46 # map.resource('user', 'users')
47 47
48 48 @LoginRequired()
49 49 @HasPermissionAllDecorator('hg.admin')
50 50 def __before__(self):
51 51 c.admin_user = session.get('admin_user')
52 52 c.admin_username = session.get('admin_username')
53 53 super(UsersController, self).__before__()
54 54
55 55
56 56 def index(self, format='html'):
57 57 """GET /users: All items in the collection"""
58 58 # url('users')
59 59
60 60 c.users_list = self.sa.query(User).all()
61 61 return render('admin/users/users.html')
62 62
63 63 def create(self):
64 64 """POST /users: Create a new item"""
65 65 # url('users')
66 66
67 67 user_model = UserModel()
68 68 login_form = UserForm()()
69 69 try:
70 70 form_result = login_form.to_python(dict(request.POST))
71 71 user_model.create(form_result)
72 72 h.flash(_('created user %s') % form_result['username'],
73 73 category='success')
74 74 except formencode.Invalid as errors:
75 75 return htmlfill.render(
76 76 render('admin/users/user_add.html'),
77 77 defaults=errors.value,
78 78 errors=errors.error_dict or {},
79 79 prefix_error=False,
80 80 encoding="UTF-8")
81 81 except Exception:
82 82 log.error(traceback.format_exc())
83 83 h.flash(_('error occured during creation of user %s') \
84 84 % request.POST.get('username'), category='error')
85 85 return redirect(url('users'))
86 86
87 87 def new(self, format='html'):
88 88 """GET /users/new: Form to create a new item"""
89 89 # url('new_user')
90 90 return render('admin/users/user_add.html')
91 91
92 92 def update(self, id):
93 93 """PUT /users/id: Update an existing item"""
94 94 # Forms posted to this method should contain a hidden field:
95 95 # <input type="hidden" name="_method" value="PUT" />
96 96 # Or using helpers:
97 97 # h.form(url('user', id=ID),
98 98 # method='put')
99 99 # url('user', id=ID)
100 100 user_model = UserModel()
101 _form = UserForm(edit=True, old_data={'user_id':id})()
101 c.user = user_model.get_user(id)
102
103 _form = UserForm(edit=True, old_data={'user_id':id,
104 'email':c.user.email})()
102 105 form_result = {}
103 106 try:
104 107 form_result = _form.to_python(dict(request.POST))
105 108 user_model.update(id, form_result)
106 109 h.flash(_('User updated succesfully'), category='success')
107 110
108 111 except formencode.Invalid as errors:
109 c.user = user_model.get_user(id)
110 112 return htmlfill.render(
111 113 render('admin/users/user_edit.html'),
112 114 defaults=errors.value,
113 115 errors=errors.error_dict or {},
114 116 prefix_error=False,
115 117 encoding="UTF-8")
116 118 except Exception:
117 119 log.error(traceback.format_exc())
118 120 h.flash(_('error occured during update of user %s') \
119 121 % form_result.get('username'), category='error')
120 122
121 123 return redirect(url('users'))
122 124
123 125 def delete(self, id):
124 126 """DELETE /users/id: Delete an existing item"""
125 127 # Forms posted to this method should contain a hidden field:
126 128 # <input type="hidden" name="_method" value="DELETE" />
127 129 # Or using helpers:
128 130 # h.form(url('user', id=ID),
129 131 # method='delete')
130 132 # url('user', id=ID)
131 133 user_model = UserModel()
132 134 try:
133 135 user_model.delete(id)
134 136 h.flash(_('sucessfully deleted user'), category='success')
135 137 except DefaultUserException as e:
136 138 h.flash(str(e), category='warning')
137 139 except Exception:
138 140 h.flash(_('An error occured during deletion of user'),
139 141 category='error')
140 142 return redirect(url('users'))
141 143
142 144 def show(self, id, format='html'):
143 145 """GET /users/id: Show a specific item"""
144 146 # url('user', id=ID)
145 147
146 148
147 149 def edit(self, id, format='html'):
148 150 """GET /users/id/edit: Form to edit an existing item"""
149 151 # url('edit_user', id=ID)
150 152 c.user = self.sa.query(User).get(id)
153 if not c.user:
154 return redirect(url('users'))
151 155 if c.user.username == 'default':
152 156 h.flash(_("You can't edit this user since it's"
153 157 " crucial for entire application"), category='warning')
154 158 return redirect(url('users'))
155 159
156 160 defaults = c.user.__dict__
157 161 return htmlfill.render(
158 162 render('admin/users/user_edit.html'),
159 163 defaults=defaults,
160 164 encoding="UTF-8",
161 165 force_defaults=False
162 166 )
@@ -1,199 +1,207 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # files controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 21, 2010
22 22 files controller for pylons
23 23 @author: marcink
24 24 """
25 25 from mercurial import archival
26 26 from pylons import request, response, session, tmpl_context as c, url
27 27 from pylons.controllers.util import redirect
28 28 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
29 29 from pylons_app.lib.base import BaseController, render
30 30 from pylons_app.lib.utils import EmptyChangeset
31 31 from pylons_app.model.hg_model import HgModel
32 32 from vcs.exceptions import RepositoryError, ChangesetError
33 33 from vcs.nodes import FileNode
34 34 from vcs.utils import diffs as differ
35 35 import logging
36 36 import pylons_app.lib.helpers as h
37 37 import tempfile
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41 class FilesController(BaseController):
42 42
43 43 @LoginRequired()
44 44 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
45 45 'repository.admin')
46 46 def __before__(self):
47 47 super(FilesController, self).__before__()
48 c.file_size_limit = 250 * 1024 #limit of file size to display
48 49
49 50 def index(self, repo_name, revision, f_path):
50 51 hg_model = HgModel()
51 52 c.repo = repo = hg_model.get_repo(c.repo_name)
52 53 revision = request.POST.get('at_rev', None) or revision
53 54
54 55 def get_next_rev(cur):
55 56 max_rev = len(c.repo.revisions) - 1
56 57 r = cur + 1
57 58 if r > max_rev:
58 59 r = max_rev
59 60 return r
60 61
61 62 def get_prev_rev(cur):
62 63 r = cur - 1
63 64 return r
64 65
65 66 c.f_path = f_path
66 67
67 68
68 69 try:
69 70 cur_rev = repo.get_changeset(revision).revision
70 71 prev_rev = repo.get_changeset(get_prev_rev(cur_rev)).raw_id
71 72 next_rev = repo.get_changeset(get_next_rev(cur_rev)).raw_id
72 73
73 74 c.url_prev = url('files_home', repo_name=c.repo_name,
74 75 revision=prev_rev, f_path=f_path)
75 76 c.url_next = url('files_home', repo_name=c.repo_name,
76 77 revision=next_rev, f_path=f_path)
77 78
78 79 c.changeset = repo.get_changeset(revision)
79 80
80
81 81 c.cur_rev = c.changeset.raw_id
82 82 c.rev_nr = c.changeset.revision
83 83 c.files_list = c.changeset.get_node(f_path)
84 84 c.file_history = self._get_history(repo, c.files_list, f_path)
85 85
86 86 except (RepositoryError, ChangesetError):
87 87 c.files_list = None
88 88
89 89 return render('files/files.html')
90 90
91 91 def rawfile(self, repo_name, revision, f_path):
92 92 hg_model = HgModel()
93 93 c.repo = hg_model.get_repo(c.repo_name)
94 94 file_node = c.repo.get_changeset(revision).get_node(f_path)
95 95 response.content_type = file_node.mimetype
96 96 response.content_disposition = 'attachment; filename=%s' \
97 97 % f_path.split('/')[-1]
98 98 return file_node.content
99 99
100 def raw(self, repo_name, revision, f_path):
101 hg_model = HgModel()
102 c.repo = hg_model.get_repo(c.repo_name)
103 file_node = c.repo.get_changeset(revision).get_node(f_path)
104 response.content_type = 'text/plain'
105
106 return file_node.content
107
100 108 def annotate(self, repo_name, revision, f_path):
101 109 hg_model = HgModel()
102 110 c.repo = hg_model.get_repo(c.repo_name)
103 111 cs = c.repo.get_changeset(revision)
104 112 c.file = cs.get_node(f_path)
105 113 c.file_msg = cs.get_file_message(f_path)
106 114 c.cur_rev = cs.raw_id
107 115 c.rev_nr = cs.revision
108 116 c.f_path = f_path
109 117
110 118 return render('files/files_annotate.html')
111 119
112 120 def archivefile(self, repo_name, revision, fileformat):
113 121 archive_specs = {
114 122 '.tar.bz2': ('application/x-tar', 'tbz2'),
115 123 '.tar.gz': ('application/x-tar', 'tgz'),
116 124 '.zip': ('application/zip', 'zip'),
117 125 }
118 126 if not archive_specs.has_key(fileformat):
119 127 return 'Unknown archive type %s' % fileformat
120 128
121 129 def read_in_chunks(file_object, chunk_size=1024 * 40):
122 130 """Lazy function (generator) to read a file piece by piece.
123 131 Default chunk size: 40k."""
124 132 while True:
125 133 data = file_object.read(chunk_size)
126 134 if not data:
127 135 break
128 136 yield data
129 137
130 138 archive = tempfile.TemporaryFile()
131 139 repo = HgModel().get_repo(repo_name).repo
132 140 fname = '%s-%s%s' % (repo_name, revision, fileformat)
133 141 archival.archive(repo, archive, revision, archive_specs[fileformat][1],
134 142 prefix='%s-%s' % (repo_name, revision))
135 143 response.content_type = archive_specs[fileformat][0]
136 144 response.content_disposition = 'attachment; filename=%s' % fname
137 145 archive.seek(0)
138 146 return read_in_chunks(archive)
139 147
140 148 def diff(self, repo_name, f_path):
141 149 hg_model = HgModel()
142 150 diff1 = request.GET.get('diff1')
143 151 diff2 = request.GET.get('diff2')
144 152 c.action = request.GET.get('diff')
145 153 c.no_changes = diff1 == diff2
146 154 c.f_path = f_path
147 155 c.repo = hg_model.get_repo(c.repo_name)
148 156
149 157 try:
150 158 if diff1 not in ['', None, 'None', '0' * 12]:
151 159 c.changeset_1 = c.repo.get_changeset(diff1)
152 160 node1 = c.changeset_1.get_node(f_path)
153 161 else:
154 162 c.changeset_1 = EmptyChangeset()
155 163 node1 = FileNode('.', '')
156 164 if diff2 not in ['', None, 'None', '0' * 12]:
157 165 c.changeset_2 = c.repo.get_changeset(diff2)
158 166 node2 = c.changeset_2.get_node(f_path)
159 167 else:
160 168 c.changeset_2 = EmptyChangeset()
161 169 node2 = FileNode('.', '')
162 170 except RepositoryError:
163 171 return redirect(url('files_home',
164 172 repo_name=c.repo_name, f_path=f_path))
165 173
166 174 c.diff1 = 'r%s:%s' % (c.changeset_1.revision, c.changeset_1.raw_id)
167 175 c.diff2 = 'r%s:%s' % (c.changeset_2.revision, c.changeset_2.raw_id)
168 176 f_udiff = differ.get_udiff(node1, node2)
169 177
170 178 diff = differ.DiffProcessor(f_udiff)
171 179
172 180 if c.action == 'download':
173 181 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
174 182 response.content_type = 'text/plain'
175 183 response.content_disposition = 'attachment; filename=%s' \
176 184 % diff_name
177 185 return diff.raw_diff()
178 186
179 187 elif c.action == 'raw':
180 188 c.cur_diff = '<pre class="raw">%s</pre>' % h.escape(diff.raw_diff())
181 189 elif c.action == 'diff':
182 190 c.cur_diff = diff.as_html()
183 191 else:
184 192 #default option
185 193 c.cur_diff = diff.as_html()
186 194
187 195 if not c.cur_diff: c.no_changes = True
188 196 return render('files/file_diff.html')
189 197
190 198 def _get_history(self, repo, node, f_path):
191 199 from vcs.nodes import NodeKind
192 200 if not node.kind is NodeKind.FILE:
193 201 return []
194 202 changesets = node.history
195 203 hist_l = []
196 204 for chs in changesets:
197 205 n_desc = 'r%s:%s' % (chs.revision, chs._short)
198 206 hist_l.append((chs._short, n_desc,))
199 207 return hist_l
@@ -1,118 +1,144 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # login controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20
21 21 """
22 22 Created on April 22, 2010
23 23 login controller for pylons
24 24 @author: marcink
25 25 """
26 26 from formencode import htmlfill
27 27 from pylons import request, response, session, tmpl_context as c, url
28 28 from pylons.controllers.util import abort, redirect
29 29 from pylons_app.lib.auth import AuthUser, HasPermissionAnyDecorator
30 30 from pylons_app.lib.base import BaseController, render
31 from pylons_app.model.forms import LoginForm, RegisterForm
31 import pylons_app.lib.helpers as h
32 from pylons.i18n.translation import _
33 from pylons_app.model.forms import LoginForm, RegisterForm, PasswordResetForm
32 34 from pylons_app.model.user_model import UserModel
33 35 import formencode
34 36 import logging
35 37
36 38 log = logging.getLogger(__name__)
37 39
38 40 class LoginController(BaseController):
39 41
40 42 def __before__(self):
41 43 super(LoginController, self).__before__()
42 44
43 45 def index(self):
44 46 #redirect if already logged in
45 47 c.came_from = request.GET.get('came_from',None)
46 48
47 49 if c.hg_app_user.is_authenticated:
48 50 return redirect(url('hg_home'))
49 51
50 52 if request.POST:
51 53 #import Login Form validator class
52 54 login_form = LoginForm()
53 55 try:
54 56 c.form_result = login_form.to_python(dict(request.POST))
55 57 username = c.form_result['username']
56 58 user = UserModel().get_user_by_name(username)
57 59 auth_user = AuthUser()
58 60 auth_user.username = user.username
59 61 auth_user.is_authenticated = True
60 62 auth_user.is_admin = user.admin
61 63 auth_user.user_id = user.user_id
62 64 auth_user.name = user.name
63 65 auth_user.lastname = user.lastname
64 66 session['hg_app_user'] = auth_user
65 67 session.save()
66 68 log.info('user %s is now authenticated', username)
67 69
68 70 user.update_lastlogin()
69 71
70 72 if c.came_from:
71 73 return redirect(c.came_from)
72 74 else:
73 75 return redirect(url('hg_home'))
74 76
75 77 except formencode.Invalid as errors:
76 78 return htmlfill.render(
77 79 render('/login.html'),
78 80 defaults=errors.value,
79 81 errors=errors.error_dict or {},
80 82 prefix_error=False,
81 83 encoding="UTF-8")
82 84
83 85 return render('/login.html')
84 86
85 87 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
86 88 'hg.register.manual_activate')
87 89 def register(self):
88 90 user_model = UserModel()
89 91 c.auto_active = False
90 92 for perm in user_model.get_default().user_perms:
91 93 if perm.permission.permission_name == 'hg.register.auto_activate':
92 94 c.auto_active = True
93 95 break
94 96
95 97 if request.POST:
96 98
97 99 register_form = RegisterForm()()
98 100 try:
99 101 form_result = register_form.to_python(dict(request.POST))
100 102 form_result['active'] = c.auto_active
101 103 user_model.create_registration(form_result)
104 h.flash(_('You have successfully registered into hg-app'),
105 category='success')
102 106 return redirect(url('login_home'))
103 107
104 108 except formencode.Invalid as errors:
105 109 return htmlfill.render(
106 110 render('/register.html'),
107 111 defaults=errors.value,
108 112 errors=errors.error_dict or {},
109 113 prefix_error=False,
110 114 encoding="UTF-8")
111 115
112 116 return render('/register.html')
113 117
118 def password_reset(self):
119 user_model = UserModel()
120 if request.POST:
121
122 password_reset_form = PasswordResetForm()()
123 try:
124 form_result = password_reset_form.to_python(dict(request.POST))
125 user_model.reset_password(form_result)
126 h.flash(_('Your new password was sent'),
127 category='success')
128 return redirect(url('login_home'))
129
130 except formencode.Invalid as errors:
131 return htmlfill.render(
132 render('/password_reset.html'),
133 defaults=errors.value,
134 errors=errors.error_dict or {},
135 prefix_error=False,
136 encoding="UTF-8")
137
138 return render('/password_reset.html')
139
114 140 def logout(self):
115 141 session['hg_app_user'] = AuthUser()
116 142 session.save()
117 143 log.info('Logging out and setting user as Empty')
118 144 redirect(url('hg_home'))
@@ -1,113 +1,98 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # search controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on Aug 7, 2010
22 22 search controller for pylons
23 23 @author: marcink
24 24 """
25 25 from pylons import request, response, session, tmpl_context as c, url
26 26 from pylons.controllers.util import abort, redirect
27 27 from pylons_app.lib.auth import LoginRequired
28 28 from pylons_app.lib.base import BaseController, render
29 from pylons_app.lib.indexers import ANALYZER, IDX_LOCATION, SCHEMA, IDX_NAME
30 from webhelpers.html.builder import escape
31 from whoosh.highlight import highlight, SimpleFragmenter, HtmlFormatter, \
32 ContextFragmenter
29 from pylons_app.lib.indexers import IDX_LOCATION, SCHEMA, IDX_NAME, ResultWrapper
30 from webhelpers.paginate import Page
31 from webhelpers.util import update_params
33 32 from pylons.i18n.translation import _
34 33 from whoosh.index import open_dir, EmptyIndexError
35 34 from whoosh.qparser import QueryParser, QueryParserError
36 35 from whoosh.query import Phrase
37 36 import logging
38 37 import traceback
39 38
40 39 log = logging.getLogger(__name__)
41 40
42 41 class SearchController(BaseController):
43 42
44 43 @LoginRequired()
45 44 def __before__(self):
46 45 super(SearchController, self).__before__()
47 46
48
49 47 def index(self):
50 48 c.formated_results = []
51 49 c.runtime = ''
52 search_items = set()
53 50 c.cur_query = request.GET.get('q', None)
54 51 if c.cur_query:
55 52 cur_query = c.cur_query.lower()
56 53
57
58 54 if c.cur_query:
55 p = int(request.params.get('page', 1))
56 highlight_items = set()
59 57 try:
60 58 idx = open_dir(IDX_LOCATION, indexname=IDX_NAME)
61 59 searcher = idx.searcher()
62 60
63 61 qp = QueryParser("content", schema=SCHEMA)
64 62 try:
65 63 query = qp.parse(unicode(cur_query))
66 64
67 65 if isinstance(query, Phrase):
68 search_items.update(query.words)
66 highlight_items.update(query.words)
69 67 else:
70 68 for i in query.all_terms():
71 search_items.add(i[1])
69 if i[0] == 'content':
70 highlight_items.add(i[1])
71
72 matcher = query.matcher(searcher)
72 73
73 74 log.debug(query)
74 log.debug(search_items)
75 log.debug(highlight_items)
75 76 results = searcher.search(query)
77 res_ln = len(results)
76 78 c.runtime = '%s results (%.3f seconds)' \
77 % (len(results), results.runtime)
78
79 analyzer = ANALYZER
80 formatter = HtmlFormatter('span',
81 between='\n<span class="break">...</span>\n')
82
83 #how the parts are splitted within the same text part
84 fragmenter = SimpleFragmenter(200)
85 #fragmenter = ContextFragmenter(search_items)
79 % (res_ln, results.runtime)
86 80
87 for res in results:
88 d = {}
89 d.update(res)
90 hl = highlight(escape(res['content']), search_items,
91 analyzer=analyzer,
92 fragmenter=fragmenter,
93 formatter=formatter,
94 top=5)
95 f_path = res['path'][res['path'].find(res['repository']) \
96 + len(res['repository']):].lstrip('/')
97 d.update({'content_short':hl,
98 'f_path':f_path})
99 #del d['content']
100 c.formated_results.append(d)
81 def url_generator(**kw):
82 return update_params("?q=%s" % c.cur_query, **kw)
83
84 c.formated_results = Page(
85 ResultWrapper(searcher, matcher, highlight_items),
86 page=p, item_count=res_ln,
87 items_per_page=10, url=url_generator)
101 88
102 89 except QueryParserError:
103 90 c.runtime = _('Invalid search query. Try quoting it.')
104
91 searcher.close()
105 92 except (EmptyIndexError, IOError):
106 93 log.error(traceback.format_exc())
107 94 log.error('Empty Index data')
108 95 c.runtime = _('There is no index to search in. Please run whoosh indexer')
109 96
110
111
112 97 # Return a rendered template
113 98 return render('/search/search.html')
@@ -1,139 +1,96 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # summary controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 18, 2010
22 22 summary controller for pylons
23 23 @author: marcink
24 24 """
25 from datetime import datetime, timedelta
26 from pylons import tmpl_context as c, request
25 from pylons import tmpl_context as c, request, url
27 26 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
28 27 from pylons_app.lib.base import BaseController, render
29 from pylons_app.lib.helpers import person
30 28 from pylons_app.lib.utils import OrderedDict
31 29 from pylons_app.model.hg_model import HgModel
30 from pylons_app.model.db import Statistics
31 from webhelpers.paginate import Page
32 from pylons_app.lib.celerylib import run_task
33 from pylons_app.lib.celerylib.tasks import get_commits_stats
34 from datetime import datetime, timedelta
32 35 from time import mktime
33 from webhelpers.paginate import Page
34 36 import calendar
35 37 import logging
36 38
37 39 log = logging.getLogger(__name__)
38 40
39 41 class SummaryController(BaseController):
40 42
41 43 @LoginRequired()
42 44 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
43 45 'repository.admin')
44 46 def __before__(self):
45 47 super(SummaryController, self).__before__()
46 48
47 49 def index(self):
48 50 hg_model = HgModel()
49 51 c.repo_info = hg_model.get_repo(c.repo_name)
50 52 c.repo_changesets = Page(list(c.repo_info[:10]), page=1, items_per_page=20)
51 53 e = request.environ
52 54 uri = u'%(protocol)s://%(user)s@%(host)s/%(repo_name)s' % {
53 55 'protocol': e.get('wsgi.url_scheme'),
54 56 'user':str(c.hg_app_user.username),
55 57 'host':e.get('HTTP_HOST'),
56 58 'repo_name':c.repo_name, }
57 59 c.clone_repo_url = uri
58 60 c.repo_tags = OrderedDict()
59 61 for name, hash in c.repo_info.tags.items()[:10]:
60 62 c.repo_tags[name] = c.repo_info.get_changeset(hash)
61 63
62 64 c.repo_branches = OrderedDict()
63 65 for name, hash in c.repo_info.branches.items()[:10]:
64 66 c.repo_branches[name] = c.repo_info.get_changeset(hash)
65 67
66 c.commit_data = self.__get_commit_stats(c.repo_info)
68 td = datetime.today() + timedelta(days=1)
69 y, m, d = td.year, td.month, td.day
70
71 ts_min_y = mktime((y - 1, (td - timedelta(days=calendar.mdays[m])).month,
72 d, 0, 0, 0, 0, 0, 0,))
73 ts_min_m = mktime((y, (td - timedelta(days=calendar.mdays[m])).month,
74 d, 0, 0, 0, 0, 0, 0,))
75
76 ts_max_y = mktime((y, m, d, 0, 0, 0, 0, 0, 0,))
77
78 run_task(get_commits_stats, c.repo_info.name, ts_min_y, ts_max_y)
79 c.ts_min = ts_min_m
80 c.ts_max = ts_max_y
81
82
83 stats = self.sa.query(Statistics)\
84 .filter(Statistics.repository == c.repo_info.dbrepo)\
85 .scalar()
86
87 if stats:
88 c.commit_data = stats.commit_activity
89 c.overview_data = stats.commit_activity_combined
90 else:
91 import json
92 c.commit_data = json.dumps({})
93 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 0] ])
67 94
68 95 return render('summary/summary.html')
69 96
70
71
72 def __get_commit_stats(self, repo):
73 aggregate = OrderedDict()
74
75 #graph range
76 td = datetime.today() + timedelta(days=1)
77 y, m, d = td.year, td.month, td.day
78 c.ts_min = mktime((y, (td - timedelta(days=calendar.mdays[m])).month,
79 d, 0, 0, 0, 0, 0, 0,))
80 c.ts_max = mktime((y, m, d, 0, 0, 0, 0, 0, 0,))
81
82 def author_key_cleaner(k):
83 k = person(k)
84 k = k.replace('"', "'") #for js data compatibilty
85 return k
86
87 for cs in repo[:200]:#added limit 200 until fix #29 is made
88 k = '%s-%s-%s' % (cs.date.timetuple()[0], cs.date.timetuple()[1],
89 cs.date.timetuple()[2])
90 timetupple = [int(x) for x in k.split('-')]
91 timetupple.extend([0 for _ in xrange(6)])
92 k = mktime(timetupple)
93 if aggregate.has_key(author_key_cleaner(cs.author)):
94 if aggregate[author_key_cleaner(cs.author)].has_key(k):
95 aggregate[author_key_cleaner(cs.author)][k]["commits"] += 1
96 aggregate[author_key_cleaner(cs.author)][k]["added"] += len(cs.added)
97 aggregate[author_key_cleaner(cs.author)][k]["changed"] += len(cs.changed)
98 aggregate[author_key_cleaner(cs.author)][k]["removed"] += len(cs.removed)
99
100 else:
101 #aggregate[author_key_cleaner(cs.author)].update(dates_range)
102 if k >= c.ts_min and k <= c.ts_max:
103 aggregate[author_key_cleaner(cs.author)][k] = {}
104 aggregate[author_key_cleaner(cs.author)][k]["commits"] = 1
105 aggregate[author_key_cleaner(cs.author)][k]["added"] = len(cs.added)
106 aggregate[author_key_cleaner(cs.author)][k]["changed"] = len(cs.changed)
107 aggregate[author_key_cleaner(cs.author)][k]["removed"] = len(cs.removed)
108
109 else:
110 if k >= c.ts_min and k <= c.ts_max:
111 aggregate[author_key_cleaner(cs.author)] = OrderedDict()
112 #aggregate[author_key_cleaner(cs.author)].update(dates_range)
113 aggregate[author_key_cleaner(cs.author)][k] = {}
114 aggregate[author_key_cleaner(cs.author)][k]["commits"] = 1
115 aggregate[author_key_cleaner(cs.author)][k]["added"] = len(cs.added)
116 aggregate[author_key_cleaner(cs.author)][k]["changed"] = len(cs.changed)
117 aggregate[author_key_cleaner(cs.author)][k]["removed"] = len(cs.removed)
118
119 d = ''
120 tmpl0 = u""""%s":%s"""
121 tmpl1 = u"""{label:"%s",data:%s,schema:["commits"]},"""
122 for author in aggregate:
123
124 d += tmpl0 % (author,
125 tmpl1 \
126 % (author,
127 [{"time":x,
128 "commits":aggregate[author][x]['commits'],
129 "added":aggregate[author][x]['added'],
130 "changed":aggregate[author][x]['changed'],
131 "removed":aggregate[author][x]['removed'],
132 } for x in aggregate[author]]))
133 if d == '':
134 d = '"%s":{label:"%s",data:[[0,1],]}' \
135 % (author_key_cleaner(repo.contact),
136 author_key_cleaner(repo.contact))
137 return d
138
139
@@ -1,454 +1,481 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # authentication and permission libraries
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 4, 2010
22 22
23 23 @author: marcink
24 24 """
25 25 from beaker.cache import cache_region
26 26 from pylons import config, session, url, request
27 27 from pylons.controllers.util import abort, redirect
28 28 from pylons_app.lib.utils import get_repo_slug
29 29 from pylons_app.model import meta
30 30 from pylons_app.model.db import User, RepoToPerm, Repository, Permission, \
31 31 UserToPerm
32 32 from sqlalchemy.exc import OperationalError
33 33 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
34 34 import bcrypt
35 35 from decorator import decorator
36 36 import logging
37 import random
37 38
38 39 log = logging.getLogger(__name__)
39 40
41 class PasswordGenerator(object):
42 """This is a simple class for generating password from
43 different sets of characters
44 usage:
45 passwd_gen = PasswordGenerator()
46 #print 8-letter password containing only big and small letters of alphabet
47 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
48 """
49 ALPHABETS_NUM = r'''1234567890'''#[0]
50 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
51 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
52 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' #[3]
53 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4]
54 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5]
55 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
56 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6]
57 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7]
58
59 def __init__(self, passwd=''):
60 self.passwd = passwd
61
62 def gen_password(self, len, type):
63 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
64 return self.passwd
65
66
40 67 def get_crypt_password(password):
41 68 """Cryptographic function used for password hashing based on sha1
42 69 @param password: password to hash
43 70 """
44 71 return bcrypt.hashpw(password, bcrypt.gensalt(10))
45 72
46 73 def check_password(password, hashed):
47 74 return bcrypt.hashpw(password, hashed) == hashed
48 75
49 76 @cache_region('super_short_term', 'cached_user')
50 77 def get_user_cached(username):
51 78 sa = meta.Session
52 79 try:
53 80 user = sa.query(User).filter(User.username == username).one()
54 81 finally:
55 82 meta.Session.remove()
56 83 return user
57 84
58 85 def authfunc(environ, username, password):
59 86 try:
60 87 user = get_user_cached(username)
61 88 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
62 89 log.error(e)
63 90 user = None
64 91
65 92 if user:
66 93 if user.active:
67 94 if user.username == username and check_password(password, user.password):
68 95 log.info('user %s authenticated correctly', username)
69 96 return True
70 97 else:
71 98 log.error('user %s is disabled', username)
72 99
73 100 return False
74 101
75 102 class AuthUser(object):
76 103 """
77 104 A simple object that handles a mercurial username for authentication
78 105 """
79 106 def __init__(self):
80 107 self.username = 'None'
81 108 self.name = ''
82 109 self.lastname = ''
83 110 self.email = ''
84 111 self.user_id = None
85 112 self.is_authenticated = False
86 113 self.is_admin = False
87 114 self.permissions = {}
88 115
89 116
90 117 def set_available_permissions(config):
91 118 """
92 119 This function will propagate pylons globals with all available defined
93 120 permission given in db. We don't wannt to check each time from db for new
94 121 permissions since adding a new permission also requires application restart
95 122 ie. to decorate new views with the newly created permission
96 123 @param config:
97 124 """
98 125 log.info('getting information about all available permissions')
99 126 try:
100 127 sa = meta.Session
101 128 all_perms = sa.query(Permission).all()
102 129 finally:
103 130 meta.Session.remove()
104 131
105 132 config['available_permissions'] = [x.permission_name for x in all_perms]
106 133
107 134 def set_base_path(config):
108 135 config['base_path'] = config['pylons.app_globals'].base_path
109 136
110 137 def fill_data(user):
111 138 """
112 139 Fills user data with those from database and log out user if not present
113 140 in database
114 141 @param user:
115 142 """
116 143 sa = meta.Session
117 144 dbuser = sa.query(User).get(user.user_id)
118 145 if dbuser:
119 146 user.username = dbuser.username
120 147 user.is_admin = dbuser.admin
121 148 user.name = dbuser.name
122 149 user.lastname = dbuser.lastname
123 150 user.email = dbuser.email
124 151 else:
125 152 user.is_authenticated = False
126 153 meta.Session.remove()
127 154 return user
128 155
129 156 def fill_perms(user):
130 157 """
131 158 Fills user permission attribute with permissions taken from database
132 159 @param user:
133 160 """
134 161
135 162 sa = meta.Session
136 163 user.permissions['repositories'] = {}
137 164 user.permissions['global'] = set()
138 165
139 166 #===========================================================================
140 167 # fetch default permissions
141 168 #===========================================================================
142 169 default_perms = sa.query(RepoToPerm, Repository, Permission)\
143 170 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
144 171 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
145 172 .filter(RepoToPerm.user == sa.query(User).filter(User.username ==
146 173 'default').scalar()).all()
147 174
148 175 if user.is_admin:
149 176 #=======================================================================
150 177 # #admin have all default rights set to admin
151 178 #=======================================================================
152 179 user.permissions['global'].add('hg.admin')
153 180
154 181 for perm in default_perms:
155 182 p = 'repository.admin'
156 183 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
157 184
158 185 else:
159 186 #=======================================================================
160 187 # set default permissions
161 188 #=======================================================================
162 189
163 190 #default global
164 191 default_global_perms = sa.query(UserToPerm)\
165 192 .filter(UserToPerm.user == sa.query(User).filter(User.username ==
166 193 'default').one())
167 194
168 195 for perm in default_global_perms:
169 196 user.permissions['global'].add(perm.permission.permission_name)
170 197
171 198 #default repositories
172 199 for perm in default_perms:
173 200 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
174 201 #disable defaults for private repos,
175 202 p = 'repository.none'
176 203 elif perm.Repository.user_id == user.user_id:
177 204 #set admin if owner
178 205 p = 'repository.admin'
179 206 else:
180 207 p = perm.Permission.permission_name
181 208
182 209 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
183 210
184 211 #=======================================================================
185 212 # #overwrite default with user permissions if any
186 213 #=======================================================================
187 214 user_perms = sa.query(RepoToPerm, Permission, Repository)\
188 215 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
189 216 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
190 217 .filter(RepoToPerm.user_id == user.user_id).all()
191 218
192 219 for perm in user_perms:
193 220 if perm.Repository.user_id == user.user_id:#set admin if owner
194 221 p = 'repository.admin'
195 222 else:
196 223 p = perm.Permission.permission_name
197 224 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
198 225 meta.Session.remove()
199 226 return user
200 227
201 228 def get_user(session):
202 229 """
203 230 Gets user from session, and wraps permissions into user
204 231 @param session:
205 232 """
206 233 user = session.get('hg_app_user', AuthUser())
207 234 if user.is_authenticated:
208 235 user = fill_data(user)
209 236 user = fill_perms(user)
210 237 session['hg_app_user'] = user
211 238 session.save()
212 239 return user
213 240
214 241 #===============================================================================
215 242 # CHECK DECORATORS
216 243 #===============================================================================
217 244 class LoginRequired(object):
218 245 """Must be logged in to execute this function else redirect to login page"""
219 246
220 247 def __call__(self, func):
221 248 return decorator(self.__wrapper, func)
222 249
223 250 def __wrapper(self, func, *fargs, **fkwargs):
224 251 user = session.get('hg_app_user', AuthUser())
225 252 log.debug('Checking login required for user:%s', user.username)
226 253 if user.is_authenticated:
227 254 log.debug('user %s is authenticated', user.username)
228 255 return func(*fargs, **fkwargs)
229 256 else:
230 257 log.warn('user %s not authenticated', user.username)
231 258
232 259 p = request.environ.get('PATH_INFO')
233 260 if request.environ.get('QUERY_STRING'):
234 261 p+='?'+request.environ.get('QUERY_STRING')
235 262 log.debug('redirecting to login page with %s',p)
236 263 return redirect(url('login_home',came_from=p))
237 264
238 265 class PermsDecorator(object):
239 266 """Base class for decorators"""
240 267
241 268 def __init__(self, *required_perms):
242 269 available_perms = config['available_permissions']
243 270 for perm in required_perms:
244 271 if perm not in available_perms:
245 272 raise Exception("'%s' permission is not defined" % perm)
246 273 self.required_perms = set(required_perms)
247 274 self.user_perms = None
248 275
249 276 def __call__(self, func):
250 277 return decorator(self.__wrapper, func)
251 278
252 279
253 280 def __wrapper(self, func, *fargs, **fkwargs):
254 281 # _wrapper.__name__ = func.__name__
255 282 # _wrapper.__dict__.update(func.__dict__)
256 283 # _wrapper.__doc__ = func.__doc__
257 284
258 285 self.user_perms = session.get('hg_app_user', AuthUser()).permissions
259 286 log.debug('checking %s permissions %s for %s',
260 287 self.__class__.__name__, self.required_perms, func.__name__)
261 288
262 289 if self.check_permissions():
263 290 log.debug('Permission granted for %s', func.__name__)
264 291
265 292 return func(*fargs, **fkwargs)
266 293
267 294 else:
268 295 log.warning('Permission denied for %s', func.__name__)
269 296 #redirect with forbidden ret code
270 297 return abort(403)
271 298
272 299
273 300
274 301 def check_permissions(self):
275 302 """Dummy function for overriding"""
276 303 raise Exception('You have to write this function in child class')
277 304
278 305 class HasPermissionAllDecorator(PermsDecorator):
279 306 """Checks for access permission for all given predicates. All of them
280 307 have to be meet in order to fulfill the request
281 308 """
282 309
283 310 def check_permissions(self):
284 311 if self.required_perms.issubset(self.user_perms.get('global')):
285 312 return True
286 313 return False
287 314
288 315
289 316 class HasPermissionAnyDecorator(PermsDecorator):
290 317 """Checks for access permission for any of given predicates. In order to
291 318 fulfill the request any of predicates must be meet
292 319 """
293 320
294 321 def check_permissions(self):
295 322 if self.required_perms.intersection(self.user_perms.get('global')):
296 323 return True
297 324 return False
298 325
299 326 class HasRepoPermissionAllDecorator(PermsDecorator):
300 327 """Checks for access permission for all given predicates for specific
301 328 repository. All of them have to be meet in order to fulfill the request
302 329 """
303 330
304 331 def check_permissions(self):
305 332 repo_name = get_repo_slug(request)
306 333 try:
307 334 user_perms = set([self.user_perms['repositories'][repo_name]])
308 335 except KeyError:
309 336 return False
310 337 if self.required_perms.issubset(user_perms):
311 338 return True
312 339 return False
313 340
314 341
315 342 class HasRepoPermissionAnyDecorator(PermsDecorator):
316 343 """Checks for access permission for any of given predicates for specific
317 344 repository. In order to fulfill the request any of predicates must be meet
318 345 """
319 346
320 347 def check_permissions(self):
321 348 repo_name = get_repo_slug(request)
322 349
323 350 try:
324 351 user_perms = set([self.user_perms['repositories'][repo_name]])
325 352 except KeyError:
326 353 return False
327 354 if self.required_perms.intersection(user_perms):
328 355 return True
329 356 return False
330 357 #===============================================================================
331 358 # CHECK FUNCTIONS
332 359 #===============================================================================
333 360
334 361 class PermsFunction(object):
335 362 """Base function for other check functions"""
336 363
337 364 def __init__(self, *perms):
338 365 available_perms = config['available_permissions']
339 366
340 367 for perm in perms:
341 368 if perm not in available_perms:
342 369 raise Exception("'%s' permission in not defined" % perm)
343 370 self.required_perms = set(perms)
344 371 self.user_perms = None
345 372 self.granted_for = ''
346 373 self.repo_name = None
347 374
348 375 def __call__(self, check_Location=''):
349 376 user = session.get('hg_app_user', False)
350 377 if not user:
351 378 return False
352 379 self.user_perms = user.permissions
353 380 self.granted_for = user.username
354 381 log.debug('checking %s %s', self.__class__.__name__, self.required_perms)
355 382
356 383 if self.check_permissions():
357 384 log.debug('Permission granted for %s @%s', self.granted_for,
358 385 check_Location)
359 386 return True
360 387
361 388 else:
362 389 log.warning('Permission denied for %s @%s', self.granted_for,
363 390 check_Location)
364 391 return False
365 392
366 393 def check_permissions(self):
367 394 """Dummy function for overriding"""
368 395 raise Exception('You have to write this function in child class')
369 396
370 397 class HasPermissionAll(PermsFunction):
371 398 def check_permissions(self):
372 399 if self.required_perms.issubset(self.user_perms.get('global')):
373 400 return True
374 401 return False
375 402
376 403 class HasPermissionAny(PermsFunction):
377 404 def check_permissions(self):
378 405 if self.required_perms.intersection(self.user_perms.get('global')):
379 406 return True
380 407 return False
381 408
382 409 class HasRepoPermissionAll(PermsFunction):
383 410
384 411 def __call__(self, repo_name=None, check_Location=''):
385 412 self.repo_name = repo_name
386 413 return super(HasRepoPermissionAll, self).__call__(check_Location)
387 414
388 415 def check_permissions(self):
389 416 if not self.repo_name:
390 417 self.repo_name = get_repo_slug(request)
391 418
392 419 try:
393 420 self.user_perms = set([self.user_perms['repositories']\
394 421 [self.repo_name]])
395 422 except KeyError:
396 423 return False
397 424 self.granted_for = self.repo_name
398 425 if self.required_perms.issubset(self.user_perms):
399 426 return True
400 427 return False
401 428
402 429 class HasRepoPermissionAny(PermsFunction):
403 430
404 431 def __call__(self, repo_name=None, check_Location=''):
405 432 self.repo_name = repo_name
406 433 return super(HasRepoPermissionAny, self).__call__(check_Location)
407 434
408 435 def check_permissions(self):
409 436 if not self.repo_name:
410 437 self.repo_name = get_repo_slug(request)
411 438
412 439 try:
413 440 self.user_perms = set([self.user_perms['repositories']\
414 441 [self.repo_name]])
415 442 except KeyError:
416 443 return False
417 444 self.granted_for = self.repo_name
418 445 if self.required_perms.intersection(self.user_perms):
419 446 return True
420 447 return False
421 448
422 449 #===============================================================================
423 450 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
424 451 #===============================================================================
425 452
426 453 class HasPermissionAnyMiddleware(object):
427 454 def __init__(self, *perms):
428 455 self.required_perms = set(perms)
429 456
430 457 def __call__(self, user, repo_name):
431 458 usr = AuthUser()
432 459 usr.user_id = user.user_id
433 460 usr.username = user.username
434 461 usr.is_admin = user.admin
435 462
436 463 try:
437 464 self.user_perms = set([fill_perms(usr)\
438 465 .permissions['repositories'][repo_name]])
439 466 except:
440 467 self.user_perms = set()
441 468 self.granted_for = ''
442 469 self.username = user.username
443 470 self.repo_name = repo_name
444 471 return self.check_permissions()
445 472
446 473 def check_permissions(self):
447 474 log.debug('checking mercurial protocol '
448 475 'permissions for user:%s repository:%s',
449 476 self.username, self.repo_name)
450 477 if self.required_perms.intersection(self.user_perms):
451 478 log.debug('permission granted')
452 479 return True
453 480 log.debug('permission denied')
454 481 return False
@@ -1,263 +1,265 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # database managment for hg app
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20
21 21 """
22 22 Created on April 10, 2010
23 23 database managment and creation for hg app
24 24 @author: marcink
25 25 """
26 26
27 27 from os.path import dirname as dn, join as jn
28 28 import os
29 29 import sys
30 30 import uuid
31 31 ROOT = dn(dn(dn(os.path.realpath(__file__))))
32 32 sys.path.append(ROOT)
33 33
34 34 from pylons_app.lib.auth import get_crypt_password
35 35 from pylons_app.lib.utils import ask_ok
36 36 from pylons_app.model import init_model
37 37 from pylons_app.model.db import User, Permission, HgAppUi, HgAppSettings, \
38 38 UserToPerm
39 39 from pylons_app.model import meta
40 40 from sqlalchemy.engine import create_engine
41 41 import logging
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45 class DbManage(object):
46 46 def __init__(self, log_sql, dbname,tests=False):
47 47 self.dbname = dbname
48 48 self.tests = tests
49 49 dburi = 'sqlite:////%s' % jn(ROOT, self.dbname)
50 50 engine = create_engine(dburi, echo=log_sql)
51 51 init_model(engine)
52 52 self.sa = meta.Session
53 53 self.db_exists = False
54 54
55 55 def check_for_db(self, override):
56 56 log.info('checking for exisiting db')
57 57 if os.path.isfile(jn(ROOT, self.dbname)):
58 58 self.db_exists = True
59 59 log.info('database exisist')
60 60 if not override:
61 61 raise Exception('database already exists')
62 62
63 63 def create_tables(self, override=False):
64 64 """
65 65 Create a auth database
66 66 """
67 67 self.check_for_db(override)
68 68 if override:
69 69 log.info("database exisist and it's going to be destroyed")
70 70 if self.tests:
71 71 destroy=True
72 72 else:
73 73 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
74 74 if not destroy:
75 75 sys.exit()
76 76 if self.db_exists and destroy:
77 77 os.remove(jn(ROOT, self.dbname))
78 78 checkfirst = not override
79 79 meta.Base.metadata.create_all(checkfirst=checkfirst)
80 80 log.info('Created tables for %s', self.dbname)
81 81
82 82 def admin_prompt(self):
83 83 if not self.tests:
84 84 import getpass
85 85 username = raw_input('Specify admin username:')
86 86 password = getpass.getpass('Specify admin password:')
87 self.create_user(username, password, True)
87 email = raw_input('Specify admin email:')
88 self.create_user(username, password, email, True)
88 89 else:
89 90 log.info('creating admin and regular test users')
90 self.create_user('test_admin', 'test', True)
91 self.create_user('test_regular', 'test', False)
91 self.create_user('test_admin', 'test', 'test_admin@mail.com', True)
92 self.create_user('test_regular', 'test', 'test_regular@mail.com', False)
93 self.create_user('test_regular2', 'test', 'test_regular2@mail.com', False)
92 94
93 95
94 96
95 97 def config_prompt(self,test_repo_path=''):
96 98 log.info('Setting up repositories config')
97 99
98 100 if not self.tests and not test_repo_path:
99 101 path = raw_input('Specify valid full path to your repositories'
100 102 ' you can change this later in application settings:')
101 103 else:
102 104 path = test_repo_path
103 105
104 106 if not os.path.isdir(path):
105 107 log.error('You entered wrong path: %s',path)
106 108 sys.exit()
107 109
108 110 hooks1 = HgAppUi()
109 111 hooks1.ui_section = 'hooks'
110 112 hooks1.ui_key = 'changegroup.update'
111 113 hooks1.ui_value = 'hg update >&2'
112 114
113 115 hooks2 = HgAppUi()
114 116 hooks2.ui_section = 'hooks'
115 117 hooks2.ui_key = 'changegroup.repo_size'
116 118 hooks2.ui_value = 'python:pylons_app.lib.hooks.repo_size'
117 119
118 120 web1 = HgAppUi()
119 121 web1.ui_section = 'web'
120 122 web1.ui_key = 'push_ssl'
121 123 web1.ui_value = 'false'
122 124
123 125 web2 = HgAppUi()
124 126 web2.ui_section = 'web'
125 127 web2.ui_key = 'allow_archive'
126 128 web2.ui_value = 'gz zip bz2'
127 129
128 130 web3 = HgAppUi()
129 131 web3.ui_section = 'web'
130 132 web3.ui_key = 'allow_push'
131 133 web3.ui_value = '*'
132 134
133 135 web4 = HgAppUi()
134 136 web4.ui_section = 'web'
135 137 web4.ui_key = 'baseurl'
136 138 web4.ui_value = '/'
137 139
138 140 paths = HgAppUi()
139 141 paths.ui_section = 'paths'
140 142 paths.ui_key = '/'
141 143 paths.ui_value = os.path.join(path, '*')
142 144
143 145
144 146 hgsettings1 = HgAppSettings()
145 147
146 148 hgsettings1.app_settings_name = 'realm'
147 149 hgsettings1.app_settings_value = 'hg-app authentication'
148 150
149 151 hgsettings2 = HgAppSettings()
150 152 hgsettings2.app_settings_name = 'title'
151 153 hgsettings2.app_settings_value = 'hg-app'
152 154
153 155 try:
154 156 self.sa.add(hooks1)
155 157 self.sa.add(hooks2)
156 158 self.sa.add(web1)
157 159 self.sa.add(web2)
158 160 self.sa.add(web3)
159 161 self.sa.add(web4)
160 162 self.sa.add(paths)
161 163 self.sa.add(hgsettings1)
162 164 self.sa.add(hgsettings2)
163 165 self.sa.commit()
164 166 except:
165 167 self.sa.rollback()
166 168 raise
167 169 log.info('created ui config')
168 170
169 def create_user(self, username, password, admin=False):
171 def create_user(self, username, password, email='', admin=False):
170 172 log.info('creating administrator user %s', username)
171 173 new_user = User()
172 174 new_user.username = username
173 175 new_user.password = get_crypt_password(password)
174 176 new_user.name = 'Hg'
175 177 new_user.lastname = 'Admin'
176 new_user.email = 'admin@localhost'
178 new_user.email = email
177 179 new_user.admin = admin
178 180 new_user.active = True
179 181
180 182 try:
181 183 self.sa.add(new_user)
182 184 self.sa.commit()
183 185 except:
184 186 self.sa.rollback()
185 187 raise
186 188
187 189 def create_default_user(self):
188 190 log.info('creating default user')
189 191 #create default user for handling default permissions.
190 192 def_user = User()
191 193 def_user.username = 'default'
192 194 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
193 195 def_user.name = 'default'
194 196 def_user.lastname = 'default'
195 197 def_user.email = 'default@default.com'
196 198 def_user.admin = False
197 199 def_user.active = False
198 200 try:
199 201 self.sa.add(def_user)
200 202 self.sa.commit()
201 203 except:
202 204 self.sa.rollback()
203 205 raise
204 206
205 207 def create_permissions(self):
206 208 #module.(access|create|change|delete)_[name]
207 209 #module.(read|write|owner)
208 210 perms = [('repository.none', 'Repository no access'),
209 211 ('repository.read', 'Repository read access'),
210 212 ('repository.write', 'Repository write access'),
211 213 ('repository.admin', 'Repository admin access'),
212 214 ('hg.admin', 'Hg Administrator'),
213 215 ('hg.create.repository', 'Repository create'),
214 216 ('hg.create.none', 'Repository creation disabled'),
215 217 ('hg.register.none', 'Register disabled'),
216 218 ('hg.register.manual_activate', 'Register new user with hg-app without manual activation'),
217 219 ('hg.register.auto_activate', 'Register new user with hg-app without auto activation'),
218 220 ]
219 221
220 222 for p in perms:
221 223 new_perm = Permission()
222 224 new_perm.permission_name = p[0]
223 225 new_perm.permission_longname = p[1]
224 226 try:
225 227 self.sa.add(new_perm)
226 228 self.sa.commit()
227 229 except:
228 230 self.sa.rollback()
229 231 raise
230 232
231 233 def populate_default_permissions(self):
232 234 log.info('creating default user permissions')
233 235
234 236 default_user = self.sa.query(User)\
235 237 .filter(User.username == 'default').scalar()
236 238
237 239 reg_perm = UserToPerm()
238 240 reg_perm.user = default_user
239 241 reg_perm.permission = self.sa.query(Permission)\
240 242 .filter(Permission.permission_name == 'hg.register.manual_activate')\
241 243 .scalar()
242 244
243 245 create_repo_perm = UserToPerm()
244 246 create_repo_perm.user = default_user
245 247 create_repo_perm.permission = self.sa.query(Permission)\
246 248 .filter(Permission.permission_name == 'hg.create.repository')\
247 249 .scalar()
248 250
249 251 default_repo_perm = UserToPerm()
250 252 default_repo_perm.user = default_user
251 253 default_repo_perm.permission = self.sa.query(Permission)\
252 254 .filter(Permission.permission_name == 'repository.read')\
253 255 .scalar()
254 256
255 257 try:
256 258 self.sa.add(reg_perm)
257 259 self.sa.add(create_repo_perm)
258 260 self.sa.add(default_repo_perm)
259 261 self.sa.commit()
260 262 except:
261 263 self.sa.rollback()
262 264 raise
263 265
@@ -1,369 +1,374 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 from pygments.formatters import HtmlFormatter
7 7 from pygments import highlight as code_highlight
8 8 from pylons import url, app_globals as g
9 9 from pylons.i18n.translation import _, ungettext
10 10 from vcs.utils.annotate import annotate_highlight
11 11 from webhelpers.html import literal, HTML, escape
12 12 from webhelpers.html.tools import *
13 13 from webhelpers.html.builder import make_tag
14 14 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
15 15 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
16 16 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
17 17 password, textarea, title, ul, xml_declaration, radio
18 18 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
19 19 mail_to, strip_links, strip_tags, tag_re
20 20 from webhelpers.number import format_byte_size, format_bit_size
21 21 from webhelpers.pylonslib import Flash as _Flash
22 22 from webhelpers.pylonslib.secure_form import secure_form
23 23 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
24 24 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
25 25 replace_whitespace, urlify, truncate, wrap_paragraphs
26 26
27 27 #Custom helpers here :)
28 28 class _Link(object):
29 29 '''
30 30 Make a url based on label and url with help of url_for
31 31 @param label:name of link if not defined url is used
32 32 @param url: the url for link
33 33 '''
34 34
35 35 def __call__(self, label='', *url_, **urlargs):
36 36 if label is None or '':
37 37 label = url
38 38 link_fn = link_to(label, url(*url_, **urlargs))
39 39 return link_fn
40 40
41 41 link = _Link()
42 42
43 43 class _GetError(object):
44 44
45 45 def __call__(self, field_name, form_errors):
46 46 tmpl = """<span class="error_msg">%s</span>"""
47 47 if form_errors and form_errors.has_key(field_name):
48 48 return literal(tmpl % form_errors.get(field_name))
49 49
50 50 get_error = _GetError()
51 51
52 52 def recursive_replace(str, replace=' '):
53 53 """
54 54 Recursive replace of given sign to just one instance
55 55 @param str: given string
56 56 @param replace:char to find and replace multiple instances
57 57
58 58 Examples::
59 59 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
60 60 'Mighty-Mighty-Bo-sstones'
61 61 """
62 62
63 63 if str.find(replace * 2) == -1:
64 64 return str
65 65 else:
66 66 str = str.replace(replace * 2, replace)
67 67 return recursive_replace(str, replace)
68 68
69 69 class _ToolTip(object):
70 70
71 71 def __call__(self, tooltip_title, trim_at=50):
72 72 """
73 73 Special function just to wrap our text into nice formatted autowrapped
74 74 text
75 75 @param tooltip_title:
76 76 """
77 77
78 78 return wrap_paragraphs(escape(tooltip_title), trim_at)\
79 79 .replace('\n', '<br/>')
80 80
81 81 def activate(self):
82 82 """
83 83 Adds tooltip mechanism to the given Html all tooltips have to have
84 84 set class tooltip and set attribute tooltip_title.
85 85 Then a tooltip will be generated based on that
86 86 All with yui js tooltip
87 87 """
88 88
89 89 js = '''
90 90 YAHOO.util.Event.onDOMReady(function(){
91 91 function toolTipsId(){
92 92 var ids = [];
93 93 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
94 94
95 95 for (var i = 0; i < tts.length; i++) {
96 96 //if element doesn not have and id autgenerate one for tooltip
97 97
98 98 if (!tts[i].id){
99 99 tts[i].id='tt'+i*100;
100 100 }
101 101 ids.push(tts[i].id);
102 102 }
103 103 return ids
104 104 };
105 105 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
106 106 context: toolTipsId(),
107 107 monitorresize:false,
108 108 xyoffset :[0,0],
109 109 autodismissdelay:300000,
110 110 hidedelay:5,
111 111 showdelay:20,
112 112 });
113 113
114 114 //Mouse Over event disabled for new repositories since they dont
115 115 //have last commit message
116 116 myToolTips.contextMouseOverEvent.subscribe(
117 117 function(type, args) {
118 118 var context = args[0];
119 119 var txt = context.getAttribute('tooltip_title');
120 120 if(txt){
121 121 return true;
122 122 }
123 123 else{
124 124 return false;
125 125 }
126 126 });
127 127
128 128
129 129 // Set the text for the tooltip just before we display it. Lazy method
130 130 myToolTips.contextTriggerEvent.subscribe(
131 131 function(type, args) {
132 132
133 133
134 134 var context = args[0];
135 135
136 136 var txt = context.getAttribute('tooltip_title');
137 137 this.cfg.setProperty("text", txt);
138 138
139 139
140 140 // positioning of tooltip
141 141 var tt_w = this.element.clientWidth;
142 142 var tt_h = this.element.clientHeight;
143 143
144 144 var context_w = context.offsetWidth;
145 145 var context_h = context.offsetHeight;
146 146
147 147 var pos_x = YAHOO.util.Dom.getX(context);
148 148 var pos_y = YAHOO.util.Dom.getY(context);
149 149
150 150 var display_strategy = 'top';
151 151 var xy_pos = [0,0];
152 152 switch (display_strategy){
153 153
154 154 case 'top':
155 155 var cur_x = (pos_x+context_w/2)-(tt_w/2);
156 156 var cur_y = pos_y-tt_h-4;
157 157 xy_pos = [cur_x,cur_y];
158 158 break;
159 159 case 'bottom':
160 160 var cur_x = (pos_x+context_w/2)-(tt_w/2);
161 161 var cur_y = pos_y+context_h+4;
162 162 xy_pos = [cur_x,cur_y];
163 163 break;
164 164 case 'left':
165 165 var cur_x = (pos_x-tt_w-4);
166 166 var cur_y = pos_y-((tt_h/2)-context_h/2);
167 167 xy_pos = [cur_x,cur_y];
168 168 break;
169 169 case 'right':
170 170 var cur_x = (pos_x+context_w+4);
171 171 var cur_y = pos_y-((tt_h/2)-context_h/2);
172 172 xy_pos = [cur_x,cur_y];
173 173 break;
174 174 default:
175 175 var cur_x = (pos_x+context_w/2)-(tt_w/2);
176 176 var cur_y = pos_y-tt_h-4;
177 177 xy_pos = [cur_x,cur_y];
178 178 break;
179 179
180 180 }
181 181
182 182 this.cfg.setProperty("xy",xy_pos);
183 183
184 184 });
185 185
186 186 //Mouse out
187 187 myToolTips.contextMouseOutEvent.subscribe(
188 188 function(type, args) {
189 189 var context = args[0];
190 190
191 191 });
192 192 });
193 193 '''
194 194 return literal(js)
195 195
196 196 tooltip = _ToolTip()
197 197
198 198 class _FilesBreadCrumbs(object):
199 199
200 200 def __call__(self, repo_name, rev, paths):
201 201 url_l = [link_to(repo_name, url('files_home',
202 202 repo_name=repo_name,
203 203 revision=rev, f_path=''))]
204 204 paths_l = paths.split('/')
205 205
206 206 for cnt, p in enumerate(paths_l, 1):
207 207 if p != '':
208 208 url_l.append(link_to(p, url('files_home',
209 209 repo_name=repo_name,
210 210 revision=rev,
211 211 f_path='/'.join(paths_l[:cnt]))))
212 212
213 213 return literal('/'.join(url_l))
214 214
215 215 files_breadcrumbs = _FilesBreadCrumbs()
216 216
217 217 def pygmentize(filenode, **kwargs):
218 218 """
219 219 pygmentize function using pygments
220 220 @param filenode:
221 221 """
222 222 return literal(code_highlight(filenode.content,
223 223 filenode.lexer, HtmlFormatter(**kwargs)))
224 224
225 225 def pygmentize_annotation(filenode, **kwargs):
226 226 """
227 227 pygmentize function for annotation
228 228 @param filenode:
229 229 """
230 230
231 231 color_dict = {}
232 232 def gen_color():
233 233 """generator for getting 10k of evenly distibuted colors using hsv color
234 234 and golden ratio.
235 235 """
236 236 import colorsys
237 237 n = 10000
238 238 golden_ratio = 0.618033988749895
239 239 h = 0.22717784590367374
240 240 #generate 10k nice web friendly colors in the same order
241 241 for c in xrange(n):
242 242 h += golden_ratio
243 243 h %= 1
244 244 HSV_tuple = [h, 0.95, 0.95]
245 245 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
246 246 yield map(lambda x:str(int(x * 256)), RGB_tuple)
247 247
248 248 cgenerator = gen_color()
249 249
250 250 def get_color_string(cs):
251 251 if color_dict.has_key(cs):
252 252 col = color_dict[cs]
253 253 else:
254 254 col = color_dict[cs] = cgenerator.next()
255 255 return "color: rgb(%s)! important;" % (', '.join(col))
256 256
257 257 def url_func(changeset):
258 258 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
259 259 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
260 260
261 261 tooltip_html = tooltip_html % (changeset.author,
262 262 changeset.date,
263 263 tooltip(changeset.message))
264 264 lnk_format = 'r%-5s:%s' % (changeset.revision,
265 265 changeset.raw_id)
266 266 uri = link_to(
267 267 lnk_format,
268 268 url('changeset_home', repo_name=changeset.repository.name,
269 269 revision=changeset.raw_id),
270 270 style=get_color_string(changeset.raw_id),
271 271 class_='tooltip',
272 272 tooltip_title=tooltip_html
273 273 )
274 274
275 275 uri += '\n'
276 276 return uri
277 277 return literal(annotate_highlight(filenode, url_func, **kwargs))
278 278
279 279 def repo_name_slug(value):
280 """Return slug of name of repository
281 This function is called on each creation/modification
282 of repository to prevent bad names in repo
280 283 """
281 Return slug of name of repository
282 """
283 slug = urlify(value)
284 slug = remove_formatting(value)
285 slug = strip_tags(slug)
286
284 287 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|:""":
285 288 slug = slug.replace(c, '-')
286 289 slug = recursive_replace(slug, '-')
290 slug = collapse(slug, '-')
287 291 return slug
288 292
289 293 def get_changeset_safe(repo, rev):
290 294 from vcs.backends.base import BaseRepository
291 295 from vcs.exceptions import RepositoryError
292 296 if not isinstance(repo, BaseRepository):
293 297 raise Exception('You must pass an Repository '
294 298 'object as first argument got %s', type(repo))
295 299
296 300 try:
297 301 cs = repo.get_changeset(rev)
298 302 except RepositoryError:
299 303 from pylons_app.lib.utils import EmptyChangeset
300 304 cs = EmptyChangeset()
301 305 return cs
302 306
303 307
304 308 flash = _Flash()
305 309
306 310
307 311 #===============================================================================
308 312 # MERCURIAL FILTERS available via h.
309 313 #===============================================================================
310 314 from mercurial import util
311 315 from mercurial.templatefilters import age as _age, person as _person
312 316
313 317 age = lambda x:_age(x)
314 318 capitalize = lambda x: x.capitalize()
315 319 date = lambda x: util.datestr(x)
316 320 email = util.email
317 321 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
318 322 person = lambda x: _person(x)
319 323 hgdate = lambda x: "%d %d" % x
320 324 isodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2')
321 325 isodatesec = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2')
322 326 localdate = lambda x: (x[0], util.makedate()[1])
323 327 rfc822date = lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2")
328 rfc822date_notz = lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S")
324 329 rfc3339date = lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2")
325 330 time_ago = lambda x: util.datestr(_age(x), "%a, %d %b %Y %H:%M:%S %1%2")
326 331
327 332
328 333 #===============================================================================
329 334 # PERMS
330 335 #===============================================================================
331 336 from pylons_app.lib.auth import HasPermissionAny, HasPermissionAll, \
332 337 HasRepoPermissionAny, HasRepoPermissionAll
333 338
334 339 #===============================================================================
335 340 # GRAVATAR URL
336 341 #===============================================================================
337 342 import hashlib
338 343 import urllib
339 344 from pylons import request
340 345
341 346 def gravatar_url(email_address, size=30):
342 347 ssl_enabled = 'https' == request.environ.get('HTTP_X_URL_SCHEME')
343 348 default = 'identicon'
344 349 baseurl_nossl = "http://www.gravatar.com/avatar/"
345 350 baseurl_ssl = "https://secure.gravatar.com/avatar/"
346 351 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
347 352
348 353
349 354 # construct the url
350 355 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
351 356 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
352 357
353 358 return gravatar_url
354 359
355 360 def safe_unicode(str):
356 361 """safe unicode function. In case of UnicodeDecode error we try to return
357 362 unicode with errors replace, if this failes we return unicode with
358 363 string_escape decoding """
359 364
360 365 try:
361 366 u_str = unicode(str)
362 367 except UnicodeDecodeError:
363 368 try:
364 369 u_str = unicode(str, 'utf-8', 'replace')
365 370 except UnicodeDecodeError:
366 371 #incase we have a decode error just represent as byte string
367 372 u_str = unicode(str(str).encode('string_escape'))
368 373
369 374 return u_str
@@ -1,41 +1,139 b''
1 import sys
1 from os.path import dirname as dn, join as jn
2 from pylons_app.config.environment import load_environment
3 from pylons_app.model.hg_model import HgModel
4 from shutil import rmtree
5 from webhelpers.html.builder import escape
6 from vcs.utils.lazy import LazyProperty
7
8 from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter
9 from whoosh.fields import TEXT, ID, STORED, Schema, FieldType
10 from whoosh.index import create_in, open_dir
11 from whoosh.formats import Characters
12 from whoosh.highlight import highlight, SimpleFragmenter, HtmlFormatter
13
2 14 import os
3 from pidlock import LockHeld, DaemonLock
15 import sys
4 16 import traceback
5 17
6 from os.path import dirname as dn
7 from os.path import join as jn
8
9 18 #to get the pylons_app import
10 19 sys.path.append(dn(dn(dn(os.path.realpath(__file__)))))
11 20
12 from pylons_app.config.environment import load_environment
13 from pylons_app.model.hg_model import HgModel
14 from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter
15 from whoosh.fields import TEXT, ID, STORED, Schema
16 from whoosh.index import create_in, open_dir
17 from shutil import rmtree
18 21
19 22 #LOCATION WE KEEP THE INDEX
20 23 IDX_LOCATION = jn(dn(dn(dn(dn(os.path.abspath(__file__))))), 'data', 'index')
21 24
22 25 #EXTENSIONS WE WANT TO INDEX CONTENT OFF
23 26 INDEX_EXTENSIONS = ['action', 'adp', 'ashx', 'asmx', 'aspx', 'asx', 'axd', 'c',
24 'cfm', 'cpp', 'cs', 'css', 'diff', 'do', 'el', 'erl', 'h',
25 'htm', 'html', 'ini', 'java', 'js', 'jsp', 'jspx', 'lisp',
27 'cfg', 'cfm', 'cpp', 'cs', 'css', 'diff', 'do', 'el', 'erl',
28 'h', 'htm', 'html', 'ini', 'java', 'js', 'jsp', 'jspx', 'lisp',
26 29 'lua', 'm', 'mako', 'ml', 'pas', 'patch', 'php', 'php3',
27 30 'php4', 'phtml', 'pm', 'py', 'rb', 'rst', 's', 'sh', 'sql',
28 31 'tpl', 'txt', 'vim', 'wss', 'xhtml', 'xml','xsl','xslt',
29 32 'yaws']
30 33
31 34 #CUSTOM ANALYZER wordsplit + lowercase filter
32 35 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
33 36
37
34 38 #INDEX SCHEMA DEFINITION
35 39 SCHEMA = Schema(owner=TEXT(),
36 40 repository=TEXT(stored=True),
37 41 path=ID(stored=True, unique=True),
38 content=TEXT(stored=True, analyzer=ANALYZER),
42 content=FieldType(format=Characters(ANALYZER),
43 scorable=True, stored=True),
39 44 modtime=STORED(),extension=TEXT(stored=True))
40 45
41 IDX_NAME = 'HG_INDEX' No newline at end of file
46
47 IDX_NAME = 'HG_INDEX'
48 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
49 FRAGMENTER = SimpleFragmenter(200)
50
51 class ResultWrapper(object):
52 def __init__(self, searcher, matcher, highlight_items):
53 self.searcher = searcher
54 self.matcher = matcher
55 self.highlight_items = highlight_items
56 self.fragment_size = 200 / 2
57
58 @LazyProperty
59 def doc_ids(self):
60 docs_id = []
61 while self.matcher.is_active():
62 docnum = self.matcher.id()
63 chunks = [offsets for offsets in self.get_chunks()]
64 docs_id.append([docnum, chunks])
65 self.matcher.next()
66 return docs_id
67
68 def __str__(self):
69 return '<%s at %s>' % (self.__class__.__name__, len(self.doc_ids))
70
71 def __repr__(self):
72 return self.__str__()
73
74 def __len__(self):
75 return len(self.doc_ids)
76
77 def __iter__(self):
78 """
79 Allows Iteration over results,and lazy generate content
80
81 *Requires* implementation of ``__getitem__`` method.
82 """
83 for docid in self.doc_ids:
84 yield self.get_full_content(docid)
85
86 def __getslice__(self, i, j):
87 """
88 Slicing of resultWrapper
89 """
90 slice = []
91 for docid in self.doc_ids[i:j]:
92 slice.append(self.get_full_content(docid))
93 return slice
94
95
96 def get_full_content(self, docid):
97 res = self.searcher.stored_fields(docid[0])
98 f_path = res['path'][res['path'].find(res['repository']) \
99 + len(res['repository']):].lstrip('/')
100
101 content_short = self.get_short_content(res, docid[1])
102 res.update({'content_short':content_short,
103 'content_short_hl':self.highlight(content_short),
104 'f_path':f_path})
105
106 return res
107
108 def get_short_content(self, res, chunks):
109
110 return ''.join([res['content'][chunk[0]:chunk[1]] for chunk in chunks])
111
112 def get_chunks(self):
113 """
114 Smart function that implements chunking the content
115 but not overlap chunks so it doesn't highlight the same
116 close occurences twice.
117 @param matcher:
118 @param size:
119 """
120 memory = [(0, 0)]
121 for span in self.matcher.spans():
122 start = span.startchar or 0
123 end = span.endchar or 0
124 start_offseted = max(0, start - self.fragment_size)
125 end_offseted = end + self.fragment_size
126
127 if start_offseted < memory[-1][1]:
128 start_offseted = memory[-1][1]
129 memory.append((start_offseted, end_offseted,))
130 yield (start_offseted, end_offseted,)
131
132 def highlight(self, content, top=5):
133 hl = highlight(escape(content),
134 self.highlight_items,
135 analyzer=ANALYZER,
136 fragmenter=FRAGMENTER,
137 formatter=FORMATTER,
138 top=top)
139 return hl
@@ -1,226 +1,238 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # whoosh indexer daemon for hg-app
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on Jan 26, 2010
22 22
23 23 @author: marcink
24 24 A deamon will read from task table and run tasks
25 25 """
26 26 import sys
27 27 import os
28 28 from os.path import dirname as dn
29 29 from os.path import join as jn
30 30
31 31 #to get the pylons_app import
32 32 project_path = dn(dn(dn(dn(os.path.realpath(__file__)))))
33 33 sys.path.append(project_path)
34 34
35 from pidlock import LockHeld, DaemonLock
36 import traceback
37 from pylons_app.config.environment import load_environment
35 from pylons_app.lib.pidlock import LockHeld, DaemonLock
38 36 from pylons_app.model.hg_model import HgModel
39 37 from pylons_app.lib.helpers import safe_unicode
40 38 from whoosh.index import create_in, open_dir
41 39 from shutil import rmtree
42 from pylons_app.lib.indexers import ANALYZER, INDEX_EXTENSIONS, IDX_LOCATION, \
43 SCHEMA, IDX_NAME
40 from pylons_app.lib.indexers import INDEX_EXTENSIONS, IDX_LOCATION, SCHEMA, IDX_NAME
44 41
45 42 import logging
46 import logging.config
47 logging.config.fileConfig(jn(project_path, 'development.ini'))
43
48 44 log = logging.getLogger('whooshIndexer')
45 # create logger
46 log.setLevel(logging.DEBUG)
47 log.propagate = False
48 # create console handler and set level to debug
49 ch = logging.StreamHandler()
50 ch.setLevel(logging.DEBUG)
51
52 # create formatter
53 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
54
55 # add formatter to ch
56 ch.setFormatter(formatter)
57
58 # add ch to logger
59 log.addHandler(ch)
49 60
50 61 def scan_paths(root_location):
51 62 return HgModel.repo_scan('/', root_location, None, True)
52 63
53 64 class WhooshIndexingDaemon(object):
54 65 """Deamon for atomic jobs"""
55 66
56 67 def __init__(self, indexname='HG_INDEX', repo_location=None):
57 68 self.indexname = indexname
58 69 self.repo_location = repo_location
59 70 self.initial = False
60 71 if not os.path.isdir(IDX_LOCATION):
61 72 os.mkdir(IDX_LOCATION)
62 73 log.info('Cannot run incremental index since it does not'
63 74 ' yet exist running full build')
64 75 self.initial = True
65 76
66 77 def get_paths(self, root_dir):
67 78 """recursive walk in root dir and return a set of all path in that dir
68 79 excluding files in .hg dir"""
69 80 index_paths_ = set()
70 81 for path, dirs, files in os.walk(root_dir):
71 82 if path.find('.hg') == -1:
72 83 for f in files:
73 84 index_paths_.add(jn(path, f))
74 85
75 86 return index_paths_
76 87
77 88 def add_doc(self, writer, path, repo):
78 89 """Adding doc to writer"""
79 90
80 91 ext = unicode(path.split('/')[-1].split('.')[-1].lower())
81 92 #we just index the content of choosen files
82 93 if ext in INDEX_EXTENSIONS:
83 94 log.debug(' >> %s [WITH CONTENT]' % path)
84 95 fobj = open(path, 'rb')
85 96 content = fobj.read()
86 97 fobj.close()
87 98 u_content = safe_unicode(content)
88 99 else:
89 100 log.debug(' >> %s' % path)
90 101 #just index file name without it's content
91 102 u_content = u''
92 103
93 104
94 105
95 106 try:
96 107 os.stat(path)
97 108 writer.add_document(owner=unicode(repo.contact),
98 109 repository=u"%s" % repo.name,
99 110 path=u"%s" % path,
100 111 content=u_content,
101 112 modtime=os.path.getmtime(path),
102 113 extension=ext)
103 114 except OSError, e:
104 115 import errno
105 116 if e.errno == errno.ENOENT:
106 117 log.debug('path %s does not exist or is a broken symlink' % path)
107 118 else:
108 119 raise e
109 120
110 121
111 122 def build_index(self):
112 123 if os.path.exists(IDX_LOCATION):
113 124 log.debug('removing previos index')
114 125 rmtree(IDX_LOCATION)
115 126
116 127 if not os.path.exists(IDX_LOCATION):
117 128 os.mkdir(IDX_LOCATION)
118 129
119 130 idx = create_in(IDX_LOCATION, SCHEMA, indexname=IDX_NAME)
120 131 writer = idx.writer()
121 132
122 133 for cnt, repo in enumerate(scan_paths(self.repo_location).values()):
123 134 log.debug('building index @ %s' % repo.path)
124 135
125 136 for idx_path in self.get_paths(repo.path):
126 137 self.add_doc(writer, idx_path, repo)
127 138 writer.commit(merge=True)
128 139
129 140 log.debug('>>> FINISHED BUILDING INDEX <<<')
130 141
131 142
132 143 def update_index(self):
133 144 log.debug('STARTING INCREMENTAL INDEXING UPDATE')
134 145
135 146 idx = open_dir(IDX_LOCATION, indexname=self.indexname)
136 147 # The set of all paths in the index
137 148 indexed_paths = set()
138 149 # The set of all paths we need to re-index
139 150 to_index = set()
140 151
141 152 reader = idx.reader()
142 153 writer = idx.writer()
143 154
144 155 # Loop over the stored fields in the index
145 156 for fields in reader.all_stored_fields():
146 157 indexed_path = fields['path']
147 158 indexed_paths.add(indexed_path)
148 159
149 160 if not os.path.exists(indexed_path):
150 161 # This file was deleted since it was indexed
151 162 log.debug('removing from index %s' % indexed_path)
152 163 writer.delete_by_term('path', indexed_path)
153 164
154 165 else:
155 166 # Check if this file was changed since it
156 167 # was indexed
157 168 indexed_time = fields['modtime']
158 169
159 170 mtime = os.path.getmtime(indexed_path)
160 171
161 172 if mtime > indexed_time:
162 173
163 174 # The file has changed, delete it and add it to the list of
164 175 # files to reindex
165 176 log.debug('adding to reindex list %s' % indexed_path)
166 177 writer.delete_by_term('path', indexed_path)
167 178 to_index.add(indexed_path)
168 179 #writer.commit()
169 180
170 181 # Loop over the files in the filesystem
171 182 # Assume we have a function that gathers the filenames of the
172 183 # documents to be indexed
173 184 for repo in scan_paths(self.repo_location).values():
174 185 for path in self.get_paths(repo.path):
175 186 if path in to_index or path not in indexed_paths:
176 187 # This is either a file that's changed, or a new file
177 188 # that wasn't indexed before. So index it!
178 189 self.add_doc(writer, path, repo)
179 190 log.debug('reindexing %s' % path)
180 191
181 192 writer.commit(merge=True)
182 193 #idx.optimize()
183 194 log.debug('>>> FINISHED <<<')
184 195
185 196 def run(self, full_index=False):
186 197 """Run daemon"""
187 198 if full_index or self.initial:
188 199 self.build_index()
189 200 else:
190 201 self.update_index()
191 202
192 203 if __name__ == "__main__":
193 204 arg = sys.argv[1:]
194 205 if len(arg) != 2:
195 206 sys.stderr.write('Please specify indexing type [full|incremental]'
196 207 'and path to repositories as script args \n')
197 208 sys.exit()
198 209
199 210
200 211 if arg[0] == 'full':
201 212 full_index = True
202 213 elif arg[0] == 'incremental':
203 214 # False means looking just for changes
204 215 full_index = False
205 216 else:
206 217 sys.stdout.write('Please use [full|incremental]'
207 218 ' as script first arg \n')
208 219 sys.exit()
209 220
210 221 if not os.path.isdir(arg[1]):
211 222 sys.stderr.write('%s is not a valid path \n' % arg[1])
212 223 sys.exit()
213 224 else:
214 225 if arg[1].endswith('/'):
215 226 repo_location = arg[1] + '*'
216 227 else:
217 228 repo_location = arg[1] + '/*'
218 229
219 230 try:
220 231 l = DaemonLock()
221 232 WhooshIndexingDaemon(repo_location=repo_location)\
222 233 .run(full_index=full_index)
223 234 l.release()
235 reload(logging)
224 236 except LockHeld:
225 237 sys.exit(1)
226 238
@@ -1,127 +1,127 b''
1 1 import os, time
2 2 import sys
3 3 from warnings import warn
4 4
5 5 class LockHeld(Exception):pass
6 6
7 7
8 8 class DaemonLock(object):
9 '''daemon locking
9 """daemon locking
10 10 USAGE:
11 11 try:
12 12 l = lock()
13 13 main()
14 14 l.release()
15 15 except LockHeld:
16 16 sys.exit(1)
17 '''
17 """
18 18
19 19 def __init__(self, file=None, callbackfn=None,
20 20 desc='daemon lock', debug=False):
21 21
22 22 self.pidfile = file if file else os.path.join(os.path.dirname(__file__),
23 23 'running.lock')
24 24 self.callbackfn = callbackfn
25 25 self.desc = desc
26 26 self.debug = debug
27 27 self.held = False
28 28 #run the lock automatically !
29 29 self.lock()
30 30
31 31 def __del__(self):
32 32 if self.held:
33 33
34 34 # warn("use lock.release instead of del lock",
35 35 # category = DeprecationWarning,
36 36 # stacklevel = 2)
37 37
38 38 # ensure the lock will be removed
39 39 self.release()
40 40
41 41
42 42 def lock(self):
43 '''
43 """
44 44 locking function, if lock is present it will raise LockHeld exception
45 '''
45 """
46 46 lockname = '%s' % (os.getpid())
47 47
48 48 self.trylock()
49 49 self.makelock(lockname, self.pidfile)
50 50 return True
51 51
52 52 def trylock(self):
53 53 running_pid = False
54 54 try:
55 55 pidfile = open(self.pidfile, "r")
56 56 pidfile.seek(0)
57 57 running_pid = pidfile.readline()
58 58 if self.debug:
59 59 print 'lock file present running_pid: %s, checking for execution'\
60 60 % running_pid
61 61 # Now we check the PID from lock file matches to the current
62 62 # process PID
63 63 if running_pid:
64 64 if os.path.exists("/proc/%s" % running_pid):
65 65 print "You already have an instance of the program running"
66 66 print "It is running as process %s" % running_pid
67 67 raise LockHeld
68 68 else:
69 69 print "Lock File is there but the program is not running"
70 70 print "Removing lock file for the: %s" % running_pid
71 71 self.release()
72 72 except IOError, e:
73 73 if e.errno != 2:
74 74 raise
75 75
76 76
77 77 def release(self):
78 '''
78 """
79 79 releases the pid by removing the pidfile
80 '''
80 """
81 81 if self.callbackfn:
82 82 #execute callback function on release
83 83 if self.debug:
84 84 print 'executing callback function %s' % self.callbackfn
85 85 self.callbackfn()
86 86 try:
87 87 if self.debug:
88 88 print 'removing pidfile %s' % self.pidfile
89 89 os.remove(self.pidfile)
90 90 self.held = False
91 91 except OSError, e:
92 92 if self.debug:
93 93 print 'removing pidfile failed %s' % e
94 94 pass
95 95
96 96 def makelock(self, lockname, pidfile):
97 '''
97 """
98 98 this function will make an actual lock
99 99 @param lockname: acctual pid of file
100 100 @param pidfile: the file to write the pid in
101 '''
101 """
102 102 if self.debug:
103 103 print 'creating a file %s and pid: %s' % (pidfile, lockname)
104 104 pidfile = open(self.pidfile, "wb")
105 105 pidfile.write(lockname)
106 106 pidfile.close
107 107 self.held = True
108 108
109 109
110 110 def main():
111 111 print 'func is running'
112 112 cnt = 20
113 113 while 1:
114 114 print cnt
115 115 if cnt == 0:
116 116 break
117 117 time.sleep(1)
118 118 cnt -= 1
119 119
120 120
121 121 if __name__ == "__main__":
122 122 try:
123 123 l = DaemonLock(desc='test lock')
124 124 main()
125 125 l.release()
126 126 except LockHeld:
127 127 sys.exit(1)
@@ -1,57 +1,59 b''
1 1 from sqlalchemy.interfaces import ConnectionProxy
2 2 import time
3 import logging
4 log = logging.getLogger('timerproxy')
3 from sqlalchemy import log
5 4 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
6 5
7 6 def color_sql(sql):
8 7 COLOR_SEQ = "\033[1;%dm"
9 8 COLOR_SQL = YELLOW
10 9 normal = '\x1b[0m'
11 10 return COLOR_SEQ % COLOR_SQL + sql + normal
12 11
13 12 def one_space_trim(s):
14 13 if s.find(" ") == -1:
15 14 return s
16 15 else:
17 16 s = s.replace(' ', ' ')
18 17 return one_space_trim(s)
19 18
20 19 def format_sql(sql):
21 20 sql = color_sql(sql)
22 21 sql = sql.replace('\n', '')
23 22 sql = one_space_trim(sql)
24 23 sql = sql\
25 24 .replace(',',',\n\t')\
26 25 .replace('SELECT', '\n\tSELECT \n\t')\
27 26 .replace('UPDATE', '\n\tUPDATE \n\t')\
28 27 .replace('DELETE', '\n\tDELETE \n\t')\
29 28 .replace('FROM', '\n\tFROM')\
30 29 .replace('ORDER BY', '\n\tORDER BY')\
31 30 .replace('LIMIT', '\n\tLIMIT')\
32 31 .replace('WHERE', '\n\tWHERE')\
33 32 .replace('AND', '\n\tAND')\
34 33 .replace('LEFT', '\n\tLEFT')\
35 34 .replace('INNER', '\n\tINNER')\
36 35 .replace('INSERT', '\n\tINSERT')\
37 36 .replace('DELETE', '\n\tDELETE')
38 37 return sql
39 38
40 39
41 40 class TimerProxy(ConnectionProxy):
41
42 def __init__(self):
43 super(TimerProxy, self).__init__()
44 self.logging_name = 'timerProxy'
45 self.log = log.instance_logger(self, True)
46
42 47 def cursor_execute(self, execute, cursor, statement, parameters, context, executemany):
48
43 49 now = time.time()
44 50 try:
45 log.info(">>>>> STARTING QUERY >>>>>")
51 self.log.info(">>>>> STARTING QUERY >>>>>")
46 52 return execute(cursor, statement, parameters, context)
47 53 finally:
48 54 total = time.time() - now
49 55 try:
50 log.info(format_sql("Query: %s" % statement % parameters))
56 self.log.info(format_sql("Query: %s" % statement % parameters))
51 57 except TypeError:
52 log.info(format_sql("Query: %s %s" % (statement, parameters)))
53 log.info("<<<<< TOTAL TIME: %f <<<<<" % total)
54
55
56
57
58 self.log.info(format_sql("Query: %s %s" % (statement, parameters)))
59 self.log.info("<<<<< TOTAL TIME: %f <<<<<" % total)
@@ -1,364 +1,438 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Utilities for hg app
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; version 2
8 8 # of the License or (at your opinion) any later version of the license.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 18 # MA 02110-1301, USA.
19 19
20 20 """
21 21 Created on April 18, 2010
22 22 Utilities for hg app
23 23 @author: marcink
24 24 """
25 25 from beaker.cache import cache_region
26 26 from mercurial import ui, config, hg
27 27 from mercurial.error import RepoError
28 28 from pylons_app.model import meta
29 29 from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings
30 30 from vcs.backends.base import BaseChangeset
31 31 from vcs.utils.lazy import LazyProperty
32 32 import logging
33 33 import os
34
34 35 log = logging.getLogger(__name__)
35 36
36 37
37 38 def get_repo_slug(request):
38 39 return request.environ['pylons.routes_dict'].get('repo_name')
39 40
40 41 def is_mercurial(environ):
41 42 """
42 43 Returns True if request's target is mercurial server - header
43 44 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
44 45 """
45 46 http_accept = environ.get('HTTP_ACCEPT')
46 47 if http_accept and http_accept.startswith('application/mercurial'):
47 48 return True
48 49 return False
49 50
50 51 def check_repo_dir(paths):
51 52 repos_path = paths[0][1].split('/')
52 53 if repos_path[-1] in ['*', '**']:
53 54 repos_path = repos_path[:-1]
54 55 if repos_path[0] != '/':
55 56 repos_path[0] = '/'
56 57 if not os.path.isdir(os.path.join(*repos_path)):
57 58 raise Exception('Not a valid repository in %s' % paths[0][1])
58 59
59 60 def check_repo_fast(repo_name, base_path):
60 61 if os.path.isdir(os.path.join(base_path, repo_name)):return False
61 62 return True
62 63
63 64 def check_repo(repo_name, base_path, verify=True):
64 65
65 66 repo_path = os.path.join(base_path, repo_name)
66 67
67 68 try:
68 69 if not check_repo_fast(repo_name, base_path):
69 70 return False
70 71 r = hg.repository(ui.ui(), repo_path)
71 72 if verify:
72 73 hg.verify(r)
73 74 #here we hnow that repo exists it was verified
74 75 log.info('%s repo is already created', repo_name)
75 76 return False
76 77 except RepoError:
77 78 #it means that there is no valid repo there...
78 79 log.info('%s repo is free for creation', repo_name)
79 80 return True
80 81
81 82 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
82 83 while True:
83 84 ok = raw_input(prompt)
84 85 if ok in ('y', 'ye', 'yes'): return True
85 86 if ok in ('n', 'no', 'nop', 'nope'): return False
86 87 retries = retries - 1
87 88 if retries < 0: raise IOError
88 89 print complaint
89 90
90 91 @cache_region('super_short_term', 'cached_hg_ui')
91 92 def get_hg_ui_cached():
92 93 try:
93 94 sa = meta.Session
94 95 ret = sa.query(HgAppUi).all()
95 96 finally:
96 97 meta.Session.remove()
97 98 return ret
98 99
99 100
100 101 def get_hg_settings():
101 102 try:
102 103 sa = meta.Session
103 104 ret = sa.query(HgAppSettings).all()
104 105 finally:
105 106 meta.Session.remove()
106 107
107 108 if not ret:
108 109 raise Exception('Could not get application settings !')
109 110 settings = {}
110 111 for each in ret:
111 112 settings['hg_app_' + each.app_settings_name] = each.app_settings_value
112 113
113 114 return settings
114 115
115 116 def get_hg_ui_settings():
116 117 try:
117 118 sa = meta.Session
118 119 ret = sa.query(HgAppUi).all()
119 120 finally:
120 121 meta.Session.remove()
121 122
122 123 if not ret:
123 124 raise Exception('Could not get application ui settings !')
124 125 settings = {}
125 126 for each in ret:
126 127 k = each.ui_key
127 128 v = each.ui_value
128 129 if k == '/':
129 130 k = 'root_path'
130 131
131 132 if k.find('.') != -1:
132 133 k = k.replace('.', '_')
133 134
134 135 if each.ui_section == 'hooks':
135 136 v = each.ui_active
136 137
137 138 settings[each.ui_section + '_' + k] = v
138 139
139 140 return settings
140 141
141 142 #propagated from mercurial documentation
142 143 ui_sections = ['alias', 'auth',
143 144 'decode/encode', 'defaults',
144 145 'diff', 'email',
145 146 'extensions', 'format',
146 147 'merge-patterns', 'merge-tools',
147 148 'hooks', 'http_proxy',
148 149 'smtp', 'patch',
149 150 'paths', 'profiling',
150 151 'server', 'trusted',
151 152 'ui', 'web', ]
152 153
153 154 def make_ui(read_from='file', path=None, checkpaths=True):
154 155 """
155 156 A function that will read python rc files or database
156 157 and make an mercurial ui object from read options
157 158
158 159 @param path: path to mercurial config file
159 160 @param checkpaths: check the path
160 161 @param read_from: read from 'file' or 'db'
161 162 """
162 163
163 164 baseui = ui.ui()
164 165
165 166 if read_from == 'file':
166 167 if not os.path.isfile(path):
167 168 log.warning('Unable to read config file %s' % path)
168 169 return False
169 170 log.debug('reading hgrc from %s', path)
170 171 cfg = config.config()
171 172 cfg.read(path)
172 173 for section in ui_sections:
173 174 for k, v in cfg.items(section):
174 175 baseui.setconfig(section, k, v)
175 176 log.debug('settings ui from file[%s]%s:%s', section, k, v)
176 177 if checkpaths:check_repo_dir(cfg.items('paths'))
177 178
178 179
179 180 elif read_from == 'db':
180 181 hg_ui = get_hg_ui_cached()
181 182 for ui_ in hg_ui:
182 183 if ui_.ui_active:
183 184 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
184 185 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
185 186
186 187
187 188 return baseui
188 189
189 190
190 191 def set_hg_app_config(config):
191 192 hgsettings = get_hg_settings()
192 193
193 194 for k, v in hgsettings.items():
194 195 config[k] = v
195 196
196 197 def invalidate_cache(name, *args):
197 198 """Invalidates given name cache"""
198 199
199 200 from beaker.cache import region_invalidate
200 201 log.info('INVALIDATING CACHE FOR %s', name)
201 202
202 203 """propagate our arguments to make sure invalidation works. First
203 204 argument has to be the name of cached func name give to cache decorator
204 205 without that the invalidation would not work"""
205 206 tmp = [name]
206 207 tmp.extend(args)
207 208 args = tuple(tmp)
208 209
209 210 if name == 'cached_repo_list':
210 211 from pylons_app.model.hg_model import _get_repos_cached
211 212 region_invalidate(_get_repos_cached, None, *args)
212 213
213 214 if name == 'full_changelog':
214 215 from pylons_app.model.hg_model import _full_changelog_cached
215 216 region_invalidate(_full_changelog_cached, None, *args)
216 217
217 218 class EmptyChangeset(BaseChangeset):
218 219
219 220 revision = -1
220 221 message = ''
222 author = ''
221 223
222 224 @LazyProperty
223 225 def raw_id(self):
224 226 """
225 227 Returns raw string identifing this changeset, useful for web
226 228 representation.
227 229 """
228 230 return '0' * 12
229 231
230 232
231 233 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
232 234 """
233 235 maps all found repositories into db
234 236 """
235 237 from pylons_app.model.repo_model import RepoModel
236 238
237 239 sa = meta.Session
238 240 user = sa.query(User).filter(User.admin == True).first()
239 241
240 242 rm = RepoModel()
241 243
242 244 for name, repo in initial_repo_list.items():
243 245 if not sa.query(Repository).filter(Repository.repo_name == name).scalar():
244 246 log.info('repository %s not found creating default', name)
245 247
246 248 form_data = {
247 249 'repo_name':name,
248 250 'description':repo.description if repo.description != 'unknown' else \
249 251 'auto description for %s' % name,
250 252 'private':False
251 253 }
252 254 rm.create(form_data, user, just_db=True)
253 255
254 256
255 257 if remove_obsolete:
256 258 #remove from database those repositories that are not in the filesystem
257 259 for repo in sa.query(Repository).all():
258 260 if repo.repo_name not in initial_repo_list.keys():
259 261 sa.delete(repo)
260 262 sa.commit()
261 263
262 264
263 265 meta.Session.remove()
264 266
265 267 from UserDict import DictMixin
266 268
267 269 class OrderedDict(dict, DictMixin):
268 270
269 271 def __init__(self, *args, **kwds):
270 272 if len(args) > 1:
271 273 raise TypeError('expected at most 1 arguments, got %d' % len(args))
272 274 try:
273 275 self.__end
274 276 except AttributeError:
275 277 self.clear()
276 278 self.update(*args, **kwds)
277 279
278 280 def clear(self):
279 281 self.__end = end = []
280 282 end += [None, end, end] # sentinel node for doubly linked list
281 283 self.__map = {} # key --> [key, prev, next]
282 284 dict.clear(self)
283 285
284 286 def __setitem__(self, key, value):
285 287 if key not in self:
286 288 end = self.__end
287 289 curr = end[1]
288 290 curr[2] = end[1] = self.__map[key] = [key, curr, end]
289 291 dict.__setitem__(self, key, value)
290 292
291 293 def __delitem__(self, key):
292 294 dict.__delitem__(self, key)
293 295 key, prev, next = self.__map.pop(key)
294 296 prev[2] = next
295 297 next[1] = prev
296 298
297 299 def __iter__(self):
298 300 end = self.__end
299 301 curr = end[2]
300 302 while curr is not end:
301 303 yield curr[0]
302 304 curr = curr[2]
303 305
304 306 def __reversed__(self):
305 307 end = self.__end
306 308 curr = end[1]
307 309 while curr is not end:
308 310 yield curr[0]
309 311 curr = curr[1]
310 312
311 313 def popitem(self, last=True):
312 314 if not self:
313 315 raise KeyError('dictionary is empty')
314 316 if last:
315 317 key = reversed(self).next()
316 318 else:
317 319 key = iter(self).next()
318 320 value = self.pop(key)
319 321 return key, value
320 322
321 323 def __reduce__(self):
322 324 items = [[k, self[k]] for k in self]
323 325 tmp = self.__map, self.__end
324 326 del self.__map, self.__end
325 327 inst_dict = vars(self).copy()
326 328 self.__map, self.__end = tmp
327 329 if inst_dict:
328 330 return (self.__class__, (items,), inst_dict)
329 331 return self.__class__, (items,)
330 332
331 333 def keys(self):
332 334 return list(self)
333 335
334 336 setdefault = DictMixin.setdefault
335 337 update = DictMixin.update
336 338 pop = DictMixin.pop
337 339 values = DictMixin.values
338 340 items = DictMixin.items
339 341 iterkeys = DictMixin.iterkeys
340 342 itervalues = DictMixin.itervalues
341 343 iteritems = DictMixin.iteritems
342 344
343 345 def __repr__(self):
344 346 if not self:
345 347 return '%s()' % (self.__class__.__name__,)
346 348 return '%s(%r)' % (self.__class__.__name__, self.items())
347 349
348 350 def copy(self):
349 351 return self.__class__(self)
350 352
351 353 @classmethod
352 354 def fromkeys(cls, iterable, value=None):
353 355 d = cls()
354 356 for key in iterable:
355 357 d[key] = value
356 358 return d
357 359
358 360 def __eq__(self, other):
359 361 if isinstance(other, OrderedDict):
360 362 return len(self) == len(other) and self.items() == other.items()
361 363 return dict.__eq__(self, other)
362 364
363 365 def __ne__(self, other):
364 366 return not self == other
367
368
369 #===============================================================================
370 # TEST FUNCTIONS
371 #===============================================================================
372 def create_test_index(repo_location, full_index):
373 """Makes default test index
374 @param repo_location:
375 @param full_index:
376 """
377 from pylons_app.lib.indexers.daemon import WhooshIndexingDaemon
378 from pylons_app.lib.pidlock import DaemonLock, LockHeld
379 from pylons_app.lib.indexers import IDX_LOCATION
380 import shutil
381
382 if os.path.exists(IDX_LOCATION):
383 shutil.rmtree(IDX_LOCATION)
384
385 try:
386 l = DaemonLock()
387 WhooshIndexingDaemon(repo_location=repo_location)\
388 .run(full_index=full_index)
389 l.release()
390 except LockHeld:
391 pass
392
393 def create_test_env(repos_test_path, config):
394 """Makes a fresh database and
395 install test repository into tmp dir
396 """
397 from pylons_app.lib.db_manage import DbManage
398 import tarfile
399 import shutil
400 from os.path import dirname as dn, join as jn, abspath
401
402 log = logging.getLogger('TestEnvCreator')
403 # create logger
404 log.setLevel(logging.DEBUG)
405 log.propagate = True
406 # create console handler and set level to debug
407 ch = logging.StreamHandler()
408 ch.setLevel(logging.DEBUG)
409
410 # create formatter
411 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
412
413 # add formatter to ch
414 ch.setFormatter(formatter)
415
416 # add ch to logger
417 log.addHandler(ch)
418
419 #PART ONE create db
420 log.debug('making test db')
421 dbname = config['sqlalchemy.db1.url'].split('/')[-1]
422 dbmanage = DbManage(log_sql=True, dbname=dbname, tests=True)
423 dbmanage.create_tables(override=True)
424 dbmanage.config_prompt(repos_test_path)
425 dbmanage.create_default_user()
426 dbmanage.admin_prompt()
427 dbmanage.create_permissions()
428 dbmanage.populate_default_permissions()
429
430 #PART TWO make test repo
431 log.debug('making test vcs repo')
432 if os.path.isdir('/tmp/vcs_test'):
433 shutil.rmtree('/tmp/vcs_test')
434
435 cur_dir = dn(dn(abspath(__file__)))
436 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test.tar.gz"))
437 tar.extractall('/tmp')
438 tar.close()
@@ -1,30 +1,23 b''
1 1 """The application's model objects"""
2 2 import logging
3 import sqlalchemy as sa
4 from sqlalchemy import orm
5 3 from pylons_app.model import meta
6 from pylons_app.model.meta import Session
7 4 log = logging.getLogger(__name__)
8 5
9 # Add these two imports:
10 import datetime
11 from sqlalchemy import schema, types
12
13 6 def init_model(engine):
14 7 """Call me before using any of the tables or classes in the model"""
15 8 log.info("INITIALIZING DB MODELS")
16 9 meta.Base.metadata.bind = engine
17 10 #meta.Base2.metadata.bind = engine2
18 11
19 12 #THIS IS A TEST FOR EXECUTING SCRIPT AND LOAD PYLONS APPLICATION GLOBALS
20 13 #from paste.deploy import appconfig
21 14 #from pylons import config
22 15 #from sqlalchemy import engine_from_config
23 16 #from pylons_app.config.environment import load_environment
24 17 #
25 18 #conf = appconfig('config:development.ini', relative_to = './../../')
26 19 #load_environment(conf.global_conf, conf.local_conf)
27 20 #
28 21 #engine = engine_from_config(config, 'sqlalchemy.')
29 22 #init_model(engine)
30 23 # DO SOMETHING
@@ -1,125 +1,134 b''
1 1 from pylons_app.model.meta import Base
2 2 from sqlalchemy import *
3 3 from sqlalchemy.orm import relation, backref
4 4 from sqlalchemy.orm.session import Session
5 5 from vcs.utils.lazy import LazyProperty
6 6 import logging
7 7
8 8 log = logging.getLogger(__name__)
9 9
10 10 class HgAppSettings(Base):
11 11 __tablename__ = 'hg_app_settings'
12 12 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
13 13 app_settings_id = Column("app_settings_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
14 14 app_settings_name = Column("app_settings_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
15 15 app_settings_value = Column("app_settings_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
16 16
17 17 class HgAppUi(Base):
18 18 __tablename__ = 'hg_app_ui'
19 19 __table_args__ = {'useexisting':True}
20 20 ui_id = Column("ui_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
21 21 ui_section = Column("ui_section", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
22 22 ui_key = Column("ui_key", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
23 23 ui_value = Column("ui_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
24 24 ui_active = Column("ui_active", BOOLEAN(), nullable=True, unique=None, default=True)
25 25
26 26
27 27 class User(Base):
28 28 __tablename__ = 'users'
29 __table_args__ = (UniqueConstraint('username'), {'useexisting':True})
29 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
30 30 user_id = Column("user_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
31 31 username = Column("username", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
32 32 password = Column("password", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
33 33 active = Column("active", BOOLEAN(), nullable=True, unique=None, default=None)
34 34 admin = Column("admin", BOOLEAN(), nullable=True, unique=None, default=False)
35 35 name = Column("name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
36 36 lastname = Column("lastname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
37 37 email = Column("email", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
38 38 last_login = Column("last_login", DATETIME(timezone=False), nullable=True, unique=None, default=None)
39 39
40 40 user_log = relation('UserLog')
41 41 user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id")
42 42
43 43 @LazyProperty
44 44 def full_contact(self):
45 45 return '%s %s <%s>' % (self.name, self.lastname, self.email)
46 46
47 47 def __repr__(self):
48 48 return "<User('id:%s:%s')>" % (self.user_id, self.username)
49 49
50 50 def update_lastlogin(self):
51 51 """Update user lastlogin"""
52 52 import datetime
53 53
54 54 try:
55 55 session = Session.object_session(self)
56 56 self.last_login = datetime.datetime.now()
57 57 session.add(self)
58 58 session.commit()
59 59 log.debug('updated user %s lastlogin',self.username)
60 60 except Exception:
61 61 session.rollback()
62 62
63 63
64 64 class UserLog(Base):
65 65 __tablename__ = 'user_logs'
66 66 __table_args__ = {'useexisting':True}
67 67 user_log_id = Column("user_log_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
68 68 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
69 69 user_ip = Column("user_ip", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
70 70 repository = Column("repository", TEXT(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_name'), nullable=False, unique=None, default=None)
71 71 action = Column("action", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
72 72 action_date = Column("action_date", DATETIME(timezone=False), nullable=True, unique=None, default=None)
73 73
74 74 user = relation('User')
75 75
76 76 class Repository(Base):
77 77 __tablename__ = 'repositories'
78 78 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
79 79 repo_id = Column("repo_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
80 80 repo_name = Column("repo_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
81 81 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=False, default=None)
82 82 private = Column("private", BOOLEAN(), nullable=True, unique=None, default=None)
83 83 description = Column("description", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
84 84
85 85 user = relation('User')
86 86 repo_to_perm = relation('RepoToPerm', cascade='all')
87 87
88 88 def __repr__(self):
89 89 return "<Repository('id:%s:%s')>" % (self.repo_id, self.repo_name)
90 90
91 91 class Permission(Base):
92 92 __tablename__ = 'permissions'
93 93 __table_args__ = {'useexisting':True}
94 94 permission_id = Column("permission_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
95 95 permission_name = Column("permission_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
96 96 permission_longname = Column("permission_longname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
97 97
98 98 def __repr__(self):
99 99 return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name)
100 100
101 101 class RepoToPerm(Base):
102 102 __tablename__ = 'repo_to_perm'
103 103 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
104 104 repo_to_perm_id = Column("repo_to_perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
105 105 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
106 106 permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
107 107 repository_id = Column("repository_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None)
108 108
109 109 user = relation('User')
110 110 permission = relation('Permission')
111 111 repository = relation('Repository')
112 112
113 113 class UserToPerm(Base):
114 114 __tablename__ = 'user_to_perm'
115 115 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
116 116 user_to_perm_id = Column("user_to_perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
117 117 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
118 118 permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
119 119
120 120 user = relation('User')
121 121 permission = relation('Permission')
122 122
123
123 class Statistics(Base):
124 __tablename__ = 'statistics'
125 __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
126 stat_id = Column("stat_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
127 repository_id = Column("repository_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=True, default=None)
128 stat_on_revision = Column("stat_on_revision", INTEGER(), nullable=False)
129 commit_activity = Column("commit_activity", BLOB(), nullable=False)#JSON data
130 commit_activity_combined = Column("commit_activity_combined", BLOB(), nullable=False)#JSON data
131 languages = Column("languages", BLOB(), nullable=False)#JSON data
124 132
133 repository = relation('Repository')
125 134
@@ -1,315 +1,351 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 from formencode import All
23 23 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
24 24 Email, Bool, StringBoolean
25 25 from pylons import session
26 26 from pylons.i18n.translation import _
27 27 from pylons_app.lib.auth import check_password, get_crypt_password
28 28 from pylons_app.model import meta
29 29 from pylons_app.model.user_model import UserModel
30 30 from pylons_app.model.db import User, Repository
31 31 from sqlalchemy.exc import OperationalError
32 32 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
33 33 from webhelpers.pylonslib.secure_form import authentication_token
34 34 import formencode
35 35 import logging
36 36 import os
37 37 import pylons_app.lib.helpers as h
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 #this is needed to translate the messages using _() in validators
42 42 class State_obj(object):
43 43 _ = staticmethod(_)
44 44
45 45 #===============================================================================
46 46 # VALIDATORS
47 47 #===============================================================================
48 48 class ValidAuthToken(formencode.validators.FancyValidator):
49 49 messages = {'invalid_token':_('Token mismatch')}
50 50
51 51 def validate_python(self, value, state):
52 52
53 53 if value != authentication_token():
54 54 raise formencode.Invalid(self.message('invalid_token', state,
55 55 search_number=value), value, state)
56 56
57 57 def ValidUsername(edit, old_data):
58 58 class _ValidUsername(formencode.validators.FancyValidator):
59 59
60 60 def validate_python(self, value, state):
61 61 if value in ['default', 'new_user']:
62 62 raise formencode.Invalid(_('Invalid username'), value, state)
63 63 #check if user is uniq
64 64 sa = meta.Session
65 65 old_un = None
66 66 if edit:
67 67 old_un = sa.query(User).get(old_data.get('user_id')).username
68 68
69 69 if old_un != value or not edit:
70 70 if sa.query(User).filter(User.username == value).scalar():
71 71 raise formencode.Invalid(_('This username already exists') ,
72 72 value, state)
73 73 meta.Session.remove()
74 74
75 75 return _ValidUsername
76 76
77 77 class ValidPassword(formencode.validators.FancyValidator):
78 78
79 79 def to_python(self, value, state):
80 80 if value:
81 81 return get_crypt_password(value)
82 82
83 83 class ValidAuth(formencode.validators.FancyValidator):
84 84 messages = {
85 85 'invalid_password':_('invalid password'),
86 86 'invalid_login':_('invalid user name'),
87 87 'disabled_account':_('Your acccount is disabled')
88 88
89 89 }
90 90 #error mapping
91 91 e_dict = {'username':messages['invalid_login'],
92 92 'password':messages['invalid_password']}
93 93 e_dict_disable = {'username':messages['disabled_account']}
94 94
95 95 def validate_python(self, value, state):
96 96 password = value['password']
97 97 username = value['username']
98 98 user = UserModel().get_user_by_name(username)
99 99 if user is None:
100 100 raise formencode.Invalid(self.message('invalid_password',
101 101 state=State_obj), value, state,
102 102 error_dict=self.e_dict)
103 103 if user:
104 104 if user.active:
105 105 if user.username == username and check_password(password,
106 106 user.password):
107 107 return value
108 108 else:
109 109 log.warning('user %s not authenticated', username)
110 110 raise formencode.Invalid(self.message('invalid_password',
111 111 state=State_obj), value, state,
112 112 error_dict=self.e_dict)
113 113 else:
114 114 log.warning('user %s is disabled', username)
115 115 raise formencode.Invalid(self.message('disabled_account',
116 116 state=State_obj),
117 117 value, state,
118 118 error_dict=self.e_dict_disable)
119 119
120 120 class ValidRepoUser(formencode.validators.FancyValidator):
121 121
122 122 def to_python(self, value, state):
123 123 try:
124 124 self.user_db = meta.Session.query(User)\
125 125 .filter(User.active == True)\
126 126 .filter(User.username == value).one()
127 127 except Exception:
128 128 raise formencode.Invalid(_('This username is not valid'),
129 129 value, state)
130 130 finally:
131 131 meta.Session.remove()
132 132
133 133 return self.user_db.user_id
134 134
135 135 def ValidRepoName(edit, old_data):
136 136 class _ValidRepoName(formencode.validators.FancyValidator):
137 137
138 138 def to_python(self, value, state):
139 139 slug = h.repo_name_slug(value)
140 140 if slug in ['_admin']:
141 141 raise formencode.Invalid(_('This repository name is disallowed'),
142 142 value, state)
143 143 if old_data.get('repo_name') != value or not edit:
144 144 sa = meta.Session
145 145 if sa.query(Repository).filter(Repository.repo_name == slug).scalar():
146 146 raise formencode.Invalid(_('This repository already exists') ,
147 147 value, state)
148 148 meta.Session.remove()
149 149 return slug
150 150
151 151
152 152 return _ValidRepoName
153 153
154 154 class ValidPerms(formencode.validators.FancyValidator):
155 155 messages = {'perm_new_user_name':_('This username is not valid')}
156 156
157 157 def to_python(self, value, state):
158 158 perms_update = []
159 159 perms_new = []
160 160 #build a list of permission to update and new permission to create
161 161 for k, v in value.items():
162 162 if k.startswith('perm_'):
163 163 if k.startswith('perm_new_user'):
164 164 new_perm = value.get('perm_new_user', False)
165 165 new_user = value.get('perm_new_user_name', False)
166 166 if new_user and new_perm:
167 167 if (new_user, new_perm) not in perms_new:
168 168 perms_new.append((new_user, new_perm))
169 169 else:
170 170 usr = k[5:]
171 171 if usr == 'default':
172 172 if value['private']:
173 173 #set none for default when updating to private repo
174 174 v = 'repository.none'
175 175 perms_update.append((usr, v))
176 176 value['perms_updates'] = perms_update
177 177 value['perms_new'] = perms_new
178 178 sa = meta.Session
179 179 for k, v in perms_new:
180 180 try:
181 181 self.user_db = sa.query(User)\
182 182 .filter(User.active == True)\
183 183 .filter(User.username == k).one()
184 184 except Exception:
185 185 msg = self.message('perm_new_user_name',
186 186 state=State_obj)
187 187 raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg})
188 188 return value
189 189
190 190 class ValidSettings(formencode.validators.FancyValidator):
191 191
192 192 def to_python(self, value, state):
193 193 #settings form can't edit user
194 194 if value.has_key('user'):
195 195 del['value']['user']
196 196
197 197 return value
198 198
199 199 class ValidPath(formencode.validators.FancyValidator):
200 200 def to_python(self, value, state):
201 201 isdir = os.path.isdir(value.replace('*', ''))
202 202 if (value.endswith('/*') or value.endswith('/**')) and isdir:
203 203 return value
204 204 elif not isdir:
205 205 msg = _('This is not a valid path')
206 206 else:
207 207 msg = _('You need to specify * or ** at the end of path (ie. /tmp/*)')
208 208
209 209 raise formencode.Invalid(msg, value, state,
210 210 error_dict={'paths_root_path':msg})
211 211
212 def UniqSystemEmail(old_data):
213 class _UniqSystemEmail(formencode.validators.FancyValidator):
214 def to_python(self, value, state):
215 if old_data.get('email') != value:
216 sa = meta.Session
217 try:
218 user = sa.query(User).filter(User.email == value).scalar()
219 if user:
220 raise formencode.Invalid(_("That e-mail address is already taken") ,
221 value, state)
222 finally:
223 meta.Session.remove()
224
225 return value
226
227 return _UniqSystemEmail
228
229 class ValidSystemEmail(formencode.validators.FancyValidator):
230 def to_python(self, value, state):
231 sa = meta.Session
232 try:
233 user = sa.query(User).filter(User.email == value).scalar()
234 if user is None:
235 raise formencode.Invalid(_("That e-mail address doesn't exist.") ,
236 value, state)
237 finally:
238 meta.Session.remove()
239
240 return value
241
212 242 #===============================================================================
213 243 # FORMS
214 244 #===============================================================================
215 245 class LoginForm(formencode.Schema):
216 246 allow_extra_fields = True
217 247 filter_extra_fields = True
218 248 username = UnicodeString(
219 249 strip=True,
220 250 min=3,
221 251 not_empty=True,
222 252 messages={
223 253 'empty':_('Please enter a login'),
224 254 'tooShort':_('Enter a value %(min)i characters long or more')}
225 255 )
226 256
227 257 password = UnicodeString(
228 258 strip=True,
229 259 min=3,
230 260 not_empty=True,
231 261 messages={
232 262 'empty':_('Please enter a password'),
233 263 'tooShort':_('Enter a value %(min)i characters long or more')}
234 264 )
235 265
236 266
237 267 #chained validators have access to all data
238 268 chained_validators = [ValidAuth]
239 269
240 270 def UserForm(edit=False, old_data={}):
241 271 class _UserForm(formencode.Schema):
242 272 allow_extra_fields = True
243 273 filter_extra_fields = True
244 274 username = All(UnicodeString(strip=True, min=3, not_empty=True), ValidUsername(edit, old_data))
245 275 if edit:
246 276 new_password = All(UnicodeString(strip=True, min=3, not_empty=False), ValidPassword)
247 277 admin = StringBoolean(if_missing=False)
248 278 else:
249 279 password = All(UnicodeString(strip=True, min=8, not_empty=True), ValidPassword)
250 280 active = StringBoolean(if_missing=False)
251 281 name = UnicodeString(strip=True, min=3, not_empty=True)
252 282 lastname = UnicodeString(strip=True, min=3, not_empty=True)
253 email = Email(not_empty=True)
283 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
254 284
255 285 return _UserForm
256 286
257 287 RegisterForm = UserForm
258 288
289 def PasswordResetForm():
290 class _PasswordResetForm(formencode.Schema):
291 allow_extra_fields = True
292 filter_extra_fields = True
293 email = All(ValidSystemEmail(), Email(not_empty=True))
294 return _PasswordResetForm
259 295
260 296 def RepoForm(edit=False, old_data={}):
261 297 class _RepoForm(formencode.Schema):
262 298 allow_extra_fields = True
263 299 filter_extra_fields = False
264 300 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
265 301 description = UnicodeString(strip=True, min=3, not_empty=True)
266 302 private = StringBoolean(if_missing=False)
267 303
268 304 if edit:
269 305 user = All(Int(not_empty=True), ValidRepoUser)
270 306
271 307 chained_validators = [ValidPerms]
272 308 return _RepoForm
273 309
274 310 def RepoSettingsForm(edit=False, old_data={}):
275 311 class _RepoForm(formencode.Schema):
276 312 allow_extra_fields = True
277 313 filter_extra_fields = False
278 314 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
279 315 description = UnicodeString(strip=True, min=3, not_empty=True)
280 316 private = StringBoolean(if_missing=False)
281 317
282 318 chained_validators = [ValidPerms, ValidSettings]
283 319 return _RepoForm
284 320
285 321
286 322 def ApplicationSettingsForm():
287 323 class _ApplicationSettingsForm(formencode.Schema):
288 324 allow_extra_fields = True
289 325 filter_extra_fields = False
290 326 hg_app_title = UnicodeString(strip=True, min=3, not_empty=True)
291 327 hg_app_realm = UnicodeString(strip=True, min=3, not_empty=True)
292 328
293 329 return _ApplicationSettingsForm
294 330
295 331 def ApplicationUiSettingsForm():
296 332 class _ApplicationUiSettingsForm(formencode.Schema):
297 333 allow_extra_fields = True
298 334 filter_extra_fields = False
299 335 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
300 336 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=3, not_empty=True))
301 337 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
302 338 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
303 339
304 340 return _ApplicationUiSettingsForm
305 341
306 342 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
307 343 class _DefaultPermissionsForm(formencode.Schema):
308 344 allow_extra_fields = True
309 345 filter_extra_fields = True
310 346 overwrite_default = OneOf(['true', 'false'], if_missing='false')
311 347 default_perm = OneOf(perms_choices)
312 348 default_register = OneOf(register_choices)
313 349 default_create = OneOf(create_choices)
314 350
315 351 return _DefaultPermissionsForm
@@ -1,174 +1,169 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Model for hg app
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 9, 2010
22 22 Model for hg app
23 23 @author: marcink
24 24 """
25 25 from beaker.cache import cache_region
26 26 from mercurial import ui
27 27 from mercurial.hgweb.hgwebdir_mod import findrepos
28 28 from pylons.i18n.translation import _
29 29 from pylons_app.lib.auth import HasRepoPermissionAny
30 30 from pylons_app.model import meta
31 31 from pylons_app.model.db import Repository, User
32 32 from pylons_app.lib import helpers as h
33 33 from vcs.exceptions import RepositoryError, VCSError
34 34 import logging
35 35 import os
36 36 import sys
37 37 log = logging.getLogger(__name__)
38 38
39 39 try:
40 40 from vcs.backends.hg import MercurialRepository
41 41 except ImportError:
42 42 sys.stderr.write('You have to import vcs module')
43 43 raise Exception('Unable to import vcs')
44 44
45 45 def _get_repos_cached_initial(app_globals, initial):
46 """
47 return cached dict with repos
46 """return cached dict with repos
48 47 """
49 48 g = app_globals
50 49 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui, initial)
51 50
52 51 @cache_region('long_term', 'cached_repo_list')
53 52 def _get_repos_cached():
54 """
55 return cached dict with repos
53 """return cached dict with repos
56 54 """
57 55 log.info('getting all repositories list')
58 56 from pylons import app_globals as g
59 57 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
60 58
61 59 @cache_region('super_short_term', 'cached_repos_switcher_list')
62 60 def _get_repos_switcher_cached(cached_repo_list):
63 61 repos_lst = []
64 for repo in sorted(x.name.lower() for x in cached_repo_list.values()):
65 if HasRepoPermissionAny('repository.write', 'repository.read', 'repository.admin')(repo, 'main page check'):
66 repos_lst.append(repo)
62 for repo in [x for x in cached_repo_list.values()]:
63 if HasRepoPermissionAny('repository.write', 'repository.read',
64 'repository.admin')(repo.name.lower(), 'main page check'):
65 repos_lst.append((repo.name, repo.dbrepo.private,))
67 66
68 return repos_lst
67 return sorted(repos_lst, key=lambda k:k[0])
69 68
70 69 @cache_region('long_term', 'full_changelog')
71 70 def _full_changelog_cached(repo_name):
72 71 log.info('getting full changelog for %s', repo_name)
73 72 return list(reversed(list(HgModel().get_repo(repo_name))))
74 73
75 74 class HgModel(object):
76 """
77 Mercurial Model
75 """Mercurial Model
78 76 """
79 77
80 78 def __init__(self):
81 """
82 Constructor
83 """
79 pass
84 80
85 81 @staticmethod
86 82 def repo_scan(repos_prefix, repos_path, baseui, initial=False):
87 83 """
88 84 Listing of repositories in given path. This path should not be a
89 85 repository itself. Return a dictionary of repository objects
90 86 :param repos_path: path to directory it could take syntax with
91 87 * or ** for deep recursive displaying repositories
92 88 """
93 89 sa = meta.Session()
94 90 def check_repo_dir(path):
95 """
96 Checks the repository
91 """Checks the repository
97 92 :param path:
98 93 """
99 94 repos_path = path.split('/')
100 95 if repos_path[-1] in ['*', '**']:
101 96 repos_path = repos_path[:-1]
102 97 if repos_path[0] != '/':
103 98 repos_path[0] = '/'
104 99 if not os.path.isdir(os.path.join(*repos_path)):
105 raise RepositoryError('Not a valid repository in %s' % path[0][1])
100 raise RepositoryError('Not a valid repository in %s' % path)
106 101 if not repos_path.endswith('*'):
107 102 raise VCSError('You need to specify * or ** at the end of path '
108 103 'for recursive scanning')
109 104
110 105 check_repo_dir(repos_path)
111 106 log.info('scanning for repositories in %s', repos_path)
112 107 repos = findrepos([(repos_prefix, repos_path)])
113 108 if not isinstance(baseui, ui.ui):
114 109 baseui = ui.ui()
115 110
116 111 repos_list = {}
117 112 for name, path in repos:
118 113 try:
119 114 #name = name.split('/')[-1]
120 115 if repos_list.has_key(name):
121 116 raise RepositoryError('Duplicate repository name %s found in'
122 117 ' %s' % (name, path))
123 118 else:
124 119
125 120 repos_list[name] = MercurialRepository(path, baseui=baseui)
126 121 repos_list[name].name = name
127 122
128 123 dbrepo = None
129 124 if not initial:
130 125 dbrepo = sa.query(Repository)\
131 126 .filter(Repository.repo_name == name).scalar()
132 127
133 128 if dbrepo:
134 129 log.info('Adding db instance to cached list')
135 130 repos_list[name].dbrepo = dbrepo
136 131 repos_list[name].description = dbrepo.description
137 132 if dbrepo.user:
138 133 repos_list[name].contact = dbrepo.user.full_contact
139 134 else:
140 135 repos_list[name].contact = sa.query(User)\
141 136 .filter(User.admin == True).first().full_contact
142 137 except OSError:
143 138 continue
144 139 meta.Session.remove()
145 140 return repos_list
146 141
147 142 def get_repos(self):
148 143 for name, repo in _get_repos_cached().items():
149 144 if repo._get_hidden():
150 145 #skip hidden web repository
151 146 continue
152 147
153 148 last_change = repo.last_change
154 149 tip = h.get_changeset_safe(repo, 'tip')
155 150
156 151 tmp_d = {}
157 152 tmp_d['name'] = repo.name
158 153 tmp_d['name_sort'] = tmp_d['name'].lower()
159 154 tmp_d['description'] = repo.description
160 155 tmp_d['description_sort'] = tmp_d['description']
161 156 tmp_d['last_change'] = last_change
162 157 tmp_d['last_change_sort'] = last_change[1] - last_change[0]
163 158 tmp_d['tip'] = tip.raw_id
164 159 tmp_d['tip_sort'] = tip.revision
165 160 tmp_d['rev'] = tip.revision
166 161 tmp_d['contact'] = repo.contact
167 162 tmp_d['contact_sort'] = tmp_d['contact']
168 163 tmp_d['repo_archives'] = list(repo._get_archives())
169 164 tmp_d['last_msg'] = tip.message
170 165 tmp_d['repo'] = repo
171 166 yield tmp_d
172 167
173 168 def get_repo(self, repo_name):
174 169 return _get_repos_cached()[repo_name]
@@ -1,15 +1,58 b''
1 1 """SQLAlchemy Metadata and Session object"""
2 2 from sqlalchemy.ext.declarative import declarative_base
3 3 from sqlalchemy.orm import scoped_session, sessionmaker
4 from pylons_app.model import caching_query
5 from beaker import cache
6 import os
7 from os.path import join as jn, dirname as dn, abspath
8 import time
9
10 # Beaker CacheManager. A home base for cache configurations.
11 cache_manager = cache.CacheManager()
4 12
5 13 __all__ = ['Base', 'Session']
6 14 #
7 15 # SQLAlchemy session manager. Updated by model.init_model()
8 16 #
9 Session = scoped_session(sessionmaker())
10 #
17 Session = scoped_session(
18 sessionmaker(
19 query_cls=caching_query.query_callable(cache_manager)
20 )
21 )
11 22
12 23 # The declarative Base
13 24 Base = declarative_base()
14 25 #For another db...
15 26 #Base2 = declarative_base()
27
28 #===============================================================================
29 # CACHE OPTIONS
30 #===============================================================================
31 cache_dir = jn(dn(dn(dn(abspath(__file__)))), 'data', 'cache')
32 if not os.path.isdir(cache_dir):
33 os.mkdir(cache_dir)
34 # set start_time to current time
35 # to re-cache everything
36 # upon application startup
37 start_time = time.time()
38 # configure the "sqlalchemy" cache region.
39 cache_manager.regions['sql_cache_short'] = {
40 'type':'memory',
41 'data_dir':cache_dir,
42 'expire':10,
43 'start_time':start_time
44 }
45 cache_manager.regions['sql_cache_med'] = {
46 'type':'memory',
47 'data_dir':cache_dir,
48 'expire':360,
49 'start_time':start_time
50 }
51 cache_manager.regions['sql_cache_long'] = {
52 'type':'file',
53 'data_dir':cache_dir,
54 'expire':3600,
55 'start_time':start_time
56 }
57 #to use cache use this in query
58 #.options(FromCache("sqlalchemy_cache_type", "cachekey"))
@@ -1,131 +1,135 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Model for users
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20
21 21 """
22 22 Created on April 9, 2010
23 23 Model for users
24 24 @author: marcink
25 25 """
26
26 from pylons_app.lib import auth
27 from pylons.i18n.translation import _
28 from pylons_app.lib.celerylib import tasks, run_task
27 29 from pylons_app.model.db import User
28 30 from pylons_app.model.meta import Session
29 from pylons.i18n.translation import _
31 import traceback
30 32 import logging
31 33 log = logging.getLogger(__name__)
32 34
33 35 class DefaultUserException(Exception):pass
34 36
35 37 class UserModel(object):
36 38
37 39 def __init__(self):
38 40 self.sa = Session()
39 41
40 42 def get_default(self):
41 43 return self.sa.query(User).filter(User.username == 'default').scalar()
42 44
43 45 def get_user(self, id):
44 46 return self.sa.query(User).get(id)
45 47
46 48 def get_user_by_name(self,name):
47 49 return self.sa.query(User).filter(User.username == name).scalar()
48 50
49 51 def create(self, form_data):
50 52 try:
51 53 new_user = User()
52 54 for k, v in form_data.items():
53 55 setattr(new_user, k, v)
54 56
55 57 self.sa.add(new_user)
56 58 self.sa.commit()
57 except Exception as e:
58 log.error(e)
59 except:
60 log.error(traceback.format_exc())
59 61 self.sa.rollback()
60 62 raise
61 63
62 64 def create_registration(self, form_data):
63 65 try:
64 66 new_user = User()
65 67 for k, v in form_data.items():
66 68 if k != 'admin':
67 69 setattr(new_user, k, v)
68 70
69 71 self.sa.add(new_user)
70 72 self.sa.commit()
71 except Exception as e:
72 log.error(e)
73 except:
74 log.error(traceback.format_exc())
73 75 self.sa.rollback()
74 76 raise
75 77
76 78 def update(self, uid, form_data):
77 79 try:
78 80 new_user = self.sa.query(User).get(uid)
79 81 if new_user.username == 'default':
80 82 raise DefaultUserException(
81 83 _("You can't Edit this user since it's"
82 84 " crucial for entire application"))
83 85 for k, v in form_data.items():
84 86 if k == 'new_password' and v != '':
85 87 new_user.password = v
86 88 else:
87 89 setattr(new_user, k, v)
88 90
89 91 self.sa.add(new_user)
90 92 self.sa.commit()
91 except Exception as e:
92 log.error(e)
93 except:
94 log.error(traceback.format_exc())
93 95 self.sa.rollback()
94 96 raise
95 97
96 98 def update_my_account(self, uid, form_data):
97 99 try:
98 100 new_user = self.sa.query(User).get(uid)
99 101 if new_user.username == 'default':
100 102 raise DefaultUserException(
101 103 _("You can't Edit this user since it's"
102 104 " crucial for entire application"))
103 105 for k, v in form_data.items():
104 106 if k == 'new_password' and v != '':
105 107 new_user.password = v
106 108 else:
107 109 if k not in ['admin', 'active']:
108 110 setattr(new_user, k, v)
109 111
110 112 self.sa.add(new_user)
111 113 self.sa.commit()
112 except Exception as e:
113 log.error(e)
114 except:
115 log.error(traceback.format_exc())
114 116 self.sa.rollback()
115 117 raise
116 118
117 119 def delete(self, id):
118
119 120 try:
120 121
121 122 user = self.sa.query(User).get(id)
122 123 if user.username == 'default':
123 124 raise DefaultUserException(
124 125 _("You can't remove this user since it's"
125 126 " crucial for entire application"))
126 127 self.sa.delete(user)
127 128 self.sa.commit()
128 except Exception as e:
129 log.error(e)
129 except:
130 log.error(traceback.format_exc())
130 131 self.sa.rollback()
131 132 raise
133
134 def reset_password(self, data):
135 run_task(tasks.reset_user_password, data['email'])
@@ -1,3633 +1,3660 b''
1 1 /* -----------------------------------------------------------
2 2 main stylesheet
3 3 ----------------------------------------------------------- */
4 4
5 5 html
6 6 {
7 7 height: 100%;
8 8 }
9 9
10 10 body
11 11 {
12 12 margin: 0;
13 13 padding: 0;
14 14 height: 100%;
15 15 background: #d1d1d1 url("../images/background.png") repeat;
16 16 font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
17 17 font-size: 11px;
18 18 }
19 19
20 20 /* -----------------------------------------------------------
21 21 images
22 22 ----------------------------------------------------------- */
23 23
24 24 img
25 25 {
26 26 border: none;
27 27 }
28 28
29 29 img.icon{
30 30 vertical-align: bottom;
31 31
32 32 }
33 33 /* -----------------------------------------------------------
34 34 anchors
35 35 ----------------------------------------------------------- */
36 36
37 37 a
38 38 {
39 39 color: #0066CC;
40 40 text-decoration: none;
41 41 cursor: pointer;
42 42 }
43 43
44 44 a:hover
45 45 {
46 46 color: #000000;
47 47 text-decoration: underline;
48 48 }
49 49
50 50 /* -----------------------------------------------------------
51 51 headings
52 52 ----------------------------------------------------------- */
53 53
54 54 h1, h2, h3, h4, h5, h6
55 55 {
56 56 color: #292929;
57 57 font-weight: bold;
58 58 }
59 59
60 60 h1
61 61 {
62 62 font-size: 22px;
63 63 }
64 64
65 65 h2
66 66 {
67 67 font-size: 20px;
68 68 }
69 69
70 70 h3
71 71 {
72 72 font-size: 18px;
73 73 }
74 74
75 75 h4
76 76 {
77 77 font-size: 16px;
78 78 }
79 79
80 80 h5
81 81 {
82 82 font-size: 14px;
83 83 }
84 84
85 85 h6
86 86 {
87 87 font-size: 11px;
88 88 }
89 89
90 90 /* -----------------------------------------------------------
91 91 lists
92 92 ----------------------------------------------------------- */
93 93
94 94 ul.circle { list-style-type: circle; }
95 95 ul.disc { list-style-type: disc; }
96 96 ul.square { list-style-type: square; }
97 97 ol.lower-roman { list-style-type: lower-roman; }
98 98 ol.upper-roman { list-style-type: upper-roman; }
99 99 ol.lower-alpha { list-style-type: lower-alpha; }
100 100 ol.upper-alpha { list-style-type: upper-alpha; }
101 101 ol.decimal { list-style-type: decimal; }
102 102
103 103 /* -----------------------------------------------------------
104 104 colors
105 105 ----------------------------------------------------------- */
106 106
107 107 div.color
108 108 {
109 109 margin: 7px 0 0 60px;
110 110 padding: 1px 1px 1px 0px;
111 111 clear: both;
112 112 overflow: hidden;
113 113 position: absolute;
114 114 background: #FFFFFF;
115 115 }
116 116
117 117 div.color a
118 118 {
119 119 margin: 0 0 0 1px;
120 120 padding: 0;
121 121 width: 15px;
122 122 height: 15px;
123 123 display: block;
124 124 float: left;
125 125 }
126 126
127 127 div.color a.blue
128 128 {
129 129 background: #376ea6;
130 130 }
131 131
132 132 div.color a.green
133 133 {
134 134 background: #85924b;
135 135 }
136 136
137 137 div.color a.brown
138 138 {
139 139 background: #9b6e42;
140 140 }
141 141
142 142 div.color a.purple
143 143 {
144 144 background: #88528b;
145 145 }
146 146
147 147 div.color a.red
148 148 {
149 149 background: #bd3220;
150 150 }
151 151
152 152 div.color a.greyblue
153 153 {
154 154 background: #566e86;
155 155 }
156 156
157 157 /* -----------------------------------------------------------
158 158 options
159 159 ----------------------------------------------------------- */
160 160
161 161 div.options
162 162 {
163 163 margin: 7px 0 0 162px;
164 164 padding: 0;
165 165 clear: both;
166 166 overflow: hidden;
167 167 position: absolute;
168 168 background: #FFFFFF;
169 169 }
170 170
171 171 div.options a
172 172 {
173 173 margin: 0;
174 174 padding: 3px 8px 3px 8px;
175 175 height: 1%;
176 176 display: block;
177 177 text-decoration: none;
178 178 }
179 179
180 180 div.options a:hover
181 181 {
182 182 text-decoration: none;
183 183 }
184 184
185 185 /* -----------------------------------------------------------
186 186 header
187 187 ----------------------------------------------------------- */
188 188
189 189 #header
190 190 {
191 191 margin: 0;
192 192 padding: 0 30px 0 30px;
193 193 background: #b0b0b0 url("../images/header_background.png") repeat;
194 194 }
195 195
196 196
197 197 /* -----------------------------------------------------------
198 198 header -> user
199 199 ----------------------------------------------------------- */
200 200
201 201 #header ul#logged-user
202 202 {
203 203 margin: 0;
204 204 padding: 0;
205 205 float: right;
206 206 }
207 207
208 208 #header ul#logged-user li
209 209 {
210 210 margin: 0;
211 211 padding: 10px 12px 10px 12px;
212 212 list-style: none;
213 213 float: left;
214 214 border-left: 1px solid #bbbbbb;
215 215 border-right: 1px solid #a5a5a5;
216 216 }
217 217
218 218 #header ul#logged-user li.first
219 219 {
220 220 border-left: none;
221 221 margin:-6px;
222 222 }
223 223 #header ul#logged-user li.first div.account
224 224 {
225 225 padding-top: 4px;
226 226 float: left;
227 227 }
228 228
229 229
230 230 #header ul#logged-user li.last
231 231 {
232 232 border-right: none;
233 233 }
234 234
235 235 #header ul#logged-user li a
236 236 {
237 237 color: #4e4e4e;
238 238 font-weight: bold;
239 239 text-decoration: none;
240 240 }
241 241
242 242 #header ul#logged-user li a:hover
243 243 {
244 244 color: #376ea6;
245 245 text-decoration: underline;
246 246 }
247 247
248 248 #header ul#logged-user li.highlight a
249 249 {
250 250 color: #ffffff;
251 251 }
252 252
253 253 #header ul#logged-user li.highlight a:hover
254 254 {
255 255 color: #376ea6;
256 256 }
257 257
258 258 #header #header-inner
259 259 {
260 260 margin: 0;
261 261 padding: 0;
262 262 height: 40px;
263 263 clear: both;
264 264 position: relative;
265 265 background: #003367 url("../images/colors/blue/header_inner.png") repeat-x;
266 266 border-bottom: 6px solid #ffffff;
267 267 }
268 268
269 269 /* -----------------------------------------------------------
270 270 header -> home
271 271 ----------------------------------------------------------- */
272 272
273 273 #header #header-inner #home
274 274 {
275 275 float: left;
276 276 }
277 277
278 278 #header #header-inner #home a
279 279 {
280 280 margin: 0;
281 281 padding: 0;
282 282 height: 40px;
283 283 width: 46px;
284 284 display: block;
285 285 background: url("../images/colors/blue/button_home.png");
286 286 background-position: 0 0;
287 287 }
288 288
289 289 #header #header-inner #home a:hover
290 290 {
291 291 background-position: 0 -40px;
292 292 }
293 293
294 294 /* -----------------------------------------------------------
295 295 header -> logo
296 296 ----------------------------------------------------------- */
297 297
298 298 #header #header-inner #logo
299 299 {
300 300 float: left;
301 301 }
302 302
303 303 #header #header-inner #logo h1
304 304 {
305 305 margin: 13px 0 0 13px;
306 306 padding: 0;
307 307 color: #FFFFFF;
308 308 font-size: 14px;
309 309 text-transform: uppercase;
310 310 }
311 311
312 312 #header #header-inner #logo a
313 313 {
314 314 color: #ffffff;
315 315 text-decoration: none;
316 316 }
317 317
318 318 #header #header-inner #logo a:hover
319 319 {
320 320 color: #dabf29;
321 321 }
322 322
323 323 /* -----------------------------------------------------------
324 324 header -> quick
325 325 ----------------------------------------------------------- */
326 326 #header #header-inner #quick,
327 327 #header #header-inner #quick ul
328 328 {
329 329 margin: 10px 5px 0 0;
330 330 padding: 0;
331 331 position: relative;
332 332 float: right;
333 333 list-style-type: none;
334 334 list-style-position: outside;
335 335 }
336 336
337 337 #header #header-inner #quick li
338 338 {
339 339 margin: 0 5px 0 0;
340 340 padding: 0;
341 341 position: relative;
342 342 float: left;
343 343 }
344 344
345 345 #header #header-inner #quick li a
346 346 {
347 347 top: 0;
348 348 left: 0;
349 349 padding: 0;
350 350 height: 1%;
351 351 display: block;
352 352 clear: both;
353 353 overflow: hidden;
354 354 background: #336699 url("../../resources/images/colors/blue/quick_l.png") no-repeat top left;
355 355 color: #FFFFFF;
356 356 font-weight: bold;
357 357 text-decoration: none;
358 358 }
359 359
360 360 #header #header-inner #quick li span
361 361 {
362 362 top: 0;
363 363 right: 0;
364 364 margin: 0;
365 365 padding: 10px 12px 8px 10px;
366 366 height: 1%;
367 367 display: block;
368 368 float: left;
369 369 background: url("../../resources/images/colors/blue/quick_r.png") no-repeat top right;
370 370 border-left: 1px solid #3f6f9f;
371 371 }
372 372
373 373 #header #header-inner #quick li span.normal
374 374 {
375 375 padding: 10px 12px 8px 12px;
376 376 border: none;
377 377 }
378 378
379 379 #header #header-inner #quick li span.icon
380 380 {
381 381 top: 0;
382 382 left: 0;
383 383 padding: 8px 8px 4px 8px;
384 384 background: url("../../resources/images/colors/blue/quick_l.png") no-repeat top left;
385 385 border-left: none;
386 386 border-right: 1px solid #2e5c89;
387 387 }
388 388
389 389 #header #header-inner #quick li a:hover
390 390 {
391 391 background: #4e4e4e url("../../resources/images/colors/blue/quick_l_selected.png") no-repeat top left;
392 392 }
393 393
394 394 #header #header-inner #quick li a:hover span
395 395 {
396 396 background: url("../../resources/images/colors/blue/quick_r_selected.png") no-repeat top right;
397 397 border-left: 1px solid #545454;
398 398 }
399 399
400 400 #header #header-inner #quick li a:hover span.normal
401 401 {
402 402 border: none;
403 403 }
404 404
405 405 #header #header-inner #quick li a:hover span.icon
406 406 {
407 407 background: url("../../resources/images/colors/blue/quick_l_selected.png") no-repeat top left;
408 408 border-left: none;
409 409 border-right: 1px solid #464646;
410 410 }
411 411
412 412 #header #header-inner #quick ul
413 413 {
414 414 top: 29px;
415 415 right: 0;
416 416 margin: 0;
417 417 padding: 0;
418 418 width: 200px;
419 419 display: none;
420 420 position: absolute;
421 421 background: #FFFFFF;
422 422 border: 1px solid #666;
423 423 border-top: 1px solid #003367;
424 424 z-index: 100;
425 425 }
426 426
427 427 #header #header-inner #quick ul.repo_switcher{
428 428 max-height:275px;
429 429 overflow-x:hidden;
430 430 overflow-y:auto;
431 431 white-space:nowrap;
432 432 }
433 433
434 434 #header #header-inner #quick li ul li
435 435 {
436 436 border-bottom: 1px solid #dddddd;
437 437 }
438 438
439 439 #header #header-inner #quick li ul li.last
440 440 {
441 441 border: none;
442 442 }
443 443
444 444 #header #header-inner #quick li ul li a
445 445 {
446 446 margin: 0;
447 447 padding: 7px 9px 7px 9px;
448 448 height: 1%;
449 449 width: 182px;
450 450 height: auto;
451 451 display: block;
452 452 float: left;
453 453 background: #FFFFFF;
454 454 color: #0066CC;
455 455 font-weight: normal;
456 456 }
457 457
458 458 #header #header-inner #quick li ul li a.childs
459 459 {
460 460 margin: 0;
461 461 padding: 7px 9px 7px 24px;
462 462 width: 167px;
463 463 background: #FFFFFF url("../../resources/images/plus.png") no-repeat 8px 9px;
464 464 }
465 465
466 466 #header #header-inner #quick li ul li a:hover
467 467 {
468 468 color: #000000;
469 469 background: #FFFFFF;
470 470 }
471 471
472 472 #header #header-inner #quick li ul li a.childs:hover
473 473 {
474 474 background: #FFFFFF url("../../resources/images/minus.png") no-repeat 8px 9px;
475 475 }
476 476
477 477 #header #header-inner #quick ul ul
478 478 {
479 479 top: auto;
480 480 }
481 481
482 482 #header #header-inner #quick li ul ul
483 483 {
484 484 right: 200px;
485 485 max-height: 275px;
486 486 overflow: auto;
487 487 overflow-x: hidden;
488 488 white-space:nowrap;
489 489 }
490 490
491 491 #header #header-inner #quick li:hover ul ul,
492 492 #header #header-inner #quick li:hover ul ul ul,
493 493 #header #header-inner #quick li:hover ul ul ul ul
494 494 {
495 495 display: none;
496 496 }
497 497
498 498 #header #header-inner #quick li:hover ul,
499 499 #header #header-inner #quick li li:hover ul,
500 500 #header #header-inner #quick li li li:hover ul,
501 501 #header #header-inner #quick li li li li:hover ul
502 502 {
503 503 display: block;
504 504 }
505 505
506 506
507 507 /*ICONS*/
508 #header #header-inner #quick li ul li a.journal,
509 #header #header-inner #quick li ul li a.journal:hover
510 {
511 background:url("../images/icons/book.png") no-repeat scroll 4px 9px #FFFFFF;
512 margin:0;
513 padding:12px 9px 7px 24px;
514 width:167px;
515
516 }
517 #header #header-inner #quick li ul li a.private_repo,
518 #header #header-inner #quick li ul li a.private_repo:hover
519 {
520 background:url("../images/icons/lock.png") no-repeat scroll 4px 9px #FFFFFF;
521 margin:0;
522 padding:12px 9px 7px 24px;
523 width:167px;
524
525 }
526 #header #header-inner #quick li ul li a.public_repo,
527 #header #header-inner #quick li ul li a.public_repo:hover
528 {
529 background:url("../images/icons/lock_open.png") no-repeat scroll 4px 9px #FFFFFF;
530 margin:0;
531 padding:12px 9px 7px 24px;
532 width:167px;
533
534 }
508 535
509 536 #header #header-inner #quick li ul li a.repos,
510 537 #header #header-inner #quick li ul li a.repos:hover
511 538 {
512 539 background:url("../images/icons/folder_edit.png") no-repeat scroll 4px 9px #FFFFFF;
513 540 margin:0;
514 541 padding:12px 9px 7px 24px;
515 542 width:167px;
516 543
517 544 }
518 545 #header #header-inner #quick li ul li a.users,
519 546 #header #header-inner #quick li ul li a.users:hover
520 547 {
521 548 background: #FFFFFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
522 549 margin:0;
523 550 padding:12px 9px 7px 24px;
524 551 width:167px;
525 552 }
526 553 #header #header-inner #quick li ul li a.settings,
527 554 #header #header-inner #quick li ul li a.settings:hover
528 555 {
529 556 background: #FFFFFF url("../images/icons/cog.png") no-repeat 4px 9px;
530 557 margin:0;
531 558 padding:12px 9px 7px 24px;
532 559 width:167px;
533 560 }
534 561
535 562 #header #header-inner #quick li ul li a.permissions,
536 563 #header #header-inner #quick li ul li a.permissions:hover
537 564 {
538 565
539 566 background: #FFFFFF url("../images/icons/key.png") no-repeat 4px 9px;
540 567 margin:0;
541 568 padding:12px 9px 7px 24px;
542 569 width:167px;
543 570 }
544 571
545 572 #header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover
546 573 {
547 574
548 575 background: #FFFFFF url("../images/icons/arrow_branch.png") no-repeat 4px 9px;
549 576 margin:0;
550 577 padding:12px 9px 7px 24px;
551 578 width:167px;
552 579 }
553 580
554 581 #header #header-inner #quick li ul li a.tags,#header #header-inner #quick li ul li a.tags:hover
555 582 {
556 583
557 584 background: #FFFFFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
558 585 margin:0;
559 586 padding:12px 9px 7px 24px;
560 587 width:167px;
561 588 }
562 589 /* -----------------------------------------------------------
563 590 header corners
564 591 ----------------------------------------------------------- */
565 592
566 593 #header #header-inner div.corner
567 594 {
568 595 height: 6px;
569 596 width: 6px;
570 597 position: absolute;
571 598 background: url("../images/colors/blue/header_inner_corners.png") no-repeat;
572 599 }
573 600
574 601 #header #header-inner div.tl
575 602 {
576 603 top: 0;
577 604 left: 0;
578 605 background-position: 0 0;
579 606 }
580 607
581 608 #header #header-inner div.tr
582 609 {
583 610 top: 0;
584 611 right: 0;
585 612 background-position: -6px 0;
586 613 }
587 614
588 615 /* -----------------------------------------------------------
589 616 content
590 617 ----------------------------------------------------------- */
591 618
592 619 #content
593 620 {
594 621 margin: 10px 0 0 0;
595 622 padding: 0;
596 623 min-height: 100%;
597 624 clear: both;
598 625 overflow: hidden;
599 626 background: url("../images/content.png") repeat-y top left;
600 627 }
601 628
602 629 /* -----------------------------------------------------------
603 630 content -> left
604 631 ----------------------------------------------------------- */
605 632
606 633 #content #left
607 634 {
608 635 left: 0;
609 636 width: 280px;
610 637 position: absolute;
611 638 }
612 639
613 640 /* -----------------------------------------------------------
614 641 content -> left -> menu
615 642 ----------------------------------------------------------- */
616 643
617 644 #content #left #menu
618 645 {
619 646 margin: 5px 10px 0 60px;
620 647 padding: 0;
621 648 clear: both;
622 649 overflow: hidden;
623 650 }
624 651
625 652 /* -----------------------------------------------------------
626 653 content -> left -> menu / heading
627 654 ----------------------------------------------------------- */
628 655
629 656 #content #left #menu h6
630 657 {
631 658 margin: 5px 0 0 0;
632 659 padding: 0;
633 660 clear: both;
634 661 overflow: hidden;
635 662 background: #dfdfdf url("../images/menu.png") repeat-x;
636 663 color: #6e6e6e;
637 664 }
638 665
639 666 #content #left #menu h6 a
640 667 {
641 668 margin: 0;
642 669 padding: 0;
643 670 height: 1%;
644 671 display: block;
645 672 clear: both;
646 673 overflow: hidden;
647 674 background: url("../images/menu_l.png") no-repeat top left;
648 675 color: #6e6e6e;
649 676 text-decoration: none;
650 677 }
651 678
652 679 #content #left #menu h6 span
653 680 {
654 681 margin: 0;
655 682 padding: 9px 10px 10px 10px;
656 683 height: 1%;
657 684 display: block;
658 685 background: url("../images/menu_r.png") no-repeat top right;
659 686 }
660 687
661 688 #content #left #menu h6.selected
662 689 {
663 690 background: #00376e url("../images/colors/blue/menu_selected.png") repeat-x;
664 691 color: #FFFFFF;
665 692 }
666 693
667 694 #content #left #menu h6.selected a
668 695 {
669 696 background: url("../images/colors/blue/menu_l_selected.png") no-repeat top left;
670 697 color: #ffffff;
671 698 }
672 699
673 700 #content #left #menu h6.selected span
674 701 {
675 702 background: url("../images/colors/blue/menu_r_selected.png") no-repeat top right;
676 703 }
677 704
678 705 /* -----------------------------------------------------------
679 706 content -> left -> menu / links
680 707 ----------------------------------------------------------- */
681 708
682 709 #content #left #menu ul
683 710 {
684 711 margin: 0;
685 712 padding: 0;
686 713 background: #376ea6;
687 714 }
688 715
689 716 #content #left #menu ul.opened
690 717 {
691 718 display: block;
692 719 }
693 720
694 721 #content #left #menu ul.closed
695 722 {
696 723 display: none;
697 724 }
698 725
699 726 #content #left #menu li
700 727 {
701 728 margin: 0;
702 729 padding: 0;
703 730 clear: both;
704 731 overflow: hidden;
705 732 list-style: none;
706 733 border-bottom: 1px solid #5f8bb7;
707 734 color: #ffffff;
708 735 }
709 736
710 737 #content #left #menu li a
711 738 {
712 739 margin: 0 0 0 6px;
713 740 padding: 8px 0 8px 18px;
714 741 height: 1%;
715 742 display: block;
716 743 float: left;
717 744 background: url("../images/colors/colors/blue/menu_arrow.png") no-repeat 0 9px;
718 745 color: #ffffff;
719 746 text-decoration: none;
720 747 }
721 748
722 749 #content #left #menu li a:hover
723 750 {
724 751 color: #b9dcff;
725 752 }
726 753
727 754 /* -----------------------------------------------------------
728 755 content -> left -> menu / collapsible
729 756 ----------------------------------------------------------- */
730 757
731 758 #content #left #menu li.collapsible
732 759 {
733 760 background: url("../images/colors/blue/menu_border.png") no-repeat top left;
734 761 }
735 762
736 763 #content #left #menu li.collapsible a
737 764 {
738 765 margin: 0 0 0 6px;
739 766 padding: 8px 0 8px 0;
740 767 height: 1%;
741 768 display: block;
742 769 background: transparent;
743 770 float: left;
744 771 font-weight: bold;
745 772 }
746 773
747 774 #content #left #menu li.collapsible a.plus
748 775 {
749 776 margin: 0;
750 777 padding: 8px 0 9px 24px;
751 778 height: 10px;
752 779 width: 10px;
753 780 display: block;
754 781 float: left;
755 782 background: url("../images/menu_plus.png") no-repeat 5px 10px;
756 783 border: none;
757 784 }
758 785
759 786 #content #left #menu li.collapsible a.minus
760 787 {
761 788 margin: 0;
762 789 padding: 8px 0 9px 24px;
763 790 height: 10px;
764 791 width: 10px;
765 792 display: block;
766 793 float: left;
767 794 background: url("../images/menu_minus.png") no-repeat 5px 10px;
768 795 border: none;
769 796 }
770 797
771 798 #content #left #menu li ul
772 799 {
773 800 margin: 0;
774 801 padding: 0;
775 802 border-left: 18px solid #285889;
776 803 }
777 804
778 805 #content #left #menu li ul.expanded
779 806 {
780 807 display: block;
781 808 }
782 809
783 810 #content #left #menu li ul.collapsed
784 811 {
785 812 display: none;
786 813 }
787 814
788 815 #content #left #menu li ul li
789 816 {
790 817 margin: 0;
791 818 padding: 0;
792 819 clear: both;
793 820 overflow: hidden;
794 821 list-style: none;
795 822 border-bottom: 1px solid #5f8bb7;
796 823 color: #ffffff;
797 824 }
798 825
799 826 #content #left #menu li.collapsible ul li a
800 827 {
801 828 font-weight: normal;
802 829 }
803 830
804 831 #content #left #menu li.last
805 832 {
806 833 border-bottom: none;
807 834 }
808 835
809 836 /* -----------------------------------------------------------
810 837 content -> left -> date picker
811 838 ----------------------------------------------------------- */
812 839
813 840 #content #left #date-picker
814 841 {
815 842 margin: 10px 10px 0 60px;
816 843 padding: 0;
817 844 clear: both;
818 845 overflow: hidden;
819 846 }
820 847
821 848 #content #left #date-picker .ui-datepicker
822 849 {
823 850 width: auto;
824 851 padding: 0;
825 852 clear: both;
826 853 overflow: hidden;
827 854 background: #FFFFFF;
828 855 border: 1px solid #d1d1d1;
829 856 }
830 857
831 858 #content #left #date-picker .ui-datepicker .ui-datepicker-header
832 859 {
833 860 padding: 5px 0;
834 861 }
835 862
836 863 #content #left #date-picker .ui-datepicker .ui-datepicker-prev
837 864 {
838 865 top: 5px;
839 866 left: 4px;
840 867 }
841 868
842 869 #content #left #date-picker .ui-datepicker .ui-datepicker-next
843 870 {
844 871 top: 5px;
845 872 right: 4px;
846 873 }
847 874
848 875 #content #left #date-picker .ui-datepicker .ui-datepicker-prev-hover
849 876 {
850 877 top: 5px;
851 878 left: 4px;
852 879 }
853 880
854 881 #content #left #date-picker .ui-datepicker .ui-datepicker-next-hover
855 882 {
856 883 top: 5px;
857 884 right: 4px;
858 885 }
859 886
860 887 /* -----------------------------------------------------------
861 888 content -> right
862 889 ----------------------------------------------------------- */
863 890
864 891 #content #right
865 892 {
866 893 margin: 0 60px 10px 290px;
867 894 }
868 895
869 896 /* -----------------------------------------------------------
870 897 content -> right -> box
871 898 ----------------------------------------------------------- */
872 899
873 900 #content div.box
874 901 {
875 902 margin: 0 0 10px 0;
876 903 padding: 0 0 10px 0;
877 904 clear: both;
878 905 overflow: hidden;
879 906 background: #ffffff;
880 907 }
881 908
882 909 #content div.box-left
883 910 {
884 911 margin: 0 0 10px;
885 912 width: 49%;
886 913 clear: none;
887 914 float: left;
888 915 }
889 916
890 917 #content div.box-right
891 918 {
892 919 margin: 0 0 10px;
893 920 width: 49%;
894 921 clear: none;
895 922 float: right;
896 923 }
897 924
898 925 /* -----------------------------------------------------------
899 926 content -> right -> box / title
900 927 ----------------------------------------------------------- */
901 928
902 929 #content div.box div.title
903 930 {
904 931 margin: 0 0 20px 0;
905 932 padding: 0;
906 933 clear: both;
907 934 overflow: hidden;
908 935 background: #336699 url("../images/colors/blue/title.png") repeat-x;
909 936 }
910 937
911 938 #content div.box div.title h5
912 939 {
913 940 margin: 0;
914 941 padding: 11px 0 11px 10px;
915 942 float: left;
916 943 border: none;
917 944 color: #ffffff;
918 945 text-transform: uppercase;
919 946 }
920 947
921 948 #content div.box div.title ul.links
922 949 {
923 950 margin: 0;
924 951 padding: 0;
925 952 float: right;
926 953 }
927 954
928 955 #content div.box div.title ul.links li
929 956 {
930 957 margin: 0;
931 958 padding: 0;
932 959 list-style: none;
933 960 float: left;
934 961 }
935 962
936 963 #content div.box div.title ul.links li a
937 964 {
938 965 margin: 0;
939 966 padding: 13px 16px 12px 16px;
940 967 height: 1%;
941 968 display: block;
942 969 float: left;
943 970 background: url("../images/colors/blue/title_link.png") no-repeat top left;
944 971 border-left: 1px solid #316293;
945 972 color: #ffffff;
946 973 font-size: 11px;
947 974 font-weight: bold;
948 975 text-decoration: none;
949 976 }
950 977
951 978 #content div.box div.title ul.links li a:hover
952 979 {
953 980 color: #bfe3ff;
954 981 }
955 982
956 983 #content div.box div.title ul.links li.ui-tabs-selected a
957 984 {
958 985 background: url("../../../resources/images/colors/blue/title_tab_selected.png") no-repeat bottom center;
959 986 color: #bfe3ff;
960 987 }
961 988
962 989 /* -----------------------------------------------------------
963 990 content -> right -> box / headings
964 991 ----------------------------------------------------------- */
965 992
966 993 #content div.box h1,
967 994 #content div.box h2,
968 995 #content div.box h3,
969 996 #content div.box h4,
970 997 #content div.box h5,
971 998 #content div.box h6
972 999 {
973 1000 margin: 10px 20px 10px 20px;
974 1001 padding: 0 0 15px 0;
975 1002 clear: both;
976 1003 overflow: hidden;
977 1004 border-bottom: 1px solid #DDDDDD;
978 1005 }
979 1006
980 1007 /* -----------------------------------------------------------
981 1008 content -> right -> box / paragraphs
982 1009 ----------------------------------------------------------- */
983 1010
984 1011 #content div.box p
985 1012 {
986 1013 margin: 0 24px 10px 24px;
987 1014 padding: 0;
988 1015 color: #5f5f5f;
989 1016 font-size: 12px;
990 1017 line-height: 150%;
991 1018 }
992 1019
993 1020 #content div.box blockquote
994 1021 {
995 1022 margin: 0 34px 0 34px;
996 1023 padding: 0 0 0 14px;
997 1024 border-left: 4px solid #DDDDDD;
998 1025 color: #5f5f5f;
999 1026 font-size: 11px;
1000 1027 line-height: 150%;
1001 1028 }
1002 1029
1003 1030 #content div.box blockquote p
1004 1031 {
1005 1032 margin: 10px 0 10px 0;
1006 1033 padding: 0;
1007 1034 }
1008 1035
1009 1036 /* -----------------------------------------------------------
1010 1037 content -> right -> box / lists
1011 1038 ----------------------------------------------------------- */
1012 1039
1013 1040 #content div.box dl
1014 1041 {
1015 1042 margin: 10px 24px 10px 24px;
1016 1043 }
1017 1044
1018 1045 #content div.box dt
1019 1046 {
1020 1047 margin: 0;
1021 1048 font-size: 12px;
1022 1049 }
1023 1050
1024 1051 #content div.box dd
1025 1052 {
1026 1053 margin: 0;
1027 1054 padding: 8px 0 8px 15px;
1028 1055 font-size: 12px;
1029 1056 }
1030 1057
1031 1058 #content div.box ul.left
1032 1059 {
1033 1060 float: left;
1034 1061 }
1035 1062
1036 1063 #content div.box ol.left
1037 1064 {
1038 1065 float: left;
1039 1066 }
1040 1067
1041 1068 #content div.box li
1042 1069 {
1043 1070 padding: 4px 0 4px 0;
1044 1071 font-size: 12px;
1045 1072 }
1046 1073
1047 1074 #content div.box ol.lower-roman,
1048 1075 #content div.box ol.upper-roman
1049 1076 {
1050 1077 margin: 10px 24px 10px 44px;
1051 1078 }
1052 1079
1053 1080 #content div.box ol.lower-alpha,
1054 1081 #content div.box ol.upper-alpha
1055 1082 {
1056 1083 margin: 10px 24px 10px 44px;
1057 1084 }
1058 1085
1059 1086 #content div.box ol.decimal
1060 1087 {
1061 1088 margin: 10px 24px 10px 44px;
1062 1089 }
1063 1090
1064 1091 #content div.box ul.disc,
1065 1092 #content div.box ul.circle
1066 1093 {
1067 1094 margin: 10px 24px 10px 38px;
1068 1095 }
1069 1096
1070 1097 #content div.box ul.square
1071 1098 {
1072 1099 margin: 10px 24px 10px 40px;
1073 1100 }
1074 1101
1075 1102 /* -----------------------------------------------------------
1076 1103 content -> right -> box / images
1077 1104 ----------------------------------------------------------- */
1078 1105
1079 1106 #content div.box img.left
1080 1107 {
1081 1108 margin: 10px 10px 10px 0;
1082 1109 border: none;
1083 1110 float: left;
1084 1111 }
1085 1112
1086 1113 #content div.box img.right
1087 1114 {
1088 1115 margin: 10px 0 10px 10px;
1089 1116 border: none;
1090 1117 float: right;
1091 1118 }
1092 1119
1093 1120 /* -----------------------------------------------------------
1094 1121 content -> right -> box / messages
1095 1122 ----------------------------------------------------------- */
1096 1123
1097 1124 #content div.box div.messages
1098 1125 {
1099 1126 margin: 0 20px 0 20px;
1100 1127 padding: 0;
1101 1128 clear: both;
1102 1129 overflow: hidden;
1103 1130 }
1104 1131
1105 1132 #content div.box div.message
1106 1133 {
1107 1134 margin: 0 0 0px 0;
1108 1135 padding: 0 0 10px 0;
1109 1136 clear: both;
1110 1137 overflow: hidden;
1111 1138 }
1112 1139
1113 1140 #content div.box div.message div.image
1114 1141 {
1115 1142 margin: 9px 0 0 5px;
1116 1143 padding: 6px;
1117 1144 float: left;
1118 1145 }
1119 1146
1120 1147 #content div.box div.message div.image img
1121 1148 {
1122 1149 margin: 0;
1123 1150 vertical-align: middle;
1124 1151 }
1125 1152
1126 1153 #content div.box div.message div.text
1127 1154 {
1128 1155 margin: 0;
1129 1156 padding: 9px 6px 9px 6px;
1130 1157 float: left;
1131 1158 }
1132 1159
1133 1160 #content div.box div.message div.dismiss
1134 1161 {
1135 1162 margin: 0;
1136 1163 padding: 0;
1137 1164 float: right;
1138 1165 }
1139 1166
1140 1167 #content div.box div.message div.dismiss a
1141 1168 {
1142 1169 margin: 15px 14px 0 0;
1143 1170 padding: 0;
1144 1171 height: 16px;
1145 1172 width: 16px;
1146 1173 display: block;
1147 1174 background: url("../images/icons/cross.png") no-repeat;
1148 1175 }
1149 1176
1150 1177 #content div.box div.message div.text h1,
1151 1178 #content div.box div.message div.text h2,
1152 1179 #content div.box div.message div.text h3,
1153 1180 #content div.box div.message div.text h4,
1154 1181 #content div.box div.message div.text h5,
1155 1182 #content div.box div.message div.text h6
1156 1183 {
1157 1184 margin: 0;
1158 1185 padding: 0px;
1159 1186 border: none;
1160 1187 }
1161 1188
1162 1189 #content div.box div.message div.text span
1163 1190 {
1164 1191 margin: 0;
1165 1192 padding: 5px 0 0 0;
1166 1193 height: 1%;
1167 1194 display: block;
1168 1195 }
1169 1196
1170 1197 #content div.box div.message-error
1171 1198 {
1172 1199 height: 1%;
1173 1200 clear: both;
1174 1201 overflow: hidden;
1175 1202 background: #FBE3E4;
1176 1203 border: 1px solid #FBC2C4;
1177 1204 color: #860006;
1178 1205 }
1179 1206
1180 1207 #content div.box div.message-error h6
1181 1208 {
1182 1209 color: #860006;
1183 1210 }
1184 1211
1185 1212 #content div.box div.message-warning
1186 1213 {
1187 1214 height: 1%;
1188 1215 clear: both;
1189 1216 overflow: hidden;
1190 1217 background: #FFF6BF;
1191 1218 border: 1px solid #FFD324;
1192 1219 color: #5f5200;
1193 1220 }
1194 1221
1195 1222 #content div.box div.message-warning h6
1196 1223 {
1197 1224 color: #5f5200;
1198 1225 }
1199 1226
1200 1227 #content div.box div.message-notice
1201 1228 {
1202 1229 height: 1%;
1203 1230 clear: both;
1204 1231 overflow: hidden;
1205 1232 background: #8FBDE0;
1206 1233 border: 1px solid #6BACDE;
1207 1234 color: #003863;
1208 1235 }
1209 1236
1210 1237 #content div.box div.message-notice h6
1211 1238 {
1212 1239 color: #003863;
1213 1240 }
1214 1241
1215 1242 #content div.box div.message-success
1216 1243 {
1217 1244 height: 1%;
1218 1245 clear: both;
1219 1246 overflow: hidden;
1220 1247 background: #E6EFC2;
1221 1248 border: 1px solid #C6D880;
1222 1249 color: #4e6100;
1223 1250 }
1224 1251
1225 1252 #content div.box div.message-success h6
1226 1253 {
1227 1254 color: #4e6100;
1228 1255 }
1229 1256
1230 1257 /* -----------------------------------------------------------
1231 1258 content -> right -> box / forms
1232 1259 ----------------------------------------------------------- */
1233 1260
1234 1261 #content div.box div.form
1235 1262 {
1236 1263 margin: 0;
1237 1264 padding: 0 20px 10px 20px;
1238 1265 clear: both;
1239 1266 overflow: hidden;
1240 1267 }
1241 1268
1242 1269 #content div.box div.form div.fields
1243 1270 {
1244 1271 margin: 0;
1245 1272 padding: 0;
1246 1273 clear: both;
1247 1274 overflow: hidden;
1248 1275 }
1249 1276
1250 1277 #content div.box div.form div.fields div.field
1251 1278 {
1252 1279 margin: 0;
1253 1280 padding: 10px 0 10px 0;
1254 1281 height: 1%;
1255 1282 border-bottom: 1px solid #DDDDDD;
1256 1283 clear: both;
1257 1284 overflow: hidden;
1258 1285 }
1259 1286
1260 1287 #content div.box div.form div.fields div.field-first
1261 1288 {
1262 1289 padding: 0 0 10px 0;
1263 1290 }
1264 1291
1265 1292 #content div.box div.form div.fields div.field span.error-message
1266 1293 {
1267 1294 margin: 8px 0 0 0;
1268 1295 padding: 0;
1269 1296 height: 1%;
1270 1297 display: block;
1271 1298 color: #FF0000;
1272 1299 }
1273 1300
1274 1301 #content div.box div.form div.fields div.field span.success
1275 1302 {
1276 1303 margin: 8px 0 0 0;
1277 1304 padding: 0;
1278 1305 height: 1%;
1279 1306 display: block;
1280 1307 color: #316309;
1281 1308 }
1282 1309
1283 1310 /* -----------------------------------------------------------
1284 1311 content -> right -> forms -> labels
1285 1312 ----------------------------------------------------------- */
1286 1313
1287 1314 #content div.box div.form div.fields div.field div.label
1288 1315 {
1289 1316 left: 310px;
1290 1317 margin: 0;
1291 1318 padding: 8px 0 0 5px;
1292 1319 width: auto;
1293 1320 position: absolute;
1294 1321 }
1295 1322
1296 1323 #content div.box-left div.form div.fields div.field div.label,
1297 1324 #content div.box-right div.form div.fields div.field div.label
1298 1325 {
1299 1326 left: 0;
1300 1327 margin: 0;
1301 1328 padding: 0 0 8px 0;
1302 1329 width: auto;
1303 1330 position: relative;
1304 1331 clear: both;
1305 1332 overflow: hidden;
1306 1333
1307 1334 }
1308 1335
1309 1336 /* -----------------------------------------------------------
1310 1337 content -> right -> forms -> label (select)
1311 1338 ----------------------------------------------------------- */
1312 1339
1313 1340 #content div.box div.form div.fields div.field div.label-select
1314 1341 {
1315 1342 padding: 2px 0 0 5px;
1316 1343 }
1317 1344
1318 1345 #content div.box-left div.form div.fields div.field div.label-select,
1319 1346 #content div.box-right div.form div.fields div.field div.label-select
1320 1347 {
1321 1348 padding: 0 0 8px 0;
1322 1349 }
1323 1350
1324 1351 /* -----------------------------------------------------------
1325 1352 content -> right -> forms -> label (checkbox)
1326 1353 ----------------------------------------------------------- */
1327 1354
1328 1355 #content div.box div.form div.fields div.field div.label-checkbox
1329 1356 {
1330 1357 padding:0 0 0 5px !important;
1331 1358 }
1332 1359
1333 1360 /* -----------------------------------------------------------
1334 1361 content -> right -> forms -> label (radio)
1335 1362 ----------------------------------------------------------- */
1336 1363
1337 1364 #content div.box div.form div.fields div.field div.label-radio
1338 1365 {
1339 1366 padding:0 0 0 5px !important;
1340 1367 }
1341 1368
1342 1369 /* -----------------------------------------------------------
1343 1370 content -> right -> forms -> label (textarea)
1344 1371 ----------------------------------------------------------- */
1345 1372
1346 1373 #content div.box div.form div.fields div.field div.label-textarea
1347 1374 {
1348 1375 padding:0 0 0 5px !important;
1349 1376 }
1350 1377
1351 1378 #content div.box-left div.form div.fields div.field div.label-textarea,
1352 1379 #content div.box-right div.form div.fields div.field div.label-textarea
1353 1380 {
1354 1381 padding: 0 0 8px 0 !important;
1355 1382 }
1356 1383
1357 1384 /* -----------------------------------------------------------
1358 1385 content -> right -> forms -> labels (label)
1359 1386 ----------------------------------------------------------- */
1360 1387
1361 1388 #content div.box div.form div.fields div.field div.label label
1362 1389 {
1363 1390 color: #393939;
1364 1391 font-weight: bold;
1365 1392 }
1366 1393
1367 1394 #content div.box div.form div.fields div.field div.label span
1368 1395 {
1369 1396 margin: 0;
1370 1397 padding: 2px 0 0 0;
1371 1398 height: 1%;
1372 1399 display: block;
1373 1400 color: #363636;
1374 1401 }
1375 1402
1376 1403 /* -----------------------------------------------------------
1377 1404 content -> right -> forms -> input
1378 1405 ----------------------------------------------------------- */
1379 1406
1380 1407 #content div.box div.form div.fields div.field div.input
1381 1408 {
1382 1409 margin: 0 0 0 200px;
1383 1410 padding: 0;
1384 1411 }
1385 1412
1386 1413 #content div.box-left div.form div.fields div.field div.input,
1387 1414 #content div.box-right div.form div.fields div.field div.input
1388 1415 {
1389 1416 margin: 0;
1390 1417 padding: 7px 7px 6px 7px;
1391 1418 clear: both;
1392 1419 overflow: hidden;
1393 1420 border-top: 1px solid #b3b3b3;
1394 1421 border-left: 1px solid #b3b3b3;
1395 1422 border-right: 1px solid #eaeaea;
1396 1423 border-bottom: 1px solid #eaeaea;
1397 1424
1398 1425 }
1399 1426
1400 1427 #content div.box div.form div.fields div.field div.input input
1401 1428 {
1402 1429 margin: 0;
1403 1430 padding: 7px 7px 6px 7px;
1404 1431 background: #FFFFFF;
1405 1432 border-top: 1px solid #b3b3b3;
1406 1433 border-left: 1px solid #b3b3b3;
1407 1434 border-right: 1px solid #eaeaea;
1408 1435 border-bottom: 1px solid #eaeaea;
1409 1436 color: #000000;
1410 1437 font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1411 1438 font-size: 11px;
1412 1439 float: left;
1413 1440 }
1414 1441
1415 1442 #content div.box-left div.form div.fields div.field div.input input,
1416 1443 #content div.box-right div.form div.fields div.field div.input input
1417 1444 {
1418 1445 width: 100%;
1419 1446 padding: 0;
1420 1447 border: none;
1421 1448 }
1422 1449
1423 1450 #content div.box div.form div.fields div.field div.input input.small
1424 1451 {
1425 1452 width: 30%;
1426 1453 }
1427 1454
1428 1455 #content div.box div.form div.fields div.field div.input input.medium
1429 1456 {
1430 1457 width: 55%;
1431 1458 }
1432 1459
1433 1460 #content div.box div.form div.fields div.field div.input input.large
1434 1461 {
1435 1462 width: 85%;
1436 1463 }
1437 1464
1438 1465 #content div.box div.form div.fields div.field div.input input.date
1439 1466 {
1440 1467 width: 177px;
1441 1468 }
1442 1469
1443 1470 #content div.box div.form div.fields div.field div.input input.button
1444 1471 {
1445 1472 margin: 0;
1446 1473 padding: 4px 8px 4px 8px;
1447 1474 background: #D4D0C8;
1448 1475 border-top: 1px solid #FFFFFF;
1449 1476 border-left: 1px solid #FFFFFF;
1450 1477 border-right: 1px solid #404040;
1451 1478 border-bottom: 1px solid #404040;
1452 1479 color: #000000;
1453 1480 }
1454 1481
1455 1482 #content div.box div.form div.fields div.field div.input input.error
1456 1483 {
1457 1484 background: #FBE3E4;
1458 1485 border-top: 1px solid #e1b2b3;
1459 1486 border-left: 1px solid #e1b2b3;
1460 1487 border-right: 1px solid #FBC2C4;
1461 1488 border-bottom: 1px solid #FBC2C4;
1462 1489 }
1463 1490
1464 1491 #content div.box div.form div.fields div.field div.input input.success
1465 1492 {
1466 1493 background: #E6EFC2;
1467 1494 border-top: 1px solid #cebb98;
1468 1495 border-left: 1px solid #cebb98;
1469 1496 border-right: 1px solid #c6d880;
1470 1497 border-bottom: 1px solid #c6d880;
1471 1498 }
1472 1499
1473 1500 #content div.box div.form div.fields div.field div.input img.ui-datepicker-trigger
1474 1501 {
1475 1502 margin: 0 0 0 6px;
1476 1503 }
1477 1504
1478 1505 /* -----------------------------------------------------------
1479 1506 content -> right -> forms -> input (file styling)
1480 1507 ----------------------------------------------------------- */
1481 1508
1482 1509 #content div.box div.form div.fields div.field div.input a.ui-input-file
1483 1510 {
1484 1511 margin: 0 0 0 6px;
1485 1512 padding: 0;
1486 1513 width: 28px;
1487 1514 height: 28px;
1488 1515 display: inline;
1489 1516 position: absolute;
1490 1517 overflow: hidden;
1491 1518 cursor: pointer;
1492 1519 background: #e5e3e3 url("../images/button_browse.png") no-repeat;
1493 1520 border: none;
1494 1521 text-decoration: none;
1495 1522 }
1496 1523
1497 1524 #content div.box div.form div.fields div.field div.input a:hover.ui-input-file
1498 1525 {
1499 1526 background: #e5e3e3 url("../images/button_browse_selected.png") no-repeat;
1500 1527 }
1501 1528
1502 1529 /* -----------------------------------------------------------
1503 1530 content -> right -> forms -> textarea
1504 1531 ----------------------------------------------------------- */
1505 1532
1506 1533 #content div.box div.form div.fields div.field div.textarea
1507 1534 {
1508 1535 margin: 0 0 0 200px;
1509 1536 padding: 10px;
1510 1537 border-top: 1px solid #b3b3b3;
1511 1538 border-left: 1px solid #b3b3b3;
1512 1539 border-right: 1px solid #eaeaea;
1513 1540 border-bottom: 1px solid #eaeaea;
1514 1541 }
1515 1542
1516 1543 #content div.box div.form div.fields div.field div.textarea-editor
1517 1544 {
1518 1545 padding: 0;
1519 1546 border: 1px solid #dddddd;
1520 1547 }
1521 1548
1522 1549 #content div.box-left div.form div.fields div.field div.textarea,
1523 1550 #content div.box-right div.form div.fields div.field div.textarea
1524 1551 {
1525 1552 margin: 0;
1526 1553 }
1527 1554
1528 1555 #content div.box div.form div.fields div.field div.textarea textarea
1529 1556 {
1530 1557 margin: 0;
1531 1558 padding: 0;
1532 1559 width: 100%;
1533 1560 height: 220px;
1534 1561 overflow: hidden;
1535 1562 background: #FFFFFF;
1536 1563 border-width: 0;
1537 1564 color: #000000;
1538 1565 font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1539 1566 font-size: 11px;
1540 1567 outline: none;
1541 1568 }
1542 1569
1543 1570 #content div.box-left div.form div.fields div.field div.textarea textarea,
1544 1571 #content div.box-right div.form div.fields div.field div.textarea textarea
1545 1572 {
1546 1573 width: 100%;
1547 1574 height: 100px;
1548 1575 }
1549 1576
1550 1577 #content div.box div.form div.fields div.field div.textarea textarea.error
1551 1578 {
1552 1579 padding: 3px 10px 10px 23px;
1553 1580 background-color: #FBE3E4;
1554 1581 background-image: url("../../../resources/images/icons/exclamation.png");
1555 1582 background-repeat: no-repeat;
1556 1583 background-position: 3px 3px;
1557 1584 border: 1px solid #FBC2C4;
1558 1585 }
1559 1586
1560 1587 #content div.box div.form div.fields div.field div.textarea textarea.success
1561 1588 {
1562 1589 padding: 3px 10px 10px 23px;
1563 1590 background-color: #E6EFC2;
1564 1591 background-image: url("../../../resources/images/icons/accept.png");
1565 1592 background-repeat: no-repeat;
1566 1593 background-position: 3px 3px;
1567 1594 border: 1px solid #C6D880;
1568 1595 }
1569 1596
1570 1597 /* -----------------------------------------------------------
1571 1598 content -> right -> forms -> textarea (tinymce editor)
1572 1599 ----------------------------------------------------------- */
1573 1600
1574 1601 #content div.box div.form div.fields div.field div.textarea table
1575 1602 {
1576 1603 margin: 0;
1577 1604 padding: 0;
1578 1605 width: 100%;
1579 1606 border: none;
1580 1607 }
1581 1608
1582 1609 #content div.box div.form div.fields div.field div.textarea table td
1583 1610 {
1584 1611 padding: 0;
1585 1612 background: #DDDDDD;
1586 1613 border: none;
1587 1614 }
1588 1615
1589 1616 #content div.box div.form div.fields div.field div.textarea table td table
1590 1617 {
1591 1618 margin: 0;
1592 1619 padding: 0;
1593 1620 width: auto;
1594 1621 border: none;
1595 1622 }
1596 1623
1597 1624 #content div.box div.form div.fields div.field div.textarea table td table td
1598 1625 {
1599 1626 padding: 5px 5px 5px 0;
1600 1627 font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1601 1628 font-size: 11px;
1602 1629 }
1603 1630
1604 1631 #content div.box div.form div.fields div.field div.textarea table td table td a
1605 1632 {
1606 1633 border: none;
1607 1634 }
1608 1635
1609 1636 #content div.box div.form div.fields div.field div.textarea table td table td a.mceButtonActive
1610 1637 {
1611 1638 background: #b1b1b1;
1612 1639 }
1613 1640
1614 1641 /* -----------------------------------------------------------
1615 1642 content -> right -> forms -> select
1616 1643 ----------------------------------------------------------- */
1617 1644
1618 1645 #content div.box div.form div.fields div.field div.select
1619 1646 {
1620 1647 margin: 0 0 0 200px;
1621 1648 padding: 0;
1622 1649 }
1623 1650
1624 1651 #content div.box div.form div.fields div.field div.select a:hover
1625 1652 {
1626 1653 color: #000000;
1627 1654 text-decoration: none;
1628 1655 }
1629 1656
1630 1657 #content div.box div.form div.fields div.field div.select select
1631 1658 {
1632 1659 margin: 0;
1633 1660 }
1634 1661
1635 1662 /* -----------------------------------------------------------
1636 1663 content -> right -> forms -> select (jquery styling)
1637 1664 ----------------------------------------------------------- */
1638 1665
1639 1666 #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus
1640 1667 {
1641 1668 border: 1px solid #666666;
1642 1669 }
1643 1670
1644 1671 #content div.box div.form div.fields div.field div.select a.ui-selectmenu
1645 1672 {
1646 1673 color: #565656;
1647 1674 text-decoration: none;
1648 1675 }
1649 1676
1650 1677 #content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover
1651 1678 {
1652 1679 color: #000000;
1653 1680 text-decoration: none;
1654 1681 }
1655 1682
1656 1683 #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus span.ui-icon
1657 1684 {
1658 1685 background-image: url(../images/ui/ui-icons_222222_256x240.png);
1659 1686 }
1660 1687
1661 1688 /* -----------------------------------------------------------
1662 1689 content -> right -> forms -> element focus
1663 1690 ----------------------------------------------------------- */
1664 1691
1665 1692 #content div.box div.form div.fields div.field input[type=text]:focus,
1666 1693 #content div.box div.form div.fields div.field input[type=password]:focus,
1667 1694 #content div.box div.form div.fields div.field input[type=file]:focus,
1668 1695 #content div.box div.form div.fields div.field textarea:focus,
1669 1696 #content div.box div.form div.fields div.field select:focus
1670 1697 {
1671 1698 background: #f6f6f6;
1672 1699 border-color: #666;
1673 1700 }
1674 1701
1675 1702 /* -----------------------------------------------------------
1676 1703 content -> right -> forms -> checkboxes
1677 1704 ----------------------------------------------------------- */
1678 1705
1679 1706 #content div.box div.form div.fields div.field div.checkboxes
1680 1707 {
1681 1708 margin: 0 0 0 200px;
1682 1709 padding: 0;
1683 1710 }
1684 1711
1685 1712 #content div.box div.form div.fields div.field div.checkboxes div.checkbox
1686 1713 {
1687 1714 margin: 0;
1688 1715 padding: 2px 0 2px 0;
1689 1716 clear: both;
1690 1717 overflow: hidden;
1691 1718 }
1692 1719
1693 1720 #content div.box div.form div.fields div.field div.checkboxes div.checkbox input
1694 1721 {
1695 1722 margin: 0;
1696 1723 float: left;
1697 1724 }
1698 1725
1699 1726 #content div.box div.form div.fields div.field div.checkboxes div.checkbox label
1700 1727 {
1701 1728 margin: 3px 0 0 4px;
1702 1729 height: 1%;
1703 1730 display: block;
1704 1731 float: left;
1705 1732 }
1706 1733
1707 1734 /* -----------------------------------------------------------
1708 1735 content -> right -> forms -> radios
1709 1736 ----------------------------------------------------------- */
1710 1737
1711 1738 #content div.box div.form div.fields div.field div.radios
1712 1739 {
1713 1740 margin: 0 0 0 200px;
1714 1741 padding: 0;
1715 1742 }
1716 1743
1717 1744 #content div.box div.form div.fields div.field div.radios div.radio
1718 1745 {
1719 1746 margin: 0;
1720 1747 padding: 2px 0 2px 0;
1721 1748 clear: both;
1722 1749 overflow: hidden;
1723 1750 }
1724 1751
1725 1752 #content div.box div.form div.fields div.field div.radios div.radio input
1726 1753 {
1727 1754 margin: 0;
1728 1755 float: left;
1729 1756 }
1730 1757
1731 1758 #content div.box div.form div.fields div.field div.radios div.radio label
1732 1759 {
1733 1760 margin: 3px 0 0 4px;
1734 1761 height: 1%;
1735 1762 display: block;
1736 1763 float: left;
1737 1764 }
1738 1765 /* -----------------------------------------------------------
1739 1766 content -> right -> forms -> button
1740 1767 ----------------------------------------------------------- */
1741 1768
1742 1769 div.form div.fields div.field div.button
1743 1770 {
1744 1771 margin: 0;
1745 1772 padding: 0 0 0 8px;
1746 1773 float: left;
1747 1774 }
1748 1775
1749 1776 div.form div.fields div.field div.button input
1750 1777 {
1751 1778 margin: 0;
1752 1779 color: #000000;
1753 1780 font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1754 1781 font-size: 11px;
1755 1782 font-weight: bold;
1756 1783 }
1757 1784
1758 1785 div.form div.fields div.field div.button .ui-state-default
1759 1786 {
1760 1787 margin: 0;
1761 1788 padding: 6px 12px 6px 12px;
1762 1789 background: #e5e3e3 url("../images/button.png") repeat-x;
1763 1790 border-top: 1px solid #DDDDDD;
1764 1791 border-left: 1px solid #c6c6c6;
1765 1792 border-right: 1px solid #DDDDDD;
1766 1793 border-bottom: 1px solid #c6c6c6;
1767 1794 color: #515151;
1768 1795 outline: none;
1769 1796 }
1770 1797
1771 1798 div.form div.fields div.field div.button .ui-state-hover
1772 1799 {
1773 1800 margin: 0;
1774 1801 padding: 6px 12px 6px 12px;
1775 1802 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
1776 1803 border-top: 1px solid #cccccc;
1777 1804 border-left: 1px solid #bebebe;
1778 1805 border-right: 1px solid #b1b1b1;
1779 1806 border-bottom: 1px solid #afafaf;
1780 1807 color: #515151;
1781 1808 outline: none;
1782 1809 }
1783 1810
1784 1811 div.form div.fields div.field div.highlight
1785 1812 {
1786 1813 display: inline;
1787 1814 }
1788 1815
1789 1816 div.form div.fields div.field div.highlight .ui-state-default
1790 1817 {
1791 1818 margin: 0;
1792 1819 padding: 6px 12px 6px 12px;
1793 1820 background: #4e85bb url("../images/colors/blue/button_highlight.png") repeat-x;
1794 1821 border-top: 1px solid #5c91a4;
1795 1822 border-left: 1px solid #2a6f89;
1796 1823 border-right: 1px solid #2b7089;
1797 1824 border-bottom: 1px solid #1a6480;
1798 1825 color: #FFFFFF;
1799 1826 }
1800 1827
1801 1828 div.form div.fields div.field div.highlight .ui-state-hover
1802 1829 {
1803 1830 margin: 0;
1804 1831 padding: 6px 12px 6px 12px;
1805 1832 background: #46a0c1 url("../images/colors/blue/button_highlight_selected.png") repeat-x;
1806 1833 border-top: 1px solid #78acbf;
1807 1834 border-left: 1px solid #34819e;
1808 1835 border-right: 1px solid #35829f;
1809 1836 border-bottom: 1px solid #257897;
1810 1837 color: #FFFFFF;
1811 1838 }
1812 1839
1813 1840
1814 1841 /* -----------------------------------------------------------
1815 1842 content -> right -> forms -> buttons
1816 1843 ----------------------------------------------------------- */
1817 1844
1818 1845 #content div.box div.form div.fields div.buttons
1819 1846 {
1820 1847 margin: 10px 0 0 200px;
1821 1848 padding: 0;
1822 1849 }
1823 1850
1824 1851 #content div.box-left div.form div.fields div.buttons,
1825 1852 #content div.box-right div.form div.fields div.buttons
1826 1853 {
1827 1854 margin: 10px 0 0 0;
1828 1855 }
1829 1856
1830 1857 #content div.box div.form div.fields div.buttons input
1831 1858 {
1832 1859 margin: 0;
1833 1860 color: #000000;
1834 1861 font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1835 1862 font-size: 11px;
1836 1863 font-weight: bold;
1837 1864 }
1838 1865 /* -----------------------------------------------------------
1839 1866 content -> right -> forms -> buttons
1840 1867 ----------------------------------------------------------- */
1841 1868
1842 1869 div.form div.fields div.buttons
1843 1870 {
1844 1871 margin: 10px 0 0 200px;
1845 1872 padding: 0;
1846 1873 }
1847 1874
1848 1875 div.box-left div.form div.fields div.buttons,
1849 1876 div.box-right div.form div.fields div.buttons
1850 1877 {
1851 1878 margin: 10px 0 0 0;
1852 1879 }
1853 1880
1854 1881 div.form div.fields div.buttons input
1855 1882 {
1856 1883 margin: 0;
1857 1884 color: #000000;
1858 1885 font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1859 1886 font-size: 11px;
1860 1887 font-weight: bold;
1861 1888 }
1862 1889
1863 1890 /* -----------------------------------------------------------
1864 1891 content -> right -> forms -> buttons (jquery styling)
1865 1892 ----------------------------------------------------------- */
1866 1893
1867 1894 #content div.box div.form div.fields div.buttons input.ui-state-default
1868 1895 {
1869 1896 margin: 0;
1870 1897 padding: 6px 12px 6px 12px;
1871 1898 background: #e5e3e3 url("../images/button.png") repeat-x;
1872 1899 border-top: 1px solid #DDDDDD;
1873 1900 border-left: 1px solid #c6c6c6;
1874 1901 border-right: 1px solid #DDDDDD;
1875 1902 border-bottom: 1px solid #c6c6c6;
1876 1903 color: #515151;
1877 1904 outline: none;
1878 1905 }
1879 1906
1880 1907 #content div.box div.form div.fields div.buttons input.ui-state-hover
1881 1908 {
1882 1909 margin: 0;
1883 1910 padding: 6px 12px 6px 12px;
1884 1911 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
1885 1912 border-top: 1px solid #cccccc;
1886 1913 border-left: 1px solid #bebebe;
1887 1914 border-right: 1px solid #b1b1b1;
1888 1915 border-bottom: 1px solid #afafaf;
1889 1916 color: #515151;
1890 1917 outline: none;
1891 1918 }
1892 1919
1893 1920 #content div.box div.form div.fields div.buttons div.highlight
1894 1921 {
1895 1922 display: inline;
1896 1923 }
1897 1924
1898 1925 #content div.box div.form div.fields div.buttons div.highlight input.ui-state-default
1899 1926 {
1900 1927 margin: 0;
1901 1928 padding: 6px 12px 6px 12px;
1902 1929 background: #4e85bb url("../images/colors/blue/button_highlight.png") repeat-x;
1903 1930 border-top: 1px solid #5c91a4;
1904 1931 border-left: 1px solid #2a6f89;
1905 1932 border-right: 1px solid #2b7089;
1906 1933 border-bottom: 1px solid #1a6480;
1907 1934 color: #FFFFFF;
1908 1935 }
1909 1936
1910 1937 #content div.box div.form div.fields div.buttons div.highlight input.ui-state-hover
1911 1938 {
1912 1939 margin: 0;
1913 1940 padding: 6px 12px 6px 12px;
1914 1941 background: #46a0c1 url("../images/colors/blue/button_highlight_selected.png") repeat-x;
1915 1942 border-top: 1px solid #78acbf;
1916 1943 border-left: 1px solid #34819e;
1917 1944 border-right: 1px solid #35829f;
1918 1945 border-bottom: 1px solid #257897;
1919 1946 color: #FFFFFF;
1920 1947 }
1921 1948
1922 1949 /* -----------------------------------------------------------
1923 1950 content -> right -> box / tables
1924 1951 ----------------------------------------------------------- */
1925 1952
1926 1953 #content div.box div.table
1927 1954 {
1928 1955 margin: 0;
1929 1956 padding: 0 20px 10px 20px;
1930 1957 clear: both;
1931 1958 overflow: hidden;
1932 1959 }
1933 1960
1934 1961 #content div.box table
1935 1962 {
1936 1963 margin: 0;
1937 1964 padding: 0;
1938 1965 width: 100%;
1939 1966 border-collapse: collapse;
1940 1967 }
1941 1968
1942 1969 #content div.box table th
1943 1970 {
1944 1971 padding: 10px;
1945 1972 background: #eeeeee;
1946 1973 border-bottom: 1px solid #dddddd;
1947 1974 }
1948 1975
1949 1976 #content div.box table th.left
1950 1977 {
1951 1978 text-align: left;
1952 1979 }
1953 1980
1954 1981 #content div.box table th.right
1955 1982 {
1956 1983 text-align: right;
1957 1984 }
1958 1985
1959 1986 #content div.box table th.center
1960 1987 {
1961 1988 text-align: center;
1962 1989 }
1963 1990
1964 1991 #content div.box table th.selected
1965 1992 {
1966 1993 padding: 0;
1967 1994 vertical-align: middle;
1968 1995 }
1969 1996
1970 1997 #content div.box table th.selected input
1971 1998 {
1972 1999 margin: 0;
1973 2000 }
1974 2001
1975 2002 #content div.box table td
1976 2003 {
1977 2004 padding: 5px;
1978 2005 background: #ffffff;
1979 2006 border-bottom: 1px solid #cdcdcd;
1980 2007 vertical-align:middle;
1981 2008 }
1982 2009
1983 2010 #content div.box table tr.selected td
1984 2011 {
1985 2012 background: #FFFFCC;
1986 2013 }
1987 2014
1988 2015 #content div.box table td.selected
1989 2016 {
1990 2017 padding: 0;
1991 2018 width: 3%;
1992 2019 text-align: center;
1993 2020 vertical-align: middle;
1994 2021 }
1995 2022
1996 2023 #content div.box table td.selected input
1997 2024 {
1998 2025 margin: 0;
1999 2026 }
2000 2027
2001 2028 #content div.box table td.action
2002 2029 {
2003 2030 width: 45%;
2004 2031 text-align: left;
2005 2032 }
2006 2033
2007 2034 #content div.box table td.user
2008 2035 {
2009 2036 width: 10%;
2010 2037 text-align: center;
2011 2038 }
2012 2039
2013 2040 #content div.box table td.date
2014 2041 {
2015 2042 width: 33%;
2016 2043 text-align: center;
2017 2044 }
2018 2045
2019 2046 #content div.box table td.address
2020 2047 {
2021 2048 width: 10%;
2022 2049 text-align: center;
2023 2050 }
2024 2051
2025 2052 /* -----------------------------------------------------------
2026 2053 content -> right -> box / table action
2027 2054 ----------------------------------------------------------- */
2028 2055
2029 2056 #content div.box div.action
2030 2057 {
2031 2058 margin: 10px 0 0 0;
2032 2059 padding: 0;
2033 2060 float: right;
2034 2061 background: #FFFFFF;
2035 2062 text-align: right;
2036 2063 }
2037 2064
2038 2065 #content div.box div.action a:hover
2039 2066 {
2040 2067 color: #000000;
2041 2068 text-decoration: none;
2042 2069 }
2043 2070
2044 2071 #content div.box div.action select
2045 2072 {
2046 2073 margin: 0;
2047 2074 font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
2048 2075 font-size: 11px;
2049 2076 }
2050 2077
2051 2078 #content div.box div.action div.button
2052 2079 {
2053 2080 margin: 6px 0 0 0;
2054 2081 padding: 0;
2055 2082 text-align: right;
2056 2083 }
2057 2084
2058 2085 #content div.box div.action div.button input
2059 2086 {
2060 2087 margin: 0;
2061 2088 color: #000000;
2062 2089 font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
2063 2090 font-size: 11px;
2064 2091 font-weight: bold;
2065 2092 }
2066 2093
2067 2094 #content div.box div.action div.button input.ui-state-default
2068 2095 {
2069 2096 margin: 0;
2070 2097 padding: 6px 12px 6px 12px;
2071 2098 background: #e5e3e3 url("../images/button.png") repeat-x;
2072 2099 border-top: 1px solid #DDDDDD;
2073 2100 border-left: 1px solid #c6c6c6;
2074 2101 border-right: 1px solid #DDDDDD;
2075 2102 border-bottom: 1px solid #c6c6c6;
2076 2103 color: #515151;
2077 2104 }
2078 2105
2079 2106 #content div.box div.action div.button input.ui-state-hover
2080 2107 {
2081 2108 margin: 0;
2082 2109 padding: 6px 12px 6px 12px;
2083 2110 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
2084 2111 border-top: 1px solid #cccccc;
2085 2112 border-left: 1px solid #bebebe;
2086 2113 border-right: 1px solid #b1b1b1;
2087 2114 border-bottom: 1px solid #afafaf;
2088 2115 color: #515151;
2089 2116 }
2090 2117
2091 2118 #content div.box div.action .ui-selectmenu
2092 2119 {
2093 2120 margin: 0;
2094 2121 padding: 0;
2095 2122 }
2096 2123
2097 2124 #content div.box div.action a.ui-selectmenu-focus
2098 2125 {
2099 2126 border: 1px solid #666666;
2100 2127 }
2101 2128
2102 2129 #content div.box div.action a.ui-selectmenu-focus span.ui-icon
2103 2130 {
2104 2131 background-image: url(../images/ui/ui-icons_222222_256x240.png);
2105 2132 }
2106 2133
2107 2134 /* -----------------------------------------------------------
2108 2135 content -> right -> pagination
2109 2136 ----------------------------------------------------------- */
2110 2137
2111 2138 #content div.box div.pagination
2112 2139 {
2113 2140 margin: 10px 0 0 0;
2114 2141 padding: 0;
2115 2142 height: 1%;
2116 2143 clear: both;
2117 2144 overflow: hidden;
2118 2145 }
2119 2146
2120 2147 #content div.box div.pagination div.results
2121 2148 {
2122 2149 margin: 0;
2123 2150 padding: 0;
2124 2151 text-align: left;
2125 2152 float: left
2126 2153 }
2127 2154
2128 2155 #content div.box div.pagination div.results span
2129 2156 {
2130 2157 margin: 0;
2131 2158 padding: 6px 8px 6px 8px;
2132 2159 height: 1%;
2133 2160 display: block;
2134 2161 float: left;
2135 2162 background: #ebebeb url("../images/pager.png") repeat-x;
2136 2163 border-top: 1px solid #dedede;
2137 2164 border-left: 1px solid #cfcfcf;
2138 2165 border-right: 1px solid #c4c4c4;
2139 2166 border-bottom: 1px solid #c4c4c4;
2140 2167 color: #4A4A4A;
2141 2168 font-weight: bold;
2142 2169 }
2143 2170
2144 2171 #content div.box div.pagination ul.pager
2145 2172 {
2146 2173 margin: 0;
2147 2174 padding: 0;
2148 2175 float: right;
2149 2176 text-align: right;
2150 2177 }
2151 2178
2152 2179 #content div.box div.pagination ul.pager li
2153 2180 {
2154 2181 margin: 0 0 0 4px;
2155 2182 padding: 0;
2156 2183 height: 1%;
2157 2184 float: left;
2158 2185 list-style: none;
2159 2186 background: #ebebeb url("../images/pager.png") repeat-x;
2160 2187 border-top: 1px solid #dedede;
2161 2188 border-left: 1px solid #cfcfcf;
2162 2189 border-right: 1px solid #c4c4c4;
2163 2190 border-bottom: 1px solid #c4c4c4;
2164 2191 color: #4A4A4A;
2165 2192 font-weight: bold;
2166 2193 }
2167 2194
2168 2195 #content div.box div.pagination ul.pager li.separator
2169 2196 {
2170 2197 padding: 6px;
2171 2198 }
2172 2199
2173 2200 #content div.box div.pagination ul.pager li.current
2174 2201 {
2175 2202 padding: 6px;
2176 2203 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
2177 2204 border-top: 1px solid #cccccc;
2178 2205 border-left: 1px solid #bebebe;
2179 2206 border-right: 1px solid #b1b1b1;
2180 2207 border-bottom: 1px solid #afafaf;
2181 2208 color: #515151;
2182 2209 }
2183 2210
2184 2211 #content div.box div.pagination ul.pager li.disabled
2185 2212 {
2186 2213 padding: 6px;
2187 2214 color: #B4B4B4;
2188 2215 }
2189 2216
2190 2217 #content div.box div.pagination ul.pager li a
2191 2218 {
2192 2219 margin: 0;
2193 2220 padding: 6px;
2194 2221 height: 1%;
2195 2222 display: block;
2196 2223 float: left;
2197 2224 color: #515151;
2198 2225 text-decoration: none;
2199 2226 }
2200 2227
2201 2228 #content div.box div.pagination ul.pager li a:hover,
2202 2229 #content div.box div.pagination ul.pager li a:active
2203 2230 {
2204 2231 margin: -1px;
2205 2232 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
2206 2233 border-top: 1px solid #cccccc;
2207 2234 border-left: 1px solid #bebebe;
2208 2235 border-right: 1px solid #b1b1b1;
2209 2236 border-bottom: 1px solid #afafaf;
2210 2237 }
2211 2238
2212 2239 /* -----------------------------------------------------------
2213 2240 content -> webhelpers pagination
2214 2241 ----------------------------------------------------------- */
2215 2242
2216 2243 #content div.box div.pagination-wh
2217 2244 {
2218 2245 margin: 10px 0 0 0;
2219 2246 padding: 0;
2220 2247 height: 1%;
2221 2248 clear: both;
2222 2249 overflow: hidden;
2223 2250 text-align: right;
2224 2251 }
2225 2252
2226 2253 #content div.box div.pagination-wh div.results
2227 2254 {
2228 2255 margin: 0;
2229 2256 padding: 0;
2230 2257 text-align: left;
2231 2258 float: left
2232 2259 }
2233 2260
2234 2261 #content div.box div.pagination-wh div.results span
2235 2262 {
2236 2263 margin: 0;
2237 2264 padding: 6px 8px 6px 8px;
2238 2265 height: 1%;
2239 2266 display: block;
2240 2267 float: left;
2241 2268 background: #ebebeb url("../images/pager.png") repeat-x;
2242 2269 border-top: 1px solid #dedede;
2243 2270 border-left: 1px solid #cfcfcf;
2244 2271 border-right: 1px solid #c4c4c4;
2245 2272 border-bottom: 1px solid #c4c4c4;
2246 2273 color: #4A4A4A;
2247 2274 font-weight: bold;
2248 2275 }
2249 2276
2250 2277 #content div.box div.pagination-left{
2251 2278 float:left;
2252 2279 }
2253 2280 #content div.box div.pagination-right{
2254 2281 float:right;
2255 2282 }
2256 2283
2257 2284 #content div.box div.pagination-wh a,
2258 2285 #content div.box div.pagination-wh span.pager_dotdot
2259 2286 {
2260 2287 margin: 0 0 0 4px;
2261 2288 padding: 6px;
2262 2289 height: 1%;
2263 2290 float: left;
2264 2291 background: #ebebeb url("../images/pager.png") repeat-x;
2265 2292 border-top: 1px solid #dedede;
2266 2293 border-left: 1px solid #cfcfcf;
2267 2294 border-right: 1px solid #c4c4c4;
2268 2295 border-bottom: 1px solid #c4c4c4;
2269 2296 color: #4A4A4A;
2270 2297 font-weight: bold;
2271 2298 }
2272 2299 #content div.box div.pagination-wh span.pager_curpage
2273 2300 {
2274 2301 margin: 0 0 0 4px;
2275 2302 padding: 6px;
2276 2303 height: 1%;
2277 2304 float: left;
2278 2305 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
2279 2306 border-top: 1px solid #cccccc;
2280 2307 border-left: 1px solid #bebebe;
2281 2308 border-right: 1px solid #b1b1b1;
2282 2309 border-bottom: 1px solid #afafaf;
2283 2310 color: #515151;
2284 2311 font-weight: bold;
2285 2312 }
2286 2313
2287 2314 #content div.box div.pagination-wh a.disabled
2288 2315 {
2289 2316 padding: 6px;
2290 2317 color: #B4B4B4;
2291 2318 }
2292 2319
2293 2320
2294 2321 #content div.box div.pagination-wh a:hover,
2295 2322 #content div.box div.pagination-wh a:active
2296 2323 {
2297 2324 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
2298 2325 border-top: 1px solid #cccccc;
2299 2326 border-left: 1px solid #bebebe;
2300 2327 border-right: 1px solid #b1b1b1;
2301 2328 border-bottom: 1px solid #afafaf;
2302 2329 text-decoration: none;
2303 2330 }
2304 2331
2305 2332
2306 2333 /* -----------------------------------------------------------
2307 2334 content -> right -> traffic chart
2308 2335 ----------------------------------------------------------- */
2309 2336
2310 2337 #content div.box div.traffic
2311 2338 {
2312 2339 margin: 0;
2313 2340 padding: 0 20px 10px 20px;
2314 2341 clear: both;
2315 2342 overflow: hidden;
2316 2343 }
2317 2344
2318 2345 #content div.box div.traffic div.legend
2319 2346 {
2320 2347 margin: 0 0 10px 0;
2321 2348 padding: 0 0 10px 0;
2322 2349 clear: both;
2323 2350 overflow: hidden;
2324 2351 border-bottom: 1px solid #dddddd;
2325 2352 }
2326 2353
2327 2354 #content div.box div.traffic div.legend h6
2328 2355 {
2329 2356 margin: 0;
2330 2357 padding: 0;
2331 2358 float: left;
2332 2359 border: none;
2333 2360 }
2334 2361
2335 2362 #content div.box div.traffic div.legend ul
2336 2363 {
2337 2364 margin: 0;
2338 2365 padding: 0;
2339 2366 float: right;
2340 2367 }
2341 2368
2342 2369 #content div.box div.traffic div.legend li
2343 2370 {
2344 2371 margin: 0;
2345 2372 padding: 0 8px 0 4px;
2346 2373 list-style: none;
2347 2374 float: left;
2348 2375 font-size: 11px;
2349 2376 }
2350 2377
2351 2378 #content div.box div.traffic div.legend li.visits
2352 2379 {
2353 2380 border-left: 12px solid #edc240;
2354 2381 }
2355 2382
2356 2383 #content div.box div.traffic div.legend li.pageviews
2357 2384 {
2358 2385 border-left: 12px solid #afd8f8;
2359 2386 }
2360 2387
2361 2388 #content div.box div.traffic table
2362 2389 {
2363 2390 width: auto;
2364 2391 }
2365 2392
2366 2393 #content div.box div.traffic table td
2367 2394 {
2368 2395 padding: 2px 3px 3px 3px;
2369 2396 background: transparent;
2370 2397 border: none;
2371 2398 }
2372 2399
2373 2400 #content div.box div.traffic table td.legendLabel
2374 2401 {
2375 2402 padding: 0 3px 2px 3px;
2376 2403 }
2377 2404
2378 2405 /* -----------------------------------------------------------
2379 2406 footer
2380 2407 ----------------------------------------------------------- */
2381 2408
2382 2409 #footer
2383 2410 {
2384 2411 margin: 0;
2385 2412 padding: 5px 0 5px 0;
2386 2413 clear: both;
2387 2414 overflow: hidden;
2388 2415 background: #2a2a2a;
2389 2416 text-align: right;
2390 2417 }
2391 2418
2392 2419 #footer p
2393 2420 {
2394 2421 margin: 0 80px 0 80px;
2395 2422 padding: 10px 0 10px 0;
2396 2423 color: #ffffff;
2397 2424 }
2398 2425
2399 2426 /* -----------------------------------------------------------
2400 2427 login
2401 2428 ----------------------------------------------------------- */
2402 2429
2403 2430 #login
2404 2431 {
2405 2432 margin: 10% auto 0 auto;
2406 2433 padding: 0;
2407 2434 width: 420px;
2408 2435 }
2409 2436
2410 2437 /* -----------------------------------------------------------
2411 2438 login -> colors
2412 2439 ----------------------------------------------------------- */
2413 2440
2414 2441 #login div.color
2415 2442 {
2416 2443 margin: 10px auto 0 auto;
2417 2444 padding: 3px 3px 3px 0;
2418 2445 clear: both;
2419 2446 overflow: hidden;
2420 2447 background: #FFFFFF;
2421 2448 }
2422 2449
2423 2450 #login div.color a
2424 2451 {
2425 2452 margin: 0 0 0 3px;
2426 2453 padding: 0;
2427 2454 width: 20px;
2428 2455 height: 20px;
2429 2456 display: block;
2430 2457 float: left;
2431 2458 }
2432 2459
2433 2460 /* -----------------------------------------------------------
2434 2461 login -> title
2435 2462 ----------------------------------------------------------- */
2436 2463
2437 2464 #login div.title
2438 2465 {
2439 2466 margin: 0 auto;
2440 2467 padding: 0;
2441 2468 width: 420px;
2442 2469 clear: both;
2443 2470 overflow: hidden;
2444 2471 position: relative;
2445 2472 background: #003367 url("../images/colors/blue/header_inner.png") repeat-x;
2446 2473 }
2447 2474
2448 2475 #login div.title h5
2449 2476 {
2450 2477 margin: 10px;
2451 2478 padding: 0;
2452 2479 color: #ffffff;
2453 2480 }
2454 2481
2455 2482 /* -----------------------------------------------------------
2456 2483 login -> title / corners
2457 2484 ----------------------------------------------------------- */
2458 2485
2459 2486 #login div.title div.corner
2460 2487 {
2461 2488 height: 6px;
2462 2489 width: 6px;
2463 2490 position: absolute;
2464 2491 background: url("../images/colors/blue/login_corners.png") no-repeat;
2465 2492 }
2466 2493
2467 2494 #login div.title div.tl
2468 2495 {
2469 2496 top: 0;
2470 2497 left: 0;
2471 2498 background-position: 0 0;
2472 2499 }
2473 2500
2474 2501 #login div.title div.tr
2475 2502 {
2476 2503 top: 0;
2477 2504 right: 0;
2478 2505 background-position: -6px 0;
2479 2506 }
2480 2507
2481 2508 #login div.inner
2482 2509 {
2483 2510 margin: 0 auto;
2484 2511 padding: 20px;
2485 2512 width: 380px;
2486 2513 background: #FFFFFF url("../images/login.png") no-repeat top left;
2487 2514 border-top: none;
2488 2515 border-bottom: none;
2489 2516 }
2490 2517
2491 2518 /* -----------------------------------------------------------
2492 2519 login -> form
2493 2520 ----------------------------------------------------------- */
2494 2521
2495 2522 #login div.form
2496 2523 {
2497 2524 margin: 0;
2498 2525 padding: 0;
2499 2526 clear: both;
2500 2527 overflow: hidden;
2501 2528 }
2502 2529
2503 2530 #login div.form div.fields
2504 2531 {
2505 2532 margin: 0;
2506 2533 padding: 0;
2507 2534 clear: both;
2508 2535 overflow: hidden;
2509 2536 }
2510 2537
2511 2538 #login div.form div.fields div.field
2512 2539 {
2513 2540 margin: 0;
2514 2541 padding: 0 0 10px 0;
2515 2542 clear: both;
2516 2543 overflow: hidden;
2517 2544 }
2518 2545
2519 2546 #login div.form div.fields div.field span.error-message
2520 2547 {
2521 2548 margin: 8px 0 0 0;
2522 2549 padding: 0;
2523 2550 height: 1%;
2524 2551 display: block;
2525 2552 color: #FF0000;
2526 2553 }
2527 2554
2528 2555 #login div.form div.fields div.field div.label
2529 2556 {
2530 2557 margin: 2px 10px 0 0;
2531 2558 padding: 5px 0 0 5px;
2532 2559 width: 173px;
2533 2560 float: left;
2534 2561 text-align: right;
2535 2562 }
2536 2563
2537 2564 #login div.form div.fields div.field div.label label
2538 2565 {
2539 2566 color: #000000;
2540 2567 font-weight: bold;
2541 2568 }
2542 2569
2543 2570 #login div.form div.fields div.field div.label span
2544 2571 {
2545 2572 margin: 0;
2546 2573 padding: 2px 0 0 0;
2547 2574 height: 1%;
2548 2575 display: block;
2549 2576 color: #363636;
2550 2577 }
2551 2578
2552 2579 #login div.form div.fields div.field div.input
2553 2580 {
2554 2581 margin: 0;
2555 2582 padding: 0;
2556 2583 float: left;
2557 2584 }
2558 2585
2559 2586 #login div.form div.fields div.field div.input input
2560 2587 {
2561 2588 margin: 0;
2562 2589 padding: 7px 7px 6px 7px;
2563 2590 width: 176px;
2564 2591 background: #FFFFFF;
2565 2592 border-top: 1px solid #b3b3b3;
2566 2593 border-left: 1px solid #b3b3b3;
2567 2594 border-right: 1px solid #eaeaea;
2568 2595 border-bottom: 1px solid #eaeaea;
2569 2596 color: #000000;
2570 2597 font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
2571 2598 font-size: 11px;
2572 2599 }
2573 2600
2574 2601 #login div.form div.fields div.field div.input input.error
2575 2602 {
2576 2603 background: #FBE3E4;
2577 2604 border-top: 1px solid #e1b2b3;
2578 2605 border-left: 1px solid #e1b2b3;
2579 2606 border-right: 1px solid #FBC2C4;
2580 2607 border-bottom: 1px solid #FBC2C4;
2581 2608 }
2582 2609
2583 2610 #login div.form div.fields div.field div.input input.success
2584 2611 {
2585 2612 background: #E6EFC2;
2586 2613 border-top: 1px solid #cebb98;
2587 2614 border-left: 1px solid #cebb98;
2588 2615 border-right: 1px solid #c6d880;
2589 2616 border-bottom: 1px solid #c6d880;
2590 2617 }
2591 2618
2592 2619 #login div.form div.fields div.field div.input div.link
2593 2620 {
2594 2621 margin: 6px 0 0 0;
2595 2622 padding: 0;
2596 2623 text-align: right;
2597 2624 }
2598 2625
2599 2626 #login div.form div.fields div.field div.checkbox
2600 2627 {
2601 2628 margin: 0 0 0 184px;
2602 2629 padding: 0;
2603 2630 }
2604 2631
2605 2632 #login div.form div.fields div.field div.checkbox label
2606 2633 {
2607 2634 color: #565656;
2608 2635 font-weight: bold;
2609 2636 }
2610 2637
2611 2638 #login div.form div.fields div.buttons
2612 2639 {
2613 2640 margin: 0;
2614 2641 padding: 10px 0 0 0;
2615 2642 clear: both;
2616 2643 overflow: hidden;
2617 2644 border-top: 1px solid #DDDDDD;
2618 2645 text-align: right;
2619 2646 }
2620 2647
2621 2648 #login div.form div.fields div.buttons input
2622 2649 {
2623 2650 margin: 0;
2624 2651 color: #000000;
2625 2652 font-size: 1.0em;
2626 2653 font-weight: bold;
2627 2654 font-family: Verdana, Helvetica, Sans-Serif;
2628 2655 }
2629 2656
2630 2657 #login div.form div.fields div.buttons input.ui-state-default
2631 2658 {
2632 2659 margin: 0;
2633 2660 padding: 6px 12px 6px 12px;
2634 2661 background: #e5e3e3 url("../images/button.png") repeat-x;
2635 2662 border-top: 1px solid #DDDDDD;
2636 2663 border-left: 1px solid #c6c6c6;
2637 2664 border-right: 1px solid #DDDDDD;
2638 2665 border-bottom: 1px solid #c6c6c6;
2639 2666 color: #515151;
2640 2667 }
2641 2668
2642 2669 #login div.form div.fields div.buttons input.ui-state-hover
2643 2670 {
2644 2671 margin: 0;
2645 2672 padding: 6px 12px 6px 12px;
2646 2673 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
2647 2674 border-top: 1px solid #cccccc;
2648 2675 border-left: 1px solid #bebebe;
2649 2676 border-right: 1px solid #b1b1b1;
2650 2677 border-bottom: 1px solid #afafaf;
2651 2678 color: #515151;
2652 2679 }
2653 2680
2654 2681 /* -----------------------------------------------------------
2655 2682 login -> links
2656 2683 ----------------------------------------------------------- */
2657 2684
2658 2685 #login div.form div.links
2659 2686 {
2660 2687 margin: 10px 0 0 0;
2661 2688 padding: 0 0 2px 0;
2662 2689 clear: both;
2663 2690 overflow: hidden;
2664 2691 }
2665 2692
2666 2693 /* -----------------------------------------------------------
2667 2694 register
2668 2695 ----------------------------------------------------------- */
2669 2696
2670 2697 #register
2671 2698 {
2672 2699 margin: 10% auto 0 auto;
2673 2700 padding: 0;
2674 2701 width: 420px;
2675 2702 }
2676 2703
2677 2704 /* -----------------------------------------------------------
2678 2705 register -> colors
2679 2706 ----------------------------------------------------------- */
2680 2707
2681 2708 #register div.color
2682 2709 {
2683 2710 margin: 10px auto 0 auto;
2684 2711 padding: 3px 3px 3px 0;
2685 2712 clear: both;
2686 2713 overflow: hidden;
2687 2714 background: #FFFFFF;
2688 2715 }
2689 2716
2690 2717 #register div.color a
2691 2718 {
2692 2719 margin: 0 0 0 3px;
2693 2720 padding: 0;
2694 2721 width: 20px;
2695 2722 height: 20px;
2696 2723 display: block;
2697 2724 float: left;
2698 2725 }
2699 2726
2700 2727 /* -----------------------------------------------------------
2701 2728 register -> title
2702 2729 ----------------------------------------------------------- */
2703 2730
2704 2731 #register div.title
2705 2732 {
2706 2733 margin: 0 auto;
2707 2734 padding: 0;
2708 2735 width: 420px;
2709 2736 clear: both;
2710 2737 overflow: hidden;
2711 2738 position: relative;
2712 2739 background: #003367 url("../images/colors/blue/header_inner.png") repeat-x;
2713 2740 }
2714 2741
2715 2742 #register div.title h5
2716 2743 {
2717 2744 margin: 10px;
2718 2745 padding: 0;
2719 2746 color: #ffffff;
2720 2747 }
2721 2748
2722 2749 /* -----------------------------------------------------------
2723 2750 register -> inner
2724 2751 ----------------------------------------------------------- */
2725 2752 #register div.title div.corner
2726 2753 {
2727 2754 height: 6px;
2728 2755 width: 6px;
2729 2756 position: absolute;
2730 2757 background: url("../images/colors/blue/login_corners.png") no-repeat;
2731 2758 }
2732 2759
2733 2760 #register div.title div.tl
2734 2761 {
2735 2762 top: 0;
2736 2763 left: 0;
2737 2764 background-position: 0 0;
2738 2765 }
2739 2766
2740 2767 #register div.title div.tr
2741 2768 {
2742 2769 top: 0;
2743 2770 right: 0;
2744 2771 background-position: -6px 0;
2745 2772
2746 2773 }
2747 2774 #register div.inner
2748 2775 {
2749 2776 margin: 0 auto;
2750 2777 padding: 20px;
2751 2778 width: 380px;
2752 2779 background: #FFFFFF;
2753 2780 border-top: none;
2754 2781 border-bottom: none;
2755 2782 }
2756 2783
2757 2784 /* -----------------------------------------------------------
2758 2785 register -> form
2759 2786 ----------------------------------------------------------- */
2760 2787
2761 2788 #register div.form
2762 2789 {
2763 2790 margin: 0;
2764 2791 padding: 0;
2765 2792 clear: both;
2766 2793 overflow: hidden;
2767 2794 }
2768 2795
2769 2796 #register div.form div.fields
2770 2797 {
2771 2798 margin: 0;
2772 2799 padding: 0;
2773 2800 clear: both;
2774 2801 overflow: hidden;
2775 2802 }
2776 2803
2777 2804 #register div.form div.fields div.field
2778 2805 {
2779 2806 margin: 0;
2780 2807 padding: 0 0 10px 0;
2781 2808 clear: both;
2782 2809 overflow: hidden;
2783 2810 }
2784 2811
2785 2812 #register div.form div.fields div.field span.error-message
2786 2813 {
2787 2814 margin: 8px 0 0 0;
2788 2815 padding: 0;
2789 2816 height: 1%;
2790 2817 display: block;
2791 2818 color: #FF0000;
2792 2819 }
2793 2820
2794 2821 #register div.form div.fields div.field div.label
2795 2822 {
2796 2823 margin: 2px 10px 0 0;
2797 2824 padding: 5px 0 0 5px;
2798 2825 width: 100px;
2799 2826 float: left;
2800 2827 text-align: right;
2801 2828 }
2802 2829
2803 2830 #register div.form div.fields div.field div.label label
2804 2831 {
2805 2832 color: #000000;
2806 2833 font-weight: bold;
2807 2834 }
2808 2835
2809 2836 #register div.form div.fields div.field div.label span
2810 2837 {
2811 2838 margin: 0;
2812 2839 padding: 2px 0 0 0;
2813 2840 height: 1%;
2814 2841 display: block;
2815 2842 color: #363636;
2816 2843 }
2817 2844
2818 2845 #register div.form div.fields div.field div.input
2819 2846 {
2820 2847 margin: 0;
2821 2848 padding: 0;
2822 2849 float: left;
2823 2850 }
2824 2851
2825 2852 #register div.form div.fields div.field div.input input
2826 2853 {
2827 2854 margin: 0;
2828 2855 padding: 7px 7px 6px 7px;
2829 2856 width: 245px;
2830 2857 background: #FFFFFF;
2831 2858 border-top: 1px solid #b3b3b3;
2832 2859 border-left: 1px solid #b3b3b3;
2833 2860 border-right: 1px solid #eaeaea;
2834 2861 border-bottom: 1px solid #eaeaea;
2835 2862 color: #000000;
2836 2863 font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
2837 2864 font-size: 11px;
2838 2865 }
2839 2866
2840 2867 #register div.form div.fields div.field div.input input.error
2841 2868 {
2842 2869 background: #FBE3E4;
2843 2870 border-top: 1px solid #e1b2b3;
2844 2871 border-left: 1px solid #e1b2b3;
2845 2872 border-right: 1px solid #FBC2C4;
2846 2873 border-bottom: 1px solid #FBC2C4;
2847 2874 }
2848 2875
2849 2876 #register div.form div.fields div.field div.input input.success
2850 2877 {
2851 2878 background: #E6EFC2;
2852 2879 border-top: 1px solid #cebb98;
2853 2880 border-left: 1px solid #cebb98;
2854 2881 border-right: 1px solid #c6d880;
2855 2882 border-bottom: 1px solid #c6d880;
2856 2883 }
2857 2884
2858 2885 #register div.form div.fields div.field div.input div.link
2859 2886 {
2860 2887 margin: 6px 0 0 0;
2861 2888 padding: 0;
2862 2889 text-align: right;
2863 2890 }
2864 2891
2865 2892 #register div.form div.fields div.field div.checkbox
2866 2893 {
2867 2894 margin: 0 0 0 184px;
2868 2895 padding: 0;
2869 2896 }
2870 2897
2871 2898 #register div.form div.fields div.field div.checkbox label
2872 2899 {
2873 2900 color: #565656;
2874 2901 font-weight: bold;
2875 2902 }
2876 2903
2877 2904 #register div.form div.fields div.buttons
2878 2905 {
2879 2906 margin: 0;
2880 padding: 10px 0 0 97px;
2907 padding: 10px 0 0 114px;
2881 2908 clear: both;
2882 2909 overflow: hidden;
2883 2910 border-top: 1px solid #DDDDDD;
2884 2911 text-align: left;
2885 2912 }
2886 2913
2887 2914 #register div.form div.fields div.buttons input
2888 2915 {
2889 2916 margin: 0;
2890 2917 color: #000000;
2891 2918 font-size: 1.0em;
2892 2919 font-weight: bold;
2893 2920 font-family: Verdana, Helvetica, Sans-Serif;
2894 2921 }
2895 2922
2896 2923 #register div.form div.fields div.buttons input.ui-state-default
2897 2924 {
2898 2925 margin: 0;
2899 2926 padding: 6px 12px 6px 12px;
2900 2927 background: #e5e3e3 url("../images/button.png") repeat-x;
2901 2928 border-top: 1px solid #DDDDDD;
2902 2929 border-left: 1px solid #c6c6c6;
2903 2930 border-right: 1px solid #DDDDDD;
2904 2931 border-bottom: 1px solid #c6c6c6;
2905 2932 color: #515151;
2906 2933 }
2907 2934 #register div.form div.fields div.buttons div.highlight input.ui-state-default
2908 2935 {
2909 2936 background:url("../images/colors/blue/button_highlight.png") repeat-x scroll 0 0 #4E85BB;
2910 2937 border-color:#5C91A4 #2B7089 #1A6480 #2A6F89;
2911 2938 border-style:solid;
2912 2939 border-width:1px;
2913 2940 color:#FFFFFF;
2914 2941 }
2915 2942
2916 2943
2917 2944
2918 2945 #register div.form div.fields div.buttons input.ui-state-hover
2919 2946 {
2920 2947 margin: 0;
2921 2948 padding: 6px 12px 6px 12px;
2922 2949 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
2923 2950 border-top: 1px solid #cccccc;
2924 2951 border-left: 1px solid #bebebe;
2925 2952 border-right: 1px solid #b1b1b1;
2926 2953 border-bottom: 1px solid #afafaf;
2927 2954 color: #515151;
2928 2955 }
2929 2956
2930 2957 #register div.form div.activation_msg {
2931 2958 padding-top:4px;
2932 2959 padding-bottom:4px;
2933 2960
2934 2961 }
2935 2962
2936 2963 /* -----------------------------------------------------------
2937 2964 SUMMARY
2938 2965 ----------------------------------------------------------- */
2939 2966
2940 2967 #clone_url{
2941 2968 border: none;
2942 2969 }
2943 2970 /* -----------------------------------------------------------
2944 2971 FILES
2945 2972 ----------------------------------------------------------- */
2946 2973
2947 2974 h3.files_location{
2948 2975 font-size: 1.8em;
2949 2976 font-weight: bold;
2950 2977 margin: 10px 0 !important;
2951 2978 border-bottom: none !important;
2952 2979 }
2953 2980
2954 2981 #files_data.dl{
2955 2982
2956 2983
2957 2984 }
2958 2985 #files_data dl dt{
2959 2986 float:left;
2960 2987 margin:0 !important;
2961 2988 padding:5px;
2962 2989 width:115px;
2963 2990 }
2964 2991 #files_data dl dd{
2965 2992 margin:0 !important;
2966 2993 padding: 5px !important;
2967 2994 }
2968 2995
2969 2996
2970 2997 /* -----------------------------------------------------------
2971 2998 CHANGESETS
2972 2999 ----------------------------------------------------------- */
2973 3000 #changeset_content {
2974 3001 border:1px solid #CCCCCC;
2975 3002 padding:5px;
2976 3003 }
2977 3004
2978 3005 #changeset_content .container .wrapper {
2979 3006 width: 600px;
2980 3007 }
2981 3008
2982 3009 #changeset_content .container {
2983 3010 height: 120px;
2984 3011 }
2985 3012
2986 3013 #changeset_content .container .left {
2987 3014 float: left;
2988 3015 width: 70%;
2989 3016 padding-left: 5px;
2990 3017 }
2991 3018
2992 3019 #changeset_content .container .right {
2993 3020 float: right;
2994 3021 width: 25%;
2995 3022 text-align: right;
2996 3023 }
2997 3024
2998 3025 #changeset_content .container .left .date {
2999 3026 font-weight: bold;
3000 3027 }
3001 3028
3002 3029 #changeset_content .container .left .author {
3003 3030
3004 3031 }
3005 3032
3006 3033 #changeset_content .container .left .message {
3007 3034 font-style: italic;
3008 3035 color: #556CB5;
3009 3036 }
3010 3037
3011 3038 .cs_files {
3012 3039
3013 3040 }
3014 3041
3015 3042 .cs_files .cs_added {
3016 3043 background: url("/images/icons/page_white_add.png") no-repeat scroll 3px;
3017 3044 /*background-color:#BBFFBB;*/
3018 3045 height: 16px;
3019 3046 padding-left: 20px;
3020 3047 margin-top: 7px;
3021 3048 text-align: left;
3022 3049 }
3023 3050
3024 3051 .cs_files .cs_changed {
3025 3052 background: url("/images/icons/page_white_edit.png") no-repeat scroll
3026 3053 3px;
3027 3054 /*background-color: #FFDD88;*/
3028 3055 height: 16px;
3029 3056 padding-left: 20px;
3030 3057 margin-top: 7px;
3031 3058 text-align: left;
3032 3059 }
3033 3060
3034 3061 .cs_files .cs_removed {
3035 3062 background: url("/images/icons/page_white_delete.png") no-repeat scroll
3036 3063 3px;
3037 3064 /*background-color: #FF8888;*/
3038 3065 height: 16px;
3039 3066 padding-left: 20px;
3040 3067 margin-top: 7px;
3041 3068 text-align: left;
3042 3069 }
3043 3070
3044 3071 /* -----------------------------------------------------------
3045 3072 CHANGESETS - CANVAS
3046 3073 ----------------------------------------------------------- */
3047 3074
3048 3075 #graph {
3049 3076 overflow: hidden;
3050 3077 }
3051 3078
3052 3079 #graph_nodes {
3053 3080 width: 160px;
3054 3081 float: left;
3055 3082 margin-left:-50px;
3056 3083 margin-top: 5px;
3057 3084 }
3058 3085
3059 3086 #graph_content {
3060 3087 width: 800px;
3061 3088 float: left;
3062 3089 }
3063 3090
3064 3091 #graph_content .container_header {
3065 3092 border: 1px solid #CCCCCC;
3066 3093 padding:10px;
3067 3094 }
3068 3095
3069 3096 #graph_content .container .wrapper {
3070 3097 width: 600px;
3071 3098 }
3072 3099
3073 3100 #graph_content .container {
3074 3101 border-bottom: 1px solid #CCCCCC;
3075 3102 border-left: 1px solid #CCCCCC;
3076 3103 border-right: 1px solid #CCCCCC;
3077 3104 min-height: 90px;
3078 3105 overflow: hidden;
3079 3106 font-size:1.2em;
3080 3107 }
3081 3108
3082 3109 #graph_content .container .left {
3083 3110 float: left;
3084 3111 width: 70%;
3085 3112 padding-left: 5px;
3086 3113 }
3087 3114
3088 3115 #graph_content .container .right {
3089 3116 float: right;
3090 3117 width: 25%;
3091 3118 text-align: right;
3092 3119 }
3093 3120
3094 3121 #graph_content .container .left .date {
3095 3122 font-weight: bold;
3096 3123 }
3097 3124
3098 3125 #graph_content .container .left .author {
3099 3126
3100 3127 }
3101 3128
3102 3129 #graph_content .container .left .message {
3103 3130 font-size: 100%;
3104 3131 padding-top: 3px;
3105 3132 }
3106 3133
3107 3134 .right div {
3108 3135 clear: both;
3109 3136 }
3110 3137
3111 3138 .right .changes .added,.changed,.removed {
3112 3139 border: 1px solid #DDDDDD;
3113 3140 display: block;
3114 3141 float: right;
3115 3142 font-size: 0.75em;
3116 3143 text-align: center;
3117 3144 min-width: 15px;
3118 3145 }
3119 3146
3120 3147 .right .changes .added {
3121 3148 background: #BBFFBB;
3122 3149 }
3123 3150
3124 3151 .right .changes .changed {
3125 3152 background: #FFDD88;
3126 3153 }
3127 3154
3128 3155 .right .changes .removed {
3129 3156 background: #FF8888;
3130 3157 }
3131 3158
3132 3159 .right .merge {
3133 3160 vertical-align: top;
3134 3161 font-size: 60%;
3135 3162 font-weight: bold;
3136 3163 }
3137 3164
3138 3165 .right .merge img {
3139 3166 vertical-align: bottom;
3140 3167 }
3141 3168
3142 3169 .right .parent {
3143 3170 font-size: 90%;
3144 3171 font-family: monospace;
3145 3172 }
3146 3173
3147 3174
3148 3175
3149 3176 /* -----------------------------------------------------------
3150 3177 FILE BROWSER
3151 3178 ----------------------------------------------------------- */
3152 3179 div.browserblock {
3153 3180 overflow: hidden;
3154 3181 padding: 0px;
3155 3182 border: 1px solid #ccc;
3156 3183 background: #f8f8f8;
3157 3184 font-size: 100%;
3158 3185 line-height: 100%;
3159 3186 /* new */
3160 3187 line-height: 125%;
3161 3188 }
3162 3189
3163 3190 div.browserblock .browser-header {
3164 3191 border-bottom: 1px solid #CCCCCC;
3165 3192 background: #FFFFFF;
3166 3193 color: blue;
3167 3194 padding: 10px 0 10px 0;
3168 3195 }
3169 3196
3170 3197 div.browserblock .browser-header span {
3171 3198 margin-left: 25px;
3172 3199 font-weight: bold;
3173 3200 }
3174 3201
3175 3202 div.browserblock .browser-body {
3176 3203 background: #EEEEEE;
3177 3204 }
3178 3205
3179 3206 table.code-browser {
3180 3207 border-collapse: collapse;
3181 3208 width: 100%;
3182 3209 }
3183 3210
3184 3211 table.code-browser tr {
3185 3212 margin: 3px;
3186 3213 }
3187 3214
3188 3215 table.code-browser thead th {
3189 3216 background-color: #EEEEEE;
3190 3217 height: 20px;
3191 3218 font-size: 1.1em;
3192 3219 font-weight: bold;
3193 3220 text-align: center;
3194 3221 text-align: left;
3195 3222 padding-left: 10px;
3196 3223 }
3197 3224
3198 3225 table.code-browser tbody tr {
3199 3226
3200 3227 }
3201 3228
3202 3229 table.code-browser tbody td {
3203 3230 padding-left: 10px;
3204 3231 height: 20px;
3205 3232 }
3206 3233 table.code-browser .browser-file {
3207 3234 background: url("/images/icons/document_16.png") no-repeat scroll 3px;
3208 3235 height: 16px;
3209 3236 padding-left: 20px;
3210 3237 text-align: left;
3211 3238 }
3212 3239
3213 3240 table.code-browser .browser-dir {
3214 3241 background: url("/images/icons/folder_16.png") no-repeat scroll 3px;
3215 3242 height: 16px;
3216 3243 padding-left: 20px;
3217 3244 text-align: left;
3218 3245 }
3219 3246
3220 3247 /* -----------------------------------------------------------
3221 3248 ADMIN - SETTINGS
3222 3249 ----------------------------------------------------------- */
3223 3250 #path_unlock{
3224 3251 color: red;
3225 3252 font-size: 1.2em;
3226 3253 padding-left: 4px;
3227 3254 }
3228 3255
3229 3256 /* -----------------------------------------------------------
3230 3257 INFOBOX
3231 3258 ----------------------------------------------------------- */
3232 3259 .info_box *{
3233 3260 background:url("../../images/pager.png") repeat-x scroll 0 0 #EBEBEB;
3234 3261 border-color:#DEDEDE #C4C4C4 #C4C4C4 #CFCFCF;
3235 3262 border-style:solid;
3236 3263 border-width:1px;
3237 3264 color:#4A4A4A;
3238 3265 display:block;
3239 3266 font-weight:bold;
3240 3267 height:1%;
3241 3268 padding:4px 6px;
3242 3269 display: inline;
3243 3270 }
3244 3271 .info_box span{
3245 3272 margin-left:3px;
3246 3273 margin-right:3px;
3247 3274 }
3248 3275 .info_box input#at_rev {
3249 3276 padding:1px 3px 3px 2px;
3250 3277 text-align:center;
3251 3278 }
3252 3279 .info_box input#view {
3253 3280 padding:0px 3px 2px 2px;
3254 3281 text-align:center;
3255 3282 }
3256 3283 /* -----------------------------------------------------------
3257 3284 YUI TOOLTIP
3258 3285 ----------------------------------------------------------- */
3259 3286 .yui-overlay,.yui-panel-container {
3260 3287 visibility: hidden;
3261 3288 position: absolute;
3262 3289 z-index: 2;
3263 3290 }
3264 3291
3265 3292 .yui-tt {
3266 3293 visibility: hidden;
3267 3294 position: absolute;
3268 3295 color: #666666;
3269 3296 background-color: #FFFFFF;
3270 3297 font-family: arial, helvetica, verdana, sans-serif;
3271 3298 padding: 8px;
3272 3299 border: 2px solid #556CB5;
3273 3300 font: 100% sans-serif;
3274 3301 width: auto;
3275 3302 opacity: 1.0;
3276 3303 }
3277 3304
3278 3305 .yui-tt-shadow {
3279 3306 display: none;
3280 3307 }
3281 3308
3282 3309 /* -----------------------------------------------------------
3283 3310 YUI AUTOCOMPLETE
3284 3311 ----------------------------------------------------------- */
3285 3312
3286 3313 .ac{
3287 3314 vertical-align: top;
3288 3315
3289 3316 }
3290 3317 .ac .match {
3291 3318 font-weight:bold;
3292 3319 }
3293 3320
3294 3321 .ac .yui-ac {
3295 3322 position: relative;
3296 3323 font-family: arial;
3297 3324 font-size: 100%;
3298 3325 }
3299 3326
3300 3327 .ac .perm_ac{
3301 3328 width:15em;
3302 3329 }
3303 3330 /* styles for input field */
3304 3331 .ac .yui-ac-input {
3305 3332 width: 100%;
3306 3333 }
3307 3334
3308 3335 /* styles for results container */
3309 3336 .ac .yui-ac-container {
3310 3337 position: absolute;
3311 3338 top: 1.6em;
3312 3339 width: 100%;
3313 3340 }
3314 3341
3315 3342 /* styles for header/body/footer wrapper within container */
3316 3343 .ac .yui-ac-content {
3317 3344 position: absolute;
3318 3345 width: 100%;
3319 3346 border: 1px solid #808080;
3320 3347 background: #fff;
3321 3348 overflow: hidden;
3322 3349 z-index: 9050;
3323 3350 }
3324 3351
3325 3352 /* styles for container shadow */
3326 3353 .ac .yui-ac-shadow {
3327 3354 position: absolute;
3328 3355 margin: .3em;
3329 3356 width: 100%;
3330 3357 background: #000;
3331 3358 -moz-opacity: 0.10;
3332 3359 opacity: .10;
3333 3360 filter: alpha(opacity = 10);
3334 3361 z-index: 9049;
3335 3362 }
3336 3363
3337 3364 /* styles for results list */
3338 3365 .ac .yui-ac-content ul {
3339 3366 margin: 0;
3340 3367 padding: 0;
3341 3368 width: 100%;
3342 3369 }
3343 3370
3344 3371 /* styles for result item */
3345 3372 .ac .yui-ac-content li {
3346 3373 margin: 0;
3347 3374 padding: 2px 5px;
3348 3375 cursor: default;
3349 3376 white-space: nowrap;
3350 3377 }
3351 3378
3352 3379 /* styles for prehighlighted result item */
3353 3380 .ac .yui-ac-content li.yui-ac-prehighlight {
3354 3381 background: #B3D4FF;
3355 3382 }
3356 3383
3357 3384 /* styles for highlighted result item */
3358 3385 .ac .yui-ac-content li.yui-ac-highlight {
3359 3386 background: #556CB5;
3360 3387 color: #FFF;
3361 3388 }
3362 3389
3363 3390
3364 3391 /* -----------------------------------------------------------
3365 3392 ACTION ICONS
3366 3393 ----------------------------------------------------------- */
3367 3394 .add_icon {
3368 3395 background: url("/images/icons/add.png") no-repeat scroll 3px ;
3369 3396 height: 16px;
3370 3397 padding-left: 20px;
3371 3398 padding-top: 1px;
3372 3399 text-align: left;
3373 3400 }
3374 3401
3375 3402 .edit_icon {
3376 3403 background: url("/images/icons/folder_edit.png") no-repeat scroll 3px;
3377 3404 height: 16px;
3378 3405 padding-left: 20px;
3379 3406 padding-top: 1px;
3380 3407 text-align: left;
3381 3408 }
3382 3409
3383 3410 .delete_icon {
3384 3411 background: url("/images/icons/delete.png") no-repeat scroll 3px;
3385 3412 height: 16px;
3386 3413 padding-left: 20px;
3387 3414 padding-top: 1px;
3388 3415 text-align: left;
3389 3416 }
3390 3417
3391 3418 .rss_icon {
3392 3419 background: url("/images/icons/rss_16.png") no-repeat scroll 3px;
3393 3420 height: 16px;
3394 3421 padding-left: 20px;
3395 3422 padding-top: 1px;
3396 3423 text-align: left;
3397 3424 }
3398 3425
3399 3426 .atom_icon {
3400 3427 background: url("/images/icons/atom.png") no-repeat scroll 3px;
3401 3428 height: 16px;
3402 3429 padding-left: 20px;
3403 3430 padding-top: 1px;
3404 3431 text-align: left;
3405 3432 }
3406 3433
3407 3434 .archive_icon {
3408 3435 background: url("/images/icons/compress.png") no-repeat scroll 3px;
3409 3436 height: 16px;
3410 3437 padding-left: 20px;
3411 3438 text-align: left;
3412 3439 padding-top: 1px;
3413 3440 }
3414 3441
3415 3442 .action_button {
3416 3443 border: 0px;
3417 3444 display: block;
3418 3445 }
3419 3446
3420 3447 .action_button:hover {
3421 3448 border: 0px;
3422 3449 font-style: italic;
3423 3450 cursor: pointer;
3424 3451 }
3425 3452
3426 3453 /* -----------------------------------------------------------
3427 3454 REPO SWITCHER
3428 3455 ----------------------------------------------------------- */
3429 3456
3430 3457 #switch_repos{
3431 3458 position: absolute;
3432 3459 height: 25px;
3433 3460 z-index: 1;
3434 3461 }
3435 3462 #switch_repos select{
3436 3463 min-width:150px;
3437 3464 max-height: 250px;
3438 3465 z-index: 1;
3439 3466 }
3440 3467 /* -----------------------------------------------------------
3441 3468 BREADCRUMBS
3442 3469 ----------------------------------------------------------- */
3443 3470
3444 3471 .breadcrumbs{
3445 3472 border:medium none;
3446 3473 color:#FFFFFF;
3447 3474 float:left;
3448 3475 margin:0;
3449 3476 padding:11px 0 11px 10px;
3450 3477 text-transform:uppercase;
3451 3478 font-weight: bold;
3452 3479 font-size: 14px;
3453 3480 }
3454 3481 .breadcrumbs a{
3455 3482 color: #FFFFFF;
3456 3483 }
3457 3484
3458 3485
3459 3486 /* -----------------------------------------------------------
3460 3487 FLASH MSG
3461 3488 ----------------------------------------------------------- */
3462 3489 .flash_msg ul {
3463 3490 margin: 0;
3464 3491 padding: 0px 0px 10px 0px;
3465 3492 }
3466 3493
3467 3494 .error_msg {
3468 3495 background-color: #FFCFCF;
3469 3496 background-image: url("/images/icons/error_msg.png");
3470 3497 border: 1px solid #FF9595;
3471 3498 color: #CC3300;
3472 3499 }
3473 3500
3474 3501 .warning_msg {
3475 3502 background-color: #FFFBCC;
3476 3503 background-image: url("/images/icons/warning_msg.png");
3477 3504 border: 1px solid #FFF35E;
3478 3505 color: #C69E00;
3479 3506 }
3480 3507
3481 3508 .success_msg {
3482 3509 background-color: #D5FFCF;
3483 3510 background-image: url("/images/icons/success_msg.png");
3484 3511 border: 1px solid #97FF88;
3485 3512 color: #009900;
3486 3513 }
3487 3514
3488 3515 .notice_msg {
3489 3516 background-color: #DCE3FF;
3490 3517 background-image: url("/images/icons/notice_msg.png");
3491 3518 border: 1px solid #93A8FF;
3492 3519 color: #556CB5;
3493 3520 }
3494 3521
3495 3522 .success_msg,.error_msg,.notice_msg,.warning_msg {
3496 3523 background-position: 10px center;
3497 3524 background-repeat: no-repeat;
3498 3525 font-size: 12px;
3499 3526 font-weight: bold;
3500 3527 min-height: 14px;
3501 3528 line-height: 14px;
3502 3529 margin-bottom: 0px;
3503 3530 margin-top: 0px;
3504 3531 padding: 6px 10px 6px 40px;
3505 3532 display: block;
3506 3533 overflow: auto;
3507 3534 }
3508 3535
3509 3536 #msg_close {
3510 3537 background: transparent url("icons/cross_grey_small.png") no-repeat
3511 3538 scroll 0 0;
3512 3539 cursor: pointer;
3513 3540 height: 16px;
3514 3541 position: absolute;
3515 3542 right: 5px;
3516 3543 top: 5px;
3517 3544 width: 16px;
3518 3545 }
3519 3546 /* -----------------------------------------------------------
3520 3547 YUI FLOT
3521 3548 ----------------------------------------------------------- */
3522 3549
3523 3550 div#commit_history{
3524 3551 float: left;
3525 3552 }
3526 3553 div#legend_data{
3527 3554 float:left;
3528 3555
3529 3556 }
3530 3557 div#legend_container {
3531 3558 float: left;
3532 3559 }
3533 3560
3534 3561 div#legend_container table,div#legend_choices table{
3535 3562 width:auto !important;
3536 3563 }
3537 3564
3538 3565 div#legend_container table td{
3539 3566 border: none !important;
3540 3567 padding: 0px !important;
3541 3568 height: 20px !important;
3542 3569 }
3543 3570
3544 3571 div#legend_choices table td{
3545 3572 border: none !important;
3546 3573 padding: 0px !important;
3547 3574 height: 20px !important;
3548 3575 }
3549 3576
3550 3577 div#legend_choices{
3551 3578 float:left;
3552 3579 }
3553 3580
3554 3581 /* -----------------------------------------------------------
3555 3582 PERMISSIONS TABLE
3556 3583 ----------------------------------------------------------- */
3557 3584 table#permissions_manage{
3558 3585 width: 0 !important;
3559 3586
3560 3587 }
3561 3588 table#permissions_manage span.private_repo_msg{
3562 3589 font-size: 0.8em;
3563 3590 opacity:0.6;
3564 3591
3565 3592 }
3566 3593 table#permissions_manage td.private_repo_msg{
3567 3594 font-size: 0.8em;
3568 3595
3569 3596 }
3570 3597 table#permissions_manage tr#add_perm_input td{
3571 3598 vertical-align:middle;
3572 3599
3573 3600 }
3574 3601
3575 3602 /* -----------------------------------------------------------
3576 3603 GRAVATARS
3577 3604 ----------------------------------------------------------- */
3578 3605 div.gravatar{
3579 3606 background-color:white;
3580 3607 border:1px solid #D0D0D0;
3581 3608 float:left;
3582 3609 margin-right:0.7em;
3583 3610 padding: 2px 2px 0px;
3584 3611 }
3585 3612
3586 3613 /* -----------------------------------------------------------
3587 3614 STYLING OF LAYOUT
3588 3615 ----------------------------------------------------------- */
3589 3616
3590 3617
3591 3618 /* -----------------------------------------------------------
3592 3619 GLOBAL WIDTH
3593 3620 ----------------------------------------------------------- */
3594 3621 #header,#content,#footer{
3595 3622 min-width: 1224px;
3596 3623 }
3597 3624
3598 3625 /* -----------------------------------------------------------
3599 3626 content
3600 3627 ----------------------------------------------------------- */
3601 3628
3602 3629 #content
3603 3630 {
3604 3631 margin: 10px 30px 0 30px;
3605 3632 padding: 0;
3606 3633 min-height: 100%;
3607 3634 clear: both;
3608 3635 overflow: hidden;
3609 3636 background: transparent;
3610 3637 }
3611 3638
3612 3639 /* -----------------------------------------------------------
3613 3640 content -> right -> forms -> labels
3614 3641 ----------------------------------------------------------- */
3615 3642
3616 3643 #content div.box div.form div.fields div.field div.label
3617 3644 {
3618 3645 left: 80px;
3619 3646 margin: 0;
3620 3647 padding: 8px 0 0 5px;
3621 3648 width: auto;
3622 3649 position: absolute;
3623 3650 }
3624 3651
3625 3652 #content div.box-left div.form div.fields div.field div.label,
3626 3653 #content div.box-right div.form div.fields div.field div.label
3627 3654 {
3628 3655 left: 0;
3629 3656 margin: 0;
3630 3657 padding: 0 0 8px 0;
3631 3658 width: auto;
3632 3659 position: relative;
3633 3660 } No newline at end of file
@@ -1,41 +1,41 b''
1 1 ## -*- coding: utf-8 -*-
2 2 %if c.users_log:
3 3 <table>
4 4 <tr>
5 5 <th class="left">${_('Username')}</th>
6 6 <th class="left">${_('Repository')}</th>
7 7 <th class="left">${_('Action')}</th>
8 8 <th class="left">${_('Date')}</th>
9 9 <th class="left">${_('From IP')}</th>
10 10 </tr>
11 11
12 12 %for cnt,l in enumerate(c.users_log):
13 13 <tr class="parity${cnt%2}">
14 <td>${l.user.username}</td>
15 <td>${l.repository}</td>
14 <td>${h.link_to(l.user.username,h.url('edit_user', id=l.user.user_id))}</td>
15 <td>${h.link_to(l.repository,h.url('summary_home',repo_name=l.repository))}</td>
16 16 <td>${l.action}</td>
17 17 <td>${l.action_date}</td>
18 18 <td>${l.user_ip}</td>
19 19 </tr>
20 20 %endfor
21 21 </table>
22 22
23 23 <script type="text/javascript">
24 24 var data_div = 'user_log';
25 25 YAHOO.util.Event.onDOMReady(function(){
26 26 YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){
27 27 YAHOO.util.Dom.setStyle('shortlog_data','opacity','0.3');});});
28 28 </script>
29 29
30 30
31 31 <div class="pagination-wh pagination-left">
32 32 ${c.users_log.pager('$link_previous ~2~ $link_next',
33 33 onclick="""YAHOO.util.Connect.asyncRequest('GET','$partial_url',{
34 34 success:function(o){YAHOO.util.Dom.get(data_div).innerHTML=o.responseText;
35 35 YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){
36 36 YAHOO.util.Dom.setStyle(data_div,'opacity','0.3');});
37 37 YAHOO.util.Dom.setStyle(data_div,'opacity','1');}},null); return false;""")}
38 38 </div>
39 39 %else:
40 40 ${_('No actions yet')}
41 41 %endif
@@ -1,68 +1,68 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Permissions administration')}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${_('Permissions')}
12 12 </%def>
13 13
14 14 <%def name="page_nav()">
15 15 ${self.menu('admin')}
16 16 </%def>
17 17
18 18 <%def name="main()">
19 19 <div class="box">
20 20 <!-- box / title -->
21 21 <div class="title">
22 22 ${self.breadcrumbs()}
23 23 </div>
24 24 <h3>${_('Default permissions')}</h3>
25 25 ${h.form(url('permission', id='default'),method='put')}
26 26 <div class="form">
27 27 <!-- fields -->
28 28 <div class="fields">
29 29
30 30 <div class="field">
31 31 <div class="label">
32 <label for="default_perm">${_('Default repository permission')}:</label>
32 <label for="default_perm">${_('Repository permission')}:</label>
33 33 </div>
34 34 <div class="select">
35 35 ${h.select('default_perm','',c.perms_choices)}
36 36
37 37 ${h.checkbox('overwrite_default','true')}
38 38 <label for="overwrite_default">
39 39 <span class="tooltip"
40 40 tooltip_title="${h.tooltip(_('All default permissions on each repository will be reset to choosen permission, note that all custom default permission on repositories will be lost'))}">
41 41 ${_('overwrite existing settings')}</span> </label>
42 42 </div>
43 43 </div>
44 44 <div class="field">
45 45 <div class="label">
46 46 <label for="default_register">${_('Registration')}:</label>
47 47 </div>
48 48 <div class="select">
49 49 ${h.select('default_register','',c.register_choices)}
50 50 </div>
51 51 </div>
52 52 <div class="field">
53 53 <div class="label">
54 <label for="default_create">${_('Allow repository creation')}:</label>
54 <label for="default_create">${_('Repository creation')}:</label>
55 55 </div>
56 56 <div class="select">
57 57 ${h.select('default_create','',c.create_choices)}
58 58 </div>
59 59 </div>
60 60
61 61 <div class="buttons">
62 62 ${h.submit('set','set',class_="ui-button ui-widget ui-state-default ui-corner-all")}
63 63 </div>
64 64 </div>
65 65 </div>
66 66 ${h.end_form()}
67 67 </div>
68 68 </%def>
@@ -1,145 +1,170 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Settings administration')}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
10 10 </%def>
11 11
12 12 <%def name="page_nav()">
13 13 ${self.menu('admin')}
14 14 </%def>
15 15
16 16 <%def name="main()">
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 </div>
22 22 <!-- end box / title -->
23 23
24 24 <h3>${_('Remap and rescan repositories')}</h3>
25 25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
26 26 <div class="form">
27 27 <!-- fields -->
28 28
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label label-checkbox">
32 32 <label for="destroy">${_('rescan option')}:</label>
33 33 </div>
34 34 <div class="checkboxes">
35 35 <div class="checkbox">
36 36 ${h.checkbox('destroy',True)}
37 37 <label for="checkbox-1">
38 38 <span class="tooltip" tooltip_title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
39 39 ${_('destroy old data')}</span> </label>
40 40 </div>
41 41 </div>
42 42 </div>
43 43
44 44 <div class="buttons">
45 45 ${h.submit('rescan','rescan repositories',class_="ui-button ui-widget ui-state-default ui-corner-all")}
46 46 </div>
47 47 </div>
48 48 </div>
49 49 ${h.end_form()}
50 50
51 <h3>${_('Whoosh indexing')}</h3>
52 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
53 <div class="form">
54 <!-- fields -->
55
56 <div class="fields">
57 <div class="field">
58 <div class="label label-checkbox">
59 <label for="destroy">${_('index build option')}:</label>
60 </div>
61 <div class="checkboxes">
62 <div class="checkbox">
63 ${h.checkbox('full_index',True)}
64 <label for="checkbox-1">${_('build from scratch')}</label>
65 </div>
66 </div>
67 </div>
68
69 <div class="buttons">
70 ${h.submit('reindex','reindex',class_="ui-button ui-widget ui-state-default ui-corner-all")}
71 </div>
72 </div>
73 </div>
74 ${h.end_form()}
75
51 76 <h3>${_('Global application settings')}</h3>
52 77 ${h.form(url('admin_setting', setting_id='global'),method='put')}
53 78 <div class="form">
54 79 <!-- fields -->
55 80
56 81 <div class="fields">
57 82
58 83 <div class="field">
59 84 <div class="label">
60 85 <label for="hg_app_title">${_('Application name')}:</label>
61 86 </div>
62 87 <div class="input">
63 88 ${h.text('hg_app_title',size=30)}
64 89 </div>
65 90 </div>
66 91
67 92 <div class="field">
68 93 <div class="label">
69 94 <label for="hg_app_realm">${_('Realm text')}:</label>
70 95 </div>
71 96 <div class="input">
72 97 ${h.text('hg_app_realm',size=30)}
73 98 </div>
74 99 </div>
75 100
76 101 <div class="buttons">
77 102 ${h.submit('save','save settings',class_="ui-button ui-widget ui-state-default ui-corner-all")}
78 103 </div>
79 104 </div>
80 105 </div>
81 106 ${h.end_form()}
82 107
83 108 <h3>${_('Mercurial settings')}</h3>
84 109 ${h.form(url('admin_setting', setting_id='mercurial'),method='put')}
85 110 <div class="form">
86 111 <!-- fields -->
87 112
88 113 <div class="fields">
89 114
90 115 <div class="field">
91 116 <div class="label label-checkbox">
92 117 <label for="web_push_ssl">${_('Web')}:</label>
93 118 </div>
94 119 <div class="checkboxes">
95 120 <div class="checkbox">
96 121 ${h.checkbox('web_push_ssl','true')}
97 122 <label for="web_push_ssl">${_('require ssl for pushing')}</label>
98 123 </div>
99 124 </div>
100 125 </div>
101 126
102 127 <div class="field">
103 128 <div class="label label-checkbox">
104 129 <label for="web_push_ssl">${_('Hooks')}:</label>
105 130 </div>
106 131 <div class="checkboxes">
107 132 <div class="checkbox">
108 133 ${h.checkbox('hooks_changegroup_update','True')}
109 134 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
110 135 </div>
111 136 <div class="checkbox">
112 137 ${h.checkbox('hooks_changegroup_repo_size','True')}
113 138 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
114 139 </div>
115 140 </div>
116 141 </div>
117 142
118 143 <div class="field">
119 144 <div class="label">
120 145 <label for="paths_root_path">${_('Repositories location')}:</label>
121 146 </div>
122 147 <div class="input">
123 148 ${h.text('paths_root_path',size=30,readonly="readonly")}
124 149 <span id="path_unlock" class="tooltip"
125 150 tooltip_title="${h.tooltip(_('This a crucial application setting. If You really sure you need to change this, you must restart application in order to make this settings take effect. Click this label to unlock.'))}">
126 151 ${_('unlock')}</span>
127 152 </div>
128 153 </div>
129 154
130 155 <div class="buttons">
131 156 ${h.submit('save','save settings',class_="ui-button ui-widget ui-state-default ui-corner-all")}
132 157 </div>
133 158 </div>
134 159 </div>
135 160 ${h.end_form()}
136 161
137 162 <script type="text/javascript">
138 163 YAHOO.util.Event.onDOMReady(function(){
139 164 YAHOO.util.Event.addListener('path_unlock','click',function(){
140 165 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
141 166 });
142 167 });
143 168 </script>
144 169 </div>
145 170 </%def>
@@ -1,242 +1,247 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
4 4 <head>
5 5 <title>${next.title()}</title>
6 6 <link rel="icon" href="/images/hgicon.png" type="image/png" />
7 7 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
8 8 <meta name="robots" content="index, nofollow"/>
9 9 <!-- stylesheets -->
10 10 ${self.css()}
11 11 <!-- scripts -->
12 12 ${self.js()}
13 13 </head>
14 14 <body>
15 15 <!-- header -->
16 16 <div id="header">
17 17 <!-- user -->
18 18 <ul id="logged-user">
19 19 <li class="first">
20 20 <div class="gravatar">
21 21 <img alt="gravatar" src="${h.gravatar_url(c.hg_app_user.email,24)}" />
22 22 </div>
23 23 <div class="account">
24 24 ${h.link_to('%s %s'%(c.hg_app_user.name,c.hg_app_user.lastname),h.url('admin_settings_my_account'))}<br/>
25 25 ${h.link_to(c.hg_app_user.username,h.url('admin_settings_my_account'))}
26 26 </div>
27 27 </li>
28 28 <li class="last highlight">${h.link_to(u'Logout',h.url('logout_home'))}</li>
29 29 </ul>
30 30 <!-- end user -->
31 31 <div id="header-inner">
32 32 <div id="home">
33 33 <a href="${h.url('hg_home')}"></a>
34 34 </div>
35 35 <!-- logo -->
36 36 <div id="logo">
37 37 <h1><a href="${h.url('hg_home')}">${c.hg_app_name}</a></h1>
38 38 </div>
39 39 <!-- end logo -->
40 40 <!-- quick menu -->
41 41 ${self.page_nav()}
42 42 <!-- end quick -->
43 43 <div class="corner tl"></div>
44 44 <div class="corner tr"></div>
45 45 </div>
46 46 </div>
47 47 <!-- end header -->
48 48
49 49 <!-- CONTENT -->
50 50 <div id="content">
51 51 <div class="flash_msg">
52 52 <% messages = h.flash.pop_messages() %>
53 53 % if messages:
54 54 <ul id="flash-messages">
55 55 % for message in messages:
56 56 <li class="${message.category}_msg">${message}</li>
57 57 % endfor
58 58 </ul>
59 59 % endif
60 60 </div>
61 61 <div id="main">
62 62 ${next.main()}
63 63 </div>
64 64 </div>
65 65 <!-- END CONTENT -->
66 66
67 67 <!-- footer -->
68 68 <div id="footer">
69 69 <p>Hg App ${c.hg_app_version} &copy; 2010 by Marcin Kuzminski</p>
70 70 <script type="text/javascript">${h.tooltip.activate()}</script>
71 71 </div>
72 72 <!-- end footer -->
73 73 </body>
74 74
75 75 </html>
76 76
77 77 ### MAKO DEFS ###
78 78 <%def name="page_nav()">
79 79 ${self.menu()}
80 80 </%def>
81 81
82 82 <%def name="menu(current=None)">
83 83 <%
84 84 def is_current(selected):
85 85 if selected == current:
86 86 return h.literal('class="current"')
87 87 %>
88 88 %if current not in ['home','admin']:
89 89 ##REGULAR MENU
90 90 <ul id="quick">
91 91 <!-- repo switcher -->
92 92 <li>
93 93 <a id="repo_switcher" title="${_('Switch repository')}" href="#">
94 94 <span class="icon">
95 95 <img src="/images/icons/database.png" alt="${_('Products')}" />
96 96 </span>
97 97 <span>&darr;</span>
98 98 </a>
99 99 <ul class="repo_switcher">
100 %for repo in c.repo_switcher_list:
101 <li>${h.link_to(repo,h.url('summary_home',repo_name=repo))}</li>
100 %for repo,private in c.repo_switcher_list:
101 %if private:
102 <li>${h.link_to(repo,h.url('summary_home',repo_name=repo),class_="private_repo")}</li>
103 %else:
104 <li>${h.link_to(repo,h.url('summary_home',repo_name=repo),class_="public_repo")}</li>
105 %endif
102 106 %endfor
103 107 </ul>
104 108 </li>
105 109
106 110 <li ${is_current('summary')}>
107 111 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
108 112 <span class="icon">
109 113 <img src="/images/icons/clipboard_16.png" alt="${_('Summary')}" />
110 114 </span>
111 115 <span>${_('Summary')}</span>
112 116 </a>
113 117 </li>
114 118 <li ${is_current('shortlog')}>
115 119 <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
116 120 <span class="icon">
117 121 <img src="/images/icons/application_double.png" alt="${_('Shortlog')}" />
118 122 </span>
119 123 <span>${_('Shortlog')}</span>
120 124 </a>
121 125 </li>
122 126 <li ${is_current('changelog')}>
123 127 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
124 128 <span class="icon">
125 129 <img src="/images/icons/time.png" alt="${_('Changelog')}" />
126 130 </span>
127 131 <span>${_('Changelog')}</span>
128 132 </a>
129 133 </li>
130 134
131 135 <li ${is_current('switch_to')}>
132 136 <a title="${_('Switch to')}" href="#">
133 137 <span class="icon">
134 138 <img src="/images/icons/arrow_switch.png" alt="${_('Switch to')}" />
135 139 </span>
136 140 <span>${_('Switch to')}</span>
137 141 </a>
138 142 <ul>
139 143 <li>
140 144 ${h.link_to(_('branches'),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
141 145 <ul>
142 146 %for cnt,branch in enumerate(c.repository_branches.items()):
143 147 <li>${h.link_to('%s - %s' % (branch[0],branch[1]),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</li>
144 148 %endfor
145 149 </ul>
146 150 </li>
147 151 <li>
148 152 ${h.link_to(_('tags'),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
149 153 <ul>
150 154 %for cnt,tag in enumerate(c.repository_tags.items()):
151 155 <li>${h.link_to('%s - %s' % (tag[0],tag[1]),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</li>
152 156 %endfor
153 157 </ul>
154 158 </li>
155 159 </ul>
156 160 </li>
157 161 <li ${is_current('files')}>
158 162 <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
159 163 <span class="icon">
160 164 <img src="/images/icons/file.png" alt="${_('Files')}" />
161 165 </span>
162 166 <span>${_('Files')}</span>
163 167 </a>
164 168 </li>
165 169 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
166 170 <li ${is_current('settings')}>
167 171 <a title="${_('Settings')}" href="${h.url('repo_settings_home',repo_name=c.repo_name)}">
168 172 <span class="icon">
169 173 <img src="/images/icons/cog_edit.png" alt="${_('Settings')}" />
170 174 </span>
171 175 <span>${_('Settings')}</span>
172 176 </a>
173 177 </li>
174 178 %endif
175 179 </ul>
176 180 %else:
177 181 ##ROOT MENU
178 182 <ul id="quick">
179 183 <li>
180 184 <a title="${_('Home')}" href="${h.url('hg_home')}">
181 185 <span class="icon">
182 186 <img src="/images/icons/home_16.png" alt="${_('Home')}" />
183 187 </span>
184 188 <span>${_('Home')}</span>
185 189 </a>
186 190 </li>
187 191
188 192 <li>
189 193 <a title="${_('Search')}" href="${h.url('search')}">
190 194 <span class="icon">
191 195 <img src="/images/icons/search_16.png" alt="${_('Search')}" />
192 196 </span>
193 197 <span>${_('Search')}</span>
194 198 </a>
195 199 </li>
196 200
197 201 %if h.HasPermissionAll('hg.admin')('access admin main page'):
198 202 <li ${is_current('admin')}>
199 203 <a title="${_('Admin')}" href="${h.url('admin_home')}">
200 204 <span class="icon">
201 205 <img src="/images/icons/cog_edit.png" alt="${_('Admin')}" />
202 206 </span>
203 207 <span>${_('Admin')}</span>
204 208 </a>
205 209 <ul>
210 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
206 211 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
207 212 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
208 213 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
209 214 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
210 215 </ul>
211 216 </li>
212 217 %endif
213 218
214 219 </ul>
215 220 %endif
216 221 </%def>
217 222
218 223
219 224 <%def name="css()">
220 225 <link rel="stylesheet" type="text/css" href="/css/reset.css" />
221 226 <link rel="stylesheet" type="text/css" href="/css/style.css" media="screen" />
222 227 <link id="color" rel="stylesheet" type="text/css" href="/css/colors/blue.css" />
223 228 <link rel="stylesheet" type="text/css" href="/css/pygments.css" />
224 229 <link rel="stylesheet" type="text/css" href="/css/diff.css" />
225 230 </%def>
226 231
227 232 <%def name="js()">
228 233 ##<script type="text/javascript" src="/js/yui/utilities/utilities.js"></script>
229 234 ##<script type="text/javascript" src="/js/yui/container/container.js"></script>
230 235 ##<script type="text/javascript" src="/js/yui/datasource/datasource.js"></script>
231 236 ##<script type="text/javascript" src="/js/yui/autocomplete/autocomplete.js"></script>
232 237
233 238 <script type="text/javascript" src="/js/yui2.js"></script>
234 239 <!--[if IE]><script language="javascript" type="text/javascript" src="/js/excanvas.min.js"></script><![endif]-->
235 240 <script type="text/javascript" src="/js/yui.flot.js"></script>
236 241 </%def>
237 242
238 243 <%def name="breadcrumbs()">
239 244 <div class="breadcrumbs">
240 245 ${self.breadcrumbs_links()}
241 246 </div>
242 247 </%def> No newline at end of file
@@ -1,52 +1,61 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${_('File annotate')}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(u'Home',h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('annotate')} @ R${c.rev_nr}:${c.cur_rev}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('files')}
17 17 </%def>
18 18 <%def name="main()">
19 19 <div class="box">
20 20 <!-- box / title -->
21 21 <div class="title">
22 22 ${self.breadcrumbs()}
23 23 </div>
24 24 <div class="table">
25 25 <div id="files_data">
26 <h2>${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cur_rev,c.file.path)}</h2>
26 <h3 class="files_location">${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cur_rev,c.file.path)}</h3>
27 27 <dl class="overview">
28 28 <dt>${_('Last revision')}</dt>
29 29 <dd>${h.link_to("r%s:%s" % (c.file.last_changeset.revision,c.file.last_changeset._short),
30 30 h.url('files_annotate_home',repo_name=c.repo_name,revision=c.file.last_changeset._short,f_path=c.f_path))} </dd>
31 31 <dt>${_('Size')}</dt>
32 32 <dd>${h.format_byte_size(c.file.size,binary=True)}</dd>
33 <dt>${_('Mimetype')}</dt>
34 <dd>${c.file.mimetype}</dd>
33 35 <dt>${_('Options')}</dt>
34 36 <dd>${h.link_to(_('show source'),
35 37 h.url('files_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
38 / ${h.link_to(_('show as raw'),
39 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
36 40 / ${h.link_to(_('download as raw'),
37 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
41 h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
38 42 </dd>
39 43 </dl>
40 44 <div id="body" class="codeblock">
41 45 <div class="code-header">
42 46 <div class="revision">${c.file.name}@r${c.file.last_changeset.revision}:${c.file.last_changeset._short}</div>
43 47 <div class="commit">"${c.file_msg}"</div>
44 48 </div>
45 49 <div class="code-body">
50 % if c.file.size < c.file_size_limit:
46 51 ${h.pygmentize_annotation(c.file,linenos=True,anchorlinenos=True,lineanchors='S',cssclass="code-highlight")}
52 %else:
53 ${_('File is to big to display')} ${h.link_to(_('show as raw'),
54 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
55 %endif
47 56 </div>
48 57 </div>
49 58 </div>
50 59 </div>
51 60 </div>
52 61 </%def> No newline at end of file
@@ -1,71 +1,78 b''
1 1 <%def name="file_class(node)">
2 2 %if node.is_file():
3 3 <%return "browser-file" %>
4 4 %else:
5 5 <%return "browser-dir"%>
6 6 %endif
7 7 </%def>
8 8 <div id="body" class="browserblock">
9 9 <div class="browser-header">
10 10 ${h.form(h.url.current())}
11 11 <div class="info_box">
12 12 <span >${_('view')}@rev</span>
13 13 <a href="${c.url_prev}">&laquo;</a>
14 14 ${h.text('at_rev',value=c.rev_nr,size=3)}
15 15 <a href="${c.url_next}">&raquo;</a>
16 16 ${h.submit('view','view')}
17 17 </div>
18 18 ${h.end_form()}
19 19 </div>
20 20 <div class="browser-body">
21 21 <table class="code-browser">
22 22 <thead>
23 23 <tr>
24 24 <th>${_('Name')}</th>
25 25 <th>${_('Size')}</th>
26 <th>${_('Mimetype')}</th>
26 27 <th>${_('Revision')}</th>
27 28 <th>${_('Last modified')}</th>
28 29 <th>${_('Last commiter')}</th>
29 30 </tr>
30 31 </thead>
32
33 % if c.files_list.parent:
31 34 <tr class="parity0">
32 35 <td>
33 % if c.files_list.parent:
34 36 ${h.link_to('..',h.url('files_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.files_list.parent.path),class_="browser-dir")}
35 %endif
36 37 </td>
37 38 <td></td>
38 39 <td></td>
39 40 <td></td>
40 41 <td></td>
42 <td></td>
41 43 </tr>
44 %endif
45
42 46 %for cnt,node in enumerate(c.files_list,1):
43 47 <tr class="parity${cnt%2}">
44 48 <td>
45 49 ${h.link_to(node.name,h.url('files_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=node.path),class_=file_class(node))}
46 50 </td>
47 51 <td>
52 ${h.format_byte_size(node.size,binary=True)}
53 </td>
54 <td>
48 55 %if node.is_file():
49 ${h.format_byte_size(node.size,binary=True)}
56 ${node.mimetype}
50 57 %endif
51 58 </td>
52 59 <td>
53 60 %if node.is_file():
54 61 ${node.last_changeset.revision}
55 62 %endif
56 63 </td>
57 64 <td>
58 65 %if node.is_file():
59 66 ${h.age(node.last_changeset._ctx.date())} - ${node.last_changeset.date}
60 67 %endif
61 68 </td>
62 69 <td>
63 70 %if node.is_file():
64 71 ${node.last_changeset.author}
65 72 %endif
66 73 </td>
67 74 </tr>
68 75 %endfor
69 76 </table>
70 77 </div>
71 78 </div> No newline at end of file
@@ -1,48 +1,57 b''
1 1 <dl>
2 2 <dt>${_('Last revision')}</dt>
3 3 <dd>
4 4 ${h.link_to("r%s:%s" % (c.files_list.last_changeset.revision,c.files_list.last_changeset._short),
5 5 h.url('files_home',repo_name=c.repo_name,revision=c.files_list.last_changeset._short,f_path=c.f_path))}
6 6 </dd>
7 7 <dt>${_('Size')}</dt>
8 8 <dd>${h.format_byte_size(c.files_list.size,binary=True)}</dd>
9 <dt>${_('Mimetype')}</dt>
10 <dd>${c.files_list.mimetype}</dd>
9 11 <dt>${_('Options')}</dt>
10 12 <dd>${h.link_to(_('show annotation'),
11 13 h.url('files_annotate_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
14 / ${h.link_to(_('show as raw'),
15 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
12 16 / ${h.link_to(_('download as raw'),
13 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
17 h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
14 18 </dd>
15 19 <dt>${_('History')}</dt>
16 20 <dd>
17 21 <div>
18 22 ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
19 23 ${h.hidden('diff2',c.files_list.last_changeset._short)}
20 24 ${h.select('diff1',c.files_list.last_changeset._short,c.file_history)}
21 25 ${h.submit('diff','diff to revision',class_="ui-button ui-widget ui-state-default ui-corner-all")}
22 26 ${h.submit('show_rev','show at revision',class_="ui-button ui-widget ui-state-default ui-corner-all")}
23 27 ${h.end_form()}
24 28 </div>
25 29 </dd>
26 30 </dl>
27 31
28 32
29 33 <div id="body" class="codeblock">
30 34 <div class="code-header">
31 35 <div class="revision">${c.files_list.name}@r${c.files_list.last_changeset.revision}:${c.files_list.last_changeset._short}</div>
32 36 <div class="commit">"${c.files_list.last_changeset.message}"</div>
33 37 </div>
34 38 <div class="code-body">
39 % if c.files_list.size < c.file_size_limit:
35 40 ${h.pygmentize(c.files_list,linenos=True,anchorlinenos=True,lineanchors='S',cssclass="code-highlight")}
41 %else:
42 ${_('File is to big to display')} ${h.link_to(_('show as raw'),
43 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
44 %endif
36 45 </div>
37 46 </div>
38 47
39 48 <script type="text/javascript">
40 49 YAHOO.util.Event.onDOMReady(function(){
41 50 YAHOO.util.Event.addListener('show_rev','click',function(e){
42 51 YAHOO.util.Event.preventDefault(e);
43 52 var cs = YAHOO.util.Dom.get('diff1').value;
44 53 var url = "${h.url('files_home',repo_name=c.repo_name,revision='__CS__',f_path=c.f_path)}".replace('__CS__',cs);
45 54 window.location = url;
46 55 });
47 56 });
48 57 </script> No newline at end of file
@@ -1,78 +1,78 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
4 4 <head>
5 5 <title>${_('Sign In to hg-app')}</title>
6 6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 7 <link rel="icon" href="/images/hgicon.png" type="image/png" />
8 8 <meta name="robots" content="index, nofollow"/>
9 9
10 10 <!-- stylesheets -->
11 11 <link rel="stylesheet" type="text/css" href="/css/reset.css" />
12 12 <link rel="stylesheet" type="text/css" href="/css/style.css" media="screen" />
13 13 <link id="color" rel="stylesheet" type="text/css" href="/css/colors/blue.css" />
14 14
15 15 <!-- scripts -->
16 16
17 17 </head>
18 18 <body>
19 19 <div id="login">
20 20 <!-- login -->
21 21 <div class="title">
22 22 <h5>${_('Sign In to hg-app')}</h5>
23 23 <div class="corner tl"></div>
24 24 <div class="corner tr"></div>
25 25 </div>
26 26 <div class="inner">
27 27 ${h.form(h.url.current(came_from=c.came_from))}
28 28 <div class="form">
29 29 <!-- fields -->
30 30
31 31 <div class="fields">
32 32 <div class="field">
33 33 <div class="label">
34 34 <label for="username">${_('Username')}:</label>
35 35 </div>
36 36 <div class="input">
37 37 ${h.text('username',class_='focus',size=40)}
38 38 </div>
39 39
40 40 </div>
41 41 <div class="field">
42 42 <div class="label">
43 43 <label for="password">${_('Password')}:</label>
44 44 </div>
45 45 <div class="input">
46 46 ${h.password('password',class_='focus',size=40)}
47 47 </div>
48 48
49 49 </div>
50 50 ##<div class="field">
51 51 ## <div class="checkbox">
52 52 ## <input type="checkbox" id="remember" name="remember" />
53 53 ## <label for="remember">Remember me</label>
54 54 ## </div>
55 55 ##</div>
56 56 <div class="buttons">
57 57 ${h.submit('sign_in','Sign In',class_="ui-button ui-widget ui-state-default ui-corner-all")}
58 58 </div>
59 59 </div>
60 60 <!-- end fields -->
61 61 <!-- links -->
62 62 <div class="links">
63 ${h.link_to(_('Forgot your password ?'),h.url('#'))}
63 ${h.link_to(_('Forgot your password ?'),h.url('reset_password'))}
64 64 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
65 65 /
66 66 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
67 67 %endif
68 68 </div>
69 69
70 70 <!-- end links -->
71 71 </div>
72 72 ${h.end_form()}
73 73 </div>
74 74 <!-- end login -->
75 75 </div>
76 76 </body>
77 77 </html>
78 78
@@ -1,69 +1,71 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3 <%def name="title()">
4 4 ${_('Search')}: ${c.cur_query}
5 5 </%def>
6 6 <%def name="breadcrumbs()">
7 7 ${c.hg_app_name}
8 8 </%def>
9 9 <%def name="page_nav()">
10 10 ${self.menu('home')}
11 11 </%def>
12 12 <%def name="main()">
13 13
14 14 <div class="box">
15 15 <!-- box / title -->
16 16 <div class="title">
17 17 <h5>${_('Search')}</h5>
18 18 </div>
19 19 <!-- end box / title -->
20 20 ${h.form('search',method='get')}
21 21 <div class="form">
22 22 <div class="fields">
23 23
24 24 <div class="field ">
25 25 <div class="label">
26 26 <label for="q">${_('Search:')}</label>
27 27 </div>
28 28 <div class="input">
29 29 ${h.text('q',c.cur_query,class_="small")}
30 30 <div class="button highlight">
31 31 <input type="submit" value="${_('Search')}" class="ui-button ui-widget ui-state-default ui-corner-all"/>
32 32 </div>
33 33 <div style="font-weight: bold;clear:both;padding: 5px">${c.runtime}</div>
34 34 </div>
35 35 </div>
36 36 </div>
37 37 </div>
38 38 ${h.end_form()}
39 39
40 40 %for cnt,sr in enumerate(c.formated_results):
41 41 %if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(sr['repository'],'search results check'):
42 42 <div class="table">
43 43 <div id="body${cnt}" class="codeblock">
44 44 <div class="code-header">
45 45 <div class="revision">${h.link_to(h.literal('%s &raquo; %s' % (sr['repository'],sr['f_path'])),
46 46 h.url('files_home',repo_name=sr['repository'],revision='tip',f_path=sr['f_path']))}</div>
47 47 </div>
48 48 <div class="code-body">
49 <pre>${h.literal(sr['content_short'])}</pre>
49 <pre>${h.literal(sr['content_short_hl'])}</pre>
50 50 </div>
51 51 </div>
52 52 </div>
53 53 %else:
54 54 %if cnt == 0:
55 55 <div class="table">
56 56 <div id="body${cnt}" class="codeblock">
57 57 <div class="error">${_('Permission denied')}</div>
58 58 </div>
59 59 </div>
60 60 %endif
61 61
62 62 %endif
63 63 %endfor
64
65
66
64 %if c.cur_query:
65 <div class="pagination-wh pagination-left">
66 ${c.formated_results.pager('$link_previous ~2~ $link_next')}
67 </div>
68 %endif
67 69 </div>
68 70
69 71 </%def>
@@ -1,63 +1,63 b''
1 1 ## -*- coding: utf-8 -*-
2 2 % if c.repo_changesets:
3 3 <table>
4 4 <tr>
5 5 <th class="left">${_('date')}</th>
6 6 <th class="left">${_('author')}</th>
7 7 <th class="left">${_('revision')}</th>
8 8 <th class="left">${_('commit message')}</th>
9 9 <th class="left">${_('branch')}</th>
10 10 <th class="left">${_('tags')}</th>
11 11 <th class="left">${_('links')}</th>
12 12
13 13 </tr>
14 14 %for cnt,cs in enumerate(c.repo_changesets):
15 15 <tr class="parity${cnt%2}">
16 <td>${h.age(cs._ctx.date())}</td>
16 <td>${h.age(cs._ctx.date())} - ${h.rfc822date_notz(cs._ctx.date())} </td>
17 17 <td title="${cs.author}">${h.person(cs.author)}</td>
18 18 <td>r${cs.revision}:${cs.raw_id}</td>
19 19 <td>
20 20 ${h.link_to(h.truncate(cs.message,60),
21 21 h.url('changeset_home',repo_name=c.repo_name,revision=cs._short),
22 22 title=cs.message)}
23 23 </td>
24 24 <td>
25 25 <span class="logtags">
26 26 <span class="branchtag">${cs.branch}</span>
27 27 </span>
28 28 </td>
29 29 <td>
30 30 <span class="logtags">
31 31 %for tag in cs.tags:
32 32 <span class="tagtag">${tag}</span>
33 33 %endfor
34 34 </span>
35 35 </td>
36 36 <td class="nowrap">
37 37 ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
38 38 |
39 39 ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
40 40 </td>
41 41 </tr>
42 42 %endfor
43 43
44 44 </table>
45 45
46 46 <script type="text/javascript">
47 47 var data_div = 'shortlog_data';
48 48 YAHOO.util.Event.onDOMReady(function(){
49 49 YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){
50 50 YAHOO.util.Dom.setStyle('shortlog_data','opacity','0.3');});});
51 51 </script>
52 52
53 53 <div class="pagination-wh pagination-left">
54 54 ${c.repo_changesets.pager('$link_previous ~2~ $link_next',
55 55 onclick="""YAHOO.util.Connect.asyncRequest('GET','$partial_url',{
56 56 success:function(o){YAHOO.util.Dom.get(data_div).innerHTML=o.responseText;
57 57 YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){
58 58 YAHOO.util.Dom.setStyle(data_div,'opacity','0.3');});
59 59 YAHOO.util.Dom.setStyle(data_div,'opacity','1');}},null); return false;""")}
60 60 </div>
61 61 %else:
62 62 ${_('There are no commits yet')}
63 63 %endif
@@ -1,301 +1,508 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${_('Mercurial Repository Overview')}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(u'Home',h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('summary')}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('summary')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <script type="text/javascript">
21 21 var E = YAHOO.util.Event;
22 22 var D = YAHOO.util.Dom;
23 23
24 24 E.onDOMReady(function(e){
25 25 id = 'clone_url';
26 26 E.addListener(id,'click',function(e){
27 27 D.get('clone_url').select();
28 28 })
29 29 })
30 30 </script>
31 31 <div class="box box-left">
32 32 <!-- box / title -->
33 33 <div class="title">
34 34 ${self.breadcrumbs()}
35 35 </div>
36 36 <!-- end box / title -->
37 37 <div class="form">
38 38 <div class="fields">
39 39
40 40 <div class="field">
41 41 <div class="label">
42 42 <label>${_('Name')}:</label>
43 43 </div>
44 44 <div class="input-short">
45 45 <span style="font-size: 1.6em;font-weight: bold">${c.repo_info.name}</span>
46 46 </div>
47 47 </div>
48 48
49 49
50 50 <div class="field">
51 51 <div class="label">
52 52 <label>${_('Description')}:</label>
53 53 </div>
54 54 <div class="input-short">
55 55 ${c.repo_info.description}
56 56 </div>
57 57 </div>
58 58
59 59
60 60 <div class="field">
61 61 <div class="label">
62 62 <label>${_('Contact')}:</label>
63 63 </div>
64 64 <div class="input-short">
65 65 <div class="gravatar">
66 66 <img alt="gravatar" src="${h.gravatar_url(c.repo_info.dbrepo.user.email)}"/>
67 67 </div>
68 68 ${_('Username')}: ${c.repo_info.dbrepo.user.username}<br/>
69 69 ${_('Name')}: ${c.repo_info.dbrepo.user.name} ${c.repo_info.dbrepo.user.lastname}<br/>
70 70 ${_('Email')}: <a href="mailto:${c.repo_info.dbrepo.user.email}">${c.repo_info.dbrepo.user.email}</a>
71 71 </div>
72 72 </div>
73 73
74 74 <div class="field">
75 75 <div class="label">
76 76 <label>${_('Last change')}:</label>
77 77 </div>
78 78 <div class="input-short">
79 79 ${h.age(c.repo_info.last_change)} - ${h.rfc822date(c.repo_info.last_change)}
80 ${_('by')} ${h.get_changeset_safe(c.repo_info,'tip').author}
81
80 82 </div>
81 83 </div>
82 84
83 85 <div class="field">
84 86 <div class="label">
85 87 <label>${_('Clone url')}:</label>
86 88 </div>
87 89 <div class="input-short">
88 90 <input type="text" id="clone_url" readonly="readonly" value="hg clone ${c.clone_repo_url}" size="70"/>
89 91 </div>
90 92 </div>
91 93
92 94 <div class="field">
93 95 <div class="label">
94 96 <label>${_('Download')}:</label>
95 97 </div>
96 98 <div class="input-short">
97 99 %for cnt,archive in enumerate(c.repo_info._get_archives()):
98 100 %if cnt >=1:
99 101 |
100 102 %endif
101 103 ${h.link_to(c.repo_info.name+'.'+archive['type'],
102 104 h.url('files_archive_home',repo_name=c.repo_info.name,
103 105 revision='tip',fileformat=archive['extension']),class_="archive_icon")}
104 106 %endfor
105 107 </div>
106 108 </div>
107 109
108 110 <div class="field">
109 111 <div class="label">
110 112 <label>${_('Feeds')}:</label>
111 113 </div>
112 114 <div class="input-short">
113 115 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo_info.name),class_='rss_icon')}
114 116 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo_info.name),class_='atom_icon')}
115 117 </div>
116 118 </div>
117 119 </div>
118 120 </div>
119 121 </div>
120 122
121 123 <div class="box box-right" style="min-height:455px">
122 124 <!-- box / title -->
123 125 <div class="title">
124 <h5>${_('Last month commit activity')}</h5>
126 <h5>${_('Commit activity by day / author')}</h5>
125 127 </div>
126 128
127 129 <div class="table">
128 130 <div id="commit_history" style="width:560px;height:300px;float:left"></div>
129 <div id="legend_data">
131 <div style="clear: both;height: 10px"></div>
132 <div id="overview" style="width:560px;height:100px;float:left"></div>
133
134 <div id="legend_data" style="clear:both;margin-top:10px;">
130 135 <div id="legend_container"></div>
131 136 <div id="legend_choices">
132 137 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
133 138 </div>
134 139 </div>
135 140 <script type="text/javascript">
136
137 (function () {
138 var datasets = {${c.commit_data|n}};
139 var i = 0;
141 /**
142 * Plots summary graph
143 *
144 * @class SummaryPlot
145 * @param {from} initial from for detailed graph
146 * @param {to} initial to for detailed graph
147 * @param {dataset}
148 * @param {overview_dataset}
149 */
150 function SummaryPlot(from,to,dataset,overview_dataset) {
151 var initial_ranges = {
152 "xaxis":{
153 "from":from,
154 "to":to,
155 },
156 };
157 var dataset = dataset;
158 var overview_dataset = [overview_dataset];
140 159 var choiceContainer = YAHOO.util.Dom.get("legend_choices");
141 160 var choiceContainerTable = YAHOO.util.Dom.get("legend_choices_tables");
142 for(var key in datasets) {
143 datasets[key].color = i;
144 i++;
145 choiceContainerTable.innerHTML += '<tr><td>'+
146 '<input type="checkbox" name="' + key +'" checked="checked" />'
147 +datasets[key].label+
148 '</td></tr>';
161 var plotContainer = YAHOO.util.Dom.get('commit_history');
162 var overviewContainer = YAHOO.util.Dom.get('overview');
163
164 var plot_options = {
165 bars: {show:true,align:'center',lineWidth:4},
166 legend: {show:true, container:"legend_container"},
167 points: {show:true,radius:0,fill:false},
168 yaxis: {tickDecimals:0,},
169 xaxis: {
170 mode: "time",
171 timeformat: "%d/%m",
172 min:from,
173 max:to,
174 },
175 grid: {
176 hoverable: true,
177 clickable: true,
178 autoHighlight:true,
179 color: "#999"
180 },
181 //selection: {mode: "x"}
182 };
183 var overview_options = {
184 legend:{show:false},
185 bars: {show:true,barWidth: 2,},
186 shadowSize: 0,
187 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
188 yaxis: {ticks: 3, min: 0,},
189 grid: {color: "#999",},
190 selection: {mode: "x"}
149 191 };
150 192
151
152 function plotAccordingToChoices() {
153 var data = [];
154
155 var inputs = choiceContainer.getElementsByTagName("input");
156 for(var i=0; i<inputs.length; i++) {
157 var key = inputs[i].name;
158 if (key && datasets[key]){
159 if(!inputs[i].checked){
160 data.push({label:key,data:[[0,1],]});
193 /**
194 *get dummy data needed in few places
195 */
196 function getDummyData(label){
197 return {"label":label,
198 "data":[{"time":0,
199 "commits":0,
200 "added":0,
201 "changed":0,
202 "removed":0,
203 }],
204 "schema":["commits"],
205 "color":'#ffffff',
161 206 }
162 else{
163 data.push(datasets[key]);
164 207 }
165 208
209 /**
210 * generate checkboxes accordindly to data
211 * @param keys
212 * @returns
213 */
214 function generateCheckboxes(data) {
215 //append checkboxes
216 var i = 0;
217 choiceContainerTable.innerHTML = '';
218 for(var pos in data) {
219
220 data[pos].color = i;
221 i++;
222 if(data[pos].label != ''){
223 choiceContainerTable.innerHTML += '<tr><td>'+
224 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
225 +data[pos].label+
226 '</td></tr>';
227 }
228 }
166 229 }
167 230
168 };
169
170 if (data.length > 0){
171
172 var plot = YAHOO.widget.Flot("commit_history", data,
173 { bars: { show: true, align:'center',lineWidth:4 },
174 points: { show: true, radius:0,fill:true },
175 legend:{show:true, container:"legend_container"},
176 selection: { mode: "xy" },
177 yaxis: {tickDecimals:0},
178 xaxis: { mode: "time", timeformat: "%d",tickSize:[1, "day"],min:${c.ts_min},max:${c.ts_max} },
179 grid: { hoverable: true, clickable: true,autoHighlight:true },
180 });
181
231 /**
232 * ToolTip show
233 */
182 234 function showTooltip(x, y, contents) {
183 235 var div=document.getElementById('tooltip');
184 236 if(!div) {
185 237 div = document.createElement('div');
186 238 div.id="tooltip";
187 239 div.style.position="absolute";
188 240 div.style.border='1px solid #fdd';
189 241 div.style.padding='2px';
190 242 div.style.backgroundColor='#fee';
191 243 document.body.appendChild(div);
192 244 }
193 245 YAHOO.util.Dom.setStyle(div, 'opacity', 0);
194 246 div.innerHTML = contents;
195 247 div.style.top=(y + 5) + "px";
196 248 div.style.left=(x + 5) + "px";
197 249
198 250 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
199 251 anim.animate();
200 252 }
201 253
254 /**
255 * This function will detect if selected period has some changesets for this user
256 if it does this data is then pushed for displaying
257 Additionally it will only display users that are selected by the checkbox
258 */
259 function getDataAccordingToRanges(ranges) {
260
261 var data = [];
262 var keys = [];
263 for(var key in dataset){
264 var push = false;
265 //method1 slow !!
266 ///*
267 for(var ds in dataset[key].data){
268 commit_data = dataset[key].data[ds];
269 //console.log(key);
270 //console.log(new Date(commit_data.time*1000));
271 //console.log(new Date(ranges.xaxis.from*1000));
272 //console.log(new Date(ranges.xaxis.to*1000));
273 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
274 push = true;
275 break;
276 }
277 }
278 //*/
279 /*//method2 sorted commit data !!!
280 var first_commit = dataset[key].data[0].time;
281 var last_commit = dataset[key].data[dataset[key].data.length-1].time;
282
283 console.log(first_commit);
284 console.log(last_commit);
285
286 if (first_commit >= ranges.xaxis.from && last_commit <= ranges.xaxis.to){
287 push = true;
288 }
289 */
290 if(push){
291 data.push(dataset[key]);
292 }
293 }
294 if(data.length >= 1){
295 return data;
296 }
297 else{
298 //just return dummy data for graph to plot itself
299 return [getDummyData('')];
300 }
301
302 }
303
304 /**
305 * redraw using new checkbox data
306 */
307 function plotchoiced(e,args){
308 var cur_data = args[0];
309 var cur_ranges = args[1];
310
311 var new_data = [];
312 var inputs = choiceContainer.getElementsByTagName("input");
313
314 //show only checked labels
315 for(var i=0; i<inputs.length; i++) {
316 var checkbox_key = inputs[i].name;
317
318 if(inputs[i].checked){
319 for(var d in cur_data){
320 if(cur_data[d].label == checkbox_key){
321 new_data.push(cur_data[d]);
322 }
323 }
324 }
325 else{
326 //push dummy data to not hide the label
327 new_data.push(getDummyData(checkbox_key));
328 }
329 }
330
331 var new_options = YAHOO.lang.merge(plot_options, {
332 xaxis: {
333 min: cur_ranges.xaxis.from,
334 max: cur_ranges.xaxis.to,
335 mode:"time",
336 timeformat: "%d/%m",
337 }
338 });
339 if (!new_data){
340 new_data = [[0,1]];
341 }
342 // do the zooming
343 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
344
345 plot.subscribe("plotselected", plotselected);
346
347 //resubscribe plothover
348 plot.subscribe("plothover", plothover);
349
350 // don't fire event on the overview to prevent eternal loop
351 overview.setSelection(cur_ranges, true);
352
353 }
354
355 /**
356 * plot only selected items from overview
357 * @param ranges
358 * @returns
359 */
360 function plotselected(ranges,cur_data) {
361 //updates the data for new plot
362 data = getDataAccordingToRanges(ranges);
363 generateCheckboxes(data);
364
365 var new_options = YAHOO.lang.merge(plot_options, {
366 xaxis: {
367 min: ranges.xaxis.from,
368 max: ranges.xaxis.to,
369 mode:"time",
370 timeformat: "%d/%m",
371 }
372 });
373 // do the zooming
374 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
375
376 plot.subscribe("plotselected", plotselected);
377
378 //resubscribe plothover
379 plot.subscribe("plothover", plothover);
380
381 // don't fire event on the overview to prevent eternal loop
382 overview.setSelection(ranges, true);
383
384 //resubscribe choiced
385 YAHOO.util.Event.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
386 }
387
202 388 var previousPoint = null;
203 plot.subscribe("plothover", function (o) {
389
390 function plothover(o) {
204 391 var pos = o.pos;
205 392 var item = o.item;
206 393
207 394 //YAHOO.util.Dom.get("x").innerHTML = pos.x.toFixed(2);
208 395 //YAHOO.util.Dom.get("y").innerHTML = pos.y.toFixed(2);
209 396 if (item) {
210 397 if (previousPoint != item.datapoint) {
211 398 previousPoint = item.datapoint;
212 399
213 400 var tooltip = YAHOO.util.Dom.get("tooltip");
214 401 if(tooltip) {
215 402 tooltip.parentNode.removeChild(tooltip);
216 403 }
217 404 var x = item.datapoint.x.toFixed(2);
218 405 var y = item.datapoint.y.toFixed(2);
219 406
220 407 if (!item.series.label){
221 408 item.series.label = 'commits';
222 409 }
223 410 var d = new Date(x*1000);
224 var fd = d.getFullYear()+'-'+(d.getMonth()+1)+'-'+d.getDate();
411 var fd = d.toDateString()
225 412 var nr_commits = parseInt(y);
226 413
227 var cur_data = datasets[item.series.label].data[item.dataIndex];
414 var cur_data = dataset[item.series.label].data[item.dataIndex];
228 415 var added = cur_data.added;
229 416 var changed = cur_data.changed;
230 417 var removed = cur_data.removed;
231 418
232 419 var nr_commits_suffix = " ${_('commits')} ";
233 420 var added_suffix = " ${_('files added')} ";
234 421 var changed_suffix = " ${_('files changed')} ";
235 422 var removed_suffix = " ${_('files removed')} ";
236 423
237 424
238 425 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
239 426 if(added==1){added_suffix=" ${_('file added')} ";}
240 427 if(changed==1){changed_suffix=" ${_('file changed')} ";}
241 428 if(removed==1){removed_suffix=" ${_('file removed')} ";}
242 429
243 430 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
244 431 +'<br/>'+
245 432 nr_commits + nr_commits_suffix+'<br/>'+
246 433 added + added_suffix +'<br/>'+
247 434 changed + changed_suffix + '<br/>'+
248 435 removed + removed_suffix + '<br/>');
249 436 }
250 437 }
251 438 else {
252 439 var tooltip = YAHOO.util.Dom.get("tooltip");
253 440
254 441 if(tooltip) {
255 442 tooltip.parentNode.removeChild(tooltip);
256 443 }
257 444 previousPoint = null;
258 445 }
259 });
260
261 }
262 446 }
263 447
264 YAHOO.util.Event.on(choiceContainer.getElementsByTagName("input"), "click", plotAccordingToChoices);
448 /**
449 * MAIN EXECUTION
450 */
451
452 var data = getDataAccordingToRanges(initial_ranges);
453 generateCheckboxes(data);
454
455 //main plot
456 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
457
458 //overview
459 var overview = YAHOO.widget.Flot(overviewContainer, overview_dataset, overview_options);
265 460
266 plotAccordingToChoices();
267 })();
461 //show initial selection on overview
462 overview.setSelection(initial_ranges);
463
464 plot.subscribe("plotselected", plotselected);
465
466 overview.subscribe("plotselected", function (ranges) {
467 plot.setSelection(ranges);
468 });
469
470 plot.subscribe("plothover", plothover);
471
472 YAHOO.util.Event.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
473 }
474 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
268 475 </script>
269 476
270 477 </div>
271 478 </div>
272 479
273 480 <div class="box">
274 481 <div class="title">
275 482 <div class="breadcrumbs">${h.link_to(_('Last ten changes'),h.url('changelog_home',repo_name=c.repo_name))}</div>
276 483 </div>
277 484 <div class="table">
278 485 <%include file='../shortlog/shortlog_data.html'/>
279 486 ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
280 487 </div>
281 488 </div>
282 489 <div class="box">
283 490 <div class="title">
284 491 <div class="breadcrumbs">${h.link_to(_('Last ten tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
285 492 </div>
286 493 <div class="table">
287 494 <%include file='../tags/tags_data.html'/>
288 495 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
289 496 </div>
290 497 </div>
291 498 <div class="box">
292 499 <div class="title">
293 500 <div class="breadcrumbs">${h.link_to(_('Last ten branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
294 501 </div>
295 502 <div class="table">
296 503 <%include file='../branches/branches_data.html'/>
297 504 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
298 505 </div>
299 506 </div>
300 507
301 508 </%def> No newline at end of file
@@ -1,45 +1,51 b''
1 1 """Pylons application test package
2 2
3 3 This package assumes the Pylons environment is already loaded, such as
4 4 when this script is imported from the `nosetests --with-pylons=test.ini`
5 5 command.
6 6
7 7 This module initializes the application via ``websetup`` (`paster
8 8 setup-app`) and provides the base testing objects.
9 9 """
10 10 from unittest import TestCase
11 11
12 12 from paste.deploy import loadapp
13 13 from paste.script.appinstall import SetupCommand
14 14 from pylons import config, url
15 15 from routes.util import URLGenerator
16 16 from webtest import TestApp
17 17 import os
18 18 from pylons_app.model import meta
19 import logging
20
21
22 log = logging.getLogger(__name__)
23
19 24 import pylons.test
20 25
21 26 __all__ = ['environ', 'url', 'TestController']
22 27
23 28 # Invoke websetup with the current config file
24 SetupCommand('setup-app').run([pylons.test.pylonsapp.config['__file__']])
29 #SetupCommand('setup-app').run([config_file])
30
25 31
26 32 environ = {}
27 33
28 34 class TestController(TestCase):
29 35
30 36 def __init__(self, *args, **kwargs):
31 37 wsgiapp = pylons.test.pylonsapp
32 38 config = wsgiapp.config
33 39 self.app = TestApp(wsgiapp)
34 40 url._push_object(URLGenerator(config['routes.map'], environ))
35 41 self.sa = meta.Session
42
36 43 TestCase.__init__(self, *args, **kwargs)
37 44
38
39 def log_user(self):
45 def log_user(self, username='test_admin', password='test'):
40 46 response = self.app.post(url(controller='login', action='index'),
41 {'username':'test_admin',
42 'password':'test'})
47 {'username':username,
48 'password':password})
43 49 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
44 50 assert response.session['hg_app_user'].username == 'test_admin', 'wrong logged in user'
45 return response.follow() No newline at end of file
51 return response.follow()
@@ -1,7 +1,9 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestAdminController(TestController):
4 4
5 5 def test_index(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='admin/admin', action='index'))
8 assert 'Admin dashboard - journal' in response.body,'No proper title in dashboard'
7 9 # Test response...
@@ -1,43 +1,116 b''
1 1 from pylons_app.tests import *
2 from pylons_app.model.db import User
2 3
3 4 class TestSettingsController(TestController):
4 5
5 6 def test_index(self):
6 7 response = self.app.get(url('admin_settings'))
7 8 # Test response...
8 9
9 10 def test_index_as_xml(self):
10 11 response = self.app.get(url('formatted_admin_settings', format='xml'))
11 12
12 13 def test_create(self):
13 14 response = self.app.post(url('admin_settings'))
14 15
15 16 def test_new(self):
16 17 response = self.app.get(url('admin_new_setting'))
17 18
18 19 def test_new_as_xml(self):
19 20 response = self.app.get(url('formatted_admin_new_setting', format='xml'))
20 21
21 22 def test_update(self):
22 23 response = self.app.put(url('admin_setting', setting_id=1))
23 24
24 25 def test_update_browser_fakeout(self):
25 26 response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='put'))
26 27
27 28 def test_delete(self):
28 29 response = self.app.delete(url('admin_setting', setting_id=1))
29 30
30 31 def test_delete_browser_fakeout(self):
31 32 response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='delete'))
32 33
33 34 def test_show(self):
34 35 response = self.app.get(url('admin_setting', setting_id=1))
35 36
36 37 def test_show_as_xml(self):
37 38 response = self.app.get(url('formatted_admin_setting', setting_id=1, format='xml'))
38 39
39 40 def test_edit(self):
40 41 response = self.app.get(url('admin_edit_setting', setting_id=1))
41 42
42 43 def test_edit_as_xml(self):
43 44 response = self.app.get(url('formatted_admin_edit_setting', setting_id=1, format='xml'))
45
46 def test_my_account(self):
47 self.log_user()
48 response = self.app.get(url('admin_settings_my_account'))
49 print response
50 assert 'value="test_admin' in response.body
51
52
53
54 def test_my_account_update(self):
55 self.log_user()
56 new_email = 'new@mail.pl'
57 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
58 _method='put',
59 username='test_admin',
60 new_password='test',
61 password='',
62 name='NewName',
63 lastname='NewLastname',
64 email=new_email,))
65 response.follow()
66 print response
67
68 print 'x' * 100
69 print response.session
70 assert 'Your account was updated succesfully' in response.session['flash'][0][1], 'no flash message about success of change'
71 user = self.sa.query(User).filter(User.username == 'test_admin').one()
72 assert user.email == new_email , 'incorrect user email after update got %s vs %s' % (user.email, new_email)
73
74 def test_my_account_update_own_email_ok(self):
75 self.log_user()
76
77 new_email = 'new@mail.pl'
78 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
79 _method='put',
80 username='test_admin',
81 new_password='test',
82 name='NewName',
83 lastname='NewLastname',
84 email=new_email,))
85 print response
86
87 def test_my_account_update_err_email_exists(self):
88 self.log_user()
89
90 new_email = 'test_regular@mail.com'#already exisitn email
91 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
92 _method='put',
93 username='test_admin',
94 new_password='test',
95 name='NewName',
96 lastname='NewLastname',
97 email=new_email,))
98 print response
99
100 assert 'That e-mail address is already taken' in response.body, 'Missing error message about existing email'
101
102
103 def test_my_account_update_err(self):
104 self.log_user()
105
106 new_email = 'newmail.pl'
107 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
108 _method='put',
109 username='test_regular2',
110 new_password='test',
111 name='NewName',
112 lastname='NewLastname',
113 email=new_email,))
114 print response
115 assert 'An email address must contain a single @' in response.body, 'Missing error message about wrong email'
116 assert 'This username already exists' in response.body, 'Missing error message about existing user'
@@ -1,7 +1,8 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestBranchesController(TestController):
4 4
5 5 def test_index(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='branches', action='index',repo_name='vcs_test'))
7 8 # Test response...
@@ -1,7 +1,8 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestChangelogController(TestController):
4 4
5 5 def test_index(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='changelog', action='index',repo_name='vcs_test'))
7 8 # Test response...
@@ -1,13 +1,15 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestFeedController(TestController):
4 4
5 5 def test_rss(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='feed', action='rss',
7 8 repo_name='vcs_test'))
8 9 # Test response...
9 10
10 11 def test_atom(self):
12 self.log_user()
11 13 response = self.app.get(url(controller='feed', action='atom',
12 14 repo_name='vcs_test'))
13 15 # Test response... No newline at end of file
@@ -1,10 +1,11 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestFilesController(TestController):
4 4
5 5 def test_index(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='files', action='index',
7 8 repo_name='vcs_test',
8 9 revision='tip',
9 10 f_path='/'))
10 11 # Test response...
@@ -1,111 +1,139 b''
1 1 from pylons_app.tests import *
2 2 from pylons_app.model.db import User
3 3 from pylons_app.lib.auth import check_password
4 4
5 5
6 6 class TestLoginController(TestController):
7 7
8 8 def test_index(self):
9 9 response = self.app.get(url(controller='login', action='index'))
10 10 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
11 11 # Test response...
12 12
13 13 def test_login_admin_ok(self):
14 14 response = self.app.post(url(controller='login', action='index'),
15 15 {'username':'test_admin',
16 16 'password':'test'})
17 17 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
18 18 assert response.session['hg_app_user'].username == 'test_admin', 'wrong logged in user'
19 19 response = response.follow()
20 20 assert 'auto description for vcs_test' in response.body
21 21
22 22 def test_login_regular_ok(self):
23 23 response = self.app.post(url(controller='login', action='index'),
24 24 {'username':'test_regular',
25 25 'password':'test'})
26 26 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
27 27 assert response.session['hg_app_user'].username == 'test_regular', 'wrong logged in user'
28 28 response = response.follow()
29 29 assert 'auto description for vcs_test' in response.body
30 30 assert '<a title="Admin" href="/_admin">' not in response.body
31 31
32 32 def test_login_ok_came_from(self):
33 33 test_came_from = '/_admin/users'
34 34 response = self.app.post(url(controller='login', action='index', came_from=test_came_from),
35 35 {'username':'test_admin',
36 36 'password':'test'})
37 37 assert response.status == '302 Found', 'Wrong response code from came from redirection'
38 38 response = response.follow()
39 39
40 40 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
41 41 assert 'Users administration' in response.body, 'No proper title in response'
42 42
43 43
44 44 def test_login_wrong(self):
45 45 response = self.app.post(url(controller='login', action='index'),
46 46 {'username':'error',
47 47 'password':'test'})
48 48 assert response.status == '200 OK', 'Wrong response from login page'
49 49
50 50 assert 'invalid user name' in response.body, 'No error username message in response'
51 51 assert 'invalid password' in response.body, 'No error password message in response'
52 52
53 53
54 54 def test_register(self):
55 55 response = self.app.get(url(controller='login', action='register'))
56 56 assert 'Sign Up to hg-app' in response.body, 'wrong page for user registration'
57 57
58 58 def test_register_err_same_username(self):
59 59 response = self.app.post(url(controller='login', action='register'),
60 60 {'username':'test_admin',
61 61 'password':'test',
62 62 'email':'goodmail@domain.com',
63 63 'name':'test',
64 64 'lastname':'test'})
65 65
66 66 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
67 67 assert 'This username already exists' in response.body
68 68
69 69 def test_register_err_wrong_data(self):
70 70 response = self.app.post(url(controller='login', action='register'),
71 71 {'username':'xs',
72 72 'password':'',
73 73 'email':'goodmailm',
74 74 'name':'test',
75 75 'lastname':'test'})
76 76
77 77 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
78 78 assert 'An email address must contain a single @' in response.body
79 79 assert 'Enter a value 3 characters long or more' in response.body
80 80 assert 'Please enter a value<' in response.body
81 81
82 82
83 83
84 84 def test_register_ok(self):
85 username = 'test_regular2'
85 username = 'test_regular4'
86 86 password = 'qweqwe'
87 email = 'goodmail@mail.com'
87 email = 'marcin@test.com'
88 88 name = 'testname'
89 89 lastname = 'testlastname'
90 90
91 91 response = self.app.post(url(controller='login', action='register'),
92 92 {'username':username,
93 93 'password':password,
94 94 'email':email,
95 95 'name':name,
96 96 'lastname':lastname})
97
97 print response.body
98 98 assert response.status == '302 Found', 'Wrong response from register page got %s' % response.status
99 assert 'You have successfully registered into hg-app' in response.session['flash'][0], 'No flash message about user registration'
99 100
100 ret = self.sa.query(User).filter(User.username == 'test_regular2').one()
101 ret = self.sa.query(User).filter(User.username == 'test_regular4').one()
101 102 assert ret.username == username , 'field mismatch %s %s' % (ret.username, username)
102 103 assert check_password(password,ret.password) == True , 'password mismatch'
103 104 assert ret.email == email , 'field mismatch %s %s' % (ret.email, email)
104 105 assert ret.name == name , 'field mismatch %s %s' % (ret.name, name)
105 106 assert ret.lastname == lastname , 'field mismatch %s %s' % (ret.lastname, lastname)
106 107
107 108
109 def test_forgot_password_wrong_mail(self):
110 response = self.app.post(url(controller='login', action='password_reset'),
111 {'email':'marcin@wrongmail.org', })
112
113 assert "That e-mail address doesn't exist" in response.body, 'Missing error message about wrong email'
114
115 def test_forgot_password(self):
116 response = self.app.get(url(controller='login', action='password_reset'))
117 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
118
119 username = 'test_password_reset_1'
120 password = 'qweqwe'
121 email = 'marcin@python-works.com'
122 name = 'passwd'
123 lastname = 'reset'
124
125 response = self.app.post(url(controller='login', action='register'),
126 {'username':username,
127 'password':password,
128 'email':email,
129 'name':name,
130 'lastname':lastname})
131 #register new user for email test
132 response = self.app.post(url(controller='login', action='password_reset'),
133 {'email':email, })
134 print response.session['flash']
135 assert 'You have successfully registered into hg-app' in response.session['flash'][0], 'No flash message about user registration'
136 assert 'Your new password was sent' in response.session['flash'][1], 'No flash message about password reset'
108 137
109 138
110 139
111
@@ -1,29 +1,38 b''
1 1 from pylons_app.tests import *
2 2 from pylons_app.lib.indexers import IDX_LOCATION
3 3 import os
4 4 from nose.plugins.skip import SkipTest
5 5
6 6 class TestSearchController(TestController):
7 7
8 8 def test_index(self):
9 9 self.log_user()
10 10 response = self.app.get(url(controller='search', action='index'))
11 11 print response.body
12 12 assert 'class="small" id="q" name="q" type="text"' in response.body,'Search box content error'
13 13 # Test response...
14 14
15 15 def test_empty_search(self):
16 16
17 17 if os.path.isdir(IDX_LOCATION):
18 18 raise SkipTest('skipped due to existing index')
19 19 else:
20 20 self.log_user()
21 21 response = self.app.get(url(controller='search', action='index'),{'q':'vcs_test'})
22 22 assert 'There is no index to search in. Please run whoosh indexer' in response.body,'No error message about empty index'
23 23
24 24 def test_normal_search(self):
25 25 self.log_user()
26 response = self.app.get(url(controller='search', action='index'),{'q':'def+repo'})
26 response = self.app.get(url(controller='search', action='index'), {'q':'def repo'})
27 27 print response.body
28 assert '9 results' in response.body,'no message about proper search results'
28 assert '10 results' in response.body, 'no message about proper search results'
29 assert 'Permission denied' not in response.body, 'Wrong permissions settings for that repo and user'
30
29 31
32 def test_repo_search(self):
33 self.log_user()
34 response = self.app.get(url(controller='search', action='index'), {'q':'repository:vcs_test def test'})
35 print response.body
36 assert '4 results' in response.body, 'no message about proper search results'
37 assert 'Permission denied' not in response.body, 'Wrong permissions settings for that repo and user'
38
@@ -1,8 +1,9 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestSettingsController(TestController):
4 4
5 5 def test_index(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='settings', action='index',
7 8 repo_name='vcs_test'))
8 9 # Test response...
@@ -1,7 +1,8 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestShortlogController(TestController):
4 4
5 5 def test_index(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='shortlog', action='index',repo_name='vcs_test'))
7 8 # Test response...
@@ -1,7 +1,8 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestSummaryController(TestController):
4 4
5 5 def test_index(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='summary', action='index',repo_name='vcs_test'))
7 8 # Test response...
@@ -1,7 +1,8 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestTagsController(TestController):
4 4
5 5 def test_index(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='tags', action='index',repo_name='vcs_test'))
7 8 # Test response...
@@ -1,47 +1,32 b''
1 1 """Setup the pylons_app application"""
2 2
3 from os.path import dirname as dn, join as jn
3 from os.path import dirname as dn
4 4 from pylons_app.config.environment import load_environment
5 5 from pylons_app.lib.db_manage import DbManage
6 import datetime
7 from time import mktime
8 6 import logging
9 7 import os
10 8 import sys
11 import tarfile
12 9
13 10 log = logging.getLogger(__name__)
14 11
15 12 ROOT = dn(dn(os.path.realpath(__file__)))
16 13 sys.path.append(ROOT)
17 14
15
18 16 def setup_app(command, conf, vars):
19 17 """Place any commands to setup pylons_app here"""
20 18 log_sql = True
21 19 tests = False
20 REPO_TEST_PATH = None
22 21
23 22 dbname = os.path.split(conf['sqlalchemy.db1.url'])[-1]
24 filename = os.path.split(conf.filename)[-1]
25
26 if filename == 'tests.ini':
27 uniq_suffix = str(int(mktime(datetime.datetime.now().timetuple())))
28 REPO_TEST_PATH = '/tmp/hg_app_test_%s' % uniq_suffix
29
30 if not os.path.isdir(REPO_TEST_PATH):
31 os.mkdir(REPO_TEST_PATH)
32 cur_dir = dn(os.path.abspath(__file__))
33 tar = tarfile.open(jn(cur_dir,'tests',"vcs_test.tar.gz"))
34 tar.extractall(REPO_TEST_PATH)
35 tar.close()
36
37 tests = True
38 23
39 24 dbmanage = DbManage(log_sql, dbname, tests)
40 25 dbmanage.create_tables(override=True)
41 26 dbmanage.config_prompt(REPO_TEST_PATH)
42 27 dbmanage.create_default_user()
43 28 dbmanage.admin_prompt()
44 29 dbmanage.create_permissions()
45 30 dbmanage.populate_default_permissions()
46 31 load_environment(conf.global_conf, conf.local_conf, initial=True)
47 32
@@ -1,34 +1,34 b''
1 1 [egg_info]
2 2 tag_build = dev
3 3 tag_svn_revision = true
4 4
5 5 [easy_install]
6 6 find_links = http://www.pylonshq.com/download/
7 7
8 8 [nosetests]
9 9 verbose=True
10 10 verbosity=2
11 with-pylons=tests.ini
11 with-pylons=test.ini
12 12 detailed-errors=1
13 13
14 14 # Babel configuration
15 15 [compile_catalog]
16 16 domain = pylons_app
17 17 directory = pylons_app/i18n
18 18 statistics = true
19 19
20 20 [extract_messages]
21 21 add_comments = TRANSLATORS:
22 22 output_file = pylons_app/i18n/pylons_app.pot
23 23 width = 80
24 24
25 25 [init_catalog]
26 26 domain = pylons_app
27 27 input_file = pylons_app/i18n/pylons_app.pot
28 28 output_dir = pylons_app/i18n
29 29
30 30 [update_catalog]
31 31 domain = pylons_app
32 32 input_file = pylons_app/i18n/pylons_app.pot
33 33 output_dir = pylons_app/i18n
34 34 previous = true
@@ -1,48 +1,49 b''
1 1 from pylons_app import get_version
2 2 try:
3 3 from setuptools import setup, find_packages
4 4 except ImportError:
5 5 from ez_setup import use_setuptools
6 6 use_setuptools()
7 7 from setuptools import setup, find_packages
8 8
9 9 setup(
10 10 name='HgApp-%s'%get_version(),
11 11 version=get_version(),
12 12 description='Mercurial repository serving and browsing app',
13 13 keywords='mercurial web hgwebdir replacement serving hgweb',
14 14 license='BSD',
15 15 author='marcin kuzminski',
16 16 author_email='marcin@python-works.com',
17 17 url='http://hg.python-works.com',
18 18 install_requires=[
19 19 "Pylons>=1.0.0",
20 20 "SQLAlchemy>=0.6",
21 21 "babel",
22 22 "Mako>=0.3.2",
23 "vcs>=0.1.4",
23 "vcs>=0.1.5",
24 24 "pygments>=1.3.0",
25 25 "mercurial>=1.6",
26 26 "pysqlite",
27 "whoosh==1.0.0b10",
27 "whoosh==1.0.0b17",
28 28 "py-bcrypt",
29 "celery",
29 30 ],
30 31 setup_requires=["PasteScript>=1.6.3"],
31 32 packages=find_packages(exclude=['ez_setup']),
32 33 include_package_data=True,
33 34 test_suite='nose.collector',
34 35 package_data={'pylons_app': ['i18n/*/LC_MESSAGES/*.mo']},
35 36 message_extractors={'pylons_app': [
36 37 ('**.py', 'python', None),
37 38 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
38 39 ('public/**', 'ignore', None)]},
39 40 zip_safe=False,
40 41 paster_plugins=['PasteScript', 'Pylons'],
41 42 entry_points="""
42 43 [paste.app_factory]
43 44 main = pylons_app.config.middleware:make_app
44 45
45 46 [paste.app_install]
46 47 main = pylons.util:PylonsInstaller
47 48 """,
48 49 )
@@ -1,155 +1,160 b''
1 1 ################################################################################
2 2 ################################################################################
3 # pylons_app - Pylons environment configuration #
3 # hg-app - Pylons environment configuration #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
9 9 debug = true
10 ############################################
11 ## Uncomment and replace with the address ##
12 ## which should receive any error reports ##
13 ############################################
10 ################################################################################
11 ## Uncomment and replace with the address which should receive ##
12 ## any error reports after application crash ##
13 ## Additionally those settings will be used by hg-app mailing system ##
14 ################################################################################
14 15 #email_to = admin@localhost
16 #error_email_from = paste_error@localhost
17 #app_email_from = hg-app-noreply@localhost
18 #error_message =
19
15 20 #smtp_server = mail.server.com
16 #error_email_from = paste_error@localhost
17 21 #smtp_username =
18 22 #smtp_password =
19 #error_message = 'mercurial crash !'
23 #smtp_port =
24 #smtp_use_tls = false
20 25
21 26 [server:main]
22 27 ##nr of threads to spawn
23 28 threadpool_workers = 5
24 29
25 ##max request before
30 ##max request before thread respawn
26 31 threadpool_max_requests = 2
27 32
28 33 ##option to use threads of process
29 34 use_threadpool = true
30 35
31 36 use = egg:Paste#http
32 37 host = 127.0.0.1
33 38 port = 5000
34 39
35 40 [app:main]
36 41 use = egg:pylons_app
37 42 full_stack = true
38 43 static_files = true
39 44 lang=en
40 45 cache_dir = %(here)s/data
41 46
42 47 ####################################
43 48 ### BEAKER CACHE ####
44 49 ####################################
45 50 beaker.cache.data_dir=/%(here)s/data/cache/data
46 51 beaker.cache.lock_dir=/%(here)s/data/cache/lock
47 52 beaker.cache.regions=super_short_term,short_term,long_term
48 53 beaker.cache.long_term.type=memory
49 54 beaker.cache.long_term.expire=36000
50 55 beaker.cache.short_term.type=memory
51 56 beaker.cache.short_term.expire=60
52 57 beaker.cache.super_short_term.type=memory
53 58 beaker.cache.super_short_term.expire=10
54 59
55 60 ####################################
56 61 ### BEAKER SESSION ####
57 62 ####################################
58 63 ## Type of storage used for the session, current types are
59 ## “dbm”, “file”, “memcached”, “database”, and “memory”.
64 ## "dbm", "file", "memcached", "database", and "memory".
60 65 ## The storage uses the Container API
61 66 ##that is also used by the cache system.
62 67 beaker.session.type = file
63 68
64 69 beaker.session.key = hg-app
65 70 beaker.session.secret = g654dcno0-9873jhgfreyu
66 71 beaker.session.timeout = 36000
67 72
68 73 ##auto save the session to not to use .save()
69 74 beaker.session.auto = False
70 75
71 76 ##true exire at browser close
72 77 #beaker.session.cookie_expires = 3600
73 78
74 79
75 80 ################################################################################
76 81 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
77 82 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
78 83 ## execute malicious code after an exception is raised. ##
79 84 ################################################################################
80 85 #set debug = false
81 86
82 87 ##################################
83 88 ### LOGVIEW CONFIG ###
84 89 ##################################
85 90 logview.sqlalchemy = #faa
86 91 logview.pylons.templating = #bfb
87 92 logview.pylons.util = #eee
88 93
89 94 #########################################################
90 95 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
91 96 #########################################################
92 97 sqlalchemy.db1.url = sqlite:///%(here)s/test.db
93 98 #sqlalchemy.db1.echo = False
94 99 #sqlalchemy.db1.pool_recycle = 3600
95 100 sqlalchemy.convert_unicode = true
96 101
97 102 ################################
98 103 ### LOGGING CONFIGURATION ####
99 104 ################################
100 105 [loggers]
101 106 keys = root, routes, pylons_app, sqlalchemy
102 107
103 108 [handlers]
104 109 keys = console
105 110
106 111 [formatters]
107 112 keys = generic,color_formatter
108 113
109 114 #############
110 115 ## LOGGERS ##
111 116 #############
112 117 [logger_root]
113 118 level = ERROR
114 119 handlers = console
115 120
116 121 [logger_routes]
117 122 level = ERROR
118 123 handlers = console
119 124 qualname = routes.middleware
120 125 # "level = DEBUG" logs the route matched and routing variables.
121 126
122 127 [logger_pylons_app]
123 128 level = ERROR
124 129 handlers = console
125 130 qualname = pylons_app
126 131 propagate = 0
127 132
128 133 [logger_sqlalchemy]
129 134 level = ERROR
130 135 handlers = console
131 136 qualname = sqlalchemy.engine
132 137 propagate = 0
133 138
134 139 ##############
135 140 ## HANDLERS ##
136 141 ##############
137 142
138 143 [handler_console]
139 144 class = StreamHandler
140 145 args = (sys.stderr,)
141 146 level = NOTSET
142 147 formatter = color_formatter
143 148
144 149 ################
145 150 ## FORMATTERS ##
146 151 ################
147 152
148 153 [formatter_generic]
149 154 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
150 155 datefmt = %Y-%m-%d %H:%M:%S
151 156
152 157 [formatter_color_formatter]
153 158 class=pylons_app.lib.colored_formatter.ColorFormatter
154 159 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
155 160 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now