Skip to content

Commit 930af31

Browse files
Merge remote-tracking branch 'upstream/master'
2 parents 9912785 + ed4baf1 commit 930af31

File tree

8 files changed

+172
-15
lines changed

8 files changed

+172
-15
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ Contributing
7878
[new-issue]: https://github.com/pythonindia/junction/issues/new
7979
[guidelines]: https://github.com/pythonindia/junction/blob/master/CONTRIBUTING.md
8080

81+
### API
82+
83+
- HTTP API documentation is [here](https://github.com/pythonindia/junction/blob/master/docs/api.md).
84+
- Python Client for junction is [here](https://github.com/pythonindia/junction-client).
85+
8186
## Contributors
8287

8388
<table>

junction/proposals/admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def get_queryset(self, request):
103103
@admin.register(models.ProposalComment)
104104
class ProposalCommentAdmin(TimeAuditAdmin):
105105
list_display = ('comment', 'proposal', 'commenter', 'private', 'reviewer') + TimeAuditAdmin.list_display
106-
list_filter = ['private', 'reviewer']
106+
list_filter = ['private', 'reviewer', 'commenter']
107107

108108
def get_queryset(self, request):
109109
qs = super(ProposalCommentAdmin, self).get_queryset(request)

junction/proposals/dashboard.py

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# -*- coding: utf-8 -*-
2+
23
from __future__ import absolute_import, unicode_literals
34

45
# Standard Library
@@ -15,10 +16,16 @@
1516
from xlsxwriter.workbook import Workbook
1617

1718
# Junction Stuff
18-
from junction.base.constants import ProposalReviewVote, ProposalStatus, ProposalVotesFilter
19+
from junction.base.constants import (
20+
ProposalReviewVote,
21+
ProposalStatus,
22+
ProposalVotesFilter
23+
)
1924
from junction.conferences.models import Conference, ConferenceProposalReviewer
2025

2126
from .forms import ProposalVotesFilterForm
27+
from .permissions import is_conference_moderator
28+
2229
from .models import (
2330
Proposal,
2431
ProposalComment,
@@ -27,13 +34,15 @@
2734
ProposalSectionReviewerVoteValue
2835
)
2936

37+
from . import services
38+
3039

3140
@login_required
3241
@require_http_methods(['GET'])
3342
def proposals_dashboard(request, conference_slug):
3443
conference = get_object_or_404(Conference, slug=conference_slug)
3544

36-
if not request.user.is_superuser:
45+
if not is_conference_moderator(user=request.user, conference=conference):
3746
raise PermissionDenied
3847

3948
proposals_qs = Proposal.objects.filter(
@@ -128,7 +137,7 @@ def proposals_dashboard(request, conference_slug):
128137
def reviewer_comments_dashboard(request, conference_slug):
129138
conference = get_object_or_404(Conference, slug=conference_slug)
130139

131-
if not request.user.is_superuser:
140+
if not is_conference_moderator(user=request.user, conference=conference):
132141
raise PermissionDenied
133142
conference_reviewers = ConferenceProposalReviewer.objects.filter(
134143
conference=conference, active=True)
@@ -142,7 +151,9 @@ def reviewer_comments_dashboard(request, conference_slug):
142151
by_conference.setdefault(id, [reviewers.reviewer, 0])
143152
by_conference[id][1] = ProposalComment.objects.filter(
144153
commenter=reviewers.reviewer,
145-
deleted=False, private=True).count()
154+
deleted=False, private=True,
155+
proposal__status=ProposalStatus.PUBLIC,
156+
proposal__conference=conference).distinct('proposal').count()
146157
# by_section is dict with
147158
# find each reviewers section and their comments
148159
# Need to rework on this code section to make it 1-2 loops
@@ -181,11 +192,11 @@ def reviewer_comments_dashboard(request, conference_slug):
181192

182193
@require_http_methods(['GET', 'POST'])
183194
def reviewer_votes_dashboard(request, conference_slug):
195+
conference = get_object_or_404(Conference, slug=conference_slug)
184196

185-
if not request.user.is_superuser:
197+
if not is_conference_moderator(user=request.user, conference=conference):
186198
raise PermissionDenied
187199

188-
conference = get_object_or_404(Conference, slug=conference_slug)
189200
proposal_sections = conference.proposal_sections.all()
190201
proposals_qs = Proposal.objects.select_related(
191202
'proposal_type', 'proposal_section', 'conference', 'author',
@@ -238,7 +249,8 @@ def reviewer_votes_dashboard(request, conference_slug):
238249
p for p in proposals_qs if p.get_reviewer_votes_count() >= votes]
239250
elif votes == ProposalVotesFilter.SORT:
240251
proposals_qs = sorted(
241-
proposals_qs, key=lambda x: x.get_reviewer_votes_sum(), reverse=True)
252+
proposals_qs, key=lambda x: x.get_reviewer_votes_sum(),
253+
reverse=True)
242254

243255
for section in proposal_sections:
244256
section_proposals = [
@@ -256,10 +268,11 @@ def export_reviewer_votes(request, conference_slug):
256268
"""
257269
Write reviewer votes to a spreadsheet.
258270
"""
259-
if not request.user.is_superuser:
271+
conference = get_object_or_404(Conference, slug=conference_slug)
272+
273+
if not is_conference_moderator(user=request.user, conference=conference):
260274
raise PermissionDenied
261275

262-
conference = get_object_or_404(Conference, slug=conference_slug)
263276
proposal_sections = conference.proposal_sections.all()
264277
proposals_qs = Proposal.objects.select_related(
265278
'proposal_type', 'proposal_section', 'conference', 'author',
@@ -293,10 +306,12 @@ def export_reviewer_votes(request, conference_slug):
293306
p.get_reviewer_votes_count(),) + \
294307
vote_details + (p.get_votes_count(), vote_comment,)
295308
if p.get_reviewer_votes_count_by_value(
296-
ProposalSectionReviewerVoteValue.objects.get(vote_value=ProposalReviewVote.NOT_ALLOWED)) > 0:
309+
ProposalSectionReviewerVoteValue.objects.get(
310+
vote_value=ProposalReviewVote.NOT_ALLOWED)) > 0:
297311
cell_format = book.add_format({'bg_color': 'red'})
298312
elif p.get_reviewer_votes_count_by_value(
299-
ProposalSectionReviewerVoteValue.objects.get(vote_value=ProposalReviewVote.MUST_HAVE)) > 2:
313+
ProposalSectionReviewerVoteValue.objects.get(
314+
vote_value=ProposalReviewVote.MUST_HAVE)) > 2:
300315
cell_format = book.add_format({'bg_color': 'green'})
301316
elif p.get_reviewer_votes_count() < 2:
302317
cell_format = book.add_format({'bg_color': 'yellow'})
@@ -314,3 +329,19 @@ def export_reviewer_votes(request, conference_slug):
314329
response['Content-Disposition'] = "attachment; filename=junction-{}.xlsx".format(file_name)
315330

