Commit 317676c503ceb27170037ab2f18aa9baa770d021

Authored by Lucas Kanashiro
2 parents 9eb1b7a9 91d859bb

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
@@ -7,6 +7,8 @@ omit = @@ -7,6 +7,8 @@ omit =
7 */urls.py 7 */urls.py
8 */settings.py 8 */settings.py
9 */tests.py 9 */tests.py
  10 + colab/celery.py
  11 + colab/wsgi.py
10 source = 12 source =
11 colab/ 13 colab/
12 14
colab/accounts/filters.py 0 → 100644
@@ -0,0 +1,24 @@ @@ -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 # -*- coding: utf-8 -*- 1 # -*- coding: utf-8 -*-
2 2
3 -from collections import OrderedDict 3 +from importlib import import_module
4 4
5 from django import forms 5 from django import forms
6 from django.conf import settings 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 from django.core.urlresolvers import reverse 10 from django.core.urlresolvers import reverse
12 -from django.template import loader  
13 -from django.utils.encoding import force_bytes  
14 from django.utils.functional import lazy 11 from django.utils.functional import lazy
15 -from django.utils.http import urlsafe_base64_encode  
16 -from django.utils.text import capfirst  
17 from django.utils.translation import ugettext_lazy as _ 12 from django.utils.translation import ugettext_lazy as _
18 from django.utils.safestring import mark_safe 13 from django.utils.safestring import mark_safe
19 14
20 - 15 +from .signals import user_created
21 from .utils.validators import validate_social_account 16 from .utils.validators import validate_social_account
22 from .utils import mailman 17 from .utils import mailman
23 18
@@ -168,7 +163,41 @@ class ListsForm(forms.Form): @@ -168,7 +163,41 @@ class ListsForm(forms.Form):
168 choices=lazy(get_lists_choices, list)()) 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 A form that creates a user, with no privileges, from the given username and 202 A form that creates a user, with no privileges, from the given username and
174 password. 203 password.
@@ -181,12 +210,13 @@ class UserCreationForm(UserForm): @@ -181,12 +210,13 @@ class UserCreationForm(UserForm):
181 'password_mismatch': _("The two password fields didn't match."), 210 'password_mismatch': _("The two password fields didn't match."),
182 } 211 }
183 username = forms.RegexField(label=_("Username"), max_length=30, 212 username = forms.RegexField(label=_("Username"), max_length=30,
184 - regex=r'^[\w.@+-]+$', 213 + regex=r'^[\w]+$',
185 help_text=_(("Required. 30 characters or fewer" 214 help_text=_(("Required. 30 characters or fewer"
186 ". Letter and digits.")), 215 ". Letter and digits.")),
187 error_messages={ 216 error_messages={
188 'invalid': _(("This value may contain only" 217 'invalid': _(("This value may contain only"
189 " letters and numbers."))}) 218 " letters and numbers."))})
  219 +
190 password1 = forms.CharField(label=_("Password"), 220 password1 = forms.CharField(label=_("Password"),
191 widget=forms.PasswordInput) 221 widget=forms.PasswordInput)
192 password2 = forms.CharField(label=_("Password confirmation"), 222 password2 = forms.CharField(label=_("Password confirmation"),
@@ -238,19 +268,26 @@ class UserCreationForm(UserForm): @@ -238,19 +268,26 @@ class UserCreationForm(UserForm):
238 self.error_messages['password_mismatch'], 268 self.error_messages['password_mismatch'],
239 code='password_mismatch', 269 code='password_mismatch',
240 ) 270 )
  271 +
  272 + super(UserCreationForm, self).clean_password2()
241 return password2 273 return password2
242 274
243 def save(self, commit=True): 275 def save(self, commit=True):
244 user = super(UserCreationForm, self).save(commit=False) 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 if commit: 280 if commit:
247 user.save() 281 user.save()
  282 +
  283 + user_created.send(user.__class__, user=user, password=password)
  284 +
248 return user 285 return user
249 286
250 287
251 class UserChangeForm(forms.ModelForm): 288 class UserChangeForm(forms.ModelForm):
252 username = forms.RegexField( 289 username = forms.RegexField(
253 - label=_("Username"), max_length=30, regex=r"^[\w*]", 290 + label=_("Username"), max_length=30, regex=r'^[\w]+$',
254 help_text=_("Required. 30 characters or fewer. Letters and digits."), 291 help_text=_("Required. 30 characters or fewer. Letters and digits."),
255 error_messages={ 292 error_messages={
256 'invalid': _("This value may contain only letters and numbers.")}) 293 'invalid': _("This value may contain only letters and numbers.")})
@@ -286,234 +323,9 @@ class UserChangeForm(forms.ModelForm): @@ -286,234 +323,9 @@ class UserChangeForm(forms.ModelForm):
286 return self.initial["password"] 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,18 +2,26 @@
2 2
3 import urlparse 3 import urlparse
4 4
5 -from django.db import models  
6 from django.contrib.auth.models import AbstractUser, UserManager 5 from django.contrib.auth.models import AbstractUser, UserManager
7 -from django.core import validators  
8 from django.core.urlresolvers import reverse 6 from django.core.urlresolvers import reverse
  7 +from django.db import models
9 from django.utils.crypto import get_random_string 8 from django.utils.crypto import get_random_string
10 from django.utils.translation import ugettext_lazy as _ 9 from django.utils.translation import ugettext_lazy as _
11 10
  11 +from .signals import user_created, user_password_changed
