From 65a567433ce15cc40b31964fd9568fc30457b924 Mon Sep 17 00:00:00 2001 From: Christophe Siraut Date: Wed, 13 Aug 2014 17:45:42 +0200 Subject: [PATCH] Serlize results for faster retrieving. --- meetingpoll/management/__init__.py | 0 meetingpoll/management/commands/__init__.py | 0 .../commands/nuages_compile_results.py | 10 ++ .../0002_auto__add_field_poll_result.py | 99 +++++++++++++++ meetingpoll/models.py | 36 +++++- .../meetingpoll/poll_detail-orig.html | 120 ++++++++++++++++++ .../templates/meetingpoll/poll_detail.html | 50 ++++---- meetingpoll/templatetags/parse_date.py | 32 +++++ meetingpoll/views.py | 2 + nuages_templates/static/css/nuages.css | 2 +- nuages_templates/templates/index.html | 8 +- nuages_templates/templates/nuages.html | 9 +- 12 files changed, 331 insertions(+), 37 deletions(-) create mode 100644 meetingpoll/management/__init__.py create mode 100644 meetingpoll/management/commands/__init__.py create mode 100644 meetingpoll/management/commands/nuages_compile_results.py create mode 100644 meetingpoll/migrations/0002_auto__add_field_poll_result.py create mode 100644 meetingpoll/templates/meetingpoll/poll_detail-orig.html create mode 100644 meetingpoll/templatetags/parse_date.py diff --git a/meetingpoll/management/__init__.py b/meetingpoll/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/meetingpoll/management/commands/__init__.py b/meetingpoll/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/meetingpoll/management/commands/nuages_compile_results.py b/meetingpoll/management/commands/nuages_compile_results.py new file mode 100644 index 0000000..af4c3f1 --- /dev/null +++ b/meetingpoll/management/commands/nuages_compile_results.py @@ -0,0 +1,10 @@ +from django.core.management.base import BaseCommand, CommandError +from meetingpoll.models import Poll + + +class Command(BaseCommand): + help = 'Compile results to jsonfield' + + def handle(self, *args, **options): + for p in Poll.objects.all(): + p.compile() diff --git a/meetingpoll/migrations/0002_auto__add_field_poll_result.py b/meetingpoll/migrations/0002_auto__add_field_poll_result.py new file mode 100644 index 0000000..2123841 --- /dev/null +++ b/meetingpoll/migrations/0002_auto__add_field_poll_result.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Poll.result' + db.add_column(u'meetingpoll_poll', 'result', + self.gf('jsonfield.fields.JSONField')(default={}), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Poll.result' + db.delete_column(u'meetingpoll_poll', 'result') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'meetingpoll.bulletin': { + 'Meta': {'object_name': 'Bulletin'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'poll': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meetingpoll.Poll']"}), + 'voter': ('django.db.models.fields.CharField', [], {'max_length': '40'}) + }, + u'meetingpoll.choice': { + 'Meta': {'ordering': "['choice']", 'object_name': 'Choice'}, + 'choice': ('django.db.models.fields.DateTimeField', [], {}), + 'details': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'poll': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meetingpoll.Poll']"}), + 'votecount': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}) + }, + u'meetingpoll.poll': { + 'Meta': {'object_name': 'Poll'}, + 'description': ('django.db.models.fields.CharField', [], {'max_length': '300'}), + 'id': ('django.db.models.fields.CharField', [], {'default': "'fNLPA'", 'max_length': '8', 'primary_key': 'True'}), + 'pub_date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime.now'}), + 'result': ('jsonfield.fields.JSONField', [], {'default': '{}'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'upd_date': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True'}) + }, + u'meetingpoll.userprofile': { + 'Meta': {'object_name': 'UserProfile'}, + 'email_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'}) + }, + u'meetingpoll.vote': { + 'Meta': {'ordering': "['choice']", 'object_name': 'Vote'}, + 'bulletin': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meetingpoll.Bulletin']"}), + 'choice': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meetingpoll.Choice']"}), + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'voice': ('django.db.models.fields.BooleanField', [], {}) + } + } + + complete_apps = ['meetingpoll'] \ No newline at end of file diff --git a/meetingpoll/models.py b/meetingpoll/models.py index 1c93834..1d0d234 100644 --- a/meetingpoll/models.py +++ b/meetingpoll/models.py @@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _ import datetime import string import random - +import jsonfield def createId(length): firstchar = string.ascii_letters @@ -45,6 +45,40 @@ class Poll(models.Model): upd_date = models.DateField(auto_now=True) description = models.CharField(max_length=300) user = models.ForeignKey(User, null=True) + result = jsonfield.JSONField() + + def compile(self): + self.result = {'voters': self.compile_voters(), + 'choices': self.compile_choices()} + self.save() + + def compile_voters(self): + '''Return a list of votes by voter''' + voters = [] + for b in self.bulletin_set.all(): + say = [] + for c in self.choice_set.all(): + try: + v = b.vote_set.get(choice=c) + say.append((v.voice, v.comment)) + except: + say.append((None, None)) + voters.append({b.voter: say}) + return voters + + def compile_choices(self): + '''Return a list of choices''' + choices = [] + for c in self.choice_set.all(): + t = 0 + l = [] + for v in c.vote_set.filter(choice=c): + if v.voice: + t += 1 + if v.comment: + l.append([v.bulletin.voter, v.comment]) + choices.append((c.choice, c.details, t, l)) + return choices def __unicode__(self): return self.title diff --git a/meetingpoll/templates/meetingpoll/poll_detail-orig.html b/meetingpoll/templates/meetingpoll/poll_detail-orig.html new file mode 100644 index 0000000..b27e201 --- /dev/null +++ b/meetingpoll/templates/meetingpoll/poll_detail-orig.html @@ -0,0 +1,120 @@ +{% extends "base.html" %} +{% load url from future %} +{% load i18n %} +{% load bulletin_results %} + +{% block menu %} + {% if has_voted %} + {% trans "Forget me" %} + {% endif %} + {% trans "Export" %} +{% endblock %} + +{% block title %}nuages - {{object}}{% endblock %} + +{% block main %} +

{{object}}

+ +
+
+

{{object.description}}

+ {% trans "Posted by" %} {% if object.user %}{{object.user}}{% else %}{% trans 'Anonymous' %}{% endif %} {% trans "on" %} {{object.pub_date|date:"d F"}}. +
+
+ + + + {% for choice in object.choice_set.all %} + + {% endfor %} + + + {% for bulletin in object.bulletin_set.all %} + + + {% for vote in bulletin|bulletin_results:object.choice_set.all %} + + {% endfor %} + + {% endfor %} + + + + {% for choice in object.choice_set.all %} + + {% endfor %} + + + + {% csrf_token %} + + + {% for forms in vforms %} + {{ vforms.management_form }} + {% for form in forms %} + + {% endfor %} + {% endfor %} + +
{{ choice.choice|date:"d/m" }}
+ {{ choice.choice|date:'H:i' }}
+ {{ choice.choice|date:'l d F'}}
+ {{ choice.choice|date:'H:i a'}}
+ {{ choice.details }} +
+
+
{{ bulletin.voter }}
{% if vote.comment %}
{{ vote.comment }}{% endif %}
{{ choice.votecount }}
+
{% trans "Your name:" %}
+ {{ form.as_p }} + +
+ {{ form.voice }}
+ {{ form.comment }} +
+ {{ form.choice }} + {% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %} +
+ {{ field.errors }} +
+ +
+

{% trans "Instructions" %}

+ + + + + + +
1. {% trans "Point columns to see choice details and comments" %} 2. {% trans "Check the boxes for positive answers. " %} {%trans "You may provide a comment for each choice." %}3.
+ + + +
+

{% trans "Details" %}

+ {% for choice in object.choice_set.all %} +

+ {{ choice.choice|date:"l d F, H:i a"}} - + {{ choice.details }}  ({{ choice.votecount }})
+ + {% for bulletin in object.bulletin_set.all %} + {% for vote in bulletin.vote_set.all %} + {% ifequal choice vote.choice %} + {% if vote.comment %} + {{ bulletin.voter }}: {{ vote.comment }}
+ {% endif %} + {% endifequal %} + {% endfor %} + {% endfor %} + +

+ {% endfor %} + + +{% endblock %} + +{% block footer %} +

{{ object }} +{% trans "Shared address" %} → {{ object.link }}

+{% endblock %} diff --git a/meetingpoll/templates/meetingpoll/poll_detail.html b/meetingpoll/templates/meetingpoll/poll_detail.html index b27e201..ab75326 100644 --- a/meetingpoll/templates/meetingpoll/poll_detail.html +++ b/meetingpoll/templates/meetingpoll/poll_detail.html @@ -2,6 +2,7 @@ {% load url from future %} {% load i18n %} {% load bulletin_results %} +{% load parse_date %} {% block menu %} {% if has_voted %} @@ -13,6 +14,7 @@ {% block title %}nuages - {{object}}{% endblock %} {% block main %} +{% with result=object.result %}

{{object}}

@@ -24,31 +26,35 @@ - {% for choice in object.choice_set.all %} - + {% endwith %} {% endfor %} - {% for bulletin in object.bulletin_set.all %} + {% for voterdict in result.voters %} + {% for voter, data in voterdict.iteritems %} - - {% for vote in bulletin|bulletin_results:object.choice_set.all %} - + + {% for voice, comment in data %} + {% endfor %} {% endfor %} + {% endfor %} - {% for choice in object.choice_set.all %} - + {% for choice, details, total, data in result.choices %} + {% endfor %} @@ -91,27 +97,21 @@

{% trans "Details" %}

- {% for choice in object.choice_set.all %} + {% for choice, details, total, comments in result.choices %}

- {{ choice.choice|date:"l d F, H:i a"}} - - {{ choice.details }}  ({{ choice.votecount }})
+ {{ choice|parse_date|date:"l d F, H:i a"}} - + {{ details }}  ({{ total }})
- {% for bulletin in object.bulletin_set.all %} - {% for vote in bulletin.vote_set.all %} - {% ifequal choice vote.choice %} - {% if vote.comment %} - {{ bulletin.voter }}: {{ vote.comment }}
- {% endif %} - {% endifequal %} - {% endfor %} + {% for voter, comment in comments %} + {{ voter }}: {{ comment }}
{% endfor %} -

{% endfor %} - +{% endwith %} {% endblock %} {% block footer %} diff --git a/meetingpoll/templatetags/parse_date.py b/meetingpoll/templatetags/parse_date.py new file mode 100644 index 0000000..4ead08d --- /dev/null +++ b/meetingpoll/templatetags/parse_date.py @@ -0,0 +1,32 @@ +import datetime + +from django.template import Library +from django.template.defaultfilters import stringfilter + +register = Library() + +@stringfilter +def parse_date(date_string, format="%Y-%m-%dT%H:%M:%S"): + """ + Return a datetime corresponding to date_string, parsed according to format. + + For example, to re-display a date string in another format:: + + {{ "01/01/1970"|parse_date:"%m/%d/%Y"|date:"F jS, Y" }} + + """ + try: + return datetime.datetime.strptime(date_string, format) + except ValueError: + return None + + +@stringfilter +def parse_date_fancy(date_string): + try: + return parse_date(date_string).strftime("%l %d %F, %H:%i %a") + except ValueError: + return None + +register.filter(parse_date) +register.filter(parse_date_fancy) diff --git a/meetingpoll/views.py b/meetingpoll/views.py index b9846d3..6d926b8 100644 --- a/meetingpoll/views.py +++ b/meetingpoll/views.py @@ -184,6 +184,8 @@ def vote(request, poll_id): old.comment = vorm.cleaned_data['comment'] old.save() is_updated = True + poll.compile() + if has_voted: messages.success(request, _("Your vote has been counted, thank you.")) if is_updated: diff --git a/nuages_templates/static/css/nuages.css b/nuages_templates/static/css/nuages.css index 6546128..3e2b545 100644 --- a/nuages_templates/static/css/nuages.css +++ b/nuages_templates/static/css/nuages.css @@ -143,7 +143,7 @@ input:focus, textarea:focus { .msg { width: 4px; height: 4px; - background-color: #fefeff; + background-color: #ffffff; border: 1px solid #999; border-radius: 3px; border-radius: 3px; diff --git a/nuages_templates/templates/index.html b/nuages_templates/templates/index.html index ae67052..7dab3c2 100644 --- a/nuages_templates/templates/index.html +++ b/nuages_templates/templates/index.html @@ -12,17 +12,17 @@ {% trans "Create polls easily, and publish them for your mates, in order to plan meetings and activities." %}

- {% trans "Shedule an event" %} + ✏ {% trans "Shedule an event" %}

-
+
+
+

{% trans "Examples" %}: Quand allons nous au potager?

- -
{% block userspecific %} diff --git a/nuages_templates/templates/nuages.html b/nuages_templates/templates/nuages.html index 7cee565..1f631e3 100644 --- a/nuages_templates/templates/nuages.html +++ b/nuages_templates/templates/nuages.html @@ -1,7 +1,4 @@ -{% load i18n %} -{% load staticfiles %} - -{% block main %} +{% load i18n %} {% load staticfiles %} {% block main %}

{% trans "About Nuages" %}

@@ -23,8 +20,8 @@
-
    nuage  /ny.aʒ/

    - +

    nuage  /ny.aʒ/

    +
    1. (Météorologie) Amas de gouttelettes d’eau maintenues en suspension dans l’atmosphère et qui se résolvent ordinairement en pluie.
      • La nuit était sombre, pas une étoile ne brillait au ciel, des nuages lourds et chargés de pluie glissaient au-dessus de la tête de nos voyageurs, comme les vagues d’une mer aérienne. — (Alexandre Dumas, Othon l’archer, 1839)
      • -- GitLab
{{ choice.choice|date:"d/m" }}
- {{ choice.choice|date:'H:i' }}
- {{ choice.choice|date:'l d F'}}
- {{ choice.choice|date:'H:i a'}}
- {{ choice.details }} + {% for choice, details, total, data in result.choices %} + {% with choice_p=choice|parse_date %} +
{{ choice_p|date:"d/m" }}
+ {{ choice_p|date:'H:i' }}
+ {{ choice_p|date:'l d F'}}
+ {{ choice_p|date:'H:i a'}}
+ {{ details }}
{{ bulletin.voter }}
{% if vote.comment %}
{{ vote.comment }}{% endif %}
{{ voter }}
{% if comment %}
{{ comment }}{% endif %}
{{ choice.votecount }}{{ total }}