316331
return response
332+
333+
334+
@login_required
335+
@require_http_methods(['GET'])
336+
def proposal_state(request, conference_slug):
337+
conf = get_object_or_404(Conference, slug=conference_slug)
338+
339+
if not is_conference_moderator(user=request.user, conference=conf):
340+
raise PermissionDenied
341+
342+
state = request.GET.get('q', 'unreviewed')
343+
proposals = services.group_proposals_by_reveiew_state(conf=conf, state=state)
344+
return render(request, 'proposals/review_state.html',
345+
{'conference': conf,
346+
'proposals': dict(proposals),
347+
'state': state.title()})

junction/proposals/permissions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,11 @@ def is_proposal_author_or_permisson_denied(user, proposal):
4949
if is_proposal_author(user, proposal):
5050
return True
5151
raise PermissionDenied
52+
53+
54+
def is_conference_moderator(user, conference):
55+
if user.is_superuser:
56+
return True
57+
58+
users = [mod.moderator for mod in conference.moderators.all()]
59+
return user in users

junction/proposals/services.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
# -*- coding: utf-8 -*-
2+
23
from __future__ import absolute_import, unicode_literals
34

45
# Standard Library
56
import logging
7+
import collections
68

79
# Third Party Stuff
810
from django.conf import settings
911
from markdown2 import markdown
1012

1113
# Junction Stuff
1214
from junction.base.emailer import send_email
15+
from junction.base.constants import ProposalStatus
1316

1417
from .models import ProposalSection, ProposalSectionReviewer
1518

@@ -111,4 +114,30 @@ def send_mail_for_proposal_content(conference, proposal, host):
111114
'proposal': proposal,
112115
'author_name': author_name,
113116
}
114-
return send_email(to=author, template_dir='proposals/email/upload_content', context=context)
117+
return send_email(to=author, template_dir='proposals/email/upload_content',
118+
context=context)
119+
120+
121+
def group_proposals_by_reveiew_state(conf, state='reviewed'):
122+
reviewed_qs = conf.proposal_set.filter(
123+
status=ProposalStatus.PUBLIC).select_related(
124+
'proposal_type', 'proposal_section',
125+
'proposalsection').filter(proposalcomment__private=True,
126+
proposalcomment__deleted=False)
127+
if state == 'reviewed':
128+
proposal_qs = reviewed_qs.distinct()
129+
return _arrange_proposals_by_section(proposal_qs)
130+
else:
131+
ids = reviewed_qs.values_list('id').distinct()
132+
qs = conf.proposal_set.filter(
133+
status=ProposalStatus.PUBLIC).select_related(
134+
'proposal_type', 'proposal_section',
135+
'proposalsection').exclude(pk__in=ids)
136+
return _arrange_proposals_by_section(qs)
137+
138+
139+
def _arrange_proposals_by_section(proposal_qs):
140+
res = collections.defaultdict(list)
141+
for proposal in proposal_qs:
142+
res[proposal.proposal_section.name].append(proposal)
143+
return res

