Commit 4c5db5b95a5aed67a5bf33f04f6a0ec94926750b

Authored by Sergio Oliveira
1 parent b8b73eef

Renamed src directory to colab

Showing 473 changed files with 12561 additions and 12561 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 473 files displayed.

colab/__init__.py 0 → 100644
colab/accounts/__init__.py 0 → 100644
colab/accounts/admin.py 0 → 100644
... ... @@ -0,0 +1,56 @@
  1 +
  2 +from django import forms
  3 +from django.contrib import admin
  4 +from django.contrib.auth.admin import UserAdmin
  5 +from django.utils.translation import ugettext_lazy as _
  6 +
  7 +from .models import User
  8 +
  9 +
  10 +class UserCreationForm(forms.ModelForm):
  11 + class Meta:
  12 + model = User
  13 + fields = ('username', 'email')
  14 +
  15 + def __init__(self, *args, **kwargs):
  16 + super(UserCreationForm, self).__init__(*args, **kwargs)
  17 + self.fields['email'].required = True
  18 +
  19 +
  20 +class UserChangeForm(forms.ModelForm):
  21 + class Meta:
  22 + model = User
  23 + fields = ('username', 'first_name', 'last_name', 'email', 'is_active',
  24 + 'is_staff', 'is_superuser', 'groups', 'last_login',
  25 + 'date_joined', 'twitter', 'facebook', 'google_talk',
  26 + 'webpage')
  27 +
  28 + def __init__(self, *args, **kwargs):
  29 + super(UserChangeForm, self).__init__(*args, **kwargs)
  30 + self.fields['email'].required = True
  31 +
  32 +
  33 +
  34 +class MyUserAdmin(UserAdmin):
  35 + form = UserChangeForm
  36 + add_form = UserCreationForm
  37 +
  38 + fieldsets = (
  39 + (None, {'fields': ('username', 'email')}),
  40 + (_('Personal info'), {'fields': ('first_name', 'last_name', 'twitter',
  41 + 'facebook', 'google_talk', 'webpage',
  42 + )}),
  43 + (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
  44 + 'groups')}),
  45 + (_('Important dates'), {'fields': ('last_login', 'date_joined')})
  46 + )
  47 +
  48 + add_fieldsets = (
  49 + (None, {
  50 + 'classes': ('wide',),
  51 + 'fields': ('username', 'email')}
  52 + ),
  53 + )
  54 +
  55 +
  56 +admin.site.register(User, MyUserAdmin)
... ...
colab/accounts/auth.py 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +
  2 +from django_browserid.auth import BrowserIDBackend
  3 +
  4 +class ColabBrowserIDBackend(BrowserIDBackend):
  5 + def filter_users_by_email(self, email):
  6 + return self.User.objects.filter(emails__address=email)
... ...
colab/accounts/errors.py 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +class XMPPChangePwdException(Exception):
  2 + """Error changing XMPP Account password"""
... ...
colab/accounts/forms.py 0 → 100644
... ... @@ -0,0 +1,114 @@
  1 +# -*- coding: utf-8 -*-
  2 +
  3 +from django import forms
  4 +from django.contrib.auth import get_user_model
  5 +from django.utils.translation import ugettext_lazy as _
  6 +
  7 +from conversejs.models import XMPPAccount
  8 +
  9 +from accounts.utils import mailman
  10 +from super_archives.models import MailingList
  11 +from .utils.validators import validate_social_account
  12 +
  13 +User = get_user_model()
  14 +
  15 +
  16 +class SocialAccountField(forms.Field):
  17 + def __init__(self, *args, **kwargs):
  18 + self.url = kwargs.pop('url', None)
  19 + super(SocialAccountField, self).__init__(*args, **kwargs)
  20 +
  21 + def validate(self, value):
  22 + super(SocialAccountField, self).validate(value)
  23 +
  24 + if value and not validate_social_account(value, self.url):
  25 + raise forms.ValidationError(_('Social account does not exist'),
  26 + code='social-account-doesnot-exist')
  27 +
  28 +
  29 +class UserForm(forms.ModelForm):
  30 + required = ('first_name', 'last_name', 'email', 'username')
  31 +
  32 + class Meta:
  33 + model = User
  34 +
  35 + def __init__(self, *args, **kwargs):
  36 + super(UserForm, self).__init__(*args, **kwargs)
  37 + for field_name, field in self.fields.items():
  38 + # Adds form-control class to all form fields
  39 + field.widget.attrs.update({'class': 'form-control'})
  40 +
  41 + # Set UserForm.required fields as required
  42 + if field_name in UserForm.required:
  43 + field.required = True
  44 +
  45 +
  46 +class UserCreationForm(UserForm):
  47 + class Meta:
  48 + model = User
  49 + fields = ('first_name', 'last_name', 'email', 'username')
  50 +
  51 +
  52 +class UserUpdateForm(UserForm):
  53 + bio = forms.CharField(
  54 + widget=forms.Textarea(attrs={'rows': '6', 'maxlength': '200'}),
  55 + max_length=200,
  56 + label=_(u'Bio'),
  57 + help_text=_(u'Write something about you in 200 characters or less.'),
  58 + required=False,
  59 + )
  60 +
  61 + class Meta:
  62 + model = User
  63 + fields = ('first_name', 'last_name',
  64 + 'institution', 'role', 'twitter', 'facebook',
  65 + 'google_talk', 'github', 'webpage', 'bio')
  66 +
  67 + twitter = SocialAccountField(url='https://twitter.com/', required=False)
  68 + facebook = SocialAccountField(url='https://graph.facebook.com/', required=False)
  69 +
  70 +
  71 +class ListsForm(forms.Form):
  72 + LISTS_NAMES = ((
  73 + listname, u'{} ({})'.format(listname, description)
  74 + ) for listname, description in mailman.all_lists(description=True))
  75 +
  76 + lists = forms.MultipleChoiceField(label=_(u'Mailing lists'),
  77 + required=False,
  78 + widget=forms.CheckboxSelectMultiple,
  79 + choices=LISTS_NAMES)
  80 +
  81 +
  82 +class ChangeXMPPPasswordForm(forms.ModelForm):
  83 + password1 = forms.CharField(label=_("Password"),
  84 + widget=forms.PasswordInput)
  85 + password2 = forms.CharField(label=_("Password confirmation"),
  86 + widget=forms.PasswordInput,
  87 + help_text=_("Enter the same password as above, for verification."))
  88 +
  89 + class Meta:
  90 + model = XMPPAccount
  91 + fields = ('password1', 'password2')
  92 +
  93 + def __init__(self, *args, **kwargs):
  94 + super(ChangeXMPPPasswordForm, self).__init__(*args, **kwargs)
  95 +
  96 + for field_name, field in self.fields.items():
  97 + # Adds form-control class to all form fields
  98 + field.widget.attrs.update({'class': 'form-control'})
  99 +
  100 + def clean_password2(self):
  101 + password1 = self.cleaned_data.get("password1")
  102 + password2 = self.cleaned_data.get("password2")
  103 + if password1 and password2 and password1 != password2:
  104 + raise forms.ValidationError(
  105 + _("Password mismatch"),
  106 + code='password_mismatch',
  107 + )
  108 + return password2
  109 +
  110 + def save(self, commit=True):
  111 + self.instance.password = self.cleaned_data['password2']
  112 + if commit:
  113 + self.instance.save()
  114 + return self.instance
... ...
colab/accounts/management/__init__.py 0 → 100644
colab/accounts/management/commands/__init__.py 0 → 100644
colab/accounts/management/commands/delete_invalid.py 0 → 100644
... ... @@ -0,0 +1,42 @@
  1 +
  2 +
  3 +from django.db.models import F
  4 +from django.utils import timezone
  5 +from django.utils.translation import ugettext as _
  6 +from django.core.management.base import BaseCommand, CommandError
  7 +
  8 +
  9 +from ...models import User
  10 +
  11 +
  12 +class Command(BaseCommand):
  13 + """Delete user accounts that have never logged in.
  14 +
  15 + Delete from database user accounts that have never logged in
  16 + and are at least 24h older.
  17 +
  18 + """
  19 +
  20 + help = __doc__
  21 +
  22 + def handle(self, *args, **kwargs):
  23 + seconds = timezone.timedelta(seconds=1)
  24 + now = timezone.now()
  25 + one_day_ago = timezone.timedelta(days=1)
  26 +
  27 + # Query for users that have NEVER logged in
  28 + #
  29 + # By default django sets the last_login as auto_now and then
  30 + # last_login is pretty much the same than date_joined
  31 + # (instead of null as I expected). Because of that we query
  32 + # for users which last_login is between date_joined - N and
  33 + # date_joined + N, where N is a small constant in seconds.
  34 + users = User.objects.filter(last_login__gt=(F('date_joined') - seconds),
  35 + last_login__lt=(F('date_joined') + seconds),
  36 + date_joined__lt=now-one_day_ago)
  37 + count = 0
  38 + for user in users:
  39 + count += 1
  40 + user.delete()
  41 +
  42 + print _(u'%(count)s users deleted.') % {'count': count}
