Commit d281e23bb665e949cf8dedc12725838405f1ce12

Authored by Sergio Oliveira
2 parents 4a86615c 1ce3e428

Merge pull request #4 from TracyWebTech/master

Updating
Showing 48 changed files with 682 additions and 546 deletions   Show diff stats
@@ -81,7 +81,6 @@ def deploy(update=False): @@ -81,7 +81,6 @@ def deploy(update=False):
81 if update: 81 if update:
82 update_requirements() 82 update_requirements()
83 83
84 - load_badges()  
85 with cd('~/colab/src/'), prefix(WORKON_COLAB): 84 with cd('~/colab/src/'), prefix(WORKON_COLAB):
86 run('python manage.py syncdb') 85 run('python manage.py syncdb')
87 run('python manage.py migrate') 86 run('python manage.py migrate')
@@ -91,17 +90,6 @@ def deploy(update=False): @@ -91,17 +90,6 @@ def deploy(update=False):
91 sudo('supervisorctl restart all') 90 sudo('supervisorctl restart all')
92 91
93 92
94 -def load_badges(local=False):  
95 - path = '/vagrant/' if local else '~/colab/'  
96 -  
97 - run(u'mkdir -p {}www/media/badges'.format(path))  
98 -  
99 - with cd(u'{}src/'.format(path)), prefix(WORKON_COLAB):  
100 - run('cp badger/fixtures/images/*.png ../www/media/badges/')  
101 - run('python manage.py loaddata badger/fixtures/badges.json')  
102 - run('python manage.py update_badges')  
103 -  
104 -  
105 def rebuild_index(age=None, batch=None): 93 def rebuild_index(age=None, batch=None):
106 with cd('~/colab/src/'), prefix(WORKON_COLAB): 94 with cd('~/colab/src/'), prefix(WORKON_COLAB):
107 age_arg = '' 95 age_arg = ''
@@ -139,5 +127,4 @@ def runserver(update_requirements=False): @@ -139,5 +127,4 @@ def runserver(update_requirements=False):
139 127
140 run('python manage.py syncdb') 128 run('python manage.py syncdb')
141 run('python manage.py migrate') 129 run('python manage.py migrate')
142 - load_badges(local=True)  
143 run('python manage.py runserver 0.0.0.0:7000') 130 run('python manage.py runserver 0.0.0.0:7000')
puppet/modules/colab/templates/nginx/site_default.erb
@@ -40,6 +40,14 @@ server { @@ -40,6 +40,14 @@ server {
40 proxy_pass http://10.1.2.81; 40 proxy_pass http://10.1.2.81;
41 } 41 }
42 42
  43 + location /ci/static {
  44 + proxy_pass http://10.1.2.171:8080;
  45 + }
  46 +
  47 + location /ci/adjuncts {
  48 + proxy_pass http://10.1.2.171:8080;
  49 + }
  50 +
43 location / { 51 location / {
44 try_files /home/colab/colab/www$uri @django; 52 try_files /home/colab/colab/www$uri @django;
45 } 53 }
requirements.txt
@@ -5,7 +5,7 @@ django-piston==0.2.3 @@ -5,7 +5,7 @@ django-piston==0.2.3
5 pytz==2011n 5 pytz==2011n
6 chardet==1.0.1 6 chardet==1.0.1
7 python-dateutil==1.5 7 python-dateutil==1.5
8 -django-cliauth==0.9 8 +django-cliauth==0.9.1
9 django-mobile==0.3.0 9 django-mobile==0.3.0
10 django-haystack==2.1 10 django-haystack==2.1
11 pysolr==2.1 11 pysolr==2.1
@@ -28,10 +28,10 @@ tornado==3.1.1 @@ -28,10 +28,10 @@ tornado==3.1.1
28 # Deps for Single SignOn (SSO) 28 # Deps for Single SignOn (SSO)
29 git+https://github.com/mozilla/django-browserid 29 git+https://github.com/mozilla/django-browserid
30 30
31 -django-revproxy==0.2.5 31 +django-revproxy==0.2.7
32 32
33 # Converse.js (XMPP client) 33 # Converse.js (XMPP client)
34 -django-conversejs==0.2.8 34 +django-conversejs==0.2.9
35 git+https://github.com/TracyWebTech/SleekXMPP@fix-gevent 35 git+https://github.com/TracyWebTech/SleekXMPP@fix-gevent
36 36
37 # Feedzilla (planet) and deps 37 # Feedzilla (planet) and deps
src/accounts/errors.py 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +class XMPPChangePwdException(Exception):
  2 + """Error changing XMPP Account password"""
src/accounts/forms.py
@@ -4,6 +4,8 @@ from django import forms @@ -4,6 +4,8 @@ from django import forms
4 from django.contrib.auth import get_user_model 4 from django.contrib.auth import get_user_model
5 from django.utils.translation import ugettext_lazy as _ 5 from django.utils.translation import ugettext_lazy as _
6 6
  7 +from conversejs.models import XMPPAccount
  8 +
7 from super_archives.models import MailingList 9 from super_archives.models import MailingList
8 from .utils.validators import validate_social_account 10 from .utils.validators import validate_social_account
9 11
@@ -50,7 +52,7 @@ class UserUpdateForm(UserForm): @@ -50,7 +52,7 @@ class UserUpdateForm(UserForm):
50 52
51 class Meta: 53 class Meta:
52 model = User 54 model = User
53 - fields = ('username', 'first_name', 'last_name', 55 + fields = ('first_name', 'last_name',
54 'institution', 'role', 'twitter', 'facebook', 56 'institution', 'role', 'twitter', 'facebook',
55 'google_talk', 'webpage') 57 'google_talk', 'webpage')
56 58
@@ -64,3 +66,38 @@ class ListsForm(forms.Form): @@ -64,3 +66,38 @@ class ListsForm(forms.Form):
64 required=False, 66 required=False,
65 widget=forms.CheckboxSelectMultiple, 67 widget=forms.CheckboxSelectMultiple,
66 choices=LISTS_NAMES) 68 choices=LISTS_NAMES)
  69 +
  70 +
  71 +class ChangeXMPPPasswordForm(forms.ModelForm):
  72 + password1 = forms.CharField(label=_("Password"),
  73 + widget=forms.PasswordInput)
  74 + password2 = forms.CharField(label=_("Password confirmation"),
  75 + widget=forms.PasswordInput,
  76 + help_text=_("Enter the same password as above, for verification."))
  77 +
  78 + class Meta:
  79 + model = XMPPAccount
  80 + fields = ('password1', 'password2')
  81 +
  82 + def __init__(self, *args, **kwargs):
  83 + super(ChangeXMPPPasswordForm, self).__init__(*args, **kwargs)
  84 +
  85 + for field_name, field in self.fields.items():
  86 + # Adds form-control class to all form fields
  87 + field.widget.attrs.update({'class': 'form-control'})
  88 +
  89 + def clean_password2(self):
  90 + password1 = self.cleaned_data.get("password1")
  91 + password2 = self.cleaned_data.get("password2")
  92 + if password1 and password2 and password1 != password2:
  93 + raise forms.ValidationError(
  94 + _("Password mismatch"),
  95 + code='password_mismatch',
  96 + )
  97 + return password2
  98 +
  99 + def save(self, commit=True):
  100 + self.instance.password = self.cleaned_data['password2']
  101 + if commit:
  102 + self.instance.save()
  103 + return self.instance
src/accounts/models.py
  1 +# -*- coding: utf-8 -*-
1 2
2 import urlparse 3 import urlparse
3 4
4 -from django.db import models 5 +from django.db import models, DatabaseError
  6 +from django.contrib.auth.hashers import check_password
