Commit 317676c503ceb27170037ab2f18aa9baa770d021
Exists in
master
and in
3 other branches
Merge pull request #128 from colab/1.12.x
1.12.x
Showing
93 changed files
with
3224 additions
and
1505 deletions
Show diff stats
.coveragerc
... | ... | @@ -0,0 +1,24 @@ |
1 | +from django.utils.translation import ugettext as _ | |
2 | + | |
3 | + | |
4 | +def get_filters(request): | |
5 | + return { | |
6 | + 'user': { | |
7 | + 'name': _(u'User'), | |
8 | + 'icon': 'user', | |
9 | + 'fields': ( | |
10 | + ( | |
11 | + 'username', | |
12 | + _(u'Username'), | |
13 | + request.get('username'), | |
14 | + ), | |
15 | + ('name', _(u'Name'), request.get('name')), | |
16 | + ( | |
17 | + 'institution', | |
18 | + _(u'Institution'), | |
19 | + request.get('institution'), | |
20 | + ), | |
21 | + ('role', _(u'Role'), request.get('role')) | |
22 | + ), | |
23 | + }, | |
24 | + } | ... | ... |
colab/accounts/forms.py
1 | 1 | # -*- coding: utf-8 -*- |
2 | 2 | |
3 | -from collections import OrderedDict | |
3 | +from importlib import import_module | |
4 | 4 | |
5 | 5 | from django import forms |
6 | 6 | from django.conf import settings |
7 | -from django.contrib.auth import authenticate, get_user_model | |
8 | -from django.contrib.auth.forms import ReadOnlyPasswordHashField | |
9 | -from django.contrib.auth.tokens import default_token_generator | |
10 | -from django.contrib.sites.shortcuts import get_current_site | |
7 | +from django.contrib.auth import get_user_model | |
8 | +from django.contrib.auth.forms import (ReadOnlyPasswordHashField, | |
9 | + SetPasswordForm, PasswordChangeForm) | |
11 | 10 | from django.core.urlresolvers import reverse |
12 | -from django.template import loader | |
13 | -from django.utils.encoding import force_bytes | |
14 | 11 | from django.utils.functional import lazy |
15 | -from django.utils.http import urlsafe_base64_encode | |
16 | -from django.utils.text import capfirst | |
17 | 12 | from django.utils.translation import ugettext_lazy as _ |
18 | 13 | from django.utils.safestring import mark_safe |
19 | 14 | |
20 | - | |
15 | +from .signals import user_created | |
21 | 16 | from .utils.validators import validate_social_account |
22 | 17 | from .utils import mailman |
23 | 18 | |
... | ... | @@ -168,7 +163,41 @@ class ListsForm(forms.Form): |
168 | 163 | choices=lazy(get_lists_choices, list)()) |
169 | 164 | |
170 | 165 | |
171 | -class UserCreationForm(UserForm): | |
166 | +class ColabSetPasswordFormMixin(object): | |
167 | + | |
168 | + def apply_custom_validators(self, password): | |
169 | + for app in settings.COLAB_APPS.values(): | |
170 | + if 'password_validators' in app: | |
171 | + for validator_path in app.get('password_validators'): | |
172 | + module_path, func_name = validator_path.rsplit('.', 1) | |
173 | + module = import_module(module_path) | |
174 | + validator_func = getattr(module, func_name, None) | |
175 | + if validator_func: | |
176 | + validator_func(password) | |
177 | + | |
178 | + return password | |
179 | + | |
180 | + def clean_new_password2(self): | |
181 | + try: | |
182 | + password = super(ColabSetPasswordFormMixin, | |
183 | + self).clean_new_password2() | |
184 | + except AttributeError: | |
185 | + password = self.cleaned_data['new_password2'] | |
186 | + | |
187 | + self.apply_custom_validators(password) | |
188 | + return password | |
189 | + | |
190 | + def clean_password2(self): | |
191 | + try: | |
192 | + password = super(ColabSetPasswordFormMixin, self).clean_password2() | |
193 | + except AttributeError: | |
194 | + password = self.cleaned_data['password2'] | |
195 | + | |
196 | + self.apply_custom_validators(password) | |
197 | + return password | |
198 | + | |
199 | + | |
200 | +class UserCreationForm(UserForm, ColabSetPasswordFormMixin): | |
172 | 201 | """ |
173 | 202 | A form that creates a user, with no privileges, from the given username and |
174 | 203 | password. |
... | ... | @@ -181,12 +210,13 @@ class UserCreationForm(UserForm): |
181 | 210 | 'password_mismatch': _("The two password fields didn't match."), |
182 | 211 | } |
183 | 212 | username = forms.RegexField(label=_("Username"), max_length=30, |
184 | - regex=r'^[\w.@+-]+$', | |
213 | + regex=r'^[\w]+$', | |
185 | 214 | help_text=_(("Required. 30 characters or fewer" |
186 | 215 | ". Letter and digits.")), |
187 | 216 | error_messages={ |
188 | 217 | 'invalid': _(("This value may contain only" |
189 | 218 | " letters and numbers."))}) |
219 | + | |
190 | 220 | password1 = forms.CharField(label=_("Password"), |
191 | 221 | widget=forms.PasswordInput) |
192 | 222 | password2 = forms.CharField(label=_("Password confirmation"), |
... | ... | @@ -238,19 +268,26 @@ class UserCreationForm(UserForm): |
238 | 268 | self.error_messages['password_mismatch'], |
239 | 269 | code='password_mismatch', |
240 | 270 | ) |
271 | + | |
272 | + super(UserCreationForm, self).clean_password2() | |
241 | 273 | return password2 |
242 | 274 | |
243 | 275 | def save(self, commit=True): |
244 | 276 | user = super(UserCreationForm, self).save(commit=False) |
245 | - user.set_password(self.cleaned_data["password1"]) | |
277 | + password = self.cleaned_data["password1"] | |
278 | + user.set_password(password) | |
279 | + | |
246 | 280 | if commit: |
247 | 281 | user.save() |
282 | + | |
283 | + user_created.send(user.__class__, user=user, password=password) | |
284 | + | |
248 | 285 | return user |
249 | 286 | |
250 | 287 | |
251 | 288 | class UserChangeForm(forms.ModelForm): |
252 | 289 | username = forms.RegexField( |
253 | - label=_("Username"), max_length=30, regex=r"^[\w*]", | |
290 | + label=_("Username"), max_length=30, regex=r'^[\w]+$', | |
254 | 291 | help_text=_("Required. 30 characters or fewer. Letters and digits."), |
255 | 292 | error_messages={ |
256 | 293 | 'invalid': _("This value may contain only letters and numbers.")}) |
... | ... | @@ -286,234 +323,9 @@ class UserChangeForm(forms.ModelForm): |
286 | 323 | return self.initial["password"] |
287 | 324 | |
288 | 325 | |
289 | -class AuthenticationForm(forms.Form): | |
290 | - """ | |
291 | - Base class for authenticating users. Extend this to get a form that accepts | |
292 | - username/password logins. | |
293 | - """ | |
294 | - username = forms.CharField(max_length=254) | |
295 | - password = forms.CharField(label=_("Password"), widget=forms.PasswordInput) | |
326 | +class ColabSetPasswordForm(ColabSetPasswordFormMixin, SetPasswordForm): | |
327 | + pass | |
296 | 328 | |
297 | - error_messages = { | |
298 | - 'invalid_login': _("Please enter a correct %(username)s and password. " | |
299 | - "Note that both fields may be case-sensitive."), | |
300 | - 'inactive': _("This account is inactive."), | |
301 | - } | |
302 | - | |
303 | - def __init__(self, request=None, *args, **kwargs): | |
304 | - """ | |
305 | - The 'request' parameter is set for custom auth use by subclasses. | |
306 | - The form data comes in via the standard 'data' kwarg. | |
307 | - """ | |
308 | - self.request = request | |
309 | - self.user_cache = None | |
310 | - super(AuthenticationForm, self).__init__(*args, **kwargs) | |
311 | - | |
312 | - # Set the label for the "username" field. | |
313 | - UserModel = get_user_model() | |
314 | - self.username_field = UserModel._meta.get_field( | |
315 | - UserModel.USERNAME_FIELD) | |
316 | - if self.fields['username'].label is None: | |
317 | - self.fields['username'].label = capfirst( | |
318 | - self.username_field.verbose_name) | |
319 | - | |
320 | - def clean(self): | |
321 | - username = self.cleaned_data.get('username') | |
322 | - password = self.cleaned_data.get('password') | |
323 | - | |
324 | - if username and password: | |
325 | - self.user_cache = authenticate(username=username, | |
326 | - password=password) | |
327 | - if self.user_cache is None: | |
328 | - raise forms.ValidationError( | |
329 | - self.error_messages['invalid_login'], | |
330 | - code='invalid_login', | |
331 | - params={'username': self.username_field.verbose_name}, | |
332 | - ) | |
333 | - else: | |
334 | - self.confirm_login_allowed(self.user_cache) | |
335 | - | |
336 | - return self.cleaned_data | |
337 | - | |
338 | - def confirm_login_allowed(self, user): | |
339 | - """ | |
340 | - Controls whether the given User may log in. This is a policy setting, | |
341 | - independent of end-user authentication. This default behavior is to | |
342 | - allow login by active users, and reject login by inactive users. | |
343 | - If the given user cannot log in, this method should raise a | |
344 | - ``forms.ValidationError``. | |
345 | - If the given user may log in, this method should return None. | |
346 | - """ | |
347 | - if not user.is_active: | |
348 | - raise forms.ValidationError( | |
349 | - self.error_messages['inactive'], | |
350 | - code='inactive', | |
351 | - ) | |
352 | 329 | |
353 | - def get_user_id(self): | |
354 | - if self.user_cache: | |
355 | - return self.user_cache.id | |
356 | - return None | |
357 | - | |
358 | - def get_user(self): | |
359 | - return self.user_cache | |
360 | - | |
361 | - | |
362 | -class PasswordResetForm(forms.Form): | |
363 | - email = forms.EmailField(label=_("Email"), max_length=254) | |
364 | - | |
365 | - def save(self, domain_override=None, | |
366 | - subject_template_name='registration/password_reset_subject.txt', | |
367 | - email_template_name='registration/password_reset_email.html', | |
368 | - use_https=False, token_generator=default_token_generator, | |
369 | - from_email=None, request=None, html_email_template_name=None): | |
370 | - """ | |
371 | - Generates a one-use only link for resetting password and sends to the | |
372 | - user. | |
373 | - """ | |
374 | - from django.core.mail import send_mail | |
375 | - UserModel = get_user_model() | |
376 | - email = self.cleaned_data["email"] | |
377 | - active_users = UserModel._default_manager.filter( | |
378 | - email__iexact=email, is_active=True) | |
379 | - for user in active_users: | |
380 | - # Make sure that no email is sent to a user that actually has | |
381 | - # a password marked as unusable | |
382 | - if not user.has_usable_password(): | |
383 | - continue | |
384 | - if not domain_override: | |
385 | - current_site = get_current_site(request) | |
386 | - site_name = current_site.name | |
387 | - domain = current_site.domain | |
388 | - else: | |
389 | - site_name = domain = domain_override | |
390 | - c = { | |
391 | - 'email': user.email, | |
392 | - 'domain': domain, | |
393 | - 'site_name': site_name, | |
394 | - 'uid': urlsafe_base64_encode(force_bytes(user.pk)), | |
395 | - 'user': user, | |
396 | - 'token': token_generator.make_token(user), | |
397 | - 'protocol': 'https' if use_https else 'http', | |
398 | - } | |
399 | - subject = loader.render_to_string(subject_template_name, c) | |
400 | - # Email subject *must not* contain newlines | |
401 | - subject = ''.join(subject.splitlines()) | |
402 | - email = loader.render_to_string(email_template_name, c) | |
403 | - | |
404 | - if html_email_template_name: | |
405 | - html_email = loader.render_to_string(html_email_template_name, | |
406 | - c) | |
407 | - else: | |
408 | - html_email = None | |
409 | - send_mail(subject, email, from_email, [user.email], | |
410 | - html_message=html_email) | |
411 | - | |
412 | - | |
413 | -class SetPasswordForm(forms.Form): | |
414 | - """ | |
415 | - A form that lets a user change set their password without entering the old | |
416 | - password | |
417 | - """ | |
418 | - error_messages = { | |
419 | - 'password_mismatch': _("The two password fields didn't match."), | |
420 | - } | |
421 | - new_password1 = forms.CharField(label=_("New password"), | |
422 | - widget=forms.PasswordInput) | |
423 | - new_password2 = forms.CharField(label=_("New password confirmation"), | |
424 | - widget=forms.PasswordInput) | |
425 | - | |
426 | - def __init__(self, user, *args, **kwargs): | |
427 | - self.user = user | |
428 | - super(SetPasswordForm, self).__init__(*args, **kwargs) | |
429 | - | |
430 | - def clean_new_password2(self): | |
431 | - password1 = self.cleaned_data.get('new_password1') | |
432 | - password2 = self.cleaned_data.get('new_password2') | |
433 | - if password1 and password2: | |
434 | - if password1 != password2: | |
435 | - raise forms.ValidationError( | |
436 | - self.error_messages['password_mismatch'], | |
437 | - code='password_mismatch', | |
438 | - ) | |
439 | - return password2 | |
440 | - | |
441 | - def save(self, commit=True): | |
442 | - self.user.set_password(self.cleaned_data['new_password1']) | |
443 | - if commit: | |
444 | - self.user.save() | |
445 | - return self.user | |
446 | - | |
447 | - | |
448 | -class PasswordChangeForm(SetPasswordForm): | |
449 | - """ | |
450 | - A form that lets a user change their password by entering their old | |
451 | - password. | |
452 | - """ | |
453 | - error_messages = dict(SetPasswordForm.error_messages, **{ | |
454 | - 'password_incorrect': _("Your old password was entered incorrectly. " | |
455 | - "Please enter it again."), | |
456 | - }) | |
457 | - old_password = forms.CharField(label=_("Old password"), | |
458 | - widget=forms.PasswordInput) | |
459 | - | |
460 | - def clean_old_password(self): | |
461 | - """ | |
462 | - Validates that the old_password field is correct. | |
463 | - """ | |
464 | - old_password = self.cleaned_data["old_password"] | |
465 | - if not self.user.check_password(old_password): | |
466 | - raise forms.ValidationError( | |
467 | - self.error_messages['password_incorrect'], | |
468 | - code='password_incorrect', | |
469 | - ) | |
470 | - return old_password | |
471 | - | |
472 | -PasswordChangeForm.base_fields = OrderedDict( | |
473 | - (k, PasswordChangeForm.base_fields[k]) | |
474 | - for k in ['old_password', 'new_password1', 'new_password2'] | |
475 | -) | |
476 | - | |
477 | - | |
478 | -class AdminPasswordChangeForm(forms.Form): | |
479 | - """ | |
480 | - A form used to change the password of a user in the admin interface. | |
481 | - """ | |
482 | - error_messages = { | |
483 | - 'password_mismatch': _("The two password fields didn't match."), | |
484 | - } | |
485 | - password1 = forms.CharField(label=_("Password"), | |
486 | - widget=forms.PasswordInput) | |
487 | - password2 = forms.CharField(label=_("Password (again)"), | |
488 | - widget=forms.PasswordInput) | |
489 | - | |
490 | - def __init__(self, user, *args, **kwargs): | |
491 | - self.user = user | |
492 | - super(AdminPasswordChangeForm, self).__init__(*args, **kwargs) | |
493 | - | |
494 | - def clean_password2(self): | |
495 | - password1 = self.cleaned_data.get('password1') | |
496 | - password2 = self.cleaned_data.get('password2') | |
497 | - if password1 and password2: | |
498 | - if password1 != password2: | |
499 | - raise forms.ValidationError( | |
500 | - self.error_messages['password_mismatch'], | |
501 | - code='password_mismatch', | |
502 | - ) | |
503 | - return password2 | |
504 | - | |
505 | - def save(self, commit=True): | |
506 | - """ | |
507 | - Saves the new password. | |
508 | - """ | |
509 | - self.user.set_password(self.cleaned_data["password1"]) | |
510 | - if commit: | |
511 | - self.user.save() | |
512 | - return self.user | |
513 | - | |
514 | - def _get_changed_data(self): | |
515 | - data = super(AdminPasswordChangeForm, self).changed_data | |
516 | - for name in self.fields.keys(): | |
517 | - if name not in data: | |
518 | - return [] | |
519 | - return ['password'] | |
330 | +class ColabPasswordChangeForm(ColabSetPasswordFormMixin, PasswordChangeForm): | |
331 | + pass | ... | ... |
colab/accounts/models.py
... | ... | @@ -2,18 +2,26 @@ |
2 | 2 | |
3 | 3 | import urlparse |
4 | 4 | |
5 | -from django.db import models | |
6 | 5 | from django.contrib.auth.models import AbstractUser, UserManager |
7 | -from django.core import validators | |
8 | 6 | from django.core.urlresolvers import reverse |
7 | +from django.db import models | |
9 | 8 | from django.utils.crypto import get_random_string |
10 | 9 | from django.utils.translation import ugettext_lazy as _ |
11 | 10 | |
11 | +from .signals import user_created, user_password_changed | |
12 | 12 | from .utils import mailman |
13 | 13 | |
14 | 14 | |
15 | 15 | class ColabUserManager(UserManager): |
16 | 16 | |
17 | + def _create_user(self, username, email, password, | |
18 | + is_staff, is_superuser, **kwargs): | |
19 | + args = (username, email, password, is_staff, is_superuser) | |
20 | + user = super(ColabUserManager, self)._create_user(*args, **kwargs) | |
21 | + | |
22 | + user_created.send(user.__class__, user=user, password=password) | |
23 | + return user | |
24 | + | |
17 | 25 | def create_user(self, username, email=None, password=None, **extra_fields): |
18 | 26 | |
19 | 27 | # It creates a valid password for users |
... | ... | @@ -68,6 +76,11 @@ class User(AbstractUser): |
68 | 76 | self.username = self.username.lower() |
69 | 77 | super(User, self).save(*args, **kwargs) |
70 | 78 | |
79 | + def set_password(self, raw_password): | |
80 | + super(User, self).set_password(raw_password) | |
81 | + if self.pk: | |
82 | + user_password_changed.send(User, user=self, password=raw_password) | |
83 | + | |
71 | 84 | |
72 | 85 | # We need to have `email` field set as unique but Django does not |
73 | 86 | # support field overriding (at least not until 1.6). |
... | ... | @@ -77,8 +90,3 @@ User._meta.get_field('email')._unique = True |
77 | 90 | User._meta.get_field('username').help_text = _( |
78 | 91 | u'Required. 30 characters or fewer. Letters and digits.' |
79 | 92 | ) |
80 | -User._meta.get_field('username').validators[0] = validators.RegexValidator( | |
81 | - r'^\w+$', | |
82 | - _('Enter a valid username.'), | |
83 | - 'invalid' | |
84 | -) | ... | ... |
colab/accounts/search_indexes.py
... | ... | @@ -22,6 +22,7 @@ class UserIndex(indexes.SearchIndex, indexes.Indexable): |
22 | 22 | google_talk = indexes.CharField(model_attr='google_talk', null=True, |
23 | 23 | stored=False) |
24 | 24 | webpage = indexes.CharField(model_attr='webpage', null=True, stored=False) |
25 | + date_joined = indexes.DateTimeField(model_attr='date_joined') | |
25 | 26 | |
26 | 27 | def get_model(self): |
27 | 28 | return User | ... | ... |
colab/accounts/templates/accounts/manage_subscriptions.html
... | ... | @@ -12,7 +12,7 @@ |
12 | 12 | |
13 | 13 | <div class="row"> |
14 | 14 | {% for email, lists in membership.items %} |
15 | - <div class="col-lg-3 col-md-4 col-sm-6 col-xs-12"> | |
15 | + <div class="col-lg-6 col-md-6 col-sm-6 col-xs-12"> | |
16 | 16 | <div class="panel panel-default"> |
17 | 17 | <div class="panel-heading"> |
18 | 18 | <h3 class="panel-title">{{ email }}</h3> | ... | ... |
colab/accounts/templates/accounts/user_detail.html
... | ... | @@ -133,7 +133,7 @@ |
133 | 133 | <h3>{% trans "Latest posted" %} </h3> |
134 | 134 | <ul class="message-list"> |
135 | 135 | {% for doc in emails %} |
136 | - {% include "message-preview.html" with result=doc %} | |
136 | + {% include "dashboard-message-preview.html" with result=doc %} | |
137 | 137 | {% empty %} |
138 | 138 | <li>{% trans "There are no posts by this user so far." %}</li> |
139 | 139 | {% endfor %} |
... | ... | @@ -148,7 +148,7 @@ |
148 | 148 | <h3>{% trans "Latest contributions" %}</h3> |
149 | 149 | <ul class="message-list"> |
150 | 150 | {% for result in results %} |
151 | - {% include "message-preview.html" %} | |
151 | + {% include "dashboard-message-preview.html" %} | |
152 | 152 | {% empty %} |
153 | 153 | <li>{% trans "No contributions of this user so far." %}</li> |
154 | 154 | {% endfor %} | ... | ... |
colab/accounts/templates/accounts/user_update_form.html
1 | 1 | {% extends "base.html" %} |
2 | -{% load i18n gravatar %} | |
2 | +{% load i18n gravatar plugins widgets_tag %} | |
3 | 3 | |
4 | 4 | {% block head_js %} |
5 | 5 | <script> |
... | ... | @@ -102,7 +102,6 @@ $(function() { |
102 | 102 | </script> |
103 | 103 | {% endblock %} |
104 | 104 | |
105 | - | |
106 | 105 | {% block main-content %} |
107 | 106 | |
108 | 107 | <div class="col-lg-12"> |
... | ... | @@ -118,6 +117,17 @@ $(function() { |
118 | 117 | <br> |
119 | 118 | <br> |
120 | 119 | |
120 | + <style type="text/css"> | |
121 | + .tab-pane{ | |
122 | + border: 1px solid #DDD; | |
123 | + border-top: none; | |
124 | + padding: 10px; | |
125 | + } | |
126 | + | |
127 | + .nav-tabs a:focus { | |
128 | + outline: none; | |
129 | + } | |
130 | + </style> | |
121 | 131 | <form method="post"> |
122 | 132 | {% csrf_token %} |
123 | 133 | |
... | ... | @@ -192,9 +202,13 @@ $(function() { |
192 | 202 | </div> |
193 | 203 | </div> |
194 | 204 | <div class="row"> |
195 | - <div class="submit"> | |
196 | - <button type="submit" class="btn btn-primary btn-lg btn-block">{% trans "Update" %}</button> | |
205 | + <div class="col-md-12"> | |
206 | + <div class="links-group"> | |
207 | + <button type="submit" class="btn btn-primary btn-lg" expanded="false">{% trans "Update" %}</button> | |
208 | + <a href="{% url 'user_profile' user %}" class="btn btn-default btn-lg" role="button">{% trans "Go to profile panel" %}</a> | |
209 | + </div> | |
197 | 210 | </div> |
198 | 211 | </div> |
199 | 212 | </form> |
213 | + | |
200 | 214 | {% endblock %} | ... | ... |
colab/accounts/templates/search/user_search_preview.html
0 → 100644
... | ... | @@ -0,0 +1,25 @@ |
1 | +{% load i18n tz highlight gravatar date_format %} | |
2 | + | |
3 | +<div class="row"> | |
4 | + <div class="col-md-2 center"> | |
5 | + <a href="{% url 'user_profile' username=result.username %}"> | |
6 | + {% block gravatar_img %}{% gravatar result.email 100 %}{% endblock gravatar_img %} | |
7 | + </a> | |
8 | + </div> | |
9 | + <div class="col-md-10"> | |
10 | + <strong><a href="{% url 'user_profile' username=result.username %}"> | |
11 | + | |
12 | + {% if query %} | |
13 | + <h4>{% highlight result.name with query %}</h4></a> | |
14 | + {% else %} | |
15 | + <h4>{{ result.name }}</h4></a> | |
16 | + {% endif %} | |
17 | + | |
18 | + </strong> | |
19 | + <small><strong>{% trans "Since" %}: {% date_format result.date_joined %}</strong></small><br> | |
20 | + <small>{% trans "Registered in" %}: <strong>{% trans "User" %}</strong></small><br> | |
21 | + </div> | |
22 | +</div> | |
23 | +<div class="row"> | |
24 | + <hr> | |
25 | +</div> | ... | ... |
... | ... | @@ -0,0 +1,20 @@ |
1 | +from django import template | |
2 | +from django.utils.translation import ugettext as _ | |
3 | +register = template.Library() | |
4 | + | |
5 | + | |
6 | +@register.simple_tag(takes_context=True) | |
7 | +def date_format(context, date): | |
8 | + formatted_date = _('%(m)s %(d)s %(y)s' % {'m': date.strftime('%B'), | |
9 | + 'd': date.day, | |
10 | + 'y': date.year}) | |
11 | + return formatted_date | |
12 | + | |
13 | + | |
14 | +@register.simple_tag(takes_context=True) | |
15 | +def datetime_format(context, date): | |
16 | + formatted_date = date_format(context, date) | |
17 | + formatted_time = _('%(hour)s:%(min)s' % {'hour': date.hour, | |
18 | + 'min': date.strftime('%I')}) | |
19 | + formatted_datetime = _('%s at %s' % (formatted_date, formatted_time)) | |
20 | + return formatted_datetime | ... | ... |
colab/accounts/tests/test_forms.py
... | ... | @@ -3,11 +3,51 @@ Test Form class. |
3 | 3 | Objective: Test parameters, and behavior. |
4 | 4 | """ |
5 | 5 | |
6 | -from django.test import TestCase | |
6 | +import datetime | |
7 | +from mock import patch | |
8 | + | |
9 | +from django.test import TestCase, override_settings | |
7 | 10 | from django.core.urlresolvers import reverse |
8 | 11 | |
9 | -from colab.accounts.forms import UserCreationForm | |
12 | +from colab.accounts.forms import (UserCreationForm, UserChangeForm, | |
13 | + UserUpdateForm, UserForm, get_lists_choices, | |
14 | + ColabSetPasswordForm, | |
15 | + ColabPasswordChangeForm) | |
16 | +from colab.accounts import forms as accounts_forms | |
10 | 17 | from colab.accounts.models import User |
18 | +from colab.accounts.utils import mailman | |
19 | + | |
20 | + | |
21 | +class SetPasswordFormTestCase(TestCase): | |
22 | + | |
23 | + TEST_COLAB_APPS = { | |
24 | + 'test_plugin': { | |
25 | + 'password_validators': ( | |
26 | + 'colab.accounts.tests.utils.password_validator', | |
27 | + ) | |
28 | + } | |
29 | + } | |
30 | + | |
31 | + @property | |
32 | + def user(self): | |
33 | + return User.objects.create_user(username='test_user', | |
34 | + email='test@example.com') | |
35 | + | |
36 | + @property | |
37 | + def valid_form_data(self): | |
38 | + return {'new_password1': '12345', | |
39 | + 'new_password2': '12345'} | |
40 | + | |
41 | + def test_no_custom_validators(self): | |
42 | + form = ColabSetPasswordForm(self.user, data=self.valid_form_data) | |
43 | + self.assertTrue(form.is_valid(), True) | |
44 | + | |
45 | + @override_settings(COLAB_APPS=TEST_COLAB_APPS) | |
46 | + @patch('colab.accounts.tests.utils.password_validator') | |
47 | + def test_custom_validator(self, validator): | |
48 | + form = ColabSetPasswordForm(self.user, data=self.valid_form_data) | |
49 | + self.assertTrue(form.is_valid()) | |
50 | + validator.assert_called_with('12345') | |
11 | 51 | |
12 | 52 | |
13 | 53 | class FormTest(TestCase): |
... | ... | @@ -24,26 +64,274 @@ class FormTest(TestCase): |
24 | 64 | user.last_name = "COLAB" |
25 | 65 | user.save() |
26 | 66 | |
27 | - def create_form_data(self): | |
28 | - form_data = {'email': 'usertest@colab.com.br', | |
67 | + def tearDown(self): | |
68 | + pass | |
69 | + | |
70 | + def create_form_data(self, email, username): | |
71 | + form_data = {'email': email, | |
29 | 72 | 'first_name': 'colabName', |
30 | 73 | 'last_name': 'secondName', |
31 | - 'username': 'colab', | |
74 | + 'username': username, | |
32 | 75 | 'password1': '123colab4', |
33 | 76 | 'password2': '123colab4'} |
34 | 77 | form = UserCreationForm(data=form_data) |
35 | 78 | return form |
36 | 79 | |
80 | + def create_update_form_data(self): | |
81 | + updated_data = {'username': "colab", | |
82 | + 'email': 'email@email.com', | |
83 | + 'last_login': datetime.date.today(), | |
84 | + 'date_joined': datetime.date.today(), | |
85 | + 'twitter': 'nick_twitter', | |
86 | + 'first_name': 'colabName', | |
87 | + 'last_name': 'secondName', | |
88 | + } | |
89 | + initial = {'email': 'email@email.com', | |
90 | + 'first_name': 'colabName', | |
91 | + 'last_name': 'secondName', | |
92 | + 'username': 'colab', | |
93 | + 'password': '123colab4'} | |
94 | + form = UserUpdateForm(initial=initial, data=updated_data) | |
95 | + return form | |
96 | + | |
97 | + def create_change_form_data(self, username): | |
98 | + updated_data = {'username': username, | |
99 | + 'email': 'email@email.com', | |
100 | + 'last_login': datetime.date.today(), | |
101 | + 'date_joined': datetime.date.today()} | |
102 | + | |
103 | + initial = {'email': 'email@email.com', | |
104 | + 'first_name': 'colabName', | |
105 | + 'last_name': 'secondName', | |
106 | + 'username': 'colab', | |
107 | + 'password': '123colab4'} | |
108 | + form = UserChangeForm(initial=initial, data=updated_data) | |
109 | + return form | |
110 | + | |
111 | + def create_user_form_data(self): | |
112 | + initial = {'email': 'email@email.com', | |
113 | + 'first_name': 'colabName', | |
114 | + 'last_name': 'secondName', | |
115 | + 'username': 'colab', | |
116 | + 'password': '123colab4'} | |
117 | + form = UserForm(data=initial) | |
118 | + return form | |
119 | + | |
37 | 120 | def test_already_registered_email(self): |
38 | - form = self.create_form_data() | |
121 | + form = self.create_form_data('usertest@colab.com.br', | |
122 | + 'colab') | |
39 | 123 | self.assertFalse(form.is_valid()) |
124 | + self.assertIn('duplicate_email', form.error_messages) | |
40 | 125 | |
41 | 126 | def test_registered_email_message(self): |
42 | - form = self.create_form_data() | |
127 | + form = self.create_form_data('usertest@colab.com.br', | |
128 | + 'colab') | |
43 | 129 | msg = form.error_messages.get('duplicate_email') % { |
44 | 130 | 'url': reverse('login') |
45 | 131 | } |
46 | 132 | self.assertIn(msg, str(form)) |
47 | 133 | |
48 | - def tearDown(self): | |
49 | - pass | |
134 | + def test_valid_username(self): | |
135 | + form = self.create_form_data('user@email.com', | |
136 | + 'colab123') | |
137 | + self.assertTrue(form.is_valid()) | |
138 | + | |
139 | + def test_already_created_username(self): | |
140 | + form = self.create_form_data('usertest@colab.com.br', | |
141 | + 'USERtestCoLaB') | |
142 | + self.assertFalse(form.is_valid()) | |
143 | + self.assertIn('duplicate_username', form.error_messages) | |
144 | + | |
145 | + def test_not_valid_username(self): | |
146 | + form = self.create_form_data('user@email.com', | |
147 | + 'colab!') | |
148 | + self.assertFalse(form.is_valid()) | |
149 | + | |
150 | + def test_update_valid_username(self): | |
151 | + form = self.create_change_form_data('colab123') | |
152 | + self.assertTrue(form.is_valid()) | |
153 | + | |
154 | + def test_update_not_valid_username(self): | |
155 | + form = self.create_change_form_data('colab!') | |
156 | + self.assertFalse(form.is_valid()) | |
157 | + | |
158 | + @patch.object(accounts_forms, "validate_social_account") | |
159 | + def test_validate_social_account(self, validate_social_account): | |
160 | + validate_social_account.return_value = False | |
161 | + | |
162 | + form = self.create_update_form_data() | |
163 | + self.assertFalse(form.is_valid()) | |
164 | + self.assertIn("Social account does not exist", form.errors['twitter']) | |
165 | + | |
166 | + def test_required_valid_fields_user_form(self): | |
167 | + form_data = { | |
168 | + 'first_name': 'colabName', | |
169 | + 'last_name': 'secondName', | |
170 | + 'username': 'colab', | |
171 | + } | |
172 | + | |
173 | + form = UserForm(data=form_data) | |
174 | + | |
175 | + self.assertTrue(form.is_valid()) | |
176 | + | |
177 | + def test_required_empty_fields_user_form(self): | |
178 | + form_data = { | |
179 | + 'first_name': '', | |
180 | + 'last_name': '', | |
181 | + 'username': '', | |
182 | + } | |
183 | + | |
184 | + form = UserForm(data=form_data) | |
185 | + | |
186 | + self.assertFalse(form.is_valid()) | |
187 | + | |
188 | + self.assertIn('first_name', form.errors) | |
189 | + self.assertIn('last_name', form.errors) | |
190 | + self.assertIn('username', form.errors) | |
191 | + | |
192 | + def test_blank_required_fields_user_form(self): | |
193 | + form_data = { | |
194 | + 'first_name': ' ', | |
195 | + 'last_name': ' ', | |
196 | + 'username': ' ', | |
197 | + } | |
198 | + | |
199 | + form = UserForm(data=form_data) | |
200 | + | |
201 | + self.assertFalse(form.is_valid()) | |
202 | + | |
203 | + self.assertIn('first_name', form.errors) | |
204 | + self.assertIn('last_name', form.errors) | |
205 | + self.assertIn('username', form.errors) | |
206 | + | |
207 | + @patch.object(mailman, "all_lists") | |
208 | + def test_get_list_choices(self, all_lists): | |
209 | + all_lists.return_value = [ | |
210 | + {'listname': 'listA', 'description': 'A'}, | |
211 | + {'listname': 'listB', 'description': 'B'}, | |
212 | + {'listname': 'listC', 'description': 'C'}, | |
213 | + {'listname': 'listD', 'description': 'D'}, | |
214 | + ] | |
215 | + lists = get_lists_choices() | |
216 | + self.assertEqual(lists, [('listA', u'listA (A)'), | |
217 | + ('listB', u'listB (B)'), | |
218 | + ('listC', u'listC (C)'), | |
219 | + ('listD', u'listD (D)')]) | |
220 | + | |
221 | + | |
222 | +class ChangePasswordFormTestCase(TestCase): | |
223 | + | |
224 | + TEST_COLAB_APPS = { | |
225 | + 'test_plugin': { | |
226 | + 'password_validators': ( | |
227 | + 'colab.accounts.tests.utils.password_validator', | |
228 | + ) | |
229 | + } | |
230 | + } | |
231 | + | |
232 | + @property | |
233 | + def user(self): | |
234 | + u = User.objects.create_user(username='test_user', | |
235 | + email='test@example.com') | |
236 | + u.set_password("123colab4") | |
237 | + return u | |
238 | + | |
239 | + @property | |
240 | + def valid_form_data(self): | |
241 | + return {'old_password': '123colab4', | |
242 | + 'new_password1': '12345', | |
243 | + 'new_password2': '12345'} | |
244 | + | |
245 | + def test_no_custom_validators(self): | |
246 | + form = ColabPasswordChangeForm(self.user, data=self.valid_form_data) | |
247 | + self.assertTrue(form.is_valid(), True) | |
248 | + | |
249 | + @override_settings(COLAB_APPS=TEST_COLAB_APPS) | |
250 | + @patch('colab.accounts.tests.utils.password_validator') | |
251 | + def test_custom_validator(self, validator): | |
252 | + form = ColabPasswordChangeForm(self.user, data=self.valid_form_data) | |
253 | + self.assertTrue(form.is_valid()) | |
254 | + validator.assert_called_with('12345') | |
255 | + | |
256 | + | |
257 | +class UserCreationFormTestCase(TestCase): | |
258 | + | |
259 | + @classmethod | |
260 | + def setUpClass(cls): | |
261 | + cls.user = User.objects.create_user(username='user1234', | |
262 | + email='teste1234@example.com', | |
263 | + first_name='test_first_name', | |
264 | + last_name='test_last_name') | |
265 | + | |
266 | + cls.user.set_password("123colab4") | |
267 | + cls.user.save() | |
268 | + | |
269 | + def get_form_data(self, email, username='test_user', | |
270 | + password1='12345', password2='12345'): | |
271 | + return { | |
272 | + 'first_name': 'test_first_name', | |
273 | + 'last_name': 'test_last_name', | |
274 | + 'username': username, | |
275 | + 'email': email, | |
276 | + 'password1': password1, | |
277 | + 'password2': password2 | |
278 | + } | |
279 | + | |
280 | + def test_clean_mail_error(self): | |
281 | + creation_form = UserCreationForm( | |
282 | + data=self.get_form_data('teste1234@example.com')) | |
283 | + self.assertFalse(creation_form.is_valid()) | |
284 | + self.assertTrue('email' in creation_form.errors) | |
285 | + self.assertEqual(1, len(creation_form.errors)) | |
286 | + | |
287 | + def test_clean_mail(self): | |
288 | + creation_form = UserCreationForm( | |
289 | + data=self.get_form_data('teste12345@example.com')) | |
290 | + self.assertTrue(creation_form.is_valid()) | |
291 | + | |
292 | + def test_clean_username_error(self): | |
293 | + creation_form = UserCreationForm( | |
294 | + data=self.get_form_data('teste12345@example.com', | |
295 | + username='user1234')) | |
296 | + self.assertFalse(creation_form.is_valid()) | |
297 | + self.assertTrue('username' in creation_form.errors) | |
298 | + self.assertEqual(1, len(creation_form.errors)) | |
299 | + | |
300 | + def test_clean_username(self): | |
301 | + creation_form = UserCreationForm( | |
302 | + data=self.get_form_data('teste12345@example.com', | |
303 | + username='user12345')) | |
304 | + self.assertTrue(creation_form.is_valid()) | |
305 | + | |
306 | + def test_clean_password2_empty_password1(self): | |
307 | + creation_form = UserCreationForm( | |
308 | + data=self.get_form_data('teste12345@example.com', | |
309 | + username='user12345', | |
310 | + password1='')) | |
311 | + self.assertFalse(creation_form.is_valid()) | |
312 | + self.assertTrue('password1' in creation_form.errors) | |
313 | + self.assertEqual(1, len(creation_form.errors)) | |
314 | + | |
315 | + def test_clean_password2_empty_password2(self): | |
316 | + creation_form = UserCreationForm( | |
317 | + data=self.get_form_data('teste12345@example.com', | |
318 | + username='user12345', | |
319 | + password2='')) | |
320 | + self.assertFalse(creation_form.is_valid()) | |
321 | + self.assertTrue('password2' in creation_form.errors) | |
322 | + | |
323 | + def test_clean_password2_different_passwords(self): | |
324 | + creation_form = UserCreationForm( | |
325 | + data=self.get_form_data('teste12345@example.com', | |
326 | + username='user12345', | |
327 | + password1='1234')) | |
328 | + self.assertFalse(creation_form.is_valid()) | |
329 | + self.assertTrue('password2' in creation_form.errors) | |
330 | + self.assertEqual(1, len(creation_form.errors)) | |
331 | + self.assertEqual(1, len(creation_form.errors)) | |
332 | + | |
333 | + def test_clean_password(self): | |
334 | + creation_form = UserCreationForm( | |
335 | + data=self.get_form_data('teste12345@example.com', | |
336 | + username='user12345')) | |
337 | + self.assertTrue(creation_form.is_valid()) | ... | ... |
colab/accounts/tests/test_request.py
... | ... | @@ -6,6 +6,8 @@ Objective: Test requests. |
6 | 6 | from django.test import TestCase, Client |
7 | 7 | from django.test.client import RequestFactory |
8 | 8 | from colab.accounts.models import User |
9 | +from colab.accounts.context_processors import social_network_enabled | |
10 | +from django.conf import settings | |
9 | 11 | |
10 | 12 | |
11 | 13 | class RequestTest(TestCase): |
... | ... | @@ -65,3 +67,26 @@ class RequestTest(TestCase): |
65 | 67 | self.assertEqual(302, response.status_code) |
66 | 68 | self.assertEqual("http://testserver/account/usertest/subscriptions", |
67 | 69 | response.url) |
70 | + | |
71 | + | |
72 | +class SocialNetworkTest(TestCase): | |
73 | + """docstring for SocialNetworkTest""" | |
74 | + | |
75 | + def setUp(self): | |
76 | + self.factory = RequestFactory() | |
77 | + self.client = Client() | |
78 | + | |
79 | + def create_user(self): | |
80 | + self.user_test = User() | |
81 | + self.user_test.username = "usertest" | |
82 | + self.user_test.email = "usertest@colab.com.br" | |
83 | + self.user_test.set_password("1234colab") | |
84 | + self.user_test.save() | |
85 | + | |
86 | + def test_social_network(self): | |
87 | + self.create_user() | |
88 | + self.client.login(username="usertest", password='1234colab') | |
89 | + response = self.client.get('/myaccount/') | |
90 | + result = social_network_enabled(response)['SOCIAL_NETWORK_ENABLED'] | |
91 | + self.assertTrue(result) | |
92 | + self.assertTrue(settings.SOCIAL_NETWORK_ENABLED) | ... | ... |
colab/accounts/tests/test_user.py
... | ... | @@ -43,7 +43,7 @@ class UserTest(TestCase): |
43 | 43 | expected_last_name, first_name, last_name): |
44 | 44 | data = {'first_name': first_name, |
45 | 45 | 'last_name': last_name} |
46 | - self.client.post('/account/usertestcolab/edit', data) | |
46 | + self.client.post('/account/' + self.user.username + '/edit', data) | |
47 | 47 | user = User.objects.get(id=1) |
48 | 48 | self.assertEqual(expected_first_name, user.first_name) |
49 | 49 | self.assertEqual(expected_last_name, user.last_name) |
... | ... | @@ -52,7 +52,7 @@ class UserTest(TestCase): |
52 | 52 | data = {'first_name': 'usertestcolab', |
53 | 53 | 'last_name': 'colab', |
54 | 54 | field_name: value} |
55 | - self.client.post('/account/usertestcolab/edit', data) | |
55 | + self.client.post('/account/' + self.user.username + '/edit', data) | |
56 | 56 | user = User.objects.get(id=1) |
57 | 57 | self.assertEqual(expected_value, getattr(user, field_name)) |
58 | 58 | |
... | ... | @@ -77,10 +77,6 @@ class UserTest(TestCase): |
77 | 77 | empty_list = () |
78 | 78 | self.assertEqual(empty_list, self.user.mailinglists()) |
79 | 79 | |
80 | - def test_update_subscription(self): | |
81 | - pass | |
82 | - # TODO: You should have mailman connection. | |
83 | - | |
84 | 80 | def test_save(self): |
85 | 81 | username_test = "USERtestCoLaB" |
86 | 82 | |
... | ... | @@ -374,3 +370,78 @@ class UserTest(TestCase): |
374 | 370 | self.authenticate_user() |
375 | 371 | self.validate_non_mandatory_fields('bio', '', ' ') |
376 | 372 | self.user.delete() |
373 | + | |
374 | + def test_user_without_login(self): | |
375 | + response = self.client.get("/account/" + self.user.username + "/edit") | |
376 | + self.assertEqual(response.status_code, 403) | |
377 | + | |
378 | + def test_signup_with_post_not_success(self): | |
379 | + data_user = { | |
380 | + 'username': 'username', | |
381 | + 'password1': 'safepassword', | |
382 | + 'password2': 'safepassword', | |
383 | + } | |
384 | + before = User.objects.count() | |
385 | + self.client.post('/account/register', data=data_user) | |
386 | + after = User.objects.count() | |
387 | + self.assertEqual(before, after) | |
388 | + | |
389 | + def test_signup_with_post_with_success(self): | |
390 | + data_user = { | |
391 | + 'username': 'username', | |
392 | + 'first_name': 'first name', | |
393 | + 'last_name': 'last name', | |
394 | + 'email': 'mail@mail.com', | |
395 | + 'password1': 'safepassword', | |
396 | + 'password2': 'safepassword', | |
397 | + } | |
398 | + before = User.objects.count() | |
399 | + self.client.post('/account/register', data=data_user) | |
400 | + after = User.objects.count() | |
401 | + self.assertEqual(before + 1, after) | |
402 | + | |
403 | + def test_user_logged_in_profile(self): | |
404 | + self.authenticate_user() | |
405 | + self.client.get("/account/" + self.user.username) | |
406 | + self.assertEqual(self.client.session['_auth_user_id'], self.user.id) | |
407 | + | |
408 | + def test_user_not_logged_in_profile(self): | |
409 | + self.client.get("/account/" + self.user.username) | |
410 | + self.assertEqual(self.client.session, {}) | |
411 | + | |
412 | + def test_password_changed_message(self): | |
413 | + self.message_test('Your password was changed.', | |
414 | + "/account/change-password-done") | |
415 | + | |
416 | + def test_password_reset_done_custom_message(self): | |
417 | + self.message_test("We've emailed you instructions for setting " + | |
418 | + "your password. You should be receiving them " + | |
419 | + "shortly.", "/account/password-reset-done/") | |
420 | + | |
421 | + def test_password_rest_complete_message(self): | |
422 | + self.message_test("Your password has been set. You may go ahead and " + | |
423 | + "log in now.", "/account/password-reset-complete/") | |
424 | + | |
425 | + def message_test(self, message, url): | |
426 | + self.authenticate_user() | |
427 | + response = self.client.get(url, follow=True) | |
428 | + self.assertIn(message, response.content) | |
429 | + | |
430 | + @mock.patch('colab.accounts.signals.user_password_changed.send') | |
431 | + @mock.patch('colab.accounts.signals.user_created.send') | |
432 | + def test_user_created_signal(self, user_created_send, | |
433 | + user_password_changed_send): | |
434 | + user = User.objects.create_user( | |
435 | + username='test_user', | |
436 | + password='12345', | |
437 | + email='test@example.com', | |
438 | + ) | |
439 | + user_created_send.assert_called_with(User, user=user, password='12345') | |
440 | + user_password_changed_send.assert_not_called() | |
441 | + | |
442 | + @mock.patch('colab.accounts.signals.user_password_changed.send') | |
443 | + def test_user_password_changed_signal(self, user_password_changed_send): | |
444 | + user = User.objects.first() | |
445 | + user.set_password('54321') | |
446 | + user_password_changed_send.assert_called_with(User, user=user, | |
447 | + password='54321') | ... | ... |
... | ... | @@ -0,0 +1,91 @@ |
1 | +""" | |
2 | +Test User Mailing list Subscriptions class. | |
3 | +Objective: Test parameters, and behavior. | |
4 | +""" | |
5 | + | |
6 | +from mock import patch | |
7 | +from colab.accounts.models import User | |
8 | +from django.test import TestCase, Client | |
9 | +from colab.accounts.utils import mailman | |
10 | + | |
11 | + | |
12 | +class UserSubscriptionTest(TestCase): | |
13 | + OK = 200 | |
14 | + FORBIDDEN_ACCESS = 403 | |
15 | + | |
16 | + def setUp(self): | |
17 | + self.user = self.create_user() | |
18 | + self.client = Client() | |
19 | + | |
20 | + def tearDown(self): | |
21 | + pass | |
22 | + | |
23 | + def create_user(self): | |
24 | + user = User() | |
25 | + user.username = "USERtestCoLaB" | |
26 | + user.set_password("123colab4") | |
27 | + user.email = "usertest@colab.com.br" | |
28 | + user.id = 1 | |
29 | + user.twitter = "usertestcolab" | |
30 | + user.facebook = "usertestcolab" | |
31 | + user.first_name = "USERtestCoLaB" | |
32 | + user.last_name = "COLAB" | |
33 | + user.save() | |
34 | + | |
35 | + return user | |
36 | + | |
37 | + def authenticate_user(self, user=None, password='123colab4'): | |
38 | + if not user: | |
39 | + user = self.user | |
40 | + user.needs_update = False | |
41 | + user.save() | |
42 | + self.client.login(username=user.username, | |
43 | + password=password) | |
44 | + | |
45 | + def test_manage_subscription_logged_in(self): | |
46 | + self.authenticate_user() | |
47 | + response = self.client.get("/account/" + self.user.username + | |
48 | + "/subscriptions") | |
49 | + self.assertEqual(response.status_code, self.OK) | |
50 | + | |
51 | + def test_manage_subscription_without_login(self): | |
52 | + response = self.client.get("/account/" + self.user.username + | |
53 | + "/subscriptions") | |
54 | + self.assertEqual(response.status_code, self.FORBIDDEN_ACCESS) | |
55 | + | |
56 | + @patch.object(mailman, 'all_lists') | |
57 | + @patch.object(mailman, 'mailing_lists') | |
58 | + def test_context_data_generation(self, mailing_lists, all_lists): | |
59 | + data_user = { | |
60 | + 'username': 'username1', | |
61 | + 'first_name': 'first name1', | |
62 | + 'last_name': 'last name1', | |
63 | + 'email': 'mail1@mail.com', | |
64 | + 'password1': 'safepassword', | |
65 | + 'password2': 'safepassword', | |
66 | + } | |
67 | + self.client.post('/account/register', data=data_user) | |
68 | + user1 = User.objects.last() | |
69 | + user1.is_active = True | |
70 | + user1.save() | |
71 | + self.authenticate_user(user1, 'safepassword') | |
72 | + | |
73 | + mail_lists = [ | |
74 | + {"listname": "name_mock_1", "description": "descript_1"}, | |
75 | + {"listname": "name_mock_2", "description": "descript_2"}, | |
76 | + {"listname": "name_mock_3", "description": "descript_3"}, | |
77 | + ] | |
78 | + all_lists.return_value = mail_lists | |
79 | + | |
80 | + my_mail_lists = [ | |
81 | + "name_mock_1", | |
82 | + "name_mock_3", | |
83 | + ] | |
84 | + mailing_lists.return_value = my_mail_lists | |
85 | + response = self.client.get("/account/" + user1.username + | |
86 | + "/subscriptions") | |
87 | + self.assertEqual(response.status_code, self.OK) | |
88 | + mailresponse = response.context_data['membership'][user1.email] | |
89 | + mailresponse = map(lambda x: x[-1], mailresponse) | |
90 | + expected_value = [True, False, True] | |
91 | + self.assertEqual(mailresponse, expected_value) | ... | ... |
... | ... | @@ -0,0 +1,19 @@ |
1 | +import urllib2 | |
2 | +from mock import patch, Mock | |
3 | + | |
4 | +from django.test import TestCase | |
5 | + | |
6 | +from ..utils.validators import validate_social_account | |
7 | + | |
8 | + | |
9 | +class TestValidators(TestCase): | |
10 | + | |
11 | + @patch('urllib2.urlopen', | |
12 | + side_effect=urllib2.HTTPError(500, "test", 1, 2, None)) | |
13 | + def test_validate_social_account_with_fake_account(self, urlopen_mock): | |
14 | + self.assertFalse(validate_social_account('john-fake', | |
15 | + 'http://twitter.com')) | |
16 | + | |
17 | + @patch('urllib2.urlopen', return_value=Mock(code=200)) | |
18 | + def test_validate_social_account(self, urlopen_mock): | |
19 | + self.assertTrue(validate_social_account('john', 'http://twitter.com')) | ... | ... |
colab/accounts/urls.py
1 | 1 | |
2 | 2 | from django.conf import settings |
3 | 3 | from django.conf.urls import patterns, url |
4 | +from django.contrib.auth import views as auth_views | |
4 | 5 | |
5 | 6 | from .views import (UserProfileDetailView, UserProfileUpdateView, |
6 | 7 | ManageUserSubscriptionsView) |
7 | - | |
8 | -from colab.accounts import views | |
9 | -from django.contrib.auth import views as auth_views | |
8 | +from .forms import ColabPasswordChangeForm, ColabSetPasswordForm | |
10 | 9 | |
11 | 10 | |
12 | 11 | urlpatterns = patterns('', |
... | ... | @@ -22,15 +21,17 @@ urlpatterns = patterns('', |
22 | 21 | |
23 | 22 | url(r'^password-reset-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$', |
24 | 23 | auth_views.password_reset_confirm, |
25 | - {'template_name':'registration/password_reset_confirm_custom.html'}, | |
24 | + {'template_name':'registration/password_reset_confirm_custom.html', | |
25 | + 'set_password_form': ColabSetPasswordForm}, | |
26 | 26 | name="password_reset_confirm"), |
27 | 27 | |
28 | 28 | url(r'^password-reset/?$', auth_views.password_reset, |
29 | 29 | {'template_name':'registration/password_reset_form_custom.html'}, |
30 | 30 | name="password_reset"), |
31 | 31 | |
32 | - url(r'^change-password/?$',auth_views.password_change, | |
33 | - {'template_name':'registration/password_change_form_custom.html'}, | |
32 | + url(r'^change-password/?$', auth_views.password_change, | |
33 | + {'template_name': 'registration/password_change_form_custom.html', | |
34 | + 'password_change_form': ColabPasswordChangeForm}, | |
34 | 35 | name='password_change'), |
35 | 36 | |
36 | 37 | url(r'^change-password-done/?$', | ... | ... |
colab/home/context_processors.py
colab/locale/en/LC_MESSAGES/django.mo
No preview for this file type
colab/locale/en/LC_MESSAGES/django.po
1 | 1 | # SOME DESCRIPTIVE TITLE. |
2 | 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER |
3 | 3 | # This file is distributed under the same license as the PACKAGE package. |
4 | -# | |
4 | +# | |
5 | 5 | # Translators: |
6 | 6 | msgid "" |
7 | 7 | msgstr "" |
8 | 8 | "Project-Id-Version: colab\n" |
9 | 9 | "Report-Msgid-Bugs-To: \n" |
10 | -"POT-Creation-Date: 2015-09-01 13:15+0000\n" | |
10 | +"POT-Creation-Date: 2015-11-24 17:46+0000\n" | |
11 | 11 | "PO-Revision-Date: 2015-09-04 19:21+0000\n" |
12 | 12 | "Last-Translator: Lucas Kanashiro Duarte <kanashiro.duarte@gmail.com>\n" |
13 | 13 | "Language-Team: English (http://www.transifex.com/colab/colab/language/en/)\n" |
14 | +"Language: en\n" | |
14 | 15 | "MIME-Version: 1.0\n" |
15 | 16 | "Content-Type: text/plain; charset=UTF-8\n" |
16 | 17 | "Content-Transfer-Encoding: 8bit\n" |
17 | -"Language: en\n" | |
18 | 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" |
19 | 19 | |
20 | -#: accounts/admin.py:18 | |
20 | +#: colab-noosfero-plugin/tests/plugins.d/noosfero.py:21 | |
21 | +msgid "Social" | |
22 | +msgstr "" | |
23 | + | |
24 | +#: colab-noosfero-plugin/tests/plugins.d/noosfero.py:26 | |
25 | +#, fuzzy | |
26 | +#| msgid "Username" | |
27 | +msgid "Users" | |
28 | +msgstr "Username" | |
29 | + | |
30 | +#: colab-noosfero-plugin/tests/plugins.d/noosfero.py:28 | |
31 | +msgid "Communities" | |
32 | +msgstr "Communities" | |
33 | + | |
34 | +#: colab-noosfero-plugin/tests/plugins.d/noosfero.py:30 | |
35 | +#, fuzzy | |
36 | +#| msgid "My Profile" | |
37 | +msgid "Profile" | |
38 | +msgstr "My Profile" | |
39 | + | |
40 | +#: colab-noosfero-plugin/tests/plugins.d/noosfero.py:32 | |
41 | +msgid "Control Panel" | |
42 | +msgstr "" | |
43 | + | |
44 | +#: colab/accounts/admin.py:18 | |
21 | 45 | msgid "Personal info" |
22 | 46 | msgstr "Personal info" |
23 | 47 | |
24 | -#: accounts/admin.py:24 | |
48 | +#: colab/accounts/admin.py:24 | |
25 | 49 | msgid "Permissions" |
26 | 50 | msgstr "Permissions" |
27 | 51 | |
28 | -#: accounts/admin.py:28 | |
52 | +#: colab/accounts/admin.py:28 | |
29 | 53 | msgid "Important dates" |
30 | 54 | msgstr "Important dates" |
31 | 55 | |
32 | -#: accounts/forms.py:37 | |
56 | +#: colab/accounts/filters.py:7 | |
57 | +#: colab/accounts/templates/search/user_search_preview.html:12 | |
58 | +#, fuzzy | |
59 | +#| msgid "Username" | |
60 | +msgid "User" | |
61 | +msgstr "Username" | |
62 | + | |
63 | +#: colab/accounts/filters.py:12 colab/accounts/forms.py:175 | |
64 | +#: colab/accounts/forms.py:246 | |
65 | +#, fuzzy | |
66 | +#| msgid "Filename" | |
67 | +msgid "Username" | |
68 | +msgstr "Filename" | |
69 | + | |
70 | +#: colab/accounts/filters.py:15 | |
71 | +msgid "Name" | |
72 | +msgstr "Name" | |
73 | + | |
74 | +#: colab/accounts/filters.py:18 | |
75 | +msgid "Institution" | |
76 | +msgstr "Institution" | |
77 | + | |
78 | +#: colab/accounts/filters.py:21 | |
79 | +msgid "Role" | |
80 | +msgstr "Role" | |
81 | + | |
82 | +#: colab/accounts/forms.py:30 | |
33 | 83 | msgid "Social account does not exist" |
34 | 84 | msgstr "Social account does not exist" |
35 | 85 | |
36 | -#: accounts/forms.py:65 accounts/forms.py:71 accounts/forms.py:77 | |
86 | +#: colab/accounts/forms.py:58 colab/accounts/forms.py:64 | |
87 | +#: colab/accounts/forms.py:70 | |
37 | 88 | msgid "This field cannot be blank." |
38 | 89 | msgstr "This field cannot be blank." |
39 | 90 | |
40 | -#: accounts/forms.py:118 accounts/templates/accounts/user_detail.html:38 | |
91 | +#: colab/accounts/forms.py:111 | |
92 | +#: colab/accounts/templates/accounts/user_detail.html:38 | |
41 | 93 | msgid "Bio" |
42 | 94 | msgstr "Bio" |
43 | 95 | |
44 | -#: accounts/forms.py:119 | |
96 | +#: colab/accounts/forms.py:112 | |
45 | 97 | msgid "Write something about you in 200 characters or less." |
46 | 98 | msgstr "Write something about you in 200 characters or less." |
47 | 99 | |
48 | -#: accounts/forms.py:148 | |
100 | +#: colab/accounts/forms.py:157 | |
49 | 101 | msgid "Mailing lists" |
50 | 102 | msgstr "Mailing lists" |
51 | 103 | |
52 | -#: accounts/forms.py:161 | |
104 | +#: colab/accounts/forms.py:170 | |
53 | 105 | #, python-format |
54 | 106 | msgid "Email already used. Is it you? Please <a href='%(url)s'>login</a>" |
55 | 107 | msgstr "Email already used. Is it you? Please <a href='%(url)s'>login</a>" |
56 | 108 | |
57 | -#: accounts/forms.py:163 | |
109 | +#: colab/accounts/forms.py:172 | |
58 | 110 | msgid "A user with that username already exists." |
59 | 111 | msgstr "A user with that username already exists." |
60 | 112 | |
61 | -#: accounts/forms.py:164 accounts/forms.py:401 accounts/forms.py:465 | |
113 | +#: colab/accounts/forms.py:173 | |
62 | 114 | msgid "The two password fields didn't match." |
63 | 115 | msgstr "The two password fields didn't match." |
64 | 116 | |
65 | -#: accounts/forms.py:166 accounts/forms.py:236 search/forms.py:38 | |
66 | -msgid "Username" | |
67 | -msgstr "Username" | |
68 | - | |
69 | -#: accounts/forms.py:173 accounts/forms.py:240 accounts/forms.py:277 | |
70 | -#: accounts/forms.py:467 | |
117 | +#: colab/accounts/forms.py:183 colab/accounts/forms.py:251 | |
71 | 118 | msgid "Password" |
72 | 119 | msgstr "Password" |
73 | 120 | |
74 | -#: accounts/forms.py:175 | |
121 | +#: colab/accounts/forms.py:185 | |
75 | 122 | msgid "Password confirmation" |
76 | 123 | msgstr "Password confirmation" |
77 | 124 | |
78 | -#: accounts/forms.py:237 accounts/models.py:78 | |
125 | +#: colab/accounts/forms.py:247 colab/accounts/models.py:77 | |
79 | 126 | msgid "Required. 30 characters or fewer. Letters and digits." |
80 | 127 | msgstr "Required. 30 characters or fewer. Letters and digits." |
81 | 128 | |
82 | -#: accounts/forms.py:239 | |
129 | +#: colab/accounts/forms.py:249 | |
83 | 130 | msgid "This value may contain only letters and numbers." |
84 | 131 | msgstr "This value may contain only letters and numbers." |
85 | 132 | |
86 | -#: accounts/forms.py:241 | |
133 | +#: colab/accounts/forms.py:252 | |
87 | 134 | msgid "" |
88 | 135 | "Raw passwords are not stored, so there is no way to see this user's " |
89 | 136 | "password, but you can change the password using <a href=\"password/\">this " |
90 | 137 | "form</a>." |
91 | -msgstr "Raw passwords are not stored, so there is no way to see this user's password, but you can change the password using <a href=\"password/\">this form</a>." | |
92 | - | |
93 | -#: accounts/forms.py:280 | |
94 | -#, python-format | |
95 | -msgid "" | |
96 | -"Please enter a correct %(username)s and password. Note that both fields may " | |
97 | -"be case-sensitive." | |
98 | -msgstr "Please enter a correct %(username)s and password. Note that both fields may be case-sensitive." | |
99 | - | |
100 | -#: accounts/forms.py:282 | |
101 | -msgid "This account is inactive." | |
102 | -msgstr "This account is inactive." | |
103 | - | |
104 | -#: accounts/forms.py:345 | |
105 | -msgid "Email" | |
106 | -msgstr "Email" | |
107 | - | |
108 | -#: accounts/forms.py:403 | |
109 | -msgid "New password" | |
110 | -msgstr "New password" | |
111 | - | |
112 | -#: accounts/forms.py:405 | |
113 | -msgid "New password confirmation" | |
114 | -msgstr "New password confirmation" | |
115 | - | |
116 | -#: accounts/forms.py:436 | |
117 | -msgid "Your old password was entered incorrectly. Please enter it again." | |
118 | -msgstr "Your old password was entered incorrectly. Please enter it again." | |
119 | - | |
120 | -#: accounts/forms.py:439 | |
121 | -msgid "Old password" | |
122 | -msgstr "Old password" | |
123 | - | |
124 | -#: accounts/forms.py:469 | |
125 | -msgid "Password (again)" | |
126 | -msgstr "Password (again)" | |
127 | - | |
128 | -#: accounts/models.py:82 | |
129 | -msgid "Enter a valid username." | |
130 | -msgstr "Enter a valid username." | |
138 | +msgstr "" | |
139 | +"Raw passwords are not stored, so there is no way to see this user's " | |
140 | +"password, but you can change the password using <a href=\"password/\">this " | |
141 | +"form</a>." | |
131 | 142 | |
132 | -#: accounts/templates/accounts/manage_subscriptions.html:6 | |
143 | +#: colab/accounts/templates/accounts/manage_subscriptions.html:6 | |
133 | 144 | msgid "Group Subscriptions" |
134 | 145 | msgstr "Group Subscriptions" |
135 | 146 | |
136 | -#: accounts/templates/accounts/manage_subscriptions.html:36 | |
147 | +#: colab/accounts/templates/accounts/manage_subscriptions.html:36 | |
137 | 148 | msgid "Update subscriptions" |
138 | 149 | msgstr "Update subscriptions" |
139 | 150 | |
140 | -#: accounts/templates/accounts/user_create_form.html:5 | |
151 | +#: colab/accounts/templates/accounts/user_create_form.html:5 | |
141 | 152 | msgid "Sign up" |
142 | 153 | msgstr "Sign up" |
143 | 154 | |
144 | -#: accounts/templates/accounts/user_create_form.html:10 | |
145 | -#: accounts/templates/registration/login.html:14 | |
146 | -#: accounts/templates/registration/password_change_form_custom.html:12 | |
147 | -#: accounts/templates/registration/password_reset_confirm_custom.html:14 | |
148 | -#: accounts/templates/registration/password_reset_form_custom.html:9 | |
155 | +#: colab/accounts/templates/accounts/user_create_form.html:10 | |
156 | +#: colab/accounts/templates/registration/login.html:14 | |
157 | +#: colab/accounts/templates/registration/password_change_form_custom.html:12 | |
158 | +#: colab/accounts/templates/registration/password_reset_confirm_custom.html:14 | |
159 | +#: colab/accounts/templates/registration/password_reset_form_custom.html:9 | |
149 | 160 | msgid "Please correct the errors below and try again." |
150 | 161 | msgstr "Please correct the errors below and try again." |
151 | 162 | |
152 | -#: accounts/templates/accounts/user_create_form.html:17 | |
163 | +#: colab/accounts/templates/accounts/user_create_form.html:17 | |
153 | 164 | msgid "Required fields" |
154 | 165 | msgstr "Required fields" |
155 | 166 | |
156 | -#: accounts/templates/accounts/user_create_form.html:29 | |
167 | +#: colab/accounts/templates/accounts/user_create_form.html:29 | |
157 | 168 | msgid "Personal Information" |
158 | 169 | msgstr "Personal Information" |
159 | 170 | |
160 | -#: accounts/templates/accounts/user_create_form.html:46 | |
171 | +#: colab/accounts/templates/accounts/user_create_form.html:46 | |
161 | 172 | msgid "Subscribe to groups" |
162 | 173 | msgstr "Subscribe to groups" |
163 | 174 | |
164 | -#: accounts/templates/accounts/user_create_form.html:60 | |
165 | -#: templates/base.html:100 templates/base.html.py:105 templates/header.html:40 | |
166 | -#: templates/header.html.py:45 | |
175 | +#: colab/accounts/templates/accounts/user_create_form.html:60 | |
176 | +#: colab/templates/header.html:38 colab/templates/header.html.py:43 | |
167 | 177 | msgid "Register" |
168 | 178 | msgstr "Register" |
169 | 179 | |
170 | -#: accounts/templates/accounts/user_detail.html:8 | |
180 | +#: colab/accounts/templates/accounts/user_detail.html:8 | |
171 | 181 | msgid "Messages" |
172 | 182 | msgstr "Messages" |
173 | 183 | |
174 | -#: accounts/templates/accounts/user_detail.html:9 templates/home.html:7 | |
184 | +#: colab/accounts/templates/accounts/user_detail.html:9 | |
185 | +#: colab/templates/home.html:7 | |
175 | 186 | msgid "Contributions" |
176 | 187 | msgstr "Contributions" |
177 | 188 | |
178 | -#: accounts/templates/accounts/user_detail.html:29 | |
189 | +#: colab/accounts/templates/accounts/user_detail.html:29 | |
179 | 190 | msgid "edit profile" |
180 | 191 | msgstr "edit profile" |
181 | 192 | |
182 | -#: accounts/templates/accounts/user_detail.html:30 | |
193 | +#: colab/accounts/templates/accounts/user_detail.html:30 | |
183 | 194 | msgid "group membership" |
184 | 195 | msgstr "group membership" |
185 | 196 | |
186 | -#: accounts/templates/accounts/user_detail.html:66 | |
197 | +#: colab/accounts/templates/accounts/user_detail.html:66 | |
187 | 198 | msgid "Twitter account" |
188 | 199 | msgstr "Twitter account" |
189 | 200 | |
190 | -#: accounts/templates/accounts/user_detail.html:69 | |
201 | +#: colab/accounts/templates/accounts/user_detail.html:69 | |
191 | 202 | msgid "Facebook account" |
192 | 203 | msgstr "Facebook account" |
193 | 204 | |
194 | -#: accounts/templates/accounts/user_detail.html:74 | |
205 | +#: colab/accounts/templates/accounts/user_detail.html:74 | |
195 | 206 | msgid "Google talk account" |
196 | 207 | msgstr "Google talk account" |
197 | 208 | |
198 | -#: accounts/templates/accounts/user_detail.html:78 | |
209 | +#: colab/accounts/templates/accounts/user_detail.html:78 | |
199 | 210 | msgid "Github account" |
200 | 211 | msgstr "Github account" |
201 | 212 | |
202 | -#: accounts/templates/accounts/user_detail.html:82 | |
213 | +#: colab/accounts/templates/accounts/user_detail.html:82 | |
203 | 214 | msgid "Personal webpage" |
204 | 215 | msgstr "Personal webpage" |
205 | 216 | |
206 | -#: accounts/templates/accounts/user_detail.html:88 | |
217 | +#: colab/accounts/templates/accounts/user_detail.html:88 | |
207 | 218 | msgid "Groups: " |
208 | 219 | msgstr "Groups: " |
209 | 220 | |
210 | -#: accounts/templates/accounts/user_detail.html:101 | |
221 | +#: colab/accounts/templates/accounts/user_detail.html:101 | |
211 | 222 | msgid "Collaborations by Type" |
212 | 223 | msgstr "Collaborations by Type" |
213 | 224 | |
214 | -#: accounts/templates/accounts/user_detail.html:117 | |
225 | +#: colab/accounts/templates/accounts/user_detail.html:117 | |
215 | 226 | msgid "Participation by Group" |
216 | 227 | msgstr "Participation by Group" |
217 | 228 | |
218 | -#: accounts/templates/accounts/user_detail.html:133 | |
229 | +#: colab/accounts/templates/accounts/user_detail.html:133 | |
219 | 230 | msgid "Latest posted" |
220 | 231 | msgstr "Latest posted" |
221 | 232 | |
222 | -#: accounts/templates/accounts/user_detail.html:138 | |
233 | +#: colab/accounts/templates/accounts/user_detail.html:138 | |
223 | 234 | msgid "There are no posts by this user so far." |
224 | 235 | msgstr "There are no posts by this user so far." |
225 | 236 | |
226 | -#: accounts/templates/accounts/user_detail.html:142 | |
237 | +#: colab/accounts/templates/accounts/user_detail.html:142 | |
227 | 238 | msgid "View more posts..." |
228 | 239 | msgstr "View more posts..." |
229 | 240 | |
230 | -#: accounts/templates/accounts/user_detail.html:148 | |
241 | +#: colab/accounts/templates/accounts/user_detail.html:148 | |
231 | 242 | msgid "Latest contributions" |
232 | 243 | msgstr "Latest contributions" |
233 | 244 | |
234 | -#: accounts/templates/accounts/user_detail.html:153 | |
245 | +#: colab/accounts/templates/accounts/user_detail.html:153 | |
235 | 246 | msgid "No contributions of this user so far." |
236 | 247 | msgstr "No contributions of this user so far." |
237 | 248 | |
238 | -#: accounts/templates/accounts/user_detail.html:157 | |
249 | +#: colab/accounts/templates/accounts/user_detail.html:157 | |
239 | 250 | msgid "View more contributions..." |
240 | 251 | msgstr "View more contributions..." |
241 | 252 | |
242 | -#: accounts/templates/accounts/user_update_form.html:65 | |
253 | +#: colab/accounts/templates/accounts/user_update_form.html:70 | |
243 | 254 | msgid "We sent a verification email to " |
244 | 255 | msgstr "We sent a verification email to " |
245 | 256 | |
246 | -#: accounts/templates/accounts/user_update_form.html:66 | |
257 | +#: colab/accounts/templates/accounts/user_update_form.html:71 | |
247 | 258 | msgid "Please follow the instructions in it." |
248 | 259 | msgstr "Please follow the instructions in it." |
249 | 260 | |
250 | -#: accounts/templates/accounts/user_update_form.html:110 | |
261 | +#: colab/accounts/templates/accounts/user_update_form.html:123 | |
251 | 262 | msgid "profile information" |
252 | 263 | msgstr "profile information" |
253 | 264 | |
254 | -#: accounts/templates/accounts/user_update_form.html:115 | |
265 | +#: colab/accounts/templates/accounts/user_update_form.html:128 | |
255 | 266 | msgid "Change your avatar at Gravatar.com" |
256 | 267 | msgstr "Change your avatar at Gravatar.com" |
257 | 268 | |
258 | -#: accounts/templates/accounts/user_update_form.html:142 search/utils.py:56 | |
269 | +#: colab/accounts/templates/accounts/user_update_form.html:178 | |
270 | +#: colab/search/utils.py:56 | |
259 | 271 | msgid "Emails" |
260 | 272 | msgstr "Emails" |
261 | 273 | |
262 | -#: accounts/templates/accounts/user_update_form.html:151 | |
274 | +#: colab/accounts/templates/accounts/user_update_form.html:187 | |
263 | 275 | msgid "Primary" |
264 | 276 | msgstr "Primary" |
265 | 277 | |
266 | -#: accounts/templates/accounts/user_update_form.html:154 | |
278 | +#: colab/accounts/templates/accounts/user_update_form.html:190 | |
267 | 279 | msgid "Setting..." |
268 | 280 | msgstr "Setting..." |
269 | 281 | |
270 | -#: accounts/templates/accounts/user_update_form.html:154 | |
282 | +#: colab/accounts/templates/accounts/user_update_form.html:190 | |
271 | 283 | msgid "Set as Primary" |
272 | 284 | msgstr "Set as Primary" |
273 | 285 | |
274 | -#: accounts/templates/accounts/user_update_form.html:155 | |
286 | +#: colab/accounts/templates/accounts/user_update_form.html:191 | |
275 | 287 | msgid "Deleting..." |
276 | 288 | msgstr "Deleting..." |
277 | 289 | |
278 | -#: accounts/templates/accounts/user_update_form.html:155 | |
279 | -#: accounts/templates/accounts/user_update_form.html:167 | |
290 | +#: colab/accounts/templates/accounts/user_update_form.html:191 | |
291 | +#: colab/accounts/templates/accounts/user_update_form.html:203 | |
280 | 292 | msgid "Delete" |
281 | 293 | msgstr "Delete" |
282 | 294 | |
283 | -#: accounts/templates/accounts/user_update_form.html:166 | |
295 | +#: colab/accounts/templates/accounts/user_update_form.html:202 | |
284 | 296 | msgid "Sending verification..." |
285 | 297 | msgstr "Sending verification..." |
286 | 298 | |
287 | -#: accounts/templates/accounts/user_update_form.html:166 | |
299 | +#: colab/accounts/templates/accounts/user_update_form.html:202 | |
288 | 300 | msgid "Verify" |
289 | 301 | msgstr "Verify" |
290 | 302 | |
291 | -#: accounts/templates/accounts/user_update_form.html:174 | |
303 | +#: colab/accounts/templates/accounts/user_update_form.html:210 | |
292 | 304 | msgid "Add another email address:" |
293 | 305 | msgstr "Add another email address:" |
294 | 306 | |
295 | -#: accounts/templates/accounts/user_update_form.html:177 | |
307 | +#: colab/accounts/templates/accounts/user_update_form.html:213 | |
296 | 308 | msgid "Add" |
297 | 309 | msgstr "Add" |
298 | 310 | |
299 | -#: accounts/templates/accounts/user_update_form.html:185 | |
300 | -#: accounts/templates/accounts/user_update_form.html:189 | |
301 | -#: accounts/templates/registration/password_change_form_custom.html:26 | |
302 | -#: accounts/templates/registration/password_reset_confirm_custom.html:28 | |
311 | +#: colab/accounts/templates/accounts/user_update_form.html:221 | |
312 | +#: colab/accounts/templates/accounts/user_update_form.html:225 | |
313 | +#: colab/accounts/templates/registration/password_change_form_custom.html:26 | |
314 | +#: colab/accounts/templates/registration/password_reset_confirm_custom.html:28 | |
303 | 315 | msgid "Change Password" |
304 | 316 | msgstr "Change Password" |
305 | 317 | |
306 | -#: accounts/templates/accounts/user_update_form.html:196 | |
318 | +#: colab/accounts/templates/accounts/user_update_form.html:233 | |
307 | 319 | msgid "Update" |
308 | 320 | msgstr "Update" |
309 | 321 | |
310 | -#: accounts/templates/registration/login.html:10 | |
311 | -#: accounts/templates/registration/password_change_form_custom.html:10 | |
312 | -#: accounts/templates/registration/password_reset_confirm_custom.html:12 | |
322 | +#: colab/accounts/templates/accounts/user_update_form.html:234 | |
323 | +msgid "Go to profile panel" | |
324 | +msgstr "" | |
325 | + | |
326 | +#: colab/accounts/templates/registration/login.html:10 | |
327 | +#: colab/accounts/templates/registration/password_change_form_custom.html:10 | |
328 | +#: colab/accounts/templates/registration/password_reset_confirm_custom.html:12 | |
313 | 329 | msgid "Please correct the error below and try again." |
314 | 330 | msgstr "Please correct the error below and try again." |
315 | 331 | |
316 | -#: accounts/templates/registration/login.html:34 | |
317 | -#: accounts/templates/registration/login.html:54 templates/base.html:99 | |
318 | -#: templates/base.html.py:101 templates/base.html:104 | |
319 | -#: templates/base.html.py:106 templates/header.html:39 | |
320 | -#: templates/header.html.py:41 templates/header.html:44 | |
321 | -#: templates/header.html.py:46 | |
332 | +#: colab/accounts/templates/registration/login.html:34 | |
333 | +#: colab/accounts/templates/registration/login.html:54 | |
334 | +#: colab/templates/header.html:37 colab/templates/header.html.py:39 | |
335 | +#: colab/templates/header.html:42 colab/templates/header.html.py:44 | |
322 | 336 | msgid "Login" |
323 | 337 | msgstr "Login" |
324 | 338 | |
325 | -#: accounts/templates/registration/login.html:56 | |
339 | +#: colab/accounts/templates/registration/login.html:56 | |
326 | 340 | msgid "Forgot Password?" |
327 | 341 | msgstr "Forgot Password?" |
328 | 342 | |
329 | -#: accounts/templates/registration/password_change_form_custom.html:54 | |
330 | -#: accounts/templates/registration/password_reset_confirm_custom.html:51 | |
343 | +#: colab/accounts/templates/registration/password_change_form_custom.html:54 | |
344 | +#: colab/accounts/templates/registration/password_reset_confirm_custom.html:51 | |
331 | 345 | msgid "Change my password" |
332 | 346 | msgstr "Change my password" |
333 | 347 | |
334 | -#: accounts/templates/registration/password_reset_confirm_custom.html:3 | |
348 | +#: colab/accounts/templates/registration/password_reset_confirm_custom.html:3 | |
335 | 349 | msgid "Setting New password" |
336 | 350 | msgstr "Setting New password" |
337 | 351 | |
338 | -#: accounts/templates/registration/password_reset_form_custom.html:23 | |
352 | +#: colab/accounts/templates/registration/password_reset_form_custom.html:23 | |
339 | 353 | msgid "" |
340 | 354 | "Forgotten your password? Enter your email address below, and we'll email " |
341 | 355 | "instructions for setting a new one." |
342 | -msgstr "Forgotten your password? Enter your email address below, and we'll email instructions for setting a new one." | |
356 | +msgstr "" | |
357 | +"Forgotten your password? Enter your email address below, and we'll email " | |
358 | +"instructions for setting a new one." | |
343 | 359 | |
344 | -#: accounts/templates/registration/password_reset_form_custom.html:26 | |
360 | +#: colab/accounts/templates/registration/password_reset_form_custom.html:26 | |
345 | 361 | msgid "Email address:" |
346 | 362 | msgstr "Email address:" |
347 | 363 | |
348 | -#: accounts/templates/registration/password_reset_form_custom.html:37 | |
364 | +#: colab/accounts/templates/registration/password_reset_form_custom.html:37 | |
349 | 365 | msgid "Reset password" |
350 | 366 | msgstr "Reset password" |
351 | 367 | |
352 | -#: accounts/views.py:126 | |
368 | +#: colab/accounts/templates/search/user_search_preview.html:11 | |
369 | +#: colab/search/forms.py:19 | |
370 | +#: colab/search/templates/search/includes/search_filters.html:134 | |
371 | +#: colab/search/templates/search/includes/search_filters.html:136 | |
372 | +#: colab/search/templates/search/includes/search_filters.html:168 | |
373 | +#: colab/search/templates/search/includes/search_filters.html:169 | |
374 | +msgid "Since" | |
375 | +msgstr "Since" | |
376 | + | |
377 | +#: colab/accounts/templates/search/user_search_preview.html:12 | |
378 | +#, fuzzy | |
379 | +#| msgid "Register" | |
380 | +msgid "Registered in" | |
381 | +msgstr "Register" | |
382 | + | |
383 | +#: colab/accounts/templatetags/date_format.py:8 | |
384 | +#, python-format | |
385 | +msgid "%(m)s %(d)s %(y)s" | |
386 | +msgstr "" | |
387 | + | |
388 | +#: colab/accounts/templatetags/date_format.py:17 | |
389 | +#, python-format | |
390 | +msgid "%(hour)s:%(min)s" | |
391 | +msgstr "" | |
392 | + | |
393 | +#: colab/accounts/templatetags/date_format.py:19 | |
394 | +#, python-format | |
395 | +msgid "%s at %s" | |
396 | +msgstr "" | |
397 | + | |
398 | +#: colab/accounts/views.py:126 | |
353 | 399 | msgid "Your profile has been created!" |
354 | 400 | msgstr "Your profile has been created!" |
355 | 401 | |
356 | -#: accounts/views.py:186 | |
402 | +#: colab/accounts/views.py:186 | |
357 | 403 | msgid "Your password was changed." |
358 | 404 | msgstr "Your password was changed." |
359 | 405 | |
360 | -#: accounts/views.py:202 | |
406 | +#: colab/accounts/views.py:202 | |
361 | 407 | msgid "Your password has been set. You may go ahead and log in now." |
362 | 408 | msgstr "Your password has been set. You may go ahead and log in now." |
363 | 409 | |
364 | -#: home/context_processors.py:20 | |
410 | +#: colab/home/context_processors.py:20 | |
365 | 411 | msgid "Fork me!" |
366 | 412 | msgstr "Fork me!" |
367 | 413 | |
368 | -#: plugins/gitlab/models.py:27 | |
369 | -msgid "Gitlab Project" | |
370 | -msgstr "Gitlab Project" | |
371 | - | |
372 | -#: plugins/gitlab/models.py:28 | |
373 | -msgid "Gitlab Projects" | |
374 | -msgstr "Gitlab Projects" | |
375 | - | |
376 | -#: plugins/gitlab/models.py:54 | |
377 | -msgid "Gitlab Group" | |
378 | -msgstr "Gitlab Group" | |
379 | - | |
380 | -#: plugins/gitlab/models.py:55 | |
381 | -msgid "Gitlab Groups" | |
382 | -msgstr "Gitlab Groups" | |
383 | - | |
384 | -#: plugins/gitlab/models.py:91 | |
385 | -msgid "Gitlab Merge Request" | |
386 | -msgstr "Gitlab Merge Request" | |
387 | - | |
388 | -#: plugins/gitlab/models.py:92 | |
389 | -msgid "Gitlab Merge Requests" | |
390 | -msgstr "Gitlab Merge Requests" | |
391 | - | |
392 | -#: plugins/gitlab/models.py:120 | |
393 | -msgid "Gitlab Issue" | |
394 | -msgstr "Gitlab Issue" | |
395 | - | |
396 | -#: plugins/gitlab/models.py:121 | |
397 | -msgid "Gitlab Issues" | |
398 | -msgstr "Gitlab Issues" | |
399 | - | |
400 | -#: plugins/gitlab/models.py:176 plugins/gitlab/models.py:177 | |
401 | -msgid "Gitlab Comments" | |
402 | -msgstr "Gitlab Comments" | |
403 | - | |
404 | -#: plugins/noosfero/models.py:39 | |
405 | -msgid "Community" | |
406 | -msgstr "Community" | |
407 | - | |
408 | -#: plugins/noosfero/models.py:40 | |
409 | -msgid "Communities" | |
410 | -msgstr "Communities" | |
411 | - | |
412 | -#: plugins/noosfero/models.py:67 | |
413 | -msgid "Article" | |
414 | -msgstr "Article" | |
415 | - | |
416 | -#: plugins/noosfero/models.py:68 | |
417 | -msgid "Articles" | |
418 | -msgstr "Articles" | |
419 | - | |
420 | -#: rss/feeds.py:13 | |
414 | +#: colab/rss/feeds.py:13 | |
421 | 415 | msgid "Latest Discussions" |
422 | 416 | msgstr "Latest Discussions" |
423 | 417 | |
424 | -#: rss/feeds.py:32 | |
418 | +#: colab/rss/feeds.py:32 | |
425 | 419 | msgid "Discussions Most Relevance" |
426 | 420 | msgstr "Discussions Most Relevance" |
427 | 421 | |
428 | -#: rss/feeds.py:51 | |
422 | +#: colab/rss/feeds.py:51 | |
429 | 423 | msgid "Latest collaborations" |
430 | 424 | msgstr "Latest collaborations" |
431 | 425 | |
432 | -#: search/forms.py:16 search/templates/search/search.html:46 | |
433 | -#: templates/base.html:89 templates/header.html:29 | |
426 | +#: colab/search/forms.py:16 colab/search/templates/search/search.html:39 | |
427 | +#: colab/templates/header.html:27 | |
434 | 428 | msgid "Search" |
435 | 429 | msgstr "Search" |
436 | 430 | |
437 | -#: search/forms.py:18 | |
431 | +#: colab/search/forms.py:18 | |
438 | 432 | msgid "Type" |
439 | 433 | msgstr "Type" |
440 | 434 | |
441 | -#: search/forms.py:19 search/views.py:20 | |
442 | -msgid "Author" | |
443 | -msgstr "Author" | |
444 | - | |
445 | -#: search/forms.py:20 | |
446 | -msgid "Modified by" | |
447 | -msgstr "Modified by" | |
448 | - | |
449 | -#: search/forms.py:22 | |
450 | -msgid "Status" | |
451 | -msgstr "Status" | |
452 | - | |
453 | -#: search/forms.py:26 search/views.py:23 | |
454 | -msgid "Mailinglist" | |
455 | -msgstr "Mailinglist" | |
456 | - | |
457 | -#: search/forms.py:30 | |
458 | -msgid "Milestone" | |
459 | -msgstr "Milestone" | |
460 | - | |
461 | -#: search/forms.py:31 | |
462 | -msgid "Priority" | |
463 | -msgstr "Priority" | |
464 | - | |
465 | -#: search/forms.py:32 | |
466 | -msgid "Component" | |
467 | -msgstr "Component" | |
468 | - | |
469 | -#: search/forms.py:33 | |
470 | -msgid "Severity" | |
471 | -msgstr "Severity" | |
472 | - | |
473 | -#: search/forms.py:34 | |
474 | -msgid "Reporter" | |
475 | -msgstr "Reporter" | |
476 | - | |
477 | -#: search/forms.py:35 | |
478 | -msgid "Keywords" | |
479 | -msgstr "Keywords" | |
480 | - | |
481 | -#: search/forms.py:36 | |
482 | -msgid "Collaborators" | |
483 | -msgstr "Collaborators" | |
484 | - | |
485 | -#: search/forms.py:37 | |
486 | -msgid "Repository" | |
487 | -msgstr "Repository" | |
488 | - | |
489 | -#: search/forms.py:39 | |
490 | -msgid "Name" | |
491 | -msgstr "Name" | |
492 | - | |
493 | -#: search/forms.py:40 | |
494 | -msgid "Institution" | |
495 | -msgstr "Institution" | |
496 | - | |
497 | -#: search/forms.py:41 | |
498 | -msgid "Role" | |
499 | -msgstr "Role" | |
500 | - | |
501 | -#: search/forms.py:42 search/templates/search/includes/search_filters.html:132 | |
502 | -#: search/templates/search/includes/search_filters.html:134 | |
503 | -#: search/templates/search/includes/search_filters.html:166 | |
504 | -#: search/templates/search/includes/search_filters.html:167 | |
505 | -msgid "Since" | |
506 | -msgstr "Since" | |
507 | - | |
508 | -#: search/forms.py:43 search/templates/search/includes/search_filters.html:141 | |
509 | -#: search/templates/search/includes/search_filters.html:143 | |
510 | -#: search/templates/search/includes/search_filters.html:170 | |
511 | -#: search/templates/search/includes/search_filters.html:171 | |
435 | +#: colab/search/forms.py:20 | |
436 | +#: colab/search/templates/search/includes/search_filters.html:143 | |
437 | +#: colab/search/templates/search/includes/search_filters.html:145 | |
438 | +#: colab/search/templates/search/includes/search_filters.html:172 | |
439 | +#: colab/search/templates/search/includes/search_filters.html:173 | |
512 | 440 | msgid "Until" |
513 | 441 | msgstr "Until" |
514 | 442 | |
515 | -#: search/forms.py:44 | |
516 | -msgid "Filename" | |
517 | -msgstr "Filename" | |
518 | - | |
519 | -#: search/forms.py:45 | |
520 | -msgid "Used by" | |
521 | -msgstr "Used by" | |
522 | - | |
523 | -#: search/forms.py:46 | |
524 | -msgid "File type" | |
525 | -msgstr "File type" | |
526 | - | |
527 | -#: search/forms.py:47 | |
528 | -msgid "Size" | |
529 | -msgstr "Size" | |
530 | - | |
531 | -#: search/templates/search/includes/search_filters.html:5 | |
532 | -#: search/templates/search/includes/search_filters.html:33 | |
533 | -#: search/templates/search/includes/search_filters.html:51 | |
534 | -#: search/templates/search/includes/search_filters.html:69 | |
443 | +#: colab/search/templates/search/includes/search_filters.html:5 | |
444 | +#: colab/search/templates/search/includes/search_filters.html:33 | |
445 | +#: colab/search/templates/search/includes/search_filters.html:51 | |
446 | +#: colab/search/templates/search/includes/search_filters.html:69 | |
535 | 447 | msgid "Remove filter" |
536 | 448 | msgstr "Remove filter" |
537 | 449 | |
538 | -#: search/templates/search/includes/search_filters.html:88 | |
539 | -#: search/templates/search/includes/search_filters.html:152 | |
540 | -#: search/templates/search/includes/search_filters.html:176 | |
450 | +#: colab/search/templates/search/includes/search_filters.html:88 | |
451 | +#: colab/search/templates/search/includes/search_filters.html:154 | |
452 | +#: colab/search/templates/search/includes/search_filters.html:178 | |
541 | 453 | msgid "Filter" |
542 | 454 | msgstr "Filter" |
543 | 455 | |
544 | -#: search/templates/search/includes/search_filters.html:94 | |
456 | +#: colab/search/templates/search/includes/search_filters.html:94 | |
545 | 457 | msgid "Sort by" |
546 | 458 | msgstr "Sort by" |
547 | 459 | |
548 | -#: search/templates/search/includes/search_filters.html:111 | |
460 | +#: colab/search/templates/search/includes/search_filters.html:111 | |
549 | 461 | msgid "Types" |
550 | 462 | msgstr "Types" |
551 | 463 | |
552 | -#: search/templates/search/includes/search_filters.html:117 search/views.py:18 | |
553 | -msgid "Discussion" | |
554 | -msgstr "Discussion" | |
555 | - | |
556 | -#: search/templates/search/search.html:5 | |
464 | +#: colab/search/templates/search/search.html:5 | |
557 | 465 | msgid "search" |
558 | 466 | msgstr "search" |
559 | 467 | |
560 | -#: search/templates/search/search.html:51 | |
468 | +#: colab/search/templates/search/search.html:44 | |
561 | 469 | msgid "documents found" |
562 | 470 | msgstr "documents found" |
563 | 471 | |
564 | -#: search/templates/search/search.html:62 | |
472 | +#: colab/search/templates/search/search.html:55 | |
565 | 473 | msgid "Search here" |
566 | 474 | msgstr "Search here" |
567 | 475 | |
568 | -#: search/templates/search/search.html:74 | |
569 | -#: search/templates/search/search.html:84 | |
476 | +#: colab/search/templates/search/search.html:67 | |
570 | 477 | msgid "Filters" |
571 | 478 | msgstr "Filters" |
572 | 479 | |
573 | -#: search/templates/search/search.html:105 | |
480 | +#: colab/search/templates/search/search.html:76 | |
574 | 481 | msgid "No results for your search." |
575 | 482 | msgstr "No results for your search." |
576 | 483 | |
577 | -#: search/templates/search/search.html:107 | |
484 | +#: colab/search/templates/search/search.html:78 | |
578 | 485 | msgid "You are searching for" |
579 | 486 | msgstr "You are searching for" |
580 | 487 | |
581 | -#: settings.py:113 | |
488 | +#: colab/settings.py:111 | |
582 | 489 | msgid "English" |
583 | 490 | msgstr "English" |
584 | 491 | |
585 | -#: settings.py:114 | |
492 | +#: colab/settings.py:112 | |
586 | 493 | msgid "Portuguese" |
587 | 494 | msgstr "Portuguese" |
588 | 495 | |
589 | -#: settings.py:115 | |
496 | +#: colab/settings.py:113 | |
590 | 497 | msgid "Spanish" |
591 | 498 | msgstr "Spanish" |
592 | 499 | |
593 | -#: settings.py:138 | |
500 | +#: colab/settings.py:136 | |
594 | 501 | msgid "Recent activity" |
595 | 502 | msgstr "Recent activity" |
596 | 503 | |
597 | -#: settings.py:142 | |
504 | +#: colab/settings.py:140 | |
598 | 505 | msgid "Relevance" |
599 | 506 | msgstr "Relevance" |
600 | 507 | |
601 | -#: settings.py:151 | |
508 | +#: colab/settings.py:149 | |
602 | 509 | msgid "Document" |
603 | 510 | msgstr "Document" |
604 | 511 | |
605 | -#: settings.py:153 | |
512 | +#: colab/settings.py:151 | |
606 | 513 | msgid "Presentation" |
607 | 514 | msgstr "Presentation" |
608 | 515 | |
609 | -#: settings.py:154 | |
516 | +#: colab/settings.py:152 | |
610 | 517 | msgid "Text" |
611 | 518 | msgstr "Text" |
612 | 519 | |
613 | -#: settings.py:155 | |
520 | +#: colab/settings.py:153 | |
614 | 521 | msgid "Code" |
615 | 522 | msgstr "Code" |
616 | 523 | |
617 | -#: settings.py:157 | |
524 | +#: colab/settings.py:155 | |
618 | 525 | msgid "Compressed" |
619 | 526 | msgstr "Compressed" |
620 | 527 | |
621 | -#: settings.py:158 | |
528 | +#: colab/settings.py:156 | |
622 | 529 | msgid "Image" |
623 | 530 | msgstr "Image" |
624 | 531 | |
625 | -#: settings.py:160 | |
532 | +#: colab/settings.py:158 | |
626 | 533 | msgid "Spreadsheet" |
627 | 534 | msgstr "Spreadsheet" |
628 | 535 | |
629 | -#: templates/404.html:5 | |
536 | +#: colab/templates/404.html:5 | |
630 | 537 | msgid "Not found. Keep searching! :)" |
631 | 538 | msgstr "Not found. Keep searching! :)" |
632 | 539 | |
633 | -#: templates/500.html:2 | |
540 | +#: colab/templates/500.html:2 | |
634 | 541 | msgid "Ooopz... something went wrong!" |
635 | 542 | msgstr "Ooopz... something went wrong!" |
636 | 543 | |
637 | -#: templates/base.html:83 templates/header.html:21 | |
638 | -msgid "Groups" | |
639 | -msgstr "Groups" | |
640 | - | |
641 | -#: templates/base.html:119 templates/header.html:59 | |
642 | -msgid "My Profile" | |
643 | -msgstr "My Profile" | |
644 | - | |
645 | -#: templates/base.html:120 templates/base.html.py:121 templates/header.html:60 | |
646 | -#: templates/header.html.py:61 | |
647 | -msgid "Logout" | |
648 | -msgstr "Logout" | |
649 | - | |
650 | -#: templates/base.html:132 templates/base.html.py:135 templates/header.html:72 | |
651 | -#: templates/header.html.py:75 | |
652 | -msgid "Search here..." | |
653 | -msgstr "Search here..." | |
654 | - | |
655 | -#: templates/base.html:149 | |
544 | +#: colab/templates/base.html:74 | |
656 | 545 | msgid "The login has failed. Please, try again." |
657 | 546 | msgstr "The login has failed. Please, try again." |
658 | 547 | |
659 | -#: templates/footer.html:6 | |
548 | +#: colab/templates/footer.html:6 | |
660 | 549 | msgid "Last email imported at" |
661 | 550 | msgstr "Last email imported at" |
662 | 551 | |
663 | -#: templates/footer.html:12 | |
552 | +#: colab/templates/footer.html:12 | |
664 | 553 | msgid "The contents of this site is published under license" |
665 | 554 | msgstr "The contents of this site is published under license" |
666 | 555 | |
667 | -#: templates/footer.html:15 | |
556 | +#: colab/templates/footer.html:15 | |
668 | 557 | msgid "Creative Commons 4.0 Brasil - Atribuir Fonte - Compartilhar Igual" |
669 | 558 | msgstr "Creative Commons 4.0 Brasil - Atribuir Fonte - Compartilhar Igual" |
670 | 559 | |
671 | -#: templates/header.html:26 | |
672 | -msgid "Paste" | |
673 | -msgstr "Paste" | |
560 | +#: colab/templates/header.html:21 | |
561 | +msgid "Groups" | |
562 | +msgstr "Groups" | |
563 | + | |
564 | +#: colab/templates/header.html:57 | |
565 | +#, fuzzy | |
566 | +#| msgid "My Profile" | |
567 | +msgid "My Profile" | |
568 | +msgstr "My Profile" | |
569 | + | |
570 | +#: colab/templates/header.html:58 colab/templates/header.html.py:59 | |
571 | +msgid "Logout" | |
572 | +msgstr "Logout" | |
674 | 573 | |
675 | -#: templates/home.html:17 | |
574 | +#: colab/templates/header.html:70 colab/templates/header.html.py:73 | |
575 | +msgid "Search here..." | |
576 | +msgstr "Search here..." | |
577 | + | |
578 | +#: colab/templates/home.html:17 | |
676 | 579 | msgid "Latest Collaborations" |
677 | 580 | msgstr "Latest Collaborations" |
678 | 581 | |
679 | -#: templates/home.html:21 | |
582 | +#: colab/templates/home.html:21 | |
680 | 583 | msgid "RSS - Latest collaborations" |
681 | 584 | msgstr "RSS - Latest collaborations" |
682 | 585 | |
683 | -#: templates/home.html:30 | |
586 | +#: colab/templates/home.html:30 | |
684 | 587 | msgid "View more collaborations..." |
685 | 588 | msgstr "View more collaborations..." |
686 | 589 | |
687 | -#: templates/home.html:37 | |
590 | +#: colab/templates/home.html:37 | |
688 | 591 | msgid "Collaboration Graph" |
689 | 592 | msgstr "Collaboration Graph" |
690 | 593 | |
691 | -#: templates/home.html:48 | |
594 | +#: colab/templates/home.html:48 | |
692 | 595 | msgid "Most Relevant Threads" |
693 | 596 | msgstr "Most Relevant Threads" |
694 | 597 | |
695 | -#: templates/home.html:52 | |
598 | +#: colab/templates/home.html:52 | |
696 | 599 | msgid "RSS - Most Relevant Threads" |
697 | 600 | msgstr "RSS - Most Relevant Threads" |
698 | 601 | |
699 | -#: templates/home.html:60 templates/home.html.py:79 | |
602 | +#: colab/templates/home.html:60 colab/templates/home.html.py:79 | |
700 | 603 | msgid "View more discussions..." |
701 | 604 | msgstr "View more discussions..." |
702 | 605 | |
703 | -#: templates/home.html:67 | |
606 | +#: colab/templates/home.html:67 | |
704 | 607 | msgid "Latest Threads" |
705 | 608 | msgstr "Latest Threads" |
706 | 609 | |
707 | -#: templates/home.html:71 | |
610 | +#: colab/templates/home.html:71 | |
708 | 611 | msgid "RSS - Latest Threads" |
709 | 612 | msgstr "RSS - Latest Threads" |
613 | + | |
614 | +#, fuzzy | |
615 | +#~| msgid "Gitlab Projects" | |
616 | +#~ msgid "Public Projects" | |
617 | +#~ msgstr "Gitlab Projects" | |
618 | + | |
619 | +#, fuzzy | |
620 | +#~| msgid "Gitlab Project" | |
621 | +#~ msgid "New Project" | |
622 | +#~ msgstr "Gitlab Project" | |
623 | + | |
624 | +#, fuzzy | |
625 | +#~| msgid "Gitlab Projects" | |
626 | +#~ msgid "Projects" | |
627 | +#~ msgstr "Gitlab Projects" | |
628 | + | |
629 | +#, fuzzy | |
630 | +#~| msgid "Gitlab Issues" | |
631 | +#~ msgid "Issues" | |
632 | +#~ msgstr "Gitlab Issues" | |
633 | + | |
634 | +#, fuzzy | |
635 | +#~| msgid "Gitlab Merge Requests" | |
636 | +#~ msgid "Merge Requests" | |
637 | +#~ msgstr "Gitlab Merge Requests" | |
638 | + | |
639 | +#~ msgid "" | |
640 | +#~ "Please enter a correct %(username)s and password. Note that both fields " | |
641 | +#~ "may be case-sensitive." | |
642 | +#~ msgstr "" | |
643 | +#~ "Please enter a correct %(username)s and password. Note that both fields " | |
644 | +#~ "may be case-sensitive." | |
645 | + | |
646 | +#~ msgid "This account is inactive." | |
647 | +#~ msgstr "This account is inactive." | |
648 | + | |
649 | +#~ msgid "Email" | |
650 | +#~ msgstr "Email" | |
651 | + | |
652 | +#~ msgid "New password" | |
653 | +#~ msgstr "New password" | |
654 | + | |
655 | +#~ msgid "New password confirmation" | |
656 | +#~ msgstr "New password confirmation" | |
657 | + | |
658 | +#~ msgid "Your old password was entered incorrectly. Please enter it again." | |
659 | +#~ msgstr "Your old password was entered incorrectly. Please enter it again." | |
660 | + | |
661 | +#~ msgid "Old password" | |
662 | +#~ msgstr "Old password" | |
663 | + | |
664 | +#~ msgid "Password (again)" | |
665 | +#~ msgstr "Password (again)" | |
666 | + | |
667 | +#~ msgid "Enter a valid username." | |
668 | +#~ msgstr "Enter a valid username." | |
669 | + | |
670 | +#~ msgid "Gitlab Group" | |
671 | +#~ msgstr "Gitlab Group" | |
672 | + | |
673 | +#~ msgid "Gitlab Groups" | |
674 | +#~ msgstr "Gitlab Groups" | |
675 | + | |
676 | +#~ msgid "Gitlab Merge Request" | |
677 | +#~ msgstr "Gitlab Merge Request" | |
678 | + | |
679 | +#~ msgid "Gitlab Issue" | |
680 | +#~ msgstr "Gitlab Issue" | |
681 | + | |
682 | +#~ msgid "Gitlab Comments" | |
683 | +#~ msgstr "Gitlab Comments" | |
684 | + | |
685 | +#~ msgid "Community" | |
686 | +#~ msgstr "Community" | |
687 | + | |
688 | +#~ msgid "Article" | |
689 | +#~ msgstr "Article" | |
690 | + | |
691 | +#~ msgid "Articles" | |
692 | +#~ msgstr "Articles" | |
693 | + | |
694 | +#~ msgid "Author" | |
695 | +#~ msgstr "Author" | |
696 | + | |
697 | +#~ msgid "Modified by" | |
698 | +#~ msgstr "Modified by" | |
699 | + | |
700 | +#~ msgid "Status" | |
701 | +#~ msgstr "Status" | |
702 | + | |
703 | +#~ msgid "Mailinglist" | |
704 | +#~ msgstr "Mailinglist" | |
705 | + | |
706 | +#~ msgid "Milestone" | |
707 | +#~ msgstr "Milestone" | |
708 | + | |
709 | +#~ msgid "Priority" | |
710 | +#~ msgstr "Priority" | |
711 | + | |
712 | +#~ msgid "Component" | |
713 | +#~ msgstr "Component" | |
714 | + | |
715 | +#~ msgid "Severity" | |
716 | +#~ msgstr "Severity" | |
717 | + | |
718 | +#~ msgid "Reporter" | |
719 | +#~ msgstr "Reporter" | |
720 | + | |
721 | +#~ msgid "Keywords" | |
722 | +#~ msgstr "Keywords" | |
723 | + | |
724 | +#~ msgid "Collaborators" | |
725 | +#~ msgstr "Collaborators" | |
726 | + | |
727 | +#~ msgid "Repository" | |
728 | +#~ msgstr "Repository" | |
729 | + | |
730 | +#~ msgid "Used by" | |
731 | +#~ msgstr "Used by" | |
732 | + | |
733 | +#~ msgid "File type" | |
734 | +#~ msgstr "File type" | |
735 | + | |
736 | +#~ msgid "Size" | |
737 | +#~ msgstr "Size" | |
738 | + | |
739 | +#~ msgid "Discussion" | |
740 | +#~ msgstr "Discussion" | |
741 | + | |
742 | +#~ msgid "Paste" | |
743 | +#~ msgstr "Paste" | ... | ... |
colab/locale/pt_BR/LC_MESSAGES/django.mo
No preview for this file type
colab/locale/pt_BR/LC_MESSAGES/django.po
1 | 1 | # SOME DESCRIPTIVE TITLE. |
2 | 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER |
3 | 3 | # This file is distributed under the same license as the PACKAGE package. |
4 | -# | |
4 | +# | |
5 | 5 | # Translators: |
6 | 6 | # Lucas Kanashiro Duarte <kanashiro.duarte@gmail.com>, 2015 |
7 | 7 | # macártur de sousa carvalho <macartur.sc@gmail.com>, 2015 |
... | ... | @@ -10,703 +10,740 @@ msgid "" |
10 | 10 | msgstr "" |
11 | 11 | "Project-Id-Version: colab\n" |
12 | 12 | "Report-Msgid-Bugs-To: \n" |
13 | -"POT-Creation-Date: 2015-09-01 13:15+0000\n" | |
13 | +"POT-Creation-Date: 2015-11-24 17:46+0000\n" | |
14 | 14 | "PO-Revision-Date: 2015-09-07 19:13+0000\n" |
15 | 15 | "Last-Translator: Lucas Kanashiro Duarte <kanashiro.duarte@gmail.com>\n" |
16 | -"Language-Team: Portuguese (Brazil) (http://www.transifex.com/colab/colab/language/pt_BR/)\n" | |
16 | +"Language-Team: Portuguese (Brazil) (http://www.transifex.com/colab/colab/" | |
17 | +"language/pt_BR/)\n" | |
18 | +"Language: pt_BR\n" | |
17 | 19 | "MIME-Version: 1.0\n" |
18 | 20 | "Content-Type: text/plain; charset=UTF-8\n" |
19 | 21 | "Content-Transfer-Encoding: 8bit\n" |
20 | -"Language: pt_BR\n" | |
21 | 22 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" |
22 | 23 | |
23 | -#: accounts/admin.py:18 | |
24 | +#: colab-noosfero-plugin/tests/plugins.d/noosfero.py:21 | |
25 | +msgid "Social" | |
26 | +msgstr "Social" | |
27 | + | |
28 | +#: colab-noosfero-plugin/tests/plugins.d/noosfero.py:26 | |
29 | +#, fuzzy | |
30 | +#| msgid "Username" | |
31 | +msgid "Users" | |
32 | +msgstr "Usuário" | |
33 | + | |
34 | +#: colab-noosfero-plugin/tests/plugins.d/noosfero.py:28 | |
35 | +msgid "Communities" | |
36 | +msgstr "Comunidades" | |
37 | + | |
38 | +#: colab-noosfero-plugin/tests/plugins.d/noosfero.py:30 | |
39 | +#, fuzzy | |
40 | +#| msgid "My Profile" | |
41 | +msgid "Profile" | |
42 | +msgstr "Meu Perfil" | |
43 | + | |
44 | +#: colab-noosfero-plugin/tests/plugins.d/noosfero.py:32 | |
45 | +msgid "Control Panel" | |
46 | +msgstr "Painel de Controle" | |
47 | + | |
48 | +#: colab/accounts/admin.py:18 | |
24 | 49 | msgid "Personal info" |
25 | 50 | msgstr "Informações Pessoais" |
26 | 51 | |
27 | -#: accounts/admin.py:24 | |
52 | +#: colab/accounts/admin.py:24 | |
28 | 53 | msgid "Permissions" |
29 | 54 | msgstr "Permissões" |
30 | 55 | |
31 | -#: accounts/admin.py:28 | |
56 | +#: colab/accounts/admin.py:28 | |
32 | 57 | msgid "Important dates" |
33 | 58 | msgstr "Datas importantes" |
34 | 59 | |
35 | -#: accounts/forms.py:37 | |
60 | +#: colab/accounts/filters.py:7 | |
61 | +#: colab/accounts/templates/search/user_search_preview.html:12 | |
62 | +#, fuzzy | |
63 | +#| msgid "Username" | |
64 | +msgid "User" | |
65 | +msgstr "Usuário" | |
66 | + | |
67 | +#: colab/accounts/filters.py:12 colab/accounts/forms.py:175 | |
68 | +#: colab/accounts/forms.py:246 | |
69 | +#, fuzzy | |
70 | +#| msgid "Filename" | |
71 | +msgid "Username" | |
72 | +msgstr "Nome do arquivo" | |
73 | + | |
74 | +#: colab/accounts/filters.py:15 | |
75 | +msgid "Name" | |
76 | +msgstr "Nome" | |
77 | + | |
78 | +#: colab/accounts/filters.py:18 | |
79 | +msgid "Institution" | |
80 | +msgstr "Instituição" | |
81 | + | |
82 | +#: colab/accounts/filters.py:21 | |
83 | +msgid "Role" | |
84 | +msgstr "Papel" | |
85 | + | |
86 | +#: colab/accounts/forms.py:30 | |
36 | 87 | msgid "Social account does not exist" |
37 | 88 | msgstr "Conta social não existe" |
38 | 89 | |
39 | -#: accounts/forms.py:65 accounts/forms.py:71 accounts/forms.py:77 | |
90 | +#: colab/accounts/forms.py:58 colab/accounts/forms.py:64 | |
91 | +#: colab/accounts/forms.py:70 | |
40 | 92 | msgid "This field cannot be blank." |
41 | 93 | msgstr "Este campo não pode ficar em branco." |
42 | 94 | |
43 | -#: accounts/forms.py:118 accounts/templates/accounts/user_detail.html:38 | |
95 | +#: colab/accounts/forms.py:111 | |
96 | +#: colab/accounts/templates/accounts/user_detail.html:38 | |
44 | 97 | msgid "Bio" |
45 | 98 | msgstr "Bio" |
46 | 99 | |
47 | -#: accounts/forms.py:119 | |
100 | +#: colab/accounts/forms.py:112 | |
48 | 101 | msgid "Write something about you in 200 characters or less." |
49 | 102 | msgstr "Escreva algo sobre você em 200 caracteres ou menos." |
50 | 103 | |
51 | -#: accounts/forms.py:148 | |
104 | +#: colab/accounts/forms.py:157 | |
52 | 105 | msgid "Mailing lists" |
53 | 106 | msgstr "Listas de e-mail" |
54 | 107 | |
55 | -#: accounts/forms.py:161 | |
108 | +#: colab/accounts/forms.py:170 | |
56 | 109 | #, python-format |
57 | 110 | msgid "Email already used. Is it you? Please <a href='%(url)s'>login</a>" |
58 | -msgstr "Este email ja está sendo utilizado.Caso seja o seu efetue o <a href='%(url)s'>login</a>" | |
111 | +msgstr "" | |
112 | +"Este email ja está sendo utilizado.Caso seja o seu efetue o <a " | |
113 | +"href='%(url)s'>login</a>" | |
59 | 114 | |
60 | -#: accounts/forms.py:163 | |
115 | +#: colab/accounts/forms.py:172 | |
61 | 116 | msgid "A user with that username already exists." |
62 | 117 | msgstr "Já existe um usuário com este nome." |
63 | 118 | |
64 | -#: accounts/forms.py:164 accounts/forms.py:401 accounts/forms.py:465 | |
119 | +#: colab/accounts/forms.py:173 | |
65 | 120 | msgid "The two password fields didn't match." |
66 | 121 | msgstr "Os dois campos de senha não conferem." |
67 | 122 | |
68 | -#: accounts/forms.py:166 accounts/forms.py:236 search/forms.py:38 | |
69 | -msgid "Username" | |
70 | -msgstr "Usuário" | |
71 | - | |
72 | -#: accounts/forms.py:173 accounts/forms.py:240 accounts/forms.py:277 | |
73 | -#: accounts/forms.py:467 | |
123 | +#: colab/accounts/forms.py:183 colab/accounts/forms.py:251 | |
74 | 124 | msgid "Password" |
75 | 125 | msgstr "Senha" |
76 | 126 | |
77 | -#: accounts/forms.py:175 | |
127 | +#: colab/accounts/forms.py:185 | |
78 | 128 | msgid "Password confirmation" |
79 | 129 | msgstr "Confirmação de senha" |
80 | 130 | |
81 | -#: accounts/forms.py:237 accounts/models.py:78 | |
131 | +#: colab/accounts/forms.py:247 colab/accounts/models.py:77 | |
82 | 132 | msgid "Required. 30 characters or fewer. Letters and digits." |
83 | 133 | msgstr "Obrigatório. 30 caracteres ou menos. Letras ou digitos." |
84 | 134 | |
85 | -#: accounts/forms.py:239 | |
135 | +#: colab/accounts/forms.py:249 | |
86 | 136 | msgid "This value may contain only letters and numbers." |
87 | 137 | msgstr "Este campo pode conter apenas letras e números." |
88 | 138 | |
89 | -#: accounts/forms.py:241 | |
139 | +#: colab/accounts/forms.py:252 | |
90 | 140 | msgid "" |
91 | 141 | "Raw passwords are not stored, so there is no way to see this user's " |
92 | 142 | "password, but you can change the password using <a href=\"password/\">this " |
93 | 143 | "form</a>." |
94 | -msgstr "Senhas em claro não são armazenadas, então não será possível visualizar sua senha, no entanto você poderá modificar sua senha utilizando <a href=\\\"password/\\\">este formulário</a>." | |
95 | - | |
96 | -#: accounts/forms.py:280 | |
97 | -#, python-format | |
98 | -msgid "" | |
99 | -"Please enter a correct %(username)s and password. Note that both fields may " | |
100 | -"be case-sensitive." | |
101 | -msgstr "Por favor entre com um %(username)s e senha. Note que ambos os campos diferenciam letras maiúsculas de minúsculas" | |
102 | - | |
103 | -#: accounts/forms.py:282 | |
104 | -msgid "This account is inactive." | |
105 | -msgstr "Esta conta está inativa." | |
106 | - | |
107 | -#: accounts/forms.py:345 | |
108 | -msgid "Email" | |
109 | -msgstr "Email" | |
110 | - | |
111 | -#: accounts/forms.py:403 | |
112 | -msgid "New password" | |
113 | -msgstr "Nova senha" | |
114 | - | |
115 | -#: accounts/forms.py:405 | |
116 | -msgid "New password confirmation" | |
117 | -msgstr "Confirmar nova senha" | |
118 | - | |
119 | -#: accounts/forms.py:436 | |
120 | -msgid "Your old password was entered incorrectly. Please enter it again." | |
121 | -msgstr "Sua senha atual está incorreta. Por favor tente novamente." | |
122 | - | |
123 | -#: accounts/forms.py:439 | |
124 | -msgid "Old password" | |
125 | -msgstr "Senha atual" | |
126 | - | |
127 | -#: accounts/forms.py:469 | |
128 | -msgid "Password (again)" | |
129 | -msgstr "Senha (novamente)" | |
130 | - | |
131 | -#: accounts/models.py:82 | |
132 | -msgid "Enter a valid username." | |
133 | -msgstr "Insira um nome de usuário válido." | |
144 | +msgstr "" | |
145 | +"Senhas em claro não são armazenadas, então não será possível visualizar sua " | |
146 | +"senha, no entanto você poderá modificar sua senha utilizando <a href=\\" | |
147 | +"\"password/\\\">este formulário</a>." | |
134 | 148 | |
135 | -#: accounts/templates/accounts/manage_subscriptions.html:6 | |
149 | +#: colab/accounts/templates/accounts/manage_subscriptions.html:6 | |
136 | 150 | msgid "Group Subscriptions" |
137 | 151 | msgstr "Inscrições em grupos" |
138 | 152 | |
139 | -#: accounts/templates/accounts/manage_subscriptions.html:36 | |
153 | +#: colab/accounts/templates/accounts/manage_subscriptions.html:36 | |
140 | 154 | msgid "Update subscriptions" |
141 | 155 | msgstr "Atualizar inscrições" |
142 | 156 | |
143 | -#: accounts/templates/accounts/user_create_form.html:5 | |
157 | +#: colab/accounts/templates/accounts/user_create_form.html:5 | |
144 | 158 | msgid "Sign up" |
145 | 159 | msgstr "Cadastrar" |
146 | 160 | |
147 | -#: accounts/templates/accounts/user_create_form.html:10 | |
148 | -#: accounts/templates/registration/login.html:14 | |
149 | -#: accounts/templates/registration/password_change_form_custom.html:12 | |
150 | -#: accounts/templates/registration/password_reset_confirm_custom.html:14 | |
151 | -#: accounts/templates/registration/password_reset_form_custom.html:9 | |
161 | +#: colab/accounts/templates/accounts/user_create_form.html:10 | |
162 | +#: colab/accounts/templates/registration/login.html:14 | |
163 | +#: colab/accounts/templates/registration/password_change_form_custom.html:12 | |
164 | +#: colab/accounts/templates/registration/password_reset_confirm_custom.html:14 | |
165 | +#: colab/accounts/templates/registration/password_reset_form_custom.html:9 | |
152 | 166 | msgid "Please correct the errors below and try again." |
153 | 167 | msgstr "Por favor corrija os erros abaixo e tente novamente." |
154 | 168 | |
155 | -#: accounts/templates/accounts/user_create_form.html:17 | |
169 | +#: colab/accounts/templates/accounts/user_create_form.html:17 | |
156 | 170 | msgid "Required fields" |
157 | 171 | msgstr "Campos obrigatórios" |
158 | 172 | |
159 | -#: accounts/templates/accounts/user_create_form.html:29 | |
173 | +#: colab/accounts/templates/accounts/user_create_form.html:29 | |
160 | 174 | msgid "Personal Information" |
161 | 175 | msgstr "Informações pessoais" |
162 | 176 | |
163 | -#: accounts/templates/accounts/user_create_form.html:46 | |
177 | +#: colab/accounts/templates/accounts/user_create_form.html:46 | |
164 | 178 | msgid "Subscribe to groups" |
165 | 179 | msgstr "Inscreva-se nos grupos" |
166 | 180 | |
167 | -#: accounts/templates/accounts/user_create_form.html:60 | |
168 | -#: templates/base.html:100 templates/base.html.py:105 templates/header.html:40 | |
169 | -#: templates/header.html.py:45 | |
181 | +#: colab/accounts/templates/accounts/user_create_form.html:60 | |
182 | +#: colab/templates/header.html:38 colab/templates/header.html.py:43 | |
170 | 183 | msgid "Register" |
171 | 184 | msgstr "Cadastre-se" |
172 | 185 | |
173 | -#: accounts/templates/accounts/user_detail.html:8 | |
186 | +#: colab/accounts/templates/accounts/user_detail.html:8 | |
174 | 187 | msgid "Messages" |
175 | 188 | msgstr "Mensagens" |
176 | 189 | |
177 | -#: accounts/templates/accounts/user_detail.html:9 templates/home.html:7 | |
190 | +#: colab/accounts/templates/accounts/user_detail.html:9 | |
191 | +#: colab/templates/home.html:7 | |
178 | 192 | msgid "Contributions" |
179 | 193 | msgstr "Contribuições" |
180 | 194 | |
181 | -#: accounts/templates/accounts/user_detail.html:29 | |
195 | +#: colab/accounts/templates/accounts/user_detail.html:29 | |
182 | 196 | msgid "edit profile" |
183 | 197 | msgstr "editar perfil" |
184 | 198 | |
185 | -#: accounts/templates/accounts/user_detail.html:30 | |
199 | +#: colab/accounts/templates/accounts/user_detail.html:30 | |
186 | 200 | msgid "group membership" |
187 | 201 | msgstr "Inscrições nos grupos" |
188 | 202 | |
189 | -#: accounts/templates/accounts/user_detail.html:66 | |
203 | +#: colab/accounts/templates/accounts/user_detail.html:66 | |
190 | 204 | msgid "Twitter account" |
191 | 205 | msgstr "Conta Twitter" |
192 | 206 | |
193 | -#: accounts/templates/accounts/user_detail.html:69 | |
207 | +#: colab/accounts/templates/accounts/user_detail.html:69 | |
194 | 208 | msgid "Facebook account" |
195 | 209 | msgstr "Conta Facebook" |
196 | 210 | |
197 | -#: accounts/templates/accounts/user_detail.html:74 | |
211 | +#: colab/accounts/templates/accounts/user_detail.html:74 | |
198 | 212 | msgid "Google talk account" |
199 | 213 | msgstr "Conta Google talk" |
200 | 214 | |
201 | -#: accounts/templates/accounts/user_detail.html:78 | |
215 | +#: colab/accounts/templates/accounts/user_detail.html:78 | |
202 | 216 | msgid "Github account" |
203 | 217 | msgstr "Conta Github" |
204 | 218 | |
205 | -#: accounts/templates/accounts/user_detail.html:82 | |
219 | +#: colab/accounts/templates/accounts/user_detail.html:82 | |
206 | 220 | msgid "Personal webpage" |
207 | 221 | msgstr "Página web pessoal" |
208 | 222 | |
209 | -#: accounts/templates/accounts/user_detail.html:88 | |
223 | +#: colab/accounts/templates/accounts/user_detail.html:88 | |
210 | 224 | msgid "Groups: " |
211 | 225 | msgstr "Grupos: " |
212 | 226 | |
213 | -#: accounts/templates/accounts/user_detail.html:101 | |
227 | +#: colab/accounts/templates/accounts/user_detail.html:101 | |
214 | 228 | msgid "Collaborations by Type" |
215 | 229 | msgstr "Colaborações por tipo" |
216 | 230 | |
217 | -#: accounts/templates/accounts/user_detail.html:117 | |
231 | +#: colab/accounts/templates/accounts/user_detail.html:117 | |
218 | 232 | msgid "Participation by Group" |
219 | 233 | msgstr "Participação por grupo" |
220 | 234 | |
221 | -#: accounts/templates/accounts/user_detail.html:133 | |
235 | +#: colab/accounts/templates/accounts/user_detail.html:133 | |
222 | 236 | msgid "Latest posted" |
223 | 237 | msgstr "Últimas postagens" |
224 | 238 | |
225 | -#: accounts/templates/accounts/user_detail.html:138 | |
239 | +#: colab/accounts/templates/accounts/user_detail.html:138 | |
226 | 240 | msgid "There are no posts by this user so far." |
227 | 241 | msgstr "Não há posts deste usuário até agora." |
228 | 242 | |
229 | -#: accounts/templates/accounts/user_detail.html:142 | |
243 | +#: colab/accounts/templates/accounts/user_detail.html:142 | |
230 | 244 | msgid "View more posts..." |
231 | 245 | msgstr "Ver mais postagens..." |
232 | 246 | |
233 | -#: accounts/templates/accounts/user_detail.html:148 | |
247 | +#: colab/accounts/templates/accounts/user_detail.html:148 | |
234 | 248 | msgid "Latest contributions" |
235 | 249 | msgstr "Últimas colaborações" |
236 | 250 | |
237 | -#: accounts/templates/accounts/user_detail.html:153 | |
251 | +#: colab/accounts/templates/accounts/user_detail.html:153 | |
238 | 252 | msgid "No contributions of this user so far." |
239 | 253 | msgstr "Não há contribuições deste usuário até agora." |
240 | 254 | |
241 | -#: accounts/templates/accounts/user_detail.html:157 | |
255 | +#: colab/accounts/templates/accounts/user_detail.html:157 | |
242 | 256 | msgid "View more contributions..." |
243 | 257 | msgstr "Ver mais colaborações..." |
244 | 258 | |
245 | -#: accounts/templates/accounts/user_update_form.html:65 | |
259 | +#: colab/accounts/templates/accounts/user_update_form.html:70 | |
246 | 260 | msgid "We sent a verification email to " |
247 | 261 | msgstr "Enviamos um email de verificação para " |
248 | 262 | |
249 | -#: accounts/templates/accounts/user_update_form.html:66 | |
263 | +#: colab/accounts/templates/accounts/user_update_form.html:71 | |
250 | 264 | msgid "Please follow the instructions in it." |
251 | 265 | msgstr "Por favor, siga as instruções." |
252 | 266 | |
253 | -#: accounts/templates/accounts/user_update_form.html:110 | |
267 | +#: colab/accounts/templates/accounts/user_update_form.html:123 | |
254 | 268 | msgid "profile information" |
255 | 269 | msgstr "informações do perfil" |
256 | 270 | |
257 | -#: accounts/templates/accounts/user_update_form.html:115 | |
271 | +#: colab/accounts/templates/accounts/user_update_form.html:128 | |
258 | 272 | msgid "Change your avatar at Gravatar.com" |
259 | 273 | msgstr "Troque seu avatar em Gravatar.com" |
260 | 274 | |
261 | -#: accounts/templates/accounts/user_update_form.html:142 search/utils.py:56 | |
275 | +#: colab/accounts/templates/accounts/user_update_form.html:178 | |
276 | +#: colab/search/utils.py:56 | |
262 | 277 | msgid "Emails" |
263 | 278 | msgstr "Emails" |
264 | 279 | |
265 | -#: accounts/templates/accounts/user_update_form.html:151 | |
280 | +#: colab/accounts/templates/accounts/user_update_form.html:187 | |
266 | 281 | msgid "Primary" |
267 | 282 | msgstr "Primário" |
268 | 283 | |
269 | -#: accounts/templates/accounts/user_update_form.html:154 | |
284 | +#: colab/accounts/templates/accounts/user_update_form.html:190 | |
270 | 285 | msgid "Setting..." |
271 | 286 | msgstr "Definindo..." |
272 | 287 | |
273 | -#: accounts/templates/accounts/user_update_form.html:154 | |
288 | +#: colab/accounts/templates/accounts/user_update_form.html:190 | |
274 | 289 | msgid "Set as Primary" |
275 | 290 | msgstr "Definir como Primário" |
276 | 291 | |
277 | -#: accounts/templates/accounts/user_update_form.html:155 | |
292 | +#: colab/accounts/templates/accounts/user_update_form.html:191 | |
278 | 293 | msgid "Deleting..." |
279 | 294 | msgstr "Apagando..." |
280 | 295 | |
281 | -#: accounts/templates/accounts/user_update_form.html:155 | |
282 | -#: accounts/templates/accounts/user_update_form.html:167 | |
296 | +#: colab/accounts/templates/accounts/user_update_form.html:191 | |
297 | +#: colab/accounts/templates/accounts/user_update_form.html:203 | |
283 | 298 | msgid "Delete" |
284 | 299 | msgstr "Apagar" |
285 | 300 | |
286 | -#: accounts/templates/accounts/user_update_form.html:166 | |
301 | +#: colab/accounts/templates/accounts/user_update_form.html:202 | |
287 | 302 | msgid "Sending verification..." |
288 | 303 | msgstr "Enviando verificação..." |
289 | 304 | |
290 | -#: accounts/templates/accounts/user_update_form.html:166 | |
305 | +#: colab/accounts/templates/accounts/user_update_form.html:202 | |
291 | 306 | msgid "Verify" |
292 | 307 | msgstr "Verificar" |
293 | 308 | |
294 | -#: accounts/templates/accounts/user_update_form.html:174 | |
309 | +#: colab/accounts/templates/accounts/user_update_form.html:210 | |
295 | 310 | msgid "Add another email address:" |
296 | 311 | msgstr "Adicionar outro endereço de email" |
297 | 312 | |
298 | -#: accounts/templates/accounts/user_update_form.html:177 | |
313 | +#: colab/accounts/templates/accounts/user_update_form.html:213 | |
299 | 314 | msgid "Add" |
300 | 315 | msgstr "Adicionar" |
301 | 316 | |
302 | -#: accounts/templates/accounts/user_update_form.html:185 | |
303 | -#: accounts/templates/accounts/user_update_form.html:189 | |
304 | -#: accounts/templates/registration/password_change_form_custom.html:26 | |
305 | -#: accounts/templates/registration/password_reset_confirm_custom.html:28 | |
317 | +#: colab/accounts/templates/accounts/user_update_form.html:221 | |
318 | +#: colab/accounts/templates/accounts/user_update_form.html:225 | |
319 | +#: colab/accounts/templates/registration/password_change_form_custom.html:26 | |
320 | +#: colab/accounts/templates/registration/password_reset_confirm_custom.html:28 | |
306 | 321 | msgid "Change Password" |
307 | 322 | msgstr "Trocar senha" |
308 | 323 | |
309 | -#: accounts/templates/accounts/user_update_form.html:196 | |
324 | +#: colab/accounts/templates/accounts/user_update_form.html:233 | |
310 | 325 | msgid "Update" |
311 | 326 | msgstr "Atualizar" |
312 | 327 | |
313 | -#: accounts/templates/registration/login.html:10 | |
314 | -#: accounts/templates/registration/password_change_form_custom.html:10 | |
315 | -#: accounts/templates/registration/password_reset_confirm_custom.html:12 | |
328 | +#: colab/accounts/templates/accounts/user_update_form.html:234 | |
329 | +#| msgid "Control Panel" | |
330 | +msgid "Go to profile panel" | |
331 | +msgstr "Ir para meu perfil" | |
332 | + | |
333 | +#: colab/accounts/templates/registration/login.html:10 | |
334 | +#: colab/accounts/templates/registration/password_change_form_custom.html:10 | |
335 | +#: colab/accounts/templates/registration/password_reset_confirm_custom.html:12 | |
316 | 336 | msgid "Please correct the error below and try again." |
317 | 337 | msgstr "Por favor corrija o erro abaixo e tente novamente." |
318 | 338 | |
319 | -#: accounts/templates/registration/login.html:34 | |
320 | -#: accounts/templates/registration/login.html:54 templates/base.html:99 | |
321 | -#: templates/base.html.py:101 templates/base.html:104 | |
322 | -#: templates/base.html.py:106 templates/header.html:39 | |
323 | -#: templates/header.html.py:41 templates/header.html:44 | |
324 | -#: templates/header.html.py:46 | |
339 | +#: colab/accounts/templates/registration/login.html:34 | |
340 | +#: colab/accounts/templates/registration/login.html:54 | |
341 | +#: colab/templates/header.html:37 colab/templates/header.html.py:39 | |
342 | +#: colab/templates/header.html:42 colab/templates/header.html.py:44 | |
325 | 343 | msgid "Login" |
326 | 344 | msgstr "Entrar" |
327 | 345 | |
328 | -#: accounts/templates/registration/login.html:56 | |
346 | +#: colab/accounts/templates/registration/login.html:56 | |
329 | 347 | msgid "Forgot Password?" |
330 | 348 | msgstr "Esqueci minha Senha?" |
331 | 349 | |
332 | -#: accounts/templates/registration/password_change_form_custom.html:54 | |
333 | -#: accounts/templates/registration/password_reset_confirm_custom.html:51 | |
350 | +#: colab/accounts/templates/registration/password_change_form_custom.html:54 | |
351 | +#: colab/accounts/templates/registration/password_reset_confirm_custom.html:51 | |
334 | 352 | msgid "Change my password" |
335 | 353 | msgstr "Alterar minha senha" |
336 | 354 | |
337 | -#: accounts/templates/registration/password_reset_confirm_custom.html:3 | |
355 | +#: colab/accounts/templates/registration/password_reset_confirm_custom.html:3 | |
338 | 356 | msgid "Setting New password" |
339 | 357 | msgstr "Definir Nova Senha" |
340 | 358 | |
341 | -#: accounts/templates/registration/password_reset_form_custom.html:23 | |
359 | +#: colab/accounts/templates/registration/password_reset_form_custom.html:23 | |
342 | 360 | msgid "" |
343 | 361 | "Forgotten your password? Enter your email address below, and we'll email " |
344 | 362 | "instructions for setting a new one." |
345 | -msgstr "Esqueceu sua senha? Digite seu email abaixo, e enviaremos instruções para redefinir sua senha." | |
363 | +msgstr "" | |
364 | +"Esqueceu sua senha? Digite seu email abaixo, e enviaremos instruções para " | |
365 | +"redefinir sua senha." | |
346 | 366 | |
347 | -#: accounts/templates/registration/password_reset_form_custom.html:26 | |
367 | +#: colab/accounts/templates/registration/password_reset_form_custom.html:26 | |
348 | 368 | msgid "Email address:" |
349 | 369 | msgstr "Endereço de email: " |
350 | 370 | |
351 | -#: accounts/templates/registration/password_reset_form_custom.html:37 | |
371 | +#: colab/accounts/templates/registration/password_reset_form_custom.html:37 | |
352 | 372 | msgid "Reset password" |
353 | 373 | msgstr "Redefinir senha" |
354 | 374 | |
355 | -#: accounts/views.py:126 | |
375 | +#: colab/accounts/templates/search/user_search_preview.html:11 | |
376 | +#: colab/search/forms.py:19 | |
377 | +#: colab/search/templates/search/includes/search_filters.html:134 | |
378 | +#: colab/search/templates/search/includes/search_filters.html:136 | |
379 | +#: colab/search/templates/search/includes/search_filters.html:168 | |
380 | +#: colab/search/templates/search/includes/search_filters.html:169 | |
381 | +msgid "Since" | |
382 | +msgstr "Desde" | |
383 | + | |
384 | +#: colab/accounts/templates/search/user_search_preview.html:12 | |
385 | +#, fuzzy | |
386 | +#| msgid "Register" | |
387 | +msgid "Registered in" | |
388 | +msgstr "Cadastre-se" | |
389 | + | |
390 | +#: colab/accounts/templatetags/date_format.py:8 | |
391 | +#, python-format | |
392 | +msgid "%(m)s %(d)s %(y)s" | |
393 | +msgstr "" | |
394 | + | |
395 | +#: colab/accounts/templatetags/date_format.py:17 | |
396 | +#, python-format | |
397 | +msgid "%(hour)s:%(min)s" | |
398 | +msgstr "" | |
399 | + | |
400 | +#: colab/accounts/templatetags/date_format.py:19 | |
401 | +#, python-format | |
402 | +msgid "%s at %s" | |
403 | +msgstr "" | |
404 | + | |
405 | +#: colab/accounts/views.py:126 | |
356 | 406 | msgid "Your profile has been created!" |
357 | 407 | msgstr "Seu perfil foi criado!" |
358 | 408 | |
359 | -#: accounts/views.py:186 | |
409 | +#: colab/accounts/views.py:186 | |
360 | 410 | msgid "Your password was changed." |
361 | 411 | msgstr "Sua senha foi modificada." |
362 | 412 | |
363 | -#: accounts/views.py:202 | |
413 | +#: colab/accounts/views.py:202 | |
364 | 414 | msgid "Your password has been set. You may go ahead and log in now." |
365 | 415 | msgstr "Sua senha foi modificada. Você já pode efetuar o login agora." |
366 | 416 | |
367 | -#: home/context_processors.py:20 | |
417 | +#: colab/home/context_processors.py:20 | |
368 | 418 | msgid "Fork me!" |
369 | 419 | msgstr "Fork me!" |
370 | 420 | |
371 | -#: plugins/gitlab/models.py:27 | |
372 | -msgid "Gitlab Project" | |
373 | -msgstr "Projeto no Gitlab" | |
374 | - | |
375 | -#: plugins/gitlab/models.py:28 | |
376 | -msgid "Gitlab Projects" | |
377 | -msgstr "Projetos no Gitlab" | |
378 | - | |
379 | -#: plugins/gitlab/models.py:54 | |
380 | -msgid "Gitlab Group" | |
381 | -msgstr "Grupo no Gitlab" | |
382 | - | |
383 | -#: plugins/gitlab/models.py:55 | |
384 | -msgid "Gitlab Groups" | |
385 | -msgstr "Grupos no Gitlab" | |
386 | - | |
387 | -#: plugins/gitlab/models.py:91 | |
388 | -msgid "Gitlab Merge Request" | |
389 | -msgstr "Envio de Contribuição no Gitlab" | |
390 | - | |
391 | -#: plugins/gitlab/models.py:92 | |
392 | -msgid "Gitlab Merge Requests" | |
393 | -msgstr "Envio de Contribuições no Gitlab" | |
394 | - | |
395 | -#: plugins/gitlab/models.py:120 | |
396 | -msgid "Gitlab Issue" | |
397 | -msgstr "Tíquete no Gitlab" | |
398 | - | |
399 | -#: plugins/gitlab/models.py:121 | |
400 | -msgid "Gitlab Issues" | |
401 | -msgstr "Tíquetes no Gitlab" | |
402 | - | |
403 | -#: plugins/gitlab/models.py:176 plugins/gitlab/models.py:177 | |
404 | -msgid "Gitlab Comments" | |
405 | -msgstr "Comentários no Gitlab" | |
406 | - | |
407 | -#: plugins/noosfero/models.py:39 | |
408 | -msgid "Community" | |
409 | -msgstr "Comunidade" | |
410 | - | |
411 | -#: plugins/noosfero/models.py:40 | |
412 | -msgid "Communities" | |
413 | -msgstr "Comunidades" | |
414 | - | |
415 | -#: plugins/noosfero/models.py:67 | |
416 | -msgid "Article" | |
417 | -msgstr "Artigo" | |
418 | - | |
419 | -#: plugins/noosfero/models.py:68 | |
420 | -msgid "Articles" | |
421 | -msgstr "Artigos" | |
422 | - | |
423 | -#: rss/feeds.py:13 | |
421 | +#: colab/rss/feeds.py:13 | |
424 | 422 | msgid "Latest Discussions" |
425 | 423 | msgstr "Últimas discussões" |
426 | 424 | |
427 | -#: rss/feeds.py:32 | |
425 | +#: colab/rss/feeds.py:32 | |
428 | 426 | msgid "Discussions Most Relevance" |
429 | 427 | msgstr "Discussões Mais Relevantes" |
430 | 428 | |
431 | -#: rss/feeds.py:51 | |
429 | +#: colab/rss/feeds.py:51 | |
432 | 430 | msgid "Latest collaborations" |
433 | 431 | msgstr "Últimas colaborações" |
434 | 432 | |
435 | -#: search/forms.py:16 search/templates/search/search.html:46 | |
436 | -#: templates/base.html:89 templates/header.html:29 | |
433 | +#: colab/search/forms.py:16 colab/search/templates/search/search.html:39 | |
434 | +#: colab/templates/header.html:27 | |
437 | 435 | msgid "Search" |
438 | 436 | msgstr "Busca" |
439 | 437 | |
440 | -#: search/forms.py:18 | |
438 | +#: colab/search/forms.py:18 | |
441 | 439 | msgid "Type" |
442 | 440 | msgstr "Tipo" |
443 | 441 | |
444 | -#: search/forms.py:19 search/views.py:20 | |
445 | -msgid "Author" | |
446 | -msgstr "Autor" | |
447 | - | |
448 | -#: search/forms.py:20 | |
449 | -msgid "Modified by" | |
450 | -msgstr "Modificado por" | |
451 | - | |
452 | -#: search/forms.py:22 | |
453 | -msgid "Status" | |
454 | -msgstr "Status" | |
455 | - | |
456 | -#: search/forms.py:26 search/views.py:23 | |
457 | -msgid "Mailinglist" | |
458 | -msgstr "Lista de discussão" | |
459 | - | |
460 | -#: search/forms.py:30 | |
461 | -msgid "Milestone" | |
462 | -msgstr "Marco" | |
463 | - | |
464 | -#: search/forms.py:31 | |
465 | -msgid "Priority" | |
466 | -msgstr "Prioridade" | |
467 | - | |
468 | -#: search/forms.py:32 | |
469 | -msgid "Component" | |
470 | -msgstr "Componente" | |
471 | - | |
472 | -#: search/forms.py:33 | |
473 | -msgid "Severity" | |
474 | -msgstr "Severidade" | |
475 | - | |
476 | -#: search/forms.py:34 | |
477 | -msgid "Reporter" | |
478 | -msgstr "Relator" | |
479 | - | |
480 | -#: search/forms.py:35 | |
481 | -msgid "Keywords" | |
482 | -msgstr "Palavras chave" | |
483 | - | |
484 | -#: search/forms.py:36 | |
485 | -msgid "Collaborators" | |
486 | -msgstr "Colaboradores" | |
487 | - | |
488 | -#: search/forms.py:37 | |
489 | -msgid "Repository" | |
490 | -msgstr "Repositório" | |
491 | - | |
492 | -#: search/forms.py:39 | |
493 | -msgid "Name" | |
494 | -msgstr "Nome" | |
495 | - | |
496 | -#: search/forms.py:40 | |
497 | -msgid "Institution" | |
498 | -msgstr "Instituição" | |
499 | - | |
500 | -#: search/forms.py:41 | |
501 | -msgid "Role" | |
502 | -msgstr "Papel" | |
503 | - | |
504 | -#: search/forms.py:42 search/templates/search/includes/search_filters.html:132 | |
505 | -#: search/templates/search/includes/search_filters.html:134 | |
506 | -#: search/templates/search/includes/search_filters.html:166 | |
507 | -#: search/templates/search/includes/search_filters.html:167 | |
508 | -msgid "Since" | |
509 | -msgstr "Desde" | |
510 | - | |
511 | -#: search/forms.py:43 search/templates/search/includes/search_filters.html:141 | |
512 | -#: search/templates/search/includes/search_filters.html:143 | |
513 | -#: search/templates/search/includes/search_filters.html:170 | |
514 | -#: search/templates/search/includes/search_filters.html:171 | |
442 | +#: colab/search/forms.py:20 | |
443 | +#: colab/search/templates/search/includes/search_filters.html:143 | |
444 | +#: colab/search/templates/search/includes/search_filters.html:145 | |
445 | +#: colab/search/templates/search/includes/search_filters.html:172 | |
446 | +#: colab/search/templates/search/includes/search_filters.html:173 | |
515 | 447 | msgid "Until" |
516 | 448 | msgstr "Até" |
517 | 449 | |
518 | -#: search/forms.py:44 | |
519 | -msgid "Filename" | |
520 | -msgstr "Nome do arquivo" | |
521 | - | |
522 | -#: search/forms.py:45 | |
523 | -msgid "Used by" | |
524 | -msgstr "Usado por" | |
525 | - | |
526 | -#: search/forms.py:46 | |
527 | -msgid "File type" | |
528 | -msgstr "Tipo do arquivo" | |
529 | - | |
530 | -#: search/forms.py:47 | |
531 | -msgid "Size" | |
532 | -msgstr "Tamanho" | |
533 | - | |
534 | -#: search/templates/search/includes/search_filters.html:5 | |
535 | -#: search/templates/search/includes/search_filters.html:33 | |
536 | -#: search/templates/search/includes/search_filters.html:51 | |
537 | -#: search/templates/search/includes/search_filters.html:69 | |
450 | +#: colab/search/templates/search/includes/search_filters.html:5 | |
451 | +#: colab/search/templates/search/includes/search_filters.html:33 | |
452 | +#: colab/search/templates/search/includes/search_filters.html:51 | |
453 | +#: colab/search/templates/search/includes/search_filters.html:69 | |
538 | 454 | msgid "Remove filter" |
539 | 455 | msgstr "Remover filtro" |
540 | 456 | |
541 | -#: search/templates/search/includes/search_filters.html:88 | |
542 | -#: search/templates/search/includes/search_filters.html:152 | |
543 | -#: search/templates/search/includes/search_filters.html:176 | |
457 | +#: colab/search/templates/search/includes/search_filters.html:88 | |
458 | +#: colab/search/templates/search/includes/search_filters.html:154 | |
459 | +#: colab/search/templates/search/includes/search_filters.html:178 | |
544 | 460 | msgid "Filter" |
545 | 461 | msgstr "Filtro" |
546 | 462 | |
547 | -#: search/templates/search/includes/search_filters.html:94 | |
463 | +#: colab/search/templates/search/includes/search_filters.html:94 | |
548 | 464 | msgid "Sort by" |
549 | 465 | msgstr "Ordenar por" |
550 | 466 | |
551 | -#: search/templates/search/includes/search_filters.html:111 | |
467 | +#: colab/search/templates/search/includes/search_filters.html:111 | |
552 | 468 | msgid "Types" |
553 | 469 | msgstr "Tipos" |
554 | 470 | |
555 | -#: search/templates/search/includes/search_filters.html:117 search/views.py:18 | |
556 | -msgid "Discussion" | |
557 | -msgstr "Discussão" | |
558 | - | |
559 | -#: search/templates/search/search.html:5 | |
471 | +#: colab/search/templates/search/search.html:5 | |
560 | 472 | msgid "search" |
561 | 473 | msgstr "busca" |
562 | 474 | |
563 | -#: search/templates/search/search.html:51 | |
475 | +#: colab/search/templates/search/search.html:44 | |
564 | 476 | msgid "documents found" |
565 | 477 | msgstr "documentos encontrados" |
566 | 478 | |
567 | -#: search/templates/search/search.html:62 | |
479 | +#: colab/search/templates/search/search.html:55 | |
568 | 480 | msgid "Search here" |
569 | 481 | msgstr "Pesquise aqui" |
570 | 482 | |
571 | -#: search/templates/search/search.html:74 | |
572 | -#: search/templates/search/search.html:84 | |
483 | +#: colab/search/templates/search/search.html:67 | |
573 | 484 | msgid "Filters" |
574 | 485 | msgstr "Filtros" |
575 | 486 | |
576 | -#: search/templates/search/search.html:105 | |
487 | +#: colab/search/templates/search/search.html:76 | |
577 | 488 | msgid "No results for your search." |
578 | 489 | msgstr "Não há resultados para sua busca." |
579 | 490 | |
580 | -#: search/templates/search/search.html:107 | |
491 | +#: colab/search/templates/search/search.html:78 | |
581 | 492 | msgid "You are searching for" |
582 | 493 | msgstr "Você está procurando por" |
583 | 494 | |
584 | -#: settings.py:113 | |
495 | +#: colab/settings.py:111 | |
585 | 496 | msgid "English" |
586 | 497 | msgstr "Inglês" |
587 | 498 | |
588 | -#: settings.py:114 | |
499 | +#: colab/settings.py:112 | |
589 | 500 | msgid "Portuguese" |
590 | 501 | msgstr "Português" |
591 | 502 | |
592 | -#: settings.py:115 | |
503 | +#: colab/settings.py:113 | |
593 | 504 | msgid "Spanish" |
594 | 505 | msgstr "Espanhol" |
595 | 506 | |
596 | -#: settings.py:138 | |
507 | +#: colab/settings.py:136 | |
597 | 508 | msgid "Recent activity" |
598 | 509 | msgstr "Atividade recente" |
599 | 510 | |
600 | -#: settings.py:142 | |
511 | +#: colab/settings.py:140 | |
601 | 512 | msgid "Relevance" |
602 | 513 | msgstr "Relevância" |
603 | 514 | |
604 | -#: settings.py:151 | |
515 | +#: colab/settings.py:149 | |
605 | 516 | msgid "Document" |
606 | 517 | msgstr "Documento" |
607 | 518 | |
608 | -#: settings.py:153 | |
519 | +#: colab/settings.py:151 | |
609 | 520 | msgid "Presentation" |
610 | 521 | msgstr "Apresentação" |
611 | 522 | |
612 | -#: settings.py:154 | |
523 | +#: colab/settings.py:152 | |
613 | 524 | msgid "Text" |
614 | 525 | msgstr "Texto" |
615 | 526 | |
616 | -#: settings.py:155 | |
527 | +#: colab/settings.py:153 | |
617 | 528 | msgid "Code" |
618 | 529 | msgstr "Código" |
619 | 530 | |
620 | -#: settings.py:157 | |
531 | +#: colab/settings.py:155 | |
621 | 532 | msgid "Compressed" |
622 | 533 | msgstr "Compactado" |
623 | 534 | |
624 | -#: settings.py:158 | |
535 | +#: colab/settings.py:156 | |
625 | 536 | msgid "Image" |
626 | 537 | msgstr "Imagem" |
627 | 538 | |
628 | -#: settings.py:160 | |
539 | +#: colab/settings.py:158 | |
629 | 540 | msgid "Spreadsheet" |
630 | 541 | msgstr "Planilha" |
631 | 542 | |
632 | -#: templates/404.html:5 | |
543 | +#: colab/templates/404.html:5 | |
633 | 544 | msgid "Not found. Keep searching! :)" |
634 | 545 | msgstr "Não encontrado. Continue procurando! :)" |
635 | 546 | |
636 | -#: templates/500.html:2 | |
547 | +#: colab/templates/500.html:2 | |
637 | 548 | msgid "Ooopz... something went wrong!" |
638 | 549 | msgstr "Ooopa... algo saiu errado!" |
639 | 550 | |
640 | -#: templates/base.html:83 templates/header.html:21 | |
641 | -msgid "Groups" | |
642 | -msgstr "Grupos" | |
643 | - | |
644 | -#: templates/base.html:119 templates/header.html:59 | |
645 | -msgid "My Profile" | |
646 | -msgstr "Meu Perfil" | |
647 | - | |
648 | -#: templates/base.html:120 templates/base.html.py:121 templates/header.html:60 | |
649 | -#: templates/header.html.py:61 | |
650 | -msgid "Logout" | |
651 | -msgstr "Sair" | |
652 | - | |
653 | -#: templates/base.html:132 templates/base.html.py:135 templates/header.html:72 | |
654 | -#: templates/header.html.py:75 | |
655 | -msgid "Search here..." | |
656 | -msgstr "Pesquise aqui..." | |
657 | - | |
658 | -#: templates/base.html:149 | |
551 | +#: colab/templates/base.html:74 | |
659 | 552 | msgid "The login has failed. Please, try again." |
660 | 553 | msgstr "O login falhou. Por favor, tente novamente." |
661 | 554 | |
662 | -#: templates/footer.html:6 | |
555 | +#: colab/templates/footer.html:6 | |
663 | 556 | msgid "Last email imported at" |
664 | 557 | msgstr "Último email importado em" |
665 | 558 | |
666 | -#: templates/footer.html:12 | |
559 | +#: colab/templates/footer.html:12 | |
667 | 560 | msgid "The contents of this site is published under license" |
668 | 561 | msgstr "O conteúdo deste site está publicado sob a licença" |
669 | 562 | |
670 | -#: templates/footer.html:15 | |
563 | +#: colab/templates/footer.html:15 | |
671 | 564 | msgid "Creative Commons 4.0 Brasil - Atribuir Fonte - Compartilhar Igual" |
672 | 565 | msgstr "Creative Commons 4.0 Brasil - Atribuir Fonte - Compartilhar Igual" |
673 | 566 | |
674 | -#: templates/header.html:26 | |
675 | -msgid "Paste" | |
676 | -msgstr "Cole aqui" | |
567 | +#: colab/templates/header.html:21 | |
568 | +msgid "Groups" | |
569 | +msgstr "Grupos" | |
570 | + | |
571 | +#: colab/templates/header.html:57 | |
572 | +#| msgid "My Profile" | |
573 | +msgid "My Profile" | |
574 | +msgstr "Meu Perfil" | |
575 | + | |
576 | +#: colab/templates/header.html:58 colab/templates/header.html.py:59 | |
577 | +msgid "Logout" | |
578 | +msgstr "Sair" | |
579 | + | |
580 | +#: colab/templates/header.html:70 colab/templates/header.html.py:73 | |
581 | +msgid "Search here..." | |
582 | +msgstr "Pesquise aqui..." | |
677 | 583 | |
678 | -#: templates/home.html:17 | |
584 | +#: colab/templates/home.html:17 | |
679 | 585 | msgid "Latest Collaborations" |
680 | 586 | msgstr "Últimas Colaborações" |
681 | 587 | |
682 | -#: templates/home.html:21 | |
588 | +#: colab/templates/home.html:21 | |
683 | 589 | msgid "RSS - Latest collaborations" |
684 | 590 | msgstr "RSS - Últimas Colaborações" |
685 | 591 | |
686 | -#: templates/home.html:30 | |
592 | +#: colab/templates/home.html:30 | |
687 | 593 | msgid "View more collaborations..." |
688 | 594 | msgstr "Ver mais colaborações..." |
689 | 595 | |
690 | -#: templates/home.html:37 | |
596 | +#: colab/templates/home.html:37 | |
691 | 597 | msgid "Collaboration Graph" |
692 | 598 | msgstr "Gráfico de Colaborações" |
693 | 599 | |
694 | -#: templates/home.html:48 | |
600 | +#: colab/templates/home.html:48 | |
695 | 601 | msgid "Most Relevant Threads" |
696 | 602 | msgstr "Discussões Mais Relevantes" |
697 | 603 | |
698 | -#: templates/home.html:52 | |
604 | +#: colab/templates/home.html:52 | |
699 | 605 | msgid "RSS - Most Relevant Threads" |
700 | 606 | msgstr "RSS - Discussões Mais Relevantes" |
701 | 607 | |
702 | -#: templates/home.html:60 templates/home.html.py:79 | |
608 | +#: colab/templates/home.html:60 colab/templates/home.html.py:79 | |
703 | 609 | msgid "View more discussions..." |
704 | 610 | msgstr "Ver mais discussões..." |
705 | 611 | |
706 | -#: templates/home.html:67 | |
612 | +#: colab/templates/home.html:67 | |
707 | 613 | msgid "Latest Threads" |
708 | 614 | msgstr "Últimas Discussões" |
709 | 615 | |
710 | -#: templates/home.html:71 | |
616 | +#: colab/templates/home.html:71 | |
711 | 617 | msgid "RSS - Latest Threads" |
712 | 618 | msgstr "RSS - Últimas Discussões" |
619 | + | |
620 | +#, fuzzy | |
621 | +#~| msgid "Gitlab Projects" | |
622 | +#~ msgid "Public Projects" | |
623 | +#~ msgstr "Projetos no Gitlab" | |
624 | + | |
625 | +#, fuzzy | |
626 | +#~| msgid "Gitlab Project" | |
627 | +#~ msgid "New Project" | |
628 | +#~ msgstr "Projeto no Gitlab" | |
629 | + | |
630 | +#, fuzzy | |
631 | +#~| msgid "Gitlab Projects" | |
632 | +#~ msgid "Projects" | |
633 | +#~ msgstr "Projetos no Gitlab" | |
634 | + | |
635 | +#, fuzzy | |
636 | +#~| msgid "Gitlab Issues" | |
637 | +#~ msgid "Issues" | |
638 | +#~ msgstr "Tíquetes no Gitlab" | |
639 | + | |
640 | +#, fuzzy | |
641 | +#~| msgid "Gitlab Merge Requests" | |
642 | +#~ msgid "Merge Requests" | |
643 | +#~ msgstr "Envio de Contribuições no Gitlab" | |
644 | + | |
645 | +#~ msgid "" | |
646 | +#~ "Please enter a correct %(username)s and password. Note that both fields " | |
647 | +#~ "may be case-sensitive." | |
648 | +#~ msgstr "" | |
649 | +#~ "Por favor entre com um %(username)s e senha. Note que ambos os campos " | |
650 | +#~ "diferenciam letras maiúsculas de minúsculas" | |
651 | + | |
652 | +#~ msgid "This account is inactive." | |
653 | +#~ msgstr "Esta conta está inativa." | |
654 | + | |
655 | +#~ msgid "Email" | |
656 | +#~ msgstr "Email" | |
657 | + | |
658 | +#~ msgid "New password" | |
659 | +#~ msgstr "Nova senha" | |
660 | + | |
661 | +#~ msgid "New password confirmation" | |
662 | +#~ msgstr "Confirmar nova senha" | |
663 | + | |
664 | +#~ msgid "Your old password was entered incorrectly. Please enter it again." | |
665 | +#~ msgstr "Sua senha atual está incorreta. Por favor tente novamente." | |
666 | + | |
667 | +#~ msgid "Old password" | |
668 | +#~ msgstr "Senha atual" | |
669 | + | |
670 | +#~ msgid "Password (again)" | |
671 | +#~ msgstr "Senha (novamente)" | |
672 | + | |
673 | +#~ msgid "Enter a valid username." | |
674 | +#~ msgstr "Insira um nome de usuário válido." | |
675 | + | |
676 | +#~ msgid "Gitlab Group" | |
677 | +#~ msgstr "Grupo no Gitlab" | |
678 | + | |
679 | +#~ msgid "Gitlab Groups" | |
680 | +#~ msgstr "Grupos no Gitlab" | |
681 | + | |
682 | +#~ msgid "Gitlab Merge Request" | |
683 | +#~ msgstr "Envio de Contribuição no Gitlab" | |
684 | + | |
685 | +#~ msgid "Gitlab Issue" | |
686 | +#~ msgstr "Tíquete no Gitlab" | |
687 | + | |
688 | +#~ msgid "Gitlab Comments" | |
689 | +#~ msgstr "Comentários no Gitlab" | |
690 | + | |
691 | +#~ msgid "Community" | |
692 | +#~ msgstr "Comunidade" | |
693 | + | |
694 | +#~ msgid "Article" | |
695 | +#~ msgstr "Artigo" | |
696 | + | |
697 | +#~ msgid "Articles" | |
698 | +#~ msgstr "Artigos" | |
699 | + | |
700 | +#~ msgid "Author" | |
701 | +#~ msgstr "Autor" | |
702 | + | |
703 | +#~ msgid "Modified by" | |
704 | +#~ msgstr "Modificado por" | |
705 | + | |
706 | +#~ msgid "Status" | |
707 | +#~ msgstr "Status" | |
708 | + | |
709 | +#~ msgid "Mailinglist" | |
710 | +#~ msgstr "Lista de discussão" | |
711 | + | |
712 | +#~ msgid "Milestone" | |
713 | +#~ msgstr "Marco" | |
714 | + | |
715 | +#~ msgid "Priority" | |
716 | +#~ msgstr "Prioridade" | |
717 | + | |
718 | +#~ msgid "Component" | |
719 | +#~ msgstr "Componente" | |
720 | + | |
721 | +#~ msgid "Severity" | |
722 | +#~ msgstr "Severidade" | |
723 | + | |
724 | +#~ msgid "Reporter" | |
725 | +#~ msgstr "Relator" | |
726 | + | |
727 | +#~ msgid "Keywords" | |
728 | +#~ msgstr "Palavras chave" | |
729 | + | |
730 | +#~ msgid "Collaborators" | |
731 | +#~ msgstr "Colaboradores" | |
732 | + | |
733 | +#~ msgid "Repository" | |
734 | +#~ msgstr "Repositório" | |
735 | + | |
736 | +#~ msgid "Used by" | |
737 | +#~ msgstr "Usado por" | |
738 | + | |
739 | +#~ msgid "File type" | |
740 | +#~ msgstr "Tipo do arquivo" | |
741 | + | |
742 | +#~ msgid "Size" | |
743 | +#~ msgstr "Tamanho" | |
744 | + | |
745 | +#~ msgid "Discussion" | |
746 | +#~ msgstr "Discussão" | |
747 | + | |
748 | +#~ msgid "Paste" | |
749 | +#~ msgstr "Cole aqui" | ... | ... |
... | ... | @@ -0,0 +1,25 @@ |
1 | +# -*- coding: utf-8 -*- | |
2 | +from __future__ import unicode_literals | |
3 | + | |
4 | +from django.db import models, migrations | |
5 | +import datetime | |
6 | + | |
7 | + | |
8 | +class Migration(migrations.Migration): | |
9 | + | |
10 | + dependencies = [ | |
11 | + ] | |
12 | + | |
13 | + operations = [ | |
14 | + migrations.CreateModel( | |
15 | + name='TimeStampPlugin', | |
16 | + fields=[ | |
17 | + ('id', models.IntegerField(serialize=False, primary_key=True)), | |
18 | + ('name', models.CharField(unique=True, max_length=255)), | |
19 | + ('timestamp', models.DateTimeField(default=datetime.datetime(1, 1, 1, 0, 0), blank=True)), | |
20 | + ], | |
21 | + options={ | |
22 | + }, | |
23 | + bases=(models.Model,), | |
24 | + ), | |
25 | + ] | ... | ... |
... | ... | @@ -0,0 +1,29 @@ |
1 | +from django.db import models | |
2 | +from django.utils import timezone | |
3 | + | |
4 | + | |
5 | +class TimeStampPlugin(models.Model): | |
6 | + ''' | |
7 | + Class used to store timestamps from plugins | |
8 | + ''' | |
9 | + id = models.IntegerField(primary_key=True) | |
10 | + name = models.CharField(max_length=255, unique=True, null=False) | |
11 | + timestamp = models.DateTimeField(default=timezone.datetime.min, blank=True) | |
12 | + | |
13 | + @classmethod | |
14 | + def update_timestamp(cls, class_name, **kwargs): | |
15 | + instance = TimeStampPlugin.objects.get_or_create(name=class_name)[0] | |
16 | + last_updated = kwargs.get('last_updated', '') | |
17 | + | |
18 | + if last_updated: | |
19 | + format = "%Y/%m/%d %H:%M:%S" | |
20 | + instance.timestamp = timezone.datetime.strptime(last_updated, | |
21 | + format) | |
22 | + else: | |
23 | + instance.timestamp = timezone.datetime.now() | |
24 | + instance.save() | |
25 | + | |
26 | + @classmethod | |
27 | + def get_last_updated(cls, class_name): | |
28 | + instance = TimeStampPlugin.objects.get_or_create(name=class_name)[0] | |
29 | + return instance.timestamp | ... | ... |
... | ... | @@ -0,0 +1,135 @@ |
1 | +from mock import Mock | |
2 | + | |
3 | +from django.test import TestCase, Client | |
4 | +from django.core.cache import cache | |
5 | + | |
6 | +from colab.plugins.templatetags import plugins | |
7 | +from colab.accounts.models import User | |
8 | + | |
9 | + | |
10 | +class PluginsMenuTest(TestCase): | |
11 | + | |
12 | + def setUp(self): | |
13 | + self.user = self.create_user() | |
14 | + self.client = Client() | |
15 | + cache.clear() | |
16 | + | |
17 | + def tearDown(self): | |
18 | + cache.clear() | |
19 | + self.client.logout() | |
20 | + | |
21 | + def create_user(self): | |
22 | + user = User() | |
23 | + user.username = "USERtestCoLaB" | |
24 | + user.set_password("123colab4") | |
25 | + user.email = "usertest@colab.com.br" | |
26 | + user.id = 1 | |
27 | + user.first_name = "USERtestCoLaB" | |
28 | + user.last_name = "COLAB" | |
29 | + user.save() | |
30 | + | |
31 | + return user | |
32 | + | |
33 | + def authenticate_user(self): | |
34 | + self.client.login(username=self.user.username, | |
35 | + password="123colab4") | |
36 | + | |
37 | + def test_plugins_menu_without_menu_urls(self): | |
38 | + self.authenticate_user() | |
39 | + plugin_1 = {'menu_title': 'myTitle', 'menu_urls': []} | |
40 | + | |
41 | + test_context = {'user': self.user, | |
42 | + 'plugins': {'plugin_1': plugin_1}} | |
43 | + | |
44 | + menu = plugins.plugins_menu(test_context) | |
45 | + | |
46 | + self.assertEquals(menu.strip(), "") | |
47 | + | |
48 | + def test_plugins_menu_with_1_menu_urls(self): | |
49 | + self.authenticate_user() | |
50 | + link = 'http://url' | |
51 | + title = 'myTitle' | |
52 | + plugin_1 = {'menu_title': title, | |
53 | + 'menu_urls': [{'url': link, 'display': 'LRU'}]} | |
54 | + | |
55 | + test_context = {'user': self.user, | |
56 | + 'plugins': {'plugin_1': plugin_1}} | |
57 | + | |
58 | + menu = plugins.plugins_menu(test_context) | |
59 | + | |
60 | + self.assertIn(link, menu) | |
61 | + self.assertIn(title, menu) | |
62 | + | |
63 | + def test_plugins_menu_with_many_menu_urls(self): | |
64 | + self.authenticate_user() | |
65 | + | |
66 | + link1 = 'http://url1' | |
67 | + title1 = 'myTitle1' | |
68 | + display1 = 'LRU1' | |
69 | + link2 = 'http://url2' | |
70 | + display2 = 'LRU2' | |
71 | + | |
72 | + plugin_1 = {'menu_title': title1, | |
73 | + 'menu_urls': [{'url': link1, 'display': display1}, | |
74 | + {'url': link2, 'display': display2}]} | |
75 | + | |
76 | + test_context = {'user': self.user, | |
77 | + 'plugins': {'plugin_1': plugin_1}} | |
78 | + | |
79 | + menu = plugins.plugins_menu(test_context) | |
80 | + | |
81 | + self.assertIn(link1, menu) | |
82 | + self.assertIn(title1, menu) | |
83 | + self.assertIn(display1, menu) | |
84 | + self.assertIn(link2, menu) | |
85 | + self.assertIn(display2, menu) | |
86 | + | |
87 | + def test_plugins_menu_with_multiple_plugins(self): | |
88 | + self.authenticate_user() | |
89 | + | |
90 | + link1 = 'http://url1' | |
91 | + title1 = 'myTitle1' | |
92 | + display1 = 'LRU1' | |
93 | + link2 = 'http://url2' | |
94 | + display2 = 'LRU2' | |
95 | + | |
96 | + plugin_1 = {'menu_title': title1, | |
97 | + 'menu_urls': [{'url': link1, 'display': display1}, | |
98 | + {'url': link2, 'display': display2}]} | |
99 | + | |
100 | + title2 = 'myTitle2' | |
101 | + plugin_2 = {'menu_title': title2, | |
102 | + 'menu_urls': []} | |
103 | + | |
104 | + test_context = {'user': self.user, | |
105 | + 'plugins': {'plugin_1': plugin_1, | |
106 | + 'plugin_2': plugin_2}} | |
107 | + | |
108 | + menu = plugins.plugins_menu(test_context) | |
109 | + | |
110 | + self.assertIn(link1, menu) | |
111 | + self.assertIn(title1, menu) | |
112 | + self.assertIn(display1, menu) | |
113 | + self.assertIn(link2, menu) | |
114 | + self.assertIn(display2, menu) | |
115 | + self.assertNotIn(title2, menu) | |
116 | + | |
117 | + class ColabUrlMock(Mock): | |
118 | + def auth(self): | |
119 | + return True | |
120 | + | |
121 | + def test_plugins_menu_with_inactivate_user(self): | |
122 | + self.user.is_active = False | |
123 | + self.user.save() | |
124 | + | |
125 | + self.authenticate_user() | |
126 | + title = 'myTitle' | |
127 | + plugin_1 = {'menu_title': title, | |
128 | + 'menu_urls': [self.ColabUrlMock()]} | |
129 | + | |
130 | + test_context = {'user': self.user, | |
131 | + 'plugins': {'plugin_1': plugin_1}} | |
132 | + | |
133 | + menu = plugins.plugins_menu(test_context) | |
134 | + | |
135 | + self.assertEquals("", menu.strip()) | ... | ... |
... | ... | @@ -0,0 +1,32 @@ |
1 | +import mock | |
2 | + | |
3 | +from django.test import TestCase | |
4 | +from django.utils import timezone | |
5 | +from colab.plugins.models import TimeStampPlugin | |
6 | + | |
7 | + | |
8 | +class UserTest(TestCase): | |
9 | + | |
10 | + def test_update_timestamp_without_last_updated(self): | |
11 | + result = timezone.datetime(2009, 1, 1).replace(tzinfo=timezone.utc) | |
12 | + with mock.patch.object(timezone, 'datetime', | |
13 | + mock.Mock(wraps=timezone.datetime)) as mock_: | |
14 | + mock_.now.return_value = result | |
15 | + TimeStampPlugin.get_last_updated('TestPluginUpdate') | |
16 | + TimeStampPlugin.update_timestamp('TestPluginUpdate') | |
17 | + timestamp = TimeStampPlugin.get_last_updated('TestPluginUpdate') | |
18 | + self.assertEquals(result, timestamp) | |
19 | + | |
20 | + def test_update_timestamp_with_last_updated(self): | |
21 | + TimeStampPlugin.get_last_updated('TestPluginUpdate') | |
22 | + date = '2015/12/23 00:00:00' | |
23 | + TimeStampPlugin.update_timestamp('TestPluginUpdate', last_updated=date) | |
24 | + | |
25 | + timestamp = TimeStampPlugin.get_last_updated('TestPluginUpdate') | |
26 | + result = timezone.datetime.strptime(date, "%Y/%m/%d %H:%M:%S")\ | |
27 | + .replace(tzinfo=timezone.utc) | |
28 | + self.assertEquals(timestamp, result) | |
29 | + | |
30 | + def test_first_get_last_update(self): | |
31 | + timestamp = TimeStampPlugin.get_last_updated('Test') | |
32 | + self.assertEqual(timezone.datetime.min, timestamp) | ... | ... |
... | ... | @@ -0,0 +1,22 @@ |
1 | +from django.test import TestCase | |
2 | +from django.test.client import RequestFactory | |
3 | + | |
4 | +from ..views import ColabProxyView | |
5 | +from colab.accounts.models import User | |
6 | + | |
7 | + | |
8 | +class ViewsTest(TestCase): | |
9 | + | |
10 | + def setUp(self): | |
11 | + self.view = ColabProxyView() | |
12 | + self.factory = RequestFactory() | |
13 | + self.user = User.objects.create_user( | |
14 | + username='john', email='john@test.org', password='123', | |
15 | + first_name='John', last_name='John') | |
16 | + | |
17 | + def test_dispatch_without_app_label(self): | |
18 | + request = self.factory.get('/') | |
19 | + request.user = self.user | |
20 | + | |
21 | + with self.assertRaises(NotImplementedError): | |
22 | + self.view.dispatch(request, '/') | ... | ... |
colab/plugins/utils/apps.py
1 | 1 | |
2 | 2 | from django.apps import AppConfig |
3 | +from ..conf import get_plugin_config | |
3 | 4 | |
4 | 5 | |
5 | 6 | class ColabPluginAppConfig(AppConfig): |
6 | 7 | colab_proxied_app = True |
8 | + namespace = None | |
7 | 9 | |
8 | - def register_signals(self): | |
10 | + def __init__(self, app_name, app_module): | |
11 | + super(ColabPluginAppConfig, self).__init__(app_name, app_module) | |
12 | + self.set_namespace() | |
13 | + | |
14 | + def set_namespace(self): | |
15 | + config = get_plugin_config(self.name) | |
16 | + config['urls']['namespace'] = self.namespace | |
17 | + | |
18 | + def register_signal(self): | |
9 | 19 | pass |
10 | 20 | |
11 | - def connect_signals(self): | |
21 | + def connect_signal(self): | |
12 | 22 | pass | ... | ... |
... | ... | @@ -0,0 +1,22 @@ |
1 | +#!/usr/bin/env python | |
2 | + | |
3 | +import importlib | |
4 | + | |
5 | +from django.conf import settings | |
6 | + | |
7 | + | |
8 | +def import_plugin_filters(request): | |
9 | + plugin_filters = {} | |
10 | + for app_name in settings.INSTALLED_APPS: | |
11 | + | |
12 | + module_name = '{}.filters'.format(app_name) | |
13 | + try: | |
14 | + module = importlib.import_module(module_name) | |
15 | + except ImportError: | |
16 | + continue | |
17 | + | |
18 | + get_filters = getattr(module, 'get_filters', None) | |
19 | + if get_filters: | |
20 | + plugin_filters.update(get_filters(request)) | |
21 | + | |
22 | + return plugin_filters | ... | ... |
... | ... | @@ -0,0 +1,23 @@ |
1 | +from mock import patch | |
2 | + | |
3 | +from django.test import TestCase | |
4 | +from django.apps import AppConfig | |
5 | + | |
6 | +from colab.plugins.utils.apps import ColabPluginAppConfig | |
7 | + | |
8 | + | |
9 | +class AppsTest(TestCase): | |
10 | + | |
11 | + @patch.object(AppConfig, '_path_from_module') | |
12 | + @patch('colab.plugins.utils.apps.get_plugin_config') | |
13 | + def test_set_namespace(self, get_plugin_config_mock, | |
14 | + path_from_module_mock): | |
15 | + path_from_module_mock.return_value = "/fake/path" | |
16 | + | |
17 | + get_plugin_config_mock.return_value = {'urls': {}} | |
18 | + conf = get_plugin_config_mock() | |
19 | + | |
20 | + ColabPluginAppConfig("test", "test_app") | |
21 | + | |
22 | + self.assertIn('namespace', conf['urls']) | |
23 | + self.assertEquals(None, conf['urls']['namespace']) | ... | ... |
colab/search/base_indexes.py
... | ... | @@ -76,13 +76,7 @@ class BaseIndex(indexes.SearchIndex): |
76 | 76 | ) |
77 | 77 | |
78 | 78 | def prepare_modified_by(self, obj): |
79 | - if hasattr(obj, 'modified_by'): | |
80 | - modified_by = obj.get_modified_by() | |
81 | - if modified_by: | |
82 | - return modified_by.get_full_name() | |
83 | - if self.author_obj: | |
84 | - return self.author_obj.get_full_name() | |
85 | - return obj.author | |
79 | + return self.prepare_fullname(obj) | |
86 | 80 | |
87 | 81 | def prepare_modified_by_url(self, obj): |
88 | 82 | if hasattr(obj, 'modified_by'): | ... | ... |
colab/search/forms.py
... | ... | @@ -9,42 +9,25 @@ from haystack.forms import SearchForm |
9 | 9 | from haystack.inputs import AltParser |
10 | 10 | from haystack.inputs import AutoQuery |
11 | 11 | |
12 | -from colab.super_archives.models import MailingList | |
12 | +from colab.plugins.utils import filters_importer | |
13 | 13 | |
14 | 14 | |
15 | 15 | class ColabSearchForm(SearchForm): |
16 | 16 | q = forms.CharField(label=_('Search'), required=False) |
17 | 17 | order = forms.CharField(widget=forms.HiddenInput(), required=False) |
18 | 18 | type = forms.CharField(required=False, label=_(u'Type')) |
19 | - author = forms.CharField(required=False, label=_(u'Author')) | |
20 | - modified_by = forms.CharField(required=False, label=_(u'Modified by')) | |
21 | - # ticket status | |
22 | - tag = forms.CharField(required=False, label=_(u'Status')) | |
23 | - # mailinglist tag | |
24 | - list = forms.MultipleChoiceField( | |
25 | - required=False, | |
26 | - label=_(u'Mailinglist'), | |
27 | - choices=[(v, v) for v in MailingList.objects.values_list( | |
28 | - 'name', flat=True)] | |
29 | - ) | |
30 | - milestone = forms.CharField(required=False, label=_(u'Milestone')) | |
31 | - priority = forms.CharField(required=False, label=_(u'Priority')) | |
32 | - component = forms.CharField(required=False, label=_(u'Component')) | |
33 | - severity = forms.CharField(required=False, label=_(u'Severity')) | |
34 | - reporter = forms.CharField(required=False, label=_(u'Reporter')) | |
35 | - keywords = forms.CharField(required=False, label=_(u'Keywords')) | |
36 | - collaborators = forms.CharField(required=False, label=_(u'Collaborators')) | |
37 | - repository_name = forms.CharField(required=False, label=_(u'Repository')) | |
38 | - username = forms.CharField(required=False, label=_(u'Username')) | |
39 | - name = forms.CharField(required=False, label=_(u'Name')) | |
40 | - institution = forms.CharField(required=False, label=_(u'Institution')) | |
41 | - role = forms.CharField(required=False, label=_(u'Role')) | |
42 | 19 | since = forms.DateField(required=False, label=_(u'Since')) |
43 | 20 | until = forms.DateField(required=False, label=_(u'Until')) |
44 | - filename = forms.CharField(required=False, label=_(u'Filename')) | |
45 | - used_by = forms.CharField(required=False, label=_(u'Used by')) | |
46 | - mimetype = forms.CharField(required=False, label=_(u'File type')) | |
47 | - size = forms.CharField(required=False, label=_(u'Size')) | |
21 | + | |
22 | + excluded_fields = [] | |
23 | + | |
24 | + def __init__(self, *args, **kwargs): | |
25 | + super(ColabSearchForm, self).__init__(*args, **kwargs) | |
26 | + extra = filters_importer.import_plugin_filters({}) | |
27 | + for filter_types in extra.values(): | |
28 | + for field in filter_types['fields']: | |
29 | + self.fields[field[0]] = forms.CharField(required=False, | |
30 | + label=field[1]) | |
48 | 31 | |
49 | 32 | def search(self): |
50 | 33 | if not self.is_valid(): |
... | ... | @@ -52,46 +35,22 @@ class ColabSearchForm(SearchForm): |
52 | 35 | |
53 | 36 | # filter_or goes here |
54 | 37 | sqs = self.searchqueryset.all() |
55 | - mimetype = self.cleaned_data['mimetype'] | |
56 | - if mimetype: | |
57 | - filter_mimetypes = {'mimetype__in': []} | |
58 | - for type_, display, mimelist in settings.FILE_TYPE_GROUPINGS: | |
59 | - if type_ in mimetype: | |
60 | - filter_mimetypes['mimetype__in'] += mimelist | |
61 | - if not self.cleaned_data['size']: | |
62 | - sqs = sqs.filter_or(mimetype__in=mimelist) | |
63 | - | |
64 | - if self.cleaned_data['size']: | |
65 | - # (1024 * 1024) / 2 | |
66 | - # (1024 * 1024) * 10 | |
67 | - filter_sizes = {} | |
68 | - filter_sizes_exp = {} | |
69 | - if '<500KB' in self.cleaned_data['size']: | |
70 | - filter_sizes['size__lt'] = 524288 | |
71 | - if '500KB__10MB' in self.cleaned_data['size']: | |
72 | - filter_sizes_exp['size__gte'] = 524288 | |
73 | - filter_sizes_exp['size__lte'] = 10485760 | |
74 | - if '>10MB' in self.cleaned_data['size']: | |
75 | - filter_sizes['size__gt'] = 10485760 | |
76 | - | |
77 | - if self.cleaned_data['mimetype']: | |
78 | - # Add the mimetypes filters to this dict and filter it | |
79 | - if filter_sizes_exp: | |
80 | - filter_sizes_exp.update(filter_mimetypes) | |
81 | - sqs = sqs.filter_or(**filter_sizes_exp) | |
82 | - for filter_or in filter_sizes.items(): | |
83 | - filter_or = dict((filter_or, )) | |
84 | - filter_or.update(filter_mimetypes) | |
85 | - sqs = sqs.filter_or(**filter_or) | |
86 | - else: | |
87 | - for filter_or in filter_sizes.items(): | |
88 | - filter_or = dict((filter_or, )) | |
89 | - sqs = sqs.filter_or(**filter_or) | |
90 | - sqs = sqs.filter_or(**filter_sizes_exp) | |
91 | 38 | |
92 | - if self.cleaned_data['used_by']: | |
93 | - sqs = sqs.filter_or(used_by__in=self.cleaned_data['used_by'] | |
94 | - .split()) | |
39 | + kwargs = {} | |
40 | + | |
41 | + self.excluded_fields.extend(['q', 'type', 'since', 'until', 'order']) | |
42 | + | |
43 | + if self.cleaned_data['type']: | |
44 | + all_types = self.cleaned_data['type'].split(' ') | |
45 | + sqs = sqs.filter_or(type__in=all_types) | |
46 | + | |
47 | + for key in self.fields.keys(): | |
48 | + value = self.cleaned_data[key] | |
49 | + if value and key not in self.excluded_fields: | |
50 | + print key, '-' + value | |
51 | + kwargs[key] = self.cleaned_data[key] | |
52 | + | |
53 | + sqs = sqs.filter(**kwargs) | |
95 | 54 | |
96 | 55 | if self.cleaned_data['q']: |
97 | 56 | q = unicodedata.normalize( |
... | ... | @@ -115,62 +74,15 @@ class ColabSearchForm(SearchForm): |
115 | 74 | else: |
116 | 75 | sqs = sqs.filter(content=AutoQuery(q)) |
117 | 76 | |
118 | - if self.cleaned_data['type']: | |
119 | - sqs = sqs.filter(type=self.cleaned_data['type']) | |
120 | - | |
121 | 77 | if self.cleaned_data['order']: |
122 | 78 | for option, dict_order in settings.ORDERING_DATA.items(): |
123 | 79 | if self.cleaned_data['order'] == option: |
124 | 80 | if dict_order['fields']: |
125 | 81 | sqs = sqs.order_by(*dict_order['fields']) |
126 | 82 | |
127 | - if self.cleaned_data['author']: | |
128 | - sqs = sqs.filter( | |
129 | - fullname_and_username__contains=self.cleaned_data['author'] | |
130 | - ) | |
131 | - | |
132 | - if self.cleaned_data['modified_by']: | |
133 | - modified_by_data = self.cleaned_date['modified_by'] | |
134 | - sqs = sqs.filter( | |
135 | - fullname_and_username__contains=modified_by_data | |
136 | - ) | |
137 | - | |
138 | - if self.cleaned_data['milestone']: | |
139 | - sqs = sqs.filter(milestone=self.cleaned_data['milestone']) | |
140 | - if self.cleaned_data['priority']: | |
141 | - sqs = sqs.filter(priority=self.cleaned_data['priority']) | |
142 | - if self.cleaned_data['severity']: | |
143 | - sqs = sqs.filter(severity=self.cleaned_data['severity']) | |
144 | - if self.cleaned_data['reporter']: | |
145 | - sqs = sqs.filter(reporter=self.cleaned_data['reporter']) | |
146 | - if self.cleaned_data['keywords']: | |
147 | - sqs = sqs.filter(keywords=self.cleaned_data['keywords']) | |
148 | - if self.cleaned_data['collaborators']: | |
149 | - sqs = sqs.filter(collaborators=self.cleaned_data['collaborators']) | |
150 | - if self.cleaned_data['repository_name']: | |
151 | - sqs = sqs.filter( | |
152 | - repository_name=self.cleaned_data['repository_name'] | |
153 | - ) | |
154 | - if self.cleaned_data['username']: | |
155 | - sqs = sqs.filter(username=self.cleaned_data['username']) | |
156 | - if self.cleaned_data['name']: | |
157 | - sqs = sqs.filter(name=self.cleaned_data['name']) | |
158 | - if self.cleaned_data['institution']: | |
159 | - sqs = sqs.filter(institution=self.cleaned_data['institution']) | |
160 | - if self.cleaned_data['role']: | |
161 | - sqs = sqs.filter(role=self.cleaned_data['role']) | |
162 | - if self.cleaned_data['tag']: | |
163 | - sqs = sqs.filter(tag=self.cleaned_data['tag']) | |
164 | - | |
165 | - if self.cleaned_data['list']: | |
166 | - sqs = sqs.filter(tag__in=self.cleaned_data['list']) | |
167 | - | |
168 | 83 | if self.cleaned_data['since']: |
169 | 84 | sqs = sqs.filter(modified__gte=self.cleaned_data['since']) |
170 | 85 | if self.cleaned_data['until']: |
171 | 86 | sqs = sqs.filter(modified__lte=self.cleaned_data['until']) |
172 | 87 | |
173 | - if self.cleaned_data['filename']: | |
174 | - sqs = sqs.filter(filename=self.cleaned_data['filename']) | |
175 | - | |
176 | 88 | return sqs | ... | ... |
colab/search/templates/search/includes/search_filters.html
... | ... | @@ -16,12 +16,12 @@ |
16 | 16 | <input type="hidden" name="since" value="{{ request.GET.since }}" /> |
17 | 17 | <input type="hidden" name="until" value="{{ request.GET.until }}" /> |
18 | 18 | |
19 | - {% for field_lookup, field_display, field_value in filters.fields %} | |
19 | + {% for field_lookup, field_display, field_value, field_type, field_choices in filters.fields %} | |
20 | 20 | <div class="form-group"> |
21 | 21 | <label for="{{ field_lookup }}">{{ field_display }}</label> |
22 | - {% if field_lookup == "list" %} | |
22 | + {% if field_type == "list" %} | |
23 | 23 | <select name="{{ field_lookup }}" class="form-control" multiple> |
24 | - {% for value, option in form.fields.list.choices %} | |
24 | + {% for value, option in field_choices %} | |
25 | 25 | <option value="{{ value }}" {% if value in field_value %}selected{% endif %}>{{ option }}</option> |
26 | 26 | {% endfor %} |
27 | 27 | </select> |
... | ... | @@ -112,10 +112,12 @@ |
112 | 112 | |
113 | 113 | <ul class="unstyled-list"> |
114 | 114 | |
115 | - <li> | |
116 | - <span class="glyphicon glyphicon-envelope"></span> | |
117 | - <a href="{% append_to_get type='thread' %}">{% trans "Discussion" %}</a> | |
118 | - </li> | |
115 | + {% for type, name, icon in filters_options %} | |
116 | + <li> | |
117 | + <span class="glyphicon glyphicon-{{icon}}"></span> | |
118 | + <a href="{% append_to_get type=type %}">{% trans name %}</a> | |
119 | + </li> | |
120 | + {% endfor %} | |
119 | 121 | </ul> |
120 | 122 | {% endif %} |
121 | 123 | <hr /> | ... | ... |
colab/search/templates/search/search.html
... | ... | @@ -25,13 +25,6 @@ We must use STATIC_URL because we have a language composing the URL |
25 | 25 | {% endif %} |
26 | 26 | }); |
27 | 27 | |
28 | - $('#collapseFilters').on('hide.bs.collapse', function() { | |
29 | - $('.collapse-icon-controller').toggleClass('glyphicon-collapse-down glyphicon-collapse-up'); | |
30 | - }); | |
31 | - $('#collapseFilters').on('show.bs.collapse', function() { | |
32 | - $('.collapse-icon-controller').toggleClass('glyphicon-collapse-down glyphicon-collapse-up'); | |
33 | - }); | |
34 | - | |
35 | 28 | }); |
36 | 29 | </script> |
37 | 30 | {% endblock %} |
... | ... | @@ -74,28 +67,6 @@ We must use STATIC_URL because we have a language composing the URL |
74 | 67 | <h3>{% trans "Filters" %}</h3> |
75 | 68 | {% include "search/includes/search_filters.html" %} |
76 | 69 | </div> |
77 | - | |
78 | - <div class="col-xs-12 col-sm-12 hidden-md hidden-lg"> | |
79 | - <div class="panel-group" id="accordion"> | |
80 | - <div class="panel panel-default"> | |
81 | - <div class="panel-heading subject"> | |
82 | - <a data-toggle="collapse" data-parent="#accordion" href="#collapseFilters"> | |
83 | - <h4 class="panel-title"> | |
84 | - {% trans "Filters" %} | |
85 | - <span class="glyphicon glyphicon-collapse-down pull-right collapse-icon-controller"></span> | |
86 | - </h4> | |
87 | - </a> | |
88 | - </div> | |
89 | - <div id="collapseFilters" class="panel-collapse collapse"> | |
90 | - <div class="panel-body"> | |
91 | - {% include "search/includes/search_filters.html" %} | |
92 | - </div> | |
93 | - </div> | |
94 | - </div> | |
95 | - </div> | |
96 | - <hr /> | |
97 | - </div> | |
98 | - | |
99 | 70 | <div class="col-md-10 col-lg-10"> |
100 | 71 | <ul class="list-unstyled"> |
101 | 72 | {% for result in page.object_list %} | ... | ... |
... | ... | @@ -0,0 +1,18 @@ |
1 | +from django import template | |
2 | + | |
3 | + | |
4 | +register = template.Library() | |
5 | + | |
6 | + | |
7 | +@register.assignment_tag | |
8 | +def get_search_preview_templates(model_indexed): | |
9 | + app_type = model_indexed.type | |
10 | + | |
11 | + return "search/{}_search_preview.html".format(app_type) | |
12 | + | |
13 | + | |
14 | +@register.assignment_tag | |
15 | +def get_dashboard_search_preview_templates(model_indexed): | |
16 | + app_type = model_indexed.type | |
17 | + | |
18 | + return "dashboard/{}_search_preview.html".format(app_type) | ... | ... |
colab/search/tests.py
... | ... | @@ -1,59 +0,0 @@ |
1 | -# -*- coding:utf-8 -*- | |
2 | - | |
3 | -from django.test import TestCase, Client | |
4 | -from django.core.management import call_command | |
5 | - | |
6 | - | |
7 | -class SearchViewTest(TestCase): | |
8 | - | |
9 | - fixtures = ['test_data.json'] | |
10 | - | |
11 | - def setUp(self): | |
12 | - call_command('rebuild_index', interactive=False, verbosity=0) | |
13 | - self.client = Client() | |
14 | - | |
15 | - def tearDown(self): | |
16 | - call_command('clear_index', interactive=False, verbosity=0) | |
17 | - | |
18 | - def test_search_thread(self): | |
19 | - request = self.client.get('/search/?q=thread') | |
20 | - thread_list = request.context['page'].object_list | |
21 | - | |
22 | - self.assertEqual(3, len(thread_list)) | |
23 | - | |
24 | - condition = any('This is a repply to Thread 1 on list A' in | |
25 | - t.description for t in thread_list) | |
26 | - self.assertTrue(condition) | |
27 | - condition = any('This is a repply to Thread 1 on list B' in | |
28 | - t.description for t in thread_list) | |
29 | - self.assertTrue(condition) | |
30 | - condition = any('This is a repply to Thread 1 on list C' in | |
31 | - t.description for t in thread_list) | |
32 | - self.assertTrue(condition) | |
33 | - | |
34 | - def test_search_account_by_firstName(self): | |
35 | - request = self.client.get('/search/?q=Chuck') | |
36 | - user_list = request.context['page'].object_list | |
37 | - | |
38 | - self.assertEqual(1, len(user_list)) | |
39 | - | |
40 | - self.assertIn('chucknorris@mail.com', user_list[0].object.email) | |
41 | - self.assertIn('Chuck', user_list[0].object.first_name) | |
42 | - self.assertIn('Norris', user_list[0].object.last_name) | |
43 | - self.assertIn('chucknorris', user_list[0].object.username) | |
44 | - | |
45 | - def test_search_account_by_lastName(self): | |
46 | - request = self.client.get('/search/?q=Norris') | |
47 | - user_list = request.context['page'].object_list | |
48 | - | |
49 | - self.assertEqual(2, len(user_list)) | |
50 | - | |
51 | - self.assertIn('heisenberg@mail.com', user_list[1].object.email) | |
52 | - self.assertIn('Heisenberg', user_list[1].object.first_name) | |
53 | - self.assertIn('Norris', user_list[1].object.last_name) | |
54 | - self.assertIn('heisenbergnorris', user_list[1].object.username) | |
55 | - | |
56 | - self.assertIn('chucknorris@mail.com', user_list[0].object.email) | |
57 | - self.assertIn('Chuck', user_list[0].object.first_name) | |
58 | - self.assertIn('Norris', user_list[0].object.last_name) | |
59 | - self.assertIn('chucknorris', user_list[0].object.username) |
... | ... | @@ -0,0 +1,106 @@ |
1 | +# -*- coding:utf-8 -*- | |
2 | + | |
3 | +import math | |
4 | +from mock import Mock | |
5 | + | |
6 | +from django.test import TestCase, Client | |
7 | +from colab.search.base_indexes import BaseIndex | |
8 | + | |
9 | + | |
10 | +class SearchViewTest(TestCase): | |
11 | + | |
12 | + def setUp(self): | |
13 | + self.client = Client() | |
14 | + | |
15 | + def tearDown(self): | |
16 | + pass | |
17 | + | |
18 | + def test_get_updated_field(self): | |
19 | + base_index = BaseIndex() | |
20 | + | |
21 | + self.assertEquals('modified', base_index.get_updated_field()) | |
22 | + | |
23 | + def test_get_boost(self): | |
24 | + obj = Mock(hits=10) | |
25 | + base_index = BaseIndex() | |
26 | + | |
27 | + self.assertEquals(1, base_index.get_boost(obj)) | |
28 | + | |
29 | + obj = Mock(hits=11) | |
30 | + self.assertEquals(math.log(11), base_index.get_boost(obj)) | |
31 | + | |
32 | + def test_prepare_author(self): | |
33 | + obj = Mock(author="author") | |
34 | + base_index = BaseIndex() | |
35 | + setattr(base_index, 'author_obj', None) | |
36 | + | |
37 | + self.assertEquals("author", base_index.prepare_author(obj)) | |
38 | + | |
39 | + base_index.author_obj = Mock(username="carlin") | |
40 | + self.assertEquals("carlin", base_index.prepare_author(obj)) | |
41 | + | |
42 | + def test_prepare_author_url(self): | |
43 | + base_index = BaseIndex() | |
44 | + setattr(base_index, 'author_obj', None) | |
45 | + | |
46 | + self.assertEquals(None, base_index.prepare_author_url(None)) | |
47 | + | |
48 | + base_index.author_obj = Mock(get_absolute_url=lambda: "url_test") | |
49 | + self.assertEquals("url_test", base_index.prepare_author_url(None)) | |
50 | + | |
51 | + class AuthorMockObject: | |
52 | + author = "author" | |
53 | + | |
54 | + def test_prepare_modified_by(self): | |
55 | + base_index = BaseIndex() | |
56 | + setattr(base_index, 'author_obj', None) | |
57 | + obj = self.AuthorMockObject() | |
58 | + | |
59 | + self.assertEquals("author", base_index.prepare_modified_by(obj)) | |
60 | + | |
61 | + base_index.author_obj = Mock(get_full_name=lambda: "full_name") | |
62 | + self.assertEquals("full_name", base_index.prepare_modified_by(obj)) | |
63 | + | |
64 | + mock_modified_by = Mock(get_full_name=lambda: "full_name") | |
65 | + obj = Mock(modified_by="somebody", | |
66 | + get_modified_by=lambda: mock_modified_by) | |
67 | + | |
68 | + self.assertEquals("full_name", base_index.prepare_modified_by(obj)) | |
69 | + | |
70 | + def test_prepare_fullname_and_username(self): | |
71 | + base_index = BaseIndex() | |
72 | + setattr(base_index, 'author_obj', Mock(username="user", | |
73 | + get_full_name=lambda: "name")) | |
74 | + | |
75 | + expected = "{}\n{}".format("name", "user") | |
76 | + self.assertEquals(expected, | |
77 | + base_index.prepare_fullname_and_username(None)) | |
78 | + | |
79 | + base_index.author_obj = None | |
80 | + obj = self.AuthorMockObject() | |
81 | + self.assertEquals("author", | |
82 | + base_index.prepare_fullname_and_username(obj)) | |
83 | + | |
84 | + mock_modified_by = Mock(get_full_name=lambda: "full_name", | |
85 | + username="user") | |
86 | + obj = Mock(modified_by="somebody", | |
87 | + get_modified_by=lambda: mock_modified_by) | |
88 | + | |
89 | + expected = "{}\n{}".format("full_name", "user") | |
90 | + self.assertEquals(expected, | |
91 | + base_index.prepare_fullname_and_username(obj)) | |
92 | + | |
93 | + def test_prepare_modified_by_url(self): | |
94 | + base_index = BaseIndex() | |
95 | + setattr(base_index, 'author_obj', None) | |
96 | + | |
97 | + self.assertEquals(None, base_index.prepare_modified_by_url(None)) | |
98 | + | |
99 | + base_index.author_obj = Mock(get_absolute_url=lambda: "urlurl") | |
100 | + self.assertEquals("urlurl", base_index.prepare_modified_by_url(None)) | |
101 | + | |
102 | + mock_modified_by = Mock(get_absolute_url=lambda: "urlurl") | |
103 | + obj = Mock(modified_by="somebody", | |
104 | + get_modified_by=lambda: mock_modified_by) | |
105 | + | |
106 | + self.assertEquals("urlurl", base_index.prepare_modified_by_url(obj)) | ... | ... |
... | ... | @@ -0,0 +1,130 @@ |
1 | +# -*- coding:utf-8 -*- | |
2 | + | |
3 | +import mock | |
4 | + | |
5 | +from colab.plugins.utils import filters_importer | |
6 | +from django.test import TestCase, Client | |
7 | +from django.core.management import call_command | |
8 | +from colab.search.forms import ColabSearchForm | |
9 | + | |
10 | + | |
11 | +class SearchViewTest(TestCase): | |
12 | + | |
13 | + fixtures = ['test_data.json'] | |
14 | + | |
15 | + def setUp(self): | |
16 | + call_command('rebuild_index', interactive=False, verbosity=0) | |
17 | + self.client = Client() | |
18 | + | |
19 | + def tearDown(self): | |
20 | + call_command('clear_index', interactive=False, verbosity=0) | |
21 | + | |
22 | + def test_search_thread(self): | |
23 | + request = self.client.get('/search/?q=thread') | |
24 | + thread_list = request.context['page'].object_list | |
25 | + | |
26 | + self.assertEqual(3, len(thread_list)) | |
27 | + | |
28 | + condition = any('This is a repply to Thread 1 on list A' in | |
29 | + t.description for t in thread_list) | |
30 | + self.assertTrue(condition) | |
31 | + condition = any('This is a repply to Thread 1 on list B' in | |
32 | + t.description for t in thread_list) | |
33 | + self.assertTrue(condition) | |
34 | + condition = any('This is a repply to Thread 1 on list C' in | |
35 | + t.description for t in thread_list) | |
36 | + self.assertTrue(condition) | |
37 | + | |
38 | + def test_search_account_by_firstName(self): | |
39 | + request = self.client.get('/search/?q=Chuck') | |
40 | + user_list = request.context['page'].object_list | |
41 | + | |
42 | + self.assertEqual(1, len(user_list)) | |
43 | + | |
44 | + self.assertIn('chucknorris@mail.com', user_list[0].object.email) | |
45 | + self.assertIn('Chuck', user_list[0].object.first_name) | |
46 | + self.assertIn('Norris', user_list[0].object.last_name) | |
47 | + self.assertIn('chucknorris', user_list[0].object.username) | |
48 | + | |
49 | + def test_search_account_by_lastName(self): | |
50 | + request = self.client.get('/search/?q=Norris') | |
51 | + user_list = request.context['page'].object_list | |
52 | + | |
53 | + self.assertEqual(2, len(user_list)) | |
54 | + | |
55 | + self.assertIn('heisenberg@mail.com', user_list[1].object.email) | |
56 | + self.assertIn('Heisenberg', user_list[1].object.first_name) | |
57 | + self.assertIn('Norris', user_list[1].object.last_name) | |
58 | + self.assertIn('heisenbergnorris', user_list[1].object.username) | |
59 | + | |
60 | + self.assertIn('chucknorris@mail.com', user_list[0].object.email) | |
61 | + self.assertIn('Chuck', user_list[0].object.first_name) | |
62 | + self.assertIn('Norris', user_list[0].object.last_name) | |
63 | + self.assertIn('chucknorris', user_list[0].object.username) | |
64 | + | |
65 | + def test_search_plugin_filters(self): | |
66 | + plugin_filter = { | |
67 | + 'plugin_name': { | |
68 | + 'name': 'PluginData', | |
69 | + 'icon': 'plugin_icon', | |
70 | + 'fields': ( | |
71 | + ('field_1', 'Field1', ''), | |
72 | + ('field_2', 'Field2', ''), | |
73 | + ), | |
74 | + }, | |
75 | + } | |
76 | + filters_importer.import_plugin_filters = mock.Mock( | |
77 | + return_value=plugin_filter) | |
78 | + | |
79 | + request = self.client.get('/search/?q=') | |
80 | + | |
81 | + value = [('plugin_name', 'PluginData', 'plugin_icon')] | |
82 | + | |
83 | + self.assertEqual(request.context['filters_options'], value) | |
84 | + | |
85 | + def test_search_dynamic_form_fields(self): | |
86 | + plugin_filter = { | |
87 | + 'plugin_name': { | |
88 | + 'name': 'PluginData', | |
89 | + 'icon': 'plugin_icon', | |
90 | + 'fields': ( | |
91 | + ('field_1', 'Field1', ''), | |
92 | + ('field_2', 'Field2', ''), | |
93 | + ), | |
94 | + }, | |
95 | + } | |
96 | + filters_importer.import_plugin_filters = mock.Mock( | |
97 | + return_value=plugin_filter) | |
98 | + | |
99 | + form = ColabSearchForm() | |
100 | + | |
101 | + self.assertIn('field_1', form.fields.keys()) | |
102 | + self.assertIn('field_2', form.fields.keys()) | |
103 | + | |
104 | + def test_search_multiple_filters(self): | |
105 | + request = self.client.get('/search/?q=&type=thread+user') | |
106 | + user_list = request.context['page'].object_list | |
107 | + | |
108 | + self.assertEqual(6, len(user_list)) | |
109 | + | |
110 | + self.assertIn('admin@mail.com', user_list[0].object.email) | |
111 | + self.assertIn('admin', user_list[0].object.username) | |
112 | + | |
113 | + self.assertIn('chucknorris@mail.com', user_list[1].object.email) | |
114 | + self.assertIn('Chuck', user_list[1].object.first_name) | |
115 | + self.assertIn('Norris', user_list[1].object.last_name) | |
116 | + self.assertIn('chucknorris', user_list[1].object.username) | |
117 | + | |
118 | + self.assertIn('heisenberg@mail.com', user_list[2].object.email) | |
119 | + self.assertIn('Heisenberg', user_list[2].object.first_name) | |
120 | + self.assertIn('Norris', user_list[2].object.last_name) | |
121 | + self.assertIn('heisenbergnorris', user_list[2].object.username) | |
122 | + | |
123 | + self.assertIn('Admin Administrator', user_list[3].author) | |
124 | + self.assertIn('Response to Thread 1A', user_list[3].title) | |
125 | + | |
126 | + self.assertIn('Admin Administrator', user_list[4].author) | |
127 | + self.assertIn('Message 1 on Thread 1B', user_list[4].title) | |
128 | + | |
129 | + self.assertIn('Admin Administrator', user_list[5].author) | |
130 | + self.assertIn('Message 1 on Thread 1C', user_list[5].title) | ... | ... |
... | ... | @@ -0,0 +1,34 @@ |
1 | +# -*- coding:utf-8 -*- | |
2 | + | |
3 | +from django.test import TestCase | |
4 | +from colab.search.templatetags.search_preview_templates import ( | |
5 | + get_search_preview_templates) | |
6 | +from mock import MagicMock, PropertyMock | |
7 | + | |
8 | + | |
9 | +class SearchTemplateTagsTest(TestCase): | |
10 | + | |
11 | + def setUp(self): | |
12 | + self.model_indexed_mock = MagicMock() | |
13 | + self.template_path = "{}/{}_search_preview.html" | |
14 | + | |
15 | + def set_mock_value(self, value): | |
16 | + type(self.model_indexed_mock).type = PropertyMock(return_value=value) | |
17 | + | |
18 | + def test_get_search_preview_templates_with_user(self): | |
19 | + self.set_mock_value("user") | |
20 | + include_path = get_search_preview_templates(self.model_indexed_mock) | |
21 | + self.assertEqual(include_path, self.template_path.format("search", | |
22 | + "user")) | |
23 | + | |
24 | + def test_get_search_preview_templates_with_thread(self): | |
25 | + self.set_mock_value("thread") | |
26 | + include_path = get_search_preview_templates(self.model_indexed_mock) | |
27 | + self.assertEqual(include_path, | |
28 | + self.template_path.format("search", "thread")) | |
29 | + | |
30 | + def test_get_search_preview_templates_with_plugin(self): | |
31 | + self.set_mock_value("plugin_model") | |
32 | + include_path = get_search_preview_templates(self.model_indexed_mock) | |
33 | + self.assertEqual(include_path, | |
34 | + self.template_path.format("search", "plugin_model")) | ... | ... |
colab/search/views.py
1 | 1 | # -*- coding:utf-8 -*- |
2 | 2 | |
3 | 3 | from django.conf import settings |
4 | -from django.utils.translation import ugettext as _ | |
5 | 4 | |
6 | 5 | from haystack.views import SearchView |
6 | +from colab.plugins.utils import filters_importer | |
7 | 7 | |
8 | 8 | |
9 | 9 | class ColabSearchView(SearchView): |
... | ... | @@ -13,127 +13,6 @@ class ColabSearchView(SearchView): |
13 | 13 | self.request.LANGUAGE_CODE, (None, None) |
14 | 14 | ) |
15 | 15 | |
16 | - types = { | |
17 | - 'thread': { | |
18 | - 'name': _(u'Discussion'), | |
19 | - 'fields': ( | |
20 | - ('author', _(u'Author'), self.request.GET.get('author')), | |
21 | - ( | |
22 | - 'list', | |
23 | - _(u'Mailinglist'), | |
24 | - self.request.GET.getlist('list') | |
25 | - ), | |
26 | - ), | |
27 | - }, | |
28 | - } | |
29 | - # TODO: Replace for a more generic plugin architecture | |
30 | - # if settings.TRAC_ENABLED: | |
31 | - # types['wiki'] = { | |
32 | - # 'name': _(u'Wiki'), | |
33 | - # 'fields': ( | |
34 | - # ('author', _(u'Author'), self.request.GET.get('author')), | |
35 | - # ( | |
36 | - # 'collaborators', | |
37 | - # _(u'Collaborators'), | |
38 | - # self.request.GET.get('collaborators'), | |
39 | - # ), | |
40 | - # ), | |
41 | - # } | |
42 | - | |
43 | - # types['ticket'] = { | |
44 | - # 'name': _(u'Ticket'), | |
45 | - # 'fields': ( | |
46 | - # ( | |
47 | - # 'milestone', | |
48 | - # _(u'Milestone'), | |
49 | - # self.request.GET.get('milestone') | |
50 | - # ), | |
51 | - # ( | |
52 | - # 'priority', | |
53 | - # _(u'Priority'), | |
54 | - # self.request.GET.get('priority') | |
55 | - # ), | |
56 | - # ( | |
57 | - # 'component', | |
58 | - # _(u'Component'), | |
59 | - # self.request.GET.get('component') | |
60 | - # ), | |
61 | - # ( | |
62 | - # 'severity', | |
63 | - # _(u'Severity'), | |
64 | - # self.request.GET.get('severity') | |
65 | - # ), | |
66 | - # ( | |
67 | - # 'reporter', | |
68 | - # _(u'Reporter'), | |
69 | - # self.request.GET.get('reporter') | |
70 | - # ), | |
71 | - # ('author', _(u'Author'), self.request.GET.get('author')), | |
72 | - # ('tag', _(u'Status'), self.request.GET.get('tag')), | |
73 | - # ( | |
74 | - # 'keywords', | |
75 | - # _(u'Keywords'), | |
76 | - # self.request.GET.get('keywords'), | |
77 | - # ), | |
78 | - # ( | |
79 | - # 'collaborators', | |
80 | - # _(u'Collaborators'), | |
81 | - # self.request.GET.get('collaborators') | |
82 | - # ), | |
83 | - # ), | |
84 | - # } | |
85 | - | |
86 | - # types['changeset'] = { | |
87 | - # 'name': _(u'Changeset'), | |
88 | - # 'fields': ( | |
89 | - # ('author', _(u'Author'), self.request.GET.get('author')), | |
90 | - # ( | |
91 | - # 'repository_name', | |
92 | - # _(u'Repository'), | |
93 | - # self.request.GET.get('repository_name'), | |
94 | - # ), | |
95 | - # ) | |
96 | - # } | |
97 | - | |
98 | - # types['user'] = { | |
99 | - # 'name': _(u'User'), | |
100 | - # 'fields': ( | |
101 | - # ( | |
102 | - # 'username', | |
103 | - # _(u'Username'), | |
104 | - # self.request.GET.get('username'), | |
105 | - # ), | |
106 | - # ('name', _(u'Name'), self.request.GET.get('name')), | |
107 | - # ( | |
108 | - # 'institution', | |
109 | - # _(u'Institution'), | |
110 | - # self.request.GET.get('institution'), | |
111 | - # ), | |
112 | - # ('role', _(u'Role'), self.request.GET.get('role')) | |
113 | - # ), | |
114 | - # } | |
115 | - | |
116 | - # types['attachment'] = { | |
117 | - # 'name': _(u'Attachment'), | |
118 | - # 'fields': ( | |
119 | - # ( | |
120 | - # 'filename', | |
121 | - # _(u'Filename'), | |
122 | - # self.request.GET.get('filename') | |
123 | - # ), | |
124 | - # ('author', _(u'Author'), self.request.GET.get('author')), | |
125 | - # ( | |
126 | - # 'used_by', | |
127 | - # _(u'Used by'), self.request.GET.get('used_by')), | |
128 | - # ( | |
129 | - # 'mimetype', | |
130 | - # _(u'File type'), | |
131 | - # self.request.GET.get('mimetype') | |
132 | - # ), | |
133 | - # ('size', _(u'Size'), self.request.GET.get('size')), | |
134 | - # ) | |
135 | - # } | |
136 | - | |
137 | 16 | try: |
138 | 17 | type_chosen = self.form.cleaned_data.get('type') |
139 | 18 | except AttributeError: |
... | ... | @@ -143,26 +22,17 @@ class ColabSearchView(SearchView): |
143 | 22 | size_choices = () |
144 | 23 | used_by_choices = () |
145 | 24 | |
146 | - # if type_chosen == 'attachment': | |
147 | - # mimetype_choices = [ | |
148 | - # (type_, display) | |
149 | - # for type_, display, mimelist_ in settings.FILE_TYPE_GROUPINGS] | |
150 | - # size_choices = [ | |
151 | - # ('<500KB', u'< 500 KB'), | |
152 | - # ('500KB__10MB', u'>= 500 KB <= 10 MB'), | |
153 | - # ('>10MB', u'> 10 MB'), | |
154 | - # ] | |
155 | - # used_by_choices = set([ | |
156 | - # (v, v) for v in Attachment.objects.values_list('used_by', | |
157 | - # flat=True) | |
158 | - # ]) | |
159 | - | |
160 | 25 | mimetype_chosen = self.request.GET.get('mimetype') |
161 | 26 | size_chosen = self.request.GET.get('size') |
162 | 27 | used_by_chosen = self.request.GET.get('used_by') |
163 | 28 | |
29 | + types = filters_importer.import_plugin_filters(self.request.GET) | |
30 | + | |
31 | + filters_options = [(k, v['name'], v['icon']) | |
32 | + for (k, v) in types.iteritems()] | |
164 | 33 | return dict( |
165 | 34 | filters=types.get(type_chosen), |
35 | + filters_options=filters_options, | |
166 | 36 | type_chosen=type_chosen, |
167 | 37 | order_data=settings.ORDERING_DATA, |
168 | 38 | date_format=date_format, | ... | ... |
colab/settings.py
... | ... | @@ -53,6 +53,7 @@ INSTALLED_APPS = ( |
53 | 53 | 'colab', |
54 | 54 | 'colab.home', |
55 | 55 | 'colab.plugins', |
56 | + 'colab.widgets', | |
56 | 57 | 'colab.super_archives', |
57 | 58 | 'colab.rss', |
58 | 59 | 'colab.search', |
... | ... | @@ -80,11 +81,8 @@ USE_TZ = True |
80 | 81 | # Static files (CSS, JavaScript, Images) |
81 | 82 | # https://docs.djangoproject.com/en/1.7/howto/static-files/ |
82 | 83 | |
83 | -STATIC_ROOT = '/usr/share/nginx/colab/static/' | |
84 | -MEDIA_ROOT = '/usr/share/nginx/colab/media/' | |
85 | - | |
84 | +STATIC_ROOT = '/var/lib/colab/static/' | |
86 | 85 | STATIC_URL = '/static/' |
87 | -MEDIA_URL = '/media/' | |
88 | 86 | |
89 | 87 | STATICFILES_STORAGE = \ |
90 | 88 | 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' |
... | ... | @@ -134,7 +132,7 @@ ATTACHMENTS_FOLDER_PATH = '/mnt/trac/attachments/' |
134 | 132 | # the indexes |
135 | 133 | |
136 | 134 | ORDERING_DATA = { |
137 | - 'latest': { | |
135 | + 'latest': { | |
138 | 136 | 'name': _(u'Recent activity'), |
139 | 137 | 'fields': ('-modified', '-created'), |
140 | 138 | }, |
... | ... | @@ -142,6 +140,10 @@ ORDERING_DATA = { |
142 | 140 | 'name': _(u'Relevance'), |
143 | 141 | 'fields': None, |
144 | 142 | }, |
143 | + 'type': { | |
144 | + 'name': _(u'Type'), | |
145 | + 'fields': ('type',), | |
146 | + } | |
145 | 147 | } |
146 | 148 | |
147 | 149 | |
... | ... | @@ -293,3 +295,5 @@ TEMPLATE_DIRS += ( |
293 | 295 | ) |
294 | 296 | |
295 | 297 | conf.validate_database(DATABASES, DEFAULT_DATABASE, DEBUG) |
298 | + | |
299 | +conf.load_widgets_settings() | ... | ... |
... | ... | @@ -0,0 +1,46 @@ |
1 | +/* Header */ | |
2 | + | |
3 | +#header-searchbox { | |
4 | + width: 190px; | |
5 | +} | |
6 | + | |
7 | +#header-hr { | |
8 | + margin-top: 0; | |
9 | +} | |
10 | + | |
11 | +.navbar-default .navbar-brand, | |
12 | +.navbar a.dropdown-toggle.user { | |
13 | + padding: 0; | |
14 | + margin-top: 5px; | |
15 | + margin-left: 10px; | |
16 | +} | |
17 | + | |
18 | +.navbar-brand img { | |
19 | + height: 40px; | |
20 | +} | |
21 | + | |
22 | +#user-menu .wrapper { | |
23 | + padding: 3px 10px; | |
24 | + white-space: nowrap; | |
25 | +} | |
26 | + | |
27 | +#user-menu .wrapper a { | |
28 | + margin: 5px 0; | |
29 | +} | |
30 | + | |
31 | +#user-menu .user-info { | |
32 | + display: inline-block; | |
33 | + vertical-align: top; | |
34 | + padding-left: 5px; | |
35 | +} | |
36 | + | |
37 | +#user-menu .user-info span { | |
38 | + display: block; | |
39 | +} | |
40 | + | |
41 | +#user-menu .dropdown-menu .thumbnail { | |
42 | + width: 100px; | |
43 | + display: inline-block; | |
44 | +} | |
45 | + | |
46 | +/* End of Header */ | |
0 | 47 | \ No newline at end of file | ... | ... |
colab/static/css/screen.css
... | ... | @@ -7,54 +7,6 @@ li hr { |
7 | 7 | margin: 10px 0; |
8 | 8 | } |
9 | 9 | |
10 | -/* Header */ | |
11 | - | |
12 | -#header-searchbox { | |
13 | - width: 190px; | |
14 | -} | |
15 | - | |
16 | -#header-hr { | |
17 | - margin-top: 0; | |
18 | -} | |
19 | - | |
20 | -.navbar-default .navbar-brand, | |
21 | -.navbar a.dropdown-toggle.user { | |
22 | - padding: 0; | |
23 | - margin-top: 5px; | |
24 | - margin-left: 10px; | |
25 | -} | |
26 | - | |
27 | -.navbar-brand img { | |
28 | - height: 40px; | |
29 | -} | |
30 | - | |
31 | -#user-menu .wrapper { | |
32 | - padding: 3px 10px; | |
33 | - white-space: nowrap; | |
34 | -} | |
35 | - | |
36 | -#user-menu .wrapper a { | |
37 | - margin: 5px 0; | |
38 | -} | |
39 | - | |
40 | -#user-menu .user-info { | |
41 | - display: inline-block; | |
42 | - vertical-align: top; | |
43 | - padding-left: 5px; | |
44 | -} | |
45 | - | |
46 | -#user-menu .user-info span { | |
47 | - display: block; | |
48 | -} | |
49 | - | |
50 | -#user-menu .dropdown-menu .thumbnail { | |
51 | - width: 80px; | |
52 | - display: inline-block; | |
53 | -} | |
54 | - | |
55 | -/* End of Header */ | |
56 | - | |
57 | - | |
58 | 10 | /* From message-preview.html*/ |
59 | 11 | .quiet { |
60 | 12 | color: #999; |
... | ... | @@ -115,6 +67,11 @@ form.signup .form-group { |
115 | 67 | margin-left: 0.5em; |
116 | 68 | } |
117 | 69 | |
70 | +div.links-group { | |
71 | + text-align: center; | |
72 | + margin-bottom: 20px; | |
73 | +} | |
74 | + | |
118 | 75 | div.submit { |
119 | 76 | margin: auto; |
120 | 77 | margin-bottom: 2em; | ... | ... |
... | ... | @@ -0,0 +1,22 @@ |
1 | +from django.utils.translation import ugettext as _ | |
2 | +from colab.super_archives.models import MailingList | |
3 | + | |
4 | + | |
5 | +def get_filters(request): | |
6 | + return { | |
7 | + 'thread': { | |
8 | + 'name': _(u'Discussion'), | |
9 | + 'icon': 'envelope', | |
10 | + 'fields': ( | |
11 | + ('author', _(u'Author'), request.get('author')), | |
12 | + ( | |
13 | + 'tag', | |
14 | + _(u'Mailinglist'), | |
15 | + request.get('tag'), | |
16 | + 'list', | |
17 | + [(v, v) for v in MailingList.objects.values_list( | |
18 | + 'name', flat=True)] | |
19 | + ), | |
20 | + ), | |
21 | + }, | |
22 | + } | ... | ... |
... | ... | @@ -0,0 +1,100 @@ |
1 | +[ | |
2 | + { | |
3 | + "fields": { | |
4 | + "last_name": "Administrator", | |
5 | + "webpage": "", | |
6 | + "twitter": "", | |
7 | + "is_staff": true, | |
8 | + "user_permissions": [], | |
9 | + "date_joined": "2015-01-28T12:34:58.770Z", | |
10 | + "google_talk": "", | |
11 | + "first_name": "Admin", | |
12 | + "is_superuser": true, | |
13 | + "last_login": "2015-01-28T12:35:39.621Z", | |
14 | + "verification_hash": null, | |
15 | + "role": "", | |
16 | + "email": "admin@mail.com", | |
17 | + "username": "admin", | |
18 | + "bio": "", | |
19 | + "needs_update": true, | |
20 | + "is_active": true, | |
21 | + "facebook": "", | |
22 | + "groups": [], | |
23 | + "password": "pbkdf2_sha256$12000$iiKCMnLZnFJw$UTx89LB8oYTiw9UqkcglzFLmIaZtbr+ZzF1cG3vfcyo=", | |
24 | + "institution": "", | |
25 | + "github": "", | |
26 | + "modified": "2015-01-28T12:45:27.375Z" | |
27 | + }, | |
28 | + "model": "accounts.user", | |
29 | + "pk": 1 | |
30 | + }, | |
31 | + { | |
32 | + "fields": { | |
33 | + "last_name": "Norris", | |
34 | + "webpage": "", | |
35 | + "twitter": "", | |
36 | + "is_staff": true, | |
37 | + "user_permissions": [], | |
38 | + "date_joined": "2015-01-28T12:34:58.770Z", | |
39 | + "google_talk": "", | |
40 | + "first_name": "Chuck", | |
41 | + "is_superuser": true, | |
42 | + "last_login": "2015-01-28T12:35:39.621Z", | |
43 | + "verification_hash": null, | |
44 | + "role": "", | |
45 | + "email": "chucknorris@mail.com", | |
46 | + "username": "chucknorris", | |
47 | + "bio": "", | |
48 | + "needs_update": true, | |
49 | + "is_active": true, | |
50 | + "facebook": "", | |
51 | + "groups": [], | |
52 | + "password": "123colab4", | |
53 | + "institution": "", | |
54 | + "github": "", | |
55 | + "modified": "2015-01-28T12:45:27.375Z" | |
56 | + }, | |
57 | + "model": "accounts.user", | |
58 | + "pk": 2 | |
59 | + }, | |
60 | + | |
61 | + { | |
62 | + "fields": { | |
63 | + "last_name": "Norris", | |
64 | + "webpage": "", | |
65 | + "twitter": "", | |
66 | + "is_staff": true, | |
67 | + "user_permissions": [], | |
68 | + "date_joined": "2015-01-28T12:34:58.770Z", | |
69 | + "google_talk": "", | |
70 | + "first_name": "Heisenberg", | |
71 | + "is_superuser": true, | |
72 | + "last_login": "2015-01-28T12:35:39.621Z", | |
73 | + "verification_hash": null, | |
74 | + "role": "", | |
75 | + "email": "heisenberg@mail.com", | |
76 | + "username": "heisenbergnorris", | |
77 | + "bio": "", | |
78 | + "needs_update": true, | |
79 | + "is_active": true, | |
80 | + "facebook": "", | |
81 | + "groups": [], | |
82 | + "password": "pbkdf2_sha256$12000$iiKCMnLZnFJw$UTx89LB8oYTiw9UqkcglzFLmIaZtbr+ZzF1cG3vfcyo=", | |
83 | + "institution": "", | |
84 | + "github": "", | |
85 | + "modified": "2015-01-28T12:45:27.375Z" | |
86 | + }, | |
87 | + "model": "accounts.user", | |
88 | + "pk": 3 | |
89 | + }, | |
90 | + { | |
91 | + "fields": { | |
92 | + "real_name": "Administrator", | |
93 | + "user": 1, | |
94 | + "md5": "edb0e96701c209ab4b50211c856c50c4", | |
95 | + "address": "admin@mail.com" | |
96 | + }, | |
97 | + "model": "super_archives.emailaddress", | |
98 | + "pk": 1 | |
99 | + } | |
100 | +] | ... | ... |
colab/super_archives/management/commands/import_emails.py
... | ... | @@ -117,7 +117,7 @@ class Command(BaseCommand, object): |
117 | 117 | # Get messages from each mbox |
118 | 118 | for mbox in mailing_lists_mboxes: |
119 | 119 | mbox_path = os.path.join(mailinglist_dir, mbox, mbox) |
120 | - mailinglist_name = mbox.split('.')[0] | |
120 | + mailinglist_name = os.path.splitext(mbox)[0] | |
121 | 121 | |
122 | 122 | # Check if the mailinglist is set not to be imported |
123 | 123 | if exclude_lists and mailinglist_name in exclude_lists: | ... | ... |
colab/super_archives/templates/dashboard/thread_search_preview.html
0 → 100644
... | ... | @@ -0,0 +1,69 @@ |
1 | +{% load i18n tz highlight %} | |
2 | + | |
3 | +<li class="preview-message"> | |
4 | +<span class="glyphicon glyphicon-{{ result.icon_name }}" title="{{ result.type }}"></span> | |
5 | + | |
6 | +{% if result.tag %} | |
7 | +<a href="{% firstof result.mailinglist_url result.mailinglist.get_absolute_url result.url %}"> | |
8 | + <span class="label label-primary">{{ result.tag }}</span> | |
9 | +</a> | |
10 | +{% endif %} | |
11 | + | |
12 | +{% if result.title %} | |
13 | + <a href="{{ result.url }}{% if result.type == 'thread' and result.latest_message_pk %}#msg-{{ result.latest_message_pk }}{% elif result.type == 'thread' and result.pk %}#msg-{{ result.pk }}{% endif %}" {% if result.latest_description %}title="{{ result.latest_description|escape|truncatechars:150 }}"{% elif result.description %}title="{{ result.description|escape|truncatechars:150 }}"{% endif %}> | |
14 | + <span class="subject"> | |
15 | + <!-- a striptags filter was raising an error here because using with highlight --> | |
16 | + {% if query %} | |
17 | + {% highlight result.title with query max_length "1000" %} | |
18 | + {% else %} | |
19 | + {{ result.title }} | |
20 | + {% endif %} | |
21 | + </span> | |
22 | + </a> | |
23 | +{% endif %} | |
24 | + | |
25 | +{% if result.description %} | |
26 | + <!-- a striptags filter was raising an error here because using with highlight --> | |
27 | + <span class="quiet">- {% if query %} | |
28 | + {% highlight result.description with query max_length "150" %} | |
29 | + {% else %} | |
30 | + {% if result.latest_description %} | |
31 | + {{ result.latest_description|striptags|escape|truncatechars:150 }} | |
32 | + {% elif result.description %} | |
33 | + {{ result.description|striptags|escape|truncatechars:150 }} | |
34 | + {% endif %} | |
35 | + {% endif %} | |
36 | + </span> | |
37 | +{% endif %} | |
38 | + | |
39 | +{% if result.fullname or result.modified or result.modified_by %} | |
40 | + <div class="quiet"> | |
41 | + {% if result.modified_by %} | |
42 | + <span class="pull-left">{% trans "by" %} | |
43 | + {% if result.modified_by_url %} | |
44 | + <a href="{{ result.modified_by_url }}"> | |
45 | + {% else %} | |
46 | + <span> | |
47 | + {% endif %} | |
48 | + | |
49 | + {% if query %} | |
50 | + {% highlight result.modified_by with query %} | |
51 | + {% else %} | |
52 | + {{ result.modified_by }} | |
53 | + {% endif %} | |
54 | + | |
55 | + {% if result.modified_by_url %} | |
56 | + </a> | |
57 | + {% else %} | |
58 | + </span> | |
59 | + {% endif %} | |
60 | + </span> | |
61 | + {% else %} | |
62 | + <span class="pull-left">{% trans "by" %} {% trans "Anonymous" %}</span> | |
63 | + {% endif %} | |
64 | + {% if result.modified %} | |
65 | + <span class="pull-right">{{ result.modified|localtime|timesince }} {% trans "ago" %}</span> | |
66 | + {% endif %} | |
67 | + </div> | |
68 | +{% endif %} | |
69 | +</li> | |
0 | 70 | \ No newline at end of file | ... | ... |
colab/super_archives/templates/message-preview.html
... | ... | @@ -1,69 +0,0 @@ |
1 | -{% load i18n tz highlight %} | |
2 | - | |
3 | -<li class="preview-message"> | |
4 | -<span class="glyphicon glyphicon-{{ result.icon_name }}" title="{{ result.type }}"></span> | |
5 | - | |
6 | -{% if result.tag %} | |
7 | -<a href="{% firstof result.mailinglist_url result.mailinglist.get_absolute_url result.url %}"> | |
8 | - <span class="label label-primary">{{ result.tag }}</span> | |
9 | -</a> | |
10 | -{% endif %} | |
11 | - | |
12 | -{% if result.title %} | |
13 | - <a href="{{ result.url }}{% if result.type == 'thread' and result.latest_message_pk %}#msg-{{ result.latest_message_pk }}{% elif result.type == 'thread' and result.pk %}#msg-{{ result.pk }}{% endif %}" {% if result.latest_description %}title="{{ result.latest_description|escape|truncatechars:150 }}"{% elif result.description %}title="{{ result.description|escape|truncatechars:150 }}"{% endif %}> | |
14 | - <span class="subject"> | |
15 | - <!-- a striptags filter was raising an error here because using with highlight --> | |
16 | - {% if query %} | |
17 | - {% highlight result.title with query max_length "1000" %} | |
18 | - {% else %} | |
19 | - {{ result.title }} | |
20 | - {% endif %} | |
21 | - </span> | |
22 | - </a> | |
23 | -{% endif %} | |
24 | - | |
25 | -{% if result.description %} | |
26 | - <!-- a striptags filter was raising an error here because using with highlight --> | |
27 | - <span class="quiet">- {% if query %} | |
28 | - {% highlight result.description with query max_length "150" %} | |
29 | - {% else %} | |
30 | - {% if result.latest_description %} | |
31 | - {{ result.latest_description|striptags|escape|truncatechars:150 }} | |
32 | - {% elif result.description %} | |
33 | - {{ result.description|striptags|escape|truncatechars:150 }} | |
34 | - {% endif %} | |
35 | - {% endif %} | |
36 | - </span> | |
37 | -{% endif %} | |
38 | - | |
39 | -{% if result.fullname or result.modified or result.modified_by %} | |
40 | - <div class="quiet"> | |
41 | - {% if result.modified_by %} | |
42 | - <span class="pull-left">{% trans "by" %} | |
43 | - {% if result.modified_by_url %} | |
44 | - <a href="{{ result.modified_by_url }}"> | |
45 | - {% else %} | |
46 | - <span> | |
47 | - {% endif %} | |
48 | - | |
49 | - {% if query %} | |
50 | - {% highlight result.modified_by with query %} | |
51 | - {% else %} | |
52 | - {{ result.modified_by }} | |
53 | - {% endif %} | |
54 | - | |
55 | - {% if result.modified_by_url %} | |
56 | - </a> | |
57 | - {% else %} | |
58 | - </span> | |
59 | - {% endif %} | |
60 | - </span> | |
61 | - {% else %} | |
62 | - <span class="pull-left">{% trans "by" %} {% trans "Anonymous" %}</span> | |
63 | - {% endif %} | |
64 | - {% if result.modified %} | |
65 | - <span class="pull-right">{{ result.modified|localtime|timesince }} {% trans "ago" %}</span> | |
66 | - {% endif %} | |
67 | - </div> | |
68 | -{% endif %} | |
69 | -</li> |
colab/super_archives/templates/search/thread_search_preview.html
0 → 100644
... | ... | @@ -0,0 +1,33 @@ |
1 | +{% load i18n tz highlight date_format %} | |
2 | + | |
3 | +<div class="row"> | |
4 | + <div class="col-md-12"> | |
5 | + <small>{% datetime_format result.modified %} - | |
6 | + | |
7 | + {% if query %} | |
8 | + <a href="{{result.modified_by_url}}">{% highlight result.modified_by with query max_length "1000" %}</a> | |
9 | + {% else %} | |
10 | + <a href="{{result.modified_by_url}}">{{result.modified_by }}</a> | |
11 | + {% endif %} | |
12 | + | |
13 | + </small><br> | |
14 | + | |
15 | + {% if query %} | |
16 | + <h4><a href="{{result.url}}">{% highlight result.title with query max_length "1000"%}</a></h4> | |
17 | + {% else %} | |
18 | + <h4><a href="{{result.url}}">{{ result.title }}</a></h4> | |
19 | + {% endif %} | |
20 | + | |
21 | + {% with result.latest_description|truncatewords:"85" as description %} | |
22 | + | |
23 | + {% if query %} | |
24 | + {% highlight description with query max_length "1000" %}<br> | |
25 | + {% else %} | |
26 | + {{description | default_if_none:"a"}}<br> | |
27 | + {% endif %} | |
28 | + | |
29 | + {% endwith %} | |
30 | + <small>{% trans "Registred in" %}: <strong>{% trans "Discussion" %}</strong></small> | |
31 | + </div> | |
32 | + <hr> | |
33 | +</div> | ... | ... |
... | ... | @@ -0,0 +1,71 @@ |
1 | +# -*- coding:utf-8 -*- | |
2 | + | |
3 | +from mock import patch | |
4 | + | |
5 | +from colab.accounts.models import User | |
6 | +from django.test import TestCase, Client | |
7 | +from colab.super_archives.models import EmailAddressValidation, EmailAddress | |
8 | + | |
9 | + | |
10 | +class EmailValidationTest(TestCase): | |
11 | + | |
12 | + fixtures = ['test_user.json'] | |
13 | + | |
14 | + def setUp(self): | |
15 | + self.client = Client() | |
16 | + | |
17 | + def authenticate_user(self): | |
18 | + self.client.login(username='chucknorris', password='123colab4') | |
19 | + | |
20 | + @patch('colab.super_archives.views.send_verification_email', | |
21 | + return_value="") | |
22 | + def test_send_verification_email_successfuly(self, | |
23 | + mock_send_verification_email): | |
24 | + user = User.objects.get(username='chucknorris') | |
25 | + | |
26 | + EmailAddressValidation.create(user.email, user) | |
27 | + | |
28 | + email_addr, created = EmailAddress.objects.get_or_create( | |
29 | + address=user.email) | |
30 | + email_addr.user = user | |
31 | + email_addr.save() | |
32 | + | |
33 | + self.authenticate_user() | |
34 | + | |
35 | + response = self.client.post('/archives/manage/email/validate/', | |
36 | + {'user': user.id, 'email': user.email}) | |
37 | + | |
38 | + self.assertEqual(response.status_code, 204) | |
39 | + self.assertTrue(mock_send_verification_email.called) | |
40 | + | |
41 | + @patch('colab.super_archives.views.send_verification_email', | |
42 | + return_value="") | |
43 | + def test_send_verification_email_with_not_verified_email( | |
44 | + self, send_verification_email): | |
45 | + self.authenticate_user() | |
46 | + | |
47 | + user = User.objects.get(username='chucknorris') | |
48 | + | |
49 | + response = self.client.post('/archives/manage/email/validate/', | |
50 | + { | |
51 | + 'user': user.id, | |
52 | + 'email': "email@mail.com", | |
53 | + }) | |
54 | + | |
55 | + self.assertEqual(response.status_code, 404) | |
56 | + self.assertFalse(send_verification_email.called) | |
57 | + | |
58 | + @patch('colab.super_archives.views.send_verification_email', | |
59 | + return_value="") | |
60 | + def test_send_verification_email_with_not_valid_user_id( | |
61 | + self, send_verification_email): | |
62 | + self.authenticate_user() | |
63 | + | |
64 | + user = User.objects.get(username='chucknorris') | |
65 | + | |
66 | + response = self.client.post('/archives/manage/email/validate/', | |
67 | + {'user': len(User.objects.all()) + 1, | |
68 | + 'email': user.email}) | |
69 | + | |
70 | + self.assertEqual(response.status_code, 404) | |
71 | + self.assertFalse(send_verification_email.called) | ... | ... |
... | ... | @@ -0,0 +1,28 @@ |
1 | +from django.test import TestCase | |
2 | + | |
3 | +from ..utils.blocks import EmailBlock | |
4 | + | |
5 | + | |
6 | +class TestEmailBlock(TestCase): | |
7 | + | |
8 | + def setUp(self): | |
9 | + self.email_block = EmailBlock() | |
10 | + | |
11 | + def test_html2text_without_br_tag(self): | |
12 | + html = '<a>Hello, world!</a>' | |
13 | + text = self.email_block._html2text(html) | |
14 | + | |
15 | + self.assertEquals(text, 'Hello, world!') | |
16 | + | |
17 | + def test_html2text_with_br_tag(self): | |
18 | + html = '<a>Hello, world</a>!<br><p>Test with br tag</p>!' | |
19 | + text = self.email_block._html2text(html) | |
20 | + | |
21 | + self.assertEquals(text, 'Hello, world!\nTest with br tag!') | |
22 | + | |
23 | + def test_mark_links(self): | |
24 | + html = 'http://test.org/' | |
25 | + text = self.email_block._mark_links(html) | |
26 | + | |
27 | + self.assertEquals(text, '<a target="_blank" href=' + | |
28 | + '"http://test.org/">http://test.org/</a>') | ... | ... |
colab/super_archives/urls.py
... | ... | @@ -6,7 +6,7 @@ from .views import (EmailView, EmailValidationView, ThreadView, |
6 | 6 | |
7 | 7 | urlpatterns = patterns( |
8 | 8 | 'super_archives.views', |
9 | - url(r'thread/(?P<mailinglist>[-\w]+)/(?P<thread_token>[-\w]+)$', | |
9 | + url(r'thread/(?P<mailinglist>[-.\w]+)/(?P<thread_token>[-.\w]+)$', | |
10 | 10 | ThreadView.as_view(), name="thread_view"), |
11 | 11 | url(r'thread/$', ThreadDashboardView.as_view(), name='thread_list'), |
12 | 12 | url(r'manage/email/validate/?$', EmailValidationView.as_view(), | ... | ... |
colab/super_archives/utils/blocks.py
colab/super_archives/views.py
... | ... | @@ -103,7 +103,7 @@ class ThreadView(View): |
103 | 103 | } |
104 | 104 | |
105 | 105 | url = urlparse.urljoin(settings.MAILMAN_API_URL, |
106 | - mailinglist + '/sendmail') | |
106 | + 'sendmail/' + mailinglist) | |
107 | 107 | |
108 | 108 | error_msg = None |
109 | 109 | try: |
... | ... | @@ -277,14 +277,14 @@ class EmailValidationView(View): |
277 | 277 | try: |
278 | 278 | email = EmailAddressValidation.objects.get(address=email_addr, |
279 | 279 | user_id=user_id) |
280 | - except http.DoesNotExist: | |
280 | + except EmailAddressValidation.DoesNotExist: | |
281 | 281 | raise http.Http404 |
282 | 282 | |
283 | 283 | try: |
284 | 284 | location = reverse('archive_email_view', |
285 | 285 | kwargs={'key': email.validation_key}) |
286 | 286 | verification_url = request.build_absolute_uri(location) |
287 | - send_verification_email(request, email_addr, email.user, | |
287 | + send_verification_email(email_addr, email.user, | |
288 | 288 | email.validation_key, verification_url) |
289 | 289 | except smtplib.SMTPException: |
290 | 290 | logging.exception('Error sending validation email') | ... | ... |
colab/templates/base.html
... | ... | @@ -2,11 +2,12 @@ |
2 | 2 | {% load i18n gravatar plugins %} |
3 | 3 | {% load static from staticfiles %} |
4 | 4 | |
5 | +{% block html %} | |
5 | 6 | <html> |
6 | 7 | <head> |
7 | 8 | {% block head %} |
8 | 9 | <meta charset="UTF-8" /> |
9 | - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> | |
10 | + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=yes" /> | |
10 | 11 | {% block metarobots %} |
11 | 12 | {% if ROBOTS_NOINDEX %} |
12 | 13 | <meta name="robots" content="noindex, nofollow" /> |
... | ... | @@ -46,6 +47,11 @@ |
46 | 47 | <link rel="stylesheet" href="{% static 'css/screen.css' %}" |
47 | 48 | type="text/css" media="screen" /> |
48 | 49 | |
50 | + <link rel="stylesheet" href="{% static 'css/header.css' %}" | |
51 | + type="text/css" media="screen" /> | |
52 | + | |
53 | + <link rel="stylesheet" href="{% static 'css/footer.css' %}" | |
54 | + type="text/css" media="screen" /> | |
49 | 55 | {% endblock %} |
50 | 56 | </head> |
51 | 57 | |
... | ... | @@ -101,3 +107,4 @@ |
101 | 107 | {% block footer_js %}{% endblock %} |
102 | 108 | </body> |
103 | 109 | </html> |
110 | +{% endblock %} | ... | ... |
colab/templates/home.html
... | ... | @@ -22,7 +22,7 @@ |
22 | 22 | </a> |
23 | 23 | <ul class="message-list"> |
24 | 24 | {% for result in latest_results %} |
25 | - {% include "message-preview.html" %} | |
25 | + {% include "dashboard-message-preview.html" %} | |
26 | 26 | {% endfor %} |
27 | 27 | </ul> |
28 | 28 | <a class="column-align" |
... | ... | @@ -53,7 +53,7 @@ |
53 | 53 | </a> |
54 | 54 | <ul class="message-list"> |
55 | 55 | {% for thread in hottest_threads %} |
56 | - {% include "message-preview.html" with result=thread %} | |
56 | + {% include "dashboard-message-preview.html" with result=thread %} | |
57 | 57 | {% endfor %} |
58 | 58 | </ul> |
59 | 59 | <a href="{% url 'haystack_search' %}?type=thread"> |
... | ... | @@ -72,7 +72,7 @@ |
72 | 72 | </a> |
73 | 73 | <ul class="message-list"> |
74 | 74 | {% for thread in latest_threads %} |
75 | - {% include "message-preview.html" with result=thread.latest_message %} | |
75 | + {% include "dashboard-message-preview.html" with result=thread.latest_message %} | |
76 | 76 | {% endfor %} |
77 | 77 | </ul> |
78 | 78 | <a href="{% url 'haystack_search' %}?type=thread&order=latest"> | ... | ... |
colab/urls.py
... | ... | @@ -3,7 +3,7 @@ from django.conf import settings |
3 | 3 | from django.views.generic import TemplateView |
4 | 4 | from django.contrib import admin |
5 | 5 | from django.views.generic import RedirectView |
6 | - | |
6 | +from accounts.views import UserProfileUpdateView | |
7 | 7 | |
8 | 8 | admin.autodiscover() |
9 | 9 | |
... | ... | @@ -24,9 +24,3 @@ urlpatterns = patterns('', |
24 | 24 | |
25 | 25 | url(r'', include('colab.plugins.urls')), |
26 | 26 | ) |
27 | - | |
28 | -if settings.DEBUG: | |
29 | - urlpatterns += static.static( | |
30 | - settings.MEDIA_URL, | |
31 | - document_root=settings.MEDIA_ROOT | |
32 | - ) | ... | ... |
colab/utils/conf.py
... | ... | @@ -32,7 +32,7 @@ def _load_py_file(py_path, path): |
32 | 32 | try: |
33 | 33 | py_settings = importlib.import_module(py_path) |
34 | 34 | |
35 | - except IOError: | |
35 | + except ImportError: | |
36 | 36 | msg = ('Could not open settings file {}. Please ' |
37 | 37 | 'check if the file exists and if user ' |
38 | 38 | 'has read rights.').format(py_path) |
... | ... | @@ -49,12 +49,12 @@ def _load_py_file(py_path, path): |
49 | 49 | sys.path.remove(path) |
50 | 50 | |
51 | 51 | py_setting = {var: getattr(py_settings, var) for var in dir(py_settings) |
52 | - if not var.startswith('__')} | |
52 | + if not var.startswith('_')} | |
53 | 53 | |
54 | 54 | return py_setting |
55 | 55 | |
56 | 56 | |
57 | -def load_py_settings(): | |
57 | +def load_py_settings(settings_dir='/etc/colab/settings.d'): | |
58 | 58 | settings_file = os.getenv('COLAB_SETTINGS', '/etc/colab/settings.py') |
59 | 59 | settings_module = settings_file.split('.')[-2].split('/')[-1] |
60 | 60 | py_path = "/".join(settings_file.split('/')[:-1]) |
... | ... | @@ -67,8 +67,6 @@ def load_py_settings(): |
67 | 67 | |
68 | 68 | py_settings = _load_py_file(settings_module, py_path) |
69 | 69 | |
70 | - # Read settings from settings.d | |
71 | - settings_dir = '/etc/colab/settings.d' | |
72 | 70 | logger.info('Settings directory: %s', settings_dir) |
73 | 71 | |
74 | 72 | if not os.path.exists(settings_dir): |
... | ... | @@ -127,18 +125,47 @@ def load_colab_apps(): |
127 | 125 | |
128 | 126 | app_label = app_name.split('.')[-1] |
129 | 127 | COLAB_APPS[app_label] = {} |
130 | - COLAB_APPS[app_label]['menu_title'] = py_settings_d.get('menu_title') | |
128 | + COLAB_APPS[app_label] = py_settings_d | |
131 | 129 | |
132 | - fields = ['verbose_name', 'upstream', 'urls', | |
133 | - 'menu_urls', 'middlewares', 'dependencies', | |
134 | - 'context_processors', 'private_token', 'name', 'extra'] | |
130 | + return {'COLAB_APPS': COLAB_APPS} | |
135 | 131 | |
136 | - for key in fields: | |
137 | - value = py_settings_d.get(key) | |
138 | - if value: | |
139 | - COLAB_APPS[app_label][key] = value | |
140 | 132 | |
141 | - return {'COLAB_APPS': COLAB_APPS} | |
133 | +def load_widgets_settings(): | |
134 | + settings_file = os.getenv('COLAB_WIDGETS_SETTINGS', | |
135 | + '/etc/colab/widgets_settings.py') | |
136 | + settings_module = settings_file.split('.')[-2].split('/')[-1] | |
137 | + py_path = "/".join(settings_file.split('/')[:-1]) | |
138 | + logger.info('Widgets Settings file: %s', settings_file) | |
139 | + | |
140 | + if not os.path.exists(py_path): | |
141 | + return | |
142 | + | |
143 | + original_path = sys.path | |
144 | + sys.path.append(py_path) | |
145 | + | |
146 | + if os.path.exists(settings_file): | |
147 | + importlib.import_module(settings_module) | |
148 | + | |
149 | + # Read settings from widgets.d | |
150 | + settings_dir = os.getenv('COLAB_WIDGETS', '/etc/colab/widgets.d') | |
151 | + logger.info('Widgets Settings directory: %s', settings_dir) | |
152 | + sys.path = original_path | |
153 | + | |
154 | + if not os.path.exists(settings_dir): | |
155 | + return | |
156 | + | |
157 | + for file_name in os.listdir(settings_dir): | |
158 | + if not file_name.endswith('.py'): | |
159 | + continue | |
160 | + | |
161 | + original_path = sys.path | |
162 | + sys.path.append(settings_dir) | |
163 | + | |
164 | + file_module = file_name.split('.')[0] | |
165 | + importlib.import_module(file_module) | |
166 | + logger.info('Loaded %s/%s', settings_dir, file_name) | |
167 | + | |
168 | + sys.path = original_path | |
142 | 169 | |
143 | 170 | |
144 | 171 | def validate_database(database_dict, default_db, debug): | ... | ... |
... | ... | @@ -0,0 +1,56 @@ |
1 | + | |
2 | +# Set to false in production | |
3 | +DEBUG = True | |
4 | +TEMPLATE_DEBUG = False | |
5 | + | |
6 | +# System admins | |
7 | +ADMINS = [['John Foo', 'john@example.com'], ['Mary Bar', 'mary@example.com']] | |
8 | + | |
9 | +MANAGERS = ADMINS | |
10 | + | |
11 | +COLAB_FROM_ADDRESS = '"Colab" <noreply@example.com>' | |
12 | +SERVER_EMAIL = '"Colab" <noreply@example.com>' | |
13 | + | |
14 | +EMAIL_HOST = 'localhost' | |
15 | +EMAIL_PORT = 25 | |
16 | +EMAIL_SUBJECT_PREFIX = '[colab]' | |
17 | + | |
18 | +SECRET_KEY = 'not-a-secret' | |
19 | + | |
20 | +ALLOWED_HOSTS = [ | |
21 | + 'localhost', | |
22 | +] | |
23 | + | |
24 | +# Uncomment to enable social networks fields profile | |
25 | +SOCIAL_NETWORK_ENABLED = True | |
26 | + | |
27 | +# Disable indexing | |
28 | +ROBOTS_NOINDEX = True | |
29 | + | |
30 | +LOGGING = { | |
31 | + 'version': 1, | |
32 | + | |
33 | + 'handlers': { | |
34 | + 'null': { | |
35 | + 'level': 'DEBUG', | |
36 | + 'class': 'logging.NullHandler', | |
37 | + }, | |
38 | + }, | |
39 | + | |
40 | + 'loggers': { | |
41 | + 'colab.mailman': { | |
42 | + 'handlers': ['null'], | |
43 | + 'propagate': False, | |
44 | + }, | |
45 | + 'haystack': { | |
46 | + 'handlers': ['null'], | |
47 | + 'propagate': False, | |
48 | + }, | |
49 | + }, | |
50 | +} | |
51 | + | |
52 | +STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage' | |
53 | + | |
54 | +from colab.settings import INSTALLED_APPS | |
55 | + | |
56 | +INSTALLED_APPS += ('behave_django', ) | ... | ... |
... | ... | @@ -0,0 +1,15 @@ |
1 | +from colab.plugins.utils.menu import colab_url_factory | |
2 | + | |
3 | +name = "gitlab" | |
4 | +verbose_name = "Gitlab" | |
5 | + | |
6 | +upstream = 'https://localhost/gitlab/' | |
7 | +private_token = 'AVA8vrohDpoSws41zd1w' | |
8 | + | |
9 | +urls = { | |
10 | + "include": "gitlab.urls", | |
11 | + "prefix": 'gitlab/', | |
12 | + "namespace": "gitlab" | |
13 | +} | |
14 | + | |
15 | +url = colab_url_factory('gitlab') | ... | ... |
... | ... | @@ -0,0 +1,15 @@ |
1 | +from colab.plugins.utils.menu import colab_url_factory | |
2 | + | |
3 | +name = "noosfero" | |
4 | +verbose_name = "Noosfero" | |
5 | +private_token = "ef9a334177c620b68e75a89844e8a402" | |
6 | + | |
7 | +upstream = 'http://localhost/social/' | |
8 | + | |
9 | +urls = { | |
10 | + "include": "noosfero.urls", | |
11 | + "prefix": '^social/', | |
12 | + "namespace": "social" | |
13 | +} | |
14 | + | |
15 | +url = colab_url_factory('social') | ... | ... |
... | ... | @@ -0,0 +1 @@ |
1 | +) | ... | ... |
colab/utils/tests/test_conf.py
1 | +import sys | |
1 | 2 | |
2 | 3 | from django.test import TestCase, override_settings |
3 | 4 | from django.conf import settings |
4 | 5 | |
5 | -from ..conf import DatabaseUndefined, validate_database | |
6 | +from ..conf import (DatabaseUndefined, validate_database, | |
7 | + InaccessibleSettings, _load_py_file, load_py_settings, | |
8 | + load_colab_apps, load_widgets_settings) | |
9 | + | |
10 | +from mock import patch | |
11 | + | |
12 | + | |
13 | +test_files_dir = "./colab/utils/tests" | |
6 | 14 | |
7 | 15 | |
8 | 16 | class TestConf(TestCase): |
... | ... | @@ -16,3 +24,78 @@ class TestConf(TestCase): |
16 | 24 | with self.assertRaises(DatabaseUndefined): |
17 | 25 | validate_database(settings.DATABASES, settings.DEFAULT_DATABASE, |
18 | 26 | settings.DEBUG) |
27 | + | |
28 | + def test_load_py_file_with_io_error(self): | |
29 | + self.assertRaises(InaccessibleSettings, | |
30 | + _load_py_file, 'settings_test', '/etc/colab/') | |
31 | + | |
32 | + def test_load_py_file_with_syntax_error(self): | |
33 | + self.assertRaises(InaccessibleSettings, | |
34 | + _load_py_file, 'settings_with_syntax_error', | |
35 | + test_files_dir) | |
36 | + | |
37 | + def test_load_py_file(self): | |
38 | + py_settings = _load_py_file('colab_settings', test_files_dir) | |
39 | + | |
40 | + self.assertIn('SOCIAL_NETWORK_ENABLED', py_settings) | |
41 | + self.assertTrue(py_settings['SOCIAL_NETWORK_ENABLED']) | |
42 | + | |
43 | + self.assertIn('EMAIL_PORT', py_settings) | |
44 | + self.assertEquals(py_settings['EMAIL_PORT'], 25) | |
45 | + | |
46 | + @patch('os.getenv', return_value='/path/fake/settings.py') | |
47 | + def test_load_py_settings_with_inaccessible_settings(self, mock): | |
48 | + self.assertRaises(InaccessibleSettings, load_py_settings) | |
49 | + | |
50 | + @patch('os.getenv', return_value=test_files_dir + '/colab_settings.py') | |
51 | + def test_load_py_settings_without_settings_d(self, mock): | |
52 | + py_settings = load_py_settings('/path/fake/settings.d/test.py') | |
53 | + | |
54 | + self.assertIn('SOCIAL_NETWORK_ENABLED', py_settings) | |
55 | + self.assertTrue(py_settings['SOCIAL_NETWORK_ENABLED']) | |
56 | + | |
57 | + self.assertIn('EMAIL_PORT', py_settings) | |
58 | + self.assertEquals(py_settings['EMAIL_PORT'], 25) | |
59 | + | |
60 | + @patch('os.listdir', return_value=[test_files_dir + '/settings.d/test.py', | |
61 | + 'non_python_file']) | |
62 | + @patch('colab.utils.conf._load_py_file', | |
63 | + side_effect=[{'SOCIAL_NETWORK_ENABLED': True, 'EMAIL_PORT': 25}, | |
64 | + {'TEST': 'test'}]) | |
65 | + def test_load_py_settings_with_settings_d(self, mock_py, mock_listdir): | |
66 | + py_settings = load_py_settings(test_files_dir + '/settings.d/') | |
67 | + | |
68 | + self.assertIn('SOCIAL_NETWORK_ENABLED', py_settings) | |
69 | + self.assertTrue(py_settings['SOCIAL_NETWORK_ENABLED']) | |
70 | + | |
71 | + self.assertIn('EMAIL_PORT', py_settings) | |
72 | + self.assertEquals(py_settings['EMAIL_PORT'], 25) | |
73 | + | |
74 | + self.assertIn('TEST', py_settings) | |
75 | + self.assertEquals(py_settings['TEST'], 'test') | |
76 | + | |
77 | + @patch('os.getenv', return_value='/path/fake/plugins.d/') | |
78 | + def test_load_colab_apps_without_plugins_d_directory(self, mock): | |
79 | + colab_apps = load_colab_apps() | |
80 | + self.assertIn('COLAB_APPS', colab_apps) | |
81 | + self.assertEquals(colab_apps['COLAB_APPS'], {}) | |
82 | + | |
83 | + @patch('os.getenv', return_value=test_files_dir + '/plugins.d/') | |
84 | + def test_load_colab_apps_with_plugins_d_directory(self, os_getenv): | |
85 | + sys.path.insert(0, os_getenv.return_value) | |
86 | + colab_apps = load_colab_apps() | |
87 | + | |
88 | + self.assertIn('gitlab', colab_apps['COLAB_APPS']) | |
89 | + self.assertIn('noosfero', colab_apps['COLAB_APPS']) | |
90 | + sys.path.remove(os_getenv.return_value) | |
91 | + | |
92 | + self.assertNotIn(os_getenv.return_value, sys.path) | |
93 | + | |
94 | + @patch('os.getenv', return_value='/path/fake/widgets_settings.py') | |
95 | + def test_load_widgets_settings_without_settings(self, mock): | |
96 | + self.assertIsNone(load_widgets_settings()) | |
97 | + | |
98 | + @patch('os.getenv', side_effect=[test_files_dir + '/colab_settings.py', | |
99 | + '/path/fake/widgets_settings.py']) | |
100 | + def test_load_widgets_settings_without_settings_d(self, mock): | |
101 | + self.assertIsNone(load_widgets_settings()) | ... | ... |
... | ... | @@ -0,0 +1,15 @@ |
1 | +from django import template | |
2 | +from colab.widgets.widget_manager import WidgetManager | |
3 | + | |
4 | + | |
5 | +register = template.Library() | |
6 | + | |
7 | + | |
8 | +@register.simple_tag(takes_context=True) | |
9 | +def import_widgets(context, area_id, widget_var=None): | |
10 | + if not widget_var: | |
11 | + widget_var = "widgets_{}".format(area_id) | |
12 | + | |
13 | + context[widget_var] = WidgetManager.get_widgets(area_id, context=context) | |
14 | + | |
15 | + return "" | ... | ... |
... | ... | @@ -0,0 +1,98 @@ |
1 | +from django.test import TestCase | |
2 | + | |
3 | +from colab.widgets.widget_manager import WidgetManager, Widget | |
4 | + | |
5 | + | |
6 | +class WigetMock(Widget): | |
7 | + | |
8 | + def __init__(self, content=""): | |
9 | + self.content = content | |
10 | + | |
11 | + | |
12 | +class WidgetManagerTest(TestCase): | |
13 | + | |
14 | + html_content = "<head><meta charset='UTF-8'></head><body><p>T</p></body>" | |
15 | + widget_area = 'profile' | |
16 | + widget_id = 'widget_id' | |
17 | + | |
18 | + def ovewrited_widget_instance(self, content): | |
19 | + | |
20 | + class WidgetOverwrited(Widget): | |
21 | + identifier = 'widget_id' | |
22 | + | |
23 | + def generate_content(self, request=None): | |
24 | + self.content = content | |
25 | + return WidgetOverwrited() | |
26 | + | |
27 | + def default_widget_instance(self): | |
28 | + | |
29 | + class WidgetDefault(Widget): | |
30 | + pass | |
31 | + | |
32 | + return WidgetDefault() | |
33 | + | |
34 | + def setUp(self): | |
35 | + custom_widget = self.ovewrited_widget_instance(self.html_content) | |
36 | + WidgetManager.register_widget(self.widget_area, custom_widget) | |
37 | + | |
38 | + def tearDown(self): | |
39 | + WidgetManager.unregister_widget(self.widget_area, self.widget_id) | |
40 | + | |
41 | + def test_widget_default_values(self): | |
42 | + widget = self.default_widget_instance() | |
43 | + self.assertEqual(widget.identifier, None) | |
44 | + self.assertEqual(widget.name, None) | |
45 | + self.assertEqual(widget.content, '') | |
46 | + self.assertEqual(widget.template, '') | |
47 | + | |
48 | + def test_add_widgets_to_key_area(self): | |
49 | + self.assertEqual(len(WidgetManager.get_widgets(self.widget_area)), 1) | |
50 | + | |
51 | + def test_remove_widgets_in_key_area(self): | |
52 | + area = 'admin' | |
53 | + widget_instance = self.ovewrited_widget_instance(self.html_content) | |
54 | + | |
55 | + WidgetManager.register_widget(area, widget_instance) | |
56 | + WidgetManager.unregister_widget(area, self.widget_id) | |
57 | + | |
58 | + self.assertEqual(len(WidgetManager.get_widgets(area)), 0) | |
59 | + | |
60 | + def test_get_body(self): | |
61 | + custom_widget = self.ovewrited_widget_instance(self.html_content) | |
62 | + | |
63 | + custom_widget.generate_content() | |
64 | + self.assertEqual(custom_widget.get_body(), "<p>T</p>") | |
65 | + | |
66 | + def test_get_header(self): | |
67 | + custom_widget = self.ovewrited_widget_instance(self.html_content) | |
68 | + | |
69 | + custom_widget.generate_content() | |
70 | + self.assertEqual(custom_widget.get_header(), "<meta charset='UTF-8'>") | |
71 | + | |
72 | + def test_get_header_wrong(self): | |
73 | + widget = self.default_widget_instance() | |
74 | + widget.content = "<head> Teste <head>" | |
75 | + self.assertEqual(widget.get_header(), '') | |
76 | + | |
77 | + def test_get_body_wrong(self): | |
78 | + widget = self.default_widget_instance() | |
79 | + widget.content = "<body> Teste <body>" | |
80 | + self.assertEqual(widget.get_body(), '') | |
81 | + | |
82 | + def test_generate_content(self): | |
83 | + widgets = WidgetManager.get_widgets(self.widget_area) | |
84 | + self.assertEqual(widgets[0].content, self.html_content) | |
85 | + | |
86 | + def test_widget_with_invalid_area(self): | |
87 | + self.assertEqual(WidgetManager.get_widgets("area"), []) | |
88 | + | |
89 | + def test_generate_content_without_template(self): | |
90 | + widget = self.default_widget_instance() | |
91 | + with self.assertRaises(Exception): | |
92 | + widget.generate_content() | |
93 | + | |
94 | + def test_generate_content_with_template(self): | |
95 | + widget = self.default_widget_instance() | |
96 | + widget.template = self.html_content | |
97 | + with self.assertRaises(Exception): | |
98 | + widget.generate_content() | ... | ... |
... | ... | @@ -0,0 +1,37 @@ |
1 | +import unittest | |
2 | +from mock import patch | |
3 | + | |
4 | +from colab.widgets.templatetags.widgets_tag import import_widgets | |
5 | +from colab.widgets.widget_manager import WidgetManager, Widget | |
6 | +from django.template import Context | |
7 | + | |
8 | + | |
9 | +class WigetMock(Widget): | |
10 | + | |
11 | + def __init__(self, content=""): | |
12 | + self.content = content | |
13 | + | |
14 | + | |
15 | +class WidgetsTest(unittest.TestCase): | |
16 | + | |
17 | + @patch.object(WidgetManager, 'get_widgets') | |
18 | + def test_import_widgets_tag(self, get_widgets): | |
19 | + return_list = [WigetMock(), WigetMock(), WigetMock()] | |
20 | + get_widgets.return_value = return_list | |
21 | + | |
22 | + context = Context({'request': ""}) | |
23 | + import_widgets(context, 'area') | |
24 | + | |
25 | + self.assertIn('widgets_area', context) | |
26 | + self.assertEquals(context['widgets_area'], return_list) | |
27 | + | |
28 | + @patch.object(WidgetManager, 'get_widgets') | |
29 | + def test_import_widgets_tag_with_named_var(self, get_widgets): | |
30 | + return_list = [WigetMock(), WigetMock(), WigetMock()] | |
31 | + get_widgets.return_value = return_list | |
32 | + | |
33 | + context = Context({'request': ""}) | |
34 | + import_widgets(context, 'area', 'var') | |
35 | + | |
36 | + self.assertIn('var', context) | |
37 | + self.assertEquals(context['var'], return_list) | ... | ... |
... | ... | @@ -0,0 +1,65 @@ |
1 | +from django.utils.safestring import mark_safe | |
2 | +from django.template.loader import render_to_string | |
3 | + | |
4 | + | |
5 | +class Widget(object): | |
6 | + identifier = None | |
7 | + name = None | |
8 | + content = '' | |
9 | + template = '' | |
10 | + | |
11 | + def get_body(self): | |
12 | + # avoiding regex in favor of performance | |
13 | + start = self.content.find('<body>') | |
14 | + end = self.content.find('</body>') | |
15 | + | |
16 | + if -1 in [start, end]: | |
17 | + return '' | |
18 | + | |
19 | + body = self.content[start + len('<body>'):end] | |
20 | + return mark_safe(body) | |
21 | + | |
22 | + def get_header(self): | |
23 | + # avoiding regex in favor of performance | |
24 | + start = self.content.find('<head>') | |
25 | + end = self.content.find('</head>') | |
26 | + | |
27 | + if -1 in [start, end]: | |
28 | + return '' | |
29 | + | |
30 | + head = self.content[start + len('<head>'):end] | |
31 | + return mark_safe(head) | |
32 | + | |
33 | + def generate_content(self, **kwargs): | |
34 | + if not self.template: | |
35 | + class_name = self.__class__.__name__ | |
36 | + raise Exception("Template not defined in {}.".format(class_name)) | |
37 | + self.content = render_to_string(self.template, kwargs.get('context')) | |
38 | + | |
39 | + | |
40 | +class WidgetManager(object): | |
41 | + widget_categories = {} | |
42 | + | |
43 | + @staticmethod | |
44 | + def register_widget(category, widget): | |
45 | + if category not in WidgetManager.widget_categories: | |
46 | + WidgetManager.widget_categories[category] = [] | |
47 | + | |
48 | + WidgetManager.widget_categories[category].append(widget) | |
49 | + | |
50 | + @staticmethod | |
51 | + def unregister_widget(category, widget_identifier): | |
52 | + if category in WidgetManager.widget_categories: | |
53 | + for widget in WidgetManager.widget_categories[category]: | |
54 | + if widget.identifier == widget_identifier: | |
55 | + WidgetManager.widget_categories[category].remove(widget) | |
56 | + | |
57 | + @staticmethod | |
58 | + def get_widgets(category, **kwargs): | |
59 | + if category not in WidgetManager.widget_categories: | |
60 | + return [] | |
61 | + | |
62 | + widgets = WidgetManager.widget_categories[category][:] | |
63 | + for widget in widgets: | |
64 | + widget.generate_content(**kwargs) | |
65 | + return widgets | ... | ... |
No preview for this file type
docs/source/dev.rst
... | ... | @@ -4,3 +4,77 @@ Developer Documentation |
4 | 4 | Getting Started |
5 | 5 | --------------- |
6 | 6 | .. TODO |
7 | + | |
8 | +Widgets | |
9 | +------- | |
10 | + | |
11 | +A widget is a piece of HTML that will be inserted in a specific spot in a page to render some view. | |
12 | + | |
13 | +To create a new widget you need to extend the ``Widget`` class from ``colab.widgets``. In the child class you can override the methods below, but it is not mandatory: | |
14 | + | |
15 | +.. attribute:: get_header | |
16 | + | |
17 | + This method should return the HTML code to be used in the page's head. So, it will extract the head content from the ``content``. | |
18 | + | |
19 | +.. attribute:: get_body | |
20 | + | |
21 | + This method should return the HTML code to be used in the page's body. So, it will extract the body content from the ``content``. | |
22 | + | |
23 | +.. attribute:: generate_content | |
24 | + | |
25 | + This method will set the ``content`` when the widget is requested by the ``WidgetManager``. The ``content`` contains a HTML code that will be rendered in the target page. | |
26 | + | |
27 | +The Widget class has the following attributes: | |
28 | + | |
29 | +.. attribute:: identifier | |
30 | + | |
31 | + The identifier has to be a unique string across the widgets. | |
32 | + | |
33 | +.. attribute:: name | |
34 | + | |
35 | + The widget name is the string that will be used to render in the view, if needed. | |
36 | + | |
37 | +Example Widget: | |
38 | + | |
39 | +.. code-block:: python | |
40 | + | |
41 | + from colab.widgets.widget_manager import Widget | |
42 | + | |
43 | + class MyWidget(Widget): | |
44 | + identifier = 'my_widget_id' | |
45 | + name = 'My Widget' | |
46 | + def generate_content(self, **kwargs): | |
47 | + # process HTML content | |
48 | + self.content = processed_content | |
49 | + | |
50 | +To add the widget in a view check the Widgets section in User Documentation. | |
51 | +To use a widget in the templates, you have to use the ``import_widget`` tag inside the ``html`` block. | |
52 | +This way, the kwargs parameter will have a ``context`` key from the ``html``. | |
53 | +You can also set the variable that the widgets of an area will be imported. | |
54 | +Or you can use the default name, which is ``widgets_area_name``. | |
55 | +For example, in the ``profile`` area the variable name is ``widgets_profile``. | |
56 | +This variable will be inserted directly in the page ``context``. | |
57 | + | |
58 | +.. code-block:: python | |
59 | + | |
60 | + {% load widgets_tag %} | |
61 | + | |
62 | + {% block html %} | |
63 | + {% import_widgets 'profile' %} | |
64 | + {{ block.super }} | |
65 | + {% endblock %} | |
66 | + | |
67 | + {# example of how to use #} | |
68 | + {% block head %} | |
69 | + {{ block.super }} | |
70 | + | |
71 | + {% for widget in widgets_profile %} | |
72 | + {{ widget.get_header }} | |
73 | + {% endfor %} | |
74 | + | |
75 | + {% endblock %} | |
76 | + | |
77 | + | |
78 | +.. warning:: | |
79 | + | |
80 | + Warning! Remember to use the tag ``{{ block.super }}`` inside the html block. Otherwise, the page will appear blank. | ... | ... |
docs/source/plugindev.rst
... | ... | @@ -23,6 +23,8 @@ signals structure, some steps are required: |
23 | 23 | contain any special character, such as dot or comma. It is suggested to name |
24 | 24 | the variable "short_name", but that nomenclature is not strictly |
25 | 25 | necessary. |
26 | +* Finally, the variable namespace should also be defined. This variable is the | |
27 | + url namespace for django reverse. | |
26 | 28 | * In order to actually register the signals, it is necessary to implement the |
27 | 29 | method register_signal, which require the name of the plugin that is |
28 | 30 | registering the signals and a list of signals to be registered as parameters. |
... | ... | @@ -34,6 +36,7 @@ signals structure, some steps are required: |
34 | 36 | be seen below: |
35 | 37 | |
36 | 38 | .. code-block:: python |
39 | + | |
37 | 40 | from colab.celery import app |
38 | 41 | |
39 | 42 | @app.task(bind=True) |
... | ... | @@ -48,6 +51,7 @@ signals structure, some steps are required: |
48 | 51 | |
49 | 52 | |
50 | 53 | .. code-block:: python |
54 | + | |
51 | 55 | from colab.plugins.utils.apps import ColabPluginAppConfig |
52 | 56 | from colab.signals.signals import register_signal, connect_signal |
53 | 57 | from colab.plugins.PLUGIN.tasks import HANDLING_METHOD |
... | ... | @@ -55,6 +59,7 @@ signals structure, some steps are required: |
55 | 59 | class PluginApps(ColabPluginAppConfig): |
56 | 60 | short_name = PLUGIN_NAME |
57 | 61 | signals_list = [SIGNAL1, SIGNAL2] |
62 | + namespace = PLUGIN_NAMESPACE | |
58 | 63 | |
59 | 64 | def registered_signal(self): |
60 | 65 | register_signal(self.short_name, self.signals_list) |
... | ... | @@ -71,6 +76,7 @@ signals structure, some steps are required: |
71 | 76 | \*\*kwargs. As you can see below: |
72 | 77 | |
73 | 78 | .. code-block:: python |
79 | + | |
74 | 80 | from colab.signals.signals import send |
75 | 81 | |
76 | 82 | send(signal_name, sender) |
... | ... | @@ -78,4 +84,137 @@ signals structure, some steps are required: |
78 | 84 | * If you want to run celery manually to make some tests, you should execute: |
79 | 85 | |
80 | 86 | .. code-block:: shell |
81 | - celery -A colab worker --loglevel=debug | |
87 | + | |
88 | + colab-admin celeryC worker --loglevel=debug | |
89 | + | |
90 | +Search | |
91 | +---------- | |
92 | + | |
93 | +In order to make some plugin model's searchable, it is necessary to create | |
94 | +some files. First of all, it is necessary to create a directory named "search" | |
95 | +inside the templates directory, that should be found on the plugin root | |
96 | +directory. | |
97 | + | |
98 | +Once the search folder exist, it is necessary to create a html file that will | |
99 | +describe how a model item will be displayed on the colab search page. This file | |
100 | +name must follow the pattern: | |
101 | + | |
102 | +MODELNAME_search_preview.html | |
103 | + | |
104 | +Where the MODELNAME should be the name of the model object that will be | |
105 | +represented on the html file. An example for this file can be seen bellow: | |
106 | + | |
107 | +.. code-block:: guess | |
108 | + | |
109 | + {% load i18n tz highlight gravatar date_format %} | |
110 | + | |
111 | + <div class="row"> | |
112 | + <div class="col-md-2 center"> | |
113 | + <a href="{% url 'user_profile' username=result.username %}"> | |
114 | + {% block gravatar_img %}{% gravatar result.email 100 %}{% endblock gravatar_img %} | |
115 | + </a> | |
116 | + </div> | |
117 | + <div class="col-md-10"> | |
118 | + <strong><a href="{% url 'user_profile' username=result.username %}"> | |
119 | + | |
120 | + {% if query %} | |
121 | + <h4>{% highlight result.name with query %}</h4></a> | |
122 | + {% else %} | |
123 | + <h4>{{ result.name }}</h4></a> | |
124 | + {% endif %} | |
125 | + | |
126 | + </strong> | |
127 | + <small><strong>{% trans "Since" %}: {% date_format result.date_joined %}</strong></small><br> | |
128 | + <small>{% trans "Registered in" %}: <strong>{% trans "User" %}</strong></small><br> | |
129 | + </div> | |
130 | + </div> | |
131 | + <div class="row"> | |
132 | + <hr> | |
133 | + </div> | |
134 | + | |
135 | +As can be seen in the above example, it also possible to highlight the elements being searched. This can be seen on | |
136 | +the following example: | |
137 | + | |
138 | +.. code-block:: html | |
139 | + | |
140 | + {% if query %} | |
141 | + <h4>{% highlight result.name with query %}</h4></a> | |
142 | + {% else %} | |
143 | + <h4>{{ result.name }}</h4></a> | |
144 | + {% endif %} | |
145 | + | |
146 | +It can be seen that if a query text was used on the search, it will highlight the element if it is present on the query, if not, | |
147 | +the element will be displayed without a highlight. Therefore, in order to highlight some fields, it is necessary | |
148 | +to first check if there is a query search. If there is, use the tag "highlight" before the field name. However, it | |
149 | +must be said that the highlight tag should be followed by a complement, such as "with query", as can be seen on the example | |
150 | +above. This complement is used to allow the highlight only if the attribute is actually present on the query used to perform a search. | |
151 | + | |
152 | +Also a another file that must be created is the search_index.py one. This file | |
153 | +must be placed at the plugin root directory. This file dictates how haystack | |
154 | +will index the plugins models. If there is any doubt about how to create this | |
155 | +file, it's possible to check the official haystack documentation that can be | |
156 | +seen on the bellow link. | |
157 | + | |
158 | +`Guide to create a SearchIndexesFiles`_ | |
159 | + | |
160 | +.. _`Guide to create a SearchIndexesFiles`: http://django-haystack.readthedocs.org/en/v2.4.0/tutorial.html#creating-searchindexes | |
161 | + | |
162 | +It can also be seen in the guide above that an indexes directory should be | |
163 | +created. This directory should be placed inside the search directory originally | |
164 | +created in this tutorial. Inside this directory, create a txt file for each | |
165 | +model that can be queried. Each of this files must contain the model fields that | |
166 | +will be search if no filter is applied. If there is any doubts to create these | |
167 | +files, please check the `Guide to create a SearchIndexesFiles`_. | |
168 | + | |
169 | +Storing TimeStamp | |
170 | +--------------- | |
171 | +TimeStamp is a parameter to control the last time a model was updated, you should use it | |
172 | +when you want the data updated after a given time. To do that the colab's model (colab.plugins.models) have a | |
173 | +TimeStampPlugin class, used to store all last updates from timestamp from all plugins. | |
174 | + | |
175 | +Class Methods: | |
176 | + update_timestamp(cls,class_name): allow store a current datetime. | |
177 | + | |
178 | + get_last_updated_timestamp(cls,class_name): allow get a datetime with last timestamp stored from class_name. | |
179 | + | |
180 | +Example Usage: | |
181 | + | |
182 | +.. code-block:: python | |
183 | + from colab.plugins.models import TimeStampPlugin | |
184 | + | |
185 | + class TestPlugin(): | |
186 | + | |
187 | + def update_timestamp(self): | |
188 | + TimeStampPlugin.update_timestamp('TestPlugin') | |
189 | + | |
190 | + def get_last_updated_timestamp(self): | |
191 | + return TimeStampPlugin.get_last_updated_timestamp('TestPlugin') | |
192 | + | |
193 | + | |
194 | +Password Validation | |
195 | +------------------- | |
196 | + | |
197 | +Allows the plugin to define rules to set the password. The validators | |
198 | +are functions which receive the password as only argument and if it | |
199 | +doesn't match the desired rules raises a `ValidationError`. The message | |
200 | +sent in the validation error will be displayed to user in the HTML form. | |
201 | + | |
202 | +Example: | |
203 | + | |
204 | +.. code-block:: python | |
205 | + | |
206 | + ## myplugin/password_validators.py | |
207 | + | |
208 | + def has_uppercase_char(password): | |
209 | + for char in password: | |
210 | + if char.isupper(): | |
211 | + return | |
212 | + | |
213 | + raise ValidationError('Password must have at least one upper case char') | |
214 | + | |
215 | + ## /etc/colab/plugins.d/myplugin.py | |
216 | + | |
217 | + password_validators = ( | |
218 | + 'myplugin.password_validators.has_uppercase_char', | |
219 | + ) | |
220 | + | ... | ... |
docs/source/user.rst
... | ... | @@ -58,6 +58,27 @@ View the following file: |
58 | 58 | |
59 | 59 | The file /etc/colab/settings.py have the configurations of colab, this configurations overrides the django settings.py |
60 | 60 | |
61 | +Widgets | |
62 | +------- | |
63 | + | |
64 | +A widget is a piece of HTML that will be inserted in a specific spot in a page to render some view. | |
65 | + | |
66 | +To configure the widgets you have to edit, or create, the file ``/etc/colab/widgets_settings.py``. Or you can create a py file inside the folder ``/etc/colab/widgets.d``. | |
67 | + | |
68 | +Example: | |
69 | + | |
70 | +.. code-block:: python | |
71 | + | |
72 | + # Widget Manager handles all widgets and must be imported to register them | |
73 | + from colab.widgets.widget_manager import WidgetManager | |
74 | + | |
75 | + # Specific code for Gitlab's Widget | |
76 | + from colab_gitlab.widgets import GitlabProfileWidget | |
77 | + | |
78 | + WidgetManager.register_widget('profile', GitlabProfileWidget()) | |
79 | + | |
80 | + | |
81 | +In this example the Gitlab's widget is added in a new tab inside the user profile. | |
61 | 82 | |
62 | 83 | Add a new plugin |
63 | 84 | ---------------- |
... | ... | @@ -65,7 +86,7 @@ Add a new plugin |
65 | 86 | |
66 | 87 | - Make sure the application has the following requirements |
67 | 88 | |
68 | - - Suport for remote user authentication | |
89 | + - Support for remote user authentication | |
69 | 90 | |
70 | 91 | - A relative url root |
71 | 92 | |
... | ... | @@ -77,6 +98,8 @@ Add a new plugin |
77 | 98 | |
78 | 99 | - create file: [plugin_name].py |
79 | 100 | |
101 | +- Atention: Any URL used in the plugins' settings should not be preceded by "/" | |
102 | + | |
80 | 103 | Use this template for the plugin configuration file |
81 | 104 | |
82 | 105 | .. code-block:: python |
... | ... | @@ -95,7 +118,6 @@ Use this template for the plugin configuration file |
95 | 118 | |
96 | 119 | urls = { |
97 | 120 | 'include': '[plugin_module_path].urls', |
98 | - 'namespace': '[plugin_name]', | |
99 | 121 | 'prefix': '[application_prefix]/', # Exemple: http://site.com/[application_prefix]/ |
100 | 122 | } |
101 | 123 | |
... | ... | @@ -104,10 +126,10 @@ Use this template for the plugin configuration file |
104 | 126 | url = colab_url_factory('[plugin_name]') |
105 | 127 | |
106 | 128 | menu_urls = { |
107 | - url(display=_('[name_of_link_page]'), viewname='[name_of_view_in_the_application]', kwargs={'path': '/[page_appication_path]/' }, auth=True), | |
129 | + url(display=_('[name_of_link_page]'), viewname='[name_of_view_in_the_application]', kwargs={'path': '[page_appication_path]/' }, auth=True), | |
108 | 130 | |
109 | 131 | # You can have more than one url |
110 | - url(display=_('[name_of_link_page]'), viewname='[another_name_of_view_in_the_application]', kwargs={'path': '/[another_page_appication_path]/' }, auth=True), | |
132 | + url(display=_('[name_of_link_page]'), viewname='[another_name_of_view_in_the_application]', kwargs={'path': '[another_page_appication_path]/' }, auth=True), | |
111 | 133 | } |
112 | 134 | |
113 | 135 | |
... | ... | @@ -143,6 +165,15 @@ Declares the additional installed apps that this plugin depends on. |
143 | 165 | This doesn't automatically install the python dependecies, only add to django |
144 | 166 | apps. |
145 | 167 | |
168 | +.. attribute:: password_validators | |
169 | + | |
170 | +A lista of functions to validade password in the moment it's set. | |
171 | +This allows plugins to define their own password validators. For | |
172 | +example if the proxied app requires the password to have at least | |
173 | +one upper case character it should provide a password validator | |
174 | +for that. | |
175 | + | |
176 | + | |
146 | 177 | urls |
147 | 178 | ++++ |
148 | 179 | |
... | ... | @@ -152,9 +183,8 @@ urls |
152 | 183 | .. attribute:: prefix |
153 | 184 | |
154 | 185 | Declares the prefix for the url. |
155 | -.. attribute:: namespace | |
156 | 186 | |
157 | - Declares the namespace for the url. | |
187 | + - Atention: Any URL used in the plugins' settings should not be preceded by "/" | |
158 | 188 | |
159 | 189 | menu |
160 | 190 | ++++ |
... | ... | @@ -164,7 +194,7 @@ These variables defines the menu title and links of the plugin. |
164 | 194 | .. attribute:: menu_title |
165 | 195 | |
166 | 196 | Declares the menu title. |
167 | -.. attribute:: menu_links | |
197 | +.. attribute:: menu_urls | |
168 | 198 | |
169 | 199 | Declares the menu items and its links. |
170 | 200 | This should be a tuple object with several colab_url elements. |
... | ... | @@ -172,6 +202,9 @@ These variables defines the menu title and links of the plugin. |
172 | 202 | namespace. |
173 | 203 | The auth parameter indicates wether the link should only be displayed when |
174 | 204 | the user is logged in. |
205 | + The ``kwargs`` parameter receives a dict, where the key ``path`` should be | |
206 | + a path URL to the page. Remember that this path is a URL, therefore it | |
207 | + should never be preceded by "/". | |
175 | 208 | |
176 | 209 | Example: |
177 | 210 | |
... | ... | @@ -182,8 +215,8 @@ Example: |
182 | 215 | url = colab_url_factory('plugin_app_name') |
183 | 216 | |
184 | 217 | menu_urls = ( |
185 | - url(display=_('Profile'), viewname='profile', kwargs={'path': '/profile/'}, auth=True), | |
186 | - url(display=_('Profile Two'), viewname='profile2', kwargs={'path': '/profile/2'}, auth=True), | |
218 | + url(display=_('Profile'), viewname='profile', kwargs={'path': 'profile/'}, auth=True), | |
219 | + url(display=_('Profile Two'), viewname='profile2', kwargs={'path': 'profile/2'}, auth=True), | |
187 | 220 | ) |
188 | 221 | |
189 | 222 | Extra Template Folders | ... | ... |
setup.cfg
tests/run.py
... | ... | @@ -5,7 +5,9 @@ import sys |
5 | 5 | |
6 | 6 | os.environ['DJANGO_SETTINGS_MODULE'] = 'colab.settings' |
7 | 7 | os.environ['COLAB_SETTINGS'] = 'tests/colab_settings.py' |
8 | +os.environ['COLAB_WIDGETS_SETTINGS'] = 'tests/widgets_settings.py' | |
8 | 9 | os.environ['COLAB_PLUGINS'] = 'tests/plugins.d' |
10 | +os.environ['COLAB_WIDGETS'] = 'tests/widgets.d' | |
9 | 11 | os.environ['COVERAGE_PROCESS_START'] = '.coveragerc' |
10 | 12 | |
11 | 13 | |
... | ... | @@ -14,23 +16,34 @@ import coverage |
14 | 16 | |
15 | 17 | from django.conf import settings |
16 | 18 | from django.test.utils import get_runner |
19 | +import colab.settings | |
17 | 20 | |
18 | 21 | |
19 | -def runtests(): | |
22 | +def runtests(test_suites=[]): | |
20 | 23 | if django.VERSION >= (1, 7, 0): |
21 | 24 | django.setup() |
22 | 25 | |
23 | 26 | test_runner = get_runner(settings) |
24 | - failures = test_runner(interactive=False, failfast=False).run_tests([]) | |
27 | + failures = test_runner(interactive=False, failfast=False).run_tests( | |
28 | + test_suites) | |
25 | 29 | sys.exit(failures) |
26 | 30 | |
27 | 31 | |
28 | -def run_with_coverage(): | |
32 | +def run_with_coverage(test_suites=[]): | |
29 | 33 | if os.path.exists('.coverage'): |
30 | 34 | os.remove('.coverage') |
31 | 35 | coverage.process_startup() |
32 | - runtests() | |
36 | + runtests(test_suites) | |
33 | 37 | |
34 | 38 | |
35 | 39 | if __name__ == '__main__': |
36 | - run_with_coverage() | |
40 | + all_valid_apps = True | |
41 | + | |
42 | + for arg in sys.argv[1:]: | |
43 | + if arg not in colab.settings.INSTALLED_APPS: | |
44 | + print arg + " App not found" | |
45 | + print "Try colab." + arg | |
46 | + all_valid_apps = False | |
47 | + | |
48 | + if all_valid_apps: | |
49 | + run_with_coverage(sys.argv[1:]) | ... | ... |