... ...
colab/accounts/migrations/0001_initial.py 0 → 100644
... ... @@ -0,0 +1,50 @@
  1 +# -*- coding: utf-8 -*-
  2 +from __future__ import unicode_literals
  3 +
  4 +from django.db import models, migrations
  5 +import django.utils.timezone
  6 +import django.core.validators
  7 +
  8 +
  9 +class Migration(migrations.Migration):
  10 +
  11 + dependencies = [
  12 + ('auth', '0001_initial'),
  13 + ]
  14 +
  15 + operations = [
  16 + migrations.CreateModel(
  17 + name='User',
  18 + fields=[
  19 + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
  20 + ('password', models.CharField(max_length=128, verbose_name='password')),
  21 + ('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')),
  22 + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
  23 + ('username', models.CharField(help_text='Required. 30 characters or fewer. Letters, digits and ./+/-/_ only.', unique=True, max_length=30, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')])),
  24 + ('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)),
  25 + ('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)),
  26 + ('email', models.EmailField(unique=True, max_length=75, verbose_name='email address', blank=True)),
  27 + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
  28 + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
  29 + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
  30 + ('institution', models.CharField(max_length=128, null=True, blank=True)),
  31 + ('role', models.CharField(max_length=128, null=True, blank=True)),
  32 + ('twitter', models.CharField(max_length=128, null=True, blank=True)),
  33 + ('facebook', models.CharField(max_length=128, null=True, blank=True)),
  34 + ('google_talk', models.EmailField(max_length=75, null=True, blank=True)),
  35 + ('github', models.CharField(max_length=128, null=True, verbose_name='github', blank=True)),
  36 + ('webpage', models.CharField(max_length=256, null=True, blank=True)),
  37 + ('verification_hash', models.CharField(max_length=32, null=True, blank=True)),
  38 + ('modified', models.DateTimeField(auto_now=True)),
  39 + ('bio', models.CharField(max_length=200, null=True, blank=True)),
  40 + ('groups', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.', verbose_name='groups')),
  41 + ('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')),
  42 + ],
  43 + options={
  44 + 'abstract': False,
  45 + 'verbose_name': 'user',
  46 + 'verbose_name_plural': 'users',
  47 + },
  48 + bases=(models.Model,),
  49 + ),
  50 + ]
... ...
colab/accounts/migrations/__init__.py 0 → 100644
colab/accounts/models.py 0 → 100644
... ... @@ -0,0 +1,66 @@
  1 +# -*- coding: utf-8 -*-
  2 +
  3 +import urlparse
  4 +
  5 +from django.db import models, DatabaseError
  6 +from django.contrib.auth.hashers import check_password
  7 +from django.contrib.auth.models import AbstractUser
  8 +from django.core import validators
  9 +from django.core.urlresolvers import reverse
  10 +from django.utils.translation import ugettext_lazy as _
  11 +
  12 +from conversejs import xmpp
  13 +
  14 +from .utils import mailman
  15 +
  16 +
  17 +class User(AbstractUser):
  18 + institution = models.CharField(max_length=128, null=True, blank=True)
  19 + role = models.CharField(max_length=128, null=True, blank=True)
  20 + twitter = models.CharField(max_length=128, null=True, blank=True)
  21 + facebook = models.CharField(max_length=128, null=True, blank=True)
  22 + google_talk = models.EmailField(null=True, blank=True)
  23 + github = models.CharField(max_length=128, null=True, blank=True,
  24 + verbose_name=u'github')
  25 + webpage = models.CharField(max_length=256, null=True, blank=True)
  26 + verification_hash = models.CharField(max_length=32, null=True, blank=True)
  27 + modified = models.DateTimeField(auto_now=True)
  28 + bio = models.CharField(max_length=200, null=True, blank=True)
  29 +
  30 + def check_password(self, raw_password):
  31 +
  32 + if self.xmpp.exists() and raw_password == self.xmpp.first().password:
  33 + return True
  34 +
  35 + return super(User, self).check_password(raw_password)
  36 +
  37 + def get_absolute_url(self):
  38 + return reverse('user_profile', kwargs={'username': self.username})
  39 +
  40 + def twitter_link(self):
  41 + return urlparse.urljoin('https://twitter.com', self.twitter)
  42 +
  43 + def facebook_link(self):
  44 + return urlparse.urljoin('https://www.facebook.com', self.facebook)
  45 +
  46 + def mailinglists(self):
  47 + return mailman.user_lists(self)
  48 +
  49 + def update_subscription(self, email, lists):
  50 + mailman.update_subscription(email, lists)
  51 +
  52 +
  53 +# We need to have `email` field set as unique but Django does not
  54 +# support field overriding (at least not until 1.6).
  55 +# The following workaroud allows to change email field to unique
  56 +# without having to rewrite all AbstractUser here
  57 +User._meta.get_field('email')._unique = True
  58 +User._meta.get_field('username').help_text = _(
  59 + u'Required. 30 characters or fewer. Letters, digits and '
  60 + u'./+/-/_ only.'
  61 +)
  62 +User._meta.get_field('username').validators[0] = validators.RegexValidator(
  63 + r'^[\w.+-]+$',
  64 + _('Enter a valid username.'),
  65 + 'invalid'
  66 +)
... ...
colab/accounts/search_indexes.py 0 → 100644
... ... @@ -0,0 +1,79 @@
  1 +# -*- coding: utf-8 -*-
  2 +
  3 +from haystack import indexes
  4 +from django.db.models import Count
  5 +
  6 +from badger.utils import get_users_counters
  7 +from .models import User
  8 +
  9 +
  10 +class UserIndex(indexes.SearchIndex, indexes.Indexable):
  11 + # common fields
  12 + text = indexes.CharField(document=True, use_template=True, stored=False)
  13 + url = indexes.CharField(model_attr='get_absolute_url', indexed=False)
  14 + title = indexes.CharField(model_attr='get_full_name')
  15 + description = indexes.CharField(null=True)
  16 + type = indexes.CharField()
  17 + icon_name = indexes.CharField()
  18 +
  19 + # extra fields
  20 + username = indexes.CharField(model_attr='username', stored=False)
  21 + name = indexes.CharField(model_attr='get_full_name')
  22 + email = indexes.CharField(model_attr='email', stored=False)
  23 + institution = indexes.CharField(model_attr='institution', null=True)
  24 + role = indexes.CharField(model_attr='role', null=True)
  25 + google_talk = indexes.CharField(model_attr='google_talk', null=True,
  26 + stored=False)
  27 + webpage = indexes.CharField(model_attr='webpage', null=True, stored=False)
  28 + message_count = indexes.IntegerField(stored=False)
  29 + changeset_count = indexes.IntegerField(stored=False)
  30 + ticket_count = indexes.IntegerField(stored=False)
  31 + wiki_count = indexes.IntegerField(stored=False)
  32 + contribution_count = indexes.IntegerField(stored=False)
  33 +
  34 + def get_model(self):
  35 + return User
  36 +
  37 + @property
  38 + def badge_counters(self):
  39 + if not hasattr(self, '_badge_counters'):
  40 + self._badge_counters = get_users_counters()
  41 + return self._badge_counters
  42 +
  43 + def prepare(self, obj):
  44 + prepared_data = super(UserIndex, self).prepare(obj)
  45 +
  46 + prepared_data['contribution_count'] = sum((
  47 + self.prepared_data['message_count'],
  48 + self.prepared_data['changeset_count'],
  49 + self.prepared_data['ticket_count'],
  50 + self.prepared_data['wiki_count']
  51 + ))
  52 +
  53 + return prepared_data
  54 +
  55 + def prepare_description(self, obj):
  56 + return u'{}\n{}\n{}\n{}'.format(
  57 + obj.institution, obj.role, obj.username, obj.get_full_name()
  58 + )
  59 +
  60 + def prepare_icon_name(self, obj):
  61 + return u'user'
  62 +
  63 + def prepare_type(self, obj):
  64 + return u'user'
  65 +
  66 + def prepare_message_count(self, obj):
  67 + return self.badge_counters[obj.username]['messages']
  68 +
  69 + def prepare_changeset_count(self, obj):
  70 + return self.badge_counters[obj.username]['revisions']
  71 +
  72 + def prepare_ticket_count(self, obj):
  73 + return self.badge_counters[obj.username]['tickets']
  74 +
  75 + def prepare_wiki_count(self, obj):
  76 + return self.badge_counters[obj.username]['wikis']
  77 +
  78 + def index_queryset(self, using=None):
  79 + return self.get_model().objects.filter(is_active=True)
... ...
colab/accounts/templates/accounts/change_password.html 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +{% extends "base.html" %}
  2 +{% load i18n %}
  3 +
  4 +{% block main-content %}
  5 +<form method="POST" role="form">
  6 + {% csrf_token %}
  7 + <div class="row">
  8 + <h2>{% trans "Change XMPP Client and SVN Password" %}</h2>
  9 + <div class="col-lg-4 col-md-4 col-sm-12 col-xs-12">
  10 + {% for field in form %}
  11 + <div class="form-group required{% if field.errors %} alert alert-danger has-error{% endif %}">
  12 + <label for="{{ field.name }}" class="control-label">{{ field.label }}</label>
  13 + {{ field }}
  14 + {{ field.errors }}
  15 + </div>
  16 + {% endfor %}
  17 + <button class="btn btn-primary">{% trans "Change Password" %}</button>
  18 + </div>
  19 + </div>
  20 +</form>
  21 +{% endblock %}
... ...
colab/accounts/templates/accounts/manage_subscriptions.html 0 → 100644
... ... @@ -0,0 +1,45 @@
  1 +{% extends 'base.html' %}
  2 +{% load i18n gravatar %}
  3 +
  4 +{% block main-content %}
  5 +
  6 + <h2>{% blocktrans %}Group Subscriptions{% endblocktrans %}</h2>
  7 + <h3>{% gravatar user_.email 50 %} {{ user_.get_full_name }} ({{ user_.username }})</h3>
  8 + <br>
  9 +
  10 + <form method='post'>
  11 + {% csrf_token %}
  12 +
  13 + <div class="row">
  14 + {% for email, lists in membership.items %}
  15 + <div class="col-lg-3 col-md-4 col-sm-6 col-xs-12">
  16 + <div class="panel panel-default">
  17 + <div class="panel-heading">
  18 + <h3 class="panel-title">{{ email }}</h3>
  19 + </div>
  20 + <div class="panel-body">
  21 + {% for list, checked in lists %}
  22 + <div class="checkbox" title="{{ list.description }}">
  23 + <label>
  24 + <input name="{{ email }}" value="{{ list.listname }}" type="checkbox" {% if checked %}checked{% endif%}>{{ list.listname }}</input>
  25 + </label>
  26 + </div>
  27 + {% endfor %}
  28 + </div>
  29 + </div>
  30 + </div>
  31 + {% endfor %}
  32 + </div>
  33 +
  34 + <div class="row">
  35 + <div class="text-center">
  36 + <button class="btn btn-lg btn-primary" type="submit">{% trans 'Update subscriptions' %}</button>
  37 + </div>
  38 + </div>
  39 +
  40 + </form>
  41 +
  42 + <br><br>
  43 + <br><br>
  44 +
  45 +{% endblock %}
... ...
colab/accounts/templates/accounts/user_create_form.html 0 → 100644
... ... @@ -0,0 +1,66 @@
  1 +{% extends "base.html" %}
  2 +{% load i18n %}
  3 +{% block main-content %}
  4 +
  5 +<h2>{% trans "Sign up" %}</h2>
  6 +
  7 +<div class="row">
  8 + {% if form.errors %}
  9 + <div class="alert alert-danger">
  10 + <b>{% trans "Please correct the errors below and try again" %}</b>
  11 + </div>
  12 + {% endif %}
  13 +</div>
  14 +
  15 +
  16 +<p class="required">
  17 + <label>{% trans "Required fields" %}</label>
  18 +</p>
  19 +
  20 +<form action="." method="post" role="form" class="form-horizontal signup">
  21 + {% csrf_token %}
  22 +
  23 + <div class="row">
  24 +
  25 + <div>
  26 + <div class="col-md-6 col-lg-6 col-sm-6 col-xs-12" style="display: inline-block; vertical-align:top;">
  27 + <div class="well">
  28 + <fieldset>
  29 + <legend>{% trans 'Personal Information' %}</legend>
  30 + {% for field in user_form %}
  31 + <div class="form-group{% if field.field.required %} required{% endif %}{% if field.errors %} alert alert-danger has-error{% endif %}">
  32 + <label for="{{ field.name }}" class="control-label">
  33 + {{ field.label }}
  34 + </label>
  35 + {{ field }}
  36 + {{ field.errors }}
  37 + </div>
  38 + {% endfor %}
  39 + </fieldset>
  40 + </div>
  41 + </div>
  42 +
  43 + <div class="col-md-6 col-lg-6 col-sm-6 col-xs-12" style="display: inline-block; vertical-align:top;">
  44 + <div class="well">
  45 + <fieldset>
  46 + <legend>{% trans 'Subscribe to groups' %}</legend>
  47 + {% for choice in lists_form.lists %}
  48 + <div class="checkbox">{{ choice }}</div>
  49 + {% endfor %}
  50 + {{ lists_form.errors }}
  51 + </fieldset>
  52 + </div>
  53 + </div>
  54 +
  55 + </div>
  56 + </div>
  57 +
  58 + <div class="row">
  59 + <div class="submit">
  60 + <input type="submit" value="{% trans 'Register' %}" class="btn btn-primary btn-lg btn-block">
  61 + </div>
  62 + </div>
  63 +
  64 +</form>
  65 +
  66 +{% endblock %}
... ...
colab/accounts/templates/accounts/user_detail.html 0 → 100644
... ... @@ -0,0 +1,182 @@
  1 +{% extends "base.html" %}
  2 +
  3 +{% load i18n gravatar i18n_model %}
  4 +
  5 +{% block title %}Perfil{% endblock %}
  6 +
  7 +{% block head_js %}
  8 + {% trans "Messages" as group_collabs %}
  9 + {% trans "Contributions" as type_collabs %}
  10 +
  11 + {% include "doughnut-chart.html" with chart_data=type_count chart_canvas="collabs" name=type_collabs %}
  12 + {% include "doughnut-chart.html" with chart_data=list_activity chart_canvas="collabs2" name=group_collabs %}
  13 +{% endblock %}
  14 +
  15 +{% block main-content %}
  16 +
  17 + <div id="user-profile" class="row">
  18 + <div class="vcard col-lg-4 col-md-4 col-sm-5">
  19 + <div class="thumbnail">
  20 + {% gravatar user_.email 200 %}
  21 + </div>
  22 +
  23 + <h1>
  24 + <span>{{ user_.get_full_name }}</span>
  25 + <em>{{ user_.username }}</em>
  26 + </h1>
  27 +
  28 + {% if request.user == user_ or request.user.is_superuser %}
  29 + <a class="btn btn-info" href="{% url 'user_profile_update' user_ %}"><span class="glyphicon glyphicon-pencil"></span>&nbsp;&nbsp;{% trans "edit profile"|title %}</a>
  30 + <a class="btn btn-info" href="{% url 'user_list_subscriptions' user_ %}"><span class="glyphicon glyphicon-pencil"></span>&nbsp;&nbsp;{% trans "group membership"|title %}</a>
  31 + {% endif %}
  32 +
  33 + {% if request.user.is_active %}
  34 + {% if user_.bio %}
  35 + <div class="divider"></div>
  36 + <ul class="unstyled-list">
  37 + <li>
  38 + <strong>{% trans 'Bio' %}</strong>
  39 + </li>
  40 + <li class="text-muted"> {{ user_.bio }}</li>
  41 + </ul>
  42 + {% endif %}
  43 + {% endif %}
  44 +
  45 + <div class="divider"></div>
  46 + {% if request.user.is_active %}
  47 + <ul class="unstyled-list">
  48 + <li><span class="icon-envelope icon-fixed-width"></span> <a href="mailto:{{ user_.email }}">{{ user_.email }}</a></li>
  49 + </ul>
  50 + <div class="divider"></div>
  51 + {% endif %}
  52 +
  53 + <ul class="unstyled-list">
  54 + {% if user_.institution or user_.role %}
  55 + <li>
  56 + <span class="icon-briefcase icon-fixed-width"></span>
  57 + {{ user_.role }}
  58 + {% if user_.institution and user_.role %}-{% endif %}
  59 + {{ user_.institution }}
  60 + </li>
  61 + {% endif %}
  62 + {% if request.user.is_active %}
  63 + <li>
  64 + {% if user_.twitter %}
  65 + <span class="icon-twitter icon-fixed-width" title="{% trans 'Twitter account' %}"></span> <a target="_blank" href="{{ user_.twitter_link }}" title="{% trans 'Twitter account' %}">{{ user_.twitter }}</a>
  66 + {% endif %}
  67 + {% if user_.facebook %}
  68 + <span class="icon-facebook icon-fixed-width" title="{% trans 'Facebook account' %}"></span> <a target="_blank" href="{{ user_.facebook_link }}" title="{% trans 'Facebook account' %}">{{ user_.facebook }}</a>
  69 + {% endif %}
  70 + </li>
  71 +
  72 + {% if user_.google_talk %}
  73 + <li><span class="icon-google-plus icon-fixed-width" title="{% trans 'Google talk account' %}"></span> {{ user_.google_talk }}</li>
  74 + {% endif %}
  75 +
  76 + {% if user_.github %}
  77 + <li><span class="icon-github icon-fixed-width" title="{% trans 'Github account' %}"></span> <a target="_blank" href="https://github.com/{{ user_.github }}">{{ user_.github }}</a></li>
  78 + {% endif %}
  79 +
  80 + {% if user_.webpage %}
  81 + <li><span class="icon-link icon-fixed-width" title="{% trans 'Personal webpage' %}"></span> <a target="_blank" href="{{ user_.webpage }}" title="{% trans 'Personal webpage' %}">{{ user_.webpage }}</a></li>
  82 + {% endif %}
  83 + {% endif %}
  84 + </ul>
  85 +
  86 + {% if user_.mailinglists %}
  87 + <b>{% trans 'Groups: ' %}</b>
  88 + {% for list in user_.mailinglists %}
  89 + <a href="{% url 'haystack_search' %}?order=latest&amp;type=thread&amp;list={{ list }}"><span class="label label-primary">{{ list }}</span></a>
  90 + {% endfor %}
  91 + {% endif %}
  92 +
  93 + <div class="divider"></div>
  94 +
  95 + </div>
  96 +
  97 + <div class="col-lg-4 col-md-4 col-sm-7">
  98 + <div class="panel panel-default">
  99 + <div class="panel-heading">
  100 + <h3 class="panel-title">{% trans "Collaborations by Type" %}</h3>
  101 + </div>
  102 + <div class="panel-body">
  103 + <div id="collabs"></div>
  104 + <div class="chart collabs">
  105 + <canvas width="200" height="200"></canvas>
  106 + <p></p>
  107 + </div>
  108 + </div>
  109 + </div>
  110 + </div>
  111 +
  112 +
  113 + <div class="col-lg-4 col-md-4 col-sm-7">
  114 + <div class="panel panel-default">
  115 + <div class="panel-heading">
  116 + <h3 class="panel-title">{% trans "Participation by Group" %}</h3>
  117 + </div>
  118 + <div class="panel-body">
  119 + <div class="chart collabs2">
  120 + <canvas width="200" height="200"></canvas>
  121 + <p></p>
  122 + </div>
  123 + </div>
  124 + </div>
  125 + </div>
  126 +
  127 +
  128 + {% if user_.badge_set.exists %}
  129 + <div class="col-lg-8 col-md-12 col-sm-7">
  130 + <div class="panel panel-default">
  131 + <div class="panel-heading">
  132 + <h3 class="panel-title">{% trans "Badges" %}</h3>
  133 + </div>
  134 + <div class="panel-body">
  135 + <div>
  136 + {% for badge in user_.badge_set.all %}
  137 + {% translate badge as badge_trans %}
  138 + <img src="data:image/png;base64,{{ badge.image_base64 }}" title="({{ badge_trans.title }}) {{ badge_trans.description }}" />
  139 + {% endfor %}
  140 + </div>
  141 + </div>
  142 + </div>
  143 + </div>
  144 + {% endif %}
  145 +
  146 + </div> <!-- End of user-profile row -->
  147 +
  148 + <div class="row">
  149 +
  150 + <div class="col-lg-6 col-md-6 col-sm-12">
  151 + <h3>{% trans "Latest posted" %} </h3>
  152 + <ul class="message-list">
  153 + {% for doc in emails %}
  154 + {% include "message-preview.html" with result=doc %}
  155 + {% empty %}
  156 + <li>{% trans "There are no posts by this user so far." %}</li>
  157 + {% endfor %}
  158 + </ul>
  159 + <a href="{% url 'haystack_search' %}?type=thread&amp;author={{ user_.username }}">
  160 + {% trans "View more posts..." %}
  161 + </a>
  162 + <div>&nbsp;</div>
  163 + </div>
  164 +
  165 + <div class="col-lg-6 col-md-6 col-sm-12">
  166 + <h3>{% trans "Latest contributions" %}</h3>
  167 + <ul class="message-list">
  168 + {% for result in results %}
  169 + {% include "message-preview.html" %}
  170 + {% empty %}
  171 + <li>{% trans "No contributions of this user so far." %}</li>
  172 + {% endfor %}
  173 + </ul>
  174 + <a href="{% url 'haystack_search' %}?order=latest&amp;collaborators={{ user_.username }}">
  175 + {% trans "View more contributions..." %}
  176 + </a>
  177 + <div>&nbsp;</div>
  178 + </div>
  179 +
  180 + </div>
  181 +
  182 +{% endblock %}
... ...
colab/accounts/templates/accounts/user_update_form.html 0 → 100644
... ... @@ -0,0 +1,207 @@
  1 +{% extends "base.html" %}
  2 +{% load i18n gravatar %}
  3 +
  4 +{% block head_js %}
  5 +<script>
  6 +$(function() {
  7 +
  8 + $('#add-email').on('click', function(event) {
  9 + $.ajax({
  10 + url: "{% url 'archive_email_view' %}",
  11 + type: 'post',
  12 + data: { email: $('#new_email').val(), user: '{{ user_.pk }}' },
  13 + beforeSend: function(xhr, settings) {
  14 + xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
  15 + }
  16 + }).done(function() {
  17 + location.reload();
  18 + });
  19 +
  20 + event.preventDefault();
  21 + });
  22 +
  23 + $('#new_email').on('keypress', function(event) {
  24 + if (event.which == 13) {
  25 + event.preventDefault();
  26 + $('#add-email').trigger('click');
  27 + }
  28 + });
  29 +
  30 + $('.delete-email').on('click', function(event) {
  31 + var $email_block = $(event.target).parent().parent();
  32 + $.ajax({
  33 + url: "{% url 'archive_email_view' %}",
  34 + type: 'delete',
  35 + data: {
  36 + email: $('.email-address', $email_block).text(),
  37 + user: '{{ user_.pk }}'
  38 + },
  39 + context: $email_block[0],
  40 + beforeSend: function(xhr, settings) {
  41 + xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
  42 + }
  43 + }).done(function() {
  44 + $(this).remove();
  45 + });
  46 +
  47 + event.preventDefault();
  48 + });
  49 +
  50 + $('.verify-email').on('click', function(event) {
  51 + var $email_block = $(event.target).parent().parent();
  52 + $.ajax({
  53 + url: "{% url 'archive_email_validation_view' %}",
  54 + type: 'post',
  55 + data: {
  56 + email: $('.email-address', $email_block).text(),
  57 + user: '{{ user_.pk }}'
  58 + },
  59 + context: $email_block[0],
  60 + beforeSend: function(xhr, settings) {
  61 + xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
  62 + }
  63 + }).done(function() {
  64 + var email = $('.email-address', $(this)).text();
  65 + var msg = '{% trans "We sent a verification email to " %}' + email + '. ' +
  66 + '{% trans "Please follow the instructions in it." %}';
  67 + $('#alert-message').text(msg);
  68 + $('#alert-js').removeClass('alert-warning').addClass('alert-success');
  69 + $('#alert-js').show();
  70 + window.scroll(0, 0);
  71 + $('.verify-email').button('reset');
  72 + });
  73 +
  74 + event.preventDefault();
  75 + });
  76 +
  77 + $('.set-primary').on('click', function(event) {
  78 + var $email_block = $(event.target).parent().parent();
  79 + $.ajax({
  80 + url: "{% url 'archive_email_view' %}",
  81 + type: 'update',
  82 + data: {
  83 + email: $('.email-address', $email_block).text(),
  84 + user: '{{ user_.pk }}'
  85 + },
  86 + beforeSend: function(xhr, settings) {
  87 + xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
  88 + }
  89 + }).done(function() {
  90 + location.reload();
  91 + });
  92 +
  93 + event.preventDefault();
  94 + });
  95 +
  96 + // User feedbacks
  97 + $('.panel-default').on('click', '.set-primary, .verify-email, .delete-email', function() {
  98 + $(this).button('loading');
  99 + });
  100 +
  101 +});
  102 +</script>
  103 +{% endblock %}
  104 +
  105 +
  106 +{% block main-content %}
  107 +
  108 + <div class="col-lg-12">
  109 + {% with user_.first_name as firstname %}
  110 + <h2>{% trans 'profile information'|title %}</h2>
  111 + {% endwith %}
  112 +
  113 + <h3>{% gravatar user_.email 50 %} {{ user_.get_full_name }} ({{ user_.username }})</h3>
  114 + <a href="https://gravatar.com" target="_blank">
  115 + {% trans "Change your avatar at Gravatar.com" %}
  116 + </a>
  117 + </div>
  118 + <br>
  119 + <br>
  120 +
  121 + <form method="post">
  122 + {% csrf_token %}
  123 +
  124 + <div class="row">
  125 + <div class="col-lg-8 col-md-7 col-sm-12 col-xm-12">
  126 + {% for field in form %}
  127 + <div class="col-lg-6 col-md-6 col-sm-12 col-xm-12">
  128 + <div class="form-group{% if field.field.required %} required{% endif %}{% if field.errors %} alert alert-danger has-error{% endif %}">
  129 + <label for="{{ field.name }}" class="control-label">
  130 + {{ field.label }}
  131 + </label>
  132 + {{ field }}
  133 + {{ field.errors }}
  134 + </div>
  135 + </div>
  136 + {% endfor %}
  137 + </div>
  138 +
  139 + <div class="col-lg-4 col-md-5 col-sm-12 col-xm-12">
  140 + <div class="panel panel-default">
  141 + <div class="panel-heading">
  142 + <h3 class="panel-title">{% trans "Emails" %}</h3>
  143 + </div>
  144 + <div class="panel-body">
  145 + <ul class="unstyled-list emails">
  146 + {% for email in user_.emails.iterator %}
  147 + <li>
  148 + {% gravatar user_.email 30 %}
  149 + <span class="email-address">{{ email.address }}</span>
  150 + {% if email.address == user_.email %}
  151 + <span class="label label-success">{% trans "Primary" %}</span>
  152 + {% else %}
  153 + <div class="text-right">
  154 + <button class="btn btn-default set-primary" data-loading-text="{% trans 'Setting...' %}">{% trans "Set as Primary" %}</button>
  155 + <button class="btn btn-danger delete-email" data-loading-text="{% trans 'Deleting...' %}">{% trans "Delete" %}</button>
  156 + </div>
  157 + {% endif %}
  158 + <hr />
  159 + </li>
  160 + {% endfor %}
  161 + {% for email in user_.emails_not_validated.iterator %}
  162 + <li>
  163 + {% gravatar user_.email 30 %}
  164 + <span class="email-address">{{ email.address }}</span>
  165 + <div class="text-right">
  166 + <button class="btn btn-default verify-email" data-loading-text="{% trans 'Sending verification...' %}"><span class="icon-warning-sign"></span> {% trans "Verify" %}</button>
  167 + <button class="btn btn-danger delete-email">{% trans "Delete" %}</button>
  168 + </div>
  169 + <hr />
  170 + </li>
  171 + {% endfor %}
  172 + </ul>
  173 + <div class="form-group">
  174 + <label for="new_email">{% trans "Add another email address:" %}</label>
  175 + <input id="new_email" name="new_email" class="form-control" autocomplete="off" />
  176 + </div>
  177 + <button class="btn btn-primary pull-right" id="add-email">{% trans "Add" %}</button>
  178 + </div>
  179 + </div>
  180 + </div>
  181 + </div>
  182 +
  183 + <div class="row">
  184 + <div class="col-lg-12">
  185 + <div class="panel panel-default">
  186 + <div class="panel-heading">
  187 + <h3 class="panel-title">
  188 + {% trans 'Change Password' %}
  189 + </h3>
  190 + </div>
  191 + <div class="panel-body">
  192 + <div class="form-group">
  193 + {% trans "This feature is available only for those who need to change the password for some reason as having an old user with the same username, forgot your password to commit, usage of other XMPP Client for connection. Usually, you won't need to change this password. Only change it if you are sure about what you are doing." %}
  194 + </div>
  195 + <a href="{% url 'change_password' %}" class="btn btn-default pull-right"><span class="icon-warning-sign"></span> {% trans "Change Password" %}</a>
  196 + </div>
  197 + </div>
  198 + </div>
  199 + </div>
  200 +
  201 + <div class="row">
  202 + <div class="submit">
  203 + <button type="submit" class="btn btn-primary btn-lg btn-block">{% trans "Update" %}</button>
  204 + </div>
  205 + </div>
  206 + </form>
  207 +{% endblock %}
... ...
colab/accounts/templates/search/indexes/accounts/user_text.txt 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +{{ object.username }}
  2 +{{ object.get_full_name }}
  3 +{{ object.get_full_name|slugify }}
  4 +{{ object.institution }}
  5 +{{ object.institution|slugify }}
  6 +{{ object.role }}
  7 +{{ object.role|slugify }}
... ...
colab/accounts/templatetags/__init__.py 0 → 100644
colab/accounts/templatetags/gravatar.py 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +
  2 +from django import template
  3 +
  4 +from super_archives.models import EmailAddress
  5 +
  6 +
  7 +register = template.Library()
  8 +
  9 +
  10 +@register.simple_tag
  11 +def gravatar(email, size=80):
  12 + if isinstance(email, basestring):
  13 + try:
  14 + email = EmailAddress.objects.get(address=email)
  15 + except EmailAddress.DoesNotExist:
  16 + pass
  17 +
  18 + email_md5 = getattr(email, 'md5', 'anonymous')
  19 +
  20 + return u'<img src="http://www.gravatar.com/avatar/{}?s={}&d=mm" height="{}px" width="{}px" />'.format(email_md5, size, size, size)
... ...
colab/accounts/tests.py 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +"""
  2 +This file demonstrates writing tests using the unittest module. These will pass
  3 +when you run "manage.py test".
  4 +
  5 +Replace this with more appropriate tests for your application.
  6 +"""
  7 +
  8 +from django.test import TestCase
  9 +
  10 +
  11 +class SimpleTest(TestCase):
  12 + def test_basic_addition(self):
  13 + """
  14 + Tests that 1 + 1 always equals 2.
  15 + """
  16 + self.assertEqual(1 + 1, 2)
... ...
colab/accounts/urls.py 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +
  2 +from django.conf.urls import patterns, include, url
  3 +
  4 +from .views import (UserProfileDetailView, UserProfileUpdateView,
  5 + ManageUserSubscriptionsView, ChangeXMPPPasswordView)
  6 +
  7 +from accounts import views
  8 +
  9 +urlpatterns = patterns('',
  10 + url(r'^register/$', 'accounts.views.signup', name='signup'),
  11 +
  12 + url(r'^change-password/$',
  13 + ChangeXMPPPasswordView.as_view(), name='change_password'),
  14 +
  15 + url(r'^logout/?$', 'accounts.views.logoutColab', name='logout'),
  16 +
  17 + url(r'^(?P<username>[\w@+.-]+)/?$',
  18 + UserProfileDetailView.as_view(), name='user_profile'),
  19 +
  20 + url(r'^(?P<username>[\w@+.-]+)/edit/?$',
  21 + UserProfileUpdateView.as_view(), name='user_profile_update'),
  22 +
  23 + url(r'^(?P<username>[\w@+.-]+)/subscriptions/?$',
  24 + ManageUserSubscriptionsView.as_view(), name='user_list_subscriptions'),
  25 +)
... ...
colab/accounts/utils/__init__.py 0 → 100644
colab/accounts/utils/mailman.py 0 → 100644
... ... @@ -0,0 +1,97 @@
  1 +
  2 +import urlparse
  3 +import requests
  4 +import logging
  5 +
  6 +from django.conf import settings
  7 +
  8 +TIMEOUT = 1
  9 +
  10 +
  11 +def get_url(listname=None):
  12 + if listname:
  13 + return urlparse.urljoin(settings.MAILMAN_API_URL, '/' + listname)
  14 +
  15 + return settings.MAILMAN_API_URL
  16 +
  17 +
  18 +def subscribe(listname, address):
  19 + url = get_url(listname)
  20 + try:
  21 + requests.put(url, timeout=TIMEOUT, data={'address': address})
  22 + except:
  23 + logging.exception('Unable to subscribe user')
  24 + return False
  25 + return True
  26 +
  27 +
  28 +def unsubscribe(listname, address):
  29 + url = get_url(listname)
  30 + try:
  31 + requests.delete(url, timeout=TIMEOUT, data={'address': address})
  32 + except:
  33 + logging.exception('Unable to unsubscribe user')
  34 + return False
  35 + return True
  36 +
  37 +
  38 +def update_subscription(address, lists):
  39 + current_lists = address_lists(address)
  40 +
  41 + for maillist in current_lists:
  42 + if maillist not in lists:
  43 + unsubscribe(maillist, address)
  44 +
  45 + for maillist in lists:
  46 + if maillist not in current_lists:
  47 + subscribe(maillist, address)
  48 +
  49 +
  50 +def address_lists(address, description=''):
  51 + url = get_url()
  52 +
  53 + params = {'address': address,
  54 + 'description': description}
  55 +
  56 + try:
  57 + lists = requests.get(url, timeout=TIMEOUT, params=params)
  58 + except:
  59 + logging.exception('Unable to list mailing lists')
  60 + return []
  61 +
  62 + return lists.json()
  63 +
  64 +
  65 +def all_lists(*args, **kwargs):
  66 + return address_lists('', *args, **kwargs)
  67 +
  68 +
  69 +def user_lists(user):
  70 + list_set = set()
  71 +
  72 + for email in user.emails.values_list('address', flat=True):
  73 + list_set.update(address_lists(email))
  74 +
  75 + return tuple(list_set)
  76 +
  77 +
  78 +def get_list_description(listname, lists=None):
  79 + if not lists:
  80 + lists = dict(all_lists(description=True))
  81 + elif not isinstance(lists, dict):
  82 + lists = dict(lists)
  83 +
  84 + return lists.get(listname)
  85 +
  86 +
  87 +def list_users(listname):
  88 + url = get_url(listname)
  89 +
  90 + params = {}
  91 +
  92 + try:
  93 + users = requests.get(url, timeout=TIMEOUT, params=params)
  94 + except requests.exceptions.RequestException:
  95 + return []
  96 +
  97 + return users.json()
... ...
colab/accounts/utils/validators.py 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +
  2 +import urllib2
  3 +import urlparse
  4 +
  5 +
  6 +def validate_social_account(account, url):
  7 + """Verifies if a social account is valid.
  8 +
  9 + Examples:
  10 +
  11 + >>> validate_social_account('seocam', 'http://twitter.com')
  12 + True
  13 +
  14 + >>> validate_social_account('seocam-fake-should-fail', 'http://twitter.com')
  15 + False
  16 + """
  17 +
  18 + request = urllib2.Request(urlparse.urljoin(url, account))
  19 + request.get_method = lambda: 'HEAD'
  20 +
  21 + try:
  22 + response = urllib2.urlopen(request)
  23 + except urllib2.HTTPError:
  24 + return False
  25 +
  26 + return response.code == 200
... ...
colab/accounts/views.py 0 → 100644
... ... @@ -0,0 +1,251 @@
  1 +#!/usr/bin/env python
  2 +# encoding: utf-8
  3 +
  4 +import datetime
  5 +
  6 +from collections import OrderedDict
  7 +
  8 +from django.contrib.auth.views import logout
  9 +from django.contrib import messages
  10 +from django.db import transaction
  11 +from django.db.models import Count
  12 +from django.contrib.auth import get_user_model
  13 +from django.utils.translation import ugettext as _
  14 +from django.shortcuts import render, redirect, get_object_or_404
  15 +from django.core.urlresolvers import reverse
  16 +from django.core.exceptions import PermissionDenied
  17 +from django.views.generic import DetailView, UpdateView
  18 +from django.utils.decorators import method_decorator
  19 +
  20 +from django.http import HttpResponse
  21 +from conversejs import xmpp
  22 +from conversejs.models import XMPPAccount
  23 +from haystack.query import SearchQuerySet
  24 +
  25 +from super_archives.models import EmailAddress, Message
  26 +from search.utils import trans
  27 +#from proxy.trac.models import WikiCollabCount, TicketCollabCount
  28 +from .forms import (UserCreationForm, ListsForm, UserUpdateForm,
  29 + ChangeXMPPPasswordForm)
  30 +from .errors import XMPPChangePwdException
  31 +from .utils import mailman
  32 +
  33 +
  34 +class UserProfileBaseMixin(object):
  35 + model = get_user_model()
  36 + slug_field = 'username'
  37 + slug_url_kwarg = 'username'
  38 + context_object_name = 'user_'
  39 +
  40 +
  41 +class UserProfileUpdateView(UserProfileBaseMixin, UpdateView):
  42 + template_name = 'accounts/user_update_form.html'
  43 + form_class = UserUpdateForm
  44 +
  45 + def get_success_url(self):
  46 + return reverse('user_profile', kwargs={'username': self.object.username})
  47 +
  48 + def get_object(self, *args, **kwargs):
  49 + obj = super(UserProfileUpdateView, self).get_object(*args, **kwargs)
  50 + if self.request.user != obj and not self.request.user.is_superuser:
  51 + raise PermissionDenied
  52 +
  53 + return obj
  54 +
  55 +
  56 +class UserProfileDetailView(UserProfileBaseMixin, DetailView):
  57 + template_name = 'accounts/user_detail.html'
  58 +
  59 + def get_context_data(self, **kwargs):
  60 + user = self.object
  61 + context = {}
  62 +
  63 + count_types = OrderedDict()
  64 +
  65 + fields_or_lookup = (
  66 + {'collaborators__contains': user.username},
  67 + {'fullname_and_username__contains': user.username},
  68 + )
  69 +
  70 + counter_class = {}
  71 + #{
  72 + # 'wiki': WikiCollabCount,
  73 + # 'ticket': TicketCollabCount,
  74 + #}
  75 +
  76 + types = ['thread']
  77 + #types.extend(['ticket', 'wiki', 'changeset', 'attachment'])
  78 +
  79 + messages = Message.objects.filter(from_address__user__pk=user.pk)
  80 + for type in types:
  81 + CounterClass = counter_class.get(type)
  82 + if CounterClass:
  83 + try:
  84 + counter = CounterClass.objects.get(author=user.username)
  85 + except CounterClass.DoesNotExist:
  86 + count_types[trans(type)] = 0
  87 + else:
  88 + count_types[trans(type)] = counter.count
  89 + elif type == 'thread':
  90 + count_types[trans(type)] = messages.count()
  91 + else:
  92 + sqs = SearchQuerySet()
  93 + for filter_or in fields_or_lookup:
  94 + sqs = sqs.filter_or(type=type, **filter_or)
  95 + count_types[trans(type)] = sqs.count()
  96 +
  97 + context['type_count'] = count_types
  98 +
  99 + sqs = SearchQuerySet()
  100 + for filter_or in fields_or_lookup:
  101 + sqs = sqs.filter_or(**filter_or).exclude(type='thread')
  102 +
  103 + context['results'] = sqs.order_by('-modified', '-created')[:10]
  104 +
  105 + email_pks = [addr.pk for addr in user.emails.iterator()]
  106 + query = Message.objects.filter(from_address__in=email_pks)
  107 + query = query.order_by('-received_time')
  108 + context['emails'] = query[:10]
  109 +
  110 + count_by = 'thread__mailinglist__name'
  111 + context['list_activity'] = dict(messages.values_list(count_by)\
  112 + .annotate(Count(count_by))\
  113 + .order_by(count_by))
  114 +
  115 + context.update(kwargs)
  116 + return super(UserProfileDetailView, self).get_context_data(**context)
  117 +
  118 +
  119 +def logoutColab(request):
  120 + response = logout(request, next_page='/')
  121 + response.delete_cookie('_redmine_session')
  122 + response.delete_cookie('_gitlab_session')
  123 + return response
  124 +
  125 +
  126 +def signup(request):
  127 + # If the request method is GET just return the form
  128 + if request.method == 'GET':
  129 + user_form = UserCreationForm()
  130 + lists_form = ListsForm()
  131 + return render(request, 'accounts/user_create_form.html',
  132 + {'user_form': user_form, 'lists_form': lists_form})
  133 +
  134 + user_form = UserCreationForm(request.POST)
  135 + lists_form = ListsForm(request.POST)
  136 +
  137 + if not user_form.is_valid() or not lists_form.is_valid():
  138 + return render(request, 'accounts/user_create_form.html',
  139 + {'user_form': user_form, 'lists_form': lists_form})
  140 +
  141 + user = user_form.save()
  142 +
  143 + # Check if the user's email have been used previously
  144 + # in the mainling lists to link the user to old messages
  145 + email_addr, created = EmailAddress.objects.get_or_create(address=user.email)
  146 + if created:
  147 + email_addr.real_name = user.get_full_name()
  148 +
  149 + email_addr.user = user
  150 + email_addr.save()
  151 +
  152 + mailing_lists = lists_form.cleaned_data.get('lists')
  153 + mailman.update_subscription(user.email, mailing_lists)
  154 +
  155 + messages.success(request, _('Your profile has been created!'))
  156 + messages.warning(request, _('You must login to validated your profile. '
  157 + 'Profiles not validated are deleted in 24h.'))
  158 +
  159 + return redirect('user_profile', username=user.username)
  160 +
  161 +
  162 +class ManageUserSubscriptionsView(UserProfileBaseMixin, DetailView):
  163 + http_method_names = [u'get', u'post']
  164 + template_name = u'accounts/manage_subscriptions.html'
  165 +
  166 + def get_object(self, *args, **kwargs):
  167 + obj = super(ManageUserSubscriptionsView, self).get_object(*args,
  168 + **kwargs)
  169 + if self.request.user != obj and not self.request.user.is_superuser:
  170 + raise PermissionDenied
  171 +
  172 + return obj
  173 +
  174 + def post(self, request, *args, **kwargs):
  175 + user = self.get_object()
  176 + for email in user.emails.values_list('address', flat=True):
  177 + lists = self.request.POST.getlist(email)
  178 + user.update_subscription(email, lists)
  179 +
  180 + return redirect('user_profile', username=user.username)
  181 +
  182 + def get_context_data(self, **kwargs):
  183 + context = {}
  184 + context['membership'] = {}
  185 +
  186 + user = self.get_object()
  187 + emails = user.emails.values_list('address', flat=True)
  188 + all_lists = mailman.all_lists(description=True)
  189 +
  190 + for email in emails:
  191 + lists = []
  192 + lists_for_address = mailman.address_lists(email)
  193 + for listname, description in all_lists:
  194 + if listname in lists_for_address:
  195 + checked = True
  196 + else:
  197 + checked = False
  198 + lists.append((
  199 + {'listname': listname, 'description': description},
  200 + checked
  201 + ))
  202 +
  203 + context['membership'].update({email: lists})
  204 +
  205 + context.update(kwargs)
  206 +
  207 + return super(ManageUserSubscriptionsView, self).get_context_data(**context)
  208 +
  209 +
  210 +class ChangeXMPPPasswordView(UpdateView):
  211 + model = XMPPAccount
  212 + form_class = ChangeXMPPPasswordForm
  213 + fields = ['password', ]
  214 + template_name = 'accounts/change_password.html'
  215 +
  216 + def get_success_url(self):
  217 + return reverse('user_profile', kwargs={
  218 + 'username': self.request.user.username
  219 + })
  220 +
  221 + def get_object(self, queryset=None):
  222 + obj = get_object_or_404(XMPPAccount, user=self.request.user.pk)
  223 + self.old_password = obj.password
  224 + return obj
  225 +
  226 + def form_valid(self, form):
  227 + transaction.set_autocommit(False)
  228 +
  229 + response = super(ChangeXMPPPasswordView, self).form_valid(form)
  230 +
  231 + changed = xmpp.change_password(
  232 + self.object.jid,
  233 + self.old_password,
  234 + form.cleaned_data['password1']
  235 + )
  236 +
  237 + if not changed:
  238 + messages.error(
  239 + self.request,
  240 + _(u'Could not change your password. Please, try again later.')
  241 + )
  242 + transaction.rollback()
  243 + return response
  244 + else:
  245 + transaction.commit()
  246 +
  247 + messages.success(
  248 + self.request,
  249 + _("You've changed your password successfully!")
  250 + )
  251 + return response
... ...
colab/api/__init__.py 0 → 100644
colab/api/resources.py 0 → 100644
... ... @@ -0,0 +1,120 @@
  1 +# -*- coding: utf-8 -*-
  2 +
  3 +from django.contrib.auth import get_user_model
  4 +
  5 +from tastypie import fields
  6 +from tastypie.constants import ALL_WITH_RELATIONS, ALL
  7 +from tastypie.resources import ModelResource
  8 +
  9 +from super_archives.models import Message, EmailAddress
  10 +#from proxy.trac.models import Revision, Ticket, Wiki
  11 +
  12 +User = get_user_model()
  13 +
  14 +
  15 +class UserResource(ModelResource):
  16 + class Meta:
  17 + queryset = User.objects.filter(is_active=True)
  18 + resource_name = 'user'
  19 + fields = ['username', 'institution', 'role', 'bio', 'first_name',
  20 + 'last_name', 'email']
  21 + allowed_methods = ['get', ]
  22 + filtering = {
  23 + 'email': ('exact', ),
  24 + 'username': ALL,
  25 + 'institution': ALL,
  26 + 'role': ALL,
  27 + 'bio': ALL,
  28 + }
  29 +
  30 + def dehydrate_email(self, bundle):
  31 + return ''
  32 +
  33 +
  34 +class EmailAddressResource(ModelResource):
  35 + user = fields.ForeignKey(UserResource, 'user', full=False, null=True)
  36 +
  37 + class Meta:
  38 + queryset = EmailAddress.objects.all()
  39 + resource_name = 'emailaddress'
  40 + excludes = ['md5', ]
  41 + allowed_methods = ['get', ]
  42 + filtering = {
  43 + 'address': ('exact', ),
  44 + 'user': ALL_WITH_RELATIONS,
  45 + 'real_name': ALL,
  46 + }
  47 +
  48 + def dehydrate_address(self, bundle):
  49 + return ''
  50 +
  51 +
  52 +class MessageResource(ModelResource):
  53 + from_address = fields.ForeignKey(EmailAddressResource, 'from_address',
  54 + full=False)
  55 +
  56 + class Meta:
  57 + queryset = Message.objects.all()
  58 + resource_name = 'message'
  59 + excludes = ['spam', 'subject_clean', 'message_id']
  60 + filtering = {
  61 + 'from_address': ALL_WITH_RELATIONS,
  62 + 'subject': ALL,
  63 + 'body': ALL,
  64 + 'received_time': ALL,
  65 + }
  66 +
  67 +
  68 +#class RevisionResource(ModelResource):
  69 +# class Meta:
  70 +# queryset = Revision.objects.all()
  71 +# resource_name = 'revision'
  72 +# excludes = ['collaborators', ]
  73 +# filtering = {
  74 +# 'key': ALL,
  75 +# 'rev': ALL,
  76 +# 'author': ALL,
  77 +# 'message': ALL,
  78 +# 'repository_name': ALL,
  79 +# 'created': ALL,
  80 +# }
  81 +#
  82 +#
  83 +#class TicketResource(ModelResource):
  84 +# class Meta:
  85 +# queryset = Ticket.objects.all()
  86 +# resource_name = 'ticket'
  87 +# excludes = ['collaborators', ]
  88 +# filtering = {
  89 +# 'id': ALL,
  90 +# 'summary': ALL,
  91 +# 'description': ALL,
  92 +# 'milestone': ALL,
  93 +# 'priority': ALL,
  94 +# 'component': ALL,
  95 +# 'version': ALL,
  96 +# 'severity': ALL,
  97 +# 'reporter': ALL,
  98 +# 'author': ALL,
  99 +# 'status': ALL,
  100 +# 'keywords': ALL,
  101 +# 'created': ALL,
  102 +# 'modified': ALL,
  103 +# 'modified_by': ALL,
  104 +# }
  105 +#
  106 +#
  107 +#class WikiResource(ModelResource):
  108 +# class Meta:
  109 +# queryset = Wiki.objects.all()
  110 +# resource_name = 'wiki'
  111 +# excludes = ['collaborators', ]
  112 +# filtering = {
  113 +# 'name': ALL,
  114 +# 'wiki_text': ALL,
  115 +# 'author': ALL,
  116 +# 'name': ALL,
  117 +# 'created': ALL,
  118 +# 'modified': ALL,
  119 +# 'modified_by': ALL,
  120 +# }
... ...
colab/api/urls.py 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +# -*- coding: utf-8 -*-
  2 +
  3 +from django.conf.urls import patterns, include, url
  4 +
  5 +from tastypie.api import Api
  6 +
  7 +from .resources import (UserResource, EmailAddressResource, MessageResource)
  8 +from .views import VoteView
  9 +
  10 +
  11 +api = Api(api_name='v1')
  12 +api.register(UserResource())
  13 +api.register(EmailAddressResource())
  14 +api.register(MessageResource())
  15 +
  16 +
  17 +urlpatterns = patterns('',
  18 + url(r'message/(?P<msg_id>\d+)/vote$', VoteView.as_view()),
  19 +
  20 + # tastypie urls
  21 + url(r'', include(api.urls)),
  22 +)
... ...
colab/api/views.py 0 → 100644
... ... @@ -0,0 +1,45 @@
  1 +
  2 +from django import http
  3 +from django.db import IntegrityError
  4 +from django.views.generic import View
  5 +from django.core.exceptions import ObjectDoesNotExist
  6 +
  7 +
  8 +from super_archives.models import Message
  9 +
  10 +
  11 +class VoteView(View):
  12 +
  13 + http_method_names = [u'get', u'put', u'delete', u'head']
  14 +
  15 + def put(self, request, msg_id):
  16 + if not request.user.is_authenticated():
  17 + return http.HttpResponseForbidden()
  18 +
  19 + try:
  20 + Message.objects.get(id=msg_id).vote(request.user)
  21 + except IntegrityError:
  22 + # 409 Conflict
  23 + # used for duplicated entries
  24 + return http.HttpResponse(status=409)
  25 +
  26 + # 201 Created
  27 + return http.HttpResponse(status=201)
  28 +
  29 + def get(self, request, msg_id):
  30 + votes = Message.objects.get(id=msg_id).votes_count()
  31 + return http.HttpResponse(votes, content_type='application/json')
  32 +
  33 + def delete(self, request, msg_id):
  34 + if not request.user.is_authenticated():
  35 + return http.HttpResponseForbidden()
  36 +
  37 + try:
  38 + Message.objects.get(id=msg_id).unvote(request.user)
  39 + except ObjectDoesNotExist:
  40 + return http.HttpResponseGone()
  41 +
  42 + # 204 No Content
  43 + # empty body, as per RFC2616.
  44 + # object deleted
  45 + return http.HttpResponse(status=204)
... ...
colab/badger/__init__.py 0 → 100644
colab/badger/admin.py 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +# -*- coding: utf-8 -*-
  2 +
  3 +from django.contrib import admin
  4 +from django.utils.translation import ugettext_lazy as _
  5 +
  6 +from .forms import BadgeForm
  7 +from .models import Badge, BadgeI18N
  8 +
  9 +
  10 +class BadgeI18NInline(admin.TabularInline):
  11 + model = BadgeI18N
  12 +
  13 +
  14 +class BadgeAdmin(admin.ModelAdmin):
  15 + form = BadgeForm
  16 + inlines = [BadgeI18NInline, ]
  17 + list_display = ['title', 'description', 'order']
  18 + list_editable = ['order', ]
  19 +
  20 +
  21 +admin.site.register(Badge, BadgeAdmin)
... ...
colab/badger/forms.py 0 → 100644
... ... @@ -0,0 +1,48 @@
  1 +# -*- coding: utf-8 -*-
  2 +
  3 +import base64
  4 +
  5 +from django import forms
  6 +from django.utils.translation import ugettext_lazy as _
  7 +
  8 +from PIL import Image
  9 +
  10 +from .models import Badge
  11 +
  12 +try:
  13 + from cStringIO import StringIO
  14 +except ImportError:
  15 + from StringIO import StringIO
  16 +
  17 +
  18 +class BadgeForm(forms.ModelForm):
  19 + image = forms.ImageField(label=_(u'Image'), required=False)
  20 +
  21 + class Meta:
  22 + model = Badge
  23 + fields = (
  24 + 'title', 'description', 'image', 'user_attr', 'comparison',
  25 + 'value', 'awardees'
  26 + )
  27 +
  28 + def clean_image(self):
  29 + if not self.instance.pk and not self.cleaned_data['image']:
  30 + raise forms.ValidationError(_(u'You must add an Image'))
  31 + return self.cleaned_data['image']
  32 +
  33 + def save(self, commit=True):
  34 +
  35 + instance = super(BadgeForm, self).save(commit=False)
  36 +
  37 + if self.cleaned_data['image']:
  38 + img = Image.open(self.cleaned_data['image'])
  39 + img = img.resize((50, 50), Image.ANTIALIAS)
  40 + f = StringIO()
  41 + img.save(f, 'png')
  42 + instance.image_base64 = f.getvalue().encode('base64')
  43 + f.close()
  44 +
  45 + if commit:
  46 + instance.save()
  47 +
  48 + return instance
... ...
colab/badger/management/__init__.py 0 → 100644
colab/badger/management/commands/__init__.py 0 → 100644
colab/badger/management/commands/rebuild_badges.py 0 → 100644
... ... @@ -0,0 +1,44 @@
  1 +# -*- coding: utf-8 -*-
  2 +
  3 +from django.core.management.base import BaseCommand, CommandError
  4 +from haystack.query import SearchQuerySet
  5 +
  6 +from accounts.models import User
  7 +from badger.models import Badge
  8 +
  9 +
  10 +class Command(BaseCommand):
  11 + help = "Rebuild the user's badges."
  12 +
  13 + def handle(self, *args, **kwargs):
  14 + for badge in Badge.objects.filter(type='auto'):
  15 + if not badge.comparison:
  16 + continue
  17 + elif badge.comparison == 'biggest':
  18 + order = u'-{}'.format(Badge.USER_ATTR_OPTS[badge.user_attr])
  19 + sqs = SearchQuerySet().filter(type='user')
  20 + user = sqs.order_by(order)[0]
  21 + badge.awardees.remove(*list(badge.awardees.all()))
  22 + badge.awardees.add(User.objects.get(pk=user.pk))
  23 + continue
  24 +
  25 + comparison = u'__{}'.format(badge.comparison) if badge.comparison \
  26 + is not 'equal' else u''
  27 +
  28 + key = u'{}{}'.format(
  29 + Badge.USER_ATTR_OPTS[badge.user_attr],
  30 + comparison
  31 + )
  32 + opts = {key: badge.value}
  33 +
  34 + sqs = SearchQuerySet().filter(
  35 + type='user',
  36 + **opts
  37 + )
  38 +
  39 + # Remove all awardees to make sure that all of then
  40 + # still accomplish the necessary to keep the badge
  41 + badge.awardees.remove(*list(badge.awardees.all()))
  42 +
  43 + for user in sqs:
  44 + badge.awardees.add(User.objects.get(pk=user.pk))
... ...
colab/badger/management/commands/update_badges.py 0 → 100644
... ... @@ -0,0 +1,44 @@
  1 +# -*- coding: utf-8 -*-
  2 +
  3 +from django.core.management.base import BaseCommand, CommandError
  4 +from haystack.query import SearchQuerySet
  5 +
  6 +from accounts.models import User
  7 +from badger.models import Badge
  8 +
  9 +import logging
  10 +
  11 +class Command(BaseCommand):
  12 + help = "Update the user's badges"
  13 +
  14 + def update_badges(self):
  15 + for badge in Badge.objects.filter(type='auto'):
  16 + if not badge.comparison:
  17 + continue
  18 + elif badge.comparison == 'biggest':
  19 + order = u'-{}'.format(Badge.USER_ATTR_OPTS[badge.user_attr])
  20 + sqs = SearchQuerySet().filter(type='user')
  21 + user = sqs.order_by(order)[0]
  22 + badge.awardees.add(User.objects.get(pk=user.pk))
  23 + continue
  24 +
  25 + comparison = u'__{}'.format(badge.comparison) if badge.comparison \
  26 + is not 'equal' else u''
  27 +
  28 + key = u'{}{}'.format(
  29 + Badge.USER_ATTR_OPTS[badge.user_attr],
  30 + comparison
  31 + )
  32 + opts = {key: badge.value}
  33 +
  34 + sqs = SearchQuerySet().filter(type='user', **opts)
  35 +
  36 + for user in sqs:
  37 + badge.awardees.add(User.objects.get(pk=user.pk))
  38 +
  39 + def handle(self, *args, **kwargs):
  40 + try:
  41 + self.update_badges()
  42 + except Exception as e:
  43 + logging.exception(e)
  44 + raise
... ...
colab/badger/migrations/0001_initial.py 0 → 100644
... ... @@ -0,0 +1,53 @@
  1 +# -*- coding: utf-8 -*-
  2 +from __future__ import unicode_literals
  3 +
  4 +from django.db import models, migrations
  5 +from django.conf import settings
  6 +
  7 +
  8 +class Migration(migrations.Migration):
  9 +
  10 + dependencies = [
  11 + migrations.swappable_dependency(settings.AUTH_USER_MODEL),
  12 + ]
  13 +
  14 + operations = [
  15 + migrations.CreateModel(
  16 + name='Badge',
  17 + fields=[
  18 + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
  19 + ('title', models.CharField(max_length=200, null=True, verbose_name='Title', blank=True)),
  20 + ('description', models.CharField(max_length=200, null=True, verbose_name='Description', blank=True)),
  21 + ('image_base64', models.TextField(verbose_name='Image')),
  22 + ('type', models.CharField(max_length=200, verbose_name='Type', choices=[('auto', 'Automatically'), ('manual', 'Manual')])),
  23 + ('user_attr', models.CharField(blank=True, max_length=100, null=True, verbose_name='User attribute', choices=[('messages', 'Messages'), ('contributions', 'Contributions'), ('wikis', 'Wikis'), ('revisions', 'Revisions'), ('tickets', 'Ticket')])),
  24 + ('comparison', models.CharField(blank=True, max_length=10, null=True, verbose_name='Comparison', choices=[('gte', 'Greater than or equal'), ('lte', 'less than or equal'), ('equal', 'Equal'), ('biggest', 'Biggest')])),
  25 + ('value', models.PositiveSmallIntegerField(null=True, verbose_name='Value', blank=True)),
  26 + ('order', models.PositiveSmallIntegerField(default=100, verbose_name='Order')),
  27 + ('awardees', models.ManyToManyField(to=settings.AUTH_USER_MODEL, null=True, verbose_name='Awardees', blank=True)),
  28 + ],
  29 + options={
  30 + 'ordering': ['order'],
  31 + 'verbose_name': 'Badge',
  32 + 'verbose_name_plural': 'Badges',
  33 + },
  34 + bases=(models.Model,),
  35 + ),
  36 + migrations.CreateModel(
  37 + name='BadgeI18N',
  38 + fields=[
  39 + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
  40 + ('i18n_language', models.CharField(max_length=10, verbose_name='language', choices=[(b'pt-br', 'Portuguese'), (b'es', 'Spanish')])),
  41 + ('title', models.CharField(max_length=200, null=True, verbose_name='Title', blank=True)),
  42 + ('description', models.CharField(max_length=200, null=True, verbose_name='Description', blank=True)),
  43 + ('i18n_source', models.ForeignKey(related_name=b'translations', editable=False, to='badger.Badge', verbose_name='source')),
  44 + ],
  45 + options={
  46 + },
  47 + bases=(models.Model,),
  48 + ),
  49 + migrations.AlterUniqueTogether(
  50 + name='badgei18n',
  51 + unique_together=set([('i18n_source', 'i18n_language')]),
  52 + ),
  53 + ]
... ...
colab/badger/migrations/__init__.py 0 → 100644
colab/badger/models.py 0 → 100644
... ... @@ -0,0 +1,83 @@
  1 +# -*- coding: utf-8 -*-
  2 +
  3 +from django.conf import settings
  4 +from django.db import models
  5 +from django.utils.translation import ugettext_lazy as _
  6 +from i18n_model.models import I18nModel
  7 +
  8 +
  9 +class Badge(models.Model):
  10 + COMPARISON_CHOICES = (
  11 + (u'gte', _(u'Greater than or equal')),
  12 + (u'lte', _(u'less than or equal')),
  13 + (u'equal', _(u'Equal')),
  14 + (u'biggest', _(u'Biggest')),
  15 + )
  16 + TYPE_CHOICES = (
  17 + (u'auto', _(u'Automatically')),
  18 + (u'manual', _(u'Manual')),
  19 + )
  20 + USER_ATTR_CHOICES = (
  21 + (u'messages', _(u'Messages')),
  22 + (u'contributions', _(u'Contributions')),
  23 + (u'wikis', _(u'Wikis')),
  24 + (u'revisions', _(u'Revisions')),
  25 + (u'tickets', _(u'Ticket')),
  26 + )
  27 + USER_ATTR_OPTS = {
  28 + u'messages': u'message_count',
  29 + u'revisions': u'changeset_count',
  30 + u'tickets': u'ticket_count',
  31 + u'wikis': u'wiki_count',
  32 + u'contributions': u'contribution_count',
  33 + }
  34 +
  35 + title = models.CharField(_(u'Title'), max_length=200, blank=True,
  36 + null=True)
  37 + description = models.CharField(_(u'Description'), max_length=200,
  38 + blank=True, null=True)
  39 + image_base64 = models.TextField(_(u'Image'))
  40 + type = models.CharField(_(u'Type'), max_length=200, choices=TYPE_CHOICES)
  41 + user_attr = models.CharField(
  42 + _(u'User attribute'),max_length=100,
  43 + choices=USER_ATTR_CHOICES,
  44 + blank=True,
  45 + null=True,
  46 + )
  47 + comparison = models.CharField(
  48 + _(u'Comparison'),
  49 + max_length=10,
  50 + choices=COMPARISON_CHOICES,
  51 + blank=True,
  52 + null=True
  53 + )
  54 + value = models.PositiveSmallIntegerField(
  55 + _(u'Value'),
  56 + blank=True,
  57 + null=True
  58 + )
  59 + awardees = models.ManyToManyField(
  60 + settings.AUTH_USER_MODEL,
  61 + verbose_name=_(u'Awardees'),
  62 + blank=True,
  63 + null=True
  64 + )
  65 + order = models.PositiveSmallIntegerField(_(u'Order'), default=100)
  66 +
  67 + class Meta:
  68 + verbose_name = _(u'Badge')
  69 + verbose_name_plural = _(u'Badges')
  70 + ordering = ['order', ]
  71 +
  72 + def __unicode__(self):
  73 + return u'{} ({}, {})'.format(
  74 + self.title,
  75 + self.get_user_attr_display(),
  76 + self.get_type_display(),
  77 + )
  78 +
  79 +
  80 +class BadgeI18N(I18nModel):
  81 + class Meta:
  82 + source_model = Badge
  83 + translation_fields = ('title', 'description')
... ...
colab/badger/tests.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.test import TestCase
  2 +
  3 +# Create your tests here.
... ...
colab/badger/utils.py 0 → 100644
... ... @@ -0,0 +1,41 @@
  1 +# -*- coding: utf-8 -*-
  2 +
  3 +from django.db.models import Count
  4 +
  5 +#from proxy.trac.models import (Revision, Ticket, Wiki,
  6 +# WikiCollabCount, TicketCollabCount)
  7 +from accounts.models import User
  8 +
  9 +
  10 +def get_wiki_counters():
  11 + return {author: count for author, count in
  12 + WikiCollabCount.objects.values_list()}
  13 +
  14 +
  15 +def get_revision_counters():
  16 + return {
  17 + author: count for author, count in Revision.objects.values_list(
  18 + 'author'
  19 + ).annotate(count=Count('author'))
  20 + }
  21 +
  22 +
  23 +def get_ticket_counters():
  24 + return {author: count for author, count in
  25 + TicketCollabCount.objects.values_list()}
  26 +
  27 +
  28 +def get_users_counters():
  29 + wiki_counters = get_wiki_counters()
  30 + revision_counters = get_revision_counters()
  31 + ticket_counters = get_ticket_counters()
  32 +
  33 + users_counters = {}
  34 + for user in User.objects.annotate(message_count=Count('emails__message')):
  35 + users_counters[user.username] = {
  36 + 'messages': user.message_count,
  37 + 'wikis': wiki_counters.get(user.username, 0),
  38 + 'revisions': revision_counters.get(user.username, 0),
  39 + 'tickets': ticket_counters.get(user.username, 0),
  40 + }
  41 + return users_counters
... ...
colab/badger/views.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.shortcuts import render
  2 +
  3 +# Create your views here.
... ...
colab/colab/__init__.py 0 → 100644
colab/colab/colab.template.yaml 0 → 100644
... ... @@ -0,0 +1,53 @@
  1 +
  2 +DEBUG: false
  3 +TEMPLATE_DEBUG: false
  4 +
  5 +ADMINS: &admin
  6 + -
  7 + - John Foo
  8 + - john@example.com
  9 + -
  10 + - Mary Bar
  11 + - mary@example.com
  12 +
  13 +MANAGERS: *admin
  14 +
  15 +COLAB_FROM_ADDRESS: '"Colab" <noreply@example.com>'
  16 +SERVER_EMAIL: '"Colab" <noreply@example.com>'
  17 +
  18 +EMAIL_HOST: localhost
  19 +EMAIL_PORT: 25
  20 +EMAIL_SUBJECT_PREFIX: '[colab]'
  21 +
  22 +SECRET_KEY: '{{ secret_key }}'
  23 +
  24 +SITE_URL: 'http://www.example.com/'
  25 +
  26 +ALLOWED_HOSTS:
  27 + - example.com
  28 + - example.org
  29 + - example.net
  30 +
  31 +CONVERSEJS_ENABLED: false
  32 +
  33 +CONVERSEJS_AUTO_REGISTER: 'xmpp.example.com'
  34 +
  35 +DATABASES:
  36 + default:
  37 + ENGINE: django.db.backends.postgresql_psycopg2
  38 + HOST: localhost
  39 + NAME: colab
  40 + USER: colab
  41 + PASSWORD: colab
  42 +
  43 +ROBOTS_NOINDEX: false
  44 +
  45 +# Set to false to disable
  46 +RAVEN_DSN: 'http://public:secret@example.com/1'
  47 +
  48 +PROXIED_APPS:
  49 + gitlab:
  50 + upstream: 'http://localhost:8090/gitlab/'
  51 + trac:
  52 + upstream: 'http://localhost:5000/trac/'
  53 +
... ...
colab/colab/settings.py 0 → 100644
... ... @@ -0,0 +1,315 @@
  1 +"""
  2 +Django settings for colab project.
  3 +
  4 +For more information on this file, see
  5 +https://docs.djangoproject.com/en/1.7/topics/settings/
  6 +
  7 +For the full list of settings and their values, see
  8 +https://docs.djangoproject.com/en/1.7/ref/settings/
  9 +"""
  10 +
  11 +# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
  12 +import os
  13 +BASE_DIR = os.path.dirname(os.path.dirname(__file__))
  14 +
  15 +# Used for settings translation
  16 +from django.utils.translation import ugettext_lazy as _
  17 +
  18 +# Quick-start development settings - unsuitable for production
  19 +# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
  20 +
  21 +# SECURITY WARNING: keep the secret key used in production secret!
  22 +SECRET_KEY = "{{ secret_key }}"
  23 +
  24 +# SECURITY WARNING: don't run with debug turned on in production!
  25 +DEBUG = True
  26 +
  27 +TEMPLATE_DEBUG = True
  28 +
  29 +ALLOWED_HOSTS = []
  30 +
  31 +DATABASE_ROUTERS = []
  32 +
  33 +# Application definition
  34 +
  35 +INSTALLED_APPS = (
  36 + 'django.contrib.admin',
  37 + 'django.contrib.auth',
  38 + 'django.contrib.contenttypes',
  39 + 'django.contrib.sessions',
  40 + 'django.contrib.messages',
  41 + 'django.contrib.staticfiles',
  42 +
  43 + # First app to provide AUTH_USER_MODEL to others
  44 + 'accounts',
  45 +
  46 + # Not standard apps
  47 + 'raven.contrib.django.raven_compat',
  48 + 'cliauth',
  49 + 'django_mobile',
  50 + 'django_browserid',
  51 + 'conversejs',
  52 + 'haystack',
  53 + 'hitcounter',
  54 + 'i18n_model',
  55 + 'mptt',
  56 + 'dpaste',
  57 +
  58 + # Own apps
  59 + 'super_archives',
  60 + 'api',
  61 + 'rss',
  62 + 'planet',
  63 + 'search',
  64 + 'badger',
  65 + 'tz',
  66 +
  67 + # Feedzilla and deps
  68 + 'feedzilla',
  69 + 'taggit',
  70 + 'common',
  71 +)
  72 +
  73 +MIDDLEWARE_CLASSES = (
  74 + 'django.contrib.sessions.middleware.SessionMiddleware',
  75 + 'django.middleware.common.CommonMiddleware',
  76 + 'django.middleware.csrf.CsrfViewMiddleware',
  77 + 'django.contrib.auth.middleware.AuthenticationMiddleware',
  78 + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
  79 + 'django.contrib.messages.middleware.MessageMiddleware',
  80 + 'django.middleware.clickjacking.XFrameOptionsMiddleware',
  81 +)
  82 +
  83 +ROOT_URLCONF = 'colab.urls'
  84 +
  85 +WSGI_APPLICATION = 'colab.wsgi.application'
  86 +
  87 +# Internationalization
  88 +# https://docs.djangoproject.com/en/1.7/topics/i18n/
  89 +
  90 +LANGUAGE_CODE = 'en-us'
  91 +
  92 +TIME_ZONE = 'UTC'
  93 +
  94 +USE_I18N = True
  95 +
  96 +USE_L10N = True
  97 +
  98 +USE_TZ = True
  99 +
  100 +
  101 +# Static files (CSS, JavaScript, Images)
  102 +# https://docs.djangoproject.com/en/1.7/howto/static-files/
  103 +
  104 +STATIC_ROOT = '/usr/share/nginx/colab/static/'
  105 +MEDIA_ROOT = '/usr/share/nginx/colab/media/'
  106 +
  107 +STATIC_URL = '/static/'
  108 +MEDIA_URL = '/media/'
  109 +
  110 +
  111 +# Normally you should not import ANYTHING from Django directly
  112 +# into your settings, but ImproperlyConfigured is an exception.
  113 +from django.core.exceptions import ImproperlyConfigured
  114 +
  115 +
  116 +def get_env_setting(setting):
  117 + """ Get the environment setting or return exception """
  118 + try:
  119 + return os.environ[setting]
  120 + except KeyError:
  121 + error_msg = "Set the %s env variable" % setting
  122 + raise ImproperlyConfigured(error_msg)
  123 +
  124 +
  125 +# Allow Django runserver to serve SVG files
  126 +# https://code.djangoproject.com/ticket/20162
  127 +import mimetypes
  128 +mimetypes.add_type('image/svg+xml', '.svg')
  129 +
  130 +LANGUAGES = (
  131 + ('en', _('English')),
  132 + ('pt-br', _('Portuguese')),
  133 + ('es', _('Spanish')),
  134 +)
  135 +
  136 +DJANGO_DATE_FORMAT_TO_JS = {
  137 + 'pt-br': ('pt-BR', 'dd/MM/yyyy'),
  138 + 'es': ('es', 'dd/MM/yyyy'),
  139 +}
  140 +
  141 +LANGUAGE_CODE = 'en'
  142 +
  143 +# The absolute path to the folder containing the attachments
  144 +ATTACHMENTS_FOLDER_PATH = '/mnt/trac/attachments/'
  145 +
  146 +# ORDERING_DATA receives the options to order for as it's keys and a dict as
  147 +# value, if you want to order for the last name, you can use something like:
  148 +# 'last_name': {'name': 'Last Name', 'fields': 'last_name'} inside the dict,
  149 +# you pass two major keys (name, fields)
  150 +# The major key name is the name to appear on the template
  151 +# the major key fields it show receive the name of the fields to order for in
  152 +# the indexes
  153 +
  154 +ORDERING_DATA = {
  155 + 'latest': {
  156 + 'name': _(u'Recent activity'),
  157 + 'fields': ('-modified', '-created'),
  158 + },
  159 + 'hottest': {
  160 + 'name': _(u'Relevance'),
  161 + 'fields': None,
  162 + },
  163 +}
  164 +
  165 +
  166 +# File type groupings is a tuple of tuples containg what it should filter,
  167 +# how it should be displayed, and a tuple of which mimetypes it includes
  168 +FILE_TYPE_GROUPINGS = (
  169 + ('document', _(u'Document'),
  170 + ('doc', 'docx', 'odt', 'otx', 'dotx', 'pdf', 'ott')),
  171 + ('presentation', _(u'Presentation'), ('ppt', 'pptx', 'odp')),
  172 + ('text', _(u'Text'), ('txt', 'po', 'conf', 'log')),
  173 + ('code', _(u'Code'),
  174 + ('py', 'php', 'js', 'sql', 'sh', 'patch', 'diff', 'html', '')),
  175 + ('compressed', _(u'Compressed'), ('rar', 'zip', 'gz', 'tgz', 'bz2')),
  176 + ('image', _(u'Image'),
  177 + ('jpg', 'jpeg', 'png', 'tiff', 'gif', 'svg', 'psd', 'planner', 'cdr')),
  178 + ('spreadsheet', _(u'Spreadsheet'),
  179 + ('ods', 'xls', 'xlsx', 'xslt', 'csv')),
  180 +)
  181 +
  182 +# the following variable define how many characters should be shown before
  183 +# a highlighted word, to make sure that the highlighted word will appear
  184 +HIGHLIGHT_NUM_CHARS_BEFORE_MATCH = 30
  185 +HAYSTACK_CUSTOM_HIGHLIGHTER = 'colab.utils.highlighting.ColabHighlighter'
  186 +
  187 +HAYSTACK_CONNECTIONS = {
  188 + 'default': {
  189 + 'ENGINE': 'haystack.backends.solr_backend.SolrEngine',
  190 + 'URL': 'http://localhost:8983/solr/',
  191 + }
  192 +}
  193 +
  194 +CACHES = {
  195 + 'default': {
  196 + 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
  197 + 'LOCATION': '127.0.0.1:11211',
  198 + }
  199 +}
  200 +
  201 +DATABASE_ROUTERS = []
  202 +
  203 +
  204 +TEMPLATE_CONTEXT_PROCESSORS = (
  205 + 'django.contrib.auth.context_processors.auth',
  206 + 'django.core.context_processors.debug',
  207 + 'django.core.context_processors.i18n',
  208 + 'django.core.context_processors.media',
  209 + 'django.core.context_processors.static',
  210 + 'django.core.context_processors.tz',
  211 + 'django.contrib.messages.context_processors.messages',
  212 + 'django.core.context_processors.request',
  213 + 'django_mobile.context_processors.is_mobile',
  214 + 'super_archives.context_processors.mailarchive',
  215 + 'proxy.context_processors.proxied_apps',
  216 + 'home.context_processors.robots',
  217 +)
  218 +
  219 +MIDDLEWARE_CLASSES = (
  220 + 'django.contrib.sessions.middleware.SessionMiddleware',
  221 + 'django.middleware.locale.LocaleMiddleware',
  222 + 'django.middleware.common.CommonMiddleware',
  223 + 'django.middleware.csrf.CsrfViewMiddleware',
  224 + 'django.contrib.auth.middleware.AuthenticationMiddleware',
  225 + 'django.contrib.messages.middleware.MessageMiddleware',
  226 + 'django.middleware.clickjacking.XFrameOptionsMiddleware',
  227 + 'django_mobile.middleware.MobileDetectionMiddleware',
  228 + 'django_mobile.middleware.SetFlavourMiddleware',
  229 + 'tz.middleware.TimezoneMiddleware',
  230 +)
  231 +
  232 +# Add the django_browserid authentication backend.
  233 +AUTHENTICATION_BACKENDS = (
  234 + 'django.contrib.auth.backends.ModelBackend',
  235 + 'accounts.auth.ColabBrowserIDBackend',
  236 +)
  237 +
  238 +STATICFILES_DIRS = (
  239 + os.path.join(BASE_DIR, 'static'),
  240 +)
  241 +
  242 +TEMPLATE_DIRS = (
  243 + os.path.join(BASE_DIR, 'templates'),
  244 +)
  245 +
  246 +LOCALE_PATHS = (
  247 + os.path.join(BASE_DIR, 'locale'),
  248 +)
  249 +
  250 +AUTH_USER_MODEL = 'accounts.User'
  251 +
  252 +from django.contrib.messages import constants as messages
  253 +MESSAGE_TAGS = {
  254 + messages.INFO: 'alert-info',
  255 + messages.SUCCESS: 'alert-success',
  256 + messages.WARNING: 'alert-warning',
  257 + messages.ERROR: 'alert-danger',
  258 +}
  259 +
  260 +### Feedzilla (planet)
  261 +from feedzilla.settings import *
  262 +FEEDZILLA_PAGE_SIZE = 5
  263 +FEEDZILLA_SITE_TITLE = _(u'Planet Colab')
  264 +FEEDZILLA_SITE_DESCRIPTION = _(u'Colab blog aggregator')
  265 +
  266 +### Mailman API settings
  267 +MAILMAN_API_URL = 'http://localhost:9000'
  268 +
  269 +### BrowserID / Persona
  270 +SITE_URL = 'localhost:8000'
  271 +BROWSERID_AUDIENCES = [SITE_URL, SITE_URL.replace('https', 'http')]
  272 +
  273 +
  274 +LOGIN_URL = '/'
  275 +LOGIN_REDIRECT_URL = '/'
  276 +LOGIN_REDIRECT_URL_FAILURE = '/'
  277 +LOGOUT_REDIRECT_URL = '/user/logout'
  278 +BROWSERID_CREATE_USER = False
  279 +
  280 +REVPROXY_ADD_REMOTE_USER = True
  281 +
  282 +## Converse.js settings
  283 +# This URL must use SSL in order to keep chat sessions secure
  284 +CONVERSEJS_BOSH_SERVICE_URL = SITE_URL + '/http-bind'
  285 +
  286 +CONVERSEJS_ALLOW_CONTACT_REQUESTS = False
  287 +CONVERSEJS_SHOW_ONLY_ONLINE_USERS = True
  288 +
  289 +
  290 +# Tastypie settings
  291 +TASTYPIE_DEFAULT_FORMATS = ['json', ]
  292 +
  293 +# Dpaste settings
  294 +DPASTE_EXPIRE_CHOICES = (
  295 + ('onetime', _(u'One Time Snippet')),
  296 + (3600, _(u'In one hour')),
  297 + (3600 * 24 * 7, _(u'In one week')),
  298 + (3600 * 24 * 30, _(u'In one month')),
  299 + ('never', _(u'Never')),
  300 +)
  301 +DPASTE_EXPIRE_DEFAULT = DPASTE_EXPIRE_CHOICES[4][0]
  302 +DPASTE_DEFAULT_GIST_DESCRIPTION = 'Gist created from Colab DPaste'
  303 +DPASTE_DEFAULT_GIST_NAME = 'colab_paste'
  304 +DPASTE_LEXER_DEFAULT = 'text'
  305 +
  306 +from .utils.conf import load_yaml_settings
  307 +locals().update(load_yaml_settings())
  308 +
  309 +if locals().get('RAVEN_DSN', False):
  310 + RAVEN_CONFIG = {
  311 + 'dsn': RAVEN_DSN + '?timeout=30',
  312 + }
  313 +
  314 +for app_label in locals().get('PROXIED_APPS', {}).keys():
  315 + INSTALLED_APPS += ('proxy.{}'.format(app_label),)
... ...
colab/colab/urls.py 0 → 100644
... ... @@ -0,0 +1,45 @@
  1 +from django.conf.urls import patterns, include, url, static
  2 +from django.conf import settings
  3 +from django.views.generic import TemplateView
  4 +from django.contrib import admin
  5 +
  6 +from accounts.models import User
  7 +from search.forms import ColabSearchForm
  8 +from super_archives.models import Message
  9 +
  10 +
  11 +admin.autodiscover()
  12 +
  13 +urlpatterns = patterns('',
  14 + url(r'^$', 'home.views.index', name='home'),
  15 + url(r'^robots.txt$', 'home.views.robots', name='robots'),
  16 +
  17 + url(r'^open-data/$', TemplateView.as_view(template_name='open-data.html'),
  18 + name='opendata'),
  19 +
  20 + url(r'^search/', include('search.urls')),
  21 + url(r'^archives/', include('super_archives.urls')),
  22 + url(r'^api/', include('api.urls')),
  23 + url(r'^rss/', include('rss.urls')),
  24 +
  25 + url(r'^user/', include('accounts.urls')), # Kept for backwards compatibility
  26 + url(r'^signup/', include('accounts.urls')), # (same here) TODO: move to nginx
  27 + url(r'^account/', include('accounts.urls')),
  28 +
  29 + url(r'', include('django_browserid.urls')),
  30 +
  31 + url(r'^planet/', include('feedzilla.urls')),
  32 +
  33 + url(r'paste/', include('dpaste.urls.dpaste')),
  34 +
  35 + # Uncomment the next line to enable the admin:
  36 + url(r'^colab/admin/', include(admin.site.urls)),
  37 +
  38 + url(r'^trac/', include('proxy.trac.urls')),
  39 +)
  40 +
  41 +if settings.DEBUG:
  42 + urlpatterns += static.static(
  43 + settings.MEDIA_URL,
  44 + document_root=settings.MEDIA_ROOT
  45 + )
... ...
colab/colab/utils/__init__.py 0 → 100644
colab/colab/utils/conf.py 0 → 100644
... ... @@ -0,0 +1,32 @@
  1 +
  2 +import os
  3 +import yaml
  4 +
  5 +from django.core.exceptions import ImproperlyConfigured
  6 +
  7 +
  8 +class InaccessibleYAMLSettings(ImproperlyConfigured):
  9 + """Settings YAML is Inaccessible.
  10 +
  11 + Check if the file exists and if you have read permissions."""
  12 +
  13 +
  14 +def load_yaml_settings():
  15 + yaml_path = os.getenv('COLAB_SETTINGS', '/etc/colab.yaml')
  16 +
  17 + if not os.path.exists(yaml_path):
  18 + msg = "The yaml file {} does not exist".format(yaml_path)
  19 + raise InaccessibleYAMLSettings(msg)
  20 +
  21 + try:
  22 + with open(yaml_path) as yaml_file:
  23 + yaml_settings = yaml.load(yaml_file.read())
  24 + except IOError:
  25 + msg = ('Could not open settings file {}. Please '
  26 + 'check if the file exists and if user '
  27 + 'has read rights.').format(yaml_path)
  28 + raise InaccessibleYAMLSettings(msg)
  29 +
  30 + return yaml_settings
  31 +
  32 +yaml_settings = load_yaml_settings()
... ...
colab/colab/utils/highlighting.py 0 → 100644
... ... @@ -0,0 +1,38 @@
  1 +from haystack.utils import Highlighter
  2 +from django.conf import settings
  3 +from django.utils.html import escape, strip_tags
  4 +
  5 +
  6 +class ColabHighlighter(Highlighter):
  7 + def highlight(self, text_block):
  8 + self.text_block = escape(strip_tags(text_block))
  9 + highlight_locations = self.find_highlightable_words()
  10 + start_offset, end_offset = self.find_window(highlight_locations)
  11 + return self.render_html(highlight_locations, start_offset, end_offset)
  12 +
  13 + def find_window(self, highlight_locations):
  14 + """Getting the HIGHLIGHT_NUM_CHARS_BEFORE_MATCH setting
  15 + to find how many characters before the first word found should
  16 + be removed from the window
  17 + """
  18 +
  19 + if len(self.text_block) <= self.max_length:
  20 + return (0, self.max_length)
  21 +
  22 + num_chars_before = getattr(
  23 + settings,
  24 + 'HIGHLIGHT_NUM_CHARS_BEFORE_MATCH',
  25 + 0
  26 + )
  27 +
  28 + best_start, best_end = super(ColabHighlighter, self).find_window(
  29 + highlight_locations
  30 + )
  31 + if best_start <= num_chars_before:
  32 + best_end -= best_start
  33 + best_start = 0
  34 + else:
  35 + best_start -= num_chars_before
  36 + best_end -= num_chars_before
  37 +
  38 + return (best_start, best_end)
... ...
colab/colab/wsgi.py 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +"""
  2 +WSGI config for colab project.
  3 +
  4 +It exposes the WSGI callable as a module-level variable named ``application``.
  5 +
  6 +For more information on this file, see
  7 +https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/
  8 +"""
  9 +
  10 +import os
  11 +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "colab.settings")
  12 +
  13 +from django.core.wsgi import get_wsgi_application
  14 +application = get_wsgi_application()
... ...
colab/home/__init__.py 0 → 100644
colab/home/admin.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.contrib import admin
  2 +
  3 +# Register your models here.
... ...
colab/home/context_processors.py 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +from django.conf import settings
  2 +
  3 +def robots(request):
  4 + return {'ROBOTS_NOINDEX': getattr(settings, 'ROBOTS_NOINDEX', False)}
... ...
colab/home/models.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.db import models
  2 +
  3 +# Create your models here.
... ...
colab/home/tests.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.test import TestCase
  2 +
  3 +# Create your tests here.
... ...
colab/home/views.py 0 → 100644
... ... @@ -0,0 +1,65 @@
  1 +
  2 +from collections import OrderedDict
  3 +
  4 +from django.conf import settings
  5 +from django.core.cache import cache
  6 +from django.shortcuts import render
  7 +from django.http import HttpResponse, Http404
  8 +
  9 +from search.utils import trans
  10 +from haystack.query import SearchQuerySet
  11 +
  12 +#from proxy.trac.models import WikiCollabCount, TicketCollabCount
  13 +from super_archives.models import Thread
  14 +
  15 +
  16 +def index(request):
  17 + """Index page view"""
  18 +
  19 +
  20 + latest_threads = Thread.objects.all()[:6]
  21 + hottest_threads = Thread.highest_score.from_haystack()[:6]
  22 +
  23 + count_types = cache.get('home_chart')
  24 + if count_types is None:
  25 + count_types = OrderedDict()
  26 + count_types['thread'] = SearchQuerySet().filter(
  27 + type='thread',
  28 + ).count()
  29 + # TODO: this section should be inside trac app and only use it here
  30 + #if settings.TRAC_ENABLED:
  31 + # for type in ['changeset', 'attachment']:
  32 + # count_types[type] = SearchQuerySet().filter(
  33 + # type=type,
  34 + # ).count()
  35 +
  36 + # count_types['ticket'] = sum([
  37 + # ticket.count for ticket in TicketCollabCount.objects.all()
  38 + # ])
  39 +
  40 + # count_types['wiki'] = sum([
  41 + # wiki.count for wiki in WikiCollabCount.objects.all()
  42 + # ])
  43 +
  44 + cache.set('home_chart', count_types)
  45 +
  46 + for key in count_types.keys():
  47 + count_types[trans(key)] = count_types.pop(key)
  48 +
  49 + context = {
  50 + 'hottest_threads': hottest_threads[:6],
  51 + 'latest_threads': latest_threads,
  52 + 'type_count': count_types,
  53 + 'latest_results': SearchQuerySet().all().order_by(
  54 + '-modified', '-created'
  55 + )[:6],
  56 + }
  57 + return render(request, 'home.html', context)
  58 +
  59 +
  60 +def robots(request):
  61 + if getattr(settings, 'ROBOTS_NOINDEX', False):
  62 + return HttpResponse('User-agent: *\nDisallow: /',
  63 + content_type='text/plain')
  64 +
  65 + raise Http404
... ...
colab/locale/pt_BR/LC_MESSAGES/django.mo 0 → 100644
No preview for this file type
colab/locale/pt_BR/LC_MESSAGES/django.po 0 → 100644
... ... @@ -0,0 +1,1540 @@
  1 +# SOME DESCRIPTIVE TITLE.
  2 +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
  3 +# This file is distributed under the same license as the PACKAGE package.
  4 +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
  5 +#
  6 +msgid ""
  7 +msgstr ""
  8 +"Project-Id-Version: PACKAGE VERSION\n"
  9 +"Report-Msgid-Bugs-To: \n"
  10 +"POT-Creation-Date: 2014-08-07 12:49+0000\n"
  11 +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
  12 +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
  13 +"Language-Team: LANGUAGE <LL@li.org>\n"
  14 +"Language: \n"
  15 +"MIME-Version: 1.0\n"
  16 +"Content-Type: text/plain; charset=UTF-8\n"
  17 +"Content-Transfer-Encoding: 8bit\n"
  18 +"Plural-Forms: nplurals=2; plural=(n > 1);\n"
  19 +
  20 +#: accounts/admin.py:40
  21 +msgid "Personal info"
  22 +msgstr "Informações Pessoais"
  23 +
  24 +#: accounts/admin.py:43
  25 +msgid "Permissions"
  26 +msgstr "Permissões"
  27 +
  28 +#: accounts/admin.py:45
  29 +msgid "Important dates"
  30 +msgstr "Datas importantes"
  31 +
  32 +#: accounts/forms.py:25
  33 +msgid "Social account does not exist"
  34 +msgstr "Conta social não existe"
  35 +
  36 +#: accounts/forms.py:56 accounts/templates/accounts/user_detail.html:38
  37 +msgid "Bio"
  38 +msgstr "Bio"
  39 +
  40 +#: accounts/forms.py:57
  41 +msgid "Write something about you in 200 characters or less."
  42 +msgstr "Escreva algo sobre você em 200 caracteres ou menos."
  43 +
  44 +#: accounts/forms.py:76
  45 +msgid "Mailing lists"
  46 +msgstr "Listas de e-mail"
  47 +
  48 +#: accounts/forms.py:83
  49 +msgid "Password"
  50 +msgstr "Senha"
  51 +
  52 +#: accounts/forms.py:85
  53 +msgid "Password confirmation"
  54 +msgstr "Confirmação de senha"
  55 +
  56 +#: accounts/forms.py:87
  57 +msgid "Enter the same password as above, for verification."
  58 +msgstr "Digite a mesma senha que acima, para verificação."
  59 +
  60 +#: accounts/forms.py:105
  61 +msgid "Password mismatch"
  62 +msgstr "Senhas diferentes"
  63 +
  64 +#: accounts/models.py:59
  65 +msgid "Required. 30 characters or fewer. Letters, digits and ./+/-/_ only."
  66 +msgstr ""
  67 +"Obrigatório. 30 caracteres ou menos. Letras, números e ./+/-/_ somente."
  68 +
  69 +#: accounts/models.py:64
  70 +msgid "Enter a valid username."
  71 +msgstr "Insira um nome de usuário válido."
  72 +
  73 +#: accounts/views.py:144
  74 +msgid "Your profile has been created!"
  75 +msgstr "Seu perfil foi criado!"
  76 +
  77 +#: accounts/views.py:145
  78 +msgid ""
  79 +"You must login to validated your profile. Profiles not validated are deleted "
  80 +"in 24h."
  81 +msgstr ""
  82 +"Você deve se logar para validar seu perfil. Perfis não validados serão "
  83 +"deletados em 24h."
  84 +
  85 +#: accounts/views.py:229
  86 +msgid "Could not change your password. Please, try again later."
  87 +msgstr ""
  88 +"Não conseguimos alterar sua senha. Por favor, tente novamente mais tarde."
  89 +
  90 +#: accounts/views.py:238
  91 +msgid "You've changed your password successfully!"
  92 +msgstr "Senha alterada com sucesso!"
  93 +
  94 +#: accounts/management/commands/delete_invalid.py:42
  95 +#, python-format
  96 +msgid "%(count)s users deleted."
  97 +msgstr "%(count)s usuários deletados."
  98 +
  99 +#: accounts/templates/accounts/change_password.html:8
  100 +msgid "Change XMPP Client and SVN Password"
  101 +msgstr "Trocar senha do Repositório e do Mensageiro"
  102 +
  103 +#: accounts/templates/accounts/change_password.html:17
  104 +#: accounts/templates/accounts/user_update_form.html:195
  105 +msgid "Change Password"
  106 +msgstr "Trocar senha"
  107 +
  108 +#: accounts/templates/accounts/manage_subscriptions.html:6
  109 +msgid "Group Subscriptions"
  110 +msgstr "Inscrições em grupos"
  111 +
  112 +#: accounts/templates/accounts/manage_subscriptions.html:36
  113 +msgid "Update subscriptions"
  114 +msgstr "Atualizar inscrições"
  115 +
  116 +#: accounts/templates/accounts/user_create_form.html:5
  117 +msgid "Sign up"
  118 +msgstr "Cadastrar"
  119 +
  120 +#: accounts/templates/accounts/user_create_form.html:10
  121 +msgid "Please correct the errors below and try again"
  122 +msgstr "Por favor, corrija os erros abaixo e tente novamente"
  123 +
  124 +#: accounts/templates/accounts/user_create_form.html:17
  125 +msgid "Required fields"
  126 +msgstr "Campos obrigatórios"
  127 +
  128 +#: accounts/templates/accounts/user_create_form.html:29
  129 +msgid "Personal Information"
  130 +msgstr "Informações pessoais"
  131 +
  132 +#: accounts/templates/accounts/user_create_form.html:46
  133 +msgid "Subscribe to groups"
  134 +msgstr "Inscreva-se nos grupos"
  135 +
  136 +#: accounts/templates/accounts/user_create_form.html:60
  137 +#: templates/base.html:106 templates/base.html.py:111
  138 +msgid "Register"
  139 +msgstr "Cadastre-se"
  140 +
  141 +#: accounts/templates/accounts/user_detail.html:8 badger/models.py:22
  142 +#: super_archives/models.py:258
  143 +msgid "Messages"
  144 +msgstr "Mensagens"
  145 +
  146 +#: accounts/templates/accounts/user_detail.html:9 badger/models.py:23
  147 +#: templates/home.html:7
  148 +msgid "Contributions"
  149 +msgstr "Contribuições"
  150 +
  151 +#: accounts/templates/accounts/user_detail.html:29
  152 +msgid "edit profile"
  153 +msgstr "editar perfil"
  154 +
  155 +#: accounts/templates/accounts/user_detail.html:30
  156 +msgid "group membership"
  157 +msgstr "Inscrições nos grupos"
  158 +
  159 +#: accounts/templates/accounts/user_detail.html:65
  160 +msgid "Twitter account"
  161 +msgstr "Conta Twitter"
  162 +
  163 +#: accounts/templates/accounts/user_detail.html:68
  164 +msgid "Facebook account"
  165 +msgstr "Conta Facebook"
  166 +
  167 +#: accounts/templates/accounts/user_detail.html:73
  168 +msgid "Google talk account"
  169 +msgstr "Conta Google"
  170 +
  171 +#: accounts/templates/accounts/user_detail.html:77
  172 +msgid "Github account"
  173 +msgstr "Conta Github"
  174 +
  175 +#: accounts/templates/accounts/user_detail.html:81
  176 +msgid "Personal webpage"
  177 +msgstr "Página web pessoal"
  178 +
  179 +#: accounts/templates/accounts/user_detail.html:87
  180 +msgid "Groups: "
  181 +msgstr "Grupos: "
  182 +
  183 +#: accounts/templates/accounts/user_detail.html:100
  184 +msgid "Collaborations by Type"
  185 +msgstr "Colaborações por tipo"
  186 +
  187 +#: accounts/templates/accounts/user_detail.html:116
  188 +msgid "Participation by Group"
  189 +msgstr "Participação por grupo"
  190 +
  191 +#: accounts/templates/accounts/user_detail.html:132 badger/models.py:70
  192 +msgid "Badges"
  193 +msgstr "Medalhas"
  194 +
  195 +#: accounts/templates/accounts/user_detail.html:151
  196 +msgid "Latest posted"
  197 +msgstr "Últimas postagens"
  198 +
  199 +#: accounts/templates/accounts/user_detail.html:156
  200 +msgid "There are no posts by this user so far."
  201 +msgstr "Não há posts deste usuário até agora."
  202 +
  203 +#: accounts/templates/accounts/user_detail.html:160
  204 +msgid "View more posts..."
  205 +msgstr "Ver mais postagens..."
  206 +
  207 +#: accounts/templates/accounts/user_detail.html:166
  208 +msgid "Latest contributions"
  209 +msgstr "Últimas colaborações"
  210 +
  211 +#: accounts/templates/accounts/user_detail.html:171
  212 +msgid "No contributions of this user so far."
  213 +msgstr "Não há posts deste usuário até agora."
  214 +
  215 +#: accounts/templates/accounts/user_detail.html:175
  216 +msgid "View more contributions..."
  217 +msgstr "Ver mais colaborações..."
  218 +
  219 +#: accounts/templates/accounts/user_update_form.html:65
  220 +msgid "We sent a verification email to "
  221 +msgstr "Enviamos um email de verificação para "
  222 +
  223 +#: accounts/templates/accounts/user_update_form.html:66
  224 +msgid "Please follow the instructions in it."
  225 +msgstr "Por favor, siga as instruções."
  226 +
  227 +#: accounts/templates/accounts/user_update_form.html:110
  228 +msgid "profile information"
  229 +msgstr "informações do perfil"
  230 +
  231 +#: accounts/templates/accounts/user_update_form.html:115
  232 +msgid "Change your avatar at Gravatar.com"
  233 +msgstr "Troque seu avatar em Gravatar.com"
  234 +
  235 +#: accounts/templates/accounts/user_update_form.html:142 search/utils.py:8
  236 +msgid "Emails"
  237 +msgstr "E-mails"
  238 +
  239 +#: accounts/templates/accounts/user_update_form.html:151
  240 +msgid "Primary"
  241 +msgstr "Primário"
  242 +
  243 +#: accounts/templates/accounts/user_update_form.html:154
  244 +msgid "Setting..."
  245 +msgstr "Definindo..."
  246 +
  247 +#: accounts/templates/accounts/user_update_form.html:154
  248 +msgid "Set as Primary"
  249 +msgstr "Definir como Primário"
  250 +
  251 +#: accounts/templates/accounts/user_update_form.html:155
  252 +msgid "Deleting..."
  253 +msgstr "Deletando..."
  254 +
  255 +#: accounts/templates/accounts/user_update_form.html:155
  256 +#: accounts/templates/accounts/user_update_form.html:167
  257 +msgid "Delete"
  258 +msgstr "Apagar"
  259 +
  260 +#: accounts/templates/accounts/user_update_form.html:166
  261 +msgid "Sending verification..."
  262 +msgstr "Enviando verificação..."
  263 +
  264 +#: accounts/templates/accounts/user_update_form.html:166
  265 +msgid "Verify"
  266 +msgstr "Verificar"
  267 +
  268 +#: accounts/templates/accounts/user_update_form.html:174
  269 +msgid "Add another email address:"
  270 +msgstr "Adicionar outro endereço de e-mail"
  271 +
  272 +#: accounts/templates/accounts/user_update_form.html:177
  273 +msgid "Add"
  274 +msgstr "Adicionar"
  275 +
  276 +#: accounts/templates/accounts/user_update_form.html:193
  277 +msgid ""
  278 +"This feature is available only for those who need to change the password for some "
  279 +"reason as having an old user with the same username, forgot your password to "
  280 +"commit, usage of other XMPP Client for connection. Usually, you won't need "
  281 +"to change this password. Only change it if you are sure about what you are "
  282 +"doing."
  283 +msgstr ""
  284 +"Este recurso está disponível para quem precisa trocar a senha por algum "
  285 +"motivo como ter um usuário antigo com mesmo nome de usuário, esqueceu da "
  286 +"senha antiga para commit, uso de outro cliente XMPP para conexão. "
  287 +"Normalmente, você não terá que trocar essa senha. Somente troque essa senha "
  288 +"se tiver certeza do que está fazendo."
  289 +
  290 +#: accounts/templates/accounts/user_update_form.html:203
  291 +msgid "Update"
  292 +msgstr "Atualizar"
  293 +
  294 +#: badger/forms.py:19 badger/models.py:40 colab/custom_settings.py:53
  295 +msgid "Image"
  296 +msgstr "Imagem"
  297 +
  298 +#: badger/forms.py:30
  299 +msgid "You must add an Image"
  300 +msgstr "Você deve adicionar uma imagem"
  301 +
  302 +#: badger/models.py:12
  303 +msgid "Greater than or equal"
  304 +msgstr "Maior que ou igual"
  305 +
  306 +#: badger/models.py:13
  307 +msgid "less than or equal"
  308 +msgstr "menor que ou igual"
  309 +
  310 +#: badger/models.py:14
  311 +msgid "Equal"
  312 +msgstr "Igual"
  313 +
  314 +#: badger/models.py:15
  315 +msgid "Biggest"
  316 +msgstr "Maior"
  317 +
  318 +#: badger/models.py:18
  319 +msgid "Automatically"
  320 +msgstr "Automaticamente"
  321 +
  322 +#: badger/models.py:19
  323 +msgid "Manual"
  324 +msgstr "Manual"
  325 +
  326 +#: badger/models.py:24
  327 +msgid "Wikis"
  328 +msgstr "Wikis"
  329 +
  330 +#: badger/models.py:25
  331 +msgid "Revisions"
  332 +msgstr "Conjunto de mudanças"
  333 +
  334 +#: badger/models.py:26 search/views.py:42
  335 +#: search/templates/search/includes/search_filters.html:124
  336 +msgid "Ticket"
  337 +msgstr "Tíquetes"
  338 +
  339 +#: badger/models.py:36
  340 +msgid "Title"
  341 +msgstr "Título"
  342 +
  343 +#: badger/models.py:38
  344 +msgid "Description"
  345 +msgstr "Descrição"
  346 +
  347 +#: badger/models.py:41 search/forms.py:18
  348 +msgid "Type"
  349 +msgstr "Tipo"
  350 +
  351 +#: badger/models.py:43
  352 +msgid "User attribute"
  353 +msgstr "Atributo do usuário"
  354 +
  355 +#: badger/models.py:49
  356 +msgid "Comparison"
  357 +msgstr "Comparação"
  358 +
  359 +#: badger/models.py:56
  360 +msgid "Value"
  361 +msgstr "Valor"
  362 +
  363 +#: badger/models.py:62
  364 +msgid "Awardees"
  365 +msgstr "Premiados"
  366 +
  367 +#: badger/models.py:66
  368 +msgid "Order"
  369 +msgstr "Ordem"
  370 +
  371 +#: badger/models.py:69
  372 +msgid "Badge"
  373 +msgstr "Medalha"
  374 +
  375 +#: colab/custom_settings.py:9
  376 +msgid "English"
  377 +msgstr "Inglês"
  378 +
  379 +#: colab/custom_settings.py:10
  380 +msgid "Portuguese"
  381 +msgstr "Português"
  382 +
  383 +#: colab/custom_settings.py:11
  384 +msgid "Spanish"
  385 +msgstr "Espanhol"
  386 +
  387 +#: colab/custom_settings.py:34
  388 +msgid "Recent activity"
  389 +msgstr "Atividade recente"
  390 +
  391 +#: colab/custom_settings.py:38
  392 +msgid "Relevance"
  393 +msgstr "Relevância"
  394 +
  395 +#: colab/custom_settings.py:46
  396 +msgid "Document"
  397 +msgstr "Documento"
  398 +
  399 +#: colab/custom_settings.py:48
  400 +msgid "Presentation"
  401 +msgstr "Apresentação"
  402 +
  403 +#: colab/custom_settings.py:49
  404 +msgid "Text"
  405 +msgstr "Texto"
  406 +
  407 +#: colab/custom_settings.py:50 search/utils.py:9
  408 +msgid "Code"
  409 +msgstr "Código"
  410 +
  411 +#: colab/custom_settings.py:52
  412 +msgid "Compressed"
  413 +msgstr "Compactado"
  414 +
  415 +#: colab/custom_settings.py:55
  416 +msgid "Spreadsheet"
  417 +msgstr "Planilha"
  418 +
  419 +#: colab/custom_settings.py:267
  420 +msgid "Planet Colab"
  421 +msgstr ""
  422 +
  423 +#: colab/custom_settings.py:268
  424 +msgid "Colab blog aggregator"
  425 +msgstr "Agregador de blog Colab"
  426 +
  427 +#: colab/custom_settings.py:309
  428 +msgid "One Time Snippet"
  429 +msgstr ""
  430 +
  431 +#: colab/custom_settings.py:310
  432 +msgid "In one hour"
  433 +msgstr ""
  434 +
  435 +#: colab/custom_settings.py:311
  436 +msgid "In one week"
  437 +msgstr ""
  438 +
  439 +#: colab/custom_settings.py:312
  440 +msgid "In one month"
  441 +msgstr ""
  442 +
  443 +#: colab/custom_settings.py:313
  444 +#, fuzzy
  445 +msgid "Never"
  446 +msgstr "Seriedade"
  447 +
  448 +#: planet/templates/feedzilla/_post_template.html:8
  449 +msgid "From"
  450 +msgstr "De"
  451 +
  452 +#: planet/templates/feedzilla/_post_template.html:8
  453 +msgid "on"
  454 +msgstr "em"
  455 +
  456 +#: planet/templates/feedzilla/_post_template.html:12
  457 +msgid "Read original"
  458 +msgstr "Leia o original"
  459 +
  460 +#: planet/templates/feedzilla/base.html:7
  461 +msgid "Community Blogs"
  462 +msgstr "Blogs da Comunidade"
  463 +
  464 +#: planet/templates/feedzilla/base.html:17
  465 +msgid "Tags"
  466 +msgstr "Etiquetas"
  467 +
  468 +#: planet/templates/feedzilla/base.html:21
  469 +msgid "Source Blogs"
  470 +msgstr "Blogs de origem"
  471 +
  472 +#: planet/templates/feedzilla/base.html:25
  473 +#: planet/templates/feedzilla/submit_blog.html:5
  474 +msgid "Submit a blog"
  475 +msgstr "Sugerir um blog"
  476 +
  477 +#: planet/templates/feedzilla/index.html:10
  478 +msgid "There is no RSS registered"
  479 +msgstr "Não há RSS registrado"
  480 +
  481 +#: planet/templates/feedzilla/index.html:12
  482 +msgid "Please"
  483 +msgstr "Por favor"
  484 +
  485 +#: planet/templates/feedzilla/index.html:13
  486 +msgid "click here"
  487 +msgstr "clique aqui"
  488 +
  489 +#: planet/templates/feedzilla/index.html:14
  490 +msgid "to submit a blog"
  491 +msgstr "enviar um blog"
  492 +
  493 +#: planet/templates/feedzilla/submit_blog.html:8
  494 +msgid ""
  495 +"Thank you. Your application has been accepted and will be reviewed by admin "
  496 +"in the near time."
  497 +msgstr ""
  498 +"Obrigado. Sua aplicação foi aceita e logo será revisada por um administrador."
  499 +
  500 +#: planet/templates/feedzilla/submit_blog.html:29
  501 +msgid "Submit"
  502 +msgstr "Enviar"
  503 +
  504 +#: planet/templates/feedzilla/tag.html:7
  505 +#, python-format
  506 +msgid "Posts with &laquo;%(tag)s&raquo; label"
  507 +msgstr "Postagens com a etiqueta &laquo;%(tag)s&raquo;"
  508 +
  509 +#: planet/templates/feedzilla/tag.html:16
  510 +msgid "No posts with such label"
  511 +msgstr "Não há posts com essa etiqueta"
  512 +
  513 +#: rss/feeds.py:13
  514 +msgid "Latest Discussions"
  515 +msgstr "Últimas discussões"
  516 +
  517 +#: rss/feeds.py:32
  518 +msgid "Discussions Most Relevance"
  519 +msgstr "Discussões Mais Relevantes"
  520 +
  521 +#: rss/feeds.py:51
  522 +msgid "Latest collaborations"
  523 +msgstr "Últimas colaborações"
  524 +
  525 +#: search/forms.py:16 search/templates/search/search.html:41
  526 +#: templates/base.html:91
  527 +msgid "Search"
  528 +msgstr "Busca"
  529 +
  530 +#: search/forms.py:19 search/views.py:22 search/views.py:33 search/views.py:69
  531 +#: search/views.py:86 search/views.py:119
  532 +msgid "Author"
  533 +msgstr "Autor"
  534 +
  535 +#: search/forms.py:20
  536 +msgid "Modified by"
  537 +msgstr "Modificado por"
  538 +
  539 +#: search/forms.py:22 search/views.py:70
  540 +msgid "Status"
  541 +msgstr ""
  542 +
  543 +#: search/forms.py:26 search/views.py:36
  544 +msgid "Mailinglist"
  545 +msgstr "Grupo"
  546 +
  547 +#: search/forms.py:30 search/views.py:46
  548 +msgid "Milestone"
  549 +msgstr "Etapa"
  550 +
  551 +#: search/forms.py:31 search/views.py:51
  552 +msgid "Priority"
  553 +msgstr "Prioridade"
  554 +
  555 +#: search/forms.py:32 search/views.py:56
  556 +msgid "Component"
  557 +msgstr "Componente"
  558 +
  559 +#: search/forms.py:33 search/views.py:61
  560 +msgid "Severity"
  561 +msgstr "Seriedade"
  562 +
  563 +#: search/forms.py:34 search/views.py:66
  564 +msgid "Reporter"
  565 +msgstr "Relator"
  566 +
  567 +#: search/forms.py:35 search/views.py:73
  568 +msgid "Keywords"
  569 +msgstr "Palavras chaves"
  570 +
  571 +#: search/forms.py:36 search/views.py:25 search/views.py:78
  572 +msgid "Collaborators"
  573 +msgstr "Colaboradores"
  574 +
  575 +#: search/forms.py:37 search/views.py:89
  576 +msgid "Repository"
  577 +msgstr "Repositório"
  578 +
  579 +#: search/forms.py:38 search/views.py:99
  580 +msgid "Username"
  581 +msgstr "Usuário"
  582 +
  583 +#: search/forms.py:39 search/views.py:102
  584 +msgid "Name"
  585 +msgstr "Nome"
  586 +
  587 +#: search/forms.py:40 search/views.py:105
  588 +msgid "Institution"
  589 +msgstr "Instituição"
  590 +
  591 +#: search/forms.py:41 search/views.py:108
  592 +msgid "Role"
  593 +msgstr "Cargo"
  594 +
  595 +#: search/forms.py:42 search/templates/search/includes/search_filters.html:151
  596 +#: search/templates/search/includes/search_filters.html:153
  597 +#: search/templates/search/includes/search_filters.html:185
  598 +#: search/templates/search/includes/search_filters.html:186
  599 +msgid "Since"
  600 +msgstr "Desde"
  601 +
  602 +#: search/forms.py:43 search/templates/search/includes/search_filters.html:160
  603 +#: search/templates/search/includes/search_filters.html:162
  604 +#: search/templates/search/includes/search_filters.html:189
  605 +#: search/templates/search/includes/search_filters.html:190
  606 +msgid "Until"
  607 +msgstr "Até"
  608 +
  609 +#: search/forms.py:44 search/views.py:116
  610 +msgid "Filename"
  611 +msgstr "Nome do arquivo"
  612 +
  613 +#: search/forms.py:45 search/views.py:122
  614 +msgid "Used by"
  615 +msgstr "Usado por"
  616 +
  617 +#: search/forms.py:46 search/views.py:125
  618 +msgid "File type"
  619 +msgstr "Tipo do arquivo"
  620 +
  621 +#: search/forms.py:47 search/views.py:128
  622 +msgid "Size"
  623 +msgstr "Tamanho"
  624 +
  625 +#: search/utils.py:7 search/views.py:20
  626 +#: search/templates/search/includes/search_filters.html:116
  627 +#: templates/open-data.html:130
  628 +msgid "Wiki"
  629 +msgstr "Wiki"
  630 +
  631 +#: search/utils.py:10
  632 +msgid "Tickets"
  633 +msgstr "Tíquetes"
  634 +
  635 +#: search/utils.py:11
  636 +msgid "Attachments"
  637 +msgstr "Anexos"
  638 +
  639 +#: search/views.py:31 search/templates/search/includes/search_filters.html:120
  640 +msgid "Discussion"
  641 +msgstr "Discussões"
  642 +
  643 +#: search/views.py:84 search/templates/search/includes/search_filters.html:128
  644 +msgid "Changeset"
  645 +msgstr "Conjunto de Mudanças"
  646 +
  647 +#: search/views.py:95 search/templates/search/includes/search_filters.html:132
  648 +msgid "User"
  649 +msgstr "Usuário"
  650 +
  651 +#: search/views.py:112
  652 +#: search/templates/search/includes/search_filters.html:136
  653 +msgid "Attachment"
  654 +msgstr "Anexo"
  655 +
  656 +#: search/templates/search/search.html:4
  657 +msgid "search"
  658 +msgstr "busca"
  659 +
  660 +#: search/templates/search/search.html:46
  661 +msgid "documents found"
  662 +msgstr "documentos encontrados"
  663 +
  664 +#: search/templates/search/search.html:57
  665 +msgid "Search here"
  666 +msgstr "Pesquise aqui"
  667 +
  668 +#: search/templates/search/search.html:69
  669 +#: search/templates/search/search.html:79
  670 +msgid "Filters"
  671 +msgstr "Filtros"
  672 +
  673 +#: search/templates/search/search.html:100
  674 +msgid "No results for your search."
  675 +msgstr "Não há resultados para sua busca."
  676 +
  677 +#: search/templates/search/search.html:102
  678 +msgid "You are searching for"
  679 +msgstr "Você está procurando por"
  680 +
  681 +#: search/templates/search/includes/search_filters.html:5
  682 +#: search/templates/search/includes/search_filters.html:33
  683 +#: search/templates/search/includes/search_filters.html:51
  684 +#: search/templates/search/includes/search_filters.html:69
  685 +msgid "Remove filter"
  686 +msgstr "Remover filtro"
  687 +
  688 +#: search/templates/search/includes/search_filters.html:88
  689 +#: search/templates/search/includes/search_filters.html:171
  690 +#: search/templates/search/includes/search_filters.html:195
  691 +msgid "Filter"
  692 +msgstr "Filtro"
  693 +
  694 +#: search/templates/search/includes/search_filters.html:94
  695 +msgid "Sort by"
  696 +msgstr "Ordenar por"
  697 +
  698 +#: search/templates/search/includes/search_filters.html:111
  699 +msgid "Types"
  700 +msgstr "Tipos"
  701 +
  702 +#: super_archives/models.py:62
  703 +#: super_archives/templates/message-preview.html:62
  704 +#: super_archives/templates/message-thread.html:4
  705 +msgid "Anonymous"
  706 +msgstr "Anônimo"
  707 +
  708 +#: super_archives/models.py:112
  709 +msgid "Mailing List"
  710 +msgstr "Lista de e-mail"
  711 +
  712 +#: super_archives/models.py:113
  713 +msgid "The Mailing List where is the thread"
  714 +msgstr "A lista de e-mail onde estão as mensagens"
  715 +
  716 +#: super_archives/models.py:116
  717 +msgid "Latest message"
  718 +msgstr "Última mensagem"
  719 +
  720 +#: super_archives/models.py:117
  721 +msgid "Latest message posted"
  722 +msgstr "Última mensagem postada"
  723 +
  724 +#: super_archives/models.py:118
  725 +msgid "Score"
  726 +msgstr "Pontuação"
  727 +
  728 +#: super_archives/models.py:118
  729 +msgid "Thread score"
  730 +msgstr "Pontuação do conjunto de mensagens"
  731 +
  732 +#: super_archives/models.py:127
  733 +msgid "Thread"
  734 +msgstr "Conjunto de mensagens"
  735 +
  736 +#: super_archives/models.py:128
  737 +msgid "Threads"
  738 +msgstr "Conjuntos de mensagens"
  739 +
  740 +#: super_archives/models.py:242
  741 +msgid "Subject"
  742 +msgstr "Assunto"
  743 +
  744 +#: super_archives/models.py:243
  745 +msgid "Please enter a message subject"
  746 +msgstr "Por favor, digite o assunto da mensagem"
  747 +
  748 +#: super_archives/models.py:246
  749 +msgid "Message body"
  750 +msgstr "Corpo da mensagem"
  751 +
  752 +#: super_archives/models.py:247
  753 +msgid "Please enter a message body"
  754 +msgstr "Por favor, digite o corpo da mensagem"
  755 +
  756 +#: super_archives/models.py:257
  757 +msgid "Message"
  758 +msgstr "Mensagem"
  759 +
  760 +#: super_archives/views.py:92
  761 +msgid "Error trying to connect to Mailman API"
  762 +msgstr "Erro na conexão com a API do Mailman"
  763 +
  764 +#: super_archives/views.py:95
  765 +msgid "Timeout trying to connect to Mailman API"
  766 +msgstr "Tempo de espera esgotado na conexão com a API do Mailman"
  767 +
  768 +#: super_archives/views.py:99
  769 +msgid ""
  770 +"Your message was sent to this topic. It may take some minutes before it's "
  771 +"delivered by email to the group. Why don't you breath some fresh air in the "
  772 +"meanwhile?"
  773 +msgstr ""
  774 +"Sua mensagem foi enviada para esse tópico. Pode levar alguns minutos até ser "
  775 +"entregue via e-mail para o grupo. Por quê você não respira um ar fresco "
  776 +"enquanto isso?"
  777 +
  778 +#: super_archives/views.py:108
  779 +msgid "You cannot send an empty email"
  780 +msgstr "Você não pode enviar um e-mail vazio"
  781 +
  782 +#: super_archives/views.py:110
  783 +msgid "Mailing list does not exist"
  784 +msgstr "Lista de e-mail não existe"
  785 +
  786 +#: super_archives/views.py:112
  787 +msgid "Unknown error trying to connect to Mailman API"
  788 +msgstr "Erro desconhecido na conexão com a API do Mailman"
  789 +
  790 +#: super_archives/views.py:151
  791 +msgid ""
  792 +"The email address you are trying to verify either has already been verified "
  793 +"or does not exist."
  794 +msgstr ""
  795 +"O endereço de e-mail que você está tentando verificar ou já foi verificado "
  796 +"ou não existe."
  797 +
  798 +#: super_archives/views.py:162
  799 +msgid ""
  800 +"The email address you are trying to verify is already an active email "
  801 +"address."
  802 +msgstr ""
  803 +"O endereço de e-mail que você está tentando verificar já é um endereço de e-"
  804 +"mail ativo"
  805 +
  806 +#: super_archives/views.py:172
  807 +msgid "Email address verified!"
  808 +msgstr "Endereço de e-mail verificado!"
  809 +
  810 +#: super_archives/management/commands/import_emails.py:207
  811 +msgid "[Colab] Warning - Email sent with a blank subject."
  812 +msgstr "[Colab] Aviso - E-mail enviado com o campo assunto em branco."
  813 +
  814 +#: super_archives/templates/message-preview.html:42
  815 +#: super_archives/templates/message-preview.html:62
  816 +msgid "by"
  817 +msgstr "por"
  818 +
  819 +#: super_archives/templates/message-preview.html:65
  820 +#: super_archives/templates/message-thread.html:161
  821 +msgid "ago"
  822 +msgstr "atrás"
  823 +
  824 +#: super_archives/templates/message-thread.html:35
  825 +msgid "You must login before voting."
  826 +msgstr "Você deve estar logado antes de votar."
  827 +
  828 +#: super_archives/templates/message-thread.html:132
  829 +msgid "Order by"
  830 +msgstr "Ordernar por"
  831 +
  832 +#: super_archives/templates/message-thread.html:136
  833 +msgid "Votes"
  834 +msgstr "Votos"
  835 +
  836 +#: super_archives/templates/message-thread.html:140
  837 +msgid "Date"
  838 +msgstr "Data"
  839 +
  840 +#: super_archives/templates/message-thread.html:145
  841 +msgid "Related:"
  842 +msgstr "Relacionado:"
  843 +
  844 +#: super_archives/templates/message-thread.html:156
  845 +msgid "Statistics:"
  846 +msgstr "Estatísticas:"
  847 +
  848 +#: super_archives/templates/message-thread.html:160
  849 +msgid "started at"
  850 +msgstr "começou à"
  851 +
  852 +#: super_archives/templates/message-thread.html:166
  853 +msgid "viewed"
  854 +msgstr "visualizado"
  855 +
  856 +#: super_archives/templates/message-thread.html:167
  857 +#: super_archives/templates/message-thread.html:172
  858 +#: super_archives/templates/message-thread.html:177
  859 +msgid "times"
  860 +msgstr "vezes"
  861 +
  862 +#: super_archives/templates/message-thread.html:171
  863 +msgid "answered"
  864 +msgstr "respondido"
  865 +
  866 +#: super_archives/templates/message-thread.html:176
  867 +msgid "voted"
  868 +msgstr "votado"
  869 +
  870 +#: super_archives/templates/message-thread.html:182
  871 +msgid "Tags:"
  872 +msgstr "Etiquetas:"
  873 +
  874 +#: super_archives/templates/superarchives/thread-dashboard.html:4
  875 +#: super_archives/templates/superarchives/thread-dashboard.html:7
  876 +#: templates/base.html:66
  877 +msgid "Groups"
  878 +msgstr "Grupos"
  879 +
  880 +#: super_archives/templates/superarchives/thread-dashboard.html:17
  881 +msgid "latest"
  882 +msgstr "mais recentes"
  883 +
  884 +#: super_archives/templates/superarchives/thread-dashboard.html:25
  885 +#: super_archives/templates/superarchives/thread-dashboard.html:39
  886 +msgid "more..."
  887 +msgstr "mais..."
  888 +
  889 +#: super_archives/templates/superarchives/thread-dashboard.html:31
  890 +msgid "most relevant"
  891 +msgstr "mais relevantes"
  892 +
  893 +#: super_archives/templates/superarchives/emails/email_blank_subject.txt:2
  894 +msgid "Hello"
  895 +msgstr "Olá"
  896 +
  897 +#: super_archives/templates/superarchives/emails/email_blank_subject.txt:3
  898 +#, python-format
  899 +msgid ""
  900 +"\n"
  901 +"You've sent an email to %(mailinglist)s with a blank subject and the "
  902 +"following content:\n"
  903 +"\n"
  904 +"\"%(body)s\"\n"
  905 +"\n"
  906 +"Please, fill the subject in every email you send it.\n"
  907 +"\n"
  908 +"Thank you.\n"
  909 +msgstr ""
  910 +"\n"
  911 +"Você enviou um e-mail para %(mailinglist)s com o campo Assunto em branco e o "
  912 +"seguinte conteúdo:\n"
  913 +"\n"
  914 +"\"%(body)s\"\n"
  915 +"\n"
  916 +"Por favor, preencha o assunto em todos os e-mails que você enviar.\n"
  917 +"\n"
  918 +"Obrigado.\n"
  919 +
  920 +#: super_archives/templates/superarchives/emails/email_verification.txt:2
  921 +#, python-format
  922 +msgid ""
  923 +"Hey, we want to verify that you are indeed \"%(fullname)s (%(username)s)\". "
  924 +"If that's the case, please follow the link below:"
  925 +msgstr ""
  926 +"Hey, queremos verificar se você realmente é \"%(fullname)s (%(username)s)\". "
  927 +"Se esse é o caso, por favor, clique no link abaixo:"
  928 +
  929 +#: super_archives/templates/superarchives/emails/email_verification.txt:6
  930 +#, python-format
  931 +msgid ""
  932 +"If you're not %(username)s or didn't request verification you can ignore "
  933 +"this email."
  934 +msgstr ""
  935 +"Se você não é %(username)s ou não pediu uma verificação você pode ignorar "
  936 +"esse e-mail"
  937 +
  938 +#: super_archives/templates/superarchives/includes/message.html:17
  939 +#: super_archives/templates/superarchives/includes/message.html:18
  940 +msgid "Link to this message"
  941 +msgstr "Link para essa mensagem"
  942 +
  943 +#: super_archives/templates/superarchives/includes/message.html:46
  944 +msgid "Reply"
  945 +msgstr "Responder"
  946 +
  947 +#: super_archives/templates/superarchives/includes/message.html:63
  948 +msgid "Send a message"
  949 +msgstr "Enviar uma mensagem"
  950 +
  951 +#: super_archives/templates/superarchives/includes/message.html:66
  952 +msgid ""
  953 +"After sending a message it will take few minutes before it shows up in here. "
  954 +"Why don't you grab a coffee?"
  955 +msgstr ""
  956 +"Depois de enviar uma mensagem levará alguns minutos antes dela aparecer "
  957 +"aqui. Por que você não pega um café?"
  958 +
  959 +#: super_archives/templates/superarchives/includes/message.html:69
  960 +msgid "Send"
  961 +msgstr "Enviar"
  962 +
  963 +#: super_archives/utils/email.py:14
  964 +msgid "Please verify your email "
  965 +msgstr "Por favor, verifique seu e-mail "
  966 +
  967 +#: super_archives/utils/email.py:25
  968 +msgid "Registration on the mailing list"
  969 +msgstr "Inscrição na lista de e-mail"
  970 +
  971 +#: templates/404.html:5
  972 +msgid "Not found. Keep searching! :)"
  973 +msgstr "Não encontrado. Continue procurando! :)"
  974 +
  975 +#: templates/500.html:2
  976 +msgid "Ooopz... something went wrong!"
  977 +msgstr "Opa... algo saiu errado!"
  978 +
  979 +#: templates/base.html:63
  980 +msgid "Timeline"
  981 +msgstr "Linha do Tempo"
  982 +
  983 +#: templates/base.html:69
  984 +msgid "Blogs"
  985 +msgstr ""
  986 +
  987 +#: templates/base.html:72
  988 +msgid "Contribute"
  989 +msgstr "Contribua"
  990 +
  991 +#: templates/base.html:76
  992 +msgid "New Wiki Page"
  993 +msgstr "Nova Página Wiki"
  994 +
  995 +#: templates/base.html:78
  996 +msgid "View Tickets"
  997 +msgstr "Ver Tiquetes"
  998 +
  999 +#: templates/base.html:80
  1000 +msgid "New Ticket"
  1001 +msgstr "Novo Tíquete"
  1002 +
  1003 +#: templates/base.html:82
  1004 +msgid "Roadmap"
  1005 +msgstr "Planejamento"
  1006 +
  1007 +#: templates/base.html:86
  1008 +msgid "Browse Source"
  1009 +msgstr "Códigos Fontes"
  1010 +
  1011 +#: templates/base.html:87
  1012 +msgid "Continuous Integration"
  1013 +msgstr "Integração Contínua"
  1014 +
  1015 +#: templates/base.html:95
  1016 +msgid "Help"
  1017 +msgstr "Ajuda"
  1018 +
  1019 +#: templates/base.html:107 templates/base.html.py:112
  1020 +msgid "Login"
  1021 +msgstr "Entrar"
  1022 +
  1023 +#: templates/base.html:126
  1024 +msgid "My Profile"
  1025 +msgstr "Meu Perfil"
  1026 +
  1027 +#: templates/base.html:127
  1028 +msgid "Logout"
  1029 +msgstr "Sair"
  1030 +
  1031 +#: templates/base.html:139 templates/base.html.py:142
  1032 +msgid "Search here..."
  1033 +msgstr "Pesquise aqui..."
  1034 +
  1035 +#: templates/base.html:155
  1036 +msgid "The login has failed. Please, try again."
  1037 +msgstr "O login falhou. Por favor, tente novamente."
  1038 +
  1039 +#: templates/base.html:182
  1040 +msgid "Last email imported at"
  1041 +msgstr "Último e-mail importado em"
  1042 +
  1043 +#: templates/base.html:189
  1044 +msgid "The contents of this site is published under license"
  1045 +msgstr "O conteúdo deste site está publicado sob a licença"
  1046 +
  1047 +#: templates/base.html:192
  1048 +msgid ""
  1049 +"Creative Commons 3.0 Brasil - Atribuição - Não-Comercial - Compartilha-Igual"
  1050 +msgstr ""
  1051 +
  1052 +#: templates/home.html:21
  1053 +msgid "Latest Collaborations"
  1054 +msgstr "Últimas Colaborações"
  1055 +
  1056 +#: templates/home.html:25
  1057 +msgid "RSS - Latest collaborations"
  1058 +msgstr "RSS - Últimas Colaborações"
  1059 +
  1060 +#: templates/home.html:34
  1061 +msgid "View more collaborations..."
  1062 +msgstr "Ver mais colaborações..."
  1063 +
  1064 +#: templates/home.html:41
  1065 +msgid "Collaboration Graph"
  1066 +msgstr "Gráfico de Colaborações"
  1067 +
  1068 +#: templates/home.html:52
  1069 +msgid "Most Relevant Threads"
  1070 +msgstr "Discussões Mais Relevantes"
  1071 +
  1072 +#: templates/home.html:56
  1073 +msgid "RSS - Most Relevant Threads"
  1074 +msgstr "RSS - Discussões Mais Relevantes"
  1075 +
  1076 +#: templates/home.html:64 templates/home.html.py:83
  1077 +msgid "View more discussions..."
  1078 +msgstr "Ver mais discussões..."
  1079 +
  1080 +#: templates/home.html:71
  1081 +msgid "Latest Threads"
  1082 +msgstr "Últimas Discussões"
  1083 +
  1084 +#: templates/home.html:75
  1085 +msgid "RSS - Latest Threads"
  1086 +msgstr "RSS - Últimas Discussões"
  1087 +
  1088 +#: templates/open-data.html:6
  1089 +msgid "OpenData"
  1090 +msgstr "OpenData"
  1091 +
  1092 +#: templates/open-data.html:7
  1093 +msgid ""
  1094 +"If you are interested in any other data that is not provided by this API, "
  1095 +"please contact us via the ticketing system (you must be registered in order "
  1096 +"to create a ticket)."
  1097 +msgstr ""
  1098 +
  1099 +#: templates/open-data.html:9
  1100 +msgid "Retrieving data via API"
  1101 +msgstr ""
  1102 +
  1103 +#: templates/open-data.html:10
  1104 +msgid "Colab API works through HTTP/REST, always returning JSON objects."
  1105 +msgstr ""
  1106 +
  1107 +#: templates/open-data.html:12
  1108 +msgid "The base API URL is"
  1109 +msgstr ""
  1110 +
  1111 +#: templates/open-data.html:19
  1112 +msgid ""
  1113 +"Each model listed below has a resource_uri field available, which is the "
  1114 +"object's data URI."
  1115 +msgstr ""
  1116 +
  1117 +#: templates/open-data.html:20
  1118 +msgid ""
  1119 +"The following list contains the available models to retrieve data and its "
  1120 +"fields available for filtering"
  1121 +msgstr ""
  1122 +
  1123 +#: templates/open-data.html:24 templates/open-data.html.py:39
  1124 +#: templates/open-data.html:50 templates/open-data.html.py:62
  1125 +#: templates/open-data.html:74 templates/open-data.html.py:95
  1126 +msgid "Fields"
  1127 +msgstr ""
  1128 +
  1129 +#: templates/open-data.html:25
  1130 +msgid ""
  1131 +"The email field is not shown for user's privacy, but you can use it to filter"
  1132 +msgstr ""
  1133 +
  1134 +#: templates/open-data.html:27
  1135 +msgid "The user's username"
  1136 +msgstr ""
  1137 +
  1138 +#: templates/open-data.html:28
  1139 +#, fuzzy
  1140 +msgid "The user's email address"
  1141 +msgstr "Adicionar outro endereço de e-mail"
  1142 +
  1143 +#: templates/open-data.html:29
  1144 +msgid "What is the user's institution"
  1145 +msgstr ""
  1146 +
  1147 +#: templates/open-data.html:30
  1148 +msgid "What is the user's role"
  1149 +msgstr ""
  1150 +
  1151 +#: templates/open-data.html:31
  1152 +msgid "The user's first name"
  1153 +msgstr ""
  1154 +
  1155 +#: templates/open-data.html:32
  1156 +msgid "The user's last name"
  1157 +msgstr ""
  1158 +
  1159 +#: templates/open-data.html:33
  1160 +msgid "A mini bio of the user"
  1161 +msgstr ""
  1162 +
  1163 +#: templates/open-data.html:40
  1164 +msgid ""
  1165 +"The address field is not shown for user's privacy, but you can use it to "
  1166 +"filter"
  1167 +msgstr ""
  1168 +
  1169 +#: templates/open-data.html:42
  1170 +msgid "It has a relationshop with the user described above"
  1171 +msgstr ""
  1172 +
  1173 +#: templates/open-data.html:43
  1174 +#, fuzzy
  1175 +msgid "An email address"
  1176 +msgstr "Adicionar outro endereço de e-mail"
  1177 +
  1178 +#: templates/open-data.html:44
  1179 +msgid "The user's real name"
  1180 +msgstr ""
  1181 +
  1182 +#: templates/open-data.html:52
  1183 +msgid "It has a relationship with the emailaddress described above"
  1184 +msgstr ""
  1185 +
  1186 +#: templates/open-data.html:53
  1187 +#, fuzzy
  1188 +msgid "The message's body"
  1189 +msgstr "Corpo da mensagem"
  1190 +
  1191 +#: templates/open-data.html:54
  1192 +#, fuzzy
  1193 +msgid "The message's subject"
  1194 +msgstr "Por favor, digite o assunto da mensagem"
  1195 +
  1196 +#: templates/open-data.html:55
  1197 +#, fuzzy
  1198 +msgid "The message's id"
  1199 +msgstr "Última mensagem"
  1200 +
  1201 +#: templates/open-data.html:56
  1202 +msgid "The message's received time"
  1203 +msgstr ""
  1204 +
  1205 +#: templates/open-data.html:64
  1206 +msgid "The revision's author username"
  1207 +msgstr ""
  1208 +
  1209 +#: templates/open-data.html:65
  1210 +msgid "When the revision's were created"
  1211 +msgstr ""
  1212 +
  1213 +#: templates/open-data.html:66
  1214 +msgid "The revision's key"
  1215 +msgstr ""
  1216 +
  1217 +#: templates/open-data.html:67
  1218 +msgid "The revision's message"
  1219 +msgstr ""
  1220 +
  1221 +#: templates/open-data.html:68
  1222 +msgid "The revision's repository name"
  1223 +msgstr ""
  1224 +
  1225 +#: templates/open-data.html:76
  1226 +msgid "The ticket's author username"
  1227 +msgstr ""
  1228 +
  1229 +#: templates/open-data.html:77
  1230 +msgid "The ticket's component"
  1231 +msgstr ""
  1232 +
  1233 +#: templates/open-data.html:78
  1234 +msgid "When the ticket's were created"
  1235 +msgstr ""
  1236 +
  1237 +#: templates/open-data.html:79
  1238 +msgid "The ticket's description"
  1239 +msgstr ""
  1240 +
  1241 +#: templates/open-data.html:80
  1242 +#, fuzzy
  1243 +msgid "The ticket's id"
  1244 +msgstr "Tíquetes"
  1245 +
  1246 +#: templates/open-data.html:81
  1247 +msgid "The ticket's keywords"
  1248 +msgstr ""
  1249 +
  1250 +#: templates/open-data.html:82
  1251 +msgid "The ticket's milestone"
  1252 +msgstr ""
  1253 +
  1254 +#: templates/open-data.html:83 templates/open-data.html.py:99
  1255 +msgid "The time of the last modification"
  1256 +msgstr ""
  1257 +
  1258 +#: templates/open-data.html:84
  1259 +msgid "The username of the last user who modified the ticket"
  1260 +msgstr ""
  1261 +
  1262 +#: templates/open-data.html:85
  1263 +msgid "The ticket's priority"
  1264 +msgstr ""
  1265 +
  1266 +#: templates/open-data.html:86
  1267 +msgid "The ticket's severity"
  1268 +msgstr ""
  1269 +
  1270 +#: templates/open-data.html:87
  1271 +msgid "The ticket's status"
  1272 +msgstr ""
  1273 +
  1274 +#: templates/open-data.html:88
  1275 +msgid "The ticket's summary"
  1276 +msgstr ""
  1277 +
  1278 +#: templates/open-data.html:89
  1279 +msgid "The ticket's version"
  1280 +msgstr ""
  1281 +
  1282 +#: templates/open-data.html:97
  1283 +msgid "The wiki's author username"
  1284 +msgstr ""
  1285 +
  1286 +#: templates/open-data.html:98
  1287 +msgid "When the wiki's were created"
  1288 +msgstr ""
  1289 +
  1290 +#: templates/open-data.html:100
  1291 +msgid "The username of the last user who modified the wiki"
  1292 +msgstr ""
  1293 +
  1294 +#: templates/open-data.html:101
  1295 +msgid "The wiki's name"
  1296 +msgstr ""
  1297 +
  1298 +#: templates/open-data.html:102
  1299 +msgid "the wiki's content"
  1300 +msgstr ""
  1301 +
  1302 +#: templates/open-data.html:109
  1303 +msgid "Parameters"
  1304 +msgstr ""
  1305 +
  1306 +#: templates/open-data.html:112
  1307 +msgid "Results per page"
  1308 +msgstr ""
  1309 +
  1310 +#: templates/open-data.html:113
  1311 +msgid "Number of results to be displayed per page."
  1312 +msgstr ""
  1313 +
  1314 +#: templates/open-data.html:114
  1315 +msgid "Default: 20"
  1316 +msgstr ""
  1317 +
  1318 +#: templates/open-data.html:118
  1319 +msgid "Starts of n element"
  1320 +msgstr ""
  1321 +
  1322 +#: templates/open-data.html:119
  1323 +msgid "Where n is the index of the first result to appear in the page."
  1324 +msgstr ""
  1325 +
  1326 +#: templates/open-data.html:120
  1327 +msgid "Default: 0"
  1328 +msgstr ""
  1329 +
  1330 +#: templates/open-data.html:122
  1331 +#, fuzzy
  1332 +msgid "Filtering"
  1333 +msgstr "Filtro"
  1334 +
  1335 +#: templates/open-data.html:124
  1336 +msgid "The field name"
  1337 +msgstr ""
  1338 +
  1339 +#: templates/open-data.html:125
  1340 +msgid ""
  1341 +"If you are looking for a specific wiki, and you know the wiki's name, you "
  1342 +"can filter it as below"
  1343 +msgstr ""
  1344 +
  1345 +#: templates/open-data.html:126
  1346 +#, fuzzy
  1347 +msgid "WikiName"
  1348 +msgstr "Nome"
  1349 +
  1350 +#: templates/open-data.html:127
  1351 +msgid ""
  1352 +"Where &quot;name&quot; is the fieldname and &quot;WikiName&quot; is the "
  1353 +"value you want to filter."
  1354 +msgstr ""
  1355 +
  1356 +#: templates/open-data.html:128
  1357 +#, fuzzy
  1358 +msgid "Usage"
  1359 +msgstr "Mensagem"
  1360 +
  1361 +#: templates/open-data.html:129
  1362 +msgid ""
  1363 +"You can also filter using Django lookup fields with the double underscores, "
  1364 +"just as below"
  1365 +msgstr ""
  1366 +
  1367 +#: templates/open-data.html:131 templates/open-data.html.py:132
  1368 +#, fuzzy
  1369 +msgid "test"
  1370 +msgstr "mais recentes"
  1371 +
  1372 +#: templates/open-data.html:133
  1373 +msgid "Usage with relationships"
  1374 +msgstr ""
  1375 +
  1376 +#: templates/open-data.html:134
  1377 +msgid ""
  1378 +"You can use related fields to filter too. So, you can filter by any field of "
  1379 +"emailaddress using the 'from_address' field of message, which has a relation "
  1380 +"to emailaddress. You will achieve the related fields by using double "
  1381 +"underscore and the field's name. See the example below"
  1382 +msgstr ""
  1383 +
  1384 +#: templates/open-data.html:136
  1385 +msgid ""
  1386 +"So, real_name is a field of emailaddress, and you had access to this field "
  1387 +"by a message field called from_address and using double underscore to say "
  1388 +"you want to use a field of that relationship"
  1389 +msgstr ""
  1390 +
  1391 +#: templates/open-data.html:137
  1392 +msgid ""
  1393 +"Note: email filters must be exact. Which means that __contains, "
  1394 +"__startswith, __endswith and others won't work"
  1395 +msgstr ""
  1396 +
  1397 +#: templates/open-data.html:138
  1398 +msgid ""
  1399 +"Another example of usage with relations. Used to retrieve all messages of a "
  1400 +"given user, using the username or the email field"
  1401 +msgstr ""
  1402 +
  1403 +#: templates/dpaste/snippet_details.html:37
  1404 +#, fuzzy
  1405 +msgid "Compare"
  1406 +msgstr "Comparação"
  1407 +
  1408 +#: templates/dpaste/snippet_details.html:47
  1409 +#, python-format
  1410 +msgid "Expires in: %(date)s"
  1411 +msgstr ""
  1412 +
  1413 +#: templates/dpaste/snippet_details.html:49
  1414 +msgid "Snippet never expires"
  1415 +msgstr ""
  1416 +
  1417 +#: templates/dpaste/snippet_details.html:51
  1418 +msgid "One-time snippet"
  1419 +msgstr ""
  1420 +
  1421 +#: templates/dpaste/snippet_details.html:56
  1422 +msgid "Really delete this snippet?"
  1423 +msgstr ""
  1424 +
  1425 +#: templates/dpaste/snippet_details.html:58
  1426 +#, fuzzy
  1427 +msgid "Delete Now"
  1428 +msgstr "Apagar"
  1429 +
  1430 +#: templates/dpaste/snippet_details.html:63
  1431 +#: templates/dpaste/snippet_details.html:65
  1432 +#, fuzzy
  1433 +msgid "Compare Snippets"
  1434 +msgstr "Comparação"
  1435 +
  1436 +#: templates/dpaste/snippet_details.html:69
  1437 +#: templates/dpaste/snippet_details.html:71
  1438 +msgid "View Raw"
  1439 +msgstr ""
  1440 +
  1441 +#: templates/dpaste/snippet_details.html:75
  1442 +msgid "Gist"
  1443 +msgstr ""
  1444 +
  1445 +#: templates/dpaste/snippet_details.html:88
  1446 +msgid "This is a one-time snippet."
  1447 +msgstr ""
  1448 +
  1449 +#: templates/dpaste/snippet_details.html:90
  1450 +msgid "It will automatically get deleted after {{ remaining }} further views."
  1451 +msgstr ""
  1452 +
  1453 +#: templates/dpaste/snippet_details.html:92
  1454 +msgid "It will automatically get deleted after the next view."
  1455 +msgstr ""
  1456 +
  1457 +#: templates/dpaste/snippet_details.html:94
  1458 +msgid "It cannot be viewed again."
  1459 +msgstr ""
  1460 +
  1461 +#: templates/dpaste/snippet_details.html:109
  1462 +msgid "Reply to this snippet"
  1463 +msgstr ""
  1464 +
  1465 +#: templates/dpaste/snippet_diff.html:5
  1466 +#, python-format
  1467 +msgid ""
  1468 +"\n"
  1469 +" Diff between <a href=\"%(filea_url)s\">#%(filea_id)s</a> and <a href="
  1470 +"\"%(fileb_url)s\">#%(fileb_id)s</a>\n"
  1471 +" "
  1472 +msgstr ""
  1473 +
  1474 +#: templates/dpaste/snippet_form.html:28
  1475 +msgid "Paste it"
  1476 +msgstr ""
  1477 +
  1478 +#~ msgid "Creative Commons - attribution, non-commercial"
  1479 +#~ msgstr "Creative Commons - atribuição e não-comercial"
  1480 +
  1481 +#~ msgid "Willing to help"
  1482 +#~ msgstr "Vontade de ajudar"
  1483 +
  1484 +#~ msgid "Identi.ca account"
  1485 +#~ msgstr "Conta Identi.ca"
  1486 +
  1487 +#~ msgid "Biography"
  1488 +#~ msgstr "Biografia"
  1489 +
  1490 +#~ msgid "Other Collaborations"
  1491 +#~ msgstr "Outras Colaborações"
  1492 +
  1493 +#~ msgid "Mailing List Subscriptions"
  1494 +#~ msgstr "Inscrições em listas de e-mails"
  1495 +
  1496 +#~ msgid "Change SVN and XMPP Client password"
  1497 +#~ msgstr "Trocar a senha do SVN e do Mensageiro"
  1498 +
  1499 +#~ msgid ""
  1500 +#~ "You don't need to change this password. Change it only if you really know "
  1501 +#~ "what you are doing."
  1502 +#~ msgstr ""
  1503 +#~ "Você não precisa trocar essa senha. Troque-a somente se tem certeza do "
  1504 +#~ "que está fazendo."
  1505 +
  1506 +#~ msgid "Subscribes: "
  1507 +#~ msgstr "Inscrições: "
  1508 +
  1509 +#~ msgid "Discussions"
  1510 +#~ msgstr "Discussões"
  1511 +
  1512 +#~ msgid "Community inside participations"
  1513 +#~ msgstr "Participações internas da comunidade"
  1514 +
  1515 +#~ msgid "documents found in"
  1516 +#~ msgstr "documentos encontrados em"
  1517 +
  1518 +#~ msgid "seconds"
  1519 +#~ msgstr "segundos"
  1520 +
  1521 +#~ msgid "Previous"
  1522 +#~ msgstr "Anterior"
  1523 +
  1524 +#~ msgid "Page"
  1525 +#~ msgstr "Página"
  1526 +
  1527 +#~ msgid "of"
  1528 +#~ msgstr "de"
  1529 +
  1530 +#~ msgid "Next"
  1531 +#~ msgstr "Pŕoximo"
  1532 +
  1533 +#~ msgid "%(option)s"
  1534 +#~ msgstr "%(option)s"
  1535 +
  1536 +#~ msgid "%(name)s"
  1537 +#~ msgstr "%(name)s"
  1538 +
  1539 +#~ msgid "<strong>%(name)s</strong>"
  1540 +#~ msgstr "<strong>%(name)s</strong>"
... ...
colab/manage.py 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +#!/usr/bin/env python
  2 +import os
  3 +import sys
  4 +
  5 +if __name__ == "__main__":
  6 + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "colab.settings")
  7 +
  8 + from django.core.management import execute_from_command_line
  9 +
  10 + execute_from_command_line(sys.argv)
... ...
colab/planet/__init__.py 0 → 100644
colab/planet/admin.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.contrib import admin
  2 +
  3 +# Register your models here.
... ...
colab/planet/locale/es/LC_MESSAGES/django.po 0 → 100644
... ... @@ -0,0 +1,103 @@
  1 +# SOME DESCRIPTIVE TITLE.
  2 +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
  3 +# This file is distributed under the same license as the PACKAGE package.
  4 +# Leonardo J. Caballero G. <leonardocaballero@gmail.com>, 2013.
  5 +msgid ""
  6 +msgstr ""
  7 +"Project-Id-Version: PACKAGE VERSION\n"
  8 +"Report-Msgid-Bugs-To: \n"
  9 +"POT-Creation-Date: 2013-07-26 11:28-0300\n"
  10 +"PO-Revision-Date: 2013-10-15 23:46-0430\n"
  11 +"Last-Translator: Leonardo J. Caballero G. <leonardocaballero@gmail.com>\n"
  12 +"Language-Team: ES <LL@li.org>\n"
  13 +"Language: es\n"
  14 +"MIME-Version: 1.0\n"
  15 +"Content-Type: text/plain; charset=UTF-8\n"
  16 +"Content-Transfer-Encoding: 8bit\n"
  17 +"Plural-Forms: nplurals=2; plural=(n != 1);\n"
  18 +"X-Generator: Virtaal 0.7.1\n"
  19 +
  20 +#: templates/common/pagination.html:5 templates/common/pagination.html.py:7
  21 +msgid "previous"
  22 +msgstr "anterior"
  23 +
  24 +#: templates/common/pagination.html:31 templates/common/pagination.html:33
  25 +msgid "next"
  26 +msgstr "próximo"
  27 +
  28 +#: templates/feedzilla/_post_template.html:9
  29 +msgid "From"
  30 +msgstr "De"
  31 +
  32 +#: templates/feedzilla/_post_template.html:9
  33 +msgid "on"
  34 +msgstr "en"
  35 +
  36 +#: templates/feedzilla/_post_template.html:17
  37 +msgid "Read original"
  38 +msgstr "Leer original"
  39 +
  40 +#: templates/feedzilla/base.html:6
  41 +msgid "Planet"
  42 +msgstr "Planeta"
  43 +
  44 +#: templates/feedzilla/base.html:15 templates/feedzilla/submit_blog.html:5
  45 +msgid "Submit a blog"
  46 +msgstr "Solicite la inclusión de un blog"
  47 +
  48 +#: templates/feedzilla/base.html:18
  49 +msgid "Tags"
  50 +msgstr "Etiquetas"
  51 +
  52 +#: templates/feedzilla/base.html:22
  53 +msgid "Source Blogs"
  54 +msgstr "Fuente de Blogs"
  55 +
  56 +#: templates/feedzilla/submit_blog.html:8
  57 +msgid ""
  58 +"Thank you. Your application has been accepted and will be reviewed by admin "
  59 +"in the near time."
  60 +msgstr "Gracias. Su solicitud ha sido aceptado y sera revisado por el administrador, lo mas pronto posible."
  61 +
  62 +#: templates/feedzilla/submit_blog.html:10
  63 +msgid "Required fields"
  64 +msgstr "Campos requeridos"
  65 +
  66 +#: templates/feedzilla/submit_blog.html:14
  67 +msgid "Blog Information"
  68 +msgstr "Información del blog"
  69 +
  70 +#: templates/feedzilla/submit_blog.html:15
  71 +msgid "Blog URL"
  72 +msgstr "Dirección URL de Blog"
  73 +
  74 +#: templates/feedzilla/submit_blog.html:16
  75 +msgid "Blog name"
  76 +msgstr "Nombre de blog"
  77 +
  78 +#: templates/feedzilla/submit_blog.html:17
  79 +msgid "Name of author of the blog"
  80 +msgstr "Nombre del autor del blog"
  81 +
  82 +#: templates/feedzilla/submit_blog.html:18
  83 +msgid "Feed URL"
  84 +msgstr "Dirección URL de Feed"
  85 +
  86 +#: templates/feedzilla/submit_blog.html:18
  87 +msgid "You can specify what exactly feed you want submit"
  88 +msgstr ""
  89 +"Usted puede especificar cual feed exactamente usted quiere solicitar su "
  90 +"inclusión"
  91 +
  92 +#: templates/feedzilla/submit_blog.html:19
  93 +msgid "Submit"
  94 +msgstr "Solicite la inclusión de un blog"
  95 +
  96 +#: templates/feedzilla/tag.html:5
  97 +#, python-format
  98 +msgid "Posts with &laquo;%(tag)s&raquo; label"
  99 +msgstr "Envíos con etiqueta &laquo;%(tag)s&raquo;"
  100 +
  101 +#: templates/feedzilla/tag.html:14
  102 +msgid "No posts with such label"
  103 +msgstr "No hay envíos con dicha etiqueta"
... ...
colab/planet/locale/pt_BR/LC_MESSAGES/django.mo 0 → 100644
No preview for this file type
colab/planet/locale/pt_BR/LC_MESSAGES/django.po 0 → 100644
... ... @@ -0,0 +1,119 @@
  1 +# SOME DESCRIPTIVE TITLE.
  2 +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
  3 +# This file is distributed under the same license as the PACKAGE package.
  4 +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
  5 +#
  6 +msgid ""
  7 +msgstr ""
  8 +"Project-Id-Version: PACKAGE VERSION\n"
  9 +"Report-Msgid-Bugs-To: \n"
  10 +"POT-Creation-Date: 2013-08-20 14:52-0300\n"
  11 +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
  12 +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
  13 +"Language-Team: LANGUAGE <LL@li.org>\n"
  14 +"Language: \n"
  15 +"MIME-Version: 1.0\n"
  16 +"Content-Type: text/plain; charset=UTF-8\n"
  17 +"Content-Transfer-Encoding: 8bit\n"
  18 +"Plural-Forms: nplurals=2; plural=(n > 1);\n"
  19 +
  20 +#: templates/common/pagination.html:5 templates/common/pagination.html.py:7
  21 +msgid "previous"
  22 +msgstr "anterior"
  23 +
  24 +#: templates/common/pagination.html:31 templates/common/pagination.html:33
  25 +msgid "next"
  26 +msgstr "próxima"
  27 +
  28 +#: templates/feedzilla/_post_template.html:9
  29 +msgid "From"
  30 +msgstr "De"
  31 +
  32 +#: templates/feedzilla/_post_template.html:9
  33 +msgid "on"
  34 +msgstr "em"
  35 +
  36 +#: templates/feedzilla/_post_template.html:13
  37 +msgid "Read original"
  38 +msgstr "Ler original"
  39 +
  40 +#: templates/feedzilla/base.html:6
  41 +msgid "Planet"
  42 +msgstr "Planet"
  43 +
  44 +#: templates/feedzilla/base.html:14 templates/feedzilla/submit_blog.html:7
  45 +msgid "Submit a blog"
  46 +msgstr "Solicite a inclusão de um blog"
  47 +
  48 +#: templates/feedzilla/base.html:17
  49 +msgid "Tags"
  50 +msgstr "Tags"
  51 +
  52 +#: templates/feedzilla/base.html:21
  53 +msgid "Source Blogs"
  54 +msgstr "Blogs Fonte"
  55 +
  56 +#: templates/feedzilla/index.html:11
  57 +msgid "There&#39;s no RSS registered"
  58 +msgstr "Não há RSS cadastrado"
  59 +
  60 +#: templates/feedzilla/index.html:12
  61 +msgid "Please"
  62 +msgstr "Por favor"
  63 +
  64 +#: templates/feedzilla/index.html:13
  65 +msgid "click here"
  66 +msgstr "clique aqui"
  67 +
  68 +#: templates/feedzilla/index.html:14
  69 +msgid "to submit a blog"
  70 +msgstr "para a inclusão de um blog"
  71 +
  72 +#: templates/feedzilla/submit_blog.html:11
  73 +msgid ""
  74 +"Thank you. Your application has been accepted and will be reviewed by admin "
  75 +"in the near time."
  76 +msgstr ""
  77 +
  78 +#: templates/feedzilla/submit_blog.html:13
  79 +msgid "Required fields"
  80 +msgstr ""
  81 +
  82 +#: templates/feedzilla/submit_blog.html:17
  83 +msgid "Blog Information"
  84 +msgstr "Informações do blog"
  85 +
  86 +#: templates/feedzilla/submit_blog.html:18
  87 +msgid "Blog URL"
  88 +msgstr ""
  89 +
  90 +#: templates/feedzilla/submit_blog.html:19
  91 +#, fuzzy
  92 +msgid "Blog name"
  93 +msgstr "Blogs"
  94 +
  95 +#: templates/feedzilla/submit_blog.html:20
  96 +msgid "Name of author of the blog"
  97 +msgstr ""
  98 +
  99 +#: templates/feedzilla/submit_blog.html:21
  100 +msgid "Feed URL"
  101 +msgstr ""
  102 +
  103 +#: templates/feedzilla/submit_blog.html:21
  104 +msgid "You can specify what exactly feed you want submit"
  105 +msgstr ""
  106 +
  107 +#: templates/feedzilla/submit_blog.html:22
  108 +#, fuzzy
  109 +msgid "Submit"
  110 +msgstr "Solicite a inclusão de um blog"
  111 +
  112 +#: templates/feedzilla/tag.html:7
  113 +#, python-format
  114 +msgid "Posts with &laquo;%(tag)s&raquo; label"
  115 +msgstr ""
  116 +
  117 +#: templates/feedzilla/tag.html:16
  118 +msgid "No posts with such label"
  119 +msgstr ""
... ...
colab/planet/models.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.db import models
  2 +
  3 +# Create your models here.
... ...
colab/planet/templates/common/pagination.html 0 → 100644
... ... @@ -0,0 +1,34 @@
  1 +{% load i18n %}
  2 +{% if page.paginator.num_pages > 1 %}
  3 +<div class="text-center">
  4 + <ul class="pagination">
  5 + {% if page.has_previous %}
  6 + <li><a href="{{ alterpath }}{{ page.previous_page_url }}">&laquo;</a></li>
  7 + {% endif %}
  8 +
  9 + {% if page.paginator.frame_start_page > 1 %}
  10 + <li><a href="{{ alterpath }}{{ page.first_page_url }}">1</a></li>
  11 + {% endif %}
  12 + {% if page.paginator.frame_start_page > 2 %}
  13 + <li class="disabled"><a href="#">...</a></li>
  14 + {% endif %}
  15 +
  16 + {% for number, url in page.paginator.frame %}
  17 + {% if not url %}..{% else %}
  18 + <li {% ifequal page.number number%}class="active"{% endifequal %}><a href="{{ alterpath }}{{ url }}">{{ number }}</a></li>
  19 + {% endif %}
  20 + {% endfor %}
  21 +
  22 + {% if page.paginator.frame_end_page != page.paginator.num_pages %}
  23 + {% if page.paginator.frame_end_page != page.paginator.num_pages|add:"-1" %}
  24 + <li class="disabled"><a href="#">...</a></li>
  25 + {% endif %}
  26 + <li><a href="{{ alterpath }}{{ page.last_page_url }}">{{ page.paginator.num_pages }}</a></li>
  27 + {% endif %}
  28 +
  29 + {% if page.has_next %}
  30 + <li><a href="{{ alterpath }}{{ page.next_page_url }}">&raquo;</a></li>
  31 + {% endif %}
  32 + </ul>
  33 +</div>
  34 +{% endif %}
... ...
colab/planet/templates/feedzilla/_post_template.html 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +{% load i18n %}
  2 +
  3 +<div class="blog-post-item">
  4 + <a name="post-{{ post.pk }}"></a>
  5 +
  6 + <h3><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h3>
  7 +
  8 + <div class="post-meta">{% trans "From" %} <a href="{{ post.feed.site_url }}">{{ post.feed.author_or_title }}</a> {% trans "on" %} {{ post.created }}</div>
  9 +
  10 + {{ post.summary|safe }}
  11 +
  12 + <b><a href="{{ post.get_absolute_url }}">{% trans "Read original" %}</a></b>
  13 +
  14 + {% if post.tags.count %}
  15 + <div class="tags">
  16 + <span><b>Tags:</b>
  17 + {% for tag in post.tags.all %}
  18 + <a href="{% url "feedzilla_tag" tag.name %}" class="tag">{{ tag }}</a>
  19 + {% endfor %}
  20 + </span>
  21 + </div>
  22 + {% endif %}
  23 +</div>
... ...
colab/planet/templates/feedzilla/base.html 0 → 100644
... ... @@ -0,0 +1,32 @@
  1 +{% extends 'base.html' %}
  2 +{% load i18n feedzilla_tags %}
  3 +
  4 +{% block title %}Blogs{% endblock %}
  5 +
  6 +{% block main-content %}
  7 + <h2>{% trans 'Community Blogs' %}</h2>
  8 + <hr/>
  9 +
  10 + <div id="planet" class="row">
  11 + <div class="col-lg-9 col-md-8 col-sm-12">
  12 + {% block feedzilla_content %}{% endblock %}
  13 + </div>
  14 +
  15 + <div class="col-lg-3 col-md-4 col-sm-12">
  16 + <div class="well">
  17 + <h3>{% trans 'Tags' %}</h3>
  18 + {% feedzilla_tag_cloud %}
  19 + </div>
  20 + <div class="well">
  21 + <h3>{% trans 'Source Blogs' %}</h3>
  22 + {% feedzilla_donor_list order_by="title" %}
  23 + {% if user.is_authenticated %}
  24 + <div class="text-center">
  25 + <a class="btn btn-primary" href="{% url "feedzilla_submit_blog" %}">{% trans "Submit a blog" %}</a>
  26 + </div>
  27 + {% endif %}
  28 + </div>
  29 + </div>
  30 + </div>
  31 +
  32 +{% endblock %}
... ...
colab/planet/templates/feedzilla/index.html 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +{% extends 'feedzilla/base.html' %}
  2 +{% load i18n %}
  3 +
  4 +{% block feedzilla_content %}
  5 +
  6 +{% for post in page.object_list %}
  7 + {% include 'feedzilla/_post_template.html' %}
  8 + <hr>
  9 + {% empty %}
  10 + <h2>{% trans 'There is no RSS registered' %}</h2>
  11 + <h3>
  12 + {% trans 'Please' %}
  13 + <a href="{% url "feedzilla_submit_blog" %}">{% trans 'click here' %}</a>
  14 + {% trans 'to submit a blog' %}</h3>
  15 + </h3>
  16 +{% endfor %}
  17 +
  18 +{% include "common/pagination.html" %}
  19 +
  20 +{% endblock %}
... ...
colab/planet/templates/feedzilla/submit_blog.html 0 → 100644
... ... @@ -0,0 +1,37 @@
  1 +{% extends 'feedzilla/base.html' %}
  2 +{% load i18n %}
  3 +
  4 +{% block feedzilla_content %}
  5 +<h1>{% trans "Submit a blog" %}</h1>
  6 +
  7 +{% if success %}
  8 +<p>{% trans "Thank you. Your application has been accepted and will be reviewed by admin in the near time." %}</p>
  9 +{% else %}
  10 +<form method="post" class="common-form">
  11 + {% csrf_token %}
  12 +
  13 + <div class="row">
  14 + {% for field in form %}
  15 + <div class="col-lg-6 col-md-6 col-sm-12 col-xm-12">
  16 + <div class="form-group{% if field.field.required %} required{% endif %}{% if field.errors %} alert alert-danger has-error{% endif %}">
  17 + <label for="{{ field.name }}" class="control-label">
  18 + {{ field.label }}
  19 + </label>
  20 + <input id="id_{{ field.name }}" name="{{ field.name }}" type="text" class="form-control" />
  21 + {{ field.errors }}
  22 + </div>
  23 + </div>
  24 + {% endfor %}
  25 + </div>
  26 +
  27 + <div class="row">
  28 + <div class="col-lg-12 text-center">
  29 + <button class="btn btn-primary btn-lg">{% trans "Submit" %}</button>
  30 + </div>
  31 + </div>
  32 +
  33 + <br>
  34 +
  35 +</form>
  36 +{% endif %}
  37 +{% endblock %}
... ...
colab/planet/templates/feedzilla/tag.html 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +{% extends 'feedzilla/base.html' %}
  2 +{% load i18n %}
  3 +
  4 +{% block feedzilla_content %}
  5 +
  6 +<div class="col-lg-9">
  7 + <h3>{% block title %}{% blocktrans %}Posts with &laquo;{{ tag }}&raquo; label{% endblocktrans %}{% endblock title %}</h3>
  8 + <hr>
  9 + {% if page.object_list %}
  10 + {% for post in page.object_list %}
  11 + {% include 'feedzilla/_post_template.html' %}
  12 + <hr>
  13 + {% endfor %}
  14 + {% include "pagination.html" %}
  15 + {% else %}
  16 + <p>{% trans "No posts with such label" %}</p>
  17 + {% endif %}
  18 +</div>
  19 +{% endblock %}
... ...
colab/planet/tests.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.test import TestCase
  2 +
  3 +# Create your tests here.
... ...
colab/planet/views.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.shortcuts import render
  2 +
  3 +# Create your views here.
... ...
colab/proxy/__init__.py 0 → 100644
colab/proxy/context_processors.py 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +
  2 +from django.apps import apps
  3 +
  4 +
  5 +def proxied_apps(request):
  6 + proxied_apps = {}
  7 +
  8 + for app in apps.get_app_configs():
  9 + if getattr(app, 'colab_proxied_app', False):
  10 + proxied_apps[app.label] = True
  11 +
  12 + return {'proxy': proxied_apps}
... ...
colab/proxy/gitlab/__init__.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +
  2 +
  3 +default_app_config = 'proxy.gitlab.apps.ProxyGitlabAppConfig'
... ...
colab/proxy/gitlab/admin.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.contrib import admin
  2 +
  3 +# Register your models here.
... ...
colab/proxy/gitlab/apps.py 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +
  2 +from ..utils.apps import ColabProxiedAppConfig
  3 +
  4 +
  5 +class ProxyGitlabAppConfig(ColabProxiedAppConfig):
  6 + name = 'proxy.gitlab'
  7 + verbose_name = 'Gitlab Proxy'
... ...
colab/proxy/gitlab/diazo.xml 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +<rules
  2 + xmlns="http://namespaces.plone.org/diazo"
  3 + xmlns:css="http://namespaces.plone.org/diazo/css"
  4 + xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  5 +
  6 + <before theme-children="/html/head" content-children="/html/head" />
  7 + <before css:theme-children="#main-content" css:content-children="body" />
  8 +
  9 + <merge attributes="class" css:theme="body" css:content="body" />
  10 +
  11 + <!-- Add gitlab properties -->
  12 + <merge attributes="data-page" css:theme="body" css:content="body" />
  13 + <merge attributes="data-project-id" css:theme="body" css:content="body" />
  14 +
  15 + <drop css:content="#top-panel" />
  16 + <drop css:content=".navbar-gitlab" />
  17 +</rules>
... ...
colab/proxy/gitlab/models.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.db import models
  2 +
  3 +# Create your models here.
... ...
colab/proxy/gitlab/templates/proxy/gitlab.html 0 → 100644
... ... @@ -0,0 +1,47 @@
  1 +{% extends 'base.html' %}
  2 +{% load static %}
  3 +
  4 +{% block head_css %}
  5 +<style>
  6 + /* Reset left and with for .modal-dialog style (like gitlab does),
  7 + the bootstrap.css one makes it small */
  8 + @media screen and (min-width: 768px) {
  9 + .modal-dialog {
  10 + left: auto;
  11 + width: auto;
  12 + }
  13 + }
  14 + div#main-content {
  15 + margin-top: 65px;
  16 + }
  17 +
  18 + div#main-content div.container {
  19 + width: 1110px;
  20 + }
  21 + div#main-content div.flash-container{
  22 + width: 85%;
  23 + }
  24 + #breadcrumbs {
  25 + border: 0 !important;
  26 + }
  27 +
  28 + #right-top-nav {
  29 + margin-right: 5em !important;
  30 + }
  31 +</style>
  32 +{% endblock %}
  33 +
  34 +{% block head_js %}
  35 +<script type="text/javascript">
  36 + $(function(){
  37 + // bootstrap.css forces .hide {display:none!important}, and this makes
  38 + // gitlab .hide elements NEVER have a display:block, so
  39 + // instead of editing bootstrap.css, we just removed '.hide' css class and
  40 + // toggled
  41 + $('.hide').removeClass('hide').css('display', 'none');
  42 + });
  43 +</script>
  44 +<script type="text/javascript" src="{% static 'third-party/bootstrap/js/bootstrap.min.js' %}"></script>
  45 +<script type="text/javascript" src="{% static 'third-party/jquery.cookie.js' %}"></script>
  46 +<script>jQuery.noConflict();</script>
  47 +{% endblock %}
