Skip to content

Commit

Permalink
Merge pull request #42 from zackmdavis/reynolds_v_sims
Browse files Browse the repository at this point in the history
Reynolds v. Sims
  • Loading branch information
cookjw committed Apr 22, 2015
2 parents 8a1f379 + 2cb1f56 commit 41eed89
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 32 deletions.
26 changes: 21 additions & 5 deletions core/colorize.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import itertools

def diffract(hex_encoding):
return [int(band, 16) for band in (hex_encoding[i:i+2] for i in (0, 2, 4))]

Expand Down Expand Up @@ -31,17 +33,31 @@ def populate_stops(color_stops):
)
return full_stops

def style_block(value, color):
return "\n".join(["[data-value=\"{}\"] {{".format(value),
" color: #{};".format(color),

def style_block(data_attribute, style_property, state, color):
return "\n".join(["[data-{}=\"{}\"] {{".format(data_attribute, state),
" {}: #{};".format(style_property, color),
"}\n"])

def value_style_block(value, color):
return style_block("value", "color", value, color)

def mark_style_block(mark, color):
return style_block("mark", "background-color", mark, color)


def stylesheet(low_score, low_color, high_score, high_color):
stops = {0: "000000"}
if low_score < 0:
stops.update({low_score: low_color})
if high_score > 0:
stops.update({high_score: high_color})
colors = populate_stops(stops)
return "\n".join(style_block(value, color)
for value, color in colors.items())
return "\n".join(
itertools.chain(
(value_style_block(value, color)
for value, color in colors.items()),
(mark_style_block(mark, color)
for mark, color in ((-1, "FFD6D6"), (1, "D6D6FF")))
)
)
30 changes: 29 additions & 1 deletion core/tests/view_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,20 @@ def setUpTestData(cls):
cls.the_user = f.FinetoothUserFactory.create()
cls.the_post = f.PostFactory.create(content="hello Django world")

def test_canot_vote_if_not_logged_in(self):
def test_can_vote(self):
self.client.login(
username=self.the_user.username, password=f.FACTORY_USER_PASSWORD
)
for start_index, end_index in ((0, 1), (1, 3), (5, 8)):
response = self.client.post(
reverse('vote', args=("post", self.the_post.pk)),
{'startIndex': start_index, 'endIndex': end_index,
'value': 1}
)
self.assertEqual(response.status_code, 204)


def test_cannot_vote_if_not_logged_in(self):
response = self.client.post(
reverse('vote', args=("post", self.the_post.pk)),
{'startIndex': 1, 'endIndex': 5, 'value': 1}
Expand All @@ -181,6 +194,21 @@ def test_cannot_submit_invalid_vote(self):
)
self.assertEqual(response.status_code, 400)

def test_one_user_one_vote(self):
self.client.login(
username=self.the_user.username, password=f.FACTORY_USER_PASSWORD
)
response = self.client.post(
reverse('vote', args=("post", self.the_post.pk)),
{'startIndex': 0, 'endIndex': 5, 'value': 1}
)
self.assertEqual(response.status_code, 204)
response = self.client.post(
reverse('vote', args=("post", self.the_post.pk)),
{'startIndex': 2, 'endIndex': 4, 'value': 1}
)
self.assertContains(response, "Overlapping votes are not allowed!",
status_code=403)

class ProfileTestCase(TestCase):

Expand Down
25 changes: 14 additions & 11 deletions core/tests/votable_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from core.models import Post
from core.votable import Tagnostic

from core.tests.factories import PostFactory, PostVoteFactory
from core.tests.factories import (
FinetoothUserFactory, PostFactory, PostVoteFactory)

class TagnosticismTestCase(TestCase):

Expand Down Expand Up @@ -42,8 +43,8 @@ def setUpTestData(self):
def test_scored_plaintext(self):
self.assertEqual(
self.the_post.scored_plaintext(),
(('f', 1), ('r', 2), ('i', 3), ('e', 4), ('n', 5), ('d', 6),
('s', 6), ('h', 6), ('i', 6), ('p', 6))
(('f', 1, 0), ('r', 2, 0), ('i', 3, 0), ('e', 4, 0), ('n', 5, 0),
('d', 6, 0), ('s', 6, 0), ('h', 6, 0), ('i', 6, 0), ('p', 6, 0))
)

class RenderingTestCase(TestCase):
Expand All @@ -65,17 +66,19 @@ def test_rendering(self):
self.assertHTMLEqual(
self.the_post.render(),
"""<p>
<span data-value="1">We\'ll </span>
<span data-value="1" data-mark="0">We\'ll </span>
<em>
<span data-value="1">al</span>
<span data-value="2">ways</span>
<span data-value="1">find</span>
<span data-value="0"> a way</span>
<span data-value="1" data-mark="0">al</span>
<span data-value="2" data-mark="0">ways</span>
<span data-value="1" data-mark="0">find</span>
<span data-value="0" data-mark="0"> a way</span>
</em>
<span data-value="0">; that\'s why the people of </span>
<span data-value="0" data-mark="0">
; that\'s why the people of
</span>
<em>
<span data-value="0">this</span>
<span data-value="0" data-mark="0">this</span>
</em>
<span data-value="0">world believe</span>
<span data-value="0" data-mark="0">world believe</span>
</p>"""
)
13 changes: 8 additions & 5 deletions core/views/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,14 @@ def ballot_box(request, kind, pk):
item = kinds[kind].objects.get(pk=pk)
if start_index < 0 or end_index > len(item.plaintext):
return HttpResponseBadRequest("Invalid vote not recorded!")
item.vote_set.create(
voter=request.user, value=value,
start_index=start_index, end_index=end_index
)
return HttpResponse(status=204)
if item.vote_in_range_for_user(request.user, start_index, end_index):
return HttpResponseForbidden("Overlapping votes are not allowed!")
else:
item.vote_set.create(
voter=request.user, value=value,
start_index=start_index, end_index=end_index
)
return HttpResponse(status=204)

def check_slug(request):
slug = request.GET.get('slug')
Expand Down
7 changes: 7 additions & 0 deletions core/views/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def home(request, page_number):
.prefetch_related('vote_set') \
.prefetch_related('comment_set') \
.select_related('author')
for post in all_posts:
post.request_user = request.user
context = paginated_context(request, 'posts', all_posts, page_number, {})
context = scored_context(context['posts'], context)
return render(request, "home.html", context)
Expand Down Expand Up @@ -66,6 +68,7 @@ def show_post(request, year, month, slug):
post = Post.objects.get(
slug=slug, published_at__year=int(year), published_at__month=int(month)
)
post.request_user = request.user
top_level_comments = post.comment_set.filter(parent=None)
return render(
request, "post.html",
Expand All @@ -85,6 +88,8 @@ def get_queryset(self):
end_year = year if month < 12 else (year + 1)
next_month = (month % 12) + 1
end_of_month = datetime(year=end_year, month=next_month, day=1)
# XXX: is it possible to use my post.request_user hack with
# class-based views??
return Post.objects.filter(
published_at__gte=start_of_month, published_at__lt=end_of_month
)
Expand Down Expand Up @@ -128,6 +133,8 @@ def new_post(request):
def tagged(request, label, page_number):
tag = Tag.objects.get(label=label)
tagged_posts = tag.posts.all()
for post in tagged_posts:
post.request_user = request.user
context = paginated_context(
request, 'posts', tagged_posts, page_number, {'tag': tag}
)
Expand Down
35 changes: 27 additions & 8 deletions core/votable.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from markdown import markdown as markdown_to_html

from django.db.models import Q

logger = logging.getLogger(__name__)

class Tagnostic(HTMLParser):
Expand Down Expand Up @@ -41,40 +43,50 @@ def score(self):
def plaintext(self):
return Tagnostic(self.content).plaintext()

def scored_plaintext(self):
def scored_plaintext(self, for_voter=None):
plaintext = Tagnostic(self.content).plaintext()
score_increments = [0] * (len(plaintext) + 1)
mark_increments = [0] * (len(plaintext) + 1)
for vote in self.vote_set.all():
score_increments[vote.start_index] += vote.value
score_increments[vote.end_index] -= vote.value
return tuple(zip(plaintext, itertools.accumulate(score_increments)))
if for_voter and vote.voter == for_voter:
mark_increments[vote.start_index] += vote.value
mark_increments[vote.end_index] -= vote.value
return tuple(zip(plaintext,
itertools.accumulate(score_increments),
itertools.accumulate(mark_increments)))

@staticmethod
def _render_scored_substring(scored_characters):
join_to_render_partial = []
value_at_index = None
mark_at_index = None
open_span = False
for character, value in scored_characters:
if value == value_at_index:
for character, value, mark in scored_characters:
if value == value_at_index and mark == mark_at_index:
join_to_render_partial.append(character)
else:
if open_span:
join_to_render_partial.append('</span>')
open_span = False
join_to_render_partial.append(
'<span data-value="{}">'.format(value)
'<span data-value="{}" data-mark="{}">'.format(value, mark)
)
open_span = True
value_at_index = value
mark_at_index = mark
join_to_render_partial.append(character)
if open_span:
join_to_render_partial.append('</span>')
return ''.join(join_to_render_partial)

def render(self):
for_voter = getattr(self, 'request_user', None)
parsed_content = Tagnostic(self.content).content
# XXX inefficiency
scored_plaintext_stack = list(reversed(self.scored_plaintext()))
scored_plaintext_stack = list(
reversed(self.scored_plaintext(for_voter)))
join_to_render = []
for token in parsed_content:
if isinstance(token, str): # text
Expand All @@ -100,7 +112,14 @@ def render(self):
return "".join(join_to_render)

def low_score(self):
return min(v for c, v in self.scored_plaintext())
return min(v for c, v, _m in self.scored_plaintext())

def high_score(self):
return max(v for c, v in self.scored_plaintext())
return max(v for c, v, _m in self.scored_plaintext())

def vote_in_range_for_user(self, voter,
ballot_start_index, ballot_end_index):
return self.vote_set.filter(
end_index__gt=ballot_start_index, start_index__lt=ballot_end_index,
voter=voter
).first()
7 changes: 5 additions & 2 deletions static/finetooth.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,12 @@ function instarender(range, value) {
$node = $(node);
if (node.nodeType === Node.TEXT_NODE) {
var oldValue = $node.parents('[data-value]').data('value');
$node.wrap($('<span>').attr('data-value', oldValue + value));
$node.wrap($('<span>')
.attr('data-value', oldValue + value)
.attr('data-mark', value));
} else {
$node.attr('data-value', $node.data('value') + value);
$node.attr('data-value', $node.data('value') + value)
.attr('data-mark', value);
}
});
window.getSelection().collapse($('body')[0],0);
Expand Down

0 comments on commit 41eed89

Please sign in to comment.