5 from django.contrib.auth.models import AbstractUser 7 from django.contrib.auth.models import AbstractUser
6 from django.core.urlresolvers import reverse 8 from django.core.urlresolvers import reverse
7 9
  10 +from conversejs import xmpp
  11 +
8 from .utils import mailman 12 from .utils import mailman
9 13
10 14
@@ -18,6 +22,13 @@ class User(AbstractUser): @@ -18,6 +22,13 @@ class User(AbstractUser):
18 verification_hash = models.CharField(max_length=32, null=True, blank=True) 22 verification_hash = models.CharField(max_length=32, null=True, blank=True)
19 modified = models.DateTimeField(auto_now=True) 23 modified = models.DateTimeField(auto_now=True)
20 24
  25 + def check_password(self, raw_password):
  26 +
  27 + if self.xmpp.exists() and raw_password == self.xmpp.first().password:
  28 + return True
  29 +
  30 + return super(User, self).check_password(raw_password)
  31 +
21 def get_absolute_url(self): 32 def get_absolute_url(self):
22 return reverse('user_profile', kwargs={'username': self.username}) 33 return reverse('user_profile', kwargs={'username': self.username})
23 34
src/accounts/templates/accounts/change_password.html 0 → 100644
@@ -0,0 +1,21 @@ @@ -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 %}
src/accounts/templates/accounts/user_detail.html
@@ -109,7 +109,7 @@ @@ -109,7 +109,7 @@
109 <div> 109 <div>
110 {% for badge in user_.badge_set.all %} 110 {% for badge in user_.badge_set.all %}
111 {% translate badge as badge_trans %} 111 {% translate badge as badge_trans %}
112 - <img src="{{ badge.get_badge_url }}" title="({{ badge_trans.title }}) {{ badge_trans.description }}" /> 112 + <img src="data:image/png;base64,{{ badge.image_base64 }}" title="({{ badge_trans.title }}) {{ badge_trans.description }}" />
113 {% endfor %} 113 {% endfor %}
114 </div> 114 </div>
115 </div> 115 </div>
src/accounts/templates/accounts/user_update_form.html
@@ -178,6 +178,21 @@ $(function() { @@ -178,6 +178,21 @@ $(function() {
178 </div> 178 </div>
179 </div> 179 </div>
180 </div> 180 </div>
  181 +
  182 + <div class="col-lg-4 col-md-5 col-sm-12 col-xm-12">
  183 + <div class="panel panel-default">
  184 + <div class="panel-heading">
  185 + <h3 class="panel-title">{% trans "Change SVN and XMPP Client password" %}</h3>
  186 + </div>
  187 + <div class="panel-body">
  188 + <div class="form-group">
  189 + {% trans "You don't need to change this password. Change it only if you really know what you are doing." %}
  190 + </div>
  191 + <a href="{% url 'change_password' %}" class="btn btn-primary pull-right">{% trans "Change Password" %}</a>
  192 + </div>
  193 + </div>
  194 + </div>
  195 +
181 </div> 196 </div>
182 <div class="row"> 197 <div class="row">
183 <div class="submit"> 198 <div class="submit">
src/accounts/urls.py
1 1
2 from django.conf.urls import patterns, include, url 2 from django.conf.urls import patterns, include, url
3 3
4 -from .views import UserProfileDetailView, UserProfileUpdateView, \  
5 - ManageUserSubscriptionsView 4 +from .views import (UserProfileDetailView, UserProfileUpdateView,
  5 + ManageUserSubscriptionsView, ChangeXMPPPasswordView)
6 6
7 7
8 urlpatterns = patterns('', 8 urlpatterns = patterns('',
9 url(r'^register/$', 'accounts.views.signup', name='signup'), 9 url(r'^register/$', 'accounts.views.signup', name='signup'),
10 10
  11 + url(r'^change-password/$',
  12 + ChangeXMPPPasswordView.as_view(), name='change_password'),
  13 +
  14 + url(r'^logout/?$', 'django.contrib.auth.views.logout'),
  15 +
11 url(r'^(?P<username>[\w@+.-]+)/?$', 16 url(r'^(?P<username>[\w@+.-]+)/?$',
12 UserProfileDetailView.as_view(), name='user_profile'), 17 UserProfileDetailView.as_view(), name='user_profile'),
13 18
src/accounts/views.py
@@ -6,20 +6,26 @@ import datetime @@ -6,20 +6,26 @@ import datetime
6 from collections import OrderedDict 6 from collections import OrderedDict
7 7
8 from django.contrib import messages 8 from django.contrib import messages
  9 +from django.db import transaction
9 from django.db.models import Count 10 from django.db.models import Count
10 from django.contrib.auth import get_user_model 11 from django.contrib.auth import get_user_model
11 from django.utils.translation import ugettext as _ 12 from django.utils.translation import ugettext as _
12 -from django.shortcuts import render, redirect 13 +from django.shortcuts import render, redirect, get_object_or_404
13 from django.core.urlresolvers import reverse 14 from django.core.urlresolvers import reverse
14 from django.core.exceptions import PermissionDenied 15 from django.core.exceptions import PermissionDenied
15 from django.views.generic import DetailView, UpdateView 16 from django.views.generic import DetailView, UpdateView
  17 +from django.utils.decorators import method_decorator
16 18
  19 +from conversejs import xmpp
  20 +from conversejs.models import XMPPAccount
17 from haystack.query import SearchQuerySet 21 from haystack.query import SearchQuerySet
18 22
19 from super_archives.models import EmailAddress, Message 23 from super_archives.models import EmailAddress, Message
20 from super_archives.utils.email import send_email_lists 24 from super_archives.utils.email import send_email_lists
21 from search.utils import trans 25 from search.utils import trans
22 -from .forms import UserCreationForm, ListsForm, UserUpdateForm 26 +from .forms import (UserCreationForm, ListsForm, UserUpdateForm,
  27 + ChangeXMPPPasswordForm)
  28 +from .errors import XMPPChangePwdException
23 from .utils import mailman 29 from .utils import mailman
24 30
25 31
@@ -129,6 +135,14 @@ class ManageUserSubscriptionsView(UserProfileBaseMixin, DetailView): @@ -129,6 +135,14 @@ class ManageUserSubscriptionsView(UserProfileBaseMixin, DetailView):
129 http_method_names = [u'get', u'post'] 135 http_method_names = [u'get', u'post']
130 template_name = u'accounts/manage_subscriptions.html' 136 template_name = u'accounts/manage_subscriptions.html'
131 137
  138 + def get_object(self, *args, **kwargs):
  139 + obj = super(ManageUserSubscriptionsView, self).get_object(*args,
  140 + **kwargs)
  141 + if self.request.user != obj and not self.request.user.is_superuser:
  142 + raise PermissionDenied
  143 +
  144 + return obj
  145 +
132 def post(self, request, *args, **kwargs): 146 def post(self, request, *args, **kwargs):
133 user = self.get_object() 147 user = self.get_object()
134 for email in user.emails.values_list('address', flat=True): 148 for email in user.emails.values_list('address', flat=True):
@@ -160,3 +174,47 @@ class ManageUserSubscriptionsView(UserProfileBaseMixin, DetailView): @@ -160,3 +174,47 @@ class ManageUserSubscriptionsView(UserProfileBaseMixin, DetailView):
160 context.update(kwargs) 174 context.update(kwargs)
161 175
162 return super(ManageUserSubscriptionsView, self).get_context_data(**context) 176 return super(ManageUserSubscriptionsView, self).get_context_data(**context)
  177 +
  178 +
  179 +class ChangeXMPPPasswordView(UpdateView):
  180 + model = XMPPAccount
  181 + form_class = ChangeXMPPPasswordForm
  182 + fields = ['password', ]
  183 + template_name = 'accounts/change_password.html'
  184 +
  185 + def get_success_url(self):
  186 + return reverse('user_profile', kwargs={
  187 + 'username': self.request.user.username
  188 + })
  189 +
  190 + def get_object(self, queryset=None):
  191 + obj = get_object_or_404(XMPPAccount, user=self.request.user.pk)
  192 + self.old_password = obj.password
  193 + return obj
  194 +
  195 + def form_valid(self, form):
  196 + transaction.set_autocommit(False)
  197 +
  198 + response = super(ChangeXMPPPasswordView, self).form_valid(form)
  199 +
  200 + changed = xmpp.change_password(
  201 + self.object.jid,
  202 + self.old_password,
  203 + form.cleaned_data['password1']
  204 + )
  205 +
  206 + if not changed:
  207 + messages.error(
  208 + self.request,
  209 + _(u'Could not change your password. Please, try again later.')
  210 + )
  211 + transaction.rollback()
  212 + return response
  213 + else:
  214 + transaction.commit()
  215 +
  216 + messages.success(
  217 + self.request,
  218 + _("You've changed your password successfully!")
  219 + )
  220 + return response
src/badger/admin.py
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 from django.contrib import admin 3 from django.contrib import admin
4 from django.utils.translation import ugettext_lazy as _ 4 from django.utils.translation import ugettext_lazy as _
5 5
  6 +from .forms import BadgeForm
6 from .models import Badge, BadgeI18N 7 from .models import Badge, BadgeI18N
7 8
8 9
@@ -11,6 +12,7 @@ class BadgeI18NInline(admin.TabularInline): @@ -11,6 +12,7 @@ class BadgeI18NInline(admin.TabularInline):
11 12
12 13
13 class BadgeAdmin(admin.ModelAdmin): 14 class BadgeAdmin(admin.ModelAdmin):
  15 + form = BadgeForm
14 inlines = [BadgeI18NInline, ] 16 inlines = [BadgeI18NInline, ]
15 list_display = ['title', 'description', 'order'] 17 list_display = ['title', 'description', 'order']
16 list_editable = ['order', ] 18 list_editable = ['order', ]
src/badger/fixtures/badges.json
@@ -1,502 +0,0 @@ @@ -1,502 +0,0 @@
1 -[  
2 -{  
3 - "pk": 6,  
4 - "model": "badger.badge",  
5 - "fields": {  
6 - "awardees": [],  
7 - "comparison": "gte",  
8 - "description": "More than 10000 Messages",  
9 - "title": "Diamond Messenger",  
10 - "image": "badges/diamond_messenger.png",  
11 - "value": 10000,  
12 - "user_attr": "messages",  
13 - "type": "auto",  
14 - "order": 4  
15 - }  
16 -},  
17 -{  
18 - "pk": 8,  
19 - "model": "badger.badge",  
20 - "fields": {  
21 - "awardees": [],  
22 - "comparison": "gte",  
23 - "description": "More than 100 messages",  
24 - "title": "Brass Messenger",  
25 - "image": "badges/brass_messenger.png",  
26 - "value": 100,  
27 - "user_attr": "messages",  
28 - "type": "auto",  
29 - "order": 1  
30 - }  
31 -},  
32 -{  
33 - "pk": 9,  
34 - "model": "badger.badge",  
35 - "fields": {  
36 - "awardees": [],  
37 - "comparison": "gte",  
38 - "description": "More than 500 Messages",  
39 - "title": "Silver Messenger",  
40 - "image": "badges/silver_messenger.png",  
41 - "value": 500,  
42 - "user_attr": "messages",  
43 - "type": "auto",  
44 - "order": 2  
45 - }  
46 -},  
47 -{  
48 - "pk": 10,  
49 - "model": "badger.badge",  
50 - "fields": {  
51 - "awardees": [],  
52 - "comparison": "gte",  
53 - "description": "More than 1000 Messages",  
54 - "title": "Gold Messenger",  
55 - "image": "badges/gold_messenger.png",  
56 - "value": 1000,  
57 - "user_attr": "messages",  
58 - "type": "auto",  
59 - "order": 3  
60 - }  
61 -},  
62 -{  
63 - "pk": 11,  
64 - "model": "badger.badge",  
65 - "fields": {  
66 - "awardees": [],  
67 - "comparison": "gte",  
68 - "description": "More than 10 Tickets",  
69 - "title": "Brass Tracker",  
70 - "image": "badges/brass_tracker.png",  
71 - "value": 10,  
72 - "user_attr": "tickets",  
73 - "type": "auto",  
74 - "order": 5  
75 - }  
76 -},  
77 -{  
78 - "pk": 12,  
79 - "model": "badger.badge",  
80 - "fields": {  
81 - "awardees": [],  
82 - "comparison": "gte",  
83 - "description": "More than 50 Tickets",  
84 - "title": "Silver Tracker",  
85 - "image": "badges/silver_tracker.png",  
86 - "value": 50,  
87 - "user_attr": "tickets",  
88 - "type": "auto",  
89 - "order": 6  
90 - }  
91 -},  
92 -{  
93 - "pk": 13,  
94 - "model": "badger.badge",  
95 - "fields": {  
96 - "awardees": [],  
97 - "comparison": "gte",  
98 - "description": "Mais que 100 Tickets",  
99 - "title": "Gold Tracker",  
100 - "image": "badges/gold_tracker.png",  
101 - "value": 100,  
102 - "user_attr": "tickets",  
103 - "type": "auto",  
104 - "order": 7  
105 - }  
106 -},  
107 -{  
108 - "pk": 14,  
109 - "model": "badger.badge",  
110 - "fields": {  
111 - "awardees": [],  
112 - "comparison": "gte",  
113 - "description": "Mais que 500 Tickets",  
114 - "title": "Diamond Tracker",  
115 - "image": "badges/diamond_tracker.png",  
116 - "value": 500,  
117 - "user_attr": "tickets",  
118 - "type": "auto",  
119 - "order": 8  
120 - }  
121 -},  
122 -{  
123 - "pk": 15,  
124 - "model": "badger.badge",  
125 - "fields": {  
126 - "awardees": [],  
127 - "comparison": "gte",  
128 - "description": "More than 100 Changesets",  
129 - "title": "Brass Coder",  
130 - "image": "badges/brass_coder.png",  
131 - "value": 100,  
132 - "user_attr": "revisions",  
133 - "type": "auto",  
134 - "order": 9  
135 - }  
136 -},  
137 -{  
138 - "pk": 16,  
139 - "model": "badger.badge",  
140 - "fields": {  
141 - "awardees": [],  
142 - "comparison": "gte",  
143 - "description": "More than 500 Changesets",  
144 - "title": "Silver Coder",  
145 - "image": "badges/silver_coder.png",  
146 - "value": 500,  
147 - "user_attr": "revisions",  
148 - "type": "auto",  
149 - "order": 10  
150 - }  
151 -},  
152 -{  
153 - "pk": 17,  
154 - "model": "badger.badge",  
155 - "fields": {  
156 - "awardees": [],  
157 - "comparison": "gte",  
158 - "description": "More than 1000 Changesets",  
159 - "title": "Gold Coder",  
160 - "image": "badges/gold_coder.png",  
161 - "value": 1000,  
162 - "user_attr": "revisions",  
163 - "type": "auto",  
164 - "order": 11  
165 - }  
166 -},  
167 -{  
168 - "pk": 18,  
169 - "model": "badger.badge",  
170 - "fields": {  
171 - "awardees": [],  
172 - "comparison": "gte",  
173 - "description": "More than 10000 Changesets",  
174 - "title": "Diamond Coder",  
175 - "image": "badges/diamond_coder.png",  
176 - "value": 10000,  
177 - "user_attr": "revisions",  
178 - "type": "auto",  
179 - "order": 12  
180 - }  
181 -},  
182 -{  
183 - "pk": 19,  
184 - "model": "badger.badge",  
185 - "fields": {  
186 - "awardees": [],  
187 - "comparison": "gte",  
188 - "description": "More than 10 Wikis",  
189 - "title": "Brass Writer",  
190 - "image": "badges/brass_writer.png",  
191 - "value": 10,  
192 - "user_attr": "wikis",  
193 - "type": "auto",  
194 - "order": 13  
195 - }  
196 -},  
197 -{  
198 - "pk": 20,  
199 - "model": "badger.badge",  
200 - "fields": {  
201 - "awardees": [],  
202 - "comparison": "gte",  
203 - "description": "More than 50 Wikis",  
204 - "title": "Silver Writer",  
205 - "image": "badges/silver_writer.png",  
206 - "value": 50,  
207 - "user_attr": "wikis",  
208 - "type": "auto",  
209 - "order": 14  
210 - }  
211 -},  
212 -{  
213 - "pk": 21,  
214 - "model": "badger.badge",  
215 - "fields": {  
216 - "awardees": [],  
217 - "comparison": "gte",  
218 - "description": "More than 100 Wikis",  
219 - "title": "Gold Writer",  
220 - "image": "badges/gold_writer.png",  
221 - "value": 100,  
222 - "user_attr": "wikis",  
223 - "type": "auto",  
224 - "order": 15  
225 - }  
226 -},  
227 -{  
228 - "pk": 22,  
229 - "model": "badger.badge",  
230 - "fields": {  
231 - "awardees": [],  
232 - "comparison": "gte",  
233 - "description": "More than 500 Wikis",  
234 - "title": "Diamond Writer",  
235 - "image": "badges/diamond_writer.png",  
236 - "value": 500,  
237 - "user_attr": "wikis",  
238 - "type": "auto",  
239 - "order": 16  
240 - }  
241 -},  
242 -{  
243 - "pk": 23,  
244 - "model": "badger.badge",  
245 - "fields": {  
246 - "awardees": [],  
247 - "comparison": "gte",  
248 - "description": "More than 500 Contributions",  
249 - "title": "Brass Contributor",  
250 - "image": "badges/brass_contributor.png",  
251 - "value": 500,  
252 - "user_attr": "contributions",  
253 - "type": "auto",  
254 - "order": 17  
255 - }  
256 -},  
257 -{  
258 - "pk": 24,  
259 - "model": "badger.badge",  
260 - "fields": {  
261 - "awardees": [],  
262 - "comparison": "gte",  
263 - "description": "More than 1000 Contributions",  
264 - "title": "Silver Contributor",  
265 - "image": "badges/silver_contributor.png",  
266 - "value": 1000,  
267 - "user_attr": "contributions",  
268 - "type": "auto",  
269 - "order": 18  
270 - }  
271 -},  
272 -{  
273 - "pk": 25,  
274 - "model": "badger.badge",  
275 - "fields": {  
276 - "awardees": [],  
277 - "comparison": "gte",  
278 - "description": "More than 3000 Contributions",  
279 - "title": "Gold Contributor",  
280 - "image": "badges/gold_contributor.png",  
281 - "value": 3000,  
282 - "user_attr": "contributions",  
283 - "type": "auto",  
284 - "order": 19  
285 - }  
286 -},  
287 -{  
288 - "pk": 26,  
289 - "model": "badger.badge",  
290 - "fields": {  
291 - "awardees": [],  
292 - "comparison": "gte",  
293 - "description": "More than 5000 Contributions",  
294 - "title": "Diamond Contributor",  
295 - "image": "badges/diamond_contributor.png",  
296 - "value": 5000,  
297 - "user_attr": "contributions",  
298 - "type": "auto",  
299 - "order": 20  
300 - }  
301 -},  
302 -{  
303 - "pk": 1,  
304 - "model": "badger.badgei18n",  
305 - "fields": {  
306 - "i18n_language": "pt-br",  
307 - "i18n_source": 8,  
308 - "description": "Mais que 100 Mensagens",  
309 - "title": "Mensageiro Bronze"  
310 - }  
311 -},  
312 -{  
313 - "pk": 2,  
314 - "model": "badger.badgei18n",  
315 - "fields": {  
316 - "i18n_language": "pt-br",  
317 - "i18n_source": 9,  
318 - "description": "Mais que 500 Mensagens",  
319 - "title": "Mensageiro Prata"  
320 - }  
321 -},  
322 -{  
323 - "pk": 3,  
324 - "model": "badger.badgei18n",  
325 - "fields": {  
326 - "i18n_language": "pt-br",  
327 - "i18n_source": 6,  
328 - "description": "Mais que 10000 Mensagens",  
329 - "title": "Mensageiro Diamante"  
330 - }  
331 -},  
332 -{  
333 - "pk": 4,  
334 - "model": "badger.badgei18n",  
335 - "fields": {  
336 - "i18n_language": "pt-br",  
337 - "i18n_source": 10,  
338 - "description": "Mais que 1000 Mensagens",  
339 - "title": "Mensageiro Ouro"  
340 - }  
341 -},  
342 -{  
343 - "pk": 5,  
344 - "model": "badger.badgei18n",  
345 - "fields": {  
346 - "i18n_language": "pt-br",  
347 - "i18n_source": 11,  
348 - "description": "Mais que 10 T\u00edquetes",  
349 - "title": "Rastreador Bronze"  
350 - }  
351 -},  
352 -{  
353 - "pk": 6,  
354 - "model": "badger.badgei18n",  
355 - "fields": {  
356 - "i18n_language": "pt-br",  
357 - "i18n_source": 12,  
358 - "description": "Mais que 50 T\u00edquetes",  
359 - "title": "Rastreador Prata"  
360 - }  
361 -},  
362 -{  
363 - "pk": 7,  
364 - "model": "badger.badgei18n",  
365 - "fields": {  
366 - "i18n_language": "pt-br",  
367 - "i18n_source": 13,  
368 - "description": "Mais que 100 T\u00edquetes",  
369 - "title": "Rastreador Ouro"  
370 - }  
371 -},  
372 -{  
373 - "pk": 8,  
374 - "model": "badger.badgei18n",  
375 - "fields": {  
376 - "i18n_language": "pt-br",  
377 - "i18n_source": 14,  
378 - "description": "Mais que 500 T\u00edquetes",  
379 - "title": "Rastreador Diamante"  
380 - }  
381 -},  
382 -{  
383 - "pk": 9,  
384 - "model": "badger.badgei18n",  
385 - "fields": {  
386 - "i18n_language": "pt-br",  
387 - "i18n_source": 15,  
388 - "description": "Mais que 100 Modifica\u00e7\u00f5es de c\u00f3digo",  
389 - "title": "Programador Bronze"  
390 - }  
391 -},  
392 -{  
393 - "pk": 10,  
394 - "model": "badger.badgei18n",  
395 - "fields": {  
396 - "i18n_language": "pt-br",  
397 - "i18n_source": 16,  
398 - "description": "Mais que 500 Modifica\u00e7\u00f5es de c\u00f3digo",  
399 - "title": "Programador Prata"  
400 - }  
401 -},  
402 -{  
403 - "pk": 11,  
404 - "model": "badger.badgei18n",  
405 - "fields": {  
406 - "i18n_language": "pt-br",  
407 - "i18n_source": 17,  
408 - "description": "Mais que 1000 Modifica\u00e7\u00f5es de c\u00f3digo",  
409 - "title": "Programador Ouro"  
410 - }  
411 -},  
412 -{  
413 - "pk": 12,  
414 - "model": "badger.badgei18n",  
415 - "fields": {  
416 - "i18n_language": "pt-br",  
417 - "i18n_source": 18,  
418 - "description": "Mais que 10000 Modifica\u00e7\u00f5es de c\u00f3digo",  
419 - "title": "Programador Diamante"  
420 - }  
421 -},  
422 -{  
423 - "pk": 13,  
424 - "model": "badger.badgei18n",  
425 - "fields": {  
426 - "i18n_language": "pt-br",  
427 - "i18n_source": 19,  
428 - "description": "Escreveu mais que 10 Wikis",  
429 - "title": "Escritor Bronze"  
430 - }  
431 -},  
432 -{  
433 - "pk": 14,  
434 - "model": "badger.badgei18n",  
435 - "fields": {  
436 - "i18n_language": "pt-br",  
437 - "i18n_source": 20,  
438 - "description": "Escreveu mais que 50 Wikis",  
439 - "title": "Escritor Prata"  
440 - }  
441 -},  
442 -{  
443 - "pk": 15,  
444 - "model": "badger.badgei18n",  
445 - "fields": {  
446 - "i18n_language": "pt-br",  
447 - "i18n_source": 21,  
448 - "description": "Escreveu mais que 100 Wikis",  
449 - "title": "Escritor Ouro"  
450 - }  
451 -},  
452 -{  
453 - "pk": 16,  
454 - "model": "badger.badgei18n",  
455 - "fields": {  
456 - "i18n_language": "pt-br",  
457 - "i18n_source": 22,  
458 - "description": "Escreveu mais que 500 Wikis",  
459 - "title": "Escritor Diamante"  
460 - }  
461 -},  
462 -{  
463 - "pk": 17,  
464 - "model": "badger.badgei18n",  
465 - "fields": {  
466 - "i18n_language": "pt-br",  
467 - "i18n_source": 23,  
468 - "description": "Mais que 500 Contribui\u00e7\u00f5es",  
469 - "title": "Contribuidor Bronze"  
470 - }  
471 -},  
472 -{  
473 - "pk": 18,  
474 - "model": "badger.badgei18n",  
475 - "fields": {  
476 - "i18n_language": "pt-br",  
477 - "i18n_source": 24,  
478 - "description": "Mais que 1000 Contribui\u00e7\u00f5es",  
479 - "title": "Contribuidor Prata"  
480 - }  
481 -},  
482 -{  
483 - "pk": 19,  
484 - "model": "badger.badgei18n",  
485 - "fields": {  
486 - "i18n_language": "pt-br",  
487 - "i18n_source": 25,  
488 - "description": "Mais que 3000 Contribui\u00e7\u00f5es",  
489 - "title": "Contribuidor Ouro"  
490 - }  
491 -},  
492 -{  
493 - "pk": 20,  
494 - "model": "badger.badgei18n",  
495 - "fields": {  
496 - "i18n_language": "pt-br",  
497 - "i18n_source": 26,  
498 - "description": "Mais que 5000 Contribui\u00e7\u00f5es",  
499 - "title": "Contribuidor Diamante"  
500 - }  
501 -}  
502 -]  
src/badger/fixtures/images/brass_coder.png

3.33 KB

src/badger/fixtures/images/brass_contributor.png

3.65 KB

src/badger/fixtures/images/brass_messenger.png

3.2 KB

src/badger/fixtures/images/brass_tracker.png

3.48 KB

src/badger/fixtures/images/brass_writer.png

3.51 KB

src/badger/fixtures/images/diamond_coder.png

3.04 KB

src/badger/fixtures/images/diamond_contributor.png

3.16 KB

src/badger/fixtures/images/diamond_messenger.png

2.81 KB

src/badger/fixtures/images/diamond_tracker.png

3.02 KB

src/badger/fixtures/images/diamond_writer.png

3.05 KB

src/badger/fixtures/images/gold_coder.png

2.77 KB

src/badger/fixtures/images/gold_contributor.png

2.87 KB

src/badger/fixtures/images/gold_messenger.png

2.56 KB

src/badger/fixtures/images/gold_tracker.png

2.81 KB

src/badger/fixtures/images/gold_writer.png

2.81 KB

src/badger/fixtures/images/silver_coder.png

2.75 KB

src/badger/fixtures/images/silver_contributor.png

2.71 KB

src/badger/fixtures/images/silver_messenger.png

2.47 KB

src/badger/fixtures/images/silver_tracker.png

2.62 KB

src/badger/fixtures/images/silver_writer.png

2.66 KB

src/badger/forms.py 0 → 100644
@@ -0,0 +1,48 @@ @@ -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
src/badger/migrations/0005_auto__add_field_badge_image_base64.py 0 → 100644
@@ -0,0 +1,91 @@ @@ -0,0 +1,91 @@
  1 +# -*- coding: utf-8 -*-
  2 +import datetime
  3 +from south.db import db
  4 +from south.v2 import SchemaMigration
  5 +from django.db import models
  6 +
  7 +
  8 +class Migration(SchemaMigration):
  9 +
  10 + def forwards(self, orm):
  11 + # Adding field 'Badge.image_base64'
  12 + db.add_column(u'badger_badge', 'image_base64',
  13 + self.gf('django.db.models.fields.TextField')(default=''),
  14 + keep_default=False)
  15 +
  16 +
  17 + def backwards(self, orm):
  18 + # Deleting field 'Badge.image_base64'
  19 + db.delete_column(u'badger_badge', 'image_base64')
  20 +
  21 +
  22 + models = {
  23 + u'accounts.user': {
  24 + 'Meta': {'object_name': 'User'},
  25 + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
  26 + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
  27 + 'facebook': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
  28 + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
  29 + 'google_talk': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
  30 + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
  31 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  32 + 'institution': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
  33 + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
  34 + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
  35 + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
  36 + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
  37 + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
  38 + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
  39 + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
  40 + 'role': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
  41 + 'twitter': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
  42 + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
  43 + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
  44 + 'verification_hash': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
  45 + 'webpage': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'})
  46 + },
  47 + u'auth.group': {
  48 + 'Meta': {'object_name': 'Group'},
  49 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  50 + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
  51 + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
  52 + },
  53 + u'auth.permission': {
  54 + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
  55 + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
  56 + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
  57 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  58 + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
  59 + },
  60 + u'badger.badge': {
  61 + 'Meta': {'ordering': "['order']", 'object_name': 'Badge'},
  62 + 'awardees': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['accounts.User']", 'null': 'True', 'blank': 'True'}),
  63 + 'comparison': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
  64 + 'description': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
  65 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  66 + 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
  67 + 'image_base64': ('django.db.models.fields.TextField', [], {}),
  68 + 'order': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '100'}),
  69 + 'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
  70 + 'type': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
  71 + 'user_attr': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
  72 + 'value': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'})
  73 + },
  74 + u'badger.badgei18n': {
  75 + 'Meta': {'unique_together': "(('i18n_source', 'i18n_language'),)", 'object_name': 'BadgeI18N'},
  76 + 'description': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
  77 + 'i18n_language': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
  78 + 'i18n_source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'to': u"orm['badger.Badge']"}),
  79 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  80 + 'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
  81 + },
  82 + u'contenttypes.contenttype': {
  83 + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
  84 + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
  85 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  86 + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
  87 + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
  88 + }
  89 + }
  90 +
  91 + complete_apps = ['badger']
0 \ No newline at end of file 92 \ No newline at end of file
src/badger/migrations/0006_imgpath_to_base64_field.py 0 → 100644
@@ -0,0 +1,94 @@ @@ -0,0 +1,94 @@
  1 +# -*- coding: utf-8 -*-
  2 +import base64
  3 +import os
  4 +
  5 +import datetime
  6 +from south.db import db
  7 +from south.v2 import DataMigration
  8 +from django.db import models
  9 +from django.conf import settings
  10 +
  11 +class Migration(DataMigration):
  12 +
  13 + def forwards(self, orm):
  14 + for obj in orm.Badge.objects.all():
  15 + img = open(os.path.join(settings.MEDIA_ROOT, obj.image.path))
  16 + obj.image_base64 = base64.b64encode(img.read())
  17 + obj.save()
  18 +
  19 + def backwards(self, orm):
  20 + for obj in orm.Badge.objects.all():
  21 + obj.image_base64 = ''
  22 + obj.save()
  23 +
  24 + models = {
  25 + u'accounts.user': {
  26 + 'Meta': {'object_name': 'User'},
  27 + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
  28 + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
  29 + 'facebook': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
  30 + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
  31 + 'google_talk': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
  32 + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
  33 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  34 + 'institution': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
  35 + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
  36 + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
  37 + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
  38 + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
  39 + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
  40 + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
  41 + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
  42 + 'role': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
  43 + 'twitter': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
  44 + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
  45 + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
  46 + 'verification_hash': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
  47 + 'webpage': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'})
  48 + },
  49 + u'auth.group': {
  50 + 'Meta': {'object_name': 'Group'},
  51 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  52 + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
  53 + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
  54 + },
  55 + u'auth.permission': {
  56 + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
  57 + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
  58 + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
  59 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  60 + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
  61 + },
  62 + u'badger.badge': {
  63 + 'Meta': {'ordering': "['order']", 'object_name': 'Badge'},
  64 + 'awardees': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['accounts.User']", 'null': 'True', 'blank': 'True'}),
  65 + 'comparison': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
  66 + 'description': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
  67 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  68 + 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
  69 + 'image_base64': ('django.db.models.fields.TextField', [], {}),
  70 + 'order': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '100'}),
  71 + 'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
  72 + 'type': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
  73 + 'user_attr': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
  74 + 'value': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'})
  75 + },
  76 + u'badger.badgei18n': {
  77 + 'Meta': {'unique_together': "(('i18n_source', 'i18n_language'),)", 'object_name': 'BadgeI18N'},
  78 + 'description': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
  79 + 'i18n_language': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
  80 + 'i18n_source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'to': u"orm['badger.Badge']"}),
  81 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  82 + 'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
  83 + },
  84 + u'contenttypes.contenttype': {
  85 + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
  86 + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
  87 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  88 + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
  89 + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
  90 + }
  91 + }
  92 +
  93 + complete_apps = ['badger']
  94 + symmetrical = True
src/badger/migrations/0007_auto__del_field_badge_image.py 0 → 100644
@@ -0,0 +1,90 @@ @@ -0,0 +1,90 @@
  1 +# -*- coding: utf-8 -*-
  2 +import datetime
  3 +from south.db import db
  4 +from south.v2 import SchemaMigration
  5 +from django.db import models
  6 +
  7 +
  8 +class Migration(SchemaMigration):
  9 +
  10 + def forwards(self, orm):
  11 + # Deleting field 'Badge.image'
  12 + db.delete_column(u'badger_badge', 'image')
  13 +
  14 +
  15 + def backwards(self, orm):
  16 + # Adding field 'Badge.image'
  17 + db.add_column(u'badger_badge', 'image',
  18 + self.gf('django.db.models.fields.files.ImageField')(default='', max_length=100),
  19 + keep_default=False)
  20 +
  21 +
  22 + models = {
  23 + u'accounts.user': {
  24 + 'Meta': {'object_name': 'User'},
  25 + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
  26 + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
  27 + 'facebook': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
  28 + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
  29 + 'google_talk': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
  30 + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
  31 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  32 + 'institution': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
  33 + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
  34 + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
  35 + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
  36 + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
  37 + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
  38 + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
  39 + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
  40 + 'role': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
  41 + 'twitter': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
  42 + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
  43 + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
  44 + 'verification_hash': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
  45 + 'webpage': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'})
  46 + },
  47 + u'auth.group': {
  48 + 'Meta': {'object_name': 'Group'},
  49 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  50 + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
  51 + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
  52 + },
  53 + u'auth.permission': {
  54 + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
  55 + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
  56 + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
  57 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  58 + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
  59 + },
  60 + u'badger.badge': {
  61 + 'Meta': {'ordering': "['order']", 'object_name': 'Badge'},
  62 + 'awardees': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['accounts.User']", 'null': 'True', 'blank': 'True'}),
  63 + 'comparison': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
  64 + 'description': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
  65 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  66 + 'image_base64': ('django.db.models.fields.TextField', [], {}),
  67 + 'order': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '100'}),
  68 + 'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
  69 + 'type': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
  70 + 'user_attr': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
  71 + 'value': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'})
  72 + },
  73 + u'badger.badgei18n': {
  74 + 'Meta': {'unique_together': "(('i18n_source', 'i18n_language'),)", 'object_name': 'BadgeI18N'},
  75 + 'description': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
  76 + 'i18n_language': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
  77 + 'i18n_source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'to': u"orm['badger.Badge']"}),
  78 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  79 + 'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
  80 + },
  81 + u'contenttypes.contenttype': {
  82 + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
  83 + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
  84 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  85 + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
  86 + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
  87 + }
  88 + }
  89 +
  90 + complete_apps = ['badger']