12 from .utils import mailman 12 from .utils import mailman
13 13
14 14
15 class ColabUserManager(UserManager): 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 def create_user(self, username, email=None, password=None, **extra_fields): 25 def create_user(self, username, email=None, password=None, **extra_fields):
18 26
19 # It creates a valid password for users 27 # It creates a valid password for users
@@ -68,6 +76,11 @@ class User(AbstractUser): @@ -68,6 +76,11 @@ class User(AbstractUser):
68 self.username = self.username.lower() 76 self.username = self.username.lower()
69 super(User, self).save(*args, **kwargs) 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 # We need to have `email` field set as unique but Django does not 85 # We need to have `email` field set as unique but Django does not
73 # support field overriding (at least not until 1.6). 86 # support field overriding (at least not until 1.6).
@@ -77,8 +90,3 @@ User._meta.get_field('email')._unique = True @@ -77,8 +90,3 @@ User._meta.get_field('email')._unique = True
77 User._meta.get_field('username').help_text = _( 90 User._meta.get_field('username').help_text = _(
78 u'Required. 30 characters or fewer. Letters and digits.' 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,6 +22,7 @@ class UserIndex(indexes.SearchIndex, indexes.Indexable):
22 google_talk = indexes.CharField(model_attr='google_talk', null=True, 22 google_talk = indexes.CharField(model_attr='google_talk', null=True,
23 stored=False) 23 stored=False)
24 webpage = indexes.CharField(model_attr='webpage', null=True, stored=False) 24 webpage = indexes.CharField(model_attr='webpage', null=True, stored=False)
  25 + date_joined = indexes.DateTimeField(model_attr='date_joined')
25 26
26 def get_model(self): 27 def get_model(self):
27 return User 28 return User
colab/accounts/signals.py 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +
  2 +from django.dispatch import Signal
  3 +
  4 +
  5 +user_created = Signal(providing_args=['user', 'password'])
  6 +user_password_changed = Signal(providing_args=['user', 'password'])
colab/accounts/templates/accounts/manage_subscriptions.html
@@ -12,7 +12,7 @@ @@ -12,7 +12,7 @@
12 12
13 <div class="row"> 13 <div class="row">
14 {% for email, lists in membership.items %} 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 <div class="panel panel-default"> 16 <div class="panel panel-default">
17 <div class="panel-heading"> 17 <div class="panel-heading">
18 <h3 class="panel-title">{{ email }}</h3> 18 <h3 class="panel-title">{{ email }}</h3>
colab/accounts/templates/accounts/user_detail.html
@@ -133,7 +133,7 @@ @@ -133,7 +133,7 @@
133 <h3>{% trans "Latest posted" %} </h3> 133 <h3>{% trans "Latest posted" %} </h3>
134 <ul class="message-list"> 134 <ul class="message-list">
135 {% for doc in emails %} 135 {% for doc in emails %}
136 - {% include "message-preview.html" with result=doc %} 136 + {% include "dashboard-message-preview.html" with result=doc %}
137 {% empty %} 137 {% empty %}
138 <li>{% trans "There are no posts by this user so far." %}</li> 138 <li>{% trans "There are no posts by this user so far." %}</li>
139 {% endfor %} 139 {% endfor %}
@@ -148,7 +148,7 @@ @@ -148,7 +148,7 @@
148 <h3>{% trans "Latest contributions" %}</h3> 148 <h3>{% trans "Latest contributions" %}</h3>
149 <ul class="message-list"> 149 <ul class="message-list">
150 {% for result in results %} 150 {% for result in results %}
151 - {% include "message-preview.html" %} 151 + {% include "dashboard-message-preview.html" %}
152 {% empty %} 152 {% empty %}
153 <li>{% trans "No contributions of this user so far." %}</li> 153 <li>{% trans "No contributions of this user so far." %}</li>
154 {% endfor %} 154 {% endfor %}
colab/accounts/templates/accounts/user_update_form.html
1 {% extends "base.html" %} 1 {% extends "base.html" %}
2 -{% load i18n gravatar %} 2 +{% load i18n gravatar plugins widgets_tag %}
3 3
4 {% block head_js %} 4 {% block head_js %}
5 <script> 5 <script>
@@ -102,7 +102,6 @@ $(function() { @@ -102,7 +102,6 @@ $(function() {
102 </script> 102 </script>
103 {% endblock %} 103 {% endblock %}
104 104
105 -  
106 {% block main-content %} 105 {% block main-content %}
107 106
108 <div class="col-lg-12"> 107 <div class="col-lg-12">
@@ -118,6 +117,17 @@ $(function() { @@ -118,6 +117,17 @@ $(function() {
118 <br> 117 <br>
119 <br> 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 <form method="post"> 131 <form method="post">
122 {% csrf_token %} 132 {% csrf_token %}
123 133
@@ -192,9 +202,13 @@ $(function() { @@ -192,9 +202,13 @@ $(function() {
192 </div> 202 </div>
193 </div> 203 </div>
194 <div class="row"> 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 </div> 210 </div>
198 </div> 211 </div>
199 </form> 212 </form>
  213 +
200 {% endblock %} 214 {% endblock %}
colab/accounts/templates/search/user_search_preview.html 0 → 100644
@@ -0,0 +1,25 @@ @@ -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>
colab/accounts/templatetags/date_format.py 0 → 100644
@@ -0,0 +1,20 @@ @@ -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,11 +3,51 @@ Test Form class.
3 Objective: Test parameters, and behavior. 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 from django.core.urlresolvers import reverse 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 from colab.accounts.models import User 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 class FormTest(TestCase): 53 class FormTest(TestCase):
@@ -24,26 +64,274 @@ class FormTest(TestCase): @@ -24,26 +64,274 @@ class FormTest(TestCase):
24 user.last_name = "COLAB" 64 user.last_name = "COLAB"
25 user.save() 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 'first_name': 'colabName', 72 'first_name': 'colabName',
30 'last_name': 'secondName', 73 'last_name': 'secondName',
31 - 'username': 'colab', 74 + 'username': username,
32 'password1': '123colab4', 75 'password1': '123colab4',
33 'password2': '123colab4'} 76 'password2': '123colab4'}
34 form = UserCreationForm(data=form_data) 77 form = UserCreationForm(data=form_data)
35 return form 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 def test_already_registered_email(self): 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 self.assertFalse(form.is_valid()) 123 self.assertFalse(form.is_valid())
  124 + self.assertIn('duplicate_email', form.error_messages)
40 125
41 def test_registered_email_message(self): 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 msg = form.error_messages.get('duplicate_email') % { 129 msg = form.error_messages.get('duplicate_email') % {
44 'url': reverse('login') 130 'url': reverse('login')
45 } 131 }
46 self.assertIn(msg, str(form)) 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 +6,8 @@ Objective: Test requests.
6 from django.test import TestCase, Client 6 from django.test import TestCase, Client
7 from django.test.client import RequestFactory 7 from django.test.client import RequestFactory
8 from colab.accounts.models import User 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 class RequestTest(TestCase): 13 class RequestTest(TestCase):
@@ -65,3 +67,26 @@ class RequestTest(TestCase): @@ -65,3 +67,26 @@ class RequestTest(TestCase):
65 self.assertEqual(302, response.status_code) 67 self.assertEqual(302, response.status_code)
66 self.assertEqual("http://testserver/account/usertest/subscriptions", 68 self.assertEqual("http://testserver/account/usertest/subscriptions",
67 response.url) 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,7 +43,7 @@ class UserTest(TestCase):
43 expected_last_name, first_name, last_name): 43 expected_last_name, first_name, last_name):
44 data = {'first_name': first_name, 44 data = {'first_name': first_name,
45 'last_name': last_name} 45 'last_name': last_name}
46 - self.client.post('/account/usertestcolab/edit', data) 46 + self.client.post('/account/' + self.user.username + '/edit', data)
47 user = User.objects.get(id=1) 47 user = User.objects.get(id=1)
48 self.assertEqual(expected_first_name, user.first_name) 48 self.assertEqual(expected_first_name, user.first_name)
49 self.assertEqual(expected_last_name, user.last_name) 49 self.assertEqual(expected_last_name, user.last_name)
@@ -52,7 +52,7 @@ class UserTest(TestCase): @@ -52,7 +52,7 @@ class UserTest(TestCase):
52 data = {'first_name': 'usertestcolab', 52 data = {'first_name': 'usertestcolab',
53 'last_name': 'colab', 53 'last_name': 'colab',
54 field_name: value} 54 field_name: value}
55 - self.client.post('/account/usertestcolab/edit', data) 55 + self.client.post('/account/' + self.user.username + '/edit', data)
56 user = User.objects.get(id=1) 56 user = User.objects.get(id=1)
57 self.assertEqual(expected_value, getattr(user, field_name)) 57 self.assertEqual(expected_value, getattr(user, field_name))
58 58
@@ -77,10 +77,6 @@ class UserTest(TestCase): @@ -77,10 +77,6 @@ class UserTest(TestCase):
77 empty_list = () 77 empty_list = ()
78 self.assertEqual(empty_list, self.user.mailinglists()) 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 def test_save(self): 80 def test_save(self):
85 username_test = "USERtestCoLaB" 81 username_test = "USERtestCoLaB"
86 82
@@ -374,3 +370,78 @@ class UserTest(TestCase): @@ -374,3 +370,78 @@ class UserTest(TestCase):
374 self.authenticate_user() 370 self.authenticate_user()
375 self.validate_non_mandatory_fields('bio', '', ' ') 371 self.validate_non_mandatory_fields('bio', '', ' ')
376 self.user.delete() 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&#39;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')
colab/accounts/tests/test_user_subscription.py 0 → 100644
@@ -0,0 +1,91 @@ @@ -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)
colab/accounts/tests/test_utils_validators.py 0 → 100644
@@ -0,0 +1,19 @@ @@ -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/tests/utils.py 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +from django.forms import ValidationError
  2 +
  3 +
  4 +def password_validator(password):
  5 + raise ValidationError('Test error')
colab/accounts/urls.py
1 1
2 from django.conf import settings 2 from django.conf import settings
3 from django.conf.urls import patterns, url 3 from django.conf.urls import patterns, url
  4 +from django.contrib.auth import views as auth_views
4 5
5 from .views import (UserProfileDetailView, UserProfileUpdateView, 6 from .views import (UserProfileDetailView, UserProfileUpdateView,
6 ManageUserSubscriptionsView) 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 urlpatterns = patterns('', 11 urlpatterns = patterns('',
@@ -22,15 +21,17 @@ urlpatterns = patterns(&#39;&#39;, @@ -22,15 +21,17 @@ urlpatterns = patterns(&#39;&#39;,
22 21
23 url(r'^password-reset-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$', 22 url(r'^password-reset-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$',
24 auth_views.password_reset_confirm, 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 name="password_reset_confirm"), 26 name="password_reset_confirm"),
27 27
28 url(r'^password-reset/?$', auth_views.password_reset, 28 url(r'^password-reset/?$', auth_views.password_reset,
29 {'template_name':'registration/password_reset_form_custom.html'}, 29 {'template_name':'registration/password_reset_form_custom.html'},
30 name="password_reset"), 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 name='password_change'), 35 name='password_change'),
35 36
36 url(r'^change-password-done/?$', 37 url(r'^change-password-done/?$',
colab/accounts/widgets/__init__.py 0 → 100644
colab/home/context_processors.py
@@ -16,7 +16,7 @@ def ribbon(request): @@ -16,7 +16,7 @@ def ribbon(request):
16 if not enabled: 16 if not enabled:
17 return {'ribbon': False} 17 return {'ribbon': False}
18 18
19 - url = 'http://beta.softwarepublico.gov.br/gitlab/softwarepublico/colab' 19 + url = 'http://github.com/colab/colab'
20 text = _('Fork me!') 20 text = _('Fork me!')
21 21
22 return { 22 return {
colab/locale/en/LC_MESSAGES/django.mo
No preview for this file type
colab/locale/en/LC_MESSAGES/django.po
1 # SOME DESCRIPTIVE TITLE. 1 # SOME DESCRIPTIVE TITLE.
2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 # This file is distributed under the same license as the PACKAGE package. 3 # This file is distributed under the same license as the PACKAGE package.
4 -# 4 +#
5 # Translators: 5 # Translators:
6 msgid "" 6 msgid ""
7 msgstr "" 7 msgstr ""
8 "Project-Id-Version: colab\n" 8 "Project-Id-Version: colab\n"
9 "Report-Msgid-Bugs-To: \n" 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 "PO-Revision-Date: 2015-09-04 19:21+0000\n" 11 "PO-Revision-Date: 2015-09-04 19:21+0000\n"
12 "Last-Translator: Lucas Kanashiro Duarte <kanashiro.duarte@gmail.com>\n" 12 "Last-Translator: Lucas Kanashiro Duarte <kanashiro.duarte@gmail.com>\n"
13 "Language-Team: English (http://www.transifex.com/colab/colab/language/en/)\n" 13 "Language-Team: English (http://www.transifex.com/colab/colab/language/en/)\n"
  14 +"Language: en\n"
14 "MIME-Version: 1.0\n" 15 "MIME-Version: 1.0\n"
15 "Content-Type: text/plain; charset=UTF-8\n" 16 "Content-Type: text/plain; charset=UTF-8\n"
16 "Content-Transfer-Encoding: 8bit\n" 17 "Content-Transfer-Encoding: 8bit\n"
17 -"Language: en\n"  
18 "Plural-Forms: nplurals=2; plural=(n != 1);\n" 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 msgid "Personal info" 45 msgid "Personal info"
22 msgstr "Personal info" 46 msgstr "Personal info"
23 47
24 -#: accounts/admin.py:24 48 +#: colab/accounts/admin.py:24
25 msgid "Permissions" 49 msgid "Permissions"
26 msgstr "Permissions" 50 msgstr "Permissions"
27 51
28 -#: accounts/admin.py:28 52 +#: colab/accounts/admin.py:28
29 msgid "Important dates" 53 msgid "Important dates"
30 msgstr "Important dates" 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 msgid "Social account does not exist" 83 msgid "Social account does not exist"
34 msgstr "Social account does not exist" 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 msgid "This field cannot be blank." 88 msgid "This field cannot be blank."
38 msgstr "This field cannot be blank." 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 msgid "Bio" 93 msgid "Bio"
42 msgstr "Bio" 94 msgstr "Bio"
43 95
44 -#: accounts/forms.py:119 96 +#: colab/accounts/forms.py:112
45 msgid "Write something about you in 200 characters or less." 97 msgid "Write something about you in 200 characters or less."
46 msgstr "Write something about you in 200 characters or less." 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 msgid "Mailing lists" 101 msgid "Mailing lists"
50 msgstr "Mailing lists" 102 msgstr "Mailing lists"
51 103
52 -#: accounts/forms.py:161 104 +#: colab/accounts/forms.py:170
53 #, python-format 105 #, python-format
54 msgid "Email already used. Is it you? Please <a href='%(url)s'>login</a>" 106 msgid "Email already used. Is it you? Please <a href='%(url)s'>login</a>"
55 msgstr "Email already used. Is it you? Please <a href='%(url)s'>login</a>" 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 msgid "A user with that username already exists." 110 msgid "A user with that username already exists."
59 msgstr "A user with that username already exists." 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 msgid "The two password fields didn't match." 114 msgid "The two password fields didn't match."
63 msgstr "The two password fields didn't match." 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 msgid "Password" 118 msgid "Password"
72 msgstr "Password" 119 msgstr "Password"
73 120
74 -#: accounts/forms.py:175 121 +#: colab/accounts/forms.py:185
75 msgid "Password confirmation" 122 msgid "Password confirmation"
76 msgstr "Password confirmation" 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 msgid "Required. 30 characters or fewer. Letters and digits." 126 msgid "Required. 30 characters or fewer. Letters and digits."
80 msgstr "Required. 30 characters or fewer. Letters and digits." 127 msgstr "Required. 30 characters or fewer. Letters and digits."
81 128
82 -#: accounts/forms.py:239 129 +#: colab/accounts/forms.py:249
83 msgid "This value may contain only letters and numbers." 130 msgid "This value may contain only letters and numbers."
84 msgstr "This value may contain only letters and numbers." 131 msgstr "This value may contain only letters and numbers."
85 132
86 -#: accounts/forms.py:241 133 +#: colab/accounts/forms.py:252
87 msgid "" 134 msgid ""
88 "Raw passwords are not stored, so there is no way to see this user's " 135 "Raw passwords are not stored, so there is no way to see this user's "
89 "password, but you can change the password using <a href=\"password/\">this " 136 "password, but you can change the password using <a href=\"password/\">this "
90 "form</a>." 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 msgid "Group Subscriptions" 144 msgid "Group Subscriptions"
134 msgstr "Group Subscriptions" 145 msgstr "Group Subscriptions"
135 146
136 -#: accounts/templates/accounts/manage_subscriptions.html:36 147 +#: colab/accounts/templates/accounts/manage_subscriptions.html:36
137 msgid "Update subscriptions" 148 msgid "Update subscriptions"
138 msgstr "Update subscriptions" 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 msgid "Sign up" 152 msgid "Sign up"
142 msgstr "Sign up" 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 msgid "Please correct the errors below and try again." 160 msgid "Please correct the errors below and try again."
150 msgstr "Please correct the errors below and try again." 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 msgid "Required fields" 164 msgid "Required fields"
154 msgstr "Required fields" 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 msgid "Personal Information" 168 msgid "Personal Information"
158 msgstr "Personal Information" 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 msgid "Subscribe to groups" 172 msgid "Subscribe to groups"
162 msgstr "Subscribe to groups" 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 msgid "Register" 177 msgid "Register"
168 msgstr "Register" 178 msgstr "Register"
169 179
170 -#: accounts/templates/accounts/user_detail.html:8 180 +#: colab/accounts/templates/accounts/user_detail.html:8
171 msgid "Messages" 181 msgid "Messages"
172 msgstr "Messages" 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 msgid "Contributions" 186 msgid "Contributions"
176 msgstr "Contributions" 187 msgstr "Contributions"
177 188
178 -#: accounts/templates/accounts/user_detail.html:29 189 +#: colab/accounts/templates/accounts/user_detail.html:29
179 msgid "edit profile" 190 msgid "edit profile"
180 msgstr "edit profile" 191 msgstr "edit profile"
181 192
182 -#: accounts/templates/accounts/user_detail.html:30 193 +#: colab/accounts/templates/accounts/user_detail.html:30
183 msgid "group membership" 194 msgid "group membership"
184 msgstr "group membership" 195 msgstr "group membership"
185 196
186 -#: accounts/templates/accounts/user_detail.html:66 197 +#: colab/accounts/templates/accounts/user_detail.html:66
187 msgid "Twitter account" 198 msgid "Twitter account"
188 msgstr "Twitter account" 199 msgstr "Twitter account"
189 200
190 -#: accounts/templates/accounts/user_detail.html:69 201 +#: colab/accounts/templates/accounts/user_detail.html:69
191 msgid "Facebook account" 202 msgid "Facebook account"
192 msgstr "Facebook account" 203 msgstr "Facebook account"
193 204
194 -#: accounts/templates/accounts/user_detail.html:74 205 +#: colab/accounts/templates/accounts/user_detail.html:74
195 msgid "Google talk account" 206 msgid "Google talk account"
196 msgstr "Google talk account" 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 msgid "Github account" 210 msgid "Github account"
200 msgstr "Github account" 211 msgstr "Github account"
201 212
202 -#: accounts/templates/accounts/user_detail.html:82 213 +#: colab/accounts/templates/accounts/user_detail.html:82
203 msgid "Personal webpage" 214 msgid "Personal webpage"
204 msgstr "Personal webpage" 215 msgstr "Personal webpage"
205 216
206 -#: accounts/templates/accounts/user_detail.html:88 217 +#: colab/accounts/templates/accounts/user_detail.html:88
207 msgid "Groups: " 218 msgid "Groups: "
208 msgstr "Groups: " 219 msgstr "Groups: "
209 220
210 -#: accounts/templates/accounts/user_detail.html:101 221 +#: colab/accounts/templates/accounts/user_detail.html:101
211 msgid "Collaborations by Type" 222 msgid "Collaborations by Type"
212 msgstr "Collaborations by Type" 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 msgid "Participation by Group" 226 msgid "Participation by Group"
216 msgstr "Participation by Group" 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 msgid "Latest posted" 230 msgid "Latest posted"
220 msgstr "Latest posted" 231 msgstr "Latest posted"
221 232
222 -#: accounts/templates/accounts/user_detail.html:138 233 +#: colab/accounts/templates/accounts/user_detail.html:138
223 msgid "There are no posts by this user so far." 234 msgid "There are no posts by this user so far."
224 msgstr "There are no posts by this user so far." 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 msgid "View more posts..." 238 msgid "View more posts..."
228 msgstr "View more posts..." 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 msgid "Latest contributions" 242 msgid "Latest contributions"
232 msgstr "Latest contributions" 243 msgstr "Latest contributions"
233 244
234 -#: accounts/templates/accounts/user_detail.html:153 245 +#: colab/accounts/templates/accounts/user_detail.html:153
235 msgid "No contributions of this user so far." 246 msgid "No contributions of this user so far."
236 msgstr "No contributions of this user so far." 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 msgid "View more contributions..." 250 msgid "View more contributions..."
240 msgstr "View more contributions..." 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 msgid "We sent a verification email to " 254 msgid "We sent a verification email to "
244 msgstr "We sent a verification email to " 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 msgid "Please follow the instructions in it." 258 msgid "Please follow the instructions in it."
248 msgstr "Please follow the instructions in it." 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 msgid "profile information" 262 msgid "profile information"
252 msgstr "profile information" 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 msgid "Change your avatar at Gravatar.com" 266 msgid "Change your avatar at Gravatar.com"
256 msgstr "Change your avatar at Gravatar.com" 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 msgid "Emails" 271 msgid "Emails"
260 msgstr "Emails" 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 msgid "Primary" 275 msgid "Primary"
264 msgstr "Primary" 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 msgid "Setting..." 279 msgid "Setting..."
268 msgstr "Setting..." 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 msgid "Set as Primary" 283 msgid "Set as Primary"
272 msgstr "Set as Primary" 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 msgid "Deleting..." 287 msgid "Deleting..."
276 msgstr "Deleting..." 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 msgid "Delete" 292 msgid "Delete"
281 msgstr "Delete" 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 msgid "Sending verification..." 296 msgid "Sending verification..."
285 msgstr "Sending verification..." 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 msgid "Verify" 300 msgid "Verify"
289 msgstr "Verify" 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 msgid "Add another email address:" 304 msgid "Add another email address:"
293 msgstr "Add another email address:" 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 msgid "Add" 308 msgid "Add"
297 msgstr "Add" 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 msgid "Change Password" 315 msgid "Change Password"
304 msgstr "Change Password" 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 msgid "Update" 319 msgid "Update"
308 msgstr "Update" 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 msgid "Please correct the error below and try again." 329 msgid "Please correct the error below and try again."
314 msgstr "Please correct the error below and try again." 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 msgid "Login" 336 msgid "Login"
323 msgstr "Login" 337 msgstr "Login"
324 338
325 -#: accounts/templates/registration/login.html:56 339 +#: colab/accounts/templates/registration/login.html:56
326 msgid "Forgot Password?" 340 msgid "Forgot Password?"
327 msgstr "Forgot Password?" 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 msgid "Change my password" 345 msgid "Change my password"
332 msgstr "Change my password" 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 msgid "Setting New password" 349 msgid "Setting New password"
336 msgstr "Setting New password" 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 msgid "" 353 msgid ""
340 "Forgotten your password? Enter your email address below, and we'll email " 354 "Forgotten your password? Enter your email address below, and we'll email "
341 "instructions for setting a new one." 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 msgid "Email address:" 361 msgid "Email address:"
346 msgstr "Email address:" 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 msgid "Reset password" 365 msgid "Reset password"
350 msgstr "Reset password" 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 msgid "Your profile has been created!" 399 msgid "Your profile has been created!"
354 msgstr "Your profile has been created!" 400 msgstr "Your profile has been created!"
355 401
356 -#: accounts/views.py:186 402 +#: colab/accounts/views.py:186
357 msgid "Your password was changed." 403 msgid "Your password was changed."
358 msgstr "Your password was changed." 404 msgstr "Your password was changed."
359 405
360 -#: accounts/views.py:202 406 +#: colab/accounts/views.py:202
361 msgid "Your password has been set. You may go ahead and log in now." 407 msgid "Your password has been set. You may go ahead and log in now."
362 msgstr "Your password has been set. You may go ahead and log in now." 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 msgid "Fork me!" 411 msgid "Fork me!"
366 msgstr "Fork me!" 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 msgid "Latest Discussions" 415 msgid "Latest Discussions"
422 msgstr "Latest Discussions" 416 msgstr "Latest Discussions"
423 417
424 -#: rss/feeds.py:32 418 +#: colab/rss/feeds.py:32
425 msgid "Discussions Most Relevance" 419 msgid "Discussions Most Relevance"
426 msgstr "Discussions Most Relevance" 420 msgstr "Discussions Most Relevance"
427 421
428 -#: rss/feeds.py:51 422 +#: colab/rss/feeds.py:51
429 msgid "Latest collaborations" 423 msgid "Latest collaborations"
430 msgstr "Latest collaborations" 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 msgid "Search" 428 msgid "Search"
435 msgstr "Search" 429 msgstr "Search"
436 430
437 -#: search/forms.py:18 431 +#: colab/search/forms.py:18
438 msgid "Type" 432 msgid "Type"
439 msgstr "Type" 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 msgid "Until" 440 msgid "Until"
513 msgstr "Until" 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 msgid "Remove filter" 447 msgid "Remove filter"
536 msgstr "Remove filter" 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 msgid "Filter" 453 msgid "Filter"
542 msgstr "Filter" 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 msgid "Sort by" 457 msgid "Sort by"
546 msgstr "Sort by" 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 msgid "Types" 461 msgid "Types"
550 msgstr "Types" 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 msgid "search" 465 msgid "search"
558 msgstr "search" 466 msgstr "search"
559 467
560 -#: search/templates/search/search.html:51 468 +#: colab/search/templates/search/search.html:44
561 msgid "documents found" 469 msgid "documents found"
562 msgstr "documents found" 470 msgstr "documents found"
563 471
564 -#: search/templates/search/search.html:62 472 +#: colab/search/templates/search/search.html:55
565 msgid "Search here" 473 msgid "Search here"
566 msgstr "Search here" 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 msgid "Filters" 477 msgid "Filters"
571 msgstr "Filters" 478 msgstr "Filters"
572 479
573 -#: search/templates/search/search.html:105 480 +#: colab/search/templates/search/search.html:76
574 msgid "No results for your search." 481 msgid "No results for your search."
575 msgstr "No results for your search." 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 msgid "You are searching for" 485 msgid "You are searching for"
579 msgstr "You are searching for" 486 msgstr "You are searching for"
580 487
581 -#: settings.py:113 488 +#: colab/settings.py:111
582 msgid "English" 489 msgid "English"
583 msgstr "English" 490 msgstr "English"
584 491
585 -#: settings.py:114 492 +#: colab/settings.py:112
586 msgid "Portuguese" 493 msgid "Portuguese"
587 msgstr "Portuguese" 494 msgstr "Portuguese"
588 495
589 -#: settings.py:115 496 +#: colab/settings.py:113
590 msgid "Spanish" 497 msgid "Spanish"
591 msgstr "Spanish" 498 msgstr "Spanish"
592 499
593 -#: settings.py:138 500 +#: colab/settings.py:136
594 msgid "Recent activity" 501 msgid "Recent activity"
595 msgstr "Recent activity" 502 msgstr "Recent activity"
596 503
597 -#: settings.py:142 504 +#: colab/settings.py:140
598 msgid "Relevance" 505 msgid "Relevance"
599 msgstr "Relevance" 506 msgstr "Relevance"
600 507
601 -#: settings.py:151 508 +#: colab/settings.py:149
602 msgid "Document" 509 msgid "Document"
603 msgstr "Document" 510 msgstr "Document"
604 511
605 -#: settings.py:153 512 +#: colab/settings.py:151
606 msgid "Presentation" 513 msgid "Presentation"
607 msgstr "Presentation" 514 msgstr "Presentation"
608 515
609 -#: settings.py:154 516 +#: colab/settings.py:152
610 msgid "Text" 517 msgid "Text"
611 msgstr "Text" 518 msgstr "Text"
612 519
613 -#: settings.py:155 520 +#: colab/settings.py:153
614 msgid "Code" 521 msgid "Code"
615 msgstr "Code" 522 msgstr "Code"
616 523
617 -#: settings.py:157 524 +#: colab/settings.py:155
618 msgid "Compressed" 525 msgid "Compressed"
619 msgstr "Compressed" 526 msgstr "Compressed"
620 527
621 -#: settings.py:158 528 +#: colab/settings.py:156
622 msgid "Image" 529 msgid "Image"
623 msgstr "Image" 530 msgstr "Image"
624 531
625 -#: settings.py:160 532 +#: colab/settings.py:158
626 msgid "Spreadsheet" 533 msgid "Spreadsheet"
627 msgstr "Spreadsheet" 534 msgstr "Spreadsheet"
628 535
629 -#: templates/404.html:5 536 +#: colab/templates/404.html:5
630 msgid "Not found. Keep searching! :)" 537 msgid "Not found. Keep searching! :)"
631 msgstr "Not found. Keep searching! :)" 538 msgstr "Not found. Keep searching! :)"
632 539
633 -#: templates/500.html:2 540 +#: colab/templates/500.html:2
634 msgid "Ooopz... something went wrong!" 541 msgid "Ooopz... something went wrong!"
635 msgstr "Ooopz... something went wrong!" 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 msgid "The login has failed. Please, try again." 545 msgid "The login has failed. Please, try again."
657 msgstr "The login has failed. Please, try again." 546 msgstr "The login has failed. Please, try again."
658 547
659 -#: templates/footer.html:6 548 +#: colab/templates/footer.html:6
660 msgid "Last email imported at" 549 msgid "Last email imported at"
661 msgstr "Last email imported at" 550 msgstr "Last email imported at"
662 551
663 -#: templates/footer.html:12 552 +#: colab/templates/footer.html:12
664 msgid "The contents of this site is published under license" 553 msgid "The contents of this site is published under license"
665 msgstr "The contents of this site is published under license" 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 msgid "Creative Commons 4.0 Brasil - Atribuir Fonte - Compartilhar Igual" 557 msgid "Creative Commons 4.0 Brasil - Atribuir Fonte - Compartilhar Igual"
669 msgstr "Creative Commons 4.0 Brasil - Atribuir Fonte - Compartilhar Igual" 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 msgid "Latest Collaborations" 579 msgid "Latest Collaborations"
677 msgstr "Latest Collaborations" 580 msgstr "Latest Collaborations"
678 581
679 -#: templates/home.html:21 582 +#: colab/templates/home.html:21
680 msgid "RSS - Latest collaborations" 583 msgid "RSS - Latest collaborations"
681 msgstr "RSS - Latest collaborations" 584 msgstr "RSS - Latest collaborations"
682 585
683 -#: templates/home.html:30 586 +#: colab/templates/home.html:30
684 msgid "View more collaborations..." 587 msgid "View more collaborations..."
685 msgstr "View more collaborations..." 588 msgstr "View more collaborations..."
686 589
687 -#: templates/home.html:37 590 +#: colab/templates/home.html:37
688 msgid "Collaboration Graph" 591 msgid "Collaboration Graph"
689 msgstr "Collaboration Graph" 592 msgstr "Collaboration Graph"
690 593
691 -#: templates/home.html:48 594 +#: colab/templates/home.html:48
692 msgid "Most Relevant Threads" 595 msgid "Most Relevant Threads"
693 msgstr "Most Relevant Threads" 596 msgstr "Most Relevant Threads"
694 597
695 -#: templates/home.html:52 598 +#: colab/templates/home.html:52
696 msgid "RSS - Most Relevant Threads" 599 msgid "RSS - Most Relevant Threads"
697 msgstr "RSS - Most Relevant Threads" 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 msgid "View more discussions..." 603 msgid "View more discussions..."
701 msgstr "View more discussions..." 604 msgstr "View more discussions..."
702 605
703 -#: templates/home.html:67 606 +#: colab/templates/home.html:67
704 msgid "Latest Threads" 607 msgid "Latest Threads"
705 msgstr "Latest Threads" 608 msgstr "Latest Threads"
706 609
707 -#: templates/home.html:71 610 +#: colab/templates/home.html:71
708 msgid "RSS - Latest Threads" 611 msgid "RSS - Latest Threads"
709 msgstr "RSS - Latest Threads" 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 # SOME DESCRIPTIVE TITLE. 1 # SOME DESCRIPTIVE TITLE.
2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 # This file is distributed under the same license as the PACKAGE package. 3 # This file is distributed under the same license as the PACKAGE package.
4 -# 4 +#
5 # Translators: 5 # Translators:
6 # Lucas Kanashiro Duarte <kanashiro.duarte@gmail.com>, 2015 6 # Lucas Kanashiro Duarte <kanashiro.duarte@gmail.com>, 2015
7 # macártur de sousa carvalho <macartur.sc@gmail.com>, 2015 7 # macártur de sousa carvalho <macartur.sc@gmail.com>, 2015
@@ -10,703 +10,740 @@ msgid &quot;&quot; @@ -10,703 +10,740 @@ msgid &quot;&quot;
10 msgstr "" 10 msgstr ""
11 "Project-Id-Version: colab\n" 11 "Project-Id-Version: colab\n"
12 "Report-Msgid-Bugs-To: \n" 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 "PO-Revision-Date: 2015-09-07 19:13+0000\n" 14 "PO-Revision-Date: 2015-09-07 19:13+0000\n"
15 "Last-Translator: Lucas Kanashiro Duarte <kanashiro.duarte@gmail.com>\n" 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 "MIME-Version: 1.0\n" 19 "MIME-Version: 1.0\n"
18 "Content-Type: text/plain; charset=UTF-8\n" 20 "Content-Type: text/plain; charset=UTF-8\n"
19 "Content-Transfer-Encoding: 8bit\n" 21 "Content-Transfer-Encoding: 8bit\n"
20 -"Language: pt_BR\n"  
21 "Plural-Forms: nplurals=2; plural=(n > 1);\n" 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 msgid "Personal info" 49 msgid "Personal info"
25 msgstr "Informações Pessoais" 50 msgstr "Informações Pessoais"
26 51
27 -#: accounts/admin.py:24 52 +#: colab/accounts/admin.py:24
28 msgid "Permissions" 53 msgid "Permissions"
29 msgstr "Permissões" 54 msgstr "Permissões"
30 55
31 -#: accounts/admin.py:28 56 +#: colab/accounts/admin.py:28
32 msgid "Important dates" 57 msgid "Important dates"
33 msgstr "Datas importantes" 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 msgid "Social account does not exist" 87 msgid "Social account does not exist"
37 msgstr "Conta social não existe" 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 msgid "This field cannot be blank." 92 msgid "This field cannot be blank."
41 msgstr "Este campo não pode ficar em branco." 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 msgid "Bio" 97 msgid "Bio"
45 msgstr "Bio" 98 msgstr "Bio"
46 99
47 -#: accounts/forms.py:119 100 +#: colab/accounts/forms.py:112
48 msgid "Write something about you in 200 characters or less." 101 msgid "Write something about you in 200 characters or less."
49 msgstr "Escreva algo sobre você em 200 caracteres ou menos." 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 msgid "Mailing lists" 105 msgid "Mailing lists"
53 msgstr "Listas de e-mail" 106 msgstr "Listas de e-mail"
54 107
55 -#: accounts/forms.py:161 108 +#: colab/accounts/forms.py:170
56 #, python-format 109 #, python-format
57 msgid "Email already used. Is it you? Please <a href='%(url)s'>login</a>" 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 msgid "A user with that username already exists." 116 msgid "A user with that username already exists."
62 msgstr "Já existe um usuário com este nome." 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 msgid "The two password fields didn't match." 120 msgid "The two password fields didn't match."
66 msgstr "Os dois campos de senha não conferem." 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 msgid "Password" 124 msgid "Password"
75 msgstr "Senha" 125 msgstr "Senha"
76 126
77 -#: accounts/forms.py:175 127 +#: colab/accounts/forms.py:185
78 msgid "Password confirmation" 128 msgid "Password confirmation"
79 msgstr "Confirmação de senha" 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 msgid "Required. 30 characters or fewer. Letters and digits." 132 msgid "Required. 30 characters or fewer. Letters and digits."
83 msgstr "Obrigatório. 30 caracteres ou menos. Letras ou digitos." 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 msgid "This value may contain only letters and numbers." 136 msgid "This value may contain only letters and numbers."
87 msgstr "Este campo pode conter apenas letras e números." 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 msgid "" 140 msgid ""
91 "Raw passwords are not stored, so there is no way to see this user's " 141 "Raw passwords are not stored, so there is no way to see this user's "
92 "password, but you can change the password using <a href=\"password/\">this " 142 "password, but you can change the password using <a href=\"password/\">this "
93 "form</a>." 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 msgid "Group Subscriptions" 150 msgid "Group Subscriptions"
137 msgstr "Inscrições em grupos" 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 msgid "Update subscriptions" 154 msgid "Update subscriptions"
141 msgstr "Atualizar inscrições" 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 msgid "Sign up" 158 msgid "Sign up"
145 msgstr "Cadastrar" 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 msgid "Please correct the errors below and try again." 166 msgid "Please correct the errors below and try again."
153 msgstr "Por favor corrija os erros abaixo e tente novamente." 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 msgid "Required fields" 170 msgid "Required fields"
157 msgstr "Campos obrigatórios" 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 msgid "Personal Information" 174 msgid "Personal Information"
161 msgstr "Informações pessoais" 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 msgid "Subscribe to groups" 178 msgid "Subscribe to groups"
165 msgstr "Inscreva-se nos grupos" 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 msgid "Register" 183 msgid "Register"
171 msgstr "Cadastre-se" 184 msgstr "Cadastre-se"
172 185
173 -#: accounts/templates/accounts/user_detail.html:8 186 +#: colab/accounts/templates/accounts/user_detail.html:8
174 msgid "Messages" 187 msgid "Messages"
175 msgstr "Mensagens" 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 msgid "Contributions" 192 msgid "Contributions"
179 msgstr "Contribuições" 193 msgstr "Contribuições"
180 194
181 -#: accounts/templates/accounts/user_detail.html:29 195 +#: colab/accounts/templates/accounts/user_detail.html:29
182 msgid "edit profile" 196 msgid "edit profile"
183 msgstr "editar perfil" 197 msgstr "editar perfil"
184 198
185 -#: accounts/templates/accounts/user_detail.html:30 199 +#: colab/accounts/templates/accounts/user_detail.html:30
186 msgid "group membership" 200 msgid "group membership"
187 msgstr "Inscrições nos grupos" 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 msgid "Twitter account" 204 msgid "Twitter account"
191 msgstr "Conta Twitter" 205 msgstr "Conta Twitter"
192 206
193 -#: accounts/templates/accounts/user_detail.html:69 207 +#: colab/accounts/templates/accounts/user_detail.html:69
194 msgid "Facebook account" 208 msgid "Facebook account"
195 msgstr "Conta Facebook" 209 msgstr "Conta Facebook"
196 210
197 -#: accounts/templates/accounts/user_detail.html:74 211 +#: colab/accounts/templates/accounts/user_detail.html:74
198 msgid "Google talk account" 212 msgid "Google talk account"
199 msgstr "Conta Google talk" 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 msgid "Github account" 216 msgid "Github account"
203 msgstr "Conta Github" 217 msgstr "Conta Github"
204 218
205 -#: accounts/templates/accounts/user_detail.html:82 219 +#: colab/accounts/templates/accounts/user_detail.html:82
206 msgid "Personal webpage" 220 msgid "Personal webpage"
207 msgstr "Página web pessoal" 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 msgid "Groups: " 224 msgid "Groups: "
211 msgstr "Grupos: " 225 msgstr "Grupos: "
212 226
213 -#: accounts/templates/accounts/user_detail.html:101 227 +#: colab/accounts/templates/accounts/user_detail.html:101
214 msgid "Collaborations by Type" 228 msgid "Collaborations by Type"
215 msgstr "Colaborações por tipo" 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 msgid "Participation by Group" 232 msgid "Participation by Group"
219 msgstr "Participação por grupo" 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 msgid "Latest posted" 236 msgid "Latest posted"
223 msgstr "Últimas postagens" 237 msgstr "Últimas postagens"
224 238
225 -#: accounts/templates/accounts/user_detail.html:138 239 +#: colab/accounts/templates/accounts/user_detail.html:138
226 msgid "There are no posts by this user so far." 240 msgid "There are no posts by this user so far."
227 msgstr "Não há posts deste usuário até agora." 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 msgid "View more posts..." 244 msgid "View more posts..."
231 msgstr "Ver mais postagens..." 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 msgid "Latest contributions" 248 msgid "Latest contributions"
235 msgstr "Últimas colaborações" 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 msgid "No contributions of this user so far." 252 msgid "No contributions of this user so far."
239 msgstr "Não há contribuições deste usuário até agora." 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 msgid "View more contributions..." 256 msgid "View more contributions..."
243 msgstr "Ver mais colaborações..." 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 msgid "We sent a verification email to " 260 msgid "We sent a verification email to "
247 msgstr "Enviamos um email de verificação para " 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 msgid "Please follow the instructions in it." 264 msgid "Please follow the instructions in it."
251 msgstr "Por favor, siga as instruções." 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 msgid "profile information" 268 msgid "profile information"
255 msgstr "informações do perfil" 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 msgid "Change your avatar at Gravatar.com" 272 msgid "Change your avatar at Gravatar.com"
259 msgstr "Troque seu avatar em Gravatar.com" 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 msgid "Emails" 277 msgid "Emails"
263 msgstr "Emails" 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 msgid "Primary" 281 msgid "Primary"
267 msgstr "Primário" 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 msgid "Setting..." 285 msgid "Setting..."
271 msgstr "Definindo..." 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 msgid "Set as Primary" 289 msgid "Set as Primary"
275 msgstr "Definir como Primário" 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 msgid "Deleting..." 293 msgid "Deleting..."
279 msgstr "Apagando..." 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 msgid "Delete" 298 msgid "Delete"
284 msgstr "Apagar" 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 msgid "Sending verification..." 302 msgid "Sending verification..."
288 msgstr "Enviando verificação..." 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 msgid "Verify" 306 msgid "Verify"
292 msgstr "Verificar" 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 msgid "Add another email address:" 310 msgid "Add another email address:"
296 msgstr "Adicionar outro endereço de email" 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 msgid "Add" 314 msgid "Add"
300 msgstr "Adicionar" 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 msgid "Change Password" 321 msgid "Change Password"
307 msgstr "Trocar senha" 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 msgid "Update" 325 msgid "Update"
311 msgstr "Atualizar" 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 msgid "Please correct the error below and try again." 336 msgid "Please correct the error below and try again."
317 msgstr "Por favor corrija o erro abaixo e tente novamente." 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 msgid "Login" 343 msgid "Login"
326 msgstr "Entrar" 344 msgstr "Entrar"
327 345
328 -#: accounts/templates/registration/login.html:56 346 +#: colab/accounts/templates/registration/login.html:56
329 msgid "Forgot Password?" 347 msgid "Forgot Password?"
330 msgstr "Esqueci minha Senha?" 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 msgid "Change my password" 352 msgid "Change my password"
335 msgstr "Alterar minha senha" 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 msgid "Setting New password" 356 msgid "Setting New password"
339 msgstr "Definir Nova Senha" 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 msgid "" 360 msgid ""
343 "Forgotten your password? Enter your email address below, and we'll email " 361 "Forgotten your password? Enter your email address below, and we'll email "
344 "instructions for setting a new one." 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 msgid "Email address:" 368 msgid "Email address:"
349 msgstr "Endereço de email: " 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 msgid "Reset password" 372 msgid "Reset password"
353 msgstr "Redefinir senha" 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 msgid "Your profile has been created!" 406 msgid "Your profile has been created!"
357 msgstr "Seu perfil foi criado!" 407 msgstr "Seu perfil foi criado!"
358 408
359 -#: accounts/views.py:186 409 +#: colab/accounts/views.py:186
360 msgid "Your password was changed." 410 msgid "Your password was changed."
361 msgstr "Sua senha foi modificada." 411 msgstr "Sua senha foi modificada."
362 412
363 -#: accounts/views.py:202 413 +#: colab/accounts/views.py:202
364 msgid "Your password has been set. You may go ahead and log in now." 414 msgid "Your password has been set. You may go ahead and log in now."
365 msgstr "Sua senha foi modificada. Você já pode efetuar o login agora." 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 msgid "Fork me!" 418 msgid "Fork me!"
369 msgstr "Fork me!" 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 msgid "Latest Discussions" 422 msgid "Latest Discussions"
425 msgstr "Últimas discussões" 423 msgstr "Últimas discussões"
426 424
427 -#: rss/feeds.py:32 425 +#: colab/rss/feeds.py:32
428 msgid "Discussions Most Relevance" 426 msgid "Discussions Most Relevance"
429 msgstr "Discussões Mais Relevantes" 427 msgstr "Discussões Mais Relevantes"
430 428
431 -#: rss/feeds.py:51 429 +#: colab/rss/feeds.py:51
432 msgid "Latest collaborations" 430 msgid "Latest collaborations"
433 msgstr "Últimas colaborações" 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 msgid "Search" 435 msgid "Search"
438 msgstr "Busca" 436 msgstr "Busca"
439 437
440 -#: search/forms.py:18 438 +#: colab/search/forms.py:18
441 msgid "Type" 439 msgid "Type"
442 msgstr "Tipo" 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 msgid "Until" 447 msgid "Until"
516 msgstr "Até" 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 msgid "Remove filter" 454 msgid "Remove filter"
539 msgstr "Remover filtro" 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 msgid "Filter" 460 msgid "Filter"
545 msgstr "Filtro" 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 msgid "Sort by" 464 msgid "Sort by"
549 msgstr "Ordenar por" 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 msgid "Types" 468 msgid "Types"
553 msgstr "Tipos" 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 msgid "search" 472 msgid "search"
561 msgstr "busca" 473 msgstr "busca"
562 474
563 -#: search/templates/search/search.html:51 475 +#: colab/search/templates/search/search.html:44
564 msgid "documents found" 476 msgid "documents found"
565 msgstr "documentos encontrados" 477 msgstr "documentos encontrados"
566 478
567 -#: search/templates/search/search.html:62 479 +#: colab/search/templates/search/search.html:55
568 msgid "Search here" 480 msgid "Search here"
569 msgstr "Pesquise aqui" 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 msgid "Filters" 484 msgid "Filters"
574 msgstr "Filtros" 485 msgstr "Filtros"
575 486
576 -#: search/templates/search/search.html:105 487 +#: colab/search/templates/search/search.html:76
577 msgid "No results for your search." 488 msgid "No results for your search."
578 msgstr "Não há resultados para sua busca." 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 msgid "You are searching for" 492 msgid "You are searching for"
582 msgstr "Você está procurando por" 493 msgstr "Você está procurando por"
583 494
584 -#: settings.py:113 495 +#: colab/settings.py:111
585 msgid "English" 496 msgid "English"
586 msgstr "Inglês" 497 msgstr "Inglês"
587 498
588 -#: settings.py:114 499 +#: colab/settings.py:112
589 msgid "Portuguese" 500 msgid "Portuguese"
590 msgstr "Português" 501 msgstr "Português"
591 502
592 -#: settings.py:115 503 +#: colab/settings.py:113
593 msgid "Spanish" 504 msgid "Spanish"
594 msgstr "Espanhol" 505 msgstr "Espanhol"
595 506
596 -#: settings.py:138 507 +#: colab/settings.py:136
597 msgid "Recent activity" 508 msgid "Recent activity"
598 msgstr "Atividade recente" 509 msgstr "Atividade recente"
599 510
600 -#: settings.py:142 511 +#: colab/settings.py:140
601 msgid "Relevance" 512 msgid "Relevance"
602 msgstr "Relevância" 513 msgstr "Relevância"
603 514
604 -#: settings.py:151 515 +#: colab/settings.py:149
605 msgid "Document" 516 msgid "Document"
606 msgstr "Documento" 517 msgstr "Documento"
607 518
608 -#: settings.py:153 519 +#: colab/settings.py:151
609 msgid "Presentation" 520 msgid "Presentation"
610 msgstr "Apresentação" 521 msgstr "Apresentação"
611 522
612 -#: settings.py:154 523 +#: colab/settings.py:152
613 msgid "Text" 524 msgid "Text"
614 msgstr "Texto" 525 msgstr "Texto"
615 526
616 -#: settings.py:155 527 +#: colab/settings.py:153
617 msgid "Code" 528 msgid "Code"
618 msgstr "Código" 529 msgstr "Código"
619 530
620 -#: settings.py:157 531 +#: colab/settings.py:155
621 msgid "Compressed" 532 msgid "Compressed"
622 msgstr "Compactado" 533 msgstr "Compactado"
623 534
624 -#: settings.py:158 535 +#: colab/settings.py:156
625 msgid "Image" 536 msgid "Image"
626 msgstr "Imagem" 537 msgstr "Imagem"
627 538
628 -#: settings.py:160 539 +#: colab/settings.py:158
629 msgid "Spreadsheet" 540 msgid "Spreadsheet"
630 msgstr "Planilha" 541 msgstr "Planilha"
631 542
632 -#: templates/404.html:5 543 +#: colab/templates/404.html:5
633 msgid "Not found. Keep searching! :)" 544 msgid "Not found. Keep searching! :)"
634 msgstr "Não encontrado. Continue procurando! :)" 545 msgstr "Não encontrado. Continue procurando! :)"
635 546
636 -#: templates/500.html:2 547 +#: colab/templates/500.html:2
637 msgid "Ooopz... something went wrong!" 548 msgid "Ooopz... something went wrong!"
638 msgstr "Ooopa... algo saiu errado!" 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 msgid "The login has failed. Please, try again." 552 msgid "The login has failed. Please, try again."
660 msgstr "O login falhou. Por favor, tente novamente." 553 msgstr "O login falhou. Por favor, tente novamente."
661 554
662 -#: templates/footer.html:6 555 +#: colab/templates/footer.html:6
663 msgid "Last email imported at" 556 msgid "Last email imported at"
664 msgstr "Último email importado em" 557 msgstr "Último email importado em"
665 558
666 -#: templates/footer.html:12 559 +#: colab/templates/footer.html:12
667 msgid "The contents of this site is published under license" 560 msgid "The contents of this site is published under license"
668 msgstr "O conteúdo deste site está publicado sob a licença" 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 msgid "Creative Commons 4.0 Brasil - Atribuir Fonte - Compartilhar Igual" 564 msgid "Creative Commons 4.0 Brasil - Atribuir Fonte - Compartilhar Igual"
672 msgstr "Creative Commons 4.0 Brasil - Atribuir Fonte - Compartilhar Igual" 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 msgid "Latest Collaborations" 585 msgid "Latest Collaborations"
680 msgstr "Últimas Colaborações" 586 msgstr "Últimas Colaborações"
681 587
682 -#: templates/home.html:21 588 +#: colab/templates/home.html:21
683 msgid "RSS - Latest collaborations" 589 msgid "RSS - Latest collaborations"
684 msgstr "RSS - Últimas Colaborações" 590 msgstr "RSS - Últimas Colaborações"
685 591
686 -#: templates/home.html:30 592 +#: colab/templates/home.html:30
687 msgid "View more collaborations..." 593 msgid "View more collaborations..."
688 msgstr "Ver mais colaborações..." 594 msgstr "Ver mais colaborações..."
689 595
690 -#: templates/home.html:37 596 +#: colab/templates/home.html:37
691 msgid "Collaboration Graph" 597 msgid "Collaboration Graph"
692 msgstr "Gráfico de Colaborações" 598 msgstr "Gráfico de Colaborações"
693 599
694 -#: templates/home.html:48 600 +#: colab/templates/home.html:48
695 msgid "Most Relevant Threads" 601 msgid "Most Relevant Threads"
696 msgstr "Discussões Mais Relevantes" 602 msgstr "Discussões Mais Relevantes"
697 603
698 -#: templates/home.html:52 604 +#: colab/templates/home.html:52
699 msgid "RSS - Most Relevant Threads" 605 msgid "RSS - Most Relevant Threads"
700 msgstr "RSS - Discussões Mais Relevantes" 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 msgid "View more discussions..." 609 msgid "View more discussions..."
704 msgstr "Ver mais discussões..." 610 msgstr "Ver mais discussões..."
705 611
706 -#: templates/home.html:67 612 +#: colab/templates/home.html:67
707 msgid "Latest Threads" 613 msgid "Latest Threads"
708 msgstr "Últimas Discussões" 614 msgstr "Últimas Discussões"
709 615
710 -#: templates/home.html:71 616 +#: colab/templates/home.html:71
711 msgid "RSS - Latest Threads" 617 msgid "RSS - Latest Threads"
712 msgstr "RSS - Últimas Discussões" 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"
colab/plugins/migrations/0001_initial.py 0 → 100644
@@ -0,0 +1,25 @@ @@ -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 + ]
colab/plugins/migrations/__init__.py 0 → 100644
colab/plugins/models.py 0 → 100644
@@ -0,0 +1,29 @@ @@ -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
colab/plugins/tests/__init__.py 0 → 100644
colab/plugins/tests/test_templatetags.py 0 → 100644
@@ -0,0 +1,135 @@ @@ -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())
colab/plugins/tests/test_timestamp.py 0 → 100644
@@ -0,0 +1,32 @@ @@ -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)
colab/plugins/tests/test_views.py 0 → 100644
@@ -0,0 +1,22 @@ @@ -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 from django.apps import AppConfig 2 from django.apps import AppConfig
  3 +from ..conf import get_plugin_config
3 4
4 5
5 class ColabPluginAppConfig(AppConfig): 6 class ColabPluginAppConfig(AppConfig):
6 colab_proxied_app = True 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 pass 19 pass
10 20
11 - def connect_signals(self): 21 + def connect_signal(self):
12 pass 22 pass
colab/plugins/utils/filters_importer.py 0 → 100644
@@ -0,0 +1,22 @@ @@ -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
colab/plugins/utils/tests/test_apps.py 0 → 100644
@@ -0,0 +1,23 @@ @@ -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,13 +76,7 @@ class BaseIndex(indexes.SearchIndex):
76 ) 76 )
77 77
78 def prepare_modified_by(self, obj): 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 def prepare_modified_by_url(self, obj): 81 def prepare_modified_by_url(self, obj):
88 if hasattr(obj, 'modified_by'): 82 if hasattr(obj, 'modified_by'):
colab/search/forms.py
@@ -9,42 +9,25 @@ from haystack.forms import SearchForm @@ -9,42 +9,25 @@ from haystack.forms import SearchForm
9 from haystack.inputs import AltParser 9 from haystack.inputs import AltParser
10 from haystack.inputs import AutoQuery 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 class ColabSearchForm(SearchForm): 15 class ColabSearchForm(SearchForm):
16 q = forms.CharField(label=_('Search'), required=False) 16 q = forms.CharField(label=_('Search'), required=False)
17 order = forms.CharField(widget=forms.HiddenInput(), required=False) 17 order = forms.CharField(widget=forms.HiddenInput(), required=False)
18 type = forms.CharField(required=False, label=_(u'Type')) 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 since = forms.DateField(required=False, label=_(u'Since')) 19 since = forms.DateField(required=False, label=_(u'Since'))
43 until = forms.DateField(required=False, label=_(u'Until')) 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 def search(self): 32 def search(self):
50 if not self.is_valid(): 33 if not self.is_valid():
@@ -52,46 +35,22 @@ class ColabSearchForm(SearchForm): @@ -52,46 +35,22 @@ class ColabSearchForm(SearchForm):
52 35
53 # filter_or goes here 36 # filter_or goes here
54 sqs = self.searchqueryset.all() 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 if self.cleaned_data['q']: 55 if self.cleaned_data['q']:
97 q = unicodedata.normalize( 56 q = unicodedata.normalize(
@@ -115,62 +74,15 @@ class ColabSearchForm(SearchForm): @@ -115,62 +74,15 @@ class ColabSearchForm(SearchForm):
115 else: 74 else:
116 sqs = sqs.filter(content=AutoQuery(q)) 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 if self.cleaned_data['order']: 77 if self.cleaned_data['order']:
122 for option, dict_order in settings.ORDERING_DATA.items(): 78 for option, dict_order in settings.ORDERING_DATA.items():
123 if self.cleaned_data['order'] == option: 79 if self.cleaned_data['order'] == option:
124 if dict_order['fields']: 80 if dict_order['fields']:
125 sqs = sqs.order_by(*dict_order['fields']) 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 if self.cleaned_data['since']: 83 if self.cleaned_data['since']:
169 sqs = sqs.filter(modified__gte=self.cleaned_data['since']) 84 sqs = sqs.filter(modified__gte=self.cleaned_data['since'])
170 if self.cleaned_data['until']: 85 if self.cleaned_data['until']:
171 sqs = sqs.filter(modified__lte=self.cleaned_data['until']) 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 return sqs 88 return sqs
colab/search/templates/dashboard-message-preview.html 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +{% load i18n tz highlight search_preview_templates %}
  2 +{% get_dashboard_search_preview_templates result as template_target %}
  3 +{% include template_target %}
colab/search/templates/message-preview.html 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +{% load i18n tz highlight search_preview_templates %}
  2 +{% get_search_preview_templates result as template_target %}
  3 +{% include template_target %}
colab/search/templates/search/includes/search_filters.html
@@ -16,12 +16,12 @@ @@ -16,12 +16,12 @@
16 <input type="hidden" name="since" value="{{ request.GET.since }}" /> 16 <input type="hidden" name="since" value="{{ request.GET.since }}" />
17 <input type="hidden" name="until" value="{{ request.GET.until }}" /> 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 <div class="form-group"> 20 <div class="form-group">
21 <label for="{{ field_lookup }}">{{ field_display }}</label> 21 <label for="{{ field_lookup }}">{{ field_display }}</label>
22 - {% if field_lookup == "list" %} 22 + {% if field_type == "list" %}
23 <select name="{{ field_lookup }}" class="form-control" multiple> 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 <option value="{{ value }}" {% if value in field_value %}selected{% endif %}>{{ option }}</option> 25 <option value="{{ value }}" {% if value in field_value %}selected{% endif %}>{{ option }}</option>
26 {% endfor %} 26 {% endfor %}
27 </select> 27 </select>
@@ -112,10 +112,12 @@ @@ -112,10 +112,12 @@
112 112
113 <ul class="unstyled-list"> 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 </ul> 121 </ul>
120 {% endif %} 122 {% endif %}
121 <hr /> 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,13 +25,6 @@ We must use STATIC_URL because we have a language composing the URL
25 {% endif %} 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 </script> 29 </script>
37 {% endblock %} 30 {% endblock %}
@@ -74,28 +67,6 @@ We must use STATIC_URL because we have a language composing the URL @@ -74,28 +67,6 @@ We must use STATIC_URL because we have a language composing the URL
74 <h3>{% trans "Filters" %}</h3> 67 <h3>{% trans "Filters" %}</h3>
75 {% include "search/includes/search_filters.html" %} 68 {% include "search/includes/search_filters.html" %}
76 </div> 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 <div class="col-md-10 col-lg-10"> 70 <div class="col-md-10 col-lg-10">
100 <ul class="list-unstyled"> 71 <ul class="list-unstyled">
101 {% for result in page.object_list %} 72 {% for result in page.object_list %}
colab/search/templatetags/__init__.py 0 → 100644
colab/search/templatetags/search_preview_templates.py 0 → 100644
@@ -0,0 +1,18 @@ @@ -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,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)  
colab/search/tests/__init__.py 0 → 100644
colab/search/tests/test_base_index.py 0 → 100644
@@ -0,0 +1,106 @@ @@ -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))
colab/search/tests/test_search_view.py 0 → 100644
@@ -0,0 +1,130 @@ @@ -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)
colab/search/tests/test_templatetags.py 0 → 100644
@@ -0,0 +1,34 @@ @@ -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 # -*- coding:utf-8 -*- 1 # -*- coding:utf-8 -*-
2 2
3 from django.conf import settings 3 from django.conf import settings
4 -from django.utils.translation import ugettext as _  
5 4
6 from haystack.views import SearchView 5 from haystack.views import SearchView
  6 +from colab.plugins.utils import filters_importer
7 7
8 8
9 class ColabSearchView(SearchView): 9 class ColabSearchView(SearchView):
@@ -13,127 +13,6 @@ class ColabSearchView(SearchView): @@ -13,127 +13,6 @@ class ColabSearchView(SearchView):
13 self.request.LANGUAGE_CODE, (None, None) 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 try: 16 try:
138 type_chosen = self.form.cleaned_data.get('type') 17 type_chosen = self.form.cleaned_data.get('type')
139 except AttributeError: 18 except AttributeError:
@@ -143,26 +22,17 @@ class ColabSearchView(SearchView): @@ -143,26 +22,17 @@ class ColabSearchView(SearchView):
143 size_choices = () 22 size_choices = ()
144 used_by_choices = () 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 mimetype_chosen = self.request.GET.get('mimetype') 25 mimetype_chosen = self.request.GET.get('mimetype')
161 size_chosen = self.request.GET.get('size') 26 size_chosen = self.request.GET.get('size')
162 used_by_chosen = self.request.GET.get('used_by') 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 return dict( 33 return dict(
165 filters=types.get(type_chosen), 34 filters=types.get(type_chosen),
  35 + filters_options=filters_options,
166 type_chosen=type_chosen, 36 type_chosen=type_chosen,
167 order_data=settings.ORDERING_DATA, 37 order_data=settings.ORDERING_DATA,
168 date_format=date_format, 38 date_format=date_format,
colab/settings.py
@@ -53,6 +53,7 @@ INSTALLED_APPS = ( @@ -53,6 +53,7 @@ INSTALLED_APPS = (
53 'colab', 53 'colab',
54 'colab.home', 54 'colab.home',
55 'colab.plugins', 55 'colab.plugins',
  56 + 'colab.widgets',
56 'colab.super_archives', 57 'colab.super_archives',
57 'colab.rss', 58 'colab.rss',
58 'colab.search', 59 'colab.search',
@@ -80,11 +81,8 @@ USE_TZ = True @@ -80,11 +81,8 @@ USE_TZ = True
80 # Static files (CSS, JavaScript, Images) 81 # Static files (CSS, JavaScript, Images)
81 # https://docs.djangoproject.com/en/1.7/howto/static-files/ 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 STATIC_URL = '/static/' 85 STATIC_URL = '/static/'
87 -MEDIA_URL = '/media/'  
88 86
89 STATICFILES_STORAGE = \ 87 STATICFILES_STORAGE = \
90 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' 88 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
@@ -134,7 +132,7 @@ ATTACHMENTS_FOLDER_PATH = &#39;/mnt/trac/attachments/&#39; @@ -134,7 +132,7 @@ ATTACHMENTS_FOLDER_PATH = &#39;/mnt/trac/attachments/&#39;
134 # the indexes 132 # the indexes
135 133
136 ORDERING_DATA = { 134 ORDERING_DATA = {
137 - 'latest': { 135 + 'latest': {
138 'name': _(u'Recent activity'), 136 'name': _(u'Recent activity'),
139 'fields': ('-modified', '-created'), 137 'fields': ('-modified', '-created'),
140 }, 138 },
@@ -142,6 +140,10 @@ ORDERING_DATA = { @@ -142,6 +140,10 @@ ORDERING_DATA = {
142 'name': _(u'Relevance'), 140 'name': _(u'Relevance'),
143 'fields': None, 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,3 +295,5 @@ TEMPLATE_DIRS += (
293 ) 295 )
294 296
295 conf.validate_database(DATABASES, DEFAULT_DATABASE, DEBUG) 297 conf.validate_database(DATABASES, DEFAULT_DATABASE, DEBUG)
  298 +
  299 +conf.load_widgets_settings()
colab/static/css/footer.css 0 → 100644
colab/static/css/header.css 0 → 100644
@@ -0,0 +1,46 @@ @@ -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 \ No newline at end of file 47 \ No newline at end of file
colab/static/css/screen.css
@@ -7,54 +7,6 @@ li hr { @@ -7,54 +7,6 @@ li hr {
7 margin: 10px 0; 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 /* From message-preview.html*/ 10 /* From message-preview.html*/
59 .quiet { 11 .quiet {
60 color: #999; 12 color: #999;
@@ -115,6 +67,11 @@ form.signup .form-group { @@ -115,6 +67,11 @@ form.signup .form-group {
115 margin-left: 0.5em; 67 margin-left: 0.5em;
116 } 68 }
117 69
  70 +div.links-group {
  71 + text-align: center;
  72 + margin-bottom: 20px;
  73 +}
  74 +
118 div.submit { 75 div.submit {
119 margin: auto; 76 margin: auto;
120 margin-bottom: 2em; 77 margin-bottom: 2em;
colab/super_archives/filters.py 0 → 100644
@@ -0,0 +1,22 @@ @@ -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 + }
colab/super_archives/fixtures/test_user.json 0 → 100644
@@ -0,0 +1,100 @@ @@ -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,7 +117,7 @@ class Command(BaseCommand, object):
117 # Get messages from each mbox 117 # Get messages from each mbox
118 for mbox in mailing_lists_mboxes: 118 for mbox in mailing_lists_mboxes:
119 mbox_path = os.path.join(mailinglist_dir, mbox, mbox) 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 # Check if the mailinglist is set not to be imported 122 # Check if the mailinglist is set not to be imported
123 if exclude_lists and mailinglist_name in exclude_lists: 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 @@ @@ -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 \ No newline at end of file 70 \ No newline at end of file
colab/super_archives/templates/message-preview.html
@@ -1,69 +0,0 @@ @@ -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 @@ @@ -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>
colab/super_archives/tests/test_email_validation.py 0 → 100644
@@ -0,0 +1,71 @@ @@ -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)
colab/super_archives/tests/test_utils_blocks.py 0 → 100644
@@ -0,0 +1,28 @@ @@ -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,7 +6,7 @@ from .views import (EmailView, EmailValidationView, ThreadView,
6 6
7 urlpatterns = patterns( 7 urlpatterns = patterns(
8 'super_archives.views', 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 ThreadView.as_view(), name="thread_view"), 10 ThreadView.as_view(), name="thread_view"),
11 url(r'thread/$', ThreadDashboardView.as_view(), name='thread_list'), 11 url(r'thread/$', ThreadDashboardView.as_view(), name='thread_list'),
12 url(r'manage/email/validate/?$', EmailValidationView.as_view(), 12 url(r'manage/email/validate/?$', EmailValidationView.as_view(),
colab/super_archives/utils/blocks.py
@@ -25,7 +25,7 @@ class EmailBlock(list): @@ -25,7 +25,7 @@ class EmailBlock(list):
25 25
26 def _html2text(self, text): 26 def _html2text(self, text):
27 if RE_WRAPPED_BY_HTML.match(text.strip()): 27 if RE_WRAPPED_BY_HTML.match(text.strip()):
28 - return html2text(text) 28 + return html2text(text).strip()
29 29
30 text, n = RE_BR_TO_LINEBREAK.subn('\n', text) 30 text, n = RE_BR_TO_LINEBREAK.subn('\n', text)
31 text = strip_tags(text) 31 text = strip_tags(text)
colab/super_archives/views.py
@@ -103,7 +103,7 @@ class ThreadView(View): @@ -103,7 +103,7 @@ class ThreadView(View):
103 } 103 }
104 104
105 url = urlparse.urljoin(settings.MAILMAN_API_URL, 105 url = urlparse.urljoin(settings.MAILMAN_API_URL,
106 - mailinglist + '/sendmail') 106 + 'sendmail/' + mailinglist)
107 107
108 error_msg = None 108 error_msg = None
109 try: 109 try:
@@ -277,14 +277,14 @@ class EmailValidationView(View): @@ -277,14 +277,14 @@ class EmailValidationView(View):
277 try: 277 try:
278 email = EmailAddressValidation.objects.get(address=email_addr, 278 email = EmailAddressValidation.objects.get(address=email_addr,
279 user_id=user_id) 279 user_id=user_id)
280 - except http.DoesNotExist: 280 + except EmailAddressValidation.DoesNotExist:
281 raise http.Http404 281 raise http.Http404
282 282
283 try: 283 try:
284 location = reverse('archive_email_view', 284 location = reverse('archive_email_view',
285 kwargs={'key': email.validation_key}) 285 kwargs={'key': email.validation_key})
286 verification_url = request.build_absolute_uri(location) 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 email.validation_key, verification_url) 288 email.validation_key, verification_url)
289 except smtplib.SMTPException: 289 except smtplib.SMTPException:
290 logging.exception('Error sending validation email') 290 logging.exception('Error sending validation email')
colab/templates/base.html
@@ -2,11 +2,12 @@ @@ -2,11 +2,12 @@
2 {% load i18n gravatar plugins %} 2 {% load i18n gravatar plugins %}
3 {% load static from staticfiles %} 3 {% load static from staticfiles %}
4 4
  5 +{% block html %}
5 <html> 6 <html>
6 <head> 7 <head>
7 {% block head %} 8 {% block head %}
8 <meta charset="UTF-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 {% block metarobots %} 11 {% block metarobots %}
11 {% if ROBOTS_NOINDEX %} 12 {% if ROBOTS_NOINDEX %}
12 <meta name="robots" content="noindex, nofollow" /> 13 <meta name="robots" content="noindex, nofollow" />
@@ -46,6 +47,11 @@ @@ -46,6 +47,11 @@
46 <link rel="stylesheet" href="{% static 'css/screen.css' %}" 47 <link rel="stylesheet" href="{% static 'css/screen.css' %}"
47 type="text/css" media="screen" /> 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 {% endblock %} 55 {% endblock %}
50 </head> 56 </head>
51 57
@@ -101,3 +107,4 @@ @@ -101,3 +107,4 @@
101 {% block footer_js %}{% endblock %} 107 {% block footer_js %}{% endblock %}
102 </body> 108 </body>
103 </html> 109 </html>
  110 +{% endblock %}
colab/templates/home.html
@@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
22 </a> 22 </a>
23 <ul class="message-list"> 23 <ul class="message-list">
24 {% for result in latest_results %} 24 {% for result in latest_results %}
25 - {% include "message-preview.html" %} 25 + {% include "dashboard-message-preview.html" %}
26 {% endfor %} 26 {% endfor %}
27 </ul> 27 </ul>
28 <a class="column-align" 28 <a class="column-align"
@@ -53,7 +53,7 @@ @@ -53,7 +53,7 @@
53 </a> 53 </a>
54 <ul class="message-list"> 54 <ul class="message-list">
55 {% for thread in hottest_threads %} 55 {% for thread in hottest_threads %}
56 - {% include "message-preview.html" with result=thread %} 56 + {% include "dashboard-message-preview.html" with result=thread %}
57 {% endfor %} 57 {% endfor %}
58 </ul> 58 </ul>
59 <a href="{% url 'haystack_search' %}?type=thread"> 59 <a href="{% url 'haystack_search' %}?type=thread">
@@ -72,7 +72,7 @@ @@ -72,7 +72,7 @@
72 </a> 72 </a>
73 <ul class="message-list"> 73 <ul class="message-list">
74 {% for thread in latest_threads %} 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 {% endfor %} 76 {% endfor %}
77 </ul> 77 </ul>
78 <a href="{% url 'haystack_search' %}?type=thread&amp;order=latest"> 78 <a href="{% url 'haystack_search' %}?type=thread&amp;order=latest">
@@ -3,7 +3,7 @@ from django.conf import settings @@ -3,7 +3,7 @@ from django.conf import settings
3 from django.views.generic import TemplateView 3 from django.views.generic import TemplateView
4 from django.contrib import admin 4 from django.contrib import admin
5 from django.views.generic import RedirectView 5 from django.views.generic import RedirectView
6 - 6 +from accounts.views import UserProfileUpdateView
7 7
8 admin.autodiscover() 8 admin.autodiscover()
9 9
@@ -24,9 +24,3 @@ urlpatterns = patterns(&#39;&#39;, @@ -24,9 +24,3 @@ urlpatterns = patterns(&#39;&#39;,
24 24
25 url(r'', include('colab.plugins.urls')), 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,7 +32,7 @@ def _load_py_file(py_path, path):
32 try: 32 try:
33 py_settings = importlib.import_module(py_path) 33 py_settings = importlib.import_module(py_path)
34 34
35 - except IOError: 35 + except ImportError:
36 msg = ('Could not open settings file {}. Please ' 36 msg = ('Could not open settings file {}. Please '
37 'check if the file exists and if user ' 37 'check if the file exists and if user '
38 'has read rights.').format(py_path) 38 'has read rights.').format(py_path)
@@ -49,12 +49,12 @@ def _load_py_file(py_path, path): @@ -49,12 +49,12 @@ def _load_py_file(py_path, path):
49 sys.path.remove(path) 49 sys.path.remove(path)
50 50
51 py_setting = {var: getattr(py_settings, var) for var in dir(py_settings) 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 return py_setting 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 settings_file = os.getenv('COLAB_SETTINGS', '/etc/colab/settings.py') 58 settings_file = os.getenv('COLAB_SETTINGS', '/etc/colab/settings.py')
59 settings_module = settings_file.split('.')[-2].split('/')[-1] 59 settings_module = settings_file.split('.')[-2].split('/')[-1]
60 py_path = "/".join(settings_file.split('/')[:-1]) 60 py_path = "/".join(settings_file.split('/')[:-1])
@@ -67,8 +67,6 @@ def load_py_settings(): @@ -67,8 +67,6 @@ def load_py_settings():
67 67
68 py_settings = _load_py_file(settings_module, py_path) 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 logger.info('Settings directory: %s', settings_dir) 70 logger.info('Settings directory: %s', settings_dir)
73 71
74 if not os.path.exists(settings_dir): 72 if not os.path.exists(settings_dir):
@@ -127,18 +125,47 @@ def load_colab_apps(): @@ -127,18 +125,47 @@ def load_colab_apps():
127 125
128 app_label = app_name.split('.')[-1] 126 app_label = app_name.split('.')[-1]
129 COLAB_APPS[app_label] = {} 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 def validate_database(database_dict, default_db, debug): 171 def validate_database(database_dict, default_db, debug):
colab/utils/tests/colab_settings.py 0 → 100644
@@ -0,0 +1,56 @@ @@ -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', )
colab/utils/tests/plugins.d/gitlab.py 0 → 100644
@@ -0,0 +1,15 @@ @@ -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')
colab/utils/tests/plugins.d/noosfero.py 0 → 100644
@@ -0,0 +1,15 @@ @@ -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')
colab/utils/tests/plugins.d/plugin_test 0 → 100644
colab/utils/tests/plugins.d/spb.py 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +from colab.plugins.utils.menu import colab_url_factory
  2 +
  3 +verbose_name = "SPB Plugin"
  4 +urls = {
  5 + "include": "colab_spb.urls",
  6 + "prefix": '^spb/',
  7 + "namespace": "colab_spb"
  8 +}
  9 +
  10 +url = colab_url_factory('colab_spb')
colab/utils/tests/settings.d/test.py 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +RIBBON_ENABLED = False
  2 +TEST = 'test'
colab/utils/tests/settings_with_syntax_error.py 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +)
colab/utils/tests/test_conf.py
  1 +import sys
1 2
2 from django.test import TestCase, override_settings 3 from django.test import TestCase, override_settings
3 from django.conf import settings 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 class TestConf(TestCase): 16 class TestConf(TestCase):
@@ -16,3 +24,78 @@ class TestConf(TestCase): @@ -16,3 +24,78 @@ class TestConf(TestCase):
16 with self.assertRaises(DatabaseUndefined): 24 with self.assertRaises(DatabaseUndefined):
17 validate_database(settings.DATABASES, settings.DEFAULT_DATABASE, 25 validate_database(settings.DATABASES, settings.DEFAULT_DATABASE,
18 settings.DEBUG) 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())
colab/widgets/__init__.py 0 → 100644
colab/widgets/admin.py 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +# from django.contrib import admin
  2 +
  3 +# Register your models here.
colab/widgets/migrations/__init__.py 0 → 100644
colab/widgets/models.py 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +# from django.db import models
  2 +
  3 +# Create your models here.
colab/widgets/templatetags/__init__.py 0 → 100644
colab/widgets/templatetags/widgets_tag.py 0 → 100644
@@ -0,0 +1,15 @@ @@ -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 ""
colab/widgets/tests/__init__.py 0 → 100644
colab/widgets/tests/test_widget_manager.py 0 → 100644
@@ -0,0 +1,98 @@ @@ -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()
colab/widgets/tests/test_widgets.py 0 → 100644
@@ -0,0 +1,37 @@ @@ -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)
colab/widgets/views.py 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +# from django.shortcuts import render
  2 +
  3 +# Create your views here.
colab/widgets/widget_manager.py 0 → 100644
@@ -0,0 +1,65 @@ @@ -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
diagrama_classes.asta 0 → 100644
No preview for this file type
docs/source/dev.rst
@@ -4,3 +4,77 @@ Developer Documentation @@ -4,3 +4,77 @@ Developer Documentation
4 Getting Started 4 Getting Started
5 --------------- 5 ---------------
6 .. TODO 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,6 +23,8 @@ signals structure, some steps are required:
23 contain any special character, such as dot or comma. It is suggested to name 23 contain any special character, such as dot or comma. It is suggested to name
24 the variable "short_name", but that nomenclature is not strictly 24 the variable "short_name", but that nomenclature is not strictly
25 necessary. 25 necessary.
  26 +* Finally, the variable namespace should also be defined. This variable is the
  27 + url namespace for django reverse.
26 * In order to actually register the signals, it is necessary to implement the 28 * In order to actually register the signals, it is necessary to implement the
27 method register_signal, which require the name of the plugin that is 29 method register_signal, which require the name of the plugin that is
28 registering the signals and a list of signals to be registered as parameters. 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,6 +36,7 @@ signals structure, some steps are required:
34 be seen below: 36 be seen below:
35 37
36 .. code-block:: python 38 .. code-block:: python
  39 +
37 from colab.celery import app 40 from colab.celery import app
38 41
39 @app.task(bind=True) 42 @app.task(bind=True)
@@ -48,6 +51,7 @@ signals structure, some steps are required: @@ -48,6 +51,7 @@ signals structure, some steps are required:
48 51
49 52
50 .. code-block:: python 53 .. code-block:: python
  54 +
51 from colab.plugins.utils.apps import ColabPluginAppConfig 55 from colab.plugins.utils.apps import ColabPluginAppConfig
52 from colab.signals.signals import register_signal, connect_signal 56 from colab.signals.signals import register_signal, connect_signal
53 from colab.plugins.PLUGIN.tasks import HANDLING_METHOD 57 from colab.plugins.PLUGIN.tasks import HANDLING_METHOD
@@ -55,6 +59,7 @@ signals structure, some steps are required: @@ -55,6 +59,7 @@ signals structure, some steps are required:
55 class PluginApps(ColabPluginAppConfig): 59 class PluginApps(ColabPluginAppConfig):
56 short_name = PLUGIN_NAME 60 short_name = PLUGIN_NAME
57 signals_list = [SIGNAL1, SIGNAL2] 61 signals_list = [SIGNAL1, SIGNAL2]
  62 + namespace = PLUGIN_NAMESPACE
58 63
59 def registered_signal(self): 64 def registered_signal(self):
60 register_signal(self.short_name, self.signals_list) 65 register_signal(self.short_name, self.signals_list)
@@ -71,6 +76,7 @@ signals structure, some steps are required: @@ -71,6 +76,7 @@ signals structure, some steps are required:
71 \*\*kwargs. As you can see below: 76 \*\*kwargs. As you can see below:
72 77
73 .. code-block:: python 78 .. code-block:: python
  79 +
74 from colab.signals.signals import send 80 from colab.signals.signals import send
75 81
76 send(signal_name, sender) 82 send(signal_name, sender)
@@ -78,4 +84,137 @@ signals structure, some steps are required: @@ -78,4 +84,137 @@ signals structure, some steps are required:
78 * If you want to run celery manually to make some tests, you should execute: 84 * If you want to run celery manually to make some tests, you should execute:
79 85
80 .. code-block:: shell 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,6 +58,27 @@ View the following file:
58 58
59 The file /etc/colab/settings.py have the configurations of colab, this configurations overrides the django settings.py 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 Add a new plugin 83 Add a new plugin
63 ---------------- 84 ----------------
@@ -65,7 +86,7 @@ Add a new plugin @@ -65,7 +86,7 @@ Add a new plugin
65 86
66 - Make sure the application has the following requirements 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 - A relative url root 91 - A relative url root
71 92
@@ -77,6 +98,8 @@ Add a new plugin @@ -77,6 +98,8 @@ Add a new plugin
77 98
78 - create file: [plugin_name].py 99 - create file: [plugin_name].py
79 100
  101 +- Atention: Any URL used in the plugins' settings should not be preceded by "/"
  102 +
80 Use this template for the plugin configuration file 103 Use this template for the plugin configuration file
81 104
82 .. code-block:: python 105 .. code-block:: python
@@ -95,7 +118,6 @@ Use this template for the plugin configuration file @@ -95,7 +118,6 @@ Use this template for the plugin configuration file
95 118
96 urls = { 119 urls = {
97 'include': '[plugin_module_path].urls', 120 'include': '[plugin_module_path].urls',
98 - 'namespace': '[plugin_name]',  
99 'prefix': '[application_prefix]/', # Exemple: http://site.com/[application_prefix]/ 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,10 +126,10 @@ Use this template for the plugin configuration file
104 url = colab_url_factory('[plugin_name]') 126 url = colab_url_factory('[plugin_name]')
105 127
106 menu_urls = { 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 # You can have more than one url 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,6 +165,15 @@ Declares the additional installed apps that this plugin depends on.
143 This doesn't automatically install the python dependecies, only add to django 165 This doesn't automatically install the python dependecies, only add to django
144 apps. 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 urls 177 urls
147 ++++ 178 ++++
148 179
@@ -152,9 +183,8 @@ urls @@ -152,9 +183,8 @@ urls
152 .. attribute:: prefix 183 .. attribute:: prefix
153 184
154 Declares the prefix for the url. 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 menu 189 menu
160 ++++ 190 ++++
@@ -164,7 +194,7 @@ These variables defines the menu title and links of the plugin. @@ -164,7 +194,7 @@ These variables defines the menu title and links of the plugin.
164 .. attribute:: menu_title 194 .. attribute:: menu_title
165 195
166 Declares the menu title. 196 Declares the menu title.
167 -.. attribute:: menu_links 197 +.. attribute:: menu_urls
168 198
169 Declares the menu items and its links. 199 Declares the menu items and its links.
170 This should be a tuple object with several colab_url elements. 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,6 +202,9 @@ These variables defines the menu title and links of the plugin.
172 namespace. 202 namespace.
173 The auth parameter indicates wether the link should only be displayed when 203 The auth parameter indicates wether the link should only be displayed when
174 the user is logged in. 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 Example: 209 Example:
177 210
@@ -182,8 +215,8 @@ Example: @@ -182,8 +215,8 @@ Example:
182 url = colab_url_factory('plugin_app_name') 215 url = colab_url_factory('plugin_app_name')
183 216
184 menu_urls = ( 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 Extra Template Folders 222 Extra Template Folders
1 [flake8] 1 [flake8]
2 -exclude = **/migrations/*,**/urls.py 2 +exclude = **/migrations/*,**/urls.py,colab/utils/tests/settings_with_syntax_error.py
@@ -5,7 +5,9 @@ import sys @@ -5,7 +5,9 @@ import sys
5 5
6 os.environ['DJANGO_SETTINGS_MODULE'] = 'colab.settings' 6 os.environ['DJANGO_SETTINGS_MODULE'] = 'colab.settings'
7 os.environ['COLAB_SETTINGS'] = 'tests/colab_settings.py' 7 os.environ['COLAB_SETTINGS'] = 'tests/colab_settings.py'
  8 +os.environ['COLAB_WIDGETS_SETTINGS'] = 'tests/widgets_settings.py'
8 os.environ['COLAB_PLUGINS'] = 'tests/plugins.d' 9 os.environ['COLAB_PLUGINS'] = 'tests/plugins.d'
  10 +os.environ['COLAB_WIDGETS'] = 'tests/widgets.d'
9 os.environ['COVERAGE_PROCESS_START'] = '.coveragerc' 11 os.environ['COVERAGE_PROCESS_START'] = '.coveragerc'
10 12
11 13
@@ -14,23 +16,34 @@ import coverage @@ -14,23 +16,34 @@ import coverage
14 16
15 from django.conf import settings 17 from django.conf import settings
16 from django.test.utils import get_runner 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 if django.VERSION >= (1, 7, 0): 23 if django.VERSION >= (1, 7, 0):
21 django.setup() 24 django.setup()
22 25
23 test_runner = get_runner(settings) 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 sys.exit(failures) 29 sys.exit(failures)
26 30
27 31
28 -def run_with_coverage(): 32 +def run_with_coverage(test_suites=[]):
29 if os.path.exists('.coverage'): 33 if os.path.exists('.coverage'):
30 os.remove('.coverage') 34 os.remove('.coverage')
31 coverage.process_startup() 35 coverage.process_startup()
32 - runtests() 36 + runtests(test_suites)
33 37
34 38
35 if __name__ == '__main__': 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:])
tests/widgets_settings.py 0 → 100644