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