0 \ No newline at end of file 91 \ No newline at end of file
src/badger/models.py
@@ -4,7 +4,6 @@ from django.conf import settings @@ -4,7 +4,6 @@ from django.conf import settings
4 from django.contrib.auth import get_user_model 4 from django.contrib.auth import get_user_model
5 from django.db import models 5 from django.db import models
6 from django.utils.translation import ugettext_lazy as _ 6 from django.utils.translation import ugettext_lazy as _
7 -from PIL import Image  
8 from i18n_model.models import I18nModel 7 from i18n_model.models import I18nModel
9 8
10 9
@@ -38,7 +37,7 @@ class Badge(models.Model): @@ -38,7 +37,7 @@ class Badge(models.Model):
38 null=True) 37 null=True)
39 description = models.CharField(_(u'Description'), max_length=200, 38 description = models.CharField(_(u'Description'), max_length=200,
40 blank=True, null=True) 39 blank=True, null=True)
41 - image = models.ImageField(upload_to='badges') 40 + image_base64 = models.TextField(_(u'Image'))
42 type = models.CharField(_(u'Type'), max_length=200, choices=TYPE_CHOICES) 41 type = models.CharField(_(u'Type'), max_length=200, choices=TYPE_CHOICES)
43 user_attr = models.CharField( 42 user_attr = models.CharField(
44 _(u'User attribute'),max_length=100, 43 _(u'User attribute'),max_length=100,
@@ -71,16 +70,6 @@ class Badge(models.Model): @@ -71,16 +70,6 @@ class Badge(models.Model):
71 verbose_name_plural = _(u'Badges') 70 verbose_name_plural = _(u'Badges')
72 ordering = ['order', ] 71 ordering = ['order', ]
73 72
74 - def get_badge_url(self):  
75 - return u'{}{}'.format(settings.MEDIA_URL, self.image)  
76 -  
77 - def save(self, *args, **kwargs):  
78 - img = Image.open(self.image)  
79 - (width, height) = img.size  
80 - img = img.resize((50, 50), Image.ANTIALIAS)  
81 - super(Badge, self).save(*args, **kwargs)  
82 - img.save(self.image.path)  
83 -  
84 def __unicode__(self): 73 def __unicode__(self):
85 return u'{} ({}, {})'.format( 74 return u'{} ({}, {})'.format(
86 self.title, 75 self.title,
src/colab/custom_settings.py
@@ -183,10 +183,12 @@ LOGGING = { @@ -183,10 +183,12 @@ LOGGING = {
183 'django_browserid': { 183 'django_browserid': {
184 'handlers': ['sentry'], 184 'handlers': ['sentry'],
185 'level': 'WARNING', 185 'level': 'WARNING',
  186 + 'propagate': False,
186 }, 187 },
187 'conversejs': { 188 'conversejs': {
188 'handlers': ['console'], 189 'handlers': ['console'],
189 'level': 'DEBUG', 190 'level': 'DEBUG',
  191 + 'propagate': False,
190 }, 192 },
191 } 193 }
192 } 194 }
src/locale/pt_BR/LC_MESSAGES/django.mo
No preview for this file type
src/locale/pt_BR/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid &quot;&quot; @@ -7,7 +7,7 @@ msgid &quot;&quot;
7 msgstr "" 7 msgstr ""
8 "Project-Id-Version: PACKAGE VERSION\n" 8 "Project-Id-Version: PACKAGE VERSION\n"
9 "Report-Msgid-Bugs-To: \n" 9 "Report-Msgid-Bugs-To: \n"
10 -"POT-Creation-Date: 2013-11-21 10:48+0000\n" 10 +"POT-Creation-Date: 2013-11-26 13:17+0000\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" 12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n" 13 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -29,19 +29,35 @@ msgstr &quot;Permissões&quot; @@ -29,19 +29,35 @@ msgstr &quot;Permissões&quot;
29 msgid "Important dates" 29 msgid "Important dates"
30 msgstr "Datas importantes" 30 msgstr "Datas importantes"
31 31
32 -#: accounts/forms.py:22 32 +#: accounts/forms.py:24
33 msgid "Social account does not exist" 33 msgid "Social account does not exist"
34 msgstr "Conta social não existe" 34 msgstr "Conta social não existe"
35 35
36 -#: accounts/forms.py:63 36 +#: accounts/forms.py:65
37 msgid "Mailing lists" 37 msgid "Mailing lists"
38 msgstr "Listas de e-mail" 38 msgstr "Listas de e-mail"
39 39
40 -#: accounts/views.py:121 40 +#: accounts/forms.py:72
  41 +msgid "Password"
  42 +msgstr "Senha"
  43 +
  44 +#: accounts/forms.py:74
  45 +msgid "Password confirmation"
  46 +msgstr "Confirmação de senha"
  47 +
  48 +#: accounts/forms.py:76
  49 +msgid "Enter the same password as above, for verification."
  50 +msgstr "Digite a mesma senha que acima, para verificação."
  51 +
  52 +#: accounts/forms.py:94
  53 +msgid "Password mismatch"
  54 +msgstr "Senhas diferentes"
  55 +
  56 +#: accounts/views.py:127
41 msgid "Your profile has been created!" 57 msgid "Your profile has been created!"
42 msgstr "Seu perfil foi criado!" 58 msgstr "Seu perfil foi criado!"
43 59
44 -#: accounts/views.py:122 60 +#: accounts/views.py:128
45 msgid "" 61 msgid ""
46 "You must login to validated your profile. Profiles not validated are deleted " 62 "You must login to validated your profile. Profiles not validated are deleted "
47 "in 24h." 63 "in 24h."
@@ -49,11 +65,29 @@ msgstr &quot;&quot; @@ -49,11 +65,29 @@ msgstr &quot;&quot;
49 "Você deve se logar para validar seu perfil. Perfis não validados serão " 65 "Você deve se logar para validar seu perfil. Perfis não validados serão "
50 "deletados em 24h." 66 "deletados em 24h."
51 67
  68 +#: accounts/views.py:209
  69 +msgid "Could not change your password. Please, try again later."
  70 +msgstr ""
  71 +"Não conseguimos alterar sua senha. Por favor, tente novamente mais tarde."
  72 +
  73 +#: accounts/views.py:218
  74 +msgid "You've changed your password successfully!"
  75 +msgstr "Senha alterada com sucesso!"
  76 +
52 #: accounts/management/commands/delete_invalid.py:42 77 #: accounts/management/commands/delete_invalid.py:42
53 #, python-format 78 #, python-format
54 msgid "%(count)s users deleted." 79 msgid "%(count)s users deleted."
55 msgstr "%(count)s usuários deletados." 80 msgstr "%(count)s usuários deletados."
56 81
  82 +#: accounts/templates/accounts/change_password.html:8
  83 +msgid "Change XMPP Client and SVN Password"
  84 +msgstr "Trocar senha do SVN e do Mensageiro"
  85 +
  86 +#: accounts/templates/accounts/change_password.html:17
  87 +#: accounts/templates/accounts/user_update_form.html:191
  88 +msgid "Change Password"
  89 +msgstr "Trocar senha"
  90 +
57 #: accounts/templates/accounts/manage_subscriptions.html:6 91 #: accounts/templates/accounts/manage_subscriptions.html:6
58 msgid "Mailing List Subscriptions" 92 msgid "Mailing List Subscriptions"
59 msgstr "Inscrições em listas de e-mails" 93 msgstr "Inscrições em listas de e-mails"
@@ -195,7 +229,19 @@ msgstr &quot;Adicionar outro endereço de e-mail&quot; @@ -195,7 +229,19 @@ msgstr &quot;Adicionar outro endereço de e-mail&quot;
195 msgid "Add" 229 msgid "Add"
196 msgstr "Adicionar" 230 msgstr "Adicionar"
197 231
198 -#: accounts/templates/accounts/user_update_form.html:184 232 +#: accounts/templates/accounts/user_update_form.html:185
  233 +msgid "Change SVN and XMPP Client password"
  234 +msgstr "Trocar a senha do SVN e do Mensageiro"
  235 +
  236 +#: accounts/templates/accounts/user_update_form.html:189
  237 +msgid ""
  238 +"You don't need to change this password. Change it only if you really know "
  239 +"what you are doing."
  240 +msgstr ""
  241 +"Você não precisa trocar essa senha. Troque-a somente se tem certeza do que "
  242 +"está fazendo."
  243 +
  244 +#: accounts/templates/accounts/user_update_form.html:199
199 msgid "Update" 245 msgid "Update"
200 msgstr "Atualizar" 246 msgstr "Atualizar"
201 247
src/proxy/diazo/jenkins.xml 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  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 + <after theme-children="/html/head">
  13 + <script>jQuery.noConflict();</script>
  14 + <style>
  15 + #breadcrumbs {
  16 + border: 0 !important;
  17 + }
  18 +
  19 + #right-top-nav {
  20 + margin-right: 5em !important;
  21 + }
  22 + </style>
  23 + </after>
  24 +
  25 +</rules>