... ...
colab/proxy/gitlab/tests.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.test import TestCase
  2 +
  3 +# Create your tests here.
... ...
colab/proxy/gitlab/urls.py 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +
  2 +from django.conf.urls import patterns, url
  3 +
  4 +from .views import GitlabProxyView
  5 +
  6 +
  7 +urlpatterns = patterns('',
  8 + # Gitlab URLs
  9 + url(r'^gitlab/(?P<path>.*)$', GitlabProxyView.as_view()),
  10 +)
... ...
colab/proxy/gitlab/views.py 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +
  2 +from django.conf import settings
  3 +
  4 +from ..utils.views import ColabProxyView
  5 +
  6 +
  7 +class GitlabProxyView(ColabProxyView):
  8 + upstream = settings.PROXIED_APPS['gitlab']['upstream']
  9 + diazo_theme_template = 'proxy/gitlab.html'
... ...
colab/proxy/jenkins/__init__.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +
  2 +
  3 +default_app_config = 'proxy.jenkins.apps.ProxyJenkinsAppConfig'
... ...
colab/proxy/jenkins/admin.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.contrib import admin
  2 +
  3 +# Register your models here.
... ...
colab/proxy/jenkins/apps.py 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +
  2 +from ..utils.apps import ColabProxiedAppConfig
  3 +
  4 +
  5 +class ProxyJenkinsAppConfig(ColabProxiedAppConfig):
  6 + name = 'proxy.jenkins'
  7 + verbose_name = 'Jenkins Proxy'