junction/templates/proposals/dashboard.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,10 @@
7272
<tr>
7373
<td></td>
7474
<td>{{total}}</td>
75-
<td>{{reviewed}}</td>
76-
<td>{{unreviewed}}</td>
75+
<td><a href="{% url 'proposal-state' conference.slug%}?q=reviewed">
76+
{{reviewed}}</a></td>
77+
<td><a href="{% url 'proposal-state' conference.slug%}?q=unreviewed">
78+
{{unreviewed}}</a></td>
7779
</tr>
7880
</table>
7981
</div>
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
{% extends 'base.html' %}
2+
3+
{% load django_markdown %}
4+
{% load static from staticfiles %}
5+
{% load django_bootstrap_breadcrumbs %}
6+
7+
{% block head_title %} {{ conference.name }} Reviewers dashbaord {% endblock %}
8+
{% block og_title %} {{ conference.name }} Reviewers dashboard {% endblock %}
9+
{% block og_description %} {{ conference.description|markdown|safe|striptags}} {% endblock %}
10+
{% block page_description %} {{ conference.description|markdown|safe|striptags}} {% endblock %}
11+
12+
{% block endhead %}
13+
<!-- Custom CSS -->
14+
<link href="{% static 'css/list.css' %}" rel="stylesheet">
15+
{% endblock %}
16+
17+
{% block breadcrumbs %}
18+
{{ block.super }}
19+
{% breadcrumb conference.name "conference-detail" conference.slug %}
20+
{% breadcrumb "Dashboard" "proposals-dashoboard" conference.slug %}
21+
{% endblock %}
22+
23+
24+
{% block navbar_logo %}
25+
{% if conference.logo %}
26+
<a href="{% url "conference-detail" conference.slug %}">
27+
<img src="{{ conference.logo.url }}">
28+
</a>
29+
{% else %}
30+
<a href="#" class="navbar-brand">{{ conference.name }}</a>
31+
{% endif %}
32+
{% endblock navbar_logo %}
33+
34+
{% block page_classes %}{{ block.super}} page-proposals{% endblock page_classes %}
35+
36+
{% block content %}
37+
<section class="content custom-container proposal-list">
38+
<div class="push-4-bottom push-1-top">
39+
<div class="row">
40+
<div class="col-xs -12 col-sm-12 ">
41+
<p class="meta">
42+
<b class="text-muted">
43+
<span class="start_date">{{ conference.start_date }}</span>
44+
<span class="end_date">{{ conference.end_date }}</span>
45+
</b>
46+
<span class="status status-{{conference.status}}">
47+
{{ conference.get_status_display }}
48+
</span>
49+
</p>
50+
<hr>
51+
</div>
52+
</div>
53+
</div>
54+
55+
<section class="content custom-container proposal-list">
56+
<div class="push-4-bottom push-1-top">
57+
<div class="row">
58+
<div class="col-xs -12 col-sm-12 ">
59+
<p class="meta">
60+
<h2 class="panel-heading">{{ state }} proposals</h2>
61+
</p>
62+
<hr>
63+
<div class="#proposals" class="row"">
64+
{% for section, proposal_list in proposals.items %}
65+
<h3 class="section-title">{{ section }}</h3>
66+
{% for proposal in proposal_list %}
67+
<h4 class="proposal--title">
68+
<a href='{{ proposal.get_absolute_url }}'>{{ forloop.counter}}. {{ proposal.title|capfirst }}</a>
69+
- {{ proposal.proposal_type }}
70+
</h4>
71+
{% endfor %}
72+
<hr>
73+
{% endfor %}
74+
</div>
75+
</div>
76+
</div>
77+
</div>
78+
</section>
79+
{% endblock %}

junction/urls.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@
5252
url(r'^(?P<conference_slug>[\w-]+)/dashboard/reviewers/',
5353
'junction.proposals.dashboard.reviewer_comments_dashboard',
5454
name='proposal-reviewers-dashboard'),
55+
url(r'^(?P<conference_slug>[\w-]+)/dashboard/proposal_state/$',
56+
'junction.proposals.dashboard.proposal_state',
57+
name='proposal-state'),
5558
url(r'^(?P<conference_slug>[\w-]+)/dashboard/$',
5659
'junction.proposals.dashboard.proposals_dashboard', name='proposal-dashboard'),
5760
url(r'^(?P<conference_slug>[\w-]+)/dashboard/votes/$',

0 commit comments

Comments
 (0)