src/proxy/migrations/0004_replace_wiki_view.py 0 → 100644
@@ -0,0 +1,102 @@ @@ -0,0 +1,102 @@
  1 +# -*- coding: utf-8 -*-
  2 +import datetime
  3 +from django.db import connections
  4 +from south.db import db
  5 +from south.v2 import DataMigration
  6 +from django.db import models
  7 +
  8 +class Migration(DataMigration):
  9 +
  10 + def forwards(self, orm):
  11 + # Selecting trac database
  12 + connection = connections['trac']
  13 +
  14 + cursor = connection.cursor()
  15 +
  16 + cursor.execute('''
  17 + CREATE OR REPLACE VIEW wiki_view AS SELECT
  18 + wiki.name AS name,
  19 + (SELECT wiki2.text FROM wiki AS wiki2 WHERE wiki2.name = wiki.name
  20 + AND wiki2.version = MAX(wiki.version)) AS wiki_text,
  21 + (SELECT wiki3.author FROM wiki AS wiki3 WHERE wiki3.name = wiki.name
  22 + AND wiki3.version = 1) AS author,
  23 + string_agg(DISTINCT wiki.author, ', ') AS collaborators,
  24 + TIMESTAMP WITH TIME ZONE 'epoch' + (MIN(wiki.time)/1000000) * INTERVAL '1s' AS created,
  25 + TIMESTAMP WITH TIME ZONE 'epoch' + (MAX(wiki.time)/1000000) * INTERVAL '1s' AS modified
  26 + FROM wiki
  27 + GROUP BY wiki.name;
  28 + ''')
  29 +
  30 + def backwards(self, orm):
  31 + # Selecting trac database
  32 + connection = connections['trac']
  33 +
  34 + cursor = connection.cursor()
  35 +
  36 + cursor.execute('''
  37 + CREATE OR REPLACE VIEW wiki_view AS SELECT
  38 + wiki.name AS name,
  39 + (SELECT wiki2.text FROM wiki AS wiki2 WHERE wiki2.name = wiki.name
  40 + AND wiki2.version = MAX(wiki.version)) AS wiki_text,
  41 + (SELECT wiki3.author FROM wiki AS wiki3 WHERE wiki3.name = wiki.name
  42 + AND wiki3.version = 1) AS author,
  43 + string_agg(DISTINCT wiki.author, ', ') AS collaborators,
  44 + TIMESTAMP WITH TIME ZONE 'epoch' + (MAX(wiki.time)/1000000) * INTERVAL '1s' AS created,
  45 + TIMESTAMP WITH TIME ZONE 'epoch' + (MIN(wiki.time)/1000000) * INTERVAL '1s' AS modified
  46 + FROM wiki
  47 + GROUP BY wiki.name;
  48 + ''')
  49 +
  50 + models = {
  51 + u'proxy.attachment': {
  52 + 'Meta': {'object_name': 'Attachment', 'db_table': "'attachment_view'", 'managed': 'False'},
  53 + 'attach_id': ('django.db.models.fields.TextField', [], {}),
  54 + 'author': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  55 + 'created': ('django.db.models.fields.DateTimeField', [], {'blank': 'True'}),
  56 + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  57 + 'filename': ('django.db.models.fields.TextField', [], {}),
  58 + 'mimetype': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  59 + 'size': ('django.db.models.fields.IntegerField', [], {'blank': 'True'}),
  60 + 'url': ('django.db.models.fields.TextField', [], {'primary_key': 'True'}),
  61 + 'used_by': ('django.db.models.fields.TextField', [], {})
  62 + },
  63 + u'proxy.revision': {
  64 + 'Meta': {'object_name': 'Revision', 'db_table': "'revision_view'", 'managed': 'False'},
  65 + 'author': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  66 + 'created': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
  67 + 'key': ('django.db.models.fields.TextField', [], {'primary_key': 'True'}),
  68 + 'message': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  69 + 'repository_name': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  70 + 'rev': ('django.db.models.fields.TextField', [], {'blank': 'True'})
  71 + },
  72 + u'proxy.ticket': {
  73 + 'Meta': {'object_name': 'Ticket', 'db_table': "'ticket_view'", 'managed': 'False'},
  74 + 'author': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  75 + 'collaborators': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  76 + 'component': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  77 + 'created': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
  78 + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  79 + 'id': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}),
  80 + 'keywords': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  81 + 'milestone': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  82 + 'modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
  83 + 'priority': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  84 + 'reporter': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  85 + 'severity': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  86 + 'status': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  87 + 'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  88 + 'version': ('django.db.models.fields.TextField', [], {'blank': 'True'})
  89 + },
  90 + u'proxy.wiki': {
  91 + 'Meta': {'object_name': 'Wiki', 'db_table': "'wiki_view'", 'managed': 'False'},
  92 + 'author': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  93 + 'collaborators': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
  94 + 'created': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
  95 + 'modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
  96 + 'name': ('django.db.models.fields.TextField', [], {'primary_key': 'True'}),
  97 + 'wiki_text': ('django.db.models.fields.TextField', [], {'blank': 'True'})
  98 + }
  99 + }
  100 +
  101 + complete_apps = ['proxy']
  102 + symmetrical = True