... ...
colab/proxy/jenkins/diazo.xml 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +<rules
  2 + xmlns="http://namespaces.plone.org/diazo"
  3 + xmlns:css="http://namespaces.plone.org/diazo/css"
  4 + xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  5 +
  6 + <before theme-children="/html/head" content-children="/html/head" />
  7 + <before css:theme-children="#main-content" css:content-children="body" />
  8 +
  9 + <merge attributes="class" css:theme="body" css:content="body" />
  10 + <drop css:content="#top-panel" />
  11 +
  12 + <drop attributes="style" css:content="#main-table" />
  13 +
  14 + <after theme-children="/html/head">
  15 + <script>jQuery.noConflict();</script>
  16 + <style>
  17 + #breadcrumbs {
  18 + border: 0 !important;
  19 + }
  20 +
  21 + #right-top-nav {
  22 + margin-right: 5em !important;
  23 + }
  24 + </style>
  25 + </after>
  26 +
  27 +</rules>
... ...
colab/proxy/jenkins/models.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.db import models
  2 +
  3 +# Create your models here.
... ...
colab/proxy/jenkins/tests.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.test import TestCase
  2 +
  3 +# Create your tests here.
... ...
colab/proxy/jenkins/urls.py 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +
  2 +from django.conf.urls import patterns, url
  3 +
  4 +from .views import JenkinsProxyView
  5 +
  6 +
  7 +urlpatterns = patterns('',
  8 + # Jenkins URLs
  9 + url(r'^ci/(?P<path>.*)$', JenkinsProxyView.as_view()),
  10 +)
