Commit 05aa61b1d95c30e49eb49d7f9d744d83055794a0
Exists in
master
and in
39 other branches
Merge pull request #50 from colab/pypi
Python Packing for Colab
Showing
507 changed files
with
12882 additions
and
18383 deletions
Show diff stats
Too many changes.
To preserve performance only 100 of 507 files displayed.
.gitignore
README.rst
... | ... | @@ -8,6 +8,8 @@ |
8 | 8 | Colab, a Software for Communities |
9 | 9 | ================================= |
10 | 10 | |
11 | + | |
12 | + | |
11 | 13 | What is Colab? |
12 | 14 | ============== |
13 | 15 | |
... | ... | @@ -21,149 +23,52 @@ Application that integrates existing systems to represent the contributions of t |
21 | 23 | |
22 | 24 | * And other systems in the community. |
23 | 25 | |
26 | + | |
27 | + | |
24 | 28 | Features |
25 | 29 | ======== |
26 | 30 | |
27 | 31 | * Developed by Interlegis Communities http://colab.interlegis.leg.br/ |
28 | 32 | |
29 | -* Written with Python http://python.org/ | |
33 | +* Written in Python http://python.org/ | |
30 | 34 | |
31 | -* Built in Django Web Framework https://www.djangoproject.com/ | |
35 | +* Built with Django Web Framework https://www.djangoproject.com/ | |
32 | 36 | |
33 | 37 | * Search engine with Solr https://lucene.apache.org/solr/ |
34 | 38 | |
35 | -Colab and Solr | |
36 | -============== | |
37 | - | |
38 | -This software uses Apache Solr as search platform based on Apache Lucene. | |
39 | - | |
40 | -With Solr generates the REST style API with which you can make HTTP requests | |
41 | -to get results: natively in XML or JSON, PHP, Ruby and Python and then treatment. | |
42 | - | |
43 | -Installation (Development Environment) | |
44 | -========================================== | |
45 | - | |
46 | -A detailed installation steps can be found in: | |
47 | - | |
48 | -`Detailed full environment installation <install.rst>`_ | |
49 | - | |
50 | -Here we'll cover how to setup a development environment using a Vagrant | |
51 | -virtual machine. | |
52 | - | |
53 | -Before getting started you should install the following softwares: | |
54 | - | |
55 | -* Vagrant (tested with version 1.2.7) | |
56 | 39 | |
57 | -* Virtualbox (version >= 4.0) | |
58 | 40 | |
59 | -* fabric (tested with version 1.7.0) | |
41 | +Installation | |
42 | +============ | |
60 | 43 | |
61 | -* Git | |
62 | - | |
63 | - | |
64 | -Getting started with the Virtual Machine | |
65 | ------------------------------------------- | |
66 | - | |
67 | -First you will need to clone the repository: | |
44 | +First install the dependencies and than the project it self: | |
68 | 45 | |
69 | 46 | .. code-block:: |
70 | 47 | |
71 | - git clone git@github.com:colab-community/colab.git | |
72 | - | |
73 | - | |
74 | -*NOTE:* | |
75 | - | |
76 | - Here we are assuming you have ssh permissions to clone the repo using ssh. If not | |
77 | - fork it and clone your own fork (or use https instead of ssh). | |
78 | - | |
48 | + pip install -r requirements.txt | |
49 | + pip install . | |
79 | 50 | |
80 | -Enter in the repository you've just cloned. | |
81 | -To start working all you need is to turn the virtual machine on with the command: | |
82 | 51 | |
83 | -.. code-block:: | |
84 | - | |
85 | - vagrant up | |
86 | - | |
87 | - | |
88 | -*NOTE:* | |
89 | - | |
90 | - BE PATIENT! | |
91 | - | |
92 | - This will take a while. The `vagrant up` will download a full vm (virtualbox) | |
93 | - running a Ubuntu 12.04 64bits. After the vm is up and running the command | |
94 | - will also configure it (using puppet) and that will also take a bit. | |
95 | - | |
96 | 52 | |
97 | 53 | Running Colab |
98 | --------------- | |
54 | +============= | |
99 | 55 | |
100 | -Now that you have a vm running we have two options to run Colab: | |
56 | +To run Colab with development server you will have to: | |
101 | 57 | |
102 | -* Django development server (runserver) | |
103 | - | |
104 | -* Gunicorn + supervisor + Nginx | |
105 | - | |
106 | - | |
107 | -Django development server (runserver) | |
108 | -++++++++++++++++++++++++++++++++++++++ | |
109 | - | |
110 | -This option is advised for developers working in new features for Colab. | |
111 | -The code used to run Colab will be the same code placed on your machine, | |
112 | -that means that if you change the code in your own computer the code on | |
113 | -the vm will also change. | |
114 | - | |
115 | -Make sure you have a ``local_settings.py`` file placed in your repository. It | |
116 | -should be located in ``src/colab/``. | |
117 | - | |
118 | -To get started you can copy the example file as follow: | |
58 | +1- Create the example configuration file: | |
119 | 59 | |
120 | 60 | .. code-block:: |
121 | 61 | |
122 | - cp src/colab/local_settings-dev.py src/colab/local_settings.py | |
123 | - | |
124 | - | |
125 | -Now we are ready to run: | |
126 | - | |
127 | -.. code-block:: | |
128 | - | |
129 | - fab runserver | |
62 | + colab-init-config > /etc/colab/settings.yaml | |
130 | 63 | |
131 | - | |
132 | -*Note* | |
133 | - | |
134 | - As this is the first time you run this command it will install all | |
135 | - requirements from ``requirements.txt`` into a virtualenv. To update | |
136 | - those requirements you should run ``fab runserver:update``. | |
137 | - | |
138 | - | |
139 | -The ``fab runserver`` command will open the django builtin development | |
140 | -server on the port 7000 but due to vagrant magic you will be able to | |
141 | -access it on ``http://localhost:8000/``. | |
142 | - | |
143 | - | |
144 | -Gunicorn + supervisor + Nginx | |
145 | -++++++++++++++++++++++++++++++ | |
146 | - | |
147 | -This option will run Colab in a way very similar to the production | |
148 | -environment. This should be used to test puppet manifests and also | |
149 | -the configuration of each one of the services running. | |
150 | - | |
151 | -First of all we need to clone the repo and configure your ``local_settings.py``. | |
152 | -That is done by calling the command: | |
153 | - | |
154 | -.. code-block:: | |
155 | - | |
156 | - fab install:path/to/your/local_settings.py | |
157 | - | |
158 | - | |
159 | -Now you need to deploy the code using the command: | |
64 | +2- Edit the configuration file. Make sure you set everything you need including **database** credentials. | |
65 | + | |
66 | +3- Run the development server: | |
160 | 67 | |
161 | 68 | .. code-block:: |
162 | 69 | |
163 | - fab deploy | |
164 | - | |
70 | + colab-admin runserver 0.0.0.0:8000 | |
165 | 71 | |
166 | -For the next deploy you can just run ``fab deploy`` and in case your | |
167 | -``requirements.txt`` changes ``fab deploy:update``. | |
168 | 72 | |
169 | -The deployed code will be accessible on ``http://localhost:8080``. | |
73 | +**NOTE**: In case you want to keep the configuration file else where just set the | |
74 | +desired location in environment variable **COLAB_SETTINGS**. | ... | ... |
Vagrantfile
... | ... | @@ -6,7 +6,26 @@ |
6 | 6 | # - trusty64 |
7 | 7 | # - centos6.5 |
8 | 8 | |
9 | -distro = "precise64" | |
9 | +default_box = "precise64" | |
10 | +if $stdin.isatty | |
11 | + if Dir.glob(File.join(File.dirname("__FILE__"), '.vagrant/**/id')).empty? | |
12 | + puts "Bases boxes available locally:" | |
13 | + puts '------------------------------' | |
14 | + system('vagrant', 'box', 'list') | |
15 | + puts | |
16 | + puts 'Base boxes we can provide you:' | |
17 | + puts '------------------------------' | |
18 | + puts 'precise64 (virtualbox)' | |
19 | + puts 'trusty64 (virtualbox)' | |
20 | + puts 'centos6.5 (virtualbox)' | |
21 | + puts | |
22 | + print "Which box to use [#{default_box}]: " | |
23 | + choice = $stdin.gets.strip | |
24 | + if !choice.empty? | |
25 | + default_box = choice | |
26 | + end | |
27 | + end | |
28 | +end | |
10 | 29 | |
11 | 30 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! |
12 | 31 | VAGRANTFILE_API_VERSION = "2" |
... | ... | @@ -18,32 +37,23 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| |
18 | 37 | |
19 | 38 | # Every Vagrant virtual environment requires a box to build off of. |
20 | 39 | |
21 | - config.vm.define "colab" do |colab| | |
22 | - | |
23 | - colab.vm.box = distro | |
24 | - | |
25 | - if distro == "precise64" | |
26 | - colab.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/precise/current/precise-server-cloudimg-amd64-vagrant-disk1.box" | |
27 | - elsif distro == "trusty64" | |
28 | - colab.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box" | |
29 | - elsif distro == "centos6.5" | |
30 | - colab.vm.box_url = "https://github.com/2creatives/vagrant-centos/releases/download/v6.5.3/centos65-x86_64-20140116.box" | |
31 | - end | |
32 | - | |
33 | - colab.vm.network :forwarded_port, guest: 80, host: 8080 | |
34 | - colab.vm.network :forwarded_port, guest: 8000, host: 8000 | |
35 | - colab.vm.network :forwarded_port, guest: 5280, host: 5280 | |
36 | - colab.vm.network :forwarded_port, guest: 8080, host: 8081 | |
37 | - colab.vm.network :forwarded_port, guest: 8983, host: 8983 | |
40 | + config.vm.box = default_box | |
38 | 41 | |
39 | - colab.vm.network "private_network", ip: "192.168.33.10" | |
42 | + case config.vm.box | |
43 | + when "precise64" | |
44 | + config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/precise/current/precise-server-cloudimg-amd64-vagrant-disk1.box" | |
45 | + when "trusty64" | |
46 | + config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box" | |
47 | + when "centos6.5" | |
48 | + config.vm.box_url = "https://github.com/2creatives/vagrant-centos/releases/download/v6.5.3/centos65-x86_64-20140116.box" | |
49 | + end | |
40 | 50 | |
41 | - colab.vm.provider "virtualbox" do |vb| | |
42 | - # Use VBoxManage to customize the VM. For example to change memory: | |
43 | - vb.customize ["modifyvm", :id, "--memory", "1024"] | |
44 | - end | |
51 | + config.vm.provision "shell", keep_color: true, path: 'vagrant/bootstrap.sh' | |
52 | + config.vm.provision "shell", privileged: false, keep_color: true, path: "vagrant/provision.sh" | |
45 | 53 | |
46 | - end | |
54 | + config.vm.network :forwarded_port, guest: 8000, host: 8000 # Colab (runserver) | |
55 | + config.vm.network :forwarded_port, guest: 5280, host: 5280 # BOSH server | |
56 | + config.vm.network :forwarded_port, guest: 8983, host: 8983 # Solr | |
47 | 57 | |
48 | 58 | # Disable automatic box update checking. If you disable this, then |
49 | 59 | # boxes will only be checked for updates when the user runs |
... | ... | @@ -73,19 +83,18 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| |
73 | 83 | # the path on the guest to mount the folder. And the optional third |
74 | 84 | # argument is a set of non-required options. |
75 | 85 | # config.vm.synced_folder "../data", "/vagrant_data" |
76 | - config.vm.synced_folder ".", "/vagrant", type: "nfs" | |
77 | 86 | |
78 | 87 | # Provider-specific configuration so you can fine-tune various |
79 | 88 | # backing providers for Vagrant. These expose provider-specific options. |
80 | 89 | # Example for VirtualBox: |
81 | 90 | # |
82 | - # config.vm.provider "virtualbox" do |vb| | |
91 | + config.vm.provider "virtualbox" do |vb| | |
83 | 92 | # # Don't boot with headless mode |
84 | 93 | # vb.gui = true |
85 | - # | |
86 | - # # Use VBoxManage to customize the VM. For example to change memory: | |
87 | - # vb.customize ["modifyvm", :id, "--memory", "1024"] | |
88 | - # end | |
94 | + | |
95 | + # Use VBoxManage to customize the VM. For example to change memory: | |
96 | + vb.customize ["modifyvm", :id, "--memory", "1024"] | |
97 | + end | |
89 | 98 | # |
90 | 99 | # View the documentation for the provider you're using for more |
91 | 100 | # information on available options. | ... | ... |
... | ... | @@ -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) | ... | ... |
... | ... | @@ -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 ..super_archives.models import MailingList | |
10 | +from .utils.validators import validate_social_account | |
11 | +from .utils import mailman | |
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 | ... | ... |
... | ... | @@ -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} | ... | ... |
... | ... | @@ -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 | + ] | ... | ... |
... | ... | @@ -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 | +) | ... | ... |
... | ... | @@ -0,0 +1,79 @@ |
1 | +# -*- coding: utf-8 -*- | |
2 | + | |
3 | +from haystack import indexes | |
4 | +from django.db.models import Count | |
5 | + | |
6 | +from colab.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) | ... | ... |
... | ... | @@ -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 %} | ... | ... |
... | ... | @@ -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 %} | ... | ... |
... | ... | @@ -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> {% trans "edit profile"|title %}</a> | |
30 | + <a class="btn btn-info" href="{% url 'user_list_subscriptions' user_ %}"><span class="glyphicon glyphicon-pencil"></span> {% 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&type=thread&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&author={{ user_.username }}"> | |
160 | + {% trans "View more posts..." %} | |
161 | + </a> | |
162 | + <div> </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&collaborators={{ user_.username }}"> | |
175 | + {% trans "View more contributions..." %} | |
176 | + </a> | |
177 | + <div> </div> | |
178 | + </div> | |
179 | + | |
180 | + </div> | |
181 | + | |
182 | +{% endblock %} | ... | ... |
... | ... | @@ -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,20 @@ |
1 | + | |
2 | +from django import template | |
3 | + | |
4 | +from colab.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) | ... | ... |
... | ... | @@ -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) | ... | ... |
... | ... | @@ -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 . import views | |
8 | + | |
9 | +urlpatterns = patterns('', | |
10 | + url(r'^register/$', 'colab.accounts.views.signup', name='signup'), | |
11 | + | |
12 | + url(r'^change-password/$', | |
13 | + ChangeXMPPPasswordView.as_view(), name='change_password'), | |
14 | + | |
15 | + url(r'^logout/?$', 'colab.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 | +) | ... | ... |
... | ... | @@ -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() | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 colab.super_archives.models import EmailAddress, Message | |
26 | +from colab.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 | ... | ... |
... | ... | @@ -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 colab.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 | +# } | ... | ... |
... | ... | @@ -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 | +) | ... | ... |
... | ... | @@ -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 colab.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) | ... | ... |
... | ... | @@ -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) | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 colab.accounts.models import User | |
7 | +from colab.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)) | ... | ... |
... | ... | @@ -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 colab.accounts.models import User | |
7 | +from colab.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 | ... | ... |
... | ... | @@ -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 | + ] | ... | ... |
... | ... | @@ -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') | ... | ... |
... | ... | @@ -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 colab.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 | ... | ... |
... | ... | @@ -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 haystack.query import SearchQuerySet | |
10 | + | |
11 | +#from proxy.trac.models import WikiCollabCount, TicketCollabCount | |
12 | +from colab.search.utils import trans | |
13 | +from colab.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 | ... | ... |
No preview for this file type
... | ... | @@ -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 «%(tag)s» label" | |
507 | +msgstr "Postagens com a etiqueta «%(tag)s»" | |
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 "name" is the fieldname and "WikiName" 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>" | ... | ... |
... | ... | @@ -0,0 +1,21 @@ |
1 | + | |
2 | +import os | |
3 | + | |
4 | +from django.core.management import ManagementUtility | |
5 | + | |
6 | +from .initconfig import initconfig | |
7 | + | |
8 | + | |
9 | +def execute_from_command_line(argv=None): | |
10 | + """ | |
11 | + A simple method that runs a ManagementUtility. | |
12 | + """ | |
13 | + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "colab.settings") | |
14 | + os.environ.setdefault("COLAB_SETTINGS", "/etc/colab/settings.yaml") | |
15 | + | |
16 | + utility = ManagementUtility(argv) | |
17 | + utility.execute() | |
18 | + | |
19 | + | |
20 | +def run_colab_config(argv=None): | |
21 | + initconfig() | ... | ... |
... | ... | @@ -0,0 +1,76 @@ |
1 | + | |
2 | +from django.utils.crypto import get_random_string | |
3 | + | |
4 | + | |
5 | +CONFIG_TEMPLATE = """ | |
6 | + | |
7 | +## Set to false in production | |
8 | +DEBUG: true | |
9 | +TEMPLATE_DEBUG: true | |
10 | + | |
11 | +## System admins | |
12 | +ADMINS: &admin | |
13 | + - | |
14 | + - John Foo | |
15 | + - john@example.com | |
16 | + - | |
17 | + - Mary Bar | |
18 | + - mary@example.com | |
19 | + | |
20 | +MANAGERS: *admin | |
21 | + | |
22 | +COLAB_FROM_ADDRESS: '"Colab" <noreply@example.com>' | |
23 | +SERVER_EMAIL: '"Colab" <noreply@example.com>' | |
24 | + | |
25 | +EMAIL_HOST: localhost | |
26 | +EMAIL_PORT: 25 | |
27 | +EMAIL_SUBJECT_PREFIX: '[colab]' | |
28 | + | |
29 | +SECRET_KEY: '{secret_key}' | |
30 | + | |
31 | +SITE_URL: 'http://www.example.com/' | |
32 | +BROWSERID_AUDIENCES: | |
33 | + - http://example.com | |
34 | + - https://example.org | |
35 | + - http://example.net | |
36 | + | |
37 | +ALLOWED_HOSTS: | |
38 | + - example.com | |
39 | + - example.org | |
40 | + - example.net | |
41 | + | |
42 | +### Uncomment to enable Converse.js | |
43 | +# CONVERSEJS_ENABLED: True | |
44 | + | |
45 | +### Uncomment to enable auto-registration | |
46 | +# CONVERSEJS_AUTO_REGISTER: 'xmpp.example.com' | |
47 | + | |
48 | +## Database settings | |
49 | +DATABASES: | |
50 | + default: | |
51 | + ENGINE: django.db.backends.postgresql_psycopg2 | |
52 | + HOST: localhost | |
53 | + NAME: colab | |
54 | + USER: colab | |
55 | + PASSWORD: colab | |
56 | + | |
57 | +## Disable indexing | |
58 | +ROBOTS_NOINDEX: false | |
59 | + | |
60 | +### Log errors to Sentry instance | |
61 | +# RAVEN_DSN: 'http://public:secret@example.com/1' | |
62 | + | |
63 | +### Colab proxied apps | |
64 | +# PROXIED_APPS: | |
65 | +# gitlab: | |
66 | +# upstream: 'http://localhost:8090/gitlab/' | |
67 | +# trac: | |
68 | +# upstream: 'http://localhost:5000/trac/' | |
69 | + | |
70 | +""" | |
71 | + | |
72 | + | |
73 | +def initconfig(): | |
74 | + chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)' | |
75 | + secret_key = get_random_string(50, chars) | |
76 | + print(CONFIG_TEMPLATE.format(secret_key=secret_key)) | ... | ... |
... | ... | @@ -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 «%(tag)s» label" | |
99 | +msgstr "Envíos con etiqueta «%(tag)s»" | |
100 | + | |
101 | +#: templates/feedzilla/tag.html:14 | |
102 | +msgid "No posts with such label" | |
103 | +msgstr "No hay envíos con dicha etiqueta" | ... | ... |
No preview for this file type
... | ... | @@ -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'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 «%(tag)s» label" | |
115 | +msgstr "" | |
116 | + | |
117 | +#: templates/feedzilla/tag.html:16 | |
118 | +msgid "No posts with such label" | |
119 | +msgstr "" | ... | ... |
... | ... | @@ -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 }}">«</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 }}">»</a></li> | |
31 | + {% endif %} | |
32 | + </ul> | |
33 | +</div> | |
34 | +{% endif %} | ... | ... |
... | ... | @@ -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> | ... | ... |
... | ... | @@ -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 %} | ... | ... |
... | ... | @@ -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 %} | ... | ... |
... | ... | @@ -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 %} | ... | ... |
... | ... | @@ -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 «{{ tag }}» 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 %} | ... | ... |
... | ... | @@ -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> | ... | ... |
... | ... | @@ -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 %} | ... | ... |
... | ... | @@ -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> | ... | ... |
... | ... | @@ -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> | ... | ... |