Commit 5f62a7ac63ea3282a137553bc5800a93691f04d0
Exists in
master
and in
39 other branches
Merge branch 'master' of beta.softwarepublico.gov.br:softwarepublico/colab
Showing
18 changed files
with
299 additions
and
130 deletions
Show diff stats
colab/accounts/views.py
1 | -#!/usr/bin/env python | |
2 | 1 | # encoding: utf-8 |
3 | - | |
4 | 2 | from collections import OrderedDict |
5 | 3 | |
6 | -from haystack.exceptions import SearchBackendError | |
7 | - | |
8 | 4 | from django.conf import settings |
9 | 5 | from django.contrib import messages |
10 | 6 | from django.db import transaction |
... | ... | @@ -18,15 +14,13 @@ from django.views.generic import DetailView, UpdateView, TemplateView |
18 | 14 | |
19 | 15 | from conversejs import xmpp |
20 | 16 | from conversejs.models import XMPPAccount |
21 | -from haystack.query import SearchQuerySet | |
22 | 17 | |
23 | 18 | from colab.super_archives.models import (EmailAddress, Message, |
24 | 19 | EmailAddressValidation) |
25 | -from colab.search.utils import trans | |
26 | -# from proxy.trac.models import WikiCollabCount, TicketCollabCount | |
20 | +from colab.search.utils import get_collaboration_data | |
21 | + | |
27 | 22 | from .forms import (UserCreationForm, UserForm, ListsForm, |
28 | 23 | UserUpdateForm, ChangeXMPPPasswordForm) |
29 | -# from .errors import XMPPChangePwdException | |
30 | 24 | from .utils import mailman |
31 | 25 | |
32 | 26 | |
... | ... | @@ -71,54 +65,21 @@ class UserProfileDetailView(UserProfileBaseMixin, DetailView): |
71 | 65 | |
72 | 66 | count_types = OrderedDict() |
73 | 67 | |
74 | - fields_or_lookup = ( | |
75 | - {'collaborators__contains': user.username}, | |
76 | - {'fullname_and_username__contains': user.username}, | |
77 | - ) | |
78 | - | |
79 | - counter_class = {} | |
80 | - # { | |
81 | - # 'wiki': WikiCollabCount, | |
82 | - # 'ticket': TicketCollabCount, | |
83 | - # } | |
68 | + collaborations, count_types_extras = get_collaboration_data(user) | |
84 | 69 | |
85 | - types = ['thread'] | |
86 | - # types.extend(['ticket', 'wiki', 'changeset', 'attachment']) | |
70 | + collaborations.sort(key=lambda elem: elem.modified, reverse=True) | |
87 | 71 | |
88 | - messages = Message.objects.filter(from_address__user__pk=user.pk) | |
89 | - for type in types: | |
90 | - CounterClass = counter_class.get(type) | |
91 | - if CounterClass: | |
92 | - try: | |
93 | - counter = CounterClass.objects.get(author=user.username) | |
94 | - except CounterClass.DoesNotExist: | |
95 | - count_types[trans(type)] = 0 | |
96 | - else: | |
97 | - count_types[trans(type)] = counter.count | |
98 | - elif type == 'thread': | |
99 | - count_types[trans(type)] = messages.count() | |
100 | - else: | |
101 | - sqs = SearchQuerySet() | |
102 | - for filter_or in fields_or_lookup: | |
103 | - sqs = sqs.filter_or(type=type, **filter_or) | |
104 | - count_types[trans(type)] = sqs.count() | |
72 | + count_types.update(count_types_extras) | |
105 | 73 | |
106 | 74 | context['type_count'] = count_types |
107 | - | |
108 | - sqs = SearchQuerySet() | |
109 | - for filter_or in fields_or_lookup: | |
110 | - sqs = sqs.filter_or(**filter_or).exclude(type='thread') | |
111 | - | |
112 | - try: | |
113 | - context['results'] = sqs.order_by('-modified', '-created')[:10] | |
114 | - except SearchBackendError: | |
115 | - context['results'] = sqs.order_by('-modified')[:10] | |
75 | + context['results'] = collaborations[:10] | |
116 | 76 | |
117 | 77 | email_pks = [addr.pk for addr in user.emails.iterator()] |
118 | 78 | query = Message.objects.filter(from_address__in=email_pks) |
119 | 79 | query = query.order_by('-received_time') |
120 | 80 | context['emails'] = query[:10] |
121 | 81 | |
82 | + messages = Message.objects.filter(from_address__user__pk=user.pk) | |
122 | 83 | count_by = 'thread__mailinglist__name' |
123 | 84 | context['list_activity'] = dict(messages.values_list(count_by) |
124 | 85 | .annotate(Count(count_by)) | ... | ... |
colab/home/views.py
1 | - | |
2 | -from collections import OrderedDict | |
3 | - | |
4 | 1 | from django.conf import settings |
5 | -from django.core.cache import cache | |
6 | 2 | from django.shortcuts import render |
7 | 3 | from django.http import HttpResponse, Http404 |
8 | 4 | |
9 | -from haystack.query import SearchQuerySet | |
10 | - | |
11 | -# from proxy.trac.models import WikiCollabCount, TicketCollabCount | |
12 | -from colab.search.utils import trans | |
5 | +from colab.search.utils import get_collaboration_data | |
13 | 6 | from colab.super_archives.models import Thread |
14 | 7 | |
15 | 8 | |
16 | 9 | def dashboard(request): |
17 | 10 | """Dashboard page""" |
11 | + | |
12 | + highest_score_threads = Thread.highest_score.all()[:6] | |
13 | + | |
14 | + hottest_threads = [t.latest_message for t in highest_score_threads] | |
15 | + | |
18 | 16 | latest_threads = Thread.objects.all()[:6] |
19 | - hottest_threads = Thread.highest_score.from_haystack()[:6] | |
20 | - | |
21 | - count_types = cache.get('home_chart') | |
22 | - if count_types is None: | |
23 | - count_types = OrderedDict() | |
24 | - count_types['thread'] = SearchQuerySet().filter( | |
25 | - type='thread', | |
26 | - ).count() | |
27 | - # TODO: this section should be inside trac app and only use it here | |
28 | - # if settings.TRAC_ENABLED: | |
29 | - # for type in ['changeset', 'attachment']: | |
30 | - # count_types[type] = SearchQuerySet().filter( | |
31 | - # type=type, | |
32 | - # ).count() | |
33 | - | |
34 | - # count_types['ticket'] = sum([ | |
35 | - # ticket.count for ticket in TicketCollabCount.objects.all() | |
36 | - # ]) | |
37 | - | |
38 | - # count_types['wiki'] = sum([ | |
39 | - # wiki.count for wiki in WikiCollabCount.objects.all() | |
40 | - # ]) | |
41 | - | |
42 | - cache.set('home_chart', count_types) | |
43 | - | |
44 | - for key in count_types.keys(): | |
45 | - count_types[trans(key)] = count_types.pop(key) | |
17 | + | |
18 | + latest_results, count_types = get_collaboration_data() | |
19 | + latest_results.sort(key=lambda elem: elem.modified, reverse=True) | |
46 | 20 | |
47 | 21 | context = { |
48 | 22 | 'hottest_threads': hottest_threads[:6], |
49 | 23 | 'latest_threads': latest_threads, |
50 | 24 | 'type_count': count_types, |
51 | - 'latest_results': SearchQuerySet().all().order_by( | |
52 | - '-modified', '-created' | |
53 | - )[:6], | |
25 | + 'latest_results': latest_results[:6], | |
54 | 26 | } |
55 | 27 | return render(request, 'home.html', context) |
56 | 28 | ... | ... |
colab/proxy/gitlab/data_api.py
colab/proxy/gitlab/models.py
1 | 1 | from django.db import models |
2 | +from django.utils.translation import ugettext_lazy as _ | |
2 | 3 | |
3 | 4 | |
4 | 5 | class GitlabProject(models.Model): |
... | ... | @@ -10,3 +11,7 @@ class GitlabProject(models.Model): |
10 | 11 | name_with_namespace = models.TextField() |
11 | 12 | created_at = models.DateTimeField(blank=True) |
12 | 13 | last_activity_at = models.DateTimeField(blank=True) |
14 | + | |
15 | + class Meta: | |
16 | + verbose_name = _('Gitlab Project') | |
17 | + verbose_name_plural = _('Gitlab Projects') | ... | ... |
colab/proxy/jenkins/data_api.py
colab/proxy/management/commands/import_proxy_data.py
1 | 1 | #!/usr/bin/env python |
2 | 2 | |
3 | 3 | import importlib |
4 | +import inspect | |
4 | 5 | |
5 | 6 | from django.core.management.base import BaseCommand |
6 | 7 | from django.conf import settings |
7 | 8 | |
8 | -from colab.proxy.proxybase.proxy_data_api import ProxyDataAPI | |
9 | +from colab.proxy.utils.proxy_data_api import ProxyDataAPI | |
9 | 10 | |
10 | 11 | |
11 | 12 | class Command(BaseCommand): |
... | ... | @@ -20,7 +21,10 @@ class Command(BaseCommand): |
20 | 21 | |
21 | 22 | for module_item_name in dir(module): |
22 | 23 | module_item = getattr(module, module_item_name) |
24 | + if not inspect.isclass(module_item): | |
25 | + continue | |
23 | 26 | if issubclass(module_item, ProxyDataAPI): |
24 | 27 | if module_item != ProxyDataAPI: |
25 | 28 | api = module_item() |
26 | - api.fetchData() | |
29 | + api.fetch_data() | |
30 | + break | ... | ... |
colab/proxy/noosfero/data_api.py
colab/proxy/redmine/data_api.py
colab/proxy/trac/data_api.py
colab/proxy/trac/models.py
... | ... | @@ -131,21 +131,3 @@ class Wiki(models.Model, HitCounterModelMixin): |
131 | 131 | return User.objects.get(username=self.modified_by) |
132 | 132 | except User.DoesNotExist: |
133 | 133 | return None |
134 | - | |
135 | - | |
136 | -class WikiCollabCount(models.Model): | |
137 | - author = models.TextField(primary_key=True) | |
138 | - count = models.IntegerField() | |
139 | - | |
140 | - class Meta: | |
141 | - managed = False | |
142 | - db_table = 'wiki_collab_count_view' | |
143 | - | |
144 | - | |
145 | -class TicketCollabCount(models.Model): | |
146 | - author = models.TextField(primary_key=True) | |
147 | - count = models.IntegerField() | |
148 | - | |
149 | - class Meta: | |
150 | - managed = False | |
151 | - db_table = 'ticket_collab_count_view' | ... | ... |
... | ... | @@ -0,0 +1,41 @@ |
1 | +from django.db import models | |
2 | +from django.conf import settings | |
3 | +from colab.accounts.models import User | |
4 | + | |
5 | + | |
6 | +class Collaboration(models.Model): | |
7 | + ''' | |
8 | + Class to define the fields of the collaboration block | |
9 | + that are displayed at dashboard and profile pages. | |
10 | + ''' | |
11 | + | |
12 | + tag = None | |
13 | + title = None | |
14 | + description = None | |
15 | + url = None | |
16 | + modified = None | |
17 | + type = None | |
18 | + | |
19 | + user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, | |
20 | + on_delete=models.SET_NULL) | |
21 | + | |
22 | + @property | |
23 | + def modified_by(self): | |
24 | + if self.user: | |
25 | + return self.user.get_full_name() | |
26 | + return None | |
27 | + | |
28 | + @property | |
29 | + def modified_by_url(self): | |
30 | + if self.user: | |
31 | + return self.user.get_absolute_url() | |
32 | + return None | |
33 | + | |
34 | + def update_user(self, user_name): | |
35 | + try: | |
36 | + self.user = User.objects.get(username=user_name) | |
37 | + except User.DoesNotExist: | |
38 | + self.user = None | |
39 | + | |
40 | + class Meta: | |
41 | + abstract = True | ... | ... |
colab/proxy/utils/proxy_data_api.py
colab/search/templates/search/search-message-preview.html
colab/search/utils.py
1 | +import importlib | |
2 | +import inspect | |
1 | 3 | |
4 | +from collections import OrderedDict | |
5 | + | |
6 | +from django.core.cache import cache | |
2 | 7 | from django.utils.translation import ugettext as _ |
8 | +from django.conf import settings | |
9 | +from colab.super_archives.models import Thread, Message | |
10 | +from colab.proxy.utils.models import Collaboration | |
11 | + | |
12 | + | |
13 | +def get_collaboration_data(filter_by_user=None): | |
14 | + latest_results = [] | |
15 | + count_types = cache.get('home_chart') | |
16 | + populate_count_types = False | |
17 | + | |
18 | + if count_types is None: | |
19 | + populate_count_types = True | |
20 | + count_types = OrderedDict() | |
21 | + count_types[_('Emails')] = Thread.objects.count() | |
22 | + | |
23 | + if filter_by_user: | |
24 | + messages = Message.objects.filter(from_address__user__pk=filter_by_user.pk) | |
25 | + else: | |
26 | + latest_threads = Thread.objects.all()[:6] | |
27 | + messages = [t.latest_message for t in latest_threads] | |
28 | + | |
29 | + latest_results.extend(messages) | |
30 | + | |
31 | + app_names = settings.PROXIED_APPS.keys() | |
32 | + | |
33 | + for app_name in app_names: | |
34 | + module = importlib \ | |
35 | + .import_module('colab.proxy.{}.models'.format(app_name)) | |
36 | + | |
37 | + for module_item_name in dir(module): | |
38 | + module_item = getattr(module, module_item_name) | |
39 | + if not inspect.isclass(module_item): | |
40 | + continue | |
41 | + if not issubclass(module_item, Collaboration): | |
42 | + continue | |
43 | + if module_item == Collaboration: | |
44 | + continue | |
45 | + | |
46 | + queryset = module_item.objects | |
47 | + | |
48 | + if filter_by_user: | |
49 | + elements = queryset.filter( | |
50 | + user__username=filter_by_user) | |
51 | + else: | |
52 | + elements = queryset.all() | |
53 | + | |
54 | + latest_results.extend(elements) | |
55 | + elements_count = elements.count() | |
56 | + | |
57 | + if elements_count > 1: | |
58 | + verbose_name = module_item._meta.verbose_name_plural.title() | |
59 | + else: | |
60 | + verbose_name = module_item._meta.verbose_name.title() | |
3 | 61 | |
62 | + if populate_count_types: | |
63 | + count_types[verbose_name] = elements_count | |
4 | 64 | |
5 | -def trans(key): | |
6 | - translations = { | |
7 | - 'wiki': _('Wiki'), | |
8 | - 'thread': _('Emails'), | |
9 | - 'changeset': _('Code'), | |
10 | - 'ticket': _('Tickets'), | |
11 | - 'attachment': _('Attachments'), | |
12 | - } | |
65 | + if populate_count_types: | |
66 | + cache.set('home_chart', count_types, 30) | |
13 | 67 | |
14 | - return translations.get(key, key) | |
68 | + return latest_results, count_types | ... | ... |
colab/settings.py
... | ... | @@ -321,8 +321,9 @@ if FEEDZILLA_ENABLED: |
321 | 321 | 'common', |
322 | 322 | ) |
323 | 323 | |
324 | -PROXIED_APPS = locals().get('PROXIED_APPS') or {} | |
325 | 324 | BROWSERID_ENABLED = locals().get('BROWSERID_ENABLED') or False |
326 | 325 | |
326 | +PROXIED_APPS = locals().get('PROXIED_APPS') or {} | |
327 | + | |
327 | 328 | for app_label in PROXIED_APPS.keys(): |
328 | 329 | INSTALLED_APPS += ('colab.proxy.{}'.format(app_label),) | ... | ... |
colab/super_archives/views.py
... | ... | @@ -17,13 +17,11 @@ from django.utils.decorators import method_decorator |
17 | 17 | from django.contrib.auth.decorators import login_required |
18 | 18 | from django.shortcuts import render, redirect, get_object_or_404 |
19 | 19 | |
20 | -from haystack.query import SearchQuerySet | |
21 | - | |
22 | 20 | from colab.accounts.utils import mailman |
23 | 21 | from colab.accounts.models import User |
24 | 22 | from .utils.email import send_verification_email |
25 | -from .models import MailingList, Thread, EmailAddress | |
26 | -from .models import EmailAddressValidation, Message | |
23 | +from .models import (MailingList, Thread, EmailAddress, | |
24 | + EmailAddressValidation, Message) | |
27 | 25 | |
28 | 26 | |
29 | 27 | class ThreadView(View): |
... | ... | @@ -110,8 +108,9 @@ class ThreadView(View): |
110 | 108 | elif resp.status_code == 404: |
111 | 109 | error_msg = _('Mailing list does not exist') |
112 | 110 | else: |
113 | - error_msg = _('Unknown error\ | |
114 | - trying to connect to Mailman API') | |
111 | + error_msg = \ | |
112 | + _('Unknown error trying to connect to Mailman API') | |
113 | + | |
115 | 114 | messages.error(request, error_msg) |
116 | 115 | |
117 | 116 | return self.get(request, mailinglist, thread_token) |
... | ... | @@ -126,7 +125,7 @@ class ThreadDashboardView(View): |
126 | 125 | all_lists = mailman.all_lists(description=True) |
127 | 126 | |
128 | 127 | context['lists'] = [] |
129 | - # lists = MailingList.objects.filter() | |
128 | + | |
130 | 129 | for list_ in MailingList.objects.order_by('name'): |
131 | 130 | context['lists'].append(( |
132 | 131 | list_.name, |
... | ... | @@ -134,7 +133,8 @@ class ThreadDashboardView(View): |
134 | 133 | list_.thread_set.filter(spam=False).order_by( |
135 | 134 | '-latest_message__received_time' |
136 | 135 | )[:MAX], |
137 | - SearchQuerySet().filter(type='thread', tag=list_.name)[:MAX], | |
136 | + [t.latest_message for t in Thread.highest_score.filter( | |
137 | + mailinglist__name=list_.name)[:MAX]], | |
138 | 138 | len(mailman.list_users(list_.name)), |
139 | 139 | )) |
140 | 140 | |
... | ... | @@ -171,6 +171,7 @@ class EmailView(View): |
171 | 171 | email.user = email_val.user |
172 | 172 | email.save() |
173 | 173 | email_val.delete() |
174 | + | |
174 | 175 | user = User.objects.get(username=email.user.username) |
175 | 176 | user.is_active = True |
176 | 177 | user.save() | ... | ... |
... | ... | @@ -0,0 +1,149 @@ |
1 | +[ | |
2 | +{ | |
3 | + "fields": { | |
4 | + "logo": "", | |
5 | + "description": "", | |
6 | + "last_imported_index": 0, | |
7 | + "name": "ListA", | |
8 | + "email": "listA@example.com" | |
9 | + }, | |
10 | + "model": "super_archives.mailinglist", | |
11 | + "pk": 1 | |
12 | +}, | |
13 | +{ | |
14 | + "fields": { | |
15 | + "logo": "", | |
16 | + "description": "", | |
17 | + "last_imported_index": 0, | |
18 | + "name": "ListB", | |
19 | + "email": "listB@example.com" | |
20 | + }, | |
21 | + "model": "super_archives.mailinglist", | |
22 | + "pk": 2 | |
23 | +}, | |
24 | +{ | |
25 | + "fields": { | |
26 | + "logo": "", | |
27 | + "description": "", | |
28 | + "last_imported_index": 0, | |
29 | + "name": "ListC", | |
30 | + "email": "listC@example.com" | |
31 | + }, | |
32 | + "model": "super_archives.mailinglist", | |
33 | + "pk": 3 | |
34 | +}, | |
35 | +{ | |
36 | + "fields": { | |
37 | + "spam": false, | |
38 | + "subject_token": "Thread_1_on_List_A", | |
39 | + "mailinglist": 1, | |
40 | + "score": 31, | |
41 | + "latest_message": 3 | |
42 | + }, | |
43 | + "model": "super_archives.thread", | |
44 | + "pk": 1 | |
45 | +}, | |
46 | +{ | |
47 | + "fields": { | |
48 | + "spam": false, | |
49 | + "subject_token": "Thread_1_on_List_B", | |
50 | + "mailinglist": 2, | |
51 | + "score": 0, | |
52 | + "latest_message": 4 | |
53 | + }, | |
54 | + "model": "super_archives.thread", | |
55 | + "pk": 3 | |
56 | +}, | |
57 | +{ | |
58 | + "fields": { | |
59 | + "spam": false, | |
60 | + "subject_token": "Thread_1_on_List_C", | |
61 | + "mailinglist": 3, | |
62 | + "score": 0, | |
63 | + "latest_message": 5 | |
64 | + }, | |
65 | + "model": "super_archives.thread", | |
66 | + "pk": 4 | |
67 | +}, | |
68 | +{ | |
69 | + "fields": { | |
70 | + "body": "This is a repply to Thread 1 on list A", | |
71 | + "received_time": "2015-01-28T12:43:00.752Z", | |
72 | + "from_address": 1, | |
73 | + "thread": 1, | |
74 | + "spam": false, | |
75 | + "subject_clean": "Response to Thread 1A", | |
76 | + "message_id": "loreipsum", | |
77 | + "subject": "Response to Thread 1A" | |
78 | + }, | |
79 | + "model": "super_archives.message", | |
80 | + "pk": 3 | |
81 | +}, | |
82 | +{ | |
83 | + "fields": { | |
84 | + "body": "", | |
85 | + "received_time": "2015-01-28T12:57:22.180Z", | |
86 | + "from_address": 1, | |
87 | + "thread": 3, | |
88 | + "spam": false, | |
89 | + "subject_clean": "Message 1 on Thread 1B", | |
90 | + "message_id": "", | |
91 | + "subject": "Message 1 on Thread 1B" | |
92 | + }, | |
93 | + "model": "super_archives.message", | |
94 | + "pk": 4 | |
95 | +}, | |
96 | +{ | |
97 | + "fields": { | |
98 | + "body": "", | |
99 | + "received_time": "2015-01-28T13:02:12.903Z", | |
100 | + "from_address": 1, | |
101 | + "thread": 4, | |
102 | + "spam": false, | |
103 | + "subject_clean": "Message 1 on Thread 1C", | |
104 | + "message_id": "", | |
105 | + "subject": "Message 1 on Thread 1C" | |
106 | + }, | |
107 | + "model": "super_archives.message", | |
108 | + "pk": 5 | |
109 | +}, | |
110 | +{ | |
111 | + "fields": { | |
112 | + "last_name": "Administrator", | |
113 | + "webpage": "", | |
114 | + "twitter": "", | |
115 | + "is_staff": true, | |
116 | + "user_permissions": [], | |
117 | + "date_joined": "2015-01-28T12:34:58.770Z", | |
118 | + "google_talk": "", | |
119 | + "first_name": "Admin", | |
120 | + "is_superuser": true, | |
121 | + "last_login": "2015-01-28T12:35:39.621Z", | |
122 | + "verification_hash": null, | |
123 | + "role": "", | |
124 | + "email": "admin@mail.com", | |
125 | + "username": "admin", | |
126 | + "bio": "", | |
127 | + "needs_update": true, | |
128 | + "is_active": true, | |
129 | + "facebook": "", | |
130 | + "groups": [], | |
131 | + "password": "pbkdf2_sha256$12000$iiKCMnLZnFJw$UTx89LB8oYTiw9UqkcglzFLmIaZtbr+ZzF1cG3vfcyo=", | |
132 | + "institution": "", | |
133 | + "github": "", | |
134 | + "modified": "2015-01-28T12:45:27.375Z" | |
135 | + }, | |
136 | + "model": "accounts.user", | |
137 | + "pk": 1 | |
138 | +}, | |
139 | +{ | |
140 | + "fields": { | |
141 | + "real_name": "", | |
142 | + "user": 1, | |
143 | + "md5": "edb0e96701c209ab4b50211c856c50c4", | |
144 | + "address": "admin@mail.com" | |
145 | + }, | |
146 | + "model": "super_archives.emailaddress", | |
147 | + "pk": 1 | |
148 | +} | |
149 | +] | ... | ... |
vagrant/provision.sh