... ...
colab/proxy/jenkins/views.py 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +
  2 +from django.conf import settings
  3 +
  4 +from ..utils.views import ColabProxyView
  5 +
  6 +
  7 +class JenkinsProxyView(ColabProxyView):
  8 + upstream = settings.PROXIED_APPS['jenkins']['upstream']
... ...
colab/proxy/redmine/__init__.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +
  2 +
  3 +default_app_config = 'proxy.redmine.apps.ProxyRedmineAppConfig'
... ...
colab/proxy/redmine/admin.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.contrib import admin
  2 +
  3 +# Register your models here.
... ...
colab/proxy/redmine/apps.py 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +
  2 +from ..utils.apps import ColabProxiedAppConfig
  3 +
  4 +
  5 +class ProxyRedmineAppConfig(ColabProxiedAppConfig):
  6 + name = 'proxy.redmine'
  7 + verbose_name = 'Redmine Proxy'
... ...
colab/proxy/redmine/diazo.xml 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +<rules
  2 + xmlns="http://namespaces.plone.org/diazo"
  3 + xmlns:css="http://namespaces.plone.org/diazo/css"
  4 + xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  5 +
  6 + <before theme-children="/html/head" content-children="/html/head" />
  7 + <before css:theme-children="#main-content" css:content-children="body" />
  8 +
  9 + <merge attributes="class" css:theme="body" css:content="body" />
  10 + <drop css:content="#top-panel" />
  11 +</rules>
... ...
colab/proxy/redmine/models.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.db import models
  2 +
  3 +# Create your models here.
... ...
colab/proxy/redmine/tests.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.test import TestCase
  2 +
  3 +# Create your tests here.
... ...