Show More
@@ -158,9 +158,9 INPUT:: | |||||
158 | args : { |
|
158 | args : { | |
159 | "username" : "<username>", |
|
159 | "username" : "<username>", | |
160 | "password" : "<password>", |
|
160 | "password" : "<password>", | |
161 |
" |
|
161 | "email" : "<useremail>", | |
162 |
" |
|
162 | "firstname" : "<firstname> = None", | |
163 |
" |
|
163 | "lastname" : "<lastname> = None", | |
164 | "active" : "<bool> = True", |
|
164 | "active" : "<bool> = True", | |
165 | "admin" : "<bool> = False", |
|
165 | "admin" : "<bool> = False", | |
166 | "ldap_dn" : "<ldap_dn> = None" |
|
166 | "ldap_dn" : "<ldap_dn> = None" |
@@ -35,6 +35,7 news | |||||
35 | based on user defined regular expression |
|
35 | based on user defined regular expression | |
36 | - added linking of changesets in commit messages |
|
36 | - added linking of changesets in commit messages | |
37 | - new compact changelog with expandable commit messages |
|
37 | - new compact changelog with expandable commit messages | |
|
38 | - firstname and lastname are optional in user creation | |||
38 |
|
39 | |||
39 | fixes |
|
40 | fixes | |
40 | ----- |
|
41 | ----- |
@@ -108,8 +108,9 class UsersGroupsController(BaseControll | |||||
108 | # url('users_group', id=ID) |
|
108 | # url('users_group', id=ID) | |
109 |
|
109 | |||
110 | c.users_group = UsersGroup.get(id) |
|
110 | c.users_group = UsersGroup.get(id) | |
111 |
c.group_members = [ |
|
111 | c.group_members_obj = [x.user for x in c.users_group.members] | |
112 | c.users_group.members] |
|
112 | c.group_members = [(x.user_id, x.username) for x in | |
|
113 | c.group_members_obj] | |||
113 |
|
114 | |||
114 | c.available_members = [(x.user_id, x.username) for x in |
|
115 | c.available_members = [(x.user_id, x.username) for x in | |
115 | self.sa.query(User).all()] |
|
116 | self.sa.query(User).all()] | |
@@ -181,8 +182,9 class UsersGroupsController(BaseControll | |||||
181 | return redirect(url('users_groups')) |
|
182 | return redirect(url('users_groups')) | |
182 |
|
183 | |||
183 | c.users_group.permissions = {} |
|
184 | c.users_group.permissions = {} | |
184 |
c.group_members = [ |
|
185 | c.group_members_obj = [x.user for x in c.users_group.members] | |
185 | c.users_group.members] |
|
186 | c.group_members = [(x.user_id, x.username) for x in | |
|
187 | c.group_members_obj] | |||
186 | c.available_members = [(x.user_id, x.username) for x in |
|
188 | c.available_members = [(x.user_id, x.username) for x in | |
187 | self.sa.query(User).all()] |
|
189 | self.sa.query(User).all()] | |
188 | defaults = c.users_group.get_dict() |
|
190 | defaults = c.users_group.get_dict() |
@@ -131,17 +131,17 class ApiController(JSONRPCController): | |||||
131 | return result |
|
131 | return result | |
132 |
|
132 | |||
133 | @HasPermissionAllDecorator('hg.admin') |
|
133 | @HasPermissionAllDecorator('hg.admin') | |
134 | def create_user(self, apiuser, username, password, firstname, |
|
134 | def create_user(self, apiuser, username, password, email, firstname=None, | |
135 |
lastname |
|
135 | lastname=None, active=True, admin=False, ldap_dn=None): | |
136 | """ |
|
136 | """ | |
137 | Create new user or updates current one |
|
137 | Create new user or updates current one | |
138 |
|
138 | |||
139 | :param apiuser: |
|
139 | :param apiuser: | |
140 | :param username: |
|
140 | :param username: | |
141 | :param password: |
|
141 | :param password: | |
|
142 | :param email: | |||
142 | :param name: |
|
143 | :param name: | |
143 | :param lastname: |
|
144 | :param lastname: | |
144 | :param email: |
|
|||
145 | :param active: |
|
145 | :param active: | |
146 | :param admin: |
|
146 | :param admin: | |
147 | :param ldap_dn: |
|
147 | :param ldap_dn: |
@@ -129,6 +129,7 def get_crypt_password(password): | |||||
129 | def check_password(password, hashed): |
|
129 | def check_password(password, hashed): | |
130 | return RhodeCodeCrypto.hash_check(password, hashed) |
|
130 | return RhodeCodeCrypto.hash_check(password, hashed) | |
131 |
|
131 | |||
|
132 | ||||
132 | def generate_api_key(str_, salt=None): |
|
133 | def generate_api_key(str_, salt=None): | |
133 | """ |
|
134 | """ | |
134 | Generates API KEY from given string |
|
135 | Generates API KEY from given string | |
@@ -237,6 +238,7 def authenticate(username, password): | |||||
237 | pass |
|
238 | pass | |
238 | return False |
|
239 | return False | |
239 |
|
240 | |||
|
241 | ||||
240 | def login_container_auth(username): |
|
242 | def login_container_auth(username): | |
241 | user = User.get_by_username(username) |
|
243 | user = User.get_by_username(username) | |
242 | if user is None: |
|
244 | if user is None: | |
@@ -260,6 +262,7 def login_container_auth(username): | |||||
260 | user.username) |
|
262 | user.username) | |
261 | return user |
|
263 | return user | |
262 |
|
264 | |||
|
265 | ||||
263 | def get_container_username(environ, config): |
|
266 | def get_container_username(environ, config): | |
264 | username = None |
|
267 | username = None | |
265 |
|
268 | |||
@@ -278,6 +281,7 def get_container_username(environ, conf | |||||
278 |
|
281 | |||
279 | return username |
|
282 | return username | |
280 |
|
283 | |||
|
284 | ||||
281 | class AuthUser(object): |
|
285 | class AuthUser(object): | |
282 | """ |
|
286 | """ | |
283 | A simple object that handles all attributes of user in RhodeCode |
|
287 | A simple object that handles all attributes of user in RhodeCode | |
@@ -302,6 +306,7 class AuthUser(object): | |||||
302 | self.permissions = {} |
|
306 | self.permissions = {} | |
303 | self._api_key = api_key |
|
307 | self._api_key = api_key | |
304 | self.propagate_data() |
|
308 | self.propagate_data() | |
|
309 | self._instance = None | |||
305 |
|
310 | |||
306 | def propagate_data(self): |
|
311 | def propagate_data(self): | |
307 | user_model = UserModel() |
|
312 | user_model = UserModel() | |
@@ -350,10 +355,6 class AuthUser(object): | |||||
350 | def is_admin(self): |
|
355 | def is_admin(self): | |
351 | return self.admin |
|
356 | return self.admin | |
352 |
|
357 | |||
353 | @property |
|
|||
354 | def full_contact(self): |
|
|||
355 | return '%s %s <%s>' % (self.name, self.lastname, self.email) |
|
|||
356 |
|
||||
357 | def __repr__(self): |
|
358 | def __repr__(self): | |
358 | return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username, |
|
359 | return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username, | |
359 | self.is_authenticated) |
|
360 | self.is_authenticated) | |
@@ -363,9 +364,9 class AuthUser(object): | |||||
363 | self.is_authenticated = authenticated |
|
364 | self.is_authenticated = authenticated | |
364 |
|
365 | |||
365 | def get_cookie_store(self): |
|
366 | def get_cookie_store(self): | |
366 | return {'username':self.username, |
|
367 | return {'username': self.username, | |
367 | 'user_id': self.user_id, |
|
368 | 'user_id': self.user_id, | |
368 | 'is_authenticated':self.is_authenticated} |
|
369 | 'is_authenticated': self.is_authenticated} | |
369 |
|
370 | |||
370 | @classmethod |
|
371 | @classmethod | |
371 | def from_cookie_store(cls, cookie_store): |
|
372 | def from_cookie_store(cls, cookie_store): | |
@@ -374,6 +375,7 class AuthUser(object): | |||||
374 | api_key = cookie_store.get('api_key') |
|
375 | api_key = cookie_store.get('api_key') | |
375 | return AuthUser(user_id, api_key, username) |
|
376 | return AuthUser(user_id, api_key, username) | |
376 |
|
377 | |||
|
378 | ||||
377 | def set_available_permissions(config): |
|
379 | def set_available_permissions(config): | |
378 | """ |
|
380 | """ | |
379 | This function will propagate pylons globals with all available defined |
|
381 | This function will propagate pylons globals with all available defined | |
@@ -388,7 +390,7 def set_available_permissions(config): | |||||
388 | try: |
|
390 | try: | |
389 | sa = meta.Session |
|
391 | sa = meta.Session | |
390 | all_perms = sa.query(Permission).all() |
|
392 | all_perms = sa.query(Permission).all() | |
391 | except: |
|
393 | except Exception: | |
392 | pass |
|
394 | pass | |
393 | finally: |
|
395 | finally: | |
394 | meta.Session.remove() |
|
396 | meta.Session.remove() |
@@ -299,6 +299,11 class User(Base, BaseModel): | |||||
299 | return '%s %s' % (self.name, self.lastname) |
|
299 | return '%s %s' % (self.name, self.lastname) | |
300 |
|
300 | |||
301 | @property |
|
301 | @property | |
|
302 | def full_name_or_username(self): | |||
|
303 | return ('%s %s' % (self.name, self.lastname) | |||
|
304 | if (self.name and self.lastname) else self.username) | |||
|
305 | ||||
|
306 | @property | |||
302 | def full_contact(self): |
|
307 | def full_contact(self): | |
303 | return '%s %s <%s>' % (self.name, self.lastname, self.email) |
|
308 | return '%s %s <%s>' % (self.name, self.lastname, self.email) | |
304 |
|
309 | |||
@@ -354,8 +359,13 class User(Base, BaseModel): | |||||
354 | log.debug('updated user %s lastlogin', self.username) |
|
359 | log.debug('updated user %s lastlogin', self.username) | |
355 |
|
360 | |||
356 | def __json__(self): |
|
361 | def __json__(self): | |
357 |
return dict( |
|
362 | return dict( | |
358 | full_name=self.full_name) |
|
363 | email=self.email, | |
|
364 | full_name=self.full_name, | |||
|
365 | full_name_or_username=self.full_name_or_username, | |||
|
366 | short_contact=self.short_contact, | |||
|
367 | full_contact=self.full_contact | |||
|
368 | ) | |||
359 |
|
369 | |||
360 |
|
370 | |||
361 | class UserLog(Base, BaseModel): |
|
371 | class UserLog(Base, BaseModel): |
@@ -51,13 +51,17 class State_obj(object): | |||||
51 | # VALIDATORS |
|
51 | # VALIDATORS | |
52 | #============================================================================== |
|
52 | #============================================================================== | |
53 | class ValidAuthToken(formencode.validators.FancyValidator): |
|
53 | class ValidAuthToken(formencode.validators.FancyValidator): | |
54 | messages = {'invalid_token':_('Token mismatch')} |
|
54 | messages = {'invalid_token': _('Token mismatch')} | |
55 |
|
55 | |||
56 | def validate_python(self, value, state): |
|
56 | def validate_python(self, value, state): | |
57 |
|
57 | |||
58 | if value != authentication_token(): |
|
58 | if value != authentication_token(): | |
59 |
raise formencode.Invalid( |
|
59 | raise formencode.Invalid( | |
60 | search_number=value), value, state) |
|
60 | self.message('invalid_token', | |
|
61 | state, search_number=value), | |||
|
62 | value, | |||
|
63 | state | |||
|
64 | ) | |||
61 |
|
65 | |||
62 |
|
66 | |||
63 | def ValidUsername(edit, old_data): |
|
67 | def ValidUsername(edit, old_data): | |
@@ -77,11 +81,13 def ValidUsername(edit, old_data): | |||||
77 | 'exists') , value, state) |
|
81 | 'exists') , value, state) | |
78 |
|
82 | |||
79 | if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: |
|
83 | if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: | |
80 |
raise formencode.Invalid( |
|
84 | raise formencode.Invalid( | |
81 |
|
|
85 | _('Username may only contain alphanumeric characters ' | |
82 |
|
|
86 | 'underscores, periods or dashes and must begin with ' | |
83 | 'and must begin with alphanumeric ' |
|
87 | 'alphanumeric character'), | |
84 | 'character'), value, state) |
|
88 | value, | |
|
89 | state | |||
|
90 | ) | |||
85 |
|
91 | |||
86 | return _ValidUsername |
|
92 | return _ValidUsername | |
87 |
|
93 | |||
@@ -103,15 +109,17 def ValidUsersGroup(edit, old_data): | |||||
103 | if UsersGroup.get_by_group_name(value, cache=False, |
|
109 | if UsersGroup.get_by_group_name(value, cache=False, | |
104 | case_insensitive=True): |
|
110 | case_insensitive=True): | |
105 | raise formencode.Invalid(_('This users group ' |
|
111 | raise formencode.Invalid(_('This users group ' | |
106 |
'already exists') |
|
112 | 'already exists'), value, | |
107 | state) |
|
113 | state) | |
108 |
|
114 | |||
109 | if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: |
|
115 | if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: | |
110 |
raise formencode.Invalid( |
|
116 | raise formencode.Invalid( | |
111 |
|
|
117 | _('RepoGroup name may only contain alphanumeric characters ' | |
112 |
|
|
118 | 'underscores, periods or dashes and must begin with ' | |
113 | 'and must begin with alphanumeric ' |
|
119 | 'alphanumeric character'), | |
114 | 'character'), value, state) |
|
120 | value, | |
|
121 | state | |||
|
122 | ) | |||
115 |
|
123 | |||
116 | return _ValidUsersGroup |
|
124 | return _ValidUsersGroup | |
117 |
|
125 | |||
@@ -177,32 +185,35 class ValidPassword(formencode.validator | |||||
177 |
|
185 | |||
178 | def to_python(self, value, state): |
|
186 | def to_python(self, value, state): | |
179 |
|
187 | |||
180 | if value: |
|
188 | if not value: | |
|
189 | return | |||
181 |
|
190 | |||
182 |
|
|
191 | if value.get('password'): | |
183 |
|
|
192 | try: | |
184 |
|
|
193 | value['password'] = get_crypt_password(value['password']) | |
185 |
|
|
194 | except UnicodeEncodeError: | |
186 |
|
|
195 | e_dict = {'password': _('Invalid characters in password')} | |
187 |
|
|
196 | raise formencode.Invalid('', value, state, error_dict=e_dict) | |
188 |
|
197 | |||
189 |
|
|
198 | if value.get('password_confirmation'): | |
190 |
|
|
199 | try: | |
191 |
|
|
200 | value['password_confirmation'] = \ | |
192 |
|
|
201 | get_crypt_password(value['password_confirmation']) | |
193 |
|
|
202 | except UnicodeEncodeError: | |
194 | e_dict = {'password_confirmation':_('Invalid characters in password')} |
|
203 | e_dict = { | |
195 | raise formencode.Invalid('', value, state, error_dict=e_dict) |
|
204 | 'password_confirmation': _('Invalid characters in password') | |
|
205 | } | |||
|
206 | raise formencode.Invalid('', value, state, error_dict=e_dict) | |||
196 |
|
207 | |||
197 |
|
|
208 | if value.get('new_password'): | |
198 |
|
|
209 | try: | |
199 |
|
|
210 | value['new_password'] = \ | |
200 |
|
|
211 | get_crypt_password(value['new_password']) | |
201 |
|
|
212 | except UnicodeEncodeError: | |
202 |
|
|
213 | e_dict = {'new_password': _('Invalid characters in password')} | |
203 |
|
|
214 | raise formencode.Invalid('', value, state, error_dict=e_dict) | |
204 |
|
215 | |||
205 |
|
|
216 | return value | |
206 |
|
217 | |||
207 |
|
218 | |||
208 | class ValidPasswordsMatch(formencode.validators.FancyValidator): |
|
219 | class ValidPasswordsMatch(formencode.validators.FancyValidator): | |
@@ -224,9 +235,9 class ValidAuth(formencode.validators.Fa | |||||
224 | } |
|
235 | } | |
225 |
|
236 | |||
226 | # error mapping |
|
237 | # error mapping | |
227 | e_dict = {'username':messages['invalid_login'], |
|
238 | e_dict = {'username': messages['invalid_login'], | |
228 | 'password':messages['invalid_password']} |
|
239 | 'password': messages['invalid_password']} | |
229 | e_dict_disable = {'username':messages['disabled_account']} |
|
240 | e_dict_disable = {'username': messages['disabled_account']} | |
230 |
|
241 | |||
231 | def validate_python(self, value, state): |
|
242 | def validate_python(self, value, state): | |
232 | password = value['password'] |
|
243 | password = value['password'] | |
@@ -238,15 +249,19 class ValidAuth(formencode.validators.Fa | |||||
238 | else: |
|
249 | else: | |
239 | if user and user.active is False: |
|
250 | if user and user.active is False: | |
240 | log.warning('user %s is disabled', username) |
|
251 | log.warning('user %s is disabled', username) | |
241 |
raise formencode.Invalid( |
|
252 | raise formencode.Invalid( | |
242 | state=State_obj), |
|
253 | self.message('disabled_account', | |
243 | value, state, |
|
254 | state=State_obj), | |
244 | error_dict=self.e_dict_disable) |
|
255 | value, state, | |
|
256 | error_dict=self.e_dict_disable | |||
|
257 | ) | |||
245 | else: |
|
258 | else: | |
246 | log.warning('user %s not authenticated', username) |
|
259 | log.warning('user %s not authenticated', username) | |
247 |
raise formencode.Invalid( |
|
260 | raise formencode.Invalid( | |
248 | state=State_obj), value, state, |
|
261 | self.message('invalid_password', | |
249 | error_dict=self.e_dict) |
|
262 | state=State_obj), value, state, | |
|
263 | error_dict=self.e_dict | |||
|
264 | ) | |||
250 |
|
265 | |||
251 |
|
266 | |||
252 | class ValidRepoUser(formencode.validators.FancyValidator): |
|
267 | class ValidRepoUser(formencode.validators.FancyValidator): | |
@@ -272,7 +287,6 def ValidRepoName(edit, old_data): | |||||
272 | e_dict = {'repo_name': _('This repository name is disallowed')} |
|
287 | e_dict = {'repo_name': _('This repository name is disallowed')} | |
273 | raise formencode.Invalid('', value, state, error_dict=e_dict) |
|
288 | raise formencode.Invalid('', value, state, error_dict=e_dict) | |
274 |
|
289 | |||
275 |
|
||||
276 | if value.get('repo_group'): |
|
290 | if value.get('repo_group'): | |
277 | gr = RepoGroup.get(value.get('repo_group')) |
|
291 | gr = RepoGroup.get(value.get('repo_group')) | |
278 | group_path = gr.full_path |
|
292 | group_path = gr.full_path | |
@@ -285,7 +299,6 def ValidRepoName(edit, old_data): | |||||
285 | group_path = '' |
|
299 | group_path = '' | |
286 | repo_name_full = repo_name |
|
300 | repo_name_full = repo_name | |
287 |
|
301 | |||
288 |
|
||||
289 | value['repo_name_full'] = repo_name_full |
|
302 | value['repo_name_full'] = repo_name_full | |
290 | rename = old_data.get('repo_name') != repo_name_full |
|
303 | rename = old_data.get('repo_name') != repo_name_full | |
291 | create = not edit |
|
304 | create = not edit | |
@@ -293,20 +306,22 def ValidRepoName(edit, old_data): | |||||
293 |
|
306 | |||
294 | if group_path != '': |
|
307 | if group_path != '': | |
295 | if Repository.get_by_repo_name(repo_name_full): |
|
308 | if Repository.get_by_repo_name(repo_name_full): | |
296 |
e_dict = { |
|
309 | e_dict = { | |
297 | 'exists in a group "%s"') % |
|
310 | 'repo_name': _('This repository already exists in ' | |
298 |
gr.group_name |
|
311 | 'a group "%s"') % gr.group_name | |
|
312 | } | |||
299 | raise formencode.Invalid('', value, state, |
|
313 | raise formencode.Invalid('', value, state, | |
300 | error_dict=e_dict) |
|
314 | error_dict=e_dict) | |
301 | elif RepoGroup.get_by_group_name(repo_name_full): |
|
315 | elif RepoGroup.get_by_group_name(repo_name_full): | |
302 |
e_dict = { |
|
316 | e_dict = { | |
303 | ' name already "%s"') % |
|
317 | 'repo_name': _('There is a group with this name ' | |
304 |
repo_name_full |
|
318 | 'already "%s"') % repo_name_full | |
|
319 | } | |||
305 | raise formencode.Invalid('', value, state, |
|
320 | raise formencode.Invalid('', value, state, | |
306 | error_dict=e_dict) |
|
321 | error_dict=e_dict) | |
307 |
|
322 | |||
308 | elif Repository.get_by_repo_name(repo_name_full): |
|
323 | elif Repository.get_by_repo_name(repo_name_full): | |
309 | e_dict = {'repo_name':_('This repository ' |
|
324 | e_dict = {'repo_name': _('This repository ' | |
310 | 'already exists')} |
|
325 | 'already exists')} | |
311 | raise formencode.Invalid('', value, state, |
|
326 | raise formencode.Invalid('', value, state, | |
312 | error_dict=e_dict) |
|
327 | error_dict=e_dict) | |
@@ -341,14 +356,14 def ValidCloneUri(): | |||||
341 | elif value.startswith('https'): |
|
356 | elif value.startswith('https'): | |
342 | try: |
|
357 | try: | |
343 | httpsrepository(make_ui('db'), value).capabilities |
|
358 | httpsrepository(make_ui('db'), value).capabilities | |
344 |
except Exception |
|
359 | except Exception: | |
345 | log.error(traceback.format_exc()) |
|
360 | log.error(traceback.format_exc()) | |
346 | raise formencode.Invalid(_('invalid clone url'), value, |
|
361 | raise formencode.Invalid(_('invalid clone url'), value, | |
347 | state) |
|
362 | state) | |
348 | elif value.startswith('http'): |
|
363 | elif value.startswith('http'): | |
349 | try: |
|
364 | try: | |
350 | httprepository(make_ui('db'), value).capabilities |
|
365 | httprepository(make_ui('db'), value).capabilities | |
351 |
except Exception |
|
366 | except Exception: | |
352 | log.error(traceback.format_exc()) |
|
367 | log.error(traceback.format_exc()) | |
353 | raise formencode.Invalid(_('invalid clone url'), value, |
|
368 | raise formencode.Invalid(_('invalid clone url'), value, | |
354 | state) |
|
369 | state) | |
@@ -374,7 +389,7 def ValidForkType(old_data): | |||||
374 |
|
389 | |||
375 |
|
390 | |||
376 | class ValidPerms(formencode.validators.FancyValidator): |
|
391 | class ValidPerms(formencode.validators.FancyValidator): | |
377 | messages = {'perm_new_member_name':_('This username or users group name' |
|
392 | messages = {'perm_new_member_name': _('This username or users group name' | |
378 | ' is not valid')} |
|
393 | ' is not valid')} | |
379 |
|
394 | |||
380 | def to_python(self, value, state): |
|
395 | def to_python(self, value, state): | |
@@ -393,8 +408,9 class ValidPerms(formencode.validators.F | |||||
393 | perms_new.append((new_member, new_perm, new_type)) |
|
408 | perms_new.append((new_member, new_perm, new_type)) | |
394 | elif k.startswith('u_perm_') or k.startswith('g_perm_'): |
|
409 | elif k.startswith('u_perm_') or k.startswith('g_perm_'): | |
395 | member = k[7:] |
|
410 | member = k[7:] | |
396 | t = {'u':'user', |
|
411 | t = {'u': 'user', | |
397 |
'g':'users_group' |
|
412 | 'g': 'users_group' | |
|
413 | }[k[0]] | |||
398 | if member == 'default': |
|
414 | if member == 'default': | |
399 | if value['private']: |
|
415 | if value['private']: | |
400 | #set none for default when updating to private repo |
|
416 | #set none for default when updating to private repo | |
@@ -419,8 +435,9 class ValidPerms(formencode.validators.F | |||||
419 | except Exception: |
|
435 | except Exception: | |
420 | msg = self.message('perm_new_member_name', |
|
436 | msg = self.message('perm_new_member_name', | |
421 | state=State_obj) |
|
437 | state=State_obj) | |
422 |
raise formencode.Invalid( |
|
438 | raise formencode.Invalid( | |
423 |
|
|
439 | msg, value, state, error_dict={'perm_new_member_name': msg} | |
|
440 | ) | |||
424 | return value |
|
441 | return value | |
425 |
|
442 | |||
426 |
|
443 | |||
@@ -428,9 +445,8 class ValidSettings(formencode.validator | |||||
428 |
|
445 | |||
429 | def to_python(self, value, state): |
|
446 | def to_python(self, value, state): | |
430 | # settings form can't edit user |
|
447 | # settings form can't edit user | |
431 |
if |
|
448 | if 'user' in value: | |
432 | del['value']['user'] |
|
449 | del['value']['user'] | |
433 |
|
||||
434 | return value |
|
450 | return value | |
435 |
|
451 | |||
436 |
|
452 | |||
@@ -440,7 +456,7 class ValidPath(formencode.validators.Fa | |||||
440 | if not os.path.isdir(value): |
|
456 | if not os.path.isdir(value): | |
441 | msg = _('This is not a valid path') |
|
457 | msg = _('This is not a valid path') | |
442 | raise formencode.Invalid(msg, value, state, |
|
458 | raise formencode.Invalid(msg, value, state, | |
443 | error_dict={'paths_root_path':msg}) |
|
459 | error_dict={'paths_root_path': msg}) | |
444 | return value |
|
460 | return value | |
445 |
|
461 | |||
446 |
|
462 | |||
@@ -448,12 +464,12 def UniqSystemEmail(old_data): | |||||
448 | class _UniqSystemEmail(formencode.validators.FancyValidator): |
|
464 | class _UniqSystemEmail(formencode.validators.FancyValidator): | |
449 | def to_python(self, value, state): |
|
465 | def to_python(self, value, state): | |
450 | value = value.lower() |
|
466 | value = value.lower() | |
451 | if old_data.get('email','').lower() != value: |
|
467 | if old_data.get('email', '').lower() != value: | |
452 | user = User.get_by_email(value, case_insensitive=True) |
|
468 | user = User.get_by_email(value, case_insensitive=True) | |
453 | if user: |
|
469 | if user: | |
454 | raise formencode.Invalid( |
|
470 | raise formencode.Invalid( | |
455 |
|
|
471 | _("This e-mail address is already taken"), value, state | |
456 |
|
|
472 | ) | |
457 | return value |
|
473 | return value | |
458 |
|
474 | |||
459 | return _UniqSystemEmail |
|
475 | return _UniqSystemEmail | |
@@ -464,8 +480,9 class ValidSystemEmail(formencode.valida | |||||
464 | value = value.lower() |
|
480 | value = value.lower() | |
465 | user = User.get_by_email(value, case_insensitive=True) |
|
481 | user = User.get_by_email(value, case_insensitive=True) | |
466 | if user is None: |
|
482 | if user is None: | |
467 |
raise formencode.Invalid( |
|
483 | raise formencode.Invalid( | |
468 | value, state) |
|
484 | _("This e-mail address doesn't exist."), value, state | |
|
485 | ) | |||
469 |
|
486 | |||
470 | return value |
|
487 | return value | |
471 |
|
488 | |||
@@ -486,14 +503,15 class AttrLoginValidator(formencode.vali | |||||
486 | def to_python(self, value, state): |
|
503 | def to_python(self, value, state): | |
487 |
|
504 | |||
488 | if not value or not isinstance(value, (str, unicode)): |
|
505 | if not value or not isinstance(value, (str, unicode)): | |
489 |
raise formencode.Invalid( |
|
506 | raise formencode.Invalid( | |
490 | "must be specified - this is the name " |
|
507 | _("The LDAP Login attribute of the CN must be specified - " | |
491 |
|
|
508 | "this is the name of the attribute that is equivalent " | |
492 |
|
|
509 | "to 'username'"), value, state | |
493 | value, state) |
|
510 | ) | |
494 |
|
511 | |||
495 | return value |
|
512 | return value | |
496 |
|
513 | |||
|
514 | ||||
497 | #============================================================================== |
|
515 | #============================================================================== | |
498 | # FORMS |
|
516 | # FORMS | |
499 | #============================================================================== |
|
517 | #============================================================================== | |
@@ -501,22 +519,22 class LoginForm(formencode.Schema): | |||||
501 | allow_extra_fields = True |
|
519 | allow_extra_fields = True | |
502 | filter_extra_fields = True |
|
520 | filter_extra_fields = True | |
503 | username = UnicodeString( |
|
521 | username = UnicodeString( | |
504 |
|
|
522 | strip=True, | |
505 | min=1, |
|
523 | min=1, | |
506 |
|
|
524 | not_empty=True, | |
507 |
|
|
525 | messages={ | |
508 |
|
|
526 | 'empty': _('Please enter a login'), | |
509 |
|
|
527 | 'tooShort': _('Enter a value %(min)i characters long or more')} | |
510 | ) |
|
528 | ) | |
511 |
|
529 | |||
512 | password = UnicodeString( |
|
530 | password = UnicodeString( | |
513 |
|
|
531 | strip=True, | |
514 | min=3, |
|
532 | min=3, | |
515 |
|
|
533 | not_empty=True, | |
516 |
|
|
534 | messages={ | |
517 |
|
|
535 | 'empty': _('Please enter a password'), | |
518 |
|
|
536 | 'tooShort': _('Enter %(min)i characters or more')} | |
519 | ) |
|
537 | ) | |
520 |
|
538 | |||
521 | remember = StringBoolean(if_missing=False) |
|
539 | remember = StringBoolean(if_missing=False) | |
522 |
|
540 | |||
@@ -531,15 +549,17 def UserForm(edit=False, old_data={}): | |||||
531 | ValidUsername(edit, old_data)) |
|
549 | ValidUsername(edit, old_data)) | |
532 | if edit: |
|
550 | if edit: | |
533 | new_password = All(UnicodeString(strip=True, min=6, not_empty=False)) |
|
551 | new_password = All(UnicodeString(strip=True, min=6, not_empty=False)) | |
534 |
password_confirmation = All(UnicodeString(strip=True, min=6, |
|
552 | password_confirmation = All(UnicodeString(strip=True, min=6, | |
|
553 | not_empty=False)) | |||
535 | admin = StringBoolean(if_missing=False) |
|
554 | admin = StringBoolean(if_missing=False) | |
536 | else: |
|
555 | else: | |
537 | password = All(UnicodeString(strip=True, min=6, not_empty=True)) |
|
556 | password = All(UnicodeString(strip=True, min=6, not_empty=True)) | |
538 |
password_confirmation = All(UnicodeString(strip=True, min=6, |
|
557 | password_confirmation = All(UnicodeString(strip=True, min=6, | |
|
558 | not_empty=False)) | |||
539 |
|
559 | |||
540 | active = StringBoolean(if_missing=False) |
|
560 | active = StringBoolean(if_missing=False) | |
541 |
name = UnicodeString(strip=True, min=1, not_empty= |
|
561 | name = UnicodeString(strip=True, min=1, not_empty=False) | |
542 |
lastname = UnicodeString(strip=True, min=1, not_empty= |
|
562 | lastname = UnicodeString(strip=True, min=1, not_empty=False) | |
543 | email = All(Email(not_empty=True), UniqSystemEmail(old_data)) |
|
563 | email = All(Email(not_empty=True), UniqSystemEmail(old_data)) | |
544 |
|
564 | |||
545 | chained_validators = [ValidPasswordsMatch, ValidPassword] |
|
565 | chained_validators = [ValidPasswordsMatch, ValidPassword] | |
@@ -592,14 +612,15 def RegisterForm(edit=False, old_data={} | |||||
592 | password = All(UnicodeString(strip=True, min=6, not_empty=True)) |
|
612 | password = All(UnicodeString(strip=True, min=6, not_empty=True)) | |
593 | password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True)) |
|
613 | password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True)) | |
594 | active = StringBoolean(if_missing=False) |
|
614 | active = StringBoolean(if_missing=False) | |
595 |
name = UnicodeString(strip=True, min=1, not_empty= |
|
615 | name = UnicodeString(strip=True, min=1, not_empty=False) | |
596 |
lastname = UnicodeString(strip=True, min=1, not_empty= |
|
616 | lastname = UnicodeString(strip=True, min=1, not_empty=False) | |
597 | email = All(Email(not_empty=True), UniqSystemEmail(old_data)) |
|
617 | email = All(Email(not_empty=True), UniqSystemEmail(old_data)) | |
598 |
|
618 | |||
599 | chained_validators = [ValidPasswordsMatch, ValidPassword] |
|
619 | chained_validators = [ValidPasswordsMatch, ValidPassword] | |
600 |
|
620 | |||
601 | return _RegisterForm |
|
621 | return _RegisterForm | |
602 |
|
622 | |||
|
623 | ||||
603 | def PasswordResetForm(): |
|
624 | def PasswordResetForm(): | |
604 | class _PasswordResetForm(formencode.Schema): |
|
625 | class _PasswordResetForm(formencode.Schema): | |
605 | allow_extra_fields = True |
|
626 | allow_extra_fields = True | |
@@ -607,6 +628,7 def PasswordResetForm(): | |||||
607 | email = All(ValidSystemEmail(), Email(not_empty=True)) |
|
628 | email = All(ValidSystemEmail(), Email(not_empty=True)) | |
608 | return _PasswordResetForm |
|
629 | return _PasswordResetForm | |
609 |
|
630 | |||
|
631 | ||||
610 | def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(), |
|
632 | def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(), | |
611 | repo_groups=[]): |
|
633 | repo_groups=[]): | |
612 | class _RepoForm(formencode.Schema): |
|
634 | class _RepoForm(formencode.Schema): | |
@@ -630,6 +652,7 def RepoForm(edit=False, old_data={}, su | |||||
630 | chained_validators = [ValidRepoName(edit, old_data), ValidPerms] |
|
652 | chained_validators = [ValidRepoName(edit, old_data), ValidPerms] | |
631 | return _RepoForm |
|
653 | return _RepoForm | |
632 |
|
654 | |||
|
655 | ||||
633 | def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(), |
|
656 | def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(), | |
634 | repo_groups=[]): |
|
657 | repo_groups=[]): | |
635 | class _RepoForkForm(formencode.Schema): |
|
658 | class _RepoForkForm(formencode.Schema): | |
@@ -648,6 +671,7 def RepoForkForm(edit=False, old_data={} | |||||
648 |
|
671 | |||
649 | return _RepoForkForm |
|
672 | return _RepoForkForm | |
650 |
|
673 | |||
|
674 | ||||
651 | def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(), |
|
675 | def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(), | |
652 | repo_groups=[]): |
|
676 | repo_groups=[]): | |
653 | class _RepoForm(formencode.Schema): |
|
677 | class _RepoForm(formencode.Schema): | |
@@ -674,6 +698,7 def ApplicationSettingsForm(): | |||||
674 |
|
698 | |||
675 | return _ApplicationSettingsForm |
|
699 | return _ApplicationSettingsForm | |
676 |
|
700 | |||
|
701 | ||||
677 | def ApplicationUiSettingsForm(): |
|
702 | def ApplicationUiSettingsForm(): | |
678 | class _ApplicationUiSettingsForm(formencode.Schema): |
|
703 | class _ApplicationUiSettingsForm(formencode.Schema): | |
679 | allow_extra_fields = True |
|
704 | allow_extra_fields = True | |
@@ -687,6 +712,7 def ApplicationUiSettingsForm(): | |||||
687 |
|
712 | |||
688 | return _ApplicationUiSettingsForm |
|
713 | return _ApplicationUiSettingsForm | |
689 |
|
714 | |||
|
715 | ||||
690 | def DefaultPermissionsForm(perms_choices, register_choices, create_choices): |
|
716 | def DefaultPermissionsForm(perms_choices, register_choices, create_choices): | |
691 | class _DefaultPermissionsForm(formencode.Schema): |
|
717 | class _DefaultPermissionsForm(formencode.Schema): | |
692 | allow_extra_fields = True |
|
718 | allow_extra_fields = True |
@@ -92,7 +92,6 class UserModel(BaseModel): | |||||
92 | log.error(traceback.format_exc()) |
|
92 | log.error(traceback.format_exc()) | |
93 | raise |
|
93 | raise | |
94 |
|
94 | |||
95 |
|
||||
96 | def create_or_update(self, username, password, email, name, lastname, |
|
95 | def create_or_update(self, username, password, email, name, lastname, | |
97 | active=True, admin=False, ldap_dn=None): |
|
96 | active=True, admin=False, ldap_dn=None): | |
98 | """ |
|
97 | """ | |
@@ -136,7 +135,6 class UserModel(BaseModel): | |||||
136 | log.error(traceback.format_exc()) |
|
135 | log.error(traceback.format_exc()) | |
137 | raise |
|
136 | raise | |
138 |
|
137 | |||
139 |
|
||||
140 | def create_for_container_auth(self, username, attrs): |
|
138 | def create_for_container_auth(self, username, attrs): | |
141 | """ |
|
139 | """ | |
142 | Creates the given user if it's not already in the database |
|
140 | Creates the given user if it's not already in the database | |
@@ -231,7 +229,7 class UserModel(BaseModel): | |||||
231 | body = body % (new_user.username, new_user.full_name, |
|
229 | body = body % (new_user.username, new_user.full_name, | |
232 | new_user.email) |
|
230 | new_user.email) | |
233 | edit_url = url('edit_user', id=new_user.user_id, qualified=True) |
|
231 | edit_url = url('edit_user', id=new_user.user_id, qualified=True) | |
234 | kw = {'registered_user_url':edit_url} |
|
232 | kw = {'registered_user_url': edit_url} | |
235 | NotificationModel().create(created_by=new_user, subject=subject, |
|
233 | NotificationModel().create(created_by=new_user, subject=subject, | |
236 | body=body, recipients=None, |
|
234 | body=body, recipients=None, | |
237 | type_=Notification.TYPE_REGISTRATION, |
|
235 | type_=Notification.TYPE_REGISTRATION, | |
@@ -493,7 +491,6 class UserModel(BaseModel): | |||||
493 | new.permission = perm |
|
491 | new.permission = perm | |
494 | self.sa.add(new) |
|
492 | self.sa.add(new) | |
495 |
|
493 | |||
496 |
|
||||
497 | def revoke_perm(self, user, perm): |
|
494 | def revoke_perm(self, user, perm): | |
498 | if not isinstance(perm, Permission): |
|
495 | if not isinstance(perm, Permission): | |
499 | raise Exception('perm needs to be an instance of Permission class ' |
|
496 | raise Exception('perm needs to be an instance of Permission class ' |
@@ -3639,6 +3639,16 div#legend_container table td,div#legend | |||||
3639 | padding-left: 3px; |
|
3639 | padding-left: 3px; | |
3640 | } |
|
3640 | } | |
3641 |
|
3641 | |||
|
3642 | ||||
|
3643 | .group_members_wrap{ | |||
|
3644 | ||||
|
3645 | } | |||
|
3646 | ||||
|
3647 | .group_members .group_member{ | |||
|
3648 | height: 30px; | |||
|
3649 | padding:0px 0px 0px 10px; | |||
|
3650 | } | |||
|
3651 | ||||
3642 | /*README STYLE*/ |
|
3652 | /*README STYLE*/ | |
3643 |
|
3653 | |||
3644 | div.readme { |
|
3654 | div.readme { | |
@@ -4132,7 +4142,11 div.diffblock .code-header-title{ | |||||
4132 | padding: 0px 0px 10px 5px !important; |
|
4142 | padding: 0px 0px 10px 5px !important; | |
4133 | margin: 0 !important; |
|
4143 | margin: 0 !important; | |
4134 | } |
|
4144 | } | |
4135 |
|
4145 | div.diffblock .code-header .hash{ | ||
|
4146 | float: left; | |||
|
4147 | font-family: monospace; | |||
|
4148 | padding: 3px 0 0 2px; | |||
|
4149 | } | |||
4136 | div.diffblock .code-header .date{ |
|
4150 | div.diffblock .code-header .date{ | |
4137 | float:left; |
|
4151 | float:left; | |
4138 | text-transform: uppercase; |
|
4152 | text-transform: uppercase; |
@@ -86,7 +86,7 | |||||
86 | <label for="active">${_('Active')}:</label> |
|
86 | <label for="active">${_('Active')}:</label> | |
87 | </div> |
|
87 | </div> | |
88 | <div class="checkboxes"> |
|
88 | <div class="checkboxes"> | |
89 | ${h.checkbox('active',value=True)} |
|
89 | ${h.checkbox('active',value=True,checked='checked')} | |
90 | </div> |
|
90 | </div> | |
91 | </div> |
|
91 | </div> | |
92 |
|
92 |
@@ -41,7 +41,7 | |||||
41 | <label for="users_group_active">${_('Active')}:</label> |
|
41 | <label for="users_group_active">${_('Active')}:</label> | |
42 | </div> |
|
42 | </div> | |
43 | <div class="checkboxes"> |
|
43 | <div class="checkboxes"> | |
44 | ${h.checkbox('users_group_active',value=True)} |
|
44 | ${h.checkbox('users_group_active',value=True, checked='checked')} | |
45 | </div> |
|
45 | </div> | |
46 | </div> |
|
46 | </div> | |
47 |
|
47 |
@@ -94,6 +94,51 | |||||
94 | ${h.end_form()} |
|
94 | ${h.end_form()} | |
95 | </div> |
|
95 | </div> | |
96 |
|
96 | |||
|
97 | <div class="box box-right"> | |||
|
98 | <!-- box / title --> | |||
|
99 | <div class="title"> | |||
|
100 | <h5>${_('Permissions')}</h5> | |||
|
101 | </div> | |||
|
102 | ${h.form(url('users_group_perm', id=c.users_group.users_group_id), method='put')} | |||
|
103 | <div class="form"> | |||
|
104 | <!-- fields --> | |||
|
105 | <div class="fields"> | |||
|
106 | <div class="field"> | |||
|
107 | <div class="label label-checkbox"> | |||
|
108 | <label for="create_repo_perm">${_('Create repositories')}:</label> | |||
|
109 | </div> | |||
|
110 | <div class="checkboxes"> | |||
|
111 | ${h.checkbox('create_repo_perm',value=True)} | |||
|
112 | </div> | |||
|
113 | </div> | |||
|
114 | <div class="buttons"> | |||
|
115 | ${h.submit('save',_('Save'),class_="ui-button")} | |||
|
116 | ${h.reset('reset',_('Reset'),class_="ui-button")} | |||
|
117 | </div> | |||
|
118 | </div> | |||
|
119 | </div> | |||
|
120 | ${h.end_form()} | |||
|
121 | </div> | |||
|
122 | ||||
|
123 | <div class="box box-right"> | |||
|
124 | <!-- box / title --> | |||
|
125 | <div class="title"> | |||
|
126 | <h5>${_('Group members')}</h5> | |||
|
127 | </div> | |||
|
128 | <div class="group_members_wrap"> | |||
|
129 | <ul class="group_members"> | |||
|
130 | %for user in c.group_members_obj: | |||
|
131 | <li> | |||
|
132 | <div class="group_member"> | |||
|
133 | <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,24)}"/> </div> | |||
|
134 | <div>${user.username}</div> | |||
|
135 | <div>${user.full_name}</div> | |||
|
136 | </div> | |||
|
137 | </li> | |||
|
138 | %endfor | |||
|
139 | </ul> | |||
|
140 | </div> | |||
|
141 | </div> | |||
97 | <script type="text/javascript"> |
|
142 | <script type="text/javascript"> | |
98 | YAHOO.util.Event.onDOMReady(function(){ |
|
143 | YAHOO.util.Event.onDOMReady(function(){ | |
99 | var D = YAHOO.util.Dom; |
|
144 | var D = YAHOO.util.Dom; | |
@@ -140,16 +185,16 | |||||
140 | tmp_cache = new Array(); |
|
185 | tmp_cache = new Array(); | |
141 |
|
186 | |||
142 | for(var i = 0;node = av_cache[i];i++){ |
|
187 | for(var i = 0;node = av_cache[i];i++){ | |
143 |
|
|
188 | var add = true; | |
144 |
|
|
189 | for(var i2 = 0;node_2 = cache[i2];i2++){ | |
145 |
|
|
190 | if(node.value == node_2.value){ | |
146 |
|
|
191 | add=false; | |
147 |
|
|
192 | break; | |
148 |
|
|
193 | } | |
149 |
|
|
194 | } | |
150 |
|
|
195 | if(add){ | |
151 |
|
|
196 | tmp_cache.push(new Option(node.text, node.value, false, false)); | |
152 |
|
|
197 | } | |
153 | } |
|
198 | } | |
154 |
|
199 | |||
155 | for(var i = 0;node = tmp_cache[i];i++){ |
|
200 | for(var i = 0;node = tmp_cache[i];i++){ | |
@@ -173,7 +218,7 | |||||
173 | sel_cache.push(node); |
|
218 | sel_cache.push(node); | |
174 | } |
|
219 | } | |
175 | else{ |
|
220 | else{ | |
176 |
|
|
221 | oth_cache.push(node) | |
177 | } |
|
222 | } | |
178 | } |
|
223 | } | |
179 |
|
224 | |||
@@ -182,8 +227,8 | |||||
182 |
|
227 | |||
183 | //fill the field with given options |
|
228 | //fill the field with given options | |
184 | function fill_with(field,options){ |
|
229 | function fill_with(field,options){ | |
185 |
|
|
230 | //clear firtst | |
186 |
|
|
231 | field.options.length=0; | |
187 | for(var i = 0;node = options[i];i++){ |
|
232 | for(var i = 0;node = options[i];i++){ | |
188 | field.options[i]=new Option(node.text, node.value, |
|
233 | field.options[i]=new Option(node.text, node.value, | |
189 | false, false); |
|
234 | false, false); | |
@@ -242,29 +287,4 | |||||
242 | }) |
|
287 | }) | |
243 | }); |
|
288 | }); | |
244 | </script> |
|
289 | </script> | |
245 | <div class="box box-right"> |
|
|||
246 | <!-- box / title --> |
|
|||
247 | <div class="title"> |
|
|||
248 | <h5>${_('Permissions')}</h5> |
|
|||
249 | </div> |
|
|||
250 | ${h.form(url('users_group_perm', id=c.users_group.users_group_id), method='put')} |
|
|||
251 | <div class="form"> |
|
|||
252 | <!-- fields --> |
|
|||
253 | <div class="fields"> |
|
|||
254 | <div class="field"> |
|
|||
255 | <div class="label label-checkbox"> |
|
|||
256 | <label for="create_repo_perm">${_('Create repositories')}:</label> |
|
|||
257 | </div> |
|
|||
258 | <div class="checkboxes"> |
|
|||
259 | ${h.checkbox('create_repo_perm',value=True)} |
|
|||
260 | </div> |
|
|||
261 | </div> |
|
|||
262 | <div class="buttons"> |
|
|||
263 | ${h.submit('save',_('Save'),class_="ui-button")} |
|
|||
264 | ${h.reset('reset',_('Reset'),class_="ui-button")} |
|
|||
265 | </div> |
|
|||
266 | </div> |
|
|||
267 | </div> |
|
|||
268 | ${h.end_form()} |
|
|||
269 | </div> |
|
|||
270 | </%def> |
|
290 | </%def> |
@@ -112,7 +112,7 | |||||
112 | ${h.end_form()} |
|
112 | ${h.end_form()} | |
113 | %else: |
|
113 | %else: | |
114 | <div class="links_left"> |
|
114 | <div class="links_left"> | |
115 | <div class="full_name">${c.rhodecode_user.full_name}</div> |
|
115 | <div class="full_name">${c.rhodecode_user.full_name_or_username}</div> | |
116 | <div class="email">${c.rhodecode_user.email}</div> |
|
116 | <div class="email">${c.rhodecode_user.email}</div> | |
117 | <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div> |
|
117 | <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div> | |
118 | </div> |
|
118 | </div> |
@@ -27,9 +27,12 | |||||
27 | <div class="table"> |
|
27 | <div class="table"> | |
28 | <div class="diffblock"> |
|
28 | <div class="diffblock"> | |
29 | <div class="code-header"> |
|
29 | <div class="code-header"> | |
30 |
<div class=" |
|
30 | <div class="hash"> | |
31 |
|
|
31 | r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} | |
32 |
|
|
32 | </div> | |
|
33 | <div class="date"> | |||
|
34 | ${c.changeset.date} | |||
|
35 | </div> | |||
33 | <div class="diff-actions"> |
|
36 | <div class="diff-actions"> | |
34 | <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show')}" title="${_('raw diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a> |
|
37 | <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show')}" title="${_('raw diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a> | |
35 | <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" title="${_('download diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a> |
|
38 | <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" title="${_('download diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a> |
General Comments 0
You need to be logged in to leave comments.
Login now