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:]) | ... | ... |