src/search/forms.py
@@ -101,6 +101,8 @@ class ColabSearchForm(SearchForm): @@ -101,6 +101,8 @@ class ColabSearchForm(SearchForm):
101 'q.alt': '*.*', 101 'q.alt': '*.*',
102 'pf': 'title^2.1 author^1.9 description^1.7', 102 'pf': 'title^2.1 author^1.9 description^1.7',
103 'mm': '2<70%', 103 'mm': '2<70%',
  104 +
  105 + # Date boosting: http://wiki.apache.org/solr/FunctionQuery#Date_Boosting
104 'bf': 'recip(ms(NOW/HOUR,modified),3.16e-11,1,1)^10', 106 'bf': 'recip(ms(NOW/HOUR,modified),3.16e-11,1,1)^10',
105 } 107 }
106 108
src/super_archives/search_indexes.py
@@ -25,6 +25,9 @@ class ThreadIndex(BaseIndex, indexes.Indexable): @@ -25,6 +25,9 @@ class ThreadIndex(BaseIndex, indexes.Indexable):
25 model_attr='mailinglist__get_absolute_url', 25 model_attr='mailinglist__get_absolute_url',
26 indexed=False, 26 indexed=False,
27 ) 27 )
  28 + latest_message_pk = indexes.IntegerField(
  29 + model_attr='latest_message__pk', indexed=False
  30 + )
28 score = indexes.IntegerField(model_attr='score') 31 score = indexes.IntegerField(model_attr='score')
29 32
30 def get_model(self): 33 def get_model(self):
src/super_archives/templates/message-preview.html
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 {% endif %} 10 {% endif %}
11 11
12 {% if result.title %} 12 {% if result.title %}
13 - <a href="{{ result.url }}" {% if result.latest_description %}title="{{ result.latest_description|escape|truncatechars:150 }}"{% elif result.description %}title="{{ result.description|escape|truncatechars:150 }}"{% endif %}> 13 + <a href="{{ result.url }}{% if result.type == 'thread' and result.latest_message_pk %}#msg-{{ result.latest_message_pk }}{% elif result.type == 'thread' and result.pk %}#msg-{{ result.pk }}{% endif %}" {% if result.latest_description %}title="{{ result.latest_description|escape|truncatechars:150 }}"{% elif result.description %}title="{{ result.description|escape|truncatechars:150 }}"{% endif %}>
14 <span class="subject"> 14 <span class="subject">
15 <!-- a striptags filter was raising an error here because using with highlight --> 15 <!-- a striptags filter was raising an error here because using with highlight -->
16 {% if query %} 16 {% if query %}
src/tz/middleware.py
@@ -10,11 +10,11 @@ class TimezoneMiddleware(object): @@ -10,11 +10,11 @@ class TimezoneMiddleware(object):
10 10
11 try: 11 try:
12 offset = int(offset) * -1 12 offset = int(offset) * -1
  13 + tz = pytz.FixedOffset(offset)
13 except ValueError: 14 except ValueError:
14 offset = 0 15 offset = 0
15 16
16 if offset: 17 if offset:
17 - tz = pytz.FixedOffset(offset)  
18 timezone.activate(tz) 18 timezone.activate(tz)
19 else: 19 else:
20 timezone.deactivate() 20 timezone.deactivate()
src/tz/templates/tz/set_utc_offset.html
1 <script type="text/javascript"> 1 <script type="text/javascript">
2 var date = new Date(); 2 var date = new Date();
3 - $.cookie('utc_offset', date.getTimezoneOffset(), { path: '/' }); 3 + jQuery.cookie('utc_offset', date.getTimezoneOffset(), { path: '/' });
4 </script> 4 </script>